大家好,本篇文章讨论XP/WIN7 32位下通过SSDTHook实现对系统调用的监控,所以得先知道什么是SSDT,而明白SSDT得先懂门的机制,懂门的机制得先懂GDT表....=.=
作者也是小白, 如有错误恳请指正,谢谢你的时间! [专题四]Rootkit的学习与研究-编程技术-看雪-安全社区|安全招聘|kanxue.com,我发现查阅windwos基础的文章得从后往前翻。翻到前辈的文章后就陷入了自我怀疑,别人08年玩烂的东西现在学是否有意义,大家怎么看呢?SSDT(System Service Descriptor Table)是Windows内核维护的系统服务分发表,记录了所有系统调用(Nt*函数)的内核实现地址。WRK中观察,SSDT和ShadowSSDT是两个导出的表结构。 //win7 32位后不导出 windbg中观察 输入dd nt!KeServiceDescriptorTable L4 ,第一个值就是他的函数地址表地址,第三个值代表地址有几项我们dds 地址表地址 ,解析成函数指针,就可以查阅SSDT。既然SSDT表存储的是所有系统调用的函数地址,那么如果我们修改SSDT表中的函数指针,不就能改变系统调用最终的去向吗?我们的目的是通过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.替换MSRMSR[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不到驱动。所以,SSDT hook适合监控用户层的系统调用,但对内核层直接调用Nt无能为力。下图可以看到ZwOpenProcess内部也是传一个服务号然后call系统服务。本文具体实现通过修改SSDT表中的函数指针实现hook拆解一下思路 :1.获取SSDT表地址 2.R3通过控制码传服务号 3.R0收到服务号后修改SSDT[服务号]的值为我们干活的函数地址,并保存原函数指针。 4.在hook函数内部收集信息并调用原函数指针(否则破坏系统功能),将信息放到R3能读取到的一块缓冲区 5.R3读取数据显示在UI上,实现对系统调用的监控。6.干完活或者软件关闭,需要恢复被修改的SSDT表项为保存的原函数指针。第一种方法,特征KeAddSystemServiceTable第二种方法,拿全局变量直接用,但是win7 32位后就不导出了,要解析pdb但是通过这个结构体,我们只能拿到函数地址和知道有几项服务函数,不知道函数名。由于我写的玩具要在XP上跑,所以让ai把windbg的SSDT表信息硬编码为数组。高版本系统可以集成网上各种的开源pdb解析工具(基本都是64位的) 获取函数名。这没什么好说的,确认好要hook什么函数写个枚举类表示服务号,非常老实由于我们hook函数后需要发监控的数据给R3,所以我使用mdl申请了一块共享内存,每次R3发控制码,R0都返回了这个共享内存的地址。后续R0写入,R3读取。注意:MmMapLockedPagesSpecifyCache必须在目标用户进程的上下文下调用,才能把内核内存映射到R3进程空间。我一开始把这个初始化函数放在了DriverEntry,排查了好几个小时- -主要是拿到符号号后替换SSDT中函数指针,伪代码:这里需要考虑重入和引用计数问题:如果有线程进入FakeNtCreateProcessEx但是在还没返回,此时驱动卸载了,线程继续跑被释放的代码内存,导致蓝屏。所以我们需要确保所有函数调用完才DriverUnload。重入问题:比如你在HookedNtOpenProcess里做日志、权限检查、甚至调用其他API时,间接又触发了NtOpenProcess,就会再次进入Hook函数本身,所以我们需要添加引用计数。我最后是搞了张表存储Hook需要的数据,详细代码请看文末。通过收到的服务号查表进行函数指针的替换实现hook我在hook函数内部拿信息信息,封装成PROCESS_EVENT,R3也按这个格式读。这里我纯属折腾,给共享内存维护了一个环形缓冲区,R0控制WriteIndex只写,R3控制ReadIndex只读。
假设R0写完7,WriteIndex就会变为0从头开始覆写,实现固定缓冲区大小的生产消费。数据结构不是主题,下文会贴出代码。最省事的可以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开始支
...(已截断)
---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-287696.htm
[原创]实现简易ARK工具(4) SSDT Hook
496 浏览
6 回复
写完感觉基础不行
最后于 2025-7-22 22:38
被X66iaM编辑
,原因:
最后于 2025-7-22 22:38
被X66iaM编辑
,原因:
谢谢,不错。
初始化函数放在了DriverEntry,排查了好几个小时
谢谢分享
谢谢分享
学习学习