Back to writing

Advanced Web Dev

Reverse Engineering with REA: Understand an App in 10 Steps

A practical REA guide for developers: inspect an app, trace one feature, separate evidence from guesses, and test an independent implementation.

Sandeep Machiraju
reverse engineeringdeveloper experience
REA: Inside an App. Trace behavior. Build from evidence. Sandeep Machiraju, Advanced Web Dev.

Imagine you are replacing an old desktop tool. You can run it, open its files, and export your data, but you do not have the repository that produced it. Before you write a replacement, you need to answer a smaller question: what does its export actually do?

A screenshot cannot tell you the encoding of the exported file. A function named exportNotes cannot tell you whether it preserves note order. A convincing AI explanation cannot establish either fact without inspecting something.

That is the useful promise behind REA, short for Reverse Engineer Anything: give an agent access to software-analysis tools, then use the resulting evidence to decide what to inspect next. REA exposes workflows through a CLI and an MCP server. It won't give you back the original repository. See the official project for its current capabilities.

This article was inspired by noisyb0y1’s REA setup guide. Here, I want to make the workflow concrete for developers: start with one observable feature, follow its implementation boundaries, and finish with a specification you can test.

The running example is a fictional desktop notes app whose export behavior we are investigating with permission. Example findings below are illustrative, not results of a completed REA audit. The commands below come from REA's documentation as of October 2026.

1. Choose the question you need answered

“Reverse engineer this app” leaves the investigation without a finish line. A useful question names an action, the result you can observe, and the uncertainty you want to remove.

For the notes app, start with: which records does Export include, and how are their contents represented in the resulting file?

That gives you a reason to inspect selection state, formatting, and file writing. Authentication, sync, and the editor’s rendering engine can remain outside the assignment unless the export demonstrably depends on them.

Investigate the export feature in the supplied application copy.

Goal: describe which records are exported and the exact output format.

Return source locations for code findings and reproduction steps
for observed behavior. Keep unverified interpretations separate.

Begin with static inspection. Identify any runtime experiment
needed to resolve a question before carrying it out.

Deliver a behavior specification and a list of unanswered questions.
Do not implement a replacement yet.

Choose software and test data you are authorized to inspect and use. Keep the initial assignment bounded enough that you can judge whether the final explanation answers it.

2. Identify the artifact and its version

“Without source code” can describe very different situations. You might lack the original repository but still have readable JavaScript bundles. Or you might have only machine code, with very little of the original naming or structure preserved.

For an Electron app, the distributed files may include an app.asar archive. Electron documents ASAR as a packaging format with filesystem-like access; it is not the original development repository. A bundle can omit comments, names, tests, and build context even when JavaScript remains inspectable. Electron’s ASAR documentation explains the format.

Record the app version, operating system, architecture, artifact path, and a digest of the exact file you inspect. For a directory, retain a manifest of relevant files and their digests. Keep analysis outputs outside the input directory so your investigation does not change its own evidence.

A report about one build should not silently become a claim about every version. Configuration, account state, locale, and feature flags can also change what a particular build does.

3. Set up REA for the target

Start from the official installation guide. Use Node 22 (22.23.4 or later), Node 24 (24.22.1 or later), or 26.12.1 and up. Then run:

npx rea-agents@latest setup

The documented setup flow lets you select an agent, review configuration changes, and approve them. Setup is interactive and asks before it changes anything. Restart the agent after registration. If you prefer to work entirely through the CLI, the static analysis command in the next step can be used without registering an MCP server.

To inspect installation readiness:

npx -y rea-agents@latest doctor --json

Read the diagnostics for the operation you need. An unavailable native-analysis provider does not automatically prevent static JavaScript analysis. Likewise, a healthy installation check does not prove that a particular application will be fully understood.

Use @latest to follow the current installation instructions, then record the actual REA version used for your investigation. For repeatable work, use that exact package version on subsequent runs and keep its compatible runtime. The repository’s development branch and a published package can differ.

