SHOW:
|
|
- or go back to the newest paste.
| 1 | import sys | |
| 2 | from struct import pack | |
| 3 | from io import BytesIO | |
| 4 | ||
| 5 | from ctypes import * | |
| 6 | from winappdbg import Debug, EventHandler | |
| 7 | import pefile | |
| 8 | ||
| 9 | from capstone import * | |
| 10 | from capstone.x86 import * | |
| 11 | ||
| 12 | from unicorn import * | |
| 13 | from unicorn.x86_const import * | |
| 14 | ||
| 15 | from keystone import * | |
| 16 | ||
| 17 | SECTION_HEADER_SIZE = 0x28 | |
| 18 | IMAGE_DESCRIPTOR_HEADER_SIZE = 0x14 | |
| 19 | ||
| 20 | md = Cs(CS_ARCH_X86, CS_MODE_64) | |
| 21 | md.detail = True | |
| 22 | ||
| 23 | global target_pe | |
| 24 | global mem_pages | |
| 25 | ||
| 26 | # ---------------------------------------------------------------------------------------------------------------- | |
| 27 | def align_data(data, blocksize): | |
| 28 | r = data | |
| 29 | rm = len( r ) % blocksize | |
| 30 | if rm != 0: | |
| 31 | r += (blocksize - rm) * b'\x00' | |
| 32 | return r | |
| 33 | ||
| 34 | # ---------------------------------------------------------------------------------------------------------------- | |
| 35 | def align_int(integer, blocksize): | |
| 36 | r = integer | |
| 37 | rm = r % blocksize | |
| 38 | if rm != 0: | |
| 39 | r += (blocksize - rm) | |
| 40 | return r | |
| 41 | ||
| 42 | # ---------------------------------------------------------------------------------------------------------------- | |
| 43 | def image_import_descriptor(OriginalFirstThunk, Name, FirstThunk): | |
| 44 | """ | |
| 45 | +00 DWORD OriginalFirstThunk | |
| 46 | +04 DWORD TimeDateStamp | |
| 47 | +08 DWORD ForwarderChain | |
| 48 | +12 DWORD Name | |
| 49 | +16 DWORD FirstThunk | |
| 50 | """ | |
| 51 | return pack('<LLLLL', OriginalFirstThunk, 0, 0, Name, FirstThunk)
| |
| 52 | ||
| 53 | # ---------------------------------------------------------------------------------------------------------------- | |
| 54 | def image_import_by_name(hint, name): | |
| 55 | """ | |
| 56 | +00 WORD Hint | |
| 57 | +02 BYTE Name | |
| 58 | """ | |
| 59 | return pack('<H', hint) + name.encode() + b'\0'
| |
| 60 | ||
| 61 | ||
| 62 | # ---------------------------------------------------------------------------------------------------------------- | |
| 63 | def image_thunk_data(imageimportbyname_rva, x64, lastthunk=True): | |
| 64 | if x64: | |
| 65 | r = pack('<Q', imageimportbyname_rva )
| |
| 66 | if lastthunk: | |
| 67 | r += pack('<Q', 0 )
| |
| 68 | else: | |
| 69 | r = pack('<L', imageimportbyname_rva )
| |
| 70 | if lastthunk: | |
| 71 | r += pack('<L', 0 )
| |
| 72 | return r | |
| 73 | ||
| 74 | # ---------------------------------------------------------------------------------------------------------------- | |
| 75 | def build_image_import_by_name(names, base=0): | |
| 76 | funcrva = [] | |
| 77 | r = b'' | |
| 78 | for i, name in enumerate(names): | |
| 79 | funcrva.append( len( r ) ) | |
| 80 | r += image_import_by_name(i, name) | |
| 81 | return funcrva, r | |
| 82 | ||
| 83 | # ---------------------------------------------------------------------------------------------------------------- | |
| 84 | def build_image_thunk_data(offsets, x64, base=0): | |
| 85 | r = b'' | |
| 86 | offd = dict() | |
| 87 | for i in range(len(offsets)): | |
| 88 | r += image_thunk_data(base + offsets[i], x64, bool(i==len(offsets)-1)) | |
| 89 | return r | |
| 90 | ||
| 91 | # ---------------------------------------------------------------------------------------------------------------- | |
| 92 | def section_header(roff, rsize, voff, vsize, name=b'.h4x'): | |
| 93 | """ | |
| 94 | +00 BYTE name[8] | |
| 95 | +08 DWORD virtualsize | |
| 96 | +12 DWORD virtualaddress | |
| 97 | +16 DWORD rawsize | |
| 98 | +20 DWORD rawaddress | |
| 99 | +24 DWORD relocaddress | |
| 100 | +28 DWORD linenumbers | |
| 101 | +32 WORD nrofrelocs | |
| 102 | +34 WORD nroflinenumbers | |
| 103 | +36 DWORD characteristics | |
| 104 | """ | |
| 105 | return name.ljust(8, b'\x00') + pack('<LLLLLLHHL', vsize, voff, rsize, roff, 0, 0, 0, 0, 0xC0000040)
| |
| 106 | ||
| 107 | # ---------------------------------------------------------------------------------------------------------------- | |
| 108 | def rebuild_import_table(file_data, impt): | |
| 109 | # collect some pe info from the file for later | |
| 110 | pe = pefile.PE(data=file_data, fast_load=True) | |
| 111 | is64bits = bool(pe.OPTIONAL_HEADER.Magic == 0x20b) | |
| 112 | nrofsections = pe.FILE_HEADER.NumberOfSections | |
| 113 | secalignment = pe.OPTIONAL_HEADER.SectionAlignment | |
| 114 | filealignment = pe.OPTIONAL_HEADER.FileAlignment | |
| 115 | sizeofheaders = pe.OPTIONAL_HEADER.SizeOfHeaders | |
| 116 | lastsecoffset = pe.sections[nrofsections-1].__file_offset__ | |
| 117 | lastsection = pe.sections[nrofsections-1] | |
| 118 | lastviraddr = lastsection.VirtualAddress + lastsection.Misc_VirtualSize | |
| 119 | sizeofimage = pe.OPTIONAL_HEADER.SizeOfImage | |
| 120 | pe.close() | |
| 121 | ||
| 122 | imp_tbl = b'' | |
| 123 | imp_disc = b'' | |
| 124 | rvas = dict() | |
| 125 | for dllname in impt: | |
| 126 | importbyname_offsets, importbyname_data = build_image_import_by_name(impt[dllname]) | |
| 127 | thunk_data = build_image_thunk_data(importbyname_offsets, is64bits, sizeofimage + len(imp_tbl) + len(dllname) + 1) | |
| 128 | ||
| 129 | name_rva = len(imp_tbl) | |
| 130 | ||
| 131 | imp_tbl += dllname.encode() + b'\x00' | |
| 132 | imp_tbl += importbyname_data | |
| 133 | imp_tbl = align_data(imp_tbl, 8) | |
| 134 | firstthunk_rva = sizeofimage + len(imp_tbl) | |
| 135 | imp_tbl += thunk_data | |
| 136 | ||
| 137 | rvas[dllname] = dict() | |
| 138 | for i, funcname in enumerate(impt[dllname]): | |
| 139 | rvas[dllname][funcname] = firstthunk_rva + (i * 8) | |
| 140 | ||
| 141 | imp_disc += image_import_descriptor( firstthunk_rva, sizeofimage + name_rva, firstthunk_rva ) | |
| 142 | ||
| 143 | imp_disc += image_import_descriptor( 0, 0, 0 ) | |
| 144 | imp_tbl = align_data(imp_tbl, 4) | |
| 145 | ||
| 146 | import_dir_rva = sizeofimage + len(imp_tbl) | |
| 147 | imp_tbl += imp_disc | |
| 148 | newsec_data = align_data(imp_tbl, filealignment) | |
| 149 | ||
| 150 | newsec_rawsize = len(newsec_data) | |
| 151 | # get the alignd virtual offset and size | |
| 152 | newsec_viraddr = align_int(lastviraddr, secalignment) | |
| 153 | newsec_virsize = align_int(newsec_rawsize, secalignment) | |
| 154 | newsec_rawaddr = len(file_data) | |
| 155 | ||
| 156 | # create a section header | |
| 157 | newsec_header = section_header(newsec_rawaddr, newsec_rawsize, newsec_viraddr, newsec_virsize) | |
| 158 | ||
| 159 | # contruct the new pe file | |
| 160 | new_pe = file_data[:lastsecoffset + SECTION_HEADER_SIZE] | |
| 161 | new_pe += newsec_header | |
| 162 | new_pe += (sizeofheaders - len(new_pe)) * b'\x00' | |
| 163 | new_pe += file_data[sizeofheaders:] | |
| 164 | new_pe += newsec_data | |
| 165 | ||
| 166 | # parse the pe of the rebuild data | |
| 167 | pe = pefile.PE(data=new_pe, fast_load=True) | |
| 168 | # increase nr of sections | |
| 169 | pe.FILE_HEADER.NumberOfSections += 1 | |
| 170 | # update the imagesize | |
| 171 | pe.OPTIONAL_HEADER.SizeOfImage = newsec_viraddr + newsec_virsize | |
| 172 | # update the data_dir[imports] rva and size | |
| 173 | pe.OPTIONAL_HEADER.DATA_DIRECTORY[ pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_IMPORT'] ].VirtualAddress = import_dir_rva | |
| 174 | pe.OPTIONAL_HEADER.DATA_DIRECTORY[ pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_IMPORT'] ].Size = newsec_rawsize + IMAGE_DESCRIPTOR_HEADER_SIZE | |
| 175 | # since this is removed from the header we reset its values if set | |
| 176 | bound_imorts_dir = pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT'] | |
| 177 | if pe.OPTIONAL_HEADER.DATA_DIRECTORY[ bound_imorts_dir ].VirtualAddress != 0: | |
| 178 | pe.OPTIONAL_HEADER.DATA_DIRECTORY[ bound_imorts_dir ].VirtualAddress = 0 | |
| 179 | pe.OPTIONAL_HEADER.DATA_DIRECTORY[ bound_imorts_dir ].Size = 0 | |
| 180 | ||
| 181 | # write to new file | |
| 182 | return pe.write(), rvas | |
| 183 | ||
| 184 | # ---------------------------------------------------------------------------------------------------------------- | |
| 185 | def hook_code64(uc, address, size, user_data): | |
| 186 | code = uc.mem_read(address, size) | |
| 187 | insn = disassemble(code, 0) | |
| 188 | print_insn(insn) | |
| 189 | ||
| 190 | # ---------------------------------------------------------------------------------------------------------------- | |
| 191 | def emu(pe, offsets, imports, tracecode=False): | |
| 192 | imp = dict() | |
| 193 | imgbase = pe.OPTIONAL_HEADER.ImageBase | |
| 194 | imgsize = pe.OPTIONAL_HEADER.SizeOfImage | |
| 195 | ||
| 196 | esp = 0x1000000 | |
| 197 | ||
| 198 | mu = Uc(UC_ARCH_X86, UC_MODE_64) | |
| 199 | ||
| 200 | mu.mem_map(imgbase, imgsize) | |
| 201 | mu.mem_map(esp, 2 * 1024 * 1024) | |
| 202 | ||
| 203 | mu.mem_write(imgbase, bytes(pe.__data__)) | |
| 204 | mu.mem_write(esp, int(2 * 1024 * 1024) * b'\0') | |
| 205 | ||
| 206 | if tracecode: mu.hook_add(UC_HOOK_CODE, hook_code64) | |
| 207 | ||
| 208 | for offset, vm_eip in offsets: | |
| 209 | mu.reg_write(UC_X86_REG_ESP, esp + int(1 * 1024 * 1024)) | |
| 210 | try: | |
| 211 | mu.emu_start(imgbase + (vm_eip + 0x1000), imgbase + imgsize) | |
| 212 | except UcError as e: | |
| 213 | rip = mu.reg_read(UC_X86_REG_RIP) | |
| 214 | if rip in imports: | |
| 215 | print('0x%016x => %s' % (offset, imports[rip]))
| |
| 216 | imp[offset] = imports[rip] | |
| 217 | else: | |
| 218 | print(f'0x{rip:016x} not found')
| |
| 219 | return imp | |
| 220 | ||
| 221 | # ---------------------------------------------------------------------------------------------------------------- | |
| 222 | def print_insn(insn): | |
| 223 | print("0x%016x: %s %s" % (insn.address, insn.mnemonic.ljust(5, ' '), insn.op_str))
| |
| 224 | ||
| 225 | # ---------------------------------------------------------------------------------------------------------------- | |
| 226 | def disassemble(code, ep, maxinslen=12): | |
| 227 | for insn in md.disasm(code[ep:ep+maxinslen], ep): | |
| 228 | return insn | |
| 229 | ||
| 230 | # ---------------------------------------------------------------------------------------------------------------- | |
| 231 | def call_ins_in_range(code, minaddress, maxaddress): | |
| 232 | ep = 0 | |
| 233 | codelen = len(code) | |
| 234 | addresses = list() | |
| 235 | while ep < codelen: | |
| 236 | insn = disassemble(code, ep) | |
| 237 | if insn and insn.size == 5 and insn.id == X86_INS_CALL \ | |
| 238 | and insn.operands[0].type == X86_OP_IMM: | |
| 239 | addr = insn.operands[0].imm & 0xffffffff | |
| 240 | if addr >= minaddress and addr <= maxaddress: | |
| 241 | addresses.append( (ep, addr) ) | |
| 242 | ep += 1 | |
| 243 | return addresses | |
| 244 | ||
| 245 | # ---------------------------------------------------------------------------------------------------------------- | |
| 246 | def dump_fix(img, oep=None): | |
| 247 | pe = pefile.PE(data=img, fast_load=True) | |
| 248 | sectionAlignment = pe.OPTIONAL_HEADER.SectionAlignment | |
| 249 | for section in pe.sections: | |
| 250 | section.PointerToRawData = section.VirtualAddress | |
| 251 | vsize = align_int(section.Misc_VirtualSize, sectionAlignment) | |
| 252 | # section.SizeOfRawData = section.Misc_VirtualSize | |
| 253 | section.SizeOfRawData = vsize | |
| 254 | section.Misc_VirtualSize = vsize | |
| 255 | ||
| 256 | if oep: | |
| 257 | pe.OPTIONAL_HEADER.AddressOfEntryPoint = oep | |
| 258 | ||
| 259 | # disable aslr for now, we need to do a reloc correction actualy | |
| 260 | pe.OPTIONAL_HEADER.DllCharacteristics ^= 0x40 | |
| 261 | ||
| 262 | return pe.write() | |
| 263 | ||
| 264 | # ---------------------------------------------------------------------------------------------------------------- | |
| 265 | def analyse_vmp_api_stub(code, address): | |
| 266 | vmp_api = list() | |
| 267 | while True: | |
| 268 | insn = disassemble(code, address) | |
| 269 | if insn.id in [X86_INS_PUSH, X86_INS_POP]: | |
| 270 | vmp_api.append(insn) | |
| 271 | elif insn.id in [X86_INS_LEA, X86_INS_MOV] and (insn.operands[0].type == X86_OP_MEM or insn.operands[1].type == X86_OP_MEM): | |
| 272 | vmp_api.append(insn) | |
| 273 | elif insn.id == X86_INS_XCHG and insn.operands[0].type != insn.operands[1].type: | |
| 274 | vmp_api.append(insn) | |
| 275 | elif insn.id == X86_INS_RET: | |
| 276 | vmp_api.append(insn) | |
| 277 | break | |
| 278 | ||
| 279 | if insn.id == X86_INS_JMP: | |
| 280 | address = insn.operands[0].imm | |
| 281 | else: | |
| 282 | address += insn.size | |
| 283 | return vmp_api | |
| 284 | ||
| 285 | # ---------------------------------------------------------------------------------------------------------------- | |
| 286 | def action_callback_page_access( event ): | |
| 287 | global target_pe | |
| 288 | ||
| 289 | process = event.get_process() | |
| 290 | thread = event.get_thread() | |
| 291 | context = thread.get_context() | |
| 292 | ||
| 293 | img_base = process.get_image_base() | |
| 294 | ||
| 295 | rip = context['Rip'] | |
| 296 | ||
| 297 | # we need this rip range filter, because we keep hiting a vmp section first...?! | |
| 298 | if rip >= img_base + target_pe.sections[0].VirtualAddress \ | |
| 299 | and rip <= img_base + target_pe.sections[0].VirtualAddress + target_pe.sections[0].Misc_VirtualSize: | |
| 300 | print(f'page_access: OEP => 0x{rip:016x} - 0x{rip-img_base:016x}')
| |
| 301 | event.debug.erase_all_breakpoints() | |
| 302 | ||
| 303 | img_pe = dump_fix(process.read( img_base, target_pe.OPTIONAL_HEADER.SizeOfImage ), rip-img_base) | |
| 304 | ||
| 305 | pe = pefile.PE(data=img_pe, fast_load=True) | |
| 306 | pe.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_IMPORT']]) | |
| 307 | ||
| 308 | # grab the imported dll names from the import table | |
| 309 | implookup = dict() | |
| 310 | for entry in pe.DIRECTORY_ENTRY_IMPORT: | |
| 311 | dllname = entry.dll.decode() | |
| 312 | mod = process.get_module_by_name(dllname) | |
| 313 | ||
| 314 | pedll = pefile.PE('c:\\windows\\system32\\' + dllname, fast_load=True)
| |
| 315 | pedll.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_EXPORT']]) | |
| 316 | ||
| 317 | print(f'--> {dllname}')
| |
| 318 | for exp in pedll.DIRECTORY_ENTRY_EXPORT.symbols: | |
| 319 | if exp.name == None: | |
| 320 | continue | |
| 321 | addr = mod.resolve( exp.name ) | |
| 322 | if addr not in implookup: | |
| 323 | implookup[addr] = (dllname, exp.name.decode()) | |
| 324 | pedll.close() | |
| 325 | ||
| 326 | # locate call's into the vmp section | |
| 327 | code_section_data = pe.sections[0].get_data() | |
| 328 | vm_section = pe.sections[4] | |
| 329 | ||
| 330 | calltos = call_ins_in_range( | |
| 331 | code_section_data, | |
| 332 | vm_section.VirtualAddress, | |
| 333 | vm_section.VirtualAddress + vm_section.Misc_VirtualSize | |
| 334 | ) | |
| 335 | ||
| 336 | print('got %d vmp_api call(s)' % len(calltos))
| |
| 337 | ||
| 338 | # emulate the vmp api stubs | |
| 339 | call2imp = emu(pe, calltos, implookup) | |
| 340 | ||
| 341 | pe.close() | |
| 342 | ||
| 343 | # filter imports for iat rebuilding | |
| 344 | imptbl = dict() | |
| 345 | for callfrom in call2imp: | |
| 346 | dll, func = call2imp[callfrom] | |
| 347 | if dll not in imptbl: | |
| 348 | imptbl[dll] = list() | |
| 349 | ||
| 350 | if func not in imptbl[dll]: | |
| 351 | imptbl[dll].append( func ) | |
| 352 | ||
| 353 | # rebuild import table | |
| 354 | img_pe, iat_rvas = rebuild_import_table(img_pe, imptbl) | |
| 355 | ||
| 356 | # patch vmp call instructions | |
| 357 | fio = BytesIO(img_pe) | |
| 358 | ks = Ks(KS_ARCH_X86, KS_MODE_64) | |
| 359 | for cfrom, cto in calltos: | |
| 360 | vmpapi = analyse_vmp_api_stub(img_pe, cto + 0x1000) | |
| 361 | if vmpapi[2].id == X86_INS_LEA and vmpapi[2].operands[1].type == X86_OP_MEM and vmpapi[2].operands[1].mem.disp == 1: | |
| 362 | """ | |
| 363 | 0x00000000000fd9eb: push rax | |
| 364 | 0x00000000000fd9f7: mov rax, qword ptr [rsp + 8] | |
| 365 | 0x0000000000242ad2: lea rax, [rax + 1] | |
| 366 | 0x00000000001b7986: mov qword ptr [rsp + 8], rax | |
| 367 | 0x00000000001b7992: lea rax, [rip - 0x1af489] | |
| 368 | 0x0000000000173257: mov rax, qword ptr [rax + 0xd5f69] | |
| 369 | 0x000000000017325e: lea rax, [rax + 0x7fde623c] | |
| 370 | 0x000000000023dff5: xchg qword ptr [rsp], rax | |
| 371 | 0x0000000000256c24: ret | |
| 372 | """ | |
| 373 | dllname, funcname = call2imp[cfrom] | |
| 374 | encoding, count = ks.asm('call qword ptr[0x%016x]' % (iat_rvas[dllname][funcname] - 0x1000 - cfrom))
| |
| 375 | fio.seek( cfrom + 0x1000 ) | |
| 376 | fio.write( bytes(encoding) ) | |
| 377 | elif vmpapi[0].id == X86_INS_POP and vmpapi[-1].size == 1: | |
| 378 | """ | |
| 379 | 0x000000000020c562: pop rbx | |
| 380 | 0x000000000020bcda: xchg qword ptr [rsp], rbx | |
| 381 | 0x00000000000fcc27: push rbx | |
| 382 | 0x00000000000fcc28: lea rbx, [rip - 0xf30ea] | |
| 383 | 0x0000000000162a3c: mov rbx, qword ptr [rbx + 0x261566] | |
| 384 | 0x00000000000e2e6f: lea rbx, [rbx + 0x19bd2ee5] | |
| 385 | 0x00000000000e2e76: xchg qword ptr [rsp], rbx | |
| 386 | 0x0000000000259b32: ret | |
| 387 | """ | |
| 388 | dllname, funcname = call2imp[cfrom] | |
| 389 | encoding, count = ks.asm('call qword ptr[0x%016x]' % (iat_rvas[dllname][funcname] - 0x1000 - (cfrom - 1)))
| |
| 390 | fio.seek( (cfrom - 1) + 0x1000 ) | |
| 391 | fio.write( bytes(encoding) ) | |
| 392 | elif vmpapi[0].id == X86_INS_POP and vmpapi[-1].size == 3: | |
| 393 | """ | |
| 394 | 0x0000000000199b18: pop rbp | |
| 395 | 0x0000000000287e2e: xchg qword ptr [rsp], rbp | |
| 396 | 0x000000000016eeff: push rbp | |
| 397 | 0x000000000016ef04: lea rbp, [rip - 0x167a8b] | |
| 398 | 0x00000000002735e8: mov rbp, qword ptr [rbp + 0x10440d] | |
| 399 | 0x00000000000fcc15: lea rbp, [rbp + 0x1c97310c] | |
| 400 | 0x00000000001885a6: xchg qword ptr [rsp], rbp | |
| 401 | 0x0000000000150cb8: ret 8 | |
| 402 | """ | |
| 403 | dllname, funcname = call2imp[cfrom] | |
| 404 | # print(f'pop_call_ret8: 0x{img_base+0x1000+cfrom:016x} -> {funcname}')
| |
| 405 | encoding, count = ks.asm('jmp qword ptr[0x%016x]' % (iat_rvas[dllname][funcname] - 0x1000 - (cfrom - 2)))
| |
| 406 | fio.seek( (cfrom - 2) + 0x1000 ) | |
| 407 | fio.write( bytes(encoding) ) | |
| 408 | elif vmpapi[0].id == X86_INS_PUSH and vmpapi[-1].size == 3: | |
| 409 | """ | |
| 410 | 0x0000000000149386: push rsi | |
| 411 | 0x000000000014938a: lea rsi, [rip - 0x144344] | |
| 412 | 0x00000000001bfe00: mov rsi, qword ptr [rsi + 0x12b866] | |
| 413 | 0x0000000000224a7c: lea rsi, [rsi + 0x95a4b96] | |
| 414 | 0x00000000001f6ad4: xchg qword ptr [rsp], rsi | |
| 415 | 0x00000000001f6ad8: ret 8 | |
| 416 | """ | |
| 417 | dllname, funcname = call2imp[cfrom] | |
| 418 | # print(f'push_call_ret8: 0x{img_base+0x1000+cfrom:016x} -> {funcname}')
| |
| 419 | dist = 1 if img_pe[(cfrom-1)+0x1000] == 0x48 else 0 | |
| 420 | encoding, count = ks.asm('jmp qword ptr[0x%016x]' % (iat_rvas[dllname][funcname] - 0x1000 - (cfrom - dist)))
| |
| 421 | fio.seek( (cfrom - dist) + 0x1000 ) | |
| 422 | fio.write( bytes(encoding) ) | |
| 423 | ||
| 424 | else: | |
| 425 | print(f'unknown!!!! 0x{img_base+0x1000+cfrom:016x}')
| |
| 426 | ||
| 427 | fio.seek(0) | |
| 428 | with open('dump.exe', 'wb') as fout:
| |
| 429 | fout.write( fio.read() ) | |
| 430 | ||
| 431 | print('PE image dumped')
| |
| 432 | print('Done')
| |
| 433 | exit(1) | |
| 434 | ||
| 435 | # ---------------------------------------------------------------------------------------------------------------- | |
| 436 | def action_callback_NtProtectVirtualMemory( event ): | |
| 437 | global target_pe | |
| 438 | global mem_pages | |
| 439 | ||
| 440 | process = event.get_process() | |
| 441 | thread = event.get_thread() | |
| 442 | context = thread.get_context() | |
| 443 | ||
| 444 | img_base = process.get_image_base() | |
| 445 | img_size = target_pe.OPTIONAL_HEADER.SizeOfImage | |
| 446 | ||
| 447 | address = process.read_qword(context['Rdx']) | |
| 448 | size = process.read_qword(context['R8']) | |
| 449 | mode = context['R9'] | |
| 450 | ||
| 451 | if address >= img_base and address <= img_base + img_size: | |
| 452 | print(f'NtProtectVirtualMemory: address=0x{address:016x} size=0x{size:016x} prot={mode:x}')
| |
| 453 | if address not in mem_pages: | |
| 454 | mem_pages.append( address ) | |
| 455 | elif len(mem_pages) > 1 and address == mem_pages[-1]: | |
| 456 | # memory bp on the .text(0) section | |
| 457 | event.debug.erase_all_breakpoints() | |
| 458 | pid = process.get_pid() | |
| 459 | pages = (align_int(target_pe.sections[0].Misc_VirtualSize, 4096) // 4096) | |
| 460 | print(f'page_breakpoint: address=0x{img_base + target_pe.sections[0].VirtualAddress:016x} pages={pages} size=0x{pages*4096:x}')
| |
| 461 | event.debug.define_page_breakpoint( | |
| 462 | pid, | |
| 463 | img_base + target_pe.sections[0].VirtualAddress, | |
| 464 | pages=pages, | |
| 465 | action=action_callback_page_access) | |
| 466 | event.debug.enable_page_breakpoint(pid, img_base + target_pe.sections[0].VirtualAddress) | |
| 467 | ||
| 468 | # ---------------------------------------------------------------------------------------------------------------- | |
| 469 | class MyEventHandler( EventHandler ): | |
| 470 | def load_dll( self, event ): | |
| 471 | module = event.get_module() | |
| 472 | if module.match_name('ntdll.dll'):
| |
| 473 | # set HWBP on ntdll.NtProtectVirtualMemory | |
| 474 | address = module.resolve( 'NtProtectVirtualMemory' ) | |
| 475 | tid = event.get_thread().get_tid() | |
| 476 | event.debug.define_hardware_breakpoint( | |
| 477 | tid, | |
| 478 | address, | |
| 479 | triggerFlag=Debug.BP_BREAK_ON_EXECUTION, | |
| 480 | sizeFlag=Debug.BP_WATCH_BYTE, | |
| 481 | action=action_callback_NtProtectVirtualMemory) | |
| 482 | event.debug.enable_hardware_breakpoint(tid, address) | |
| 483 | ||
| 484 | # ---------------------------------------------------------------------------------------------------------------- | |
| 485 | def debugger( argv ): | |
| 486 | global target_pe | |
| 487 | global mem_pages | |
| 488 | ||
| 489 | target_pe = pefile.PE(argv[0], fast_load=True) | |
| 490 | mem_pages = list() | |
| 491 | ||
| 492 | with Debug( MyEventHandler(), bKillOnExit = True ) as debug: | |
| 493 | debug.execv( argv ) | |
| 494 | debug.loop() | |
| 495 | ||
| 496 | # ---------------------------------------------------------------------------------------------------------------- | |
| 497 | if __name__ == "__main__": | |
| 498 | debugger( sys.argv[1:] ) |