记一次IDA分析恶意DLL文件
2021-10-30 23:37:10 Author: 0range-x.github.io(查看原文) 阅读量:105 收藏

本文首发于奇安信攻防社区,原文链接: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分析恶意样本,学到了很多,也了解到很多不足,长路慢慢~


文章来源: https://0range-x.github.io/2021/10/30/ida%E5%88%86%E6%9E%90dll/
如有侵权请联系:admin#unsafe.sh