Native binaries require a different setup from packaged JavaScript. REA supports analysis providers such as Ghidra, Hopper, and IDA; supported hosts and versions vary. Follow the provider instructions for your platform when the investigation actually reaches native code. Ghidra, for example, provides disassembly and decompilation capabilities. You do not need to install every provider before inspecting a JavaScript export path.

4. Build a static map before running the app

For an extracted JavaScript application directory or an ASAR archive, the documented CLI entry point is:

npx -y rea-agents@latest analyze-javascript-application \
  "/absolute/path/to/app.asar" --json > application-evidence.json

Replace the example path with the target copy. Run the command from your analysis-output directory. This operation inspects the supplied application files without launching the target app; it does not demonstrate how the app behaves during an actual export.

Save the full result, but ask the agent for a focused view: entry points, likely export modules, relevant imports, and unresolved relationships. A full graph can be too large to read productively in a chat. REA’s CLI and Evidence guide documents saved evidence and smaller inspection views.

Choose a concrete starting point: a visible button label, a method name, an export filename, or an IPC channel. Record where it occurs. A string match narrows the search; it does not prove that the matching code is used by the action you care about.

If the result is partial, retain that qualification. “No matching handler was resolved” and “there is no handler” are different conclusions.

5. Follow the feature across process boundaries

In an Electron app, the button you click may be several calls away from the operation that writes the file. Electron’s renderer and main processes communicate through IPC; a preload script can expose a small API to the page. The Electron IPC guide describes these patterns.

For our fictional export, the path we want to investigate might look like this:

Export button
    ↓
Page-facing export method
    ↓
Preload bridge
    ↓
IPC channel and main-process handler
    ↓
Record selection → serialization → file write

Read this as a proposed investigation map, not a finding about the target. Each arrow needs support: the caller, the receiver, and the values crossing between them.

REA’s official JavaScript guide provides a small downloadable Electron example that follows a CSV export through these layers. It is a useful practice target before opening a larger package.

Starting from the export entry point, follow the calls to the writer.

For each boundary, report:
- Caller and receiver locations.
- Arguments or data passed between them.
- How the connection was resolved.
- Any ambiguity or missing implementation.

Find where record selection and serialization happen.
Do not assume the renderer owns those decisions.

This is a familiar frontend debugging problem: the visible component may only initiate the work. Understanding the operation requires following the data beyond the component tree.

6. Keep observations separate from explanations

Use a small investigation log with three kinds of entries. Distinguish something found in the artifact, something observed while the app runs, and an interpretation that still needs testing.

For example, these hypothetical entries have different evidential weight:

  • Static finding: a formatter contains a branch that surrounds fields containing commas with quotation marks. Attach the artifact identity and exact code location.
  • Runtime observation: exporting a note titled red, blue produced the bytes "red, blue" in the title field. Attach the input fixture, output file, and app state.
  • Hypothesis: every field uses the same quoting rule. Identify which other fields and inputs would test this.

The first two entries support each other, but neither automatically proves the third. A second field might pass through a different transformation before reaching the writer.

REA’s JavaScript workflow documentation describes source locations, unresolved relations, and analysis limitations. Preserve those details when turning tool output into prose. An agent’s summary should not become more certain than the evidence it summarizes.

Prefer a short set of traceable findings over an elaborate architectural story. If a conclusion matters to the replacement, there should be a clear way to revisit the evidence behind it.

7. Run experiments that distinguish hypotheses

Once you have a plausible path through the code, use controlled inputs to check it. Work with a disposable profile or test installation and record the state before each run. Runtime inspection can execute application code, write files, or contact services; choose the specific action you intend to observe.

For export behavior, useful fixtures include:

  • An empty collection and a collection containing one record.
  • A title containing a comma, a quotation mark, or a line break.
  • Accented text and characters outside basic Latin.
  • Two notes with the same title but different contents.
  • A filtered view where visible records differ from the full collection.
  • A cancelled destination choice or a deliberately unwritable test location.

Change one relevant condition at a time. If both the collection and the destination change, an output difference becomes harder to explain.

