@ecopoesis/homebridge-dmx 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +176 -0
- package/README.md +146 -0
- package/config.schema.json +143 -0
- package/dist/cli/stick.d.ts +3 -0
- package/dist/cli/stick.d.ts.map +1 -0
- package/dist/cli/stick.js +176 -0
- package/dist/cli/stick.js.map +1 -0
- package/dist/color/cct.d.ts +3 -0
- package/dist/color/cct.d.ts.map +1 -0
- package/dist/color/cct.js +33 -0
- package/dist/color/cct.js.map +1 -0
- package/dist/color/dimmer.d.ts +3 -0
- package/dist/color/dimmer.d.ts.map +1 -0
- package/dist/color/dimmer.js +16 -0
- package/dist/color/dimmer.js.map +1 -0
- package/dist/color/hsvcct.d.ts +3 -0
- package/dist/color/hsvcct.d.ts.map +1 -0
- package/dist/color/hsvcct.js +52 -0
- package/dist/color/hsvcct.js.map +1 -0
- package/dist/color/index.d.ts +7 -0
- package/dist/color/index.d.ts.map +1 -0
- package/dist/color/index.js +28 -0
- package/dist/color/index.js.map +1 -0
- package/dist/color/parsers.d.ts +20 -0
- package/dist/color/parsers.d.ts.map +1 -0
- package/dist/color/parsers.js +232 -0
- package/dist/color/parsers.js.map +1 -0
- package/dist/color/rgb.d.ts +3 -0
- package/dist/color/rgb.d.ts.map +1 -0
- package/dist/color/rgb.js +32 -0
- package/dist/color/rgb.js.map +1 -0
- package/dist/color/rgbaw.d.ts +3 -0
- package/dist/color/rgbaw.d.ts.map +1 -0
- package/dist/color/rgbaw.js +48 -0
- package/dist/color/rgbaw.js.map +1 -0
- package/dist/color/rgbw.d.ts +3 -0
- package/dist/color/rgbw.d.ts.map +1 -0
- package/dist/color/rgbw.js +37 -0
- package/dist/color/rgbw.js.map +1 -0
- package/dist/color/rgbww.d.ts +3 -0
- package/dist/color/rgbww.d.ts.map +1 -0
- package/dist/color/rgbww.js +54 -0
- package/dist/color/rgbww.js.map +1 -0
- package/dist/color/types.d.ts +64 -0
- package/dist/color/types.d.ts.map +1 -0
- package/dist/color/types.js +91 -0
- package/dist/color/types.js.map +1 -0
- package/dist/config.d.ts +60 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +191 -0
- package/dist/config.js.map +1 -0
- package/dist/controller.d.ts +32 -0
- package/dist/controller.d.ts.map +1 -0
- package/dist/controller.js +128 -0
- package/dist/controller.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/platform.d.ts +17 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +96 -0
- package/dist/platform.js.map +1 -0
- package/dist/platformAccessory.d.ts +18 -0
- package/dist/platformAccessory.d.ts.map +1 -0
- package/dist/platformAccessory.js +81 -0
- package/dist/platformAccessory.js.map +1 -0
- package/dist/settings.d.ts +14 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +15 -0
- package/dist/settings.js.map +1 -0
- package/dist/stick/frame.d.ts +16 -0
- package/dist/stick/frame.d.ts.map +1 -0
- package/dist/stick/frame.js +51 -0
- package/dist/stick/frame.js.map +1 -0
- package/dist/stick/kdf.d.ts +23 -0
- package/dist/stick/kdf.d.ts.map +1 -0
- package/dist/stick/kdf.js +90 -0
- package/dist/stick/kdf.js.map +1 -0
- package/dist/stick/protocol.d.ts +28 -0
- package/dist/stick/protocol.d.ts.map +1 -0
- package/dist/stick/protocol.js +78 -0
- package/dist/stick/protocol.js.map +1 -0
- package/dist/stick/session.d.ts +50 -0
- package/dist/stick/session.d.ts.map +1 -0
- package/dist/stick/session.js +301 -0
- package/dist/stick/session.js.map +1 -0
- package/examples/dmx.yaml +65 -0
- package/package.json +51 -0
- package/src/cli/stick.ts +198 -0
- package/src/color/cct.ts +40 -0
- package/src/color/dimmer.ts +24 -0
- package/src/color/hsvcct.ts +59 -0
- package/src/color/index.ts +34 -0
- package/src/color/parsers.ts +232 -0
- package/src/color/rgb.ts +33 -0
- package/src/color/rgbaw.ts +46 -0
- package/src/color/rgbw.ts +36 -0
- package/src/color/rgbww.ts +56 -0
- package/src/color/types.ts +150 -0
- package/src/config.ts +248 -0
- package/src/controller.ts +136 -0
- package/src/index.ts +9 -0
- package/src/platform.ts +118 -0
- package/src/platformAccessory.ts +102 -0
- package/src/settings.ts +18 -0
- package/src/stick/frame.ts +70 -0
- package/src/stick/kdf.ts +99 -0
- package/src/stick/protocol.ts +90 -0
- package/src/stick/session.ts +348 -0
- package/stick-de3.json +6 -0
- package/tools/aes-hook.c +1404 -0
- package/tools/analyze-pcap.mjs +143 -0
- package/tools/capture-aes-key.lldb +25 -0
- package/tools/capture-and-verify.sh +46 -0
- package/tools/capture-handshake.sh +59 -0
- package/tools/capture-send.sh +39 -0
- package/tools/crack-kdf.mjs +121 -0
- package/tools/crack-key.mjs +170 -0
- package/tools/decode-frame.mjs +110 -0
- package/tools/decrypt-011c.mjs +123 -0
- package/tools/decrypt-dmx.mjs +130 -0
- package/tools/decrypt-ssl2.ts +154 -0
- package/tools/derive-dmx-key.mjs +189 -0
- package/tools/diff-011c.mjs +127 -0
- package/tools/dump-pcap-summary.mjs +54 -0
- package/tools/dump-tcp-stream.mjs +123 -0
- package/tools/extract-011c.mjs +117 -0
- package/tools/extract-frame.mjs +78 -0
- package/tools/find-011c-key.mjs +140 -0
- package/tools/find-iv.mjs +153 -0
- package/tools/ghidra/README.md +136 -0
- package/tools/ghidra/__pycache__/dump-011c-callers.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-011c-handler.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-aes-candidates.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-anchors.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-auth2.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-authenticate.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-block-cipher.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-cipher.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-ctor-chain.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-curve.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-encompassing.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-handshake.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-kdf-chain.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-kdf-context.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-profile.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-real-encrypt.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-secret.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-setsoftware.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-stick3-vtable.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-swxref.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/dump-vt-key-getter.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/find-cipher.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/find-ctor.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/find-md5.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/find-secret-users.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/find-stick3-crypto.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/find-stick3-device.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/find-stick3a-ctor.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/find-stick3anet-ctor.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/find-vtables.cpython-313.pyc +0 -0
- package/tools/ghidra/__pycache__/string-walk.cpython-313.pyc +0 -0
- package/tools/ghidra/dump-011c-callers.py +140 -0
- package/tools/ghidra/dump-011c-handler.py +158 -0
- package/tools/ghidra/dump-aes-candidates.py +99 -0
- package/tools/ghidra/dump-anchors.py +192 -0
- package/tools/ghidra/dump-auth2.py +50 -0
- package/tools/ghidra/dump-authenticate.py +71 -0
- package/tools/ghidra/dump-block-cipher.py +88 -0
- package/tools/ghidra/dump-cipher.py +106 -0
- package/tools/ghidra/dump-ctor-chain.py +107 -0
- package/tools/ghidra/dump-curve.py +112 -0
- package/tools/ghidra/dump-encompassing.py +146 -0
- package/tools/ghidra/dump-handshake.py +76 -0
- package/tools/ghidra/dump-kdf-chain.py +105 -0
- package/tools/ghidra/dump-kdf-context.py +72 -0
- package/tools/ghidra/dump-profile.py +59 -0
- package/tools/ghidra/dump-real-encrypt.py +90 -0
- package/tools/ghidra/dump-secret.py +84 -0
- package/tools/ghidra/dump-setsoftware.py +63 -0
- package/tools/ghidra/dump-stick3-vtable.py +182 -0
- package/tools/ghidra/dump-swxref.py +27 -0
- package/tools/ghidra/dump-vt-key-getter.py +170 -0
- package/tools/ghidra/find-cipher.py +210 -0
- package/tools/ghidra/find-ctor.py +109 -0
- package/tools/ghidra/find-md5.py +97 -0
- package/tools/ghidra/find-secret-users.py +98 -0
- package/tools/ghidra/find-stick3-crypto.py +190 -0
- package/tools/ghidra/find-stick3-device.py +111 -0
- package/tools/ghidra/find-stick3a-ctor.py +87 -0
- package/tools/ghidra/find-stick3anet-ctor.py +76 -0
- package/tools/ghidra/find-vtables.py +165 -0
- package/tools/ghidra/out-011c-callers-run.log +51 -0
- package/tools/ghidra/out-011c-run.log +50 -0
- package/tools/ghidra/out-vt-key-run.log +55 -0
- package/tools/ghidra/string-walk.py +180 -0
- package/tools/hmac-key.sh +51 -0
- package/tools/hwm-entitlements.plist +18 -0
- package/tools/inspect-plaintext.mjs +87 -0
- package/tools/lldb-capture-aes.py +137 -0
- package/tools/lldb_hmac_key.py +93 -0
- package/tools/lldb_pathA_scan.py +341 -0
- package/tools/lldb_pathC_keytrace.py +300 -0
- package/tools/lldb_pathD_kdf_watch.py +281 -0
- package/tools/pathA-launch-hwm.sh +86 -0
- package/tools/pathA-scan.sh +52 -0
- package/tools/pathC-keytrace.sh +49 -0
- package/tools/pathC-run.sh +71 -0
- package/tools/pathD-kdf-watch.sh +51 -0
- package/tools/quick-trigger.mjs +86 -0
- package/tools/run-hooked-hwmanager.sh +51 -0
- package/tools/scan-aes-schedule.mjs +135 -0
- package/tools/send_dmx.mjs +480 -0
- package/tools/stick-status.mjs +57 -0
- package/tools/test-aes-detector.mjs +100 -0
- package/tools/test-tcp-write.ts +274 -0
- package/tools/test-udp-dmx.ts +324 -0
- package/tools/test-x25519.mjs +87 -0
- package/tools/try-decrypt.ts +144 -0
- package/tools/try-hardcoded-key.mjs +86 -0
- package/tools/try-hardcoded-key.ts +103 -0
- package/tools/try-key.mjs +103 -0
- package/tools/verify-011c-key.mjs +121 -0
- package/tools/verify-kdf.mjs +142 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Decompile HWM's XHL_DasNetDevice::authenticate() and the LSAG_ALL handshake
|
|
2
|
+
# builder (LSAG_ALL is inline in code at 0x1001f233b). This is the 0x47/0x48
|
|
3
|
+
# TCP-auth handshake our send_dmx is failing (0x48 reply status 100 vs 0).
|
|
4
|
+
# @category Stick
|
|
5
|
+
|
|
6
|
+
import os, re
|
|
7
|
+
from ghidra.app.decompiler import DecompInterface
|
|
8
|
+
from ghidra.util.task import ConsoleTaskMonitor
|
|
9
|
+
|
|
10
|
+
OUT = os.path.join(os.path.dirname(getSourceFile().getAbsolutePath()), 'out-auth')
|
|
11
|
+
try: os.makedirs(OUT)
|
|
12
|
+
except OSError: pass
|
|
13
|
+
|
|
14
|
+
prog = currentProgram
|
|
15
|
+
fm = prog.getFunctionManager()
|
|
16
|
+
af = prog.getAddressFactory()
|
|
17
|
+
rm = prog.getReferenceManager()
|
|
18
|
+
monitor = ConsoleTaskMonitor()
|
|
19
|
+
decomp = DecompInterface(); decomp.openProgram(prog)
|
|
20
|
+
def addr(a): return af.getDefaultAddressSpace().getAddress(a)
|
|
21
|
+
|
|
22
|
+
def decompile(f):
|
|
23
|
+
r = decomp.decompileFunction(f, 240, monitor)
|
|
24
|
+
return r.getDecompiledFunction().getC() if r.decompileCompleted() else '(decompile failed)\n'
|
|
25
|
+
|
|
26
|
+
seen = set()
|
|
27
|
+
summary = open(os.path.join(OUT, 'SUMMARY.txt'), 'w')
|
|
28
|
+
|
|
29
|
+
def dump(f, tag, with_callees=False):
|
|
30
|
+
if f is None: return
|
|
31
|
+
ep = str(f.getEntryPoint())
|
|
32
|
+
if ep in seen: return
|
|
33
|
+
seen.add(ep)
|
|
34
|
+
safe = re.sub(r'[^A-Za-z0-9._-]', '_', f.getName(True))[:70]
|
|
35
|
+
callees = sorted(set(f.getCalledFunctions(monitor)),
|
|
36
|
+
key=lambda x: str(x.getEntryPoint()))
|
|
37
|
+
with open(os.path.join(OUT, '%s_%s_%s.txt' % (tag, ep, safe)), 'w') as o:
|
|
38
|
+
o.write('==== %s @ %s (%d bytes) ====\n' %
|
|
39
|
+
(f.getName(True), ep, f.getBody().getNumAddresses()))
|
|
40
|
+
o.write('callees:\n')
|
|
41
|
+
for c in callees:
|
|
42
|
+
o.write(' %s @ %s\n' % (c.getName(True), c.getEntryPoint()))
|
|
43
|
+
o.write('\ncallers:\n')
|
|
44
|
+
for c in sorted(set(f.getCallingFunctions(monitor)), key=lambda x: str(x.getEntryPoint())):
|
|
45
|
+
o.write(' %s @ %s\n' % (c.getName(True), c.getEntryPoint()))
|
|
46
|
+
o.write('\n' + decompile(f))
|
|
47
|
+
summary.write(' %s : %s @ %s (%d callees)\n' % (tag, f.getName(True), ep, len(callees)))
|
|
48
|
+
if with_callees:
|
|
49
|
+
for c in callees:
|
|
50
|
+
nm = c.getName(True)
|
|
51
|
+
if nm.startswith('_') or 'std::' in nm or 'operator' in nm: continue
|
|
52
|
+
dump(c, tag + '_callee', False)
|
|
53
|
+
|
|
54
|
+
# 1. the LSAG_ALL builder — function containing the inline "LSAG_ALL" bytes
|
|
55
|
+
f = fm.getFunctionContaining(addr(0x1001f233b))
|
|
56
|
+
summary.write('=== LSAG_ALL inline @ 0x1001f233b ===\n')
|
|
57
|
+
dump(f, '1_lsag_builder', with_callees=True)
|
|
58
|
+
|
|
59
|
+
# 2. XHL_DasNetDevice::authenticate() — xref the assert string at 0x1007ffeef
|
|
60
|
+
summary.write('\n=== authenticate() (string xref 0x1007ffeef) ===\n')
|
|
61
|
+
for r in rm.getReferencesTo(addr(0x1007ffeef)):
|
|
62
|
+
dump(fm.getFunctionContaining(r.getFromAddress()), '2_authenticate', with_callees=True)
|
|
63
|
+
|
|
64
|
+
# 3. "Configuring Capsens Security" string xref @ 0x1007b84b4
|
|
65
|
+
summary.write('\n=== Capsens Security (0x1007b84b4) ===\n')
|
|
66
|
+
for r in rm.getReferencesTo(addr(0x1007b84b4)):
|
|
67
|
+
dump(fm.getFunctionContaining(r.getFromAddress()), '3_capsens', with_callees=True)
|
|
68
|
+
|
|
69
|
+
summary.write('\n=== total: %d ===\n' % len(seen))
|
|
70
|
+
summary.close()
|
|
71
|
+
print('dump-authenticate: %d functions -> %s' % (len(seen), OUT))
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Dump the actual block cipher (AES?) under CBC and CFB modes.
|
|
2
|
+
|
|
3
|
+
# @category Stick
|
|
4
|
+
|
|
5
|
+
import os, re
|
|
6
|
+
from ghidra.app.decompiler import DecompInterface
|
|
7
|
+
from ghidra.util.task import ConsoleTaskMonitor
|
|
8
|
+
|
|
9
|
+
OUT_DIR = os.path.join(os.path.dirname(getSourceFile().getAbsolutePath()), 'out16')
|
|
10
|
+
try: os.makedirs(OUT_DIR)
|
|
11
|
+
except OSError: pass
|
|
12
|
+
|
|
13
|
+
prog = currentProgram
|
|
14
|
+
fm = prog.getFunctionManager()
|
|
15
|
+
af = prog.getAddressFactory()
|
|
16
|
+
monitor = ConsoleTaskMonitor()
|
|
17
|
+
decomp = DecompInterface()
|
|
18
|
+
decomp.openProgram(prog)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def addr(a): return af.getDefaultAddressSpace().getAddress(a)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def dump(func, out):
|
|
25
|
+
out.write('==== %s @ %s ====\n' % (func.getName(True), func.getEntryPoint()))
|
|
26
|
+
out.write('size: %d body bytes\n' % func.getBody().getNumAddresses())
|
|
27
|
+
callees = sorted(set('%s @ %s' % (f.getName(True), f.getEntryPoint())
|
|
28
|
+
for f in func.getCalledFunctions(monitor)))
|
|
29
|
+
out.write('\n-- callees (%d) --\n' % len(callees))
|
|
30
|
+
for c in callees: out.write(' ' + c + '\n')
|
|
31
|
+
callers = sorted(set('%s @ %s' % (f.getName(True), f.getEntryPoint())
|
|
32
|
+
for f in func.getCallingFunctions(monitor)))
|
|
33
|
+
out.write('\n-- callers (%d) --\n' % len(callers))
|
|
34
|
+
for c in callers: out.write(' ' + c + '\n')
|
|
35
|
+
out.write('\n-- decompilation --\n')
|
|
36
|
+
res = decomp.decompileFunction(func, 360, monitor)
|
|
37
|
+
if res.decompileCompleted():
|
|
38
|
+
out.write(res.getDecompiledFunction().getC())
|
|
39
|
+
else:
|
|
40
|
+
out.write('(decompilation failed: %s)\n' % res.getErrorMessage())
|
|
41
|
+
out.write('\n')
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
ROOTS = {
|
|
45
|
+
'CFB_block_FUN_1003f4690': 0x1003f4690,
|
|
46
|
+
'CBC_block_FUN_1003f5980': 0x1003f5980,
|
|
47
|
+
'CFB_helper_FUN_1003f4660': 0x1003f4660,
|
|
48
|
+
'preencrypt_FUN_100107a30': 0x100107a30,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
seen = set()
|
|
52
|
+
summary = open(os.path.join(OUT_DIR, 'SUMMARY.txt'), 'w')
|
|
53
|
+
|
|
54
|
+
for label, fp in ROOTS.items():
|
|
55
|
+
f = fm.getFunctionAt(addr(fp)) or fm.getFunctionContaining(addr(fp))
|
|
56
|
+
if f is None:
|
|
57
|
+
summary.write(' %s 0x%x (no fn)\n' % (label, fp))
|
|
58
|
+
continue
|
|
59
|
+
summary.write(' %-30s = %s @ %s size=%d callees=%d\n' %
|
|
60
|
+
(label, f.getName(True), f.getEntryPoint(),
|
|
61
|
+
f.getBody().getNumAddresses(),
|
|
62
|
+
len(set(f.getCalledFunctions(monitor)))))
|
|
63
|
+
if f.getEntryPoint() in seen: continue
|
|
64
|
+
seen.add(f.getEntryPoint())
|
|
65
|
+
safe = re.sub(r'[^A-Za-z0-9._-]', '_', f.getName(True))[:120]
|
|
66
|
+
with open(os.path.join(OUT_DIR, '%s_%s_%s.txt' %
|
|
67
|
+
(label, f.getEntryPoint(), safe)), 'w') as o:
|
|
68
|
+
dump(f, o)
|
|
69
|
+
|
|
70
|
+
# Walk into each block cipher to depth 1
|
|
71
|
+
for fp in (0x1003f4690, 0x1003f5980):
|
|
72
|
+
f = fm.getFunctionAt(addr(fp))
|
|
73
|
+
if f is None: continue
|
|
74
|
+
summary.write('\n=== depth-1 callees of 0x%x ===\n' % fp)
|
|
75
|
+
for c in sorted(f.getCalledFunctions(monitor), key=lambda f: f.getEntryPoint()):
|
|
76
|
+
summary.write(' %s @ %s (size %d)\n' %
|
|
77
|
+
(c.getName(True), c.getEntryPoint(),
|
|
78
|
+
c.getBody().getNumAddresses()))
|
|
79
|
+
if c.getEntryPoint() in seen: continue
|
|
80
|
+
seen.add(c.getEntryPoint())
|
|
81
|
+
safe = re.sub(r'[^A-Za-z0-9._-]', '_', c.getName(True))[:120]
|
|
82
|
+
with open(os.path.join(OUT_DIR, 'depth1_0x%x_%s_%s.txt' %
|
|
83
|
+
(fp, c.getEntryPoint(), safe)), 'w') as o:
|
|
84
|
+
dump(c, o)
|
|
85
|
+
|
|
86
|
+
summary.write('\n=== total: %d ===\n' % len(seen))
|
|
87
|
+
summary.close()
|
|
88
|
+
print('dump-block-cipher: %d functions dumped to %s' % (len(seen), OUT_DIR))
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Dump the actual cipher function chain.
|
|
2
|
+
#
|
|
3
|
+
# From FUN_1001a9d50 (the "send DMX" virtual method):
|
|
4
|
+
# FUN_1001eeac0 = builds 576-byte AES frame (the ENCRYPT function)
|
|
5
|
+
# FUN_1001b0340 = XHL_UdpSocket::send (verify it routes to sendto)
|
|
6
|
+
# FUN_100333f40 = sequence-number bookkeeping (param_1 -> &seqnum_out)
|
|
7
|
+
# FUN_100333ae0 = pre-encrypt setup (maybe IV refresh)
|
|
8
|
+
#
|
|
9
|
+
# Also dump callers of FUN_1001eeac0 (the encrypt) and all its callees:
|
|
10
|
+
# AES tables and round logic live in there.
|
|
11
|
+
|
|
12
|
+
# @category Stick
|
|
13
|
+
|
|
14
|
+
import os, re
|
|
15
|
+
from ghidra.app.decompiler import DecompInterface
|
|
16
|
+
from ghidra.util.task import ConsoleTaskMonitor
|
|
17
|
+
|
|
18
|
+
OUT_DIR = os.path.join(os.path.dirname(getSourceFile().getAbsolutePath()), 'out7')
|
|
19
|
+
try: os.makedirs(OUT_DIR)
|
|
20
|
+
except OSError: pass
|
|
21
|
+
|
|
22
|
+
prog = currentProgram
|
|
23
|
+
fm = prog.getFunctionManager()
|
|
24
|
+
af = prog.getAddressFactory()
|
|
25
|
+
monitor = ConsoleTaskMonitor()
|
|
26
|
+
decomp = DecompInterface()
|
|
27
|
+
decomp.openProgram(prog)
|
|
28
|
+
|
|
29
|
+
def addr(a): return af.getDefaultAddressSpace().getAddress(a)
|
|
30
|
+
|
|
31
|
+
def dump(func, out):
|
|
32
|
+
out.write('==== %s @ %s ====\n' % (func.getName(True), func.getEntryPoint()))
|
|
33
|
+
out.write('signature: %s\n' % func.getPrototypeString(True, True))
|
|
34
|
+
out.write('size: %d body bytes\n' % func.getBody().getNumAddresses())
|
|
35
|
+
callees = sorted(set('%s @ %s' % (f.getName(True), f.getEntryPoint())
|
|
36
|
+
for f in func.getCalledFunctions(monitor)))
|
|
37
|
+
out.write('\n-- callees (%d) --\n' % len(callees))
|
|
38
|
+
for c in callees: out.write(' ' + c + '\n')
|
|
39
|
+
callers = sorted(set('%s @ %s' % (f.getName(True), f.getEntryPoint())
|
|
40
|
+
for f in func.getCallingFunctions(monitor)))
|
|
41
|
+
out.write('\n-- callers (%d) --\n' % len(callers))
|
|
42
|
+
for c in callers: out.write(' ' + c + '\n')
|
|
43
|
+
out.write('\n-- decompilation --\n')
|
|
44
|
+
res = decomp.decompileFunction(func, 240, monitor)
|
|
45
|
+
if res.decompileCompleted():
|
|
46
|
+
out.write(res.getDecompiledFunction().getC())
|
|
47
|
+
else:
|
|
48
|
+
out.write('(decompilation failed: %s)\n' % res.getErrorMessage())
|
|
49
|
+
out.write('\n')
|
|
50
|
+
|
|
51
|
+
# Functions to dump (label -> address)
|
|
52
|
+
ROOTS = {
|
|
53
|
+
'sendDmx_FUN_1001a9d50': 0x1001a9d50, # outer; for context
|
|
54
|
+
'encrypt_FUN_1001eeac0': 0x1001eeac0, # THE ENCRYPT FUNCTION
|
|
55
|
+
'udpsend_FUN_1001b0340': 0x1001b0340, # UDP send wrapper
|
|
56
|
+
'pre_FUN_100333ae0': 0x100333ae0,
|
|
57
|
+
'getseqno_FUN_100333f40': 0x100333f40,
|
|
58
|
+
'getip_FUN_1001b1780': 0x1001b1780, # gets IP from socket?
|
|
59
|
+
'unknown_FUN_1006a70a0': 0x1006a70a0, # ??? (mutex variant)
|
|
60
|
+
'unknown_FUN_1006a6f70': 0x1006a6f70, # ??? (mutex variant)
|
|
61
|
+
'unknown_FUN_10060b8e0': 0x10060b8e0, # mutex lock?
|
|
62
|
+
'unknown_FUN_10060b980': 0x10060b980, # mutex unlock?
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# Also: recursively descend into encrypt to depth 1 — its callees are the
|
|
66
|
+
# round logic / sbox lookups.
|
|
67
|
+
ENCRYPT_ADDR = 0x1001eeac0
|
|
68
|
+
|
|
69
|
+
seen = set()
|
|
70
|
+
summary = open(os.path.join(OUT_DIR, 'SUMMARY.txt'), 'w')
|
|
71
|
+
summary.write('Cipher dump — chain rooted at FUN_1001a9d50\n\n')
|
|
72
|
+
|
|
73
|
+
for label, fp in ROOTS.items():
|
|
74
|
+
f = fm.getFunctionAt(addr(fp)) or fm.getFunctionContaining(addr(fp))
|
|
75
|
+
if f is None:
|
|
76
|
+
summary.write(' %s 0x%x (no function)\n' % (label, fp))
|
|
77
|
+
continue
|
|
78
|
+
summary.write(' %s = %s @ %s (size %d, callees=%d)\n' %
|
|
79
|
+
(label, f.getName(True), f.getEntryPoint(),
|
|
80
|
+
f.getBody().getNumAddresses(),
|
|
81
|
+
len(set(f.getCalledFunctions(monitor)))))
|
|
82
|
+
if f.getEntryPoint() in seen: continue
|
|
83
|
+
seen.add(f.getEntryPoint())
|
|
84
|
+
safe = re.sub(r'[^A-Za-z0-9._-]', '_', f.getName(True))[:120]
|
|
85
|
+
with open(os.path.join(OUT_DIR, '%s_%s_%s.txt' %
|
|
86
|
+
(label, f.getEntryPoint(), safe)), 'w') as o:
|
|
87
|
+
dump(f, o)
|
|
88
|
+
|
|
89
|
+
# Descend into encrypt fn's callees (depth 1)
|
|
90
|
+
enc = fm.getFunctionAt(addr(ENCRYPT_ADDR))
|
|
91
|
+
if enc:
|
|
92
|
+
summary.write('\n=== encrypt fn callees (depth 1) ===\n')
|
|
93
|
+
for c in sorted(enc.getCalledFunctions(monitor), key=lambda f: f.getEntryPoint()):
|
|
94
|
+
summary.write(' callee: %s @ %s (size %d)\n' %
|
|
95
|
+
(c.getName(True), c.getEntryPoint(),
|
|
96
|
+
c.getBody().getNumAddresses()))
|
|
97
|
+
if c.getEntryPoint() in seen: continue
|
|
98
|
+
seen.add(c.getEntryPoint())
|
|
99
|
+
safe = re.sub(r'[^A-Za-z0-9._-]', '_', c.getName(True))[:120]
|
|
100
|
+
with open(os.path.join(OUT_DIR, 'enccallee_%s_%s.txt' %
|
|
101
|
+
(c.getEntryPoint(), safe)), 'w') as o:
|
|
102
|
+
dump(c, o)
|
|
103
|
+
|
|
104
|
+
summary.write('\n=== total dumped: %d ===\n' % len(seen))
|
|
105
|
+
summary.close()
|
|
106
|
+
print('dump-cipher: %d functions dumped to %s' % (len(seen), OUT_DIR))
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Walk the constructor chain to find where the 8-word cipher state is
|
|
2
|
+
# initialized. Top-level: FUN_100178f10 (Stick3CryptDmxUniverse::ctor)
|
|
3
|
+
# delegates the heavy lifting to FUN_1001a9a00 (base-class ctor).
|
|
4
|
+
#
|
|
5
|
+
# Targets:
|
|
6
|
+
# FUN_1001a9a00 base constructor (sets up sub-objects incl. cipher)
|
|
7
|
+
# FUN_100178bb0 caller of Stick3 ctor (one level UP — who creates these?)
|
|
8
|
+
# FUN_100105770 AesOStream constructor
|
|
9
|
+
# the s_XHL_Stick3ANet... string is at 0x1007ff054 — dump bytes too
|
|
10
|
+
|
|
11
|
+
# @category Stick
|
|
12
|
+
|
|
13
|
+
import os, re
|
|
14
|
+
from ghidra.app.decompiler import DecompInterface
|
|
15
|
+
from ghidra.util.task import ConsoleTaskMonitor
|
|
16
|
+
|
|
17
|
+
OUT_DIR = os.path.join(os.path.dirname(getSourceFile().getAbsolutePath()), 'out10')
|
|
18
|
+
try: os.makedirs(OUT_DIR)
|
|
19
|
+
except OSError: pass
|
|
20
|
+
|
|
21
|
+
prog = currentProgram
|
|
22
|
+
fm = prog.getFunctionManager()
|
|
23
|
+
af = prog.getAddressFactory()
|
|
24
|
+
mem = prog.getMemory()
|
|
25
|
+
monitor = ConsoleTaskMonitor()
|
|
26
|
+
decomp = DecompInterface()
|
|
27
|
+
decomp.openProgram(prog)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def addr(a): return af.getDefaultAddressSpace().getAddress(a)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def dump(func, out):
|
|
34
|
+
out.write('==== %s @ %s ====\n' % (func.getName(True), func.getEntryPoint()))
|
|
35
|
+
out.write('size: %d body bytes\n' % func.getBody().getNumAddresses())
|
|
36
|
+
callees = sorted(set('%s @ %s' % (f.getName(True), f.getEntryPoint())
|
|
37
|
+
for f in func.getCalledFunctions(monitor)))
|
|
38
|
+
out.write('\n-- callees (%d) --\n' % len(callees))
|
|
39
|
+
for c in callees: out.write(' ' + c + '\n')
|
|
40
|
+
callers = sorted(set('%s @ %s' % (f.getName(True), f.getEntryPoint())
|
|
41
|
+
for f in func.getCallingFunctions(monitor)))
|
|
42
|
+
out.write('\n-- callers (%d) --\n' % len(callers))
|
|
43
|
+
for c in callers: out.write(' ' + c + '\n')
|
|
44
|
+
out.write('\n-- decompilation --\n')
|
|
45
|
+
res = decomp.decompileFunction(func, 240, monitor)
|
|
46
|
+
if res.decompileCompleted():
|
|
47
|
+
out.write(res.getDecompiledFunction().getC())
|
|
48
|
+
else:
|
|
49
|
+
out.write('(decompilation failed: %s)\n' % res.getErrorMessage())
|
|
50
|
+
out.write('\n')
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
ROOTS = {
|
|
54
|
+
'base_ctor_FUN_1001a9a00': 0x1001a9a00,
|
|
55
|
+
'caller_of_Stick3_ctor_100178bb0': 0x100178bb0,
|
|
56
|
+
'AesOStream_ctor_100105770': 0x100105770,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
seen = set()
|
|
60
|
+
summary = open(os.path.join(OUT_DIR, 'SUMMARY.txt'), 'w')
|
|
61
|
+
|
|
62
|
+
# Read and print the source-tag string at 0x1007ff054
|
|
63
|
+
summary.write('=== Source-tag string at 0x1007ff054 ===\n')
|
|
64
|
+
try:
|
|
65
|
+
sb = []
|
|
66
|
+
a = addr(0x1007ff054)
|
|
67
|
+
for i in range(120):
|
|
68
|
+
b = mem.getByte(a.add(i)) & 0xff
|
|
69
|
+
if b == 0: break
|
|
70
|
+
sb.append(chr(b))
|
|
71
|
+
summary.write(' ' + repr(''.join(sb)) + '\n\n')
|
|
72
|
+
except Exception as e:
|
|
73
|
+
summary.write(' (read failed: %s)\n' % e)
|
|
74
|
+
|
|
75
|
+
for label, fp in ROOTS.items():
|
|
76
|
+
f = fm.getFunctionAt(addr(fp)) or fm.getFunctionContaining(addr(fp))
|
|
77
|
+
if f is None:
|
|
78
|
+
summary.write(' %s 0x%x (no fn)\n' % (label, fp))
|
|
79
|
+
continue
|
|
80
|
+
summary.write(' %s = %s @ %s size=%d\n' %
|
|
81
|
+
(label, f.getName(True), f.getEntryPoint(),
|
|
82
|
+
f.getBody().getNumAddresses()))
|
|
83
|
+
if f.getEntryPoint() in seen: continue
|
|
84
|
+
seen.add(f.getEntryPoint())
|
|
85
|
+
safe = re.sub(r'[^A-Za-z0-9._-]', '_', f.getName(True))[:120]
|
|
86
|
+
with open(os.path.join(OUT_DIR, '%s_%s_%s.txt' %
|
|
87
|
+
(label, f.getEntryPoint(), safe)), 'w') as o:
|
|
88
|
+
dump(f, o)
|
|
89
|
+
|
|
90
|
+
# Also descend one hop into base ctor
|
|
91
|
+
base_ctor = fm.getFunctionAt(addr(0x1001a9a00))
|
|
92
|
+
if base_ctor:
|
|
93
|
+
summary.write('\n=== base ctor depth-1 callees ===\n')
|
|
94
|
+
for c in sorted(base_ctor.getCalledFunctions(monitor), key=lambda f: f.getEntryPoint()):
|
|
95
|
+
summary.write(' callee: %s @ %s (size %d)\n' %
|
|
96
|
+
(c.getName(True), c.getEntryPoint(),
|
|
97
|
+
c.getBody().getNumAddresses()))
|
|
98
|
+
if c.getEntryPoint() in seen: continue
|
|
99
|
+
seen.add(c.getEntryPoint())
|
|
100
|
+
safe = re.sub(r'[^A-Za-z0-9._-]', '_', c.getName(True))[:120]
|
|
101
|
+
with open(os.path.join(OUT_DIR, 'basector_d1_%s_%s.txt' %
|
|
102
|
+
(c.getEntryPoint(), safe)), 'w') as o:
|
|
103
|
+
dump(c, o)
|
|
104
|
+
|
|
105
|
+
summary.write('\n=== total dumped: %d ===\n' % len(seen))
|
|
106
|
+
summary.close()
|
|
107
|
+
print('dump-ctor-chain: %d functions dumped to %s' % (len(seen), OUT_DIR))
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Dump the EC curve constants + remaining KDF primitives.
|
|
2
|
+
# KDF (FUN_100107650): key = X(priv*staticPt) XOR X(priv*stickPub)
|
|
3
|
+
# priv generated by FUN_1004032b0 (random mod n, n at DAT_100cf5c10)
|
|
4
|
+
# scalarmult = FUN_1003ff3c0, prime p at DAT_100cf5bf0, gen G at DAT_100cf5c30
|
|
5
|
+
# @category Stick
|
|
6
|
+
|
|
7
|
+
import os, re, struct
|
|
8
|
+
from ghidra.app.decompiler import DecompInterface
|
|
9
|
+
from ghidra.util.task import ConsoleTaskMonitor
|
|
10
|
+
|
|
11
|
+
OUT_DIR = os.path.join(os.path.dirname(getSourceFile().getAbsolutePath()), 'out-curve')
|
|
12
|
+
try: os.makedirs(OUT_DIR)
|
|
13
|
+
except OSError: pass
|
|
14
|
+
|
|
15
|
+
prog = currentProgram
|
|
16
|
+
fm = prog.getFunctionManager()
|
|
17
|
+
af = prog.getAddressFactory()
|
|
18
|
+
mem = prog.getMemory()
|
|
19
|
+
monitor = ConsoleTaskMonitor()
|
|
20
|
+
decomp = DecompInterface()
|
|
21
|
+
decomp.openProgram(prog)
|
|
22
|
+
def addr(a): return af.getDefaultAddressSpace().getAddress(a)
|
|
23
|
+
|
|
24
|
+
def rd(a, n):
|
|
25
|
+
buf = bytearray(n)
|
|
26
|
+
try:
|
|
27
|
+
mem.getBytes(addr(a), buf)
|
|
28
|
+
except Exception as e:
|
|
29
|
+
return None
|
|
30
|
+
return bytes(buf)
|
|
31
|
+
|
|
32
|
+
def hexdump(label, a, n, out):
|
|
33
|
+
b = rd(a, n)
|
|
34
|
+
out.write('\n== %s @ 0x%x (%d bytes) ==\n' % (label, a, n))
|
|
35
|
+
if b is None:
|
|
36
|
+
out.write(' (unreadable)\n'); return
|
|
37
|
+
for i in range(0, n, 16):
|
|
38
|
+
row = b[i:i+16]
|
|
39
|
+
hx = ' '.join('%02x' % x for x in row)
|
|
40
|
+
out.write(' +%03x %-47s\n' % (i, hx))
|
|
41
|
+
# also as little-endian 256-bit ints, 32B at a time
|
|
42
|
+
for i in range(0, n - 31, 32):
|
|
43
|
+
words = struct.unpack('<4Q', b[i:i+32])
|
|
44
|
+
be = (words[3] << 192) | (words[2] << 128) | (words[1] << 64) | words[0]
|
|
45
|
+
out.write(' [%03x] LE-as-256bit = 0x%064x\n' % (i, be))
|
|
46
|
+
|
|
47
|
+
def decompile(func):
|
|
48
|
+
res = decomp.decompileFunction(func, 240, monitor)
|
|
49
|
+
if res.decompileCompleted():
|
|
50
|
+
return res.getDecompiledFunction().getC()
|
|
51
|
+
return '(decompile failed: %s)\n' % res.getErrorMessage()
|
|
52
|
+
|
|
53
|
+
def dump_fn(fp, label, out):
|
|
54
|
+
f = fm.getFunctionAt(addr(fp)) or fm.getFunctionContaining(addr(fp))
|
|
55
|
+
if f is None:
|
|
56
|
+
out.write('\n==== %s 0x%x : NO FUNCTION ====\n' % (label, fp)); return
|
|
57
|
+
out.write('\n==== %s : %s @ %s (%d bytes) ====\n' %
|
|
58
|
+
(label, f.getName(True), f.getEntryPoint(),
|
|
59
|
+
f.getBody().getNumAddresses()))
|
|
60
|
+
callees = sorted(set('%s@%s' % (c.getName(True), c.getEntryPoint())
|
|
61
|
+
for c in f.getCalledFunctions(monitor)))
|
|
62
|
+
out.write('callees: %s\n' % ', '.join(callees))
|
|
63
|
+
out.write(decompile(f))
|
|
64
|
+
|
|
65
|
+
# ---- 1. constants ----
|
|
66
|
+
const = open(os.path.join(OUT_DIR, 'CONSTANTS.txt'), 'w')
|
|
67
|
+
# wide region covering prime, order, generator, a/b coefficients
|
|
68
|
+
hexdump('region_cf5b00', 0x100cf5b00, 0x300, const)
|
|
69
|
+
hexdump('DAT_100cf5bf0_prime?', 0x100cf5bf0, 0x20, const)
|
|
70
|
+
hexdump('DAT_100cf5c10_order_lo', 0x100cf5c10, 0x20, const)
|
|
71
|
+
hexdump('DAT_100cf5c30_generator',0x100cf5c30, 0x40, const)
|
|
72
|
+
hexdump('decoded_secret_cf8d11', 0x100cf8d11, 0x21, const)
|
|
73
|
+
hexdump('obfuscated_src_7b0490', 0x1007b0490, 0x21, const)
|
|
74
|
+
const.close()
|
|
75
|
+
|
|
76
|
+
# ---- 2. EC primitives + static-point builder ----
|
|
77
|
+
prim = open(os.path.join(OUT_DIR, 'PRIMITIVES.txt'), 'w')
|
|
78
|
+
for fp, label in [
|
|
79
|
+
(0x100402560, 'EC_op_402560'),
|
|
80
|
+
(0x100404fc0, 'EC_op_404fc0'),
|
|
81
|
+
(0x1003ffc40, 'EC_pt_add_3ffc40'),
|
|
82
|
+
(0x100400fd0, 'EC_pt_dbl_400fd0'),
|
|
83
|
+
(0x1004061d0, 'EC_reduce_4061d0'),
|
|
84
|
+
(0x100401c50, 'EC_op_401c50'),
|
|
85
|
+
(0x100402ab0, 'static_point_builder_402ab0'),
|
|
86
|
+
(0x1003f4530, 'cfb_key_install_3f4530'),
|
|
87
|
+
]:
|
|
88
|
+
dump_fn(fp, label, prim)
|
|
89
|
+
prim.close()
|
|
90
|
+
|
|
91
|
+
# ---- 3. cipher vtable slots (vptr value 0x10095F838) ----
|
|
92
|
+
vt = open(os.path.join(OUT_DIR, 'VTABLE.txt'), 'w')
|
|
93
|
+
VTBL = 0x10095F838
|
|
94
|
+
vt.write('cipher secondary vtable @ 0x%x\n' % VTBL)
|
|
95
|
+
slots = {}
|
|
96
|
+
for off in range(0, 0x60, 8):
|
|
97
|
+
b = rd(VTBL + off, 8)
|
|
98
|
+
if b is None:
|
|
99
|
+
vt.write(' +0x%02x : (unreadable)\n' % off); continue
|
|
100
|
+
ptr = struct.unpack('<Q', b)[0]
|
|
101
|
+
slots[off] = ptr
|
|
102
|
+
f = fm.getFunctionAt(addr(ptr)) if ptr else None
|
|
103
|
+
vt.write(' +0x%02x : 0x%x %s\n' % (off, ptr, f.getName(True) if f else ''))
|
|
104
|
+
for off, label in [(0x20, 'vt20_net_exchange'),
|
|
105
|
+
(0x28, 'vt28_state_getter'),
|
|
106
|
+
(0x38, 'vt38_static_getter')]:
|
|
107
|
+
ptr = slots.get(off)
|
|
108
|
+
if ptr:
|
|
109
|
+
dump_fn(ptr, label, vt)
|
|
110
|
+
vt.close()
|
|
111
|
+
|
|
112
|
+
print('dump-curve: done -> %s' % OUT_DIR)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Walk one more level up the constructor chain — find the device-level
|
|
2
|
+
# factory that calls FUN_100179050 (caller of the Stick3CryptDmxUniverse
|
|
3
|
+
# factory FUN_100178bb0) and dump constructors that touch offset +0x1618
|
|
4
|
+
# (where the cipher object lives).
|
|
5
|
+
|
|
6
|
+
# @category Stick
|
|
7
|
+
|
|
8
|
+
import os, re
|
|
9
|
+
from ghidra.app.decompiler import DecompInterface
|
|
10
|
+
from ghidra.util.task import ConsoleTaskMonitor
|
|
11
|
+
|
|
12
|
+
OUT_DIR = os.path.join(os.path.dirname(getSourceFile().getAbsolutePath()), 'out11')
|
|
13
|
+
try: os.makedirs(OUT_DIR)
|
|
14
|
+
except OSError: pass
|
|
15
|
+
|
|
16
|
+
prog = currentProgram
|
|
17
|
+
fm = prog.getFunctionManager()
|
|
18
|
+
af = prog.getAddressFactory()
|
|
19
|
+
mem = prog.getMemory()
|
|
20
|
+
ref_mgr = prog.getReferenceManager()
|
|
21
|
+
monitor = ConsoleTaskMonitor()
|
|
22
|
+
decomp = DecompInterface()
|
|
23
|
+
decomp.openProgram(prog)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def addr(a): return af.getDefaultAddressSpace().getAddress(a)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def dump(func, out):
|
|
30
|
+
out.write('==== %s @ %s ====\n' % (func.getName(True), func.getEntryPoint()))
|
|
31
|
+
out.write('size: %d body bytes\n' % func.getBody().getNumAddresses())
|
|
32
|
+
callees = sorted(set('%s @ %s' % (f.getName(True), f.getEntryPoint())
|
|
33
|
+
for f in func.getCalledFunctions(monitor)))
|
|
34
|
+
out.write('\n-- callees (%d) --\n' % len(callees))
|
|
35
|
+
for c in callees: out.write(' ' + c + '\n')
|
|
36
|
+
callers = sorted(set('%s @ %s' % (f.getName(True), f.getEntryPoint())
|
|
37
|
+
for f in func.getCallingFunctions(monitor)))
|
|
38
|
+
out.write('\n-- callers (%d) --\n' % len(callers))
|
|
39
|
+
for c in callers: out.write(' ' + c + '\n')
|
|
40
|
+
out.write('\n-- decompilation --\n')
|
|
41
|
+
res = decomp.decompileFunction(func, 240, monitor)
|
|
42
|
+
if res.decompileCompleted():
|
|
43
|
+
out.write(res.getDecompiledFunction().getC())
|
|
44
|
+
else:
|
|
45
|
+
out.write('(decompilation failed: %s)\n' % res.getErrorMessage())
|
|
46
|
+
out.write('\n')
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# Read C string at addr
|
|
50
|
+
def cstr(a, maxlen=200):
|
|
51
|
+
out = []
|
|
52
|
+
for i in range(maxlen):
|
|
53
|
+
try:
|
|
54
|
+
b = mem.getByte(a.add(i)) & 0xff
|
|
55
|
+
except Exception:
|
|
56
|
+
break
|
|
57
|
+
if b == 0: break
|
|
58
|
+
out.append(chr(b))
|
|
59
|
+
return ''.join(out)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# All 26 vtables containing FUN_100108200 at slot vt[3] (= site = vt_base+0x18)
|
|
63
|
+
# So: vt_base = site - 0x18 = vptr - 0x00 (vptr is at vt_base+8 conceptually,
|
|
64
|
+
# but the way we labelled in fixup output, vptr = vt_base+8... let me recompute.
|
|
65
|
+
#
|
|
66
|
+
# Convention: vtable struct layout:
|
|
67
|
+
# site(typeinfo_ptr) @ X ← typeinfo rebase row
|
|
68
|
+
# vt[0] @ X + 0x08
|
|
69
|
+
# vt[1] @ X + 0x10
|
|
70
|
+
# vt[2] @ X + 0x18
|
|
71
|
+
# vt[3] @ X + 0x20
|
|
72
|
+
#
|
|
73
|
+
# So a row "rebase FUN_100108200" at addr Y means Y stores the encrypt fn.
|
|
74
|
+
# If vt[3], then Y = X + 0x20, so X (typeinfo_ptr addr) = Y - 0x20.
|
|
75
|
+
# Slot 0 (vptr) is at X + 0x08 = Y - 0x18.
|
|
76
|
+
|
|
77
|
+
# This was wrong before; the relationship is:
|
|
78
|
+
# if Y = vtable_struct_base + 0x20 (slot 3), and vptr is at +0x08,
|
|
79
|
+
# then vptr = Y - 0x18
|
|
80
|
+
|
|
81
|
+
ENCRYPT_SLOT_ADDRS = [
|
|
82
|
+
0x100851F50, 0x100A1F168, 0x100A239D0, 0x100A23A70, 0x100B289E0,
|
|
83
|
+
0x100B2EDC8, 0x100B2EE68, 0x100B35D48, 0x100B3C0F8, 0x100B3C198,
|
|
84
|
+
0x100B95940, 0x100B959E0, 0x100B9D7A8, 0x100BA25A8, 0x100BA2648,
|
|
85
|
+
0x100BA9008, 0x100BB0E18, 0x100BB0EB8, 0x100BB7E00, 0x100BBCBC8,
|
|
86
|
+
0x100BBCC68, 0x100BC1920, 0x100BC7D48, 0x100BC7DE8, 0x100C14170,
|
|
87
|
+
0x100C14260,
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
summary = open(os.path.join(OUT_DIR, 'SUMMARY.txt'), 'w')
|
|
91
|
+
seen = set()
|
|
92
|
+
|
|
93
|
+
# For each, read the typeinfo_ptr (at slot_addr - 0x20), then the name string
|
|
94
|
+
# referenced from that typeinfo struct (at typeinfo_addr + 0x08).
|
|
95
|
+
# Since chained fixups aren't materialized, we have to find the typeinfo via
|
|
96
|
+
# Ghidra refs. Try: getReferencesFrom(typeinfo_ptr_field).
|
|
97
|
+
summary.write('=== 26 vtables sharing FUN_100108200 as vt[3] ===\n')
|
|
98
|
+
for slot3 in ENCRYPT_SLOT_ADDRS:
|
|
99
|
+
typeinfo_field = slot3 - 0x20
|
|
100
|
+
vptr = slot3 - 0x18
|
|
101
|
+
summary.write('\n slot[3] @ 0x%x vptr=0x%x typeinfo_ptr_field=0x%x\n' %
|
|
102
|
+
(slot3, vptr, typeinfo_field))
|
|
103
|
+
# Look at outgoing refs from the typeinfo_field — Ghidra applies refs
|
|
104
|
+
# from chained-fixup rebases for SOME of these.
|
|
105
|
+
refs = list(ref_mgr.getReferencesFrom(addr(typeinfo_field)))
|
|
106
|
+
for r in refs:
|
|
107
|
+
ta = r.getToAddress()
|
|
108
|
+
summary.write(' -> %s\n' % ta)
|
|
109
|
+
# Then refs FROM typeinfo addr's name-ptr field (+8) give the name string
|
|
110
|
+
name_ptr_field = ta.add(8)
|
|
111
|
+
for r2 in ref_mgr.getReferencesFrom(name_ptr_field):
|
|
112
|
+
nstr = cstr(r2.getToAddress(), 200)
|
|
113
|
+
summary.write(' name @ %s : %r\n' % (r2.getToAddress(), nstr))
|
|
114
|
+
# Code refs to vptr = the constructor that uses this vtable
|
|
115
|
+
code_callers = set()
|
|
116
|
+
for r in ref_mgr.getReferencesTo(addr(vptr)):
|
|
117
|
+
f = fm.getFunctionContaining(r.getFromAddress())
|
|
118
|
+
if f is not None:
|
|
119
|
+
code_callers.add(f.getEntryPoint())
|
|
120
|
+
summary.write(' code refs to vptr from %d fn(s):\n' % len(code_callers))
|
|
121
|
+
for ep in sorted(code_callers):
|
|
122
|
+
f = fm.getFunctionAt(ep)
|
|
123
|
+
summary.write(' %s @ %s (size %d)\n' %
|
|
124
|
+
(f.getName(True), f.getEntryPoint(),
|
|
125
|
+
f.getBody().getNumAddresses()))
|
|
126
|
+
if ep in seen: continue
|
|
127
|
+
seen.add(ep)
|
|
128
|
+
safe = re.sub(r'[^A-Za-z0-9._-]', '_', f.getName(True))[:120]
|
|
129
|
+
with open(os.path.join(OUT_DIR, 'ctor_vt0x%x_%s_%s.txt' %
|
|
130
|
+
(vptr, f.getEntryPoint(), safe)), 'w') as o:
|
|
131
|
+
dump(f, o)
|
|
132
|
+
|
|
133
|
+
# Also: dump FUN_100179050 (caller of Stick3 factory)
|
|
134
|
+
for fp in [0x100179050]:
|
|
135
|
+
f = fm.getFunctionAt(addr(fp))
|
|
136
|
+
if f is None: continue
|
|
137
|
+
if f.getEntryPoint() in seen: continue
|
|
138
|
+
seen.add(f.getEntryPoint())
|
|
139
|
+
safe = re.sub(r'[^A-Za-z0-9._-]', '_', f.getName(True))[:120]
|
|
140
|
+
with open(os.path.join(OUT_DIR, 'caller_%s_%s.txt' %
|
|
141
|
+
(f.getEntryPoint(), safe)), 'w') as o:
|
|
142
|
+
dump(f, o)
|
|
143
|
+
|
|
144
|
+
summary.write('\n=== total dumped: %d ===\n' % len(seen))
|
|
145
|
+
summary.close()
|
|
146
|
+
print('dump-encompassing: %d functions dumped to %s' % (len(seen), OUT_DIR))
|