leanrea

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

leanrea: fast path

leanrea:快速路径

You already know these tools. This file only lists the fastest command for each target, and the traps.
你已经了解这些工具。本文件只列出每种 target 最快的命令,以及相关陷阱。

Rules

规则

  • Work on a copy (
    cp -c
    on APFS is instant). Put each extraction in a new dir under
    $TMPDIR/re/<name>
    . Reuse an extraction if one exists.
  • Route by
    file -b T
    and the magic bytes first. Run the cheapest inventory next. Only then decompile or read code.
  • rg
    skips hidden and ignored files, and bundlers hide code (Electron:
    .webpack/
    ,
    .vite/
    ). Always search extracted trees with
    rg -uu
    .
  • Never
    cat
    a big or minified file. Slice it:
    rg -o '.{0,200}NEEDLE.{0,200}' F | head
    . Dump a disassembly once to a file, then grep that file.
  • Batch independent probes into ONE shell call with
    ;
    and
    echo '--- name'
    markers (quoted: zsh expands a bare
    ==x
    ). Cap every output with
    head
    or
    -m
    .
  • Answer from evidence. In the answer, mark each claim as seen (cite file:offset, or a symbol/string) or inferred. A fact you did not find is unknown, not "absent". Static reading is not observed execution.
  • A missing tool is not a blocker. Use the fallback in the same row, or
    brew install <x>
    if the user allows it.
  • Turns cost more than bytes. Aim for 2–4 tool calls: one batched recon call, one or two targeted slices, then answer. Stop when every item you were asked has evidence. Do not re-verify what you already saw.
  • 在副本上工作(APFS 上
    cp -c
    可即时完成)。将每次 extraction 放入
    $TMPDIR/re/<name>
    下的新目录。如果已有 extraction,则复用。
  • 先根据
    file -b T
    和 magic bytes 路由,再运行成本最低的 inventory。只有完成这些后才 decompile 或读取代码。
  • rg
    会跳过 hidden 和 ignored 文件,bundler 还会隐藏代码(Electron:
    .webpack/
    、
    .vite/
    )。搜索 extraction 后的目录树时始终使用
    rg -uu
    。
  • 永远不要
    cat
    大型或 minified 文件。应进行切片:
    rg -o '.{0,200}NEEDLE.{0,200}' F | head
    。将 disassembly dump 到文件一次,然后 grep 该文件。
  • 将彼此独立的 probe 批处理到一次 shell call,使用
    ;
    和
    echo '--- name'
    marker(marker 要加引号:zsh 会展开未加引号的
    ==x
    )。用
    head
    或
    -m
    限制每段输出。
  • 以证据为依据。在答案中,将每项 claim 标为 seen(引用 file:offset,或 symbol/string)或 inferred。未找到的事实是 unknown,而不是“不存在”。静态阅读不等同于实际观察到的执行。
  • 缺少工具并不是 blocker。使用同一行中的 fallback,或在用户允许时执行
    brew install <x>
    。
  • 相比处理更多 byte,应尽量减少 Turn 次数。目标是 2–4 次 tool call:一次批量 recon call、一到两次定向 slice,然后作答。每个被问到的项目都有证据后即可停止。不要重复验证已经看到的内容。

First move: one call each, paste and run

第一步:每种目标调用一次,粘贴并运行

