ghidra
This commit is contained in:
1 parent
6a6553077b
commit
f71a2a3512
23 files changed
+7773
-68
No files matched your search
@@ -0,0 +1,112 @@
|
||||
# maple-py — rekordbox `export.pdb` writer + reverse-engineering notes
|
||||
|
||||
Python tooling to generate a rekordbox USB export (`PIONEER/rekordbox/export.pdb`
|
||||
+ `exportExt.pdb` + `USBANLZ` analysis files) so a CDJ/XDJ can read a
|
||||
self-assembled music library. The writer lives in `src/maple/pdb/`.
|
||||
|
||||
This README also documents what we learned reverse-engineering rekordbox's own
|
||||
exporter, because the on-disk format has real unknowns / magic numbers that the
|
||||
public kaitai schema (`src/kaitai/rekordbox_pdb.py`) only partially decodes.
|
||||
|
||||
## The format, briefly
|
||||
|
||||
`export.pdb` is a **DeviceSQL** database: a flat file of 4096-byte **pages**,
|
||||
grouped into 20 **tables** (tracks, artists, albums, playlists, artwork,
|
||||
colors, columns, history, and several still-unknown ones). Page 0 is the file
|
||||
header + table directory; each table is a linked list of pages. See
|
||||
`src/maple/pdb/{writer,pager,rows,strings}.py`.
|
||||
|
||||
## Reverse-engineering findings (rekordbox 7.0.5, Windows)
|
||||
|
||||
### Where the export code lives
|
||||
- The exporter is in **`rekordbox.exe`** (the 89 MB main binary), **not**
|
||||
`rekordboxAgent.exe`. Confirmed by string scan: only `rekordbox.exe` contains
|
||||
`USBANLZ`, `PIONEER`, `djmd…` table names, `.pdb`. It's a JUCE C++ app.
|
||||
- `export.pdb` is written by rekordbox's **bundled DeviceSQL "Page Manager"**
|
||||
engine — functions prefixed `dsql*` / `edb_dyn_*` (error strings:
|
||||
`dsqlOpenDeviceDB`, `edb_dyn_CriticalSectionOn ERROR`,
|
||||
`Page Manager (Close/)Open returns ERROR`).
|
||||
- **Key consequence:** the page-level "magic numbers" maple guesses
|
||||
(page flags `0x64`/`0x44`, `0x1fff` markers, `0x3ec`, free/used sizes) are
|
||||
**DeviceSQL internals**, not rekordbox-specific. They are NOT immediate
|
||||
constants in the code — they're produced by the page-manager logic, so they
|
||||
must be read out of the `dsql*`/`edb_dyn_*` functions, not grep'd.
|
||||
|
||||
### A rekordbox USB export writes three databases
|
||||
| file | format | writer |
|
||||
|------|--------|--------|
|
||||
| `export.pdb` | DeviceSQL (CDJ reads this) | DeviceSQL Page Manager |
|
||||
| `exportExt.pdb` | DeviceSQL extended | same |
|
||||
| `exportLibrary.db` | **SQLite** (rekordbox's own) | bundled `sqlite3_*` |
|
||||
| `USBANLZ/**/ANLZ*.DAT/.EXT/.2EX` | per-track waveform/beatgrid | main app |
|
||||
|
||||
### `export.pdb` serializer call stack (live `WriteFile` backtrace → Ghidra)
|
||||
Ghidra static address = `0x140000000 + module_offset` (Ghidra image base is
|
||||
`0x140000000`; the ASLR runtime base varies per launch — never hardcode it,
|
||||
read `module+offset` from Frida).
|
||||
|
||||
```
|
||||
0x1420e8804 calls WriteFile ─┐
|
||||
0x1420d7358 │ DeviceSQL write plumbing
|
||||
0x1420d6e74 │ (transactions, page I/O)
|
||||
0x1420d62a4 │
|
||||
0x1420eac94 │
|
||||
0x1420e07d8 │
|
||||
0x1420de140 ─┘
|
||||
FUN_141d75c20 ← dsqlOpenDeviceDB / Page Manager (export.pdb-specific)
|
||||
0x141cad100
|
||||
0x141cac390
|
||||
0x141b8b4c0
|
||||
0x141aca850
|
||||
0x1423c9ab0 ← outer export driver
|
||||
```
|
||||
Other anchors found by string xref: `FUN_14119d2d0` (export orchestrator,
|
||||
builds `/PIONEER/rekordbox/export.pdb`, walks "Contents"), `FUN_140f37e50`
|
||||
(builds `PIONEER`/`USBANLZ` paths).
|
||||
|
||||
### Gotchas
|
||||
- **Incremental writes:** rekordbox rewrites only the *changed* pages (seeked
|
||||
`WriteFile`s), so a Frida capture is partial. The complete file is on the USB
|
||||
(`F:\PIONEER\rekordbox\export.pdb`) — copy it from there for a full diff.
|
||||
- **CIG / injection:** `rekordboxAgent.exe` refuses live Frida injection
|
||||
(Code Integrity Guard). The pdb writer (main `rekordbox.exe`) is hookable, but
|
||||
only via **child-gating at spawn** — Windows has **no spawn-gating**.
|
||||
|
||||
## Tooling
|
||||
|
||||
### Static analysis (Ghidra, headless, in WSL)
|
||||
- JDK 21 (`~/jdk/jdk-21.0.11+10`), Ghidra 12.1.2. Ghidra rejects JDK 25 — use 21.
|
||||
- Analyzed project: `~/ghidra_proj` (project `rbx`, program `rekordbox.exe`).
|
||||
Full analysis ≈ 1 h with `GHIDRA_HEADLESS_MAXMEM=10G -max-cpu 8`.
|
||||
- Re-run a script without re-analyzing:
|
||||
```bash
|
||||
"$GHIDRA/support/analyzeHeadless" ~/ghidra_proj rbx -process rekordbox.exe \
|
||||
-noanalysis -scriptPath ~/ghidra_scripts -postScript FindPdb.java
|
||||
```
|
||||
Scripts: `~/ghidra_scripts/FindPdb.java` (string+constant xref search),
|
||||
`DecPdb.java` (decompile an address list), `CallTree.java` (call-tree drill).
|
||||
|
||||
### Dynamic capture (Frida, on Windows)
|
||||
- `tools/rbx_pdb_capture.js` — Frida agent. Hooks `NtCreateFile`/`NtWriteFile`/
|
||||
`NtClose` + `NtCreateSection`/`NtMapViewOfSection` (memory-mapped writes).
|
||||
Reconstructs files under `PIONEER`/`*.pdb` to `captures/`, prints each write's
|
||||
backtrace as `rekordbox.exe+0xOFFSET` (→ Ghidra `0x140000000 + offset`).
|
||||
- `tools/capture.mjs` — Node host (`npm i frida`). Attaches to all rekordbox
|
||||
processes, streams `[pid]`-tagged output live, stays alive, **child-gates** so
|
||||
the spawn writer is hooked at birth. Clears `captures/` on start.
|
||||
```powershell
|
||||
npm i frida # once
|
||||
node tools\capture.mjs --spawn # launch rekordbox under the gate, then Export to USB
|
||||
```
|
||||
|
||||
## Diffing maple's output vs rekordbox
|
||||
`compare_pdb.py` parses two pdbs with the kaitai schema and summarises tables/
|
||||
rows. Reference real exports: `data/PIONEER/rekordbox/export.pdb`,
|
||||
`data/acemo/PIONEER/rekordbox/export.pdb`.
|
||||
|
||||
## Status / next steps
|
||||
- Writer fully located; capture + diff pipeline works.
|
||||
- To decode a specific page magic number: search the `dsql*`/`edb_dyn_*`
|
||||
functions (image region `0x142000000+`) for the page-header builder.
|
||||
- To fix a rejected export: diff a complete USB `export.pdb` against maple's
|
||||
output field-by-field; resolve any wrong value via the matching `dsql` function.
|
||||
Reference in new issue
Block a user