Lab 5-1
问题
1.DllMain的地址是什么?
解答: 这个我们用IDA Pro
打开来查找,因为最新的IDA Pro
不支持我们运行病毒那个虚拟机的版本(xp pro 32),所以下面的分析都是在win7
上的
打开IDA Pro
之后就会提示你是否打开一个叫proximity browser
的东西,然后打开这个东西就可以看到DllMain
的位置
如果没有跳出来,这里可以点开这个窗口
然后右键选择Text view
就可以看到地址了
注意标黄的地方
标黄的地方都是一样的地址,不是IDA
的bug,上面那些都是一些IDA
生成的一些注释,真正在这个地址上的只有
mov eas, [esp+fdwReson]
这一行
所以DllMain
的地址就是在.text
节的0x100D02E
处
2.使用Imports窗口并浏览到gethostbyname,导入函数定位到什么地址?
解答: 我们打开Imports
窗口
然后搜索这个
然后双击这个找到的函数也行,其实Imports
这里已经标明了地址了
于是这个问题的答案就是0x100163CC
,但是我们可以用双击去原文中查找的方式
这里可以看出这是在.idata
节的0x100163CC
处
最后的答案就是gethostbyname
在.idata
节的0x100163CC
处
3.有多少函数调用了gethostbyname?
解答: 这个按照说中说的做就行了,但是注意一点,不是在Imports
窗口按Ctrl x
是在IDA View
窗口里面,也就双击Imports
窗口里面gethostbyname
函数跳出来的那个窗口
这是右击gethostbyname
之后的显示,然后点击Jump to xref to oprand...
这个选项,会跳出这个
类型r
是被”读取”的引用,CPU必须先读取这个导入项,再调用它,类型p
是被调用的引用
这里显示了18行,但是并不是18个函数调用了这个gethostbyname
,注意看,好多函数都是一样的(+后面的是偏移地址)
然后我们自己数一下就会发现,其实只有五个函数引用了gethostbyname
然后注意看就是每个r
的类型,总有一个p
的类型,所以被引用的次数就是18/2=9次
所以答案就是五个函数引用了九次
4.将精力集中在位于0x10001757处的对gethostname的调用,你能找到哪个DNS请求将被出发吗?
解答: 我们先跳过去看看
按一下g
就跳出这个窗口了
然后输入0x10001757
这个地址,就会跳了
然后我们查看这个代码
call函数默认将栈顶的一个值作为函数的参数传递给函数,然后栈顶现在是eax push进去的
所以我们找eax的值,然后找到了mov off_10019040
这个,然后查看off_10019040
然后我们可以跳到off_1001940
定义的地方,看到了这个字符串(蓝色的是IDA标识的)
[This is RDO]pics.praticalmalwareanalys
然后这里大家也看到了,ida显示不全,我们继续追查
双击后面的aThisIsRdoPics
就可以看到完整的字符串了
[This is RDO]pics.praticalmalwareanalysis.com
然后我们回过头来看当时的那段代码
将off_10019040
中的值给了eax
之后(off_10019040
是字符串指针)
学过C语言的同学都应该知道指针
现在这个off_10019040
指针指向的是字符串的第一个字符,也就是符号[
0Dh
转换成十进制是13
字符串是这个[This is RDO]pics.praticalmalwareanalysis.com
+
在指针里面的意思你可以理解成是指针向后移动,这个C语言里面有讲,然后我们讲指针往后移动13
位
最后指针指向的是p
这个字符
所以最后push进栈的值是pics.praticalmalwareanalysis.com
于是我们可以得出这个问题的答案就是会对
pics.praticalmalwareanalysis.com
进行DNS
解析
5.IDA pro识别了在0x10001656处的子过程中的多少个局部变量?
解答: 老规矩,跳先
然后会发现这些被IDA识别的变量
然后简单的一数,是24
个,和书中的计算结果有点差异,然后我数了一下书里的截图,是24
个,这里就不纠结这个了,方法知道了就行
6.IDA pro识别了在0x10001656处的子过程中的多少个参数?
解答: 参数是调用这个函数的函数传递给被调用函数的值,搞清楚这个就好办了
我们可以看见这里IDA
识别的结果是传入了一个LPVOID
类型的lpThreadParameter
所以答案就是识别了一个参数
7.使用string窗口,来在反汇编中定位字符串\cmd.exe /c。它位于哪?
解答: 这个我们按SHIFT F12
就调出String
窗口了
然后查看这个字符串,双击就行了
然后
所以答案就是在xdoors_d:10095B34
处
8.在引用\cmd.exe /c的代码所在的区域发生了什么?
解答: 我们右键查找aCmd_exeC
引用
然后跳到这个引用的地方
然后我们可以发现在这里,字符串被压入了栈中
然后分析这个函数会发现开头这个字符串
'Hi,Master [%d/%d/%d %d:%d:%d]
然后可以在这个大概位置上
发现一些比较函数,然后点开之后会发现
这些有关于系统信息和系统操作的字符串
按书中的观点这是一个远程shell会话函数
9.在同样的区域,在0x100101c8处,看起来好像dword_1008E5C4是一个全局变量,它帮助决定走哪条路径。那恶意代码是如何设置dword_1008E5C4的呢?(提示:使用dword_1008E5C4的交叉引用。)
解答: 先跳到这个地方,然后邮件查看交叉引用
其他两处都是cmp
函数
只有第一处是mov
改变了它的值
我们跳到这个地方查看
可以看到这个dword_1008E5C4
的上面,有一次sub_10003695
函数的调用,而汇编中,函数调用的返回值存储在eax
中
然后我们查看sub_10003695
这个函数到底返回了什么
我们双击这个函数就可以跳到这个函数的定义了
然后这里注意一下,就是双击会跳到.text:100036C2
这个位置的sub_10003694
,但是其实这个位置是函数的结束,上面.text:10003695
处还有一个sub_10003695
(发现没有,其实IDA命名函数是用他的位置加上sub来的)
sub_10003695 proc near ; CODE XREF:
VersionInformation= _OSVERSIONINFOA ptr -94h
push ebp
mov ebp, esp
sub esp, 94h ; 将esp增加94h也就是148d,37个字节
lea eax, [ebp+VersionInformation] ; 将ebp+VersionInformation的地址赋值给eax
mov [ebp+VersionInformation.dwOSVersionInfoSize], 94h ; 将这个地址的值赋值成94h
push eax ; lpVersionInformation
call ds:GetVersionExA ; 在一个OSVERSIONINFO结构中载入与平台和操作系统有关的版本信息
; 上面有定义这个OSVERSIONINFO结构
xor eax, eax ; 将eax置0(因为GetVersionExA的返回值在eax中)
cmp [ebp+VersionInformation.dwPlatformId], 2 ; 这里将dwPlatformId和2进行比较
; 为什么和2比较我们下面解释
setz al ; Set Byte if Zero (ZF=1) ;
leave ; High Level Procedure Exit
retn ; Return Near from Procedure
sub_10003695 endp
我们可以来查看一个OSVERSIONINFOA
的结构
查看dwPlatformId
中的可能的值
因为在windows系统中,VER_PLATFORM_WIN32_NT
代表的值是2
对windows内核不是很了解,但是从文档来看,VER_PLATFORM_WIN32_NT
等于2的话,就是代表系统是
Windows 7, Windows Server 2008, Windows Vista, Windows Server 2003, Windows XP, or Windows 2000
setz
意思是当ZF
标志被设定时,AL
寄存器设1
因为刚刚我们cmp了两个数,所以如果两个数相同,ZF=1
,然后setz
,AL
被设置为1
,反之不相同的话,AL
被设置为0
(AL
是EAX
的低8位,对应的AH
是EAX
的高8位)
一般来说,会运行这个机器的都是上面那几种windows机器,所以这里比较一般都是会相同的,所以,AL
被设置成了1
,然后就是用retn
返回了eax
中的值
但是为什么书上说的是返回的是1
,因为我们前面执行过
xor eax, eax
eax
现在是被异或都成了0
(eax
有16位)
我们现在分析一下,01234567
代表的是高8位(ah
),剩下的代表的是低八位(al
)
先是
xor eax, eax
这个运行完之后的eax
01234567 89ABCDEF
-------- --------
00000000 00000000
然后我们接着运行
cmp [ebp+VersionInformation.dwPlatformId], 2
setz al
之后的eax
,89ABCDEF
代表的al
被置为了1
,其余的ah
保存不变
01234567 89ABCDEF
-------- --------
00000000 00000001
然后这个值其实就是十进制的1
所以sub_10003694
的返回值是1
于是
mov dword_1008E5C4, eax
最后的dword_1008E5C4
的值就被赋成了1
所以这个全局变量在程序运行的时候一直保持的是1
10.在位于0x1000FF58处的子过程中的几百行指令中,一系列使用memcmp来比较字符串的比较。如果对robotword的字符串比较是成功的(当memcmp返回0),会发生什么?
解答: 这个函数我们刚刚看过,但是没细入分析过
整个从0x1000FF58
开始的函数,第一个使用memecp
是这里(标黄的那里)
一开始是比较了quit
和eax
的值,因为这两个值被压入了栈中
然后我们找到robotwork
,慢慢往右下角拖就是了
它首先压入了一个robotwork
字符串指针,然后压入了eax
,然后call memcmp
,如果两个数相同,返回0
,然后
add eap, 0Ch
0Ch
是12d
,也是4(字节)*3(个)
,因为push
后面跟的是立即数,所以一个数占4
字节,然后offset
也是4
个字节,所以,一开始的push 9
,和后面的两次push
,加起来一共是3次,所以这里回收了这3个一共12字节的空间
test eax, eax
如果eax
为0
,则ZF
置为1
,JZ
跳转,eax
为0
说明前面的memcmp
比较的结果是相同,也就是
如果前面两个数相同,则JZ
跳转,JNZ
不跳转
然后问题是当字符串比较成功,memcmp返回0会发生什么
会发生的是,JNZ
不跳转,程序继续按从上到下的顺序执行,下面要执行的就是
push [ebp+s] ; 将ebp(esp是栈顶指针,ebp是栈基址)地址增加s
; (栈中,esp地址减小,栈空间增大,ebp增加,ebp将向栈底偏移)
; 将ebp向下s的指针地址压栈
call sub_100052A2
jmp short loc_100103F6
然后就是调用了sub_100052A2
这个函数
这个函数是这样的
其他的都可以不管,看最下的地方
这个函数查询了
SOFTWARE\Microsoft\Windows\CurrentVersion
这个注册表的地方,用的是RegOpenKeyExA
其实这里也只查询了这个,没有查询书中说的那个WorKTime
,在这个注册表目录下,甚至就没有这个项
然后跳转到这里
书中说
将这一信息返回给
push [ebp+s]
处传给该函数的网络socket
s是这里定义的
但是不知道是怎么把这信息返回给上面的的参数的
11.PSLIST导出函数做了什么?
解答: 我们打开导出函数表
这个函数有两条执行大路径
然后执行的选择取决于这个sub_100036C3
这个函数返回的是1
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
代表什么意思
代表的就是这么几个版本的windows
所以这个函数的作用就是具体判断目标主机的系统版本,如果是过低的版本,就直接跳转结束
如果是符合要求的版本,则返回1
然后就是比较跳转,如果eax
为0
,test
之后,ZF
为1
,然后JZ
跳转
如果eax
不为0
,ZF
不为0
,然后JZ
不跳转
也就是如果版本符合要求,就不跳转(跳转之后是直接结束)
不跳转之后,push
了一个字符串进去,然后调用strlen
返回字符串的长度在eax
中,然后test eax, eax
如果eax(字符串长度)
为0
,ZF
置为1
,JNZ
不跳转
反之如果不为0
,JNZ
跳转
假设eax
为0
,JNZ
不跳转,我们走一下这条线
那么下一个执行完push
之后,就是执行call sub_10006518
从汇编中可以看出,这个sub_10006518
执行了这个函数CreateToolHelp32Snapshot
,这个函数是
CreateToolhelp32Snapshot函数为指定的进程、进程使用的堆[HEAP]、模块[MODULE]、线程[THREAD])建立一个快照[snapshot]。
然后我们看跳转那条线
跳转这条线上(蓝色
),它将字符串压入栈之后,又压入了一个0
,然后调用了sub_1000664C
这个函数
这个函数依旧还是调用了CreateToolHelp32Snapshot
这个函数,然后还有GetLastError
和sprintf
这些个函数
而sprintf
函数的输出是
所以我们不难想,GetLastError
是用于判断CreateToolHelp32Snapshot
这个函数有没有执行成功的
如果失败,就执行endp(也就是图中标注的exit_0)
如果成功,则执行retn(也就是图中的return_0)
按照书中的说法
这两条代码路径都通过send将进程列表通过socket发送
但是我是没找到这个send
和socket
这两个函数
唯一能扯上一点关系的就是这里有个
push [ebp+s]
但是这个s
是如何看出就是代表socket
或者send
呢,不得而解
12.使用图模式来绘制出对sub_10004E79的交叉引用图。当进入这个函数时,哪个API函数可能被调用?仅仅基于这些API函数,你会如何重命名这个函数?
解答:: 按照书上的做法
我们跳到指定位置之后,按照这个菜单,点击
然后出来这个
直接默认点确定就行了
然后就出现这个了
我们关心的主要是下面这三层
可以看出这个函数sub_10004E79
主要调用的有
GetSystemDefaultLangID
和sprintf
和sub_100038EE
和strlen
而sub_100038EE
主要调用了send
和malloc
和free
和__imp_strlen
然后GetSystemDefaultLangID
是获取系统的默认语言的函数,send
是socket
发送的函数
由此我们可以按照书上的做法,将这个函数重命名为send_languageID
13.DllMain直接调用了多少个Windows API?多少个在深度为2的时候被调用?
解答: 搜索DllMain找到这个函数的位置
然后用上面的方法打开视图
会发现视图极其庞大。。。(因为默认Recursion depth为-1)
我们重新打开,将这里改为1
然后就可以看到调用深度为1
的所有函数了
所以,DllMain
在深度为1
直接调用的API
也就是strncpy
、_strnicmp
、CreateThread
、strlen
这么几个
要知道全部的 API
,那就得一个一个数了。。。
同样的方法查看深度为2
时候的调用(很大的调用表)
14.在0x10001358处,有一个对Sleep(一个使用一个包含要睡眠的毫秒数的参数的API函数)的调用。顺着代码往后看,如果这段代码执行,这个程序会睡眠多久?
解答: 我们跳到那个地方看看
Sleep
函数的参数是压入栈的eax
,而这个eax
从哪里来,是call ds:atoi
的返回值,再乘以3E8h
,最后就被压入了栈中,供Sleep
做入参
那atoi
的参数是从前面的push eax
中来的,eax
的根源是从off_10019020
传进来的
而off_10019020
的值放在这里
[This is CTI]30
然后执行了这个(这个前面说过,指针的+
多少相当于指针往后偏移多少
add eax, 0Dh
0Dh
相当于十进制的13d
往后偏移13
个字节,最后指针指向3
(从0
开始数)
然后将指向3
的指针压入栈,其实现在指针就相当于这样
char origin_str[] = "[This is CTI]30";
char *p_str = origin_str;
p_str = p_str + 13;
最后输出的p_str
就是30
,然后调用这个atoi
,它是把字符串(char)转换成整型(int)的函数
call ds:atoi
然后这个函数的输出就是(int)30
imul eax, 3E8h
3E8h
就是1000d
,imul
是乘,然后30
乘1000
就是30000(3w)
然后我们从MSDN
确定一下单位
这里显示是以milliseconds
为单位,就是毫秒,30000
ms = 30
s
所以这个函数会休眠30
s
15.在0x10001701处是一个对socket的调用。它的3个参数是什么?
解答: 我们还是先跳到这个函数的位置
这里是将6
、1
、2
压入了栈中,然后我们不知道具体的配置信息,按照书中的做法
然后就会跳出一大堆的东西
然后我们查找一下关于socket
有关的函数,这时候如果你对socket
的相关函数的参数不是很清楚的话,建议还是查查MSDN
我们都知道栈是先进后出的类型,先压入的数据,其实是最后才调用的
最后压入的是2
这个数(汇编不像其他高级语言,参数的顺序可以颠倒),然后2
应该对应的就是af
这个参数
我们就点开2
的对话框,然后去MSDN
查对应的值,一般输入都有的头就可以了
这里是AF
,一般这种对应表里面,一个函数用的到的,肯定只会出现一次,果不其然,我们搜AF
之后,就会发现,这里之后两个AF
开头的
除了第一个的AF_INET
之外,还有一个AF_OP_COMM
,其实这个并不是socket
函数的参数
太长了截不完,不信的同学可以自己上MSDN
去查(手动滑稽)
然后我们就可以将这个参数重命名一下
大概看起来就像这样,然后我们按这种方法来找(其实双击你找到的那个对话框里面的参数名就可以重命名了)
然后第二个压入栈中的1
,在socket
中对应的是type
这里都有的都是带sock
的
然后注意就是不要和socket
混淆了,这里是sock
,然后就是为什么会出现两个一摸一样的SOCK_STREAM
,其实一个东西
一个是MS SDK
另一个是Virtual C++ 6.0
,其实都是一样的,然后我们重命名一下
最后一个6
,它是第一个压入栈中的值,但是却是最后调用的
他对应的是protocol
这里只有搜IPPROTO
就可以了
一样的方法
然后最后把这些符号常量重命名之后,汇编代码成了这样的
我们的函数入参就一目了然了,稍微解释了一下这三个参数的意思
AF_INET 用于连接连接对象是IPv4时(对应的IPv6用的是 AF_INET6)
SOCK_STREAM 用于连接方式使用TCP时候(对应的UDP对应的是SOCK_DGRAM)
IPPROTO_TCP 用于继续指明传输的方式是TCP(对应的UDP是IPPROTO_UDP)
16.使用MSDN页面的socket和IDA pro中的命名符号常量,你能使参数更加有意义吗?在你应用修改之后,参数是什么?
解答: 就是上面刚刚分析的那些参数。。。感觉分析多了
17.搜索in指令(opcode 0xED)的使用。这个指令和一个魔术字符串VMXh用来进行VMware的检测。 在这个恶意代码中被使用了吗?使用对执行in指令函数的交叉引用,能发现进一步检测VMware的证据吗?
解答: 按照书上的做法我们试试
这里搜索要学会使用技巧,不然出一大堆没用的东西
然后我们可以发现,出了一些不是运算的注释和名称之外,只剩下标黄这个东西可以值得点开看看,如果是在不确定,可以一个一个点开看看
我这一版本的IDA
并没有标注这个二进制的意思,书上是标注出来的
我们右键,会显示出各种不同的编码结果
可以发现有个VMXh
,但是书中说经过一个比较之后,后面会发现一个Found Virtual Machine
字符串,但是目前暂时没发现这个字符串
18.将你的光标跳转到0x1001D988处,你发现了什么?
解答: 这个我们跳了看看就知道了
发现一些不知道什么鬼的东西,然后按照书中说的,我们运行这个python
代码看看
运行这个脚本的前提是装了python
19.如果你安装了IDA Python插件(包裹IDA Pro的商业版本的插件),运行Lab05-01.py,一个本书中随恶意代码提供的IDA Pro Python脚本,(确定光标是在0x1001D988处。)在你运行这个脚本后发生了什么?
解答: 运行这个python
脚本之后,其实没啥变化,如果你不仔细看的话
其实字符已经变了
然后现在这串字符串已经变可读了,反正就是在那一瞬间,哈哈哈
20.将光标放在同一位置,你如何将这个数据转成一个单一的ASCII字符串?
解答: 这个其实前面的就是ASCII码,也就是db后面的
可以在设置里面开自动注释来显示
21.使用一个文本编辑器打开这个脚本。它是如何工作的?
解答: 如下
本文完