记一次IDA分析恶意DLL文件
本文首发于奇安信攻防社区,原文链接:https://forum.butian.net/share/809
0x00 前言
本文主要是通过IDA对一个恶意dll样本进行分析,来熟悉IDA的基本操作,也可以了解到一些恶意样本的底层逻辑。
0x01 Dllmain 的地址是什么?
BInary file: 二进制文件
选Binary file这个选项 是因为恶意代码有时候会带有shellcode、其他数据、加密参数,甚至在正常的PE文件中带有其他exe可执行文件,并且当包含这些附加数据的恶意代码在Windows上运行或者被加载到IDA 时,它不会被加载到内存中。因此,当加载一个包含shellcode的原始二进制文件时,应当将这个文件作为二进制文件加载并且反汇编。
但是这里切记刚开始就选portable 模式,不要选Binary FIle,我刚开始就选的Binary File,怎么也找不到入口点,具体原因还未知……
就像下面一样
跳转到 1000D02E处,这里 开始执行汇编指令的地方才是 dllmain 函数的入口点,虽然前面这个地址也有很多行,但都是注释,并没有实际含义。
切忌分析前面的那一段,因为所有从 DllEntryPoint 到 Dllmain 之间执行的代码一般是由编译器生成的。
0x02 使用imports窗口并浏览到 gethostbyname,导入函数定位到什么地址?
首先定位下这个函数,
最终定位的地址就是 idata 区段的 100163CC 处
0x03 有多少函数调用了gethostbyname
右键该函数名, Jump to xref to operated
Type 中的 r 是 read,读取的意思,函数首先要被cpu读取,才能够被调用, Type中的p是被调用的引用
这里就是5个函数一共调用了9次gethostbyname函数
0x04 将精力集中在位于 0x10001757 处的对 gethostbyname的调用,你能找出哪个DNS请求将被触发吗?
首先 g 跳转到 0x10001757 这个地址
简单分析下这段汇编
首先, 将 off_10019040 赋值给 eax 寄存器,接着 地址位 + 0Dh(转换为10进制就是13),就是将地址往后偏移 13 位,然后push 入栈,接着 call 调用 gethostbyname 参数。
大概流程是这样,要找 被触发的dns请求,就一个个分析地址吧。先拿10019040 开刀,
找到了一串字符串,跳转到这里看一看,找到完整的字符串 pics.praticalmalwareanalysis.com
所以,off_10019040 是一个字符串指针,指向字符串的 [This is RDO]pics.praticalmalwareanalysis.com 的第一个字符,然后add 0Dh 后,偏移13位,指向字符p,最后 push入栈的值是 pics.praticalmalwareanalysis.com
0x05 IDA pro 识别了在 0x10001656 处的子过程中的多少个局部变量?
还是先跳转到这里,
数一数,一共24个局部变量。
0x06 IDA Pro 识别了在 0x10001656 处的子过程中的多少参数?
首先搞清楚参数的定义: 参数是调用这个函数的函数传递给被调用函数的值
很明显,这里只传入了一个 LPVOID类型的参数 lpThreadParameter
0x07 使用string窗口,来在反汇编中定位字符串\cmd.exe /c。它位于哪?
string 窗口: shift+f12
定位 cmd.exe 的地址
定位到地址: xdoors_d:10095834处
0x08 在引用 \cmd.exe /c 的代码所在的区域发生了什么?
首先查找 cmd.exe 的引用源,
右键
下面就分析下这段汇编
首先第一眼看到的是将 \\cmd.exe /c 字符串 push 入栈,
点击字符串,跳转
看到这些字符串, Hi… Welcome… Machine Uptime… Machine IdleTime…Encrype Magic… Remote Shell Session…
大概也能猜到这是一个获取机器信息的远程shell会话
定位一下字符串的地址,看到还有 language /robotwork /mbase /mhost等等,获取的都是一些系统信息
0x09 在同样的区域,在0x100101c8处,看起来好像dword_1008E5C4是一个全局变量,它帮助决定走哪条路径。那恶意代码是如何设置dword_1008E5C4的呢?(提示:使用dword_1008E5C4的交叉引用。)
老惯例,先跳,
接着右键查看下交叉引用,或者 ctrl + x
3个指令,两个 cmp, 只有第一个 mov 指令改变了该地址值
跳:
来看一下这条指令的前后都做了些什么。
在mov之前 call sub_10003695 ,那就先看这个函数地址到底返回了什么东西。
先根据几个字符串猜测一下吧,VersionInformation/ dwOsVersionInfoSize/ Getversion/ dwPlatformId 首先猜测跟操作系统的版本信息有关。
比较关键的几步操作就是:
1 | xor eax eax: 将eax清零,此前eax中存放的是 GetVersionExA 的返回值 |
刚刚我们 cmp了两个数,所以如果两个数相同,ZF=1,然后setz,AL被设置为1,反之不相同的话,AL被设置为0(AL是 eax的低8位,对应的AH是eax的高8位),一般来说执行上面命令的都是这几种机器,所以一般情况下 AL 会被设置为1,接着ret返回eax的值。
所以ret eax最后的结果通常会被设置为1,即 sub_10003694的返回值是1,接着mov dword_1008E5C4, eax,最后dword_1008E5C4全局变量的值也是1。
0x0A 在位于0x1000FF58处的子过程中的几百行指令中,一系列使用memcmp来比较字符串的比较。如果对robotword的字符串比较是成功的(当memcmp返回0),会发生什么?
0x1000FF58处的远程shell函数从0x1000FF58开始包含一系列memcmp函数
跳:
往下找 memcpy 函数,看到前面 aQuit 和 eax 被 push入栈,所以这里memcpy这两个值
接下来找robotwork,
如果eax和 robotwork相同,返回0,0Ch是12d,也是4(字节)*3(个),因为push后面跟的是立即数,所以一个数占4字节,然后offset也是4个字节,所以,一开始的push 9,和后面的两次push,加起来一共是3次,所以这里回收了这3个一共12字节的空间
test eax,eax 按位与操作,接着如果 eax为0,则ZF置为1,JnZ跳转,eax为0说明前面的memcmp比较的结果是相同,也就是如果前面两个数相同,则JZ跳转,JNZ不跳转
push [ebp+s]: 栈中,esp是栈顶指针,ebp是栈基址,esp地址减小,栈空间增大;ebp增加,ebp将向栈底偏移 。所以这里是将ebp向下s的指针地址压栈.
然后call sub_100052A2, 来看下这个地址。
看样子是进行socket通信的函数,
仔细看下这个函数的代码,可以看到它获取了注册表的一些信息。
书上说应该是这两个键值:
SOFTWARE\Microsoft\Windows\CurrentVersion\WorkTime
SOFTWARE\Microsoft\Windows\CurrentVersion\WorkTimes
我在我的计算机上去对应的注册表目录找,并没有找到这两个键值,猜测可能是以前的Windows版本
0x0B PSLIST导出函数做了什么?
打开导出表
可以看到这个函数有两条执行路径,判断的条件是由sub_100036C3决定的
来看下 sub_100036C3函数
1 | call ds:GetVersionExA ; 调用函数查看系统版本 |
其中,cmp [ebp+VersionInformation.dwMajorVersion], 5 中的5是下面的5

如果是过低的版本,就直接跳转结束,如果是符合要求的版本,则返回 1
然后就是比较跳转,如果eax为0,test之后,ZF为1,然后JZ跳转

如果eax不为0,ZF不为0,然后JZ不跳转,也就是如果版本符合要求,就不跳转(跳转之后是直接结束),ZF 置为0
如果是不跳转的话,push了一个字符串进去,然后调用strlen返回字符串的长度在eax中,然后test eax, eax
如果eax为0ZF置为1,JNZ不跳转
反之如果不为0,JNZ跳转

假设eax为0,JNZ不跳转,我们走一下这条线
call sub_10006518
可以看到这个地址调用的一个函数 CreateToolhelp32Snapshot
CreateToolhelp32Snapshot函数为指定的进程、进程使用的堆[HEAP]、模块[MODULE]、线程[THREAD])建立一个快照[snapshot]。
简单来说这个函数用来获取进程列表。通过send 将进程列表通过 socket 发送。但是我没有找到 send 函数………..

0x0C 使用图模式来绘制出对sub_10004E79的交叉引用图。当进入这个函数时,哪个API函数可能被调用?仅仅基于这些API函数,你会如何重命名这个函数?
首先跳: sub_10004E79

使用图模式绘制交叉引用图


默认选项,可以看到交叉引用图

可以看出sub_10004E79函数调用的有GetSystemDefaultLangID、sprintf、sub_100038EE、strlen,而sub_100038EE调用了send、malloc、free、__imp_strlen,然后GetSystemDefaultLangID是获取系统的默认语言的函数,send是通过socket发送信息的函数。因此可以右键函数名,重命名为 send_languageId
1 | ps: 这种快速分析是一种获得对二进制文件高层次视图的好方法,在分析二进制文件时非常有用 |
0x0D DllMain直接调用了多少个Windows API?多少个在深度为2的时候被调用?
有两种思路:
1 | 1.逐一查看Dllmain函数的代码,在代码中看api调用 |
先定位到 Dllmain的位置

像前文一样,打开交叉引用图
默认配置后会…一言难尽……

这里修改下Recursion depth(递归深度),改为1

如下就是Dllamin所调用的api函数
strncpy、_strnicmp、CreateThread、strlen
但是很明显没有显示完全,省略了很多,可以把Recursion depth设置为2

也是一个很大的图啊…放大看吧,太多了,这里就不一一列举了

0x0E 在0x10001358处,有一个对Sleep(一个使用一个包含要睡眠的毫秒数的参数的API函数)的调用。顺着代码往后看,如果这段代码执行,这个程序会睡眠多久?
先跳后看

1 | .text:10001341 mov eax, off_10019020 ; "[This is CTI]30" |
从注释[This is CTI]30中可以猜测,睡眠30s
分析下这段汇编代码
1 | 将 off_10019020 放入寄存器中,向后偏移 0Dh(13d),push eax 入栈,调用 ds:atoi 函数,接着 eax 的值乘 3E8h, pop ecx 出栈, 再将eax push 入栈, 调用 sleep ,然后 清零 ebp, jmp到loc_100010B4。 |
跳 off_10019020

偏移 0Dh后恰好是3,所以入栈的指针指向 3,传进去的值是30,atoi函数是将char函数转化为int型,接着乘 3E8h(1000),所以push入栈的值是3w,而 slepp函数在Windows里的单位是毫秒,在Linux里的单位是s,所以这里sleep了30s,与最初的猜测也是一致的。
0x0F 在0x10001701处是一个对socket的调用。它的3个参数是什么?
跳:
看到在 call ds:socket之前,push 了 6/1/2 3个参数

右键单击每个数,选择符号变量

这里列举了ida为这个特定值找到所有的对应常量。

socket函数的原型:
1 | SOCKET socket(int af, int type, int protocol); |
而常见的创建套接字的参数
1 | SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); //创建TCP套接字 |
根据入栈规则,先进后出,先找2. 根据注释可以猜测,找对应的AF,所以 2处传递的参数就是 AF_INET
接着看1处的参数,在socket中对应的是type

最后找6,对应的是protocol

所以整个socket函数的传参是这个顺序

这三个参数大致含义:
1 | AF_INET 用于连接连接对象是IPv4时(对应的IPv6用的是 AF_INET6) |
因此这个 socket会被配置为基于IPV4 的TCP连接(常被用于HTTP)
关于socket函数的更多资料可以去 MSDN 上查
0x10 使用MSDN页面的socket和IDA pro中的命名符号常量,你能使参数更加有意义吗?在你应用修改之后,参数是什么?
emmm,修改的过程就是上文分析的过程吧……
这里附上链接:socket function (winsock2.h) - Win32 apps | Microsoft Docs

0x11 搜索in指令(opcode 0xED)的使用。这个指令和一个魔术字符串VMXh用来进行VMware的检测。 在这个恶意代码中被使用了吗?使用对执行in指令函数的交叉引用,能发现进一步检测VMware的证据吗?
搜索 in 指令的话,通过选择菜单的 Search->Text,然后输入in (或者 Search -> Sequence of Bytes,然后搜索 in 指令的 opcode,也就是ED)。

这里的选项建议全部勾选上,不然会产生一堆无用信息。

如果无法快速定位到有用的信息的话,就一个个点开试。直接找in指令,

定位到这里,in指令在的位置是 0x100061c7

在 0x100061c7处的mov指令将 0x564D5868赋值给 eax。右键可以看到它相当于 ASCII 字符串 VMXh

书上说在交叉引用中可以看到 Found VIrtual MAchine 字符串,但是我没找到……

0x12 将你的光标跳转到0x1001D988处,你发现了什么?
先跳:
看到是一些巴拉巴拉字符,不具有可读性。

0x13 如果你安装了IDA Python插件(包裹IDA Pro的商业版本的插件),运行Lab05-01.py,一个本书中随恶意代码提供的IDA Pro Python脚本,(确定光标是在0x1001D988处。)在你运行这个脚本后发生了什么?
大概可以看到这个脚本实现的是解密的操作,通过异或。

加载脚本后,字符串 xdoor is this backdoor

总结
分析恶意样本的过程是比较枯燥的,地址需要来回跳转,逻辑性要求比较高,还需要对底层汇编很熟悉,笔者是第一次使用ida分析恶意样本,学到了很多,也了解到很多不足,长路慢慢~