#include "MemBlock.h"
#include "ProcessMemory.h"
#include "ProcessCore.h"
#include "../Subsystem/NativeSubsystem.h"
#include "../Misc/Trace.hpp"
namespace blackbone
{
///
/// MemBlock ctor
///
/// Process memory routines
/// Memory address
/// Block size
/// Memory protection
/// false if caller will be responsible for block deallocation
MemBlock::MemBlock(
ProcessMemory* mem,
ptr_t ptr,
size_t size,
DWORD prot,
bool own /*= true*/,
bool physical /*= false*/
)
: _pImpl( new MemBlockImpl( mem, ptr, size, prot, own, physical ) )
{
}
///
/// MemBlock_p ctor
///
/// Process memory routines
/// Memory address
/// Block size
/// Memory protection
/// false if caller will be responsible for block deallocation
MemBlock::MemBlockImpl::MemBlockImpl(
class ProcessMemory* mem,
ptr_t ptr, size_t size,
DWORD prot,
bool own /*= true*/,
bool physical /*= false */
)
: _ptr( ptr )
, _size( size )
, _protection( prot )
, _own( own )
, _physical( physical )
, _memory( mem )
{
}
///
/// MemBlock ctor
///
/// Process memory routines
/// Memory address
/// false if caller will be responsible for block deallocation
MemBlock::MemBlock( ProcessMemory* mem, ptr_t ptr, bool own /*= true*/ )
: _pImpl( new MemBlockImpl )
{
_pImpl->_ptr = ptr;
_pImpl->_own = own;
_pImpl->_memory = mem;
MEMORY_BASIC_INFORMATION64 mbi = { 0 };
mem->Query( _pImpl->_ptr, &mbi );
_pImpl->_protection = mbi.Protect;
_pImpl->_size = (size_t)mbi.RegionSize;
}
///
/// Allocate new memory block
///
/// Process memory routines
/// Block size
/// Desired base address of new block
/// Memory protection
/// false if caller will be responsible for block deallocation
/// Memory block. If failed - returned block will be invalid
call_result_t MemBlock::Allocate(
ProcessMemory& process,
size_t size,
ptr_t desired /*= 0*/,
DWORD protection /*= PAGE_EXECUTE_READWRITE */,
bool own /*= true*/
)
{
ptr_t desired64 = desired;
DWORD newProt = CastProtection( protection, process.core().DEP() );
NTSTATUS status = process.core().native()->VirtualAllocExT( desired64, size, MEM_RESERVE | MEM_COMMIT, newProt );
if (!NT_SUCCESS( status ))
{
desired64 = 0;
status = process.core().native()->VirtualAllocExT( desired64, size, MEM_COMMIT, newProt );
if (NT_SUCCESS( status ))
return call_result_t( MemBlock( &process, desired64, size, protection, own ), STATUS_IMAGE_NOT_AT_BASE );
else
return status;
}
#ifdef _DEBUG
BLACKBONE_TRACE(L"Allocate: Allocating at address 0x%p (0x%X bytes)", static_cast(desired64), size);
#endif
return MemBlock( &process, desired64, size, protection, own );
}
///
/// Reallocate existing block for new size
///
/// New block size
/// Desired base address of new block
/// Memory protection
/// New block address
call_result_t MemBlock::Realloc( size_t size, ptr_t desired /*= 0*/, DWORD protection /*= PAGE_EXECUTE_READWRITE*/ )
{
if (!_pImpl)
return STATUS_MEMORY_NOT_ALLOCATED;
ptr_t desired64 = desired;
auto status = _pImpl->_memory->core().native()->VirtualAllocExT( desired64, size, MEM_COMMIT, protection );
if (!desired64)
{
desired64 = 0;
status = _pImpl->_memory->core().native()->VirtualAllocExT( desired64, size, MEM_COMMIT, protection );
if (!NT_SUCCESS( status ))
return status;
status = STATUS_IMAGE_NOT_AT_BASE;
}
// Replace current instance
if (desired64)
{
Free();
_pImpl->_ptr = desired64;
_pImpl->_size = size;
_pImpl->_protection = protection;
}
return call_result_t( desired64, status );
}
///
/// Change memory protection
///
/// New protection flags
/// Memory offset in block
/// Block size
/// Old protection flags
/// Status
NTSTATUS MemBlock::Protect( DWORD protection, uintptr_t offset /*= 0*/, size_t size /*= 0*/, DWORD* pOld /*= nullptr */ )
{
if (!_pImpl)
return STATUS_MEMORY_NOT_ALLOCATED;
auto prot = CastProtection( protection, _pImpl->_memory->core().DEP() );
if (size == 0)
size = _pImpl->_size;
return _pImpl->_physical ? Driver().ProtectMem( _pImpl->_memory->core().pid(), _pImpl->_ptr + offset, size, prot ) :
_pImpl->_memory->Protect( _pImpl->_ptr + offset, size, prot, pOld );
}
///
/// Free memory
///
/// Size of memory chunk to free. If 0 - whole block is freed
NTSTATUS MemBlock::Free( size_t size /*= 0*/ )
{
if (!_pImpl)
return STATUS_MEMORY_NOT_ALLOCATED;
return _pImpl->Free();
}
///
/// Free memory
///
/// Size of memory chunk to free. If 0 - whole block is freed
NTSTATUS MemBlock::MemBlockImpl::Free( size_t size /*= 0 */ )
{
if (_ptr == 0)
return STATUS_MEMORY_NOT_ALLOCATED;
size = Align( size, 0x1000 );
NTSTATUS status = _physical ? Driver().FreeMem( _memory->core().pid(), _ptr, size, MEM_RELEASE ) :
_memory->Free( _ptr, size, size == 0 ? MEM_RELEASE : MEM_DECOMMIT );
if (!NT_SUCCESS( status ))
return LastNtStatus();
if (size == 0)
{
_ptr = 0;
_size = 0;
_protection = 0;
}
else
{
_ptr += size;
_size -= size;
}
return STATUS_SUCCESS;
}
///
/// Read data
///
/// Data offset in block
/// Size of data to read
/// Output buffer
///
/// If true, function will try to read all committed pages in range ignoring uncommitted.
/// Otherwise function will fail if there is at least one non-committed page in region.
///
/// Status
NTSTATUS MemBlock::Read( uintptr_t offset, size_t size, PVOID pResult, bool handleHoles /*= false*/ )
{
if (!_pImpl)
return STATUS_MEMORY_NOT_ALLOCATED;
return _pImpl->_memory->Read( _pImpl->_ptr + offset, size, pResult, handleHoles );
}
///
/// Write data
///
/// Data offset in block
/// Size of data to write
/// Buffer to write
/// Status
NTSTATUS MemBlock::Write( uintptr_t offset, size_t size, const void* pData )
{
if (!_pImpl)
return STATUS_MEMORY_NOT_ALLOCATED;
return _pImpl->_memory->Write( _pImpl->_ptr + offset, size, pData );
}
///
/// Try to free memory and reset pointers
///
void MemBlock::Reset()
{
_pImpl.reset();
}
}