Native (Mach-O):
sh
T=./bin; D=$TMPDIR/re/$(basename $T); mkdir -p $D; file $T; lipo -info $T; otool -L $T; echo '--- EXPORTS'; nm -gU $T | head -150; echo '--- IMPORTS'; nm -u $T | rg -v '^__Z|napi_' | tr '\n' ' '; echo; echo '--- STRINGS'; strings -a -n 6 $T | rg -v '^[_$.]' | head -400; otool -tV $T > $D/dis.txt; echo '--- FUNCS'; rg '^[_a-zA-Z].*:$' $D/dis.txt | rg -v '^_?_?Z?N?St3' | head -150
Then read one function:
awk '/^FUNC_LABEL:$/{p=1;next} p&&/^[_a-zA-Z].*:$/{exit} p' $D/dis.txt | rg 'bl|b\.|cmp|literal|mov\s+w2'
.
Electron:
sh
A=./app.asar; X=$TMPDIR/re/asar; rm -rf $X; npx -y @electron/asar extract $A $X && jq '{name,version,main}' $X/package.json; find $X -path '*/node_modules' -prune -o -type f \( -name '*.js' -o -name '*.node' \) -print | head -60; ls ${A}.unpacked/node_modules 2>/dev/null; rg -uu -l --glob '!node_modules' 'exposeInMainWorld' $X
Then slice the needle across the bundle:
rg -uu -o --glob '!node_modules' '.{0,250}NEEDLE.{0,250}' $X | head -20
.
Go:
go version -m T
alone usually answers the question.
原生程序(Mach-O):
sh
T=./bin; D=$TMPDIR/re/$(basename $T); mkdir -p $D; file $T; lipo -info $T; otool -L $T; echo '--- EXPORTS'; nm -gU $T | head -150; echo '--- IMPORTS'; nm -u $T | rg -v '^__Z|napi_' | tr '\n' ' '; echo; echo '--- STRINGS'; strings -a -n 6 $T | rg -v '^[_$.]' | head -400; otool -tV $T > $D/dis.txt; echo '--- FUNCS'; rg '^[_a-zA-Z].*:$' $D/dis.txt | rg -v '^_?_?Z?N?St3' | head -150
然后读取一个 function:
awk '/^FUNC_LABEL:$/{p=1;next} p&&/^[_a-zA-Z].*:$/{exit} p' $D/dis.txt | rg 'bl|b\.|cmp|literal|mov\s+w2'
。
Electron:
sh
A=./app.asar; X=$TMPDIR/re/asar; rm -rf $X; npx -y @electron/asar extract $A $X && jq '{name,version,main}' $X/package.json; find $X -path '*/node_modules' -prune -o -type f \( -name '*.js' -o -name '*.node' \) -print | head -60; ls ${A}.unpacked/node_modules 2>/dev/null; rg -uu -l --glob '!node_modules' 'exposeInMainWorld' $X
然后在 bundle 中切取 needle 附近内容:
rg -uu -o --glob '!node_modules' '.{0,250}NEEDLE.{0,250}' $X | head -20
。
Go:通常仅运行
go version -m T
就能回答问题。

Router

路由表

