首页
论坛
课程
招聘
[原创]最适合新手的病毒分析——彩虹猫病毒
2020-10-22 23:38 6583

[原创]最适合新手的病毒分析——彩虹猫病毒

2020-10-22 23:38
6583

前言

彩虹猫是一款很有意思的病毒,结构也很简单,适合小白分析练手。这篇分析也是我的一个练习项目,不但展示了分析结果,也重点阐述了分析过程中遇到问题时的解决思路,尽量做到详细、多图、无死角,希望能对同样是新手的读者一点帮助。


样本名称:MEMZ.exe

MD5:     19DBEC50735B5F2A72D4199C4E184960

SHA1:    6FED7732F7CB6F59743795B2AB154A3676F4C822

实验平台:Windows 7 Ultimate With Service Pack 1

分析工具:PEiD v0.95、StudyPE x64 v1.11、IDA x32 v7.4、Bochs v2.6.9、VMWare WorkStation v15.56、Olly ICE v1.10

一、初步观察和信息收集

拿到样本不要一来就拖进IDA和OllyDbg,在没有任何参考信息的情况下很容易迷失方向,应该先做最基本的观察,为之后的分析提供思路。特别是这次的彩虹猫样本,有很明显的用户能直接观察的特征。让我们先把病毒在虚拟机中运行起来。



双击病毒样本后接连弹出两个警告弹窗,希望用户明白运行病毒的风险。点击确定后,桌面会慢慢出现一些诡异的现象,比如:

  • 自动弹出多个浏览器搜索窗口

  • 鼠标异常晃动

  • 窗口颜色怪异

  • 反复出现系统提示音

  • 出现6个MEMZ进程

这6个名为MEMZ的进程,用户尝试关闭任意一个,或者手动关闭计算机,都会遭至大量弹窗随后蓝屏,接着Windows无法正常启动,只会循环播放一只彩虹猫附带欢快的背景音乐,无论你重启多少次都是如此。运行观察结束后再用一些基本的可执行文件信息查看工具进行勘察。


首先上场的是PEiD,监测到该样本可能使用了ASProtect v1.32进行加壳或加密,可能会给本次分析带来一点小困难


接着使用StudyPE+查看样本的导入表,发现了很多有意思的函数,这些导入函数能给我们提供大量的信息,我把这些函数大致分为三类:

第一类:很明显能和观察到的现象直接联系

  • 自动弹出多个浏览器搜索窗口----------------------ShellExecute

  • 鼠标异常晃动--------------------------------------SetCursorPos、GetCursorPos

  • 桌面出现奇怪图标---------------------------------DrawIcon

  • 窗口颜色怪异--------------------------------------BitBlt、StretchBlt

  • 反复出现系统提示音-------------------------------PlaySoundA

  • 出现6个MEMZ进程--------------------------------ShellExecute

第二类:不能直接关联,但常成对出现完成某些功能

  • OpenProcessToken、AdjustTokenPrivilege、LookUpPrivilegeValue    给进程提权

  • GetMessage、TranslateMessage、DispatchMessage                           建立消息循环

  • CreateToolhelp32Snapshot、Process32First、Process32Next              遍历进程

  • SetWindowHookEx、UnhookWindowHookEx、CallNextHookEx         给窗口下钩子

  • LoadLibrary、GetProcAddress                                                               加载库并导入函数

第三类:不确定干了什么事或者我根本不熟悉

  • SendInput、GetSystemMetrics、DefWindowProc……


对于复杂的样本,还可以使用监控类工具看看样本对文件、进程、注册表、网络进行了哪些操作,不过这次样本比较简单,以上信息已经足够指导接下来的分析工作了。


二、入口部分

使用IDA载入样本,定位到入口函数Start并按下F5获得伪代码,该功能极大地提升了样本分析的效率,毕竟看高级语言比看汇编还是快得多。但是这些伪代码只是IDA根据现有信息猜测出来的,并不能保证100%准确,有的时候甚至会对我们的分析工作产生误导,一会你就能看到。现在让我们继续回到IDA生成的伪代码

暂时别着急深入细节,先从整体把握走向。在start函数的起始部分调用GetCommandLineW、CommandLineToArgvW获取进程参数,之后整个Start函数就根据进程参数分成了几大块,整理后其结构如下


