@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,341 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Path A — passive lldb memory scan for the Stick3 DMX AES key.
|
|
3
|
+
|
|
4
|
+
NO breakpoints. lldb attach stops the process; this script does a fast
|
|
5
|
+
in-memory scan and detaches. Common case = a few seconds stopped.
|
|
6
|
+
|
|
7
|
+
What it does:
|
|
8
|
+
1. Resolves the cipher vtable runtime address (file addr 0x10095F838 + slide).
|
|
9
|
+
2. Scans every readable+writable region for that 8-byte LE pointer value.
|
|
10
|
+
Each hit is the start of a cipher subobject (XHL_Stick3ANet device + 0x1618).
|
|
11
|
+
3. For each hit: dumps 0x800 bytes, reads the documented candidate fields,
|
|
12
|
+
and runs an AES-128 key-schedule auto-detector over the whole object.
|
|
13
|
+
4. Writes a log + raw object dumps + any recovered keys to /tmp.
|
|
14
|
+
|
|
15
|
+
If STICK_HEAP_DUMP=1 is set in the environment, it also dumps every rw region
|
|
16
|
+
to /tmp/stick-heap/ with a manifest, so tools/scan-aes-schedule.mjs can do a
|
|
17
|
+
heap-wide schedule search offline (fallback if step 3 finds nothing).
|
|
18
|
+
|
|
19
|
+
Usage (driven by tools/pathA-scan.sh):
|
|
20
|
+
lldb -p <PID> --batch \
|
|
21
|
+
-o "command script import tools/lldb_pathA_scan.py" \
|
|
22
|
+
-o "detach" -o "quit"
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import os
|
|
26
|
+
import struct
|
|
27
|
+
import time
|
|
28
|
+
|
|
29
|
+
# lldb is only present when run inside the debugger; guard the import so the
|
|
30
|
+
# pure-Python detector functions below stay unit-testable standalone.
|
|
31
|
+
try:
|
|
32
|
+
import lldb
|
|
33
|
+
except ImportError:
|
|
34
|
+
lldb = None
|
|
35
|
+
|
|
36
|
+
# --- known addresses (ESA2 2024-03-21 build, image vmaddrs) ---------------
|
|
37
|
+
CIPHER_VPTR_FILEADDR = 0x10095F838 # value stored at cipher_obj+0x00
|
|
38
|
+
CIPHER_OBJ_DUMP_LEN = 0x800 # how much of each cipher object to grab
|
|
39
|
+
|
|
40
|
+
# documented offsets within the cipher object (cipher_obj = device+0x1618)
|
|
41
|
+
OFF_MODE = 0x20 # 1 byte: 0=CBC, 1=CFB
|
|
42
|
+
OFF_KEY = 0x48 # candidate raw AES-128 key (16 bytes)
|
|
43
|
+
OFF_CBC_IV = 0x60 # candidate CBC chaining IV (16 bytes)
|
|
44
|
+
OFF_CFB_IV = 0xB0 # candidate CFB IV (16 bytes)
|
|
45
|
+
|
|
46
|
+
LOG_PATH = "/tmp/stick-pathA-scan.log"
|
|
47
|
+
HEAP_DIR = "/tmp/stick-heap"
|
|
48
|
+
|
|
49
|
+
# --- AES-128 key schedule detector ---------------------------------------
|
|
50
|
+
SBOX = bytes.fromhex(
|
|
51
|
+
"637c777bf26b6fc53001672bfed7ab76ca82c97dfa5947f0add4a2af9ca472c0"
|
|
52
|
+
"b7fd9326363ff7cc34a5e5f171d8311504c723c31896059a071280e2eb27b275"
|
|
53
|
+
"09832c1a1b6e5aa0523bd6b329e32f8453d100ed20fcb15b6acbbe394a4c58cf"
|
|
54
|
+
"d0efaafb434d338545f9027f503c9fa851a3408f929d38f5bcb6da2110fff3d2"
|
|
55
|
+
"cd0c13ec5f974417c4a77e3d645d197360814fdc222a908846eeb814de5e0bdb"
|
|
56
|
+
"e0323a0a4906245cc2d3ac629195e479e7c8376d8dd54ea96c56f4ea657aae08"
|
|
57
|
+
"ba78252e1ca6b4c6e8dd741f4bbd8b8a703eb5664803f60e613557b986c11d9e"
|
|
58
|
+
"e1f8981169d98e949b1e87e9ce5528df8ca1890dbfe6426841992d0fb054bb16"
|
|
59
|
+
)
|
|
60
|
+
RCON = (0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def aes128_schedule_check(blob):
|
|
64
|
+
"""If blob[0:176] is a valid AES-128 forward key schedule, return the
|
|
65
|
+
16-byte master key; else None."""
|
|
66
|
+
if len(blob) < 176:
|
|
67
|
+
return None
|
|
68
|
+
w = [blob[4 * i:4 * i + 4] for i in range(44)]
|
|
69
|
+
for i in range(4, 44):
|
|
70
|
+
t = w[i - 1]
|
|
71
|
+
if i % 4 == 0:
|
|
72
|
+
# RotWord -> SubWord -> xor Rcon
|
|
73
|
+
t = bytes((SBOX[t[1]], SBOX[t[2]], SBOX[t[3]], SBOX[t[0]]))
|
|
74
|
+
t = bytes((t[0] ^ RCON[i // 4 - 1], t[1], t[2], t[3]))
|
|
75
|
+
exp = bytes((w[i - 4][k] ^ t[k] for k in range(4)))
|
|
76
|
+
if exp != w[i]:
|
|
77
|
+
return None
|
|
78
|
+
return blob[0:16]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def find_schedules(blob, base_label):
|
|
82
|
+
"""Slide a 176-byte window over blob; return list of (offset, key)."""
|
|
83
|
+
hits = []
|
|
84
|
+
limit = len(blob) - 176
|
|
85
|
+
o = 0
|
|
86
|
+
while o <= limit:
|
|
87
|
+
key = aes128_schedule_check(blob[o:o + 176])
|
|
88
|
+
if key is not None:
|
|
89
|
+
hits.append((o, key))
|
|
90
|
+
o += 1
|
|
91
|
+
return hits
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# --- helpers --------------------------------------------------------------
|
|
95
|
+
_logf = None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def log(msg=""):
|
|
99
|
+
global _logf
|
|
100
|
+
print(msg)
|
|
101
|
+
if _logf:
|
|
102
|
+
_logf.write(msg + "\n")
|
|
103
|
+
_logf.flush()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def hexsp(b):
|
|
107
|
+
return " ".join("%02x" % x for x in b)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def read_mem(process, addr, size):
|
|
111
|
+
err = lldb.SBError()
|
|
112
|
+
data = process.ReadMemory(addr, size, err)
|
|
113
|
+
if err.Success() and data:
|
|
114
|
+
return data
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def find_main_module(target):
|
|
119
|
+
for m in target.module_iter():
|
|
120
|
+
fs = m.GetFileSpec()
|
|
121
|
+
name = fs.GetFilename()
|
|
122
|
+
if name == "HardwareManager":
|
|
123
|
+
return m
|
|
124
|
+
# fall back to module 0 (the main executable)
|
|
125
|
+
return target.GetModuleAtIndex(0) if target.GetNumModules() else None
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def rw_regions(process):
|
|
129
|
+
"""Yield (base, end) for readable+writable memory regions."""
|
|
130
|
+
regions = process.GetMemoryRegions()
|
|
131
|
+
for i in range(regions.GetSize()):
|
|
132
|
+
info = lldb.SBMemoryRegionInfo()
|
|
133
|
+
if not regions.GetMemoryRegionAtIndex(i, info):
|
|
134
|
+
continue
|
|
135
|
+
if info.IsReadable() and info.IsWritable():
|
|
136
|
+
yield info.GetRegionBase(), info.GetRegionEnd()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def scan_for_pattern(process, pattern):
|
|
140
|
+
"""Return list of runtime addresses where the 8-byte pattern occurs in
|
|
141
|
+
rw memory."""
|
|
142
|
+
hits = []
|
|
143
|
+
CHUNK = 8 * 1024 * 1024
|
|
144
|
+
plen = len(pattern)
|
|
145
|
+
for base, end in rw_regions(process):
|
|
146
|
+
size = end - base
|
|
147
|
+
if size <= 0 or size > 4 * 1024 * 1024 * 1024:
|
|
148
|
+
continue
|
|
149
|
+
addr = base
|
|
150
|
+
carry = b""
|
|
151
|
+
while addr < end:
|
|
152
|
+
n = min(CHUNK, end - addr)
|
|
153
|
+
data = read_mem(process, addr, n)
|
|
154
|
+
if data is None:
|
|
155
|
+
addr += n
|
|
156
|
+
carry = b""
|
|
157
|
+
continue
|
|
158
|
+
# buf[0] sits at memory address (addr - len(carry))
|
|
159
|
+
buf_base = addr - len(carry)
|
|
160
|
+
buf = carry + data
|
|
161
|
+
start = 0
|
|
162
|
+
while True:
|
|
163
|
+
idx = buf.find(pattern, start)
|
|
164
|
+
if idx < 0:
|
|
165
|
+
break
|
|
166
|
+
hits.append(buf_base + idx)
|
|
167
|
+
start = idx + 1
|
|
168
|
+
carry = buf[-(plen - 1):] if plen > 1 else b""
|
|
169
|
+
addr += n
|
|
170
|
+
return hits
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def dump_heap(process):
|
|
174
|
+
"""Dump every rw region to HEAP_DIR with a manifest (fallback path)."""
|
|
175
|
+
try:
|
|
176
|
+
os.makedirs(HEAP_DIR, exist_ok=True)
|
|
177
|
+
except Exception as e:
|
|
178
|
+
log(" heap dump: mkdir failed: %s" % e)
|
|
179
|
+
return
|
|
180
|
+
manifest = open(os.path.join(HEAP_DIR, "manifest.txt"), "w")
|
|
181
|
+
total = 0
|
|
182
|
+
idx = 0
|
|
183
|
+
CHUNK = 16 * 1024 * 1024
|
|
184
|
+
for base, end in rw_regions(process):
|
|
185
|
+
size = end - base
|
|
186
|
+
if size <= 0 or size > 2 * 1024 * 1024 * 1024:
|
|
187
|
+
continue
|
|
188
|
+
path = os.path.join(HEAP_DIR, "region-%03d-0x%x.bin" % (idx, base))
|
|
189
|
+
f = open(path, "wb")
|
|
190
|
+
got = 0
|
|
191
|
+
addr = base
|
|
192
|
+
while addr < end:
|
|
193
|
+
n = min(CHUNK, end - addr)
|
|
194
|
+
data = read_mem(process, addr, n)
|
|
195
|
+
if data is None:
|
|
196
|
+
break
|
|
197
|
+
f.write(data)
|
|
198
|
+
got += len(data)
|
|
199
|
+
addr += len(data)
|
|
200
|
+
f.close()
|
|
201
|
+
manifest.write("0x%x %d %s\n" % (base, got, os.path.basename(path)))
|
|
202
|
+
total += got
|
|
203
|
+
idx += 1
|
|
204
|
+
manifest.close()
|
|
205
|
+
log(" heap dump: %d regions, %.1f MiB -> %s" %
|
|
206
|
+
(idx, total / 1048576.0, HEAP_DIR))
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def inspect_cipher_obj(process, addr, n):
|
|
210
|
+
"""Dump + analyse one cipher object found at runtime address `addr`."""
|
|
211
|
+
log("")
|
|
212
|
+
log("=" * 70)
|
|
213
|
+
log("CIPHER OBJECT @ 0x%x" % addr)
|
|
214
|
+
log("=" * 70)
|
|
215
|
+
|
|
216
|
+
blob = read_mem(process, addr, CIPHER_OBJ_DUMP_LEN)
|
|
217
|
+
if blob is None:
|
|
218
|
+
log(" !! could not read object memory")
|
|
219
|
+
return []
|
|
220
|
+
|
|
221
|
+
# raw dump of the first 0x200 bytes (16 bytes/line)
|
|
222
|
+
log(" raw bytes 0x000..0x200:")
|
|
223
|
+
for off in range(0, 0x200, 16):
|
|
224
|
+
log(" +0x%03x %s" % (off, hexsp(blob[off:off + 16])))
|
|
225
|
+
|
|
226
|
+
# documented candidate fields
|
|
227
|
+
mode = blob[OFF_MODE]
|
|
228
|
+
log("")
|
|
229
|
+
log(" documented candidate fields:")
|
|
230
|
+
log(" +0x%02x mode flag = 0x%02x (%s)" %
|
|
231
|
+
(OFF_MODE, mode, "CFB" if mode == 1 else "CBC" if mode == 0 else "??"))
|
|
232
|
+
log(" +0x%02x key(16) = %s" % (OFF_KEY, hexsp(blob[OFF_KEY:OFF_KEY + 16])))
|
|
233
|
+
log(" +0x%02x CBC IV(16)= %s" % (OFF_CBC_IV, hexsp(blob[OFF_CBC_IV:OFF_CBC_IV + 16])))
|
|
234
|
+
log(" +0x%02x CFB IV(16)= %s" % (OFF_CFB_IV, hexsp(blob[OFF_CFB_IV:OFF_CFB_IV + 16])))
|
|
235
|
+
|
|
236
|
+
# AES-128 key schedule auto-detect over the whole object
|
|
237
|
+
scheds = find_schedules(blob, "obj@0x%x" % addr)
|
|
238
|
+
log("")
|
|
239
|
+
if scheds:
|
|
240
|
+
log(" *** AES-128 KEY SCHEDULE DETECTED (%d) ***" % len(scheds))
|
|
241
|
+
for off, key in scheds:
|
|
242
|
+
log(" +0x%03x master key = %s" % (off, hexsp(key)))
|
|
243
|
+
else:
|
|
244
|
+
log(" no valid AES-128 key schedule in this object")
|
|
245
|
+
log(" (impl may store only the 16-byte raw key — see +0x%02x above," % OFF_KEY)
|
|
246
|
+
log(" or expand the schedule on the fly per frame)")
|
|
247
|
+
return scheds
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def __lldb_init_module(debugger, internal_dict):
|
|
251
|
+
global _logf
|
|
252
|
+
_logf = open(LOG_PATH, "w")
|
|
253
|
+
|
|
254
|
+
target = debugger.GetSelectedTarget()
|
|
255
|
+
if not target or not target.IsValid():
|
|
256
|
+
log("ERROR: no target — attach with `lldb -p <PID>` first")
|
|
257
|
+
return
|
|
258
|
+
process = target.GetProcess()
|
|
259
|
+
if not process or not process.IsValid():
|
|
260
|
+
log("ERROR: no live process")
|
|
261
|
+
return
|
|
262
|
+
|
|
263
|
+
log("=== Path A passive scan %s ===" % time.strftime("%Y-%m-%d %H:%M:%S"))
|
|
264
|
+
log("pid=%d state=%s" % (process.GetProcessID(), process.GetState()))
|
|
265
|
+
|
|
266
|
+
mod = find_main_module(target)
|
|
267
|
+
if not mod:
|
|
268
|
+
log("ERROR: HardwareManager module not found")
|
|
269
|
+
return
|
|
270
|
+
|
|
271
|
+
# resolve cipher vptr file address -> runtime address (handles ASLR slide)
|
|
272
|
+
sb = mod.ResolveFileAddress(CIPHER_VPTR_FILEADDR)
|
|
273
|
+
vptr_runtime = sb.GetLoadAddress(target)
|
|
274
|
+
if vptr_runtime == lldb.LLDB_INVALID_ADDRESS:
|
|
275
|
+
log("ERROR: could not resolve cipher vptr 0x%x to a runtime address"
|
|
276
|
+
% CIPHER_VPTR_FILEADDR)
|
|
277
|
+
log(" module=%s" % mod.GetFileSpec())
|
|
278
|
+
return
|
|
279
|
+
slide = vptr_runtime - CIPHER_VPTR_FILEADDR
|
|
280
|
+
log("module : %s" % mod.GetFileSpec())
|
|
281
|
+
log("ASLR slide : 0x%x" % slide)
|
|
282
|
+
log("cipher vptr : fileaddr 0x%x -> runtime 0x%x"
|
|
283
|
+
% (CIPHER_VPTR_FILEADDR, vptr_runtime))
|
|
284
|
+
|
|
285
|
+
pattern = struct.pack("<Q", vptr_runtime)
|
|
286
|
+
log("scan pattern : %s" % hexsp(pattern))
|
|
287
|
+
log("")
|
|
288
|
+
log("scanning rw memory for the cipher vtable pointer ...")
|
|
289
|
+
|
|
290
|
+
t0 = time.time()
|
|
291
|
+
hits = scan_for_pattern(process, pattern)
|
|
292
|
+
log("scan done in %.1fs — %d hit(s)" % (time.time() - t0, len(hits)))
|
|
293
|
+
|
|
294
|
+
all_keys = []
|
|
295
|
+
for i, addr in enumerate(hits):
|
|
296
|
+
scheds = inspect_cipher_obj(process, addr, CIPHER_OBJ_DUMP_LEN)
|
|
297
|
+
# save the raw object dump
|
|
298
|
+
blob = read_mem(process, addr, CIPHER_OBJ_DUMP_LEN)
|
|
299
|
+
if blob:
|
|
300
|
+
objpath = "/tmp/stick-pathA-obj-%d-0x%x.bin" % (i, addr)
|
|
301
|
+
with open(objpath, "wb") as f:
|
|
302
|
+
f.write(blob)
|
|
303
|
+
log(" raw object saved -> %s" % objpath)
|
|
304
|
+
for off, key in scheds:
|
|
305
|
+
all_keys.append((addr, off, key))
|
|
306
|
+
|
|
307
|
+
# save any recovered keys
|
|
308
|
+
log("")
|
|
309
|
+
log("=" * 70)
|
|
310
|
+
if all_keys:
|
|
311
|
+
log("RECOVERED %d AES-128 KEY(S):" % len(all_keys))
|
|
312
|
+
seen = set()
|
|
313
|
+
n = 0
|
|
314
|
+
for addr, off, key in all_keys:
|
|
315
|
+
hexk = key.hex()
|
|
316
|
+
if hexk in seen:
|
|
317
|
+
continue
|
|
318
|
+
seen.add(hexk)
|
|
319
|
+
kp = "/tmp/stick-pathA-key-%d.bin" % n
|
|
320
|
+
with open(kp, "wb") as f:
|
|
321
|
+
f.write(key)
|
|
322
|
+
log(" key %d: %s (obj 0x%x +0x%x) -> %s"
|
|
323
|
+
% (n, hexsp(key), addr, off, kp))
|
|
324
|
+
n += 1
|
|
325
|
+
log("")
|
|
326
|
+
log("NEXT: capture a 576B frame and verify, e.g.:")
|
|
327
|
+
log(" node tools/try-key.mjs <frame.bin> $(xxd -p /tmp/stick-pathA-key-0.bin)")
|
|
328
|
+
else:
|
|
329
|
+
log("No AES-128 key schedule auto-detected.")
|
|
330
|
+
log("Inspect the +0x48 raw-key candidates in the per-object dumps above,")
|
|
331
|
+
log("or set STICK_HEAP_DUMP=1 and re-run for a heap-wide offline scan.")
|
|
332
|
+
|
|
333
|
+
if os.environ.get("STICK_HEAP_DUMP") == "1":
|
|
334
|
+
log("")
|
|
335
|
+
log("STICK_HEAP_DUMP=1 — dumping rw regions for offline scan ...")
|
|
336
|
+
dump_heap(process)
|
|
337
|
+
log(" then run: node tools/scan-aes-schedule.mjs %s" % HEAP_DIR)
|
|
338
|
+
|
|
339
|
+
log("")
|
|
340
|
+
log("scan complete — detaching. log: %s" % LOG_PATH)
|
|
341
|
+
_logf.close()
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Path C-lite — runtime key-trace via one-shot lldb breakpoints.
|
|
3
|
+
|
|
4
|
+
When the passive scan can't find the key (it isn't a byte-contiguous window
|
|
5
|
+
of the cipher object), the key/round-schedule is either expanded transiently
|
|
6
|
+
on the stack per frame, or stored somewhere a register points to during
|
|
7
|
+
encryption. This catches it: breakpoint the encrypt path, and the instant it
|
|
8
|
+
fires, dump every register + the memory it points at + the stack, then run
|
|
9
|
+
the AES key-schedule detector over all of it.
|
|
10
|
+
|
|
11
|
+
NOT a persistent breakpoint. It fires, captures once, deletes all
|
|
12
|
+
breakpoints, and the batch detaches — HWM is stopped only ~1-2s total
|
|
13
|
+
(same as the passive scan).
|
|
14
|
+
|
|
15
|
+
Breakpoints (ESA2 2024-03-21 file addrs, from the Ghidra RE):
|
|
16
|
+
0x100107950 encrypt entry (mode dispatch) -> capture device, keep going
|
|
17
|
+
0x1003f5980 AES block, CBC path -> capture stack+regs, stop
|
|
18
|
+
0x1003f4690 AES block, CFB path -> capture stack+regs, stop
|
|
19
|
+
If no block bp fires within 6 frames, capture at the entry and stop anyway,
|
|
20
|
+
so the batch always terminates cleanly (no HWM kill on hang).
|
|
21
|
+
|
|
22
|
+
Usage (driven by tools/pathC-keytrace.sh):
|
|
23
|
+
lldb -p <PID> --batch \
|
|
24
|
+
-o "command script import tools/lldb_pathC_keytrace.py" \
|
|
25
|
+
-o "continue" -o "detach" -o "quit"
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
import os
|
|
29
|
+
import time
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
import lldb
|
|
33
|
+
except ImportError:
|
|
34
|
+
lldb = None
|
|
35
|
+
|
|
36
|
+
# --- breakpoint file addresses (image vmaddrs) ----------------------------
|
|
37
|
+
FA_ENTRY = 0x100107950
|
|
38
|
+
FA_CBC_BLOCK = 0x1003F5980
|
|
39
|
+
FA_CFB_BLOCK = 0x1003F4690
|
|
40
|
+
CIPHER_VPTR_FILEADDR = 0x10095F838
|
|
41
|
+
|
|
42
|
+
LOG_PATH = "/tmp/stick-pathC-keytrace.log"
|
|
43
|
+
|
|
44
|
+
# --- AES key-schedule detectors (AES-128 and AES-256) --------------------
|
|
45
|
+
SBOX = bytes.fromhex(
|
|
46
|
+
"637c777bf26b6fc53001672bfed7ab76ca82c97dfa5947f0add4a2af9ca472c0"
|
|
47
|
+
"b7fd9326363ff7cc34a5e5f171d8311504c723c31896059a071280e2eb27b275"
|
|
48
|
+
"09832c1a1b6e5aa0523bd6b329e32f8453d100ed20fcb15b6acbbe394a4c58cf"
|
|
49
|
+
"d0efaafb434d338545f9027f503c9fa851a3408f929d38f5bcb6da2110fff3d2"
|
|
50
|
+
"cd0c13ec5f974417c4a77e3d645d197360814fdc222a908846eeb814de5e0bdb"
|
|
51
|
+
"e0323a0a4906245cc2d3ac629195e479e7c8376d8dd54ea96c56f4ea657aae08"
|
|
52
|
+
"ba78252e1ca6b4c6e8dd741f4bbd8b8a703eb5664803f60e613557b986c11d9e"
|
|
53
|
+
"e1f8981169d98e949b1e87e9ce5528df8ca1890dbfe6426841992d0fb054bb16"
|
|
54
|
+
)
|
|
55
|
+
RCON = (0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def aes128_schedule_check(b):
|
|
59
|
+
if len(b) < 176:
|
|
60
|
+
return None
|
|
61
|
+
w = [b[4 * i:4 * i + 4] for i in range(44)]
|
|
62
|
+
for i in range(4, 44):
|
|
63
|
+
t = w[i - 1]
|
|
64
|
+
if i % 4 == 0:
|
|
65
|
+
t = bytes((SBOX[t[1]], SBOX[t[2]], SBOX[t[3]], SBOX[t[0]]))
|
|
66
|
+
t = bytes((t[0] ^ RCON[i // 4 - 1], t[1], t[2], t[3]))
|
|
67
|
+
if bytes((w[i - 4][k] ^ t[k] for k in range(4))) != w[i]:
|
|
68
|
+
return None
|
|
69
|
+
return b[0:16]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def aes256_schedule_check(b):
|
|
73
|
+
if len(b) < 240:
|
|
74
|
+
return None
|
|
75
|
+
w = [b[4 * i:4 * i + 4] for i in range(60)]
|
|
76
|
+
for i in range(8, 60):
|
|
77
|
+
t = w[i - 1]
|
|
78
|
+
if i % 8 == 0:
|
|
79
|
+
t = bytes((SBOX[t[1]], SBOX[t[2]], SBOX[t[3]], SBOX[t[0]]))
|
|
80
|
+
t = bytes((t[0] ^ RCON[i // 8 - 1], t[1], t[2], t[3]))
|
|
81
|
+
elif i % 8 == 4:
|
|
82
|
+
t = bytes((SBOX[t[0]], SBOX[t[1]], SBOX[t[2]], SBOX[t[3]]))
|
|
83
|
+
if bytes((w[i - 8][k] ^ t[k] for k in range(4))) != w[i]:
|
|
84
|
+
return None
|
|
85
|
+
return b[0:32]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def find_schedules(blob):
|
|
89
|
+
"""Return list of (offset, bits, key) for every AES schedule in blob."""
|
|
90
|
+
hits = []
|
|
91
|
+
for o in range(0, len(blob) - 176):
|
|
92
|
+
k = aes128_schedule_check(blob[o:o + 176])
|
|
93
|
+
if k is not None:
|
|
94
|
+
hits.append((o, 128, k))
|
|
95
|
+
if o < len(blob) - 240:
|
|
96
|
+
k2 = aes256_schedule_check(blob[o:o + 240])
|
|
97
|
+
if k2 is not None:
|
|
98
|
+
hits.append((o, 256, k2))
|
|
99
|
+
return hits
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# --- state ---------------------------------------------------------------
|
|
103
|
+
_logf = None
|
|
104
|
+
_entry_hits = 0
|
|
105
|
+
_done = False
|
|
106
|
+
_blobs = [] # list of (label, addr, bytes)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def log(msg=""):
|
|
110
|
+
global _logf
|
|
111
|
+
print(msg)
|
|
112
|
+
if _logf:
|
|
113
|
+
_logf.write(msg + "\n")
|
|
114
|
+
_logf.flush()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def hexsp(b):
|
|
118
|
+
return " ".join("%02x" % x for x in b)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def read_mem(process, addr, size):
|
|
122
|
+
err = lldb.SBError()
|
|
123
|
+
d = process.ReadMemory(addr, size, err)
|
|
124
|
+
return d if (err.Success() and d) else None
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def cipher_vptr_runtime(target):
|
|
128
|
+
for m in target.module_iter():
|
|
129
|
+
if m.GetFileSpec().GetFilename() == "HardwareManager":
|
|
130
|
+
a = m.ResolveFileAddress(CIPHER_VPTR_FILEADDR).GetLoadAddress(target)
|
|
131
|
+
return a if a != lldb.LLDB_INVALID_ADDRESS else 0
|
|
132
|
+
return 0
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def capture(frame, where):
|
|
136
|
+
"""Dump registers + pointed-to memory + stack + (if found) the cipher
|
|
137
|
+
object / device struct. Accumulate into _blobs."""
|
|
138
|
+
thread = frame.GetThread()
|
|
139
|
+
process = thread.GetProcess()
|
|
140
|
+
target = process.GetTarget()
|
|
141
|
+
vptr = cipher_vptr_runtime(target)
|
|
142
|
+
|
|
143
|
+
log("")
|
|
144
|
+
log("=" * 68)
|
|
145
|
+
log("CAPTURE @ %s" % where)
|
|
146
|
+
log("=" * 68)
|
|
147
|
+
|
|
148
|
+
gpr = ["rdi", "rsi", "rdx", "rcx", "r8", "r9", "rax", "rbx",
|
|
149
|
+
"rbp", "rsp", "r10", "r11", "r12", "r13", "r14", "r15", "rip"]
|
|
150
|
+
regs = {}
|
|
151
|
+
for r in gpr:
|
|
152
|
+
v = frame.FindRegister(r)
|
|
153
|
+
regs[r] = v.GetValueAsUnsigned() if v.IsValid() else 0
|
|
154
|
+
log(" %-4s = 0x%x" % (r, regs[r]))
|
|
155
|
+
|
|
156
|
+
# dump memory each plausible pointer register points at
|
|
157
|
+
for r in ["rdi", "rsi", "rdx", "rcx", "r8", "r9", "rax", "rbx",
|
|
158
|
+
"r12", "r13", "r14", "r15"]:
|
|
159
|
+
a = regs[r]
|
|
160
|
+
if 0x10000 < a < 0x7fffffffffff:
|
|
161
|
+
d = read_mem(process, a, 768)
|
|
162
|
+
if d:
|
|
163
|
+
_blobs.append(("%s_ptr_0x%x" % (r, a), a, d))
|
|
164
|
+
# if this points at the cipher object, also grab the whole
|
|
165
|
+
# device struct (cipher subobject is at device + 0x1618)
|
|
166
|
+
if vptr and len(d) >= 8 and int.from_bytes(d[0:8], "little") == vptr:
|
|
167
|
+
dev = a - 0x1618
|
|
168
|
+
big = read_mem(process, dev, 0x3800)
|
|
169
|
+
if big:
|
|
170
|
+
_blobs.append(("device_0x%x" % dev, dev, big))
|
|
171
|
+
log(" %s -> cipher object; dumped device struct @ 0x%x" % (r, dev))
|
|
172
|
+
|
|
173
|
+
# the stack — a transiently-expanded schedule lives here during encrypt
|
|
174
|
+
rsp = regs["rsp"]
|
|
175
|
+
st = read_mem(process, rsp - 0x400, 0x4400)
|
|
176
|
+
if st:
|
|
177
|
+
_blobs.append(("stack_0x%x" % (rsp - 0x400), rsp - 0x400, st))
|
|
178
|
+
log(" dumped stack 0x%x .. 0x%x" % (rsp - 0x400, rsp - 0x400 + len(st)))
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def finalize():
|
|
182
|
+
global _done
|
|
183
|
+
if _done:
|
|
184
|
+
return
|
|
185
|
+
_done = True
|
|
186
|
+
|
|
187
|
+
log("")
|
|
188
|
+
log("=" * 68)
|
|
189
|
+
log("ANALYSIS — %d memory blob(s) captured" % len(_blobs))
|
|
190
|
+
log("=" * 68)
|
|
191
|
+
|
|
192
|
+
# save every blob; run the schedule detector across all of them
|
|
193
|
+
found = []
|
|
194
|
+
for label, addr, data in _blobs:
|
|
195
|
+
path = "/tmp/stick-pathC-blob-%s.bin" % label
|
|
196
|
+
with open(path, "wb") as f:
|
|
197
|
+
f.write(data)
|
|
198
|
+
scheds = find_schedules(data)
|
|
199
|
+
log(" %-26s %6d B @ 0x%x -> %s"
|
|
200
|
+
% (label, len(data), addr,
|
|
201
|
+
"%d schedule(s)!" % len(scheds) if scheds else "saved"))
|
|
202
|
+
for off, bits, key in scheds:
|
|
203
|
+
log(" *** AES-%d KEY @ %s+0x%x = %s"
|
|
204
|
+
% (bits, label, off, hexsp(key)))
|
|
205
|
+
found.append((bits, key))
|
|
206
|
+
|
|
207
|
+
log("")
|
|
208
|
+
if found:
|
|
209
|
+
log("*** KEY(S) RECOVERED FROM A LIVE ROUND-KEY SCHEDULE ***")
|
|
210
|
+
seen = set()
|
|
211
|
+
n = 0
|
|
212
|
+
for bits, key in found:
|
|
213
|
+
h = key.hex()
|
|
214
|
+
if h in seen:
|
|
215
|
+
continue
|
|
216
|
+
seen.add(h)
|
|
217
|
+
with open("/tmp/stick-pathC-key-%d.bin" % n, "wb") as f:
|
|
218
|
+
f.write(key)
|
|
219
|
+
log(" key %d (AES-%d): %s" % (n, bits, hexsp(key)))
|
|
220
|
+
n += 1
|
|
221
|
+
log("")
|
|
222
|
+
log("verify: node tools/try-key.mjs <frame.bin> <key-hex>")
|
|
223
|
+
else:
|
|
224
|
+
log("No standard AES schedule in the captured memory.")
|
|
225
|
+
log("The raw key may still be in these blobs in a non-standard layout.")
|
|
226
|
+
log("Brute-force them against a captured frame:")
|
|
227
|
+
log(" node tools/crack-key.mjs <frame.bin> /tmp/stick-pathC-blob-*.bin")
|
|
228
|
+
log("")
|
|
229
|
+
log("done — detaching. log: %s" % LOG_PATH)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
# --- breakpoint callbacks ------------------------------------------------
|
|
233
|
+
def on_entry(frame, bp_loc, internal_dict):
|
|
234
|
+
global _entry_hits
|
|
235
|
+
_entry_hits += 1
|
|
236
|
+
if _entry_hits == 1:
|
|
237
|
+
capture(frame, "encrypt-entry FUN_100107950 (frame 1)")
|
|
238
|
+
if _entry_hits >= 6:
|
|
239
|
+
log("")
|
|
240
|
+
log("no AES-block breakpoint fired in %d frames — finalizing at entry"
|
|
241
|
+
% _entry_hits)
|
|
242
|
+
finalize()
|
|
243
|
+
return True # stop
|
|
244
|
+
return False # keep running so a block bp can fire
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def on_block(frame, bp_loc, internal_dict):
|
|
248
|
+
capture(frame, "aes-block (CBC/CFB path)")
|
|
249
|
+
finalize()
|
|
250
|
+
return True # stop
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def __lldb_init_module(debugger, internal_dict):
|
|
254
|
+
global _logf
|
|
255
|
+
_logf = open(LOG_PATH, "w")
|
|
256
|
+
|
|
257
|
+
target = debugger.GetSelectedTarget()
|
|
258
|
+
if not target or not target.IsValid():
|
|
259
|
+
log("ERROR: no target")
|
|
260
|
+
return
|
|
261
|
+
process = target.GetProcess()
|
|
262
|
+
if not process or not process.IsValid():
|
|
263
|
+
log("ERROR: no live process")
|
|
264
|
+
return
|
|
265
|
+
|
|
266
|
+
log("=== Path C key-trace %s ===" % time.strftime("%Y-%m-%d %H:%M:%S"))
|
|
267
|
+
|
|
268
|
+
mod = None
|
|
269
|
+
for m in target.module_iter():
|
|
270
|
+
if m.GetFileSpec().GetFilename() == "HardwareManager":
|
|
271
|
+
mod = m
|
|
272
|
+
break
|
|
273
|
+
if not mod:
|
|
274
|
+
log("ERROR: HardwareManager module not found")
|
|
275
|
+
return
|
|
276
|
+
|
|
277
|
+
def runtime(fa):
|
|
278
|
+
return mod.ResolveFileAddress(fa).GetLoadAddress(target)
|
|
279
|
+
|
|
280
|
+
plan = [
|
|
281
|
+
("encrypt-entry", FA_ENTRY, on_entry),
|
|
282
|
+
("cbc-block", FA_CBC_BLOCK, on_block),
|
|
283
|
+
("cfb-block", FA_CFB_BLOCK, on_block),
|
|
284
|
+
]
|
|
285
|
+
for name, fa, cb in plan:
|
|
286
|
+
addr = runtime(fa)
|
|
287
|
+
if addr == lldb.LLDB_INVALID_ADDRESS:
|
|
288
|
+
log(" WARNING: could not resolve %s (0x%x)" % (name, fa))
|
|
289
|
+
continue
|
|
290
|
+
bp = target.BreakpointCreateByAddress(addr)
|
|
291
|
+
if bp.IsValid():
|
|
292
|
+
bp.SetScriptCallbackFunction("lldb_pathC_keytrace.%s" % cb.__name__)
|
|
293
|
+
bp.SetAutoContinue(False)
|
|
294
|
+
log(" bp %-14s file 0x%x -> runtime 0x%x id=%d"
|
|
295
|
+
% (name, fa, addr, bp.GetID()))
|
|
296
|
+
else:
|
|
297
|
+
log(" ERROR: breakpoint at 0x%x failed" % addr)
|
|
298
|
+
|
|
299
|
+
log("")
|
|
300
|
+
log("breakpoints armed — `continue` now; will capture on first hit.")
|