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