├── README.md ├── mazeloader_patcher.py └── maze_patcher.py /README.md: -------------------------------------------------------------------------------- 1 | Repository for Maze deobfuscation scripts. You can find the explanation of the different techniques used in Blueliv's Maze blogpost. 2 | -------------------------------------------------------------------------------- /mazeloader_patcher.py: -------------------------------------------------------------------------------- 1 | 2 | import idc, idautils, idaapi 3 | 4 | segment_name = ".text" 5 | 6 | # Function addrs from sample e5feb48ba722996c71c55ddc8b4648cdbbc1fc382e9b0bfcae904273e10ef57d where the Control Flow Flattening was found 7 | # In order to limit the pattern matching into the function 8 | init_func = 0x405DC0 9 | end_func = 0x407DC9 10 | 11 | def resolve_opaque_jnz_jz(): 12 | 13 | PATTERNS = ["0F 84 ?? ?? ?? ?? 0F 85 ?? ?? ?? ??", "0F 85 ?? ?? ?? ?? 0F 84 ?? ?? ?? ??"] 14 | 15 | count_patched = 0 16 | count_not_patched = 0 17 | 18 | for pattern in PATTERNS: 19 | 20 | ea = 0 21 | 22 | while ea != BADADDR: 23 | 24 | ea = idc.FindBinary(ea, SEARCH_NEXT|SEARCH_DOWN|SEARCH_CASE, pattern) 25 | 26 | ''' 27 | pattern: 0F 85 ?? ?? ?? ?? 0F 84 ?? ?? ?? ?? 28 | .text:0040690E 66 21 CF and di, cx 29 | .text:00406911 0F 85 AE F4 FF FF jnz loc_405DC5 <- j_1_pos 30 | .text:00406917 0F 84 A8 F4 FF FF jz loc_405DC5 <- j_2_pos 31 | 32 | patched: 33 | .text:0040690E 66 21 CF and di, cx 34 | .text:00406911 90 nop 35 | .text:00406912 90 nop 36 | .text:00406913 90 nop 37 | .text:00406914 90 nop 38 | .text:00406915 90 nop 39 | .text:00406916 90 nop 40 | .text:00406917 E9 A9 F4 FF FF jmp loc_405DC5 41 | 42 | ''' 43 | 44 | if ea_in_bounds(ea): 45 | 46 | # .text:00406911 0F 85 AE F4 FF FF jnz loc_405DC5 <- j_1_pos 47 | # AE F4 FF FF <- j_1_value Relative offset value 48 | j_1_pos = ea 49 | j_1_value = Dword( j_1_pos + 0x2 ) 50 | 51 | j_2_pos = j_1_pos + 0x6 52 | j_2_value = Dword( j_2_pos + 0x2 ) 53 | 54 | pos_jmp = j_1_pos + j_1_value + 0x6 55 | 56 | if j_1_value - j_2_value == 0x6: 57 | 58 | addr_to_jmp = j_2_value + 0x1 59 | 60 | # Patch the jz and jnz instructions with NOPs (12 bytes) 61 | for i in range(0, 12): 62 | idc.PatchByte(j_1_pos + i, 0x90) 63 | 64 | # Patch with a relative jmp (size = 5) in the position of the second conditional jmp 65 | idc.PatchByte (j_2_pos, 0xE9) 66 | idc.PatchDword(j_2_pos + 0x1, addr_to_jmp) 67 | 68 | idc.MakeCode(ea) 69 | 70 | count_patched += 1 71 | 72 | else: 73 | 74 | count_not_patched += 1 75 | 76 | print "\tPatched resolve_opaque_jnz_jz: {0}".format(count_patched) 77 | print "\tNot Patched resolve_opaque_jnz_jz: {0}".format(count_not_patched) 78 | 79 | 80 | def resolve_opaque_mov_push(): 81 | 82 | PATTERNS = ["BB 00 00 00 00", 83 | "BB 01 00 00 00", 84 | "BB 02 00 00 00", 85 | "BB 03 00 00 00", 86 | "BB 04 00 00 00", 87 | "BB 05 00 00 00", 88 | "BB 06 00 00 00", 89 | "BB 07 00 00 00", 90 | "BB 08 00 00 00", 91 | "BB 09 00 00 00", 92 | "6A ?? 5B" 93 | ] 94 | 95 | count_patched = 0 96 | count_not_patched = 0 97 | 98 | for pattern in PATTERNS: 99 | 100 | ea = 0 101 | 102 | print pattern 103 | 104 | while ea != BADADDR: 105 | 106 | ea = idc.FindBinary(ea, SEARCH_NEXT|SEARCH_DOWN|SEARCH_CASE, pattern) 107 | 108 | ''' pattern: BB 00 00 00 00 109 | .text:00406A83 BB 00 00 00 00 mov ebx, 0 110 | .text:00406A88 66 01 D0 add ax, dx 111 | .text:00406A8B 81 F1 B5 15 00 00 xor ecx, 15B5h 112 | .text:00406A91 66 35 7A 13 xor ax, 137Ah 113 | .text:00406A95 85 DB test ebx, ebx 114 | .text:00406A97 74 14 jz short loc_406AAD 115 | 116 | Patched EBX > 0 117 | pattern: BB 09 00 00 00 118 | .text:00406BE3 BB 09 00 00 00 mov ebx, 9 119 | .text:00406BE8 20 D1 and cl, dl 120 | .text:00406BEA 66 09 D6 or si, dx 121 | .text:00406BED 66 89 D6 mov si, dx 122 | .text:00406BF0 85 DB test ebx, ebx 123 | .text:00406BF2 74 02 jz short loc_406BF6 124 | 125 | Patched EBX == 0 126 | .text:00406A83 90 nop 127 | .text:00406A84 90 nop 128 | .text:00406A85 90 nop 129 | .text:00406A86 90 nop 130 | .text:00406A87 90 nop 131 | .text:00406A88 90 nop 132 | .text:00406A89 90 nop 133 | .text:00406A8A 90 nop 134 | .text:00406A8B 90 nop 135 | .text:00406A8C 90 nop 136 | .text:00406A8D 90 nop 137 | .text:00406A8E 90 nop 138 | .text:00406A8F 90 nop 139 | .text:00406A90 90 nop 140 | .text:00406A91 90 nop 141 | .text:00406A92 90 nop 142 | .text:00406A93 90 nop 143 | .text:00406A94 90 nop 144 | .text:00406A95 90 nop 145 | .text:00406A96 90 nop 146 | .text:00406A97 EB 14 jmp short loc_406AAD 147 | 148 | ''' 149 | 150 | if ea_in_bounds(ea): 151 | 152 | ''' 153 | .text:00406A83 BB 00 00 00 00 mov ebx, <0-9> <- ebx_value 154 | .text:00406A88 66 01 D0 add ax, dx 155 | .text:00406A8B 81 F1 B5 15 00 00 xor ecx, 15B5h 156 | .text:00406A91 66 35 7A 13 xor ax, 137Ah 157 | .text:00406A95 85 DB test ebx, ebx 158 | .text:00406A97 74 14 jz short loc_406AAD 159 | ''' 160 | 161 | original_ea = ea 162 | 163 | ebx_value = Byte( ea + 1 ) 164 | 165 | instr = idautils.DecodeInstruction(ea) 166 | 167 | if instr: 168 | 169 | has_test = False 170 | 171 | # while not jmp related instruction found 172 | while ( (instr.itype <= idaapi.NN_ja) or ( instr.itype >= idaapi.NN_jmpshort) ): 173 | 174 | # move to next instr 175 | ea = ea + instr.size 176 | instr = idautils.DecodeInstruction(ea) 177 | 178 | # Check in order to validate that has test func and is candidate to be patched 179 | if instr.itype == idaapi.NN_test: 180 | 181 | has_test = True 182 | 183 | # at this point "ea" variable contains the last instruction address 184 | # that is the conditional jump found 185 | if has_test: 186 | 187 | if instr.itype == idaapi.NN_jz: 188 | 189 | # ebx_value > 0 and NN_jz -> Patch with NOPs 190 | if ebx_value > 0: 191 | 192 | ''' 193 | .text:00406BE3 BB 09 00 00 00 mov ebx, 9 194 | .text:00406BE8 20 D1 and cl, dl 195 | .text:00406BEA 66 09 D6 or si, dx 196 | .text:00406BED 66 89 D6 mov si, dx 197 | .text:00406BF0 85 DB test ebx, ebx 198 | .text:00406BF2 74 02 jz short loc_406BF6 199 | ''' 200 | 201 | relative_offset = ea - original_ea 202 | 203 | # Patch the complete function 204 | number_nops = ea - original_ea + instr.size 205 | 206 | for i in range(0, number_nops): 207 | 208 | idc.PatchByte (original_ea + i, 0x90) 209 | 210 | idc.MakeCode(ea) 211 | 212 | # ebx_value = 0 and NN_jz -> Patch with JMP 213 | else: 214 | 215 | ''' 216 | .text:00406A83 BB 00 00 00 00 mov ebx, 0 217 | .text:00406A88 66 01 D0 add ax, dx 218 | .text:00406A8B 81 F1 B5 15 00 00 xor ecx, 15B5h 219 | .text:00406A91 66 35 7A 13 xor ax, 137Ah 220 | .text:00406A95 85 DB test ebx, ebx 221 | .text:00406A97 74 14 jz short loc_406AAD 222 | ''' 223 | 224 | # ea contains the conditional jmp address 225 | relative_offset = Byte( ea + 1) 226 | 227 | # NOP 228 | number_nops = ea - original_ea + instr.size 229 | 230 | for i in range(0, number_nops): 231 | 232 | idc.PatchByte (original_ea + i, 0x90) 233 | 234 | # Patch the conditional jmp to unconditional jmp 235 | idc.PatchByte( ea , 0xEB) 236 | idc.PatchByte( ea + 1, relative_offset) 237 | 238 | idc.MakeCode(ea) 239 | 240 | count_patched += 1 241 | 242 | else: 243 | 244 | count_not_patched += 1 245 | 246 | print "\tPatched resolve_opaque_mov_push: {0}".format(count_patched) 247 | print "\tNot Patched resolve_opaque_mov_push: {0}".format(count_not_patched) 248 | 249 | 250 | def resolve_loops(): 251 | 252 | PATTERNS = ["81 FB ?? ?? ?? ?? 75"] 253 | 254 | count_patched = 0 255 | count_not_patched = 0 256 | 257 | for pattern in PATTERNS: 258 | 259 | ea = 0 260 | 261 | while ea != BADADDR: 262 | 263 | ''' 264 | pattern: 81 FB ?? ?? ?? ?? 75 265 | .text:00406AA0 01 C7 add edi, eax 266 | .text:00406AA2 66 41 inc cx 267 | .text:00406AA4 43 inc ebx 268 | .text:00406AA5 81 FB A6 01 00 00 cmp ebx, 1A6h 269 | .text:00406AAB 75 F3 jnz short loc_406AA0 270 | 271 | patched: 272 | .text:00406AA0 01 C7 add edi, eax 273 | .text:00406AA2 66 41 inc cx 274 | .text:00406AA4 43 inc ebx 275 | .text:00406AA5 90 nop 276 | .text:00406AA6 90 nop 277 | .text:00406AA7 90 nop 278 | .text:00406AA8 90 nop 279 | .text:00406AA9 90 nop 280 | .text:00406AAA 90 nop 281 | .text:00406AAB 90 nop 282 | .text:00406AAC 90 nop 283 | ''' 284 | 285 | ea = idc.FindBinary(ea, SEARCH_NEXT|SEARCH_DOWN|SEARCH_CASE, pattern) 286 | 287 | if ea_in_bounds(ea): 288 | 289 | # Patch CMP and conditional jmp instructions in order to remove the loop 290 | idc.PatchByte( ea + 0, 0x90) 291 | idc.PatchByte( ea + 1, 0x90) 292 | idc.PatchByte( ea + 2, 0x90) 293 | idc.PatchByte( ea + 3, 0x90) 294 | idc.PatchByte( ea + 4, 0x90) 295 | idc.PatchByte( ea + 5, 0x90) 296 | idc.PatchByte( ea + 6, 0x90) 297 | idc.PatchByte( ea + 7, 0x90) 298 | 299 | idc.MakeCode(ea) 300 | 301 | count_patched += 1 302 | 303 | print "\tPatched resolve_loops: {0}".format(count_patched) 304 | print "\tNot Patched resolve_loops: {0}".format(count_not_patched) 305 | 306 | 307 | def resolve_fs30(): 308 | 309 | PATTERNS = ["64 ?? 30 00 00 00", "64 ?? ?? 30 00 00 00"] 310 | 311 | count_patched = 0 312 | count_not_patched = 0 313 | 314 | for pattern in PATTERNS: 315 | 316 | ea = 0 317 | 318 | while ea != BADADDR: 319 | 320 | ''' 321 | pattern: 64 ?? 30 00 00 00 322 | .text:00407644 64 A1 30 00 00 00 mov eax, large fs:30h 323 | .text:0040764A 50 push eax 324 | .text:0040764B 80 ED AD sub ch, 0ADh 325 | .text:0040764E B5 32 mov ch, 32h 326 | .text:00407650 66 21 D7 and di, dx 327 | .text:00407653 5A pop edx 328 | .text:00407654 8A 72 02 mov dh, [edx+2] 329 | .text:00407657 84 F6 test dh, dh 330 | .text:00407659 74 11 jz short loc_40766C 331 | 332 | pattern: 64 ?? ?? 30 00 00 00 333 | .text:00406E42 64 8B 15 30 00 00 00 mov edx, large fs:30h 334 | .text:00406E49 52 push edx 335 | .text:00406E4A 66 35 3D 1B xor ax, 1B3Dh 336 | .text:00406E4E 20 CD and ch, cl 337 | .text:00406E50 28 D1 sub cl, dl 338 | .text:00406E52 59 pop ecx 339 | .text:00406E53 8A 51 02 mov dl, [ecx+2] 340 | .text:00406E56 84 D2 test dl, dl 341 | .text:00406E58 74 11 jz short loc_406E6B 342 | 343 | patched: 344 | .text:00406E42 64 8B 15 30 00 00 00 mov edx, large fs:30h 345 | .text:00406E49 52 push edx 346 | .text:00406E4A 66 35 3D 1B xor ax, 1B3Dh 347 | .text:00406E4E 20 CD and ch, cl 348 | .text:00406E50 28 D1 sub cl, dl 349 | .text:00406E52 59 pop ecx 350 | .text:00406E53 8A 51 02 mov dl, [ecx+2] 351 | .text:00406E56 84 D2 test dl, dl 352 | .text:00406E58 EB 11 jmp short loc_406E6B <----- 353 | 354 | ''' 355 | 356 | ea = idc.FindBinary(ea, SEARCH_NEXT|SEARCH_DOWN|SEARCH_CASE, pattern) 357 | 358 | if ea_in_bounds(ea): 359 | 360 | instr = idautils.DecodeInstruction(ea) 361 | 362 | if instr: 363 | 364 | # while not jmp related instruction found 365 | while ( (instr.itype <= idaapi.NN_ja) or ( instr.itype >= idaapi.NN_jmpshort) ): 366 | 367 | # move to next instr 368 | ea = ea + instr.size 369 | instr = idautils.DecodeInstruction(ea) 370 | 371 | # ea contains conditional jmp instruction 372 | # Patch with relative unconditional jmp 373 | idc.PatchByte(ea, 0xEB) 374 | idc.MakeCode(ea) 375 | 376 | count_patched += 1 377 | 378 | else: 379 | 380 | count_not_patched += 1 381 | 382 | print "\tPatched resolve_fs30: {0}".format(count_patched) 383 | print "\tNot Patched resolve_fs30: {0}".format(count_not_patched) 384 | 385 | 386 | def ea_in_bounds(ea): 387 | 388 | if segment_name == idc.SegName(ea) and ea >= init_func and ea <= end_func: 389 | 390 | return True 391 | 392 | return False 393 | 394 | 395 | print "START SCRIPT" 396 | print "==========================================" 397 | print "MAZELOADER DEOBFUSCATOR" 398 | print "==========================================" 399 | print "TASK[0] - resolve_opaque_jnz_jz()" 400 | resolve_opaque_jnz_jz() 401 | 402 | print "==========================================" 403 | print "TASK[1] - resolve_opaque_mov_push()" 404 | resolve_opaque_mov_push() 405 | 406 | print "==========================================" 407 | print "TASK[2] - resolve_loops()" 408 | resolve_loops() 409 | 410 | print "==========================================" 411 | print "TASK[3] - resolve_fs30()" 412 | resolve_fs30() 413 | 414 | print "==========================================" 415 | print "END SCRIPT" 416 | 417 | 418 | 419 | -------------------------------------------------------------------------------- /maze_patcher.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import idc 3 | import idautils 4 | 5 | # MAZE - Sample 1e3c7bce7eac2516c68e5586f1c22ba06e9e4bad649c5e8117393208f2eaa7bf 6 | 7 | segment_name = ".text" 8 | 9 | 10 | # IMPORTANT this function has to be the first one to be executed 11 | def find_and_rename_memcpy_function(): 12 | 13 | ea = 0 14 | pattern = "57 56 8B 74 24 10 8B 4C 24 14 8B 7C 24 0C 8B C1 8B D1 03 C6 3B FE 76 08" 15 | 16 | memcpy_func_addr = None 17 | 18 | # Find memcpy func 19 | while ea != BADADDR: 20 | 21 | ea = idc.FindBinary(ea, SEARCH_NEXT|SEARCH_DOWN|SEARCH_CASE, pattern) 22 | 23 | if ea and segment_name == idc.SegName(ea): 24 | 25 | memcpy_func_addr = ea 26 | idaapi.set_name(ea, "_memcpy_maze", idaapi.SN_FORCE) 27 | print "Pattern: {0}".format(pattern) 28 | print "\tPatched find_and_rename_memcpy_function: {0}".format(hex(memcpy_func_addr).split("L")[0]) 29 | 30 | 31 | # Find all memcpy refs and resolve them. 32 | # This is possible to be resolved with xref IDA functions 33 | # But because the code is obfuscated, it will be done this way 34 | if memcpy_func_addr: 35 | 36 | ''' 37 | 38 | .text:1001DEE1 0F 84 69 95 01 00 jz sub_10037450 39 | .text:1001DEE7 0F 85 63 95 01 00 jnz sub_10037450 40 | 41 | ''' 42 | patterns = [ "68 ?? ?? ?? ?? 0F 84 ?? ?? ?? ?? 0F 85", "68 ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? 0F 84"] 43 | 44 | for pattern in patterns: 45 | 46 | count_patched = 0 47 | count_not_patched = 0 48 | ea = 0 49 | 50 | while ea != BADADDR: 51 | 52 | ea = idc.FindBinary(ea, SEARCH_NEXT|SEARCH_DOWN|SEARCH_CASE, pattern) 53 | 54 | ''' Identify the ones that pushes the return 55 | .text:1001E0EC 68 01 E1 01 10 push offset byte_1001E101 56 | .text:1001E0F1 0F 84 59 93 01 00 jz _memcpy_maze 57 | .text:1001E0F7 0F 85 53 93 01 00 jnz _memcpy_maze 58 | ''' 59 | 60 | # relative offset from first jz 61 | # push_instr_addr + push_instr_size + 2 (0F 84) 62 | relative_offset_addr = ea + 5 + 2 63 | # push_inst_addr + push_instr_size + relative offset + jz_instr_size 64 | absolute_addr = ea + 0x5 + Dword(relative_offset_addr) + 0x6 65 | 66 | # If points to the memcpy func 67 | if absolute_addr == memcpy_func_addr: 68 | 69 | # Copy instruction values before patching 70 | values = GetManyBytes(ea, 5 + 6 + 6) 71 | 72 | 73 | ''' Original 74 | .text:10001574 50 push eax 75 | .text:10001575 55 push ebp 76 | .text:10001576 53 push ebx 77 | .text:10001577 68 94 15 00 10 push offset dword_10001594 78 | .text:1000157C 0F 84 CE 5E 03 00 jz _memcpy_maze 79 | .text:10001582 0F 85 C8 5E 03 00 jnz _memcpy_maze 80 | 81 | The idea is to patch in order to create: 82 | .text:10021905 52 push edx 83 | .text:10021906 56 push esi 84 | .text:10021907 50 push eax 85 | .text:10021908 FF 15 50 74 03 10 call dword ptr ds:_memcpy_maze 86 | .text:1002190E 68 21 19 02 10 push offset loc_10021921 87 | .text:10021913 C3 retn 88 | .text:10021914 90 nop 89 | .text:10021915 90 nop 90 | .text:10021916 90 nop 91 | .text:10021917 90 nop 92 | .text:10021918 90 nop 93 | ''' 94 | 95 | # Patch push with the call _memcpy_maze 96 | idc.PatchByte ( ea , 0xFF) 97 | idc.PatchByte ( ea + 1, 0x15) 98 | idc.PatchDword( ea + 2, memcpy_func_addr) 99 | 100 | # Below patch with the original push 101 | # push offset dword_714522A8 102 | idc.PatchByte( ea + 6, ord(values[0]) ) 103 | idc.PatchByte( ea + 7, ord(values[1]) ) 104 | idc.PatchByte( ea + 8, ord(values[2]) ) 105 | idc.PatchByte( ea + 9, ord(values[3]) ) 106 | idc.PatchByte( ea + 10, ord(values[4]) ) 107 | 108 | # Add ret 109 | idc.PatchByte( ea + 11, 0xC3) # 8 pos ret 110 | 111 | # Nop left part of last instruction 112 | idc.PatchByte( ea + 12, 0x90) 113 | idc.PatchByte( ea + 13, 0x90) 114 | idc.PatchByte( ea + 13, 0x90) 115 | idc.PatchByte( ea + 14, 0x90) 116 | idc.PatchByte( ea + 15, 0x90) 117 | idc.PatchByte( ea + 16, 0x90) 118 | 119 | idc.MakeCode(ea) 120 | 121 | count_patched += 1 122 | 123 | else: 124 | 125 | count_not_patched += 1 126 | 127 | print "Pattern: {0}".format(pattern) 128 | print "\tPatched find_and_rename_memcpy_function: {0}".format(count_patched) 129 | print "\tNot Patched find_and_rename_memcpy_function: {0}".format(count_not_patched) 130 | 131 | def delete_fake_calls_before_jz_jnz(): 132 | 133 | ''' 134 | .text:714517B9 74 2C jz short loc_714517E7 135 | .text:714517BB 75 0A jnz short loc_714517C7 136 | .text:714517BD FF 15 0C 70 48 71 call ds:LsaClose 137 | 138 | .text:71463763 75 5F jnz short near ptr dword_714637C0+4 139 | .text:71463765 74 0A jz short near ptr dword_71463770+1 140 | .text:71463767 FF 15 EC 71 48 71 call ds:LsaConnectUntrusted 141 | 142 | 74 ?? 75 ?? FF 15 143 | 75 ?? 74 ?? FF 15 144 | 145 | ''' 146 | 147 | patterns = [ "74 ?? 75 ?? FF 15", "75 ?? 74 ?? FF 15"] 148 | 149 | for pattern in patterns: 150 | 151 | count_patched = 0 152 | count_not_patched = 0 153 | ea = 0 154 | 155 | while ea != BADADDR: 156 | 157 | ea = idc.FindBinary(ea, SEARCH_NEXT|SEARCH_DOWN|SEARCH_CASE, pattern) 158 | 159 | if ea and segment_name == idc.SegName(ea): 160 | 161 | ''' 162 | 74 2C jz short loc_714517E7 163 | 75 0A jnz short loc_714517C7 164 | FF 15 0C 70 48 71 call ds:LsaClose 165 | 166 | Result: 167 | 74 2C jz short loc_714517E7 168 | 75 0A jnz short loc_714517C7 169 | 90 90 90 90 90 90 nops 170 | ''' 171 | 172 | # Patch from pos, and delete fake calls 173 | pos = ea + 0x4 174 | 175 | patch_loop( pos, 6, 0x90) 176 | 177 | idc.MakeCode(ea) 178 | 179 | count_patched += 1 180 | 181 | else: 182 | 183 | count_not_patched += 1 184 | 185 | print "Pattern: {0}".format(pattern) 186 | print "\tPatched delete_fake_calls_before_jz_jnz: {0}".format(count_patched) 187 | print "\tNot Patched delete_fake_calls_before_jz_jnz: {0}".format(count_not_patched) 188 | 189 | 190 | ''' 191 | 192 | .text:714713E4 0F 84 36 01 FE FF jz loc_71451520 193 | .text:714713EA 75 0A jnz short near ptr loc_714713F5+1 194 | 195 | 0F ?? ?? ?? ?? ?? 75 ?? FF 15 196 | 0F ?? ?? ?? ?? ?? 74 ?? FF 15 197 | 198 | ''' 199 | patterns = [ "0F ?? ?? ?? ?? ?? 75 ?? FF 15", "0F ?? ?? ?? ?? ?? 74 ?? FF 15"] 200 | 201 | for pattern in patterns: 202 | 203 | count_patched = 0 204 | count_not_patched = 0 205 | ea = 0 206 | 207 | while ea != BADADDR: 208 | 209 | ea = idc.FindBinary(ea, SEARCH_NEXT|SEARCH_DOWN|SEARCH_CASE, pattern) 210 | 211 | if ea and segment_name == idc.SegName(ea): 212 | 213 | ''' 214 | 0F 84 36 01 FE FF jz loc_71451520 215 | 75 0A jnz short near ptr loc_714713F5+1 216 | FF 15 00 70 48 71 call ds:EqualDomainSid 217 | 218 | Result: 219 | 0F 84 36 01 FE FF jz loc_71451520 220 | 75 0A jnz short near ptr loc_714713F5+1 221 | 90 90 90 90 90 90 nops 222 | ''' 223 | 224 | # Patch from pos, and delete fake calls 225 | pos = ea + 0x8 226 | patch_loop( pos, 6, 0x90) 227 | 228 | idc.MakeCode(ea) 229 | 230 | count_patched += 1 231 | 232 | else: 233 | 234 | count_not_patched += 1 235 | 236 | print "Pattern: {0}".format(pattern) 237 | print "\tPatched delete_fake_calls_before_jz_jnz: {0}".format(count_patched) 238 | print "\tNot Patched delete_fake_calls_before_jz_jnz: {0}".format(count_not_patched) 239 | 240 | 241 | ''' 242 | .text:10021C11 0F 84 09 F9 FD FF jz loc_10001520 243 | .text:10021C17 0F 85 03 F9 FD FF jnz loc_10001520 244 | .text:10021C1D FF 15 00 91 03 10 call ds:CreateFileW 245 | ''' 246 | patterns = ["0F 84 ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? FF 15", "0F 85 ?? ?? ?? ?? 0F 84 ?? ?? ?? ?? FF 15"] 247 | 248 | for pattern in patterns: 249 | 250 | count_patched = 0 251 | count_not_patched = 0 252 | ea = 0 253 | 254 | while ea != BADADDR: 255 | 256 | ea = idc.FindBinary(ea, SEARCH_NEXT|SEARCH_DOWN|SEARCH_CASE, pattern) 257 | 258 | if ea and segment_name == idc.SegName(ea): 259 | 260 | ''' 261 | .text:10021C11 0F 84 09 F9 FD FF jz loc_10001520 262 | .text:10021C17 0F 85 03 F9 FD FF jnz loc_10001520 263 | .text:10021C1D FF 15 00 91 03 10 call ds:CreateFileW 264 | 265 | Result: 266 | .text:10021C11 0F 84 09 F9 FD FF jz loc_10001520 267 | .text:10021C17 0F 85 03 F9 FD FF jnz loc_10001520 268 | .text:10021C1D 90 nop 269 | .text:10021C1E 90 nop 270 | .text:10021C1F 90 nop 271 | .text:10021C20 90 nop 272 | .text:10021C21 90 nop 273 | .text:10021C22 90 nop 274 | ''' 275 | 276 | # Patch from pos, and delete fake calls 277 | pos = ea + 12 278 | 279 | patch_loop( pos, 6, 0x90) 280 | 281 | idc.MakeCode(ea) 282 | 283 | count_patched += 1 284 | 285 | else: 286 | 287 | count_not_patched += 1 288 | 289 | print "Pattern: {0}".format(pattern) 290 | print "\tPatched delete_fake_calls_before_jz_jnz: {0}".format(count_patched) 291 | print "\tNot Patched delete_fake_calls_before_jz_jnz: {0}".format(count_not_patched) 292 | 293 | 294 | def obfuscated_jz_jnz(): 295 | 296 | ''' 297 | Resolve opaque predicates, in case that opaque predicate points to FF 25 the call will be patched: 298 | 299 | Example: 300 | 301 | .text:10001540 68 71 15 00 10 push offset byte_10001571 302 | .text:10001545 0F 84 57 5C 03 00 jz loc_100371A2 303 | .text:1000154B 0F 85 51 5C 03 00 jnz loc_100371A2 304 | 305 | Pattern 306 | 68 ?? ?? ?? ?? ?? 307 | 0F 84 ?? ?? ?? ?? 308 | 0F 85 ?? ?? ?? ?? 309 | 310 | 68 ?? ?? ?? ?? 0F 84 ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? 311 | 312 | Operation -> DWORD( 9C 9F 01 00 ) - DWORD( 96 9F 01 00 ) == 6 313 | 314 | That means that jumps to the same relative address 315 | 316 | 0x19f9C - 0x19F96 = 0x6 317 | 318 | If that address points to FF 25 ?? ?? ?? ?? 319 | 320 | .text:100371A2 FF 25 9C 90 03 10 jmp ds:lstrlenA <---------- 321 | .text:100371A8 ----------------------------- 322 | .text:100371A8 FF 25 A0 90 03 10 jmp ds:GetModuleHandleA 323 | .text:100371AE ----------------------------- 324 | .text:100371AE FF 25 A4 90 03 10 jmp ds:LoadLibraryA 325 | .text:100371B4 ----------------------------- 326 | .text:100371B4 327 | .text:100371B4 328 | .text:100371B4 329 | .text:100371B4 FF 25 A8 90 03 10 jmp ds:GetLastError 330 | .text:100371BA ----------------------------- 331 | .text:100371BA FF 25 AC 90 03 10 jmp ds:lstrcpyA 332 | 333 | Patch with CALL PUSH RET formula 334 | 335 | .text:10001540 90 nop ; Return addr - 0x10001571 336 | .text:10001541 90 nop 337 | .text:10001542 90 nop 338 | .text:10001543 90 nop 339 | .text:10001544 90 nop 340 | .text:10001545 FF 15 9C 90 03 10 call ds:lstrlenA 341 | .text:1000154B 68 71 15 00 10 push 10001571h 342 | .text:10001550 C3 retn 343 | 344 | ''' 345 | 346 | count_patched = 0 347 | count_not_patched = 0 348 | ea = 0 349 | 350 | while ea != BADADDR: 351 | 352 | ea = idc.FindBinary(ea, SEARCH_NEXT|SEARCH_DOWN|SEARCH_CASE, "68 ?? ?? ?? ?? 0F 84 ?? ?? ?? ?? 0F 85 ?? ?? ?? ??") 353 | 354 | if ea and segment_name == idc.SegName(ea): 355 | 356 | idc.MakeComm(ea, "Return addr - {0}".format( hex(Dword(ea + 0x1)).split("L")[0])) 357 | 358 | 359 | jz_pos = ea + 0x5 360 | jz_value = Dword( jz_pos + 0x2 ) 361 | 362 | jnz_pos = jz_pos + 0x6 363 | jnz_value = Dword( jnz_pos + 0x2 ) 364 | 365 | # Check same jmp addr 366 | if jz_value - jnz_value == 0x6: 367 | 368 | pos_jmp = jz_pos + jz_value + 0x6 369 | 370 | # If the jmp points to a FF 25 instruction (absolute jmp) 371 | if Word(pos_jmp) == 0x25FF: 372 | 373 | ''' 374 | .text:1000153F 55 push ebp 375 | .text:10001540 68 71 15 00 10 push offset byte_10001571 376 | .text:10001545 0F 84 57 5C 03 00 jz loc_100371A2 377 | .text:1000154B 0F 85 51 5C 03 00 jnz loc_100371A2 378 | .text:10001551 56 push esi 379 | ''' 380 | 381 | # Patch the conditional jmp, copying the absolute jmp into it 382 | idc.PatchWord (jz_pos, Word(pos_jmp)) 383 | idc.PatchDword(jz_pos + 0x2, Dword(pos_jmp + 0x2)) 384 | 385 | # At this point the FF 25 instruction have been copied into the conditional jmp position 386 | # Ex: FF 25 3C 70 48 71 jmp ds:CryptGenRandom 387 | # But the absolute jmp will be patched with a call FF 15 388 | 389 | # FF 15 call 390 | idc.PatchByte (jz_pos, 0xFF) 391 | idc.PatchByte (jz_pos + 0x1, 0x15) 392 | 393 | # Copy the push instruction where conditonal jmp was 394 | idc.PatchWord( jnz_pos, Word(ea) ) 395 | idc.PatchWord( jnz_pos + 0x1, Word(ea + 0x1) ) 396 | idc.PatchWord( jnz_pos + 0x3, Word(ea + 0x3) ) 397 | idc.PatchByte( jnz_pos + 0x5, 0xC3) 398 | 399 | # Nop the first 5 bytes. 400 | patch_loop(ea, 0x5, 0x90) 401 | 402 | ''' 403 | .text:10001540 90 nop ; Return addr - 0x10001571 404 | .text:10001541 90 nop 405 | .text:10001542 90 nop 406 | .text:10001543 90 nop 407 | .text:10001544 90 nop 408 | .text:10001545 FF 15 9C 90 03 10 call ds:lstrlenA 409 | .text:1000154B 68 71 15 00 10 push 10001571h 410 | .text:10001550 C3 retn 411 | ''' 412 | 413 | idc.MakeCode(ea) 414 | 415 | else: 416 | 417 | ''' 418 | .text:10021C0C 68 27 1C 02 10 push offset loc_10021C27 419 | .text:10021C11 0F 84 09 F9 FD FF jz loc_10001520 420 | .text:10021C17 0F 85 03 F9 FD FF jnz loc_10001520 421 | 422 | .text:10021C0C 90 nop 423 | .text:10021C0D 90 nop 424 | .text:10021C0E 90 nop 425 | .text:10021C0F 90 nop 426 | .text:10021C10 90 nop 427 | .text:10021C11 90 nop 428 | .text:10021C12 90 nop 429 | .text:10021C13 90 nop 430 | .text:10021C14 90 nop 431 | .text:10021C15 90 nop 432 | .text:10021C16 90 nop 433 | .text:10021C17 E9 04 F9 FD FF jmp loc_10001520 434 | .text:10021C1C 90 nop <- last byte of old jnz instr 435 | ''' 436 | 437 | # The first 11 bytes to nop 438 | 439 | for i in range(0,11): 440 | 441 | idc.PatchByte (ea + i, 0x90) 442 | 443 | # Add 1 to the address, because the size of the conditional jmp is 6, and the size of the unconditional jmp is 5 444 | addr_to_jmp = Dword( jnz_pos + 0x2) + 0x1 445 | 446 | # Patch first jz_value with unconditional jmp 447 | idc.PatchByte (jnz_pos, 0xE9) 448 | 449 | # Set the relative address 450 | idc.PatchDword(jnz_pos + 0x1, addr_to_jmp) 451 | 452 | # Last byte of the jnz to NOP (0x90) 453 | idc.PatchByte (jnz_pos + 0x5 , 0x90) 454 | 455 | idc.MakeCode(ea) 456 | 457 | ''' 458 | .text:10021C0C 90 nop 459 | .text:10021C0D 90 nop 460 | .text:10021C0E 90 nop 461 | .text:10021C0F 90 nop 462 | .text:10021C10 90 nop 463 | .text:10021C11 90 nop 464 | .text:10021C12 90 nop 465 | .text:10021C13 90 nop 466 | .text:10021C14 90 nop 467 | .text:10021C15 90 nop 468 | .text:10021C16 90 nop 469 | .text:10021C17 E9 04 F9 FD FF jmp loc_10001520 470 | .text:10021C1C 90 nop <- last byte of old jnz instr 471 | ''' 472 | 473 | count_patched += 1 474 | 475 | else: 476 | 477 | count_not_patched += 1 478 | 479 | 480 | print "\tPatched obfuscated_jz_jnz: {0}".format(count_patched) 481 | print "\tNot Patched obfuscated_jz_jnz: {0}".format(count_not_patched) 482 | 483 | 484 | def patch_jmp_eax(): 485 | 486 | ''' 487 | 488 | This type of push + jmp eax can be found in the maze code 489 | 490 | .text:71452297 68 A8 22 45 71 push offset dword_714522A8 491 | .text:7145229C FF E0 jmp eax 492 | 493 | With this patter the script is able to find this type of code: 494 | 495 | 68 ?? ?? ?? ?? FF E0 496 | 497 | ''' 498 | 499 | count_patched = 0 500 | count_not_patched = 0 501 | ea = 0 502 | 503 | while ea != BADADDR: 504 | 505 | ea = idc.FindBinary(ea, SEARCH_NEXT|SEARCH_DOWN|SEARCH_CASE, "68 ?? ?? ?? ?? FF E0") 506 | 507 | if ea and segment_name == idc.SegName(ea): 508 | 509 | ''' 510 | 68 A8 22 45 71 push offset dword_714522A8 511 | FF E0 jmp eax 512 | 513 | in some cases we found this after the jmp fake api calls 514 | FF 15 08 90 03 10 call ds:LsaAddAccountRights 515 | ''' 516 | 517 | # push + jmp = 7 bytes 518 | # call Fake api call = 6 bytes 519 | # Store the byte values in "values" list 520 | values = GetManyBytes(ea, 7 + 6) 521 | 522 | ''' 523 | FF D0 call eax 524 | 68 A8 22 45 71 push offset dword_714522A8 525 | C3 ret 526 | ''' 527 | 528 | # Patch with call eax 529 | idc.PatchByte( ea , 0xFF) 530 | idc.PatchByte( ea + 0x1, 0xD0) 531 | 532 | # Patch push 533 | idc.PatchByte( ea + 0x2, ord(values[0]) ) 534 | idc.PatchByte( ea + 0x3, ord(values[1]) ) 535 | idc.PatchByte( ea + 0x4, ord(values[2]) ) 536 | idc.PatchByte( ea + 0x5, ord(values[3]) ) 537 | idc.PatchByte( ea + 0x6, ord(values[4]) ) 538 | 539 | # Patch ret 540 | idc.PatchByte( ea + 0x7, 0xC3) 541 | 542 | ''' 543 | If the script found in the position 7 and 8 FF 15 that means that it has found a fake api call 544 | 545 | 57 push edi 546 | A1 D4 90 03 10 mov eax, ds:lstrcatW 547 | 89 C6 mov esi, eax 548 | 68 C9 28 00 10 push offset loc_100028C9 549 | FF E0 jmp eax 550 | FF 15 08 90 03 10 call ds:LsaAddAccountRights 551 | 91 xchg eax, ecx 552 | 23 00 and eax, [eax] 553 | ''' 554 | 555 | # NOP the fake call 556 | if ord(values[7]) == 0xFF and ord(values[8]) == 0x15: 557 | 558 | for i in range(0, 5): 559 | 560 | idc.PatchByte( ea + 0x8 + i, 0x90) 561 | 562 | idc.MakeCode(ea) 563 | count_patched += 1 564 | 565 | else: 566 | count_not_patched += 1 567 | 568 | print "\tPatched patch_jmp_eax: {0}".format(count_patched) 569 | print "\tNot Patched patch_jmp_eax: {0}".format(count_not_patched) 570 | 571 | 572 | def obfuscated_jz_jnz_2(): 573 | 574 | patterns = [ "68 ?? ?? ?? ?? 0F 84 ?? ?? ?? ?? 75 ??", "68 ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? 74 ??" ] 575 | 576 | for pattern in patterns: 577 | 578 | ea = 0 579 | count_patched = 0 580 | count_not_patched = 0 581 | 582 | ''' 583 | 584 | It is assumed, after analysis, that the following conditions are met. 585 | 586 | - The first jz or jnz instruction is the one that contains the final address. 587 | - The second jz or jnz instruction contains the intermediate address, 588 | this intermediate address contains another conditional jmp instruction 589 | that points to the same final address as the first conditional jmp instruction. 590 | 591 | .text:1002AFE3 68 1D B0 02 10 push offset loc_1002B01D 592 | .text:1002AFE8 0F 84 12 6E 00 00 jz loc_10031E00 <- jmp to Final Addr 593 | .text:1002AFEE 75 04 jnz short loc_1002AFF4 <- jmp to Intermediate Addr 594 | .text:1002AFF0 E2 1B loop loc_1002B00D 595 | 596 | .text:1002AFF2 00 db 0 597 | .text:1002AFF3 00 db 0 598 | 599 | .text:1002AFF4 600 | .text:1002AFF4 loc_1002AFF4: 601 | .text:1002AFF4 0F 85 06 6E 00 00 jnz loc_10031E00 <- jmp to Final Addr 602 | .text:1002AFFA 74 04 jz short loc_1002B000 <- Junk Code 603 | .text:1002AFFC 13 1A adc ebx, [edx] 604 | 605 | ''' 606 | 607 | while ea != BADADDR: 608 | 609 | ea = idc.FindBinary(ea, SEARCH_NEXT|SEARCH_DOWN|SEARCH_CASE, pattern) 610 | 611 | if ea and segment_name == idc.SegName(ea): 612 | 613 | # First push 614 | push_addr = ea 615 | 616 | # First conditional jmp (jz or jnz) 617 | first_j = ea + 0x5 618 | 619 | # Second conditional jmp (jz or jnz) 620 | second_j = first_j + 0x6 621 | 622 | # pos contains the last position 623 | pos = 0xFFFFFFFF & first_j + Dword(first_j + 0x2) + 0x6 624 | 625 | # pos_2 contains the intermediate position before jmp to the final pos 626 | pos_2 = 0xFFFFFFFF & first_j + 0x6 + 0x2 + Byte(first_j + 0x6 + 0x1) 627 | 628 | # First opcode intermediate instruction 629 | value_pos_2 = Byte(pos_2) 630 | 631 | # Final address 632 | pos_3 = pos_2 + Dword(pos_2 + 0x2) + 0x6 633 | 634 | # If the OPCODE value of the intermediate jmp instruction begins with 0x0F must be patched 635 | if value_pos_2 == 0xF: 636 | 637 | value_pos_final = Word(pos_3) 638 | 639 | # If the final address first two bytes values are FF 25 (absolute jmp) 640 | # Patch with call push ret formula 641 | if value_pos_final == 0x25FF: 642 | 643 | ''' 644 | .text:1002D25D 68 9C D2 02 10 push offset dword_1002D29C 645 | .text:1002D262 0F 84 A6 9F 00 00 jz loc_1003720E <------ jmp ds:GetTickCount 646 | .text:1002D268 75 04 jnz short loc_1002D26E 647 | .text:1002D26A 86 01 xchg al, [ecx] 648 | .text:1002D26C 00 db 0 649 | .text:1002D26D 00 db 0 650 | .text:1002D26E 651 | .text:1002D26E loc_1002D26E: 652 | .text:1002D26E 0F 85 9A 9F 00 00 jnz loc_1003720E <------ jmp ds:GetTickCount 653 | .text:1002D274 74 0A jz short loc_1002D280 654 | .text:1002D276 FF 15 10 92 03 10 call ds:EnumChildWindows 655 | 656 | 657 | The first conditional jmp and the intermediate points to WinAPI function: 658 | .text:1003720E FF 25 E4 90 03 10 jmp ds:GetTickCount 659 | 660 | Patched: 661 | 662 | .text:1002D25D 90 nop 663 | .text:1002D25E 90 nop 664 | .text:1002D25F 90 nop 665 | .text:1002D260 90 nop 666 | .text:1002D261 90 nop 667 | .text:1002D262 FF 15 E4 90 03 10 call ds:GetTickCount 668 | .text:1002D268 68 9C D2 02 10 push 1002D29Ch 669 | .text:1002D26D C3 retn 670 | .text:1002D26E 90 nop 671 | .text:1002D26F 90 nop 672 | .text:1002D270 90 nop 673 | .text:1002D271 90 nop 674 | .text:1002D272 90 nop 675 | .text:1002D273 90 nop 676 | .text:1002D274 90 nop 677 | .text:1002D275 90 nop 678 | .text:1002D276 90 nop 679 | .text:1002D277 90 nop 680 | .text:1002D278 90 nop 681 | .text:1002D279 90 nop 682 | .text:1002D27A 90 nop 683 | .text:1002D27B 90 nop 684 | 685 | ''' 686 | 687 | idc.PatchByte ( first_j , 0xFF ) 688 | idc.PatchByte ( first_j + 0x1 , 0x15 ) 689 | idc.PatchDword( first_j + 0x2 , Dword( pos_3 + 0x2 ) ) 690 | 691 | # patch 0x90 692 | patch_loop( first_j + 0x6, 2, 0x90) 693 | 694 | # Patch from pos_2 8 bytes NOPS 695 | patch_loop( pos_2, 8, 0x90) 696 | 697 | # Copy push addr 698 | 699 | idc.PatchByte( second_j, Byte( push_addr )) 700 | idc.PatchByte( second_j + 0x1, Byte( push_addr + 0x1 )) 701 | idc.PatchByte( second_j + 0x2, Byte( push_addr + 0x2 )) 702 | idc.PatchByte( second_j + 0x3, Byte( push_addr + 0x3 )) 703 | idc.PatchByte( second_j + 0x4, Byte( push_addr + 0x4 )) 704 | 705 | # Patch with ret 706 | idc.PatchByte( second_j + 0x5, 0xC3 ) 707 | 708 | # Patch push_addr 709 | patch_loop( push_addr, 5, 0x90) 710 | 711 | idc.MakeCode(ea) 712 | count_patched += 1 713 | 714 | 715 | #If the final address first two bytes values are not FF 25 (jmp) 716 | else: 717 | 718 | ''' 719 | .text:1002C85C 68 99 C8 02 10 push offset loc_1002C899 720 | .text:1002C861 0F 84 B9 4C FD FF jz loc_10001520 721 | .text:1002C867 75 04 jnz short loc_1002C86D 722 | .text:1002C869 FD std 723 | .text:1002C86A 0A 00 or al, [eax] 724 | .text:1002C86C 00 db 0 725 | .text:1002C86D 726 | .text:1002C86D loc_1002C86D: 727 | .text:1002C86D 0F 85 AD 4C FD FF jnz loc_10001520 728 | .text:1002C873 74 04 jz short loc_1002C879 729 | .text:1002C875 CF iret 730 | 731 | Patched: 732 | 733 | .text:1002C85C 68 99 C8 02 10 push offset loc_1002C899 734 | .text:1002C861 E9 BA 4C FD FF jmp loc_10001520 735 | .text:1002C861 ; --------------------------------------------------------------------------- 736 | .text:1002C866 FF db 0FFh ; ÿ 737 | .text:1002C867 75 04 jnz short loc_1002C86D 738 | .text:1002C869 FD std 739 | .text:1002C86A 0A 00 or al, [eax] 740 | .text:1002C86A ; --------------------------------------------------------------------------- 741 | .text:1002C86C 00 db 0 742 | .text:1002C86D ; --------------------------------------------------------------------------- 743 | .text:1002C86D 744 | .text:1002C86D loc_1002C86D: ; CODE XREF: .text:1002C867↑j 745 | .text:1002C86D 90 nop 746 | .text:1002C86E 90 nop 747 | .text:1002C86F 90 nop 748 | .text:1002C870 90 nop 749 | .text:1002C871 90 nop 750 | .text:1002C872 90 nop 751 | .text:1002C873 90 nop 752 | .text:1002C874 90 nop 753 | ''' 754 | 755 | idc.PatchByte ( first_j , 0xE9 ) 756 | idc.PatchDword( first_j + 0x1 , Dword(first_j + 0x2) + 0x1 ) 757 | 758 | # Patch from pos_2 8 bytes NOPS 759 | patch_loop( pos_2, 8, 0x90) 760 | idc.MakeCode(ea) 761 | count_patched += 1 762 | 763 | print "Pattern: {0}".format(pattern) 764 | print "\tPatched obfuscated_jz_jnz_2: {0}".format(count_patched) 765 | print "\tNot Patched obfuscated_jz_jnz_2: {0}".format(count_not_patched) 766 | 767 | 768 | 769 | ######################################################################################### 770 | 771 | def patch_loop(address, size, value): 772 | 773 | for i in range(0, size): 774 | 775 | idc.PatchByte(address + i, value) 776 | 777 | 778 | ######################################################################################### 779 | 780 | 781 | print "START SCRIPT" 782 | print "==========================================" 783 | print "MAZE DEOBFUSCATOR" 784 | print "==========================================" 785 | print "TASK[0] - find_and_rename_memcpy_function()" 786 | find_and_rename_memcpy_function() 787 | 788 | print "==========================================" 789 | print "TASK[1] - delete_fake_calls_before_jz_jnz()" 790 | delete_fake_calls_before_jz_jnz() 791 | 792 | print "==========================================" 793 | print "TASK[2] - obfuscated_jz_jnz()" 794 | obfuscated_jz_jnz() 795 | 796 | print "==========================================" 797 | print "TASK[3] - patch_jmp_eax()" 798 | patch_jmp_eax() 799 | 800 | print "==========================================" 801 | print "TASK[4] - obfuscated_jz_jnz_2()" 802 | obfuscated_jz_jnz_2() 803 | 804 | print "==========================================" 805 | print "END SCRIPT" 806 | 807 | 808 | 809 | --------------------------------------------------------------------------------