注意,这段体现start函数结构的伪代码并不是直接照搬自IDA,而是为了整体逻辑清晰而特意整理修改过的。接下来就要依次分析这三大部分,为了方便表述,这三个部分我称为“watchdog部分”、“/main部分”和“无参数部分” 。我们第一次双击样本时自然是不带参数的,所以我们先来看start函数的无参数部分。


三、无参数部分

此处先是用MessagexBox依次弹出两个警告信息提示框,这也正是我们运行病毒时所看到的。若两个弹窗都被用户确认,使用GetModuleFileNameW获取当前进程的路径并保存,当然所保存的空间由LocalAlloc提前申请。接着用do while执行5次循环,每次循环都调用一次ShellExecuteW,参数正是刚才得到的样本进程路径,以及字符串"/watchdog",即以“/watchdog”为参数,生成5个MEMZ.exe进程。


在最后调用了ShellExecuteExW以“/main”为参数生成了一个MEMZ.exe进程,一般带Ex后缀是加强版函数,表示在普通版函数基础上进行了功能的扩展,这里特意用了一个功能更强的函数一定有特别的目的。该函数接收一个SHELLEXECUTEINFOA结构体,结构体中设定了进程路径和执行参数,跟前面普通版函数传递的参数其实没什么区别。关键是紧接着调用了SetPriorityClass,当中对hProcess成员复制为0x80,查看MSDN得知该值意味着创建的进程将拥有最高的响应优先级,最后调用ExitProcess使当前进程结束。如果你不会使用MSDN我在文末备注有介绍。


现在我们知道在观察阶段看到的6个MEMZ.exe进程是怎么来的了:最开始双击生成了一个原始进程,原始进程生成了5个watchdog进程和1个/main进程,然后原始进程迅速结束了自己,等我们打开任务管理器查看时,还存在6个MEMZ.exe


IDA全称是交互式反汇编器(Interactive Disassembler),重点在于“交互式”,我们可以把手动分析后的任何想法和有用的信息都记录在IDA里,比如给IDA自动生成的变量和函数起一个有意义的名字,这些名字会在所有用到该变量或函数的地方生效。完成后应该像下图这样。


在后面的分析中,请大家自行完成注释的添加,为了后面的文章不剧透,我贴出来的图大部分还是未注释的状态。至此无参数部分分析完毕。

四、"watchdog"部分

1.主体

该部分的主体代码量很少,我一张图就全展示清楚了,但里面的坑可一点都不少。

首先是CreateThread创建一个线程,IDA给自动命名为sub_40114A。在这里给大家提一个醒,以watchdog为参数运行的MEMZ.exe进程是有5个的,也就是说这里看到的创建一个线程是“某个进程“的行为,5个进程一共应该创建了5个线程,在后面的分析中大家也要注意这一点。我们进入sub_40114A看看这个线程干了些什么事。

2.子函数sub_40114A

同样代码很少一张图概括完。前三个函数用来获取当前进程路径,顺便说一下这种多个函数配合起来做事的情况有很多,常常是最后一个函数完成关键功能,前面几个都是为它提供必要的参数,比如LocalAlloc申请空间用来存路径字符串,GetCurrentProcess获取当前进程句柄,这都是第三个函数要用到的。


接着一个大的while循环把后面的代码都包了进去,注意该while循环的条件永远为真,说明是个死循环。在循环内部使用CreateToolhelp32Snapshot给进程拍快照,再用Process32FirstW和Process32NextW进行遍历,这都是非常常见的操作,我们在观察阶段查看导入表时也发现了这三个函数,这里就用上了。


当然光做进程遍历是没意义的,肯定要干点活。每当遍历到一个进程时都要获取它的路径,并和我们之前获取的路径对比,如果相同就让计数器加1,很明显这就是在看当前有多少个MEMZ.exe的进程。当遍历完所有进程后,我们就统计出了MEMZ.exe的进程个数。然后最关键的地方来了,由于Sleep函数的存在,死循环每隔一段时间就能统计出当前MEMEZ.exe进程的个数并存放在v4变量中,然后先判断v4和v7的大小,再让v7保存v4的值。


如此就形成了一个监测机制,v7永远只保存v4最大的值,一旦v4的值小于了v7就会被if语句监测到,并进入sub_401021子函数。这不正好和我们观察阶段的发现一致吗?只要MEMZ.exe进程数量减少,系统就会蓝屏重启,这就是它的监测原理。反过来我们可以推测sub_401021就是完成系统蓝屏重启功能的。

3.子函数sub_401021

