第一次使用IDA静态分析文件,目的旨在熟悉IDA这款工具,文章很多不足,请大佬斧正。
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 处,哪个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 识别了在 0x10001656 处的子过程中的多少个局部变量?
还是先跳转到这里,
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 恶意代码是如何设置dword_1008E5C4的呢?
接着右键查看下交叉引用,或者 ctrl + x
3个指令,两个 cmp, 只有第一个 mov 指令改变了该地址值
来看一下这条指令的前后都做了些什么。
在mov之前 call sub_10003695 ,那就先看这个函数地址到底返回了什么东西。
先根据几个字符串猜测一下吧,VersionInformation/ dwOsVersionInfoSize/ Getversion/ dwPlatformId 首先猜测跟操作系统的版本信息有关。
比较关键的几步操作就是:
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处的子过程中,分析下 robotword 字段
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函数
callds: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跳转
push1
pop eax
leave ; High Level Procedure Exit
retn
其中,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的交叉引用图
首先跳: sub_10004E79
使用图模式绘制交叉引用图
可以看出sub_10004E79函数调用的有GetSystemDefaultLangID、sprintf、sub_100038EE、strlen,而sub_100038EE调用了send、malloc、free、__imp_strlen,然后GetSystemDefaultLangID是获取系统的默认语言的函数,send是通过socket发送信息的函数。因此可以右键函数名,重命名为 send_languageId
ps: 这种快速分析是一种获得对二进制文件高层次视图的好方法,在分析二进制文件时非常有用
0x0D DllMain直接调用了多少个Windows API?多少个在深度为2的时候被调用?
有两种思路:
1.逐一查看Dllmain函数的代码,在代码中看api调用
2.利用交叉引用图
先定位到 Dllmain的位置
像前文一样,打开交叉引用图
默认配置后会…一言难尽……
这里修改下Recursion depth(递归深度),改为1
如下就是Dllamin所调用的api函数
strncpy、_strnicmp、CreateThread、strlen
但是很明显没有显示完全,省略了很多,可以把Recursion depth设置为2
也是一个很大的图啊…放大看吧,太多了,这里就不一一列举了
0x0E 在0x10001358处,对Sleep函数的调用,参数是多少?
先跳后看
.text:10001341 mov eax, off_10019020 ; "[This is CTI]30"
.text:10001346 add eax, 0Dh
.text:10001349 pusheax ; String
.text:1000134A callds:atoi
.text:10001350 imuleax, 3E8h
.text:10001356 pop ecx
.text:10001357 pusheax ; dwMilliseconds
.text:10001358 callds:Sleep
.text:1000135E xor ebp, ebp
.text:10001360 jmp loc_100010B4
.text:10001360 sub_10001074endp
从注释[This is CTI]30中可以猜测,睡眠30s
分析下这段汇编代码
将 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的参数
偏移 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函数的原型:
SOCKET socket(int af, int type, int protocol);
而常见的创建套接字的参数
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); //创建TCP套接字
根据入栈规则,先进后出,先找2. 根据注释可以猜测,找对应的AF,所以 2处传递的参数就是 AF_INET
接着看1处的参数,在socket中对应的是type
最后找6,对应的是protocol
所以整个socket函数的传参是这个顺序
这三个参数大致含义:
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 使用socket和IDA中命名符号常量,参数会有更多意义吗?
在你应用修改之后,参数是什么?
这里修改的过程就是上文分析的过程……不再多述...
这里附上链接:socket function (winsock2.h) - Win32 apps | Microsoft Docs
0x11 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 在你运行某个脚本后发生了什么?
大概可以看到这个脚本实现的是解密的操作,通过异或。

加载脚本后,字符串被解密 xdoor is this backdoor
总结
分析恶意样本的过程是比较枯燥的,地址需要来回跳转,逻辑性要求比较高,还需要对底层汇编很熟悉,笔者是第一次使用ida分析恶意样本,学到了很多,也了解到很多不足,长路慢慢~
个人博客:0range-x.github.io
欢迎师傅们一起交流~
- 本文作者: 0r@nge
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/809
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!
































































