added linux-pe dep, added vm::locate code...

merge-requests/5/head
_xeroxz 3 years ago
parent 68118f8f8b
commit 3e8df3258c

3
.gitmodules vendored

@ -7,3 +7,6 @@
[submodule "dependencies/xtils"] [submodule "dependencies/xtils"]
path = dependencies/xtils path = dependencies/xtils
url = https://githacks.org/_xeroxz/xtils.git url = https://githacks.org/_xeroxz/xtils.git
[submodule "dependencies/linux-pe"]
path = dependencies/linux-pe
url = https://github.com/can1357/linux-pe.git

@ -41,6 +41,8 @@ set(vmprofiler-cli_SOURCES "")
list(APPEND vmprofiler-cli_SOURCES list(APPEND vmprofiler-cli_SOURCES
"src/main.cpp" "src/main.cpp"
"src/vmlocate.cpp"
"include/vmlocate.hpp"
"src/icon.rc" "src/icon.rc"
) )
@ -62,10 +64,15 @@ endif()
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${vmprofiler-cli_SOURCES}) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${vmprofiler-cli_SOURCES})
target_include_directories(vmprofiler-cli PRIVATE
include
)
target_link_libraries(vmprofiler-cli PRIVATE target_link_libraries(vmprofiler-cli PRIVATE
cli-parser cli-parser
xtils xtils
vmprofiler vmprofiler
linux-pe
) )
unset(CMKR_TARGET) unset(CMKR_TARGET)

@ -7,10 +7,16 @@ name = "vmprofiler-cli"
type = "executable" type = "executable"
sources = [ sources = [
"src/main.cpp", "src/main.cpp",
"src/icon.rc", "src/vmlocate.cpp",
"include/vmlocate.hpp",
"src/icon.rc"
]
include-directories = [
"include",
] ]
link-libraries = [ link-libraries = [
"cli-parser", "cli-parser",
"xtils", "xtils",
"vmprofiler", "vmprofiler",
"linux-pe"
] ]