前面进展得很顺利,到这里就碰到麻烦了。在这个子函数的开头是一个do while循环,循环次数为20次,用CreateThread创建线程,这没什么特别的,像往常一样我们双击StartAddress准备去看这个线程做了什么事,但是双击过后却没有任何反应,这其实是IDA的代码识别出现错误,相关问题我在第七节“IDA的一些问题做详细解释。

在这里为了不影响我们的分析主线,直接键盘按G输入地址4010FE就可以跳到StartAddress的代码。该处的MessageBoxA用于弹出消息,有26条消息保存在lpText所指向的地址中。使用sub_401A55获取随机数保存在v3,v3对0x1A取余以实现在26条消息中随机选取一条并显示。

而SetWindowsHookEx和UnhookWindowsHookEx用于给窗口下钩子,干了什么事要到回调函数fn里去看。code==3表明目标窗口即将被创建,此时lParam表示该窗口的基本信息(坐标、大小等),修改这些信息可以在窗口真正创建之前生效,下方正是在随机修改窗口的位置。这些信息都可以在MSDN中查到。


sub_401021的后半部分是两种关机的方式:先主动诱发蓝屏关机,如果不成功则主动退出Windows。


前一种方式使用LoadLibrary加载ntdll库,再连续两次调用GetProcAddress获取两个函数地址,这两个函数是未公开的Windows API,只能用这种方式来隐式调用。随后依次调用这两个函数,主动引发蓝屏。顺便提一下,很多恶意程序在隐式调用时会对函数名字符串先加密,要用的时候再解密,或者干脆不使用函数名而用函数编号,显然我们分析的样本没有做这种“隐蔽处理”。


后一种方式先用一系列函数给当前进程提权,然后调用ExitWindowsEx主动关闭Windows。为什么要先提权?调用ExitWindowsEx关闭Windows需要相应的权限,详情见MSDN。

总结一下,我们确认了之前的猜想,sub_401021确实是一个强制关机的函数,先创建线程用于弹出大量位置和内容都随机的窗口,再使用蓝屏或退出Windows的方式强制关闭计算机。

4.注册并创建窗口

我们依次分析了函数开头->sub_40114A->sub_401021,已经陷入了太多细节,是时候回到watchdog的主体部分。这张图之前出现过,为了大家方便看我再次贴出来。在创建sub_40114A后,调用RegisterClassExA注册了一个名为“hax”的用户自定义窗口类型,并用CreateWindowExA将其创建。此处有个小问题,传递给RegisterClassExA函数的结构体变量pExecInfo定义为了SHELLEXECUTEINFOW类型,而不是该函数所需要的WNDCLASSEXA类型,这是IDA的识别出现了错误,为了不干扰主线分析,其讲解安排在了第七节”IDA的一些问题“。

5.回调函数sub_401000

创建的窗口有一个回调函数sub_401000。经查询MSDN,常量值16和22分别对应窗口消息WM_CLOSE和WM_ENDSESSION,那么整体含义就很明了了:该窗口回调函数会对窗口消息进行过滤,若消息为WM_CLOSE或者WM_ENDSESSION,则调用sub_401021强制关机(这个函数前面刚分析过)。而WM_CLOSE或WM_ENDSESSION消息是在系统关机时,由操作系统发送给各个窗口。


若是其他消息,则并不做任何处理,丢给系统默认处理函数DefWindowProcW。

如此一来,sub_401021这个强制关机函数在两处被调用。第一个是监测watchdog进程数量,如有减少就调用。第二个是监测用户是否主动关机,如有也调用。这和我们观察阶段看到的完全一致。

6.消息循环

紧接着的代码GetMessage、TranslateMessage、DispatchMessage被包进一个大的while循环,这是常见的操作,叫做“消息循环”。由于在前面创建了窗口,并且还对发送给窗口的消息进行了过滤,意味着我们必须自己写消息循环完成收取消息和派发消息的工作,否则创建的窗口是收不到消息的。Windows并不会自动帮我们完成消息循环,这在MSDN里有明确解释,给出两条参考链接:


一篇关于消息循环的博客

https://www.cnblogs.com/zxjay/archive/2009/06/27/1512372.html


MSDN中关于创建消息循环的官方回答

https://docs.microsoft.com/en-us/windows/win32/winmsg/using-messages-and-message-queues


五、"/main"部分

1.主体

由于开头部分代码和MBR有极强的联系,我索性把它划到MBR章节了,到时候再一块说。让我们把窗口往下拉一拉,这里有一个do while 循环,/main部分的分析就从这儿开始


