讲师中心 微信公众号
AI工具推荐 视频效率加速

一次开发的意外逆向之旅

酷雪君_7586

酷雪君_7586

发布时间:2025-09-07 08:07:19

|

875人浏览过

|

来源于php中文网

原创

在从事windows内核开发时,我最近因功能需求需要遍历pspsetcreateprocessnotifyroutine回调函数数组。在获取pspcreateprocessnotifyroutine的过程中,我发现了一些有趣的现象,决定在此与大家分享。由于这是我第一次撰写文章,难免有不足之处,还请各位读者多多包涵。

01

1

首先,让我们讨论一下获取这个数组的思路,以Windows 7 x64为例,其他版本大同小异。PspCreateProcessNotifyRoutine是一个PVOID类型的指针,它存储通过PsSetCreateProcessNotifyRoutine函数设置的各种回调函数。我们可以通过以下步骤获取这个数组:

  1. 使用MmGetSystemRoutineAddress获取PsCreateProcessNotifyRoutine的地址。在这个函数中,可以发现它调用了PspSetCreateProcessNotifyRoutine。

一次开发的意外逆向之旅2. 在PspSetCreateProcessNotifyRoutine函数中,偏移0x33处有一次对PspCreateProcessNotifyRoutine的操作。

一次开发的意外逆向之旅3. 跟入这个函数地址后,可以看到这是一个函数地址表。然而,通过uf这些地址时,发现这些地址是错误的。这是为什么呢?

一次开发的意外逆向之旅4. 实际上,微软对这些函数进行了加密。要获取真实的函数地址,需要先对其进行解密。解密方法非常简单,只需将这些地址取出并进行 &0xfffffffffffffff8 操作即可。

一次开发的意外逆向之旅5. 这样就可以获得真实的地址指针,通过访问这个指针,可以跟到真实的挂钩函数地址。按照这个思路编写的遍历代码是可行的,此处就不再展示。

一次开发的意外逆向之旅6. 我们可以通过跟入这个函数并对比Pchunter的结果来验证我们的方法是正确的。

一次开发的意外逆向之旅7. 在获取这张函数表后,通过对比模块的基地址和模块大小,可以确定函数所属的模块。同样,可以根据模块名在PsSetCreateProcessNotifyRoutine的函数地址传入有目的的地址和True来取消该模块的挂钩。也可以直接对函数头部进行Ret0操作,但由于可能出现问题,不推荐这样做。

02

2

那么我在Win7 x86版本上遇到了什么问题呢?同样使用MmGetSystemRoutineAddress获取PsCreateProcessNotifyRoutine的函数地址时,发现这个地址虽然是一个函数地址,但实际上是错误的。因为在Windbg中uf获取的PsCreateProcessNotifyRoutine与通过MmGetSystemRoutineAddress获取的地址居然不一样!起初我百思不得其解,但冷静下来后,我开始逐步分析这个过程,有趣的旅程就此展开!

  1. 首先,Windbg的u命令一定是准确的。微软自家的调试器在有符号表的情况下,如果连函数位置都不知道,那真是让人无语了!而且我确实进入过u到的函数,确认是正确的。因此,我猜想问题一定出在MmGetSystemRoutineAddress这个关键函数上。

  2. 那么MmGetSystemRoutineAddress是如何工作的呢?经过查阅现有资料,我解开了我的疑惑。MmGetSystemRoutineAddress内部实际上是解析模块文件的EAT(导出表)来获取函数调用者所需的函数。通过解析EAT和模块基地址的运算结合ImageLoad的对齐方式,返回对应的函数位置。因此我们的思路有了,因为是x86操作系统,在没有KPP保护的情况下,我的内核的EAT很可能被一些第三方软件挂钩,导致我获取的函数不正确。于是我在Windbg中使用.reload命令重新加载所有模块信息,然后使用lm命令查看所有模块的地址,通过对比各个模块的基地址和模块大小,大致确定了是哪个模块。PCHunter的内核挂钩也验证了我的猜想。

03

模块分析

既然确认内核是被EAT挂钩的,我们不妨分析一下这个模块。

  1. 通过数字签名,我们不难看出这个模块是一个安全软件的模块,到这里困惑就解开了。我们猜测是因为在x86系统上没有KPP保护,为了保护自身的钩子不被卸载或者为了监控其他软件的钩子,安全软件选择在EAT挂钩以保护自身的钩子不被其他软件取消,这是一个很好的思路。那么到底是不是这样呢,我们接着分析。

