1. 1. 0x00 前言
    1. 1.1. 0x01 Dllmain 的地址是什么?
    2. 1.2. 0x02 使用imports窗口并浏览到 gethostbyname,导入函数定位到什么地址?
    3. 1.3. 0x03 有多少函数调用了gethostbyname
    4. 1.4. 0x04 将精力集中在位于 0x10001757 处的对 gethostbyname的调用,你能找出哪个DNS请求将被触发吗?
    5. 1.5. 0x05 IDA pro 识别了在 0x10001656 处的子过程中的多少个局部变量?
    6. 1.6. 0x06 IDA Pro 识别了在 0x10001656 处的子过程中的多少参数?
    7. 1.7. 0x07 使用string窗口,来在反汇编中定位字符串\cmd.exe /c。它位于哪?
    8. 1.8. 0x08 在引用 \cmd.exe /c 的代码所在的区域发生了什么?
    9. 1.9. 0x09 在同样的区域,在0x100101c8处,看起来好像dword_1008E5C4是一个全局变量,它帮助决定走哪条路径。那恶意代码是如何设置dword_1008E5C4的呢?(提示:使用dword_1008E5C4的交叉引用。)
    10. 1.10. 0x0A 在位于0x1000FF58处的子过程中的几百行指令中,一系列使用memcmp来比较字符串的比较。如果对robotword的字符串比较是成功的(当memcmp返回0),会发生什么?
    11. 1.11. 0x0B PSLIST导出函数做了什么?
    12. 1.12. 0x0C 使用图模式来绘制出对sub_10004E79的交叉引用图。当进入这个函数时,哪个API函数可能被调用?仅仅基于这些API函数,你会如何重命名这个函数?
    13. 1.13. 0x0D DllMain直接调用了多少个Windows API?多少个在深度为2的时候被调用?
    14. 1.14. 0x0E 在0x10001358处,有一个对Sleep(一个使用一个包含要睡眠的毫秒数的参数的API函数)的调用。顺着代码往后看,如果这段代码执行,这个程序会睡眠多久?
    15. 1.15. 0x0F 在0x10001701处是一个对socket的调用。它的3个参数是什么?
    16. 1.16. 0x10 使用MSDN页面的socket和IDA pro中的命名符号常量,你能使参数更加有意义吗?在你应用修改之后,参数是什么?
    17. 1.17. 0x11 搜索in指令(opcode 0xED)的使用。这个指令和一个魔术字符串VMXh用来进行VMware的检测。 在这个恶意代码中被使用了吗?使用对执行in指令函数的交叉引用,能发现进一步检测VMware的证据吗?
    18. 1.18. 0x12 将你的光标跳转到0x1001D988处,你发现了什么?
    19. 1.19. 0x13 如果你安装了IDA Python插件(包裹IDA Pro的商业版本的插件),运行Lab05-01.py,一个本书中随恶意代码提供的IDA Pro Python脚本,(确定光标是在0x1001D988处。)在你运行这个脚本后发生了什么?
    20. 1.20. 总结

记一次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,怎么也找不到入口点,具体原因还未知……image-20211015133537658

就像下面一样

image-20211015131534620 image-20211015133446713

跳转到 1000D02E处,这里 开始执行汇编指令的地方才是 dllmain 函数的入口点,虽然前面这个地址也有很多行,但都是注释,并没有实际含义。

image-20211016100029604

切忌分析前面的那一段,因为所有从 DllEntryPointDllmain 之间执行的代码一般是由编译器生成的。

0x02 使用imports窗口并浏览到 gethostbyname,导入函数定位到什么地址?

首先定位下这个函数,

image-20211016100845892

最终定位的地址就是 idata 区段的 100163CC

image-20211016100917422

0x03 有多少函数调用了gethostbyname

右键该函数名, Jump to xref to operated

image-20211016101846526 image-20211016102747030

Type 中的 r 是 read,读取的意思,函数首先要被cpu读取,才能够被调用, Type中的p是被调用的引用

这里就是5个函数一共调用了9次gethostbyname函数

