首页
社区
课程
招聘
[原创]默默无闻·恶意代码分析Lab10
2021-4-20 19:21 11830

[原创]默默无闻·恶意代码分析Lab10

2021-4-20 19:21
11830

系列往期:

[原创]默默无闻·恶意代码分析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、两个文件都查壳一下;

 

image-20210419125845539

 

image-20210419125903537

 

干净的;

 

2、查看Lab10-1.exe的导入函数和Lab10.01.sys的导入导出函数;

  • 首先是Lab10-1.sys的;

image-20210419135924848

 

首先,导出表没有导出函数;

 

image-20210419140016540

 

导入的这仨都是内核函数,是对注册表进行操作;

  • 再看看Lab10-1.exe;

image-20210419140838924

 

导入表有加载库和服务以及查看代码页的行为;

 

3、看一看字符串;

  • 首先是Lab10-1.sys的;

image-20210419140603407

 

有注册表、内核信息等行为,还有对防火墙的操作;

 

而且\\Registry\\Machine开头,这是内核访问注册表的标准形态,等同于用户态的HKEY_LOCAL_MACHINE

  • 再看看一块Lab10-1.exe的;

image-20210419141200386

 

目前有用的就是一个文件路径和弹窗相关操作;

 

3、使用ODIDA跟踪Lab10-1.exe

 

因为在dll文件没有导出表的情况下,直接从exe查看对dll文件的使用,会更快一些;

  • 使用IDA打开Lab10-1.exe;

    image-20210419142231676

    首先打开服务控制管理器,如果打开成功,进入下一步,否则,退出程序;

  • 面对下面的好几个系统函数,我们首先要理清这个程序的框架是什么?

    1
    2
    3
    4
    5
    6
    7
    8
    if(OpenSCManagerA(...)){
        if(CreateServiceA(...) || h1 = OpenServiceA(...) != 0){
            StartService(...);
            if(h1){
                ControlService(...);
            }
        }
    }

    只是大概理了一下思路,语法规范并不完全符合

  • 首先,假设我们是正确打开了服务控制管理器,下一步我们就要看看创建了什么服务,以及打开了什么服务;

    image-20210419143856184

    我们看到一个路径,很明确,这个路径并没有这个文件,所以并不会正确创建;

    • 还有就是其中一个字段dwStartType,这个字段我们可以使用VC++和MSDN查看定义;

    image-20210419144152463

    image-20210419144227242

    翻译一下就是在进程调用StartService函数时由服务控制管理器启动的服务;

    • 以及dwServiceType字段,同样的方式;

    image-20210419144926950

    image-20210419144950364

    翻译一下就是指定为驱动服务;

    这里要说一下的是,书中说的类型3指的是dwStartType,但是真正说明是驱动服务的是dwServiceType字段,所以我觉得是书中错误,如有正确看法,还请大佬指出;

因为是无法正确创建的一个服务,但是也能够暗示出,这个服务是在这个Lab10-01.sys中;

  • 如果创建失败,那就打开服务OpenService

    这里或许会很烦恼,创建都失败了,怎么可能打的开呢?
    其实道理很简单,这个文件路径的问题,在之前的例子中,都会找到当前文件的路径,再找文件,所以找到对应文件并不难,在这里只是为了让我们体验R0层的病毒分析;

    所以创建失败的情况应该理解为系统已经有一个相应服务了,所以才会使用打开,找到名为Lab10-01的服务;

    image-20210419150029754

  • 再下面就是开始服务了,这个没什么好看的;

  • 最后就是ControlService

    因为调用它的前提是打开服务,而打开服务的前提是创建服务失败(因为已经存在);

    所以,我们要搞清楚,服务已经存在且已经运行后,再次打开服务又做了什么;

    image-20210419150445339

    我们先看看这个字段的意思;

    image-20210419151201092

    image-20210419151225719

    给大哥们翻译翻译这个惊喜:请求服务停止;

这个逻辑就很清晰了,现在问题是这个服务到底干了嘛;

  • IDA打开Lab10-1.sys;

    分析dll程序的时候,常说DllMain并不是真正的入口,只是在系统加载动态库的时候会加载这个函数进行必要的初始化后,再跳转到真正的程序入口;

    sys文件也是同样的道理,在DriverEntry的处是一些必要的初始化和检查,然后才跳转到真正的驱动入口;

    image-20210419151933037

    在这里,我们可以确认的是sub_10906才是真正的驱动程序入口,如果不放心,可以去上面的call看一下都干了啥;

  • 我们看一下sub_10906;

    image-20210419152401257

    这就结束了,就是把一个函数的偏移给了一个内存位置,既无调用也无跳转,看的有点迷糊,不慌,换个视角就相对容易理解了;

    1
    2
    3
    4
    sub_10906(...){
        DriverObject->DriverUnload = (*)sub_10486;
        return 0;
    }
  • 所以就是相当于把一个函数指针放在了一个服务里,让服务去执行;

    那就进sub_10486去瞅一瞅;

    image-20210419152856707

    然后就是一堆函数跟字符串,换个视角;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    sub(...){
        RtlCreteRegistryKey(...);
        RtlCreteRegistryKey(...);
        RtlCreteRegistryKey(...);
        RtlCreteRegistryKey(...);
        RtlWriteRegistryValue(...);
     
        return RtlWriteRegistryValue(...);
    }

    在此,因为Rtl开头的是微软未公开的函数,我使用的MSDN并没有查到,而且使用某个大佬分享的未公开函数查询网站也没有查到;

    最后还是查询了微软官方文档,了解了一些功能细节;

    1
    2
    RtlCreteRegistryKey(...);    // 沿着给定的相对路径在注册表中添加一个键值对象。
    RtlWriteRegistryValue(...);    // 调用方提供的数据沿着指定的相对路径在给定的值名处写入注册表。
    • 到这里,我们就需要关注每一次的注册表操作细节;

      分别是给一下的路径进行添加键对象:

      1
      2
      3
      4
      "\\Registry\\Machine\\SOFTWARE\\Policies\\Microsoft";
      "\\Registry\\Machine\\SOFTWARE\\Policies\\Microsoft\\WindowsFirewall";
      "\\Registry\\Machine\\SOFTWARE\\Policies\\Microsoft\\WindowsFirewall\\DomainProfile";
      "\\Registry\\Machine\\SOFTWARE\\Policies\\Microsoft\\WindowsFirewall\\StandardProfile";
    • 然后就是两次写入键值;

      在此直接给出代码观:

      1
      2
      3
      4
      5
      6
      RtlWriteRegistryValue(
       0,            L"\\Registry\\Machine\\SOFTWARE\\Policies\\Microsoft\\WindowsFirewall\\DomainProfile",
         &ValueName,
         4u,            // ; ValueType
         &ValueData,
         4u);        // ; ValueLength

      我就很好奇,这个ValueType字段是啥意思;

      找到官网,找到字段描述:

      image-20210419155503841

      点进去,找到类型;

      image-20210419155619622

      VC++中,输入字段名,右击找到定义;

      image-20210419155756283

      4的有两个,所以只能猜测是描述32位系统了;

      这是我使用的方式,如果有不对或者更好的,还请大佬赐教

到这里就很清晰了,这个函数做了很多关于注册表的操作,因为跟防火墙相关,所以我们可以合理猜测是禁用防火墙;

问题

1、这个程序是否直接修改了注册表(使用procmon来检查)?

 

没有用Procmon工具,但是经过分析,是直接修改了注册表;

2、用户态的程序调用了ControlService函数,你是否能够使用WinDbg设置一个断点,以此来观察由于ControlService的调用导致内核执行了怎样的操作?

 

没有用WinDbg工具,但是经过分析,我们知道,是卸载操作;

3、这个程序做了些什么?

 

创建一个服务加载驱动,然后创建和写入了一些注册表项,用于禁用防火墙;


Lab10-2

该实验的文件为Lab10-02.exe;

Lab10-2分析

1、查壳;

 

image-20210419160556465

 

无壳;

 

2、查看导入表;

 

因为过长,就不全面截图了;

 

image-20210419161031691

 

首先依旧是服务相关的函数,其次是发现了资源相关的函数,所以我们有理由怀疑是否资源节中隐藏了什么秘密;

 

image-20210419161203945

 

貌似加载了什么库,还申请了内存空间,并且获取程序地址;

 

3、查看字符串;

 

image-20210419161345744

 

一个路径,和一个驱动服务名;

 

4、过程分析;

  • 进入IDA看源代码;

image-20210419161438751

 

程序开始就加载了资源,所以我们还是使用Resource Hacker查看一下资源节;

 

image-20210419161614374

 

基本可以确定里面隐藏了一个程序;

 

保存到桌面,用IDA打开;

 

image-20210419161812767

 

IDA已经自动识别这是一个驱动文件了;

 

暂时不要着急分析这个驱动文件,我们先去看看原程序是如何加载这个驱动文件的;

  • 这一段主程序,不算复杂,依旧是使用代码表示结构;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    int main(){
        if(FindResourceA(...)){
            hres = LoadResource(...);
            if(CreateFileA(...) != -1){
                SizeofResource(...);
                WriteFile(...,hres,...);
                CloseHandle(...);
                if(!OpenSCManagerA(...)){
                    printf(...);
                    return 0;
                }
                if(!CreateService(...)){
                    printf(..);
                    return 0;
                }
                if(!StartServiceA(...)){
                    printf(...);
                }
                CloseServiceHandle(...);
            }
        }
        return 0;
    }

    由此,我们可以了解到,先是从资源中去除,然后将其写入到路径为C:\\Windows\\System32\\Mlwx486.sys文件,然后就是依次的创建服务打开服务;

  • 既然如此,我们把重心放在从资源节导出的驱动文件上;

    • 先看一眼导入表;

    image-20210419163631154

    都是内核操作;

    • 再看一眼字符串;

    image-20210419163729969

    暂时没看到什么亮点;

    • 过程分析;

      • 跳转sub_10706

      image-20210419163853529

      • 这一段貌似跟我们之前做的有些区别,在最后将一个函数指针给某个内存空间前,貌似做了很多的工作,我们先理一下结构;

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        int sub_10706(...){
            RtlInitUnicodeString(...);
            RtlInitUnicodeString(...);
              v2 = MmGetSystemRoutineAddress(...);
              v3 = *(PVOID **)MmGetSystemRoutineAddress(...);
              v4 = 0;
              do
              {
                ++v3;
                if ( *v3 == v2 )
                  break;
                ++v4;
              }
              while ( v4 < 284 );
              dword_1068C = (int)v2;
              dword_10690 = (int)v3;
              *v3 = sub_10486;
              return 0;
        }

        在此我们要先搞清楚这里面用到的内核函数是什么,使用了微软官方文档

        1
        2
        RtlInitUnicodeString(...);        // 将字符串转换成unicde类型;
        MmGetSystemRoutineAddress(...); // 返回一个指向SystemRoutineName指定的函数的指针;

        再看反汇编,先是把NKeServiceDescriptorTable转换成unicode,然后再解析成函数指针;

        转换成unicode好理解,因为在Windows系统内部的底层,都是先将A结尾的函数解析成W结尾的函数,也就是说,内核层应该都是使用宽字符的

        至于返回函数指针我也是有点懵,看了书后答案才知道,是返回NKeServiceDescriptorTable的地址偏移量偏移

        到此,对于do{}while语句的理解貌似卡克了,但是好在到目前为止,还不是我们要找的重点;

        在此直接借鉴书后答案的原话:我们看到DriverEntry例程以参数KeServiceDescriptorTable和·NtQueryDirectoryFile调用了RtlInitUnicodeString函数,然后调用MmGetSystemRoutineAddressPatchFunction的地址覆盖这个项。

      • 进而来分析sub_10486字段;

        还是先整理一下函数逻辑:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        sub_10486(...){
          v11 = (char *)FileInformation;
          v12 = NtQueryDirectoryFile(...);
          v13 = 0;
          RestartScana = v12;
          if ( v12.() == 3 && v12 >= 0 && !v12.() ){
            while ( 1 ){
              v14 = 0;
              if ( RtlCompareMemory(v11 + 94, &word_1051A, 8u) == 8 ){
                v14 = 1;
                if ( v13 ){
                  if ( *(_DWORD *)v11 )
                    *(_DWORD *)v13 += *(_DWORD *)v11;
                  else
                    *(_DWORD *)v13 = 0;
                }
              }
              if ( !*(_DWORD *)v11 )
                break;
              if ( !v14 )
                v13 = v11;
              v11 += *(_DWORD *)v11;
            }
          }
          return RestartScana;
        }

        再去整理一下和内核函数的功能:

        1
        2
        NtQueryDirectoryFile(...);    // 返回关于给定文件句柄指定目录中的文件的各种信息;
        RtlCompareMemory(...);    // 比较两个内存块并返回匹配的字节数

        关于NtQueryDirectoryFile书中是重点描述了关于FileInformationClass字段,如果它是3以外的值,便会返回NtQueryDirectoryFile的原始返回值

        关于这个,以我目前浅短的经验,该字段我是没遇到3以外的值,在查找相关函数功能时,以外发现了一篇写的很不错的Lab10-2习题笔记里面也提供了官方工作人员的回复,毕竟是买了正版MSDN的才有的服务,我就不厚颜无耻的复制粘贴了,有兴趣的直接点进超链接去看吧;

      • 我们再逐步的看一下;

      • 首先,我们看一下这里的NtQueryDirectoryFile函数;

        image-20210420130851235

        首先是这个call,如果不是认字不认颜色的话,我都以为这是一个参数了,这很显然是IDA想告诉我们什么;

        点进去看一看;

        image-20210420131022327

        是一个跳转语句,再双击进去就是正牌的NtQueryDirectoryFile了;

        很显然,上面程序中构造的函数,是想在伪装什么;

      • 继续往下看,我们假设它正确跳转且返回了;

        image-20210420131401638

        继续往下后,它先是比较了FileInformationClass是否等于3,我们之前说过的,它正常来讲应该是等于3的;

        第二部是检查NtQueryDirectoryFile()是否正确执行,在上面参数都正确构造,也正确跳转的情况下,我们可以合理假设它正确的执行且返回了;

        再然后就是关于ReturnSingleEntry字段了,关于该字段的描述如下:

        1
        // 如果只返回一个条目,设置为TRUE,否则设置为FALSE。如果该参数为真,NtQueryDirectoryFile只返回找到的第一个条目。

        到此,或许有点一头雾水;

        这里我们要先了解一下NtQueryDirectoryFile返回的东西是啥:

        1
        // NtQueryDirectoryFile例程返回STATUS_SUCCESS或适当的错误状态。注意,可以返回的错误状态值集是特定于文件系统的。NtQueryDirectoryFile还返回IoStatusBlock的Information成员中实际写入给定FileInformation缓冲区的字节数。

        其实返回的是FileInforMation结构,而该结构在FileInformationClass字段中有定义,经过继续查询,我们知道是由一系列的FILE_BOTH_DIR_INFORMATION结构组成;

        所以,上述的三个条件都是在检查我们伪装的这个NtQueryDirectoryFile()是否正确运行

      • 继续往下看:

        image-20210420132817463

        很显然这是一个循环,而且在没有找到循环步长的情况下,这是一个死循环;

        重点是我们要关注一下RtlCompareMemory()函数比较了什么;

        image-20210420133417491

        经过向上夙愿,我们找到FileInformation字段,我们知道这是一个叫做FILE_BOTH_DIR_INFORMATION的结构体,找到定义(上面官网插查询链接);

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        typedef struct _FILE_BOTH_DIR_INFORMATION {
          ULONG         NextEntryOffset;        // 4h
          ULONG         FileIndex;                // 4h
          LARGE_INTEGER CreationTime;            // Ch
          LARGE_INTEGER LastAccessTime;            // Ch
          LARGE_INTEGER LastWriteTime;            // Ch
          LARGE_INTEGER ChangeTime;                // Ch
          LARGE_INTEGER EndOfFile;                // Ch
          LARGE_INTEGER AllocationSize;            // Ch
          ULONG         FileAttributes;            // 4h
          ULONG         FileNameLength;            // 4h
          ULONG         EaSize;                    // 4h
          CCHAR         ShortNameLength;        // 1h
          WCHAR         ShortName[12];            // 2h
          WCHAR         FileName[1];            // 2h
        } FILE_BOTH_DIR_INFORMATION, *PFILE_BOTH_DIR_INFORMATION;

        经过计算,我们得到的是FileName字段;

        至于word_1051A字段,是内存没定义值,且交叉引用也没有找到赋值语句,所以我们可以合理猜测是一个运行时的值,具体是什么,我们暂时可以画个问号,先看如果比较成功应该会怎么样;

      • 继续往下走,假设返回值是8,也就是正确返回了,进行下一步;

        image-20210420135231123

        这一段跳转,我觉得可以使用上面的代码结构看的清楚;

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        v11 = (char *)FileInformation;
        v12 = NtQueryDirectoryFile(...);
        v13 = 0;
        while ( 1 ){
              v14 = 0;
              if ( RtlCompareMemory(v11 + 94, &word_1051A, 8u) == 8 ){
                v14 = 1;
                if ( v13 ){
                  if ( *(_DWORD *)v11 )
                    *(_DWORD *)v13 += *(_DWORD *)v11;
                  else
                    *(_DWORD *)v13 = 0;
                }
              }
              if ( !*(_DWORD *)v11 )
                break;
              if ( !v14 )
                v13 = v11;
              v11 += *(_DWORD *)v11;
            }

        这里,貌似不太好理解,其实带着上面的FILE_BOTH_DIR_INFORMATION的结构体看,再看我这个灵魂画家画个图,就会简单点;

        image-20210420140545072

        所以嘛,这就是一个隐藏功能;

到此分析就算是结束;

 

经过分析我们知道,这个程序构造了一个伪装的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:

 

image-20210420143742088

 

Lab10-03.exe:

 

image-20210420143826082

 

皆无壳;

 

2、导入表;

 

Lab10-03.sys:

 

image-20210420144023018

 

IoGetCurrentProcess说明这个驱动涉嫌修改正在运行的进程;

 

Lab10-03.exe:

 

image-20210420144551404

 

image-20210420144620240

 

有服务相关程序和加载动态库和写文件嫌疑;

 

3、字符串;

 

Lab10-03.sys:

 

image-20210420144254452

 

可以看到疑似句柄或者注册表键的字符串;

 

Lab10-03.exe:

 

image-20210420144734973

 

一个网址,两个和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的内核驱动;

    image-20210420153116058

    如果创建成功了,就启动服务,加载Lab10-03到内核,并且打开一个由ProcHer驱动创建的内核设备驱动句柄\\\\.\\ProcHelper

    image-20210420153448237

    再后就是DeviceIoControl函数的调用,我们可以看见它的关键字段lpOutBufferlpInBuffer都是0,这是很不正常的,而且在dwIoControlCode字段的值为0x0ABCDEF01也是不正常的;

    image-20210420153745808

    DeviceIoControl将控制代码直接发送到指定的设备驱动程序,使相应的设备执行相应的操作。

    既然知道是加载了内核驱动,我们重点是它做了什么坏事,看看DeviceIoControl请求了啥,所以在此可以直接去看看Lab10-03.sys,一探究竟;

  • 进入DriverEntry()找到正确的跳转sub_10706;

    image-20210420154050143

    先看一下整体结构:

    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的设备;

    image-20210420155247669

    往下是如果创建成功,则进入;

  • 成功跳转后是给DriverObject偏移添加函数;

    image-20210420161353201

    所以,我们先要去了解一下这个IoCreateDevice()函数的DeviceObject字段的意思;

    image-20210420161538349

    翻译翻译:指向调用者的驱动程序对象的指针。每个驱动接收到一个指向它的驱动对象的指针,这个指针在驱动入口例程的一个参数中。WDM函数和筛选器驱动程序也在它们的添加设备例程中接收驱动程序对象指针。

    所以,下一步搞清楚都是这些驱动程序都有哪些行为

  • sub_10606;

    image-20210420161834915

    IoCompleteRequest()表示调用者已经完成了给定I/O请求的所有处理,并将给定的IRP返回给I/O管理器;

  • sub_10666;

    image-20210420162036473

    IoGetCurrentProcess()返回一个指向当前进程的指针;

    IofCompleteRequest()指示调用者已经完成了给定I/O请求的所有处理,并将给定的IRP返回给I/O管理器。

    sub_10666在驱动正确安装,服务正常启动后,处理DeviceIoControl的请求;

    我们注意到,IoGetCurrentProcess()返回的是当前进程是指针,它返回的其实是EPROCESS(进程对象),每一个进程都有一个EPROCESS;

    想了解EPROCESS之前,先简短的总结一下TEBPEBTIB

    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结构体的地址;

    因为这两个结构太长且内容庞大这里就不赘述了,至于关于TEBPEB结构的更多细节,推荐火哥整理的内核结构网站

    image-20210420174805092

首先这里要说到的是IoGetCurrentProcess()函数,它返回调用DeviceIoControl进程的EPROCESS结构,然后访问[+88]处偏移量,然后再访问[+8C]处的;

 

关于此题在火哥整理的内核结构网站中找到的Win7中的结构跟书后答案有所区别,最后是找到了86版本的XP系统的还原了书中对于_EPROCESS结果的样子;

 

至于具体如何,还是建议使用WinDbg在当前系统中查找

 

以下关于EPROCESS截图默认为XP系统

 

EPROCESS结构的第一个叫做进程控制块的_KPROCESS;偏移0x88就在其中;

 

image-20210420181458551

 

点进去后我们发现这是一个双向链表,[+8c]其实指的是下一项的指针;

 

image-20210420181909132

 

回到sub_10666,我们可以看到:

 

image-20210420182019192

 

看着有点眼晕,直接上图吧;

 

image-20210420183849818

 

就是这么个意思,双向链表删除节点操作,但是这里只是解除链接并不删除;

 

当操作系统正常运行时,中间那个进程就被Rootkit隐藏了,当OS遍历进程链表时,隐藏进程总是被跳过;

 

在这里,或许有萌新会疑惑,隐藏后为什么不影响运行;
这里要解释的是,进程只是进程中运行线程的容器,线程被调度到CPU上运行;只要线程合理的占用操作系统,它就会被调度,所以进程也会继续正常运行;

  • sub_1062A;

    image-20210420162427841

    删除了一个名为\DosDevices\ProcHelper的符号链接,这个符号链接可以给用户态提供访问;

  • 看完上面三个功能,程序走向了最后;

    创建了一个名为\DosDevices\ProcHelper的符号链接,如果创建成功则删除设备驱动;

    image-20210420162946518

    大体的了解过Lab10-03.sys做了什么后,我们返回Lab10-03.exe看一看后续的代码

  • 接着从OleInitiallize往下看;

    我们知道这是一个COM函数,一直往下,是一个CoCreateInstance函数;

    image-20210420190320557

    在MSDN中,它的描述是:用于创建COM对象;

    image-20210420190541029

    将网址压入栈中;

    image-20210420190759395

    然后打开网址,然后睡眠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 被平头猿小哥编辑 ,原因:
上传的附件:
收藏
点赞2
打赏
分享
最新回复 (3)
雪    币: 13
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_bepgvyee 2021-4-22 20:42
2
0
大侠留下vx号,方便交流。谢谢!
雪    币: 2770
活跃值: (2863)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
平头猿小哥 2021-4-22 21:00
3
0
mb_bepgvyee 大侠留下vx号,方便交流。谢谢!
QQ私发你了,欢迎交流;
游客
登录 | 注册 方可回帖
返回