1
+ import ida_idp
2
+ import ida_auto
3
+ import idc
4
+ import ida_funcs
5
+ import ida_hexrays
6
+ import ida_bytes
7
+ import ida_ua
8
+ import re
9
+ import keystone
10
+ import stitcher # https://github.com/allthingsida/allthingsida/blob/main/ctfs/y0da_flareon10/sticher.py
11
+
12
+ ks = keystone .Ks (keystone .KS_ARCH_X86 , keystone .KS_MODE_64 )
13
+ mxcsr_loc = 0x140097AF0 + 0x7f0000
14
+ base = 0x140097AF0
15
+
16
+ CONTEXT_STRUCT = {
17
+ 0x00000030 : "ContextFlags" ,
18
+ 0x00000034 : "mxcsr" ,
19
+ 0x00000038 : "SegCs" ,
20
+ 0x0000003A : "SegDs" ,
21
+ 0x0000003C : "SegEs" ,
22
+ 0x0000003E : "SegFs" ,
23
+ 0x00000040 : "SegGs" ,
24
+ 0x00000042 : "SegSs" ,
25
+ 0x00000044 : "EFlags" ,
26
+ 0x00000048 : "Dr0" ,
27
+ 0x00000050 : "Dr1" ,
28
+ 0x00000058 : "Dr2" ,
29
+ 0x00000060 : "Dr3" ,
30
+ 0x00000068 : "Dr6" ,
31
+ 0x00000070 : "Dr7" ,
32
+ 0x00000078 : "rax" ,
33
+ 0x00000080 : "rcx" ,
34
+ 0x00000088 : "rdx" ,
35
+ 0x00000090 : "rbx" ,
36
+ 0x00000098 : "rsp" ,
37
+ 0x000000A0 : "rbp" ,
38
+ 0x000000A8 : "rsi" ,
39
+ 0x000000B0 : "rdi" ,
40
+ 0x000000B8 : "r8" ,
41
+ 0x000000C0 : "r9" ,
42
+ 0x000000C8 : "r10" ,
43
+ 0x000000D0 : "r11" ,
44
+ 0x000000D8 : "r12" ,
45
+ 0x000000E0 : "r13" ,
46
+ 0x000000E8 : "r14" ,
47
+ 0x000000F0 : "r15" ,
48
+ 0x000000F8 : "rip" ,
49
+ }
50
+
51
+ class UWOP_CODES :
52
+ UWOP_PUSH_NONVOL = 0
53
+ UWOP_ALLOC_LARGE = 1
54
+ UWOP_ALLOC_SMALL = 2
55
+ UWOP_SET_FPREG = 3
56
+ UWOP_SAVE_NONVOL = 4
57
+ UWOP_SAVE_NONVOL_FAR = 5
58
+ UWOP_EPILOG = 6
59
+ UWOP_SPARE_CODE = 7
60
+ UWOP_SAVE_XMM128 = 8
61
+ UWOP_SAVE_XMM128_FAR = 9
62
+ UWOP_PUSH_MACHFRAME = 10
63
+
64
+ codes = UWOP_CODES ()
65
+
66
+ # in this function, we can secretly fixup encrypted instructions
67
+ def disassemble_at (address ):
68
+ insn_to_disassemble = ida_ua .insn_t ()
69
+ cur = address
70
+
71
+ while True :
72
+ cur += ida_ua .decode_insn (insn_to_disassemble , cur )
73
+
74
+ if insn_to_disassemble .get_canon_mnem () == "call" :
75
+ call_loc = insn_to_disassemble .ip
76
+
77
+ routine = call_loc + ida_bytes .get_dword (call_loc + 1 ) + 5
78
+ len_decrypted_insn = ida_bytes .get_byte (routine + 2 ) - 0x29
79
+
80
+ insn = ida_ua .insn_t ()
81
+
82
+ ida_ua .decode_insn (insn , routine )
83
+ ida_bytes .patch_bytes (insn .Op1 .addr , int (call_loc + 5 - (base & 0xffff )).to_bytes (8 , 'little' )) # we resolve the dependency of call_loc+5
84
+
85
+ ida_ua .decode_insn (insn , routine + 14 )
86
+ ah = ida_bytes .get_byte (insn .Op2 .addr ) # we depend on insn.Op2.addr
87
+
88
+ ida_ua .decode_insn (insn , routine + 20 )
89
+ decrypted_insn = (((ah << 8 ) + insn .Op2 .addr ) & 0xffffffff ).to_bytes (4 , 'little' ) + ida_bytes .get_bytes (routine + 0x26 , len_decrypted_insn - 4 )
90
+ ida_bytes .patch_bytes (routine + 0x22 , decrypted_insn )
91
+ tmp = ida_ua .insn_t ()
92
+ ida_ua .create_insn (routine + 0x22 )
93
+ ida_ua .decode_insn (tmp , routine + 0x22 )
94
+
95
+ if decrypted_insn [0 ] == 0xe9 :
96
+ absolute_loc = (int .from_bytes (decrypted_insn [1 :5 ], 'little' ) + 5 + (routine + 0x22 )) & 0xffffffff
97
+ new_rela_offset = (absolute_loc - (5 + call_loc )) & 0xffffffff
98
+ decrypted_insn = decrypted_insn [0 :1 ] + new_rela_offset .to_bytes (4 , 'little' ) + decrypted_insn [5 :]
99
+ elif tmp .Op1 .type == 0x1 and tmp .Op2 .type == 0x2 :
100
+ decrypted_insn = bytes (ks .asm (f"lea { ida_idp .get_reg_name (tmp .Op1 .reg , 8 )} , ds:{ hex (tmp .Op2 .addr )} " , addr = call_loc )[0 ])
101
+ if len (decrypted_insn ) > len_decrypted_insn :
102
+ call_loc = call_loc - (len (decrypted_insn ) - len_decrypted_insn )
103
+ decrypted_insn = bytes (ks .asm (f"lea { ida_idp .get_reg_name (tmp .Op1 .reg , 8 )} , ds:{ hex (tmp .Op2 .addr )} " , addr = call_loc )[0 ])
104
+ elif len (decrypted_insn ) < len_decrypted_insn :
105
+ decrypted_insn += b"\x90 " * (len_decrypted_insn - len (decrypted_insn ))
106
+
107
+ ida_bytes .patch_bytes (call_loc , decrypted_insn )
108
+ print (f"successfully patched { hex (call_loc )} " )
109
+
110
+ insn_to_disassemble = ida_ua .insn_t ()
111
+ ida_ua .create_insn (call_loc )
112
+ cur = call_loc + ida_ua .decode_insn (insn_to_disassemble , call_loc )
113
+
114
+ if insn_to_disassemble .get_canon_mnem () == "jmp" :
115
+ if insn_to_disassemble .Op1 .addr :
116
+ cur = insn_to_disassemble .Op1 .addr
117
+ else :
118
+ print ("funny jump at" , hex (cur ))
119
+ yield insn_to_disassemble
120
+
121
+ halt_address = base
122
+ to_be_stitched = [base ]
123
+
124
+ for i in range (33 ):
125
+ while True :
126
+ # get next exception handler
127
+ unwind_info = halt_address + ida_bytes .get_byte (halt_address + 1 ) + 2
128
+ unwind_info += int ((unwind_info & 1 ) != 0 )
129
+ count_of_codes = ida_bytes .get_byte (unwind_info + 2 )
130
+ handler_offs = ida_bytes .get_dword (unwind_info + 2 * (count_of_codes + int ((count_of_codes & 1 ) != 0 ))+ 4 )
131
+ exception_handler = base + handler_offs
132
+
133
+ unwind_instructions = []
134
+
135
+ # unwind stack
136
+ unwind_codes = ida_bytes .get_bytes (unwind_info + 4 ,count_of_codes * 2 )
137
+ i = 0
138
+ OFFSET = 0
139
+ RSP_DEREF_OFFSET = 0
140
+ REG_USED = False
141
+ RSP_DEREFED = False
142
+ FINAL_REG = None
143
+ while (i < count_of_codes * 2 ):
144
+ match (unwind_codes [i + 1 ] & 0xf ):
145
+ case codes .UWOP_PUSH_NONVOL :
146
+ FINAL_REG = CONTEXT_STRUCT [0x78 + (unwind_codes [i + 1 ] >> 4 ) * 8 ]
147
+ print ("UWOP_PUSH_NONVOL" )
148
+ i += 2
149
+ case codes .UWOP_ALLOC_LARGE :
150
+ if (unwind_codes [i + 1 ] >> 4 ):
151
+ if REG_USED or RSP_DEREFED :
152
+ OFFSET += int .from_bytes (unwind_codes [i + 2 :i + 6 ], 'little' )
153
+ else :
154
+ RSP_DEREF_OFFSET += int .from_bytes (unwind_codes [i + 2 :i + 6 ], 'little' )
155
+ print ("UWOP_ALLOC_LARGE" )
156
+ i += 6
157
+ else :
158
+ if REG_USED or RSP_DEREFED :
159
+ OFFSET += int .from_bytes (unwind_codes [i + 2 :i + 4 ], 'little' ) * 8
160
+ else :
161
+ RSP_DEREF_OFFSET += int .from_bytes (unwind_codes [i + 2 :i + 4 ], 'little' ) * 8
162
+ print ("UWOP_ALLOC_LARGE" )
163
+ i += 4
164
+ case codes .UWOP_ALLOC_SMALL :
165
+ if REG_USED or RSP_DEREFED :
166
+ OFFSET += ((unwind_codes [i + 1 ] >> 4 ) * 8 ) + 8
167
+ else :
168
+ RSP_DEREF_OFFSET += ((unwind_codes [i + 1 ] >> 4 ) * 8 ) + 8
169
+ print ("UWOP_ALLOC_SMALL" )
170
+ i += 2
171
+ case codes .UWOP_SET_FPREG :
172
+ REG_USED = CONTEXT_STRUCT [0x78 + (ida_bytes .get_byte (unwind_info + 3 ) & 0xf ) * 8 ]
173
+ OFFSET -= (ida_bytes .get_byte (unwind_info + 3 ) >> 4 ) * 16
174
+ print ("UWOP_SET_FPREG" )
175
+ i += 2
176
+ case codes .UWOP_PUSH_MACHFRAME : # RSP is dereferenced!
177
+ RSP_DEREF_OFFSET += (unwind_codes [i + 1 ] >> 4 ) * 8
178
+ RSP_DEREF_OFFSET += 0x18
179
+ RSP_DEREFED = True
180
+ print ("UWOP_PUSH_MACHFRAME" )
181
+ i += 2
182
+ case _:
183
+ print (f"count of codes: { count_of_codes } \n unwind codes: { unwind_instructions } \n unwind info: { hex (unwind_info )} " )
184
+ print ("@@@@@@@@@@@@@@@@ i donut recognize this opcode" )
185
+ break
186
+
187
+ if count_of_codes :
188
+ unwind_instructions = []
189
+ if REG_USED :
190
+ unwind_instructions .append (ks .asm (f"mov { FINAL_REG } , { REG_USED } " )[0 ])
191
+ unwind_instructions .append (ks .asm (f"mov { FINAL_REG } , [{ FINAL_REG } +{ OFFSET } ]" )[0 ])
192
+ elif RSP_DEREFED :
193
+ unwind_instructions .append (ks .asm (f"mov { FINAL_REG } , [rsp+{ RSP_DEREF_OFFSET } ]" )[0 ])
194
+ unwind_instructions .append (ks .asm (f"mov { FINAL_REG } , [{ FINAL_REG } +{ OFFSET } ]" )[0 ])
195
+ else :
196
+ print (f"count of codes: { count_of_codes } \n unwind codes: { unwind_instructions } \n unwind info: { hex (unwind_info )} " )
197
+ print ("i do not know how to resolve this..." )
198
+ raise Exception
199
+
200
+ unwind_instructions = b"" .join ([bytes (i ) for i in unwind_instructions ])
201
+ ida_bytes .patch_bytes (halt_address , unwind_instructions )
202
+ ida_ua .create_insn (halt_address )
203
+ halt_address += len (unwind_instructions )
204
+
205
+
206
+ # fixup halt
207
+ insn = b"\xe9 "
208
+ insn += (exception_handler - halt_address - 5 ).to_bytes (4 , 'little' )
209
+ ida_bytes .patch_bytes (halt_address , insn )
210
+ ida_ua .create_insn (halt_address )
211
+
212
+
213
+ # fixup exception handler, we disassemble until next 'hlt'
214
+ gen = disassemble_at (exception_handler )
215
+ context_record_register = False
216
+ final_insns = [] # stores the final sets of instructions for each stub
217
+ tainted = [] # stores registers that are written to. this is to know which registers has been tainted since the last stub
218
+ unused_registers = ["r10" , "r11" , "r8" , "rax" , "rcx" , "rdx" , "rdi" , "rsi" , "rbx" , "r12" , "r13" , "r14" , "r15" , "rbp" , "r9" ] # stores all registers that has yet to be used in the stub
219
+ tainted_resolve = {}
220
+
221
+ while True :
222
+ x = next (gen )
223
+ raw = generate_disasm_line (x .ip , 1 )
224
+
225
+ if (x .Op1 .type in [0x1 , 0x2 , 0x3 , 0x4 ]):
226
+ r = ida_idp .get_reg_name (x .Op1 .reg , 8 )
227
+ if r in unused_registers :
228
+ unused_registers .remove (r )
229
+
230
+ if (x .Op2 .type in [0x1 , 0x2 , 0x3 , 0x4 ]):
231
+ r = ida_idp .get_reg_name (x .Op2 .reg , 8 )
232
+ if r in unused_registers :
233
+ unused_registers .remove (r )
234
+
235
+ if (x .Op3 .type in [0x1 , 0x2 , 0x3 , 0x4 ]):
236
+ r = ida_idp .get_reg_name (x .Op3 .reg , 8 )
237
+ if r in unused_registers :
238
+ unused_registers .remove (r )
239
+
240
+ # case 1: mnemonic is ldmxcsr, and its not a nop
241
+ if "ldmxcsr" in raw .lower ():
242
+ if CONTEXT_STRUCT [x .Op1 .addr ] != "mxcsr" :
243
+ new = f"mov ds:{ mxcsr_loc } , { CONTEXT_STRUCT [x .Op1 .addr ]} "
244
+ r = CONTEXT_STRUCT [x .Op1 .addr ]
245
+ if r in unused_registers :
246
+ unused_registers .remove (r )
247
+ final_insns .append (new )
248
+ print (raw , "=>" , new )
249
+
250
+ # case 2: mov rXX, [r9+0x28] <-- we are setting the context record
251
+ elif x .get_canon_mnem () == "mov" and x .Op2 .reg == 0x9 and x .Op2 .addr == 0x28 :
252
+ context_record_register = ida_idp .get_reg_name (x .Op1 .reg , 8 )
253
+ print (f"context record is stored in { context_record_register } " )
254
+
255
+ # case 3: we are using our context record register
256
+ elif context_record_register and context_record_register in raw :
257
+
258
+ if (context_record_register in ida_idp .get_reg_name (x .Op2 .reg , 8 ) and x .Op2 .type == 0x4 ) or (context_record_register in ida_idp .get_reg_name (x .Op1 .reg , 8 ) and x .Op1 .type == 0x4 ):
259
+
260
+ subj = re .findall (r"\[" + context_record_register + r"\+\w+?h]" , raw )[0 ]
261
+ offs = int (re .findall (r"\+(\w+?)h" , subj )[0 ], 16 )
262
+
263
+ reg_to_use = CONTEXT_STRUCT [offs ]
264
+ r = CONTEXT_STRUCT [offs ]
265
+ if r in unused_registers :
266
+ unused_registers .remove (r )
267
+
268
+ if reg_to_use in tainted :
269
+ # if reg_to_use in tainted_resolve:
270
+ # reg_to_use = tainted_resolve[reg_to_use]
271
+ # else:
272
+ tainted_resolve [reg_to_use ] = unused_registers .pop (0 )
273
+ final_insns .insert (0 , f"mov { tainted_resolve [reg_to_use ]} , { reg_to_use } " )
274
+ reg_to_use = tainted_resolve [reg_to_use ]
275
+ print ("TAINTED" , hex (x .ip ))
276
+
277
+ if reg_to_use == "mxcsr" :
278
+ new = raw .replace (subj , f"ds:{ mxcsr_loc } " )
279
+ final_insns .append (new )
280
+ print (raw , "=>" , new )
281
+ # we have to account for the case where the context register is tainted
282
+ else :
283
+ new = raw .replace (subj , reg_to_use )
284
+ final_insns .append (new )
285
+ print (raw , "=>" , new )
286
+ else :
287
+ final_insns .append (raw )
288
+
289
+ # case 3a: we are modifying our context record register in Op1
290
+ if context_record_register in ida_idp .get_reg_name (x .Op1 .reg , 8 ):
291
+ context_record_register = False
292
+
293
+ if x .Op1 .type == 0x1 :
294
+ tainted .append (ida_idp .get_reg_name (x .Op1 .reg , 8 ))
295
+
296
+ else :
297
+ if x .Op1 .type == 0x1 :
298
+ tainted .append (ida_idp .get_reg_name (x .Op1 .reg , 8 ))
299
+
300
+ if raw [:2 ] == "db" :
301
+ raise Exception
302
+ final_insns .append (raw )
303
+
304
+ if x .get_canon_mnem () == "hlt" :
305
+ halt_address = x .ip
306
+ print ("halt at" ,hex (halt_address ))
307
+ ida_ua .create_insn (halt_address )
308
+ break
309
+
310
+ cur = exception_handler
311
+ kill = False
312
+ for insn in final_insns [:- 2 ]:
313
+ if ';' in insn :
314
+ insn = insn .split (";" )[0 ]
315
+ if 'loc_' in insn :
316
+ insn = insn .replace ("loc_" , "ds:0x" )
317
+ if 'unk_' in insn :
318
+ insn = insn .replace ("unk_" , "ds:0x" )
319
+ if 'jmp' in insn :
320
+ kill = True
321
+ i = ks .asm (insn , addr = cur )[0 ]
322
+ ida_bytes .patch_bytes (cur , bytes (i ))
323
+ cur += len (i )
324
+
325
+ i = ks .asm (f"jmp { halt_address } " , addr = cur )[0 ]
326
+ ida_bytes .patch_bytes (cur , bytes (i ))
327
+ if kill :
328
+ break
329
+
330
+ if i != 32 :
331
+ stage_addr = int ("0x" + re .findall (r"unk_(\w+)" , final_insns [- 2 ])[0 ], 16 )
332
+ to_be_stitched .append (stage_addr )
333
+
334
+ # create stage functions
335
+ i = 1
336
+ for idx , stage_addr in enumerate (to_be_stitched [:- 1 ]):
337
+ ida_funcs .add_func (stage_addr )
338
+ stitcher .stitch (do_stitch = True , addr = stage_addr )
339
+ ida_auto .auto_wait ()
340
+ idc .set_name (stage_addr , f"stage{ i } " )
341
+ i += 1
342
+
343
+ # give sane symbol names
344
+ xor_table = 0x140094AC0
345
+ or_table = 0x1400952C0
346
+ addition_table = 0x140095AC0
347
+ overflow_table = 0x1400962C0
348
+ subtraction_table = 0x140096AC0
349
+ underflow_table = 0x1400972C0
350
+ tables = [xor_table , or_table , addition_table , overflow_table , subtraction_table , underflow_table ]
351
+
352
+ for i in range (256 ):
353
+ for j in tables :
354
+ idc .create_qword (j + i * 8 )
355
+ idc .set_name (xor_table + i * 8 , f"xor_table_{ hex (i )[2 :].zfill (2 )} " )
356
+ idc .set_name (addition_table + i * 8 , f"addition_table_{ hex (i )[2 :].zfill (2 )} " )
357
+ idc .set_name (subtraction_table + i * 8 , f"subtraction_table_{ hex (i )[2 :].zfill (2 )} " )
358
+ idc .set_name (underflow_table + i * 8 , f"underflow_table_{ hex (i )[2 :].zfill (2 )} " )
359
+ idc .set_name (overflow_table + i * 8 , f"overflow_table_{ hex (i )[2 :].zfill (2 )} " )
360
+ idc .set_name (or_table + i * 8 , f"or_table_{ hex (i )[2 :].zfill (2 )} " )
361
+
362
+ # dump decompilation
363
+ for i in range (1 , 33 ):
364
+ with open (rf"C:\Users\user\Desktop\Flare-On 11\9\serpentine\decompilations\stage{ i } .txt" , "w" ) as f :
365
+ f .write (str (ida_hexrays .decompile (idc .get_name_ea (0 , f"stage{ i } " ))))
0 commit comments