Reconstructing Legacy Systems Without Source Code
Large enterprises routinely depend on systems that nobody dares to change. Payroll, logistics, inventory reconciliation, and order processing often run on decades-old applications built with obsolete stacks and maintained by a shrinking pool of experts. Documentation is missing or out of sync, and the original authors have moved on. The result is a black box: you can observe outputs, but the internal logic remains opaque. For technology leaders, this creates a modernization deadlock: the system is too risky to replace, too costly to maintain, and too critical to ignore.
AI-assisted reverse engineering offers a way out. The goal is not to recover code but to reconstruct the functional intent of a system—turning fear and opacity into clarity that enables confident modernization decisions.
Assessing a Vast Legacy Application
Our engagement involved a system of considerable scale and complexity. The databases spanned multiple platforms and contained more than 650 tables and 1,200 stored procedures. Functionality extended across 24 business domains, presented through nearly 350 user screens. The application tier consisted of 45 compiled DLLs, each with thousands of functions and virtually no surviving documentation. The system was tightly integrated with multiple enterprise systems, adding another layer of difficulty.
We began with an experiment: using AI to create a functional specification detailed enough to drive implementation of a replacement system. The experiment covered an end-to-end thin slice with both reverse and forward engineering. We cross-checked and verified the results at multiple levels, including walking through the reconstructed spec with system administrators and users to confirm intended functionality.
The client ultimately chose an existing preferred partner for the full implementation, so we couldn't scale our approach to the entire system. But the exercise taught us enough to share with professional colleagues.
The Core Difficulties
- Missing source code. Some binaries are old native executables without rich metadata—not the kind you can easily decompile like .NET assemblies or JARs. Even accessible databases don't tell the whole story; stored procedures and triggers encode years of accumulated business rules, and schemas reflect compromises made in unknown contexts.
- Outdated infrastructure. The operating system and database were well past end of life, frozen in VM state. This created significant risk to business continuity, plus growing security vulnerabilities, compliance issues, and liability.
- Lost institutional knowledge. Thousands of users worked with the system daily, but business knowledge barely extended beyond occasional support activities. The live system—especially the UI—was the only reliable view of functionality. Yet the UI captures only the last mile of execution; behind each screen lies tangled logic integrated with multiple core systems.
A Multi-Lens Approach
The system followed a three-tier architecture: web tier (ASP), application tier (DLL), and persistence (SQL). This pattern gave us a starting point even without a source repository. We extracted ASP files, database schema, and stored procedures from production. For the application tier, we had only native binaries.
Our objective was to build a semi-structured description of application behavior in natural language—something business users could validate—and use that validated spec for accelerated forward engineering. The method had two broad parts:
- Using AI to connect dots across different data sources.
- AI-assisted binary archaeology to uncover hidden functionality from native DLLs.
The guiding principle was simple: don't try to recover the code—reconstruct the functional intent. One structural decision kept the approach sound: what we wanted to understand was not implementations but behavior.
UI Layer Reconstruction
By browsing the live application and taking screenshots, we identified UI elements. We then used ASP and JavaScript content to capture the dynamic behavior tied to each element, producing a UI specification with validation rules, navigation paths, and hidden fields.
Hallucination was a real risk from the start, so we attached lineage to every key piece of information, documenting where each finding came from and enabling cross-checking. The LLM accelerated the process considerably—summarizing hundreds of screen definitions and consolidating logic from ASP and JS sources that would otherwise have taken weeks.
Discovery with Change Data Capture
We planned to use Change Data Capture (CDC) to trace how UI actions mapped to database activity, retrieving change logs from MCP servers to track workflows. Environment constraints meant CDC could only be enabled partially, limiting the breadth of captured data. Even so, the partial CDC provided valuable insights linking UI behavior to underlying data changes, and it enriched the overall system blueprint.
Other techniques—network traffic between front end and back end, filesystem changes, additional persistence layers, even debugging breakpoints—remain viable for finer-grained discovery when needed.
Inferring Server Logic
We supplied typelibs extracted from the native binaries along with stored procedures and schema from the database. With layouts, presentation logic, and database change patterns in hand, the AI could infer which stored procedures each method and interface in the binaries likely called, and which tables were involved. The LLM proposed probable relationships between application-tier code and procedures or tables, which we then validated through observed data flows to produce an Inferred Server Logic Spec.
Assembly-Level Decoding
The compiled binaries — DLLs and executables — proved to be the most opaque layer of the legacy system. Rather than attempting a full rebuild, these binaries were treated as archaeological artifacts. The focus was on extracting call trees, identifying recurring assembly patterns, and locating candidate entry points. AI was used to bulk-summarize disassembled code into human-readable hypotheses and flag probable function roles, though every conclusion was validated by human experts.
The production environment added its own complications. Multiple versions of the same files coexisted, distinguished only by file names and confusing naming conventions. Timestamps offered some hints, and the Windows registry helped locate binaries. The presence of proxy binaries that shared the same names as their targets — a design intended to allow the app tier to run on a different machine from the web tier — further muddied the waters.
Tools like Ghidra could decompile binaries into thousands of assembly functions, and some offered conversion to C code. However, that conversion proved unreliable; in this case, decompilation to C missed a crucial lead. The practical approach was to identify the functions relevant to a specific functional area and decode that subtree, rather than attempting to process the entire assembly output at once.
Failed Brute-Force Attempts
Several strategies were tried before settling on this targeted methodology:
- Full workspace analysis: Loading all assembly functions into a single LLM workspace to generate readable pseudocode. This failed when the model exhausted its 1-million-token context window while trying to load all dependent and referencing functions.
- Batched analysis: Splitting functions into many files, each containing hundreds of functions, and analyzing them in isolation. This produced significant hallucination issues and file-size streaming problems. Many functions were incorrectly labeled with similar capabilities, and cross-checking revealed the LLM had fabricated much of the output.
- One-function-at-a-time: Processing each function in a fresh, narrow context window to curb hallucination. While this reduced fabrication, it created API usage and rate-limit problems, made verification of translated business logic nearly impossible, and failed to connect individual functions into a coherent flow. It did, however, uncover compiled C++ STDLIB functions like
std::vector::insertand various stack-unwinding routines used for exception destructors.
The conclusion was clear: focus on business logic and deliberately ignore the compiled library functions that were mixed into the binary. This led to a decision to slice the DLL by functional area or workflow instead of considering the complete assembly code.
Hunting for the Entry Point
The first obstacle in the workflow-based approach was identifying a link or entry point among thousands of functions. Examining constants and strings in the DLL was the most viable option. Historical context helped: systems built in the late 1990s and early 2000s typically inserted data into databases via "select for insert," stored procedures, or ADO (an early ORM). Interestingly, this system used all three patterns in different places.
The target functionality involved inserting or updating a database at the end of a process, yet no insert or update queries were found in the strings, nor were there relevant stored procedures. The system actually performed a SELECT through SQL and then updated via ADO. The breakthrough came from a table name referenced in the string constants. This led to the function using that SQL statement, though an initial review suggested it might belong to a different workflow within the same functional area.
Walking the Call Tree
With the function call reference data from the disassembly tool, the process moved up the tree. Starting from the suspected leaf function containing the SQL execution, each parent function was examined to understand context. At every step, ASM code was converted into pseudocode to build understanding.
Unlike the earlier brute-force attempts where pseudocode couldn't be verified, this reverse navigation provided a strong prior — the possible steps leading up to a SQL execution were known in advance, and context gathered from previous steps confirmed or rejected hypotheses. Mapping out relevant functions required navigation through the call tree, occasionally down wrong paths before correcting course.
One hard lesson involved context poisoning: inadvertently passing the expected outcome to the LLM caused it to color its output toward that target, leading down incorrect paths and eroding trust in the results. A clean-room environment for AI analysis had to be re-established. Ultimately, this method narrowed a workflow from over 4,000 functions down to roughly 40 relevant ones.
Enrichment in Passes
AI was applied iteratively through the assembly layer. In each pass, navigation moved either from a leaf node up to the tree's root or in the reverse direction. At each step, the context of a function was enriched using its parent's or child's context. This gradually shifted the work from technical pseudocode conversion to functional specification. Simple techniques, like asking the LLM to propose meaningful method names based on the known context, proved effective. After multiple passes, the entire functional context was mapped out.
Verifying the Root Node
The final challenge was confirming the entry function. Virtual functions, typical in C++, made it difficult to link entry points to class definitions. Even when the functionality from the root node down appeared complete, uncertainty remained about additional operations in parent or wrapper functions. A debugger with a simple breakpoint and call stack review would have made this trivial, but it was unavailable. Triangulation was required instead:
- Call stack analysis to confirm the execution path.
- Signature validation — checking argument and return signatures in the stack.
- Cross-referencing with UI calls — associating method signatures with "submit" calls from the web tier and validating parameter types and usage against that context.
Weaving a Comprehensive Specification
With the UI layer reconstructed, CDC data, server logic inferred, and the app-tier binaries analyzed, all pieces were integrated into a complete, high-confidence functional summary of the system. This specification provides a traceable foundation for business review and modernization or forward-engineering efforts.
From the experience, repeatable practices emerged. They are not strict recipes — every system differs — but guiding patterns for approaching unknown codebases:
- Start with the most visible, trustworthy artifacts: Screens, data schemas, and logs provide observable behavior users can confirm. This anchors progress before descending into opaque binaries and prevents analysis paralysis.
- Enrich in passes, not all at once: Break artifacts into manageable chunks, extract partial insights, and build context progressively. This reduces hallucination, limits assumption risk, and scales to large legacy estates.
- Triangulate every hypothesis: Confirm each finding across at least two independent sources — e.g., validate a UI flow against a stored procedure, then against the binary call tree. This exposes contradictions and strengthens confidence.
- Preserve lineage for all inferred knowledge: Track exactly which UI screen, schema field, or binary function each conclusion came from. This audit trail prevents false assumptions from propagating and permits tracing back to original evidence.
- Keep human experts involved: AI accelerates analysis but cannot replace domain understanding. Expert validation of AI hypotheses is essential, especially for business-critical rules, to avoid embedding AI errors directly into future designs.
What AI Brings to Reverse Engineering
AI-assisted black-box reverse engineering materially changes the economics of legacy modernization:
- Compressed timelines: Understanding a legacy system can move from months to weeks, as AI converts assembly into pseudocode and classifies functions into business versus utility categories at scale.
- Lowered fear of undocumented systems: Organizations no longer need to treat source-code-less systems as untouchable black boxes.
- A credible first step forward: Reverse engineering becomes a dependable pre-modernization baseline.
The payoff is full functional specifications without source code, better-informed modernization and cloud-migration decisions, and forward engineering guided by insight rather than guesswork.
The coming years could see dramatically faster legacy retirements. Advances in AI tools are reducing the steep costs and long-term risks historically associated with modernization projects. It is plausible that more systems will be retired in the next two to three years than in the last twenty. The recommendation is to start small — even a sandboxed reverse-engineering effort can yield surprising visibility into what a system actually does.



