@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,182 @@
|
|
|
1
|
+
# Dump every Stick3CryptDmxUniverse vtable slot function + every caller of
|
|
2
|
+
# XHL_UdpSocket::send. Finds the actual DMX encrypt routine.
|
|
3
|
+
#
|
|
4
|
+
# Vtable sites (from dyld_info -fixups, typeinfo @ 0x100970DC0):
|
|
5
|
+
# Primary: 0x100962BE0 (slot[0] @ +0x08, primary virtuals)
|
|
6
|
+
# Sub1: 0x100962CC0 (5 methods, secondary base #1)
|
|
7
|
+
# Sub2: 0x100962CE8 (5 methods, secondary base #2)
|
|
8
|
+
# Sub3: 0x100962D28 (5 methods, secondary base #3)
|
|
9
|
+
# Sub4: 0x100962D60 (5 methods, secondary base #4)
|
|
10
|
+
# Sub5: 0x100962DA0 (last group, also has VTT entries)
|
|
11
|
+
#
|
|
12
|
+
# UdpSocket sends are FUN_100677060 (sendUnicast?) and FUN_100677330
|
|
13
|
+
# (sendBroadcast or send-to-many?). 26 + 6 callers.
|
|
14
|
+
|
|
15
|
+
# @category Stick
|
|
16
|
+
|
|
17
|
+
import os, re
|
|
18
|
+
from ghidra.app.decompiler import DecompInterface
|
|
19
|
+
from ghidra.util.task import ConsoleTaskMonitor
|
|
20
|
+
|
|
21
|
+
OUT_DIR = os.path.join(os.path.dirname(getSourceFile().getAbsolutePath()), 'out6')
|
|
22
|
+
try:
|
|
23
|
+
os.makedirs(OUT_DIR)
|
|
24
|
+
except OSError:
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
prog = currentProgram
|
|
28
|
+
fm = prog.getFunctionManager()
|
|
29
|
+
af = prog.getAddressFactory()
|
|
30
|
+
mem = prog.getMemory()
|
|
31
|
+
ref_mgr = prog.getReferenceManager()
|
|
32
|
+
monitor = ConsoleTaskMonitor()
|
|
33
|
+
decomp = DecompInterface()
|
|
34
|
+
decomp.openProgram(prog)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def addr(a):
|
|
38
|
+
return af.getDefaultAddressSpace().getAddress(a)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def dump_function(func, out):
|
|
42
|
+
out.write('==== %s @ %s ====\n' % (func.getName(True), func.getEntryPoint()))
|
|
43
|
+
out.write('signature: %s\n' % func.getPrototypeString(True, True))
|
|
44
|
+
out.write('size: %d body bytes\n' % func.getBody().getNumAddresses())
|
|
45
|
+
callees = sorted(set('%s @ %s' % (f.getName(True), f.getEntryPoint())
|
|
46
|
+
for f in func.getCalledFunctions(monitor)))
|
|
47
|
+
out.write('\n-- callees (%d) --\n' % len(callees))
|
|
48
|
+
for c in callees:
|
|
49
|
+
out.write(' ' + c + '\n')
|
|
50
|
+
callers = sorted(set('%s @ %s' % (f.getName(True), f.getEntryPoint())
|
|
51
|
+
for f in func.getCallingFunctions(monitor)))
|
|
52
|
+
out.write('\n-- callers (%d) --\n' % len(callers))
|
|
53
|
+
for c in callers:
|
|
54
|
+
out.write(' ' + c + '\n')
|
|
55
|
+
out.write('\n-- decompilation --\n')
|
|
56
|
+
res = decomp.decompileFunction(func, 180, monitor)
|
|
57
|
+
if res.decompileCompleted():
|
|
58
|
+
out.write(res.getDecompiledFunction().getC())
|
|
59
|
+
else:
|
|
60
|
+
out.write('(decompilation failed: %s)\n' % res.getErrorMessage())
|
|
61
|
+
out.write('\n')
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Hardcoded vtable slot function addresses extracted from dyld_info -fixups
|
|
65
|
+
VTABLE_SLOTS = {
|
|
66
|
+
'Stick3CryptDmxUniverse_PRIMARY': [
|
|
67
|
+
# primary @ 0x100962BE0; slot 0 is the byte just after typeinfo_ptr.
|
|
68
|
+
# (Itanium: slot 0 typically = D0/D1 destructors, slots 2+ = virtuals.)
|
|
69
|
+
(0, 0x1001818E0),
|
|
70
|
+
(1, 0x100181A30),
|
|
71
|
+
(2, 0x1001AA240),
|
|
72
|
+
(3, 0x1005EF5D0),
|
|
73
|
+
(4, 0x100181FC0),
|
|
74
|
+
(5, 0x100181BB0),
|
|
75
|
+
(6, 0x1001AA410),
|
|
76
|
+
(7, 0x1005EF0E0),
|
|
77
|
+
(8, 0x1005EF0F0),
|
|
78
|
+
(9, 0x1005EF250),
|
|
79
|
+
(10, 0x100181CB0),
|
|
80
|
+
(11, 0x1005EF2A0),
|
|
81
|
+
(12, 0x1001C0340),
|
|
82
|
+
(13, 0x1005EF4A0),
|
|
83
|
+
(14, 0x1001AA2B0),
|
|
84
|
+
(15, 0x1001AA110),
|
|
85
|
+
(16, 0x1005EF700),
|
|
86
|
+
(17, 0x100181FB0),
|
|
87
|
+
(18, 0x100334140),
|
|
88
|
+
(19, 0x1005EF800),
|
|
89
|
+
(20, 0x1005EF8F0),
|
|
90
|
+
(21, 0x1003342B0),
|
|
91
|
+
(22, 0x1001A9D50),
|
|
92
|
+
(23, 0x100181BC0),
|
|
93
|
+
],
|
|
94
|
+
'Stick3CryptDmxUniverse_SUB1_at_0x100962CC0': [
|
|
95
|
+
(0, 0x1001A9F00),
|
|
96
|
+
(1, 0x100181910),
|
|
97
|
+
(2, 0x100181A60),
|
|
98
|
+
],
|
|
99
|
+
'Stick3CryptDmxUniverse_SUB2_at_0x100962CE8': [
|
|
100
|
+
(0, 0x100181CA0),
|
|
101
|
+
(1, 0x100181940),
|
|
102
|
+
(2, 0x100181AA0),
|
|
103
|
+
(3, 0x1005F0510),
|
|
104
|
+
(4, 0x1005F0520),
|
|
105
|
+
],
|
|
106
|
+
'Stick3CryptDmxUniverse_SUB3_at_0x100962D28': [
|
|
107
|
+
(0, 0x100181970),
|
|
108
|
+
(1, 0x100181AE0),
|
|
109
|
+
(2, 0x1001C0410),
|
|
110
|
+
(3, 0x1001C0540),
|
|
111
|
+
(4, 0x100421210),
|
|
112
|
+
],
|
|
113
|
+
'Stick3CryptDmxUniverse_SUB4_at_0x100962D60': [
|
|
114
|
+
(0, 0x1001819B0),
|
|
115
|
+
(1, 0x100181B20),
|
|
116
|
+
(2, 0x100334300),
|
|
117
|
+
],
|
|
118
|
+
'Stick3CryptDmxUniverse_SUB5_at_0x100962DA0': [
|
|
119
|
+
(0, 0x1001819F0),
|
|
120
|
+
(1, 0x100181B60),
|
|
121
|
+
(2, 0x10062E1A0),
|
|
122
|
+
(3, 0x10062E2A0),
|
|
123
|
+
],
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# UdpSocket send functions — every caller is a candidate
|
|
127
|
+
UDP_SEND_FNS = {
|
|
128
|
+
'XHL_UdpSocket_sendA_FUN_100677060': 0x100677060,
|
|
129
|
+
'XHL_UdpSocket_sendB_FUN_100677330': 0x100677330,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
summary = open(os.path.join(OUT_DIR, 'SUMMARY.txt'), 'w')
|
|
133
|
+
summary.write('Stick3 vtable + UDP-send callers — ESA2 HardwareManager\n\n')
|
|
134
|
+
|
|
135
|
+
seen = set()
|
|
136
|
+
|
|
137
|
+
# --- Dump vtable slots ---
|
|
138
|
+
for label, slots in VTABLE_SLOTS.items():
|
|
139
|
+
summary.write('\n=== %s ===\n' % label)
|
|
140
|
+
for slot_idx, fp in slots:
|
|
141
|
+
fa = addr(fp)
|
|
142
|
+
f = fm.getFunctionAt(fa) or fm.getFunctionContaining(fa)
|
|
143
|
+
if f is None:
|
|
144
|
+
summary.write(' vt[%d] @ 0x%x (no fn)\n' % (slot_idx, fp))
|
|
145
|
+
continue
|
|
146
|
+
summary.write(' vt[%d] @ 0x%x = %s @ %s (size %d)\n' %
|
|
147
|
+
(slot_idx, fp, f.getName(True),
|
|
148
|
+
f.getEntryPoint(), f.getBody().getNumAddresses()))
|
|
149
|
+
if f.getEntryPoint() in seen:
|
|
150
|
+
continue
|
|
151
|
+
seen.add(f.getEntryPoint())
|
|
152
|
+
safe = re.sub(r'[^A-Za-z0-9._-]', '_', f.getName(True))[:120]
|
|
153
|
+
path = os.path.join(OUT_DIR, '%s_vt%02d_%s_%s.txt' %
|
|
154
|
+
(label, slot_idx, f.getEntryPoint(), safe))
|
|
155
|
+
with open(path, 'w') as o:
|
|
156
|
+
dump_function(f, o)
|
|
157
|
+
|
|
158
|
+
# --- Dump every caller of UdpSocket::send ---
|
|
159
|
+
for label, fp in UDP_SEND_FNS.items():
|
|
160
|
+
summary.write('\n=== Callers of %s @ 0x%x ===\n' % (label, fp))
|
|
161
|
+
fn = fm.getFunctionAt(addr(fp))
|
|
162
|
+
if fn is None:
|
|
163
|
+
summary.write(' (function not found)\n')
|
|
164
|
+
continue
|
|
165
|
+
callers = sorted(set(f.getEntryPoint() for f in fn.getCallingFunctions(monitor)))
|
|
166
|
+
for ep in callers:
|
|
167
|
+
f = fm.getFunctionAt(ep)
|
|
168
|
+
summary.write(' %s @ %s (size %d)\n' %
|
|
169
|
+
(f.getName(True), f.getEntryPoint(),
|
|
170
|
+
f.getBody().getNumAddresses()))
|
|
171
|
+
if f.getEntryPoint() in seen:
|
|
172
|
+
continue
|
|
173
|
+
seen.add(f.getEntryPoint())
|
|
174
|
+
safe = re.sub(r'[^A-Za-z0-9._-]', '_', f.getName(True))[:120]
|
|
175
|
+
path = os.path.join(OUT_DIR, 'udpcaller_%s_%s.txt' %
|
|
176
|
+
(f.getEntryPoint(), safe))
|
|
177
|
+
with open(path, 'w') as o:
|
|
178
|
+
dump_function(f, o)
|
|
179
|
+
|
|
180
|
+
summary.write('\n=== total functions dumped: %d ===\n' % len(seen))
|
|
181
|
+
summary.close()
|
|
182
|
+
print('dump-stick3-vtable: dumped %d functions to %s' % (len(seen), OUT_DIR))
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# xref the "software" cstring (0x1007e4f83) — the function that assigns it to
|
|
2
|
+
# a device also assigns the adjacent auth key.
|
|
3
|
+
# @category Stick
|
|
4
|
+
import os, re
|
|
5
|
+
from ghidra.app.decompiler import DecompInterface
|
|
6
|
+
from ghidra.util.task import ConsoleTaskMonitor
|
|
7
|
+
OUT = os.path.join(os.path.dirname(getSourceFile().getAbsolutePath()), 'out-swxref')
|
|
8
|
+
try: os.makedirs(OUT)
|
|
9
|
+
except OSError: pass
|
|
10
|
+
prog = currentProgram; fm = prog.getFunctionManager(); af = prog.getAddressFactory()
|
|
11
|
+
rm = prog.getReferenceManager(); monitor = ConsoleTaskMonitor()
|
|
12
|
+
decomp = DecompInterface(); decomp.openProgram(prog)
|
|
13
|
+
def addr(a): return af.getDefaultAddressSpace().getAddress(a)
|
|
14
|
+
def dec(f):
|
|
15
|
+
r = decomp.decompileFunction(f, 240, monitor)
|
|
16
|
+
return r.getDecompiledFunction().getC() if r.decompileCompleted() else '(fail)\n'
|
|
17
|
+
seen=set()
|
|
18
|
+
for target in [0x1007e4f83]:
|
|
19
|
+
for r in rm.getReferencesTo(addr(target)):
|
|
20
|
+
f = fm.getFunctionContaining(r.getFromAddress())
|
|
21
|
+
if f is None or str(f.getEntryPoint()) in seen: continue
|
|
22
|
+
seen.add(str(f.getEntryPoint()))
|
|
23
|
+
safe = re.sub(r'[^A-Za-z0-9._-]','_',f.getName(True))[:70]
|
|
24
|
+
with open(os.path.join(OUT,'xref_%s_%s.txt'%(f.getEntryPoint(),safe)),'w') as o:
|
|
25
|
+
o.write('==== %s @ %s (from %s) ====\n'%(f.getName(True),f.getEntryPoint(),r.getFromAddress()))
|
|
26
|
+
o.write(dec(f))
|
|
27
|
+
print('dump-swxref: %d functions -> %s'%(len(seen),OUT))
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Identify the vtable+0xe8 entry (AES-256 key getter) for the Stick3 device
|
|
2
|
+
# class, plus the sibling crypto functions FUN_1001bb3a0 and FUN_1001bb860
|
|
3
|
+
# which also use the same key path -- so we can correlate.
|
|
4
|
+
#
|
|
5
|
+
# Strategy: pyghidra has the binary indexed, so dump the function pointer
|
|
6
|
+
# in the device's __const vtable at slot +0xe8. Stick3ANet typeinfo at
|
|
7
|
+
# 0x100970160 -- vtable_top should be at typeinfo - 0x10 or via __const xref.
|
|
8
|
+
#
|
|
9
|
+
# Also: dump FUN_1001bb3a0 (sibling that also uses vt+0xe8 then aes_install)
|
|
10
|
+
# and FUN_1001bb860 (sibling crypto fn).
|
|
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 = os.path.join(os.path.dirname(getSourceFile().getAbsolutePath()), 'out-vt-key')
|
|
19
|
+
try: os.makedirs(OUT)
|
|
20
|
+
except OSError: pass
|
|
21
|
+
|
|
22
|
+
prog = currentProgram
|
|
23
|
+
fm = prog.getFunctionManager()
|
|
24
|
+
af = prog.getAddressFactory()
|
|
25
|
+
mem = prog.getMemory()
|
|
26
|
+
listing = prog.getListing()
|
|
27
|
+
monitor = ConsoleTaskMonitor()
|
|
28
|
+
decomp = DecompInterface(); decomp.openProgram(prog)
|
|
29
|
+
def addr(a): return af.getDefaultAddressSpace().getAddress(a)
|
|
30
|
+
|
|
31
|
+
def decompile(f):
|
|
32
|
+
r = decomp.decompileFunction(f, 240, monitor)
|
|
33
|
+
return r.getDecompiledFunction().getC() if r.decompileCompleted() else '(decompile failed)\n'
|
|
34
|
+
|
|
35
|
+
def dump_fn(ep, label):
|
|
36
|
+
a = addr(ep)
|
|
37
|
+
f = fm.getFunctionAt(a) or fm.getFunctionContaining(a)
|
|
38
|
+
if not f:
|
|
39
|
+
return
|
|
40
|
+
safe = re.sub(r'[^A-Za-z0-9._-]', '_', f.getName(True))[:80]
|
|
41
|
+
fname = os.path.join(OUT, '%s_%s_%s.txt' % (label, f.getEntryPoint(), safe))
|
|
42
|
+
with open(fname, 'w') as o:
|
|
43
|
+
o.write('==== %s @ %s (%d bytes) ====\n' %
|
|
44
|
+
(f.getName(True), f.getEntryPoint(), f.getBody().getNumAddresses()))
|
|
45
|
+
callees = sorted(set('%s @ %s' % (c.getName(True), c.getEntryPoint())
|
|
46
|
+
for c in f.getCalledFunctions(monitor)))
|
|
47
|
+
callers = sorted(set('%s @ %s' % (c.getName(True), c.getEntryPoint())
|
|
48
|
+
for c in f.getCallingFunctions(monitor)))
|
|
49
|
+
o.write('-- callees (%d) --\n ' % len(callees) + '\n '.join(callees) + '\n')
|
|
50
|
+
o.write('\n-- callers (%d) --\n ' % len(callers) + '\n '.join(callers) + '\n\n')
|
|
51
|
+
o.write('-- decompilation --\n')
|
|
52
|
+
o.write(decompile(f))
|
|
53
|
+
|
|
54
|
+
# Sibling crypto functions also using AES key install.
|
|
55
|
+
SIBLINGS = [
|
|
56
|
+
(0x1001bb3a0, 'sibling_bb3a0'), # also calls FUN_1003f5740
|
|
57
|
+
(0x1001bb860, 'sibling_bb860'), # vt+0xe8 user
|
|
58
|
+
(0x100353b20, 'sibling_353b20'), # another decrypt user
|
|
59
|
+
(0x100594ba0, 'sibling_594ba0'),
|
|
60
|
+
(0x100595a40, 'sibling_595a40'),
|
|
61
|
+
(0x1001bc0a0, 'unencrypted_011c'), # the non-encrypted path -- shows what data layout the encrypted version equals
|
|
62
|
+
(0x1001f89f0, 'register_pending'), # used in handler
|
|
63
|
+
(0x100195b20, 'extract_reply'), # used in handler
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
for ep, label in SIBLINGS:
|
|
67
|
+
dump_fn(ep, label)
|
|
68
|
+
|
|
69
|
+
# Find the function called at offset 0x1001bbee5 -- the indirect vt[+0xe8] call.
|
|
70
|
+
# Walk references FROM that instruction to find the resolved target (if Ghidra
|
|
71
|
+
# resolved it via dyld_info / objc / pointer-authentication). If not resolved,
|
|
72
|
+
# we'll have to find the device class vtable by another means.
|
|
73
|
+
ins_addr = addr(0x1001bbee5)
|
|
74
|
+
print('--- references at 0x1001bbee5 ---')
|
|
75
|
+
rm = prog.getReferenceManager()
|
|
76
|
+
for r in rm.getReferencesFrom(ins_addr):
|
|
77
|
+
print(' ', r.getReferenceType(), r.getToAddress())
|
|
78
|
+
|
|
79
|
+
# Find Stick3ANet typeinfo @ 0x100970160 and the vtable preceding it.
|
|
80
|
+
ti_addr = addr(0x100970160)
|
|
81
|
+
print('\n--- references TO Stick3ANet typeinfo @ 0x100970160 ---')
|
|
82
|
+
for r in rm.getReferencesTo(ti_addr):
|
|
83
|
+
print(' ', r.getFromAddress(), r.getReferenceType())
|
|
84
|
+
|
|
85
|
+
# Also dump the bytes around the typeinfo -- the vtable is just before it (in
|
|
86
|
+
# Itanium ABI, vtable layout = [offset-to-top][typeinfo*][vfn0][vfn1]...).
|
|
87
|
+
print('\n--- bytes near typeinfo 0x100970160 (vtable + typeinfo) ---')
|
|
88
|
+
buf = bytearray(0x800)
|
|
89
|
+
try:
|
|
90
|
+
base = addr(0x10096fa00)
|
|
91
|
+
mem.getBytes(base, buf)
|
|
92
|
+
# interpret as 8-byte LE pointers; show first 256
|
|
93
|
+
for i in range(0, len(buf), 8):
|
|
94
|
+
v = int.from_bytes(buf[i:i+8], 'little')
|
|
95
|
+
if 0x100000000 <= v < 0x110000000:
|
|
96
|
+
print(' +0x%04x : 0x%016x' % (i, v))
|
|
97
|
+
except Exception as e:
|
|
98
|
+
print(' err:', e)
|
|
99
|
+
|
|
100
|
+
# Search the entire binary's __DATA segments for any pointer = 0x100970160
|
|
101
|
+
# (typeinfo of Stick3ANet) -- the location 8 bytes BEFORE that hit is the
|
|
102
|
+
# vtable start; +0xe8 + 0x10 = slot.
|
|
103
|
+
print('\n--- searching for pointer 0x100970160 in __DATA ---')
|
|
104
|
+
target_bytes = (0x100970160).to_bytes(8, 'little')
|
|
105
|
+
for block in mem.getBlocks():
|
|
106
|
+
if not block.isInitialized(): continue
|
|
107
|
+
if block.isExecute(): continue # data only
|
|
108
|
+
start = block.getStart().getOffset()
|
|
109
|
+
size = block.getSize()
|
|
110
|
+
if size > 64*1024*1024: continue
|
|
111
|
+
bb = bytearray(size)
|
|
112
|
+
try:
|
|
113
|
+
block.getBytes(block.getStart(), bb)
|
|
114
|
+
except Exception:
|
|
115
|
+
continue
|
|
116
|
+
i = 0
|
|
117
|
+
while True:
|
|
118
|
+
idx = bb.find(target_bytes, i)
|
|
119
|
+
if idx == -1: break
|
|
120
|
+
ptr_addr = start + idx
|
|
121
|
+
# In Itanium ABI, this offset = vtable + 0x8 (the typeinfo pointer slot).
|
|
122
|
+
# So vtable starts at ptr_addr - 0x8 (the offset-to-top slot before it).
|
|
123
|
+
# Slot index N is at vtable + 0x10 + N*8.
|
|
124
|
+
# vfn at offset +0xe8 in vptr-space = raw addr ptr_addr + 0x8 + 0xe8
|
|
125
|
+
# because vptr = vtable + 0x10, and slot at +0xe8 in vptr-space
|
|
126
|
+
# is at vtable + 0x10 + 0xe8 = ptr_addr - 0x8 + 0x10 + 0xe8 = ptr_addr + 0xf0.
|
|
127
|
+
slot_addr = ptr_addr + 0xf0
|
|
128
|
+
try:
|
|
129
|
+
slot_buf = bytearray(8)
|
|
130
|
+
mem.getBytes(addr(slot_addr), slot_buf)
|
|
131
|
+
slot_val = int.from_bytes(slot_buf, 'little')
|
|
132
|
+
print(' typeinfo* found at 0x%x -> vt[+0xe8] = 0x%016x (fn @ 0x%016x)'
|
|
133
|
+
% (ptr_addr, slot_val, slot_val))
|
|
134
|
+
# Dump that function.
|
|
135
|
+
f = fm.getFunctionAt(addr(slot_val)) or fm.getFunctionContaining(addr(slot_val))
|
|
136
|
+
if f:
|
|
137
|
+
dump_fn(slot_val, 'stick3anet_vt_e8')
|
|
138
|
+
except Exception as e:
|
|
139
|
+
print(' err reading slot at 0x%x: %s' % (slot_addr, e))
|
|
140
|
+
i = idx + 1
|
|
141
|
+
|
|
142
|
+
# Also try Stick3A typeinfo
|
|
143
|
+
print('\n--- searching for pointer 0x100C820F0 (Stick3A typeinfo) ---')
|
|
144
|
+
target2 = (0x100C820F0).to_bytes(8, 'little')
|
|
145
|
+
for block in mem.getBlocks():
|
|
146
|
+
if not block.isInitialized() or block.isExecute(): continue
|
|
147
|
+
size = block.getSize()
|
|
148
|
+
if size > 64*1024*1024: continue
|
|
149
|
+
bb = bytearray(size)
|
|
150
|
+
try: block.getBytes(block.getStart(), bb)
|
|
151
|
+
except: continue
|
|
152
|
+
i = 0
|
|
153
|
+
start = block.getStart().getOffset()
|
|
154
|
+
while True:
|
|
155
|
+
idx = bb.find(target2, i)
|
|
156
|
+
if idx == -1: break
|
|
157
|
+
ptr_addr = start + idx
|
|
158
|
+
slot_addr = ptr_addr + 0xf0
|
|
159
|
+
try:
|
|
160
|
+
slot_buf = bytearray(8); mem.getBytes(addr(slot_addr), slot_buf)
|
|
161
|
+
slot_val = int.from_bytes(slot_buf, 'little')
|
|
162
|
+
print(' Stick3A typeinfo @ 0x%x -> vt[+0xe8] = 0x%016x' % (ptr_addr, slot_val))
|
|
163
|
+
f = fm.getFunctionAt(addr(slot_val)) or fm.getFunctionContaining(addr(slot_val))
|
|
164
|
+
if f:
|
|
165
|
+
dump_fn(slot_val, 'stick3a_vt_e8')
|
|
166
|
+
except Exception as e:
|
|
167
|
+
print(' err: %s' % e)
|
|
168
|
+
i = idx + 1
|
|
169
|
+
|
|
170
|
+
print('dump-vt-key-getter: done -> %s' % OUT)
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Locate the real DMX cipher path in the symbolized ESA2 HardwareManager.
|
|
2
|
+
#
|
|
3
|
+
# What we know from dump-anchors:
|
|
4
|
+
# - DmxUniversePage::sendDmx (worker fn dispatched via QtConcurrent::run) calls
|
|
5
|
+
# (*((**(this+0x48))[2]))((this+0x48)+0x10, this+0x58)
|
|
6
|
+
# i.e. a virtual on an object held at DmxUniversePage::m_field_0x48,
|
|
7
|
+
# passing the 488-byte raw DMX buffer at this+0x58.
|
|
8
|
+
# - receiveDmx calls slot +0x18 of the same vtable.
|
|
9
|
+
# - That object's class is one of the RTTI-anchored ones (CryptDmxUniverse,
|
|
10
|
+
# AesOStream, ...) — only RTTI strings exist (no symbolized methods).
|
|
11
|
+
#
|
|
12
|
+
# Plan:
|
|
13
|
+
# 1. Find every RTTI typeinfo-name string that contains "CryptDmxUniverse"
|
|
14
|
+
# or "AesOStream"/"AesIStream"/"Dmx" (broader net).
|
|
15
|
+
# 2. For each, walk xrefs back to the corresponding __ZTV* (vtable) — the
|
|
16
|
+
# vtable is a sequence of function pointers and its 2nd qword points to
|
|
17
|
+
# the typeinfo. So for each xref site that looks like a vtable, list the
|
|
18
|
+
# function pointers at +0x10, +0x18, +0x20, +0x28 and dump those functions.
|
|
19
|
+
# 3. Also: cross-ref _sendto (0x1006ed9c6) callers — the encrypt+send fn
|
|
20
|
+
# should appear among them. Dump every caller.
|
|
21
|
+
# 4. Print everything to tools/ghidra/out2/.
|
|
22
|
+
|
|
23
|
+
# @category Stick
|
|
24
|
+
|
|
25
|
+
import os, re
|
|
26
|
+
from ghidra.app.decompiler import DecompInterface
|
|
27
|
+
from ghidra.util.task import ConsoleTaskMonitor
|
|
28
|
+
from ghidra.program.model.symbol import RefType
|
|
29
|
+
|
|
30
|
+
OUT_DIR = os.path.join(os.path.dirname(getSourceFile().getAbsolutePath()), 'out2')
|
|
31
|
+
try:
|
|
32
|
+
os.makedirs(OUT_DIR)
|
|
33
|
+
except OSError:
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
prog = currentProgram
|
|
37
|
+
fm = prog.getFunctionManager()
|
|
38
|
+
st = prog.getSymbolTable()
|
|
39
|
+
af = prog.getAddressFactory()
|
|
40
|
+
mem = prog.getMemory()
|
|
41
|
+
listing = prog.getListing()
|
|
42
|
+
ref_mgr = prog.getReferenceManager()
|
|
43
|
+
monitor = ConsoleTaskMonitor()
|
|
44
|
+
|
|
45
|
+
decomp = DecompInterface()
|
|
46
|
+
decomp.openProgram(prog)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def addr(a):
|
|
50
|
+
return af.getDefaultAddressSpace().getAddress(a)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_ptr(a):
|
|
54
|
+
"""Read an 8-byte LE pointer from address a."""
|
|
55
|
+
try:
|
|
56
|
+
return mem.getLong(a) & 0xFFFFFFFFFFFFFFFF
|
|
57
|
+
except Exception:
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def dump_function_brief(func, out):
|
|
62
|
+
out.write(' FUNC %s @ %s (size %d)\n' % (func.getName(True),
|
|
63
|
+
func.getEntryPoint(), func.getBody().getNumAddresses()))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def dump_function_full(func, out):
|
|
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, 120, 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
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
# 1. Find RTTI typeinfo-name strings for DMX/Crypto classes
|
|
91
|
+
# ---------------------------------------------------------------------------
|
|
92
|
+
target_class_substrings = [
|
|
93
|
+
'CryptDmxUniverse', 'AesOStream', 'AesIStream',
|
|
94
|
+
'DasEccAesCryptography', 'DasDhRsaCryptography',
|
|
95
|
+
'DasEccStreamCryptography', 'DasCryptography',
|
|
96
|
+
'DmxUniverse', # base class candidates
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
rtti_strings = [] # list of (substring, name, address)
|
|
100
|
+
for sym in st.getAllSymbols(False):
|
|
101
|
+
name = sym.getName(True)
|
|
102
|
+
if '__ZTS' in name or 'typeinfo-name' in name:
|
|
103
|
+
for sub in target_class_substrings:
|
|
104
|
+
if sub in name:
|
|
105
|
+
rtti_strings.append((sub, name, sym.getAddress()))
|
|
106
|
+
break
|
|
107
|
+
elif '__ZTI' in name or 'typeinfo' in name:
|
|
108
|
+
for sub in target_class_substrings:
|
|
109
|
+
if sub in name:
|
|
110
|
+
rtti_strings.append((sub, name, sym.getAddress()))
|
|
111
|
+
break
|
|
112
|
+
elif '__ZTV' in name or 'vtable' in name:
|
|
113
|
+
for sub in target_class_substrings:
|
|
114
|
+
if sub in name:
|
|
115
|
+
rtti_strings.append((sub, name, sym.getAddress()))
|
|
116
|
+
break
|
|
117
|
+
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
# 2. Dump summary, vtable layouts, and full decomps of every vtable slot fn
|
|
120
|
+
# ---------------------------------------------------------------------------
|
|
121
|
+
summary_path = os.path.join(OUT_DIR, 'SUMMARY.txt')
|
|
122
|
+
seen_funcs = set()
|
|
123
|
+
out_summary = open(summary_path, 'w')
|
|
124
|
+
out_summary.write('Cipher-anchor search — ESA2 HardwareManager (2024-03-21)\n\n')
|
|
125
|
+
|
|
126
|
+
out_summary.write('=== RTTI/vtable symbols for DMX/Crypto classes ===\n')
|
|
127
|
+
for sub, name, a in rtti_strings:
|
|
128
|
+
out_summary.write(' [%s] %s @ %s\n' % (sub, name, a))
|
|
129
|
+
out_summary.write('\n')
|
|
130
|
+
|
|
131
|
+
# For every vtable symbol (__ZTV*), the layout is:
|
|
132
|
+
# [+0x00] offset-to-top (signed 64)
|
|
133
|
+
# [+0x08] typeinfo pointer
|
|
134
|
+
# [+0x10] virtual fn 0
|
|
135
|
+
# [+0x18] virtual fn 1
|
|
136
|
+
# [+0x20] virtual fn 2
|
|
137
|
+
# ...
|
|
138
|
+
# We saw sendDmx do ((vt+0x10))(...) — that's the *second function pointer*
|
|
139
|
+
# in the vtable structure if you measure from the typeinfo pointer; actually
|
|
140
|
+
# *((vptr)[2])(...) at C-source level. In the Ghidra decomp it's the value
|
|
141
|
+
# obtained as **((vptr+0x10) cast to ptr-to-ptr), which means vtable[+0x10]
|
|
142
|
+
# IS the first function-pointer slot (Itanium ABI: vptr points at the first
|
|
143
|
+
# fn slot, NOT the typeinfo header). So:
|
|
144
|
+
# - The address Ghidra calls "vtable @ X" is the START of the structure
|
|
145
|
+
# (offset-to-top); typical layout above.
|
|
146
|
+
# - "vptr" stored in objects = X + 0x10
|
|
147
|
+
# - vt[0] = mem[X+0x10], vt[1] = mem[X+0x18], ...
|
|
148
|
+
# We dump fn pointers at offsets +0x10 through +0x80 (16 slots) of any vtable.
|
|
149
|
+
|
|
150
|
+
vtable_syms = [(s, n, a) for (s, n, a) in rtti_strings if '__ZTV' in n or 'vtable' in n]
|
|
151
|
+
out_summary.write('=== Vtable layouts (first 16 fn slots) ===\n')
|
|
152
|
+
for sub, name, a in vtable_syms:
|
|
153
|
+
out_summary.write('\n--- %s @ %s ---\n' % (name, a))
|
|
154
|
+
base = int(a.getOffset())
|
|
155
|
+
# Skip first two qwords (offset-to-top, typeinfo); slots start at +0x10
|
|
156
|
+
for slot in range(0, 16):
|
|
157
|
+
slot_addr = addr(base + 0x10 + slot * 8)
|
|
158
|
+
fp = get_ptr(slot_addr)
|
|
159
|
+
if fp is None:
|
|
160
|
+
out_summary.write(' +0x%02x: <unmapped>\n' % (0x10 + slot * 8))
|
|
161
|
+
continue
|
|
162
|
+
if fp == 0:
|
|
163
|
+
out_summary.write(' +0x%02x: NULL — stop\n' % (0x10 + slot * 8))
|
|
164
|
+
break
|
|
165
|
+
fa = addr(fp)
|
|
166
|
+
f = fm.getFunctionAt(fa) or fm.getFunctionContaining(fa)
|
|
167
|
+
if f is None:
|
|
168
|
+
out_summary.write(' +0x%02x: 0x%x (no function)\n' %
|
|
169
|
+
(0x10 + slot * 8, fp))
|
|
170
|
+
continue
|
|
171
|
+
out_summary.write(' +0x%02x: %s @ %s\n' %
|
|
172
|
+
(0x10 + slot * 8, f.getName(True), f.getEntryPoint()))
|
|
173
|
+
if f.getEntryPoint() in seen_funcs:
|
|
174
|
+
continue
|
|
175
|
+
seen_funcs.add(f.getEntryPoint())
|
|
176
|
+
safe = re.sub(r'[^A-Za-z0-9._-]', '_', f.getName(True))[:120]
|
|
177
|
+
path = os.path.join(OUT_DIR, 'vt_%s_+%02x_%s.txt' %
|
|
178
|
+
(re.sub(r'[^A-Za-z0-9._-]', '_', name)[:60],
|
|
179
|
+
0x10 + slot * 8, safe))
|
|
180
|
+
with open(path, 'w') as o:
|
|
181
|
+
dump_function_full(f, o)
|
|
182
|
+
|
|
183
|
+
# ---------------------------------------------------------------------------
|
|
184
|
+
# 3. Cross-reference _sendto callers — the encrypt+send fn must be among them
|
|
185
|
+
# ---------------------------------------------------------------------------
|
|
186
|
+
out_summary.write('\n=== Callers of _sendto (0x1006ed9c6) ===\n')
|
|
187
|
+
sendto_addr = addr(0x1006ed9c6)
|
|
188
|
+
sendto_fn = fm.getFunctionAt(sendto_addr)
|
|
189
|
+
sendto_callers = set()
|
|
190
|
+
if sendto_fn is not None:
|
|
191
|
+
for ref in ref_mgr.getReferencesTo(sendto_addr):
|
|
192
|
+
from_addr = ref.getFromAddress()
|
|
193
|
+
caller = fm.getFunctionContaining(from_addr)
|
|
194
|
+
if caller is not None:
|
|
195
|
+
sendto_callers.add(caller.getEntryPoint())
|
|
196
|
+
|
|
197
|
+
for ep in sorted(sendto_callers):
|
|
198
|
+
f = fm.getFunctionAt(ep)
|
|
199
|
+
out_summary.write(' %s @ %s\n' % (f.getName(True), f.getEntryPoint()))
|
|
200
|
+
if ep in seen_funcs:
|
|
201
|
+
continue
|
|
202
|
+
seen_funcs.add(ep)
|
|
203
|
+
safe = re.sub(r'[^A-Za-z0-9._-]', '_', f.getName(True))[:120]
|
|
204
|
+
path = os.path.join(OUT_DIR, 'sendto_caller_%s_%s.txt' % (f.getEntryPoint(), safe))
|
|
205
|
+
with open(path, 'w') as o:
|
|
206
|
+
dump_function_full(f, o)
|
|
207
|
+
|
|
208
|
+
out_summary.close()
|
|
209
|
+
print('find-cipher: dumped %d functions to %s' % (len(seen_funcs), OUT_DIR))
|
|
210
|
+
print('find-cipher: summary at %s' % summary_path)
|