此处的do while循环以v8为计数器,内部用CreateThread创建线程,循环跑10次(v8 < 0xA),一共创建10个线程。这不由地让人好奇,创建这么多线程是要做什么?先别慌跳进去看,它还附带一个参数v9,并被赋予初始值off_405130,每次循环自增2。以off开头表明是某个静态地址,我们双击过去看看。

off_405130地址所指向的是一块数据区,并且这里的数据还有非常强的规律性。每组都由1个dd类型的数据加4个db类型的数据组成,一共有10组,由于篇幅原因我只截取了部分组,为了方便大家观察,组与组之间我用了两种颜色隔开。


类似dd、db这样的符号在IDA里很常见,它们表示该地址所对应的数据大小,详细解释如下:

db:data byte       1字节数据

dw:data word     1字数据

dd:data dword    双字数据

dq:data qword    四字数据


1字 = 2字节

再回过头来看看前面的do while,v9被赋值为off_405130并作为参数传入。Sleep函数里也用到了v9,它将v9看作了数组首地址,并每次取数组的第1项(数组项数从0开始)。每次循环v9都会自增2,由于v9的数据类型是DWORD *,每次自增2就是自增两个DWORD*的大小,即8个字节。那么随着循环的进行,v9和v9[1]的值将会按如下所述变化:


第一次循环第二次循环第三次循环第四次循环......
v9的值0x004051300x004051380x004051400x00405148......
v9[1]的值0x004051340x0040513c0x004051440x0040514c......

v9的值指向的都是不同的静态地址,如果你挨个双击跳过去看,会发现它们其实都是函数地址。而v9[1]是Sleep函数的参数,所指向的肯定是时间量(毫秒)。v9是4个字节的DWORD * 类型,那它指向的当然就是4字节的DWORD类型,所以这些时间量都是4字节的DWORD类型。我们在IDA中右键单击地址就可以选择其解析长度,我们用4字节来解析它。


把所有时间量都这样操作,并在最后用右键选择数据展示的进制,选择10进制,毕竟我们更习惯用10进制来表示时间量。


所有步骤完成后,IDA应该显示为下图这样:

看到这样的结构,很容易让人想到病毒作者写代码时应该用了结构体数组,结构体的构成如下:

struct tagFuncWithDelay
{
    DWORD    pFunction;
    DWORD    dwTimeDelay;
}

10个这样的结构体在一个数组中

tagFuncAndDelay Array[9]

现在我们明白了,创建了10个进程,并且每次创建都会有延迟时间,这个延迟时间和进程的函数地址是放在一起的,一共有10组,都在一个大数组off_405130里。

2.子函数sub_401A2B

这个子函数的代码很短:用大数组中的函数地址依次创建线程。由于是函数指针,要用它调用函数当然得声明其参数、返回值、调用方式之类的,这都是C语言里很常见的用法。

这10个函数做的事很简单,我以sub_40156D为例。它调用GetCursorPos获取鼠标指针坐标,然后用各种方式修改这个坐标的值,比如这里调用sub_401A55生成随机数并返回,用这些随机数与鼠标指针坐标做运算,最后调用SetCursorPos来设置鼠标指针坐标,用户就会发现鼠标指针随机晃动了。所有这10个函数都干的类似的事,这里不再依次分析。

不过提醒一句,一些含有大量计算的部分,如果不是很重要,没必要完全了解算法细节。比如这个函数sub_00401AA0,它的参数是窗口名的字符串,进行的操作是把这些字符串前后颠来倒去,最后把打乱的字符串返回。你已经知道它就是为了打乱窗口名字符串,给这个函数起个合适的名字就可以先溜了,不用管它是怎么算的,如果后面有的分析必须要我们完全理解这个算法,再回过头来看也不迟。

当你把大数组里的函数分析完后,最好给他们一个统一的命名,我以MessUp为开头命名了这10个函数。

sub_401A2B大致做了什么我们清楚了,就是依次调用这10个函数。这里还有一个小细节,10个函数每个的返回值部分都有不同,有的是返回一个随机值,有的是返回一个固定值。返回值被保存在v1,每次循环都会在if语句中判断!v1--是否为真,也即v1自减1后是否为0,控制v1的值就能决定循环的次数,再配合后面的Sleep函数就能决定这10个函数的激活时间。这10个函数中,有的需要一个固定的激活时间,有的需要一个随机的激活时间,只需要控制返回值即v1就可以了。