Inspect the file itself as well as the UI. A spreadsheet viewer may display two files similarly even when their encodings or line endings differ. A success message may describe the requested operation without proving that the final file contains the expected records.

When an experiment contradicts your explanation, update the explanation. Do not discard the result merely because it disagrees with a function name or a convincing decompilation.

8. Write a behavior specification

Before implementing anything, describe what another developer would need to reproduce. Keep the specification at the observable boundary: accepted inputs, outputs, state changes, and failure behavior.

Export behavior specification

Target: exact app version, artifact identity, environment.
Trigger: the action that begins export.
Input scope: which records and fields are included.
Ordering: the rule, or an explicit unresolved question.
Representation: encoding, delimiter, quoting, line endings.
Destination: selection, filename, and overwrite behavior.
Failures: cancellation, invalid data, and write errors.
Evidence: references for each established behavior.
Unknowns: cases not inspected or not yet resolved.

Fill a field with evidence or leave it explicitly unknown.

Separate observations from product decisions. Suppose the original app drops a field on export and your replacement should preserve it. Document the intentional difference and test it as a new requirement. Otherwise, an improvement can look like a compatibility defect, or a defect can be explained away as an improvement.

A written specification makes the implementation boundary clearer. It does not, by itself, establish permission to reproduce someone else’s software. For an independent implementation, use behavior you are authorized to reproduce and avoid importing proprietary code or assets into the replacement.

9. Implement the smallest independent equivalent

Start with the serializer or record-selection rule you now understand. You do not need to recreate the entire desktop interface to test whether a collection of notes produces the expected export.

Give the implementation step the behavior specification and permitted fixtures. Keep the investigation’s uncertainties visible rather than asking the agent to fill them with conventional behavior.

Implement the export behavior described in this specification.

Use original code and the supplied permitted fixtures.
For each behavior, identify its corresponding test.

Do not invent answers for unresolved cases. List the minimum
additional observations needed to implement them.

Keep intentional departures from the original in a separate list.
Do not recreate unrelated parts of the application.

An ordinary library may be the right implementation once you know the required behavior. Choose it by comparing its output with the specification, rather than assuming that “CSV support” means matching every quoting, ordering, and error-handling decision.

The same approach applies when you only want to understand a feature. A reconstruction is optional. A traceable explanation of where the export runs and why it behaves a certain way may already complete the assignment.

10. Compare outputs and report the limits

Run the same permitted inputs against the original and the replacement, starting from equivalent state. Compare the results at the boundary you promised to preserve.

If the goal is identical file output, a small Node.js check can compare two saved exports. Save this as compare-exports.mjs:

import { readFileSync } from "node:fs";
import assert from "node:assert/strict";

const [originalPath, rebuiltPath] = process.argv.slice(2);
if (!originalPath || !rebuiltPath) {
  throw new Error("Provide the original and rebuilt export paths.");
}

assert.deepEqual(
  readFileSync(rebuiltPath),
  readFileSync(originalPath),
  "Export bytes differ"
);
console.log("Export bytes match for this fixture.");
node compare-exports.mjs original.csv rebuilt.csv

This checks one pair of files. It does not test record selection, cancellation, or the whole export workflow by itself. The fixtures and the steps that produced those files are part of the test.

If semantic equivalence is sufficient, compare parsed records instead. Define exactly which differences you will ignore and why. Normalizing timestamps or sorting records can conceal a real mismatch when those values or their order matter to the consumer.

Keep a report of passing cases, mismatches, unresolved questions, and intentional changes. State the tested app version and environment. Passing your current fixtures establishes compatibility for those fixtures, not for every possible input.

What a local investigation can establish

A local package can reveal client-side logic and the requests it makes. It cannot, on its own, establish the implementation of a remote service. Observing a successful response tells you what happened for that request; it does not reveal every rule the server applies.

Start with one feature you can exercise and one result you can inspect. REA can help you navigate the implementation. Your questions and tests determine whether the explanation holds up.