Target (detect)Fastest path
Electron app (
*.app/Contents/Resources/app.asar
)
npx -y @electron/asar extract app.asar X
(also see
app.asar.unpacked/
for
.node
addons). Entry point:
jq .main X/package.json
. Surface: `rg -n -o '.{0,120}(ipcMain.(handle
Web bundle / minified JSFind
sourceMappingURL
. If the map has
sourcesContent
, dump the sources:
jq -r '.sources[]'
. For webpack, grep the module ids, then slice around the hit. For pretty-print:
npx -y prettier --parser babel F > F.pretty.js
, then read with line ranges.
Hermes / bytenode / V8 snapshotHermes magic is
c6 1f bc 03
(
index.android.bundle
): use
hermes-dec
/
hbc-decompiler
.
.jsc
(bytenode) cannot be decompiled: use strings and runtime hooks.
Mach-O (
file
: Mach-O)
lipo -info
(thin first:
lipo -thin arm64 -o T1 T
). Then:
otool -L
(libs);
xcrun dyld_info -exports -imports
;
nm -gU
(exports) /
nm -u
(imports = the capability map);
strings -a -n 6
;
otool -l | rg -A3 'LC_(RPATH|BUILD_VERSION)'
;
codesign -d --entitlements :- T
;
otool -ov
(ObjC classes/methods);
xcrun swift-demangle --compact
.
Native code logic
otool -tV T > $D/dis.txt
(or
llvm-objdump -d --no-show-raw-insn -C
). Find the function:
rg -n '^_?.*Name.*:$'
. Read the call/string trail:
awk '/^FUNC:/{p=1} p&&/^[_a-zA-Z].*:$/&&!/FUNC/{exit} p' dis.txt | rg 'bl|call|literal|cmp'
. otool labels string literals and stubs inline, which is often enough. Only for real pseudocode: Ghidra
analyzeHeadless $D p -import T -postScript DecompileAll.java -deleteProject
or
r2 -qc 'aaa; pdc @ sym.X' T
.
ELF / PE
readelf -hlSW
,
readelf -dW
(NEEDED),
nm -D --defined-only
,
objdump -d -M intel
. PE:
objdump -x
,
pefile
/
wrestool
for resources. checksec:
pwn checksec
.
Go (
__go_buildinfo
,
.go.buildinfo
)
go version -m T
gives the toolchain, path, mod, deps and build flags in one call. Without Go:
strings -a T | rg -m1 -A200 'Go buildinf' 
. Symbols survive stripping:
go tool nm T | rg main\.
.
RustDeps (cargo-auditable):
cargo audit bin T
, or extract
.dep-v0
/
__DATA,.dep-v0
and zlib-inflate it. Compiler:
strings -a T | rg -o '/rustc/[0-9a-f]{40}' | sort -u
. Crates from paths:
strings -a T | rg -o '[a-z0-9_-]+-[0-9]+\.[0-9]+\.[0-9]+/src' | sort -u
.
.NET (
file
: Mono/.Net)
ilspycmd -p -o X T.dll
(install:
dotnet tool install -g ilspycmd
), then
rg
. P/Invoke:
rg DllImport X
. NativeAOT/IL2CPP are native: use the native rows.
Android APK
jadx -d X --show-bad-code a.apk
then
rg
. Resources/manifest only:
apktool d --no-src -o X a.apk
. Quick look:
unzip -l a.apk
.
lib/*/libflutter.so
means Flutter (Dart AOT:
blutter
);
libhermes.so
means React Native (Hermes row).
Flutter snapshot hashIn
libapp.so
, find magic
f5 f5 dc dc
; the 32 hex chars at +20 are the snapshot hash. Map it to a Dart version.
WASM
wasm-objdump -x
,
wasm2wat
,
wasm-decompile
(WABT).
SQLiteCopy the db, plus the
-wal
file if present.
sqlite3 'file:C?mode=ro' '.schema'
, then
SELECT * FROM t LIMIT 20
.
plist / nib / car
plutil -p F
. Info.plist holds the bundle id, URL schemes and privacy strings. Asset catalogs:
assetutil --info Assets.car
.
pcap / HAR
tshark -r C -q -z conv,tcp
;
tshark -r C -T fields -e dns.qry.name -e tls.handshake.extensions_server_name -e http.request.full_uri | sort -u
. HAR:
jq -r '.log.entries[].request | .method+" "+.url'
.
Firmware
binwalk -e F
or
unblob -e X F
. Then route each extracted binary through this table.
Website
curl -sL URL
for the HTML and script srcs, then fetch the JS and slice it. For runtime behavior, use a headless browser with a network log (Playwright
page.on('request')
) or Chrome
--remote-debugging-port
.
Running app / runtimemacOS:
lldb -p PID
,
dtrace
/
fs_usage
/
log stream --predicate 'process=="X"'
. Electron main:
--inspect
(only if the RunAsNode/inspect fuses allow it). Android:
adb logcat
,
frida -U -f pkg -l hook.js
.
目标(检测方式)最快路径
Electron app(
*.app/Contents/Resources/app.asar
)
npx -y @electron/asar extract app.asar X
(另见
app.asar.unpacked/
中的
.node
addon)。入口点:
jq .main X/package.json
。扫描:`rg -n -o '.{0,120}(ipcMain.(handle
Web bundle / minified JS查找
sourceMappingURL
。如果 map 含有
sourcesContent
,导出 sources:
jq -r '.sources[]'
。对于 webpack,grep module id,然后在命中位置附近切片。如需 pretty-print:
npx -y prettier --parser babel F > F.pretty.js
,再按行范围读取。
Hermes / bytenode / V8 snapshotHermes magic 为
c6 1f bc 03
(
index.android.bundle
):使用
hermes-dec
/
hbc-decompiler
。
.jsc
(bytenode)无法反编译:使用 strings 和 runtime hook。
Mach-O(
file
:Mach-O)
lipo -info
(先用
lipo -thin arm64 -o T1 T
转为 thin binary)。然后:
otool -L
(libraries);
xcrun dyld_info -exports -imports
;
nm -gU
(exports)/
nm -u
(imports,即 capability map);
strings -a -n 6
;
otool -l | rg -A3 'LC_(RPATH|BUILD_VERSION)'
;
codesign -d --entitlements :- T
;
otool -ov
(ObjC classes/methods);
xcrun swift-demangle --compact
。
原生代码逻辑
otool -tV T > $D/dis.txt
(或
llvm-objdump -d --no-show-raw-insn -C
)。查找 function:
rg -n '^_?.*Name.*:$'
。读取 call/string 路径:
awk '/^FUNC:/{p=1} p&&/^[_a-zA-Z].*:$/&&!/FUNC/{exit} p' dis.txt | rg 'bl|call|literal|cmp'
。otool 会内联标注 string literal 和 stub,通常这就足够了。只有确实需要伪代码时,才使用 Ghidra
analyzeHeadless $D p -import T -postScript DecompileAll.java -deleteProject
或
r2 -qc 'aaa; pdc @ sym.X' T
。
ELF / PE
readelf -hlSW
、
readelf -dW
(NEEDED)、
nm -D --defined-only
、
objdump -d -M intel
。PE:
objdump -x
;使用
pefile
/
wrestool
处理 resources。checksec:
pwn checksec
。
Go(
__go_buildinfo
、
.go.buildinfo
)
go version -m T
一次调用即可给出 toolchain、path、mod、deps 和 build flags。没有 Go 时:
strings -a T | rg -m1 -A200 'Go buildinf' 
。即使 stripped,symbol 仍会保留:
go tool nm T | rg main\.
。
RustDeps(cargo-auditable):
cargo audit bin T
,或提取
.dep-v0
/
__DATA,.dep-v0
并进行 zlib-inflate。Compiler:
strings -a T | rg -o '/rustc/[0-9a-f]{40}' | sort -u
。从 paths 获取 crates:
strings -a T | rg -o '[a-z0-9_-]+-[0-9]+\.[0-9]+\.[0-9]+/src' | sort -u
。
.NET(
file
:Mono/.Net)
ilspycmd -p -o X T.dll
(安装:
dotnet tool install -g ilspycmd
),然后
rg
。P/Invoke:
rg DllImport X
。NativeAOT/IL2CPP 属于原生程序:使用原生程序对应行。
Android APK
jadx -d X --show-bad-code a.apk
,然后
rg
。仅需 resources/manifest 时:
apktool d --no-src -o X a.apk
。快速查看:
unzip -l a.apk
。
lib/*/libflutter.so
表示 Flutter(Dart AOT:
blutter
);
libhermes.so
表示 React Native(Hermes 行)。
Flutter snapshot hash在
libapp.so
中查找 magic
f5 f5 dc dc
;+20 处的 32 个 hex chars 是 snapshot hash。将其映射到 Dart version。
WASM
wasm-objdump -x
、
wasm2wat
、
wasm-decompile
(WABT)。
SQLite复制 db;如果存在
-wal
文件,也一并复制。
sqlite3 'file:C?mode=ro' '.schema'
,然后
SELECT * FROM t LIMIT 20
。
plist / nib / car
plutil -p F
。Info.plist 包含 bundle id、URL schemes 和 privacy strings。Asset catalog:
assetutil --info Assets.car
。
pcap / HAR
tshark -r C -q -z conv,tcp
;
tshark -r C -T fields -e dns.qry.name -e tls.handshake.extensions_server_name -e http.request.full_uri | sort -u
。HAR:
jq -r '.log.entries[].request | .method+" "+.url'
。
Firmware
binwalk -e F
或
unblob -e X F
。然后将每个提取出的 binary 按本表进行路由。
Website使用
curl -sL URL
获取 HTML 和 script src,然后获取 JS 并切片。要观察 runtime behavior,请使用带 network log 的 headless browser(Playwright
page.on('request')
)或 Chrome
--remote-debugging-port
。
Running app / runtimemacOS:
lldb -p PID
、
dtrace
/
fs_usage
/
log stream --predicate 'process=="X"'
。Electron main:
--inspect
(仅当 RunAsNode/inspect fuses 允许时)。Android:
adb logcat
、
frida -U -f pkg -l hook.js
。

Speed patterns

速度模式

  • Strings and imports first. On a small binary,
    nm -u
    +
    strings
    + the export list usually answer "what does it do" with no disassembly.
  • To find who uses a string: get its address from
    otool -tV
    "literal pool for" comments, or
    rg -n 'literal pool for: "X'
    . Then find the enclosing function label above that line.
  • A C++ name in a
    bl
    target already tells you the protocol (for example
    getpeereid
    ,
    write
    ,
    std::hash
    ). Read the call sequence, not every instruction.
  • For JS, go from the native/JS boundary inward: find the native call, then its caller class, then the IPC registration, then the renderer/preload.
  • Comparing versions: diff the inventories (
    nm
    , the module list,
    go version -m
    ) before you diff any code.
  • Rebuilding: write the minimal reimplementation, then test it against the original (same inputs, same outputs).
  • 先查看 strings 和 imports。对于小型 binary,
    nm -u
    +
    strings
    + export list 通常无需 disassembly 就能回答“它做什么”。
  • 查找 string 的使用者:先从
    otool -tV
    的“literal pool for”comment 中获取 address,或运行
    rg -n 'literal pool for: "X'
    。然后找到该行上方的 enclosing function label。
  • bl
    target 中的 C++ name 已经能说明 protocol(例如
    getpeereid
    、
    write
    、
    std::hash
    )。读取 call sequence,而不是每条 instruction。
  • 对 JS,从 native/JS boundary 向内追踪:先找到 native call,然后是 caller class,再找到 IPC registration,最后是 renderer/preload。
  • 比较 version:先 diff inventory(
    nm
    、module list、
    go version -m
    ),再 diff code。
  • 重建:编写最小 reimplementation,然后使用相同输入和输出与 original 进行对比测试。

Install (only on request)

安装(仅在用户要求时)

brew install jadx apktool radare2 binwalk wabt wireshark ghidra
·
npm i -g @electron/asar
·
dotnet tool install -g ilspycmd
·
cargo install cargo-audit
.
brew install jadx apktool radare2 binwalk wabt wireshark ghidra
·
npm i -g @electron/asar
·
dotnet tool install -g ilspycmd
·
cargo install cargo-audit
。