六、MBR部分

1.主体

终于到了最后的重头戏了,MBR部分完成了该样本的核心功能。


在继续分析之前,得要清楚什么是MBR。MBR全称主引导记录(Master Boot Record),整个硬盘最开头的512字节就是它。计算机启动后会先运行MBR里的代码进行各种状态的检查和初始化的工作,然后再把控制权转交给操作系统(简单地讲就是一个JMP指令跳到操作系统的起始代码),Windows就加载启动了。


MEMZ病毒干了一件很可恶的事,它直接把整个MBR覆盖掉了,变成了它自己的代码,那么它想干什么都行了,只要它不主动交出代码执行流程,Windows绝没有启动的机会。

MBR里既有代码也有数据,开头的0-446字节是代码,紧接着就是数据,数据部分记录着硬盘的分区信息,结尾以固定的0x55 0xAA作为结束符,如果你了解过PE文件结构,应该对这种做法不陌生。

对MBR有个初步了解后,我们回头来看代码。现在先回到/main未分析的部分,我们前面提到过,它和MBR有紧密关联。


首先使用CreateFileA以文件形式打开了主硬盘,也即PhysicalDrive0,这样方便后续的覆盖操作。紧接着用LocalAlloc分配一段以0为初始值的内存空间,并拷贝两段神秘数据到分配的内存空间。这两段数据中第一段byte_402118大小304字节,第二段byte_402248大小1952字节,拷贝时中间跳过了206字节。最后将内存空间的数据覆盖到主硬盘(PhysicalDrive0)的开头部位,原始MBR遭到破坏。


第一段正好就覆盖了MBR的代码区域,所以几乎可以肯定这304字节是代码。第二段数据已经不属于MBR的范围,而且高达1952字节,很可能是用于显示动画的图像数据和音频数据。

一旦将MBR覆盖,侵入也即宣告成功,病毒作者赶紧写了一堆话想要好好炫耀一番,这段话被写入note.txt文件中,并用Windows自带的记事本打开。


2.安装和配置Bochs

现在新的问题摆在面前,写入硬盘的这些神秘数据我们可以复制出来,然后导入IDA里,但分析起来太困难了,有大量的地址是在运行时确定的,要是能让代码跑起来,能下断点,能单步调试就太棒了。可惜的是,MBR运行在16位环境直接和硬件打交道,此阶段Windows根本没加载,OllyDbg或者WinDebug这种常用的调试器当然用不了。这时该一个神器登场了--Bochs


Bochs和VMware一样,也是虚拟机,不过它是专门为调试而生的,MBR代码会运行在它虚拟出的硬件中,并允许我们像使用OllyDbg一样下断点、单步调试。但Bochs的调试器是纯命令行的,使用起来不太方便,而ida提供了Bochs插件,可以调用Bochs并给出图形化的界面,使调试过程更友好。不过该插件对Bochs的版本有要求,比如我用的IDA 7.4要求版本为v2.6.x,具体要求可在IDA帮助文件中搜索Bochs获得:


使用IDA的bochs插件调试MBR代码的具体的操作如下:

 

  1. 用bochs的bximage.exe工具创建硬盘镜像文件,默认名c.img。

  2. 用bximage.exe的查看镜像文件信息功能,查询c.img的CHS信息(柱面、磁头、扇区)

  3. 用16进制编辑器把要分析的mbr代码和数据拷贝到c.img(推荐使用010editor)

  4. 复制一份bochs自带的bochsrc-sample.txt,搜索ata0-master,将该行的path改为c.img的路径(路径不能含有中文)并添加CHS信息,完成后整行类似于:ata0-master:type=disk, mode=flat, path="C:\Users\Ghost\Desktop\c.img",cylinders=20, heads=16, spt=6

  5. bochsrc-sample.txt改名为.bxrc,并双击该配置文件,bochs会加载这个配置文件运行c.img,如果能成功说明bochs直接运行是可以的,下一步可以由ida来调用bochs

  6. 右键bochsrc-sample.bxrc发送到ida,ida会识别出来它是一个bochs配置文件并加载,这时就可以点击顶部的播放按钮开始调试了,ida会像od一样展示所有相关的调试信息

                          



点击调试后IDA就会展现调试界面,这个界面和OllyDbg几乎是一样的,有数据窗口、代码窗口、栈窗口、寄存器窗口等等。调试默认情况下没有断点,病毒程序的MBR代码会直接运行,打开Bochs窗口可以看到运行时生成的猫咪动画。

