add workflow

This commit is contained in:
Benjamin Palko 2026-10-14 17:04:03 -04:00
parent b8f96fc411
commit ad57d055f2
2 changed files with 222 additions and 3 deletions

203
docs/WORKFLOW.md Normal file
View file

@ -0,0 +1,203 @@
# Workflow — reverse-engineering a server binary
The repeatable process used for `Agent_Release_x64.exe`. Follow it for each
binary in `decomp/INVENTORY.md`. Ground rules, layout and tooling are in
`AGENTS.md`. What has been learned so far is in `docs/ARCHITECTURE.md`.
Every finding is recorded as **observed**, **inference** or **unknown**, with an
Evidence ID or decomp file reference. Static analysis never establishes runtime
behaviour.
---
## 0. Prerequisites (once per machine)
- Ghidra is at `/opt/ghidra`, and Java 21 and Node come from `mise.toml`.
- REA MCP is registered project-locally in `opencode.json`.
- Don't reinstall any of these; repair the configuration instead.
- **Ghidra headless fails with `UnsupportedClassVersionError` (class file
71.0):** its compiled-script cache was built by a newer JDK.
- Move `~/.config/ghidra/ghidra_<ver>_DEV/osgi/compiled-bundles` aside.
- Ghidra rebuilds the cache on the next run.
## 1. Identify the binary (cheap, before any decompilation)
```sh
sha256sum binaries/<X>.exe
objdump -p binaries/<X>.exe | grep -E 'DLL Name|Subsystem|MajorLinker|pdb'
```
In REA:
1. Run `open_binary` with the original path under `binaries/`.
2. Run `binary_overview`.
3. Run `search_strings` (regex) for config paths, log text, protocol names and
class names.
Record the SHA-256, linker version, PDB path, imports and procedure count in
`decomp/<X>/evidence.md`.
## 2. Determine language and toolchain
Check for these C++ markers:
- RTTI descriptors (`strings -a | grep '^\.?AV'`)
- `__CxxFrameHandler3`
- an `MSVCP*.dll` import
All 16 servers are MSVC 2010 C++ (see `docs/ARCHITECTURE.md`). Re-check each
new binary and record the result.
## 3. Discover third-party libraries and acquire their sources (**mandatory**)
Any library you can match against upstream source saves you from
decompiling it, and gives names, struct layouts and intent that the binary
lacks. **Every time a library is found, search for its source online before
continuing.**
### 3a. Find candidates
| Signal | How |
|---|---|
| Imported DLLs | `objdump -p` DLL names, cross-checked against `binaries/*.dll` |
| DLL version resources | `strings -el <dll> \| grep -A1 -E '^(FileVersion\|ProductName\|CompanyName)$'` |
| Embedded version text | `strings -a \| grep -iE '<lib>/[0-9]\|part of OpenSSL\|libcurl/'` |
| Statically linked C++ libraries | RTTI class prefixes (`TiXml*`, `Proud::*`, `ATL::*`) |
| Library-specific strings | assert/error messages (`Proud::CNetServerImpl::…`, TinyXML error table) |
| PDB paths | e.g. `…\OpenLibrary\openssl-1.0.0d\…`, which names the exact version |
### 3b. Pin the version
Use, in this order of preference:
1. An explicit version string (VERSIONINFO, `"OpenSSL 1.0.0d 8 Feb 2011"`).
2. A version in the PDB path.
3. **Differential evidence:** download several candidate releases and compare a
distinctive artefact with the binary. Examples are error-string tables,
exported symbol lists and header constants.
- Example: the TinyXML error table contains `Memory allocation failed.`.
That entry exists in 2.5.x and was removed in 2.6, which gives 2.5.1–2.5.3.
4. If none of these work, choose the closest era-compatible release and mark it
**closest, not exact**.
### 3c. Search for the source
Search in this order:
1. Official release archives (project site or SourceForge; check `old/` and
`archeology/` paths).
2. Official git tags (`git ls-remote --tags <repo>`), or a GitHub tag archive
if the release tarball no longer exists.
3. Vendor package repositories. These often contain headers only.
4. Clean-room equivalents (for example mingw-w64 headers for Microsoft APIs)
or open reimplementations (for example Wine).
5. `gh search repos <lib>` for forks or mirrors. Treat these as unverified and
label them that way.
Verify each download against an official checksum where one exists. Always
record its SHA-256.
### 3d. Check the licence and place the files
| Licence | Location | Committed? |
|---|---|---|
| Open source (OpenSSL, curl/MIT, zlib, BSD, public domain) | `third-party/<lib>-<version>/` | Yes |
| Proprietary or EULA-restricted (e.g. ProudNet) | `third-party/_local/<lib>-<version>/` | **No.** It is gitignored. Ask the user before fetching. |
| No public source (XTrap, Apex, HASP runtime) | nothing downloaded | Record acquisition routes only |
Other rules:
- Keep upstream files unmodified. If you must change one (for example curl's
version stamp), record exactly what changed.
- Read the licence before committing anything. If redistribution is
prohibited, it goes in `_local/` or isn't fetched.
### 3e. Document it
Add a row or section to `third-party/README.md` with:
- the observed identity in the binary, with where it was seen;
- the version and how it was pinned;
- the source URL, tag or commit, and SHA-256;
- match quality (exact / closest / headers only);
- the licence;
- any local edits.
Also:
- Mention the library in `docs/ARCHITECTURE.md` if it is statically linked.
- Add it to the list in `AGENTS.md`.
- If no source exists, add it to the "No public source" and "Acquisition
routes" tables instead.
### 3f. Use it
- Label library functions in the decompiled output from known exports, class
method tables, error strings and the matched headers.
- Exclude labelled library code from application analysis.
## 4. Decompile
In REA, choose seeds from distinctive strings or imports and use
`trace_feature` to find the functions that reference them. Then run
`procedure_pseudo_code` / `batch_decompile` on those functions.
For full coverage, run Ghidra headless with the repo scripts:
```sh
analyzeHeadless /tmp/opencode/ghproj X -import binaries/<X>.exe -overwrite \
-scriptPath decomp/scripts -postScript ExportDecomp.java "$PWD/decomp/<X>/full" -deleteProject
analyzeHeadless /tmp/opencode/ghproj X -import binaries/<X>.exe -overwrite \
-scriptPath decomp/scripts -postScript VTableMap.java "$PWD/decomp/<X>/full/vtables.tsv" -deleteProject
```
This produces:
- `full/functions/<addr>.c` and `full/index.tsv` from `ExportDecomp.java`;
- `full/vtables.tsv` from `VTableMap.java`, which maps class vftable slots to
functions.
Generate `cpp/classes.hpp` from `vtables.tsv`, then check that it compiles:
`g++ -fsyntax-only`.
## 5. Record evidence
In `decomp/<X>/`:
1. Run `export_evidence_bundle` to write `evidence-bundle.json`.
2. Extract the REA pseudocode to `<addr>.pseudo.c` and the overview to
`binary_overview.json`.
3. Write `evidence.md`: the findings table (conclusion → type → evidence), the
proposed function names, and the open questions.
4. Update the status and role of the binary in `decomp/INVENTORY.md`.
## 6. Lift to readable C++
`decomp/<X>/cpp/*.cpp` holds hand-lifted code for the analysed functions:
- Cite the original address on each function.
- Mark every name as inferred.
- Record the original bugs you notice (leaks, races).
## 7. Port to Linux
`src/<name>/` builds the lifted code with Meson (see `src/README.md`). Keep a
`platform.{hpp,cpp}` layer that maps each Win32 call to its nearest POSIX
equivalent.
The per-binary `README.md` must contain:
- a substitution table: original address → Win32 API → Linux replacement →
behaviour difference;
- what is stubbed or not ported, and why.
Build with `ninja -C build` and smoke-test it. A passing test proves only that
the port works, not how the original behaves.
## 8. Close
Run `close_binary` when the investigation is complete. Before closing, export
the Evidence bundle, because session annotations are temporary.
---
## Checklist per binary
- [ ] SHA-256, imports, linker and PDB recorded
- [ ] Language confirmed
- [ ] **Every third-party library identified, its version pinned, its source
searched for and downloaded where licensing allows, and it documented in
`third-party/README.md`**
- [ ] Key functions decompiled; `full/` export done
- [ ] `evidence.md`, `INVENTORY.md`, `docs/ARCHITECTURE.md` updated
- [ ] Lift and Linux port updated, if in scope
- [ ] Evidence bundle exported; REA session closed