首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
编程技术
发新帖
5
19
[原创]实现简易ARK工具(4) SSDT Hook
发表于: 2025-7-21 20:54
4057
[原创]实现简易ARK工具(4) SSDT Hook
X66iaM
2025-7-21 20:54
4057
# 前言 大家好,本篇文章讨论XP/WIN7 32位下通过SSDTHook实现对系统调用的监控,所以得先知道什么是SSDT,而明白SSDT得先懂门的机制,懂门的机制得先懂GDT表....=.= 作者也是小白, 如有错误恳请指正,谢谢你的时间! [[专题四\]Rootkit的学习与研究-编程技术-看雪-安全社区|安全招聘|kanxue.com](https://bbs.kanxue.com/thread-56817.htm),我发现查阅windwos基础的文章得从后往前翻。翻到前辈的文章后就陷入了自我怀疑,别人08年玩烂的东西现在学是否有意义,大家怎么看呢? ## SSDT SSDT(System Service Descriptor Table)是Windows内核维护的系统服务分发表,记录了所有系统调用(Nt*函数)的内核实现地址。 WRK中观察,SSDT和ShadowSSDT是两个导出的表结构。 //win7 32位后不导出  ``` //WRK中他就长这样 typedef struct _KSERVICE_TABLE_DESCRIPTOR { PULONG_PTR Base;//函数地址表指针 PULONG Count; // 指向计数器的指针 不管它就行 ULONG Limit; // 实际的服务数量 这个和"Count"很容易混淆 PUCHAR Number; } KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR; extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable //声明直接用 ``` windbg中观察 输入`dd nt!KeServiceDescriptorTable L4` ,第一个值就是他的函数地址表地址,第三个值代表地址有几项  我们dds 地址表地址 ,解析成函数指针,就可以查阅SSDT。  <u>既然SSDT表存储的是所有系统调用的函数地址,那么如果我们修改SSDT表中的函数指针,不就能改变系统调用最终的去向吗?</u> # SSDT Hook 我们的目的是通过hook 内核函数实现对R3的监控,有几种思路: **1:可以通过Ntdll拿到想要hook的R3的API call的服务编号,去改相应的SSDT表项。** **2:inlinehook内核API。** 由于内核API不像R3的API在开头有mov edi edi.push ebp,mov ebp,esp这样主动提供的5字节hook点,inlineHook不好找点。显然修改表项更稳定。 **3.替换MSR** MSR[175]是EIP,我们可以替换这个值,实现对系统快速调用的代理函数Hook,同时对EAX(系统服务号)做判断来选择针对的api。显然这种方法很好检查,读一下MSR的值就知道对不对劲了。 **4.直接对KiFastCallEntry函数inlineHook,但是这样兼容性也不好,这个代码会更新** **5.对某个进程做个假表 Ethread->ServiceTable**  监控目标程序的创建进程,把他的服务表替换了。这也不难检测,可以遍历枚举进程,对比所有进程的服务表做判断。 **6.新时代的对抗 不懂不懂**.... ------ SSDTHook的缺陷 这里有个Nt* 与Zw*函数的问题: - 在R3(用户层)时,Zw*和Nt*其实是同一个导出地址,最终通过int 2Eh或syscall指令,带着服务号进入内核,由SSDT查表找到真正的内核实现。 - 在R0(内核层)时,Zw*函数会无条件地通过SSDT查表,即使已经在内核态,也会走一遍“查表”流程。* - Nt*函数(如NtOpenProcess)内部是具体实现功能的代码,不会再通过SSDT查表。* 举例说明 - 用户层调用ZwOpenProcess: - R3 → ZwOpenProcess → 系统调用指令(int 2Eh/syscall)→ SSDT查表 → NtOpenProcess实现 - 内核层调用ZwOpenProcess: - R0 → ZwOpenProcess → SSDT查表 → NtOpenProcess实现 - 内核层调用NtOpenProcess: - R0 → NtOpenProcess(直接实现,不查表) 如果内核驱动强制调Nt,就可以绕过SSDT表的Hook,结果就是hook不到驱动。 <u>所以,SSDT hook适合监控用户层的系统调用,但对内核层直接调用Nt*无能为力。*</u> 下图可以看到ZwOpenProcess内部也是传一个服务号然后call系统服务。  ------ 本文具体实现通过修改SSDT表中的函数指针实现hook 拆解一下思路 : 1.获取SSDT表地址 2.R3通过控制码传服务号 3.R0收到服务号后修改SSDT[服务号]的值为我们干活的函数地址,并保存原函数指针。 4.在hook函数内部收集信息并调用原函数指针(否则破坏系统功能),将信息放到R3能读取到的一块缓冲区 5.R3读取数据显示在UI上,实现对系统调用的监控。 6.干完活或者软件关闭,需要恢复被修改的SSDT表项为保存的原函数指针。 ``` R3 R0 | | |通过DeviceIoCtl传服务号i| |--------------------->| | | 获取SSDT表地址 | | 保存原始函数指针SSDT[i] | | 修改SSDT表项为Hook函数 | | | | 系统服务会调用Hook函数,收集信息 | | 写入共享缓冲区 |<---------------------| | 开线程轮询读取缓冲区 | 展示在UI上 结束的时候 | 发送卸载命令(DeviceIoCtl) |--------------------->| | | 等待所有hook调用结束 | | 恢复SSDT表项 | | 释放共享缓冲区 |<---------------------| ``` ## 1.获取SSDT表 第一种方法,特征`KeAddSystemServiceTable` ```c void GetSSDT() { UNICODE_STRING ustrName; RtlInitUnicodeString(&ustrName, L"KeAddSystemServiceTable"); UCHAR* pCode = MmGetSystemRoutineAddress(&ustrName); // 搜索特征码 0x83 0xB8 (cmp eax, xxx) for (int i = 0; i < 100; i++) { if (pCode[i] == 0x83 && pCode[i + 1] == 0xB8) { KeServiceDescriptorTable = *(PKSERVICE_TABLE_DESCRIPTOR*)(pCode + i + 2); break; } } } ``` 第二种方法,拿全局变量直接用,但是win7 32位后就不导出了,要解析pdb ```c++ //WRK中他就长这样 typedef struct _KSERVICE_TABLE_DESCRIPTOR { PULONG_PTR Base;//函数地址表指针 PULONG Count; // 指向计数器的指针 不管它就行 ULONG Limit; // 实际的服务数量 这个和"Count"很容易混淆 PUCHAR Number; } KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR; extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable //声明直接用 ``` 但是通过这个结构体,我们只能拿到函数地址和知道有几项服务函数,不知道函数名。 由于我写的玩具要在XP上跑,所以让ai把windbg的SSDT表信息硬编码为数组。高版本系统可以集成网上各种的开源pdb解析工具(基本都是64位的) 获取函数名。 ```c++ SSDT_INFO g_SSDT_XP_SP3_Table[] = { {0, (PVOID)0x805a5614, "NtAcceptConnectPort"}, {1, (PVOID)0x805f1adc, "NtAccessCheck"}, {2, (PVOID)0x805f5312, "NtAccessCheckAndAuditAlarm"}, {3, (PVOID)0x805f1b0e, "NtAccessCheckByType"}, .... ``` ## 2.R3通过控制码传服务号 这没什么好说的,确认好要hook什么函数写个枚举类表示服务号,非常老实 ```c++ typedef enum : ULONG { HOOK_CREATE_PROCESS = 48, // NtCreateProcessEx HOOK_OPEN_PROCESS = 122, // NtOpenProcess HOOK_DEBUG_PROCESS = 57, // NtDebugActiveProcess HOOK_SUSPEND_PROCESS = 253, // NtSuspendProcess HOOK_RESUME_PROCESS = 205, // NtResumeProcess HOOK_TERMINATE_PROCESS = 257 // NtTerminateProcess } HOOK_SSDT_Index, * PHOOK_SSDT_Index; ``` ```c++ BOOL ArkR3::StartSSDTHook(HOOK_SSDT_Index flag) { PVOID sharedMemAddr = NULL; // 发送监控标志给驱动 DWORD dwRetBytes = 0; BOOL bResult = DeviceIoControl(m_hDriver, CTL_START_SSDTHOOK, &flag, sizeof(HOOK_SSDT_Index), &sharedMemAddr,sizeof(PVOID), //返回R3共享内存地址 保存到pSharedLogBuffer_ &dwRetBytes, NULL); ... ``` 由于我们hook函数后需要发监控的数据给R3,所以我使用mdl申请了一块共享内存,每次R3发控制码,R0都返回了这个共享内存的地址。后续R0写入,R3读取。 ``` PVOID g_SharedMemory = NULL; //共享内存地址 R0用 PVOID g_SharedLogBuffer = NULL;//映射到R3的内存地址 R3用 PMDL g_SharedMdl = NULL; // 初始化共享内存 NTSTATUS InitSharedMemory() { // 分配内核内存 g_SharedMemory = ExAllocatePoolWithTag(NonPagedPool, sizeof(LOG_BUFFER), 'SLOG'); if (!g_SharedMemory) { Log("[test] InitSharedMemory: !g_SharedMemory"); return STATUS_NO_MEMORY; } RtlZeroMemory(g_SharedMemory, sizeof(LOG_BUFFER)); // 创建MDL g_SharedMdl = IoAllocateMdl(g_SharedMemory, sizeof(LOG_BUFFER), FALSE, FALSE, NULL); if (!g_SharedMdl) { ExFreePoolWithTag(g_SharedMemory, 'SLOG'); Log("[test] InitSharedMemory: IoAllocateMdl STATUS_NO_MEMORY"); return STATUS_NO_MEMORY; } // 构建MDL MmBuildMdlForNonPagedPool(g_SharedMdl); // 映射到用户空间 g_SharedLogBuffer = MmMapLockedPagesSpecifyCache(g_SharedMdl, UserMode, MmNonCached, NULL, FALSE, NormalPagePriority); if (!pSharedLogBuffer_) { Log("[test] InitSharedMemory: 映射到用户空间失败"); IoFreeMdl(g_SharedMdl); ExFreePoolWithTag(g_SharedMemory, 'SLOG'); return STATUS_NO_MEMORY; } Log("[test] 共享内存初始化成功: 内核地址=%p, 用户地址=%p", g_SharedMemory, pSharedLogBuffer_); return STATUS_SUCCESS; } // 清理共享内存 void CleanupSharedMemory() { if (g_SharedLogBuffer) { MmUnmapLockedPages(g_SharedLogBuffer, g_SharedMdl); g_SharedLogBuffer = NULL; } if (g_SharedMdl) { IoFreeMdl(g_SharedMdl); g_SharedMdl = NULL; } if (g_SharedMemory) { ExFreePoolWithTag(g_SharedMemory, 'SLOG'); g_SharedMemory = NULL; } } ``` 注意:`MmMapLockedPagesSpecifyCache`必须在目标用户进程的上下文下调用,才能把内核内存映射到R3进程空间。我一开始把这个初始化函数放在了`DriverEntry`,排查了好几个小时- - ## 3.R0实现hook 主要是拿到符号号后替换SSDT中函数指针,伪代码: ```c++ // 保存原始函数指针 OriginalNtOpenProcess = SSDT[0x23]; // 替换为自定义Hook函数 SSDT[0x23] = HookedNtOpenProcess; // Hook函数内部 NTSTATUS HookedNtOpenProcess(...) { // 记录日志、收集信息 ... // 调用原始实现 return OriginalNtOpenProcess(...); } ``` 这里需要考虑**重入和引用计数问题**: 如果有线程进入`FakeNtCreateProcessEx`但是在还没返回,此时驱动卸载了,线程继续跑被释放的代码内存,导致蓝屏。 ```c++ // 线程A执行到这里 NTSTATUS NTAPI FakeNtCreateProcessEx(...) { // 此时驱动被卸载,代码段被释放 // 但线程A还在执行... NTSTATUS Status = g_NtCreateProcessEx(...); // 访问已释放的内存! // 蓝屏! return Status; } ``` 所以我们需要确保所有函数调用完才`DriverUnload`。 重入问题:比如你在`HookedNtOpenProcess`里做日志、权限检查、甚至调用其他API时,间接又触发了`NtOpenProcess`,就会再次进入Hook函数本身,所以我们需要添加引用计数。 ```c++ volatile LONG RefCount = 0; // Hook函数 NTSTATUS HookedNtOpenProcess(...) { InterlockedIncrement(&RefCount); // 进入时原子自增 // ... 记录日志 NTSTATUS status = OriginalNtOpenProcess(...); // ... 其他处理 InterlockedDecrement(&RefCount); // 离开时原子自减 return status; } // 卸载时 等待所有函数全部调用完 LARGE_INTEGER Time = { 0 }; Time.QuadPart = 10000; while (g_nRefCount != 0) { KeDelayExecutionThread(KernelMode, FALSE, &Time); } ``` 我最后是搞了张表存储Hook需要的数据,详细代码请看文末。 ```c++ typedef struct _HOOK_ENTRY { HOOK_SSDT_Index SsdtIndex; // SSDT服务号 PVOID* OriginalFunction; // 原始函数指针的存放地址 PVOID HookFunction; // Hook后的函数 const char* FunctionName; // 函数名 BOOLEAN IsHooked; // 是否已Hook LONG RefCount; // 正在执行hook的线程数 } HOOK_ENTRY; HOOK_ENTRY g_HookTable[] = { { HOOK_CREATE_PROCESS, (PVOID*)&g_NtCreateProcessEx, HookedNtCreateProcessEx, "NtCreateProcessEx", FALSE, 0 }, { HOOK_OPEN_PROCESS, (PVOID*)&g_NtOpenProcess, HookedNtOpenProcess, "NtOpenProcess", FALSE, 0 }, ... }; #define HOOK_TABLE_SIZE (sizeof(g_HookTable) / sizeof(HOOK_ENTRY)) ``` 通过收到的服务号查表进行函数指针的替换实现hook ```c++ NTSTATUS SetHookByFlag(HOOK_SSDT_Index flag) { for (ULONG i = 0; i < HOOK_TABLE_SIZE; i++) { if (g_HookTable[i].SsdtIndex == flag && g_HookTable[i].HookFunction) { // 检查是否已经被Hook if (g_HookTable[i].IsHooked) { Log("[test] Hook已存在,跳过: %s (索引:%d) ", g_HookTable[i].FunctionName, flag); return STATUS_SUCCESS; // 返回成功,避免重复Hook } // 设置Hook *(ULONG*)g_HookTable[i].OriginalFunction = KeServiceDescriptorTable->Base[flag]; KeServiceDescriptorTable->Base[flag] = (ULONG)g_HookTable[i].HookFunction; g_HookTable[i].IsHooked = TRUE; // 标记为已Hook Log("[test] Hook设置成功: %s (索引:%d) ", g_HookTable[i].FunctionName, flag); return STATUS_SUCCESS; } } Log("[test] 未找到Hook项: %d ", flag); return STATUS_UNSUCCESSFUL; } NTSTATUS UnsetHookByFlag(HOOK_SSDT_Index flag) { for (ULONG i = 0; i < HOOK_TABLE_SIZE; i++) { if (g_HookTable[i].SsdtIndex == flag && g_HookTable[i].OriginalFunction) { // 检查是否已经被Hook if (!g_HookTable[i].IsHooked) { Log("[test] Hook不存在,跳过: %s (索引:%d) ", g_HookTable[i].FunctionName, flag); return STATUS_SUCCESS; // 返回成功,避免重复取消Hook } // 恢复Hook KeServiceDescriptorTable->Base[flag] = *(ULONG*)g_HookTable[i].OriginalFunction; g_HookTable[i].IsHooked = FALSE; // 标记为未Hook Log("[test] Hook恢复成功: %s (索引:%d) ", g_HookTable[i].FunctionName, flag); return STATUS_SUCCESS; } } return STATUS_UNSUCCESSFUL; } NTSTATUS NTAPI HookedNtCreateProcessEx(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, HANDLE ParentProcess, ULONG Flags, HANDLE SectionHandle, HANDLE DebugPort, HANDLE ExceptionPort, ULONG JobMemberLevel) { InterlockedIncrement(&g_HookTable[0].RefCount); NTSTATUS status = STATUS_UNSUCCESSFUL; //调原函数 if (g_NtCreateProcessEx) { status = g_NtCreateProcessEx(ProcessHandle, DesiredAccess, ObjectAttributes, ParentProcess, Flags, SectionHandle, DebugPort, ExceptionPort, JobMemberLevel); } //做点什么 拿信息写到共享内存里... AddProcessEvent(HOOK_CREATE_PROCESS, *ProcessHandle, status); InterlockedDecrement(&g_HookTable[0].RefCount); return status; } ``` ## 4.hook函数内部收集信息 我在hook函数内部拿信息信息,封装成`PROCESS_EVENT`,R3也按这个格式读。 ```c++ typedef struct PROCESS_EVENT { CHAR SourceProcessName[16]; // 调用者进程名 ULONG SourceProcessId; // 调用者进程ID ULONG Action; // 动作 (HOOK_SSDT_Index) ULONG TargetProcessId; // targetPID LONG Result; // 操作结果 } PROCESS_EVENT, * PPROCESS_EVENT; ``` ```c++ void AddProcessEvent(ULONG Action, HANDLE TargetHandle, NTSTATUS Result){ PEPROCESS CurrentProcess = PsGetCurrentProcess();// 获取当前进程信息 HANDLE CurrentPid = PsGetCurrentProcessId(); PUCHAR CurrentProcessName = PsGetProcessImageFileName(CurrentProcess); PLOG_BUFFER pLogBuffer = (PLOG_BUFFER)g_SharedMemory;//写到共享内存 ULONG currentReadIndex = pLogBuffer->ReadIndex; // 获取目标进程PID ULONG TargetPid = 0; if (TargetHandle) { PEPROCESS TargetProcess = NULL; NTSTATUS lookupStatus = ObReferenceObjectByHandle(TargetHandle, PROCESS_QUERY_INFORMATION, *PsProcessType, KernelMode, (PVOID*)&TargetProcess, NULL); if (NT_SUCCESS(lookupStatus)) { TargetPid = (ULONG)PsGetProcessId(TargetProcess); ObDereferenceObject(TargetProcess); } } //写入共享内存... } ``` 这里我纯属折腾,给共享内存维护了一个环形缓冲区,R0控制WriteIndex只写,R3控制ReadIndex只读。 假设R0写完7,WriteIndex就会变为0从头开始覆写,实现固定缓冲区大小的生产消费。数据结构不是主题,下文会贴出代码。 ``` +---+---+---+---+---+---+---+---+ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | +---+---+---+---+---+---+---+---+ ^ ^ ReadIndex WriteIndex ``` 最省事的可以R3调`Readfile`读。也可以让R3调`DeviceIoControl`,R0收到后拷贝数据到`systembuffer`,在缓冲区满时清空这个缓冲区。 **这里有同步问题**:现在不仅仅是R0和R3两个线程在操作缓冲区,而是R0侧的每个被 hook 的 SSDT 函数都可能在不同的线程中并发地往同一个缓冲区写入数据。 这意味着R0端是多生产者(多线程写),R3端是单消费者(单线程读),存在数据竞争,记得加锁?有没有什么比较优雅的方法....0.0 这里顺便查阅了一下内核的读写锁: - KSPIN_LOCK:自旋锁,适合保护极短临界区,不支持多读单写。 - KMUTEX / FAST_MUTEX:互斥体,只能单线程独占,不支持多读。 - KSEMAPHORE:信号量,可实现计数型同步,但不直接支持读写锁语义。 - KEVENT:事件,适合信号通知,不适合实现锁。 - ERESOURCE:专为多读单写设计的内核对象,支持递归、死锁检测。这个XP能用 SpinLock从vista开始支持<a href="elink@71bK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6D9k6h3q4J5L8W2)9J5k6h3#2A6j5%4u0G2M7$3!0X3N6q4)9J5k6h3y4G2L8g2)9J5c8Y4A6Z5i4K6u0V1j5$3&6Q4x3V1k6%4K9h3&6V1L8%4N6K6i4K6u0V1K9r3q4J5k6s2N6S2M7X3g2Q4x3V1k6V1M7X3W2$3k6i4u0K6i4K6u0r3K9$3g2J5L8X3g2D9i4K6u0r3M7X3g2S2k6r3g2J5i4K6u0V1N6%4u0A6N6r3g2J5i4K6u0V1M7%4m8A6L8W2)9J5k6r3I4G2j5$3E0K6">读取器/写入器自旋锁 - Windows drivers | Microsoft Learn</a>,Xp好像只能用ERESOURCE。<a href="elink@4b6K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6D9k6h3q4J5L8W2)9J5k6h3#2A6j5%4u0G2M7$3!0X3N6q4)9J5k6h3y4G2L8g2)9J5c8Y4A6Z5i4K6u0V1j5$3&6Q4x3V1k6%4K9h3&6V1L8%4N6K6i4K6u0V1K9r3q4J5k6s2N6S2M7X3g2Q4x3V1k6V1M7X3W2$3k6i4u0K6i4K6u0r3K9$3g2J5L8X3g2D9i4K6u0r3K9h3&6@1M7X3!0V1N6h3y4@1K9h3!0F1i4K6u0V1N6r3!0Q4x3X3c8W2M7X3g2K6L8%4g2J5j5$3g2Q4x3X3c8J5L8%4g2@1K9h3&6W2M7H3`.`.">ERESOURCE 例程简介 - Windows drivers | Microsoft Learn</a> 读锁允许多个线程同时读取同一份数据,但禁止写线程在此期间修改数据,避免写线程正在修改时,读线程读到不完整数据。 ## 5.R3读共享内存解析数据 一般是开一个线程轮询读共享内存的数据写到数据结构,UI再读缓冲区展示数据,这样会导致日志线程和UI主线程的读写同步问题,记得处理。好奇:由于imgu每一帧都会刷新UI。可以在每一帧渲染前/后,顺便轮询一次共享内存,把新日志读出来,推到UI展示队列,会不会卡? 展示在UI上的结构体: ```c++ typedef struct PROCESS_EVENT { CHAR SourceProcessName[16]; // 调用者进程名 ULONG SourceProcessId; // 调用者进程ID ULONG Action; // 动作 (HOOK_SSDT_Index) ULONG TargetProcessId; // targetPID LONG Result; // 操作结果 } PROCESS_EVENT, * PPROCESS_EVENT; ``` ## 6.结束Hook 1)手动控制hook,R3发控制码,让R0将函数指针替换回去。 2)上文描述过的引用计数问题:程序退出的时候,如果贸然卸载驱动,线程继续跑被释放的代码内存,导致蓝屏。先卸载hook,再依靠引用计数确保hook函数都跑完了代码,最后卸载驱动。 ```c++ VOID Unload(__in struct _DRIVER_OBJECT* DriverObject) { UNREFERENCED_PARAMETER(DriverObject); UNICODE_STRING usSymbolicLinkName; // 先恢复所有Hook for (ULONG i = 0; i < HOOK_TABLE_SIZE; i++) { if (g_HookTable[i].IsHooked) { UnsetHookByFlag(g_HookTable[i].SsdtIndex); Log("[test] 恢复Hook: %s", g_HookTable[i].FunctionName); } } // 等待所有Hook函数调用完成 LARGE_INTEGER Time = { 0 }; Time.QuadPart = -100000; // 10ms (负数表示相对时间) Log("[test] 开始等待Hook函数调用完成..."); while (1) { BOOLEAN allClear = TRUE; // 检查所有Hook的引用计数 for (ULONG i = 0; i < HOOK_TABLE_SIZE; i++) { if (g_HookTable[i].RefCount > 0) { allClear = FALSE; break; } } if (allClear) { break; } KeDelayExecutionThread(KernelMode, FALSE, &Time); } // 删除符号链接 RtlInitUnicodeString(&usSymbolicLinkName, SYMBOL_NAME); IoDeleteSymbolicLink(&usSymbolicLinkName); // 删除设备对象 if (DriverObject->DeviceObject != nullptr) { IoDeleteDevice(DriverObject->DeviceObject); } Log("[test] Unload 完成 "); } ``` # 效果图 拿的信息比较简陋   # 部分代码 ```c++ //R0的 //NTSTATUS DispatchDeviceControl中 case CTL_START_SSDTHOOK: { PHOOK_SSDT_Index pIndex = (PHOOK_SSDT_Index)Irp->AssociatedIrp.SystemBuffer; __try { //第一次开启hook时初始化共享内存 if (g_isInitShareMemory == false) { InitSharedMemory(); g_isInitShareMemory = true; } HOOK_SSDT_Index index = *pIndex; Log("[test] CTL_START_SSDTHOOK: index=%d funcname=%s ", index, GetHookNameByIndex(index)); status = SetHookByFlag(index); if (NT_SUCCESS(status)) { //将共享内存地址复制到输出缓冲区 * (PVOID*)Irp->AssociatedIrp.SystemBuffer = pSharedLogBuffer_; info = sizeof(pSharedLogBuffer_); Log("[test]返回共享内存地址: %p ", pSharedLogBuffer_); } } __except (EXCEPTION_EXECUTE_HANDLER) { status = STATUS_UNSUCCESSFUL; Log("[test] CTL_START_SSDTHOOK exception "); } } break; case CTL_END_SSDTHOOK: { PHOOK_SSDT_Index pIndex = (PHOOK_SSDT_Index)Irp->AssociatedIrp.SystemBuffer; __try { HOOK_SSDT_Index index = *pIndex; Log("[test] CTL_END_SSDTHOOK: index=%d funcname=%s", index, GetHookNameByIndex(index)); status = UnsetHookByFlag(index); } __except (EXCEPTION_EXECUTE_HANDLER) { status = STATUS_UNSUCCESSFUL; Log("[test] CTL_START_SSDTHOOK exception "); } } break; NTSTATUS(NTAPI* g_NtCreateProcessEx)( __out PHANDLE ProcessHandle, __in ACCESS_MASK DesiredAccess, __in_opt POBJECT_ATTRIBUTES ObjectAttributes, __in HANDLE ParentProcess, __in ULONG Flags, __in_opt HANDLE SectionHandle, __in_opt HANDLE DebugPort, __in_opt HANDLE ExceptionPort, __in ULONG JobMemberLevel ); NTSTATUS(NTAPI* g_NtOpenProcess)( __out PHANDLE ProcessHandle, __in ACCESS_MASK DesiredAccess, __in POBJECT_ATTRIBUTES ObjectAttributes, __in_opt PCLIENT_ID ClientId ); NTSTATUS(NTAPI* g_NtDebugActiveProcess)( __in HANDLE ProcessHandle, __in HANDLE DebugObjectHandle ); NTSTATUS(NTAPI* g_NtSuspendProcess)( __in_opt HANDLE ProcessHandle ); NTSTATUS(NTAPI* g_NtResumeProcess)( __in_opt HANDLE ProcessHandle ); NTSTATUS(NTAPI* g_NtTerminateProcess)( __in_opt HANDLE ProcessHandle, __in NTSTATUS ExitStatus ); typedef struct _HOOK_ENTRY { HOOK_SSDT_Index SsdtIndex; PVOID* OriginalFunction; PVOID HookFunction; const char* FunctionName; BOOLEAN IsHooked; LONG RefCount; } HOOK_ENTRY; HOOK_ENTRY g_HookTable[] = { { HOOK_CREATE_PROCESS, (PVOID*)&g_NtCreateProcessEx, HookedNtCreateProcessEx, "NtCreateProcessEx", FALSE, 0 }, { HOOK_OPEN_PROCESS, (PVOID*)&g_NtOpenProcess, HookedNtOpenProcess, "NtOpenProcess", FALSE, 0 }, { HOOK_DEBUG_PROCESS, (PVOID*)&g_NtDebugActiveProcess, HookedNtDebugActiveProcess, "NtDebugActiveProcess", FALSE, 0 }, { HOOK_SUSPEND_PROCESS, (PVOID*)&g_NtSuspendProcess, HookedNtSuspendProcess, "NtSuspendProcess", FALSE, 0 }, { HOOK_RESUME_PROCESS, (PVOID*)&g_NtResumeProcess, HookedNtResumeProcess, "NtResumeProcess", FALSE, 0 }, { HOOK_TERMINATE_PROCESS, (PVOID*)&g_NtTerminateProcess, HookedNtTerminateProcess, "NtTerminateProcess", FALSE, 0 } }; #define HOOK_TABLE_SIZE (sizeof(g_HookTable) / sizeof(HOOK_ENTRY)) // 根据SSDT索引获取函数名 const char* GetHookNameByIndex(HOOK_SSDT_Index index) { for (ULONG i = 0; i < HOOK_TABLE_SIZE; i++) { if (g_HookTable[i].SsdtIndex == index) { return g_HookTable[i].FunctionName; } } return "Unknown"; } void AddProcessEvent(ULONG Action, HANDLE TargetHandle, NTSTATUS Result) { if (!g_SharedMemory) { Log("[EVENT] 错误:g_SharedMemory 为空"); return; } // 获取当前进程信息 PEPROCESS CurrentProcess = PsGetCurrentProcess(); HANDLE CurrentPid = PsGetCurrentProcessId(); PUCHAR CurrentProcessName = PsGetProcessImageFileName(CurrentProcess); PLOG_BUFFER pLogBuffer = (PLOG_BUFFER)g_SharedMemory; ULONG currentReadIndex = pLogBuffer->ReadIndex; // 获取目标进程PID ULONG TargetPid = 0; if (TargetHandle) { PEPROCESS TargetProcess = NULL; NTSTATUS lookupStatus = ObReferenceObjectByHandle(TargetHandle, PROCESS_QUERY_INFORMATION, *PsProcessType, KernelMode, (PVOID*)&TargetProcess, NULL); if (NT_SUCCESS(lookupStatus)) { TargetPid = (ULONG)PsGetProcessId(TargetProcess); ObDereferenceObject(TargetProcess); } } // 计算下一个写入位置 ULONG nextWriteIndex = (pLogBuffer->WriteIndex + 1) % 1000; // Log("[EVENT] 检测,nextWriteIndex:%d currentReadIndex:%d", nextWriteIndex, currentReadIndex); // 如果写入位置追上读取位置,移动读取索引 if (nextWriteIndex == currentReadIndex) { Log("[EVENT] 缓冲区满,nextWriteIndex:%d currentReadIndex:%d", nextWriteIndex, currentReadIndex); pLogBuffer->ReadIndex = (currentReadIndex + 1) % 1000; } // 写入数据到WriteIndex PPROCESS_EVENT pEvent = &pLogBuffer->Logs[pLogBuffer->WriteIndex]; pEvent->Action = Action; pEvent->SourceProcessId = (ULONG)CurrentPid; pEvent->TargetProcessId = TargetPid; pEvent->Result = Result; RtlStringCbCopyA(pEvent->SourceProcessName, sizeof(pEvent->SourceProcessName), (const char*)CurrentProcessName); // 更新写入索引 pLogBuffer->WriteIndex = nextWriteIndex; Log("[EVENT] Action=%d, SourcePid=%d, TargetPid=%d, Result=0x%08X,SourceName=%s", pEvent->Action, pEvent->SourceProcessId, pEvent->TargetProcessId, pEvent->Result,pEvent->SourceProcessName); //日志数量++ if (pLogBuffer->LogCount < 1000) { InterlockedIncrement((LONG*)&pLogBuffer->LogCount); } } NTSTATUS SetHookByFlag(HOOK_SSDT_Index flag) { for (ULONG i = 0; i < HOOK_TABLE_SIZE; i++) { if (g_HookTable[i].SsdtIndex == flag && g_HookTable[i].HookFunction) { // 检查是否已经被Hook if (g_HookTable[i].IsHooked) { Log("[test] Hook已存在,跳过: %s (索引:%d) ", g_HookTable[i].FunctionName, flag); return STATUS_SUCCESS; // 返回成功,避免重复Hook } // 设置Hook *(ULONG*)g_HookTable[i].OriginalFunction = KeServiceDescriptorTable->Base[flag]; KeServiceDescriptorTable->Base[flag] = (ULONG)g_HookTable[i].HookFunction; g_HookTable[i].IsHooked = TRUE; // 标记为已Hook Log("[test] Hook设置成功: %s (索引:%d) ", g_HookTable[i].FunctionName, flag); return STATUS_SUCCESS; } } Log("[test] 未找到Hook项: %d ", flag); return STATUS_UNSUCCESSFUL; } NTSTATUS UnsetHookByFlag(HOOK_SSDT_Index flag) { for (ULONG i = 0; i < HOOK_TABLE_SIZE; i++) { if (g_HookTable[i].SsdtIndex == flag && g_HookTable[i].OriginalFunction) { // 检查是否已经被Hook if (!g_HookTable[i].IsHooked) { Log("[test] Hook不存在,跳过: %s (索引:%d) ", g_HookTable[i].FunctionName, flag); return STATUS_SUCCESS; // 返回成功,避免重复取消Hook } // 恢复Hook KeServiceDescriptorTable->Base[flag] = *(ULONG*)g_HookTable[i].OriginalFunction; g_HookTable[i].IsHooked = FALSE; // 标记为未Hook Log("[test] Hook恢复成功: %s (索引:%d) ", g_HookTable[i].FunctionName, flag); return STATUS_SUCCESS; } } return STATUS_UNSUCCESSFUL; } // 初始化共享内存 NTSTATUS InitSharedMemory() { // 分配内核内存 g_SharedMemory = ExAllocatePoolWithTag(NonPagedPool, sizeof(LOG_BUFFER), 'SLOG'); if (!g_SharedMemory) { Log("[test] InitSharedMemory: !g_SharedMemory"); return STATUS_NO_MEMORY; } RtlZeroMemory(g_SharedMemory, sizeof(LOG_BUFFER)); // 创建MDL g_SharedMdl = IoAllocateMdl(g_SharedMemory, sizeof(LOG_BUFFER), FALSE, FALSE, NULL); if (!g_SharedMdl) { ExFreePoolWithTag(g_SharedMemory, 'SLOG'); Log("[test] InitSharedMemory: IoAllocateMdl STATUS_NO_MEMORY"); return STATUS_NO_MEMORY; } // 构建MDL MmBuildMdlForNonPagedPool(g_SharedMdl); // 映射到用户空间 pSharedLogBuffer_ = MmMapLockedPagesSpecifyCache(g_SharedMdl, UserMode, MmNonCached, NULL, FALSE, NormalPagePriority); if (!pSharedLogBuffer_) { Log("[test] InitSharedMemory: 映射到用户空间失败"); IoFreeMdl(g_SharedMdl); ExFreePoolWithTag(g_SharedMemory, 'SLOG'); return STATUS_NO_MEMORY; } Log("[test] 共享内存初始化成功: 内核地址=%p, 用户地址=%p", g_SharedMemory, pSharedLogBuffer_); return STATUS_SUCCESS; } // 清理共享内存 void CleanupSharedMemory() { if (pSharedLogBuffer_) { MmUnmapLockedPages(pSharedLogBuffer_, g_SharedMdl); pSharedLogBuffer_ = NULL; } if (g_SharedMdl) { IoFreeMdl(g_SharedMdl); g_SharedMdl = NULL; } if (g_SharedMemory) { ExFreePoolWithTag(g_SharedMemory, 'SLOG'); g_SharedMemory = NULL; } } ``` ```c++ //R0的hook函数 NTSTATUS NTAPI HookedNtCreateProcessEx(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, HANDLE ParentProcess, ULONG Flags, HANDLE SectionHandle, HANDLE DebugPort, HANDLE ExceptionPort, ULONG JobMemberLevel); NTSTATUS NTAPI HookedNtOpenProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId); NTSTATUS NTAPI HookedNtDebugActiveProcess( __in HANDLE ProcessHandle, __in HANDLE DebugObjectHandle ); NTSTATUS NTAPI HookedNtSuspendProcess( __in_opt HANDLE ProcessHandle ); NTSTATUS NTAPI HookedNtResumeProcess( __in_opt HANDLE ProcessHandle ); NTSTATUS NTAPI HookedNtTerminateProcess( __in_opt HANDLE ProcessHandle, __in NTSTATUS ExitStatus ); NTSTATUS NTAPI HookedNtCreateProcessEx(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, HANDLE ParentProcess, ULONG Flags, HANDLE SectionHandle, HANDLE DebugPort, HANDLE ExceptionPort, ULONG JobMemberLevel) { InterlockedIncrement(&g_HookTable[0].RefCount); NTSTATUS status = STATUS_UNSUCCESSFUL; if (g_NtCreateProcessEx) { status = g_NtCreateProcessEx(ProcessHandle, DesiredAccess, ObjectAttributes, ParentProcess, Flags, SectionHandle, DebugPort, ExceptionPort, JobMemberLevel); } AddProcessEvent(HOOK_CREATE_PROCESS, *ProcessHandle, status); InterlockedDecrement(&g_HookTable[0].RefCount); return status; } NTSTATUS NTAPI HookedNtOpenProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId) { InterlockedIncrement(&g_HookTable[1].RefCount); NTSTATUS status = STATUS_UNSUCCESSFUL; if (g_NtOpenProcess) { status = g_NtOpenProcess(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId); } AddProcessEvent(HOOK_OPEN_PROCESS, *ProcessHandle, status); InterlockedDecrement(&g_HookTable[1].RefCount); return status; } NTSTATUS NTAPI HookedNtDebugActiveProcess( __in HANDLE ProcessHandle, __in HANDLE DebugObjectHandle) { InterlockedIncrement(&g_HookTable[2].RefCount); NTSTATUS status = STATUS_UNSUCCESSFUL; if (g_NtDebugActiveProcess) { status = g_NtDebugActiveProcess(ProcessHandle, DebugObjectHandle); } AddProcessEvent(HOOK_DEBUG_PROCESS, ProcessHandle, status); InterlockedDecrement(&g_HookTable[2].RefCount); return status; } NTSTATUS NTAPI HookedNtSuspendProcess( __in_opt HANDLE ProcessHandle) { InterlockedIncrement(&g_HookTable[3].RefCount); NTSTATUS status = STATUS_UNSUCCESSFUL; if (g_NtSuspendProcess) { status = g_NtSuspendProcess(ProcessHandle); } AddProcessEvent(HOOK_SUSPEND_PROCESS, ProcessHandle, status); InterlockedDecrement(&g_HookTable[3].RefCount); return status; } NTSTATUS NTAPI HookedNtResumeProcess( __in_opt HANDLE ProcessHandle) { InterlockedIncrement(&g_HookTable[4].RefCount); NTSTATUS status = STATUS_UNSUCCESSFUL; if (g_NtResumeProcess) { status = g_NtResumeProcess(ProcessHandle); } AddProcessEvent(HOOK_RESUME_PROCESS, ProcessHandle, status); InterlockedDecrement(&g_HookTable[4].RefCount); return status; } NTSTATUS NTAPI HookedNtTerminateProcess( __in_opt HANDLE ProcessHandle, __in NTSTATUS ExitStatus) { InterlockedIncrement(&g_HookTable[5].RefCount); NTSTATUS status = STATUS_UNSUCCESSFUL; if (g_NtTerminateProcess) { status = g_NtTerminateProcess(ProcessHandle, ExitStatus); } AddProcessEvent(HOOK_TERMINATE_PROCESS, ProcessHandle, status); InterlockedDecrement(&g_HookTable[5].RefCount); return status; } ``` ```c++ //R3的 void ArkR3::ReadProcessEvents() { if (!pSharedLogBuffer_) { Log("!pSharedLogBuffer_\n"); return; } // 读取当前的写入和读取索引 ULONG currentWriteIndex = pSharedLogBuffer_->WriteIndex; ULONG currentReadIndex = pSharedLogBuffer_->ReadIndex; // 如果没有新数据,直接返回 if (currentReadIndex == currentWriteIndex) { return; } // 先收集所有事件到临时vector std::vector<PROCESS_EVENT> tempEvents; // 读取所有可用的事件 while (currentReadIndex != currentWriteIndex) { PROCESS_EVENT event = pSharedLogBuffer_->Logs[currentReadIndex]; tempEvents.emplace_back(event); // 移动到下一个读取位置 currentReadIndex = (currentReadIndex + 1) % 1000; } // 添加到全局数组时加锁 if (!tempEvents.empty()) { // EnterCriticalSection(&g_ProcessEventCS); for (const auto& event : tempEvents) { g_ProcessEvents.push_back(event); } // LeaveCriticalSection(&g_ProcessEventCS); } // 更新读取索引 pSharedLogBuffer_->ReadIndex = currentReadIndex; // 减少日志数量 InterlockedExchangeAdd((LONG*)&pSharedLogBuffer_->LogCount, -(LONG)ProcessEventsVec_.size()); ULONG logCount = pSharedLogBuffer_->LogCount; Log("ReadProcessEvents: 更新后 ReadIndex=%d, LogCount=%d\n", currentReadIndex, logCount); return; } DWORD __stdcall ArkR3::LogThreadProc(LPVOID lpParam) { ArkR3* pThis = (ArkR3*)lpParam; pThis->Log("日志读取线程启动"); while (1) { pThis->ReadProcessEvents(); Sleep(1000); } pThis->Log("日志读取线程结束"); return 0; } BOOL ArkR3::StartSSDTHook(HOOK_SSDT_Index flag) { PVOID sharedMemAddr = NULL; // 发送监控标志给驱动 DWORD dwRetBytes = 0; BOOL bResult = DeviceIoControl(m_hDriver, CTL_START_SSDTHOOK, &flag, sizeof(HOOK_SSDT_Index), &sharedMemAddr, sizeof(PVOID), //返回R3共享内存地址 保存到pSharedLogBuffer_ &dwRetBytes, NULL); const char* functime = g_SSDT_XP_SP3_Table[flag].FunctionName; if (!bResult) { Log("StartSSDTHook失败 函数%s: \n", functime); return FALSE; } if (sharedMemAddr) { Log("StartSSDTHook: 收到共享内存地址=%p\n", sharedMemAddr); // 验证地址是否可访问 __try { pSharedLogBuffer_ = (PLOG_BUFFER)sharedMemAddr; // 尝试读取第一个字段来验证访问权限 ULONG testRead = pSharedLogBuffer_->WriteIndex; Log("StartSSDTHook: 地址验证成功,WriteIndex=%d\n", testRead); } __except (EXCEPTION_EXECUTE_HANDLER) { Log("StartSSDTHook: 共享内存地址不可访问: %p\n", sharedMemAddr); pSharedLogBuffer_ = NULL; return FALSE; } } else { Log("StartSSDTHook: 未收到有效的共享内存地址\n"); return FALSE; } // 创建日志读取线程 isHookThreadRunning_ = true; if (!hLogThread_) { hLogThread_ = CreateThread(NULL, 0, LogThreadProc, this, 0, NULL); if (!hLogThread_) { LogErr("创建日志线程失败"); pSharedLogBuffer_ = NULL; return FALSE; } } Log("StartSSDTHook 函数:%s 日志共享内存地址=%p \n", functime, pSharedLogBuffer_); return TRUE; } BOOL ArkR3::EndSSDTHook(HOOK_SSDT_Index flag) { // 停止线程 isHookThreadRunning_ = false; if (hLogThread_) { WaitForSingleObject(hLogThread_, 3000); // 等待3秒 CloseHandle(hLogThread_); hLogThread_ = NULL; } // 发送停止监控标志给驱动 DWORD dwRetBytes = 0; BOOL bResult = DeviceIoControl(m_hDriver, CTL_END_SSDTHOOK, &flag, sizeof(HOOK_SSDT_Index), NULL, 0, &dwRetBytes, NULL); const char* funcname = g_SSDT_XP_SP3_Table[flag].FunctionName; if (!bResult) { Log("EndSSDTHook失败 函数:%s\n ", funcname); return FALSE; } //这里只是关闭了一个函数的hook 不用关闭线程 Log("EndSSDTHook 函数:%s \n", funcname); return TRUE; } ``` ```c++ //proto.h typedef enum : ULONG { HOOK_CREATE_PROCESS = 48, // NtCreateProcessEx HOOK_OPEN_PROCESS = 122, // NtOpenProcess HOOK_DEBUG_PROCESS = 57, // NtDebugActiveProcess HOOK_SUSPEND_PROCESS = 253, // NtSuspendProcess HOOK_RESUME_PROCESS = 205, // NtResumeProcess HOOK_TERMINATE_PROCESS = 257 // NtTerminateProcess } HOOK_SSDT_Index, * PHOOK_SSDT_Index; typedef struct PROCESS_EVENT { CHAR SourceProcessName[16]; // 调用者进程名 ULONG SourceProcessId; // 调用者进程ID ULONG Action; // 动作 (HOOK_SSDT_Index) ULONG TargetProcessId; // targetPID LONG Result; // 操作结果 } PROCESS_EVENT, * PPROCESS_EVENT; // 缓冲区结构 typedef struct _LOG_BUFFER { volatile ULONG WriteIndex; // 写入索引(R0更新) volatile ULONG ReadIndex; // 读取索引(R3更新) volatile ULONG LogCount; // 当前日志数量 PROCESS_EVENT Logs[1000]; // 日志数组 } LOG_BUFFER, *PLOG_BUFFER; ``` 其实写ssdthook的时候感觉麻烦的是代码的设计和多线程数据竞争的处理方式,安全知识倒是不复杂。 SSDTHook在XP虚拟机里多hook几个函数就会非常的卡,让我分辨不出是hook导致的卡还是我代码写的不好,蓝廋~ 后续应该是注册系统回调
回复或点赞可查看完整内容
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
最后于
2025-8-13 21:39 被X66iaM编辑 ,原因:
#基础知识
#系统内核
#驱动开发
收藏
・
5
点赞
・
19
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
git_51951meggadf3df
非常支持你的观点!
5天前
mb_lthgjpwj
为你点赞!
2026-8-21 08:51
wx_晨梦
感谢你分享这么好的资源!
2026-7-16 10:49
npc0vo
为你点赞!
2026-4-26 01:45
ink1
你的分享对大家帮助很大,非常感谢!
2026-3-12 14:41
星辰无大海
你的帖子非常有用,感谢分享!
2026-1-26 11:48
milko
感谢你的积极参与,期待更多精彩内容!
2025-12-28 17:05
git_46183EBalloon
感谢你分享这么好的资源!
2025-12-25 01:26
小柯南
非常支持你的观点!
2025-11-20 23:15
ThanatosKer
感谢你的贡献,论坛因你而更加精彩!
2025-10-6 22:00
DIO-HOHO
你的分享对大家帮助很大,非常感谢!
2025-8-3 13:00
AquaCiel
感谢你分享这么好的资源!
2025-7-30 21:28
莫名其妙自信
这个讨论对我很有帮助,谢谢!
2025-7-30 14:56
dong1e
非常支持你的观点!
2025-7-25 23:29
cydian
感谢你的积极参与,期待更多精彩内容!
2025-7-24 09:43
ldljlzw
你的分享对大家帮助很大,非常感谢!
2025-7-22 09:54
TkBinary
你的分享对大家帮助很大,非常感谢!
2025-7-22 09:50
huangyalei
感谢你分享这么好的资源!
2025-7-22 00:03
AL10000
为你点赞!
2025-7-21 21:13
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
9
)
X66iaM
雪 币:
1049
活跃值:
(3441)
能力值:
( LV3,RANK:30 )
在线值:
发帖
8
回帖
33
粉丝
19
关注
私信
X66iaM
2
楼
写完感觉基础不行
最后于
2025-7-22 22:38 被X66iaM编辑 ,原因:
2025-7-22 01:16
0
李翔_702165
雪 币:
0
活跃值:
(903)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
14
粉丝
0
关注
私信
李翔_702165
3
楼
谢谢,不错。
2025-7-22 03:02
0
TkBinary
雪 币:
6549
活跃值:
(13284)
能力值:
(RANK:385 )
在线值:
发帖
26
回帖
526
粉丝
249
关注
私信
TkBinary
5
4
楼
初始化函数放在了DriverEntry,排查了好几个小时
2025-7-22 09:53
1
byhk
雪 币:
2646
活跃值:
(8785)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
186
粉丝
1
关注
私信
byhk
5
楼
谢谢分享
2025-9-19 07:42
0
CreateFile
雪 币:
250
能力值:
( LV1,RANK:0 )
在线值:
发帖
1
回帖
10
粉丝
0
关注
私信
CreateFile
6
楼
2312
2025-10-6 16:36
0
mb_dmpagumk
雪 币:
205
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
6
粉丝
0
关注
私信
mb_dmpagumk
7
楼
谢谢分享
2025-12-24 18:51
0
Cr_Ring
雪 币:
296
活跃值:
(943)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
26
粉丝
0
关注
私信
Cr_Ring
8
楼
66666666
2025-12-28 15:51
0
mb_hncrqnsf
雪 币:
200
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
8
粉丝
0
关注
私信
mb_hncrqnsf
9
楼
2026-1-11 03:25
0
saylove
雪 币:
0
活跃值:
(120)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
1
粉丝
0
关注
私信
saylove
10
楼
学习学习
2026-1-26 23:29
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
X66iaM
8
发帖
33
回帖
30
RANK
关注
私信
他的文章
[原创]实现简易ARK工具(5) x64信息快查
4588
[原创]实现简易ARK工具(4) SSDT Hook
4057
[原创]实现简易ARK工具(3) 遍历进程和内核模块
9470
[原创]实现简易ARK工具(2) 切CR3进程读写
4208
[原创]实现简易ARK工具(1) GDT表
7100
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部