首页 > 其他分享 >几种unhook手法的学习

几种unhook手法的学习

时间:2024-04-29 16:03:50浏览次数:41  
标签:NULL ntdll dll hook unhook 手法 DWORD 几种 PTR

文章首发阿里云先知社区:https://xz.aliyun.com/t/14310

了解过免杀的都知道,杀软会对敏感 api 进行 hook 操作,而我们通常有两种方式进行解决,syscall 和 unhook,而我们在 syscall 的时候有时候会导致堆栈不完整,在杀软看来是一些异常的行为,比如下图可以看到 RIP 指针直接已经在 Program 里面了,
image.png
(正常的情况如下图所示:
image.png

而我们在 unhook 时就完全不需要这种考虑,因为我们用的是一段新的 ntdll 或者其他 dll 的内存,调用的发出在杀软看起来是合理的,接下来我们一起来学习一下。

从磁盘重载 ntdll

原理图如下:
image.png
可以看出来,其实就是从磁盘上 clean 的 ntdll 的.text 端覆盖内存中被 hook 的ntdll 的.text 端。
我们 unhook 的流程如下,如果对 pe 文件结构有了解的话会看的比较轻松。

  1. 将 ntdll.dll 的新副本从磁盘映射到进程内存
  2. 查找被 hook 的 ntdll.dll的 .text 部分的虚拟地址
    1. 获取ntdll.dll基址
    2. 模块基址 + 模块的 .text 段 VirtualAddress
  3. 查找新映射ntdll.dll的 .text 段的虚拟地址
  4. 获取被 hook 的 ntdll .text 段的内存写的权限
  5. 将新映射的ntdll.dll的 .text 段覆盖到被 hook 的 ntdll 的 .text 部分
  6. 还原之前被 hook 的 ntdll .text 段的内存被原本的内存权限

下面是一个简单的 demo:

#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;
}

这种方式是最简单的并且理论上可以对所有的 dll 进行 hook,但是缺点是需要读取磁盘上的 dll,而如果杀软对读取系统 dll 的行为进行了监控,那么我们这种方式其实是不好使的。

PE 文件映射绕过 hook

这个思路是在https://idiotc4t.com/defense-evasion/load-ntdll-too 学到的,当我们通过CreateFileMapping,MapViewOfFile 等 api 进行文件映射时,果被打开文件是 PE格式,那么这个文件会按照内存展开,那么我们猜想是不是这个被第二次载入内存的ntdll是不是就是一个干净的ntdll,能不能帮助我们绕过一些 hook。
demo 如下:

#include <Windows.h>
#include <stdio.h>

#define DEREF( name )*(UINT_PTR *)(name)
#define DEREF_64( name )*(DWORD64 *)(name)
#define DEREF_32( name )*(DWORD *)(name)
#define DEREF_16( name )*(WORD *)(name)
#define DEREF_8( name )*(BYTE *)(name)

typedef NTSTATUS(NTAPI* pNtAllocateVirtualMemory)(
	HANDLE ProcessHandle,
	PVOID* BaseAddress,
	ULONG_PTR ZeroBits,
	PSIZE_T RegionSize,
	ULONG AllocationType,
	ULONG Protect);

FARPROC WINAPI GetProcAddressR(HANDLE hModule, LPCSTR lpProcName)
{
	UINT_PTR uiLibraryAddress = 0;
	FARPROC fpResult = NULL;

	if (hModule == NULL)
		return NULL;
	uiLibraryAddress = (UINT_PTR)hModule;

	__try
	{
		UINT_PTR uiAddressArray = 0;
		UINT_PTR uiNameArray = 0;
		UINT_PTR uiNameOrdinals = 0;
		PIMAGE_NT_HEADERS pNtHeaders = NULL;
		PIMAGE_DATA_DIRECTORY pDataDirectory = NULL;
		PIMAGE_EXPORT_DIRECTORY pExportDirectory = NULL;
		pNtHeaders = (PIMAGE_NT_HEADERS)(uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew);
		pDataDirectory = (PIMAGE_DATA_DIRECTORY)&pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
		pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(uiLibraryAddress + pDataDirectory->VirtualAddress);
		uiAddressArray = (uiLibraryAddress + pExportDirectory->AddressOfFunctions);
		uiNameArray = (uiLibraryAddress + pExportDirectory->AddressOfNames);
		uiNameOrdinals = (uiLibraryAddress + pExportDirectory->AddressOfNameOrdinals);
		if (((DWORD)lpProcName & 0xFFFF0000) == 0x00000000)
		{
			uiAddressArray += ((IMAGE_ORDINAL((DWORD)lpProcName) - pExportDirectory->Base) * sizeof(DWORD));
			fpResult = (FARPROC)(uiLibraryAddress + DEREF_32(uiAddressArray));
		}
		else
		{
			DWORD dwCounter = pExportDirectory->NumberOfNames;
			while (dwCounter--)
			{
				char* cpExportedFunctionName = (char*)(uiLibraryAddress + DEREF_32(uiNameArray));
				if (strcmp(cpExportedFunctionName, lpProcName) == 0)
				{
					uiAddressArray += (DEREF_16(uiNameOrdinals) * sizeof(DWORD));
					fpResult = (FARPROC)(uiLibraryAddress + DEREF_32(uiAddressArray));

					break;
				}
				uiNameArray += sizeof(DWORD);
				uiNameOrdinals += sizeof(WORD);
			}
		}
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		fpResult = NULL;
	}

	return fpResult;
}


