第一次使用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
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!