Skip to content

Commit 53bfe85

Browse files
scripts: add dump_user.py to display license info from an idb file.
1 parent e673013 commit 53bfe85

File tree

1 file changed

+138
-0
lines changed

1 file changed

+138
-0
lines changed

scripts/dump_user.py

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
#!/usr/bin/env python
2+
'''
3+
Parse and display license information from an IDA Pro database.
4+
5+
author: Willi Ballenthin
6+
email: willi.ballenthin@gmail.com
7+
'''
8+
import sys
9+
import struct
10+
import logging
11+
import datetime
12+
13+
import argparse
14+
15+
import idb
16+
import idb.netnode
17+
18+
19+
logger = logging.getLogger(__name__)
20+
21+
22+
def is_encrypted(buf):
23+
return buf.find(b'\x00' * 4) >= 0x80
24+
25+
26+
HEXRAYS_PUBKEY = 0x93AF7A8E3A6EB93D1B4D1FB7EC29299D2BC8F3CE5F84BFE88E47DDBDD5550C3CE3D2B16A2E2FBD0FBD919E8038BB05752EC92DD1498CB283AA087A93184F1DD9DD5D5DF7857322DFCD70890F814B58448071BBABB0FC8A7868B62EB29CC2664C8FE61DFBC5DB0EE8BF6ECF0B65250514576C4384582211896E5478F95C42FDED
27+
28+
29+
def decrypt(buf):
30+
'''
31+
decrypt the given 1024-bit blob using Hex-Ray's public key.
32+
33+
i'm not sure from where this public key originally came.
34+
the algorithm is derived from here:
35+
https://github.com/nlitsme/pyidbutil/blob/87cb3235a462774eedfafca00f67c3ce01eeb326/idbtool.py#L43
36+
37+
Args:
38+
buf (bytes): at least 0x80 bytes, of which the first 1024 bits will be decrypted.
39+
40+
Returns:
41+
bytes: 0x80 bytes of decrypted data.
42+
'''
43+
enc = int.from_bytes(buf[:0x80], 'little')
44+
dec = pow(enc, 0x13, HEXRAYS_PUBKEY)
45+
return dec.to_bytes(0x80, 'big')
46+
47+
48+
def parse_user_data(buf):
49+
'''
50+
parse a decrypted user blob into a structured dictionary.
51+
52+
Args:
53+
buf (bytes): exactly 0x80 bytes of plaintext data.
54+
55+
Returns:
56+
Dict[str, Any]: a dictionary with the following values:
57+
- ts1 (datetime.datetime): timestamp in UTC of something. database creation?
58+
- ts2 (datetime.datetime): timestamp in UTC of something. sometimes zero.
59+
- id (str): the ID of the license.
60+
- name (str): the name of the user and organization that owns the license.
61+
'''
62+
if len(buf) != 0x80:
63+
raise ValueError('invalid user blob.')
64+
65+
version = struct.unpack_from('<H', buf, 0x3)
66+
if version == 0:
67+
raise NotImplementedError('user blob version not supported.')
68+
69+
ts1, _, ts2 = struct.unpack_from('<III', buf, 0x11)
70+
id = "%02X-%02X%02X-%02X%02X-%02X" % struct.unpack_from("6B", buf, 0x1D)
71+
name = buf[0x23:buf.find(b'\x00', 0x23)].decode('utf-8')
72+
73+
return {
74+
# unknown if these are in UTC or not. right now, assuming so.
75+
'ts1': datetime.datetime.utcfromtimestamp(ts1),
76+
'ts2': datetime.datetime.utcfromtimestamp(ts2),
77+
'id': id,
78+
'name': name,
79+
}
80+
81+
82+
def get_userdata(netnode):
83+
'''
84+
fetch, decrypt, and parse the user data from the given netnode.
85+
86+
Args:
87+
netnode (ida_netnode.Netnode): the netnode containing the user data.
88+
89+
Returns:
90+
dict[str, Any]: see `parse_user_data`.
91+
'''
92+
userdata = netnode.supval(0x0)
93+
94+
if is_encrypted(userdata):
95+
userdata = decrypt(userdata)
96+
else:
97+
userdata = userdata[:0x80]
98+
99+
return parse_user_data(userdata)
100+
101+
102+
def main(argv=None):
103+
if argv is None:
104+
argv = sys.argv[1:]
105+
106+
parser = argparse.ArgumentParser(description="Parse and display license information from an IDA Pro database.")
107+
parser.add_argument("idbpath", type=str,
108+
help="Path to input idb file")
109+
parser.add_argument("-v", "--verbose", action="store_true",
110+
help="Enable debug logging")
111+
parser.add_argument("-q", "--quiet", action="store_true",
112+
help="Disable all output but errors")
113+
args = parser.parse_args(args=argv)
114+
115+
if args.verbose:
116+
logging.basicConfig(level=logging.DEBUG)
117+
logging.getLogger().setLevel(logging.DEBUG)
118+
elif args.quiet:
119+
logging.basicConfig(level=logging.ERROR)
120+
logging.getLogger().setLevel(logging.ERROR)
121+
else:
122+
logging.basicConfig(level=logging.INFO)
123+
logging.getLogger().setLevel(logging.INFO)
124+
125+
with idb.from_file(args.idbpath) as db:
126+
api = idb.IDAPython(db)
127+
data = get_userdata(api.ida_netnode.netnode('$ original user'))
128+
129+
print('user: %s' % data['name'])
130+
print('id: %s' % data['id'])
131+
print('ts1: %s' % data['ts1'].isoformat(' ') + 'Z')
132+
print('ts2: %s' % data['ts2'].isoformat(' ') + 'Z')
133+
134+
return 0
135+
136+
137+
if __name__ == "__main__":
138+
sys.exit(main())

0 commit comments

Comments
 (0)