Files
dennisbabkin.com 901351c6d2 Update CSection.cpp
2021-05-29 13:03:19 -07:00

1083 lines
30 KiB
C++

//Class that deals with mapping of section (or DLL)
//
// Test solution that demonstrates DLL injection into all running processes
// Copyright (c) 2021 www.dennisbabkin.com
//
// https://dennisbabkin.com/blog/?i=AAA10800
//
// Credit: Rbmm
//
// https://github.com/rbmm/INJECT
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//
#include "CSection.h"
#include "CFunc.h" //Aux functions
extern "C" {
extern PDRIVER_OBJECT g_DriverObject;
//The following functions are defined in the Assembly file:
void __stdcall RundownRoutine(PKAPC);
void __stdcall KernelRoutine(PKAPC pApc, PKNORMAL_ROUTINE* NormalRoutine, PVOID* NormalContext, PVOID* SystemArgument1, PVOID* SystemArgument2);
void __stdcall NormalRoutine(PVOID NormalContext, PVOID SystemArgument1, PVOID SystemArgument2);
//End of functions defined in the Assembly file
}
NTSTATUS CSection::Initialize(SECTION_TYPE type)
{
//Initialize this object
//INFO: Cannot use constructor/destructor!
//'type' = type of section (or DLL) that this section will represent
sectionType = type;
//Initialize our singleton
RtlRunOnceInitialize(&SectionSingletonState);
return STATUS_SUCCESS;
}
NTSTATUS CSection::GetSection(DLL_STATS** ppOutSectionInfo)
{
//Get DLL section object
//INFO: Will create it only once, if it wasn't created earlier
//'ppOutSectionInfo' = if not NULL, receives the section object info, or NULL if error
//RETURN:
// = Status of operation
NTSTATUS status = STATUS_SUCCESS;
//Make sure that CSection::Initialize was called!
ASSERT(sectionType == SEC_TP_NATIVE || sectionType == SEC_TP_WOW);
//Use the singleton approach
PVOID Context = NULL;
status = RtlRunOnceBeginInitialize(&SectionSingletonState, 0, &Context);
if(status == STATUS_PENDING)
{
//We get here only during the first initialization
Context = NULL;
//Alloc memory
DLL_STATS* pDStats = (DLL_STATS*)ExAllocatePoolWithTag(ALLOC_TYPE_OnLoadImage, sizeof(DLL_STATS), TAG('kDSm'));
if(pDStats)
{
//Need to "trick" the system into creating a KnownDll section for us with the SD of the kernel32.dll section
//Temporarily attach the current thread to the address space of the system process
KAPC_STATE as;
KeStackAttachProcess(PsInitialSystemProcess, &as);
//Create our KnownDll section
status = CreateKnownDllSection(*pDStats);
//Revert back
KeUnstackDetachProcess(&as);
//Check the result
if(NT_SUCCESS(status))
{
//We'll keep the section info in the context
Context = pDStats;
}
else
{
//Error
DbgPrintLine("ERROR: (0x%x) CreateKnownDllSection, sectionType=%c", status, sectionType);
//Free memory
ExFreePool(pDStats);
pDStats = NULL;
}
}
else
{
//Error
status = STATUS_MEMORY_NOT_ALLOCATED;
DbgPrintLine("ERROR: (0x%x) ExAllocatePoolWithTag(kDSm), sectionType=%c", status, sectionType);
}
//Finalize our singleton
NTSTATUS status2 = RtlRunOnceComplete(&SectionSingletonState, 0, Context);
if(!NT_SUCCESS(status2))
{
//Error
DbgPrintLine("ERROR: (0x%x) RtlRunOnceComplete, sectionType=%c", status2, sectionType);
ASSERT(NULL);
if(NT_SUCCESS(status))
status = status2;
if(pDStats)
{
//Free memory
ExFreePool(pDStats);
pDStats = NULL;
}
Context = NULL;
}
}
else if(status != STATUS_SUCCESS)
{
//Error
DbgPrintLine("ERROR: (0x%x) RtlRunOnceBeginInitialize, sectionType=%c", status, sectionType);
ASSERT(NULL);
}
//Did we get the pointer?
if(!Context &&
status == STATUS_SUCCESS)
{
//We previously failed to create section
status = STATUS_NONEXISTENT_SECTOR;
}
if(ppOutSectionInfo)
*ppOutSectionInfo = (DLL_STATS*)Context;
return status;
}
NTSTATUS CSection::FreeSection()
{
//Release resources held for the mapped section
//INFO: Doesn't do anything if GetSection() wasn't called yet
//RETURN:
// = Status of the operations
NTSTATUS status;
PVOID Context = NULL;
status = RtlRunOnceBeginInitialize(&SectionSingletonState, 0, &Context);
if(NT_SUCCESS(status))
{
//We have initialized our singleton
//Do we have the context - otherwise there's nothing to delete
if(Context)
{
DLL_STATS* pDStats = (DLL_STATS*)Context;
#ifdef DBG_VERBOSE_DRV
DbgPrintLine("FreeSection, sectionType=%c", sectionType);
#endif
//Remove permanent flag from the section object
ObMakeTemporaryObject(pDStats->Section);
//And derefence it
ObDereferenceObjectWithTag(pDStats->Section, TAG('hFkS'));
pDStats->Section = NULL;
//Free memory
ExFreePool(Context);
Context = NULL;
}
//Reset the singleton back
RtlRunOnceInitialize(&SectionSingletonState);
}
else if(status == STATUS_UNSUCCESSFUL)
{
//GetSection() wasn't called yet
status = STATUS_SUCCESS;
}
else
{
//Error
DbgPrintLine("ERROR: (0x%x) FreeSection, sectionType=%c", status, sectionType);
ASSERT(NULL);
}
return status;
}
NTSTATUS CSection::CreateKnownDllSection(DLL_STATS& outStats)
{
//Create a known-DLL system section of our own
//'outStatus' = receives information on created known section (only if return success)
//RETURN:
// = Status of the operations
NTSTATUS status;
//Clear the returned data (assuming only primitive data types)
memset(&outStats, 0, sizeof(outStats));
POBJECT_ATTRIBUTES poaKernel32;
PCUNICODE_STRING pstrFakeDll;
PCOBJECT_ATTRIBUTES poaPathFakeDll;
#ifdef _WIN64
if(sectionType == SEC_TP_WOW)
{
//32-bit section loaded on a 64-bit OS
STATIC_OBJECT_ATTRIBUTES(oaKernel32, "\\KnownDlls32\\kernel32.dll");
STATIC_UNICODE_STRING(strFakeDll, "\\KnownDlls32\\" INJECTED_DLL_FILE_NAME32);
STATIC_OBJECT_ATTRIBUTES(oaPathFakeDll, INJECTED_DLL_NT_PATH_WOW);
poaKernel32 = &oaKernel32;
pstrFakeDll = &strFakeDll;
poaPathFakeDll = &oaPathFakeDll;
}
else
#endif
{
//64-bit section loaded on a 64-bit OS, or
//32-bit section loaded on a 32-bit OS
STATIC_OBJECT_ATTRIBUTES(oaKernel32, "\\KnownDlls\\kernel32.dll");
STATIC_UNICODE_STRING(strFakeDll, "\\KnownDlls\\" INJECTED_DLL_FILE_NAME);
STATIC_OBJECT_ATTRIBUTES(oaPathFakeDll, INJECTED_DLL_NT_PATH_NTV);
poaKernel32 = &oaKernel32;
pstrFakeDll = &strFakeDll;
poaPathFakeDll = &oaPathFakeDll;
}
//Need to "steal" a security descriptor from existing KnownDll - we'll use kernel32.dll
HANDLE hSectionK32;
status = ZwOpenSection(&hSectionK32, READ_CONTROL, const_cast<POBJECT_ATTRIBUTES>(poaKernel32));
if(NT_SUCCESS(status))
{
status = STATUS_GENERIC_COMMAND_FAILED;
//INFO: Make our section "permanent", which means that it won't be deleted if all of its handles are closed
// and we will need to call ZwMakeTemporaryObject() on it first to allow it
OBJECT_ATTRIBUTES oaFakeDll = { sizeof(oaFakeDll), 0,
const_cast<PUNICODE_STRING>(pstrFakeDll),
OBJ_CASE_INSENSITIVE | OBJ_PERMANENT };
//Allocate needed memory
ULONG uicbMemSz = 0;
for(;;)
{
ULONG uicbMemNeededSz = 0;
status = ZwQuerySecurityObject(hSectionK32,
PROCESS_TRUST_LABEL_SECURITY_INFORMATION |
DACL_SECURITY_INFORMATION |
LABEL_SECURITY_INFORMATION |
OWNER_SECURITY_INFORMATION,
oaFakeDll.SecurityDescriptor, //SD
uicbMemSz, //mem size
&uicbMemNeededSz);
if(NT_SUCCESS(status))
{
//Got it
break;
}
else if(status == STATUS_BUFFER_TOO_SMALL)
{
//Need more memory
ASSERT(uicbMemNeededSz > uicbMemSz);
if(oaFakeDll.SecurityDescriptor)
{
//Free previous memory
ExFreePool(oaFakeDll.SecurityDescriptor);
}
//Allocate mem
oaFakeDll.SecurityDescriptor = ExAllocatePoolWithTag(ALLOC_TYPE_OnLoadImage, uicbMemNeededSz, TAG('k32m'));
if(oaFakeDll.SecurityDescriptor)
{
//Need to retry
uicbMemSz = uicbMemNeededSz;
}
else
{
//Error
status = STATUS_MEMORY_NOT_ALLOCATED;
DbgPrintLine("ERROR: (0x%X) ExAllocatePoolWithTag(hSectionK32), PID=%u, sz=%u, sectionType=%c"
,
status,
(ULONG)(ULONG_PTR)PsGetCurrentProcessId(),
uicbMemNeededSz,
sectionType
);
break;
}
}
else
{
//Error
DbgPrintLine("ERROR: (0x%X) ZwQuerySecurityObject(hSectionK32), PID=%u, sz=%u, sectionType=%c"
,
status,
(ULONG)(ULONG_PTR)PsGetCurrentProcessId(),
uicbMemSz,
sectionType
);
break;
}
}
//Close section
VERIFY(NT_SUCCESS(ZwClose(hSectionK32)));
if(NT_SUCCESS(status))
{
//Now we can create our own section for our injected DLL in the KnownDlls kernel object directory
HANDLE hFile;
IO_STATUS_BLOCK iosb;
//Open existing DLL that we will be injecting
status = ZwOpenFile(&hFile, FILE_GENERIC_READ | FILE_EXECUTE,
const_cast<POBJECT_ATTRIBUTES>(poaPathFakeDll), &iosb, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT);
if(NT_SUCCESS(status))
{
//Open PE file as section
HANDLE hFakeSection;
status = ZwCreateSection(&hFakeSection, SECTION_MAP_EXECUTE | SECTION_QUERY,
&oaFakeDll, 0, PAGE_EXECUTE, SEC_IMAGE, hFile);
if(NT_SUCCESS(status))
{
//Map it into our process
//INFO: We need two things:
// 1. Get the offset of our shellcode - or UserModeNormalRoutine function
// 2. Verify that this is our DLL and get its name from SEARCH_TAG_W
PVOID BaseAddress = NULL;
SIZE_T ViewSize = 0;
status = ZwMapViewOfSection(hFakeSection, NtCurrentProcess(), &BaseAddress, 0, 0, 0,
&ViewSize, ViewUnmap, 0, PAGE_READONLY);
if(NT_SUCCESS(status))
{
//Need to look up our ordinal function, it will be our ShellCode that we will call later
//INFO: It is located at the ordinal number 1 (it is defined in the .def file for that DLL exports)
ASSERT(BaseAddress);
ULONG OrdinalIndex = 1;
ULONG uRVA = 0;
__try
{
status = STATUS_INVALID_IMAGE_FORMAT;
PIMAGE_NT_HEADERS pNtHdr = RtlImageNtHeader(BaseAddress);
if(pNtHdr)
{
ULONG size;
PIMAGE_EXPORT_DIRECTORY pExpDir = (PIMAGE_EXPORT_DIRECTORY)
RtlImageDirectoryEntryToData(BaseAddress, TRUE, IMAGE_DIRECTORY_ENTRY_EXPORT, &size);
if(pExpDir &&
size >= sizeof(IMAGE_EXPORT_DIRECTORY))
{
OrdinalIndex -= pExpDir->Base;
if(OrdinalIndex < pExpDir->NumberOfFunctions)
{
PULONG pAddressOfFunctions = (PULONG)((BYTE*)BaseAddress + pExpDir->AddressOfFunctions);
if(pAddressOfFunctions)
{
//Get our needed SHellCode function's RVA
uRVA = pAddressOfFunctions[OrdinalIndex];
if(uRVA > 0) //Our DLL is small - do this quick check
{
//Locate the offset of the search tag
//INFO: It will give use the injected DLL name (it will be used for Shell Code later)
static GUID guiSrch = { GUID_SearchTag_DllName_Bin };
UINT uRVA_StchTag = CFunc::FindStringByTag(BaseAddress,
pNtHdr->OptionalHeader.SizeOfImage,
&guiSrch);
if(uRVA_StchTag != -1)
{
//Get information from our section
SECTION_IMAGE_INFORMATION sii;
status = ZwQuerySection(hFakeSection, SectionImageInformation,
&sii, sizeof(sii), 0);
if(NT_SUCCESS(status))
{
//Get our section object pointer & increment its reference count
status = ObReferenceObjectByHandleWithTag(hFakeSection, 0, NULL,
KernelMode, TAG('hFkS'),
&outStats.Section, NULL);
if(NT_SUCCESS(status))
{
//Set return parameters
outStats.secType = sectionType;
outStats.SizeOfImage = pNtHdr->OptionalHeader.SizeOfImage;
outStats.uRVA_ShellCode = uRVA;
outStats.uRVA_DllName = uRVA_StchTag;
//SECTION_IMAGE_INFORMATION::TransferAddress = address of entry point
// in the module after it was randomly relocated by ASLR
outStats.PreferredAddress = (BYTE*)sii.TransferAddress -
pNtHdr->OptionalHeader.AddressOfEntryPoint;
#ifdef DBG_VERBOSE_DRV
DbgPrintLine(
"KnownDll Created! PID=%u, RVA=0x%X, PreferredAddress=0x%p, sectionType=%c"
,
(ULONG)(ULONG_PTR)PsGetCurrentProcessId(),
uRVA,
outStats.PreferredAddress,
sectionType
);
#endif
//Done
status = STATUS_SUCCESS;
}
else
{
//Error
DbgPrintLine("ERROR: (0x%X) ObReferenceObjectByHandle, PID=%u, sectionType=%c"
,
status,
(ULONG)(ULONG_PTR)PsGetCurrentProcessId(),
sectionType
);
}
}
else
{
//Error
DbgPrintLine("ERROR: (0x%X) ZwQuerySection, PID=%u, sectionType=%c"
,
status,
(ULONG)(ULONG_PTR)PsGetCurrentProcessId(),
sectionType
);
}
}
else
{
//Error
DbgPrintLine(
"ERROR: (0x%X) FindStringByTag, PID=%u, base=0x%p, sz=%u, sectionType=%c"
,
status,
(ULONG)(ULONG_PTR)PsGetCurrentProcessId(),
BaseAddress,
pNtHdr->OptionalHeader.SizeOfImage,
sectionType
);
}
}
else
{
//Error
DbgPrintLine("ERROR: (0x%X) Bad RVA=%d, PID=%u, sectionType=%c"
,
status,
uRVA,
(ULONG)(ULONG_PTR)PsGetCurrentProcessId(),
sectionType
);
}
}
else
{
//Error
DbgPrintLine("ERROR: (0x%X) Bad AddressOfFunctions, PID=%u, sectionType=%c"
,
status,
(ULONG)(ULONG_PTR)PsGetCurrentProcessId(),
sectionType
);
}
}
else
{
//Error
DbgPrintLine("ERROR: (0x%X) Bad ordinal, PID=%u cnt=%u, sectionType=%c"
,
status,
(ULONG)(ULONG_PTR)PsGetCurrentProcessId(),
pExpDir->NumberOfFunctions,
sectionType
);
}
}
else
{
//Error
DbgPrintLine("ERROR: (0x%X) Export directory, PID=%u size=%u, sectionType=%c"
,
status,
(ULONG)(ULONG_PTR)PsGetCurrentProcessId(),
size,
sectionType
);
}
}
else
{
//Error
DbgPrintLine("ERROR: (0x%X) RtlImageNtHeader, PID=%u, sectionType=%c"
,
status,
(ULONG)(ULONG_PTR)PsGetCurrentProcessId(),
sectionType
);
}
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
//Failed to parse PE file
DbgPrintLine("#EXCEPTION: (0x%X) CreateKnownDllSection(PE-scan), PID=%u, sectionType=%c"
,
GetExceptionCode(),
(ULONG)(ULONG_PTR)PsGetCurrentProcessId(),
sectionType
);
status = STATUS_INVALID_IMAGE_FORMAT;
}
//Unmap the section
VERIFY(NT_SUCCESS(ZwUnmapViewOfSection(NtCurrentProcess(), BaseAddress)));
}
else
{
//Error
DbgPrintLine("ERROR: (0x%X) ZwMapViewOfSection, PID=%u, sectionType=%c"
,
status,
(ULONG)(ULONG_PTR)PsGetCurrentProcessId(),
sectionType
);
}
//Did we fail setting the section up?
if(!NT_SUCCESS(status))
{
//Make it not permanent (so that we can remove)
VERIFY(NT_SUCCESS(ZwMakeTemporaryObject(hFakeSection)));
}
//Close our section
VERIFY(NT_SUCCESS(ZwClose(hFakeSection)));
}
else
{
//Error
DbgPrintLine("ERROR: (0x%X) ZwCreateSection, PID=%u, sectionType=%c"
,
status,
(ULONG)(ULONG_PTR)PsGetCurrentProcessId(),
sectionType
);
}
//Close file
VERIFY(NT_SUCCESS(ZwClose(hFile)));
}
else
{
//Error
DbgPrintLine("ERROR: (0x%X) ZwOpenFile, PID=%u, sectionType=%c, path=\"%wZ\""
,
status,
(ULONG)(ULONG_PTR)PsGetCurrentProcessId(),
sectionType,
poaPathFakeDll->ObjectName
);
}
}
//Free our memory
if(oaFakeDll.SecurityDescriptor)
{
ExFreePool(oaFakeDll.SecurityDescriptor);
oaFakeDll.SecurityDescriptor = NULL;
}
}
else
{
//Error
DbgPrintLine("ERROR: (0x%X) ZwOpenSection(hSectionK32), PID=%u, sectionType=%c"
,
status,
(ULONG)(ULONG_PTR)PsGetCurrentProcessId(),
sectionType
);
}
return status;
}
NTSTATUS CSection::InjectDLL(DLL_STATS* pDllStats)
{
//Inject DLL into the current process
//'pDllStats' = DLL info to inject
ASSERT(pDllStats);
NTSTATUS status;
//Sanity check
if(!pDllStats->IsValid())
{
//Invalid data
ASSERT(NULL);
return STATUS_INVALID_PARAMETER_MIX;
}
//We need to allocate our KAPC from NonPagedPool. For details check:
// https://dennisbabkin.com/blog/?i=AAA03000#kernel_apc_memory
//
PKAPC pApc = (PKAPC)ExAllocatePoolWithTag(NonPagedPool, sizeof(KAPC), TAG('apc1'));
if(!pApc)
{
//Failed to allocate mem
return STATUS_MEMORY_NOT_ALLOCATED;
}
//Because we're called from under a critical section during the OnLoadImage callback,
//initialize an APC to do all the work for us later ...
//
//INFO:
// KernelRoutine = must be provided. It runs first at APC IRQL.
// NormalRoutine = [optional] Is called after KernelRoutine at PASSIVE IRQL.
// RundownRoutine = [optional] runs only if thread was closed before it had a chance
// to run and it had pending APCs. Otherwise it's not called.
// If RundownRoutine is called, neither KernelRoutine nor
// NormalRoutine are called.
//The reason we are coding KernelRoutine, RundownRoutine, NormalRoutine the way we did:
// https://dennisbabkin.com/blog/?i=AAA03000#pslinr_gotcha
//
//Set up "regular" kernel APC (since we specified NormalRoutine and KernelMode)
KeInitializeApc(pApc, KeGetCurrentThread(),
OriginalApcEnvironment,
KernelRoutine, RundownRoutine, NormalRoutine, //These routines are implemented in Assembly language - see asm64.asm or asm32.asm
KernelMode, //Kernel APC
pApc //Pass PKAPC as context into NormalRoutine
);
//Prevent our driver from unloading be incrementing its reference count
ObReferenceObject(g_DriverObject);
//Also keep our section object loaded
ObReferenceObject(pDllStats->Section);
//And initialize the APC
if(KeInsertQueueApc(pApc,
pDllStats, //SystemArgument1 = points to DLL_STATS
NULL, //SystemArgument2 = not used
IO_NO_INCREMENT))
{
//Queued APC OK
status = STATUS_SUCCESS;
}
else
{
//Failed - do roll back
status = STATUS_BAD_DATA;
DbgPrintLine("ERROR: (0x%X) KeInsertQueueApc, PID=%u, sectionType=%c"
,
status,
(ULONG)(ULONG_PTR)PsGetCurrentProcessId(),
sectionType
);
//Deference our objects
ObDereferenceObject(pDllStats->Section);
ObDereferenceObject(g_DriverObject);
//Free mem
ExFreePool(pApc);
pApc = NULL;
}
return status;
}
extern "C" BOOL __stdcall RundownRoutine_Proc(PKAPC pApc)
{
//Called from the asm64.asm/asm32.asm file for the RundownRoutine() call back
//INFO:
// runs only if thread was closed before it had a chance
// to run and it had pending APCs. Otherwise it's not called.
// If RundownRoutine is called, neither KernelRoutine nor
// NormalRoutine are called.
//RETURN:
// = TRUE to invoke safely ObDereferenceObject(g_DriverObject)
ASSERT(pApc);
#ifdef DBG_VERBOSE_DRV
DbgPrintLine("RundownRoutine: IRQL=%u, PID=%u, APC=0x%p, mode=%d"
,
KeGetCurrentIrql(),
(ULONG)(ULONG_PTR)PsGetCurrentProcessId(),
pApc,
pApc->ApcMode
);
#endif
if(pApc)
{
//Free mem
ExFreePool(pApc);
pApc = NULL;
}
//Derefence driver object
return TRUE;
}
extern "C" BOOL __stdcall KernelRoutine_Proc(PKAPC pApc, PKNORMAL_ROUTINE* NormalRoutine, PVOID* NormalContext, PVOID* SystemArgument1, PVOID* SystemArgument2)
{
//Called from the asm64.asm/asm32.asm file for the KernelRoutine() call back
//INFO:
// It runs first at APC IRQL before NormalRoutine(). You may adjust parameters passed here fro the NormalRoutine
//RETURN:
// = TRUE to invoke safely ObDereferenceObject(g_DriverObject)
UNREFERENCED_PARAMETER(pApc);
UNREFERENCED_PARAMETER(NormalRoutine);
UNREFERENCED_PARAMETER(NormalContext);
UNREFERENCED_PARAMETER(SystemArgument1);
UNREFERENCED_PARAMETER(SystemArgument2);
ASSERT(pApc);
BOOL bDerefDriverObject = TRUE;
#ifdef DBG_VERBOSE_DRV
DbgPrintLine("KernelRoutine: IRQL=%u, PID=%u, APC=0x%p, mode=%d"
,
KeGetCurrentIrql(),
(ULONG)(ULONG_PTR)PsGetCurrentProcessId(),
pApc,
pApc->ApcMode
);
#endif
if(pApc->ApcMode == KernelMode)
{
//Kernel mode APC
//Do not dereference driver object
bDerefDriverObject = FALSE;
}
else if(pApc->ApcMode == UserMode)
{
//User-mode APC
//Free mem
ExFreePool(pApc);
pApc = NULL;
}
else
{
//Something else?
ASSERT(NULL);
}
return bDerefDriverObject;
}
extern "C" BOOL __stdcall NormalRoutine_Proc(PVOID NormalContext, PVOID SystemArgument1, PVOID SystemArgument2)
{
//Called from the asm64.asm/asm32.asm file for the NormalRoutine() call back
//INFO:
// It is called after KernelRoutine() at PASSIVE IRQL.
//RETURN:
// = TRUE to invoke safely ObDereferenceObject(g_DriverObject)
UNREFERENCED_PARAMETER(SystemArgument2);
PKAPC pApc = (PKAPC)NormalContext;
ASSERT(pApc);
DLL_STATS* pDllStats = (DLL_STATS*)SystemArgument1;
ASSERT(pDllStats);
ASSERT(pDllStats->IsValid());
#ifdef DBG_VERBOSE_DRV
DbgPrintLine("NormalRoutine: IRQL=%u, PID=%u, APC=0x%p, mode=%d, sectionType=%c"
,
KeGetCurrentIrql(),
(ULONG)(ULONG_PTR)PsGetCurrentProcessId(),
pApc,
pApc->ApcMode,
pDllStats->secType
);
#endif
//INFO:
// Our FAKE.dll contains two types of code inside of it:
// - Shell-code = small function written in Assembly language, that is used to inject our FAKE.dll from the user-mode
// (it does not require relocation and can run at any base address)
// - FAKE.dll = module itself that is being injected. The code that runs after injection is in DllMain.
// It is important to note that if our injected Dll fails to map at its PreferredAddress,
// this will make the loader return STATUS_RETRY
// and then insert a new work task (via LdrpRetryQueue) and retry to load it later using the DLL's
// full image path instead of the KnownDll section.
// Thus we need to make sure that our injected DLL is mapped at the PreferredAddress!
//Map the section to execute our Shell-code
PVOID BaseAddress = 0;
NTSTATUS status = CSection::MapSectionForShellCode(pDllStats, &BaseAddress);
//Dereference section object after we're done with it
//INFO: Since it's set as a permanent object it won't be unloaded yet
ObDereferenceObject(pDllStats->Section);
if(NT_SUCCESS(status))
{
//Our shell-code was mapped!
#ifdef _DEBUG
char buffDbg[GCPFN_BUFF_SIZE];
DbgPrintLine("Shell-code mapped: (0x%X) PID=%u: \"%S\", BaseAddr=0x%p, sectionType=%c"
,
status,
(ULONG)(ULONG_PTR)PsGetCurrentProcessId(),
CFunc::debugGetCurrentProcName(buffDbg, sizeof(buffDbg), TRUE),
BaseAddress,
pDllStats->secType
);
#endif
//Define our user-mode normal routine, which is UserModeNormalRoutine function
ASSERT(pDllStats->uRVA_ShellCode);
PKNORMAL_ROUTINE p_umNormalRoutine = (PKNORMAL_ROUTINE)((BYTE*)BaseAddress + pDllStats->uRVA_ShellCode);
//Calculate pointer to the inject DLL name in user-mode address space
//INFO: Such DLL must be placed in the System32 folder of the appropriate bitness
ASSERT(pDllStats->uRVA_DllName);
PVOID p_umDllName = (PVOID)((BYTE*)BaseAddress + pDllStats->uRVA_DllName);
#ifdef _WIN64
//For WOW64 section only
if(pDllStats->secType == SEC_TP_WOW)
{
//Instruct 64-bit version of NTDLL to use APC callback in the 32-bit NTDLL
PsWrapApcWow64Thread(&BaseAddress, (PVOID*)&p_umNormalRoutine);
}
#endif
//Set up user-mode APC that will do the injection for us
ASSERT(BaseAddress);
KeInitializeApc(pApc, KeGetCurrentThread(),
OriginalApcEnvironment,
KernelRoutine, RundownRoutine, p_umNormalRoutine, //These routines are implemented in Assembly language - see asm64.asm or asm32.asm
UserMode, //User-mode APC
BaseAddress //Pass BaseAddress as context into normal routine
);
//Prevent our driver from unloading since we're queuing another APC
ObReferenceObject(g_DriverObject);
//And initialize the APC
if(KeInsertQueueApc(pApc,
p_umDllName, //SystemArgument1 = points to injected DLL name as const WCHAR* (null terminated)
NULL, //SystemArgument2 = not used
IO_NO_INCREMENT))
{
//Queued APC OK
//Force the call to the user-mode APC
//INFO: It will be handled by the UserModeNormalRoutine() function in the dll_asm64.asm/dll_asm32.asm files
KeTestAlertThread(UserMode);
//Derefence driver object
return TRUE;
}
else
{
//Error
status = STATUS_BAD_DATA;
DbgPrintLine("ERROR: (0x%X) NormalRoutine > KeInsertQueueApc, PID=%u, sectionType=%c"
,
status,
(ULONG)(ULONG_PTR)PsGetCurrentProcessId(),
pDllStats->secType
);
//Reverse reference count
ObDereferenceObject(g_DriverObject);
//And unmap section with shell-code
ASSERT(BaseAddress);
VERIFY(NT_SUCCESS(MmUnmapViewOfSection(IoGetCurrentProcess(), BaseAddress)));
}
}
else
{
//Error
DbgPrintLine("ERROR: (0x%X) MapSectionForShellCode, PID=%u, sectionType=%c"
,
status,
(ULONG)(ULONG_PTR)PsGetCurrentProcessId(),
pDllStats->secType
);
}
//Free mem
ExFreePool(pApc);
pApc = NULL;
//Derefence driver object
return TRUE;
}
NTSTATUS CSection::MapSectionForShellCode(DLL_STATS* pDllStats, PVOID* pOutBaseAddr)
{
//Map section to run our Shell-code in into the current process
//INFO: The goal here is NOT to map it at its PreferredAddress, as otherwise it will conflict with loading
// of the FAKE.DLL itself that we are injecting ...
//'pDllStats' = stats for the injected DLL
//'pOutBaseAddr' = if not NULL, will receive base address where DLL was mapped for Shell-Code, or NULL if error
//RETURN:
// = Status code of the operation
// IMPORTANT: Upon success the 'pDllStats->Section' will be mapped and needs to be unmapped
// later with *UnmapViewOfSection!
ASSERT(pDllStats);
NTSTATUS status;
PVOID BaseAddress = NULL;
if(pDllStats->IsValid())
{
//Try to reserve memory at the PreferredAddress that we have for our injected DLL
//INFO: This should ensure that we map our section at a different address later ...
ASSERT(pDllStats->PreferredAddress);
PVOID ReservedAddress = pDllStats->PreferredAddress;
SIZE_T RegionSize = pDllStats->SizeOfImage;
NTSTATUS status2 = ZwAllocateVirtualMemory(NtCurrentProcess(),
&ReservedAddress, 0, &RegionSize, MEM_RESERVE, PAGE_NOACCESS);
//INFO: We get STATUS_CONFLICTING_ADDRESSES if there's something else at the ReservedAddress already
if(status2 != STATUS_SUCCESS &&
status2 != STATUS_CONFLICTING_ADDRESSES)
{
//Failed for some reason?
DbgPrintLine("ERROR: (0x%X) MapSectionForShellCode > ZwAllocateVirtualMemory, PID=%u, sectionType=%c, "
"addr=0x%p, sz=0x%X"
,
status2,
(ULONG)(ULONG_PTR)PsGetCurrentProcessId(),
pDllStats->secType,
pDllStats->PreferredAddress,
pDllStats->SizeOfImage
);
}
//Then map our section (at any address as our Shell-code doesn't care)
ASSERT(pDllStats->Section);
SIZE_T ViewSize = 0;
LARGE_INTEGER Offset = {};
status = MmMapViewOfSection(pDllStats->Section, IoGetCurrentProcess(), &BaseAddress, 0, 0,
&Offset, &ViewSize, ViewUnmap, 0, PAGE_EXECUTE);
if(!NT_SUCCESS(status))
{
//Error
DbgPrintLine("ERROR: (0x%X) MapSectionForShellCode > MmMapViewOfSection, PID=%u, sectionType=%c"
,
status,
(ULONG)(ULONG_PTR)PsGetCurrentProcessId(),
pDllStats->secType
);
}
if(NT_SUCCESS(status2))
{
//Free memory chunk that we allocated above
RegionSize = 0;
VERIFY(NT_SUCCESS(ZwFreeVirtualMemory(NtCurrentProcess(), &ReservedAddress, &RegionSize, MEM_RELEASE)));
}
}
else
{
//Error
ASSERT(NULL);
status = STATUS_INVALID_PARAMETER_MIX;
}
if(pOutBaseAddr)
*pOutBaseAddr = BaseAddress;
return status;
}