NTSTATUS ProcessMemory::Read(ptr_t dwAddress, size_t dwSize, PVOID pResult, bool handleHoles /*= false*/) { DWORD64 dwRead = 0; if(dwAddress == 0) return LastNtStatus(STATUS_INVALID_ADDRESS); LastNtStatus(STATUS_SUCCESS); // Simple read if(!handleHoles) { return _core.GetNative()->ReadProcessMemoryT(dwAddress, pResult, dwSize, &dwRead); } // Read all committed memory regions else { MEMORY_BASIC_INFORMATION64 mbi = {0}; for(ptr_t memptr = dwAddress; memptr < dwAddress + dwSize; memptr = mbi.BaseAddress + mbi.RegionSize) { if(_core.GetNative()->VirtualQueryExT(memptr, &mbi) != STATUS_SUCCESS) continue; // Filter empty regions if(mbi.State != MEM_COMMIT || mbi.Protect == PAGE_NOACCESS) continue; uint64_t region_ptr = memptr - dwAddress; if(_core.GetNative()->ReadProcessMemoryT(mbi.BaseAddress, reinterpret_cast<uint8_t*>(pResult) + region_ptr, static_cast<size_t>(mbi.RegionSize), &dwRead) != STATUS_SUCCESS) { return LastNtStatus(); } } } return STATUS_SUCCESS; }
#include "Wow64Subsystem.h" #include "../Misc/DynImport.h" #include "../Include/Macro.h" namespace blackbone { NativeWow64::NativeWow64( HANDLE hProcess ) : Native( hProcess ) { HMODULE ntdll32 = GetModuleHandleW( L"Ntdll.dll" ); DynImport::load( "NtWow64QueryInformationProcess64", ntdll32 ); DynImport::load( "NtWow64AllocateVirtualMemory64", ntdll32 ); DynImport::load( "NtWow64QueryVirtualMemory64", ntdll32 ); DynImport::load( "NtWow64ReadVirtualMemory64", ntdll32 ); DynImport::load( "NtWow64WriteVirtualMemory64", ntdll32 ); } NativeWow64::~NativeWow64() { } /// <summary> /// Allocate virtual memory /// </summary> /// <param name="lpAddress">Allocation address</param> /// <param name="dwSize">Region size</param> /// <param name="flAllocationType">Allocation type</param> /// <param name="flProtect">Memory protection</param> /// <returns>Status code</returns> NTSTATUS NativeWow64::VirualAllocExT( ptr_t& lpAddress, size_t dwSize, DWORD flAllocationType, DWORD flProtect ) { DWORD64 size64 = dwSize; static ptr_t ntavm = _local.GetProcAddress64( _local.getNTDLL64(), "NtAllocateVirtualMemory" ); if (ntavm == 0) return STATUS_ORDINAL_NOT_FOUND; return static_cast<NTSTATUS>(_local.X64Call( ntavm, _hProcess, &lpAddress, 0, &size64, flAllocationType, flProtect )); } /// <summary> /// Free virtual memory /// </summary> /// <param name="lpAddress">Memory address</param> /// <param name="dwSize">Region size</param> /// <param name="dwFreeType">Memory release type.</param> /// <returns>Status code</returns> NTSTATUS NativeWow64::VirualFreeExT( ptr_t lpAddress, size_t dwSize, DWORD dwFreeType ) { static ptr_t ntfvm = _local.GetProcAddress64( _local.getNTDLL64( ), "NtFreeVirtualMemory" ); if (ntfvm == 0) return STATUS_ORDINAL_NOT_FOUND; DWORD64 tmpAddr = lpAddress; DWORD64 tmpSize = dwSize; return static_cast<NTSTATUS>(_local.X64Call( ntfvm, _hProcess, &tmpAddr, &tmpSize, dwFreeType )); } /// <summary> /// Query virtual memory /// </summary> /// <param name="lpAddress">Address to query</param> /// <param name="lpBuffer">Retrieved memory info</param> /// <returns>Status code</returns> NTSTATUS NativeWow64::VirtualQueryExT( ptr_t lpAddress, PMEMORY_BASIC_INFORMATION64 lpBuffer ) { return GET_IMPORT( NtWow64QueryVirtualMemory64 )( _hProcess, lpAddress, 0, lpBuffer, sizeof(MEMORY_BASIC_INFORMATION64), nullptr ); } /// <summary> /// Query virtual memory /// </summary> /// <param name="lpAddress">Address to query</param> /// <param name="lpBuffer">Retrieved memory info</param> /// <returns>Status code</returns> NTSTATUS NativeWow64::VirtualQueryExT( ptr_t lpAddress, MEMORY_INFORMATION_CLASS infoClass, LPVOID lpBuffer, size_t bufSize ) { static ptr_t ntqvm = _local.GetProcAddress64( _local.getNTDLL64(), "NtQueryVirtualMemory" ); if (ntqvm == 0) return STATUS_ORDINAL_NOT_FOUND; return static_cast<NTSTATUS>(_local.X64Call( ntqvm, _hProcess, lpAddress, infoClass, lpBuffer, bufSize, 0 )); } /// <summary> /// Change memory protection /// </summary> /// <param name="lpAddress">Memory address.</param> /// <param name="dwSize">Region size</param> /// <param name="flProtect">New protection.</param> /// <param name="flOld">Old protection</param> /// <returns>Status code</returns> NTSTATUS NativeWow64::VirtualProtectExT( ptr_t lpAddress, DWORD64 dwSize, DWORD flProtect, DWORD* flOld ) { static ptr_t ntpvm = _local.GetProcAddress64( _local.getNTDLL64(), "NtProtectVirtualMemory" ); if (ntpvm == 0) return STATUS_ORDINAL_NOT_FOUND; return static_cast<NTSTATUS>(_local.X64Call( ntpvm, _hProcess, &lpAddress, &dwSize, flProtect, flOld )); } /// <summary> /// Read virtual memory /// </summary> /// <param name="lpBaseAddress">Memory address</param> /// <param name="lpBuffer">Output buffer</param> /// <param name="nSize">Number of bytes to read</param> /// <param name="lpBytes">Mumber of bytes read</param> /// <returns>Status code</returns> NTSTATUS NativeWow64::ReadProcessMemoryT( ptr_t lpBaseAddress, LPVOID lpBuffer, size_t nSize, DWORD64 *lpBytes /*= nullptr */ ) { DWORD64 junk = 0; if (lpBytes == nullptr) lpBytes = &junk; return GET_IMPORT( NtWow64ReadVirtualMemory64 )( _hProcess, lpBaseAddress, lpBuffer, nSize, lpBytes ); } /// <summary> /// Write virtual memory /// </summary> /// <param name="lpBaseAddress">Memory address</param> /// <param name="lpBuffer">Buffer to write</param> /// <param name="nSize">Number of bytes to read</param> /// <param name="lpBytes">Mumber of bytes read</param> /// <returns>Status code</returns> NTSTATUS NativeWow64::WriteProcessMemoryT( ptr_t lpBaseAddress, LPCVOID lpBuffer, size_t nSize, DWORD64 *lpBytes /*= nullptr */ ) { DWORD64 junk = 0; if (lpBytes == nullptr) lpBytes = &junk; return GET_IMPORT( NtWow64WriteVirtualMemory64 )( _hProcess, lpBaseAddress, (LPVOID)lpBuffer, nSize, lpBytes ); } /// <summary> /// Call NtQueryInformationProcess for underlying process /// </summary> /// <param name="infoClass">Information class</param> /// <param name="lpBuffer">Output buffer</param> /// <param name="bufSize">Buffer size</param> /// <returns>Status code</returns> NTSTATUS NativeWow64::QueryProcessInfoT( PROCESSINFOCLASS infoClass, LPVOID lpBuffer, uint32_t bufSize ) { ULONG length = 0; return GET_IMPORT( NtWow64QueryInformationProcess64 )(_hProcess, infoClass, lpBuffer, bufSize, &length); } /// <summary> /// Call NtSetInformationProcess for underlying process /// </summary> /// <param name="infoClass">Information class</param> /// <param name="lpBuffer">Input buffer</param> /// <param name="bufSize">Buffer size</param> /// <returns>Status code</returns> NTSTATUS NativeWow64::SetProcessInfoT( PROCESSINFOCLASS infoClass, LPVOID lpBuffer, uint32_t bufSize ) { static ptr_t ntspi = _local.GetProcAddress64( _local.getNTDLL64(), "NtSetInformationProcess" ); if (ntspi == 0) return STATUS_ORDINAL_NOT_FOUND; return static_cast<NTSTATUS>(_local.X64Call( ntspi, _hProcess, infoClass, lpBuffer, bufSize )); } /// <summary> /// Creates new thread in the remote process /// </summary> /// <param name="hThread">Created thread handle</param> /// <param name="entry">Thread entry point</param> /// <param name="arg">Thread argument</param> /// <param name="flags">Creation flags</param> /// <returns>Status code</returns>*/ NTSTATUS NativeWow64::CreateRemoteThreadT( HANDLE& hThread, ptr_t entry, ptr_t arg, CreateThreadFlags flags, DWORD access ) { // Try to use default routine if possible /*if(_wowBarrier.targetWow64 == true) { return Native::CreateRemoteThreadT( hThread, entry, arg, flags, access ); } else*/ { LastNtStatus( STATUS_SUCCESS ); static DWORD64 NtCreateThreadEx = _local.GetProcAddress64( _local.getNTDLL64(), "NtCreateThreadEx" ); if (NtCreateThreadEx == 0) return LastNtStatus( STATUS_ORDINAL_NOT_FOUND ); // hThread can't be used directly because x64Call will zero stack space near variable DWORD64 hThd2 = NULL; NTSTATUS status = static_cast<NTSTATUS>(_local.X64Call( NtCreateThreadEx, &hThd2, access, NULL, _hProcess, entry, arg, flags, 0, 0x1000, 0x100000, NULL )); hThread = reinterpret_cast<HANDLE>(hThd2); return status; } }
/// <summary> /// Creates new thread in the remote process /// </summary> /// <param name="hThread">Created thread handle</param> /// <param name="entry">Thread entry point</param> /// <param name="arg">Thread argument</param> /// <param name="flags">Creation flags</param> /// <param name="access">Access override</param> /// <returns>Status code</returns> NTSTATUS Native::CreateRemoteThreadT( HANDLE& hThread, ptr_t entry, ptr_t arg, CreateThreadFlags flags, DWORD access /*= THREAD_ALL_ACCESS*/ ) { LastNtStatus( STATUS_SUCCESS ); NTSTATUS status = 0; auto pCreateThread = GET_IMPORT( NtCreateThreadEx ); if (pCreateThread) { status = pCreateThread( &hThread, access, NULL, _hProcess, reinterpret_cast<PTHREAD_START_ROUTINE>(entry), reinterpret_cast<LPVOID>(arg), static_cast<DWORD>(flags), 0, 0x1000, 0x100000, NULL ); if (!NT_SUCCESS( status )) hThread = NULL; } else { DWORD win32Flags = 0; if (flags & CreateSuspended) win32Flags = CREATE_SUSPENDED; hThread = CreateRemoteThread( _hProcess, NULL, 0, reinterpret_cast<PTHREAD_START_ROUTINE>(entry), reinterpret_cast<LPVOID>(arg), win32Flags, NULL ); status = LastNtStatus(); } return status; }
/// <summary> /// Grant current process arbitrary privilege /// </summary> /// <param name="name">Privilege name</param> /// <returns>Status</returns> NTSTATUS Process::GrantPriviledge( const std::basic_string<TCHAR>& name ) { TOKEN_PRIVILEGES Priv, PrivOld; DWORD cbPriv = sizeof(PrivOld); HANDLE hToken; if (!OpenThreadToken( GetCurrentThread(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, FALSE, &hToken )) { if (GetLastError() != ERROR_NO_TOKEN) return LastNtStatus(); if (!OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken )) return LastNtStatus(); } Priv.PrivilegeCount = 1; Priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; LookupPrivilegeValue( NULL, name.c_str(), &Priv.Privileges[0].Luid ); if (!AdjustTokenPrivileges( hToken, FALSE, &Priv, sizeof(Priv), &PrivOld, &cbPriv )) { CloseHandle( hToken ); return LastNtStatus(); } if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) { CloseHandle( hToken ); return LastNtStatus(); } return STATUS_SUCCESS; }
/// <summary> /// Fix relocations if image wasn't loaded at base address /// </summary> /// <param name="pImage">image data</param> /// <returns>true on success</returns> bool MMap::RelocateImage( ImageContext* pImage ) { // Reloc delta size_t Delta = pImage->imgMem.ptr<size_t>() - static_cast<size_t>(pImage->PEImage.imageBase()); // No need to relocate if (Delta == 0) { LastNtStatus( STATUS_SUCCESS ); return true; } pe::RelocData* fixrec = reinterpret_cast<pe::RelocData*>(pImage->PEImage.DirectoryAddress( IMAGE_DIRECTORY_ENTRY_BASERELOC )); if (fixrec == nullptr) { // TODO: return proper error code LastNtStatus( STATUS_IMAGE_NOT_AT_BASE ); return false; } while (fixrec->BlockSize) { DWORD count = (fixrec->BlockSize - 8) >> 1; // records count for (DWORD i = 0; i < count; ++i) { WORD fixtype = (fixrec->Item[i].Type); // fixup type WORD fixoffset = (fixrec->Item[i].Offset) % 4096; // offset in 4K block // no fixup required if (fixtype == IMAGE_REL_BASED_ABSOLUTE) continue; // add delta if (fixtype == IMAGE_REL_BASED_HIGHLOW || fixtype == IMAGE_REL_BASED_DIR64) { size_t fixRVA = static_cast<ULONG>(fixoffset) + fixrec->PageRVA; size_t val = *reinterpret_cast<size_t*>( reinterpret_cast<size_t>(pImage->FileImage.base()) + fixoffset + fixrec->PageRVA) + Delta; // Apply relocation if (pImage->imgMem.Write( fixRVA, val ) != STATUS_SUCCESS) return false; } else { // TODO: support for all remaining relocations LastNtStatus( STATUS_INVALID_IMAGE_FORMAT ); return false; } } // next reloc entry fixrec = reinterpret_cast<pe::RelocData*>(reinterpret_cast<size_t>(fixrec) + fixrec->BlockSize); } return true; }
/// <summary> /// Allocate virtual memory /// </summary> /// <param name="lpAddress">Allocation address</param> /// <param name="dwSize">Region size</param> /// <param name="flAllocationType">Allocation type</param> /// <param name="flProtect">Memory protection</param> /// <returns>Status code</returns> NTSTATUS Native::VirualAllocExT( ptr_t& lpAddress, size_t dwSize, DWORD flAllocationType, DWORD flProtect ) { LastNtStatus( STATUS_SUCCESS ); lpAddress = reinterpret_cast<ptr_t> (VirtualAllocEx( _hProcess, reinterpret_cast<LPVOID>(lpAddress), dwSize, flAllocationType, flProtect )); return LastNtStatus(); }
/// <summary> /// Query virtual memory /// </summary> /// <param name="lpAddress">Address to query</param> /// <param name="lpBuffer">Retrieved memory info</param> /// <returns>Status code</returns> NTSTATUS Native::VirtualQueryExT( ptr_t lpAddress, PMEMORY_BASIC_INFORMATION64 lpBuffer ) { LastNtStatus( STATUS_SUCCESS ); VirtualQueryEx( _hProcess, reinterpret_cast<LPCVOID>(lpAddress), reinterpret_cast<PMEMORY_BASIC_INFORMATION>(lpBuffer), sizeof( MEMORY_BASIC_INFORMATION ) ); return LastNtStatus(); }
bool MMap::RelocateImage(ImageContext* pImage) { BLACBONE_TRACE(L"ManualMap: Relocating image '%ls'", pImage->FilePath.c_str()); // Reloc delta size_t Delta = pImage->imgMem.Ptr<size_t>() - static_cast<size_t>(pImage->peImage.ImageBase()); // No need to relocate if(Delta == 0) { BLACBONE_TRACE(L"ManualMap: No need for relocation"); LastNtStatus(STATUS_SUCCESS); return true; } auto start = pImage->peImage.DirectoryAddress(IMAGE_DIRECTORY_ENTRY_BASERELOC); auto end = start + pImage->peImage.DirectorySize(IMAGE_DIRECTORY_ENTRY_BASERELOC); RelocData* fixrec = reinterpret_cast<RelocData*>(start); if(fixrec == nullptr) { // TODO: return proper error code BLACBONE_TRACE(L"ManualMap: Can't relocate image, no relocation data"); LastNtStatus(STATUS_IMAGE_NOT_AT_BASE); return false; } while((size_t)fixrec < end && fixrec->BlockSize) { DWORD count = (fixrec->BlockSize - 8) >> 1; // records count for(DWORD i = 0; i < count; ++i) { WORD fixtype = (fixrec->Item[i].Type); // fixup type WORD fixoffset = (fixrec->Item[i].Offset) % 4096; // offset in 4K block // no fixup required if(fixtype == IMAGE_REL_BASED_ABSOLUTE) continue; // add delta if(fixtype == IMAGE_REL_BASED_HIGHLOW || fixtype == IMAGE_REL_BASED_DIR64) { size_t fixRVA = fixoffset + fixrec->PageRVA; size_t val = *reinterpret_cast<size_t*>(pImage->peImage.ResolveRVAToVA(fixoffset + fixrec->PageRVA)) + Delta; auto status = STATUS_SUCCESS; if(pImage->flags & HideVAD) status = Driver().WriteMem(_process.Id(), pImage->imgMem.Ptr() + fixRVA, sizeof(val), &val); else status = pImage->imgMem.Write(fixRVA, val); // Apply relocation if(!NT_SUCCESS(status)) { BLACBONE_TRACE(L"ManualMap: Failed to apply relocation at offset 0x%x. Status = 0x%x", fixRVA, status); return false; } } else { // TODO: support for all remaining relocations BLACBONE_TRACE(L"ManualMap: Abnormal relocation type %d. Aborting", fixtype); LastNtStatus(STATUS_INVALID_IMAGE_FORMAT); return false; } } // next reloc entry fixrec = reinterpret_cast<RelocData*>(reinterpret_cast<size_t>(fixrec) + fixrec->BlockSize); } return true; }
/// <summary> /// Create new thread and execute code in it. Wait until execution ends /// </summary> /// <param name="pCode">Code to execute</param> /// <param name="size">Code size</param> /// <param name="callResult">Code return value</param> /// <returns>Status</returns> NTSTATUS RemoteExec::ExecInNewThread( PVOID pCode, size_t size, uint64_t& callResult ) { AsmJitHelper a; NTSTATUS dwResult = STATUS_SUCCESS; // Write code dwResult = CopyCode( pCode, size ); if (dwResult != STATUS_SUCCESS) return dwResult; bool switchMode = (_proc.core().native()->GetWow64Barrier().type == wow_64_32); auto pExitThread = _mods.GetExport( _mods.GetModule( L"ntdll.dll", LdrList, switchMode ? mt_mod64 : mt_default ), "NtTerminateThread" ).procAddress; if (pExitThread == 0) return LastNtStatus( STATUS_NOT_FOUND ); a.GenPrologue( switchMode ); // Prepare thread to run in x64 mode if(switchMode) { // Allocate new x64 activation stack auto createActStack = _mods.GetExport( _mods.GetModule( L"ntdll.dll", LdrList, mt_mod64 ), "RtlAllocateActivationContextStack" ).procAddress; if (createActStack) { a.GenCall( static_cast<size_t>(createActStack), { _userData.ptr<size_t>() + 0x3100 } ); a->mov( a->zax, _userData.ptr<size_t>( ) + 0x3100 ); a->mov( a->zax, a->intptr_ptr( a->zax ) ); a.SetTebPtr(); a->mov( a->intptr_ptr( a->zdx, 0x2C8 ), a->zax ); } } a.GenCall( _userCode.ptr<size_t>(), { } ); a.ExitThreadWithStatus( (size_t)pExitThread, _userData.ptr<size_t>() + INTRET_OFFSET ); // Execute code in newly created thread if (_userCode.Write( size, a->getCodeSize(), a->make() ) == STATUS_SUCCESS) { auto thread = _threads.CreateNew( _userCode.ptr<ptr_t>() + size, _userData.ptr<ptr_t>()/*, HideFromDebug*/ ); dwResult = thread.Join(); callResult = _userData.Read<uint64_t>( INTRET_OFFSET, 0 ); } else dwResult = LastNtStatus(); return dwResult; }
/// <summary> /// Change memory protection /// </summary> /// <param name="lpAddress">Memory address.</param> /// <param name="dwSize">Region size</param> /// <param name="flProtect">New protection.</param> /// <param name="flOld">Old protection</param> /// <returns>Status code</returns> NTSTATUS Native::VirtualProtectExT( ptr_t lpAddress, DWORD64 dwSize, DWORD flProtect, DWORD* flOld ) { DWORD junk = 0; if (!flOld) flOld = &junk; LastNtStatus( STATUS_SUCCESS ); VirtualProtectEx( _hProcess, reinterpret_cast<LPVOID>(lpAddress), static_cast<SIZE_T>(dwSize), flProtect, flOld ); return LastNtStatus(); }
/// <summary> /// Set WOW64 thread context /// </summary> /// <param name="hThread">Thread handle.</param> /// <param name="ctx">Thread context</param> /// <returns>Status code</returns> NTSTATUS NativeWow64::SetThreadContextT( HANDLE hThread, _CONTEXT32& ctx ) { // Target process is x64. 32bit CONTEXT is not available. if (_wowBarrier.targetWow64 == false) { return STATUS_NOT_SUPPORTED; } else { LastNtStatus( STATUS_SUCCESS ); SetThreadContext( hThread, reinterpret_cast<const CONTEXT*>(&ctx) ); return LastNtStatus(); } }
/// <summary> /// Set WOW64 thread context /// </summary> /// <param name="hThread">Thread handle.</param> /// <param name="ctx">Thread context</param> /// <returns>Status code</returns> NTSTATUS Native::SetThreadContextT( HANDLE hThread, _CONTEXT32& ctx ) { // Target process is x64. 32bit CONTEXT is not available. if (_wowBarrier.targetWow64 == false) { return 0; } else { LastNtStatus( STATUS_SUCCESS ); SAFE_CALL( Wow64SetThreadContext, hThread, reinterpret_cast<PWOW64_CONTEXT>(&ctx)); return LastNtStatus(); } }
/// <summary> /// Copy executable code into remote codecave for future execution /// </summary> /// <param name="pCode">Code to copy</param> /// <param name="size">Code size</param> /// <returns>Status</returns> NTSTATUS RemoteExec::CopyCode( PVOID pCode, size_t size ) { if (!_userCode.valid()) _userCode = _memory.Allocate( size ); // Reallocate for larger code if (size > _userCode.size()) if ((_userCode.Realloc(size)) == 0) return LastNtStatus(); if (_userCode.Write( 0, size, pCode ) != STATUS_SUCCESS) return LastNtStatus(); return STATUS_SUCCESS; }
/// <summary> /// Reload driver /// </summary> /// <param name="path">Path to the driver file</param> /// <returns>Status code</returns> NTSTATUS DriverControl::Reload( std::wstring path /*= L"" */ ) { NTSTATUS status = STATUS_SUCCESS; Unload(); // Use default path if (path.empty()) { const wchar_t* filename = nullptr; if (IsWindows10OrGreater()) filename = L"BlackBoneDrv10.sys"; else if (IsWindows8Point1OrGreater()) filename = L"BlackBoneDrv81.sys"; else if (IsWindows8OrGreater()) filename = L"BlackBoneDrv8.sys"; else if (IsWindows7OrGreater()) filename = L"BlackBoneDrv7.sys"; else filename = L"BlackBoneDrv.sys"; path = Utils::GetExeDirectory() + L"\\" + filename; } status = _loadStatus = LoadDriver( DRIVER_SVC_NAME, path ); if (!NT_SUCCESS( status )) { BLACBONE_TRACE( L"Failed to load driver %ls. Status 0x%X", path.c_str(), status ); return LastNtStatus( status ); } _hDriver = CreateFileW( BLACKBONE_DEVICE_FILE, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL ); if (_hDriver == INVALID_HANDLE_VALUE) { status = LastNtStatus(); BLACBONE_TRACE( L"Failed to open driver handle. Status 0x%X", status ); return status; } return status; }
/// <summary> /// Create environment for future remote procedure calls /// /// _userData layout (x86/x64): /// -------------------------------------------------------------------------------------------------------------------------- /// | Internal return value | Return value | Last Status code | Event handle | Space for copied arguments and strings | /// ------------------------------------------------------------------------------------------------------------------------- /// | 8/8 bytes | 8/8 bytes | 8/8 bytes | 16/16 bytes | | /// -------------------------------------------------------------------------------------------------------------------------- /// </summary> /// <param name="noThread">Create only codecave and sync event, without thread</param> /// <returns>Status</returns> NTSTATUS RemoteExec::CreateRPCEnvironment( bool noThread /*= false*/ ) { NTSTATUS dwResult = STATUS_SUCCESS; DWORD thdID = 0; bool status = true; // // Allocate environment codecave // if (!_workerCode.valid()) _workerCode = _memory.Allocate( 0x1000 ); if (!_userData.valid()) _userData = _memory.Allocate( 0x4000, PAGE_READWRITE ); if (!_userCode.valid()) _userCode = _memory.Allocate( 0x1000 ); // Create RPC thread and sync event if (noThread == false) { thdID = CreateWorkerThread(); if (thdID) status = CreateAPCEvent( thdID ); } if (thdID == 0 || status == false) dwResult = LastNtStatus(); return dwResult; }
/// <summary> /// Maps single memory region into current process /// </summary> /// <param name="pid">Target PID</param> /// <param name="base">Region base address</param> /// <param name="size">Region size</param> /// <param name="result">Mapped region info</param> /// <returns>Status code</returns> NTSTATUS DriverControl::MapMemoryRegion( DWORD pid, ptr_t base, uint32_t size, MapMemoryRegionResult& result ) { MAP_MEMORY_REGION data = { 0 }; MAP_MEMORY_REGION_RESULT mapResult = { 0 }; DWORD bytes = 0; // Not loaded if (_hDriver == INVALID_HANDLE_VALUE) return STATUS_DEVICE_DOES_NOT_EXIST; data.pid = pid; data.base = base; data.size = size; if (DeviceIoControl( _hDriver, IOCTL_BLACKBONE_MAP_REGION, &data, sizeof( data ), &mapResult, sizeof( mapResult ), &bytes, NULL )) { result.newPtr = mapResult.newPtr; result.originalPtr = mapResult.originalPtr; result.removedPtr = mapResult.removedPtr; result.removedSize = mapResult.removedSize; result.size = mapResult.size; return STATUS_SUCCESS; } return LastNtStatus(); }
/// <summary> /// Free virtual memory /// </summary> /// <param name="pid">Tarhet PID</param> /// <param name="base">Desired base. If 0 address is chosed by the system</param> /// <param name="size">Region size</param> /// <param name="type">Free type - MEM_RELEASE/MEM_DECOMMIT</param> /// <returns>Status code</returns> NTSTATUS DriverControl::FreeMem( DWORD pid, ptr_t base, ptr_t size, DWORD type ) { DWORD bytes = 0; ALLOCATE_FREE_MEMORY freeMem = { 0 }; ALLOCATE_FREE_MEMORY_RESULT result = { 0 }; freeMem.pid = pid; freeMem.base = base; freeMem.size = size; freeMem.type = type; freeMem.allocate = FALSE; freeMem.physical = FALSE; // Not loaded if (_hDriver == INVALID_HANDLE_VALUE) return STATUS_DEVICE_DOES_NOT_EXIST; if (!DeviceIoControl( _hDriver, IOCTL_BLACKBONE_ALLOCATE_FREE_MEMORY, &freeMem, sizeof( freeMem ), &result, sizeof( result ), &bytes, NULL )) { return LastNtStatus(); } return STATUS_SUCCESS; }
/// <summary> /// Reallocate existing block for new size /// </summary> /// <param name="size">New block size</param> /// <param name="desired">Desired base address of new block</param> /// <param name="protection">Memory protection</param> /// <returns>New block address</returns> ptr_t MemBlock::Realloc( size_t size, ptr_t desired /*= 0*/, DWORD protection /*= PAGE_EXECUTE_READWRITE*/ ) { ptr_t desired64 = desired; _memory->core().native()->VirualAllocExT( desired64, size, MEM_COMMIT, protection ); if (!desired64) { desired64 = 0; _memory->core( ).native( )->VirualAllocExT( desired64, size, MEM_COMMIT, protection ); if (desired64) LastNtStatus( STATUS_IMAGE_NOT_AT_BASE ); } // Replace current instance if (desired64) { Free(); _ptr = desired64; _size = size; _protection = protection; } return desired64; }
/// <summary> /// Inject DLL into arbitrary process /// </summary> /// <param name="pid">Target PID</param> /// <param name="path">Full qualified dll path</param> /// <param name="itype">Injection type</param> /// <param name="initRVA">Init routine RVA</param> /// <param name="initArg">Init routine argument</param> /// <param name="unlink">Unlink module after injection</param> /// <param name="erasePE">Erase PE headers after injection</param> /// <param name="wait">Wait for injection</param> /// <returns>Status code</returns> NTSTATUS DriverControl::InjectDll( DWORD pid, const std::wstring& path, InjectType itype, uint32_t initRVA /*= 0*/, const std::wstring& initArg /*= L""*/, bool unlink /*= false*/, bool erasePE /*= false*/, bool wait /*= true*/ ) { DWORD bytes = 0; INJECT_DLL data = { IT_Thread }; // Not loaded if (_hDriver == INVALID_HANDLE_VALUE) return STATUS_DEVICE_DOES_NOT_EXIST; wcscpy_s( data.FullDllPath, path.c_str() ); wcscpy_s( data.initArg, initArg.c_str() ); data.type = itype; data.pid = pid; data.initRVA = initRVA; data.wait = wait; data.unlink = unlink; data.erasePE = erasePE; if (!DeviceIoControl( _hDriver, IOCTL_BLACKBONE_INJECT_DLL, &data, sizeof( data ), nullptr, 0, &bytes, NULL )) return LastNtStatus(); return STATUS_SUCCESS; }
/// <summary> /// Allocate virtual memory /// </summary> /// <param name="pid">Tarhet PID</param> /// <param name="base">Desired base. If 0 address is chosed by the system</param> /// <param name="size">Region size</param> /// <param name="type">Allocation type - MEM_RESERVE/MEM_COMMIT</param> /// <param name="protection">Memory protection</param> /// <returns>Status code</returns> NTSTATUS DriverControl::AllocateMem( DWORD pid, ptr_t& base, ptr_t& size, DWORD type, DWORD protection, bool physical /*= false*/ ) { DWORD bytes = 0; ALLOCATE_FREE_MEMORY allocMem = { 0 }; ALLOCATE_FREE_MEMORY_RESULT result = { 0 }; allocMem.pid = pid; allocMem.base = base; allocMem.size = size; allocMem.type = type; allocMem.protection = protection; allocMem.allocate = TRUE; allocMem.physical = physical; // Not loaded if (_hDriver == INVALID_HANDLE_VALUE) return STATUS_DEVICE_DOES_NOT_EXIST; if (!DeviceIoControl( _hDriver, IOCTL_BLACKBONE_ALLOCATE_FREE_MEMORY, &allocMem, sizeof( allocMem ), &result, sizeof( result ), &bytes, NULL )) { size = base = 0; return LastNtStatus(); } base = result.address; size = result.size; return STATUS_SUCCESS; }
/// <summary> /// Manually map PE image /// </summary> /// <param name="pid">Target PID</param> /// <param name="address">Memory location of the image to map</param> /// <param name="size">Image size</param> /// <param name="asImage">Memory chunk has image layout</param> /// <param name="flags">Mapping flags</param> /// <param name="initRVA">Init routine RVA</param> /// <param name="initArg">Init routine argument</param> /// <returns>Status code</returns> NTSTATUS DriverControl::MmapDll( DWORD pid, void* address, uint32_t size, bool asImage, KMmapFlags flags, uint32_t initRVA /*= 0*/, const std::wstring& initArg /*= L"" */ ) { DWORD bytes = 0; INJECT_DLL data = { IT_MMap }; memset( data.FullDllPath, 0, sizeof( data.FullDllPath ) ); wcscpy_s( data.initArg, initArg.c_str() ); data.pid = pid; data.initRVA = initRVA; data.wait = true; data.unlink = false; data.erasePE = false; data.flags = flags; data.imageBase = (ULONGLONG)address; data.imageSize = size; data.asImage = asImage; if (!DeviceIoControl( _hDriver, IOCTL_BLACKBONE_INJECT_DLL, &data, sizeof( data ), nullptr, 0, &bytes, NULL )) return LastNtStatus(); return STATUS_SUCCESS; }
/// <summary> /// Manually map PE image /// </summary> /// <param name="pid">Target PID</param> /// <param name="path">Full qualified image path</param> /// <param name="flags">Mapping flags</param> /// <param name="initRVA">Init routine RVA</param> /// <param name="initArg">Init routine argument</param> /// <returns>Status code</returns> NTSTATUS DriverControl::MmapDll( DWORD pid, const std::wstring& path, KMmapFlags flags, uint32_t initRVA /*= 0*/, const std::wstring& initArg /*= L"" */ ) { DWORD bytes = 0; INJECT_DLL data = { IT_MMap }; UNICODE_STRING ustr = { 0 }; // Convert path to native format SAFE_NATIVE_CALL( RtlDosPathNameToNtPathName_U, path.c_str(), &ustr, nullptr, nullptr ); wcscpy_s( data.FullDllPath, ustr.Buffer ); SAFE_CALL( RtlFreeUnicodeString, &ustr ); wcscpy_s( data.initArg, initArg.c_str() ); data.pid = pid; data.initRVA = initRVA; data.wait = true; data.unlink = false; data.erasePE = false; data.flags = flags; data.imageBase = 0; data.imageSize = 0; data.asImage = false; if (!DeviceIoControl( _hDriver, IOCTL_BLACKBONE_INJECT_DLL, &data, sizeof( data ), nullptr, 0, &bytes, NULL )) return LastNtStatus(); return STATUS_SUCCESS; }
/// <summary> /// Free memory /// </summary> /// <param name="size">Size of memory chunk to free. If 0 - whole block is freed</param> NTSTATUS MemBlock::Free( size_t size /*= 0*/ ) { if (_ptr != 0) { 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; }
/// <summary> /// Add hardware breakpoint to thread /// </summary> /// <param name="addr">Breakpoint address</param> /// <param name="type">Breakpoint type(read/write/execute)</param> /// <param name="length">Number of bytes to include into breakpoint</param> /// <returns>Index of used breakpoint; -1 if failed</returns> int Thread::AddHWBP( ptr_t addr, HWBPType type, HWBPLength length ) { _CONTEXT64 context64 = { 0 }; _CONTEXT32 context32 = { 0 }; bool use64 = !_core->native()->GetWow64Barrier().x86OS; // CONTEXT_DEBUG_REGISTERS can be operated without thread suspension bool res = use64 ? GetContext( context64, CONTEXT64_DEBUG_REGISTERS, true ) : GetContext( context32, CONTEXT_DEBUG_REGISTERS, true ); auto pDR7 = use64 ? reinterpret_cast<regDR7*>(&context64.Dr7) : reinterpret_cast<regDR7*>(&context32.Dr7); if (!res) return -1; // Get free DR int freeIdx = pDR7->getFreeIndex(); // If all 4 registers are occupied - error if (freeIdx < 0) { LastNtStatus( STATUS_NO_MORE_ENTRIES ); return -1; } // Enable corresponding HWBP and local BP flag pDR7->l_enable = 1; pDR7->setLocal( freeIdx, 1 ); pDR7->setRW( freeIdx, static_cast<char>(type) ); pDR7->setLen( freeIdx, static_cast<char>(length) ); use64 ? *(&context64.Dr0 + freeIdx) = addr : *(&context32.Dr0 + freeIdx) = static_cast<DWORD>(addr); // Write values to registers res = use64 ? SetContext( context64, true ) : SetContext( context32, true ); return res ? freeIdx : -1; }
/// <summary> /// Generate assembly code for remote call. /// </summary> /// <param name="a">Underlying assembler object</param> /// <param name="pfn">Remote function pointer</param> /// <param name="args">Function arguments</param> /// <param name="cc">Calling convention</param> /// <param name="retType">Return type</param> /// <returns>true on success</returns> bool RemoteExec::PrepareCallAssembly( AsmHelperBase& a, const void* pfn, std::vector<AsmVariant>& args, eCalligConvention cc, eReturnType retType ) { size_t data_offset = ARGS_OFFSET; // Invalid calling convention if (cc < cc_cdecl || cc > cc_fastcall) { LastNtStatus( STATUS_INVALID_PARAMETER_3 ); return false; } // Copy structures and strings for (auto& arg : args) { if (arg.type == AsmVariant::dataStruct || arg.type == AsmVariant::dataPtr) { _userData.Write( data_offset, arg.size, reinterpret_cast<const void*>(arg.imm_val) ); arg.new_imm_val = _userData.ptr<size_t>() + data_offset; // Add some padding after data data_offset += arg.size + 0x10; } } // Insert hidden variable if return type is struct. // This variable contains address of buffer in which return value is copied if (retType == rt_struct) { args.emplace( args.begin(), AsmVariant( _userData.ptr<size_t>() + ARGS_OFFSET ) ); args.front().new_imm_val = args.front().imm_val; args.front().type = AsmVariant::structRet; } a.GenPrologue(); a.GenCall( pfn, args, cc ); // Retrieve result from XMM0 or ST0 if (retType == rt_float || retType == rt_double) { a->mov( asmjit::host::zax, _userData.ptr<size_t>() + RET_OFFSET ); #ifdef USE64 if (retType == rt_double) a->movsd( asmjit::host::Mem( asmjit::host::zax, 0 ), asmjit::host::xmm0 ); else a->movss( asmjit::host::Mem( asmjit::host::zax, 0 ), asmjit::host::xmm0 ); #else a->fstp( asmjit::host::Mem( asmjit::host::zax, 0, retType * sizeof(float) ) ); #endif } AddReturnWithEvent( a, mt_default, retType ); a.GenEpilogue(); return true; }
/// <summary> /// Create environment for future remote procedure calls /// /// _userData layout (x86/x64): /// -------------------------------------------------------------------------------------------------------------------------- /// | Internal return value | Return value | Last Status code | Event handle | Space for copied arguments and strings | /// ------------------------------------------------------------------------------------------------------------------------- /// | 8/8 bytes | 8/8 bytes | 8/8 bytes | 16/16 bytes | | /// -------------------------------------------------------------------------------------------------------------------------- /// </summary> /// <param name="noThread">Create only codecave and sync event, without thread</param> /// <returns>Status</returns> NTSTATUS RemoteExec::CreateRPCEnvironment( bool noThread /*= false*/ ) { NTSTATUS dwResult = STATUS_SUCCESS; DWORD thdID = 0; bool status = true; // // Allocate environment codecave // if (!_workerCode.valid()) _workerCode = _memory.Allocate( 0x1000 ); if (!_userData.valid()) _userData = _memory.Allocate( 0x4000, PAGE_READWRITE ); if (!_userCode.valid()) _userCode = _memory.Allocate( 0x1000 ); // Create RPC thread and sync event if (noThread == false) thdID = CreateWorkerThread(); else // Randomize thread id for event name thdID = GetTickCount(); auto& barrier = _proc.core().native()->GetWow64Barrier(); if (barrier.type != wow_32_64) status = CreateAPCEvent( thdID ); if ((noThread == false && thdID == 0) || status == false) dwResult = LastNtStatus(); return dwResult; }
/// <summary> /// Query virtual memory /// </summary> /// <param name="lpAddress">Address to query</param> /// <param name="lpBuffer">Retrieved memory info</param> /// <returns>Status code</returns> NTSTATUS Native::VirtualQueryExT( ptr_t lpAddress, MEMORY_INFORMATION_CLASS infoClass, LPVOID lpBuffer, size_t bufSize ) { SIZE_T retLen = 0; LastNtStatus( STATUS_SUCCESS ); return SAFE_NATIVE_CALL( NtQueryVirtualMemory, _hProcess, reinterpret_cast<LPVOID>(lpAddress), infoClass, lpBuffer, bufSize, &retLen ); }
/// <summary> /// Execute code in context of our worker thread /// </summary> /// <param name="pCode">Cde to execute</param> /// <param name="size">Code size.</param> /// <param name="callResult">Execution result</param> /// <returns>Status</returns> NTSTATUS RemoteExec::ExecInWorkerThread( PVOID pCode, size_t size, uint64_t& callResult ) { NTSTATUS dwResult = STATUS_SUCCESS; // Create thread if needed CreateRPCEnvironment(); // Write code dwResult = CopyCode( pCode, size ); if (dwResult != STATUS_SUCCESS) return dwResult; if (_hWaitEvent) ResetEvent( _hWaitEvent ); // Patch KiUserApcDispatcher #ifdef USE64 if (!_apcPatched && IsWindows7OrGreater() && !IsWindows8OrGreater()) { if (_proc.core().native()->GetWow64Barrier().type == wow_64_32) { auto patchBase = _proc.nativeLdr().APC64PatchAddress(); if (patchBase != 0) { DWORD flOld = 0; _memory.Protect(patchBase, 6, PAGE_EXECUTE_READWRITE, &flOld); _memory.Write(patchBase + 0x2, (uint8_t)0x0C); _memory.Write( patchBase + 0x4, (uint8_t)0x90 ); _memory.Protect( patchBase, 6, flOld, nullptr ); } _apcPatched = true; } else _apcPatched = true; } #endif // Execute code in thread context if (QueueUserAPC( _userCode.ptr<PAPCFUNC>(), _hWorkThd.handle(), _userCode.ptr<ULONG_PTR>() )) { dwResult = WaitForSingleObject( _hWaitEvent, INFINITE ); callResult = _userData.Read<uint64_t>( RET_OFFSET, 0 ); } else return LastNtStatus(); // Ensure APC function fully returns Sleep( 1 ); return dwResult; }
/// <summary> /// Load arbitrary driver /// </summary> /// <param name="svcName">Driver service name</param> /// <param name="path">Driver file path</param> /// <returns>Status</returns> NTSTATUS DriverControl::LoadDriver( const std::wstring& svcName, const std::wstring& path ) { UNICODE_STRING Ustr; // If no file provided, try to start existing service if (!path.empty() && PrepareDriverRegEntry( svcName, path ) != 0) return LastNtStatus(); std::wstring regPath = L"\\registry\\machine\\SYSTEM\\CurrentControlSet\\Services\\" + svcName; SAFE_CALL( RtlInitUnicodeString, &Ustr, regPath.c_str() ); return SAFE_NATIVE_CALL( NtLoadDriver, &Ustr ); }
/// <summary> /// Map pure IL image /// Not supported yet /// </summary> /// <returns>Image base</returns> module_t MMap::MapPureManaged( ) { /*if(!netImg.Init(_pTopImage->FilePath)) { SetLastError(0x1337); return 0; }*/ //netImg.Parse(); LastNtStatus( STATUS_NOT_IMPLEMENTED ); return 0; }