介紹
反射式注入 dll ,不會調用 LoadLibrary 這個 API,因此也無法使用 CreateToolhelp32Snapshot 遍歷到這個模塊。同時也不需要 DLL 留在磁盤上(可以通過網絡下發,或加密后存放在磁盤),因此這種注入方式更加隱蔽。
原理
總的來說,就是我們要重新實現一遍 LoadLibrary 這個 API 🙃:
假設現在我們已經使用 ReadFile 拿到了 DLL 的所有內容
之后我們需要調用 VirtualAlloc 在目標進程中申請一塊內存用來存放這個 DLL
使用 WriteProcessMemory 將 DLL 的內容寫入剛申請的虛擬內存中
關鍵 這個 DLL 中需要有一個導出函數,我們暫且叫它 ReflectiveLoader,這個函數的功能就是裝載自身。所以我們只需要等到 DLL 被載入內存后,使用 CreateRemoteThread 創建一個遠程線程來調用這個導出函數。
最后總結一點,核心問題就是如何編寫這個 ReflectiveLoader 函數 💸
另外就是,因為調用 ReflectiveLoader 時, DLL 還沒有加載(畢竟人家的功能就是加載 DLL。。),在編寫這個函數的時候就會有很多限制(比如無法使用全局變量)。
DLL 自裝載
因為 PE 文件包含了很多區段(節),為了節省空間,這些區段在磁盤上存儲時是很緊湊的,如果把它們原模原樣的放入內存中運行一定是會出問題的。所以 DLL 子裝載函數的任務就是按照規則把這些區段按照規則映射到對應的虛擬地址中去。另外我們寫的 DLL 會用到其他的 DLL (相對於被注入進程來說),這時我們還需要把我們 DLL 所依賴的 DLL 也裝入內存,並修復導入表。
-
首先使用 _ReturnAddress() 獲取當前函數的返回地址,因為調用這個函數是在 ReflectiveLoader 的內部,因此從這個地址向上遍歷,找 0x4d ,0x5a 就可以定位到 DLL 的 PE 文件頭所在的虛擬地址 (o゚v゚)ノ
-
使用內聯匯編 mov EAX, FS:[0x30] 拿到 PEB ,用 PEB 遍歷出目標進程所有模塊的基地址,之后通過解析 PE 文件的導出表獲取導出函數的偏移地址,基地址+偏移計算出我們需要的函數( LoadLibrary, GetProcAddress, VirtualAlloc,NtFlushInstructionCache)的虛擬地址
-
雖然在調用 ReflectiveLoader 前,我們寫的注入器程序已經在目標進程申請了一塊空間,但是那是存放的是 DLL 在磁盤上的結構,要將 DLL 映射到內存需要重新分配內存。在 IMAGE_OPTIONAL_HEADER -> SizeOfImage 記錄了這個 DLL 裝入內存時占用的大小,用這個值作為 VirtualAlloc 的參數。
-
將 DLL 的 PE文件頭和各個節復制到對應的位置。
-
被注入的 DLL可能還依賴於其他的 DLL,因此我們還需要使用 LoadLibrary 加載這些 DLL(LoadLibrary 的地址在上面已經拿到)
-
被注入的 DLL 只有其 ReflectiveLoader 中的代碼是故意寫成地址無關、不需要重定位的,其他部分的代碼則需要經過重定位才能正確運行。對於重定位問題,PE 的可選頭中 DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC] 指向了重定位表:
-
IMAGE_BASE_RELOCATION結構和后面緊跟的若干個Typeoffset組成了一個塊,其大小為結構體中的SizeOfBlock。因此,Typeoffset的數量可以根據SizeofBlock算出。當一個塊結束時,后面緊跟的就是下一個塊。若SizeofBlock為0則標志着重定位表結束了。Typeoffset的高4位代表重定位類型,一般為3,低12位則表示重定位地址。這個地址和IMAGE_BASE_RELOCATION中的VirtualAddress加起來則指向一個需要重定位的指令。
-
DLL重定位。首先計算得到基地址的偏移量,也就是實際的 DLL 加載地址減去 DLL 的推薦加載地址(保存在 NT可選頭的 ImageBase 中,實際 DLL 加載地址是我們調用 VirtualAlloc()的返回值。然后將 VirtualAddress 和 Typeoffset 合力組成的地址所指向的雙字加上這個偏移量,重定位就完成了。即:(DWORD)(VirtualAddress + Typeoffset的低12位) += (實際DLL加載地址 – 推薦DLL加載地址)
代碼參考
https://github.com/SudoZhange/ProcessInjection
https://github.com/DarthTon/Xenos
以下代碼來自 blackbone 框架 🙃:
r3:
#define IOCTL_BLACKBONE_INJECT_DLL (ULONG)CTL_CODE(FILE_DEVICE_BLACKBONE, 0x80B, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
typedef enum _MmapFlags
{
KNoFlags = 0x00, // No flags
KManualImports = 0x01, // Manually map import libraries
KWipeHeader = 0x04, // Wipe image PE headers
KHideVAD = 0x10, // Make image appear as PAGE_NOACESS region
KRebaseProcess = 0x40, // If target image is an .exe file, process base address will be replaced with mapped module value
KNoThreads = 0x80, // Don't create new threads, use hijacking
KNoExceptions = 0x01000, // Do not create custom exception handler
KNoSxS = 0x08000, // Do not apply SxS activation context
KNoTLS = 0x10000, // Skip TLS initialization and don't execute TLS callbacks
} KMmapFlags;
typedef enum _InjectType
{
IT_Thread, // CreateThread into LdrLoadDll
IT_Apc, // Force user APC into LdrLoadDll
IT_MMap, // Manual map
} InjectType;
typedef struct _INJECT_DLL
{
InjectType type; // Type of injection
wchar_t FullDllPath[512]; // Fully-qualified path to the target dll
wchar_t initArg[512]; // Init routine argument
ULONG initRVA; // Init routine RVA, if 0 - no init routine
ULONG pid; // Target process ID
BOOLEAN wait; // Wait on injection thread
BOOLEAN unlink; // Unlink module after injection
BOOLEAN erasePE; // Erase PE headers after injection
KMmapFlags flags; // Manual map flags
ULONGLONG imageBase; // Image address in memory to manually map
ULONG imageSize; // Size of memory image
BOOLEAN asImage; // Memory chunk has image layout
} INJECT_DLL, *PINJECT_DLL;
/* -------------------------------------------------------- */
BOOL 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(驅動設備句柄, IOCTL_BLACKBONE_INJECT_DLL, &data, sizeof( data ), nullptr, 0, &bytes, NULL ))
return FALSE();
return TRUE;
}
r0:
typedef enum _InjectType
{
IT_Thread, // CreateThread into LdrLoadDll
IT_Apc, // Force user APC into LdrLoadDll
IT_MMap, // Manual map
} InjectType;
typedef enum _PolicyOpt
{
Policy_Disable,
Policy_Enable,
Policy_Keep, // Don't change current value
} PolicyOpt;
typedef struct _SET_PROC_PROTECTION
{
ULONG pid; // Process ID
PolicyOpt protection; // Process protection
PolicyOpt dynamicCode; // DynamiCode policy
PolicyOpt signature; // BinarySignature policy
} SET_PROC_PROTECTION, *PSET_PROC_PROTECTION;
/*------------------- 篇幅原因,只貼上核心代碼 -------------------*/
/// <summary>
/// Inject dll into process
/// </summary>
/// <param name="pid">Target PID</param>
/// <param name="pPath">TFull-qualified dll path</param>
/// <returns>Status code</returns>
NTSTATUS BBInjectDll( IN PINJECT_DLL pData )
{
NTSTATUS status = STATUS_SUCCESS;
NTSTATUS threadStatus = STATUS_SUCCESS;
PEPROCESS pProcess = NULL;
status = PsLookupProcessByProcessId( (HANDLE)pData->pid, &pProcess );
if (NT_SUCCESS( status ))
{
KAPC_STATE apc;
UNICODE_STRING ustrPath, ustrNtdll;
SET_PROC_PROTECTION prot = { 0 };
PVOID pNtdll = NULL;
PVOID LdrLoadDll = NULL;
PVOID systemBuffer = NULL;
BOOLEAN isWow64 = (PsGetProcessWow64Process( pProcess ) != NULL) ? TRUE : FALSE;
// Process in signaled state, abort any operations
if (BBCheckProcessTermination( PsGetCurrentProcess() ))
{
DPRINT( "BlackBone: %s: Process %u is terminating. Abort\n", __FUNCTION__, pData->pid );
if (pProcess)
ObDereferenceObject( pProcess );
return STATUS_PROCESS_IS_TERMINATING;
}
// Copy mmap image buffer to system space.
// Buffer will be released in mapping routine automatically
if (pData->type == IT_MMap && pData->imageBase)
{
__try
{
ProbeForRead( (PVOID)pData->imageBase, pData->imageSize, 1 );
systemBuffer = ExAllocatePoolWithTag( PagedPool, pData->imageSize, BB_POOL_TAG );
RtlCopyMemory( systemBuffer, (PVOID)pData->imageBase, pData->imageSize );
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
DPRINT( "BlackBone: %s: AV in user buffer: 0x%p - 0x%p\n", __FUNCTION__,
pData->imageBase, pData->imageBase + pData->imageSize );
if (pProcess)
ObDereferenceObject( pProcess );
return STATUS_INVALID_USER_BUFFER;
}
}
KeStackAttachProcess( pProcess, &apc );
RtlInitUnicodeString( &ustrPath, pData->FullDllPath );
RtlInitUnicodeString( &ustrNtdll, L"Ntdll.dll" );
// Handle manual map separately
if (pData->type == IT_MMap)
{
MODULE_DATA mod = { 0 };
__try {
status = BBMapUserImage(
pProcess, &ustrPath, systemBuffer,
pData->imageSize, pData->asImage, pData->flags,
pData->initRVA, pData->initArg, &mod
);
}
__except (EXCEPTION_EXECUTE_HANDLER){
DPRINT( "BlackBone: %s: Fatal exception in BBMapUserImage. Exception code 0x%x\n", __FUNCTION__, GetExceptionCode() );
}
KeUnstackDetachProcess( &apc );
if (pProcess)
ObDereferenceObject( pProcess );
return status;
}
// Get ntdll base
pNtdll = BBGetUserModule( pProcess, &ustrNtdll, isWow64 );
if (!pNtdll)
{
DPRINT( "BlackBone: %s: Failed to get Ntdll base\n", __FUNCTION__ );
status = STATUS_NOT_FOUND;
}
// Get LdrLoadDll address
if (NT_SUCCESS( status ))
{
LdrLoadDll = BBGetModuleExport( pNtdll, "LdrLoadDll", pProcess, NULL );
if (!LdrLoadDll)
{
DPRINT( "BlackBone: %s: Failed to get LdrLoadDll address\n", __FUNCTION__ );
status = STATUS_NOT_FOUND;
}
}
// If process is protected - temporarily disable protection
if (PsIsProtectedProcess( pProcess ))
{
prot.pid = pData->pid;
prot.protection = Policy_Disable;
prot.dynamicCode = Policy_Disable;
prot.signature = Policy_Disable;
BBSetProtection( &prot );
}
// Call LdrLoadDll
if (NT_SUCCESS( status ))
{
SIZE_T size = 0;
PINJECT_BUFFER pUserBuf = isWow64 ? BBGetWow64Code( LdrLoadDll, &ustrPath ) : BBGetNativeCode( LdrLoadDll, &ustrPath );
if (pData->type == IT_Thread)
{
status = BBExecuteInNewThread( pUserBuf, NULL, THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER, pData->wait, &threadStatus );
// Injection failed
if (!NT_SUCCESS( threadStatus ))
{
status = threadStatus;
DPRINT( "BlackBone: %s: User thread failed with status - 0x%X\n", __FUNCTION__, status );
}
// Call Init routine
else
{
if (pUserBuf->module != 0 && pData->initRVA != 0)
{
RtlCopyMemory( pUserBuf->buffer, pData->initArg, sizeof( pUserBuf->buffer ) );
BBExecuteInNewThread(
(PUCHAR)pUserBuf->module + pData->initRVA,
pUserBuf->buffer,
THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER,
TRUE,
&threadStatus
);
}
else if (pUserBuf->module == 0)
DPRINT( "BlackBone: %s: Module base = 0. Aborting\n", __FUNCTION__ );
}
}
else if (pData->type == IT_Apc)
{
status = BBApcInject( pUserBuf, pProcess, pData->initRVA, pData->initArg );
}
else
{
DPRINT( "BlackBone: %s: Invalid injection type specified - %d\n", __FUNCTION__, pData->type );
status = STATUS_INVALID_PARAMETER;
}
// Post-inject stuff
if (NT_SUCCESS( status ))
{
// Unlink module
if (pData->unlink)
BBUnlinkFromLoader( pProcess, pUserBuf->module, isWow64 );
// Erase header
if (pData->erasePE)
{
__try
{
PIMAGE_NT_HEADERS64 pHdr = RtlImageNtHeader( pUserBuf->module );
if (pHdr)
{
ULONG oldProt = 0;
size = (pHdr->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) ?
((PIMAGE_NT_HEADERS32)pHdr)->OptionalHeader.SizeOfHeaders :
pHdr->OptionalHeader.SizeOfHeaders;
if (NT_SUCCESS( ZwProtectVirtualMemory( ZwCurrentProcess(), &pUserBuf->module, &size, PAGE_EXECUTE_READWRITE, &oldProt ) ))
{
RtlZeroMemory( pUserBuf->module, size );
ZwProtectVirtualMemory( ZwCurrentProcess(), &pUserBuf->module, &size, oldProt, &oldProt );
DPRINT( "BlackBone: %s: PE headers erased. \n", __FUNCTION__ );
}
}
else
DPRINT( "BlackBone: %s: Failed to retrieve PE headers for image\n", __FUNCTION__ );
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
DPRINT( "BlackBone: %s: Exception during PE header erease: 0x%X\n", __FUNCTION__, GetExceptionCode() );
}
}
}
ZwFreeVirtualMemory( ZwCurrentProcess(), &pUserBuf, &size, MEM_RELEASE );
}
// Restore protection
if (prot.pid != 0)
{
prot.protection = Policy_Enable;
prot.dynamicCode = Policy_Enable;
prot.signature = Policy_Enable;
BBSetProtection( &prot );
}
KeUnstackDetachProcess( &apc );
}
else
DPRINT( "BlackBone: %s: PsLookupProcessByProcessId failed with status 0x%X\n", __FUNCTION__, status );
if (pProcess)
ObDereferenceObject( pProcess );
return status;
}