一次开发的意外逆向之旅2. 既然我们已经知道被替换函数的地址和被EAT挂钩的名称,那么我们接下来从这两点开始进行逆向。首先,我们先寻找字符串信息,根据模块名称。

☤CaduceusMail
☤CaduceusMail

☤CaduceusMail让你的OpenClaw通过一个域名/邮箱组合自动化企业级通信堆栈。

下载

一次开发的意外逆向之旅3. 我们先根据字符串找到对这个字符串引用的地址,很明显只有这一处。我们跟进去,结合上下文看到了一个关键函数ZwQuerySystemInformation。到这里,有过内核开发经验的小伙伴们肯定已经猜到这个函数是在获取模块基地址。看他对v4这个参数的引用,应该就是需要返回的模块地址。我们将这个函数命名为GetKeyModuleAddress,同时参数返回的就是模块大小。

一次开发的意外逆向之旅一次开发的意外逆向之旅4. 接着我们对其x进行调用分析,可以看到有两处。我们跟入第一处,很幸运地找到了所需的函数。在这个函数中,我们可以看到大量关键函数的字符串,而我们的PsSetCreateProcessNotifyRoutine也在其中。这时我们的第二条主线就排上用场了。我们可以看到下图中使用PsSetCreateProcessNotifyRoutine这个字符串的函数也引用了sub_4A504这个函数,而这个函数正是我们内核被挂钩EAT后跳转过去的函数。因此我们猜测sub_49F84这个函数应该是GetProcAddressAndSetHook这两个功能,于是我们对其命名GetProcAddressAndSetHook,同时对sub_49F84这个函数命名为Hook_PsSetCreateProcessNotifyRoutine。

一次开发的意外逆向之旅一次开发的意外逆向之旅5. 接着我们对GetProcAddressAndSetHook(sub_4A504)的流程进行分析。首先进入该函数后,根据传入参数我们不难发现有一个函数名称和一个hook的函数地址。根据使用这两项的函数,我们猜测sub_49DDE这个函数的功能是获取函数地址。在他调用的函数sub_49D60中可以明显看到存在PE特征0x5A 0x4D以及0x45 0x50这几个字节码,所以这个函数肯定是根据函数名称获取函数地址无疑,我们将其命名为GetFunAddress(sub_49D60),其首参数为返回需求函数的地址。

一次开发的意外逆向之旅一次开发的意外逆向之旅6. 接着跟入sub_49B6E这个函数进行分析,我们可以看到明显的内存页操作,调用了IoAllocateMdl,MmProbeAndLockPages,MmMapLockedPagesSpecifyCache这几个关键函数。这很明显是申请MDL对内存页进行锁定,防止换页造成缺页异常等问题,这一般是hook的必要操作,所以我们对其命名为LockPage。

一次开发的意外逆向之旅7. 在完成页锁定之后,必然就是进行hook操作。这里采用了一个很好的方法,并没有使用memcpy或者其他内存填充写入的方法,而是采用了原子操作。这种一箭双雕的方法,首先使用_InterlockedExchange这个原子操作交换函数可以很方便地解决了同步问题,其次在_InterlockedExchange调用的时候返回值是上一次的状态,也很方便地保存了上一次的地址,以便于恢复。所以说是一种一箭双雕的方法,InterlockedExchange的第二个参数也使用到了我们的传入地址,以及刚才的LockPage。

一次开发的意外逆向之旅8. 在完成原子交换之后,GetProcAddressAndSetHook的第五个参数被使用。这里可以看到使用结束之后,之前的地址被保存下来,所以可以论证这里是用于恢复使用的。而且结合外面的函数传入值来看,这里是一个全局对象,而且这里这个值在hook的函数之中仍然需要去调用,所以也论证了这一点。

  1. 在之后紧接着调用了sub_4B340这个函数,在这个函数中就是一些基本的解除页面锁定的函数,我们将其命名为UnlockPage。

至此,Hook的全套流程就已经分析完毕了。接下来我们来看一看hook掉的代理函数做了一些什么。

04

代理函数分析

