#include "RemoteMemory.h"
#include "../Process.h"
#include "../../Misc/Trace.hpp"
namespace blackbone
{
RemoteMemory::RemoteMemory( Process* process )
: _process( process )
{
_pipeName = Utils::RandomANString() + L"_" + std::to_wstring( _process->pid() ) + L"_" + std::to_wstring( GetCurrentProcessId() );
}
RemoteMemory::~RemoteMemory()
{
reset();
}
///
/// Map entire process address space
///
/// Set to true to map section objects. They are converted to private pages before locking
/// Status code
NTSTATUS RemoteMemory::Map( bool mapSections )
{
MapMemoryResult result = { 0 };
// IPC
if (!_hPipe)
_hPipe = CreateNamedPipeW( (L"\\\\.\\pipe\\" + _pipeName).c_str(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE, 1, 0, 0, 0, NULL );
Driver().EnsureLoaded();
NTSTATUS status = Driver().MapMemory( _process->pid(), _pipeName, mapSections, result );
if (NT_SUCCESS( status ))
{
std::swap( _mapDatabase, result.regions );
_pSharedData = (PageContext*)result.hostSharedPage;
_targetShare = result.targetSharedPage;
_targetPipe = result.targetPipe;
}
return status;
}
///
/// Map specific memory region
///
/// Region base
/// Region size
/// Status code
NTSTATUS RemoteMemory::Map( ptr_t base, uint32_t size )
{
MapMemoryRegionResult memRes = { 0 };
// IPC
if (!_hPipe)
_hPipe = CreateNamedPipeW( (L"\\\\.\\pipe\\" + _pipeName).c_str(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE, 1, 0, 0, 0, NULL );
Driver().EnsureLoaded();
NTSTATUS status = Driver().MapMemoryRegion( _process->pid(), base, size, memRes );
// Update regions
if (NT_SUCCESS( status ))
{
MapMemoryResult rgnRes = { };
if (NT_SUCCESS( Driver().MapMemory( _process->pid(), _pipeName, false, rgnRes ) ))
std::swap( _mapDatabase, rgnRes.regions );
}
return status;
}
///
/// Unmap process address space from current process
///
/// Status code
NTSTATUS RemoteMemory::Unmap( )
{
NTSTATUS status = Driver().UnmapMemory( _process->pid() );
if (NT_SUCCESS( status ))
{
_mapDatabase.clear();
_pSharedData = nullptr;
_targetShare = 0;
_targetPipe = NULL;
}
return status;
}
///
/// Unmap specific memory region from current process
///
/// Region base
/// Region size
/// Status code
NTSTATUS RemoteMemory::Unmap( ptr_t base, uint32_t size )
{
auto searchFn = [&base]( const decltype(_mapDatabase)::value_type& val )
{
return base >= val.first.first && base < val.first.first + val.first.second;
};
NTSTATUS status = Driver().UnmapMemoryRegion( _process->pid(), base, size );
if (NT_SUCCESS( status ))
{
// Remove region
auto iter = std::find_if( _mapDatabase.begin(), _mapDatabase.end(), searchFn );
if (iter != _mapDatabase.end())
_mapDatabase.erase( iter );
}
return status;
}
///
/// Translate target address accordingly to current address space
///
/// Address to translate
/// If set to true, routine will try to map non-existing region upon translation failure
/// Translated address
blackbone::ptr_t RemoteMemory::TranslateAddress( ptr_t address, bool resolveFault /*= true */ )
{
auto searchFn = [&address]( const decltype(_mapDatabase)::value_type& val )
{
return address >= val.first.first && address < val.first.first + val.first.second;
};
auto iter = std::find_if( _mapDatabase.begin(), _mapDatabase.end(), searchFn );
// Primitive Page fault. Try to resolve missing page
if (iter == _mapDatabase.end() && resolveFault && NT_SUCCESS( Map( address, 1 ) ))
// Second chance
iter = std::find_if( _mapDatabase.begin(), _mapDatabase.end(), searchFn );
if (iter != _mapDatabase.end())
return iter->second + (address - iter->first.first);
return 0;
}
///
/// Setup one of the 4 possible memory hooks:
///
///
/// Type of hook to install
/// MemVirtualAlloc - hook NtAllocateVirtualMemory
/// MemVirtualFree - hook NtFreeVirtualMemory
/// MemMapSection - hook NtMapViewOfSection
/// MemUnmapSection - hook NtUnmapViewOfSection
///
/// true on success
NTSTATUS RemoteMemory::SetupHook( OperationType hkType )
{
static const char* procNames[] = { "NtAllocateVirtualMemory", "NtFreeVirtualMemory", "NtMapViewOfSection", "NtUnmapViewOfSection" };
uint8_t* pTranslated = nullptr;
ptr_t pProc = 0;
// Can't setup hook without target pipe and shared data
if (_targetPipe == NULL || !_pSharedData || !_targetShare)
return STATUS_NONE_MAPPED;
// Cross-architecture code generation isn't supported yet
auto barrier = _process->barrier().type;
if (barrier != wow_32_32 && barrier != wow_64_64)
return STATUS_CONTEXT_MISMATCH;
// Already hooked
if (_hooked[hkType])
return STATUS_ALREADY_REGISTERED;
auto& modules = _process->modules();
// Local and remote process address
pProc = modules.GetExport( modules.GetModule( L"ntdll.dll" ), procNames[hkType] ).result( exportData() ).procAddress;
pTranslated = (uint8_t*)TranslateAddress( pProc );
if (!pTranslated)
return STATUS_INVALID_ADDRESS;
// IPC
if (!_hPipe)
_hPipe = CreateNamedPipeW( (L"\\\\.\\pipe\\" + _pipeName).c_str(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE, 1, 0, 0, 0, NULL );
// Listening thread
if (_hThread == NULL)
{
_hThread = CreateThread( NULL, 0, &RemoteMemory::HookThreadWrap, this, 0, NULL );
InitializeCriticalSection( &_pSharedData->csLock );
}
// Setup hook data
BuildTrampoline( hkType, (uintptr_t)pProc, pTranslated );
BuildGenericHookFn( hkType );
// Copy hook
memcpy( pTranslated, (uint8_t*)_pSharedData + sizeof( HookData ) * hkType + FIELD_OFFSET( HookData, jump_buf ), sizeof( _pSharedData->hkVirtualAlloc.jump_buf ) );
_hooked[hkType] = true;
return STATUS_SUCCESS;
}
///
/// Restore previously hooked function
///
/// Hook type. For more info see SetupHook
/// true on success
bool RemoteMemory::RestoreHook( OperationType hkType )
{
uint8_t* pTranslated = nullptr;
ptr_t pProc = 0;
static const char* procNames[] = { "NtAllocateVirtualMemory", "NtFreeVirtualMemory", "NtMapViewOfSection", "NtUnmapViewOfSection" };
// Not hooked
if (!_hooked[hkType])
return false;
auto& modules = _process->modules();
// Local and remote proc address
pProc = modules.GetExport( modules.GetModule( L"ntdll.dll" ), procNames[hkType] ).result( exportData() ).procAddress;
pTranslated = (uint8_t*)TranslateAddress( pProc );
if (!pTranslated)
return false;
// Restore bytes
memcpy( pTranslated, (uint8_t*)_pSharedData + sizeof( HookData ) * hkType + FIELD_OFFSET( HookData, original_code ),
sizeof( _pSharedData->hkVirtualAlloc.original_code ) / 2 );
// Reset hook data
memset( (uint8_t*)_pSharedData + sizeof( HookData ) * hkType, 0x00, sizeof( HookData ) );
_hooked[hkType] = false;
return true;
}
///
/// Unmap any mapped memory, restore hooks and free resources
///
void RemoteMemory::reset()
{
_active = false;
if (_hThread != NULL)
{
TerminateThread( _hThread, 0 );
_hThread = NULL;
}
_hPipe.reset();
for (int i = 0; i < 4; i++)
RestoreHook( (OperationType)i );
if (!_mapDatabase.empty() && !NT_SUCCESS( Unmap() ))
{
_mapDatabase.clear();
_pSharedData = nullptr;
_targetShare = 0;
_targetPipe = NULL;
}
}
///
/// Hook thread wrapper
///
/// RemoteMemory instance
/// 0
DWORD CALLBACK RemoteMemory::HookThreadWrap( LPVOID lpParam )
{
((RemoteMemory*)lpParam)->HookThread();
return 0;
}
///
/// Thread responsible for mapping and unmapping regions intercepted by remote hooks
///
void RemoteMemory::HookThread()
{
DWORD bytes = 0;
// Wait for target process to connect
ConnectNamedPipe( _hPipe, NULL );
_active = true;
while (_active)
{
OperationData opData = { 0 };
// Target endpoint closed
if (!ReadFile( _hPipe, &opData, sizeof( opData ), &bytes, NULL ))
{
_active = false;
_hThread = NULL;
return;
}
// Update mapping
if (opData.allocType == MemVirtualAlloc || opData.allocType == MemMapSection)
{
BLACKBONE_TRACE( L"Allocated 0x%x bytes at %p", opData.allocSize, opData.allocAddress );
Map( opData.allocAddress, opData.allocSize );
}
else
{
BLACKBONE_TRACE( L"Freed 0x%x bytes at %p", opData.allocSize, opData.allocAddress );
Unmap( opData.allocAddress, opData.allocSize );
}
}
}
#ifdef USE64
///
/// Build remote hook function
///
/// Hooked function
void RemoteMemory::BuildGenericHookFn( OperationType opType )
{
static int argc[] = { 6, 4, 10, 2 };
static int allocIdx[] = { 1, 1, 2, 1 };
static int sizeIdx[] = { 3, 2, 6, -1 };
static int testIdx[] = { 4, -1, -1, -1 };
int hookDataOfs = sizeof( HookData ) * opType;
auto pAsm = AsmFactory::GetAssembler( _process->core().isWow64() );
auto& a = *pAsm;
AsmStackAllocator sa( a.assembler(), 0x60 );
asmjit::Label skip1 = a->newLabel();
ALLOC_STACK_VAR( sa, data, OperationData );
ALLOC_STACK_VAR( sa, junk, SIZE_T );
size_t stack_size = Align( sa.getTotalSize(), 0x10 );
auto& modules = _process->modules();
auto pEnterCS = modules.GetExport( modules.GetModule( L"ntdll.dll" ), "RtlEnterCriticalSection" ).result( exportData() );
auto pLeaveCS = modules.GetExport( modules.GetModule( L"ntdll.dll" ), "RtlLeaveCriticalSection" ).result( exportData() );
auto pWrite = modules.GetExport( modules.GetModule( L"kernel32.dll" ), "WriteFile" ).result( exportData() );
a.GenPrologue();
a.EnableX64CallStack( false );
a->sub( asmjit::host::rsp, stack_size );
// Call original function
for (int i = 0; i < argc[opType] - 4; i++)
{
a->mov( asmjit::host::rax, asmjit::host::qword_ptr( asmjit::host::rsp, (int32_t)stack_size + 0x8 + 0x20 + i * 8 ) );
a->mov( asmjit::host::qword_ptr( asmjit::host::rsp, 0x20 + i * 8 ), asmjit::host::rax );
}
a->mov( asmjit::host::rax, _targetShare + hookDataOfs + FIELD_OFFSET( HookData, original_code ) );
a->call( asmjit::host::rax );
// Test if call was successful
a->test( asmjit::host::rax, asmjit::host::rax );
a->jnz( skip1 );
// Test if memory was committed
if (opType == MemVirtualAlloc)
{
a->mov( asmjit::host::eax, asmjit::host::dword_ptr( asmjit::host::rsp, (int32_t)stack_size + 0x8 + 0x20 ) );
a->cmp( asmjit::host::eax, MEM_COMMIT );
a->jne( skip1 );
}
// RtlEnterCriticalSection
a.GenCall( (uintptr_t)pEnterCS.procAddress, { _targetShare + FIELD_OFFSET( PageContext, csLock ) } );
// Storage pointer
a->lea( asmjit::host::rdx, data );
// Allocation address
if (allocIdx[opType] > 3)
a->mov( asmjit::host::rax, asmjit::host::qword_ptr( asmjit::host::rsp, (int32_t)stack_size + 0x8 + 0x20 + (allocIdx[opType] - 4) * 8 ) );
else
a->mov( asmjit::host::rax, asmjit::host::qword_ptr( asmjit::host::rsp, (int32_t)stack_size + 0x8 + allocIdx[opType] * 8 ) );
if (opType != MemUnmapSection)
a->mov( asmjit::host::rax, asmjit::host::qword_ptr( asmjit::host::rax ) );
a->mov( asmjit::host::qword_ptr( asmjit::host::rdx, FIELD_OFFSET( OperationData, allocAddress ) ), asmjit::host::rax );
if (sizeIdx[opType] != -1)
{
// Region size
if (sizeIdx[opType] > 3)
a->mov( asmjit::host::rax, asmjit::host::qword_ptr( asmjit::host::rsp, (int32_t)stack_size + 0x8 + 0x20 + (sizeIdx[opType] - 4) * 8 ) );
else
a->mov( asmjit::host::rax, asmjit::host::qword_ptr( asmjit::host::rsp, (int32_t)stack_size + 0x8 + sizeIdx[opType] * 8 ) );
a->mov( asmjit::host::eax, asmjit::host::dword_ptr( asmjit::host::rax ) );
a->mov( asmjit::host::dword_ptr( asmjit::host::rdx, FIELD_OFFSET( OperationData, allocSize ) ), asmjit::host::eax );
}
else
a->mov( asmjit::host::dword_ptr( asmjit::host::rdx, FIELD_OFFSET( OperationData, allocSize ) ), 0 );
// Operation type
a->mov( asmjit::host::dword_ptr( asmjit::host::rdx, FIELD_OFFSET( OperationData, allocType ) ), opType );
a.GenCall( (uintptr_t)pWrite.procAddress, { (uint64_t)_targetPipe, &data, sizeof( OperationData ), &junk, 0 } );
// RtlEnterCriticalSection
a.GenCall( (uintptr_t)pLeaveCS.procAddress, { _targetShare + FIELD_OFFSET( PageContext, csLock ) } );
// Ignore return value
a->xor_( asmjit::host::rax, asmjit::host::rax );
a->bind( skip1 );
a->add( asmjit::host::rsp, stack_size );
a->ret();
a->setBaseAddress( _targetShare + hookDataOfs + FIELD_OFFSET( HookData, hook_code ) );
a->relocCode( (uint8_t*)_pSharedData + hookDataOfs + FIELD_OFFSET( HookData, hook_code ) );
}
///
/// Build hook trampoline
///
/// Hooked function type
/// Original function ptr
/// Original function address in local address space
void RemoteMemory::BuildTrampoline( OperationType opType, uintptr_t /*pOriginal*/, uint8_t* pOriginalLocal )
{
int hookDataOfs = sizeof( HookData ) * opType;
uint8_t * pJumpBuf = (uint8_t*)_pSharedData + hookDataOfs + FIELD_OFFSET( HookData, jump_buf );
memcpy( (uint8_t*)_pSharedData + hookDataOfs + FIELD_OFFSET( HookData, original_code ), pOriginalLocal, 12 );
// mov rax, pOriginal
// call rax
*(uint16_t*)pJumpBuf = 0xB848;
*(uint64_t*)(pJumpBuf + 2) = _targetShare + hookDataOfs + FIELD_OFFSET( HookData, hook_code );
*(uint16_t*)(pJumpBuf + 10) = 0xE0FF;
}
#else
///
/// Build remote hook function
///
/// Hooked function
void RemoteMemory::BuildGenericHookFn( OperationType opType )
{
static int argc[] = { 6, 4, 10, 2 };
static int allocIdx[] = { 1, 1, 2, 1 };
static int sizeIdx[] = { 3, 2, 6, -1 };
static int testIdx[] = { 4, -1, -1, -1 };
int hookDataOfs = sizeof( HookData ) * opType;
auto pAsm = AsmFactory::GetAssembler( _process->core().isWow64() );
auto& a = *pAsm;
AsmStackAllocator sa( a.assembler() );
asmjit::Label skip1 = a->newLabel();
ALLOC_STACK_VAR( sa, data, OperationData );
ALLOC_STACK_VAR( sa, junk, DWORD );
auto& modules = _process->modules();
auto pEnterCS = modules.GetExport( modules.GetModule( L"ntdll.dll" ), "RtlEnterCriticalSection" ).result( exportData() ).procAddress;
auto pLeaveCS = modules.GetExport( modules.GetModule( L"ntdll.dll" ), "RtlLeaveCriticalSection" ).result( exportData() ).procAddress;
auto pWrite = modules.GetExport( modules.GetModule( L"kernel32.dll" ), "WriteFile" ).result( exportData() ).procAddress;
a.GenPrologue();
a->sub( asmjit::host::esp, sa.getTotalSize() );
// Call original
for (int i = 0; i < argc[opType]; i++)
a->push( asmjit::host::dword_ptr( asmjit::host::ebp, (argc[opType] - i) * 4 + 4 ) );
a->call( _targetShare + hookDataOfs + FIELD_OFFSET( HookData, original_code ) );
// Test if call was successful
a->test( asmjit::host::eax, asmjit::host::eax );
a->jnz( skip1 );
if (testIdx[opType] != -1)
{
// Test if memory was committed
a->mov( asmjit::host::eax, asmjit::host::dword_ptr( asmjit::host::ebp, 8 + testIdx[opType] * 4 ) );
a->cmp( asmjit::host::eax, MEM_COMMIT );
a->jne( skip1 );
}
// RtlEnterCriticalSection
a.GenCall( (uintptr_t)pEnterCS, { (uintptr_t)_targetShare + FIELD_OFFSET( PageContext, csLock ) } );
// Storage pointer
a->lea( asmjit::host::edx, data );
//
// Allocation address
//
a->mov( asmjit::host::eax, asmjit::host::dword_ptr( asmjit::host::ebp, 8 + 4 * allocIdx[opType] ) );
if (opType != MemUnmapSection)
a->mov( asmjit::host::eax, asmjit::host::dword_ptr( asmjit::host::eax ) );
a->mov( asmjit::host::dword_ptr( asmjit::host::edx, FIELD_OFFSET( OperationData, allocAddress ) ), asmjit::host::eax );
a->mov( asmjit::host::dword_ptr( asmjit::host::edx, FIELD_OFFSET( OperationData, allocAddress ) + 4 ), 0 );
//
// Region size
//
if (sizeIdx[opType] != -1)
{
a->mov( asmjit::host::eax, asmjit::host::dword_ptr( asmjit::host::ebp, 8 + 4 * sizeIdx[opType] ) );
a->mov( asmjit::host::eax, asmjit::host::dword_ptr( asmjit::host::eax ) );
}
else
a->xor_( asmjit::host::eax, asmjit::host::eax );
a->mov( asmjit::host::dword_ptr( asmjit::host::edx, FIELD_OFFSET( OperationData, allocSize ) ), asmjit::host::eax );
// Operation type
a->mov( asmjit::host::dword_ptr( asmjit::host::edx, FIELD_OFFSET( OperationData, allocType ) ), opType );
a.GenCall( (uintptr_t)pWrite, { (uintptr_t)_targetPipe, asmjit::host::edx, sizeof( OperationData ), &junk, 0 } );
a.GenCall( (uintptr_t)pLeaveCS, { (uintptr_t)_targetShare + FIELD_OFFSET( PageContext, csLock ) } );
// Ignore return value
a->xor_( asmjit::host::eax, asmjit::host::eax );
a->bind( skip1 );
a.GenEpilogue( false, argc[opType] * 4 );
a->setBaseAddress( _targetShare + hookDataOfs + FIELD_OFFSET( HookData, hook_code ) );
a->relocCode( (uint8_t*)_pSharedData + hookDataOfs + FIELD_OFFSET( HookData, hook_code ) );
}
///
/// Build hook trampoline
///
/// Hooked function type
/// Original function ptr
/// Original function address in local address space
void RemoteMemory::BuildTrampoline( OperationType opType, uintptr_t pOriginal, uint8_t* pOriginalLocal )
{
int hookDataOfs = sizeof( HookData ) * opType;
uintptr_t pNew = (uintptr_t)_targetShare + hookDataOfs + FIELD_OFFSET( HookData, hook_code );
uintptr_t pTrampoline = (uintptr_t)_targetShare + hookDataOfs + FIELD_OFFSET( HookData, original_code );
uint8_t* pJumpBuf = (uint8_t*)_pSharedData + hookDataOfs + FIELD_OFFSET( HookData, jump_buf );
uint8_t* pOriginalBuf = (uint8_t*)_pSharedData + hookDataOfs + FIELD_OFFSET( HookData, original_code );
memcpy( (uint8_t*)_pSharedData + hookDataOfs + FIELD_OFFSET( HookData, original_code ), pOriginalLocal, 5 );
pJumpBuf[0] = 0xE9;
*(int32_t*)(pJumpBuf + 1) = pNew - pOriginal - 5;
pOriginalBuf[5] = 0xE9;
*(int32_t*)(pOriginalBuf + 6) = (pOriginal + 5) - (int32_t)(pTrampoline + 5) - 5;
}
#endif
}