问题
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之后,驱动把进程隐藏
本文完
至此内核分析就结束了
本文详细解析了一个恶意驱动的工作原理,包括其如何通过修改进程链接表来隐藏自身,以及如何利用特定函数实现这一目的。
2195

被折叠的 条评论
为什么被折叠?