当然我们希望病毒加载好后就在第一行代码处暂停,给我们操作的机会。点击IDA顶部菜单Debugger-Debugger Options,在弹出的窗口中勾选Suspend on Process entry point即可实现。

一切准备就绪就可以进行MBR代码的分析了

3.分析MBR代码

MBR代码是16位汇编,需要对中断、寄存器、内存分段机制有所了解。无奈这篇帖子已经写得太长了,写完MBR部分会超过字数限制且阅读体验也会很差。我把代码的结构图和一些特殊地址的功能放在这里,有需要的小伙伴可以用作参考。如果你觉得这部分分析困难很大,推荐看一本《x86汇编语言 :从实模式到保护模式》,看完再回过头来分析就如鱼得水了。


如果已经完全透地分析完,还可以根据自己的需要对MEMZ进行”定制“:

  • 还原出加密算法后,修改画面数据和音频数据,达到显示全新动画的效果(Bad Apple 可以一战!)

  • 对病毒进行升级,使其不仅能感染老式的MBR格式,甚至可以感染新的GPT格式(比如Windows 10)

  • 写一个修复工具,完成对病毒感染后的修复

如果感兴趣的小伙伴多就再开一篇帖子讲讲一些有意思的玩法。




(7E0:0 - 7E0:800)         从硬盘读取的原始加密数据,共4个扇区2048字节

(7E0:4000 - 7E0:9DC0)     动画数据

(7E0:9DC0 - 7E0:9DF4)    文字的音频数据

(7E0:9DF4 - 7E0:9F9C)     动画的音频数据

(7E0:9F9C - 末尾)             文字数据


B800开始为显存地址


七、IDA的一些问题

1.栈不平衡问题

在分析sub_401021时,StartAddress双击并不能成功跳转。如果你平时有看输出窗口的习惯(你确实应该有这个习惯)就会看到一条报错信息,在汇编窗口也能看到类似的红色报错信息。它们所提供的信息并不太多,都是很简短的几个字,但是包含有ebp和sp这两个和栈有直接关系的寄存器,这暗示我们栈可能出了什么问题。

但问题到底是什么呢?猜是猜不出来,IDA自带了帮助文件,按F1打开帮助窗口,输入关键词后就能得到详细的解释。原谅我因为篇幅和时间不可能全部翻译,但大意为造成该问题的原因是“栈不平衡”。

我第一次分析到这儿时,总觉得很费解。如果代码有问题导致栈不平衡,那病毒在运行期间就会报错啊,难道是病毒作者故意手写了栈不平衡的汇编代码,用以达到某种特殊的目的?盯着IDA看了半天也没什么用,索性我决定用OllyDbg全程跟踪sub_401021,看看栈到底发生了什么事。想要在OlldyDbg里定位到这个函数是很简单的,该函数用到了两个字符串“RtlAdjustPrivilege”和“NtRaiseHardError”,并且没做任何加密处理,在OllyDbg里右键-查找-所有参考字符串,找到我们的目标字符串,直接双击过去即可成功定位。随后往上滚动几行,就是sub_401021的入口,在这行按F2下断点。

别急着按F9开始调试,这时候按F9运行程序断点根本断不下来,程序的执行流程是有条件的,每次都要合适的条件,程序最终才能走向我们想要调试的sub_401021。一个更方便我们测试的方法是在OD中直接修改指令。在参数的判断阶段,jnz指令控制了参数判断后程序的执行路径,按空格并在弹出的窗口输入nop,勾选“使用NOP填充”,点击汇编按钮后该行原本的6字节指令变为6个nop,nop的意思是CPU不执行任何动作,那么程序执行到此处,就永远不会发生跳转,一定会执行watchdog部分,我们的目的就达到了。

类似的修改不止一处,请大家自己动手修改所有必要的地方,保证程序运行后执行流程可以顺利到达sub_401021,并在我们的断点处断下来。当然为了避免每次重新调试都要做这些修改,可以将我们的修改另存到一个新的可执行文件,以后直接调试修改后的文件就方便多了。

后面我干的事,就是用OllyDbg加载修改后的文件,不停用F8单步调试sub_401021,眼睛一直盯着栈窗口,想要看看栈到底哪里不平衡了。让我失望的是,栈始终处于平衡状态,不过我确实发现了一些不同寻常的代码。为了方便展示,我回到IDA里给代码截图。


