├── .gitignore ├── LICENSE ├── imgs └── 1.gif ├── novmpy.py ├── novmpy ├── __init__.py ├── bridge.py ├── handler.py ├── match_helper.py ├── ui.py ├── views │ ├── hview.py │ └── vtil_graph.py ├── vm.py ├── vm_const.py ├── vm_lifter.py └── x86_deobf.py ├── readme.md ├── requirements.txt └── test.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # vscode 132 | .vscode/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, wallds 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /imgs/1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wallds/NoVmpy/ffdbfcf4b9f39ac598263a8f3468ff79617f09d1/imgs/1.gif -------------------------------------------------------------------------------- /novmpy.py: -------------------------------------------------------------------------------- 1 | # idapython fix <= 7.6 2 | # DO NOT REMOVE ME 3 | import sys 4 | sys.stdout.encoding = 'utf-8' 5 | 6 | 7 | # for test 8 | # ida_loader.load_plugin('F:/NoVmpy/novmpy.py') 9 | import idaapi 10 | 11 | NOVMPY_VERSION = "0.1" 12 | 13 | 14 | class NoVmpyPlugin(idaapi.plugin_t): 15 | flags = 0 16 | comment = "" 17 | help = "" 18 | wanted_name = "NoVmpy" 19 | wanted_hotkey = "" 20 | 21 | def __init__(self): 22 | super(NoVmpyPlugin, self).__init__() 23 | 24 | def init(self): 25 | from novmpy.ui import UIManager 26 | self.ui = UIManager() 27 | 28 | return idaapi.PLUGIN_KEEP 29 | 30 | def run(self, args): 31 | pass 32 | 33 | def term(self): 34 | from novmpy.handler import vm_handlers 35 | vm_handlers.clear() 36 | 37 | 38 | def PLUGIN_ENTRY(): 39 | return NoVmpyPlugin() 40 | -------------------------------------------------------------------------------- /novmpy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wallds/NoVmpy/ffdbfcf4b9f39ac598263a8f3468ff79617f09d1/novmpy/__init__.py -------------------------------------------------------------------------------- /novmpy/bridge.py: -------------------------------------------------------------------------------- 1 | from capstone import * 2 | 3 | 4 | class BridgeBase: 5 | def __init__(self): 6 | # arch size 7 | self.size = 8 if self.is64bit() else 4 8 | 9 | if self.is64bit(): 10 | self.md = Cs(CS_ARCH_X86, CS_MODE_64) 11 | else: 12 | self.md = Cs(CS_ARCH_X86, CS_MODE_32) 13 | self.md.detail = True 14 | 15 | def read(self, addr, size, dir_=1): 16 | raise NotImplementedError() 17 | 18 | def is_readable(self, addr, size, dir_): 19 | raise NotImplementedError() 20 | 21 | def is_writeable(self, addr, size, dir_): 22 | raise NotImplementedError() 23 | 24 | def get_bytes(self, addr, size): 25 | raise NotImplementedError() 26 | 27 | def get_segs(self): 28 | raise NotImplementedError() 29 | 30 | def get_base(self): 31 | raise NotImplementedError() 32 | 33 | def is64bit(self): 34 | raise NotImplementedError() 35 | 36 | def update_msg(self, msg): 37 | raise NotImplementedError() 38 | 39 | def disasm(self, code, offset, count=0): 40 | return self.md.disasm(code, offset, count) 41 | 42 | def disasm_one(self, offset, size=15): 43 | for x in self.md.disasm(self.get_bytes(offset, size), offset, 1): 44 | return x 45 | return None 46 | 47 | def reg_name(self, reg_id, default=None): 48 | return self.md.reg_name(reg_id, default) 49 | 50 | 51 | class BridgeLocal(BridgeBase): # bridge for local 52 | def __init__(self, exe_path): 53 | import cle 54 | self.ld = cle.Loader(exe_path, False) 55 | super().__init__() 56 | 57 | def read(self, addr, size, dir_=1): 58 | addr_ = addr 59 | if dir_ < 0: 60 | addr_ -= size 61 | b = self.get_bytes(addr_, size) 62 | return int.from_bytes(b, byteorder='little') 63 | 64 | def is_readable(self, addr, size, dir_=1): 65 | if dir_ > 0: 66 | return addr >= self.ld.main_object.min_addr and addr+size < self.ld.main_object.max_addr 67 | else: 68 | return addr-size >= self.ld.main_object.min_addr and addr <= self.ld.main_object.max_addr 69 | 70 | def is_writeable(self, addr, size, dir_=1): 71 | for seg in self.get_segs(): 72 | if seg.vaddr <= addr < seg.vaddr+seg.memsize: 73 | return seg.is_writable 74 | return False 75 | 76 | def get_bytes(self, addr, size): 77 | return self.ld.memory.load(addr, size) 78 | 79 | def get_segs(self): 80 | return self.ld.main_object.sections 81 | 82 | def get_base(self): 83 | return self.ld.main_object.mapped_base 84 | 85 | def is64bit(self): 86 | return self.ld.main_object.arch.name == 'AMD64' 87 | 88 | def update_msg(self, msg): 89 | pass 90 | 91 | 92 | class BridgeIda(BridgeBase): # bridge for ida: 93 | def __init__(self): 94 | super().__init__() 95 | 96 | def read(self, addr, size, dir_=1): 97 | addr_ = addr 98 | if dir_ < 0: 99 | addr_ -= size 100 | b = self.get_bytes(addr_, size) 101 | return int.from_bytes(b, byteorder='little') 102 | 103 | def is_readable(self, addr, size, dir_=1): 104 | import idc 105 | if dir_ < 0: 106 | return idc.is_mapped(addr-size) 107 | if addr == 0xffffffffffffffff: 108 | return False 109 | return idc.is_mapped(addr) and idc.is_loaded(addr) 110 | 111 | def is_writeable(self, addr, size, dir_=1): 112 | import ida_segment 113 | if dir_ < 0: 114 | addr -= size 115 | seg = ida_segment.getseg(addr) 116 | if seg is not None: 117 | return (seg.perm & ida_segment.SEGPERM_WRITE) != 0 118 | return False 119 | 120 | def get_segs(self): 121 | class dummy_seg(): 122 | pass 123 | 124 | import ida_segment 125 | n = 0 126 | seg = ida_segment.getnseg(n) 127 | while seg is not None: 128 | dummy = dummy_seg() 129 | dummy.is_executable = (seg.perm & ida_segment.SEGPERM_EXEC) != 0 130 | dummy.vaddr = seg.start_ea 131 | dummy.memsize = seg.size() 132 | dummy.min_addr = seg.start_ea 133 | dummy.max_addr = seg.end_ea 134 | yield dummy 135 | n += 1 136 | seg = ida_segment.getnseg(n) 137 | 138 | def get_base(self): 139 | return 0 140 | 141 | def get_bytes(self, addr, size): 142 | import ida_bytes 143 | return ida_bytes.get_bytes(addr, size) 144 | 145 | def is64bit(self): 146 | import idaapi 147 | return idaapi.inf_is_64bit() 148 | 149 | def update_msg(self, msg): 150 | import ida_kernwin 151 | ida_kernwin.replace_wait_box(msg) 152 | return not ida_kernwin.user_cancelled() 153 | 154 | 155 | class BridgeDummy(BridgeBase): # bridge for dummy 156 | def __init__(self): 157 | super().__init__() 158 | 159 | def is64bit(self): 160 | return True 161 | 162 | 163 | try: 164 | local_mode = False 165 | import idaapi 166 | except: 167 | local_mode = True 168 | 169 | 170 | bridge = None 171 | 172 | if local_mode: 173 | bridge = BridgeLocal( 174 | r'F:\VMP_Sample\Sample_vmp3.4\test_misc\test_misc.x86.vmp.exe') 175 | else: 176 | bridge = BridgeIda() 177 | -------------------------------------------------------------------------------- /novmpy/handler.py: -------------------------------------------------------------------------------- 1 | from unicorn import * 2 | from unicorn.x86_const import * 3 | from novmpy.match_helper import * 4 | from novmpy.x86_deobf import * 5 | from novmpy.vm import * 6 | from novmpy.vm_const import * 7 | import struct 8 | from pyvtil import * 9 | from typing import List 10 | 11 | vm_handlers = {} 12 | 13 | FLAG_CF = vtil.REG_FLAGS.select(1, 0) 14 | FLAG_PF = vtil.REG_FLAGS.select(1, 2) 15 | FLAG_AF = vtil.REG_FLAGS.select(1, 4) 16 | FLAG_ZF = vtil.REG_FLAGS.select(1, 6) 17 | FLAG_SF = vtil.REG_FLAGS.select(1, 7) 18 | FLAG_DF = vtil.REG_FLAGS.select(1, 10) 19 | FLAG_OF = vtil.REG_FLAGS.select(1, 11) 20 | 21 | if vtil.arch.size == 4: 22 | ZAX = vtil.x86_reg.EAX 23 | ZBX = vtil.x86_reg.EBX 24 | ZCX = vtil.x86_reg.ECX 25 | ZDX = vtil.x86_reg.EDX 26 | else: 27 | ZAX = vtil.x86_reg.RAX 28 | ZBX = vtil.x86_reg.RBX 29 | ZCX = vtil.x86_reg.RCX 30 | ZDX = vtil.x86_reg.RDX 31 | 32 | 33 | def make_virtual_register(context_offset, size): 34 | return vtil.register_desc(vtil.register_virtual, 35 | int(context_offset//vtil.arch.size), 36 | size*8, 37 | int(context_offset % vtil.arch.size)*8) 38 | 39 | 40 | class VMIns(object): 41 | def __init__(self): 42 | self.address = 0 43 | self.id = VM_INS_INVALID 44 | self.mne = '' 45 | self.opstr = '' 46 | self.opsize = 0 47 | self.data = 0 48 | self.haddr = 0 # vm handler address 49 | self.comment = '' 50 | 51 | def __str__(self) -> str: 52 | if not self.opstr: 53 | s = '{:08X}| {}'.format(self.address, self.mne) 54 | else: 55 | s = '{:08X}| {} {}'.format(self.address, self.mne, self.opstr) 56 | if self.comment: 57 | s += ';'+self.comment 58 | return s 59 | 60 | 61 | def align_size(v): 62 | if v == 1: 63 | return 2 64 | return v 65 | 66 | 67 | def same_size(x, y): 68 | return align_size(x) == align_size(y) 69 | 70 | 71 | def size2name(size): 72 | s = '' 73 | if size == 1: 74 | s = 'byte' 75 | elif size == 2: 76 | s = 'word' 77 | elif size == 4: 78 | s = 'dword' 79 | elif size == 8: 80 | s = 'qword' 81 | else: 82 | assert False 83 | return s 84 | 85 | 86 | def reg2name(index, off, size): 87 | prefix = 'vm_r{}'.format(index) 88 | suffix = '' 89 | if size == 1: 90 | suffix = 'b' 91 | elif size == 2: 92 | suffix = 'w' 93 | elif size == 4: 94 | suffix = 'd' 95 | elif size == 8: 96 | suffix = 'q' 97 | if size <= 2: 98 | if off != 0: 99 | suffix += 'h' 100 | else: 101 | suffix += 'l' 102 | return prefix+suffix 103 | 104 | 105 | class VMBase(object): 106 | def __init__(self, **kwargs): 107 | self.name = 'vmp_base' 108 | self.bytecode_size = 0 109 | self.opsize = 0 110 | self.address = kwargs.get('address', 0) 111 | self.hbase = kwargs.get('hbase', 0) 112 | self.insns = kwargs.get('insns', []) 113 | self.body = kwargs.get('body', []) 114 | self.connect = kwargs.get('connect', []) 115 | self.config: VMConfig = kwargs.get('config', None) 116 | self.conn_config: VMConfig = kwargs.get('conn_config', None) 117 | 118 | def get_next(self, vmstate: VMState): 119 | val = vmstate.fetch(self.dec_size) 120 | off = vmstate.decode_emu( 121 | self.dec_conn, val, self.dec_reg, self.dec_size) 122 | b1 = struct.pack('>= 8 371 | hash ^= v 372 | hash ^= self.xorkey 373 | return hash ^ 0xFFFFFFFF 374 | 375 | def match(self): 376 | if len(self.body) < 15: 377 | return False 378 | args = {} 379 | mh = MatchHelper(self.body, self.config) 380 | # movzx esi, byte ptr [eax] 381 | # mov esi, dword ptr [esi*4 + 0x7b06b8] 382 | # XOR 383 | # INC 384 | # DEC 385 | # NOT 386 | if (mh.load(0, {'reg': 'ph1'}) and 387 | mh.load(bridge.size, {'reg': 'ph2'}) and 388 | mh.batch([X86_INS_XOR, X86_INS_INC, X86_INS_DEC, X86_INS_NOT]) and 389 | mh.store_dword(0)): 390 | for i in self.body: 391 | i: CsInsn 392 | if bridge.is64bit(): 393 | if (instr_match(i, X86_INS_LEA, [X86_OP_REG, X86_OP_MEM], [None, {'base': X86_REG_RIP, 'index': X86_REG_INVALID, 'scale': 1}])): 394 | (op1, op2) = i.operands 395 | self.table = i.address+op2.mem.disp+i.size 396 | elif (instr_match(i, X86_INS_MOV, [X86_OP_REG, X86_OP_MEM], [None, {'base': X86_REG_INVALID, 'scale': 4}])): 397 | (op1, op2) = i.operands 398 | if op1.reg == op2.mem.base: 399 | self.table = op2.mem.disp 400 | if instr_match(i, X86_INS_XOR, [X86_OP_REG, X86_OP_IMM]): 401 | (op1, op2) = i.operands 402 | self.xorkey = op2.imm 403 | break 404 | return True 405 | return False 406 | 407 | def get_instr(self, vmstate: VMState): 408 | i = VMIns() 409 | i.haddr = self.address 410 | i.id = VM_INS_CRC 411 | i.address = vmstate.get_address() 412 | i.mne = 'crc' 413 | i.data = 0 414 | i.opsize = self.opsize 415 | return i 416 | 417 | def generator(self, ins: VMIns, block: vtil.basic_block): 418 | size, addr = block.tmp(vtil.arch.bit_count, vtil.arch.bit_count) 419 | crc_value = block.tmp(32) 420 | block.pop(size) 421 | block.pop(addr) 422 | block.mov(crc_value, vtil.UNDEFINED) 423 | block.push(crc_value) 424 | 425 | 426 | class VMAdd(VMBase): 427 | def __init__(self, **kwargs): 428 | super().__init__(**kwargs) 429 | self.name = 'vmp_add' 430 | 431 | def match(self): 432 | mh = MatchHelper(self.body, self.config) 433 | args = {} 434 | if mh.load(0, {'size': 'size'}) and\ 435 | mh.load(align_size(mh.get_ph('size'))) and\ 436 | mh.match(X86_INS_ADD, [X86_OP_REG, X86_OP_REG]) and\ 437 | mh.store(bridge.size) and\ 438 | mh.store_eflags(): 439 | self.opsize = mh.get_ph('size') 440 | return True 441 | return False 442 | 443 | def get_instr(self, vmstate: VMState): 444 | i = VMIns() 445 | i.haddr = self.address 446 | i.id = VM_INS_ADD 447 | i.address = vmstate.get_address() 448 | i.mne = 'add{}'.format(self.opsize) 449 | i.opstr = '' 450 | i.data = 0 451 | i.opsize = self.opsize 452 | return i 453 | 454 | def generator(self, ins: VMIns, block: vtil.basic_block): 455 | t0, t1, t2 = block.tmp(ins.opsize*8, ins.opsize*8, ins.opsize*8) 456 | b0, b1, b2, b3 = block.tmp(1, 1, 1, 1) 457 | block.pop(t0) 458 | block.pop(t1) 459 | 460 | block.mov(t2, t1) 461 | block.add(t1, t0) 462 | 463 | block.tl(FLAG_SF, t1, 0) 464 | block.te(FLAG_ZF, t1, 0) 465 | block.tul(FLAG_CF, t1, t2) 466 | 467 | block.tl(b2, t2, 0) 468 | block.tl(b3, t0, 0) 469 | block.te(b0, b2, b3) 470 | 471 | block.tl(b2, t2, 0) 472 | block.tl(b3, t1, 0) 473 | block.tne(b1, b2, b3) 474 | 475 | block.mov(FLAG_OF, b0) 476 | block.band(FLAG_OF, b1) 477 | 478 | block.push(t1) 479 | block.pushf() 480 | 481 | 482 | class VMNor(VMBase): # not not and 483 | # (~a)&(~b) = ~(a|b) 484 | 485 | def __init__(self, **kwargs): 486 | super().__init__(**kwargs) 487 | self.name = 'vmp_nor' 488 | 489 | def match(self): 490 | mh = MatchHelper(self.body, self.config) 491 | args = {} 492 | if (mh.load(0, {'size': 'size'}) and mh.load(align_size(mh.get_ph('size'))) and 493 | mh.batch([X86_INS_NOT, X86_INS_NOT, X86_INS_AND]) and 494 | mh.store(bridge.size) and mh.store_eflags()): 495 | self.opsize = mh.get_ph('size') 496 | return True 497 | return False 498 | 499 | def get_instr(self, vmstate: VMState): 500 | i = VMIns() 501 | i.haddr = self.address 502 | i.id = VM_INS_NOR 503 | i.address = vmstate.get_address() 504 | i.mne = 'nor{}'.format(self.opsize) 505 | i.opstr = '' 506 | i.data = 0 507 | i.opsize = self.opsize 508 | return i 509 | 510 | def generator(self, ins: VMIns, block: vtil.basic_block): 511 | t0, t1 = block.tmp(ins.opsize*8, ins.opsize*8) 512 | block.pop(t0) 513 | block.pop(t1) 514 | block.bnot(t0) 515 | block.bnot(t1) 516 | block.band(t0, t1) 517 | block.tl(FLAG_SF, t0, 0) 518 | block.te(FLAG_ZF, t0, 0) 519 | block.mov(FLAG_OF, 0) 520 | block.mov(FLAG_CF, 0) 521 | block.push(t0) 522 | block.pushf() 523 | 524 | 525 | class VMNand(VMBase): # not not or 526 | def __init__(self, **kwargs): 527 | super().__init__(**kwargs) 528 | self.name = 'vmp_nand' 529 | 530 | def match(self): 531 | mh = MatchHelper(self.body, self.config) 532 | args = {} 533 | if (mh.load(0, {'size': 'size'}) and mh.load(align_size(mh.get_ph('size'))) and 534 | mh.batch([X86_INS_NOT, X86_INS_NOT, X86_INS_OR]) and 535 | mh.store(bridge.size) and mh.store_eflags()): 536 | self.opsize = mh.get_ph('size') 537 | return True 538 | return False 539 | 540 | def get_instr(self, vmstate: VMState): 541 | i = VMIns() 542 | i.haddr = self.address 543 | i.id = VM_INS_NAND 544 | i.address = vmstate.get_address() 545 | i.mne = 'nand{}'.format(self.opsize) 546 | i.opstr = '' 547 | i.data = 0 548 | i.opsize = self.opsize 549 | return i 550 | 551 | def generator(self, ins: VMIns, block: vtil.basic_block): 552 | t0, t1 = block.tmp(ins.opsize*8, ins.opsize*8) 553 | block.pop(t0) 554 | block.pop(t1) 555 | block.bnot(t0) 556 | block.bnot(t1) 557 | block.bor(t0, t1) 558 | block.tl(FLAG_SF, t0, 0) 559 | block.te(FLAG_ZF, t0, 0) 560 | block.mov(FLAG_OF, 0) 561 | block.mov(FLAG_CF, 0) 562 | block.push(t0) 563 | block.pushf() 564 | 565 | 566 | class VMStr(VMBase): 567 | def __init__(self, **kwargs): 568 | super().__init__(**kwargs) 569 | self.name = 'vmp_str' 570 | 571 | def match(self): 572 | if abs(len(self.body) - 4) > 2: 573 | return False 574 | mh = MatchHelper(self.body, self.config) 575 | if mh.load_zword(0, {'reg': 'ph1'}) and\ 576 | mh.load(bridge.size, {'reg': 'ph2', 'size': 'size'}) and\ 577 | mh.mem_write({'addr': 'ph1', 'val': 'ph2', 'segment': 'ph3', 'size': 'size'}): 578 | self.segment = mh.get_ph('ph3') 579 | self.opsize = mh.get_ph('size') 580 | return True 581 | return False 582 | 583 | def get_instr(self, vmstate: VMState): 584 | i = VMIns() 585 | i.haddr = self.address 586 | i.id = VM_INS_STR 587 | i.address = vmstate.get_address() 588 | i.mne = 'store' 589 | s = size2name(self.opsize) 590 | if self.segment == X86_REG_INVALID: 591 | i.opstr = s+' '+bridge.reg_name(X86_REG_DS) 592 | else: 593 | i.opstr = s+' '+bridge.reg_name(self.segment) 594 | i.data = self.segment 595 | i.opsize = self.opsize 596 | return i 597 | 598 | def generator(self, ins: VMIns, block: vtil.basic_block): 599 | t0, t1 = block.tmp(vtil.arch.bit_count, ins.opsize*8) 600 | block.pop(t0) 601 | block.pop(t1) 602 | if self.segment == X86_REG_GS: 603 | block.vemits("mov rax, gs:0x30") 604 | block.vpinw(ZAX) 605 | block.add(t0, ZAX) 606 | elif self.segment == X86_REG_FS: 607 | block.vemits("mov eax, fs:0x18") 608 | block.vpinw(ZAX) 609 | block.add(t0, ZAX) 610 | block.str(t0, 0, t1) 611 | 612 | 613 | class VMLdr(VMBase): 614 | def __init__(self, **kwargs): 615 | super().__init__(**kwargs) 616 | self.name = 'vmp_ldr' 617 | 618 | def match(self): 619 | if abs(len(self.body) - 3) > 2: 620 | return False 621 | mh = MatchHelper(self.body, self.config) 622 | if mh.load_zword(0, {'reg': 'ph1'}) and \ 623 | mh.mem_read({'addr': 'ph1', 'val': 'ph2', 'segment': 'ph3', 'size': 'size'}) and \ 624 | mh.store(0): 625 | self.segment = mh.get_ph('ph3') 626 | self.opsize = mh.get_ph('size') 627 | return True 628 | return False 629 | 630 | def get_instr(self, vmstate: VMState): 631 | i = VMIns() 632 | i.haddr = self.address 633 | i.id = VM_INS_LDR 634 | i.address = vmstate.get_address() 635 | i.mne = 'load' 636 | s = size2name(self.opsize) 637 | if self.segment == X86_REG_INVALID: 638 | i.opstr = s+' '+bridge.reg_name(X86_REG_DS) 639 | else: 640 | i.opstr = s+' '+bridge.reg_name(self.segment) 641 | i.data = self.segment 642 | i.opsize = self.opsize 643 | return i 644 | 645 | def generator(self, ins: VMIns, block: vtil.basic_block): 646 | t0, t1 = block.tmp(vtil.arch.bit_count, ins.opsize*8) 647 | block.pop(t0) 648 | if self.segment == X86_REG_GS: 649 | block.vemits("mov rax, gs:0x30") 650 | block.vpinw(ZAX) 651 | block.add(t0, ZAX) 652 | elif self.segment == X86_REG_FS: 653 | block.vemits("mov eax, fs:0x18") 654 | block.vpinw(ZAX) 655 | block.add(t0, ZAX) 656 | block.ldd(t1, t0, 0) 657 | block.push(t1) 658 | 659 | 660 | class VMShift(VMBase): 661 | # X86_INS_RCL, X86_INS_RCR, X86_INS_ROL, X86_INS_ROR, 662 | # X86_INS_SAL, X86_INS_SAR, X86_INS_SHL, X86_INS_SHR 663 | def __init__(self, **kwargs): 664 | super().__init__(**kwargs) 665 | self.name = 'vmp_shift' 666 | self.ins = X86_INS_INVALID 667 | 668 | def match(self): 669 | if len(self.body) < 5: 670 | return False 671 | mh = MatchHelper(self.body, self.config) 672 | shi = [X86_INS_RCL, X86_INS_RCR, X86_INS_ROL, X86_INS_ROR, 673 | X86_INS_SAL, X86_INS_SAR, X86_INS_SHL, X86_INS_SHR] 674 | args = {} 675 | if mh.load(0, {'reg': 'ph1', 'size': 'size'}) and \ 676 | mh.load_byte(align_size(mh.get_ph('size')), {'reg': 'p2'}) and\ 677 | mh.among(shi, {'ins': 'ph3'}) and\ 678 | mh.store(bridge.size, {'reg': 'ph1'}) and\ 679 | mh.store_eflags(): 680 | self.ins = mh.get_ph('ph3') 681 | self.opsize = mh.get_ph('size') 682 | return True 683 | return False 684 | 685 | def get_instr(self, vmstate: VMState): 686 | m = { 687 | X86_INS_RCL: VM_INS_RCL, 688 | X86_INS_RCR: VM_INS_RCR, 689 | X86_INS_ROL: VM_INS_ROL, 690 | X86_INS_ROR: VM_INS_ROR, 691 | X86_INS_SAL: VM_INS_SAL, 692 | X86_INS_SAR: VM_INS_SAR, 693 | X86_INS_SHL: VM_INS_SHL, 694 | X86_INS_SHR: VM_INS_SHR 695 | } 696 | i2n = { 697 | VM_INS_RCL: 'rcl', 698 | VM_INS_RCR: 'rcr', 699 | VM_INS_ROL: 'rol', 700 | VM_INS_ROR: 'ror', 701 | VM_INS_SAL: 'sal', 702 | VM_INS_SAR: 'sar', 703 | VM_INS_SHL: 'shl', 704 | VM_INS_SHR: 'shr', 705 | } 706 | i = VMIns() 707 | i.haddr = self.address 708 | i.id = m[self.ins] 709 | i.address = vmstate.get_address() 710 | i.mne = '{}{}'.format(i2n[i.id], self.opsize) 711 | i.data = 0 712 | i.opsize = self.opsize 713 | return i 714 | 715 | def generator(self, ins: VMIns, block: vtil.basic_block): 716 | t0, t1, t2 = block.tmp(ins.opsize*8, ins.opsize*8, 8) 717 | cf = t1.select(1, t1.bit_count - 1) 718 | ofx = t0.select(1, t0.bit_count - 1) 719 | 720 | block.pop(t0) 721 | block.pop(t2) 722 | block.mov(t1, t0) 723 | if self.ins == X86_INS_SHL: 724 | block.bshl(t0, t2) 725 | elif self.ins == X86_INS_SHR: 726 | block.bshr(t0, t2) 727 | elif self.ins == X86_INS_ROL: 728 | block.brol(t0, t2) 729 | elif self.ins == X86_INS_ROR: 730 | block.bror(t0, t2) 731 | else: 732 | assert False 733 | block.tl(FLAG_SF, t0, 0) 734 | block.te(FLAG_ZF, t0, 0) 735 | block.mov(FLAG_OF, ofx) 736 | block.mov(FLAG_CF, cf) 737 | block.bxor(FLAG_OF, cf) 738 | block.push(t0) 739 | block.pushf() 740 | 741 | 742 | class VMShld(VMBase): 743 | def __init__(self, **kwargs): 744 | super().__init__(**kwargs) 745 | self.name = 'vmp_shld' 746 | 747 | def match(self): 748 | if len(self.body) < 7: 749 | return False 750 | args = {} 751 | mh = MatchHelper(self.body, self.config) 752 | mh.reset() 753 | if (mh.load(0, {'reg': 'ph1', 'size': 'size'}) and 754 | mh.load(mh.get_ph('size'), {'reg': 'ph2'}) and 755 | mh.load_byte(mh.get_ph('size')*2, {'reg': 'ph3'}) and 756 | mh.batch([X86_INS_SHLD]) and 757 | mh.store(bridge.size, {'reg': 'ph1'}) and 758 | mh.store_eflags()): 759 | self.opsize = mh.get_ph('size') 760 | return True 761 | return False 762 | 763 | def get_instr(self, vmstate: VMState): 764 | i = VMIns() 765 | i.haddr = self.address 766 | i.id = VM_INS_SHLD 767 | i.address = vmstate.get_address() 768 | i.mne = 'shld{}'.format(self.opsize) 769 | i.data = 0 770 | i.opsize = self.opsize 771 | return i 772 | 773 | def generator(self, ins: VMIns, block: vtil.basic_block): 774 | t0, t1, t2, t3 = block.tmp(ins.opsize*8, ins.opsize*8, 8, 8) 775 | 776 | block.pop(t0) 777 | block.pop(t1) 778 | block.pop(t2) 779 | 780 | block.bshl(t0, t2) 781 | 782 | block.mov(t3, vtil.make_uint(ins.opsize*8, 8)) 783 | block.sub(t3, t2) 784 | 785 | block.bshr(t1, t3) 786 | 787 | block.bor(t0, t1) 788 | 789 | block.tl(FLAG_SF, t0, 0) 790 | block.te(FLAG_ZF, t0, 0) 791 | block.mov(FLAG_OF, vtil.UNDEFINED) 792 | block.mov(FLAG_CF, vtil.UNDEFINED) 793 | block.push(t0) 794 | block.pushf() 795 | 796 | 797 | class VMShrd(VMBase): 798 | def __init__(self, **kwargs): 799 | super().__init__(**kwargs) 800 | self.name = 'vmp_shrd' 801 | 802 | def match(self): 803 | if len(self.body) < 7: 804 | return False 805 | args = {} 806 | mh = MatchHelper(self.body, self.config) 807 | mh.reset() 808 | if (mh.load(0, {'reg': 'ph1', 'size': 'size'}) and 809 | mh.load(mh.get_ph('size'), {'reg': 'ph2'}) and 810 | mh.load_byte(mh.get_ph('size')*2, {'reg': 'ph3'}) and 811 | mh.batch([X86_INS_SHRD]) and 812 | mh.store(bridge.size, {'reg': 'ph1'}) and 813 | mh.store_eflags()): 814 | self.opsize = mh.get_ph('size') 815 | return True 816 | return False 817 | 818 | def get_instr(self, vmstate: VMState): 819 | i = VMIns() 820 | i.haddr = self.address 821 | i.id = VM_INS_SHRD 822 | i.address = vmstate.get_address() 823 | i.mne = 'shrd{}'.format(self.opsize) 824 | i.data = 0 825 | i.opsize = self.opsize 826 | return i 827 | 828 | def generator(self, ins: VMIns, block: vtil.basic_block): 829 | t0, t1, t2, t3 = block.tmp(ins.opsize*8, ins.opsize*8, 8, 8) 830 | 831 | block.pop(t0) 832 | block.pop(t1) 833 | block.pop(t2) 834 | 835 | block.bshr(t0, t2) 836 | 837 | block.mov(t3, vtil.make_uint(ins.opsize*8, 8)) 838 | block.sub(t3, t2) 839 | 840 | block.bshl(t1, t3) 841 | 842 | block.bor(t0, t1) 843 | 844 | block.tl(FLAG_SF, t0, 0) 845 | block.te(FLAG_ZF, t0, 0) 846 | block.mov(FLAG_OF, vtil.UNDEFINED) 847 | block.mov(FLAG_CF, vtil.UNDEFINED) 848 | block.push(t0) 849 | block.pushf() 850 | 851 | 852 | class VMMul(VMBase): 853 | def __init__(self, **kwargs): 854 | super().__init__(**kwargs) 855 | self.name = 'vmp_mul' 856 | 857 | def match(self): 858 | if len(self.body) < 7: 859 | return False 860 | args = {} 861 | mh = MatchHelper(self.body, self.config) 862 | if (mh.load_byte(2, {'reg': 'ph1'}) and 863 | mh.load_byte(0, {'reg': 'ph2'}) and 864 | mh.batch([X86_INS_MUL]) and 865 | mh.store_word(bridge.size, {'reg': 'ph1'}) and 866 | mh.store_eflags()): 867 | self.opsize = 1 868 | return True 869 | mh.reset() 870 | if (mh.load(None, {'reg': 'ph1', 'size': 'size1'}) and 871 | mh.load(0, {'reg': 'ph2', 'size': 'size1'}) and 872 | mh.batch([X86_INS_MUL]) and 873 | mh.store(bridge.size, {'reg': 'ph2'}) and 874 | mh.store(bridge.size+mh.get_ph('size1'), {'reg': 'ph1'}) and 875 | mh.store_eflags()): 876 | self.opsize = mh.get_ph('size1') 877 | return True 878 | return False 879 | 880 | def get_instr(self, vmstate: VMState): 881 | i = VMIns() 882 | i.haddr = self.address 883 | i.id = VM_INS_MUL 884 | i.address = vmstate.get_address() 885 | i.mne = 'mul{}'.format(self.opsize) 886 | i.data = 0 887 | i.opsize = self.opsize 888 | return i 889 | 890 | def generator(self, ins: VMIns, block: vtil.basic_block): 891 | a0, a1, d = block.tmp(ins.opsize*8, ins.opsize*8, ins.opsize*8) 892 | if ins.opsize == 1: 893 | a2 = block.tmp(16) 894 | block.pop(d) 895 | block.pop(a0) 896 | block.mov(a2, a0) 897 | 898 | block.mul(a2, d) 899 | 900 | block.push(a2) 901 | block.pushf() 902 | else: 903 | block.pop(d) 904 | block.pop(a0) 905 | block.mov(a1, a0) 906 | 907 | block.mul(a0, d) 908 | block.mulhi(a1, d) 909 | 910 | block.push(a0) 911 | block.push(a1) 912 | block.pushf() 913 | 914 | 915 | class VMImul(VMBase): 916 | def __init__(self, **kwargs): 917 | super().__init__(**kwargs) 918 | self.name = 'vmp_imul' 919 | 920 | def match(self): 921 | if len(self.body) < 6: 922 | return False 923 | args = {} 924 | mh = MatchHelper(self.body, self.config) 925 | mh.reset() 926 | if (mh.load_byte(2, {'reg': 'ph1'}) and 927 | mh.load_byte(0, {'reg': 'ph2'}) and 928 | mh.batch([X86_INS_IMUL]) and 929 | mh.store_word(bridge.size, {'reg': 'ph1'}) and 930 | mh.store_eflags()): 931 | self.opsize = 1 932 | return True 933 | mh.reset() 934 | if (mh.load(None, {'reg': 'ph1', 'size': 'size1'}) and 935 | mh.load(0, {'reg': 'ph2', 'size': 'size1'}) and 936 | mh.batch([X86_INS_IMUL]) and 937 | mh.store(bridge.size, {'reg': 'ph2'}) and 938 | mh.store(bridge.size+mh.get_ph('size1'), {'reg': 'ph1'}) and 939 | mh.store_eflags()): 940 | self.opsize = mh.get_ph('size1') 941 | return True 942 | return False 943 | 944 | def get_instr(self, vmstate: VMState): 945 | i = VMIns() 946 | i.haddr = self.address 947 | i.id = VM_INS_IMUL 948 | i.address = vmstate.get_address() 949 | i.mne = 'imul{}'.format(self.opsize) 950 | i.data = 0 951 | i.opsize = self.opsize 952 | return i 953 | 954 | def generator(self, ins: VMIns, block: vtil.basic_block): 955 | a0, a1, d = block.tmp(ins.opsize*8, ins.opsize*8, ins.opsize*8) 956 | if ins.opsize == 1: 957 | a2, a3 = block.tmp(16, 16) 958 | block.pop(d) 959 | block.pop(a0) 960 | block.movsx(a2, a0) 961 | block.movsx(a3, d) 962 | 963 | block.imul(a2, a3) 964 | 965 | block.mov(FLAG_SF, vtil.UNDEFINED) 966 | block.mov(FLAG_ZF, vtil.UNDEFINED) 967 | block.mov(FLAG_OF, vtil.UNDEFINED) 968 | block.mov(FLAG_CF, vtil.UNDEFINED) 969 | 970 | block.push(a2) 971 | block.pushf() 972 | else: 973 | block.pop(d) 974 | block.pop(a0) 975 | block.mov(a1, a0) 976 | 977 | block.imul(a0, d) 978 | block.imulhi(a1, d) 979 | 980 | block.mov(FLAG_SF, vtil.UNDEFINED) 981 | block.mov(FLAG_ZF, vtil.UNDEFINED) 982 | block.mov(FLAG_OF, vtil.UNDEFINED) 983 | block.mov(FLAG_CF, vtil.UNDEFINED) 984 | 985 | block.push(a0) 986 | block.push(a1) 987 | block.pushf() 988 | 989 | 990 | class VMDiv(VMBase): 991 | def __init__(self, **kwargs): 992 | super().__init__(**kwargs) 993 | self.name = 'vmp_div' 994 | 995 | def match(self): 996 | if len(self.body) < 7: 997 | return False 998 | args = {} 999 | mh = MatchHelper(self.body, self.config) 1000 | mh.reset() 1001 | if (mh.load_byte(0, {'reg': 'ph1'}) and 1002 | mh.load_byte(2, {'reg': 'ph2'}) and 1003 | mh.batch([X86_INS_DIV]) and 1004 | mh.store_word(bridge.size, {'reg': 'ph1'}) and 1005 | mh.store_eflags()): 1006 | self.opsize = 1 1007 | return True 1008 | mh.reset() 1009 | if (mh.load(None, {'reg': 'ph1', 'size': 'size'}) and 1010 | mh.load(0, {'reg': 'ph2'}) and 1011 | mh.load(None, {'reg': 'ph3'}) and 1012 | mh.batch([X86_INS_DIV]) and 1013 | mh.store(bridge.size, {'reg': 'ph2'}) and 1014 | mh.store(bridge.size+mh.get_ph('size'), {'reg': 'ph1'}) and 1015 | mh.store_eflags()): 1016 | self.opsize = mh.get_ph('size') 1017 | return True 1018 | return False 1019 | 1020 | def get_instr(self, vmstate: VMState): 1021 | i = VMIns() 1022 | i.haddr = self.address 1023 | i.id = VM_INS_DIV 1024 | i.address = vmstate.get_address() 1025 | i.mne = 'div{}'.format(self.opsize) 1026 | i.data = 0 1027 | i.opsize = self.opsize 1028 | return i 1029 | 1030 | def generator(self, ins: VMIns, block: vtil.basic_block): 1031 | a0, a1, d, c = block.tmp( 1032 | ins.opsize*8, ins.opsize*8, ins.opsize*8, ins.opsize*8) 1033 | if ins.opsize == 1: 1034 | ax0 = block.tmp(16) 1035 | block.pop(a0) 1036 | block.pop(c) 1037 | block.mov(a1, a0) 1038 | 1039 | block.div(a0, 0, c) 1040 | block.rem(a1, 0, c) 1041 | 1042 | block.mov(ax0, a1) 1043 | block.bshl(ax0, 8) 1044 | block.bor(ax0, a0) 1045 | 1046 | block.push(ax0) 1047 | block.pushf() 1048 | else: 1049 | block.pop(d) 1050 | block.pop(a0) 1051 | block.pop(c) 1052 | block.mov(a1, a0) 1053 | 1054 | block.div(a0, d, c) 1055 | block.rem(a1, d, c) 1056 | 1057 | block.push(a0) 1058 | block.push(a1) 1059 | block.pushf() 1060 | 1061 | 1062 | class VMIdiv(VMBase): 1063 | def __init__(self, **kwargs): 1064 | super().__init__(**kwargs) 1065 | self.name = 'vmp_idiv' 1066 | 1067 | def match(self): 1068 | if len(self.body) < 7: 1069 | return False 1070 | args = {} 1071 | mh = MatchHelper(self.body, self.config) 1072 | mh.reset() 1073 | if (mh.load_byte(0, {'reg': 'ph1'}) and 1074 | mh.load_byte(2, {'reg': 'ph2'}) and 1075 | mh.batch([X86_INS_IDIV]) and 1076 | mh.store_word(bridge.size, {'reg': 'ph1'}) and 1077 | mh.store_eflags()): 1078 | self.opsize = 1 1079 | return True 1080 | mh.reset() 1081 | if (mh.load(None, {'reg': 'ph1', 'size': 'size'}) and 1082 | mh.load(0, {'reg': 'ph2'}) and 1083 | mh.load(None, {'reg': 'ph3'}) and 1084 | mh.batch([X86_INS_IDIV]) and 1085 | mh.store(bridge.size, {'reg': 'ph2'}) and 1086 | mh.store(bridge.size+mh.get_ph('size'), {'reg': 'ph1'}) and 1087 | mh.store_eflags()): 1088 | self.opsize = mh.get_ph('size') 1089 | return True 1090 | return False 1091 | 1092 | def get_instr(self, vmstate: VMState): 1093 | i = VMIns() 1094 | i.haddr = self.address 1095 | i.id = VM_INS_IDIV 1096 | i.address = vmstate.get_address() 1097 | i.mne = 'idiv{}'.format(self.opsize) 1098 | i.data = 0 1099 | i.opsize = self.opsize 1100 | return i 1101 | 1102 | def generator(self, ins: VMIns, block: vtil.basic_block): 1103 | a0, a1, d, c = block.tmp( 1104 | ins.opsize*8, ins.opsize*8, ins.opsize*8, ins.opsize*8) 1105 | if ins.opsize == 1: 1106 | ax0 = block.tmp(16) 1107 | block.pop(a0) 1108 | block.pop(c) 1109 | block.mov(a1, a0) 1110 | 1111 | block.idiv(a0, 0, c) 1112 | block.irem(a1, 0, c) 1113 | 1114 | block.mov(ax0, a1) 1115 | block.bshl(ax0, 8) 1116 | block.bor(ax0, a0) 1117 | 1118 | block.push(ax0) 1119 | block.pushf() 1120 | else: 1121 | block.pop(d) 1122 | block.pop(a0) 1123 | block.pop(c) 1124 | block.mov(a1, a0) 1125 | 1126 | block.idiv(a0, d, c) 1127 | block.irem(a1, d, c) 1128 | 1129 | block.push(a0) 1130 | block.push(a1) 1131 | block.pushf() 1132 | 1133 | 1134 | class VMRdtsc(VMBase): 1135 | def __init__(self, **kwargs): 1136 | super().__init__(**kwargs) 1137 | self.name = 'vmp_rdtsc' 1138 | 1139 | def match(self): 1140 | if len(self.body) < 3: 1141 | return False 1142 | args = {} 1143 | mh = MatchHelper(self.body, self.config) 1144 | mh.reset() 1145 | if (mh.batch([X86_INS_RDTSC]) and 1146 | mh.store_dword(0, {'reg': X86_REG_EDX}) and 1147 | mh.store_dword(4, {'reg': X86_REG_EAX})): 1148 | return True 1149 | return False 1150 | 1151 | def get_instr(self, vmstate: VMState): 1152 | i = VMIns() 1153 | i.haddr = self.address 1154 | i.id = VM_INS_RDTSC 1155 | i.address = vmstate.get_address() 1156 | i.mne = 'rdtsc' 1157 | i.data = 0 1158 | i.opsize = self.opsize 1159 | return i 1160 | 1161 | def generator(self, ins: VMIns, block: vtil.basic_block): 1162 | block.vemits('rdtsc') 1163 | block.vpinw(ZDX) 1164 | block.vpinw(ZAX) 1165 | 1166 | block.push(vtil.x86_reg.EAX) 1167 | block.push(vtil.x86_reg.EDX) 1168 | 1169 | 1170 | class VMCpuid(VMBase): 1171 | def __init__(self, **kwargs): 1172 | super().__init__(**kwargs) 1173 | self.name = 'vmp_cpuid' 1174 | 1175 | def match(self): 1176 | if len(self.body) < 6: 1177 | return False 1178 | mh = MatchHelper(self.body, self.config) 1179 | if (mh.load_dword(0, {'reg': 'ph1'}) and 1180 | mh.batch([X86_INS_CPUID]) and 1181 | mh.store_dword(12) and 1182 | mh.store_dword(8) and 1183 | mh.store_dword(4) and 1184 | mh.store_dword(0)): 1185 | return True 1186 | """ 1187 | mov eax, dword ptr [rbx] 1188 | mov r11, rbx 1189 | cpuid 1190 | sub r11, 0xc 1191 | mov dword ptr [r11 + 0xc], eax 1192 | mov dword ptr [r11 + 8], ebx 1193 | mov dword ptr [r11 + 4], ecx 1194 | mov dword ptr [r11], edx 1195 | mov rbx, r11 1196 | """ 1197 | if self.config.reg_sp in [X86_REG_RAX, X86_REG_RBX, X86_REG_RCX, X86_REG_RDX, 1198 | X86_REG_EAX, X86_REG_EBX, X86_REG_ECX, X86_REG_EDX]: 1199 | mh = MatchHelper(self.body, self.config) 1200 | if (mh.load_dword(0, {'reg': 'ph1'}) and 1201 | mh.batch([X86_INS_CPUID])): 1202 | return True 1203 | return False 1204 | 1205 | def get_instr(self, vmstate: VMState): 1206 | i = VMIns() 1207 | i.haddr = self.address 1208 | i.id = VM_INS_CPUID 1209 | i.address = vmstate.get_address() 1210 | i.mne = 'cpuid' 1211 | i.data = 0 1212 | i.opsize = self.opsize 1213 | return i 1214 | 1215 | def generator(self, ins: VMIns, block: vtil.basic_block): 1216 | block.pop(vtil.x86_reg.EAX) 1217 | 1218 | block.vpinr(ZCX) 1219 | block.vpinr(ZAX) 1220 | block.vemits('cpuid') 1221 | block.vpinw(ZDX) 1222 | block.vpinw(ZCX) 1223 | block.vpinw(ZBX) 1224 | block.vpinw(ZAX) 1225 | 1226 | block.push(vtil.x86_reg.EAX) 1227 | block.push(vtil.x86_reg.EBX) 1228 | block.push(vtil.x86_reg.ECX) 1229 | block.push(vtil.x86_reg.EDX) 1230 | 1231 | 1232 | class VMLockExchange(VMBase): 1233 | def __init__(self, **kwargs): 1234 | super().__init__(**kwargs) 1235 | self.name = 'vmp_lock_xchg' 1236 | 1237 | def match(self): 1238 | if len(self.body) > 5: 1239 | return False 1240 | 1241 | def is_lock_xchg(insn: CsInsn): 1242 | if insn.id != X86_INS_XCHG: 1243 | return False 1244 | if X86_PREFIX_LOCK not in insn.prefix: 1245 | return False 1246 | op1, op2 = insn.operands 1247 | if op1.type == X86_OP_MEM and op2.type == X86_OP_REG: 1248 | self.opsize = op1.size 1249 | return True 1250 | return False 1251 | args = {} 1252 | mh = MatchHelper(self.body, self.config) 1253 | if (mh.load(0, {'reg': 'ph1'}) and 1254 | mh.load(bridge.size, {'reg': 'ph2'}) and 1255 | mh.match_for(is_lock_xchg) and 1256 | mh.store(0)): 1257 | return True 1258 | return False 1259 | 1260 | def get_instr(self, vmstate: VMState): 1261 | i = VMIns() 1262 | i.haddr = self.address 1263 | i.id = VM_INS_LOCK_XCHG 1264 | i.address = vmstate.get_address() 1265 | i.mne = 'lock_xchg' 1266 | i.data = 0 1267 | i.opsize = self.opsize 1268 | return i 1269 | 1270 | def generator(self, ins: VMIns, block: vtil.basic_block): 1271 | table = {1: (X86_REG_AL, 'byte'), 1272 | 2: (X86_REG_AX, 'word'), 1273 | 4: (X86_REG_EAX, 'dword'), 1274 | 8: (X86_REG_RAX, 'qword')} 1275 | reg, str_type = table[self.opsize] 1276 | # vr = remap(ZAX, opsize) 1277 | vr = vtil.x86_reg(reg) 1278 | block.pop(ZDX) 1279 | block.pop(vr) 1280 | block.vpinr(ZDX) 1281 | block.vpinr(ZAX) 1282 | block.vemits( 1283 | f'lock xchg {str_type} ptr [{bridge.reg_name(ZDX)}], {bridge.reg_name(reg)}') 1284 | block.vpinw(ZAX) 1285 | 1286 | block.push(vr) 1287 | 1288 | 1289 | class VMPushCRX(VMBase): 1290 | def __init__(self, **kwargs): 1291 | super().__init__(**kwargs) 1292 | self.name = 'vmp_push_crx' 1293 | self.cr = X86_REG_INVALID 1294 | 1295 | def match(self): 1296 | if len(self.body) > 4: 1297 | return False 1298 | mh = MatchHelper(self.body, self.config) 1299 | for cr in [X86_REG_CR0, X86_REG_CR2, X86_REG_CR3, X86_REG_CR4, X86_REG_CR8]: 1300 | mh.reset() 1301 | if mh.match(X86_INS_MOV, [X86_OP_REG, X86_OP_REG], [None, cr]) and mh.store(0): 1302 | self.cr = cr 1303 | self.name = f'vmp_push_{bridge.reg_name(self.cr)}' 1304 | return True 1305 | return False 1306 | 1307 | def get_instr(self, vmstate: VMState): 1308 | i = VMIns() 1309 | i.haddr = self.address 1310 | i.id = VM_INS_PUSH_CRX 1311 | i.address = vmstate.get_address() 1312 | i.mne = f'push_{bridge.reg_name(self.cr)}' 1313 | i.opstr = '' 1314 | i.data = 0 1315 | i.opsize = self.opsize 1316 | return i 1317 | 1318 | def generator(self, ins: VMIns, block: vtil.basic_block): 1319 | block.vemits(f'mov {bridge.reg_name(ZAX)}, {bridge.reg_name(self.cr)}') 1320 | block.vpinw(ZAX) 1321 | block.push(ZAX) 1322 | 1323 | 1324 | class VMPopCRX(VMBase): 1325 | def __init__(self, **kwargs): 1326 | super().__init__(**kwargs) 1327 | self.name = 'vmp_pop_crx' 1328 | self.cr = X86_REG_INVALID 1329 | 1330 | def match(self): 1331 | if len(self.body) > 4: 1332 | return False 1333 | mh = MatchHelper(self.body, self.config) 1334 | for cr in [X86_REG_CR0, X86_REG_CR2, X86_REG_CR3, X86_REG_CR4, X86_REG_CR8]: 1335 | mh.reset() 1336 | if mh.load(0) and mh.match(X86_INS_MOV, [X86_OP_REG, X86_OP_REG], [cr, None]): 1337 | self.cr = cr 1338 | self.name = f'vmp_pop_{bridge.reg_name(self.cr)}' 1339 | return True 1340 | return False 1341 | 1342 | def get_instr(self, vmstate: VMState): 1343 | i = VMIns() 1344 | i.haddr = self.address 1345 | i.id = VM_INS_POP_CRX 1346 | i.address = vmstate.get_address() 1347 | i.mne = f'pop_{bridge.reg_name(self.cr)}' 1348 | i.opstr = '' 1349 | i.data = 0 1350 | i.opsize = self.opsize 1351 | return i 1352 | 1353 | def generator(self, ins: VMIns, block: vtil.basic_block): 1354 | block.pop(ZAX) 1355 | block.vpinr(ZAX) 1356 | block.vemits(f'mov {bridge.reg_name(self.cr)}, {bridge.reg_name(ZAX)}') 1357 | 1358 | 1359 | class VMPushSP(VMBase): 1360 | def __init__(self, **kwargs): 1361 | super().__init__(**kwargs) 1362 | self.name = 'vmp_push_sp' 1363 | 1364 | def match(self): 1365 | if len(self.body) > 4: 1366 | return False 1367 | mh = MatchHelper(self.body, self.config) 1368 | a = {} 1369 | if mh.get_sp({'reg': 'ph1'}) and mh.store(0, a): 1370 | self.opsize = a['size'] 1371 | return True 1372 | return False 1373 | 1374 | def get_instr(self, vmstate: VMState): 1375 | i = VMIns() 1376 | i.haddr = self.address 1377 | i.id = VM_INS_PUSH_SP 1378 | i.address = vmstate.get_address() 1379 | i.mne = 'push_sp{}'.format(self.opsize) 1380 | i.opstr = '' 1381 | i.data = 0 1382 | i.opsize = self.opsize 1383 | return i 1384 | 1385 | def generator(self, ins: VMIns, block: vtil.basic_block): 1386 | if ins.opsize == vtil.arch.size: 1387 | block.push(vtil.REG_SP) 1388 | else: 1389 | t0 = block.tmp(ins.opsize*8) 1390 | block.mov(t0, vtil.REG_SP) 1391 | block.push(t0) 1392 | 1393 | 1394 | class VMPopSP(VMBase): 1395 | def __init__(self, **kwargs): 1396 | super().__init__(**kwargs) 1397 | self.name = 'vmp_pop_sp' 1398 | 1399 | def match(self): 1400 | if len(self.body) != 1: 1401 | return False 1402 | mh = MatchHelper(self.body, self.config) 1403 | if mh.load(0, {'reg': 'ph1', 'size': 'size'}) and mh.get_ph('ph1') == self.config.reg_sp: 1404 | self.opsize = mh.get_ph('size') 1405 | return True 1406 | return False 1407 | 1408 | def get_instr(self, vmstate: VMState): 1409 | i = VMIns() 1410 | i.haddr = self.address 1411 | i.id = VM_INS_POP_SP 1412 | i.address = vmstate.get_address() 1413 | i.mne = 'pop_sp{}'.format(self.opsize) 1414 | i.opstr = '' 1415 | i.data = 0 1416 | i.opsize = self.opsize 1417 | return i 1418 | 1419 | def generator(self, ins: VMIns, block: vtil.basic_block): 1420 | block.pop(vtil.REG_SP) 1421 | 1422 | 1423 | class VMPopFlag(VMBase): 1424 | def __init__(self, **kwargs): 1425 | super().__init__(**kwargs) 1426 | self.name = 'vmp_pop_flag' 1427 | 1428 | def match(self): 1429 | if len(self.body) != 3: 1430 | return False 1431 | mh = MatchHelper(self.body, self.config) 1432 | if (mh.match(X86_INS_PUSH, [X86_OP_MEM], [{'base': self.config.reg_sp}]) and 1433 | mh.among([X86_INS_POPFD, X86_INS_POPFQ])): 1434 | self.opsize = bridge.size 1435 | return True 1436 | return False 1437 | 1438 | def get_instr(self, vmstate: VMState): 1439 | i = VMIns() 1440 | i.haddr = self.address 1441 | i.id = VM_INS_POP_EFLAGS 1442 | i.address = vmstate.get_address() 1443 | i.mne = 'pop_flag{}'.format(self.opsize) 1444 | i.opstr = '' 1445 | i.data = 0 1446 | i.opsize = self.opsize 1447 | return i 1448 | 1449 | def generator(self, ins: VMIns, block: vtil.basic_block): 1450 | assert ins.opsize == vtil.arch.size 1451 | block.popf() 1452 | 1453 | 1454 | def feeling_good(insns_connect: List[CsInsn]): 1455 | """ 1456 | dir 1457 | reg_base 1458 | reg_key 1459 | reg_ip 1460 | """ 1461 | config = VMConfig() 1462 | config.dir = 1 1463 | config.reg_base, reg_off = get_regbase(insns_connect), X86_REG_INVALID 1464 | for insn in reversed(insns_connect): 1465 | # find add edi(base), edx(off) 1466 | if (reg_off == X86_REG_INVALID and instr_match(insn, X86_INS_ADD, [X86_OP_REG, X86_OP_REG], [config.reg_base])): 1467 | op1, op2 = insn.operands 1468 | reg_off = op2.reg 1469 | if reg_off == X86_REG_INVALID: 1470 | continue 1471 | # xor r(off), r(key) 1472 | if instr_match(insn, X86_INS_XOR, [X86_OP_REG, X86_OP_REG], [get_reg32(reg_off), None]): 1473 | op1, op2 = insn.operands 1474 | config.reg_key = op2.reg 1475 | if bridge.is64bit(): 1476 | config.reg_key = get_reg64(config.reg_key) 1477 | break 1478 | 1479 | config.dir = 1 1480 | # get reg_ip 1481 | for insn in insns_connect[:3]: 1482 | r, imm = crease_match(insn) 1483 | if r != X86_REG_INVALID and imm == -4: 1484 | config.dir = -1 1485 | # mov edx(off), dword ptr [ip] ; 1486 | if instr_match(insn, X86_INS_MOV, [X86_OP_REG, X86_OP_MEM], [get_reg32(reg_off), {'disp': 0, 'index': X86_REG_INVALID, 'scale': 1}]): 1487 | config.reg_ip = insn.operands[1].mem.base 1488 | return config 1489 | 1490 | return None 1491 | 1492 | 1493 | class VMInit(VMBase): 1494 | def __init__(self, **kwargs): 1495 | super().__init__(**kwargs) 1496 | self.name = "vmp_init" 1497 | 1498 | def parse_vminit(self): 1499 | self.config = feeling_good(self.connect) 1500 | if not self.config: 1501 | return False 1502 | i_save_regs = 0 1503 | i_decode_ip = -1 1504 | i_set_sp = -1 1505 | i_set_vregs = -1 1506 | i_set_hbase = -1 1507 | self.config.rebase = -1 1508 | for i, v in enumerate(self.body): 1509 | if self.config.rebase == -1 and i_decode_ip == -1: 1510 | if instr_match(v, [X86_INS_MOV, X86_INS_MOVABS], [X86_OP_REG, X86_OP_IMM]): 1511 | op1, op2 = v.operands 1512 | self.config.rebase = op2.imm 1513 | # mov {vIP}, dword ptr [esp + 0x28] ; decode vIP 1514 | if instr_match(v, X86_INS_MOV, [X86_OP_REG, X86_OP_MEM], [self.config.reg_ip]): 1515 | i_decode_ip = i 1516 | if instr_match(v, X86_INS_MOV, [X86_OP_REG, X86_OP_REG]): 1517 | op1, op2 = v.operands 1518 | # mov {vESP}, esp ; 1519 | if op2.reg in [X86_REG_ESP, X86_REG_RSP]: 1520 | self.config.reg_sp = op1.reg 1521 | i_set_sp = i 1522 | # mov ebx, {vIP}; set key 1523 | if instr_match(v, X86_INS_MOV, [X86_OP_REG, X86_OP_REG], [self.config.reg_key, self.config.reg_ip]): 1524 | i_set_key = i 1525 | # sub esp({vRegs}), 0xc0 1526 | if instr_match(v, X86_INS_LEA, [X86_OP_REG, X86_OP_MEM]): 1527 | # lea esp({vRegs}), [esp - 0xc0] 1528 | op1, op2 = v.operands 1529 | # if op1.reg == self.config.reg_regs: 1530 | # i_set_vregs = i 1531 | # lea ebp, [L_SetHBase] 1532 | if op1.reg == self.config.reg_base: 1533 | i_set_hbase = i 1534 | i_connect = i+1 1535 | if bridge.is64bit() and op2.mem.base == X86_REG_RIP: 1536 | # x64 1537 | self.hbase = v.address + v.size + op2.mem.disp 1538 | else: 1539 | self.hbase = op2.mem.disp 1540 | assert i_decode_ip != -1 and i_set_sp != -1 1541 | # parse push list 1542 | m = {} 1543 | self.save_regs = self.body[i_save_regs:i_decode_ip] 1544 | self.pushs = [] 1545 | for insn in self.save_regs: 1546 | if instr_match(insn, [X86_INS_MOV, X86_INS_MOVABS], [X86_OP_REG, X86_OP_IMM]): 1547 | m[insn.operands[0].reg] = (X86_OP_IMM, insn.operands[1].imm) 1548 | if instr_match(insn, X86_INS_PUSH, [X86_OP_REG]): 1549 | if insn.operands[0].reg in m: 1550 | self.pushs.append(m[insn.operands[0].reg]) 1551 | else: 1552 | self.pushs.append((X86_OP_REG, insn.operands[0].reg)) 1553 | if insn.id in [X86_INS_PUSHFD, X86_INS_PUSHFQ]: 1554 | self.pushs.append((X86_OP_REG, X86_REG_EFLAGS)) 1555 | self.conn_config = self.config 1556 | self.ip_decoder = self.body[i_decode_ip+1:i_set_sp] 1557 | return True 1558 | 1559 | def decode_ip(self, ct, vmstate: VMState): 1560 | v = vmstate.decode_emu(self.ip_decoder, ct, 1561 | get_reg32(self.config.reg_ip), 4) 1562 | v &= get_mask(bridge.size*8) 1563 | return v 1564 | 1565 | def match(self): 1566 | return True 1567 | 1568 | 1569 | class VMExit(VMBase): 1570 | def __init__(self, **kwargs): 1571 | super().__init__(**kwargs) 1572 | self.name = "vmp_exit" 1573 | self.pops = [] 1574 | return 1575 | 1576 | def match(self): 1577 | if not instr_match(self.body[-1], X86_INS_RET): 1578 | return False 1579 | 1580 | for insn in self.body: 1581 | if instr_match(insn, X86_INS_POP, [X86_OP_REG]): 1582 | self.pops.append(insn.operands[0].reg) 1583 | elif insn.id in [X86_INS_POPFD, X86_INS_POPFQ]: 1584 | self.pops.append(X86_REG_EFLAGS) 1585 | 1586 | if len(self.pops) >= 7: 1587 | return True 1588 | 1589 | # x64 1590 | # pop 15 times + popfq 1591 | return False 1592 | 1593 | def get_instr(self, vmstate): 1594 | i = VMIns() 1595 | i.haddr = self.address 1596 | i.id = VM_INS_EXIT 1597 | i.address = vmstate.get_address() 1598 | i.mne = 'exit' 1599 | i.opstr = '' 1600 | i.data = 0 1601 | i.opsize = self.opsize 1602 | return i 1603 | 1604 | def generator(self, ins: VMIns, block: vtil.basic_block): 1605 | pass 1606 | 1607 | 1608 | class VMUnknown(VMBase): 1609 | def __init__(self, **kwargs): 1610 | super().__init__(**kwargs) 1611 | self.name = "vmp_unknown" 1612 | 1613 | def calc_bytecode_size(self): 1614 | n = 0 1615 | for insn in self.body: 1616 | insn: CsInsn 1617 | regs_read, regs_write = insn.regs_access() 1618 | if self.config.reg_ip in regs_write: 1619 | # lea vm_ip, [vm_ip+disp] 1620 | if instr_match(insn, X86_INS_LEA, [X86_OP_REG, X86_OP_MEM], [self.config.reg_ip, {'base': self.config.reg_ip, 'index': X86_REG_INVALID, 'scale': 1}]): 1621 | n += insn.operands[1].mem.disp 1622 | # add vm_ip, imm 1623 | elif instr_match(insn, X86_INS_ADD, [X86_OP_REG, X86_OP_IMM], [self.config.reg_ip]): 1624 | n += insn.operands[1].imm 1625 | # sub vm_ip, imm 1626 | elif instr_match(insn, X86_INS_SUB, [X86_OP_REG, X86_OP_IMM], [self.config.reg_ip]): 1627 | n -= insn.operands[1].imm 1628 | elif instr_match(insn, X86_INS_LODSD): 1629 | n += 4 1630 | elif instr_match(insn, X86_INS_LODSQ): 1631 | n += 8 1632 | # mov vm_ip, vm_ip 1633 | elif instr_match(insn, X86_INS_MOV, [X86_OP_REG, X86_OP_REG], [self.config.reg_ip, self.config.reg_ip]): 1634 | n += 0 1635 | else: 1636 | print(self.config) 1637 | dump_insns(self.body) 1638 | raise NotImplementedError('calc_bytecode_size'+str(insn)) 1639 | return abs(n) 1640 | 1641 | def match(self): 1642 | self.bytecode_size = self.calc_bytecode_size() 1643 | if self.bytecode_size != 0: 1644 | mh = MatchHelper(self.body, self.config) 1645 | args = {} 1646 | if not mh.decode(args): 1647 | return False 1648 | self.decoder = args['decoder'] 1649 | self.reg = args['reg'] 1650 | return True 1651 | 1652 | def get_instr(self, vmstate): 1653 | i = VMIns() 1654 | i.haddr = self.address 1655 | i.id = VM_INS_UNKNOWN 1656 | i.address = vmstate.get_address() 1657 | bytecode = 0 1658 | if self.bytecode_size: 1659 | bytecode = vmstate.decode_emu(self.decoder, vmstate.fetch( 1660 | self.bytecode_size), self.reg, self.bytecode_size) 1661 | 1662 | i.mne = 'unknown' 1663 | i.opstr = '{}'.format(bytecode) 1664 | i.data = bytecode 1665 | i.opsize = self.bytecode_size 1666 | return i 1667 | 1668 | 1669 | class VMInvalid(VMBase): 1670 | def __init__(self, **kwargs): 1671 | super().__init__(**kwargs) 1672 | self.name = 'vmp_invalid' 1673 | 1674 | def match(self): 1675 | return True 1676 | 1677 | def get_instr(self, vmstate): 1678 | i = VMIns() 1679 | i.haddr = self.address 1680 | i.id = VM_INS_INVALID 1681 | i.address = vmstate.get_address() 1682 | i.mne = 'invalid' 1683 | i.opstr = '' 1684 | i.data = 0 1685 | i.opsize = self.opsize 1686 | return i 1687 | 1688 | 1689 | def hook_code(uc: Uc, address, size, user_data): 1690 | emu = user_data 1691 | if size > 15: # unicorn sometimes passes in a huge size. 1692 | print(f'[hook_code] address: 0x{address:08X} size: 0x{size:08X} what?') 1693 | uc.emu_stop() 1694 | return 1695 | ins = bridge.disasm_one(address, size) 1696 | sp = uc.reg_read(UC_X86_REG_RSP if bridge.is64bit() else UC_X86_REG_ESP) 1697 | diff_sp = sp-emu.entry_sp 1698 | print(f'{address:08X} {diff_sp} {ins.mnemonic} {ins.op_str}') 1699 | if ins.group(CS_GRP_JUMP): 1700 | return 1701 | emu.trace.append((ins, diff_sp)) 1702 | if set(ins.groups) - {X86_GRP_JUMP, X86_GRP_CALL, X86_GRP_RET, X86_GRP_BRANCH_RELATIVE, X86_GRP_MODE32, X86_GRP_MODE64}: 1703 | _ip = UC_X86_REG_RIP if bridge.is64bit() else UC_X86_REG_EIP 1704 | uc.reg_write(_ip, address+size) 1705 | return 1706 | if emu.has_lea_stack and ins.id == X86_INS_CALL and ins.operands[0].type == CS_OP_IMM: 1707 | emu.last_callee = ins 1708 | emu.last_diff_sp = diff_sp 1709 | fmt = ' None: 1787 | self.vmstate = vmstate 1788 | self.vminit = vminit 1789 | self.stub_addr = stub_addr 1790 | self.vm_imm = vm_imm 1791 | self.vm_imm2 = vm_imm2 1792 | self.vm_unimpl_insn = vm_unimpl_insn 1793 | 1794 | 1795 | def vmentry_parse(addr): 1796 | # [unimpl insn] 1797 | # push 1798 | # call 1799 | insn_x = x86_simple_decode(addr, 5, True) 1800 | if len(insn_x) >= 2: 1801 | if (instr_match(insn_x[-2], X86_INS_PUSH, [X86_OP_IMM]) and 1802 | instr_match(insn_x[-1], X86_INS_CALL, [X86_OP_IMM])): 1803 | vm_imm = insn_x[-2].operands[0].imm 1804 | vm_init = insn_x[-1].operands[0].imm & get_mask(bridge.size*8) 1805 | vm_unimpl_insn = insn_x[:-2] 1806 | h = factory(vm_init, None) 1807 | if h and isinstance(h, VMInit): 1808 | mask = get_mask(bridge.size*8) 1809 | vmstate = VMState() 1810 | vmstate.config = h.config 1811 | vmstate.ip = h.decode_ip(vm_imm, vmstate) 1812 | vmstate.key = (vmstate.ip-vmstate.config.rebase) & mask 1813 | return VMEntryParseResult(vmstate, h, insn_x[-2].address, vm_imm, insn_x[-1].address+insn_x[-2].size, vm_unimpl_insn) 1814 | # vmp version >= 3.6 1815 | emu = Emu() 1816 | res = emu.run(addr) 1817 | if not res: 1818 | return None 1819 | 1820 | h = factory(emu.vm_init_addr, None) 1821 | if h and isinstance(h, VMInit): 1822 | mask = get_mask(bridge.size*8) 1823 | vmstate = VMState() 1824 | vmstate.config = h.config 1825 | vmstate.ip = h.decode_ip(emu.vm_imm, vmstate) 1826 | vmstate.key = (vmstate.ip-vmstate.config.rebase) & mask 1827 | return VMEntryParseResult(vmstate, h, emu.stub_addr, emu.vm_imm, emu.vm_imm2, emu.vm_unimpl_insn) 1828 | return None 1829 | 1830 | 1831 | class VMJmp(VMBase): 1832 | def __init__(self, **kwargs): 1833 | super().__init__(**kwargs) 1834 | self.name = 'vmp_jmp' 1835 | 1836 | def simple_parse(self): 1837 | # breakpoint() 1838 | self.conn_config = feeling_good(self.connect) 1839 | if not self.conn_config: 1840 | return False 1841 | self.conn_config.rebase = self.config.rebase 1842 | if bridge.is64bit(): 1843 | new_sp = [self.config.reg_sp] 1844 | for insn in self.body: 1845 | if instr_match(insn, X86_INS_XCHG, [X86_OP_REG, X86_OP_REG]): 1846 | op1, op2 = insn.operands 1847 | if op1.reg in new_sp: 1848 | new_sp.remove(op1.reg) 1849 | new_sp.append(op2.reg) 1850 | elif op2.reg in new_sp: 1851 | new_sp.remove(op2.reg) 1852 | new_sp.append(op1.reg) 1853 | elif instr_match(insn, X86_INS_MOV, [X86_OP_REG, X86_OP_REG]): 1854 | op1, op2 = insn.operands 1855 | if op1.reg != op2.reg and op2.reg in new_sp: 1856 | new_sp.append(op1.reg) 1857 | assert new_sp 1858 | if len(new_sp) > 1: 1859 | print(f'warning! new_sp -> {new_sp} too much') 1860 | self.conn_config.reg_sp = new_sp[-1] 1861 | else: 1862 | # 32 only 1863 | self.conn_config.reg_sp = X86_REG_EBP ^ X86_REG_EDI ^ X86_REG_ESI ^ self.conn_config.reg_ip ^ self.conn_config.reg_base 1864 | return True 1865 | 1866 | def match(self): 1867 | matched = False 1868 | reg_newip = X86_REG_INVALID 1869 | if not self.simple_parse(): 1870 | return False 1871 | for insn in self.body: 1872 | if reg_newip == X86_REG_INVALID: 1873 | # mov conn_config.reg_ip, reg 1874 | if instr_match(insn, X86_INS_MOV, [X86_OP_REG, X86_OP_MEM], [None, {'base': self.config.reg_sp, 'disp': 0, 'index': X86_REG_INVALID, 'scale': 1}]): 1875 | reg_newip = insn.operands[0].reg 1876 | if reg_newip == self.conn_config.reg_ip: 1877 | matched = True 1878 | elif instr_match(insn, X86_INS_MOV, [X86_OP_REG, X86_OP_REG], [self.conn_config.reg_ip, reg_newip]): 1879 | matched = True 1880 | elif instr_match(insn, X86_INS_XCHG, [X86_OP_REG, X86_OP_REG], [reg_newip, self.conn_config.reg_ip]): 1881 | matched = True 1882 | if self.conn_config.reg_sp: 1883 | if instr_match(insn, X86_INS_XCHG, [X86_OP_REG, X86_OP_REG], [reg_newip, self.conn_config.reg_ip]): 1884 | pass 1885 | if matched: 1886 | new_base = self.parse_hbase(insn, self.conn_config.reg_base) 1887 | if new_base != None: 1888 | self.hbase = new_base 1889 | return True 1890 | return False 1891 | 1892 | def get_instr(self, vmstate): 1893 | i = VMIns() 1894 | i.haddr = self.address 1895 | i.id = VM_INS_JMP 1896 | i.address = vmstate.get_address() 1897 | i.mne = 'jmp' 1898 | i.opstr = '' 1899 | i.data = 0 1900 | i.opsize = 0 1901 | return i 1902 | 1903 | def generator(self, ins: VMIns, block: vtil.basic_block): 1904 | pass 1905 | # t0 = block.tmp(vtil.arch.bit_count) 1906 | # block.pop(t0) 1907 | # block.jmp(t0) 1908 | 1909 | 1910 | def crease_match(insn, reg=None): 1911 | # add r, i 1912 | if instr_match(insn, X86_INS_ADD, [X86_OP_REG, X86_OP_IMM], [reg]): 1913 | return (insn.operands[0].reg, insn.operands[1].imm) 1914 | # lea r, [r + i] 1915 | if instr_match(insn, X86_INS_LEA, [X86_OP_REG, X86_OP_MEM], [reg, {'base': reg, 'index': X86_REG_INVALID, 'scale': 1}]): 1916 | if insn.operands[0].reg == insn.operands[1].mem.base: 1917 | return (insn.operands[0].reg, insn.operands[1].mem.disp) 1918 | # sub r, i 1919 | if instr_match(insn, X86_INS_SUB, [X86_OP_REG, X86_OP_IMM], [reg]): 1920 | return (insn.operands[0].reg, -insn.operands[1].imm) 1921 | return (X86_REG_INVALID, 0) 1922 | 1923 | 1924 | def get_regbase(insns): 1925 | if instr_match(insns[-1], X86_INS_JMP, [X86_OP_REG]): 1926 | return insns[-1].operands[0].reg 1927 | elif (instr_match(insns[-1], X86_INS_RET) and 1928 | instr_match(insns[-2], X86_INS_PUSH, [X86_OP_REG])): 1929 | return insns[-2].operands[0].reg 1930 | return X86_REG_INVALID 1931 | 1932 | 1933 | def get_splitline(insns: CsInsn): 1934 | reg_off = X86_REG_INVALID 1935 | reg_base = X86_REG_INVALID 1936 | splitline = -1 1937 | reg_ip = X86_REG_INVALID 1938 | ph = X86_REG_INVALID 1939 | # parse connect 1940 | reg_base = get_regbase(insns) 1941 | if reg_base == X86_REG_INVALID: 1942 | return splitline 1943 | for insn in reversed(insns): 1944 | # find add edi(base), edx(off) 1945 | if (reg_off == X86_REG_INVALID and 1946 | instr_match(insn, X86_INS_ADD, [X86_OP_REG, X86_OP_REG])): 1947 | op1, op2 = insn.operands 1948 | if op1.reg == reg_base: 1949 | reg_off = op2.reg 1950 | if reg_off == X86_REG_INVALID: 1951 | continue 1952 | # mov edx(off), dword ptr [ip] ; 1953 | if instr_match(insn, X86_INS_MOV, [X86_OP_REG, X86_OP_MEM], [get_reg32(reg_off), {'disp': 0, 'index': X86_REG_INVALID, 'scale': 1}]): 1954 | reg_ip = insn.operands[1].mem.base 1955 | if ph == reg_ip: 1956 | splitline = insns.index(insn) 1957 | break 1958 | r, imm = crease_match(insn) 1959 | if r != X86_REG_INVALID: 1960 | if imm == -4 and r == reg_ip: 1961 | splitline = insns.index(insn) 1962 | break 1963 | elif imm == 4: 1964 | ph = r 1965 | return splitline 1966 | 1967 | 1968 | h_seq = [VMPushReg, VMPopReg, 1969 | VMPushImm, 1970 | VMNor, VMNand, VMShift, 1971 | VMStr, VMLdr, 1972 | VMPushCRX, VMPopCRX, 1973 | VMPushSP, VMPopSP, 1974 | VMShld, VMShrd, 1975 | VMAdd, 1976 | VMMul, VMImul, VMDiv, VMIdiv, 1977 | VMRdtsc, VMCpuid, 1978 | VMLockExchange, 1979 | VMCrc, 1980 | VMPopFlag, 1981 | VMNop, VMJmp, VMCall, 1982 | VMUnknown] 1983 | 1984 | 1985 | def factory(address, config: VMConfig): 1986 | if address in vm_handlers: 1987 | return vm_handlers[address] 1988 | insns = x86_simple_decode(address, 150, True) 1989 | if not insns: 1990 | print(f'shit {address:X}') 1991 | breakpoint() 1992 | if (splitline := get_splitline(insns)) > 0: 1993 | body = insns[:splitline] 1994 | connect = insns[splitline:] 1995 | if config is None: # test vminit 1996 | h = VMInit(insns=insns, 1997 | body=body, 1998 | connect=connect, 1999 | address=address) 2000 | if h.parse_vminit() and h.match() and h.parse_connect(): 2001 | vm_handlers[address] = h 2002 | return h 2003 | return None 2004 | for g in h_seq: 2005 | h = g(config=config, insns=insns, body=body, connect=connect, 2006 | conn_config=config, hbase=address, address=address) 2007 | if h.match() and h.parse_connect(): 2008 | vm_handlers[address] = h 2009 | return h 2010 | else: 2011 | h = VMExit(config=config, 2012 | insns=insns, 2013 | body=insns, 2014 | hbase=address, 2015 | address=address) 2016 | if h.match(): 2017 | vm_handlers[address] = h 2018 | return h 2019 | # vm invalid 2020 | return VMInvalid(config=config, address=address, insns=insns) 2021 | -------------------------------------------------------------------------------- /novmpy/match_helper.py: -------------------------------------------------------------------------------- 1 | from capstone import * 2 | from capstone.x86 import * 3 | from novmpy.x86_deobf import * 4 | from novmpy.bridge import * 5 | 6 | 7 | class MatchHelper: 8 | """ 9 | fetch: vm_bytecode 10 | load, store: stack 11 | mem: memory pointer 12 | read, write: vm_regs 13 | """ 14 | 15 | # TODO: zword -> arch size 4 or 8 16 | 17 | def __init__(self, body, config): 18 | self.body = body 19 | self.index = 0 20 | self.config = config 21 | self.placeholder = {} 22 | 23 | def reset(self): 24 | self.index = 0 25 | self.placeholder = {} 26 | return True 27 | 28 | def update_index(self, new_index): 29 | self.index = new_index 30 | 31 | # maybe we can use __getitem__ 32 | def get_ph(self, key): 33 | if key in self.placeholder: 34 | return self.placeholder[key] 35 | raise KeyError() 36 | 37 | def check_placeholder(self, args, k, v): 38 | # placeholder mode when args[k] is a str 39 | if k in args: 40 | if isinstance(args[k], str): 41 | ph_name = args[k] 42 | # do match when ph in dict 43 | if ph_name in self.placeholder: 44 | if self.placeholder[ph_name] != v: 45 | return False 46 | else: 47 | self.placeholder[ph_name] = v 48 | else: 49 | return args[k] == v 50 | else: 51 | # not a placeholder, so just put in dict 52 | args[k] = v 53 | return True 54 | # match functions 55 | 56 | def get_sp(self, args): 57 | for i in range(self.index, len(self.body)): 58 | insn: CsInsn = self.body[i] 59 | if instr_match(insn, X86_INS_MOV, [X86_OP_REG, X86_OP_REG], [None, self.config.reg_sp]): 60 | op1, op2 = insn.operands 61 | if not self.check_placeholder(args, 'reg', op1.reg): 62 | continue 63 | self.update_index(i+1) 64 | return True 65 | return False 66 | 67 | # stack 68 | def _load(self, off, args): 69 | for i in range(self.index, len(self.body)): 70 | insn: CsInsn = self.body[i] 71 | if instr_match(insn, [X86_INS_MOV, X86_INS_MOVZX], [X86_OP_REG, X86_OP_MEM], [None, {'base': self.config.reg_sp, 'disp': off, 'index': X86_REG_INVALID, 'scale': 1}]): 72 | op1, op2 = insn.operands 73 | if not self.check_placeholder(args, 'reg', op1.reg): 74 | continue 75 | if not self.check_placeholder(args, 'size', op2.size): 76 | continue 77 | self.update_index(i+1) 78 | return True 79 | return False 80 | 81 | def _store(self, off, args): 82 | for i in range(self.index, len(self.body)): 83 | insn: CsInsn = self.body[i] 84 | if instr_match(insn, X86_INS_MOV, [X86_OP_MEM, X86_OP_REG], [{'base': self.config.reg_sp, 'disp': off, 'index': X86_REG_INVALID, 'scale': 1}]): 85 | op1, op2 = insn.operands 86 | if not self.check_placeholder(args, 'reg', op2.reg): 87 | continue 88 | if not self.check_placeholder(args, 'size', op1.size): 89 | continue 90 | self.update_index(i+1) 91 | return True 92 | return False 93 | 94 | def load_byte(self, off=0, args=None): 95 | # movzx cx, byte ptr [{sp}] 96 | if args is None: 97 | args = {} 98 | args['size'] = 1 99 | return self._load(off, args) 100 | 101 | def load_word(self, off=0, args=None): 102 | # movzx cx, word ptr [{sp}] 103 | if args is None: 104 | args = {} 105 | args['size'] = 2 106 | return self._load(off, args) 107 | 108 | def load_dword(self, off=0, args=None): 109 | # mov eax, dword ptr [{sp}] 110 | if args is None: 111 | args = {} 112 | args['size'] = 4 113 | return self._load(off, args) 114 | 115 | def load_qword(self, off=0, args=None): 116 | # mov eax, dword ptr [{sp}] 117 | if args is None: 118 | args = {} 119 | args['size'] = 8 120 | return self._load(off, args) 121 | 122 | def load_zword(self, off=0, args=None): 123 | # mov eax, dword ptr [{sp}] 124 | if args is None: 125 | args = {} 126 | args['size'] = bridge.size 127 | return self._load(off, args) 128 | 129 | def load(self, off=0, args=None): 130 | if args is None: 131 | args = {} 132 | # mov eax, dword ptr [{sp}] 133 | return self._load(off, args) 134 | 135 | def store_byte(self, off=0, args=None): 136 | # movzx cx, byte ptr [{sp}] 137 | if args is None: 138 | args = {} 139 | args['size'] = 1 140 | return self._store(off, args) 141 | 142 | def store_word(self, off=0, args=None): 143 | # movzx cx, word ptr [{sp}] 144 | if args is None: 145 | args = {} 146 | args['size'] = 2 147 | return self._store(off, args) 148 | 149 | def store_dword(self, off=0, args=None): 150 | # mov eax, dword ptr [{sp}] 151 | if args is None: 152 | args = {} 153 | args['size'] = 4 154 | return self._store(off, args) 155 | 156 | def store_qword(self, off=0, args=None): 157 | # mov eax, dword ptr [{sp}] 158 | if args is None: 159 | args = {} 160 | args['size'] = 8 161 | return self._store(off, args) 162 | 163 | def store(self, off=0, args=None): 164 | # mov eax, dword ptr [{sp}] 165 | if args is None: 166 | args = {} 167 | return self._store(off, args) 168 | 169 | def _read(self, args): 170 | id = [X86_INS_MOV, X86_INS_MOVZX] 171 | # movzx eax, byte ptr [] 172 | # mov eax, dword ptr [] 173 | for i in range(self.index, len(self.body)): 174 | insn: CsInsn = self.body[i] 175 | if instr_match(insn, id, [X86_OP_REG, X86_OP_MEM], [None, {'base': self.config.reg_regs, 'disp': 0, 'scale': 1}]): 176 | op1, op2 = insn.operands 177 | if not self.check_placeholder(args, 'reg', op1.reg): 178 | continue 179 | if not self.check_placeholder(args, 'size', op2.size): 180 | continue 181 | self.update_index(i) 182 | return True 183 | return False 184 | 185 | # vm_regs 186 | def read_byte(self, args=None): 187 | if args is None: 188 | args = {} 189 | args['size'] = 1 190 | return self._read(args) 191 | 192 | def read_word(self, args=None): 193 | if args is None: 194 | args = {} 195 | args['size'] = 2 196 | return self._read(args) 197 | 198 | def read_dword(self, args=None): 199 | if args is None: 200 | args = {} 201 | args['size'] = 4 202 | return self._read(args) 203 | 204 | def read_qword(self, args=None): 205 | if args is None: 206 | args = {} 207 | args['size'] = 8 208 | return self._read(args) 209 | 210 | def read(self, args=None): 211 | if args is None: 212 | args = {} 213 | return self._read(args) 214 | 215 | def _write(self, args): 216 | for i in range(self.index, len(self.body)): 217 | insn: CsInsn = self.body[i] 218 | if instr_match(insn, X86_INS_MOV, [X86_OP_MEM, X86_OP_REG], [{'base': self.config.reg_regs, 'disp': 0, 'scale': 1}]): 219 | op1, op2 = insn.operands 220 | if not self.check_placeholder(args, 'reg', op2.reg): 221 | continue 222 | if not self.check_placeholder(args, 'size', op1.size): 223 | continue 224 | self.update_index(i) 225 | return True 226 | return False 227 | 228 | def write_byte(self, args=None): 229 | if args is None: 230 | args = {} 231 | args['size'] = 1 232 | return self._write(args) 233 | 234 | def write_word(self, args=None): 235 | if args is None: 236 | args = {} 237 | args['size'] = 2 238 | return self._write(args) 239 | 240 | def write_dword(self, args=None): 241 | if args is None: 242 | args = {} 243 | args['size'] = 4 244 | return self._write(args) 245 | 246 | def write_qword(self, args=None): 247 | if args is None: 248 | args = {} 249 | args['size'] = 8 250 | return self._write(args) 251 | 252 | def write(self, args=None): 253 | if args is None: 254 | args = {} 255 | return self._write(args) 256 | 257 | def _mem_write(self, args): 258 | # mov dword ptr ds:[ecx], al 259 | for i in range(self.index, len(self.body)): 260 | insn: CsInsn = self.body[i] 261 | if instr_match(insn, X86_INS_MOV, [X86_OP_MEM, X86_OP_REG], [{'index': X86_REG_INVALID, 'disp': 0, 'scale': 1}]): 262 | op1, op2 = insn.operands 263 | if not self.check_placeholder(args, 'addr', op1.mem.base): 264 | continue 265 | if not self.check_placeholder(args, 'val', op2.reg): 266 | continue 267 | if not self.check_placeholder(args, 'segment', op1.mem.segment): 268 | continue 269 | if not self.check_placeholder(args, 'size', op1.size): 270 | continue 271 | self.update_index(i) 272 | return True 273 | return False 274 | 275 | def mem_write_byte(self, args=None): 276 | if args is None: 277 | args = {} 278 | args['size'] = 1 279 | return self._mem_write(args) 280 | 281 | def mem_write_word(self, args=None): 282 | if args is None: 283 | args = {} 284 | args['size'] = 2 285 | return self._mem_write(args) 286 | 287 | def mem_write_dword(self, args=None): 288 | if args is None: 289 | args = {} 290 | # mov dword ptr [ecx], eax 291 | args['size'] = 4 292 | return self._mem_write(args) 293 | 294 | def mem_write_qword(self, args=None): 295 | if args is None: 296 | args = {} 297 | args['size'] = 8 298 | return self._mem_write(args) 299 | 300 | def mem_write_zword(self, args=None): 301 | if args is None: 302 | args = {} 303 | args['size'] = bridge.size 304 | return self._mem_write(args) 305 | 306 | def mem_write(self, args=None): 307 | if args is None: 308 | args = {} 309 | return self._mem_write(args) 310 | 311 | def _mem_read(self, args): 312 | # mov al, dword ptr ds:[ecx], 313 | for i in range(self.index, len(self.body)): 314 | insn: CsInsn = self.body[i] 315 | if instr_match(insn, [X86_INS_MOV, X86_INS_MOVZX], [X86_OP_REG, X86_OP_MEM], [None, {'index': X86_REG_INVALID, 'disp': 0, 'scale': 1}]): 316 | op1, op2 = insn.operands 317 | if not self.check_placeholder(args, 'addr', op2.mem.base): 318 | continue 319 | if not self.check_placeholder(args, 'val', op1.reg): 320 | continue 321 | if not self.check_placeholder(args, 'segment', op2.mem.segment): 322 | continue 323 | if not self.check_placeholder(args, 'size', op2.size): 324 | continue 325 | self.update_index(i) 326 | return True 327 | return False 328 | 329 | def mem_read_byte(self, args=None): 330 | if args is None: 331 | args = {} 332 | args['size'] = 1 333 | return self._mem_read(args) 334 | 335 | def mem_read_word(self, args=None): 336 | if args is None: 337 | args = {} 338 | args['size'] = 2 339 | return self._mem_read(args) 340 | 341 | def mem_read_dword(self, args=None): 342 | if args is None: 343 | args = {} 344 | # mov eax, dword ptr [ecx] 345 | args['size'] = 4 346 | return self._mem_read(args) 347 | 348 | def mem_read_qword(self, args=None): 349 | if args is None: 350 | args = {} 351 | args['size'] = 8 352 | return self._mem_read(args) 353 | 354 | def mem_read(self, args=None): 355 | if args is None: 356 | args = {} 357 | return self._mem_read(args) 358 | 359 | def decode(self, args=None): 360 | if args is None: 361 | args = {} 362 | # find decode begin: xor ecx, ebx 363 | begin = -1 364 | for i in range(self.index, len(self.body)): 365 | insn: CsInsn = self.body[i] 366 | if instr_match(insn, X86_INS_XOR, [X86_OP_REG, X86_OP_REG]): 367 | op1, op2 = insn.operands 368 | if extend_reg(op2.reg) == self.config.reg_key: 369 | reg1 = op1.reg 370 | begin = i 371 | break 372 | if begin == -1: 373 | return False 374 | 375 | # find decode end: xor ebx, ecx 376 | end = -1 377 | for i in range(begin, len(self.body)): 378 | insn: CsInsn = self.body[i] 379 | if instr_match(insn, X86_INS_XOR, [X86_OP_REG, X86_OP_REG]): 380 | op1, op2 = insn.operands 381 | if extend_reg(op1.reg) == self.config.reg_key: 382 | reg2 = op2.reg 383 | end = i 384 | size = op2.size 385 | break 386 | 387 | # 388 | # 389 | # 390 | if end == -1 and bridge.is64bit(): 391 | for i in range(begin, len(self.body)): 392 | insn: CsInsn = self.body[i] 393 | if instr_match(insn, X86_INS_XOR, [X86_OP_MEM, X86_OP_REG]): 394 | op1, op2 = insn.operands 395 | reg2 = op2.reg 396 | end = i 397 | size = op1.size 398 | break 399 | 400 | if end == -1: 401 | return False 402 | 403 | if reg1 == reg2: 404 | args['reg'] = reg1 405 | args['decoder'] = self.body[begin:end+1] 406 | args['size'] = size 407 | self.update_index(end+1) 408 | return True 409 | return False 410 | 411 | def _fetch(self, args=None): 412 | if args is None: 413 | args = {} 414 | for i in range(self.index, len(self.body)): 415 | insn: CsInsn = self.body[i] 416 | if instr_match(insn, [X86_INS_MOV, X86_INS_MOVZX], [X86_OP_REG, X86_OP_MEM], 417 | [None, {'base': self.config.reg_ip, 'disp': 0, 'index': X86_REG_INVALID, 'scale': 1}]): 418 | op1, op2 = insn.operands 419 | if not self.check_placeholder(args, 'size', op2.size): 420 | continue 421 | self.update_index(i+1) 422 | return True 423 | return False 424 | 425 | # find movzx ecx, byte ptr [ip] 426 | def fetch_byte(self): 427 | return self._fetch({'size': 1}) 428 | 429 | def fetch_word(self): 430 | return self._fetch({'size': 2}) 431 | 432 | def fetch_dword(self): 433 | return self._fetch({'size': 4}) 434 | 435 | def fetch_qword(self): 436 | return self._fetch({'size': 8}) 437 | 438 | def fetch(self, args=None): 439 | if args is None: 440 | args = [] 441 | return self._fetch(args) 442 | 443 | def batch(self, id): 444 | ids = id if isinstance(id, list) else [id] 445 | if len(ids) == 0: 446 | return False 447 | match_count = 0 448 | for i in range(self.index, len(self.body)): 449 | insn: CsInsn = self.body[i] 450 | if insn.id == ids[match_count]: 451 | match_count += 1 452 | if match_count >= len(ids): 453 | self.update_index(i+1) 454 | return True 455 | return False 456 | 457 | def among(self, id, args=None): 458 | if args is None: 459 | args = {} 460 | ids = id if isinstance(id, list) else [id] 461 | if len(ids) == 0: 462 | return False 463 | for i in range(self.index, len(self.body)): 464 | insn: CsInsn = self.body[i] 465 | if insn.id in ids: 466 | self.update_index(i+1) 467 | if not self.check_placeholder(args, 'ins', insn.id): 468 | continue 469 | return True 470 | return False 471 | 472 | def store_eflags(self): 473 | eflags = X86_INS_PUSHFQ if bridge.is64bit() else X86_INS_PUSHFD 474 | # find pushfd 475 | tmp = -1 476 | for i in range(self.index, len(self.body)): 477 | insn: CsInsn = self.body[i] 478 | if insn.id == eflags: 479 | tmp = i+1 480 | break 481 | if tmp < 0: 482 | return False 483 | 484 | for i in range(tmp, len(self.body)): 485 | insn: CsInsn = self.body[i] 486 | if instr_match(insn, X86_INS_POP, [X86_OP_MEM], [{'base': self.config.reg_sp, 'disp': 0, 'index': X86_REG_INVALID, 'scale': 1}]): 487 | self.update_index(i+1) 488 | return True 489 | return False 490 | 491 | def match_for(self, callback): 492 | for i in range(self.index, len(self.body)): 493 | insn: CsInsn = self.body[i] 494 | if callback(insn): 495 | self.update_index(i+1) 496 | return True 497 | return False 498 | 499 | def match(self, id, optype=None, opx=None): 500 | if optype is None: 501 | optype = [] 502 | if opx is None: 503 | opx = [] 504 | for i in range(self.index, len(self.body)): 505 | insn: CsInsn = self.body[i] 506 | if instr_match(insn, id, optype, opx): 507 | self.update_index(i+1) 508 | return True 509 | return False 510 | 511 | # id X86_INS_ADD [X86_INS_ADD, X86_INS_SUB] 512 | # optype [X86_OP_REG, X86_OP_MEM] 513 | # opx [X86_REG_EAX, {'base': X86_REG_EBP, 'disp': 4}] 514 | 515 | 516 | def instr_match(insn: CsInsn, id, optype=None, opx=None): 517 | if optype is None: 518 | optype = [] 519 | if opx is None: 520 | opx = [] 521 | ids = id if isinstance(id, list) else [id] 522 | if insn.id not in ids: 523 | return False 524 | if len(insn.operands) < len(optype): 525 | return False 526 | for i, item in enumerate(optype): 527 | op: X86Op = insn.operands[i] 528 | if op.type != item: 529 | return False 530 | # [X86_OP_REG, X86_OP_MEM], [reg, {'base': reg, 'index': X86_REG_INVALID, 'scale': 1}]) 531 | if len(opx) > i: 532 | if opx[i] is None: 533 | continue 534 | if item == X86_OP_REG: 535 | if op.reg != opx[i]: 536 | return False 537 | elif item == X86_OP_IMM: 538 | if op.imm != opx[i]: 539 | return False 540 | elif item == X86_OP_MEM: 541 | # 'segment', 'base', 'index','scale','disp', 542 | if 'segment' in opx[i] and opx[i]['segment'] != None and op.mem.segment != opx[i]['segment']: 543 | return False 544 | elif 'base' in opx[i] and opx[i]['base'] != None and op.mem.base != opx[i]['base']: 545 | return False 546 | elif 'index' in opx[i] and opx[i]['index'] != None and op.mem.index != opx[i]['index']: 547 | return False 548 | elif 'scale' in opx[i] and opx[i]['scale'] != None and op.mem.scale != opx[i]['scale']: 549 | return False 550 | elif 'disp' in opx[i] and opx[i]['disp'] != None and op.mem.disp != opx[i]['disp']: 551 | return False 552 | return True 553 | -------------------------------------------------------------------------------- /novmpy/ui.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import ida_kernwin 3 | import idaapi 4 | 5 | from novmpy.vm_lifter import search_vmstubs 6 | from novmpy.views import vtil_graph 7 | 8 | 9 | class novmpy_letsgo_action(ida_kernwin.action_handler_t): 10 | def __init__(self): 11 | ida_kernwin.action_handler_t.__init__(self) 12 | 13 | def activate(self, ctx): 14 | # breakpoint() 15 | ea = idaapi.get_screen_ea() 16 | print('ea:', hex(ea)) 17 | vtil_graph.show_graph(ea) 18 | return 1 19 | 20 | def update(self, ctx): 21 | if ctx.widget_type == ida_kernwin.BWN_DISASM: 22 | return ida_kernwin.AST_ENABLE_FOR_WIDGET 23 | return ida_kernwin.AST_DISABLE_FOR_WIDGET 24 | 25 | 26 | class novmpy_search_action(ida_kernwin.action_handler_t): 27 | def __init__(self): 28 | ida_kernwin.action_handler_t.__init__(self) 29 | 30 | def activate(self, ctx): 31 | # breakpoint() 32 | for entry, is_mid_routine in search_vmstubs(): 33 | print('entry:', hex(entry), 'is_mid_routine:', is_mid_routine) 34 | return 1 35 | 36 | def update(self, ctx): 37 | if ctx.widget_type == ida_kernwin.BWN_DISASM: 38 | return ida_kernwin.AST_ENABLE_FOR_WIDGET 39 | return ida_kernwin.AST_DISABLE_FOR_WIDGET 40 | 41 | 42 | class novmpy_handler_view_action(ida_kernwin.action_handler_t): 43 | def __init__(self): 44 | ida_kernwin.action_handler_t.__init__(self) 45 | 46 | def activate(self, ctx): 47 | # breakpoint() 48 | from novmpy.views.hview import show_handler_list_view 49 | show_handler_list_view() 50 | return 1 51 | 52 | def update(self, ctx): 53 | if ctx.widget_type == ida_kernwin.BWN_DISASM: 54 | return ida_kernwin.AST_ENABLE_FOR_WIDGET 55 | return ida_kernwin.AST_DISABLE_FOR_WIDGET 56 | 57 | 58 | _act_dests = [ 59 | ida_kernwin.action_desc_t( 60 | "novmpy:letsgo", "LetsGo", novmpy_letsgo_action()), 61 | ida_kernwin.action_desc_t( 62 | "novmpy:search", "Search", novmpy_search_action()), 63 | ida_kernwin.action_desc_t( 64 | "novmpy:handler_view", "Handler List", novmpy_handler_view_action()) 65 | ] 66 | 67 | 68 | class HooksUI(ida_kernwin.UI_Hooks): 69 | def finish_populating_widget_popup(self, widget, popup): 70 | if ida_kernwin.get_widget_type(widget) == ida_kernwin.BWN_DISASM: 71 | for act_dest in _act_dests: 72 | ida_kernwin.attach_action_to_popup( 73 | widget, popup, act_dest.name, "NoVmpy/") 74 | 75 | 76 | class UIManager(): 77 | def __init__(self) -> None: 78 | self.ui_action_handler_register() 79 | self.hooks_ui = HooksUI() 80 | self.hooks_ui.hook() 81 | 82 | def __del__(self): 83 | self.hooks_ui = None 84 | self.ui_action_handler_unregister() 85 | 86 | def ui_action_handler_unregister(self): 87 | for act_dest in _act_dests: 88 | ida_kernwin.detach_action_from_menu( 89 | f"Edit/novmpy/{act_dest.name}", act_dest.name) 90 | # ida_kernwin.unregister_action(act_dest.name) 91 | 92 | def ui_action_handler_register(self): 93 | for act_dest in _act_dests: 94 | if not ida_kernwin.register_action(act_dest): 95 | print(f'warning failed register_action({act_dest.name})') 96 | ida_kernwin.attach_action_to_menu( 97 | f"Edit/novmpy/{act_dest.name}", act_dest.name, ida_kernwin.SETMENU_APP) 98 | -------------------------------------------------------------------------------- /novmpy/views/hview.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | from capstone import CsInsn 4 | from ida_kernwin import Choose 5 | from novmpy.handler import vm_handlers 6 | from novmpy.handler import VMJmp 7 | import ida_kernwin 8 | import idaapi 9 | import ida_lines 10 | 11 | 12 | class HandlerViewer(ida_kernwin.simplecustviewer_t): 13 | def Create(self, handler): 14 | if ida_kernwin.find_widget('handler view'): 15 | self.handler = handler 16 | self.reload() 17 | return True 18 | # Create the customviewer 19 | if not ida_kernwin.simplecustviewer_t.Create(self, 'handler view'): 20 | return False 21 | self.handler = handler 22 | self.reload() 23 | return True 24 | 25 | def Show(self, *args): 26 | return ida_kernwin.simplecustviewer_t.Show(self, *args) 27 | 28 | def reload(self): 29 | self.ClearLines() 30 | self.AddLine(ida_lines.COLSTR( 31 | str(self.handler.config), ida_lines.SCOLOR_AUTOCMT)) 32 | for i in self.handler.body: 33 | i: CsInsn 34 | self.AddLine(ida_lines.COLSTR( 35 | f'{i.address:016X} {i.mnemonic:<8} {i.op_str}', ida_lines.SCOLOR_INSN)) 36 | 37 | if isinstance(self.handler, VMJmp): 38 | self.AddLine(ida_lines.COLSTR( 39 | str(self.handler.conn_config), ida_lines.SCOLOR_AUTOCMT)) 40 | else: 41 | self.AddLine('') 42 | for i in self.handler.connect: 43 | i: CsInsn 44 | self.AddLine(ida_lines.COLSTR( 45 | f'{i.address:016X} {i.mnemonic:<8} {i.op_str}', ida_lines.SCOLOR_INSN)) 46 | self.Refresh() 47 | 48 | @staticmethod 49 | def get_instance(): 50 | return _handler_viewer 51 | 52 | def show_win(self, handler): 53 | if not self.Create(handler): 54 | return False 55 | ida_kernwin.display_widget( 56 | self.GetWidget(), idaapi.PluginForm.WOPN_DP_TAB | idaapi.PluginForm.WOPN_RESTORE) 57 | ida_kernwin.set_dock_pos( 58 | self.title, "HandlerListView", ida_kernwin.DP_RIGHT) 59 | self.Show() 60 | return True 61 | 62 | 63 | class HandlerListView(Choose): 64 | class Item(): 65 | def __init__(self, address, name, body_len) -> None: 66 | self.address = address 67 | self.name = name 68 | self.body_len = body_len 69 | 70 | def to_strings(self): 71 | s = [f'{self.address:016X}', self.name, str(self.body_len)] 72 | return s 73 | 74 | def __init__(self, title, flags=0): 75 | Choose.__init__(self, title, 76 | [["Address", 15], ["Name", 20], ["BodyLen", 5]], 77 | flags=flags | Choose.CH_CAN_REFRESH) 78 | 79 | self.items = [] 80 | self.reload() 81 | 82 | def reload(self): 83 | self.items = [] 84 | for k, v in vm_handlers.items(): 85 | self.items.append(HandlerListView.Item( 86 | k, f'{v.name}@{v.opsize}', len(v.body))) 87 | 88 | def OnInit(self): 89 | return True 90 | 91 | def OnGetSize(self): 92 | return len(self.items) 93 | 94 | def OnGetLine(self, n): 95 | return self.items[n].to_strings() 96 | 97 | def OnGetIcon(self, n): 98 | return 81 99 | 100 | def OnGetLineAttr(self, n): 101 | # return [0xFF0000, 0] 102 | return None 103 | 104 | def OnRefresh(self, n): 105 | self.reload() 106 | return None # call standard refresh 107 | 108 | def OnSelectLine(self, n): 109 | ea = self.items[n].address 110 | print(hex(ea)) 111 | h = vm_handlers.get(ea, None) 112 | if h is not None: 113 | print(h.name) 114 | HandlerViewer.get_instance().show_win(h) 115 | return (Choose.NOTHING_CHANGED, ) 116 | 117 | def OnClose(self): 118 | return 119 | 120 | def show(self): 121 | self.reload() 122 | return self.Show(False) >= 0 123 | 124 | 125 | # ----------------------------------------------------------------------- 126 | 127 | 128 | _handler_viewer = HandlerViewer() 129 | _handler_list_viewer = HandlerListView("HandlerListView") 130 | 131 | 132 | def show_handler_list_view(): 133 | _handler_list_viewer.show() 134 | -------------------------------------------------------------------------------- /novmpy/views/vtil_graph.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import idautils 3 | import ida_lines 4 | # ----------------------------------------------------------------------- 5 | # This is an example illustrating how to use the user graphing functionality 6 | # in Python 7 | # (c) Hex-Rays 8 | # 9 | import ida_dbg 10 | import ida_graph 11 | import ida_kernwin 12 | import ida_ida 13 | import ida_name 14 | from ida_nalt import get_str_type 15 | from ida_bytes import is_strlit, get_flags, get_strlit_contents 16 | 17 | from pyvtil import * 18 | from novmpy.vm_lifter import VMLifter 19 | from novmpy.vm import VMState 20 | import os 21 | from novmpy.bridge import * 22 | 23 | 24 | class _base_graph_action_handler_t(ida_kernwin.action_handler_t): 25 | def __init__(self, graph): 26 | ida_kernwin.action_handler_t.__init__(self) 27 | self.graph: MyGraph = graph 28 | 29 | def update(self, ctx): 30 | return ida_kernwin.AST_ENABLE_FOR_WIDGET 31 | 32 | 33 | def ask_list(str_list): 34 | class ListForm(ida_kernwin.Form): 35 | def __init__(self, _str_list): 36 | F = ida_kernwin.Form 37 | F.__init__(self, r"""BUTTON YES OK 38 | 39 | 40 | """, {'str_list': F.DropdownListControl(items=_str_list, readonly=True)}) 41 | 42 | f = ListForm(str_list) 43 | f, args = f.Compile() 44 | ok = f.Execute() 45 | result = '' 46 | if ok and f.str_list.value >= 0: 47 | result = str_list[f.str_list.value] 48 | f.Free() 49 | return result 50 | 51 | 52 | class GraphOptimizer(_base_graph_action_handler_t): 53 | def activate(self, ctx): 54 | print('Optimizer') 55 | if self.graph.rtn is None: 56 | return 0 57 | node_id = ida_graph.viewer_get_curnode( 58 | ida_graph.get_graph_viewer(self.graph.GetWidget())) 59 | print('node_id', node_id) 60 | block = None 61 | if node_id >= 0: 62 | if self.graph.rtn is None: 63 | return 0 64 | for vip, b in self.graph.rtn.explored_blocks.items(): 65 | if vip == self.graph.list_vip[node_id]: 66 | block = b 67 | break 68 | optimizers = { 69 | 'apply_all': vtil.optimizer.apply_all, 70 | 'stack_pinning_pass': vtil.optimizer.stack_pinning_pass, 71 | 'istack_ref_substitution_pass': vtil.optimizer.istack_ref_substitution_pass, 72 | 'bblock_extension_pass': vtil.optimizer.bblock_extension_pass, 73 | 'stack_propagation_pass': vtil.optimizer.stack_propagation_pass, 74 | 'dead_code_elimination_pass': vtil.optimizer.dead_code_elimination_pass, 75 | 'fast_dead_code_elimination_pass': vtil.optimizer.fast_dead_code_elimination_pass, 76 | 'mov_propagation_pass': vtil.optimizer.mov_propagation_pass, 77 | 'symbolic_rewrite_pass_force': vtil.optimizer.symbolic_rewrite_pass_force, 78 | 'symbolic_rewrite_pass': vtil.optimizer.symbolic_rewrite_pass, 79 | 'branch_correction_pass': vtil.optimizer.branch_correction_pass, 80 | 'register_renaming_pass': vtil.optimizer.register_renaming_pass, 81 | 'collective_cross_pass': vtil.optimizer.collective_cross_pass, 82 | } 83 | if choose := ask_list(list(optimizers.keys())): 84 | optimizer = optimizers[choose] 85 | if block is None: 86 | optimizer(self.graph.rtn) 87 | else: 88 | optimizer(block) 89 | return 1 90 | 91 | def update(self, ctx): 92 | return ida_kernwin.AST_ENABLE_FOR_WIDGET 93 | 94 | 95 | class GraphLoad(_base_graph_action_handler_t): 96 | def activate(self, ctx): 97 | print('Load') 98 | _path = ida_kernwin.ask_file(False, '*.vtil', 'Choose vtil file:') 99 | if _path: 100 | self.graph.rtn = vtil.routine.load(_path) 101 | return 1 102 | 103 | def update(self, ctx): 104 | return ida_kernwin.AST_ENABLE_FOR_WIDGET 105 | 106 | 107 | class GraphSave(_base_graph_action_handler_t): 108 | def activate(self, ctx): 109 | print('Save') 110 | if self.graph.rtn is None: 111 | return 0 112 | _path = ida_kernwin.ask_file( 113 | True, '*.vtil', 'Enter name of vtil file:') 114 | if _path: 115 | self.graph.rtn.save(_path) 116 | return 0 117 | 118 | def update(self, ctx): 119 | return ida_kernwin.AST_ENABLE_FOR_WIDGET 120 | 121 | 122 | class JumptoAction(_base_graph_action_handler_t): 123 | def activate(self, ctx): 124 | gv = ida_graph.get_graph_viewer(ctx.widget) 125 | print('whoooooops, not impl') 126 | # s = ida_kernwin.ask_str('', 0, 'addr') 127 | # if s: 128 | # ea = ida_kernwin.str2ea(s) 129 | # self.graph.jumpto(nid, lnnum) 130 | return 0 131 | 132 | def update(self, ctx): 133 | # FIXME: 134 | if not self.graph.focus or self.graph.GetWidget() != ctx.widget: 135 | return ida_kernwin.AST_DISABLE_FOR_WIDGET 136 | return ida_kernwin.AST_ENABLE_FOR_WIDGET 137 | 138 | 139 | def pack_operands(ins: vtil.instruction): 140 | operands = ins.operands 141 | opstr = [] 142 | for op in operands: 143 | color = ida_lines.SCOLOR_REG if op.is_register() else ida_lines.SCOLOR_DNUM 144 | if ins.base.name == 'js' and op.is_immediate(): 145 | s = hex(op.imm().uval) 146 | else: 147 | s = str(op) 148 | _opstr = ida_lines.COLSTR(s, color) 149 | opstr.append(_opstr) 150 | return opstr 151 | 152 | 153 | dict_cond = { 154 | 'tg': '>', 'tge': '>=', 155 | 'te': '==', 'tne': '!=', 156 | 'tl': '<', 'tle': '<=', 157 | 'tug': 'u>', 'tuge': 'u>=', 158 | 'tul': 'u<', 'tule': 'u<=', 159 | } 160 | 161 | 162 | def instruction_tostring(basic_block: vtil.basic_block, it, args=None): 163 | if args is None: 164 | args = dict() 165 | # @https://github.com/vtil-project/VTIL-BinaryNinja/blob/master/vtil/vtil.py 166 | ins: vtil.instruction = it.get() 167 | s = '' 168 | comment = '' 169 | s += ida_lines.COLSTR(f'{ins.base.to_string(ins.access_size()):6}', 170 | ida_lines.SCOLOR_INSN) 171 | s += ' ' 172 | opstr = pack_operands(ins) 173 | if ins.base.name in dict_cond: 174 | # op0 = op1 cond op2 175 | s += opstr[0] 176 | s += ida_lines.COLSTR(' := (', ida_lines.SCOLOR_SYMBOL) 177 | s += opstr[1] 178 | s += ida_lines.COLSTR(f' {dict_cond[ins.base.name]} ', 179 | ida_lines.SCOLOR_SYMBOL) 180 | s += opstr[2] 181 | s += ida_lines.COLSTR(')', ida_lines.SCOLOR_SYMBOL) 182 | elif ins.base.name == 'js': 183 | # op0 ? op1: op2 184 | s += opstr[0] 185 | s += ida_lines.COLSTR(' ? ', ida_lines.SCOLOR_SYMBOL) 186 | s += opstr[1] 187 | s += ida_lines.COLSTR(' : ', ida_lines.SCOLOR_SYMBOL) 188 | s += opstr[2] 189 | elif ins.base.name == 'str': 190 | # [op0+op1], op2 191 | s += ida_lines.COLSTR('[', ida_lines.SCOLOR_SYMBOL) + opstr[0] 192 | s += ida_lines.COLSTR('+', ida_lines.SCOLOR_SYMBOL) + opstr[1] 193 | s += ida_lines.COLSTR('], ', ida_lines.SCOLOR_SYMBOL) + opstr[2] 194 | elif ins.base.name == 'ldd': 195 | # op0, [op1, op2] 196 | s += opstr[0] 197 | s += ida_lines.COLSTR(', ', ida_lines.SCOLOR_SYMBOL) 198 | s += ida_lines.COLSTR('[', ida_lines.SCOLOR_SYMBOL) 199 | s += opstr[1] 200 | s += ida_lines.COLSTR('+', ida_lines.SCOLOR_SYMBOL) 201 | s += opstr[2] 202 | s += ida_lines.COLSTR(']', ida_lines.SCOLOR_SYMBOL) 203 | else: 204 | for i, op in enumerate(ins.operands): 205 | # chr(cvar.COLOR_ADDR+i+1) 206 | color = ida_lines.SCOLOR_DNUM 207 | if op.is_register(): 208 | color = ida_lines.SCOLOR_REG 209 | s += ida_lines.COLSTR(f'{str(op)}', color) 210 | if i+1 != len(ins.operands): 211 | s += ida_lines.COLSTR(', ', ida_lines.SCOLOR_SYMBOL) 212 | imm = 0 213 | for i, op in enumerate(ins.operands): 214 | if op.is_immediate() and op.imm().ival > imm: 215 | imm = op.imm().ival 216 | if imm > 0: 217 | if is_strlit(get_flags(imm)): 218 | content = get_strlit_contents(imm, -1, get_str_type(imm)) 219 | content = content.decode('UTF-8', 'replace') 220 | comment += repr(content[:30]) 221 | if len(content) > 30: 222 | comment += '...' 223 | else: 224 | comment += ida_name.get_name(imm) 225 | bs_size = args.get('bs_size', 0) 226 | skip_size = args.get('skip_size', 0) 227 | if ins.base == vtil.ins.vemit: 228 | asm_bytecode = b'' 229 | if bs_size == 0: 230 | bs_size = ins.operands[0].size() 231 | it2 = it 232 | while it2 != basic_block.end(): 233 | i2: vtil.instruction = it2.get() 234 | if i2.base != vtil.ins.vemit: 235 | break 236 | uval = i2.operands[0].imm().uval 237 | for _ in range(i2.operands[0].size()): 238 | asm_bytecode += bytes([uval & 0xFF]) 239 | uval >>= 8 240 | it2 = it2.next() 241 | if asm_bytecode: 242 | asm_vip = ins.vip if ins.vip != vtil.invalid_vip else 0 243 | for a in bridge.disasm(asm_bytecode, asm_vip): 244 | comment = a.mnemonic+' '+a.op_str 245 | skip_size = a.size 246 | break 247 | else: 248 | bs_size += ins.operands[0].size() 249 | if bs_size >= skip_size or ins.base != vtil.ins.vemit: 250 | skip_size = 0 251 | bs_size = 0 252 | args['bs_size'] = bs_size 253 | args['skip_size'] = skip_size 254 | if comment: 255 | s += ida_lines.COLSTR(' ; '+comment, ida_lines.SCOLOR_AUTOCMT) 256 | return s 257 | 258 | 259 | class MyGraph(ida_graph.GraphViewer): 260 | def __init__(self, title): 261 | self.list_vip = [] 262 | self.focus = True 263 | self.title = title 264 | self.rtn: vtil.routine = None 265 | ida_graph.GraphViewer.__init__(self, self.title, True) 266 | self.color = 0xffffff 267 | self.jump_to_desc = ida_kernwin.action_desc_t( 268 | f"graph_{title}:jumpto", "jumpto", JumptoAction(self), "G", "") 269 | ida_kernwin.register_action(self.jump_to_desc) 270 | 271 | def Show(self): 272 | if not ida_graph.GraphViewer.Show(self): 273 | return False 274 | ida_graph.viewer_set_titlebar_height(self.GetWidget(), 15) 275 | ida_kernwin.attach_action_to_popup( 276 | self.GetWidget(), None, self.jump_to_desc.name) 277 | self.cmd_refresh = self.AddCommand("Refresh", "") 278 | self.cmd_trace = self.AddCommand("Trace", "") 279 | self.cmd_patch = self.AddCommand("Patch Instruction", "") 280 | self.cmd_erase = self.AddCommand("Erase Instruction", "") 281 | self.cmd_hview = self.AddCommand("Handler list", "") 282 | return True 283 | 284 | def OnCommand(self, cmd_id): 285 | print('OnCommand', cmd_id) 286 | 287 | if cmd_id == self.cmd_refresh: 288 | self.Refresh() 289 | return 1 290 | elif cmd_id == self.cmd_hview: 291 | from novmpy.views.hview import show_handler_list_view 292 | show_handler_list_view() 293 | return 0 294 | widget = ida_graph.get_graph_viewer(self.GetWidget()) 295 | place, _, _ = ida_kernwin.get_custom_viewer_place(widget, False) 296 | node_id = ida_graph.viewer_get_curnode(widget) 297 | print(node_id, place.lnnum) 298 | ln = place.lnnum - 1 299 | if node_id < 0 or ln < 0: 300 | return 0 301 | block = None 302 | if self.rtn is None: 303 | return 0 304 | for vip, b in self.rtn.explored_blocks.items(): 305 | if vip == self.list_vip[node_id]: 306 | block = b 307 | break 308 | if block: 309 | it = block.begin() 310 | if cmd_id in [self.cmd_patch, self.cmd_erase] and block.size() == ln + 1: 311 | return 0 312 | for i in range(ln): 313 | it = it.next() 314 | ins = it.get() 315 | if cmd_id == self.cmd_trace: 316 | if not it.get().base.is_branching(): 317 | it = it.next() 318 | tracer = vtil.tracer() 319 | for i in ins.operands: 320 | if i.is_register() and i.reg() != vtil.REG_IMGBASE: 321 | print( 322 | f'{i} = {tracer.rtrace(vtil.symbolic.variable(it, i.reg())).simplify(True)}') 323 | elif cmd_id == self.cmd_patch: 324 | val = ida_kernwin.ask_long(0, 'patch: mov reg, {imm}') 325 | if val is not None and len(ins.operands) >= 2: 326 | ins.base = vtil.ins.mov 327 | op0 = ins.operands[0] 328 | ins.operands = [op0, vtil.make_uint(val, op0.bit_count())] 329 | self.Refresh() 330 | return 1 331 | elif cmd_id == self.cmd_erase: 332 | msg = ida_lines.tag_remove(instruction_tostring(block, it)) 333 | if ida_kernwin.ask_yn(ida_kernwin.ASKBTN_NO, f'Erase:\n{msg}') != ida_kernwin.ASKBTN_YES: 334 | return 0 335 | block.erase(it) 336 | self.Refresh() 337 | return 1 338 | return 0 339 | 340 | def OnActivate(self): 341 | self.focus = True 342 | 343 | def OnDeactivate(self): 344 | self.focus = False 345 | 346 | def OnRefresh(self): 347 | self.Clear() 348 | self.list_vip = [] 349 | if self.rtn is None: 350 | return True 351 | for vip, b in self.rtn.explored_blocks.items(): 352 | args = {} 353 | b: vtil.basic_block 354 | s = hex(vip)+':\n' 355 | it = b.begin() 356 | while it != b.end(): 357 | i: vtil.instruction = it.get() 358 | _prefix = '' 359 | # _prefix = f'[ {i.vip:016X} ]' if i.vip != vtil.invalid_vip else '[ PSEUDOCODE ]' 360 | _prefix += f'[{i.sp_index:>2}] ' if i.sp_index > 0 else ' ' 361 | x = '>' if i.sp_reset > 0 else ' ' 362 | x += '+' if i.sp_offset > 0 else '-' 363 | x += hex(abs(i.sp_offset)) 364 | _prefix += f'{x:<6} ' 365 | if ida_ida.inf_show_line_pref(): 366 | s += ida_lines.COLSTR(_prefix, ida_lines.SCOLOR_PREFIX) 367 | s += instruction_tostring(b, it, args) + '\n' 368 | it = it.next() 369 | color = self.color 370 | self.AddNode((s.rstrip('\n'), color)) 371 | self.list_vip.append(vip) 372 | 373 | for vip, b in self.rtn.explored_blocks.items(): 374 | s = self.list_vip.index(vip) 375 | # if b.size() > 0: 376 | # back = b.back() 377 | # if back.base.name == 'js': 378 | # cond, dst1, dst2 = back.operands 379 | # # op1 op2 is imm -> mark tbranch and fbranch 380 | # if dst1.is_immediate() and dst2.is_immediate(): 381 | # # WTF? how to set edge color 382 | # # SHIT! They ignore the `edge_info` parameter! 383 | # https://github.com/idapython/src/blob/3ce5b7f06dfdba36eb84d679c08248734a12036a/pywraps/py_graph.hpp#L431-L432 384 | # self.AddEdge(s, list_vip.index(dst1.imm().u64)) # T 385 | # self.AddEdge(s, list_vip.index(dst2.imm().u64)) # F 386 | # continue 387 | 388 | for next_b in b.next: 389 | d = self.list_vip.index(next_b.entry_vip) 390 | self.AddEdge(s, d) 391 | return True 392 | 393 | def OnGetText(self, node_id): 394 | return self[node_id] 395 | 396 | def OnPopup(self, form, popup_handle): 397 | ida_graph.GraphViewer.OnPopup(self, form, popup_handle) 398 | popup = collections.OrderedDict({ 399 | 'Optimizer': GraphOptimizer(self), 400 | 'Load': GraphLoad(self), 401 | 'Save': GraphSave(self), 402 | }) 403 | for k, v in popup.items(): 404 | ida_kernwin.attach_dynamic_action_to_popup( 405 | form, popup_handle, 406 | ida_kernwin.action_desc_t(k + ":" + self.title, k, v)) 407 | 408 | def OnClose(self): 409 | print('close', self.GetWidget()) 410 | ida_kernwin.detach_action_from_popup( 411 | self.GetWidget(), self.jump_to_desc.name) 412 | ida_kernwin.unregister_action(self.jump_to_desc.name) 413 | 414 | def OnHint(self, node_id): 415 | # cursor in title return [-1, -1] 416 | tmp = ida_kernwin.get_custom_viewer_place( 417 | ida_graph.get_graph_viewer(self.GetWidget()), True) 418 | if len(tmp) != 3: 419 | return 'node:'+str(node_id) 420 | place, _, _ = tmp 421 | return 'nid:'+str(node_id)+'lnnum:'+str(place.lnnum) 422 | 423 | def OnDblClick(self, node_id): 424 | """ 425 | Triggerd when a node is double-clicked. 426 | @return: False to ignore the click and True otherwise 427 | """ 428 | # print("dblclicked on", self[node_id]) 429 | hl = ida_kernwin.get_highlight(self.GetWidget()) 430 | if hl and hl[0].startswith('0x'): 431 | addr, flag = int(hl[0], 16), hl[1] 432 | if addr in self.list_vip: 433 | self.jumpto(self.list_vip.index(addr), 0) 434 | else: 435 | ida_kernwin.jumpto(addr) 436 | 437 | return True 438 | 439 | def jumpto(self, nid, lnnum): 440 | place = ida_graph.create_user_graph_place(nid, lnnum) 441 | ida_kernwin.jumpto(self.GetWidget(), place, -1, -1) 442 | 443 | 444 | def show_graph(ea): 445 | widegt = ida_kernwin.find_widget(f'VTIL: {ea:016X}') 446 | if widegt: 447 | ida_kernwin.activate_widget(widegt, True) 448 | return None 449 | g = MyGraph(f'VTIL: {ea:016X}') 450 | root = os.path.join(idautils.GetIdbDir(), 'vms') 451 | if not os.path.exists(root): 452 | os.mkdir(root) 453 | premature = os.path.join(root, f'{ea:016X}.premature.vtil') 454 | optimized = os.path.join(root, f'{ea:016X}.optimized.vtil') 455 | if os.path.exists(optimized): 456 | print(f'Load VTIL from filecache {optimized}') 457 | g.rtn = vtil.routine.load(optimized) 458 | else: 459 | lifter = VMLifter() 460 | ida_kernwin.show_wait_box("lifting") 461 | try: 462 | lifter.lift_il(None, VMState(current_handler=ea)) 463 | print('Saving premature') 464 | lifter.rtn.save(premature) 465 | ida_kernwin.replace_wait_box('apply_all_profiled') 466 | vtil.optimizer.apply_all_profiled(lifter.rtn) 467 | print('Saving optimized') 468 | lifter.rtn.save(optimized) 469 | except KeyboardInterrupt as e: 470 | print(e) 471 | lifter.rtn = None 472 | finally: 473 | ida_kernwin.hide_wait_box() 474 | # vtil.debug.dump(lifter.rtn) 475 | g.rtn = lifter.rtn 476 | if g.Show(): 477 | # jumpto(g.GetWidget(), ida_graph.create_user_graph_place(0,1), 0, 0) 478 | # ida_graph.viewer_attach_menu_item(g.GetWidget(), ) 479 | return g 480 | else: 481 | return None 482 | -------------------------------------------------------------------------------- /novmpy/vm.py: -------------------------------------------------------------------------------- 1 | from novmpy.bridge import * 2 | from capstone import * 3 | from capstone.x86 import * 4 | from novmpy.x86_deobf import * 5 | from novmpy.match_helper import * 6 | 7 | 8 | class VMConfig: 9 | def __init__(self): 10 | self.reg_key = X86_REG_INVALID 11 | self.reg_ip = X86_REG_INVALID 12 | self.reg_sp = X86_REG_INVALID 13 | self.reg_regs = extend_reg(X86_REG_ESP) 14 | self.reg_base = X86_REG_INVALID 15 | self.dir = 0 16 | self.rebase = 0 17 | 18 | def __str__(self): 19 | return 'VMConfig : r_key({}) r_ip({}) r_sp({}) r_regs({}) r_base({}) dir({}) rebase({})'.format( 20 | bridge.reg_name(self.reg_key), bridge.reg_name( 21 | self.reg_ip), bridge.reg_name(self.reg_sp), 22 | bridge.reg_name(self.reg_regs), bridge.reg_name(self.reg_base), self.dir, hex(self.rebase)) 23 | 24 | def __repr__(self) -> str: 25 | return self.__str__() 26 | 27 | 28 | class VMState: 29 | def __init__(self, **kwargs): 30 | self.ip = kwargs.get('ip', 0) 31 | self.key = kwargs.get('key', 0) 32 | self.current_handler = kwargs.get('current_handler', 0) 33 | self.config: VMConfig = kwargs.get('config', None) 34 | 35 | def get_address(self): 36 | return self.ip-self.config.dir*4 37 | 38 | def decode_emu(self, decoder, ct, reg, size): 39 | mask = get_mask(size*8) 40 | reg_key_op = X86_REG_INVALID 41 | cached_regs = {} 42 | if size == 1: 43 | reg_key_op = get_reg8(self.config.reg_key) 44 | elif size == 2: 45 | reg_key_op = get_reg16(self.config.reg_key) 46 | elif size == 4: 47 | reg_key_op = get_reg32(self.config.reg_key) 48 | elif size == 8: 49 | reg_key_op = get_reg64(self.config.reg_key) 50 | else: 51 | raise NotImplementedError('') 52 | pt = ct & mask 53 | for insn in decoder: 54 | insn: CsInsn 55 | regs_read, regs_write = insn.regs_access() 56 | # xor r11d, imm regs_write = [??,r11d] 57 | if reg in regs_write: 58 | if insn.id == X86_INS_INC: 59 | pt += 1 60 | elif insn.id == X86_INS_DEC: 61 | pt -= 1 62 | elif insn.id == X86_INS_NOT: 63 | pt = ~pt 64 | elif insn.id == X86_INS_NEG: 65 | pt = 0-pt 66 | elif insn.id == X86_INS_BSWAP: 67 | if insn.operands[0].size == 8: 68 | pt = ((pt & 0xFF) << (7*8)) |\ 69 | ((pt & 0xFF00) << (5*8)) |\ 70 | ((pt & 0xFF0000) << (3*8)) | \ 71 | ((pt & 0xFF000000) << (1*8)) |\ 72 | ((pt & 0xFF00000000) >> (1*8)) | \ 73 | ((pt & 0xFF0000000000) >> (3*8)) |\ 74 | ((pt & 0xFF000000000000) >> (5*8)) | \ 75 | ((pt & 0xFF00000000000000) >> (7*8)) 76 | elif insn.operands[0].size == 4: 77 | pt = ((pt & 0xFF) << 24) | ((pt & 0xFF00) << 8) |\ 78 | ((pt & 0xFF0000) >> 8) | ((pt & 0xFF000000) >> 24) 79 | elif instr_match(insn, [X86_INS_XOR, X86_INS_ADD, X86_INS_SUB], [X86_OP_REG, X86_OP_IMM], [reg]): 80 | if insn.id == X86_INS_XOR: 81 | pt ^= insn.operands[1].imm 82 | elif insn.id == X86_INS_ADD: 83 | pt += insn.operands[1].imm 84 | elif insn.id == X86_INS_SUB: 85 | pt -= insn.operands[1].imm 86 | elif instr_match(insn, [X86_INS_ROL, X86_INS_ROR], [X86_OP_REG, X86_OP_IMM], [reg]): 87 | n = insn.operands[1].imm & 0x1F 88 | if insn.id == X86_INS_ROL: 89 | pt = ((pt & mask) << n) | ( 90 | (pt & mask) >> ((8 * size) - n)) 91 | elif insn.id == X86_INS_ROR: 92 | pt = ((pt & mask) >> n) | ( 93 | (pt & mask) << ((8 * size) - n)) 94 | elif instr_match(insn, X86_INS_XOR, [X86_OP_REG, X86_OP_REG], [reg, reg_key_op]): 95 | pt ^= self.key 96 | elif instr_match(insn, X86_INS_ADD, [X86_OP_REG, X86_OP_REG]): 97 | # add ip, rebase 98 | pt += self.config.rebase 99 | elif instr_match(insn, X86_INS_LEA, [X86_OP_REG, X86_OP_MEM], [reg, {'base': reg, 'index': X86_REG_INVALID, 'scale': 1}]): 100 | pt += insn.operands[1].mem.disp 101 | elif instr_match(insn, X86_INS_LEA, [X86_OP_REG, X86_OP_MEM], [reg, {'base': reg, 'disp': 0, 'scale': 1}]): 102 | # fix lea ip, [ip+ecx] 103 | pt += self.config.rebase 104 | else: 105 | print(decoder) 106 | raise NotImplementedError(insn) 107 | pt &= mask 108 | elif bridge.is64bit() and size == 4: 109 | if instr_match(insn, [X86_INS_MOV, X86_INS_MOVABS], [X86_OP_REG, X86_OP_IMM]): 110 | cached_regs[insn.operands[0].reg] = insn.operands[1].imm 111 | reg_64 = extend_reg(reg) 112 | if reg_64 in regs_write: 113 | if (instr_match(insn, X86_INS_LEA, [X86_OP_REG, X86_OP_MEM], [self.config.reg_ip, {'base': self.config.reg_ip, 'disp': 0, 'scale': 1}]) or 114 | instr_match(insn, X86_INS_LEA, [X86_OP_REG, X86_OP_MEM], [self.config.reg_ip, {'index': self.config.reg_ip, 'disp': 0, 'scale': 1}]) or 115 | instr_match(insn, X86_INS_ADD, [X86_OP_REG, X86_OP_REG], [self.config.reg_ip])): 116 | if insn.id == X86_INS_ADD: 117 | src = insn.operands[1].reg 118 | else: 119 | mem = insn.operands[1].mem 120 | src = mem.index if mem.base == self.config.reg_ip else mem.base 121 | pt += cached_regs.get(src, self.config.rebase) 122 | else: 123 | print('warning decode_emu 64', insn) 124 | pt &= get_mask(64) 125 | # update key -> xor reg_key_op, reg 126 | if instr_match(insn, X86_INS_XOR, [X86_OP_REG, X86_OP_REG], [reg_key_op, reg]): 127 | self.key ^= pt & mask 128 | if instr_match(insn, X86_INS_XOR, [X86_OP_MEM, X86_OP_REG], [None, reg]): 129 | self.key ^= pt & mask 130 | return pt 131 | 132 | def fetch(self, size) -> int: 133 | i = bridge.read(self.ip, size, self.config.dir) 134 | self.ip += self.config.dir*size 135 | return i 136 | -------------------------------------------------------------------------------- /novmpy/vm_const.py: -------------------------------------------------------------------------------- 1 | VM_INS_INVALID = 0 2 | VM_INS_UNKNOWN = 1 3 | VM_INS_NOP = 2 4 | VM_INS_JMP = 3 5 | 6 | VM_INS_PUSH_REG = 4 7 | VM_INS_POP_REG = 5 8 | 9 | VM_INS_PUSH_IMM = 6 10 | 11 | VM_INS_PUSH_SP = 7 12 | VM_INS_POP_SP = 8 13 | 14 | VM_INS_ADD = 9 15 | VM_INS_NAND = 10 16 | VM_INS_NOR = 11 17 | 18 | VM_INS_STR = 12 19 | VM_INS_LDR = 13 20 | VM_INS_CRC = 14 21 | VM_INS_CALL = 15 22 | VM_INS_EXIT = 16 23 | 24 | VM_INS_RCL = 18 25 | VM_INS_RCR = 19 26 | VM_INS_ROL = 20 27 | VM_INS_ROR = 21 28 | VM_INS_SAL = 22 29 | VM_INS_SAR = 23 30 | VM_INS_SHL = 24 31 | VM_INS_SHR = 25 32 | 33 | VM_INS_SHLD = 26 34 | VM_INS_SHRD = 27 35 | 36 | VM_INS_MUL = 28 37 | VM_INS_IMUL = 29 38 | 39 | VM_INS_DIV = 30 40 | VM_INS_IDIV = 31 41 | 42 | 43 | VM_INS_RDTSC = 32 44 | VM_INS_CPUID = 33 45 | 46 | VM_INS_PUSH_EFLAGS = 34 47 | VM_INS_POP_EFLAGS = 35 48 | 49 | VM_INS_PUSH_CRX = 36 50 | VM_INS_POP_CRX = 37 51 | 52 | VM_INS_LOCK_XCHG = 38 -------------------------------------------------------------------------------- /novmpy/vm_lifter.py: -------------------------------------------------------------------------------- 1 | from capstone import * 2 | from capstone.x86 import * 3 | 4 | from novmpy import handler 5 | from novmpy.bridge import * 6 | from novmpy.match_helper import * 7 | from novmpy.x86_deobf import * 8 | from novmpy.vm_const import * 9 | from novmpy.vm import * 10 | from pyvtil import * 11 | 12 | # ref: https://github.com/can1357/NoVmp/blob/master/NoVmp/vmprotect/vtil_lifter.cpp 13 | 14 | 15 | def search_vmstubs(): 16 | """ 17 | jmp_call stub 18 | stub: 19 | push imm4 20 | call vminit 21 | """ 22 | stubs = [] 23 | for seg in bridge.get_segs(): 24 | if not seg.is_executable: 25 | continue 26 | for i, b in enumerate(bridge.get_bytes(seg.vaddr, seg.memsize)): 27 | addr = seg.vaddr + i 28 | if b == 0xE8 or b == 0xE9: 29 | dst = addr+bridge.read(addr+1, 4)+5 30 | if bridge.is_readable(dst, 1) and not (dst >= seg.min_addr and dst < seg.max_addr): 31 | if bridge.read(dst, 1) == 0x68 and bridge.read(dst+5, 1) == 0xE8: 32 | print(f'Discovered vmenter at 0x{addr:X}...') 33 | stubs.append((dst-bridge.get_base(), b == 0xE9)) 34 | return stubs 35 | 36 | 37 | def fix_constant_pool(block: vtil.basic_block, rel_base=0): 38 | vtil.optimizer.stack_pinning_pass(block) 39 | vtil.optimizer.istack_ref_substitution_pass(block) 40 | 41 | reloc_base = vtil.symbolic.pointer(vtil.symbolic.variable( 42 | block.begin(), vtil.REG_SP).to_expression()) 43 | 44 | def exp_eval(uid): 45 | var = uid.get_variable() 46 | if var.is_register() and var.reg().is_image_base(): 47 | return 0 48 | if var.is_memory() and ((var.mem().base - reloc_base) == 0): 49 | return rel_base & get_mask(bridge.size*8) 50 | return None 51 | 52 | tracer = vtil.cached_tracer() 53 | 54 | it = block.begin() 55 | while it != block.end(): 56 | ins = it.get() 57 | if ins.base != vtil.ins.ldd: 58 | it = it.next() 59 | continue 60 | base, off = ins.memory_location() 61 | if base.is_stack_pointer(): 62 | it = it.next() 63 | continue 64 | 65 | exp = tracer(vtil.symbolic.variable(it, base)) 66 | res = exp.evaluate(exp_eval) 67 | 68 | if res.is_known(): 69 | rva = (res.get_uint64() + off) & get_mask(bridge.size*8) 70 | if bridge.is_readable(rva, ins.access_size()//8) and\ 71 | not bridge.is_writeable(rva, ins.access_size()//8): 72 | value = bridge.read(rva, ins.access_size()//8) 73 | print(f'fix_constant_pool [{rva:016X}] = {value:016X}') 74 | ins.base = vtil.ins.mov 75 | ins.operands = [ins.operands[0], 76 | vtil.make_uint(value, ins.access_size())] 77 | it = it.next() 78 | 79 | 80 | class VMLifter: 81 | def __init__(self) -> None: 82 | self.rtn = vtil.routine() 83 | self.rel_base = 0 # default_base - real_base 84 | 85 | def is_valid_vmentry(self, vmentry): 86 | return handler.vmentry_parse(vmentry) != None 87 | 88 | def lift_il(self, block: vtil.basic_block, state: VMState): 89 | if state.ip == 0: 90 | vmp_entry = state.current_handler 91 | parse_result = handler.vmentry_parse(vmp_entry) 92 | assert parse_result 93 | 94 | tmp_state = parse_result.vmstate 95 | # stub.extend(vminit.save_regs) 96 | 97 | print('vmstate.ip: {:x}'.format(tmp_state.ip)) 98 | # 0x0048AFF0| vm_init 0x4514a9 99 | vip = tmp_state.ip 100 | vip += 0 if tmp_state.config.dir >= 0 else -1 101 | tmp_state.current_handler = parse_result.vminit.get_next(tmp_state) 102 | if block is None: 103 | block, _ = self.rtn.create_block(vmp_entry) 104 | else: 105 | new_block = block.fork(vmp_entry) 106 | if new_block is None: 107 | return self.rtn.get_block(vmp_entry) 108 | block = new_block 109 | # push imm 110 | block.push(vtil.make_int(parse_result.vm_imm)) 111 | # call vm_init 112 | block.push(parse_result.vm_imm2) 113 | for i, j in parse_result.vminit.pushs: 114 | if i == CS_OP_IMM: 115 | self.rel_base = j 116 | treloc = block.tmp(vtil.arch.bit_count) 117 | block.mov(treloc, vtil.REG_IMGBASE) # 0x140000000 118 | block.sub(treloc, vtil.make_int(-self.rel_base)) 119 | block.push(treloc) 120 | elif i == CS_OP_REG: 121 | if j == X86_REG_EFLAGS: 122 | block.pushf() 123 | else: 124 | block.push(vtil.x86_reg(j)) 125 | # 得切分成两个基本块,因为存在从其它基本块直接跳入vip的情况。 126 | block.jmp(vip) 127 | new_block = block.fork(vip) 128 | if new_block is None: 129 | return self.rtn.get_block(vip) 130 | block = new_block 131 | else: 132 | if block is None: 133 | return None 134 | tmp_state = state 135 | # Messages cannot be updated frequently, which can slow down performance. 136 | if not bridge.update_msg(f'lifting {block.entry_vip:08X}'): 137 | raise KeyboardInterrupt('User interrupted') 138 | while True: 139 | h = handler.factory(tmp_state.current_handler, tmp_state.config) 140 | if h is None: 141 | break 142 | i: handler.VMIns = h.get_instr(tmp_state) 143 | 144 | if isinstance(h, handler.VMInvalid): 145 | print(f'invalid handler {hex(h.address)}') 146 | 147 | print(block.sp_offset, i) 148 | block.label_begin(i.address) 149 | h.generator(i, block) 150 | block.label_end() 151 | assert not isinstance(h, handler.VMInvalid) 152 | assert not isinstance(h, handler.VMUnknown) 153 | 154 | if isinstance(h, handler.VMNop): 155 | dst = tmp_state.ip 156 | if tmp_state.config.dir < 0: 157 | dst -= 1 158 | block.jmp(dst) 159 | fix_constant_pool(block, self.rel_base) 160 | tmp_state.current_handler = h.get_next(tmp_state) 161 | self.lift_il(block.fork(dst), tmp_state) 162 | return block 163 | elif isinstance(h, handler.VMJmp): 164 | jmp_dest = block.tmp(vtil.arch.bit_count) 165 | block.pop(jmp_dest) 166 | if h.conn_config.dir < 0: 167 | block.sub(jmp_dest, 1) 168 | block.jmp(jmp_dest) 169 | 170 | fix_constant_pool(block, self.rel_base) 171 | 172 | # block.owner.local_opt_count += vtil.optimizer.apply_all(block, False) 173 | 174 | flag = vtil.optimizer.aux.branch_analysis_flags(pack=True) 175 | tracer = vtil.tracer() 176 | branch_info = vtil.optimizer.aux.analyze_branch( 177 | block, tracer, flag) 178 | 179 | print(f"CC {branch_info.cc}") 180 | print(f"VJMP {branch_info.destinations}") 181 | 182 | def eval_base_remove(uid): 183 | var = uid.get_variable() 184 | if var.is_register() and var.reg().is_image_base(): 185 | return 0 186 | return None 187 | 188 | targets = [] 189 | for branch in branch_info.destinations: 190 | if not branch.is_constant(): 191 | branch = tracer.rtrace_pexp(branch) 192 | if not branch.is_constant(): 193 | res = branch.evaluate(eval_base_remove) 194 | if res.is_known(): 195 | targets.append(res.get_uint64()) 196 | continue 197 | if not branch.is_constant(): 198 | continue 199 | print(f'Exploring branch => {branch}') 200 | targets.append(branch.get_uint64()) 201 | for target in targets: 202 | ip = target 203 | if h.conn_config.dir < 0: 204 | ip += 1 205 | if ip > 0x1000 and bridge.is_readable(ip, 1, h.conn_config.dir): 206 | tmp_state2 = copy.deepcopy(tmp_state) 207 | tmp_state2.config = h.conn_config 208 | tmp_state2.ip = ip 209 | tmp_state2.key = (tmp_state2.ip-tmp_state2.config.rebase) & get_mask(bridge.size*8) 210 | next_h = h.get_next(tmp_state2) 211 | if bridge.is_readable(next_h, 4): 212 | tmp_state2.current_handler = next_h 213 | self.lift_il(block.fork(target), tmp_state2) 214 | break 215 | elif isinstance(h, handler.VMExit): 216 | for r in h.pops: 217 | if r == X86_REG_EFLAGS: 218 | block.popf() 219 | else: 220 | block.pop(vtil.x86_reg(r)) 221 | jmp_dest = block.tmp(vtil.arch.bit_count) 222 | block.pop(jmp_dest) 223 | block.vexit(jmp_dest) 224 | 225 | fix_constant_pool(block, self.rel_base) 226 | 227 | jmp_dest = block.back().operands[0] 228 | 229 | tracer = vtil.tracer() 230 | stack_0 = vtil.symbolic.variable( 231 | block.owner.entry_point.begin(), vtil.REG_SP).to_expression() 232 | stack_1 = tracer.rtrace_p(vtil.symbolic.variable( 233 | block.end().prev(), vtil.REG_SP)) + vtil.symbolic.expression(block.sp_offset, vtil.arch.bit_count) 234 | offset = stack_1 - stack_0 235 | print(f'sp offset => {offset}') 236 | if offset.is_constant() and offset.get_int() < 0: 237 | mem = vtil.symbolic.variable.memory_t(tracer(vtil.symbolic.variable( 238 | block.end().prev(), vtil.REG_SP)) + vtil.symbolic.expression(block.sp_offset, vtil.arch.bit_count), vtil.arch.bit_count) 239 | var = vtil.symbolic.variable(block.end().prev(), mem) 240 | base_exp = vtil.symbolic.variable( 241 | vtil.REG_IMGBASE).to_expression() 242 | continue_from = tracer.rtrace_p(var) - base_exp 243 | print(f'continue => {continue_from}') 244 | if continue_from.is_constant() and self.is_valid_vmentry(continue_from.get_uint()): 245 | # block.pop_back() 246 | # block.vxcall(jmp_dest) 247 | block.wback().base = vtil.ins.vxcall 248 | block.wback().vip = tmp_state.ip 249 | block.shift_sp(vtil.arch.size, False, block.end()) 250 | 251 | tmp_state = VMState() 252 | tmp_state.current_handler = continue_from.get_uint() 253 | self.lift_il(block, tmp_state) 254 | break 255 | 256 | if jmp_dest.is_immediate(): 257 | exit_destination = vtil.symbolic.expression( 258 | jmp_dest.imm().uval, jmp_dest.bit_count) 259 | else: 260 | var = vtil.symbolic.variable( 261 | block.end().prev(), jmp_dest.reg()) 262 | exit_destination = tracer.rtrace_p( 263 | var) - vtil.symbolic.variable(vtil.REG_IMGBASE).to_expression() 264 | 265 | print(f'exit => {exit_destination}') 266 | if exit_destination.is_constant() and self.is_valid_vmentry(exit_destination.get_uint()): 267 | block.pop_back() 268 | parse_result = handler.vmentry_parse( 269 | exit_destination.get_uint()) 270 | for _ins in parse_result.vm_unimpl_insn: 271 | if _ins.id in [X86_INS_PUSHFD, X86_INS_PUSHFQ]: 272 | block.pushf() 273 | continue 274 | elif _ins.id in [X86_INS_POPFD, X86_INS_POPFQ]: 275 | block.popf() 276 | continue 277 | reads, writes = _ins.regs_access() 278 | for reg_read in reads: 279 | op = vtil.x86_reg(reg_read) 280 | if reg_read in [X86_REG_ESP, X86_REG_RSP]: 281 | op = vtil.REG_SP 282 | if reg_read == X86_REG_EFLAGS: 283 | op = vtil.REG_FLAGS 284 | block.vpinr(op) 285 | block.label_begin(_ins.address) 286 | for b in _ins.bytes: 287 | block.vemit(vtil.make_uint(b, 8)) 288 | block.label_end() 289 | for reg_write in writes: 290 | op = vtil.x86_reg(reg_write) 291 | assert reg_write not in [X86_REG_ESP, X86_REG_RSP] 292 | if reg_write == X86_REG_EFLAGS: 293 | op = vtil.REG_FLAGS 294 | block.vpinw(op) 295 | block.jmp(vtil.invalid_vip) 296 | 297 | state = VMState(current_handler=parse_result.stub_addr) 298 | block_next = self.lift_il(block, state) 299 | 300 | # block.pop_back() 301 | # block.jmp(block_next.entry_vip) 302 | block.wback().operands = [vtil.make_uint( 303 | block_next.entry_vip, vtil.arch.bit_count)] 304 | break 305 | elif isinstance(h, handler.VMCrc): 306 | # TODO: 307 | # create new block 308 | # translate crc 309 | # jump back 310 | # assert(False) 311 | pass 312 | tmp_state.current_handler = h.get_next(tmp_state) 313 | return block 314 | 315 | # for k, h in handler.vm_handlers.items(): 316 | # if isinstance(h, handler.VMUnknown): 317 | # print('-----------------') 318 | # print(h.config) 319 | # for i in h.body: 320 | # print(i) 321 | -------------------------------------------------------------------------------- /novmpy/x86_deobf.py: -------------------------------------------------------------------------------- 1 | from capstone import * 2 | from capstone.x86 import * 3 | 4 | import copy 5 | from novmpy.bridge import * 6 | 7 | 8 | def get_mask(bits): 9 | if bits < 0: 10 | return 0 11 | return 2**bits-1 12 | 13 | 14 | def shit_disasm(ea, max_insn_count=-1, term_call_imm=False): 15 | insn_count = 0 16 | bbs = [] 17 | bb = [] 18 | addr = ea 19 | branches = [addr] 20 | walk = {} 21 | while True: 22 | if addr in walk: 23 | break 24 | walk[addr] = True 25 | insn: CsInsn = bridge.disasm_one(addr) 26 | if insn is None: 27 | break 28 | if insn.id == X86_INS_INVALID: 29 | break 30 | if max_insn_count > 0 and insn_count >= max_insn_count: 31 | break 32 | # op1: X86Op = insn.op_find(X86_OP_IMM,1) 33 | if insn.group(X86_GRP_JUMP): 34 | op1: X86Op = insn.operands[0] 35 | if insn.id != X86_INS_JMP and op1.type == X86_OP_IMM: 36 | branches.append(op1.imm & get_mask(bridge.size*8)) 37 | branches.append(addr+insn.size) 38 | # jmp imm 39 | if insn.id == X86_INS_JMP: 40 | if op1.type == X86_OP_IMM: 41 | addr = op1.imm & get_mask(bridge.size*8) 42 | continue 43 | # jmp reg | jmp [mem] 44 | else: 45 | bb.append(insn) 46 | insn_count += 1 47 | break 48 | bb.append(insn) 49 | insn_count += 1 50 | if insn.group(X86_GRP_RET): 51 | break 52 | if term_call_imm: 53 | if insn.id == X86_INS_CALL and insn.operands[0].type == X86_OP_IMM: 54 | break 55 | addr += insn.size 56 | cur = 0 57 | cbb = [] 58 | for i in bb: 59 | if i.address in branches and cur != i.address: 60 | if cur != 0: 61 | bbs.append(cbb) 62 | cbb = [] 63 | cur = i.address 64 | cbb.append(i) 65 | if cbb: 66 | bbs.append(cbb) 67 | return bbs 68 | 69 | 70 | map_reg = [ 71 | [X86_REG_RAX, X86_REG_EAX, X86_REG_AX, X86_REG_AH, X86_REG_AL], 72 | [X86_REG_RCX, X86_REG_ECX, X86_REG_CX, X86_REG_CH, X86_REG_CL], 73 | [X86_REG_RDX, X86_REG_EDX, X86_REG_DX, X86_REG_DH, X86_REG_DL], 74 | [X86_REG_RBX, X86_REG_EBX, X86_REG_BX, X86_REG_BH, X86_REG_BL], 75 | 76 | [X86_REG_RSI, X86_REG_ESI, X86_REG_SI, X86_REG_INVALID, X86_REG_SIL], 77 | [X86_REG_RDI, X86_REG_EDI, X86_REG_DI, X86_REG_INVALID, X86_REG_DIL], 78 | [X86_REG_RBP, X86_REG_EBP, X86_REG_BP, X86_REG_INVALID, X86_REG_BPL], 79 | [X86_REG_RSP, X86_REG_ESP, X86_REG_SP, X86_REG_INVALID, X86_REG_SPL], 80 | 81 | [X86_REG_R8, X86_REG_R8D, X86_REG_R8W, X86_REG_INVALID, X86_REG_R8B], 82 | [X86_REG_R9, X86_REG_R9D, X86_REG_R9W, X86_REG_INVALID, X86_REG_R9B], 83 | [X86_REG_R10, X86_REG_R10D, X86_REG_R10W, X86_REG_INVALID, X86_REG_R10B], 84 | [X86_REG_R11, X86_REG_R11D, X86_REG_R11W, X86_REG_INVALID, X86_REG_R11B], 85 | [X86_REG_R12, X86_REG_R12D, X86_REG_R12W, X86_REG_INVALID, X86_REG_R12B], 86 | [X86_REG_R13, X86_REG_R13D, X86_REG_R13W, X86_REG_INVALID, X86_REG_R13B], 87 | [X86_REG_R14, X86_REG_R14D, X86_REG_R14W, X86_REG_INVALID, X86_REG_R14B], 88 | [X86_REG_R15, X86_REG_R15D, X86_REG_R15W, X86_REG_INVALID, X86_REG_R15B], 89 | [X86_REG_EFLAGS, X86_REG_INVALID, X86_REG_INVALID, 90 | X86_REG_INVALID, X86_REG_INVALID] 91 | ] 92 | 93 | MASK_EFLAGS_SHIFT = { 94 | 'CF': (1 << 0, X86_EFLAGS_MODIFY_CF | X86_EFLAGS_SET_CF | X86_EFLAGS_RESET_CF | X86_EFLAGS_UNDEFINED_CF, X86_EFLAGS_TEST_CF), 95 | 'PF': (1 << 2, X86_EFLAGS_MODIFY_PF | X86_EFLAGS_SET_PF | X86_EFLAGS_RESET_PF | X86_EFLAGS_UNDEFINED_PF, X86_EFLAGS_TEST_PF), 96 | 'AF': (1 << 4, X86_EFLAGS_MODIFY_AF | X86_EFLAGS_SET_AF | X86_EFLAGS_RESET_AF | X86_EFLAGS_UNDEFINED_AF, X86_EFLAGS_TEST_AF), 97 | 'ZF': (1 << 6, X86_EFLAGS_MODIFY_ZF | X86_EFLAGS_SET_ZF | X86_EFLAGS_RESET_ZF | X86_EFLAGS_UNDEFINED_ZF, X86_EFLAGS_TEST_ZF), 98 | 'SF': (1 << 7, X86_EFLAGS_MODIFY_SF | X86_EFLAGS_SET_SF | X86_EFLAGS_RESET_SF | X86_EFLAGS_UNDEFINED_SF, X86_EFLAGS_TEST_SF), 99 | 'OF': (1 << 11, X86_EFLAGS_MODIFY_OF | X86_EFLAGS_SET_OF | X86_EFLAGS_RESET_OF | X86_EFLAGS_UNDEFINED_OF, X86_EFLAGS_TEST_OF), 100 | 'DF': (1 << 10, X86_EFLAGS_MODIFY_DF | X86_EFLAGS_SET_DF | X86_EFLAGS_RESET_DF, X86_EFLAGS_TEST_DF), 101 | } 102 | 103 | dict_regs = { 104 | X86_REG_INVALID: [X86_REG_INVALID, 0, 0], 105 | X86_REG_RAX: [X86_REG_RAX, 0, 64], 106 | X86_REG_EAX: [X86_REG_RAX, 0, 32], 107 | X86_REG_AX: [X86_REG_RAX, 0, 16], 108 | X86_REG_AH: [X86_REG_RAX, 8, 8], 109 | X86_REG_AL: [X86_REG_RAX, 0, 8], 110 | X86_REG_RCX: [X86_REG_RCX, 0, 64], 111 | X86_REG_ECX: [X86_REG_RCX, 0, 32], 112 | X86_REG_CX: [X86_REG_RCX, 0, 16], 113 | X86_REG_CH: [X86_REG_RCX, 8, 8], 114 | X86_REG_CL: [X86_REG_RCX, 0, 8], 115 | X86_REG_RDX: [X86_REG_RDX, 0, 64], 116 | X86_REG_EDX: [X86_REG_RDX, 0, 32], 117 | X86_REG_DX: [X86_REG_RDX, 0, 16], 118 | X86_REG_DH: [X86_REG_RDX, 8, 8], 119 | X86_REG_DL: [X86_REG_RDX, 0, 8], 120 | X86_REG_RBX: [X86_REG_RBX, 0, 64], 121 | X86_REG_EBX: [X86_REG_RBX, 0, 32], 122 | X86_REG_BX: [X86_REG_RBX, 0, 16], 123 | X86_REG_BH: [X86_REG_RBX, 8, 8], 124 | X86_REG_BL: [X86_REG_RBX, 0, 8], 125 | X86_REG_RSI: [X86_REG_RSI, 0, 64], 126 | X86_REG_ESI: [X86_REG_RSI, 0, 32], 127 | X86_REG_SI: [X86_REG_RSI, 0, 16], 128 | X86_REG_SIL: [X86_REG_RSI, 0, 8], 129 | X86_REG_RDI: [X86_REG_RDI, 0, 64], 130 | X86_REG_EDI: [X86_REG_RDI, 0, 32], 131 | X86_REG_DI: [X86_REG_RDI, 0, 16], 132 | X86_REG_DIL: [X86_REG_RDI, 0, 8], 133 | X86_REG_RBP: [X86_REG_RBP, 0, 64], 134 | X86_REG_EBP: [X86_REG_RBP, 0, 32], 135 | X86_REG_BP: [X86_REG_RBP, 0, 16], 136 | X86_REG_BPL: [X86_REG_RBP, 0, 8], 137 | X86_REG_RSP: [X86_REG_RSP, 0, 64], 138 | X86_REG_ESP: [X86_REG_RSP, 0, 32], 139 | X86_REG_SP: [X86_REG_RSP, 0, 16], 140 | X86_REG_SPL: [X86_REG_RSP, 0, 8], 141 | X86_REG_R8: [X86_REG_R8, 0, 64], 142 | X86_REG_R8D: [X86_REG_R8, 0, 32], 143 | X86_REG_R8W: [X86_REG_R8, 0, 16], 144 | X86_REG_R8B: [X86_REG_R8, 0, 8], 145 | X86_REG_R9: [X86_REG_R9, 0, 64], 146 | X86_REG_R9D: [X86_REG_R9, 0, 32], 147 | X86_REG_R9W: [X86_REG_R9, 0, 16], 148 | X86_REG_R9B: [X86_REG_R9, 0, 8], 149 | X86_REG_R10: [X86_REG_R10, 0, 64], 150 | X86_REG_R10D: [X86_REG_R10, 0, 32], 151 | X86_REG_R10W: [X86_REG_R10, 0, 16], 152 | X86_REG_R10B: [X86_REG_R10, 0, 8], 153 | X86_REG_R11: [X86_REG_R11, 0, 64], 154 | X86_REG_R11D: [X86_REG_R11, 0, 32], 155 | X86_REG_R11W: [X86_REG_R11, 0, 16], 156 | X86_REG_R11B: [X86_REG_R11, 0, 8], 157 | X86_REG_R12: [X86_REG_R12, 0, 64], 158 | X86_REG_R12D: [X86_REG_R12, 0, 32], 159 | X86_REG_R12W: [X86_REG_R12, 0, 16], 160 | X86_REG_R12B: [X86_REG_R12, 0, 8], 161 | X86_REG_R13: [X86_REG_R13, 0, 64], 162 | X86_REG_R13D: [X86_REG_R13, 0, 32], 163 | X86_REG_R13W: [X86_REG_R13, 0, 16], 164 | X86_REG_R13B: [X86_REG_R13, 0, 8], 165 | X86_REG_R14: [X86_REG_R14, 0, 64], 166 | X86_REG_R14D: [X86_REG_R14, 0, 32], 167 | X86_REG_R14W: [X86_REG_R14, 0, 16], 168 | X86_REG_R14B: [X86_REG_R14, 0, 8], 169 | X86_REG_R15: [X86_REG_R15, 0, 64], 170 | X86_REG_R15D: [X86_REG_R15, 0, 32], 171 | X86_REG_R15W: [X86_REG_R15, 0, 16], 172 | X86_REG_R15B: [X86_REG_R15, 0, 8], 173 | X86_REG_EFLAGS: [X86_REG_EFLAGS, 0, 64], 174 | } 175 | 176 | 177 | class Taint64: 178 | def __init__(self): 179 | self._taint = {} 180 | 181 | def set_(self, reg): 182 | return self.or_(reg, get_mask(64)) 183 | 184 | def cls_(self, reg): 185 | return self.and_(reg, 0) 186 | 187 | def update(self, insn: CsInsn): 188 | ignore_reg = False 189 | result = True 190 | 191 | if insn.id == X86_INS_CWD: 192 | self.or_(X86_REG_EDX, get_mask(16)) 193 | return result 194 | elif insn.id == X86_INS_CDQ: 195 | self.or_(X86_REG_EDX, get_mask(32)) 196 | return result 197 | elif insn.id == X86_INS_CQO: 198 | self.or_(X86_REG_EDX, get_mask(64)) 199 | return result 200 | 201 | if insn.id == X86_INS_MOVZX: 202 | pass 203 | 204 | if insn.id == X86_INS_TEST: 205 | ignore_reg = True 206 | if not ignore_reg: 207 | regs_read, regs_write = insn.regs_access() 208 | for w in regs_write: 209 | if w == X86_REG_EFLAGS: 210 | continue 211 | if bridge.is64bit(): 212 | base, off, bits = self.get_info(w) 213 | if off == 0 and bits == 32: 214 | w = base 215 | if not self.set_(w): 216 | result = False 217 | if insn.group(X86_GRP_FPU): 218 | result = False 219 | else: 220 | eflags = insn.eflags 221 | if eflags: 222 | for k, v in MASK_EFLAGS_SHIFT.items(): 223 | shift, set_flags, test_flags = v 224 | if eflags & set_flags: 225 | self.or_(X86_REG_EFLAGS, shift) 226 | return result 227 | 228 | def get_info(self, reg): 229 | if reg in dict_regs: 230 | return dict_regs[reg] 231 | return dict_regs[X86_REG_INVALID] 232 | 233 | def or_(self, reg, value): 234 | base, off, bits = self.get_info(reg) 235 | if base == X86_REG_INVALID: 236 | return False 237 | self._taint[base] = self._taint.get( 238 | base, 0) | (value & get_mask(bits)) << off 239 | return True 240 | 241 | def and_(self, reg, value): 242 | base, off, bits = self.get_info(reg) 243 | if base == X86_REG_INVALID: 244 | return False 245 | self._taint[base] = self._taint.get(base, 0) & ( 246 | ((value | (~get_mask(bits))) << off) | get_mask(off)) 247 | return True 248 | 249 | def fetch(self, reg): 250 | base, off, bits = self.get_info(reg) 251 | if base == X86_REG_INVALID: 252 | return 0 253 | return (self._taint.get(base, 0) >> off) & get_mask(bits) 254 | 255 | def check_read(self, insn: CsInsn): 256 | regs_read, regs_write = insn.regs_access() 257 | for rr in regs_read: 258 | if rr == X86_REG_EFLAGS: 259 | continue 260 | if self.fetch(rr): 261 | return False 262 | if not insn.group(X86_GRP_FPU): 263 | eflags = insn.eflags 264 | if eflags: 265 | value = self.fetch(X86_REG_EFLAGS) 266 | for k, v in MASK_EFLAGS_SHIFT.items(): 267 | shift, set_flags, test_flags = v 268 | if (eflags & test_flags) and (value & shift): 269 | return False 270 | return True 271 | 272 | def empty(self): 273 | flag = 0 274 | m = get_mask(bridge.size*8) 275 | for k, v in self._taint.items(): 276 | flag |= v & m 277 | return flag == 0 278 | 279 | def check(self, rf, reg): 280 | v = self.fetch(reg) 281 | if v != 0: 282 | self.and_(reg, ~rf.fetch(reg)) 283 | 284 | def check_overwrite(self, rf): 285 | regs = [ 286 | X86_REG_RAX, X86_REG_RBX, X86_REG_RCX, X86_REG_RDX, 287 | X86_REG_RSI, X86_REG_RDI, X86_REG_RSP, X86_REG_RBP, 288 | X86_REG_R8, X86_REG_R9, X86_REG_R10, X86_REG_R11, 289 | X86_REG_R12, X86_REG_R13, X86_REG_R14, X86_REG_R15, 290 | X86_REG_EFLAGS 291 | ] 292 | for r in regs: 293 | self.check(rf, r) 294 | return self.empty() 295 | 296 | 297 | def x86_deobfusctor(insn_array, dump=False): 298 | # very slow! 299 | if len(insn_array) < 2: 300 | return insn_array 301 | new_array = [] 302 | useless = [False]*len(insn_array) 303 | taints = [Taint64() for i in insn_array] 304 | for i in reversed(range(0, len(insn_array))): 305 | insn: CsInsn = insn_array[i] 306 | if insn.id == X86_INS_NOP: 307 | useless[i] = True 308 | reg_read, reg_write = insn.regs_access() 309 | if len(reg_write) == 0: 310 | continue 311 | if insn.id == X86_INS_XCHG or insn.id == X86_INS_MOV or insn.group(X86_GRP_CMOV): 312 | op1, op2 = insn.operands 313 | if op1.type == op2.type and op1.size == op2.size and op1.reg == op2.reg: 314 | if (not bridge.is64bit()) or op1.size == bridge.size or op1.size < 4: 315 | useless[i] = True 316 | if (not taints[i].update(insn)) or taints[i].empty(): 317 | continue 318 | # skip call or memory access instruction 319 | if insn.id == X86_INS_CALL or insn.op_count(CS_OP_MEM) > 0: 320 | continue 321 | taint = copy.deepcopy(taints[i]) 322 | for j in range(i+1, len(insn_array)): 323 | if useless[j]: 324 | continue 325 | if not taint.check_read(insn_array[j]): 326 | break 327 | reg_read, reg_write = insn_array[j].regs_access() 328 | if reg_write and taint.check_overwrite(taints[j]): 329 | useless[i] = True 330 | break 331 | 332 | for i, e in enumerate(insn_array): 333 | insn: CsInsn = insn_array[i] 334 | if not useless[i]: 335 | if dump: 336 | print(f'0x{insn.address:X} {insn.mnemonic} {insn.op_str}') 337 | new_array.append(e) 338 | else: 339 | if dump: 340 | print(f'; 0x{insn.address:X} {insn.mnemonic} {insn.op_str}') 341 | return new_array 342 | 343 | 344 | def x86_simple_decode(ea, max_insn_count=-1, term_call_imm=False): 345 | s = [] 346 | bbs = shit_disasm(ea, max_insn_count, term_call_imm) 347 | for bb in bbs: 348 | s += x86_deobfusctor(bb) 349 | return s 350 | 351 | 352 | def get_reg8(reg): 353 | if reg == X86_REG_INVALID: 354 | return X86_REG_INVALID 355 | for elems in map_reg: 356 | if reg in elems: 357 | return elems[4] 358 | return X86_REG_INVALID 359 | 360 | 361 | def get_reg16(reg): 362 | if reg == X86_REG_INVALID: 363 | return X86_REG_INVALID 364 | for elems in map_reg: 365 | if reg in elems: 366 | return elems[2] 367 | return X86_REG_INVALID 368 | 369 | 370 | def get_reg32(reg): 371 | if reg == X86_REG_INVALID: 372 | return X86_REG_INVALID 373 | for elems in map_reg: 374 | if reg in elems: 375 | return elems[1] 376 | return X86_REG_INVALID 377 | 378 | 379 | def extend_reg(reg): 380 | if bridge.is64bit(): 381 | return get_reg64(reg) 382 | return get_reg32(reg) 383 | 384 | 385 | def get_reg64(reg): 386 | if reg == X86_REG_INVALID: 387 | return X86_REG_INVALID 388 | for elems in map_reg: 389 | if reg in elems: 390 | return elems[0] 391 | return X86_REG_INVALID 392 | 393 | 394 | def dump_insns(insns): 395 | for i in insns: 396 | print(i) 397 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | This project is just a POC and only works with VMProtect 3.x (not including the demo version). 3 | 4 | NOT CLEAN CODE. 5 | 6 | Although x86 is now available, unfortunately x86 cannot coexist with x64. 7 | 8 | 9 | ## 1. Install pyvtil first 10 | 11 | ```bash 12 | git clone -b dev-1 https://github.com/wallds/VTIL-Python.git --recursive 13 | cd VTIL-Python 14 | py setup.py install --old-and-unmanageable 15 | ``` 16 | **To use x86 you need to replace `dev-1` with `dev-x86`.** 17 | 18 | **If you are using Visual Studio 2022 build tools, then you need to update `extras` in setup.py from `Visual Studio 16 2019` to `Visual Studio 17 2022`** 19 | ## 2. Install plugin 20 | Copy novmpy&novmpy.py to IDA plugin directory. 21 | 22 | ## 3. Usage 23 | ![1](./imgs/1.gif) 24 | 25 | ## Support list 26 | Version | x86 | amd64 | arm64 27 | :------------ | :-------------| :-------------| :------------- 28 | VMProtect 3.4~3.6 | :heavy_check_mark: | :heavy_check_mark: | 29 | 30 | # Reference 31 | 32 | https://github.com/can1357/NoVmp 33 | 34 | https://github.com/0xnobody/vmpattack 35 | 36 | https://github.com/vtil-project/VTIL-BinaryNinja 37 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cle 2 | capstone 3 | unicorn -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | from novmpy.bridge import * 2 | from novmpy.vm_lifter import VMLifter 3 | from novmpy.vm_lifter import search_vmstubs 4 | from novmpy.vm import VMState 5 | from pyvtil import * 6 | 7 | 8 | def main(): 9 | for jmp_rva, is_mid_routine in search_vmstubs(): 10 | print(f'Lifting virtual-machine at 0x{jmp_rva:X}...') 11 | lifter = VMLifter() 12 | state = VMState(current_handler=bridge.get_base()+jmp_rva) 13 | lifter.lift_il(None, state) 14 | 15 | print(f'Saving premature') 16 | lifter.rtn.save('./test.premature.vtil') 17 | vtil.optimizer.apply_all_profiled(lifter.rtn) 18 | print(f'Saving optimized') 19 | lifter.rtn.save('./test.optimized.vtil') 20 | vtil.debug.dump(lifter.rtn) 21 | 22 | 23 | if __name__ == '__main__': 24 | main() 25 | --------------------------------------------------------------------------------