@ -34,6 +34,24 @@ target_include_directories(cli-parser INTERFACE
unset(CMKR_TARGET) unset(CMKR_TARGET)
unset(CMKR_SOURCES) unset(CMKR_SOURCES)
# Target linux-pe
set(CMKR_TARGET linux-pe)
set(linux-pe_SOURCES "")
set(CMKR_SOURCES ${linux-pe_SOURCES})
add_library(linux-pe INTERFACE)
if(linux-pe_SOURCES)
target_sources(linux-pe INTERFACE ${linux-pe_SOURCES})
endif()
target_include_directories(linux-pe INTERFACE
"linux-pe/includes/"
)
unset(CMKR_TARGET)
unset(CMKR_SOURCES)
# Target xtils # Target xtils
set(CMKR_TARGET xtils) set(CMKR_TARGET xtils)
set(xtils_SOURCES "") set(xtils_SOURCES "")

@ -4,6 +4,10 @@
type = "interface" type = "interface"
include-directories = ["cli-parser"] include-directories = ["cli-parser"]
[target.linux-pe]
type = "interface"
include-directories = ["linux-pe/includes/"]
[target.xtils] [target.xtils]
type = "interface" type = "interface"
include-directories = ["xtils"] include-directories = ["xtils"]

@ -0,0 +1 @@
Subproject commit db2b7af6e6beae1bc391ff8f8e5c97b963dc3258

@ -1 +1 @@
Subproject commit e8eb794dc1c853807b406815e308f3d201fa2a93 Subproject commit 7240a2a23c7d789d5973de04458c7ebca16a81d0

@ -0,0 +1,25 @@
#pragma once
#include <Zydis/Zydis.h>
#include <nt/image.hpp>
#include <vmprofiler.hpp>
#include <xtils.hpp>
#define ABS_TO_IMG( addr, mod_base, img_base ) ( addr - mod_base ) + img_base
#define LEA_R12_SIG "\x4C\x8D\x25\x00\x00\x00\x00"
#define LEA_R12_MASK "xxx????"
#define PUSH_4B_IMM "\x68\x00\x00\x00\x00"
#define PUSH_4B_MASK "x????"
namespace vm::locate
{
struct vm_handler_table_info_t
{
std::uint32_t rva, lea_r12_rva;
zydis_decoded_instr_t lea_r12_instr;
};
std::vector< vm_handler_table_info_t > all_handler_tables( std::uintptr_t module_base );
std::vector< std::pair< std::uint32_t, std::uint32_t > > all_vm_enters(
std::uintptr_t module_base, std::vector< vm_handler_table_info_t > &vm_handler_tables );
} // namespace vm::locate

Binary file not shown.

@ -1,16 +1,12 @@
#include <Windows.h> #include <Windows.h>
#include <ZydisExportConfig.h>
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <vtil/vtil> #include <vtil/vtil>
#include <ZydisExportConfig.h>
#include <cli-parser.hpp> #include <cli-parser.hpp>
#include <vmp2.hpp> #include <vmlocate.hpp>
#include <vmprofiler.hpp>
#include <xtils.hpp>
#define ABS_TO_IMG( addr, mod_base, img_base ) ( addr - mod_base ) + img_base
int __cdecl main( int argc, const char *argv[] ) int __cdecl main( int argc, const char *argv[] )
{ {
@ -21,6 +17,12 @@ int __cdecl main( int argc, const char *argv[] )
parser.add_argument().name( "--showhandlers" ).description( "show all vm handlers..." ); parser.add_argument().name( "--showhandlers" ).description( "show all vm handlers..." );
parser.add_argument().name( "--showhandler" ).description( "show a specific vm handler given its index..." ); parser.add_argument().name( "--showhandler" ).description( "show a specific vm handler given its index..." );
parser.add_argument().name( "--vmp2file" ).description( "path to .vmp2 file..." ); parser.add_argument().name( "--vmp2file" ).description( "path to .vmp2 file..." );
parser.add_argument()
.name( "--indexes" )
.description( "displays vm handler table indexes for a given vm handler name such as 'READQ', or 'WRITEQ'..." );
parser.add_argument()
.name( "--scanfortables" )
.description( "scans all executable sections for vm handler tables..." );
parser.add_argument() parser.add_argument()
.name( "--showblockinstrs" ) .name( "--showblockinstrs" )
.description( "show the virtual instructions of a specific code block..." ); .description( "show the virtual instructions of a specific code block..." );
@ -45,6 +47,32 @@ int __cdecl main( int argc, const char *argv[] )
return 0; return 0;
} }
if ( parser.exists( "bin" ) && parser.exists( "scanfortables" ) )
{
if ( !std::filesystem::exists( parser.get< std::string >( "bin" ) ) )
{
std::printf( "> path to protected file is invalid... check your cli args...\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 ) );
auto result = vm::locate::all_handler_tables( module_base );
std::vector< std::uint32_t > handler_table_rvas;
std::for_each( result.begin(), result.end(), [ & ]( const vm::locate::vm_handler_table_info_t &data ) {
if ( std::find( handler_table_rvas.begin(), handler_table_rvas.end(), data.rva ) ==
handler_table_rvas.end() )
handler_table_rvas.push_back( data.rva );
} );
std::printf( "{\n" );
for ( auto &table_rva : handler_table_rvas )
std::printf( "\t0x%x,\n", table_rva );
std::printf( "}\n" );
}
if ( parser.exists( "bin" ) && parser.exists( "vmentry" ) ) if ( parser.exists( "bin" ) && parser.exists( "vmentry" ) )
{ {
if ( !std::filesystem::exists( parser.get< std::string >( "bin" ) ) ) if ( !std::filesystem::exists( parser.get< std::string >( "bin" ) ) )
@ -140,6 +168,16 @@ int __cdecl main( int argc, const char *argv[] )
} }
std::puts( "\n" ); std::puts( "\n" );
} }
else if ( parser.exists( "indexes" ) )
{
const auto handler_name = parser.get< std::string >( "indexes" );
std::printf( "{\n" );
for ( auto idx = 0u; idx < vmctx.vm_handlers.size(); ++idx )
if ( vmctx.vm_handlers[ idx ].profile &&
!strcmp( vmctx.vm_handlers[ idx ].profile->name, handler_name.c_str() ) )
std::printf( "\t0x%x,\n", idx );
std::printf( "}\n" );
}
} }
if ( !parser.exists( "vmp2file" ) ) if ( !parser.exists( "vmp2file" ) )