sub_401021的开头并没有和大多数函数一样开辟栈空间,而是把这个动作留到loc_401050才执行,中间用一个类似nop的指令jmp $ + 5来连接,我把jmp视作桥梁连接了上下两个部分。


  • 【上半部分】这是sub_401021函数开始的地方,没有类似sub esp, XXXX的指令来开辟栈空间是可以理解的,因为这部分并没有用到局部变量。但连push ebp、mov ebp, esp这两句也没有,意味着这部分代码没有自己的栈,而是在“借用”调用者的栈,使用到的push、pop指令也都是在操作调用者的栈。不过仔细想来,只要栈是平衡的,这种“借用”栈的行为并不会有什么问题,关键它确实是平衡的。

  • 【下半部分】使用了标准的函数入口的代码,创建了自己的栈并申请了1ch字节的栈空间。

  • 【中间部分】jmp $ + 5,$代表这行语句头部的地址,而这行语句刚好是5个字节,所以jmp $ + 5表示跳转到紧接着的下一条指令,执行效果等价于一个nop。从指令的角度也可以解释,该行对应的指令是E9 00 00 00 00,E9是JMP本身,后面四个字节是目标相对于E9偏移地址,全是0表示跳转到该行指令结束后的下一行指令push ebp,其所在地址为00401050。

(显示指令码的方式:IDA顶部菜单-Options-General-Disassembly-Number of opcode bytes,输入你想显示指令的最大长度,比如8)

用OllyDbg的实践证明栈不存在平衡问题,不由得使我怀疑,会不会是代码比较特殊,IDA的分析出错了?既然下半部分是个标准的函数开头,上半部分栈也是平衡的,干脆就把上下两个部分定义为两个函数吧。在sub_401021中右键-Edit Function-End address填入希望的函数结束地址,我们填入401050即jmp指令的下一行。IDA就将jmp及其上半部分定义为一个函数,并自动将jmp的下半部分定义为另一个函数。

完成后如下图所示,我用两个颜色区分了这两个函数,此时IDA的栈不平衡红色提示消失了。折腾了这么久原来是IDA的识别出错,看来IDA也并不总是可靠的。至于编译器为什么生成这样奇怪的指令暂时还不清楚,可能是编译器的代码优化,也有可能这段代码是作者纯手工写的。

2.结构体识别问题

这里IDA的识别出现了问题。pExecInfo变量的类型是SHELLEXECUTEINFOW,此处强制转换为WNDCLASSEXA再赋值使用,意义为何?在变量pExecInfo上单击快捷键x弹出交叉引用窗口,这里展示了所有引用了该变量的地方。两个红框之外的引用是结构体赋值,这个我们并不关心,红框是两个函数调用时对pExecInfo的引用,上方红框是我们此刻看到的RegisterClassExA,下方红框SetPriorityClass可以双击过去看看。

双击跳过来一看,似曾相识,甚至有我们自己命名的变量。定睛一看,这不就是我们前面分析“无参数部分”的末尾吗?这就奇怪了,按MSDN的解释这两个函数使用的结构体类型完全不一样嘛,前一个是WNDCLASSEXA,后一个是SHELLEXECUTEINFOW,那么为什么IDA中两个函数使用了同一个结构体变量?


如果我们查一下MSDN,把这两个结构体的定义并排比较,就能得到答案。这应该是编译器优化的结果,编译器发现二者的结构体定义非常相似,SHELLEXECUTEINFOW是15个4字节类型,WNDCLASSEXA是12个4字节类型。于是编译器就想,反正你们两个结构体的使用也不冲突,那我就预留一个15*4字节的空间,对于大的那个结构体刚好满足,对于小的那个结构体会多出来3*12字节,但它可以忽略不用啊,把开头的12*4字节看作是WNDCLASSEXA不就行了?

编译器一堆骚操作省了栈内存空间,但IDA看着编译后的结果并不知情。同一片内存空间怎么能给两个结构体用,这让我怎么给这段空间命名?但也不能放着不管啊,于是选了体积更大的结构体SHELLEXECUTEINFOW来命名,而另一个只能委屈一下了,在函数调用的时候用(WNDCLASSEXA *)作指针的强制转换。


明白了这层道理,我们可以尝试给栈里的这片内存空间换个名字。切换到栈窗口后一定要想想再动手,千万别看到SHELLEXECUTEINFOW就直接右键修改成WNDCLASSEXA,此时你修改的其实是栈中偏移0x64处内存空间的定义,要知道WNDCLASSEXA是比SHELLEXECUTEINFOW小3*4字节的。


