use: git clone --recursive https://githacks.org/vmp2/vmprofiler.git

### VMProfiler - Library To Profile VMProtect 2 Virtual Machines vmprofiler is a c++ library which is used to statically analyze VMProtect 2 polymorphic virtual machines. This project is inherited in vmprofiler-qt, vmprofiler-cli, and vmemu. This is the base project for all other VMProtect 2 projects inside of this group on githacks. # [Doxygen Documentation](https://docs.back.engineering/vmprofiler/index.html) ### Credit & Contributors * [VTIL](https://github.com/vtil-project/VTIL-Core) - Virtual-machine Translation Intermediate Language * [Zydis](https://github.com/zyantific/zydis) - Fast and lightweight x86/x86-64 disassembler library * [irql0](https://github.com/irql0) - helped with the first version of vm handler pattern matching ### Basic Usage - Creating a vm::ctx_t Object The `vm::ctx_t` class is a small container-like class which is simply used to contain all information for a given vm entry. This class contains the following useful information: * all vm handlers for a given vm entry * the linear virtual address of the module base in memory * the image base address * the image size in virtual memory * which way VIP advances (exec_type) * vm entry relative virtual address * vm entry deobfuscated and flattened * calc jmp deobfuscated and flattened All of the above information is generated by executing the `vm::ctx_t::init` member function. Below is a C++ example of how to create a `vm::ctx_t` object. ```cpp const auto module_base = reinterpret_cast< std::uintptr_t >( LoadLibraryExA( parser.get< std::string >( "bin" ).c_str(), NULL, DONT_RESOLVE_DLL_REFERENCES ) ); const auto vm_entry_rva = std::strtoull( parser.get< std::string >( "vmentry" ).c_str(), nullptr, 16 ); const auto image_base = umtils->image_base( parser.get< std::string >( "bin" ).c_str() ); const auto image_size = NT_HEADER( module_base )->OptionalHeader.SizeOfImage; vm::ctx_t vmctx( module_base, image_base, image_size, vm_entry_rva ); if ( !vmctx.init() ) { std::printf( "[!] failed to init vm::ctx_t... make sure all cli arguments are correct!\n" ); return -1; } ``` ### Using vm::ctx_t Object Once you have instantiated `vm::ctx_t` and called `vm::ctx_t::init` with success, you now can directly access the data members of `vm::ctx_t`. Most importantly, `vm::ctx_t::calc_jmp`, `vm::ctx_t::vm_entry`, and `vm::ctx_t::vm_handlers`. An example usage of this data could be dumping the native x86_64 instructions which make up `vm::ctx_t::vm_entry`. Example c++ code for this is displayed below. ```cpp std::puts( "======================== [vm entry] ========================\n" ); vm::util::print( vmctx.vm_entry ); ``` ***Output*** ``` ======================== [vm entry] ======================== > 0x00007FF7911A822C push 0xFFFFFFFF890001FA > 0x00007FF7911A7FC9 push 0x45D3BF1F > 0x00007FF7911A48E4 push r13 > 0x00007FF7911A4690 push rsi > 0x00007FF7911A4E53 push r14 > 0x00007FF7911A74FB push rcx > 0x00007FF7911A607C push rsp > 0x00007FF7911A4926 pushfq > 0x00007FF7911A4DC2 push rbp > 0x00007FF7911A5C8C push r12 > 0x00007FF7911A52AC push r10 > 0x00007FF7911A51A5 push r9 > 0x00007FF7911A5189 push rdx > 0x00007FF7911A7D5F push r8 > 0x00007FF7911A4505 push rdi > 0x00007FF7911A4745 push r11 > 0x00007FF7911A478B push rax > 0x00007FF7911A7A53 push rbx > 0x00007FF7911A500D push r15 > 0x00007FF7911A6030 push [0x00007FF7911A7912] > 0x00007FF7911A593A mov rax, 0x7FF6511A0000 > 0x00007FF7911A5955 mov r13, rax > 0x00007FF7911A595F test dl, al > 0x00007FF7911A5965 push rax > 0x00007FF7911A5969 btr si, bx > 0x00007FF7911A596F mov esi, [rsp+0xA0] > 0x00007FF7911A5979 not esi > 0x00007FF7911A5985 neg esi > 0x00007FF7911A598D ror esi, 0x1A > 0x00007FF7911A599E mov rbp, rsp > 0x00007FF7911A59A8 sub rsp, 0x140 > 0x00007FF7911A59B5 and rsp, 0xFFFFFFFFFFFFFFF0 > 0x00007FF7911A59BE inc ax > 0x00007FF7911A59C1 mov rdi, rsp > 0x00007FF7911A59C7 bsr r12, rax > 0x00007FF7911A59CB lea r12, [0x00007FF7911A6473] > 0x00007FF7911A59DF mov rax, 0x100000000 > 0x00007FF7911A59EC add rsi, rax > 0x00007FF7911A59F3 mov rbx, rsi > 0x00007FF7911A59FA add rsi, [rbp] > 0x00007FF7911A5A03 rcr dl, cl > 0x00007FF7911A5A05 mov al, [rsi] > 0x00007FF7911A5A0A xor al, bl > 0x00007FF7911A5A11 neg al > 0x00007FF7911A5A19 rol al, 0x05 > 0x00007FF7911A5A26 inc al > 0x00007FF7911A5A2F xor bl, al > 0x00007FF7911A5A34 movzx rax, al > 0x00007FF7911A5A41 mov rdx, [r12+rax*8] > 0x00007FF7911A5A49 xor rdx, 0x7F3D2149 > 0x00007FF7911A5507 inc rsi > 0x00007FF7911A7951 add rdx, r13 > 0x00007FF7911A7954 jmp rdx ``` You can also loop through all vm handlers easily. Below is an example of looping through the vm handler vector inside of `vm::ctx_t::vm_handlers`. ```cpp for ( auto idx = 0u; idx < vmctx.vm_handlers.size(); ++idx ) { std::printf( "======================== [%s #%d] ========================\n", vmctx.vm_handlers[ idx ].profile ? vmctx.vm_handlers[ idx ].profile->name : "UNK", idx ); vm::util::print( vmctx.vm_handlers[ idx ].instrs ); // if there is no imm then there are no transforms... if ( !vmctx.vm_handlers[ idx ].imm_size ) { std::puts( "\n" ); continue; } std::puts( "======================== [transforms] ========================\n" ); for ( auto &[ mnemonic, instr ] : vmctx.vm_handlers[ idx ].transforms ) { if ( instr.mnemonic == ZYDIS_MNEMONIC_INVALID ) continue; vm::util::print( instr ); } std::puts( "\n" ); } ``` ***Output*** ``` ======================== [LCONSTBZXW #253] ======================== > 0x00007FF6DEA85C2C and al, 0x45 > 0x00007FF6DEA85C2E movzx eax, byte ptr [rsi] > 0x00007FF6DEA85C82 add al, bl > 0x00007FF6DEA85C85 add al, 0xD3 > 0x00007FF6DEA86FC7 not al > 0x00007FF6DEA84D23 inc al > 0x00007FF6DEA85633 add bl, al > 0x00007FF6DEA853D5 sub rsi, 0xFFFFFFFFFFFFFFFF > 0x00007FF6DEA85CD1 sub rbp, 0x02 > 0x00007FF6DEA862F8 mov [rbp], ax > 0x00007FF6DEA844A7 rol ah, 0x07 ======================== [transforms] ======================== add al, bl add al, 0xD3 not al inc al add bl, al ``` ### License & Copyright Copyright (c) 2021 _xeroxz, Independent Researcher @back.engineering Licensed under the MIT License