恶意代码分析实战 Lab 10-3 习题笔记

版权声明:本文为博主辛辛苦苦码字原创文章,转载记得表明出处哦~ https://blog.csdn.net/isinstance/article/details/79626369

问题

1.这个程序做了些什么?

解答:书上说本次实验包括一个驱动程序和一个可执行文件,还要把驱动程序放到C:\Windows\System32目录下面,我们试试

图片

把文件放到那个目录之后,点击执行就会跳出这个IE,然后不断的跳IE出来,到我打完这段话,已经跳了这么多的窗口出来了

图片

然后我们开始分析,先是安装书上的开始静态分析

我们先分析的是exe文件

KERNEL32.DLL里面我们发现这么几个有意思的函数CreateFileWriteFile

图片

然后我们查看这个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,意义就是表明这是个驱动服务

图片

最后的lpServiceNamelpDisplayName说明的是这个服务的名字是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的参数lpInBufferlpOutBuffer被设置为了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处的偏移量分分别是020xe

书上说:

查看wdm.h,我发现偏移量0、2、0xe存储Create、Close以及DeviceIoControl函数

但是在MSDN里面的帮助里面

MSDN wdm.h帮助

然后在这么页面上并不能找到这几个函数,我们现在试着通过安装wdk来安装对应的头文件看看

图片

根据MSDN的介绍

Windows 驱动程序工具包 (WDK) 与 Microsoft Visual Studio 和用于 Windows 的调试工具相集成。该集成环境给你提供了开发、构建、打包、部署、测试和调试驱动程序时所需的工具。 在集成的环境中,你可以运行各种基本的认证测试。WDK 包括多项技术和驱动程序模块的模板,其中包括 Windows 驱动程序框架 (WDF)、通用串行总线 (USB)、打印、网络和文件系统筛选器。

我们安装完之后重启之后,用VS新建一个内核驱动

图片

然后选择Windows Driver里面的Kernel Mode Driver

图片

然后把工程保存到你想保存的任何地方之后,我们打开其中任意一个源文件

比如我这里打开的是Driver.c

图片

我们通过自己增加一个头文件,然后查看头文件的方式来找wdm.h

图片

之后我们就可以右键就可以找到wdm.h

图片

书上说,这里找到的在偏移量02还有0xe上找到的函数是CreateCloseDeviceIoControl这三个,反正我是在这里找不到前两个函数的

这里能唯一找到的就是这个DeviceIoControl这个字符串,但是这个还不是函数,是个结构体,其他的CreateClose是找不到有用这个做名字的函数名的

图片

这里就跳过,无法找到这个东西,我们进入对应的偏移地址来看看具体是什么

图片

这时候在我这里的地址是这样的

图片

这是偏移为02的地址上的代码

图片

这是在偏移地址为0xe上的代码

我们可以看到偏移前两个的地址可以写出偏移地址Lab10_03+0x606,而偏移地址为0xe的代码地址可以写成Lab10_03+0x666

这表明这个地址的代码是位于Lab10_03上的代码片段

然后我们仔细看一下在主函数表上偏移地址为02上的函数,他们的地址都是一样的

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将指向第一个结构体

这里的headertail可以用prenext来表示

     ---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负责指向前一个结构体,如果是最后一个结构体,则这个结构体的FlinkBlink都指向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_ENTRYBlink指针赋值给了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#

同样需要注意的是,这步操作,ecxeax的值并没有改变,不明白的同学可以回去看看这步操作,改变的只是[ecx+4]

所以到这步未知,我们的结构体从双链表中的开头开始遍历,是无法访问到的,不管你是正向遍历还是反向遍历

然后我们下面的操作我们就没必要管了,因为ebp是栈的基地址,我们跳入的这个地址段内并没有操作了任何的ebp

所以这个程序做了什么,就是把自己的进程隐藏了起来,通过修改PEB的方式,没有任何的指针将指向他的LIST_ENTRY结构


2.一旦程序运行,你怎样停止它?

解答:书上的说法是重启,也只有这种把法了


3.它的内核组件做了什么操作?

解答: 修改了进程链接表的结构,隐藏了自己的LIST_ENTRY,通过那个偏移为0xe的函数,这个函数我们现在还不知道怎么知道把偏移量和函数名对应起来,因为我们也看了wdm.h,根本找不到这个函数,可执行文件调用了DeviceIoControl之后,驱动把进程隐藏

本文完

至此内核分析就结束了


猜你喜欢

转载自blog.csdn.net/isinstance/article/details/79626369