首页 > 系统相关 >【Windows内核】Ntdll解除挂钩学习

【Windows内核】Ntdll解除挂钩学习

时间:2025-01-09 19:22:39浏览次数:1  
标签:ntdll PVOID Windows text Ntdll dll 内核 NULL

简介

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

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

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

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

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

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

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

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

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

磁盘上的偏移与内存上的偏移

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

接下来我们将从磁盘上来获取ntdll.dll。从磁盘上获取到的ntdll.dll文件是从未被篡改的版本。在Windows操作系统中,Ntdll.dll通常位于C:\Windows\System32\目录中,通过这种方式,可以从原始的磁盘文件中获取一个干净,未被修改的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:\Windows\System32\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;  // 如果打开文件失败,跳转到结束部分
    }

    // 获取文件大小
    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);
}

接下来需要使用CreateFileMappingA和MapViewOfFile函数来映射Ntdll了。

如果我们要使用CreateFileMappingA和MapViewOfFile函数来从C:\Windows\System32读取并映射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文件如果是从磁盘上读取而不是映射到内存时,其.text段的偏移量可能是4096,而不是1024,所以将文件映射到内存时比较可靠的。
因为.text偏移量始终等于IMAGE_SECTION_HEADER.VirtualAddressDLL 文件的偏移量。

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

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

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

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

#ifdef _WIN64
	PPEB pPeb = (PPEB)__readgsqword(0x60); // 读取 GS 寄存器 0x60 偏移
#elif _WIN32
	PPEB 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_ENTRY pLdr = (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();
}

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

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

PIMAGE_DOS_HEADER	pLocalDosHdr = (PIMAGE_DOS_HEADER)ntdllbase;

那么下来就是获取Nt头。

PIMAGE_NT_HEADERS 	pLocalNtHdrs = (PIMAGE_NT_HEADERS)((PBYTE)ntdllbase + pLocalDosHdr->e_lfanew);

获取Nt头之后,通过Nt头定位到可选PE头的BaseCode字段。通过BaseCode字段的值加上ntdll的基地址就可以获取到.text段的地址了。

PVOID	pLocalNtdllTxt	= (PVOID)(pLocalNtHdrs->OptionalHeader.BaseOfCode + (ULONG_PTR)ntdllbase);

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

获取到.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函数,该函数的目标是将本地Hook的Ntdll.dll的.text部分替换为未Hook的版本。该函数使用预处理指令根据ntdll.dll的方式来调整.text部分的偏移量。

如下代码:

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

BOOL ReplaceNtdllTxtSection(IN PVOID pUnhookedNtdll) {

    PVOID pLocalNtdll = (PVOID)FetchLocalNtdllBaseAddress();  // 获取本地已钩的 Ntdll.dll 基地址

    // 打印本地和未钩住的 Ntdll 基地址
    printf("\t[i] 'Hooked' Ntdll Base Address : 0x%p \n\t[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 \n\t[i] 'Unhooked' Ntdll Text Section Address : 0x%p \n\t[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;
}

如上代码的本质其实就是获取到本地已经Hook的Ntdll的.text段和未Hook的Ntdll的.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)这里的判断很简单。

pLocalNtdllTxt和pRemoteNtdllTxt分别指向勾住和未勾住的Ntdll.dll的指针。

*(ULONG*)pLocalNtdllTxt是强制转换为 ULONG*(无符号长整型指针)然后解引用它们。

最后取出它们所指向的内存地址中的4字节数据。其实就是0xCC 0xCC 0xCC 0xCC

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

原创 relaysec Relay学安全

标签:ntdll,PVOID,Windows,text,Ntdll,dll,内核,NULL
From: https://www.cnblogs.com/o-O-oO/p/18662773

