@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,109 @@
|
|
|
1
|
+
# Locate the constructor(s) of XHL_Stick3CryptDmxUniverse and the
|
|
2
|
+
# DasEccStreamCryptography subobject it embeds.
|
|
3
|
+
#
|
|
4
|
+
# Method: in x86_64 PIC code, a constructor sets the object's vptr via
|
|
5
|
+
# lea rax, [rip + delta] ; rax = address of vt slot 0
|
|
6
|
+
# mov [rdi], rax ; obj->vptr = rax
|
|
7
|
+
# Ghidra creates a Reference of type DATA from the lea instruction's address
|
|
8
|
+
# to the vtable's slot-0 address.
|
|
9
|
+
#
|
|
10
|
+
# So xrefs to vptr values from CODE locations = constructor / VTT setup.
|
|
11
|
+
#
|
|
12
|
+
# Vptrs of interest (per our fixup mining):
|
|
13
|
+
# Stick3CryptDmxUniverse primary vptr = 0x100962BE8 (= vt site + 8)
|
|
14
|
+
# Stick3CryptDmxUniverse sub1 vptr = 0x100962CC8
|
|
15
|
+
# Stick3CryptDmxUniverse sub2 vptr = 0x100962CF0
|
|
16
|
+
# Stick3CryptDmxUniverse sub3 vptr = 0x100962D30
|
|
17
|
+
# Stick3CryptDmxUniverse sub4 vptr = 0x100962D68
|
|
18
|
+
# Stick3CryptDmxUniverse sub5 vptr = 0x100962DA8
|
|
19
|
+
# DasEccStreamCryptography vptr = 0x100851F38
|
|
20
|
+
# DasEccAesCryptography vptr = 0x100851D88
|
|
21
|
+
# AesOStream vptr = 0x100851B20
|
|
22
|
+
|
|
23
|
+
# @category Stick
|
|
24
|
+
|
|
25
|
+
import os, re
|
|
26
|
+
from ghidra.app.decompiler import DecompInterface
|
|
27
|
+
from ghidra.util.task import ConsoleTaskMonitor
|
|
28
|
+
|
|
29
|
+
OUT_DIR = os.path.join(os.path.dirname(getSourceFile().getAbsolutePath()), 'out9')
|
|
30
|
+
try: os.makedirs(OUT_DIR)
|
|
31
|
+
except OSError: pass
|
|
32
|
+
|
|
33
|
+
prog = currentProgram
|
|
34
|
+
fm = prog.getFunctionManager()
|
|
35
|
+
af = prog.getAddressFactory()
|
|
36
|
+
ref_mgr = prog.getReferenceManager()
|
|
37
|
+
mem = prog.getMemory()
|
|
38
|
+
monitor = ConsoleTaskMonitor()
|
|
39
|
+
decomp = DecompInterface()
|
|
40
|
+
decomp.openProgram(prog)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def addr(a): return af.getDefaultAddressSpace().getAddress(a)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def dump(func, out):
|
|
47
|
+
out.write('==== %s @ %s ====\n' % (func.getName(True), func.getEntryPoint()))
|
|
48
|
+
out.write('size: %d body bytes\n' % func.getBody().getNumAddresses())
|
|
49
|
+
callees = sorted(set('%s @ %s' % (f.getName(True), f.getEntryPoint())
|
|
50
|
+
for f in func.getCalledFunctions(monitor)))
|
|
51
|
+
out.write('\n-- callees (%d) --\n' % len(callees))
|
|
52
|
+
for c in callees: out.write(' ' + c + '\n')
|
|
53
|
+
callers = sorted(set('%s @ %s' % (f.getName(True), f.getEntryPoint())
|
|
54
|
+
for f in func.getCallingFunctions(monitor)))
|
|
55
|
+
out.write('\n-- callers (%d) --\n' % len(callers))
|
|
56
|
+
for c in callers: out.write(' ' + c + '\n')
|
|
57
|
+
out.write('\n-- decompilation --\n')
|
|
58
|
+
res = decomp.decompileFunction(func, 240, monitor)
|
|
59
|
+
if res.decompileCompleted():
|
|
60
|
+
out.write(res.getDecompiledFunction().getC())
|
|
61
|
+
else:
|
|
62
|
+
out.write('(decompilation failed: %s)\n' % res.getErrorMessage())
|
|
63
|
+
out.write('\n')
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
VPTRS = {
|
|
67
|
+
'Stick3_primary': 0x100962BE8,
|
|
68
|
+
'Stick3_sub1': 0x100962CC8,
|
|
69
|
+
'Stick3_sub2': 0x100962CF0,
|
|
70
|
+
'Stick3_sub3': 0x100962D30,
|
|
71
|
+
'Stick3_sub4': 0x100962D68,
|
|
72
|
+
'Stick3_sub5': 0x100962DA8,
|
|
73
|
+
'DasEccStreamCryptography': 0x100851F38,
|
|
74
|
+
'DasEccAesCryptography': 0x100851D88,
|
|
75
|
+
'AesOStream': 0x100851B20,
|
|
76
|
+
'AesOStream_sec': 0x100851B48,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
seen = set()
|
|
80
|
+
summary = open(os.path.join(OUT_DIR, 'SUMMARY.txt'), 'w')
|
|
81
|
+
|
|
82
|
+
for label, vptr in VPTRS.items():
|
|
83
|
+
a = addr(vptr)
|
|
84
|
+
summary.write('\n#### %s vptr @ 0x%x\n' % (label, vptr))
|
|
85
|
+
refs = list(ref_mgr.getReferencesTo(a))
|
|
86
|
+
code_callers = set()
|
|
87
|
+
for r in refs:
|
|
88
|
+
fa = r.getFromAddress()
|
|
89
|
+
f = fm.getFunctionContaining(fa)
|
|
90
|
+
if f is not None:
|
|
91
|
+
code_callers.add(f.getEntryPoint())
|
|
92
|
+
else:
|
|
93
|
+
summary.write(' data ref @ %s (no fn)\n' % fa)
|
|
94
|
+
summary.write(' code refs from %d function(s):\n' % len(code_callers))
|
|
95
|
+
for ep in sorted(code_callers):
|
|
96
|
+
f = fm.getFunctionAt(ep)
|
|
97
|
+
summary.write(' %s @ %s (size %d)\n' %
|
|
98
|
+
(f.getName(True), f.getEntryPoint(),
|
|
99
|
+
f.getBody().getNumAddresses()))
|
|
100
|
+
if f.getEntryPoint() in seen: continue
|
|
101
|
+
seen.add(f.getEntryPoint())
|
|
102
|
+
safe = re.sub(r'[^A-Za-z0-9._-]', '_', f.getName(True))[:120]
|
|
103
|
+
with open(os.path.join(OUT_DIR, '%s_%s_%s.txt' %
|
|
104
|
+
(label, f.getEntryPoint(), safe)), 'w') as o:
|
|
105
|
+
dump(f, o)
|
|
106
|
+
|
|
107
|
+
summary.write('\n=== total dumped: %d ===\n' % len(seen))
|
|
108
|
+
summary.close()
|
|
109
|
+
print('find-ctor: %d functions dumped to %s' % (len(seen), OUT_DIR))
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Find every function that references the MD5 init constants table
|
|
2
|
+
# at 0x1007C0290. That table is the H0 = {A,B,C,D} = {0x67452301, ...}.
|
|
3
|
+
# The function reading it IS the MD5 init / compute function. From there
|
|
4
|
+
# we can find callers and identify the KDF that produces the AES key.
|
|
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()), 'out18')
|
|
13
|
+
try: os.makedirs(OUT_DIR)
|
|
14
|
+
except OSError: pass
|
|
15
|
+
|
|
16
|
+
prog = currentProgram
|
|
17
|
+
fm = prog.getFunctionManager()
|
|
18
|
+
af = prog.getAddressFactory()
|
|
19
|
+
ref_mgr = prog.getReferenceManager()
|
|
20
|
+
monitor = ConsoleTaskMonitor()
|
|
21
|
+
decomp = DecompInterface()
|
|
22
|
+
decomp.openProgram(prog)
|
|
23
|
+
|
|
24
|
+
def addr(a): return af.getDefaultAddressSpace().getAddress(a)
|
|
25
|
+
|
|
26
|
+
def dump(func, out):
|
|
27
|
+
out.write('==== %s @ %s ====\n' % (func.getName(True), func.getEntryPoint()))
|
|
28
|
+
out.write('size: %d body bytes\n' % func.getBody().getNumAddresses())
|
|
29
|
+
callees = sorted(set('%s @ %s' % (f.getName(True), f.getEntryPoint())
|
|
30
|
+
for f in func.getCalledFunctions(monitor)))
|
|
31
|
+
out.write('\n-- callees (%d) --\n' % len(callees))
|
|
32
|
+
for c in callees: out.write(' ' + c + '\n')
|
|
33
|
+
callers = sorted(set('%s @ %s' % (f.getName(True), f.getEntryPoint())
|
|
34
|
+
for f in func.getCallingFunctions(monitor)))
|
|
35
|
+
out.write('\n-- callers (%d) --\n' % len(callers))
|
|
36
|
+
for c in callers: out.write(' ' + c + '\n')
|
|
37
|
+
out.write('\n-- decompilation --\n')
|
|
38
|
+
res = decomp.decompileFunction(func, 240, monitor)
|
|
39
|
+
if res.decompileCompleted():
|
|
40
|
+
out.write(res.getDecompiledFunction().getC())
|
|
41
|
+
else:
|
|
42
|
+
out.write('(decompilation failed: %s)\n' % res.getErrorMessage())
|
|
43
|
+
out.write('\n')
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
seen = set()
|
|
47
|
+
summary = open(os.path.join(OUT_DIR, 'SUMMARY.txt'), 'w')
|
|
48
|
+
|
|
49
|
+
# Find any function that references the MD5 init table at 0x1007C0290.
|
|
50
|
+
MD5_ADDR = 0x1007C0290
|
|
51
|
+
summary.write('Refs to MD5 init constants @ 0x%x\n\n' % MD5_ADDR)
|
|
52
|
+
refs = list(ref_mgr.getReferencesTo(addr(MD5_ADDR)))
|
|
53
|
+
summary.write('total refs: %d\n' % len(refs))
|
|
54
|
+
code_callers = set()
|
|
55
|
+
for r in refs:
|
|
56
|
+
fa = r.getFromAddress()
|
|
57
|
+
rt = r.getReferenceType().getName()
|
|
58
|
+
f = fm.getFunctionContaining(fa)
|
|
59
|
+
if f is not None:
|
|
60
|
+
code_callers.add(f.getEntryPoint())
|
|
61
|
+
summary.write(' ref @ %s (%s) -> fn %s @ %s\n' %
|
|
62
|
+
(fa, rt, f.getName(True), f.getEntryPoint()))
|
|
63
|
+
else:
|
|
64
|
+
summary.write(' ref @ %s (%s) - no fn\n' % (fa, rt))
|
|
65
|
+
|
|
66
|
+
# Dump each MD5-using function + its callers (one hop UP)
|
|
67
|
+
summary.write('\n=== MD5-init users + their callers ===\n')
|
|
68
|
+
for ep in sorted(code_callers):
|
|
69
|
+
f = fm.getFunctionAt(ep)
|
|
70
|
+
if f is None: continue
|
|
71
|
+
summary.write('\n MD5_user: %s @ %s (size %d)\n' %
|
|
72
|
+
(f.getName(True), f.getEntryPoint(),
|
|
73
|
+
f.getBody().getNumAddresses()))
|
|
74
|
+
summary.write(' callers of this fn:\n')
|
|
75
|
+
for c in sorted(f.getCallingFunctions(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
|
+
# Dump the MD5 user itself
|
|
80
|
+
if ep not in seen:
|
|
81
|
+
seen.add(ep)
|
|
82
|
+
safe = re.sub(r'[^A-Za-z0-9._-]', '_', f.getName(True))[:120]
|
|
83
|
+
with open(os.path.join(OUT_DIR, 'md5_user_%s_%s.txt' %
|
|
84
|
+
(f.getEntryPoint(), safe)), 'w') as o:
|
|
85
|
+
dump(f, o)
|
|
86
|
+
# Dump each caller too
|
|
87
|
+
for c in f.getCallingFunctions(monitor):
|
|
88
|
+
if c.getEntryPoint() in seen: continue
|
|
89
|
+
seen.add(c.getEntryPoint())
|
|
90
|
+
safe = re.sub(r'[^A-Za-z0-9._-]', '_', c.getName(True))[:120]
|
|
91
|
+
with open(os.path.join(OUT_DIR, 'md5_caller_%s_%s.txt' %
|
|
92
|
+
(c.getEntryPoint(), safe)), 'w') as o:
|
|
93
|
+
dump(c, o)
|
|
94
|
+
|
|
95
|
+
summary.write('\n=== total: %d ===\n' % len(seen))
|
|
96
|
+
summary.close()
|
|
97
|
+
print('find-md5: %d functions dumped to %s' % (len(seen), OUT_DIR))
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Find every function that reads the deobfuscated hardcoded 33-byte secret.
|
|
2
|
+
#
|
|
3
|
+
# The chain is:
|
|
4
|
+
# obfuscated raw bytes @ 0x1007B0490 (33 bytes in __TEXT __const)
|
|
5
|
+
# FUN_100182c80(&DAT_100cf8cf0, &DAT_1007b0490) loads them to __DATA
|
|
6
|
+
# cipher vt[7] (FUN_100180E90) XORs with mask 0x51..0x71, leaves decoded
|
|
7
|
+
# buffer at &DAT_100cf8d11, returns its address
|
|
8
|
+
# <somebody> calls vt[7], reads 33 bytes, uses for cipher state seeding
|
|
9
|
+
#
|
|
10
|
+
# Anyone calling vt[7] via `(*((cipher)->vptr + 0x38))(cipher)` would not
|
|
11
|
+
# show as a direct ref to FUN_100180E90. But anyone READING addresses
|
|
12
|
+
# 0x100cf8cf0 / 0x100cf8d11 / 0x1007b0490 directly would.
|
|
13
|
+
|
|
14
|
+
# @category Stick
|
|
15
|
+
|
|
16
|
+
import os, re
|
|
17
|
+
from ghidra.app.decompiler import DecompInterface
|
|
18
|
+
from ghidra.util.task import ConsoleTaskMonitor
|
|
19
|
+
|
|
20
|
+
OUT_DIR = os.path.join(os.path.dirname(getSourceFile().getAbsolutePath()), 'out17')
|
|
21
|
+
try: os.makedirs(OUT_DIR)
|
|
22
|
+
except OSError: pass
|
|
23
|
+
|
|
24
|
+
prog = currentProgram
|
|
25
|
+
fm = prog.getFunctionManager()
|
|
26
|
+
af = prog.getAddressFactory()
|
|
27
|
+
ref_mgr = prog.getReferenceManager()
|
|
28
|
+
monitor = ConsoleTaskMonitor()
|
|
29
|
+
decomp = DecompInterface()
|
|
30
|
+
decomp.openProgram(prog)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def addr(a): return af.getDefaultAddressSpace().getAddress(a)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def dump(func, out):
|
|
37
|
+
out.write('==== %s @ %s ====\n' % (func.getName(True), func.getEntryPoint()))
|
|
38
|
+
out.write('size: %d body bytes\n' % func.getBody().getNumAddresses())
|
|
39
|
+
callees = sorted(set('%s @ %s' % (f.getName(True), f.getEntryPoint())
|
|
40
|
+
for f in func.getCalledFunctions(monitor)))
|
|
41
|
+
out.write('\n-- callees (%d) --\n' % len(callees))
|
|
42
|
+
for c in callees: out.write(' ' + c + '\n')
|
|
43
|
+
callers = sorted(set('%s @ %s' % (f.getName(True), f.getEntryPoint())
|
|
44
|
+
for f in func.getCallingFunctions(monitor)))
|
|
45
|
+
out.write('\n-- callers (%d) --\n' % len(callers))
|
|
46
|
+
for c in callers: out.write(' ' + c + '\n')
|
|
47
|
+
out.write('\n-- decompilation --\n')
|
|
48
|
+
res = decomp.decompileFunction(func, 240, monitor)
|
|
49
|
+
if res.decompileCompleted():
|
|
50
|
+
out.write(res.getDecompiledFunction().getC())
|
|
51
|
+
else:
|
|
52
|
+
out.write('(decompilation failed: %s)\n' % res.getErrorMessage())
|
|
53
|
+
out.write('\n')
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# Addresses involved in the hardcoded-secret deobfuscation
|
|
57
|
+
TARGETS = {
|
|
58
|
+
'raw_obfuscated': 0x1007B0490, # 33 bytes XOR-masked
|
|
59
|
+
'staging_buffer': 0x100CF8CF0, # bytes loaded here, then XOR'd
|
|
60
|
+
'decoded_pointer': 0x100CF8D11, # vt[7] returns this; decoded bytes start
|
|
61
|
+
'guard_byte': 0x100CF8D38, # __cxa_guard for one-time init
|
|
62
|
+
'deobfuscator_fn': 0x100180E90,
|
|
63
|
+
'loader_fn': 0x100182C80,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
seen = set()
|
|
67
|
+
summary = open(os.path.join(OUT_DIR, 'SUMMARY.txt'), 'w')
|
|
68
|
+
|
|
69
|
+
for label, a in TARGETS.items():
|
|
70
|
+
summary.write('\n#### %s @ 0x%x\n' % (label, a))
|
|
71
|
+
code_refs = set()
|
|
72
|
+
refs = list(ref_mgr.getReferencesTo(addr(a)))
|
|
73
|
+
summary.write(' total refs: %d\n' % len(refs))
|
|
74
|
+
for r in refs:
|
|
75
|
+
fa = r.getFromAddress()
|
|
76
|
+
rtype = r.getReferenceType().getName()
|
|
77
|
+
f = fm.getFunctionContaining(fa)
|
|
78
|
+
if f is not None:
|
|
79
|
+
code_refs.add(f.getEntryPoint())
|
|
80
|
+
else:
|
|
81
|
+
summary.write(' %s @ %s (%s, not in any fn)\n' %
|
|
82
|
+
(label, fa, rtype))
|
|
83
|
+
summary.write(' code refs from %d fn(s):\n' % len(code_refs))
|
|
84
|
+
for ep in sorted(code_refs):
|
|
85
|
+
f = fm.getFunctionAt(ep)
|
|
86
|
+
summary.write(' %s @ %s (size %d)\n' %
|
|
87
|
+
(f.getName(True), f.getEntryPoint(),
|
|
88
|
+
f.getBody().getNumAddresses()))
|
|
89
|
+
if ep in seen: continue
|
|
90
|
+
seen.add(ep)
|
|
91
|
+
safe = re.sub(r'[^A-Za-z0-9._-]', '_', f.getName(True))[:120]
|
|
92
|
+
with open(os.path.join(OUT_DIR, '%s_%s_%s.txt' %
|
|
93
|
+
(label, f.getEntryPoint(), safe)), 'w') as o:
|
|
94
|
+
dump(f, o)
|
|
95
|
+
|
|
96
|
+
summary.write('\n=== total dumped: %d ===\n' % len(seen))
|
|
97
|
+
summary.close()
|
|
98
|
+
print('find-secret-users: %d functions dumped to %s' % (len(seen), OUT_DIR))
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Locate XHL_Stick3CryptDmxUniverse + related crypto vtables and dump methods.
|
|
2
|
+
#
|
|
3
|
+
# Now that we know the real class names (with XHL_ prefix and namespaces):
|
|
4
|
+
# XHL_Stick3ANet::XHL_Stick3CryptDmxUniverse ← DE3 DMX cipher class
|
|
5
|
+
# XHL_DasNetCryptDmxUniverse ← base class
|
|
6
|
+
# XHL_AesOStream / XHL_AesIStream ← block cipher streams
|
|
7
|
+
# XHL_AesOStreamBuffer / XHL_AesIStreamBuffer ← buffered variants
|
|
8
|
+
# XHL_DasDhRsaCryptography<1024>/<512> ← DH+RSA handshake
|
|
9
|
+
# XHL_DasEccAesCryptography ← ECC+AES
|
|
10
|
+
# XHL_DasEccStreamCryptography
|
|
11
|
+
# XHL_DasCryptography(KeySize)
|
|
12
|
+
#
|
|
13
|
+
# We dump the full method table of each, plus every called function — that
|
|
14
|
+
# walks us into the actual AES inlining and the per-session key derivation.
|
|
15
|
+
|
|
16
|
+
# @category Stick
|
|
17
|
+
|
|
18
|
+
import os, re
|
|
19
|
+
from ghidra.app.decompiler import DecompInterface
|
|
20
|
+
from ghidra.util.task import ConsoleTaskMonitor
|
|
21
|
+
|
|
22
|
+
OUT_DIR = os.path.join(os.path.dirname(getSourceFile().getAbsolutePath()), 'out3')
|
|
23
|
+
try:
|
|
24
|
+
os.makedirs(OUT_DIR)
|
|
25
|
+
except OSError:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
prog = currentProgram
|
|
29
|
+
fm = prog.getFunctionManager()
|
|
30
|
+
st = prog.getSymbolTable()
|
|
31
|
+
af = prog.getAddressFactory()
|
|
32
|
+
mem = prog.getMemory()
|
|
33
|
+
ref_mgr = prog.getReferenceManager()
|
|
34
|
+
monitor = ConsoleTaskMonitor()
|
|
35
|
+
decomp = DecompInterface()
|
|
36
|
+
decomp.openProgram(prog)
|
|
37
|
+
|
|
38
|
+
# Substrings to match in mangled or demangled symbol names
|
|
39
|
+
KEYWORDS = [
|
|
40
|
+
'Stick3CryptDmxUniverse',
|
|
41
|
+
'Stick5CryptDmxUniverse',
|
|
42
|
+
'DasNetCryptDmxUniverse',
|
|
43
|
+
'AesOStream',
|
|
44
|
+
'AesIStream',
|
|
45
|
+
'DasDhRsaCryptography',
|
|
46
|
+
'DasEccAesCryptography',
|
|
47
|
+
'DasEccStreamCryptography',
|
|
48
|
+
'DasCryptography',
|
|
49
|
+
# Also: any function whose source-file string contains these
|
|
50
|
+
'XHL_Stick3',
|
|
51
|
+
'XHL_DasNet',
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def addr(a):
|
|
56
|
+
return af.getDefaultAddressSpace().getAddress(a)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_ptr(a):
|
|
60
|
+
try:
|
|
61
|
+
return mem.getLong(a) & 0xFFFFFFFFFFFFFFFF
|
|
62
|
+
except Exception:
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def dump_function_full(func, out, depth=0):
|
|
67
|
+
out.write('==== %s @ %s ====\n' % (func.getName(True), func.getEntryPoint()))
|
|
68
|
+
out.write('signature: %s\n' % func.getPrototypeString(True, True))
|
|
69
|
+
out.write('size: %d body bytes\n' % func.getBody().getNumAddresses())
|
|
70
|
+
callees = sorted(set('%s @ %s' % (f.getName(True), f.getEntryPoint())
|
|
71
|
+
for f in func.getCalledFunctions(monitor)))
|
|
72
|
+
out.write('\n-- callees (%d) --\n' % len(callees))
|
|
73
|
+
for c in callees:
|
|
74
|
+
out.write(' ' + c + '\n')
|
|
75
|
+
callers = sorted(set('%s @ %s' % (f.getName(True), f.getEntryPoint())
|
|
76
|
+
for f in func.getCallingFunctions(monitor)))
|
|
77
|
+
out.write('\n-- callers (%d) --\n' % len(callers))
|
|
78
|
+
for c in callers:
|
|
79
|
+
out.write(' ' + c + '\n')
|
|
80
|
+
out.write('\n-- decompilation --\n')
|
|
81
|
+
res = decomp.decompileFunction(func, 180, monitor)
|
|
82
|
+
if res.decompileCompleted():
|
|
83
|
+
out.write(res.getDecompiledFunction().getC())
|
|
84
|
+
else:
|
|
85
|
+
out.write('(decompilation failed: %s)\n' % res.getErrorMessage())
|
|
86
|
+
out.write('\n')
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# Find all symbols (any) whose name contains any keyword.
|
|
90
|
+
all_hits = [] # list of (kw, symbol)
|
|
91
|
+
for sym in st.getAllSymbols(False):
|
|
92
|
+
n = sym.getName(True)
|
|
93
|
+
for kw in KEYWORDS:
|
|
94
|
+
if kw in n:
|
|
95
|
+
all_hits.append((kw, sym))
|
|
96
|
+
break
|
|
97
|
+
|
|
98
|
+
summary_path = os.path.join(OUT_DIR, 'SUMMARY.txt')
|
|
99
|
+
out_summary = open(summary_path, 'w')
|
|
100
|
+
out_summary.write('Stick3 crypto search — ESA2 HardwareManager (2024-03-21)\n\n')
|
|
101
|
+
|
|
102
|
+
# Bucket by kind
|
|
103
|
+
funcs = [] # symbols that are functions
|
|
104
|
+
vtables = [] # vtable symbols (__ZTV)
|
|
105
|
+
typeinfos = [] # __ZTI
|
|
106
|
+
typenames = [] # __ZTS
|
|
107
|
+
others = []
|
|
108
|
+
for kw, sym in all_hits:
|
|
109
|
+
n = sym.getName(False) or ''
|
|
110
|
+
nfull = sym.getName(True)
|
|
111
|
+
if fm.getFunctionAt(sym.getAddress()):
|
|
112
|
+
funcs.append(sym)
|
|
113
|
+
elif n.startswith('__ZTV') or 'vtable' in nfull:
|
|
114
|
+
vtables.append(sym)
|
|
115
|
+
elif n.startswith('__ZTI') or 'typeinfo' in nfull and 'name' not in nfull:
|
|
116
|
+
typeinfos.append(sym)
|
|
117
|
+
elif n.startswith('__ZTS') or 'typeinfo-name' in nfull:
|
|
118
|
+
typenames.append(sym)
|
|
119
|
+
else:
|
|
120
|
+
others.append(sym)
|
|
121
|
+
|
|
122
|
+
out_summary.write('=== Summary counts ===\n')
|
|
123
|
+
out_summary.write(' functions: %d\n' % len(funcs))
|
|
124
|
+
out_summary.write(' vtables: %d\n' % len(vtables))
|
|
125
|
+
out_summary.write(' typeinfos: %d\n' % len(typeinfos))
|
|
126
|
+
out_summary.write(' type-names: %d\n' % len(typenames))
|
|
127
|
+
out_summary.write(' other: %d\n\n' % len(others))
|
|
128
|
+
|
|
129
|
+
out_summary.write('=== Functions (named methods) ===\n')
|
|
130
|
+
for s in sorted(funcs, key=lambda s: s.getAddress()):
|
|
131
|
+
out_summary.write(' %s @ %s\n' % (s.getName(True), s.getAddress()))
|
|
132
|
+
|
|
133
|
+
out_summary.write('\n=== Vtables ===\n')
|
|
134
|
+
for s in sorted(vtables, key=lambda s: s.getAddress()):
|
|
135
|
+
out_summary.write(' %s @ %s\n' % (s.getName(True), s.getAddress()))
|
|
136
|
+
|
|
137
|
+
out_summary.write('\n=== Typeinfos ===\n')
|
|
138
|
+
for s in sorted(typeinfos, key=lambda s: s.getAddress()):
|
|
139
|
+
out_summary.write(' %s @ %s\n' % (s.getName(True), s.getAddress()))
|
|
140
|
+
|
|
141
|
+
out_summary.write('\n=== Type-name strings ===\n')
|
|
142
|
+
for s in sorted(typenames, key=lambda s: s.getAddress()):
|
|
143
|
+
out_summary.write(' %s @ %s\n' % (s.getName(True), s.getAddress()))
|
|
144
|
+
|
|
145
|
+
# Walk every vtable: dump fn-pointer slots and full decomp of each
|
|
146
|
+
seen = set()
|
|
147
|
+
out_summary.write('\n\n=== Vtable layouts ===\n')
|
|
148
|
+
for s in sorted(vtables, key=lambda s: s.getAddress()):
|
|
149
|
+
name = s.getName(True)
|
|
150
|
+
a = s.getAddress()
|
|
151
|
+
out_summary.write('\n--- %s @ %s ---\n' % (name, a))
|
|
152
|
+
base = int(a.getOffset())
|
|
153
|
+
for slot in range(0, 32):
|
|
154
|
+
slot_off = 0x10 + slot * 8 # skip offset-to-top + typeinfo
|
|
155
|
+
slot_addr = addr(base + slot_off)
|
|
156
|
+
fp = get_ptr(slot_addr)
|
|
157
|
+
if fp is None or fp == 0:
|
|
158
|
+
break
|
|
159
|
+
# Heuristic to stop at end of vtable: if the qword is itself another
|
|
160
|
+
# vtable symbol's address or clearly not code, bail.
|
|
161
|
+
fa = addr(fp)
|
|
162
|
+
f = fm.getFunctionAt(fa) or fm.getFunctionContaining(fa)
|
|
163
|
+
if f is None:
|
|
164
|
+
out_summary.write(' +0x%02x: 0x%x (no fn)\n' % (slot_off, fp))
|
|
165
|
+
continue
|
|
166
|
+
out_summary.write(' +0x%02x: %s @ %s\n' %
|
|
167
|
+
(slot_off, f.getName(True), f.getEntryPoint()))
|
|
168
|
+
if f.getEntryPoint() in seen:
|
|
169
|
+
continue
|
|
170
|
+
seen.add(f.getEntryPoint())
|
|
171
|
+
safe_cls = re.sub(r'[^A-Za-z0-9._-]', '_', name)[:80]
|
|
172
|
+
safe_fn = re.sub(r'[^A-Za-z0-9._-]', '_', f.getName(True))[:120]
|
|
173
|
+
path = os.path.join(OUT_DIR, '%s_+%02x_%s.txt' % (safe_cls, slot_off, safe_fn))
|
|
174
|
+
with open(path, 'w') as o:
|
|
175
|
+
dump_function_full(f, o)
|
|
176
|
+
|
|
177
|
+
# Also dump every named function we matched directly
|
|
178
|
+
for s in sorted(funcs, key=lambda s: s.getAddress()):
|
|
179
|
+
f = fm.getFunctionAt(s.getAddress())
|
|
180
|
+
if f is None or f.getEntryPoint() in seen:
|
|
181
|
+
continue
|
|
182
|
+
seen.add(f.getEntryPoint())
|
|
183
|
+
safe = re.sub(r'[^A-Za-z0-9._-]', '_', f.getName(True))[:140]
|
|
184
|
+
path = os.path.join(OUT_DIR, 'fn_%s_%s.txt' % (f.getEntryPoint(), safe))
|
|
185
|
+
with open(path, 'w') as o:
|
|
186
|
+
dump_function_full(f, o)
|
|
187
|
+
|
|
188
|
+
out_summary.close()
|
|
189
|
+
print('find-stick3-crypto: dumped %d functions to %s' % (len(seen), OUT_DIR))
|
|
190
|
+
print('find-stick3-crypto: summary at %s' % summary_path)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Trace UP from the Stick3CryptDmxUniverse factory to find the XHL_Stick3
|
|
2
|
+
# device class — where the cipher state is actually initialized.
|
|
3
|
+
#
|
|
4
|
+
# Known facts:
|
|
5
|
+
# - FUN_100178bb0 = Stick3CryptDmxUniverse factory
|
|
6
|
+
# - Called by FUN_100179050
|
|
7
|
+
# - The cipher object lives at (device + 0x1618), embedded inside the
|
|
8
|
+
# device class. The device's ctor writes the embedded cipher's vptr.
|
|
9
|
+
#
|
|
10
|
+
# Goal: dump FUN_100179050 + its callers (probably the XHL_Stick3 device's
|
|
11
|
+
# methods), and dump the 7 big constructors (2.7-8KB) that we identified as
|
|
12
|
+
# touching the encrypt-fn vtables — one of them is XHL_Stick3 itself.
|
|
13
|
+
|
|
14
|
+
# @category Stick
|
|
15
|
+
|
|
16
|
+
import os, re
|
|
17
|
+
from ghidra.app.decompiler import DecompInterface
|
|
18
|
+
from ghidra.util.task import ConsoleTaskMonitor
|
|
19
|
+
|
|
20
|
+
OUT_DIR = os.path.join(os.path.dirname(getSourceFile().getAbsolutePath()), 'out12')
|
|
21
|
+
try: os.makedirs(OUT_DIR)
|
|
22
|
+
except OSError: pass
|
|
23
|
+
|
|
24
|
+
prog = currentProgram
|
|
25
|
+
fm = prog.getFunctionManager()
|
|
26
|
+
af = prog.getAddressFactory()
|
|
27
|
+
mem = prog.getMemory()
|
|
28
|
+
ref_mgr = prog.getReferenceManager()
|
|
29
|
+
monitor = ConsoleTaskMonitor()
|
|
30
|
+
decomp = DecompInterface()
|
|
31
|
+
decomp.openProgram(prog)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def addr(a): return af.getDefaultAddressSpace().getAddress(a)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def dump(func, out):
|
|
38
|
+
out.write('==== %s @ %s ====\n' % (func.getName(True), func.getEntryPoint()))
|
|
39
|
+
out.write('size: %d body bytes\n' % func.getBody().getNumAddresses())
|
|
40
|
+
callees = sorted(set('%s @ %s' % (f.getName(True), f.getEntryPoint())
|
|
41
|
+
for f in func.getCalledFunctions(monitor)))
|
|
42
|
+
out.write('\n-- callees (%d) --\n' % len(callees))
|
|
43
|
+
for c in callees: out.write(' ' + c + '\n')
|
|
44
|
+
callers = sorted(set('%s @ %s' % (f.getName(True), f.getEntryPoint())
|
|
45
|
+
for f in func.getCallingFunctions(monitor)))
|
|
46
|
+
out.write('\n-- callers (%d) --\n' % len(callers))
|
|
47
|
+
for c in callers: out.write(' ' + c + '\n')
|
|
48
|
+
out.write('\n-- decompilation --\n')
|
|
49
|
+
res = decomp.decompileFunction(func, 240, monitor)
|
|
50
|
+
if res.decompileCompleted():
|
|
51
|
+
out.write(res.getDecompiledFunction().getC())
|
|
52
|
+
else:
|
|
53
|
+
out.write('(decompilation failed: %s)\n' % res.getErrorMessage())
|
|
54
|
+
out.write('\n')
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# 1. Caller of Stick3 factory + its callers (one more hop up)
|
|
58
|
+
factory_caller = 0x100179050
|
|
59
|
+
roots = {'caller_of_factory': factory_caller}
|
|
60
|
+
seen = set()
|
|
61
|
+
summary = open(os.path.join(OUT_DIR, 'SUMMARY.txt'), 'w')
|
|
62
|
+
|
|
63
|
+
f = fm.getFunctionAt(addr(factory_caller))
|
|
64
|
+
if f:
|
|
65
|
+
seen.add(f.getEntryPoint())
|
|
66
|
+
safe = re.sub(r'[^A-Za-z0-9._-]', '_', f.getName(True))[:120]
|
|
67
|
+
with open(os.path.join(OUT_DIR, 'A_factorycaller_%s_%s.txt' %
|
|
68
|
+
(f.getEntryPoint(), safe)), 'w') as o:
|
|
69
|
+
dump(f, o)
|
|
70
|
+
summary.write('factory_caller = %s @ %s (size %d)\n' %
|
|
71
|
+
(f.getName(True), f.getEntryPoint(),
|
|
72
|
+
f.getBody().getNumAddresses()))
|
|
73
|
+
summary.write(' one-up callers:\n')
|
|
74
|
+
for c in sorted(f.getCallingFunctions(monitor), key=lambda f: f.getEntryPoint()):
|
|
75
|
+
summary.write(' %s @ %s (size %d)\n' %
|
|
76
|
+
(c.getName(True), c.getEntryPoint(),
|
|
77
|
+
c.getBody().getNumAddresses()))
|
|
78
|
+
if c.getEntryPoint() in seen: continue
|
|
79
|
+
seen.add(c.getEntryPoint())
|
|
80
|
+
safe = re.sub(r'[^A-Za-z0-9._-]', '_', c.getName(True))[:120]
|
|
81
|
+
with open(os.path.join(OUT_DIR, 'B_oneUp_%s_%s.txt' %
|
|
82
|
+
(c.getEntryPoint(), safe)), 'w') as o:
|
|
83
|
+
dump(c, o)
|
|
84
|
+
|
|
85
|
+
# 2. The 7 big constructors that touched encrypt-fn vtables
|
|
86
|
+
BIG_CTORS = [
|
|
87
|
+
0x1002385b0,
|
|
88
|
+
0x10027d350,
|
|
89
|
+
0x100283f60,
|
|
90
|
+
0x1002b7360,
|
|
91
|
+
0x1002c1920,
|
|
92
|
+
0x1002c8ac0,
|
|
93
|
+
0x1002ceaf0,
|
|
94
|
+
]
|
|
95
|
+
summary.write('\n=== Big constructors touching encrypt-fn vtables ===\n')
|
|
96
|
+
for fp in BIG_CTORS:
|
|
97
|
+
f = fm.getFunctionAt(addr(fp))
|
|
98
|
+
if f is None: continue
|
|
99
|
+
summary.write(' %s @ %s (size %d)\n' %
|
|
100
|
+
(f.getName(True), f.getEntryPoint(),
|
|
101
|
+
f.getBody().getNumAddresses()))
|
|
102
|
+
if f.getEntryPoint() in seen: continue
|
|
103
|
+
seen.add(f.getEntryPoint())
|
|
104
|
+
safe = re.sub(r'[^A-Za-z0-9._-]', '_', f.getName(True))[:120]
|
|
105
|
+
with open(os.path.join(OUT_DIR, 'C_bigctor_%s_%s.txt' %
|
|
106
|
+
(f.getEntryPoint(), safe)), 'w') as o:
|
|
107
|
+
dump(f, o)
|
|
108
|
+
|
|
109
|
+
summary.write('\n=== total: %d ===\n' % len(seen))
|
|
110
|
+
summary.close()
|
|
111
|
+
print('find-stick3-device: %d functions dumped to %s' % (len(seen), OUT_DIR))
|