@ -0,0 +1,264 @@
#include <vmlocate.hpp>
namespace vm::locate
{
std::vector< vm_handler_table_info_t > all_handler_tables( std::uintptr_t module_base )
{
std::vector< vm_handler_table_info_t > result;
auto module_info = reinterpret_cast< win::image_t<> * >( module_base );
auto sections = module_info->get_nt_headers()->get_sections();
auto num_sections = module_info->get_file_header()->num_sections;
auto umtils = xtils::um_t::get_instance();
static const auto lea_r12_validate = []( std::uintptr_t addr ) -> bool {
ZydisDecodedInstruction instr;
ZydisDecoder decoder;
ZydisRegister jmp_reg = ZYDIS_REGISTER_NONE;
ZydisDecoderInit( &decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_ADDRESS_WIDTH_64 );
unsigned instr_count = 0u;
bool found_table_idx = false, found_valid_jmp = false;
while ( ZYAN_SUCCESS(
ZydisDecoderDecodeBuffer( &decoder, reinterpret_cast< void * >( addr ), 0x1000, &instr ) ) )
{
++instr_count;
if ( instr_count >= 0x1000 ||
instr.mnemonic == ZYDIS_MNEMONIC_INVALID ) // prevent run offs and misalignment...
break;
// determine if we are looking at a JMP RCX/JMP RDX...
if ( vm::util::is_jmp( instr ) )
{
if ( instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_REGISTER &&
( instr.operands[ 0 ].reg.value == ZYDIS_REGISTER_RDX ||
instr.operands[ 0 ].reg.value == ZYDIS_REGISTER_RCX ) )
{
if ( jmp_reg == ZYDIS_REGISTER_NONE || jmp_reg != instr.operands[ 0 ].reg.value )
break;
// else we set it to true and break...
found_valid_jmp = true;
break;
}
// take JCC branch no matter what...
ZydisCalcAbsoluteAddress( &instr, &instr.operands[ 0 ], addr, &addr );
// dont execute anymore address advancement code...
continue;
}
// else if the instruction is a MOV RDX/RCX, [R12+RAX*0x8]... we know this is an index into
// the vm handler table and thus this lea r12, xxxx is probably legit...
else if ( instr.mnemonic == ZYDIS_MNEMONIC_MOV &&
instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_REGISTER &&
( instr.operands[ 0 ].reg.value == ZYDIS_REGISTER_RDX ||
instr.operands[ 0 ].reg.value == ZYDIS_REGISTER_RCX ) &&
instr.operands[ 1 ].type == ZYDIS_OPERAND_TYPE_MEMORY &&
instr.operands[ 1 ].mem.base == ZYDIS_REGISTER_R12 &&
instr.operands[ 1 ].mem.index == ZYDIS_REGISTER_RAX && instr.operands[ 1 ].mem.scale == 0x8 )
{
found_table_idx = true;
jmp_reg = instr.operands[ 0 ].reg.value;
}
else if ( instr.mnemonic == ZYDIS_MNEMONIC_RET || instr.mnemonic == ZYDIS_MNEMONIC_CALL )
break;
// advance the instruction address "addr"...
addr += instr.length;
}
return found_table_idx && found_valid_jmp;
};
ZydisDecodedInstruction instr;
ZydisDecoder decoder;
ZydisDecoderInit( &decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_ADDRESS_WIDTH_64 );
for ( auto idx = 0u; idx < num_sections; ++idx )
{
// if the section is executable and not discardable... scan it for lea r12, xxxx and ensure its loading
// a virtual machine handler table into r12...
if ( sections[ idx ].characteristics.mem_execute && !sections[ idx ].characteristics.mem_discardable )
{
void *scan_result = reinterpret_cast< void * >( sections[ idx ].virtual_address + module_base );
do
{
// compute how far away from the beginning of the section we are...
auto section_size = reinterpret_cast< std::uintptr_t >( scan_result ) -
( sections[ idx ].virtual_address + module_base );
// scan from the last scans result to the end of the section for lea r12's...
scan_result = umtils->sigscan( scan_result, sections[ idx ].virtual_size - section_size,
LEA_R12_SIG, LEA_R12_MASK );
if ( scan_result )
{
// check to see if we are looking at:
// 1.) a legit "lea r12, xxxx" and not a misaligned instruction...
// 2.) if the instruction stream is followed in zydis it ends with a jmp rcx/jmp rdx...
if ( lea_r12_validate( reinterpret_cast< std::uintptr_t >( scan_result ) ) )
{
if ( ZYAN_SUCCESS( ZydisDecoderDecodeBuffer(
&decoder, scan_result, sections[ idx ].virtual_size - section_size, &instr ) ) )
{
vm_handler_table_info_t vm_handler_table_info;
vm_handler_table_info.rva =
( instr.operands[ 1 ].mem.disp.value +
reinterpret_cast< std::uintptr_t >( scan_result ) + instr.length ) -
module_base;
vm_handler_table_info.lea_r12_rva =
reinterpret_cast< std::uintptr_t >( scan_result ) - module_base;
vm_handler_table_info.lea_r12_instr = instr;
result.push_back( vm_handler_table_info );
}
}
scan_result = reinterpret_cast< void * >( reinterpret_cast< std::uintptr_t >( scan_result ) +
sizeof LEA_R12_SIG );
}
} while ( scan_result );
}
}
return result;
}
std::vector< std::pair< std::uint32_t, std::uint32_t > > all_vm_enters(
std::uintptr_t module_base, std::vector< vm_handler_table_info_t > &vm_handler_tables )
{
std::vector< std::pair< std::uint32_t, std::uint32_t > > result;
auto module_info = reinterpret_cast< win::image_t<> * >( module_base );
auto sections = module_info->get_nt_headers()->get_sections();
auto num_sections = module_info->get_file_header()->num_sections;
auto umtils = xtils::um_t::get_instance();
auto module_end = module_base + module_info->get_nt_headers()->optional_header.size_image;
static const auto validate_vm_enter = [ & ]( std::uintptr_t addr ) -> bool {
ZydisDecodedInstruction instr;
ZydisDecoder decoder;
ZydisDecoderInit( &decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_ADDRESS_WIDTH_64 );
unsigned instr_count = 0u;
bool found_valid_jmp = false;
std::vector< ZydisDecodedInstruction > instr_stream;
while ( addr >= module_base && addr < module_end &&
ZYAN_SUCCESS(
ZydisDecoderDecodeBuffer( &decoder, reinterpret_cast< void * >( addr ), 0x1000, &instr ) ) )
{
if ( !instr_count && instr.mnemonic != ZYDIS_MNEMONIC_PUSH )
break;
++instr_count; // handle run offs and misaligned instructions...
if ( instr_count > 500 || instr.mnemonic == ZYDIS_MNEMONIC_INVALID ||
instr.mnemonic == ZYDIS_MNEMONIC_RET || instr.mnemonic == ZYDIS_MNEMONIC_CALL )
return false;
// determine if we are looking at a JMP RCX/JMP RDX...
if ( vm::util::is_jmp( instr ) )
{
if ( instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_REGISTER &&
( instr.operands[ 0 ].reg.value == ZYDIS_REGISTER_RDX ||
instr.operands[ 0 ].reg.value == ZYDIS_REGISTER_RCX ) )
{
// else we set it to true and break...
found_valid_jmp = true;
instr_stream.push_back( instr );
break;
}
// take JCC branch no matter what...
ZydisCalcAbsoluteAddress( &instr, &instr.operands[ 0 ], addr, &addr );
// dont execute anymore address advancement code...
continue;
}
instr_stream.push_back( instr );
addr += instr.length;
}
if ( !found_valid_jmp )
return false;
// second instruction in the flattened stream should be a push...
// this is also an optimization so we dont have to hit that 0^2 std::find_if every time...
if ( instr_stream[ 1 ].mnemonic != ZYDIS_MNEMONIC_PUSH )
return false;
if ( std::find_if( instr_stream.begin() + 1, instr_stream.end(),
[ & ]( const ZydisDecodedInstruction &instr ) {
return instr.mnemonic == ZYDIS_MNEMONIC_PUSH &&
instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_IMMEDIATE;
} ) == instr_stream.end() )
return false;
// scan over the instruction stream to see if it contains an lea r12, xxxx which is a known vm handler table
// load into r12... this is O^2 and very slow... whatever...
return std::find_if(
instr_stream.begin(), instr_stream.end(), [ & ]( const ZydisDecodedInstruction &instr ) {
return instr.mnemonic == ZYDIS_MNEMONIC_LEA &&
instr.operands[ 1 ].type == ZYDIS_OPERAND_TYPE_MEMORY &&
std::find_if( vm_handler_tables.begin(), vm_handler_tables.end(),
[ & ]( const vm_handler_table_info_t &table_info ) {
return table_info.lea_r12_instr.operands[ 1 ].mem.disp.value ==
instr.operands[ 1 ].mem.disp.value;
} ) != vm_handler_tables.end();
} ) != instr_stream.end();
};
ZydisDecodedInstruction instr;
ZydisDecoder decoder;
ZydisDecoderInit( &decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_ADDRESS_WIDTH_64 );
// for each section...
for ( auto idx = 0u; idx < num_sections; ++idx )
{
// we are only interested in executable (non-discardable) sections...
if ( sections[ idx ].characteristics.mem_execute && !sections[ idx ].characteristics.mem_discardable )
{
void *scan_result = reinterpret_cast< void * >( sections[ idx ].virtual_address + module_base );
do
{
// compute how far away from the beginning of the section we are...
auto section_size = reinterpret_cast< std::uintptr_t >( scan_result ) -
( sections[ idx ].virtual_address + module_base );
if ( section_size > sections[ idx ].virtual_size )
break;
scan_result = umtils->sigscan( scan_result, sections[ idx ].virtual_size - section_size,
PUSH_4B_IMM, PUSH_4B_MASK );
if ( scan_result )
{
if ( validate_vm_enter( reinterpret_cast< std::uintptr_t >( scan_result ) ) &&
ZYAN_SUCCESS( ZydisDecoderDecodeBuffer( &decoder, scan_result, 0x1000, &instr ) ) )
{
if ( std::find_if( result.begin(), result.end(),
[ & ]( const std::pair< std::uint32_t, std::uint32_t > &info ) {
return info.second == instr.operands[ 0 ].imm.value.u;
} ) == result.end() )
{
result.push_back( { reinterpret_cast< std::uintptr_t >( scan_result ) - module_base,
instr.operands[ 0 ].imm.value.u } );
}
}
scan_result = reinterpret_cast< void * >( reinterpret_cast< std::uintptr_t >( scan_result ) +
sizeof PUSH_4B_IMM );
}
} while ( scan_result );
}
}
return result;
}
} // namespace vm::locate
Loading…
Cancel
Save