相关文章

  • windows下php安装依赖版本工具composer
    1.先把php加入到环境变量 2.直接下载composer.phar,地址:https://dl.laravel-china.org/composer.phar把下载的composer.phar放到PHP安装目录  命令下载: php-r"copy('https://getcomposer.org/installer','composer-setup.php');"phpcomposer-setup.phpphp......
  • CC430F5137IRGZR数据手册 CC430F613x、CC430F612x、CC430F513一款低功耗射频无线收发
    TICC430系列超低功耗片上系统(SoC)器微控制搭载有集成射频收发器内核,并包含数个采用各种不同外设集的器件,可广泛应用于各种应用。此架构经过优化,与五种低功耗模式相配合使用,可延长便携式测量应用中的电池寿命。该器件具有功能强大的MSP43016位RISCCPU、16位寄存器和有......
  • .NET 隐藏/显示、自定义windows系统光标
    本文介绍如何操作windows系统光标。正常我们设置/隐藏光标,只能改变当前窗体或者控件范围,无法全局操作windows光标。接到一个需求,想隐藏windows全局的鼠标光标显示,下面讲下如何操作 先了解下系统鼠标光标,在鼠标属性-自定义列表中可以看到一共有13种类型,对应13种工作状态:操作系......
  • windows C#-泛型类型参数的约束详解(二)
    未绑定的类型参数没有约束的类型参数(如公共类SampleClass<T>{}中的T)称为未绑定的类型参数。未绑定的类型参数具有以下规则:不能使用!=和==运算符,因为无法保证具体的类型参数能支持这些运算符。可以在它们与System.Object之间来回转换,或将它们显式转换为任何接口......
  • windows C#-泛型类型参数的约束详解(一)
    使用约束的原因约束指定类型参数的功能和预期。声明这些约束意味着你可以使用约束类型的操作和方法调用。如果泛型类或方法对泛型成员使用除简单赋值之外的任何操作,包括调用System.Object不支持的任何方法,则对类型参数应用约束。例如,基类约束告诉编译器,只有此类型的对象......
  • mongodb windows zip安装并服务自启动
    1.下载并解压。2.新建文件/文件夹data/db目录logs/mongod.log文件conf/mongod.conf文件3.编辑conf/mongod.conf文件systemLog:destination:filelogAppend:truepath:F:/mongodb-win32-x86_64-windows-6.0.20-rc3/logs/mongod.logstorage:dbPath:F:/m......
  • windows docker不能使用 || wsl不能使用
    使用管理员打开命令行输入bcdedit/sethypervisorlaunchtypeauto重启即可风险如果你在使用virtualbox,版本比较老的话,可能docker可以使用了,但是虚拟机不能用了;可以更新一下virtualbox解释WSL2、DockerDesktop等依赖HypervisorDockerDesktopforWindows利......
  • Veeam Backup & Replication 11备份windows服务器,物理机迁移
    原文转自VeeamBackup&Replication11备份windows服务器,物理机迁移VeeamBackup&Replication11备份windows服务器今天用Veeam给windows服务器做备份。如果是物理机迁移至虚拟机,也可以先备份,再恢复到指导位置。 安装veeambackup&replicationconsole先在server......
  • 在 PowerShell 中,您可以使用多个命令来管理和监控电池及电源设置。以下是按功能分类的
     在PowerShell中,您可以使用多个命令来管理和监控电池及电源设置。以下是按功能分类的PowerShell电池相关命令及其描述表格。功能分类命令描述电池状态查询Get-WmiObject-ClassWin32_Battery获取当前电池状态信息,如电池充电状态、剩余电量、设计容量等。......
  • [软件工具使用记录] windows离线ollama部署本地模型并配置continue实现离线代码补全
    qwen2.5coder发布之后,觉得差不多可以实现离线模型辅助编程了,所以尝试在公司内网部署模型,配合vsocde插件continue实现代码提示、聊天功能。目前使用qwen2.5coder的32b模型,体验上和gpt-4o差不多(都稀碎),适用于编写脚本,查一些简单问题,例如flask如何把变量传到前端,准确率还可以,但是补全......