-
Notifications
You must be signed in to change notification settings - Fork 62
/
linux_qq_get_key.py
179 lines (154 loc) · 5 KB
/
linux_qq_get_key.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#!/usr/bin/env python3
import subprocess
import re
import hashlib
from pathlib import Path
QQ_CONFIG = Path.home() / '.config' / 'QQ'
WRAPPER_NODE_PATH = '/opt/QQ/resources/app/wrapper.node'
def error(msg):
print(f"\033[1;31m Error: {msg}\033[0m")
exit(1)
def Assert(condition, msg):
if not condition:
error(msg)
# get Program Header and Section Mapping
result = subprocess.run(["readelf", "-lW" , WRAPPER_NODE_PATH], stdout = subprocess.PIPE, text= True)
output = result.stdout
program_header = []
rodata_info = None
read_program_header = False
read_section_mapping = False
re_ph = re.compile(r"\s*(\w+)\s+0x(\d+)\s+0x(\d+).*")
re_mp = re.compile(r"\s*(\d+)\s+(.*)")
for line in output.splitlines():
line = line.strip()
if "Program Headers:" in line:
read_program_header = True
continue
if "Section to Segment mapping:" in line:
read_program_header = False
read_section_mapping = True
continue
if read_program_header:
m = re_ph.match(line)
if m:
program_header.append({
"type": m.group(1),
"section_offset": int(m.group(2), 16),
"virt_addr": int(m.group(3), 16)
})
if read_section_mapping:
m = re_mp.match(line)
if m:
(idx , sections) = m.groups()
if ".rodata" in sections:
rodata_info = program_header[int(idx)]
break
Assert(rodata_info is not None, "rodata section not found")
print(f"rodata_info: {rodata_info}")
# get string offset
result = subprocess.Popen(["strings", "-t" , "d" , WRAPPER_NODE_PATH], stdout = subprocess.PIPE, text= True)
pattern = re.compile('nt_sqlite3_key_v2: db=%p zDb=%s$')
for line in result.stdout:
line = line.strip()
if(pattern.search(line)):
offset = line.split(" ")[0]
Assert(offset.isdecimal(), "offset should be decimal")
result.terminate()
break
str_off = int(offset)
print(f"str_off offset: {hex(str_off)}")
# get reference offset
def get_file_hash(file_path):
hasher = hashlib.sha256()
with open(file_path, 'rb') as file:
while chunk := file.read(8192):
hasher.update(chunk)
return hasher.hexdigest()
ref_off_cache_file = Path.cwd() / 'ref_off_cache'
shoud_recalculate = True
if(Path.exists(ref_off_cache_file)):
with open(ref_off_cache_file, 'r') as f:
try:
file_hash = f.readline().strip()
ref_off = list(map(str, f.readline().strip().split()))
shoud_recalculate = file_hash != get_file_hash(WRAPPER_NODE_PATH) or len(ref_off) == 0
except Exception as e:
print(f"Error: {e}\n recalculate ref_off")
if(shoud_recalculate):
result = subprocess.Popen(["objdump", "-D" , "-j" , ".text" , WRAPPER_NODE_PATH], stdout = subprocess.PIPE, text= True)
str_addr = rodata_info["virt_addr"] + str_off - rodata_info["section_offset"]
pattern = re.compile(f".*# {str_addr:x}")
ref_off = []
for line in result.stdout:
line = line.strip()
if(pattern.search(line)):
offset = line.split(":")[0]
Assert(re.match(r'[0-9a-f]+', offset), "offset should be hex")
ref_off.append(offset)
result.terminate()
with open(ref_off_cache_file, 'w') as f:
f.write(f"{get_file_hash(WRAPPER_NODE_PATH)}\n")
f.write(" ".join(map(str, ref_off)) + "\n")
print(f"ref_off offset: {ref_off}")
import gdb
def exit_gdb():
gdb.execute("set confirm off")
gdb.execute("quit")
hook_stop_script = """
define hook-stop
x /10i $pc
p (char[16])*(char *)$rsi
p $rdx
end
"""
gdb.execute(hook_stop_script, to_string=True)
gdb.execute("set pagination off")
# load wrapper.node
gdb.execute("break dlopen")
gdb.execute("run")
finish = False
i = 20
while not finish:
output = gdb.execute("x /s file", to_string=True)
if i == 0:
finish = True
if "wrapper.node" in output:
break
gdb.execute("continue")
gdb.execute("finish")
output = gdb.execute("info proc mappings", to_string=True)
base_addr = None
for line in output.splitlines():
if "wrapper" in line:
base_addr= int(line.split()[0],16)
break
print(f"Base address found: {base_addr}")
# find function address
breakpoints = []
gdb.execute("delete breakpoints")
for ref in ref_off:
breakpoint_addr = base_addr + int(ref, 16)
gdb.execute(f"break *{breakpoint_addr}")
gdb.execute(f"x /10i {breakpoint_addr}")
gdb.execute("continue")
gdb.execute("finish")
func_addr = None
for i in range(16):
inst = gdb.execute(f"x /i $pc - {i}", to_string=True)
if "call" in inst:
func_addr = int(inst.split(" ")[-1], 16)
break
if func_addr is None:
print("Function address not found.")
exit_gdb()
print(f"func_addr: {hex(func_addr)}")
#get zDb
gdb.execute("delete breakpoints")
gdb.execute(f"break *{func_addr}")
gdb.execute(f"continue")
while(gdb.parse_and_eval("$rdx") != 16):
gdb.execute("continue")
zDb = gdb.parse_and_eval("(char[16])*(char *)$rsi")
print(f"zDb: {zDb}")
exit_gdb()