正确的做法是选中偏移0x64的这行,用快捷键U清除当前的结构体定义,在比0x64小3*4字节的地方,即偏移0x58的位置定义结构体WNDCLASSEXA

修改完定义后顺便把变量重命名为wndclass,这时候再看代码已经可以用WNDCLASSEXA结构体解析了

          


八、备注

1.如何查询Windows API

可能会有初学者对查找Windows API不熟练,甚至使用百度百科。一个更高效准确的方式是使用微软的MSDN,这里有微软对Windows API最权威的解读,但大部分都是英文。如果你有翻译需求可以使用微软自家浏览器Edge,用它翻译微软的技术文档会有非常不错的效果。

下面是一个查询SetPriorityClass函数的例子,在MSDN右上角的搜索框输入后就能得到结果,在函数的介绍页面详细描述了参数、返回值、一些注意事项,甚至还有带高亮的代码示例,相信我没有别的地方比这儿信息更全了

2.如何查询中断调用

mbr分析过程中,最让我头疼的是中断和端口号没有一个权威的查询渠道,我只找到了一个可以查询中断的网站ctyme,如果有更好的查询方式记得留言告诉我。

3.关于附件的说明(ReadMe.txt)

以下说明同样也包含在附件的.txt文件中

  • “MEMZ.exe.virus”是病毒本体,加上.virus后缀是为了防止不小心双击运行了文件,若需在虚拟机中运行请删去这个后缀。

  • “MBR_code.hex”是从样本中提取的MBR部分数据,可用任何16进制编辑器打开。

  • “MEMZ.idb”是IDA 7.4导出的数据库文件(请注意不同版本可能有兼容问题),里面有贴主分析时写的注释及各种函数变量重命名,数据库文件可以脱离其原始文件单独使用。

  • 病毒感染的目标必须使用MBR格式,更新的GPT格式将不能显示彩虹猫动画,所以测试环境请使用Windows 7 或更旧的操作系统。

  • 样本只作为学习交流使用,请勿用于恶意用途。

解压密码:infected

解压密码:infected

解压密码:infected


第五届安全开发者峰会(SDC 2021)10月23日上海召开!限时2.5折门票(含自助午餐1份)

最后于 2021-4-10 16:42 被谁的狗哥编辑 ,原因: 修改bochs读音
上传的附件:
收藏
点赞10
打赏
分享
打赏 + 1.00
打赏次数 1 金额 + 1.00
 
赞赏  大煞笔   +1.00 2020/10/23 不错不错
最新回复 (9)
雪    币: 57
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
呓语。 活跃值 2020-10-24 17:23
2
0
太强了,这就是带佬吗
雪    币: 388
活跃值: 活跃值 (405)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
wowocock 活跃值 1 2020-10-26 10:37
3
0
1,彩虹猫是用自己的数据覆盖,没有备份,也不准备还原,所以不存在修复一说。只能自己用工具,搜索恢复分区表。
2,搞清楚bios-mbr和UEFI-GPT的区别,完全2套不同体系。
雪    币: 2608
活跃值: 活跃值 (1999)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
低调putchar 活跃值 1 2020-10-26 16:21
4
0
收藏了!感谢提供!
雪    币: 563
活跃值: 活跃值 (238)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
听风不是雨 活跃值 2020-12-28 19:59
5
0
这个病毒还挺有意思的,哈哈哈哈
雪    币: 563
活跃值: 活跃值 (238)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
听风不是雨 活跃值 2020-12-28 20:00
6
0
为啥你发了帖还没有转正
雪    币: 137
活跃值: 活跃值 (536)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
niuzuoquan 活跃值 2020-12-28 21:37
7
0
nice
雪    币: 388
活跃值: 活跃值 (111)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
byfhs 活跃值 2021-2-18 12:04
8
0
太厉害了
雪    币: 18
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
いろはねさん 活跃值 2021-3-13 17:08
9
0
狗哥 MBR 使用 bochsrc 这一步可以详细,说下嘛,老是失败,这块复现不了。
雪    币: 18
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
いろはねさん 活跃值 2021-3-13 18:59
10
0
大家在实验MBR的时候在内存里把1000h字节完全提取出来,,别用文本那个,我自己提取成功运行。
游客
登录 | 注册 方可回帖
返回