-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathanalysis_utils.py
More file actions
199 lines (159 loc) · 6.84 KB
/
analysis_utils.py
File metadata and controls
199 lines (159 loc) · 6.84 KB
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
import re
import semver
from utils import create_r2_byte_pattern
def get_sem_ver(exe_file: str) -> str:
"""Parses the semver from the exe_file name and returns it.
Args:
exe_file (str): The path to the executable file.
Returns:
str: The semantic version string.
"""
res = re.match(r".*ffxiv_dx11\.(\d).(\d)(\d)(\w?)(\d?)\.exe", exe_file)
sem_ver = f"{res.group(1)}.{res.group(2)}.{res.group(3)}"
if res.group(4) != "":
sem_ver = f"{sem_ver}+{res.group(4)}"
return sem_ver
def packet_handler_sig(sem_ver: str) -> str:
"""Returns the signature for Client::Network::PacketDispatcher_OnReceivePacket
for Zone packets.
Args:
sem_ver (str): The semantic version string.
"""
if semver.compare(sem_ver, "7.3.0") >= 0:
return "48 89 5C 24 ? 55 56 57 41 54 41 55 41 56 41 57 48 8D AC 24 ? ? ? ? B8 ? ? ? ? E8 ? ? ? ? 48 2B E0 48 8B 05 ? ? ? ? 48 33 C4 48 89 85 ? ? ? ? 45 0F B7"
elif semver.compare(sem_ver, "7.2.0") >= 0:
return "40 55 53 56 57 41 55 41 56 41 57 48 8D AC 24 ? ? ? ? B8 ? ? ? ? E8 ? ? ? ? 48 2B E0 48 8B 05 ? ? ? ? 48 33 C4 48 89 85 ? ? ? ? 45 0F B7 78 ?"
elif semver.compare(sem_ver, "6.4.0") >= 0:
return "40 53 56 48 81 EC ? ? ? ? 48 8B 05 ? ? ? ? 48 33 C4 48 89 44 24 ? 8B F2"
else:
return "48 89 ? 24 ? ? 48 83 EC 50 8B F2 49 8B"
def get_packet_handler_addr(r2, exe_file: str) -> str:
"""Get the address for the Client::Network::PacketDispatcher_OnReceivePacket
for Zone packets.
Args:
r2: An instance of r2pipe.
exe_file (str): The path to the executable file.
Returns:
str: The hex address of the packet dispatch handler.
"""
sem_ver = get_sem_ver(exe_file)
p = create_r2_byte_pattern(packet_handler_sig(sem_ver))
result = r2.cmd(f"/x {p}").split() # Find byte pattern
if len(result) == 0:
raise ValueError(
"Could not find packet handler address. This could be "
"because of an incorrect signature or a transient Radare failure."
)
return result[0]
def packet_handler_switch_sig(sem_ver: str) -> str:
"""Returns the signature for approximately a couple instructions before the
actual zone packet handler switch.
Args:
sem_ver (str): The semantic version string.
"""
if semver.compare(sem_ver, "7.3.0") >= 0:
return "E8 ? ? ? ? 41 83 ? ? 49 8B ? 41 81"
elif semver.compare(sem_ver, "7.2.0") >= 0:
return "E8 ? ? ? ? 41 83 C7 ? EB 1B"
elif semver.compare(sem_ver, "6.4.0") >= 0:
return "40 53 56 48 81 EC ? ? ? ? 48 8B 05 ? ? ? ? 48 33 C4 48 89 44 24 ? 8B F2"
else:
return "48 89 ? 24 ? ? 48 83 EC 50 8B F2 49 8B"
def get_packet_handler_switch_addr(r2, exe_file: str) -> str:
"""Gets the address of the approximate position where the zone packet
handler switch is located.
Args:
r2 - An instance of r2pipe.
exe_file (str): The path to the executable file.
Returns:
str: The hex address that can be used to find the opcode offset.
"""
sem_ver = get_sem_ver(exe_file)
p = create_r2_byte_pattern(packet_handler_switch_sig(sem_ver))
result = r2.cmd(f"/x {p}").split() # Find byte pattern
if len(result) == 0:
raise ValueError(
"Could not find packet handler switch address. This could be "
"because of an incorrect signature or a transient Radare failure."
)
return result[0]
def _get_opcode_offset_post_72(r2, reg):
r2.cmd("aeso") # step over call
r2.cmd(f"aer {reg}=0x500") # set reg to some arbitrary number
r2.cmd("aeso") # step
regs = r2.cmdj("arj")
return 0x500 - regs[reg]
def _get_opcode_offset_pre_72(r2):
r2.cmd("aecc") # continue until call
r2.cmd("aer rax=0x0") # set rax to 0
r2.cmd('"aesue rax,0x0,>"') # continue until rax changes?
r2.cmd("aer rdx=0x200") # set rdx to some arbitrary number
r2.cmd("aeso") # step
regs = r2.cmdj("arj")
return regs["rdx"] - regs["rax"]
def get_packet_handler_opcode_offset(r2, exe_file: str):
"""Gets the offset that maps the actual opcode to a switch case in the
zone packet handler.
Args:
r2 - An instance of r2pipe.
exe_file (str): The path to the executable file.
"""
sem_ver = get_sem_ver(exe_file)
# The opcode offset can be found somewhere right before the packet handler
# switch
opcode_offset_target = get_packet_handler_switch_addr(r2, exe_file)
orig_loc = r2.cmd("s") # Save original spot
r2.cmd(f"s {opcode_offset_target}")
r2.cmd("aei; aeim; aeip") # Initialize ESIL VM, stack, and instruction pointer
if semver.compare(sem_ver, "7.4.0") >= 0:
offset_reg = "r15"
opcode_offset = _get_opcode_offset_post_72(r2, offset_reg)
elif semver.compare(sem_ver, "7.3.8") >= 0:
offset_reg = "r13"
opcode_offset = _get_opcode_offset_post_72(r2, offset_reg)
elif semver.compare(sem_ver, "7.3.0") == 0 and semver.parse(sem_ver)["build"] == "":
offset_reg = "r13"
opcode_offset = _get_opcode_offset_post_72(r2, offset_reg)
elif semver.compare(sem_ver, "7.2.0") >= 0:
offset_reg = "r15"
opcode_offset = _get_opcode_offset_post_72(r2, offset_reg)
else:
opcode_offset = _get_opcode_offset_pre_72(r2)
# Clear the ESIL environment
r2.cmd("ar0; aeim-; aei-")
r2.cmd(f"s {orig_loc}") # Seek back to original spot
return opcode_offset
def get_correct_switch(approx_ea: str, switch_cases: list):
"""Despite seeking directly to the packet switch, we still get multiple
switches from analysis, so we need to find the right one.
Args:
approx_ea (str): The approximate address of the packet switch.
switch_cases (list): The list of switch cases to search through.
Returns:
(str, dict): The found switch address and the corresponding switch
"""
switches = dict()
approx_ea = int(approx_ea, 16)
pattern = re.compile(r"case\.(0x[0-9a-fA-F]+)\.(\d+)")
for l in switch_cases:
match = pattern.match(l["name"])
if match is not None:
switch_ea = match[1]
case_ea = l["addr"]
if switch_ea not in switches:
switches[switch_ea] = dict()
if case_ea not in switches[switch_ea]:
switches[switch_ea][case_ea] = {
"opcodes": [],
}
switches[switch_ea][case_ea]["opcodes"].append(match[2])
found_switch = None
longest_switch = dict()
for switch_ea in switches:
int_switch_ea = int(switch_ea, 16)
if int_switch_ea < approx_ea or int_switch_ea > approx_ea + 0x100:
continue
if len(switches[switch_ea].keys()) > len(longest_switch):
found_switch = switch_ea
longest_switch = switches[switch_ea]
return found_switch, longest_switch