From 3dd5f9d7caf7b75d7186095a708382451f8cb2fe Mon Sep 17 00:00:00 2001 From: _xeroxz Date: Fri, 30 Jul 2021 17:56:14 -0700 Subject: [PATCH] porting older unpacker code to vmemu... also rewriting vmemu... --- include/unpacker.hpp | 60 +++++++++ include/vmemu_t.hpp | 4 + src/main.cpp | 209 ++++++++++++++++-------------- src/unpacker.cpp | 294 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 475 insertions(+), 92 deletions(-) diff --git a/include/unpacker.hpp b/include/unpacker.hpp index e69de29..9e817a5 100644 --- a/include/unpacker.hpp +++ b/include/unpacker.hpp @@ -0,0 +1,60 @@ +#pragma once +#include + +#include +#include +#include +#include +#include +#include + +#define PAGE_4KB 0x1000 +#define STACK_SIZE PAGE_4KB * 512 + +#define IAT_VECTOR_TABLE 0xFFFFF0000000000 +#define STACK_BASE 0xFFFF000000000000 + +#define EX_ALLOCATE_POOL_VECTOR 0 +#define EX_FREE_POOL_VECTOR 1 +#define LOCAL_ALLOC_VECTOR 2 +#define LOCAL_FREE_VECTOR 3 + +namespace engine +{ + using iat_hook_t = std::function< void( uc_engine * ) >; + + class unpack_t + { + public: + explicit unpack_t( const std::vector< std::uint8_t > &bin ); + ~unpack_t( void ); + + bool init( void ); + bool unpack( std::vector< std::uint8_t > &output ); + + private: + uc_engine *uc_ctx; + std::vector< uint8_t > bin, map_bin; + std::vector< uc_hook * > uc_hooks; + + std::uintptr_t img_base, img_size; + win::image_t<> *win_img; + + static void alloc_pool_hook( uc_engine * ); + static void free_pool_hook( uc_engine * ); + static void local_alloc_hook( uc_engine * ); + static void local_free_hook( uc_engine * ); + + static bool iat_dispatcher( uc_engine *uc, uint64_t address, uint32_t size, unpack_t *unpack ); + static bool unpack_section_callback( uc_engine *uc, uint64_t address, uint32_t size, unpack_t *unpack ); + static bool code_exec_callback( uc_engine *uc, uint64_t address, uint32_t size, unpack_t *unpack ); + static void invalid_mem( uc_engine *uc, uc_mem_type type, uint64_t address, int size, int64_t value, + unpack_t *unpack ); + + std::map< std::string, std::pair< std::uint32_t, iat_hook_t > > iat_hooks = { + { "ExAllocatePool", { EX_ALLOCATE_POOL_VECTOR, &alloc_pool_hook } }, + { "ExFreePool", { EX_FREE_POOL_VECTOR, &free_pool_hook } }, + { "LocalAlloc", { LOCAL_ALLOC_VECTOR, &local_alloc_hook } }, + { "LocalFree", { LOCAL_FREE_VECTOR, &local_free_hook } } }; + }; +} // namespace engine \ No newline at end of file diff --git a/include/vmemu_t.hpp b/include/vmemu_t.hpp index 784d00e..42fc34f 100644 --- a/include/vmemu_t.hpp +++ b/include/vmemu_t.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include namespace vm @@ -11,5 +12,8 @@ namespace vm bool init(); bool get_trace( std::vector< vm::instrs::code_block_t > &code_blocks ); + + private: + uc_engine *uc_ctx; }; } // namespace vm \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index d91b5e1..0e2e832 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,4 @@ -#include +#include "unpacker.hpp" #include "vmemu_t.hpp" #include @@ -9,14 +9,9 @@ int __cdecl main( int argc, const char *argv[] ) { argparse::argument_parser_t parser( "VMEmu", "VMProtect 2 VM Handler Emulator" ); - - parser.add_argument() - .name( "--vmentry" ) - .required( true ) - .description( "relative virtual address to a vm entry..." ); - - parser.add_argument().name( "--bin" ).required( true ).description( "path to unpacked virtualized binary..." ); - parser.add_argument().name( "--out" ).required( true ).description( "output file name for trace file..." ); + parser.add_argument().name( "--vmentry" ).description( "relative virtual address to a vm entry..." ); + parser.add_argument().name( "--bin" ).description( "path to unpacked virtualized binary..." ); + parser.add_argument().required( true ).name( "--out" ).description( "output file name..." ); parser.add_argument().name( "--unpack" ).description( "unpack a vmp2 binary..." ); parser.enable_help(); @@ -35,92 +30,122 @@ int __cdecl main( int argc, const char *argv[] ) } auto umtils = xtils::um_t::get_instance(); - const auto module_base = reinterpret_cast< std::uintptr_t >( - LoadLibraryExA( parser.get< std::string >( "vmpbin" ).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 >( "vmpbin" ).c_str() ); - const auto image_size = NT_HEADER( module_base )->OptionalHeader.SizeOfImage; - - std::printf( "> image base = %p, image size = %p, module base = %p\n", image_base, image_size, module_base ); - - if ( !image_base || !image_size || !module_base ) - { - std::printf( "[!] failed to open binary on disk...\n" ); - return -1; - } - - std::vector< vm::instrs::code_block_t > code_blocks; - vm::ctx_t vmctx( module_base, image_base, image_size, vm_entry_rva ); - if ( !vmctx.init() ) + if ( !parser.exists( "unpack" ) ) { - std::printf( "[!] failed to init vmctx... this can be for many reasons..." - " try validating your vm entry rva... make sure the binary is unpacked and is" - "protected with VMProtect 2...\n" ); - return -1; + 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 >( "vmpbin" ).c_str() ); + const auto image_size = NT_HEADER( module_base )->OptionalHeader.SizeOfImage; + + std::printf( "> image base = %p, image size = %p, module base = %p\n", image_base, image_size, module_base ); + + if ( !image_base || !image_size || !module_base ) + { + std::printf( "[!] failed to open binary on disk...\n" ); + return -1; + } + + std::vector< vm::instrs::code_block_t > code_blocks; + vm::ctx_t vmctx( module_base, image_base, image_size, vm_entry_rva ); + + if ( !vmctx.init() ) + { + std::printf( "[!] failed to init vmctx... this can be for many reasons..." + " try validating your vm entry rva... make sure the binary is unpacked and is" + "protected with VMProtect 2...\n" ); + return -1; + } + + vm::emu_t emu( &vmctx ); + + if ( !emu.init() ) + { + std::printf( "[!] failed to init emulator...\n" ); + return -1; + } + + if ( !emu.get_trace( code_blocks ) ) + std::printf( "[!] something failed during tracing, review the console for more information...\n" ); + + std::printf( "> number of blocks = %d\n", code_blocks.size() ); + + for ( auto &code_block : code_blocks ) + { + std::printf( "> code block starts at = %p\n", code_block.vip_begin ); + std::printf( "> number of virtual instructions = %d\n", code_block.vinstrs.size() ); + std::printf( "> does this code block have a jcc? %s\n", code_block.jcc.has_jcc ? "yes" : "no" ); + + if ( code_block.jcc.has_jcc ) + std::printf( "> branch 1 = %p, branch 2 = %p\n", code_block.jcc.block_addr[ 0 ], + code_block.jcc.block_addr[ 1 ] ); + } + + std::printf( "> serializing results....\n" ); + vmp2::v3::file_header file_header; + file_header.magic = VMP_MAGIC; + file_header.epoch_time = std::time( nullptr ); + file_header.version = vmp2::version_t::v3; + file_header.module_base = module_base; + file_header.image_base = image_base; + file_header.vm_entry_rva = vm_entry_rva; + file_header.module_offset = sizeof file_header; + file_header.module_size = image_size; + file_header.code_block_offset = image_size + sizeof file_header; + file_header.code_block_count = code_blocks.size(); + + std::ofstream output( parser.get< std::string >( "out" ), std::ios::binary ); + output.write( reinterpret_cast< const char * >( &file_header ), sizeof file_header ); + output.write( reinterpret_cast< const char * >( module_base ), image_size ); + + for ( const auto &code_block : code_blocks ) + { + const auto _code_block_size = + ( code_block.vinstrs.size() * sizeof vm::instrs::virt_instr_t ) + sizeof vmp2::v3::code_block_t; + + vmp2::v3::code_block_t *_code_block = + reinterpret_cast< vmp2::v3::code_block_t * >( malloc( _code_block_size ) ); + + _code_block->vip_begin = code_block.vip_begin; + _code_block->jcc = code_block.jcc; + _code_block->next_block_offset = _code_block_size; + _code_block->vinstr_count = code_block.vinstrs.size(); + + for ( auto idx = 0u; idx < code_block.vinstrs.size(); ++idx ) + _code_block->vinstr[ idx ] = code_block.vinstrs[ idx ]; + + output.write( reinterpret_cast< const char * >( _code_block ), _code_block_size ); + } + + output.close(); } - - vm::emu_t emu( &vmctx ); - - if ( !emu.init() ) + else { - std::printf( "[!] failed to init emulator...\n" ); - return -1; + std::vector< std::uint8_t > packed_bin, unpacked_bin; + if ( !umtils->open_binary_file( parser.get< std::string >( "unpack" ), packed_bin ) ) + { + std::printf( "> failed to read bin off disk...\n" ); + return -1; + } + + engine::unpack_t unpacker( packed_bin ); + + if ( !unpacker.init() ) + { + std::printf( "> failed to init unpacker...\n" ); + return -1; + } + + if ( !unpacker.unpack( unpacked_bin ) ) + { + std::printf( "> failed to unpack binary... refer to log above...\n" ); + return -1; + } + + std::ofstream output( parser.get< std::string >( "output" ), std::ios::binary ); + output.write( reinterpret_cast< char * >( unpacked_bin.data() ), unpacked_bin.size() ); + output.close(); } - - if ( !emu.get_trace( code_blocks ) ) - std::printf( "[!] something failed during tracing, review the console for more information...\n" ); - - std::printf( "> number of blocks = %d\n", code_blocks.size() ); - - for ( auto &code_block : code_blocks ) - { - std::printf( "> code block starts at = %p\n", code_block.vip_begin ); - std::printf( "> number of virtual instructions = %d\n", code_block.vinstrs.size() ); - std::printf( "> does this code block have a jcc? %s\n", code_block.jcc.has_jcc ? "yes" : "no" ); - - if ( code_block.jcc.has_jcc ) - std::printf( "> branch 1 = %p, branch 2 = %p\n", code_block.jcc.block_addr[ 0 ], - code_block.jcc.block_addr[ 1 ] ); - } - - std::printf( "> serializing results....\n" ); - vmp2::v3::file_header file_header; - file_header.magic = VMP_MAGIC; - file_header.epoch_time = std::time( nullptr ); - file_header.version = vmp2::version_t::v3; - file_header.module_base = module_base; - file_header.image_base = image_base; - file_header.vm_entry_rva = vm_entry_rva; - file_header.module_offset = sizeof file_header; - file_header.module_size = image_size; - file_header.code_block_offset = image_size + sizeof file_header; - file_header.code_block_count = code_blocks.size(); - - std::ofstream output( parser.get< std::string >( "out" ), std::ios::binary ); - output.write( reinterpret_cast< const char * >( &file_header ), sizeof file_header ); - output.write( reinterpret_cast< const char * >( module_base ), image_size ); - - for ( const auto &code_block : code_blocks ) - { - const auto _code_block_size = - ( code_block.vinstrs.size() * sizeof vm::instrs::virt_instr_t ) + sizeof vmp2::v3::code_block_t; - - vmp2::v3::code_block_t *_code_block = - reinterpret_cast< vmp2::v3::code_block_t * >( malloc( _code_block_size ) ); - - _code_block->vip_begin = code_block.vip_begin; - _code_block->jcc = code_block.jcc; - _code_block->next_block_offset = _code_block_size; - _code_block->vinstr_count = code_block.vinstrs.size(); - - for ( auto idx = 0u; idx < code_block.vinstrs.size(); ++idx ) - _code_block->vinstr[ idx ] = code_block.vinstrs[ idx ]; - - output.write( reinterpret_cast< const char * >( _code_block ), _code_block_size ); - } - - output.close(); - std::printf( "> finished..." ); } \ No newline at end of file diff --git a/src/unpacker.cpp b/src/unpacker.cpp index e69de29..812a102 100644 --- a/src/unpacker.cpp +++ b/src/unpacker.cpp @@ -0,0 +1,294 @@ +#include + +namespace engine +{ + unpack_t::unpack_t( const std::vector< std::uint8_t > &packed_bin ) : bin( packed_bin ), uc_ctx( nullptr ) + { + win_img = reinterpret_cast< win::image_t<> * >( bin.data() ); + img_base = win_img->get_nt_headers()->optional_header.image_base; + img_size = win_img->get_nt_headers()->optional_header.size_image; + std::printf( "> image base = 0x%p, image size = 0x%x\n", img_base, img_size ); + } + + unpack_t::~unpack_t( void ) + { + if ( uc_ctx ) + uc_close( uc_ctx ); + + for ( auto &ptr : uc_hooks ) + if ( ptr ) + delete ptr; + } + + bool unpack_t::init( void ) + { + uc_err err; + if ( ( err = uc_open( UC_ARCH_X86, UC_MODE_64, &uc_ctx ) ) ) + { + std::printf( "> uc_open err = %d\n", err ); + return false; + } + + if ( ( err = uc_mem_map( uc_ctx, IAT_VECTOR_TABLE, PAGE_4KB, UC_PROT_ALL ) ) ) + { + std::printf( "> uc_mem_map iat vector table err = %d\n", err ); + return false; + } + + if ( ( err = uc_mem_map( uc_ctx, STACK_BASE, STACK_SIZE, UC_PROT_ALL ) ) ) + { + std::printf( "> uc_mem_map stack err, reason = %d\n", err ); + return false; + } + + if ( ( err = uc_mem_map( uc_ctx, img_base, img_size, UC_PROT_ALL ) ) ) + { + std::printf( "> map memory failed, reason = %d\n", err ); + return false; + } + + // init iat vector table full of 'ret' instructions... + auto c3_page = malloc( PAGE_4KB ); + { + memset( c3_page, 0xC3, PAGE_4KB ); + + if ( ( err = uc_mem_write( uc_ctx, IAT_VECTOR_TABLE, c3_page, PAGE_4KB ) ) ) + { + std::printf( "> failed to init iat vector table...\n" ); + free( c3_page ); + return false; + } + } + free( c3_page ); + + map_bin.resize( img_size ); + memcpy( map_bin.data(), bin.data(), // copies pe headers (includes section headers) + win_img->get_nt_headers()->optional_header.size_headers ); + + win::section_header_t *sec_begin = win_img->get_nt_headers()->get_sections(), + *sec_end = sec_begin + win_img->get_nt_headers()->file_header.num_sections; + + // map sections... + std::for_each( sec_begin, sec_end, [ & ]( const win::section_header_t &sec_header ) { + memcpy( map_bin.data() + sec_header.virtual_address, bin.data() + sec_header.ptr_raw_data, + sec_header.size_raw_data ); + + std::printf( + "> mapped section = %s, virt address = 0x%p, virt size = 0x%x, phys offset = 0x%x, phys size = 0x%x\n", + sec_header.name.to_string().data(), sec_header.virtual_address, sec_header.virtual_size, + sec_header.ptr_raw_data, sec_header.size_raw_data ); + } ); + + auto basereloc_dir = win_img->get_directory( win::directory_id::directory_entry_basereloc ); + auto reloc_dir = reinterpret_cast< win::reloc_directory_t * >( basereloc_dir->rva + map_bin.data() ); + win::reloc_block_t *reloc_block = &reloc_dir->first_block; + + // apply relocations to all sections... + while ( reloc_block->base_rva && reloc_block->size_block ) + { + std::for_each( reloc_block->begin(), reloc_block->end(), [ & ]( win::reloc_entry_t &entry ) { + switch ( entry.type ) + { + case win::reloc_type_id::rel_based_dir64: + { + auto reloc_at = + reinterpret_cast< std::uintptr_t * >( entry.offset + reloc_block->base_rva + map_bin.data() ); + + *reloc_at = img_base + ( ( *reloc_at ) - img_base ); + break; + } + default: + break; + } + } ); + + reloc_block = reloc_block->next(); + } + + // iat hook specific function... + for ( auto import_dir = reinterpret_cast< win::import_directory_t * >( + win_img->get_directory( win::directory_id::directory_entry_import )->rva + map_bin.data() ); + import_dir->rva_name; ++import_dir ) + { + for ( auto iat_thunk = reinterpret_cast< win::image_thunk_data_t<> * >( + import_dir->rva_original_first_thunk + map_bin.data() ); + iat_thunk->address; ++iat_thunk ) + { + auto iat_name = reinterpret_cast< win::image_named_import_t * >( iat_thunk->address + map_bin.data() ); + if ( iat_hooks.find( iat_name->name ) != iat_hooks.end() ) + iat_thunk->function = iat_hooks[ iat_name->name ].first + IAT_VECTOR_TABLE; + } + } + + // map the entire map buffer into unicorn-engine since we have set everything else up... + if ( ( err = uc_mem_write( uc_ctx, img_base, map_bin.data(), map_bin.size() ) ) ) + { + std::printf( "> failed to write memory... reason = %d\n", err ); + return false; + } + + // setup unicorn-engine hooks on IAT vector table, sections with 0 raw size/ptr, and an invalid memory + // handler... + + uc_hooks.push_back( new uc_hook ); + if ( ( err = uc_hook_add( uc_ctx, uc_hooks.back(), UC_HOOK_CODE, &engine::unpack_t::iat_dispatcher, this, + IAT_VECTOR_TABLE, IAT_VECTOR_TABLE + PAGE_4KB ) ) ) + { + std::printf( "> uc_hook_add error, reason = %d\n", err ); + return false; + } + + uc_hooks.push_back( new uc_hook ); + if ( ( err = uc_hook_add( uc_ctx, uc_hooks.back(), + UC_HOOK_MEM_READ_UNMAPPED | UC_HOOK_MEM_WRITE_UNMAPPED | UC_HOOK_MEM_FETCH_UNMAPPED, + &engine::unpack_t::invalid_mem, this, true, false ) ) ) + { + std::printf( "> uc_hook_add error, reason = %d\n", err ); + return false; + } + + // execution break points on all sections that are executable but have no physical size on disk... + std::for_each( sec_begin, sec_end, [ & ]( win::section_header_t &header ) { + if ( !header.ptr_raw_data && !header.size_raw_data && header.characteristics.mem_execute && + !header.is_discardable() ) + { + uc_hooks.push_back( new uc_hook ); + if ( ( err = uc_hook_add( uc_ctx, uc_hooks.back(), UC_HOOK_CODE, + &engine::unpack_t::unpack_section_callback, this, + header.virtual_address + img_base, + header.virtual_address + header.virtual_size + img_base ) ) ) + { + std::printf( "> failed to add hook... reason = %d\n", err ); + return false; + } + + std::printf( "> adding unpack watch on section = %s\n", header.name.to_string().data() ); + } + else if ( header.characteristics.mem_execute ) + { + uc_hooks.push_back( new uc_hook ); + if ( ( err = uc_hook_add( uc_ctx, uc_hooks.back(), UC_HOOK_CODE, &engine::unpack_t::code_exec_callback, + this, header.virtual_address + img_base, + header.virtual_address + header.virtual_size + img_base ) ) ) + { + std::printf( "> failed to add hook... reason = %d\n", err ); + return false; + } + + std::printf( "> added execution callback on section = %s\n", header.name.to_string().data() ); + } + } ); + + return true; + } + + bool unpack_t::unpack( std::vector< std::uint8_t > &output ) + { + uc_err err; + auto nt_headers = win_img->get_nt_headers(); + std::uintptr_t rip = nt_headers->optional_header.entry_point + img_base, rsp = STACK_BASE + STACK_SIZE; + + if ( ( err = uc_reg_write( uc_ctx, UC_X86_REG_RSP, &rsp ) ) ) + { + std::printf( "> uc_reg_write error, reason = %d\n", err ); + return false; + } + + if ( ( err = uc_reg_write( uc_ctx, UC_X86_REG_RIP, &rip ) ) ) + { + std::printf( "> uc_reg_write error, reason = %d\n", err ); + return false; + } + + std::printf( "> beginning execution at = 0x%p\n", rip ); + if ( ( err = uc_emu_start( uc_ctx, rip, 0ull, 0ull, 0ull ) ) ) + { + std::printf( "> error starting emu... reason = %d\n", err ); + return false; + } + + return true; + } + + void unpack_t::alloc_pool_hook( uc_engine *uc_ctx ) + { + } + + void unpack_t::free_pool_hook( uc_engine *uc_ctx ) + { + } + + void unpack_t::local_alloc_hook( uc_engine *uc_ctx ) + { + } + + void unpack_t::local_free_hook( uc_engine *uc_ctx ) + { + } + + bool unpack_t::iat_dispatcher( uc_engine *uc, uint64_t address, uint32_t size, unpack_t *unpack ) + { + auto vec = address - IAT_VECTOR_TABLE; + for ( auto &[ iat_name, iat_hook_data ] : unpack->iat_hooks ) + { + if ( iat_hook_data.first == vec ) + { + std::printf( "> hooking import = %s\n", iat_name.c_str() ); + iat_hook_data.second( uc ); + return true; + } + } + return false; + } + + bool unpack_t::code_exec_callback( uc_engine *uc, uint64_t address, uint32_t size, unpack_t *unpack ) + { + static ZydisDecoder decoder; + static ZydisFormatter formatter; + static ZydisDecodedInstruction instr; + + if ( static std::atomic< bool > once{ false }; !once.exchange( true ) ) + { + ZydisDecoderInit( &decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_ADDRESS_WIDTH_64 ); + ZydisFormatterInit( &formatter, ZYDIS_FORMATTER_STYLE_INTEL ); + } + + auto instr_ptr = reinterpret_cast< void * >( unpack->map_bin.data() + ( address - unpack->img_base ) ); + if ( ZYAN_SUCCESS( ZydisDecoderDecodeBuffer( &decoder, instr_ptr, PAGE_4KB, &instr ) ) ) + { + char buffer[ 0x1000 ]; + ZydisFormatterFormatInstruction( &formatter, &instr, buffer, sizeof( buffer ), address ); + std::printf( "> 0x%p ", address ); + puts( buffer ); + } + + return true; + } + + bool unpack_t::unpack_section_callback( uc_engine *uc, uint64_t address, uint32_t size, unpack_t *unpack ) + { + std::printf( "> might be dump time...\n" ); + std::getchar(); + return true; + } + + void unpack_t::invalid_mem( uc_engine *uc, uc_mem_type type, uint64_t address, int size, int64_t value, + unpack_t *unpack ) + { + switch ( type ) + { + case UC_MEM_READ_UNMAPPED: + std::printf( ">>> reading invalid memory at address = 0x%p, size = 0x%x\n", address, size ); + break; + case UC_MEM_WRITE_UNMAPPED: + std::printf( ">>> writing invalid memory at address = 0x%p, size = 0x%x, val = 0x%x\n", address, size, + value ); + break; + case UC_MEM_FETCH_UNMAPPED: + std::printf( ">>> fetching invalid instructions at address = 0x%p\n", address ); + break; + default: + break; + } + } +} // namespace engine \ No newline at end of file