int main() {

	HANDLE hNtdllfile = CreateFileA("c:\\windows\\system32\\ntdll.dll", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	HANDLE hNtdllMapping = CreateFileMapping(hNtdllfile, NULL, PAGE_READONLY | SEC_IMAGE, 0, 0, NULL);
	LPVOID lpNtdllmaping = MapViewOfFile(hNtdllMapping, FILE_MAP_READ, 0, 0, 0);

	pNtAllocateVirtualMemory NtAllocateVirtualMemory = (pNtAllocateVirtualMemory)GetProcAddressR((HMODULE)lpNtdllmaping, "NtAllocateVirtualMemory");

	int err = GetLastError();

	LPVOID Address = NULL;
	SIZE_T uSize = 0x1000;

	NTSTATUS status = NtAllocateVirtualMemory(GetCurrentProcess(), &Address, 0, &uSize, MEM_COMMIT, PAGE_READWRITE);
	
	

	return 0;
};

这种方式需要使用CreateFileMapping,MapViewOfFile 等 api 进行文件映射,此类 api 也会被杀软关注,并且我们无法保证打开哪些文件才可以获得干净的 ntdll,因此感觉这个方式的实战价值不算很高。

通过创建挂起的进程来获得干净的 ntdll

前置条件

我们都知道,每个进程的内存里都会加载各种各样的 dll,每个程序不同,其加载的 dll 也都不同,但是每个进程都应该加载Kernel32.dll、Kernelbase.dll 和 Ntdll.dll 等,因为这些 DLL 包含进程与操作系统交互所需的低级指令和 API 调用。而我们发现,在同一个系统上的两个进程在相同基地址处加载了相同的系统 DLL。
image.png
并且系统 dll 的每个模块也被加载到了相同的地址
image.png

原理

我们来看一下当我们程序在加载的时候,edr 的 dll 和系统 dll 被一起加载进来
image.png
此时,我们的进程是挂起的,我们去看一些 Nt 函数时,会发现他们还没有被 hook
image.png
而当我们恢复挂起的进程之后,可以发现 Nt 函数此时被 hook 了
image.png
此时我们可以确定两件事情:

  • 新挂起进程的内存是干净的,没有被 hook 的
  • 所有的系统 dll 在被加载时的内存空间都是一样的

所以我们接下来要做的事情就是想办法从干净的内存读取 ntdll 并且覆盖到当前进程被 hook 的内存空间。
我们可以用 ReadProcessMemory 这个 api 来读取其他进程的内存,我们先提前计算好 ntdll 在内存空间中的位置,然后直接去读取就可以了,demo 代码可以看 https://github.com/dosxuz/PerunsFart,并且 github 有一个应用此技术武器化的工具:https://github.com/optiv/Freeze

通过自定义的跳转函数进行 unhook

我们都知道加载 dll 的函数是 LoadLibrary,这个函数在 kernel32.dll 里面,然而这个函数在 ntdll 里面对应的函数时 LdrLoadDLL,而我们这个方法的主角就是 LdrLoadDLL。
在 x64 平台下,我们去查看这个函数的汇编指令
image.png
而我们就可以自实现一个函数,汇编如下:
其中第一条指令时 LdrLoadDLL 的第一条指令,我们自己实现,防止此条指令被 hook,变成 jmp 指令。
address 就是内存中 LdrLoadDLL 第二条指令的位置,在 x64 下就是 address(LdrLoadDLL)+5

  mov qword ptr[rsp + 10h]  //原始的LdrLoadDll中汇编,使用我们自己的防止被hook
	mov r11,address		//address(LdrLoadDLL)+5
	jmp rll
	ret

这里附上一张我在 vs 调试时的反汇编,我们只需要将这些字节起来放到一起就可以了。
image.png
首先先完成了LdrLoadDLL 的第一条指令,然后将address(LdrLoadDLL)+5 放到 r11 寄存器中,然后我们直接 jmp r11 就可以了,因为 r11 里面的地址就是LdrLoadDLL 第二条指令的地址,我们这样做也是避免了LdrLoadDLL 被 hook,第一条指令变成 jmp edr.address。
并且我们这样做所有的函数发出都是从 ntdll 里面发出的,如图:
image.png
这样我们就自己实现了一个跳转函数,demo 代码可以参考
https://github.com/trickster0/LdrLoadDll-Unhooking,原作者只提供了 x64 下的代码,我自己稍微改了一下兼容 x64 和 x86 ,地址:https://github.com/fdx-xdf/LdrLoadDll-Unhooking-x86-x64/
详细的分析过程可以参考:https://killer.wtf/2022/01/19/CustomJmpUnhook.html

参考文章:

https://www.ired.team/offensive-security/defense-evasion/how-to-unhook-a-dll-using-c++
https://idiotc4t.com/defense-evasion/load-ntdll-too
https://www.optiv.com/insights/source-zero/blog/sacrificing-suspended-processes
https://dosxuz.gitlab.io/post/perunsfart/
https://killer.wtf/2022/01/19/CustomJmpUnhook.html

标签:NULL,ntdll,dll,hook,unhook,手法,DWORD,几种,PTR
From: https://www.cnblogs.com/fdxsec/p/18146252

相关文章

  • 实现HTML标签的转义、反转义的几种方法
    原文链接:https://blog.csdn.net/huanggang0101/article/details/99621029方法一:通过正则表达式进行替换1,HTML标签的转义方法//HTML标签转义(<-----><)functionhtml2Escape(sHtml){returnsHtml.replace(/[<>&"]/g,function(c){return{'<':�......
  • 网页布局------几种布局方式
    1、认识布局(1)确认页面的版心宽度版心是指页面的有效使用面积,主要元素以及内容所在的区域,一般在浏览器窗口中水平居中显示。在设计网页时,页面尺寸宽度一般为1200-1920排序。但是为例适配不同分辨率的显示器,一般版心宽度为1000-1200px。例如,屏幕分辨率为1021*768的浏览器,在浏览器......
  • SpringBoot中几种好用的代码生成器(基于Mybatis-plus生成entity、mapper、xml等)
    前言熟悉Spring框架的同学一定都知道MVC开发模式吧,控制器(Controller)、业务类(Service)、持久层(Repository)、数据库映射(Mapper)、各种DO类构成了我们服务端的代码。初学的时候,觉得新鲜手写这些东西不觉得有啥,但是写久了就会觉得很烦。好不容易在数据库中写完了一遍字段,在Java代码又要......
  • .net开发还在使用guid吗?下面几种id生成器更加合适
    <ItemGroup><PackageReferenceInclude="IdGen"Version="3.0.5"/><PackageReferenceInclude="Nanoid"Version="3.0.0"/><PackageReferenceInclude="Snowflake.Core"Version=&......
  • Unlink原理和一些手法
    Unlink原理和一些手法✅简单介绍一下unlink相关的知识unlink是利用glibcmalloc的内存回收机制造成攻击的,核心就在于当两个free的堆块在物理上相邻时,会将他们合并,并将原来free的堆块在原来的链表中解链,加入新的链表中其目的是把一个双向链表中的空闲块拿出来(例如free时和目前......
  • 几种中文字体的比较
    根据自己的喜好给常见的几个中文字体的打分:字体选项字体名得分adobeAdobe宋体Std5fandolFandolSong0founder方正书宋_GPK10hanyi汉仪宋体6sinotype华文宋体3win中易宋体9fandol缺少偏僻字体,故得零分。......
  • C#实现单例模式的几种方法
    C#实现单例模式的几种方法    C#中readonly的理解与使用   Readonly(C#参考)介绍单例模式是软件工程学中最富盛名的设计模式之一。从本质上看,单例模式只允许被其自身实例化一次,且向外部提供了一个访问该实例的接口。通常来说,单例对象进行实例化时一般不带参数,因为......
  • SpringBoot项目实现日志打印SQL明细(包括SQL语句和参数)几种方式
    前言我们在开发项目的时候,都会连接数据库。有时候遇到问题需要根据我们编写的SQL进行分析,但如果不进行一些开发或者配置的话,这些SQL是不会打印到控制台的,它们默认是隐藏的。下面给大家介绍几种常用的方法。第一种、代码形式Mybatis框架是Java程序员最常用的数据库映射框架,MyBa......
  • 在Linux中,如何查看文件内容?列出几种方法。
    在Linux中,有多种方法可以查看文件内容。以下是几种常用的方法:1.cat命令cat命令用于连接并显示文件的内容。如果文件不大,可以直接使用cat命令查看整个文件内容。catfilename如果文件很大,cat命令会将整个文件内容输出到终端,这可能导致终端滚动非常快,不易阅读。为了解决......
  • 手写协议报文 c语言手法
    鉴于绝大部分文件、网络通信协议、非网络通信协议都有类似的结构{类型,长度,校验,不定长数据,结束标志},再高级点的会包含多个单层TLV,甚至嵌套TLV,状态机流转标志等等。所以编程语言上也需要采用一定的手法。建立结构结构体和联合体例如//结构体对齐宏#ifdefined(__GNUC__)#defin......