一次开发的意外逆向之旅

笔者最近从事windows内核开发的时候因为功能需要,所以需要对PspSetCreateProcessNotifyRoutine回调函数数组进行遍历,于是笔者照往常思路在获取PspCreateProcessNotifyRoutine的时候发现了一些很有意思的事情,特此拿出来与诸君分享。第一次写文章难免有不足,希望诸位看客可以海涵。

01

1

首先我们先说说如何获取这个数组的思路,以Windows7X64为例,其他各版本类似,首先;

PspCreateProcessNotifyRoutine是一个PVOID的指针,它里面存放的是通过PsSetCreateProcessNotifyRoutine这个函数设置的各种回调函数。那我们怎么获取这个数组呢很简单:

1. 我们先通过MmGetSystemRoutineAddress来获取 PsCreateProcessNotifyRoutine这个函数的地址,通过这个地址我们不难发现在这个函数中调用了PspSetCreateProcessNotifyRoutine这个函数。

2. 紧接着在PspSetCreateProcessNotifyRoutine这个函数中我们不难发现在0x33偏移位置有一次对PspCreateProcessNotifyRoutine的操作。

3. 紧接着我们跟入这个函数地址就可以看到这是一张函数地址表,但是我们通过uf这些地址会发现这些地址是错误的,这是为什么呢?

4. 其实很简单,微软对这些函数都进行了加密,如果要取得真实的函数地址必须要先对其进行解密,其实很简单只要取出来这些地址对其 &0xfffffffffffffff8就可以了。

5. 这个时候就可以获得真是的地址指针,再对这个指针访问就可以跟到真实的挂钩函数地址了,遍历的代码按照这个思路编写是没有问题,在此就不再给出了。

6. 我们可以通过对这个函数的跟入和比对Pchunter的结果来论证我们是正确的。

7. 在拿到这张函数表之后对比模块的基地址和模块大小我们不难确定函数所属的模块归谁所有,同样我们这时候根据模块名在PsSetCreateProcessNotifyRoutine的函数地址传入有目的的地址和True之后,那么该模块的挂钩也自然而软取消掉了,除此之外也可以直接对其函数头部进行Ret0,不过可能出现一些问题所以不推荐这么做。

02

2

那么我遇到了什么问题呢,在win7x86版本上同样通过MmGetSystemRoutineAddress来获取PsCreateProcessNotifyRoutine这个函数地址的时候居然发现这个地址虽然是一个函数地址,但是这个地址居然是错的,因为我发现在Windbg中uf 获取的PsCreateProcessNotifyRoutine和通过MmGetSystemRoutineAddress这个拿到的地址居然不!一!样!最开始我百思不得其解,很疑惑这是为什么,但是在冷静下来之后我开始慢慢分析这个过程,有趣的旅程就这么开始了!

1. 首先windbg的u一定是准确的,这个是根本,微软自家的调试器在自己函数有符号表的情况下都不知道在哪的话那么一首凉凉送给他不为过!而且确实u到的函数我进入看过也是正确的,于是我猜想问题一定在MmGetSystemRoutineAddtess这个关键函数上.

2. 那么MmGetSystemRoutineAddtess他是怎么做的呢,其实在翻阅了现有的资料之后我解开了我的疑惑,在MmGetSystemRoutineAddtess的内部其实是解析了模块文件的EAT也就是我们俗称的导出表来获取函数调用者所需求的函数,通过对EAT的解析以及和模块基地址的运算结合ImageLoad的对齐方式,返回对应的函数位置,于是我们的思路就有了,因为是X86的操作系统,在没有KPP保护的情况下很有可能我的内核的EAT被一些三方软件挂了钩子,导致我获取函数不正确,于是在windgb中.reload 装载所有模块信息后,lm一下所有模块地址也就出来了,对比看了一下各个模块基地址和模块大小也就大概确定了是属于哪个模块,PCHunter的内核挂钩也证明了我的猜想 。

03

模块分析

既然拿到了这个模块确认内核是被EAT挂钩那么不妨分析一下这个模块。

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

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

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的函数之中仍然需要去调用,所以也论证了这一点。

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

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

04

代理函数分析

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

2. 根据Hook_PsSetCreateProcessNotifyRoutine的第二个参数Trueor 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校验失败导致不可预料的结果,所以直接可以将获取到的函数进行指针强转直接调用即可。

原文发布于微信公众号 - 安恒信息(DBAPP2013)

原文发表时间:2018-05-08

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏hbbliyong

设计模式名录

本文给出了经典的23种设计模式的名录,包括他们的分类、名称、定义以及简要说明,方便大家能够快速的回忆起他们。也是前面写过的或者后面将要写的设计模式的一个目录。...

2907
来自专栏Kirito的技术分享

深入理解RPC之动态代理篇

提到 JAVA 中的动态代理,大多数人都不会对 JDK 动态代理感到陌生,Proxy,InvocationHandler 等类都是 J2SE 中的基础概念。动态...

58512
来自专栏芋道源码1024

Redis 从入门到起飞(上)

1. Redis 介绍1.1 NoSQL 基本概念1.2 NoSQL 分类1.3 Redis 基本概念1.4 发展历史1.5 应用场景2. Redis 安装2....

1784
来自专栏企鹅号快讯

8行代码实现ui文件到py文件转换

在用PyQt进行GUI编程时,一般先通过Qt Designer产生后缀为.ui的UI文件(类似于XML文件),接着将.ui文件转换成.py文件,再通过一个pyt...

2968
来自专栏非著名程序员

Android开发工具类之TimeUtils

开发最重要的就是速度和效率,其实我一直都非常支持使用第三方的工具类,因为毕竟是一些大牛封装好的,效率什么的,可能比一些初学者写的确实好一些,但是我建议在使用第三...

2045
来自专栏名山丶深处

Hello——Java10新特性,请了解一下

2695
来自专栏蓝天

使用#include消除重复代码

上述是最为常用的,对于C++程序,闭包可能用得相对少一点。下列代码使用的是第5种:利用#include消除重复代码:

961
来自专栏码洞

如履薄冰 —— Redis懒惰删除的巨大牺牲

之前我们介绍了Redis懒惰删除的特性,它是使用异步线程对已经删除的节点进行延后内存回收。但是还不够深入,所以本节我们要对异步线程逻辑处理的细节进行分析,看看A...

871
来自专栏编程

Java反射札记

Java反射相关内容,在阅读一些开源框架和自己动手封装逻辑时常要用到,以前陆陆续续地看到过一些文章,但是最终留下的是破碎的代码片段和类名,所以这次重新敲一遍反射...

20210
来自专栏CVer

糟了!Python3.7.0 来了

美国时间6月27日晚8点,Python 3.7.0 经过多轮测试,终于发布了正式版,增强了多处特性功能,同时 3.6 也更新到 3.6.6 稳定版本。

1924

扫码关注云+社区

领取腾讯云代金券