系列往期:
[原创]默默无闻·恶意代码分析Lab1
[原创] 默默无闻·恶意代码分析Lab5
[原创]默默无闻·恶意代码分析Lab6
[原创]默默无闻·恶意代码分析第七章
[原创] 默默无闻·恶意代码分析Lab7
[原创] 默默无闻·恶意代码分析Lab9
工具说明
参照书籍:《恶意代码分析实战》、《加密与解密》;
文件来源:文件来源:官网随书文件、或者附件中(文件密码:apebro
);;
使用工具:WinHex、CFF、StudyPE+、Exeinfo PE、Resource Hacker、Depends Walker、IDA、MSDN;
Lab10-1
本实验包括一个驱动程序和一个可执行文件。逆可以从任意位置运行可执行文件,但为了使程序能够正常运行,必须将驱动程序放到C:\Windows\System32
目录下,这个目录在受害者计算机中已经存在。可执行文件是Lab10-1.exe,驱动程序是Lab10-01.sys。
Lab10-1分析
1、两个文件都查壳一下;
干净的;
2、查看Lab10-1.exe的导入函数和Lab10.01.sys的导入导出函数;
首先,导出表没有导出函数;
导入的这仨都是内核函数,是对注册表进行操作;
导入表有加载库和服务以及查看代码页的行为;
3、看一看字符串;
有注册表、内核信息等行为,还有对防火墙的操作;
而且\\Registry\\Machine
开头,这是内核访问注册表的标准形态,等同于用户态的HKEY_LOCAL_MACHINE
;
目前有用的就是一个文件路径和弹窗相关操作;
3、使用OD
和IDA
跟踪Lab10-1.exe
;
因为在dll
文件没有导出表的情况下,直接从exe查看对dll
文件的使用,会更快一些;
首先,假设我们是正确打开了服务控制管理器,下一步我们就要看看创建了什么服务,以及打开了什么服务;
我们看到一个路径,很明确,这个路径并没有这个文件,所以并不会正确创建;
- 还有就是其中一个字段
dwStartType
,这个字段我们可以使用VC++
和MSDN查看定义;
翻译一下就是在进程调用StartService函数时由服务控制管理器启动的服务;
翻译一下就是指定为驱动服务;
这里要说一下的是,书中说的类型3
指的是dwStartType
,但是真正说明是驱动服务的是dwServiceType
字段,所以我觉得是书中错误,如有正确看法,还请大佬指出;
因为是无法正确创建的一个服务,但是也能够暗示出,这个服务是在这个Lab10-01.sys
中;
如果创建失败,那就打开服务OpenService
;
这里或许会很烦恼,创建都失败了,怎么可能打的开呢?
其实道理很简单,这个文件路径的问题,在之前的例子中,都会找到当前文件的路径,再找文件,所以找到对应文件并不难,在这里只是为了让我们体验R0层的病毒分析;
所以创建失败的情况应该理解为系统已经有一个相应服务了,所以才会使用打开,找到名为Lab10-01
的服务;
再下面就是开始服务了,这个没什么好看的;
最后就是ControlService
;
因为调用它的前提是打开服务,而打开服务的前提是创建服务失败(因为已经存在);
所以,我们要搞清楚,服务已经存在且已经运行后,再次打开服务又做了什么;
我们先看看这个字段的意思;
给大哥们翻译翻译这个惊喜:请求服务停止;
这个逻辑就很清晰了,现在问题是这个服务到底干了嘛;
IDA
打开Lab10-1.sys;
分析dll
程序的时候,常说DllMain
并不是真正的入口,只是在系统加载动态库的时候会加载这个函数进行必要的初始化后,再跳转到真正的程序入口;
sys
文件也是同样的道理,在DriverEntry
的处是一些必要的初始化和检查,然后才跳转到真正的驱动入口;
在这里,我们可以确认的是sub_10906
才是真正的驱动程序入口,如果不放心,可以去上面的call
看一下都干了啥;
我们看一下sub_10906
;
这就结束了,就是把一个函数的偏移给了一个内存位置,既无调用也无跳转,看的有点迷糊,不慌,换个视角就相对容易理解了;
1 2 3 4 | sub_10906(...){
DriverObject - >DriverUnload = ( * )sub_10486;
return 0 ;
}
|
所以就是相当于把一个函数指针放在了一个服务里,让服务去执行;
那就进sub_10486
去瞅一瞅;
然后就是一堆函数跟字符串,换个视角;
1 2 3 4 5 6 7 8 9 | sub(...){
RtlCreteRegistryKey(...);
RtlCreteRegistryKey(...);
RtlCreteRegistryKey(...);
RtlCreteRegistryKey(...);
RtlWriteRegistryValue(...);
return RtlWriteRegistryValue(...);
}
|
在此,因为Rtl
开头的是微软未公开的函数,我使用的MSDN并没有查到,而且使用某个大佬分享的未公开函数查询网站也没有查到;
最后还是查询了微软官方文档,了解了一些功能细节;
1 2 | RtlCreteRegistryKey(...); / / 沿着给定的相对路径在注册表中添加一个键值对象。
RtlWriteRegistryValue(...); / / 调用方提供的数据沿着指定的相对路径在给定的值名处写入注册表。
|
到这里就很清晰了,这个函数做了很多关于注册表的操作,因为跟防火墙相关,所以我们可以合理猜测是禁用防火墙;
问题
1、这个程序是否直接修改了注册表(使用procmon
来检查)?
没有用Procmon
工具,但是经过分析,是直接修改了注册表;
2、用户态的程序调用了ControlService
函数,你是否能够使用WinDbg
设置一个断点,以此来观察由于ControlService
的调用导致内核执行了怎样的操作?
没有用WinDbg
工具,但是经过分析,我们知道,是卸载操作;
3、这个程序做了些什么?
创建一个服务加载驱动,然后创建和写入了一些注册表项,用于禁用防火墙;
Lab10-2
该实验的文件为Lab10-02.exe;
Lab10-2分析
1、查壳;
无壳;
2、查看导入表;
因为过长,就不全面截图了;
首先依旧是服务相关的函数,其次是发现了资源相关的函数,所以我们有理由怀疑是否资源节中隐藏了什么秘密;
貌似加载了什么库,还申请了内存空间,并且获取程序地址;
3、查看字符串;
一个路径,和一个驱动服务名;
4、过程分析;
程序开始就加载了资源,所以我们还是使用Resource Hacker
查看一下资源节;
基本可以确定里面隐藏了一个程序;
保存到桌面,用IDA
打开;
IDA
已经自动识别这是一个驱动文件了;
暂时不要着急分析这个驱动文件,我们先去看看原程序是如何加载这个驱动文件的;
到此分析就算是结束;
经过分析我们知道,这个程序构造了一个伪装的NtQueryDirectoryFile
程序,并且将其隐藏;
并没有创建设备,也没有向驱动对象添加什么操作函数;
问题
1、这个程序创建文件了吗?它创建了什么文件?
创建了C:\\Windows\\System32\\Mlwx486.sys
;
2、这个程序有内核组件吗?
一个内核驱动,被隐藏在资源节中,最后作为一个服务加载到内核;
3、这个程序做了些什么?
隐藏文件的Rootkit,使用了SSDT挂钩来覆盖NtQueryDirectoryFile
的入口,会隐藏任何以Mlwx
开头的文件,使用WinDbg
进行内存查询得到字符串;
Lab10-3
本实验包括一个驱动程序和一个可执行文件。你可以从任意位置运行可执行文件,但为了程序能够正常运行,必须将驱动程序放到C:\Windows\System32
目录下,这个目录在受害者计算机中已经存在。可执行文件是Lab10-3.exe,驱动程序是Lab10-03.sys。
Lab10-3分析
1、查壳;
Lab10-03.sys:
Lab10-03.exe:
皆无壳;
2、导入表;
Lab10-03.sys:
IoGetCurrentProcess
说明这个驱动涉嫌修改正在运行的进程;
Lab10-03.exe:
有服务相关程序和加载动态库和写文件嫌疑;
3、字符串;
Lab10-03.sys:
可以看到疑似句柄或者注册表键的字符串;
Lab10-03.exe:
一个网址,两个和sys
文件相近的字符串,和上一个文件的路径名;
4、过程分析;
用IDA
打开Lab10-03.exe文件;
从main()
函数开始看起,思路比较清晰,构建程序框架:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | if (OpenSCManagerA(...)){
if (CreateServiceA(...))
StartServiceA(...);
CloseServiceHandle(...);
if (CreateFile(...) = = - 1 )
return 1 ;
DeviceIoControl(...);
if (OleInitialize( 0 ) > = 0 ){
CoCreateInstance(... , &ppv);
if (ppv){
VaiantInit(...);
v9 = SysAllocString(...);
while ( 1 ){
( * (void () * ( * )ppv + 44 ))(...);
Sleep();
}
}
OleUninitialize(...);
}
}
|
这就很显然了,创建一个叫做Process Helper
的服务,加载C:\\Windows\\System32\\Lab10-03.sys
的内核驱动;
如果创建成功了,就启动服务,加载Lab10-03
到内核,并且打开一个由ProcHer
驱动创建的内核设备驱动句柄\\\\.\\ProcHelper
;
再后就是DeviceIoControl
函数的调用,我们可以看见它的关键字段lpOutBuffer
和lpInBuffer
都是0,这是很不正常的,而且在dwIoControlCode
字段的值为0x0ABCDEF01
也是不正常的;
DeviceIoControl
将控制代码直接发送到指定的设备驱动程序,使相应的设备执行相应的操作。
既然知道是加载了内核驱动,我们重点是它做了什么坏事,看看DeviceIoControl
请求了啥,所以在此可以直接去看看Lab10-03.sys,一探究竟;
进入DriverEntry()
找到正确的跳转sub_10706
;
先看一下整体结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | sub_10706(...){
RtlInitUnicodeString(...); / / "\\Device\\ProcHelper"
result = IoCreateDevice(...);
if ( result > = 0 ){
... = sub_10606;
... = sub_10606;
... = sub_10666;
... = sub_1062A;
RtlInitUnicodeString(...); / / \DosDevices\ProcHelper
v3 = IoCreateSymbolicLink(...);
if ( v3 < 0 )
IoDeleteDevice(...);
result = v3;
}
}
|
首先,我们可以看到IoCreateDevice()
创建了名为\\Device\\ProcHelper
的设备;
往下是如果创建成功,则进入;
成功跳转后是给DriverObject
偏移添加函数;
所以,我们先要去了解一下这个IoCreateDevice()
函数的DeviceObject
字段的意思;
翻译翻译:指向调用者的驱动程序对象的指针。每个驱动接收到一个指向它的驱动对象的指针,这个指针在驱动入口例程的一个参数中。WDM函数和筛选器驱动程序也在它们的添加设备例程中接收驱动程序对象指针。
所以,下一步搞清楚都是这些驱动程序都有哪些行为;
sub_10606;
IoCompleteRequest()
宏表示调用者已经完成了给定I/O请求的所有处理,并将给定的IRP返回给I/O管理器;
sub_10666;
IoGetCurrentProcess()
返回一个指向当前进程的指针;
IofCompleteRequest()
宏指示调用者已经完成了给定I/O请求的所有处理,并将给定的IRP返回给I/O管理器。
sub_10666
在驱动正确安装,服务正常启动后,处理DeviceIoControl
的请求;
我们注意到,IoGetCurrentProcess()
返回的是当前进程是指针,它返回的其实是EPROCESS
(进程对象),每一个进程都有一个EPROCESS
;
想了解EPROCESS
之前,先简短的总结一下TEB
、PEB
和TIB
;
TEB
:(Thread Environment Block,线程环境块),进程中的每个线程都有自己的一个TEB
。一个进程的所有TEB
都以堆栈的方式,存放在从虚拟地址的某一个地方,每4KB为一个完整的TEB,向下扩展;在用户模式下,当前线程的TEB位于独立的4KB段,可通过CPU的FS寄存器来访问该段,一般存储在fs:[0x00]
,还有一个自引用fs:[0x18]
;
TIB
:(Thread Information Block,线程信息块),是TEB
的第一个成员;
PEB
:(Process Environment Block,进程环境块)存放进程信息,每个进程都有自己的PEB信息,位于用户地址空间。我们经常看见的fs:[0x30]
就是用来访问PEB
结构体的地址;
因为这两个结构太长且内容庞大这里就不赘述了,至于关于TEB
和PEB
结构的更多细节,推荐火哥整理的内核结构网站;
首先这里要说到的是IoGetCurrentProcess()
函数,它返回调用DeviceIoControl
进程的EPROCESS
结构,然后访问[+88]
处偏移量,然后再访问[+8C]
处的;
关于此题在火哥整理的内核结构网站中找到的Win7中的结构跟书后答案有所区别,最后是找到了86版本的XP系统的还原了书中对于_EPROCESS
结果的样子;
至于具体如何,还是建议使用WinDbg
在当前系统中查找;
以下关于EPROCESS
截图默认为XP系统
在EPROCESS
结构的第一个叫做进程控制块的_KPROCESS
;偏移0x88
就在其中;
点进去后我们发现这是一个双向链表,[+8c]
其实指的是下一项的指针;
回到sub_10666
,我们可以看到:
看着有点眼晕,直接上图吧;
就是这么个意思,双向链表删除节点操作,但是这里只是解除链接并不删除;
当操作系统正常运行时,中间那个进程就被Rootkit
隐藏了,当OS
遍历进程链表时,隐藏进程总是被跳过;
在这里,或许有萌新会疑惑,隐藏后为什么不影响运行;
这里要解释的是,进程只是进程中运行线程的容器,线程被调度到CPU上运行;只要线程合理的占用操作系统,它就会被调度,所以进程也会继续正常运行;
sub_1062A;
删除了一个名为\DosDevices\ProcHelper
的符号链接,这个符号链接可以给用户态提供访问;
看完上面三个功能,程序走向了最后;
创建了一个名为\DosDevices\ProcHelper
的符号链接,如果创建成功则删除设备驱动;
大体的了解过Lab10-03.sys
做了什么后,我们返回Lab10-03.exe
看一看后续的代码;
接着从OleInitiallize
往下看;
我们知道这是一个COM
函数,一直往下,是一个CoCreateInstance
函数;
在MSDN中,它的描述是:用于创建COM对象;
将网址压入栈中;
然后打开网址,然后睡眠30秒;
这里就跟Lab7-2
处是一样的,所以就不再赘述了;
1、这个程序做了些什么?
加载驱动,每隔30秒弹出一个广告。这个驱动通过系统链表中摘除进程环境块(PEB),来隐藏进程;
2、一旦程序运行,你怎样停止它?
重启,无它;
3、它的内核组件做了什么操作?
内核组件负责响应,从进程链表中摘除进程的DeviceIoControl
请求;
总结
这章节是讲WinDbg
的,可惜的是我的WinDbg
并没有配好,而且用IDA
还能解决;
至于没有用OD
那是因为OD
是R3层的调试器,内核只能用R0层的;
挖个坑吧,写完这本书就开始写内核的帖子;
欢迎大佬指正,感谢!
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2021-4-20 19:23
被平头猿小哥编辑
,原因: