#include "Loader.h" #include "Private.h" #include "Routines.h" #include "BlackBoneDef.h" #include "Utils.h" #include "apiset.h" #include #define IMAGE32(hdr) (hdr->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) #define IMAGE64(hdr) (hdr->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) #define HEADER_VAL_T(hdr, val) (IMAGE64(hdr) ? ((PIMAGE_NT_HEADERS64)hdr)->OptionalHeader.val : ((PIMAGE_NT_HEADERS32)hdr)->OptionalHeader.val) #define THUNK_VAL_T(hdr, ptr, val) (IMAGE64(hdr) ? ((PIMAGE_THUNK_DATA64)ptr)->val : ((PIMAGE_THUNK_DATA32)ptr)->val) #define TLS_VAL_T(hdr, ptr, val) (IMAGE64(hdr) ? ((PIMAGE_TLS_DIRECTORY64)ptr)->val : ((PIMAGE_TLS_DIRECTORY32)ptr)->val) #define CFG_DIR_VAL_T(hdr, dir, val) (IMAGE64(hdr) ? ((PIMAGE_LOAD_CONFIG_DIRECTORY64)dir)->val : ((PIMAGE_LOAD_CONFIG_DIRECTORY32)dir)->val) #if defined (_WIN10_) typedef PAPI_SET_VALUE_ENTRY_10 PAPISET_VALUE_ENTRY; typedef PAPI_SET_VALUE_ARRAY_10 PAPISET_VALUE_ARRAY; typedef PAPI_SET_NAMESPACE_ENTRY_10 PAPISET_NAMESPACE_ENTRY; typedef PAPI_SET_NAMESPACE_ARRAY_10 PAPISET_NAMESPACE_ARRAY; #elif defined (_WIN81_) typedef PAPI_SET_VALUE_ENTRY PAPISET_VALUE_ENTRY; typedef PAPI_SET_VALUE_ARRAY PAPISET_VALUE_ARRAY; typedef PAPI_SET_NAMESPACE_ENTRY PAPISET_NAMESPACE_ENTRY; typedef PAPI_SET_NAMESPACE_ARRAY PAPISET_NAMESPACE_ARRAY; #else typedef PAPI_SET_VALUE_ENTRY_V2 PAPISET_VALUE_ENTRY; typedef PAPI_SET_VALUE_ARRAY_V2 PAPISET_VALUE_ARRAY; typedef PAPI_SET_NAMESPACE_ENTRY_V2 PAPISET_NAMESPACE_ENTRY; typedef PAPI_SET_NAMESPACE_ARRAY_V2 PAPISET_NAMESPACE_ARRAY; #endif PLIST_ENTRY PsLoadedModuleList; extern DYNAMIC_DATA dynData; /// /// Map new module or return existing /// /// Target process /// Image path /// Image buffer /// Image buffer size /// Buffer has image memory layout /// Mapping flags /// Manual map context /// Mapped image data /// Status code NTSTATUS BBFindOrMapModule( IN PEPROCESS pProcess, IN PUNICODE_STRING path, IN PVOID buffer, IN ULONG_PTR size, IN BOOLEAN asImage, IN KMmapFlags flags, IN PMMAP_CONTEXT pContext, OUT PMODULE_DATA pImage ); /// /// Try to resolve image via API SET map /// /// Target process. Must be run in the context of this process /// Name to resolve /// Parent image name /// Resolved name if any /// Status code NTSTATUS BBResolveApiSet( IN PEPROCESS pProcess, IN PUNICODE_STRING name, IN PUNICODE_STRING baseImage, OUT PUNICODE_STRING resolved ); /// ///Try to resolve image via SxS isolation /// /// Loader context. /// Name to resolve /// Resolved name if any /// Status code NTSTATUS BBResolveSxS( IN PMMAP_CONTEXT pContext, IN PUNICODE_STRING name, OUT PUNICODE_STRING resolved ); /// /// Call module initialization routines /// /// Map context /// If TRUE - TLS callbacks will no be invoked /// Status code NTSTATUS BBCallInitializers( IN PMMAP_CONTEXT pContext, IN BOOLEAN noTLS ); /// /// Call TLS callbacks /// /// Loader context /// Target image base void BBCallTlsInitializers( IN PMMAP_CONTEXT pContext, IN PVOID imageBase ); /// /// Create activation context /// /// Manifest path /// Target file is a PE image /// Manifest ID /// Loader context /// Status code NTSTATUS BBPrepareACTX( IN PUNICODE_STRING pPath, IN BOOLEAN asImage, IN LONG manifestID, IN PMMAP_CONTEXT pContext ); /// /// Create exception table for x64 image /// /// Loader context /// Image base /// Status code NTSTATUS BBCreateExceptionTable64( IN PMMAP_CONTEXT pContext, IN PVOID imageBase ); /// /// Setup image security cookie /// /// Image base /// Status code NTSTATUS BBCreateCookie( IN PVOID imageBase ); /// /// Load image from disk into system memory /// /// Image path /// Mapped base /// Status code NTSTATUS BBLoadLocalImage( IN PUNICODE_STRING path, OUT PVOID* pBase ); /// /// Create worker thread for user-mode calls /// /// Map context /// Status code NTSTATUS BBCreateWorkerThread( IN PMMAP_CONTEXT pContext ); /// /// Find suitable thread to be used as worker for user-mode calls /// /// Map context /// Status code NTSTATUS BBFindWokerThread( IN PMMAP_CONTEXT pContext ); /// /// Call arbitrary function /// /// Perform call in a separate thread /// Loader context /// Routine to call. /// Number of arguments. /// Arguments /// Status code NTSTATUS BBCallRoutine( IN BOOLEAN newThread, IN PMMAP_CONTEXT pContext, IN PVOID pRoutine, IN INT argc, ... ); /// /// Find existing module in the list /// /// Module list /// Module name /// Module type /// Module info address; NULL if not found PMODULE_DATA BBLookupMappedModule( IN PLIST_ENTRY pList, IN PUNICODE_STRING pName, IN ModType type ); /// /// Get memory protection form section characteristics /// /// Characteristics /// Set to TRUE if DEP is disabled. This will omit creation of executable pages /// Memory protection value ULONG BBCastSectionProtection( IN ULONG characteristics, IN BOOLEAN noDEP ); /// /// Get image manifest data /// /// Image base /// Manifest size /// Manifest ID /// Manifest data pointer or NULL if no manifest PVOID BBImageManifest( IN PVOID pImageBase, OUT PULONG_PTR pSize, OUT PLONG pID ); /// /// Translate RVA address to VA /// /// RVA /// Image is mapped using image memory layout /// Image base /// Translated address PVOID BBRvaToVa( IN ULONG rva, IN BOOLEAN MappedAsImage, IN IN PVOID imageBase ); #pragma alloc_text(PAGE, BBMapUserImage) #pragma alloc_text(PAGE, BBFindOrMapModule) #pragma alloc_text(PAGE, BBResolveImageRefs) #pragma alloc_text(PAGE, BBResolveApiSet) #pragma alloc_text(PAGE, BBResolveSxS) #pragma alloc_text(PAGE, BBResolveImagePath) #pragma alloc_text(PAGE, BBCallInitializers) #pragma alloc_text(PAGE, BBPrepareACTX) #pragma alloc_text(PAGE, BBCreateExceptionTable64) #pragma alloc_text(PAGE, BBCreateCookie) #pragma alloc_text(PAGE, BBLoadLocalImage) #pragma alloc_text(PAGE, BBCreateWorkerThread) #pragma alloc_text(PAGE, BBCallRoutine) #pragma alloc_text(PAGE, BBLookupMappedModule) #pragma alloc_text(PAGE, BBCastSectionProtection) #pragma alloc_text(PAGE, BBImageManifest) #pragma alloc_text(PAGE, BBRvaToVa) /// /// Map new user module /// /// Target process /// Image path /// Image buffer /// Image buffer size /// Buffer has image memory layout /// Mapping flags /// Init routine RVA /// Init argument /// Mapped image data /// Status code NTSTATUS BBMapUserImage( IN PEPROCESS pProcess, IN PUNICODE_STRING path, IN PVOID buffer, IN ULONG_PTR size, IN BOOLEAN asImage, IN KMmapFlags flags, IN ULONG initRVA, IN PWCH initArg, OUT PMODULE_DATA pImage ) { NTSTATUS status = STATUS_SUCCESS; MMAP_CONTEXT context = { 0 }; ASSERT( pProcess != NULL ); if (pProcess == NULL) { DPRINT( "BlackBone: %s: No process provided\n", __FUNCTION__ ); return STATUS_INVALID_PARAMETER; } context.pProcess = pProcess; InitializeListHead( &context.modules ); DPRINT( "BlackBone: %s: Mapping image '%wZ' with flags 0x%X\n", __FUNCTION__, path, flags ); // Create or find worker thread context.noThreads = (flags & KNoThreads) != 0; status = context.noThreads ? BBFindWokerThread( &context ) : BBCreateWorkerThread( &context ); if (NT_SUCCESS( status )) { SIZE_T mapSize = 0x2000; status = ZwAllocateVirtualMemory( ZwCurrentProcess(), &context.userMem, 0, &mapSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE ); } // Create sync event if (NT_SUCCESS( status )) { OBJECT_ATTRIBUTES eventAttr = { 0 }; InitializeObjectAttributes( &eventAttr, 0, 0, 0, 0 ); status = ZwCreateEvent( &context.hSync, EVENT_ALL_ACCESS, &eventAttr, NotificationEvent, FALSE ); if (NT_SUCCESS( status )) status = ObReferenceObjectByHandle( context.hSync, EVENT_MODIFY_STATE, *ExEventObjectType, UserMode, &context.pSync, NULL ); if (!NT_SUCCESS( status )) DPRINT( "BlackBone: %s: Failed to create sync event. Status 0x%X\n", __FUNCTION__, status ); UNICODE_STRING ustrNtdll; RtlUnicodeStringInit( &ustrNtdll, L"ntdll.dll" ); PVOID pNtdll = BBGetUserModule( context.pProcess, &ustrNtdll, PsGetProcessWow64Process( pProcess ) != NULL ); context.pSetEvent = BBGetModuleExport( pNtdll, "NtSetEvent", pProcess, NULL ); context.pLoadImage = BBGetModuleExport( pNtdll, "LdrLoadDll", pProcess, NULL ); } // Map module if (NT_SUCCESS( status )) status = BBFindOrMapModule( pProcess, path, buffer, size, asImage, flags, &context, pImage ); // Enable exceptions for WOW64 process if (NT_SUCCESS( status ) && !(flags & KNoExceptions)) { if (dynData.KExecOpt != 0) { PKEXECUTE_OPTIONS pExecOpt = (PKEXECUTE_OPTIONS)((PUCHAR)pProcess + dynData.KExecOpt); pExecOpt->Flags.ImageDispatchEnable = 1; pExecOpt->Flags.ExecuteDispatchEnable = 1; pExecOpt->Flags.Permanent = 1; } else DPRINT( "BlackBone: %s: Invalid KEXECUTE_OPTIONS offset\n", __FUNCTION__ ); } // Rebase process if (NT_SUCCESS( status ) && flags & KRebaseProcess) { PPEB pPeb = (PPEB)PsGetProcessPeb( pProcess ); PPEB32 pPeb32 = (PPEB32)PsGetProcessWow64Process( pProcess ); pPeb->ImageBaseAddress = pImage->baseAddress; if (pPeb32) pPeb32->ImageBaseAddress = (ULONG)(ULONG_PTR)pImage->baseAddress; } // Run module initializers if (NT_SUCCESS( status )) { __try{ status = BBCallInitializers( &context, (flags & KNoTLS) != 0 ); } __except (EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); DPRINT( "BlackBone: %s: Exception during initialization phase.Exception code 0x%X\n", __FUNCTION__, status ); } } // Run user initializer if (NT_SUCCESS( status ) && initRVA != 0) { PVOID pInitRoutine = (PUCHAR)pImage->baseAddress + initRVA; memcpy( context.userMem->buffer, initArg, 512 * sizeof( WCHAR ) ); BBCallRoutine( FALSE, &context, pInitRoutine, 1, context.userMem->buffer ); } // // Cleanup // // Event if (context.pSync) ObDereferenceObject( context.pSync ); if (context.hSync && !BBCheckProcessTermination( PsGetCurrentProcess() )) ZwClose( context.hSync ); // Worker thread if (context.pWorker) ObDereferenceObject( context.pWorker ); if (context.hWorker) { LARGE_INTEGER timeout = { 0 }; timeout.QuadPart = -(10ll * 10 * 1000); // 10 ms ZwTerminateThread( context.hWorker, 0 ); ZwWaitForSingleObject( context.hWorker, TRUE, &timeout ); ZwClose( context.hWorker ); } // Worker code buffer if (context.pWorkerBuf) { SIZE_T mapSize = 0; ZwFreeVirtualMemory( ZwCurrentProcess(), &context.pWorkerBuf, &mapSize, MEM_RELEASE ); } // Code buffer if (context.userMem) { SIZE_T mapSize = 0; ZwFreeVirtualMemory( ZwCurrentProcess(), &context.userMem, &mapSize, MEM_RELEASE ); } // Cleanup module list while (!IsListEmpty( &context.modules )) { PVOID pListEntry = context.modules.Flink; PMODULE_DATA pEntry = (PMODULE_DATA)CONTAINING_RECORD( pListEntry, MODULE_DATA, link ); RemoveHeadList( &context.modules ); RtlFreeUnicodeString( &pEntry->fullPath ); ExFreePoolWithTag( pListEntry, BB_POOL_TAG ); } return status; } /// /// Map new module or return existing /// /// Target process /// Image path /// Image buffer /// Image buffer size /// Buffer has image memory layout /// Mapping flags /// Manual map context /// Mapped image data /// Status code NTSTATUS BBFindOrMapModule( IN PEPROCESS pProcess, IN PUNICODE_STRING path, IN PVOID buffer, IN ULONG_PTR size, IN BOOLEAN asImage, IN KMmapFlags flags, IN PMMAP_CONTEXT pContext, OUT PMODULE_DATA pImage ) { NTSTATUS status = STATUS_SUCCESS; PMODULE_DATA pLocalImage = NULL; PIMAGE_NT_HEADERS pHeaders = NULL; BOOLEAN wow64 = PsGetProcessWow64Process( pProcess ) != NULL; UNREFERENCED_PARAMETER( size ); // Allocate image context record pLocalImage = ExAllocatePoolWithTag( PagedPool, sizeof( MODULE_DATA ), BB_POOL_TAG ); RtlZeroMemory( pLocalImage, sizeof( MODULE_DATA ) ); BBSafeInitString( &pLocalImage->fullPath, path ); BBStripPath( &pLocalImage->fullPath, &pLocalImage->name ); pLocalImage->flags = flags; // Check if image is already loaded pLocalImage->type = wow64 ? mt_mod32 : mt_mod64; PMODULE_DATA pFoundMod = BBLookupMappedModule( &pContext->modules, &pLocalImage->name, pLocalImage->type ); if (pFoundMod != NULL) { RtlFreeUnicodeString( &pLocalImage->fullPath ); ExFreePoolWithTag( pLocalImage, BB_POOL_TAG ); *pImage = *pFoundMod; return STATUS_SUCCESS; } DPRINT( "BlackBone: %s: Loading image %wZ\n", __FUNCTION__, &pLocalImage->fullPath ); // Load image info if (buffer == NULL) { status = BBLoadLocalImage( path, &pLocalImage->localBase ); asImage = FALSE; } else pLocalImage->localBase = buffer; // Validate PE if (NT_SUCCESS( status )) { pHeaders = RtlImageNtHeader( pLocalImage->localBase ); if (!pHeaders) status = STATUS_INVALID_IMAGE_FORMAT; } // Prepare activation context if (!(flags & KNoSxS) && NT_SUCCESS( status )) { LONG manifestID = 0; ULONG_PTR manifestSize = 0; PVOID pManifest = BBImageManifest( pLocalImage->localBase, &manifestSize, &manifestID ); if (pManifest && manifestID != 0) { // Save manifest into tmp file if (buffer != NULL) { UNICODE_STRING manifestPathNt = { 0 }; UNICODE_STRING manifestPath = { 0 }; NTSTATUS status2 = STATUS_SUCCESS; HANDLE hFile = NULL; OBJECT_ATTRIBUTES obAttr = { 0 }; IO_STATUS_BLOCK statusBlock = { 0 }; BBSafeAllocateString( &manifestPath, 512 ); RtlInitUnicodeString( &manifestPathNt, L"\\SystemRoot\\Temp\\BBImage.manifest" ); InitializeObjectAttributes( &obAttr, &manifestPathNt, OBJ_KERNEL_HANDLE, NULL, NULL ); status2 = ZwCreateFile( &hFile, FILE_WRITE_DATA | DELETE | SYNCHRONIZE, &obAttr, &statusBlock, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_SUPERSEDE, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0 ); if (NT_SUCCESS( status2 )) { status2 = ZwWriteFile( hFile, NULL, NULL, NULL, &statusBlock, pManifest, (ULONG)manifestSize, NULL, NULL ); ZwClose( hFile ); } if (NT_SUCCESS( status2 )) { // Cast path RtlUnicodeStringPrintf( &manifestPath, L"%ls\\%ls", SharedUserData->NtSystemRoot, L"\\Temp\\BBImage.manifest" ); BBPrepareACTX( &manifestPath, FALSE, manifestID, pContext ); } else DPRINT( "BlackBone: %s: Failed to create temporary manifest. Status code: 0x%X\n", __FUNCTION__, status2 ); ZwDeleteFile( &obAttr ); RtlFreeUnicodeString( &manifestPath ); } else BBPrepareACTX( path, TRUE, manifestID, pContext ); } } // Allocate memory in target process if (NT_SUCCESS( status )) { // Allocate physical memory if (flags & KHideVAD) { ALLOCATE_FREE_MEMORY request = { 0 }; ALLOCATE_FREE_MEMORY_RESULT mapResult = { 0 }; request.pid = (ULONG)(ULONG_PTR)PsGetProcessId( pProcess ); request.allocate = TRUE; request.physical = TRUE; request.protection = PAGE_EXECUTE_READWRITE; request.size = HEADER_VAL_T( pHeaders, SizeOfImage ); status = BBAllocateFreePhysical( pProcess, &request, &mapResult ); if (NT_SUCCESS( status )) { pLocalImage->baseAddress = (PUCHAR)mapResult.address; pLocalImage->size = mapResult.size; } } else { pLocalImage->baseAddress = (PUCHAR)HEADER_VAL_T( pHeaders, ImageBase ); pLocalImage->size = HEADER_VAL_T( pHeaders, SizeOfImage ); status = ZwAllocateVirtualMemory( ZwCurrentProcess(), &pLocalImage->baseAddress, 0, &pLocalImage->size, MEM_COMMIT, PAGE_EXECUTE_READWRITE ); // Retry with arbitrary base if (!NT_SUCCESS( status )) { pLocalImage->baseAddress = NULL; pLocalImage->size = HEADER_VAL_T( pHeaders, SizeOfImage ); status = ZwAllocateVirtualMemory( ZwCurrentProcess(), &pLocalImage->baseAddress, 0, &pLocalImage->size, MEM_COMMIT, PAGE_EXECUTE_READWRITE ); } } } // Copy image memory if (NT_SUCCESS( status )) { // Copy header RtlCopyMemory( pLocalImage->baseAddress, pLocalImage->localBase, HEADER_VAL_T( pHeaders, SizeOfHeaders ) ); // // Copy sections // PIMAGE_SECTION_HEADER pFirstSection = (PIMAGE_SECTION_HEADER)(pHeaders + 1); if (IMAGE32( pHeaders )) pFirstSection = (PIMAGE_SECTION_HEADER)((PIMAGE_NT_HEADERS32)pHeaders + 1); for (PIMAGE_SECTION_HEADER pSection = pFirstSection; pSection < pFirstSection + pHeaders->FileHeader.NumberOfSections; pSection++) { // Skip invalid sections if (!(pSection->Characteristics & (IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_EXECUTE)) || pSection->SizeOfRawData == 0) { continue; } RtlCopyMemory( pLocalImage->baseAddress + pSection->VirtualAddress, pLocalImage->localBase + pSection->PointerToRawData, pSection->SizeOfRawData ); } } // Relocate image if (NT_SUCCESS( status )) { status = LdrRelocateImage( pLocalImage->baseAddress, STATUS_SUCCESS, STATUS_CONFLICTING_ADDRESSES, STATUS_INVALID_IMAGE_FORMAT ); if (!NT_SUCCESS( status )) DPRINT( "BlackBone: %s: Failed to relocate image '%wZ'. Status: 0x%X\n", __FUNCTION__, path, status ); } InsertHeadList( &pContext->modules, &pLocalImage->link ); // Resolve imports if (NT_SUCCESS( status )) status = BBResolveImageRefs( pLocalImage->baseAddress, FALSE, pProcess, IMAGE32( pHeaders ), pContext, flags ); // Set image memory protection if (NT_SUCCESS( status )) { if (flags & KHideVAD) { status = BBProtectVAD( pProcess, (ULONG_PTR)pLocalImage->baseAddress, MM_ZERO_ACCESS ); } else { SIZE_T tmpSize = 0; // Protect header tmpSize = HEADER_VAL_T( pHeaders, SizeOfHeaders ); status = ZwProtectVirtualMemory( ZwCurrentProcess(), &pLocalImage->baseAddress, &tmpSize, PAGE_READONLY, NULL ); // // Protect sections // PIMAGE_SECTION_HEADER pFirstSection = (PIMAGE_SECTION_HEADER)(pHeaders + 1); if (IMAGE32( pHeaders )) pFirstSection = (PIMAGE_SECTION_HEADER)((PIMAGE_NT_HEADERS32)pHeaders + 1); for (PIMAGE_SECTION_HEADER pSection = pFirstSection; pSection < pFirstSection + pHeaders->FileHeader.NumberOfSections; pSection++) { ULONG prot = BBCastSectionProtection( pSection->Characteristics, FALSE /*!(flags & KNoExceptions) && wow64*/ ); PUCHAR pAddr = pLocalImage->baseAddress + pSection->VirtualAddress; tmpSize = pSection->Misc.VirtualSize; // Decommit pages with NO_ACCESS protection if (prot != PAGE_NOACCESS) status |= ZwProtectVirtualMemory( ZwCurrentProcess(), &pAddr, &tmpSize, prot, NULL ); else ZwFreeVirtualMemory( ZwCurrentProcess(), &pAddr, &tmpSize, MEM_DECOMMIT ); } } } // Setup security cookie if (NT_SUCCESS( status )) BBCreateCookie( pLocalImage->baseAddress ); // Enable SEH support for x64 image if (!(flags & KNoExceptions) && NT_SUCCESS( status ) && wow64 == FALSE) BBCreateExceptionTable64( pContext, pLocalImage->baseAddress ); // // Cleanup // // Free system-space image memory if (pLocalImage->localBase) { ExFreePoolWithTag( pLocalImage->localBase, BB_POOL_TAG ); pLocalImage->localBase = NULL; } if (NT_SUCCESS( status )) { // Copy image info if (pImage) *pImage = *pLocalImage; } else if (pLocalImage) { // Remove entry from list RemoveEntryList( &pLocalImage->link ); // Delete remote image if (pLocalImage->baseAddress) { if (flags & KHideVAD) { ALLOCATE_FREE_MEMORY request = { 0 }; ALLOCATE_FREE_MEMORY_RESULT mapResult = { 0 }; request.pid = (ULONG)(ULONG_PTR)PsGetProcessId( pProcess ); request.allocate = FALSE; request.physical = TRUE; BBAllocateFreePhysical( pProcess, &request, &mapResult ); } else { SIZE_T tmpSize = 0; ZwFreeVirtualMemory( ZwCurrentProcess(), &pLocalImage->baseAddress, &tmpSize, MEM_RELEASE ); } } RtlFreeUnicodeString( &pLocalImage->fullPath ); ExFreePoolWithTag( pLocalImage, BB_POOL_TAG ); } return status; } /// /// Resolve import table and load missing dependencies /// /// Target image base /// If TRUE - image is driver /// Target process /// Iamge is 32bit image /// Manual map context /// Status code NTSTATUS BBResolveImageRefs( IN PVOID pImageBase, IN BOOLEAN systemImage, IN PEPROCESS pProcess, IN BOOLEAN wow64Image, IN PMMAP_CONTEXT pContext, IN KMmapFlags flags ) { NTSTATUS status = STATUS_SUCCESS; ULONG impSize = 0; PIMAGE_NT_HEADERS pHeader = RtlImageNtHeader( pImageBase ); PIMAGE_IMPORT_DESCRIPTOR pImportTbl = RtlImageDirectoryEntryToData( pImageBase, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &impSize ); // No import libs if (pImportTbl == NULL) return STATUS_SUCCESS; for (; pImportTbl->Name && NT_SUCCESS( status ); ++pImportTbl) { PVOID pThunk = ((PUCHAR)pImageBase + (pImportTbl->OriginalFirstThunk ? pImportTbl->OriginalFirstThunk : pImportTbl->FirstThunk)); UNICODE_STRING ustrImpDll = { 0 }; UNICODE_STRING resolved = { 0 }; UNICODE_STRING resolvedName = { 0 }; ANSI_STRING strImpDll = { 0 }; ULONG IAT_Index = 0; PCCHAR impFunc = NULL; union { PVOID address; PKLDR_DATA_TABLE_ENTRY ldrEntry; } pModule = { 0 }; RtlInitAnsiString( &strImpDll, (PCHAR)pImageBase + pImportTbl->Name ); RtlAnsiStringToUnicodeString( &ustrImpDll, &strImpDll, TRUE ); // Resolve image name if(!systemImage) { status = BBResolveImagePath( pContext, pProcess, 0, &ustrImpDll, NULL, &resolved ); BBStripPath( &resolved, &resolvedName ); // Something went terribly wrong if (status == STATUS_UNHANDLED_EXCEPTION) { RtlFreeUnicodeString( &ustrImpDll ); RtlFreeUnicodeString( &resolved ); return STATUS_NOT_FOUND; } } else { BBSafeInitString( &resolved, &ustrImpDll ); } // Get import module pModule.address = systemImage ? BBGetSystemModule( &ustrImpDll, NULL ) : BBGetUserModule( pProcess, &resolvedName, wow64Image ); // Load missing import if (!pModule.address && !systemImage) { if (flags & KManualImports) { MODULE_DATA modData = { 0 }; status = BBFindOrMapModule( pProcess, &resolved, NULL, 0, FALSE, flags | KNoSxS, pContext, &modData ); if (NT_SUCCESS( status )) pModule.address = modData.baseAddress; } else { DPRINT( "BlackBone: %s: Loading missing import %wZ\n", __FUNCTION__, &resolved ); if (IMAGE64( pHeader )) { RtlInitEmptyUnicodeString( &pContext->userMem->ustr, pContext->userMem->buffer, sizeof( pContext->userMem->buffer ) ); RtlCopyUnicodeString( &pContext->userMem->ustr, &resolvedName ); } else { pContext->userMem->ustr32.Buffer = (ULONG)(ULONG_PTR)pContext->userMem->buffer; pContext->userMem->ustr32.MaximumLength = sizeof( pContext->userMem->buffer ); pContext->userMem->ustr32.Length = resolvedName.Length; RtlZeroMemory( pContext->userMem->buffer, sizeof( pContext->userMem->buffer ) ); memcpy( pContext->userMem->buffer, resolvedName.Buffer, resolvedName.Length ); } // Invoke LdrLoadDll pContext->userMem->status = STATUS_SUCCESS; // Calling LdrLoadDll in worker thread breaks further APC delivery, so use new thread BBCallRoutine( !pContext->noThreads, pContext, pContext->pLoadImage, 4, NULL, NULL, &pContext->userMem->ustr, &pContext->userMem->ptr ); pModule.address = pContext->userMem->ptr; if (!pModule.address) status = pContext->userMem->status; } } // Failed to load if (!pModule.address) { DPRINT( "BlackBone: %s: Failed to load import '%wZ'. Status code: 0x%X\n", __FUNCTION__, ustrImpDll, status ); RtlFreeUnicodeString( &ustrImpDll ); RtlFreeUnicodeString( &resolved ); return STATUS_NOT_FOUND; } while (THUNK_VAL_T( pHeader, pThunk, u1.AddressOfData )) { PIMAGE_IMPORT_BY_NAME pAddressTable = (PIMAGE_IMPORT_BY_NAME)((PUCHAR)pImageBase + THUNK_VAL_T( pHeader, pThunk, u1.AddressOfData )); PVOID pFunc = NULL; // import by name if (THUNK_VAL_T( pHeader, pThunk, u1.AddressOfData ) < (IMAGE64( pHeader ) ? IMAGE_ORDINAL_FLAG64 : IMAGE_ORDINAL_FLAG32) && pAddressTable->Name[0]) { impFunc = pAddressTable->Name; } // import by ordinal else { impFunc = (PCCHAR)(THUNK_VAL_T( pHeader, pThunk, u1.AddressOfData ) & 0xFFFF); } pFunc = BBGetModuleExport( systemImage ? pModule.ldrEntry->DllBase : pModule.address, impFunc, systemImage ? NULL : pContext->pProcess, &resolved ); // No export found if (!pFunc) { if (THUNK_VAL_T( pHeader, pThunk, u1.AddressOfData ) < (IMAGE64( pHeader ) ? IMAGE_ORDINAL_FLAG64 : IMAGE_ORDINAL_FLAG32) && pAddressTable->Name[0]) DPRINT( "BlackBone: %s: Failed to resolve import '%wZ' : '%s'\n", __FUNCTION__, ustrImpDll, pAddressTable->Name ); else DPRINT( "BlackBone: %s: Failed to resolve import '%wZ' : '%d'\n", __FUNCTION__, ustrImpDll, THUNK_VAL_T( pHeader, pThunk, u1.AddressOfData ) & 0xFFFF ); status = STATUS_NOT_FOUND; break; } if(IMAGE64( pHeader )) { // Save address to IAT if (pImportTbl->FirstThunk) *(PULONG_PTR)((PUCHAR)pImageBase + pImportTbl->FirstThunk + IAT_Index) = (ULONG_PTR)pFunc; // Save address to OrigianlFirstThunk else *(PULONG_PTR)((PUCHAR)pImageBase + THUNK_VAL_T( pHeader, pThunk, u1.AddressOfData )) = (ULONG_PTR)pFunc; } else { // Save address to IAT if (pImportTbl->FirstThunk) *(PULONG)((PUCHAR)pImageBase + pImportTbl->FirstThunk + IAT_Index) = (ULONG)(ULONG_PTR)pFunc; // Save address to OrigianlFirstThunk else *(PULONG)((PUCHAR)pImageBase + THUNK_VAL_T( pHeader, pThunk, u1.AddressOfData )) = (ULONG)(ULONG_PTR)pFunc; } // Go to next entry pThunk = (PUCHAR)pThunk + (IMAGE64( pHeader ) ? sizeof( IMAGE_THUNK_DATA64 ) : sizeof( IMAGE_THUNK_DATA32 )); IAT_Index += (IMAGE64( pHeader ) ? sizeof( ULONGLONG ) : sizeof( ULONG )); } RtlFreeUnicodeString( &ustrImpDll ); RtlFreeUnicodeString( &resolved ); } return status; } /// /// Try to resolve image via API SET map /// /// Target process. Must be run in the context of this process /// Name to resolve /// Parent image name /// Resolved name if any /// Status code NTSTATUS BBResolveApiSet( IN PEPROCESS pProcess, IN PUNICODE_STRING name, IN PUNICODE_STRING baseImage, OUT PUNICODE_STRING resolved ) { NTSTATUS status = STATUS_NOT_FOUND; PPEB32 pPeb32 = (PPEB32)PsGetProcessWow64Process( pProcess ); PPEB pPeb = PsGetProcessPeb( pProcess ); PAPISET_NAMESPACE_ARRAY pApiSetMap = (PAPISET_NAMESPACE_ARRAY)(pPeb32 != NULL ? (PVOID)pPeb32->ApiSetMap : pPeb->ApiSetMap); // Invalid name if ( name == NULL || name->Length < 4 * sizeof(WCHAR) || name->Buffer == NULL || (memcmp( name->Buffer, L"api-", 4 ) != 0 && memcmp( name->Buffer, L"ext-", 4 ) != 0)) return STATUS_NOT_FOUND; // Iterate api set map for (ULONG i = 0; i < pApiSetMap->Count; i++) { PAPISET_NAMESPACE_ENTRY pDescriptor = NULL; PAPISET_VALUE_ARRAY pHostArray = NULL; wchar_t apiNameBuf[255] = { 0 }; UNICODE_STRING apiName = { 0 }; #ifdef _WIN10_ pDescriptor = (PAPISET_NAMESPACE_ENTRY)((PUCHAR)pApiSetMap + pApiSetMap->End + i * sizeof( API_SET_NAMESPACE_ENTRY_10 )); pHostArray = (PAPISET_VALUE_ARRAY)((PUCHAR)pApiSetMap + pApiSetMap->Start + sizeof( API_SET_VALUE_ARRAY_10 ) * pDescriptor->Size); memcpy( apiNameBuf, (PUCHAR)pApiSetMap + pHostArray->NameOffset, pHostArray->NameLength ); #else pDescriptor = pApiSetMap->Array + i; memcpy( apiNameBuf, (PUCHAR)pApiSetMap + pDescriptor->NameOffset, pDescriptor->NameLength ); #endif RtlUnicodeStringInit( &apiName, apiNameBuf ); // Check if this is a target api if (BBSafeSearchString( name, &apiName, TRUE ) >= 0) { PAPISET_VALUE_ENTRY pHost = NULL; wchar_t apiHostNameBuf[255] = { 0 }; UNICODE_STRING apiHostName = { 0 }; #ifdef _WIN10_ pHost = (PAPISET_VALUE_ENTRY)((PUCHAR)pApiSetMap + pHostArray->DataOffset); #else pHostArray = (PAPISET_VALUE_ARRAY)((PUCHAR)pApiSetMap + pDescriptor->DataOffset); pHost = pHostArray->Array; #endif // Sanity check if (pHostArray->Count < 1) return STATUS_NOT_FOUND; memcpy( apiHostNameBuf, (PUCHAR)pApiSetMap + pHost->ValueOffset, pHost->ValueLength ); RtlUnicodeStringInit( &apiHostName, apiHostNameBuf ); // No base name redirection if (pHostArray->Count == 1 || baseImage == NULL || baseImage->Buffer[0] == 0) { BBSafeInitString( resolved, &apiHostName ); return STATUS_SUCCESS; } // Redirect accordingly to base name else { UNICODE_STRING baseImageName = { 0 }; BBStripPath( baseImage, &baseImageName ); if (RtlCompareUnicodeString( &apiHostName, &baseImageName, TRUE ) == 0) { memset( apiHostNameBuf, 0, sizeof( apiHostNameBuf ) ); memcpy( apiHostNameBuf, (PUCHAR)pApiSetMap + pHost[1].ValueOffset, pHost[1].ValueLength ); RtlCreateUnicodeString( resolved, apiHostNameBuf ); return STATUS_SUCCESS; } else { BBSafeInitString( resolved, &apiHostName ); return STATUS_SUCCESS; } } } } return status; } /// ///Try to resolve image via SxS isolation /// /// Loader context. /// Name to resolve /// Resolved name if any /// Status code NTSTATUS BBResolveSxS( IN PMMAP_CONTEXT pContext, IN PUNICODE_STRING name, OUT PUNICODE_STRING resolved ) { NTSTATUS status = STATUS_NOT_FOUND; UNICODE_STRING ustrNtdll = { 0 }; BOOLEAN wow64 = PsGetProcessWow64Process( pContext->pProcess ) != NULL; typedef struct _STRIBG_BUF { union { UNICODE_STRING name1; UNICODE_STRING32 name132; }; union { UNICODE_STRING name2; UNICODE_STRING32 name232; }; union { UNICODE_STRING origName; UNICODE_STRING32 origName32; }; union { PUNICODE_STRING pResolved; ULONG pResolved32; }; wchar_t origBuf[0x100]; wchar_t staticBuf[0x200]; } STRIBG_BUF, *PSTRIBG_BUF; PSTRIBG_BUF pStringBuf = (PSTRIBG_BUF)pContext->userMem->buffer; RtlUnicodeStringInit( &ustrNtdll, L"ntdll.dll" ); PVOID hNtdll = BBGetUserModule( pContext->pProcess, &ustrNtdll, wow64 ); PVOID pQueryName = BBGetModuleExport( hNtdll, "RtlDosApplyFileIsolationRedirection_Ustr", pContext->pProcess, NULL ); if (pQueryName == NULL) { DPRINT( "BlackBone: %s: Failed to get RtlDosApplyFileIsolationRedirection_Ustr\n", __FUNCTION__ ); return STATUS_NOT_FOUND; } RtlZeroMemory( pStringBuf->origBuf, sizeof( pStringBuf->origBuf ) ); RtlZeroMemory( pStringBuf->staticBuf, sizeof( pStringBuf->staticBuf ) ); // Fill params memcpy( pStringBuf->origBuf, name->Buffer, name->Length ); if (wow64) { pStringBuf->origName32.Buffer = (ULONG)(ULONG_PTR)pStringBuf->origBuf; pStringBuf->origName32.MaximumLength = sizeof( pStringBuf->origBuf ); pStringBuf->origName32.Length = name->Length; pStringBuf->name132.Buffer = (ULONG)(ULONG_PTR)pStringBuf->staticBuf; pStringBuf->name132.MaximumLength = sizeof( pStringBuf->staticBuf ); pStringBuf->name132.Length = 0; pStringBuf->name232.Buffer = 0; pStringBuf->name232.Length = pStringBuf->name232.MaximumLength = 0; } else { RtlInitUnicodeString( &pStringBuf->origName, pStringBuf->origBuf ); RtlInitEmptyUnicodeString( &pStringBuf->name1, pStringBuf->staticBuf, sizeof( pStringBuf->staticBuf ) ); RtlInitEmptyUnicodeString( &pStringBuf->name2, NULL, 0 ); } // Prevent some unpredictable shit __try { // RtlDosApplyFileIsolationRedirection_Ustr status = BBCallRoutine( FALSE, pContext, pQueryName, 9, (PVOID)TRUE, &pStringBuf->origName, NULL, &pStringBuf->name1, &pStringBuf->name2, &pStringBuf->pResolved, NULL, NULL, NULL ); if (NT_SUCCESS( status ) && NT_SUCCESS( pContext->userMem->status )) { if (wow64) { ULONG tmp = ((PUNICODE_STRING32)pStringBuf->pResolved32)->Buffer; pStringBuf->pResolved = &pStringBuf->name1; pStringBuf->pResolved->Buffer = (PWCH)tmp; } RtlDowncaseUnicodeString( resolved, pStringBuf->pResolved, TRUE ); // TODO: name2 cleanup } return NT_SUCCESS( status ) ? pContext->userMem->status : status; } __except (EXCEPTION_EXECUTE_HANDLER) { DPRINT( "BlackBone: %s: Exception. Code: 0x%X\n", __FUNCTION__, GetExceptionCode() ); return STATUS_UNHANDLED_EXCEPTION; } } /// /// Resolve image name to fully qualified path /// /// Loader context /// Target process. Must be running in the context of this process /// Flags /// Image name to resolve /// Base image name for API SET translation /// Resolved image path /// Status code NTSTATUS BBResolveImagePath( IN PMMAP_CONTEXT pContext, IN PEPROCESS pProcess, IN ResolveFlags flags, IN PUNICODE_STRING path, IN PUNICODE_STRING baseImage, OUT PUNICODE_STRING resolved ) { NTSTATUS status = STATUS_SUCCESS; UNICODE_STRING pathLow = { 0 }; UNICODE_STRING filename = { 0 }; UNICODE_STRING fullResolved = { 0 }; UNREFERENCED_PARAMETER( baseImage ); ASSERT( pProcess != NULL && path != NULL && resolved != NULL ); if (pProcess == NULL || path == NULL || resolved == NULL) { DPRINT( "BlackBone: %s: Missing parameter\n", __FUNCTION__ ); return STATUS_INVALID_PARAMETER; } RtlDowncaseUnicodeString( &pathLow, path, TRUE ); BBStripPath( &pathLow, &filename ); // API Schema if (NT_SUCCESS( BBResolveApiSet( pProcess, &filename, baseImage, resolved ) )) { BBSafeAllocateString( &fullResolved, 512 ); // Perpend system directory if (PsGetProcessWow64Process( pProcess ) != NULL) RtlUnicodeStringCatString( &fullResolved, L"\\SystemRoot\\syswow64\\" ); else RtlUnicodeStringCatString( &fullResolved, L"\\SystemRoot\\system32\\" ); RtlUnicodeStringCat( &fullResolved, resolved ); RtlFreeUnicodeString( resolved ); RtlFreeUnicodeString( &pathLow ); //DPRINT( "BlackBone: %s: Resolved image '%wZ' to '%wZ' by ApiSetSchema\n", __FUNCTION__, path, fullResolved ); *resolved = fullResolved; return STATUS_SUCCESS; } // Api schema only if (flags & KApiShemaOnly) goto skip; if (flags & KSkipSxS) goto SkipSxS; // SxS status = BBResolveSxS( pContext, &filename, resolved ); if (pContext && NT_SUCCESS( status )) { BBSafeAllocateString( &fullResolved, 1024 ); RtlUnicodeStringCatString( &fullResolved, L"\\??\\" ); RtlUnicodeStringCat( &fullResolved, resolved ); RtlFreeUnicodeString( resolved ); RtlFreeUnicodeString( &pathLow ); *resolved = fullResolved; return STATUS_SUCCESS; } else if (status == STATUS_UNHANDLED_EXCEPTION) { *resolved = pathLow; return status; } else status = STATUS_SUCCESS; SkipSxS: BBSafeAllocateString( &fullResolved, 0x400 ); // // Executable directory // ULONG bytes = 0; if (NT_SUCCESS( ZwQueryInformationProcess( ZwCurrentProcess(), ProcessImageFileName, fullResolved.Buffer + 0x100, 0x200, &bytes ) )) { PUNICODE_STRING pPath = (PUNICODE_STRING)(fullResolved.Buffer + 0x100); UNICODE_STRING parentDir = { 0 }; BBStripFilename( pPath, &parentDir ); RtlCopyUnicodeString( &fullResolved, &parentDir ); RtlUnicodeStringCatString( &fullResolved, L"\\" ); RtlUnicodeStringCat( &fullResolved, &filename ); if (NT_SUCCESS( BBFileExists( &fullResolved ) )) { RtlFreeUnicodeString( resolved ); RtlFreeUnicodeString( &pathLow ); *resolved = fullResolved; return STATUS_SUCCESS; } } fullResolved.Length = 0; RtlZeroMemory( fullResolved.Buffer, 0x400 ); // // System directory // if (PsGetProcessWow64Process( pProcess ) != NULL) RtlUnicodeStringCatString( &fullResolved, L"\\SystemRoot\\SysWOW64\\" ); else RtlUnicodeStringCatString( &fullResolved, L"\\SystemRoot\\System32\\" ); RtlUnicodeStringCat( &fullResolved, &filename ); if (NT_SUCCESS( BBFileExists( &fullResolved ) )) { RtlFreeUnicodeString( resolved ); RtlFreeUnicodeString( &pathLow ); *resolved = fullResolved; return STATUS_SUCCESS; } RtlFreeUnicodeString( &fullResolved ); // Nothing found skip: *resolved = pathLow; return status; } /// /// Call module initialization routines /// /// Map context /// If TRUE - TLS callbacks will no be invoked /// Status code NTSTATUS BBCallInitializers( IN PMMAP_CONTEXT pContext, IN BOOLEAN noTLS ) { for (PLIST_ENTRY pListEntry = pContext->modules.Flink; pListEntry != &pContext->modules; pListEntry = pListEntry->Flink) { PMODULE_DATA pEntry = (PMODULE_DATA)CONTAINING_RECORD( pListEntry, MODULE_DATA, link ); if (pEntry->initialized) continue; DPRINT( "BlackBone: %s: Calling '%wZ' initializer\n", __FUNCTION__, pEntry->name ); PIMAGE_NT_HEADERS pHeaders = RtlImageNtHeader( pEntry->baseAddress ); if (noTLS == FALSE) BBCallTlsInitializers( pContext, pEntry->baseAddress ); NTSTATUS status = STATUS_SUCCESS; if (HEADER_VAL_T( pHeaders, AddressOfEntryPoint )) { PUCHAR entrypoint = pEntry->baseAddress + HEADER_VAL_T( pHeaders, AddressOfEntryPoint ); status = BBCallRoutine( FALSE, pContext, entrypoint, 3, pEntry->baseAddress, (PVOID)1, NULL ); } // Check if process is terminating if (status != STATUS_SUCCESS && BBCheckProcessTermination( PsGetCurrentProcess() )) { DPRINT( "BlackBone: %s: Process is terminating, map aborted\n", __FUNCTION__ ); return STATUS_PROCESS_IS_TERMINATING; } // // Wipe discardable sections // PIMAGE_SECTION_HEADER pFirstSection = (PIMAGE_SECTION_HEADER)(pHeaders + 1); if (IMAGE32( pHeaders )) pFirstSection = (PIMAGE_SECTION_HEADER)((PIMAGE_NT_HEADERS32)pHeaders + 1); for (PIMAGE_SECTION_HEADER pSection = pFirstSection; pSection < pFirstSection + pHeaders->FileHeader.NumberOfSections; pSection++) { if (!(pSection->Characteristics & IMAGE_SCN_MEM_DISCARDABLE)) continue; PVOID pBase = (PUCHAR)pEntry->baseAddress + pSection->VirtualAddress; SIZE_T secSize = pSection->Misc.VirtualSize; if (pEntry->flags & KHideVAD) { RtlZeroMemory( pBase, secSize ); } else { if (NT_SUCCESS( ZwProtectVirtualMemory( ZwCurrentProcess(), &pBase, &secSize, PAGE_READWRITE, NULL ) )) RtlZeroMemory( pBase, secSize ); ZwProtectVirtualMemory( ZwCurrentProcess(), &pBase, &secSize, PAGE_NOACCESS, NULL ); ZwFreeVirtualMemory( ZwCurrentProcess(), &pBase, &secSize, MEM_DECOMMIT ); } } // Wipe header if (pEntry->flags & KWipeHeader) { SIZE_T hdrSize = HEADER_VAL_T( pHeaders, SizeOfHeaders ); if (pEntry->flags & KHideVAD) { RtlZeroMemory( pEntry->baseAddress, hdrSize ); } else { PVOID pBase = pEntry->baseAddress; if (NT_SUCCESS( ZwProtectVirtualMemory( ZwCurrentProcess(), &pBase, &hdrSize, PAGE_READWRITE, NULL ) )) { RtlZeroMemory( pEntry->baseAddress, hdrSize ); ZwProtectVirtualMemory( ZwCurrentProcess(), &pBase, &hdrSize, PAGE_READONLY, NULL ); } else DPRINT( "BlackBone: %s: Failed to wipe image header\n", __FUNCTION__ ); } } pEntry->initialized = TRUE; } return STATUS_SUCCESS; } /// /// Call TLS callbacks /// /// Loader context /// Target image base void BBCallTlsInitializers( IN PMMAP_CONTEXT pContext, IN PVOID imageBase ) { ULONG size = 0; PIMAGE_NT_HEADERS pHeaders = RtlImageNtHeader( imageBase ); if (!pHeaders) return; // No TLS directory PVOID pTLS = RtlImageDirectoryEntryToData( imageBase, TRUE, IMAGE_DIRECTORY_ENTRY_TLS, &size ); if (!pTLS) return; // Prepare static TLS a bit // Still not compatible with LdrpQueueDeferredTlsData in Win8.1, but I'm tired of this shit if (!pContext->tlsInitialized && TLS_VAL_T( pHeaders, pTLS, StartAddressOfRawData ) != 0) { PVOID pTLSMem = NULL; SIZE_T tlsSize = 0x1000; if (NT_SUCCESS( ZwAllocateVirtualMemory( ZwCurrentProcess(), &pTLSMem, 0, &tlsSize, MEM_COMMIT, PAGE_READWRITE ) )) { PUCHAR pTeb64 = PsGetThreadTeb( pContext->pWorker ); // WOW64 if (PsGetProcessWow64Process( pContext->pProcess ) != NULL) { // TEB32 is 2 pages after TEB64 *(PULONG)(pTeb64 + 0x2000 + 0x2C) = (ULONG)(ULONG_PTR)pTLSMem; *(PULONG)pTLSMem = (ULONG)TLS_VAL_T( pHeaders, pTLS, StartAddressOfRawData ); } // Native else { *(PVOID*)(pTeb64 + 0x58) = pTLSMem; *(PULONGLONG)pTLSMem = TLS_VAL_T( pHeaders, pTLS, StartAddressOfRawData ); } DPRINT( "BlackBone: %s: Static TLS buffer: 0x%p\n", __FUNCTION__, pTLSMem ); pContext->tlsInitialized = TRUE; } } for ( PUCHAR pCallback = (PUCHAR)TLS_VAL_T( pHeaders, pTLS, AddressOfCallBacks ); ((IMAGE64( pHeaders ) ? *(PULONGLONG)pCallback : *(PULONG)pCallback)) != 0; pCallback += IMAGE64( pHeaders ) ? sizeof( ULONGLONG ) : sizeof( ULONG )) { ULONG_PTR callback = IMAGE64( pHeaders ) ? *(PULONGLONG)pCallback : *(PULONG)pCallback; BBCallRoutine( FALSE, pContext, (PVOID)callback, 3, imageBase, (PVOID)1, NULL ); } } /// /// Create activation context /// /// Manifest path /// Target file is a PE image /// Manifest ID /// Loader context /// Status code NTSTATUS BBPrepareACTX( IN PUNICODE_STRING pPath, IN BOOLEAN asImage, IN LONG manifestID, IN PMMAP_CONTEXT pContext ) { NTSTATUS status = STATUS_SUCCESS; UNICODE_STRING ustrKernel = { 0 }, ustrNtdll = { 0 }; BOOLEAN wow64 = PsGetProcessWow64Process( pContext->pProcess ) != NULL; RtlUnicodeStringInit( &ustrKernel, L"kernel32.dll" ); RtlUnicodeStringInit( &ustrNtdll, L"ntdll.dll" ); PVOID pCreateActCtxW = BBGetModuleExport( BBGetUserModule( pContext->pProcess, &ustrKernel, wow64 ), "CreateActCtxW", pContext->pProcess, NULL ); if (!pCreateActCtxW) { DPRINT( "BlackBone: %s: Failed to get CreateActCtxW address\n", __FUNCTION__ ); status = STATUS_NOT_FOUND; } PVOID pActivateCtx = BBGetModuleExport( BBGetUserModule( pContext->pProcess, &ustrNtdll, wow64 ), "RtlActivateActivationContext", pContext->pProcess, NULL ); if (!pActivateCtx) { DPRINT( "BlackBone: %s: Failed to get RtlActivateActivationContext address\n", __FUNCTION__ ); status = STATUS_NOT_FOUND; } // Generate activation context if (NT_SUCCESS( status )) { RtlZeroMemory( pContext->userMem->buffer, sizeof( pContext->userMem->buffer ) ); memcpy( pContext->userMem->buffer, pPath->Buffer, pPath->Length ); if (wow64 != FALSE) { RtlZeroMemory( &pContext->userMem->actx32, sizeof( pContext->userMem->actx32 ) ); pContext->userMem->actx32.cbSize = sizeof( ACTCTXW32 ); pContext->userMem->actx32.lpSource = (ULONG)(ULONG_PTR)pContext->userMem->buffer; if (asImage) { pContext->userMem->actx32.lpSource += 4 * sizeof( wchar_t ); pContext->userMem->actx32.dwFlags = ACTCTX_FLAG_RESOURCE_NAME_VALID; pContext->userMem->actx32.lpResourceName = (ULONG)(ULONG_PTR)MAKEINTRESOURCEW( manifestID ); } BBCallRoutine( FALSE, pContext, pCreateActCtxW, 1, &pContext->userMem->actx32 ); } else { RtlZeroMemory( &pContext->userMem->actx, sizeof( pContext->userMem->actx ) ); pContext->userMem->actx.cbSize = sizeof( ACTCTXW ); pContext->userMem->actx.lpSource = pContext->userMem->buffer; if (asImage) { pContext->userMem->actx.lpSource += 4; pContext->userMem->actx.dwFlags = ACTCTX_FLAG_RESOURCE_NAME_VALID; pContext->userMem->actx.lpResourceName = MAKEINTRESOURCEW( manifestID ); } BBCallRoutine( FALSE, pContext, pCreateActCtxW, 1, &pContext->userMem->actx ); } if (pContext->userMem->retVal32 != 0xFFFFFFFF) { pContext->userMem->hCTX = wow64 ? (PVOID)pContext->userMem->retVal32 : pContext->userMem->retVal; } else { DPRINT( "BlackBone: %s: CreateActCtxW failed\n", __FUNCTION__ ); status = STATUS_SXS_CANT_GEN_ACTCTX; } } // Activate context if (NT_SUCCESS( status )) { BBCallRoutine( FALSE, pContext, pActivateCtx, 3, NULL, pContext->userMem->hCTX, &pContext->userMem->hCookie ); if (!NT_SUCCESS( pContext->userMem->status )) { DPRINT( "BlackBone: %s: RtlActivateActivationContext failed with code 0x%X\n", __FUNCTION__, pContext->userMem->status ); status = pContext->userMem->status; } } return status; } /// /// Create exception table for x64 image /// /// Loader context /// Image base /// Status code NTSTATUS BBCreateExceptionTable64( IN PMMAP_CONTEXT pContext, IN PVOID imageBase ) { NTSTATUS status = STATUS_SUCCESS; ULONG dirSize = 0; _PIMAGE_RUNTIME_FUNCTION_ENTRY pExpTable = (_PIMAGE_RUNTIME_FUNCTION_ENTRY)RtlImageDirectoryEntryToData( imageBase, TRUE, IMAGE_DIRECTORY_ENTRY_EXCEPTION, &dirSize ); // Invoke RtlAddFunctionTable if (pExpTable) { UNICODE_STRING ustrNtdll = { 0 }; BOOLEAN wow64 = PsGetProcessWow64Process( pContext->pProcess ) != NULL; RtlUnicodeStringInit( &ustrNtdll, L"ntdll.dll" ); PVOID pAddTable = BBGetModuleExport( BBGetUserModule( pContext->pProcess, &ustrNtdll, wow64 ), "RtlAddFunctionTable", pContext->pProcess, NULL ); if (!pAddTable) { DPRINT( "BlackBone: %s: Failed to get RtlAddFunctionTable address\n", __FUNCTION__ ); return STATUS_NOT_FOUND; } status = BBCallRoutine( FALSE, pContext, pAddTable, 3, pExpTable, (PVOID)(dirSize / sizeof( _IMAGE_RUNTIME_FUNCTION_ENTRY )), imageBase ); if (NT_SUCCESS( status )) { if (pContext->userMem->retVal32 == FALSE) DPRINT( "BlackBone: %s: RtlAddFunctionTable failed\n", __FUNCTION__ ); } else DPRINT( "BlackBone: %s: Failed co call RtlAddFunctionTable. Status code: 0x%X\n", __FUNCTION__, status ); return status; } return status; } /// /// Setup image security cookie /// /// Image base /// Status code NTSTATUS BBCreateCookie( IN PVOID imageBase ) { NTSTATUS status = STATUS_SUCCESS; PIMAGE_NT_HEADERS pHeader = RtlImageNtHeader( imageBase ); if (pHeader) { ULONG cfgSize = 0; PVOID pCfgDir = RtlImageDirectoryEntryToData( imageBase, TRUE, IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG, &cfgSize ); // TODO: implement proper cookie algorithm if (pCfgDir && CFG_DIR_VAL_T( pHeader, pCfgDir, SecurityCookie )) { ULONG seed = (ULONG)(ULONG_PTR)imageBase ^ (ULONG)((ULONG_PTR)imageBase >> 32); ULONG_PTR cookie = (ULONG_PTR)imageBase ^ RtlRandomEx( &seed ); // SecurityCookie value must be rebased by this moment if (IMAGE64( pHeader )) *(PULONG_PTR)CFG_DIR_VAL_T( pHeader, pCfgDir, SecurityCookie ) = cookie; else *(PULONG)CFG_DIR_VAL_T( pHeader, pCfgDir, SecurityCookie ) = (ULONG)cookie; } } else status = STATUS_INVALID_IMAGE_FORMAT; return status; } /// /// Load image from disk into system memory /// /// Image path /// Mapped base /// Status code NTSTATUS BBLoadLocalImage( IN PUNICODE_STRING path, OUT PVOID* pBase ) { NTSTATUS status = STATUS_SUCCESS; HANDLE hFile = NULL; OBJECT_ATTRIBUTES obAttr = { 0 }; IO_STATUS_BLOCK statusBlock = { 0 }; FILE_STANDARD_INFORMATION fileInfo = { 0 }; ASSERT( path != NULL && pBase != NULL ); if (path == NULL || pBase == NULL) { DPRINT( "BlackBone: %s: No image path or output base\n", __FUNCTION__ ); return STATUS_INVALID_PARAMETER; } InitializeObjectAttributes( &obAttr, path, OBJ_KERNEL_HANDLE, NULL, NULL ); // Open image file status = ZwCreateFile( &hFile, FILE_READ_DATA | SYNCHRONIZE, &obAttr, &statusBlock, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0 ); if (!NT_SUCCESS( status )) { DPRINT( "BlackBone: %s: Failed to open '%wZ'. Status: 0x%X\n", __FUNCTION__, path, status ); return status; } // Allocate memory for file contents status = ZwQueryInformationFile( hFile, &statusBlock, &fileInfo, sizeof( fileInfo ), FileStandardInformation ); if (NT_SUCCESS( status )) *pBase = ExAllocatePoolWithTag( PagedPool, fileInfo.EndOfFile.QuadPart, BB_POOL_TAG ); else DPRINT( "BlackBone: %s: Failed to get '%wZ' size. Status: 0x%X\n", __FUNCTION__, path, status ); // Get file contents status = ZwReadFile( hFile, NULL, NULL, NULL, &statusBlock, *pBase, fileInfo.EndOfFile.LowPart, NULL, NULL ); if (NT_SUCCESS( status )) { PIMAGE_NT_HEADERS pNTHeader = RtlImageNtHeader( *pBase ); if (!pNTHeader) { DPRINT( "BlackBone: %s: Failed to obtaint NT Header for '%wZ'\n", __FUNCTION__, path ); status = STATUS_INVALID_IMAGE_FORMAT; } } else DPRINT( "BlackBone: %s: Failed to read '%wZ'. Status: 0x%X\n", __FUNCTION__, path, status ); ZwClose( hFile ); if (!NT_SUCCESS( status ) && *pBase) ExFreePoolWithTag( *pBase, BB_POOL_TAG ); return status; } /// /// Create worker thread for user-mode calls /// /// Map context /// Status code NTSTATUS BBCreateWorkerThread( IN PMMAP_CONTEXT pContext ) { NTSTATUS status = STATUS_SUCCESS; SIZE_T codeSize = 0x1000; ASSERT( pContext != NULL ); if (pContext == NULL) return STATUS_INVALID_PARAMETER; pContext->pWorkerBuf = NULL; status = ZwAllocateVirtualMemory( ZwCurrentProcess(), &pContext->pWorkerBuf, 0, &codeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE ); if (NT_SUCCESS( status )) { PUCHAR pBuf = pContext->pWorkerBuf; UNICODE_STRING ustrNtdll; BOOLEAN wow64 = PsGetProcessWow64Process( pContext->pProcess ) != NULL; RtlUnicodeStringInit( &ustrNtdll, L"ntdll.dll" ); PVOID pNtDelayExec = BBGetModuleExport( BBGetUserModule( pContext->pProcess, &ustrNtdll, wow64 ), "NtDelayExecution", pContext->pProcess, NULL ); if (pNtDelayExec) { OBJECT_ATTRIBUTES obattr = { 0 }; PLARGE_INTEGER pDelay = (PLARGE_INTEGER)(pBuf + 0x100); pDelay->QuadPart = -(5ll * 10 * 1000); ULONG ofst = 0; if (wow64) { *(PUCHAR)(pBuf + ofst) = 0x68; // push pDelay *(PULONG)(pBuf + ofst + 1) = (ULONG)(ULONG_PTR)pDelay; // ofst += 5; *(PUSHORT)(pBuf + ofst) = 0x016A; // push TRUE ofst += 2; *(PUCHAR)(pBuf + ofst) = 0xB8; // mov eax, pFn *(PULONG)(pBuf + ofst + 1) = (ULONG)(ULONG_PTR)pNtDelayExec;// ofst += 5; *(PUSHORT)(pBuf + ofst) = 0xD0FF; // call eax ofst += 2; *(PUSHORT)(pBuf + ofst) = 0xF0EB; // jmp ofst += 2; } else { *(PUSHORT)(pBuf + ofst) = 0xB948; // mov rcx, TRUE *(PULONG_PTR)(pBuf + ofst + 2) = TRUE; // ofst += 10; *(PUSHORT)(pBuf + ofst) = 0xBA48; // mov rdx, pDelay *(PVOID*)(pBuf + ofst + 2) = pDelay; // ofst += 10; *(PUSHORT)(pBuf + ofst) = 0xB848; // mov rax, pNtDelayExec *(PVOID*)(pBuf + ofst + 2) = pNtDelayExec; // ofst += 10; *(PUSHORT)(pBuf + ofst) = 0xD0FF; // call rax ofst += 2; *(PUSHORT)(pBuf + ofst) = 0xDEEB; // jmp ofst += 2; } InitializeObjectAttributes( &obattr, NULL, OBJ_KERNEL_HANDLE, NULL, NULL ); status = ZwCreateThreadEx( &pContext->hWorker, THREAD_ALL_ACCESS, &obattr, ZwCurrentProcess(), pContext->pWorkerBuf, NULL, 0, 0, 0x1000, 0x100000, NULL ); if (NT_SUCCESS( status )) ObReferenceObjectByHandle( pContext->hWorker, THREAD_ALL_ACCESS, *PsThreadType, KernelMode, &pContext->pWorker, NULL ); } else { status = STATUS_NOT_FOUND; } } return status; } /// /// Find suitable thread to be used as worker for user-mode calls /// /// Map context /// Status code NTSTATUS BBFindWokerThread( IN PMMAP_CONTEXT pContext ) { return BBLookupProcessThread( pContext->pProcess, &pContext->pWorker ); } /// /// Call arbitrary function /// /// Perform call in a separate thread /// Loader context /// Routine to call. /// Number of arguments. /// Arguments /// Status code NTSTATUS BBCallRoutine( IN BOOLEAN newThread, IN PMMAP_CONTEXT pContext, IN PVOID pRoutine, IN INT argc, ... ) { NTSTATUS status = STATUS_SUCCESS; va_list vl; BOOLEAN wow64 = PsGetProcessWow64Process( pContext->pProcess ) != NULL; va_start( vl, argc ); ULONG ofst = GenPrologueT( wow64, pContext->userMem->code ); ofst += GenCallTV( wow64, pContext->userMem->code + ofst, pRoutine, argc, vl ); ofst += GenSyncT( wow64, pContext->userMem->code + ofst, &pContext->userMem->status, pContext->pSetEvent, pContext->hSync ); ofst += GenEpilogueT( wow64, pContext->userMem->code + ofst, argc * sizeof( ULONG ) ); if (newThread) { status = BBExecuteInNewThread( pContext->userMem->code, NULL, 0/*THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER*/, TRUE, NULL ); } else { KeResetEvent( pContext->pSync ); status = BBQueueUserApc( pContext->pWorker, pContext->userMem->code, NULL, NULL, NULL, pContext->noThreads ); if (NT_SUCCESS( status )) { LARGE_INTEGER timeout = { 0 }; timeout.QuadPart = -(10ll * 10 * 1000 * 1000); // 10s status = KeWaitForSingleObject( pContext->pSync, Executive, UserMode, TRUE, &timeout ); timeout.QuadPart = -(1ll * 10 * 1000); // 1ms KeDelayExecutionThread( KernelMode, TRUE, &timeout ); } } va_end( vl ); return status; } /// /// Find existing module in the list /// /// Module list /// Module name /// Module type /// Module info address; NULL if not found PMODULE_DATA BBLookupMappedModule( IN PLIST_ENTRY pList, IN PUNICODE_STRING pName, IN ModType type ) { // Sanity check ASSERT( pList != NULL && pName != NULL ); if (pList == NULL || pName == NULL) return NULL; if (IsListEmpty( pList )) return NULL; // Walk list for (PLIST_ENTRY pListEntry = pList->Flink; pListEntry != pList; pListEntry = pListEntry->Flink) { PMODULE_DATA pEntry = (PMODULE_DATA)CONTAINING_RECORD( pListEntry, MODULE_DATA, link ); if (type == pEntry->type && RtlCompareUnicodeString( &pEntry->name, pName, TRUE ) == 0) return pEntry; } return NULL; } /// /// Get memory protection form section characteristics /// /// Characteristics /// Set to TRUE if DEP is disabled. This will omit creation of executable pages /// Memory protection value ULONG BBCastSectionProtection( IN ULONG characteristics, IN BOOLEAN noDEP ) { ULONG dwResult = PAGE_NOACCESS; if (characteristics & IMAGE_SCN_MEM_DISCARDABLE) { dwResult = PAGE_NOACCESS; } else if (characteristics & IMAGE_SCN_MEM_EXECUTE) { if (characteristics & IMAGE_SCN_MEM_WRITE) dwResult = noDEP ? PAGE_READWRITE : PAGE_EXECUTE_READWRITE; else if (characteristics & IMAGE_SCN_MEM_READ) dwResult = noDEP ? PAGE_READONLY : PAGE_EXECUTE_READ; else dwResult = noDEP ? PAGE_READONLY : PAGE_EXECUTE; } else { if (characteristics & IMAGE_SCN_MEM_WRITE) dwResult = PAGE_READWRITE; else if (characteristics & IMAGE_SCN_MEM_READ) dwResult = PAGE_READONLY; else dwResult = PAGE_NOACCESS; } return dwResult; } /// /// Get image manifest data /// /// Image base /// Manifest size /// Manifest ID /// Manifest data pointer or NULL if no manifest PVOID BBImageManifest( IN PVOID pImageBase, OUT PULONG_PTR pSize, OUT PLONG pID ) { ULONG secSize = 0; ASSERT( pImageBase != NULL && pSize != NULL && pID != NULL ); if (pImageBase == NULL || pSize == NULL || pID == NULL) return NULL; // 3 levels of pointers to nodes IMAGE_RESOURCE_DIRECTORY_ENTRY *pDirNode1 = NULL; IMAGE_RESOURCE_DIRECTORY_ENTRY *pDirNode2 = NULL; IMAGE_RESOURCE_DIRECTORY_ENTRY *pDirNode3 = NULL; // 3 levels of nodes IMAGE_RESOURCE_DIRECTORY *pDirNodePtr1 = NULL; IMAGE_RESOURCE_DIRECTORY *pDirNodePtr2 = NULL; IMAGE_RESOURCE_DIRECTORY *pDirNodePtr3 = NULL; // resource entry data IMAGE_RESOURCE_DATA_ENTRY *pDataNode = NULL; size_t ofst_1 = 0; // first level nodes offset size_t ofst_2 = 0; // second level nodes offset size_t ofst_3 = 0; // third level nodes offset // Get section base PUCHAR secBase = RtlImageDirectoryEntryToData( pImageBase, FALSE, IMAGE_DIRECTORY_ENTRY_RESOURCE, &secSize ); if (secBase == NULL) { *pSize = 0; *pID = 0; return NULL; } pDirNodePtr1 = (PIMAGE_RESOURCE_DIRECTORY)secBase; ofst_1 += sizeof( IMAGE_RESOURCE_DIRECTORY ); // first-level nodes for (int i = 0; i < pDirNodePtr1->NumberOfIdEntries + pDirNodePtr1->NumberOfNamedEntries; ++i) { pDirNode1 = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(secBase + ofst_1); // Not a manifest directory if (!pDirNode1->DataIsDirectory || pDirNode1->Id != 0x18) { ofst_1 += sizeof( IMAGE_RESOURCE_DIRECTORY_ENTRY ); continue; } pDirNodePtr2 = (PIMAGE_RESOURCE_DIRECTORY)(secBase + pDirNode1->OffsetToDirectory); ofst_2 = pDirNode1->OffsetToDirectory + sizeof( IMAGE_RESOURCE_DIRECTORY ); // second-level nodes for (int j = 0; j < pDirNodePtr2->NumberOfIdEntries + pDirNodePtr2->NumberOfNamedEntries; ++j) { pDirNode2 = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(secBase + ofst_2); if (!pDirNode2->DataIsDirectory) { ofst_2 += sizeof( IMAGE_RESOURCE_DIRECTORY_ENTRY ); continue; } // Check if this is a valid manifest resource if (pDirNode2->Id == 1 || pDirNode2->Id == 2 || pDirNode2->Id == 3) { pDirNodePtr3 = (PIMAGE_RESOURCE_DIRECTORY)(secBase + pDirNode2->OffsetToDirectory); ofst_3 = pDirNode2->OffsetToDirectory + sizeof( IMAGE_RESOURCE_DIRECTORY ); pDirNode3 = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(secBase + ofst_3); pDataNode = (PIMAGE_RESOURCE_DATA_ENTRY)(secBase + pDirNode3->OffsetToData); *pID = pDirNode2->Id; *pSize = pDataNode->Size; return BBRvaToVa( pDataNode->OffsetToData, FALSE, pImageBase ); } ofst_2 += sizeof( IMAGE_RESOURCE_DIRECTORY_ENTRY ); } ofst_1 += sizeof( IMAGE_RESOURCE_DIRECTORY_ENTRY ); } return NULL; } /// /// Translate RVA address to VA /// /// RVA /// Image is mapped using image memory layout /// Image base /// Translated address PVOID BBRvaToVa( IN ULONG rva, IN BOOLEAN MappedAsImage, IN PVOID imageBase ) { // Simple offset if (MappedAsImage) return (PUCHAR)imageBase + rva; PIMAGE_NT_HEADERS pHeader = RtlImageNtHeader( imageBase ); if (pHeader) { PIMAGE_SECTION_HEADER pFirstSection = (PIMAGE_SECTION_HEADER)(pHeader + 1); if (IMAGE32( pHeader )) pFirstSection = (PIMAGE_SECTION_HEADER)((PIMAGE_NT_HEADERS32)pHeader + 1); for (PIMAGE_SECTION_HEADER pSection = pFirstSection; pSection < pFirstSection + pHeader->FileHeader.NumberOfSections; pSection++) { if (rva >= pSection->VirtualAddress && rva < pSection->VirtualAddress + pSection->Misc.VirtualSize) return (PUCHAR)imageBase + pSection->PointerToRawData + (rva - pSection->VirtualAddress); } } return NULL; }