0x04 将精力集中在位于 0x10001757 处的对 gethostbyname的调用,你能找出哪个DNS请求将被触发吗?

首先 g 跳转到 0x10001757 这个地址

image-20211016105841737

简单分析下这段汇编

首先, 将 off_10019040 赋值给 eax 寄存器,接着 地址位 + 0Dh(转换为10进制就是13),就是将地址往后偏移 13 位,然后push 入栈,接着 call 调用 gethostbyname 参数。

大概流程是这样,要找 被触发的dns请求,就一个个分析地址吧。先拿10019040 开刀,

image-20211016111139289

找到了一串字符串,跳转到这里看一看,找到完整的字符串 pics.praticalmalwareanalysis.com

image-20211016111223853

所以,off_10019040 是一个字符串指针,指向字符串的 [This is RDO]pics.praticalmalwareanalysis.com 的第一个字符,然后add 0Dh 后,偏移13位,指向字符p,最后 push入栈的值是 pics.praticalmalwareanalysis.com

0x05 IDA pro 识别了在 0x10001656 处的子过程中的多少个局部变量?

还是先跳转到这里,

image-20211016112449612

数一数,一共24个局部变量。

image-20211016112549484

0x06 IDA Pro 识别了在 0x10001656 处的子过程中的多少参数?

首先搞清楚参数的定义: 参数是调用这个函数的函数传递给被调用函数的值

很明显,这里只传入了一个 LPVOID类型的参数 lpThreadParameter

image-20211016114127422

0x07 使用string窗口,来在反汇编中定位字符串\cmd.exe /c。它位于哪?

string 窗口: shift+f12

image-20211016114608893

定位 cmd.exe 的地址

image-20211016114646468 image-20211016114708879

定位到地址: xdoors_d:10095834

0x08 在引用 \cmd.exe /c 的代码所在的区域发生了什么?

首先查找 cmd.exe 的引用源,

右键

image-20211016115942699 image-20211016115958950

下面就分析下这段汇编

image-20211016120034950

首先第一眼看到的是将 \\cmd.exe /c 字符串 push 入栈,

点击字符串,跳转

image-20211016122237140

看到这些字符串, Hi… Welcome… Machine Uptime… Machine IdleTime…Encrype Magic… Remote Shell Session…

大概也能猜到这是一个获取机器信息的远程shell会话

定位一下字符串的地址,看到还有 language /robotwork /mbase /mhost等等,获取的都是一些系统信息

image-20211016123356166 image-20211016123432861 image-20211016123516965

0x09 在同样的区域,在0x100101c8处,看起来好像dword_1008E5C4是一个全局变量,它帮助决定走哪条路径。那恶意代码是如何设置dword_1008E5C4的呢?(提示:使用dword_1008E5C4的交叉引用。)

老惯例,先跳,

image-20211016123850968

接着右键查看下交叉引用,或者 ctrl + x

3个指令,两个 cmp, 只有第一个 mov 指令改变了该地址值

image-20211016123935860

跳:

image-20211016124439180

来看一下这条指令的前后都做了些什么。

mov之前 call sub_10003695 ,那就先看这个函数地址到底返回了什么东西。

image-20211016152924271

先根据几个字符串猜测一下吧,VersionInformation/ dwOsVersionInfoSize/ Getversion/ dwPlatformId 首先猜测跟操作系统的版本信息有关。

比较关键的几步操作就是:

1
2
3
4
5
xor eax eax:	将eax清零,此前eax中存放的是 GetVersionExA 的返回值	

cmp: 将 ebp+VersionInformation.dwPlatformId 的值与2进行比较(VER_PLATFORM_WIN32_NT等于2的话,代表的系统为Windows 7, Windows Server 2008, Windows Vista, Windows Server 2003, Windows XP, or Windows 2000)

setz al: 当ZF标志被设定时,AL寄存器设为1

刚刚我们 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函数

跳:

image-20211016160830165

往下找 memcpy 函数,看到前面 aQuit 和 eaxpush入栈,所以这里memcpy这两个值

image-20211016161822937

接下来找robotwork,

image-20211016162717540

如果eaxrobotwork相同,返回0,0Ch12d,也是4(字节)*3(个),因为push后面跟的是立即数,所以一个数占4字节,然后offset也是4个字节,所以,一开始的push 9,和后面的两次push,加起来一共是3次,所以这里回收了这3个一共12字节的空间

test eax,eax 按位与操作,接着如果 eax为0,则ZF置为1JnZ跳转,eax0说明前面的memcmp比较的结果是相同,也就是如果前面两个数相同,则JZ跳转,JNZ不跳转

push [ebp+s]: 栈中,esp是栈顶指针,ebp是栈基址,esp地址减小,栈空间增大;ebp增加,ebp将向栈底偏移 。所以这里是将ebp向下s的指针地址压栈.

然后call sub_100052A2, 来看下这个地址。

image-20211016171726054

看样子是进行socket通信的函数,

仔细看下这个函数的代码,可以看到它获取了注册表的一些信息。

image-20211016172714890 image-20211016173136985

书上说应该是这两个键值:

SOFTWARE\Microsoft\Windows\CurrentVersion\WorkTime

SOFTWARE\Microsoft\Windows\CurrentVersion\WorkTimes

我在我的计算机上去对应的注册表目录找,并没有找到这两个键值,猜测可能是以前的Windows版本

0x0B PSLIST导出函数做了什么?

打开导出表

image-20211016173912607

可以看到这个函数有两条执行路径,判断的条件是由sub_100036C3决定的

image-20211016174334431

来看下 sub_100036C3函数

image-20211016174743509
1
2
3
4
5
6
7
8
9
10
call    ds:GetVersionExA ; 调用函数查看系统版本
cmp [ebp+VersionInformation.dwPlatformId], 2 ; 这个我们上面说过,如果等于2,是那些windows版本
; 包括`Windows 7, Windows Server 2008, Windows Vista, Windows Server 2003, Windows XP, or Windows 2000`
jnz short loc_100036FA ; 如果不想等,则跳转结束
cmp [ebp+VersionInformation.dwMajorVersion], 5 ; 5代表特殊版本的windows
jb short loc_100036FA ; 无符号比较,如果[ebp+VersionInformation.dwMajorVersion]小于5跳转
push 1
pop eax
leave ; High Level Procedure Exit
retn

其中,cmp [ebp+VersionInformation.dwMajorVersion], 5 中的5是下面的5

image-20211016175043943

如果是过低的版本,就直接跳转结束,如果是符合要求的版本,则返回 1

然后就是比较跳转,如果eax0test之后,ZF1,然后JZ跳转

image-20211016183846582

如果eax不为0ZF不为0,然后JZ不跳转,也就是如果版本符合要求,就不跳转(跳转之后是直接结束),ZF 置为0

如果是不跳转的话,push了一个字符串进去,然后调用strlen返回字符串的长度在eax中,然后test eax, eax

如果eax为0ZF置为1,JNZ不跳转
反之如果不为0JNZ跳转

image-20211016184326470

假设eax为0,JNZ不跳转,我们走一下这条线

call sub_10006518

可以看到这个地址调用的一个函数 CreateToolhelp32Snapshot

CreateToolhelp32Snapshot函数为指定的进程、进程使用的堆[HEAP]、模块[MODULE]、线程[THREAD])建立一个快照[snapshot]。

简单来说这个函数用来获取进程列表。通过send 将进程列表通过 socket 发送。但是我没有找到 send 函数………..

image-20211016184715151

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

首先跳: sub_10004E79

image-20211017103401333

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

image-20211017103550428

image-20211017103602010

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

image-20211017103635493

可以看出sub_10004E79函数调用的有GetSystemDefaultLangIDsprintfsub_100038EEstrlen,而sub_100038EE调用了sendmallocfree__imp_strlen,然后GetSystemDefaultLangID是获取系统的默认语言的函数,send是通过socket发送信息的函数。因此可以右键函数名,重命名为 send_languageId

1
ps: 这种快速分析是一种获得对二进制文件高层次视图的好方法,在分析二进制文件时非常有用

0x0D DllMain直接调用了多少个Windows API?多少个在深度为2的时候被调用?

有两种思路:

1
2
1.逐一查看Dllmain函数的代码,在代码中看api调用
2.利用交叉引用图

先定位到 Dllmain的位置

image-20211017104627152

像前文一样,打开交叉引用图

默认配置后会…一言难尽……

image-20211017104803555

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

image-20211017105328465

如下就是Dllamin所调用的api函数

strncpy_strnicmpCreateThreadstrlen

但是很明显没有显示完全,省略了很多,可以把Recursion depth设置为2

image-20211017105348609

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

image-20211017105645298

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

先跳后看

image-20211017105852977

1
2
3
4
5
6
7
8
9
10
11
.text:10001341                 mov     eax, off_10019020 ; "[This is CTI]30"
.text:10001346 add eax, 0Dh
.text:10001349 push eax ; String
.text:1000134A call ds:atoi
.text:10001350 imul eax, 3E8h
.text:10001356 pop ecx
.text:10001357 push eax ; dwMilliseconds
.text:10001358 call ds:Sleep
.text:1000135E xor ebp, ebp
.text:10001360 jmp loc_100010B4
.text:10001360 sub_10001074 endp

从注释[This is CTI]30中可以猜测,睡眠30s

分析下这段汇编代码

1
2
将 off_10019020 放入寄存器中,向后偏移 0Dh(13d),push eax 入栈,调用 ds:atoi 函数,接着 eax 的值乘 3E8h,  pop ecx 出栈, 再将eax push 入栈, 调用 sleep ,然后 清零 ebp, jmp到loc_100010B4。
很明显,sleep 函数的参数是 eax,跟踪下eax,首先是从off_10019020这里传进来,接着做atoi函数的参数,然后atoi函数的返回值再乘3E8h,push入栈,作为sleep的参数

off_10019020

image-20211017111714716

偏移 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个参数

image-20211017112652130

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

image-20211017120533291

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

image-20211017120630212

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

image-20211017121841865

最后找6,对应的是protocol

image-20211017122002778

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

image-20211017122125015

这三个参数大致含义:

1
2
3
AF_INET 用于连接连接对象是IPv4时(对应的IPv6用的是 AF_INET6)
SOCK_STREAM 用于连接方式使用TCP时候(对应的UDP对应的是SOCK_DGRAM)
IPPROTO_TCP 用于继续指明传输的方式是TCP(对应的UDP是IPPROTO_UDP)

因此这个 socket会被配置为基于IPV4 的TCP连接(常被用于HTTP)

关于socket函数的更多资料可以去 MSDN 上查

0x10 使用MSDN页面的socket和IDA pro中的命名符号常量,你能使参数更加有意义吗?在你应用修改之后,参数是什么?

emmm,修改的过程就是上文分析的过程吧……

这里附上链接:socket function (winsock2.h) - Win32 apps | Microsoft Docs

image-20211017145730535

0x11 搜索in指令(opcode 0xED)的使用。这个指令和一个魔术字符串VMXh用来进行VMware的检测。 在这个恶意代码中被使用了吗?使用对执行in指令函数的交叉引用,能发现进一步检测VMware的证据吗?

搜索 in 指令的话,通过选择菜单的 Search->Text,然后输入in (或者 Search -> Sequence of Bytes,然后搜索 in 指令的 opcode,也就是ED)。

image-20211017151029376

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

image-20211017151048294

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

image-20211017152038722

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

image-20211017152312343

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

image-20211017152836872

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

image-20211017153549370

0x12 将你的光标跳转到0x1001D988处,你发现了什么?

先跳:

看到是一些巴拉巴拉字符,不具有可读性。

image-20211017154654565

0x13 如果你安装了IDA Python插件(包裹IDA Pro的商业版本的插件),运行Lab05-01.py,一个本书中随恶意代码提供的IDA Pro Python脚本,(确定光标是在0x1001D988处。)在你运行这个脚本后发生了什么?

大概可以看到这个脚本实现的是解密的操作,通过异或。

image-20211017160806610

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

image-20211017161309277

总结

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