问题
1.这个程序做了些什么?
解答:书上说本次实验包括一个驱动程序和一个可执行文件,还要把驱动程序放到C:\Windows\System32
目录下面,我们试试
把文件放到那个目录之后,点击执行就会跳出这个IE
,然后不断的跳IE
出来,到我打完这段话,已经跳了这么多的窗口出来了
然后我们开始分析,先是安装书上的开始静态分析
我们先分析的是exe
文件
在KERNEL32.DLL
里面我们发现这么几个有意思的函数CreateFile
和WriteFile
然后我们查看这个ADVAPI32.DLL
这个导入库
这个有一个比较让人感兴趣的导入函数就是OpenSCManagerA
,还有StartServiceA
以及CreateServiceA
这三个导出函数,说明这个代码会创建一个服务在系统中
然后我们开始分析sys
文件的导出函数有哪些
我们可以看到如下的一些函数
IoCreateDevice
IoCreateSymbolicLink
IoDeleteDevice
IoDeleteSymbolicLink
IoGetCurrentProcess
IofCompleteRequest
KeTickCount
RtlInitUnicodeString
我们每个函数都解释一下,首先是IoCreateDevice
在MSDN
中,这个函数被定义为
The IoCreateDevice routine creates a device object for use by a driver.
翻译过来就是
IoCreateDevice例程创建供驱动程序使用的设备对象
这个例程会创建一个设备,对应的例程有IoDeleteDevice
下一个IoCreateSymbolicLink
IoCreateSymbolicLink例程在设备对象名称和设备的用户可见名称之间建立符号链接
对应的例程有IoDeleteSymbolicLink
下一个IoGetCurrentProcess
IoGetCurrentProcess例程返回一个指向当前进程的指针
下一个IofCompleteRequest
IoCompleteRequest例程指示调用者已完成给定I/O请求的所有处理,并将给定的IRP返回给I/O管理器
下一个是KeTickCount
检索自系统启动以来经过的毫秒数,最长为49.7天
下一个是RtlInitUnicodeString
初始化一个统计的Unicode字符串
书上说,从IoGetCurrentProcess
这个例程可以看出,这个驱动或者在修改正在运行的进程,或者需要关于进程的信息
这个病毒好玩的地方在于,如果我们打算用任务管理器关闭这个进程的时候,会发现,根本没有这个进程,包括使用Process Explorer
里面也是没有列出来
然后我们恢复快照,准备进行高级静态分析
首先我们找到程序开始的地方,开始分析
第一个调用的函数就OpenSCManagerA
,这是一个用于打开服务控制的函数,说明程序打算在这里操作服务,这里我们就不着重分析各种跳转了
如果上面的OpenSCManagerA
调用成功,就会开始执行下面这些代码
我们可以看到一个字符串变量被压入了栈中,C:\\Windows\\System32\\Lab10-03.sys
,这就是我们那个驱动文件
然后这里我们忽略入参为0
的参数,主要看不为0
的参数
其中的一个是BinaryPathName
,它的值是我们那个驱动文件的路径,这是用于指明服务的二进制文件的位置的参数,然后从上往下的下一个入参是dwErrorControl
这个参数,这个参数是用于错误控制的,对于我们来说,没有太多的意义
我们注意到这里有个dwStartType
这个参数,值为3
这个的意义就是
用户可以使用“服务”控制面板实用程序启动服务。 用户可以在“开始参数”字段中为服务指定参数。 服务控制程序可以启动服务并使用StartService函数指定其参数。
服务启动时,SCM执行以下步骤:
检索存储在数据库中的帐户信息。
登录服务帐户。
加载用户配置文件。
在暂停状态下创建服务。
将登录令牌分配给进程。
允许该过程执行。
下一个参数是dwServiceType
,他的值为1
,意义就是表明这是个驱动服务
最后的lpServiceName
和lpDisplayName
说明的是这个服务的名字是Process Helper
如果调用CreateServiceA
成功的话,下面执行StartService
,一旦执行这个之后,恶意驱动Lab10-03.sys
就会被加载到内核中
一切顺利的话,就会执行下面这些代码
这里创建了一个文件在\\.\ProcHelper
这个地方并作为一个句柄打开,还是如果一切顺利的话,会执行下面这个代码
这里有个新函数叫DeviceIoControl
这个东西,这个函数的用途如下
将控制代码直接发送到指定的设备驱动程序,导致相应的设备执行相应的操作。
以及MSDN
中的定义如下,顺便可以得出这个入参的列表
BOOL WINAPI DeviceIoControl(
_In_ HANDLE hDevice = eax,
_In_ DWORD dwIoControlCode = 0abcdef01h,
_In_opt_ LPVOID lpInBuffer = 0,
_In_ DWORD nInBufferSize = 0,
_Out_opt_ LPVOID lpOutBuffer = 0,
_In_ DWORD nOutBufferSize = 0,
_Out_opt_ LPDWORD lpBytesReturned = eax,
_Inout_opt_ LPOVERLAPPED lpOverlapped = 0
);
这里我们需要分析一个这个DeviceIoControl
的各种用途,按照书上的说法,这里DeviceIoControl
的参数lpInBuffer
和lpOutBuffer
被设置为了Null
也就是0
很不寻常,这意味着这个请求没有发送任何的信息到内核驱动中(lpInBuffer
= 0
),并且内核驱动的反馈也是没有的(lpOutBuffer
= 0
),然后还有个古怪的地方就是dwIoControlCode
的值是abcdedf01
,这个值有点太人工了
然后下一个函数调用就是这个CoCreateInstance
,这个函数在MSDN
中的解释就是
创建与指定的CLSID关联的类的单个未初始化对象。
当您只想在本地系统上创建一个对象时调用CoCreateInstance。 要在远程系统上创建单个对象,请调用CoCreateInstanceEx函数。 要基于单个CLSID创建多个对象,请调用CoGetClassObject函数。
这是用于一个用于创建COM
对象的
然后下一个函数如下,在调用SysAllocString
之前,有个字符串被压入了栈中
http://www.malwareanalysisbook.com/ad.html
这就是我们会打开的那个广告页面的URL
最后在这里调用了Sleep
函数休眠了0x7530h
毫秒,然后就是一直循环这个代码块,直到你关机为止
接下来我们分析sys
文件
打开之后可以看出是个驱动文件
我们直接进最后那个函数调用
第一个函数调用是RtlInitUnicodeString
初始化一个统计的Unicode字符串。
这个函数的标准定义如下,根据代码中的标识,我们可以得出以下结论
VOID WINAPI RtlInitUnicodeString(
_Inout_ PUNICODE_STRING DestinationString = eax,
_In_opt_ PCWSTR SourceString = \\Device\\ProcHelper
);
其中,DestinationString
是计数的Unicode字符串被初始化的缓冲区。如果未指定SourceString,则长度初始化为零
而SourceString
是可选指针,用于初始化已计数字符串的以空字符结尾的Unicode字符串
然后下面调用的函数是IoCreateDevice
,这个函数的定义如下
NTSTATUS IoCreateDevice(
_In_ PDRIVER_OBJECT DriverObject = esi,
_In_ ULONG DeviceExtensionSize = 0,
_In_opt_ PUNICODE_STRING DeviceName = eax,
_In_ DEVICE_TYPE DeviceType = 22h,
_In_ ULONG DeviceCharacteristics = 100h,
_In_ BOOLEAN Exclusive = 0,
_Out_ PDEVICE_OBJECT *DeviceObject = eax
);
然后下一个调用
RtlInitUnicodeString
这个函数有两个入参,我们往上找两个push
的代码,倒数第一个是eax
,倒数第二个是word_107DE
,现在我们来看看这个倒数第二个是什么东西
DosDevices\ProcHelper
这个ProcHelper
就是这个驱动的名字,注意到这点我们继续
这里初始化了一个字符串,然后下面就是一个调用IoCreateSymbolicLink
IoCreateSymbolicLink例程在设备对象名称和设备的用户可见名称之间建立符号链接
定义如下
NTSTATUS IoCreateSymbolicLink(
_In_ PUNICODE_STRING SymbolicLinkName,
_In_ PUNICODE_STRING DeviceName
);
这个IoCreateSymbolicLink
创建了一个符号链接供用户态的应用程序访问这个设备
最后一个调用是IoDeleteDevice
这个函数
这个函数删除驱动之后就退出了
下一步我们开始连上WinDbg
来进行内核的分析,我们根据书上的步骤来
!devobj ProcHelper
注意,这里的命令是!devobj
而上一篇文章里我们用的是!drvobj
,注意这点区别就可以了
这里我们可以看到ProcHelper
这个DriverObject
存储的位置,DriverObject
包含了所有函数的指针,当用户空间的程序访问设备对象调用这些函数时
DriverObject
存储在一个叫做DRIVER_OBJECT
的结构里面
上面这些话是从书上摘录下来的,我的理解就是这个ProcHelper
要从用户空间访问到它内部的一些函数的时候,要通过DriverObject
来找到他的指针,然后才可以直接调用(因为这个驱动在内核里面嘛)
然后我们使用dt
命令来查看已经注册的驱动对象,这里要加上我们上面得到的那个地址(也就是在DriverObject
后面那个地址)
dt nt!_DRIVER_OBJECT 8965e978
注意这个!
和_
之间没有空格
然后我们可以得到下面这些数据
这里我们主要关注这两个地方,一个是DriverInit
另一个是DriverUnload
,中间按个DriverStartIo
的值是null
没法关注
这里的DriverInit
是驱动初始化的操作地址,下面的DriverUnload
是驱动卸载时候的操作地址,我们刚刚在IDA
分析的时候可以看出这个函数有个删除驱动的操作,当然还有一个删除符号链表的操作
接下来我们跟着书上去分析主函数,主函数的偏移地址是0x038
,也就是上图中的MajorFunction
这个地方的函数,书上说,Windows XP
允许0x1Ch
中可能的主函数代码
为什么是0x1Ch
中可能,原因如下
注意这里的[28]
,换成十六进制就是0x1Ch
,接下来我们查看主函数表
dd 8965e978+0x38 L1c
这个8965e978
是上面我们找到的DriverObject
中驱动的对应存储位置
然后后面的0x38
是函数的偏移地址,我们开始查看主函数的表项
按照书上的说法,表中的每一项都表示了驱动可以处理的不同类型请求,我们现在可以看到表中大多数函数都是804fb87e
的项,我们现在看看这个函数做了什么,然后我们开始查看这个地址上的汇编代码,我们用ln
来显示给定地址处的或者最近的符号
ln 804fb87e
我们可以看到,在804fb87e
处的函数被命名为了IopInvalidDeviceRequest
这个名字,从字面意思理解就是这个函数是处理驱动无法处理的非法请求的,如果你学过Python
,你可以把这个函数理解为if/else
判断里面最后那个else
,或者C
语言里面switch
语句的default
我们同时也可以注意到一些除了804fb87e
之外的地址
注意,红线左边的是地址,右边的才是数据,我们可以发现三个不同于804fb87e
的地址,它在896587e0+0x38
处的偏移量分分别是0
, 2
, 0xe
书上说:
查看wdm.h,我发现偏移量0、2、0xe存储Create、Close以及DeviceIoControl函数
但是在MSDN
里面的帮助里面
然后在这么页面上并不能找到这几个函数,我们现在试着通过安装wdk
来安装对应的头文件看看
根据MSDN
的介绍
Windows 驱动程序工具包 (WDK) 与 Microsoft Visual Studio 和用于 Windows 的调试工具相集成。该集成环境给你提供了开发、构建、打包、部署、测试和调试驱动程序时所需的工具。 在集成的环境中,你可以运行各种基本的认证测试。WDK 包括多项技术和驱动程序模块的模板,其中包括 Windows 驱动程序框架 (WDF)、通用串行总线 (USB)、打印、网络和文件系统筛选器。
我们安装完之后重启之后,用VS
新建一个内核驱动
然后选择Windows Driver
里面的Kernel Mode Driver
然后把工程保存到你想保存的任何地方之后,我们打开其中任意一个源文件
比如我这里打开的是Driver.c
我们通过自己增加一个头文件,然后查看头文件的方式来找wdm.h
之后我们就可以右键就可以找到wdm.h
了
书上说,这里找到的在偏移量0
、2
还有0xe
上找到的函数是Create
、Close
和DeviceIoControl
这三个,反正我是在这里找不到前两个函数的
这里能唯一找到的就是这个DeviceIoControl
这个字符串,但是这个还不是函数,是个结构体,其他的Create
和Close
是找不到有用这个做名字的函数名的
这里就跳过,无法找到这个东西,我们进入对应的偏移地址来看看具体是什么
这时候在我这里的地址是这样的
这是偏移为0
和2
的地址上的代码
这是在偏移地址为0xe
上的代码
我们可以看到偏移前两个的地址可以写出偏移地址Lab10_03+0x606
,而偏移地址为0xe
的代码地址可以写成Lab10_03+0x666
这表明这个地址的代码是位于Lab10_03
上的代码片段
然后我们仔细看一下在主函数表上偏移地址为0
、2
上的函数,他们的地址都是一样的
mov edi, edi
push edp
mov ebp, esp
mov ecx, dword ptr [ebp+0Ch]
and dword ptr [ecx+18h], 0
and dword ptr [ecx+1CH], 0
xor dl, dl
call dword ptr [Lab10_03+0x480]
xor eax, eax
pop ebp
ret 8
这里的唯一一个调用是dword ptr [Lab10_03+0x480]
,我们看看这个地址上存储了那个地址
我们可以发现在dword ptr [Lab10_03+0x480]
这个表示一个word
类型的指针上,指向的是804e4bf6
这个地址,这个地址代表了什么函数,我们继续分析
可以看出这个函数是IofCompleteRequest
这个东西,这个函数在MSDN
中的解释是
IoCompleteRequest例程指示调用者已完成给定I/O请求的所有处理,并将给定的IRP返回给I/O管理器
按照书上的说法,这个函数意味着告诉操作系统请求这个驱动成功了,但是没做什么其他的操作,主函数的下一个要分析的对象是在偏移0xe
上的那个函数,我们看看
mov edi, edi
push ebp
mov ebp, esp
call dword ptr [Lab10_03+0x490] // IoGetCurrentProcess
mov ecx, dword ptr [eax+8Ch]
add eax, 88h // ActiveProcessLinks : _LIST_ENTRY
mov edx, dword ptr [eax]
mov dword ptr [ecx], edx
mov ecx, dword ptr [eax]
mov eax, dword ptr [eax+4]
mov dword ptr [ecx+4], eax
mov ecx, dword ptr [ebp+0Ch]
and dword ptr [ecx+18h], 0
and dword ptr [ecx+1Ch], 0
xor dl, dl
call dword ptr [Lab10_03+0x480]
xor eax, eax
pop ebp
ret 8
这里我们看到,函数在初始化栈操作之后,函数的第一个操作就是调用了一个在dword ptr [Lab10_03+0x490]
的指针函数
我们可以看到这个函数的指针地址是圈黄的这个
然后这个地址上的函数是指向了IoGetCurrentProcess
,安装MSDN
的说法
IoGetCurrentProcess例程返回一个指向当前进程的指针
也就是这个代码会获得一个当前进程的指针,但是按照书上的说法,这个代码会操纵当前进程的PEB
,那什么是PEB
PEB: Process Environment Block
而在PEB
中包含进程信息
我们查看这个IoGetCurrentProcess
函数的返回类型,书上的说是返回的是调用DeviceIoControl
进程的ERPOCESS
结构,但是MSDN
中说的是返回这个
在MSDN
中说的是返回了一个指针,不是一个结构体(这里好混乱,我们还是按照书上的操作来)
然后函数把当前进程指针的eax
偏移了8Ch
之后在取这个地址上的值赋值给了ecx
mov ecx, dword ptr [eax+8Ch]
然后下一个操作把eax
指针直接往后偏移了0x88h
,之后把这个地址上的值赋值给了edx
如果我们按照书中的说法,认为这个IoGetCurrentProcess
返回的是EPROCESS
然后我们查询一个这个结构体的定义,这里说的是下面包含了在Windows
中不透明的结构,说明这个EPROCESS
结构不是我们可以查询到的结构体
那我们就从内存中找结构
dt nt!_EPROCESS
这个结构体很长
然后我们找到偏移0x88
的地方,我们可以看到一个叫ActiveProcessLinks
的_LIST_ENTRY
然后我们查看一个_LIST_ENTRY
结构是什么样的,在MSDN
中,这个结构定义如下
LIST_ENTRY结构描述双向链接列表中的条目或充当此列表的头部
A LIST_ENTRY structure describes an entry in a doubly linked list or serves as the header for such a list.
这里说了这个LIST_ENTRY
是个双向链表,这里稍微解释了一下什么双向链表
双向链表就是一个结构体有两个指针,一个指向前一个结构体,一个指向后一个结构体
这里画的比较随意,其实第二个结构体的头指针是指向第一个结构体开始的地方,也就是第一个结构体的header
这里,但是这样太难画了,所以读者注意这里的区别,当后面没有了结构体,最后那个结构体的tail
将指向第一个结构体
这里的header
和tail
可以用pre
和next
来表示
---last struct's start address
|
|
|
| ------------
<--| pre |<-------
------------ |
| data | |
------------ |
<--| next | |
| ------------ |
|<------------------- |
| | |
| ------------ | |
-->| pre |---|--->
------------ |
| data | |
------------ |
<--| next | |
| ------------ |
| |
| |
| ------------ |
-->| pre |-->
------------
| data |
------------
| next |-->
------------ |
|
|
如果用C
语句来解释的话就是如下的:
struct DoublyList {
struct DoublyList *header;
// ignoring data buf
...
struct DoublyList *tail;
};
明白上面说的这些概念之后,我们来看MSDN
中定义的LIST_ENTRY
:
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY;
对比上面我们给出的一般的双链表,可以知道这个结构体的结构如下(假设整个双链表只有三个结构体且上面的第一个结构体是链表的首header)
-----------------
| |
\|/ |
----------- |
<--| Flink |<-- |
| ----------- | |
| | Blink |---|----|--->
| ----------- | | |
|<------------------|----|----|----
| | | | |
| ----------- | | | |
-->| Flink |---|----|--->| |
----------- | | | |
| Blink |--> | | |
----------- | | |
------------------------|---- |
| | |
| ----------- | |
-->| Flink |-------> |
----------- |
| Blink |----------------->
-----------
Flink
负责指向后一个结构体,而Blink
负责指向前一个结构体,如果是最后一个结构体,则这个结构体的Flink
和Blink
都指向header,最后这个链表会形成一个闭环
然后我们好好看一下这个汇编代码(排除了栈初始化的代码和调用IoGetCurrentProcess
的代码)
mov ecx, dword ptr [eax+8Ch]
add eax, 88h // ActiveProcessLinks : _LIST_ENTRY
mov edx, dword ptr [eax]
mov dword ptr [ecx], edx
mov ecx, dword ptr [eax]
mov eax, dword ptr [eax+4]
mov dword ptr [ecx+4], eax
mov ecx, dword ptr [ebp+0Ch]
and dword ptr [ecx+18h], 0
and dword ptr [ecx+1Ch], 0
xor dl, dl
call dword ptr [Lab10_03+0x480]
xor eax, eax
pop ebp
ret 8
在调用完IoGetCurrentProcess
之后,eax
就是一个指向_EPROCESS
结构体的指针,当程序执行下面这个指令的时候
mov ecx, dword ptr [eax+8Ch]
从内存中的_ERPOCESS
的结构我们可以看出,这个结构的构造是怎样的
从地址0x088
到地址0x090
这个区间上,有8
字节的空间,下图中结构体前面的地址代表元素的起始地址
-----------
0x088 | Flink |
-----------
0x08c | Blink |
-----------
0x090 | Uint4B |
-----------
这里为什么把Flink
放在了第一个,因为在MSDN
的定义中就是这样的
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY;
如果你到这里还跟着我们的思路,你就明白
mov ecx, dword ptr [eax+8Ch]
这个操作的是将LIST_ENTRY
的Blink
指针赋值给了ecx
,而Blink
我们知道,是指向前一个结构体的,书中这里有个错误,如果你对照书就知道了,书上说这个指令做的操作是
获取列表中指向下一项的指针
所以这里我们发现了一处书上的错误
然后我们接下来看下一个操作
add eax, 88h
mov edx, dword ptr [eax]
这里是将eax
这个指针偏移了0x88h
,所以现在eax
不再是指向_EPROCESS
这个结构体的开头,而是指向_LIST_ENTRY
的开始地址
所以后面的mov
操作是将Flink
指针的值赋给了edx
ok,因为这里的操作对于理解操作PEB
很重要,所以我们现在这里总结一下此时寄存器中的各值代表的指针值
ecx = Blink 指向前一个结构体
edx = Flink 指向后一个结构体
-----------
0x088 | Flink(edx)|
-----------
0x08c | Blink(ecx)|
-----------
0x090 | Uint4B |
-----------
然后我们继续分析下面的操作
mov dword ptr [ecx], edx
这个操作的具体细节我们研究一下
[ecx]
是取ecx
那个地址上的值,ecx
指向的是前一个结构体的起始地址,那[ecx]
的值就是前一个结构体起始地址也就是前一个地址的Flink
值,也是就代表了前一个结构的Flink
指向的是下一个结构体的地址
这里将edx
的值赋值给了[ecx]
,edx
的值是当前结构体的Flink
也就是代表了下一个结构体的地址
再总结一下
此时,假设我们当前进程的_LIST_ENTRY
是第二个结构体,我们用2#
结构体表示
ecx = 1# _LIST_ENTRY start address
dword ptr [ecx] = 1# _LIST_ENTRY Flink value
edx = 3# _LIST_ENTRY start address
这里你理解我们想说的,后面的就好理解了
最后将本将指向2#
结构体的指针指向了3#
结构体,达到了跳过了当前进程的结构体的目的
这里我们画成示意图就是如下(假设我们现在进程的的PEB
是中间那个)
原始的双链表长这样,这是上面那个图copy
下来的,这里假设的是普通情况,也就是第一个结构体不是header
,最后结构体也不是tail
,所以会像下图这样
注:这里的1#
并不代表结构体的header
0#
|
----------- 1# |
<--| Flink |<-- |
| ----------- | |
| | Blink |---|-->
| ----------- |
|<------------------|---------
| | |
| ----------- 2# | |
-->| Flink |---|---> |
Here--> ----------- | | |
| Blink |--> | |
----------- | |
------------------------ |
| |
| ----------- 3# |
-->| Flink |------> |
----------- | |
| Blink |-------|---->
----------- |
|
4#
然后我们执行了mov dword ptr [ecx], edx
,第一个结构体的Flink
的值变成了3#
结构体的地址,就是将第一个结构体的Flink
指向第三个结构体的起始地址,注意第一个结构体的Flink
出来的线,他将变成这样的
0#
|
----------- 1# |
<------| Flink |<-- |
| ----------- | |
| | Blink |---|-->
| ----------- |
| <------------------|---------
| | | |
| | ----------- 2# | |
| -->| Flink |---|---> |
| ----------- | | |
| | Blink |--> | |
| ----------- | |
| ------------------------ |
| | |
| | ----------- 3# |
------>| Flink |------> |
----------- | |
| Blink |-------|---->
----------- |
|
4#
之后的操作是
mov ecx, dword ptr [eax]
eax
是指向我们的当前进程的LIST_ENTRY
,而ecx
指向的是前一个结构体,注意这里,上面只是改变了[ecx]
的值,并没有改变ecx
的值
所以我们可以得出下面的等式
dword ptr [eax] = 2# _LIST_ENTRY Flink
我们执行了mov
之后,就是将ecx
的值改变了,成为了2#
结构体的Flink
的值,也就是现在ecx
等于3#
结构体的起始地址
这是个赋值操作
接下来
mov eax, dword ptr [eax+4]
到现在代码开始操作PEB
开始,我们的eax
值没有变过,所以这个eax+4
将指向了这个结构体的Blink
的起始地址,而[eax+4]
则将把指针指向2#
结构体的前一个结构体,也就是1#
结构体
所以现在eax
指向的是1#
结构体
再进行下一个操作的分析之前,我们总结一下,以免大家又搞混乱了
到现在未知,各寄存器的状态
eax -> 1# _LIST_ENTRY
ecx -> 3# _LIST_ENTRY
然后我们清楚这点之后,看下面的代码
mov dword ptr [ecx+4], eax
然后这个ecx+4
指向的是3#
结构体的Blink
指针,这个值是指向2#
结构体的起始地址的
也就是将3#
结构体的Blink
指针指向了1#
结构体的起始地址,改变之后的结构体如下
0#
|
----------- 1# |
<------| Flink |<-- |
| ----------- | |
| | Blink |---|-->
| ----------- |
| |<--------
| | |
| ----------- 2# | |
| | Flink |---|---> |
| ----------- | | |
| | Blink |--> | |
| ----------- | |
| ------------------------ |
| | |
| | ----------- 3# |
------>| Flink |------> |
----------- | |
| Blink |-------|---->
----------- |
|
4#
同样需要注意的是,这步操作,ecx
和eax
的值并没有改变,不明白的同学可以回去看看这步操作,改变的只是[ecx+4]
的
所以到这步未知,我们的结构体从双链表中的开头开始遍历,是无法访问到的,不管你是正向遍历还是反向遍历
然后我们下面的操作我们就没必要管了,因为ebp
是栈的基地址,我们跳入的这个地址段内并没有操作了任何的ebp
所以这个程序做了什么,就是把自己的进程隐藏了起来,通过修改PEB
的方式,没有任何的指针将指向他的LIST_ENTRY
结构
2.一旦程序运行,你怎样停止它?
解答:书上的说法是重启,也只有这种把法了
3.它的内核组件做了什么操作?
解答: 修改了进程链接表的结构,隐藏了自己的LIST_ENTRY
,通过那个偏移为0xe
的函数,这个函数我们现在还不知道怎么知道把偏移量和函数名对应起来,因为我们也看了wdm.h
,根本找不到这个函数,可执行文件调用了DeviceIoControl
之后,驱动把进程隐藏
本文完
至此内核分析就结束了