• 本文作者: 漏洞应急响应中心
  • |
  • 2015年7月2日
  • |
  • APT攻防研究
  • |

隐藏在windbg下面的攻防对抗

1 关于驱动底层操作的监控调试

一般在进行常规的ring0级别的windows调试的时候都会使用windbg。但是有的时候windbg中断过于频繁,比方说经常会出现这样的场景,需要追踪一个特定的被删除的文件backdoor.exe,backdoor.exe被杀毒软件删除掉了,想观察到backdoor.exe被删除的完整的栈回溯。这个时候使用ollydbg进行追踪下断或者使用官方的监控工具procmon.exe往往是徒劳的。一般来说杀毒软件都是通过deviceiocontrol下发相关的iocode,ring0层的穿越驱动通过解析iocode和它的buffer来确定删除的文件。国内杀毒软件的穿越驱动常见的有金山的ksapi.sys,腾讯管家的tsyskit.sys,360安全卫士的bapidrv.sys,dsark.sys,以及pchunter的强制操作文件注册表进程的驱动。

 

bapidrv

ksapi

OL($38QV)VY[EAHO[7QYZ01

上图是几款安全软件的底层操作的驱动。其中封装有底层操作进程、文件、注册表的接口。

 

监控调试底层驱动对于进程文件注册表的操作    

2.1监控调试底层驱动对于进程的操作

以监控任务管理器中对打印机进程的结束的监控作为举例。

 

process_terminate

 

对于进程结束的监控还是比较简单的。直接bp命令然后F5放飞调试器,

5

 

使用.reload加载上符号表可以比较清晰的呈现出完整的栈回溯

6

 

NtTerminateProcess的函数原型是这样声明的。

7

 

可以观察到第一个传入的参数是一个handle。 首先使用dps命令看一下栈上面的参数。注意dps命令是一个组合命令。

8

 

 

其中红色剪头的就是被结束进程的handle。使用!handle命令来解析这个handle 9

 

 

可以看到这样的情形。进程名为Taskmgr.exe的进程将一个Object类型为Process的进程结束了。其中这个进程对象的地址为85568b28。 进程对象在内核中对应的结构体是EPROCESS。这个结构体不公开,并且这个结构体在不同的windows的release版本中各个成员的偏移都不相同。使用dt命令来解析EPROCESS结构体。

10

 

 

可以比较清晰的看到被结束的进程的名字。 在nt!ntterminateprocess进行下断可以截获到大多数的进程结束的操作了。 如果截获不到,那就需要追踪位于当前函数下面的更底层的函数调用,并且在更底层的函数处进行下断,可以使用windbg的一个很有用的命令wt进行更底层函数的调用的追踪。依然以下断nt! ntterminateprocess为例然后使用wt命令进行跟踪。 11

 

 

配合wt命令可以比较清晰的推断出进程被结束的逻辑。在NT5的XP系统上微软是通过PsGetNextProcessThread函数遍历进程的所有线程然后使用PspTerminateThreadByPointer函数对每个进程的线程进行结束的。如果你感兴趣可以用wt命令跟踪一下NT6,那又是另外一番情景。

 

2.2监控调试底层驱动对于文件的操作

以文件的删除的调试监控为例举例。 首先说一下在windows下面文件删除文件的ring0的三种路径。

a:常规的ring3使用DeleteFile API进行文件的删除在ring0是通过Nt/ZwSetInformationFile 传入FileDispositionInformation标志进行文件的删除的。

b:使用Nt/ZwCreateFile 、Nt/ZwOpenFile函数通过填充OpenPacket结构,标记FILE_DELETE_ON_CLOSE,在文件关闭的时候删除文件。

c:在ring0直接使用Nt/ZwDeleteFile也可以将文件删除。

Windows的文件系统是ntfs和fat32。其中适配ntfs文件系统的驱动是ntfs.sys。所有的文件系统的请求最终后进入到ntfs.sys中进行处理。 下面以cmd.exe的del命令举例说明对文件删除的栈回溯的追踪。 使用del命令删除C盘根目录下面的4这个文件夹。 12

 

使用bp命令对Ntfs!NtfsFsdSetInformation进行下断点,由于对ntfs!NtfsFsdSetInformation的访问很频繁,读者实际操作的时候如果只单纯的用bp命令无法精确的中断并将irp里面的file_object参数解析出来。这个栈回溯实际上是用windbg脚本的条件判断之后才在频繁的函数调用中中断下来的。 查看此时的栈上的参数

13

 

Ntfs!NtfsFsdSetInformation的函数原型是这样的。

14

 

需要把irp里面的参数解析出来。 15

 

红色剪头就是解析出来的_File_Ojbect结构,从第二个箭头可以看到我们删除的4目录。实际上解析的时候还可以直接使用!fileobj直接对_FILE_OBJECT的结构进行解析。

 

2.3 监控调试底层驱动对于注册表的操作

早期的国外的安全软件在操纵注册表的时候基本上是通过DeviceIoControl下发IoCode然后ring0层解析相应IoCode取出buffer中的内容ring0层驱动通过ZwxxxKey相关函数对注册表进行强制的操作。 国内的安全软件在操作注册表的时候基本上是通过在不同的系统版本上动态获取并存储nt!CmXxxKey的地址直接调用nt!CmXxxKey去对注册表进行底层操作的。比方说360的安全卫士的一个模块。Bapi.dll/Bapidrv.sys。bapi.dll是一个32位版本的dll文件,而bapidrv.sys在32位和64位windows系统下面分别是一个32位和64位的驱动。Bapi.dll导出了如下注册表操作函数

16

 

其中所有的BReg相关的注册表操作函数都会直接调用驱动封装的更底层的nt!CmxxxKey函数 nt!CmxxxKey是比nt!NtxxxKey更底层的函数。在每个不同的windows版本上面这些函数的地址都需要动态确定。 下面通过一个场景来观察一下360安全软件对于注册表的操作。 在run下面新建一个calc.exe让开机的时候每次计算器都自动启动

17

 

点击360的优化加速

18

 

然后使用bp命令下断,F5放飞调试器。然后点击禁止启动按钮。

19

 

Windbg直接会中断下来。

20

 

这个是完整的栈回溯。可以比较清晰的看到Bapi删除注册表的逻辑。 通过调用bapi.dll暴露给ring3的接口函数BRegDeleteValueW,然后DeviceIoControl与ring0驱动通讯调用底层的CmDeleteValueKey函数进行注册表键值的底层删除。在普通的ring3函数或者ntxxxkey函数进行下断的话是截获不到这种底层接口对注册表的操纵行为的。 WRK中对于CmDeleteValueKey是这样声明的。

21

 

要被删除的那个键值是一个unicode_string。使用dt命令可以比较清晰的看到我们要删除的是calc的这个键值

22

 

3 windbg脚本的引出

3.1 监控底层注册表操作的windbg脚本

在简单的情况下使用windbg的断点配合内存查看或者解析结构体命令还是能够解决一些问题。但是如果中断的位置调用过于频繁,普通的断点命令由于中断频繁,无法看到完整的栈回溯。此时可以借助于windbg的脚本的帮助。简单来了解一下windbg的脚本。 脚本文件是包含调试器命令序列的文本文件。调试器有多种多样的方法来加载和执行它。脚本文件里面的命令可以顺序执行,也可以包含复杂的流程。通常使用这个命令($$><)来执行windbg脚本即可。 比方说要监控上述的calc的注册表键值被删除的操作可以使用如下的windbg脚本。

23

解释一下这个脚本。 Windbg脚本语法中规定有二十个自定义伪寄存器:$t0, $t1, …, $t19。它们是可以通过调试器读写的变量。能用来保存任意整数值。在 $ 标记前加一个 at 标记(@)。这告诉调试器紧接着的记号是一个寄存器或者伪寄存器,不是一个符号。如果省略 @ 标记,调试器反映会慢一点,因为它要搜索整个符号表。as  命令用于定义一个新的别名或重新定义已存在的别名。通常会看到这样的命令 as /mu Name Address。 意思是将别名的等价值设置为从地址Address 开始的null结尾的Unicode字符串。 .block关键字用来引入一个语句块。 $sicmp(“String1″, “String2″)  计算后得到 -1、0 或者 1;就像 Win32 函数 stricmp。 .echo命令用于在windbg中进行屏幕的输出。 ad命令用于删除别名。指定*号则所有别名都会被删除。 gc 命令用于从一个条件断点恢复执行。

 

上面的这个脚本就是通过解析nt!CmDeleteValueKey的第二个参数UNICODE_STRING结构的ValueName并且对比其中的内容是否是calc来判定是中断还是继续执行。 使用$$><命令将脚本跑起来,然后手动使用RegWorkshop删除一个指定的注册表键值看是否能够中断下来。

`I($X`{LG]VAUNP8`F0}{{4

 

可以看到Regworkshop操纵注册表键值的逻辑,使用advapi32.dll提供的RegDeleteValueW函数层层调用到nt!CmDeleteValueKey。对于这种并不是很底层的操作注册表的方式使用windbg脚本很轻易的可以捕获到。 X64下面由于是寄存器传参监控注册表删除的脚本稍有变动。在这里也一并给出。

InsertPic_(07-03-11-14-40)

3.2 监控底层文件操作的windbg脚本

实际上如果单纯使用命令bp  Ntfs!NtfsFsdSetInformation 去实践2.2中监控底层驱动操作的场景,会由于频繁中断而导致根本获取不到完整的函数调用栈回溯。必须需要有相应的windbg脚本去配合操作。这里给出对应的windbg脚本的代码。该脚本适用情况如下。

a 任意开关机时刻挂shutdown notification或者开机挂回调的任意特定时刻对文件的删除操作。

b 任意底层驱动bapidrv.sys/tsyskit.sys/ksapi.sys/pchunter对文件的删除操作。

c 绕过常规的ring0的三种删除文件的方式直接发送IRP给文件系统的文件删除操作。

Catch(07-03-11-14-40)

简单说明一下上面的脚本。首先从栈中取出传递到各个ntfs内部函数的IRP,然后从IRP获取到FILE_OBJECT,继续从FILE_OBJECT中解析到文件名,然后对比现在操作的文件的文件名。 我们依然创建一个场景使用360文件粉碎和pchunter分别删除文件来然后使用上述的windbg脚本来迅速抓取栈回溯以搞清楚底层安全工具删除文件的原理。 1

 

首先使用360的文件粉碎。将脚本中的*targetfile*修改成*est.exe*。运行脚本之后点击粉碎文件后调试器迅速中断先来。此时看一下完整的栈回溯。

2

 

可以比较清晰的看到是通过DeleteFileW的Ring3Api对文件进行的删除。考虑到360卫士用户数量比较大,为了粉碎器的稳定,所以换成了使用ring3函数进行删除的策略。 再看一下pchunter的删除策略。 4

5

 

运行脚本之后windbg也迅速中断下来。du看一下正在删除的文件。 3

 

可以看到pchunter的强制删除是采用直接在ring3调用ring0的底层驱动直接向文件系统发送IRP来进行文件的删除。

4 小结

目前安全客户端软件竞争白热化。经常遇见的场景,竞品共存的时候加速球杀进程,强制卸载一键将竞品的所有文件强制删除,开机启动项优化强制将竞品的所有注册表全都删除掉,以及底层清除恶性rootkit的操作。这些情境下的对于进程文件注册表的操作全都是利用底层驱动去直接操作。要去捕捉和调试这些情况下的行为,使用hips的监控或者直接使用ring3的调试器都是徒劳的。本文抛砖引玉借助于以上场景与大家分享一下自己调试的心得。我还编写了其他功能的windbg脚本,也打包上传到网盘。希望能够对读者有所帮助。

windbg脚本地址: http://pan.baidu.com/s/1nrBps

Written by 漏洞应急响应中心