记一次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分析恶意样本,学到了很多,也了解到很多不足,长路慢慢~