一次开发的意外逆向之旅1. 首先,第一个函数sub_4A3F2的操作非常奇怪,该函数作为替换函数应该是一个两参函数,但很不幸IDA分析失败了。最开始因为经验欠缺我没有明白这个函数的意义,但随着之后的分析我茅塞顿开。这个函数是通过栈寄存器来获取调用地址的,因为在栈上有函数的调用地址,所以在之后的LogAboutInformation中会有使用。

一次开发的意外逆向之旅2. 根据Hook_PsSetCreateProcessNotifyRoutine的第二个参数True或False来确定具体流程,无论是在取消设置还是在设置函数中都会调用sub_49CE0这个函数,这个函数的唯一作用就是调用之前保存下来给全局变量的原始的PsSetCreateProcessNotifyRoutine,我们将其命名为CallRightOldPsSetCreaetProcess。

一次开发的意外逆向之旅3. 接着会根据对于PsSetCreateProcessNotifyRoutine调用和失败会进入到LogAboutInformation(sub_4A2C8)这个函数中,跟入该函数结合传入参数分析该函数的唯一意义就是获取设置的函数地址模块名称以及调用者的模块名称,对其进行格式化之后结合特定标志位上传到r3上。

一次开发的意外逆向之旅4. 有意思的是在设置回调的代理函数Hook_PsSetCreateProcessNotifyRoutine中在设置行为下是存在拦截操作的,拦截操作的行为依据来源于LogAboutInformation的返回值并且返回0xC0000022,但是在LogAboutInformation的第三个参数为0的情况下LogAboutInformation直接返回0,所以也就是说在该版本下拦截其实并不生效。

一次开发的意外逆向之旅05

恢复与绕过思路

  1. 通用思路,首先同样获取系统内核模块的相关函数地址,模拟MmGetSystemRoutineAddtess的流程,但是我们这里需要解析文件并且解些EAT载入内存,如果采用读文件的方式的话需要注意内存对齐问题。

  2. 拿到真正的函数地址时,如果需要恢复的话可以直接学习刚才那一套mdl锁+原子交换的理念,这样就可以解决了。

  3. 但这里其实并不提倡这种方法,因为在一些软件中会对于代码有crc校验等功能,如果强行解除hook的话很有可能导致crc校验失败导致不可预料的结果,所以直接可以将获取到的函数进行指针强转直接调用即可。

热门AI工具

更多
WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

SkildArt
SkildArt Hot

SkildArt是一款AI文本写作工具,一站式 AI 视觉创作平台。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

UP简历
UP简历 Hot

一款AI办公效率工具,主要用于基于AI技术的免费在线简历制作工具,适合需要提升相关任务效率的用户。

咔片AIPPT

一款在线AI演示文稿制作工具,可根据主题和内容需求辅助生成PPT结构与页面,提高演示材料制作效率。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

切问学术

切问学术是一款AI论文写作工具,复旦大学NLP团队推出的AI学术智能体。

小橡皮AI

一款AI工具,主要用于小橡皮一键去AI味,将机器味重的内容改得更有活人感,防止因为 AI 内容被平台打上 AI 标签,以及限流,适合需要提升相关任务效率的用户。

相关专题

更多
全局变量怎么定义
全局变量怎么定义

本专题整合了全局变量相关内容,阅读专题下面的文章了解更多详细内容。

4885

2025.09.18

python 全局变量
python 全局变量

本专题整合了python中全局变量定义相关教程,阅读专题下面的文章了解更多详细内容。

2070

2025.09.18

c++ 全局变量
c++ 全局变量

本专题整合了c++全局变量的使用、定义、作用域等等内容,阅读专题下面的文章了解更多详细内容。

199

2026.03.17

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

1838

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

2904

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

6224

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

6089

2023.11.24

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

7674

2024.03.22

Vite 项目如何打包和部署
Vite 项目如何打包和部署

Vite项目打包部署专题汇集生产构建、产物预览、输出目录、资源路径、Nginx与GitHub Pages部署及常见白屏和路由404问题的已审核文章,帮助读者完成上线流程。

60

2026.10.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Python uv官方安装与入门文档
Python uv官方安装与入门文档

共0课时 | 0人学习

Buffalo框架快速入门指南
Buffalo框架快速入门指南

共0课时 | 0人学习

Conan 2 安装指南
Conan 2 安装指南

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn