• 分享一个之前写的mbr的分析过程


    一、概述(前言)

    修改MBR,挂钩13号中断,继续挂钩BlLoaderBlock结构得到内核基址,完成对IoInitSystem挂钩执行样本的驱动文件,为防止MBR被修复又挂钩了磁盘的IRP读写。

    流程图

    图一

    二、技术细节详细分析

    脱壳

    首先查壳,显示为ASPack(2.12-2.42),方法就是直接esp定律。od会帮你断,之后一个大跳到达oep,脱壳结束,如下图二所示。

    图二

    脱壳之后的样本

    脱壳结束之后看看样本做了些什么事情,f5之后可以发现一个被标红的地址(0x7FFE0500),在32位和64位Windows中,共享数据的只读用户模式地址均为0x7FFE0000,也就是_KUSER_SHARED_DATA结构的地址。

    当然三环是没有权限修改的,在内核也有一份一模一样的数据0xffdf0000(0x32)。之后使用windbg打印了该结构发现根本没有偏移0x500的结构,打印该地址也没有什么特殊意义的值。那么该值可能是它作为零环和三环标志位的一个地址,毕竟是MBR病毒(只是猜测),如图三所示。

    图三

    日志文件

    样本接着会创建c:\windows\temp\00000218.tmp文件,该文件记录着当前程序运行之后的状态,会将当前的信息记录到文件中,如图四所示。

    图四

    获取信息

    通过IOCTL_VOLUME_LOGICAL_TO_PHYSICAL获取文件的扇区偏移量,样本查询的是\\\\.\\C:,就是得到在物理磁盘的偏移,简单来说就是找到物理磁盘下c盘的位置,如图五所示。

    图五

    样本会检查0x7FFE0500地址下的值是否为0xEE00A121(也许该标志位是用来判断当前系统是否已经被感染)。如果不是就创建名为// Global\\7BC8413E-DEF5-4BF6-9530-9EAD7F45338B的互斥体,接着会获得\\\\.\\PhysicalDrive0的信息也就是MBR(位于整个硬盘的0磁道0柱面1扇区),主要用来引导系统的启动,如图六所示。

    图六

    获得原始的mbr的信息,并判断读取出来的内容是否是正确的,如图七所示。

    图七

    修改MBR

     

    样本会将当前获得的分区信息记录到日志文件中,之后将自己的驱动文件写入到0xfff00000位置,接着将自己的其他shellcode代码写入到7800-7a00的位置7c00保留原始的mbr引导,在将原始的mbr给覆盖掉,到此写入就完成了。这里有个疑问就是怎么知道写入的扇区的位置,具体的计算方式如下(7800/200=3c,7a00/200=3d,7c00/200=3f)。在分析引导文件的时候会得到写入磁盘的位置,我们只需要观察扇区的位置是否一致就行了,如图八所示。

    图八

    接着样本使用MoveFileExA重启之后删除自身,拷贝自己到当前目录下,这里改了一个字节,并修改后缀为.dll,如图九所示。

    图九

    注册当前dll,启动当前文件夹下的dll文件,睡眠1800000之后重启系统,如图十所示,到目前为止样本第一次启动之后执行的事情基本差不多就是这样了。

     

    图十

    分析修改之后的MBR和挂钩流程

    挂钩13号中断

    开启了漫长的挂钩之旅,通过分析知道样本会申请2kb的内存空间(主要目的是长期驻留内存,保证在挂钩函数正常的执行),之后将自身的代码拷贝到申请的内存中,接着在0磁道的3d扇区取两个扇区的内容追加写入到申请的内存空间(这也解决了我们上面的疑问在图八中提出的,就是到底把代码写到哪里去了,从这里我们可以看出写入的位置和计算的位置是符合的),如图十一所示。

    图十一

    接着对13号中断挂钩,这里有很多小细节需要注意。虽然代码一直在当前内存和申请的内存来回执行,但是偏移是一样的,之后通过修改13号中断的偏移地址和段地址,完成对13号中断的hook。

    (原始的13号中断的地址记录在es:73h下面),之后将原始的mbr引导代码写回到00:7c00处,在将执行权限交还给mbr,到这里13号中断挂钩也结束了,如图十二所示。

    图十二

    挂钩实模式下的Ntldr

    系统在启动的时候会启动Ntldr,如果读入磁盘的内容到内存中就会执行int 13,那么就会进入到样本hook的流程中,之后完成对Ntldr的BlLoadBootDrivers下一行挂钩。首先样本会判断当前ah的值,其中42为拓展读,2为非拓展读,如果满足两个中的一个就会进入下面的流程,执行原来的13号中断返回,如图十三所示。

    图十三

    接下来保存现场之后执行原始流程,由于int13将代码读入到内存中,那么样本就可以对读入的代码进行特诊扫描,在int 13下不同的ah对应不同的效果。

    图十四

    这里检查的特诊码的为8B F0 85 F6 74 21/22 80 3D,如图十五所示。

    图十五

    如果找到对应的位置BlLoadBootDrivers的下一行代码,改变指定位置的代码为ff15,也就是间接call,call后面的地址存放的是要跳转的地址。

    这里存的值是下一个扇区的首地址,如图十六。

    图十六

    接着样本会进行查找特征码为83 C4 02 E9 00 00 E9 FD FF的位置,找到之后nop掉,提升栈指针,在将之后的一个字节的代码清零,如图十七所示。

    图十七

    挂钩32位保护模式IoInitSystem.

    这个样本在win7 32位下挂钩Ntldr的BlLoadBootDrivers是挂不上去的,所以调试不了,但是不影响,就算能调也不一定知道它在干嘛,一样的需要科普一下其他的知识点(到处都是知识盲区)。当样本再次获得执行权限的时候已经通过Startup.com切换到保护模式下(之后遇到一些问题还是把xp装上了,在xp下es变成了9f00)。首先是在esp+24的地方拿到osloder的基地址,之后搜索特诊码,获得BlLoaderBlock结构的地址,这个结构和_LDR_DATA_ TABLE_ENTRY前几项一样,如图十八所示。

    图十八

    可以通过遍历链表的方式得到ntosknl的基地址,之后在定位到Ntoskrnl! IoInitSystem的地址,如图十九所示。

    图十九

    最后将得到的内核基地址和IoInitSystem函数地址放在9f40c和9f404中,如图二十所示。

    图二十

    之后完成对IoInitSystem函数的hook,由于我们已经知道了IoInitSystem函数的位移,那么只需要把这个相对地址替换成样本的相对地址就行了。最后样本把9f400内存的数据放到ntosknl最后的200h中,如图二十一所示。

    图二十一

    加载驱动文件

    直接看代码当然还楞了一下,怎么把12345678h压入堆栈,这有什么用啊。但是当真实运行之后的值是被改变了的,其实在图二十的时候就已经被改变了,修改之后的值为IoInitSystemhe函数地址和内核基地址,如图二十二所示。

    图二十二

    之后由于看ida(没有符号链接,也不知道该地址代表的意义)太难了,这个时候内核都加载起来了可以使用Windbg了,那就使用Windbg来调试。之后通过ExAllocatepool的crc32得到函数地址,申请1a8h的内存空间从偏移55的地方将代码拷贝到申请的内存空间,如图二十三所示。

    图二十三

    之后进入申请的内存空间中继续执行,首先是执行IoInitSytem的流程(执行的时间有点长),之后通过ntopenfile的crc32找到对应的函数地址(构造参数的地址不是三环的地址,当时写的时候没注意),如图二十四所示。

    图二十四

    之后调用NtOpenFile打开\??\PhysicalDrive0,目的是找它写入的驱动文件,这里如果不调试的话很难知道它在干什么,一开始我也有疑问就是打开文件的字符串放在哪里,后来找了找才知道它已经被拷贝搭配申请的内存空间了,如图二十五所示。

    图二十五

    之后又调用ExAllocatepool申请了0x3bc00字节大小的内存空间用来存储读取到了驱动文件。使用NtReadFile读取指定偏移的驱动文件,如图二十六所示。

    图二十六

    之后根据文件的SizeOfImage,重新申请一块内存空间,将当前的驱动文件拉伸之后存放到新申请的内存空间中,如图二十七所示。

    图二十七

    接着样本会找到驱动的入口地址,然后直接调用(什么都没有修复,可能是在驱动中执行其他操作),之后判断成功与否,失败会释放资源,成功清零当前的代码,之后回到正常的流程中进行执行,如图二十八所示。

    图二十八

    驱动文件

    修复自身

    到目前为止整个引导的过程就结束了,现在分析一下它的驱动文件。样本进来做的第一件事是修复重定位表,从之前的分析流程中,也可以知道样本跳到这个驱动文件之前是没有做任何修复的,只是简单的拉伸了一下pe文件。样本的修复重定位如图二十九所示。

    图二十九

    最后完成导出表的修复,整个pe基本就修复完成了,如图三十所示。

    图三十

    创建系统线程

     

    样本接着创建一个系统线程,在这之前又赋值了一串代码,这个代码会在新线程中使用(看的交叉引用),如图三十一所示。

    图三十一

    进入线程函数之后可以发现,它首先解密了一段代码,这个是pe文件,在解密之后又进行了重定向,之后判断当前系统版本,主要目的是根据系统版本选择获得PsLoadedModuleList地址的方式,之后将控制权交给新的解密出来的pe文件,如图三十二所示。

    图三十二

    解密后的驱动文件

    之后又到了一个入口函数,一开始还比较疑惑它把PsLoadedModuleList传进来干什么,模块隐藏?不应该啊它压根就不是按照正常的方式加载驱动的,链表也不会有啊.之后才发现它IAT还没有修(可以通过PsLoadedModuleList找到各个模块的导出函数,重定位在进来之前就修复了),这里的第一个函数就是修改它的IAT,如图三十三所示。

    图三十三

    接着获取了之前那个驱动的第二个区段的RVA(.rdata)放在全局变量中,之后擦除PE头的信息(也是之前的那个驱动),如图三十四。

    图三十四

    接着函数格式化了它的字符串,但这个函数是真的复杂,如图三十五所示,ida还不能反编译。

    三十五

    话是这样说,但是我也没有真的去看,因为之后它调用了一个内核中初始化字符串的RtlInitUnicodeString(内核中字符不能和三环一样乱来,需要按照要求初始化为UNICODESTRING的格式),如图三十六所示。

    三十六

    之后它获得了\Device\HardDisk0对象之后在OBJECT_DIRECTORY中遍历驱动对象(完成对磁盘的hook,这里的数据结果不太熟悉,看着像是在hook)如图三十七所示。

    三十七

    接下来创建一个过滤设备,绑定函数,如果之前的代码执行失败就会执行之后的代码,这个代码就是对磁盘的hook(前提需要之前的代码执行失败,所以之前的那个猜测是另外一种方式的hook),如图三十八所示。

    三十八

    继续创建新的系统线程

    之后做了很多irp和事件的操作,由于水平有限(自己写驱动的时候就没怎么用这些东西),不知道在干嘛(但是好像是在做一些检查,这里就直接跳过了),来到新的线程中进行假装分析一下,之后样本获取了当前计算机的配置信息,如图三十九所示。

    图三十九

    接着使用NtDeviceIoControlFile函数继续获取当前计算机的配置信息,主要获取的信息包括系统盘符(c盘)、符号链接为scsi2的SCSIDISK信息、获取驱动器版本,返回GETVERSIONINPARAMS结构指针、使用IOCTL_STORAGE_QUERY_PROPERTY获取序列号,如图四十所示。

    图四十

    完成准备工作之后驱动又新启动了三个线程继续工作,线程启动后把0xFFDF0500的值改为0xEE00A121,这里也填了一个坑,当驱动正常加载之后能够将0xFFDF0500标志位的值进行修改(对应用户层是0x7ffe0500,同一份物理内存,做了虚拟地址的映射,但是对3环和0环来说权限不同)。但由于对内核结构的生疏很多东西分析起来也很困难,所以这里就到此为止了,如图四十一所示。

    图四十一

     

  • 相关阅读:
    【梦断代码】与我们队的相似之处
    梦断代码 之 你失败过吗
    梦断代码 之 程序人生
    C#中父类转换为子类
    C#中Dictionary泛型集合7种常见的用法
    Linux 常见命令 目录处理指令
    使用XSLT+XML生成网页
    我心目中的Asp.net核心对象
    配色速成
    VS.NET中JavaScript隐藏特性
  • 原文地址:https://www.cnblogs.com/csnd/p/16675599.html
Copyright © 2020-2023  润新知