porting older unpacker code to vmemu... also rewriting vmemu...

merge-requests/6/head
_xeroxz 3 years ago
parent 320c7e9638
commit 3dd5f9d7ca

@ -0,0 +1,60 @@
#pragma once
#include <unicorn/unicorn.h>
#include <Zydis/Zydis.h>
#include <atomic>
#include <functional>
#include <map>
#include <nt/image.hpp>
#include <vector>
#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

@ -1,4 +1,5 @@
#pragma once #pragma once
#include <unicorn/unicorn.h>
#include <vmprofiler.hpp> #include <vmprofiler.hpp>
namespace vm namespace vm
@ -11,5 +12,8 @@ namespace vm
bool init(); bool init();
bool get_trace( std::vector< vm::instrs::code_block_t > &code_blocks ); bool get_trace( std::vector< vm::instrs::code_block_t > &code_blocks );
private:
uc_engine *uc_ctx;
}; };
} // namespace vm } // namespace vm

@ -1,4 +1,4 @@
#include <unicorn/unicorn.h> #include "unpacker.hpp"
#include "vmemu_t.hpp" #include "vmemu_t.hpp"
#include <cli-parser.hpp> #include <cli-parser.hpp>
@ -9,14 +9,9 @@
int __cdecl main( int argc, const char *argv[] ) int __cdecl main( int argc, const char *argv[] )
{ {
argparse::argument_parser_t parser( "VMEmu", "VMProtect 2 VM Handler Emulator" ); argparse::argument_parser_t parser( "VMEmu", "VMProtect 2 VM Handler Emulator" );
parser.add_argument().name( "--vmentry" ).description( "relative virtual address to a vm entry..." );
parser.add_argument() parser.add_argument().name( "--bin" ).description( "path to unpacked virtualized binary..." );
.name( "--vmentry" ) parser.add_argument().required( true ).name( "--out" ).description( "output file name..." );
.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( "--unpack" ).description( "unpack a vmp2 binary..." ); parser.add_argument().name( "--unpack" ).description( "unpack a vmp2 binary..." );
parser.enable_help(); parser.enable_help();
@ -35,92 +30,122 @@ int __cdecl main( int argc, const char *argv[] )
} }
auto umtils = xtils::um_t::get_instance(); 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..." const auto module_base = reinterpret_cast< std::uintptr_t >(
" try validating your vm entry rva... make sure the binary is unpacked and is" LoadLibraryExA( parser.get< std::string >( "bin" ).c_str(), NULL, DONT_RESOLVE_DLL_REFERENCES ) );
"protected with VMProtect 2...\n" );
return -1; 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();
} }
else
vm::emu_t emu( &vmctx );
if ( !emu.init() )
{ {
std::printf( "[!] failed to init emulator...\n" ); std::vector< std::uint8_t > packed_bin, unpacked_bin;
return -1; 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..." );
} }

@ -0,0 +1,294 @@
#include <unpacker.hpp>
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
Loading…
Cancel
Save