0x00 前言
在上一小节,我们已经通过纯静态分析的方式分析了一个Downloader的恶意样本。在这一节,我们通过第二个Downloader对静态分析进行一些巩固。
分析的样本hash:0b244c84e6f86a6c403b9fb5e98df8cb
同样的,我已经将该样本上传到了app.any.run,可以访问如下地址进行下载:https://app.any.run/tasks/26a36b76-1092-484e-8a5c-9886690c1868
解压密码依旧是infected
0x01 恶意样本概括
在第一小节,我们已经通过IDA对一个简单的Downloader(下载器)程序进行了静态分析,在本节中,我们将继续对第二个Downloader进行静态分析,一方面,可以巩固静态分析的知识,另一方面,在本节中将会介绍有关IDA更多的一些用法。
首先,介绍一些关于恶意样本的基础相关知识,也算是为恶意样本分析做准备。
样本分类
主要分为
PE文件(Windows平台可执行文件,如exe和dll文件)
ELF文件(Linux平台下的可执行文件)
office文档文件(注意2007版本是个分界点,07版本之前的文档文件本质是二进制文件,之后本质是压缩包)
ps1文件(powershell脚本)
js文件(JavaScript脚本)
VBS文件(vbs脚本)
bat文件(windows批处理文件)
chm文件(微软帮助文档)
lnk文件(链接文件)
hwp文件(韩国office,类似于我国的wps)
…
目前比较常见的好像就是这些,在Windows平台下,PE文件是攻击主流,其他的所有恶意样本都统称非PE,通常情况下,现在的样本的流行使用非PE加载PE的方式来攻击,原因是非PE的查杀相对PE来讲较为困难,攻击者可以通过非PE文件在用户的计算机上设置计划任务等操作,来下载后续的PE文件到本地并执行。
主要分为
- Downloader(下载者) 此类木马的主要功能就是下载后续的攻击文件到本地继续执行,此类样本的特点就是小,通常来说,代码也比较简单,就是通过访问一个链接,下载文件保存到本地,然后通过一定的方法启动这个文件。
- Dropper(释放器),此类木马的主要功能是释放并执行后续的攻击文件。就攻击者可能会将恶意的程序加密成为一段数据,然后放到Dropper中,由Dropper解密释放并执行这个文件,这样做的目的是可以起到一定的免杀效果。
- 窃密木马,此类木马的主要功能是收集本地主机的一些机密信息并打包上传,比如收集各大浏览器的缓存目录、隐私目录等,拿到用户保存的密码信息。或是收集本地主机的一些文档文件,最后加密上传到攻击者的邮箱服务器、FTP服务器等。
- 远控木马,远控木马拥有对本地主机的控制权,可以随时通过下发控制指令实现不同的功能。属于在分析中很常见的木马,目前主流的远控木马大多都是由C#编写,并且很多是开源的商业马,在github都可以找到源码实现.
- Ransom(勒索病毒)
- Adware(广告类恶意软件),此类软件通常不像木马那样有侵犯用户隐私的危害,大多数是用于推广广告、篡改用户浏览器主页等。
- 感染型木马,感染型木马较为特殊,遇见的也比较少,可以等后面找一个实际的感染型木马分析。
样本分析的基本流程
总的来说,样本分析是有一个固定套路的。
通常来说流程如下:
- 文件查壳(看文件是否有壳,有壳则需要脱壳)
- 行为检测(通过行为检测工具,跑出样本的大致行为,通过此步骤,可以对样本的基本功能有个概要的了解,从而确定分析方向,比如在行为检测的时候,监控到样本运行后会访问某个域名,然后我们可以对这个域名进行查询,看该域名是否已经有公开的情报,是否被归类到了某个家族,确定这些信息之后,我们大概可以了解到本次分析的样本的基本类型,比如是下载者、是窃密的、还是远控的。)
- 静态分析(静态分析分为导入表分析、字符串分析、代码分析、在导入表分析和字符串分析的时候,可以结合行为检测得到的结果)
- 动态调试(通常来讲,阅读IDA的代码,是分析样本最快的情况,但有时候,一些较为复杂的解密运算、或是样本开辟了新内存等功能,需要使用调试器来协助分析)
- 关联分析(在样本的基本功能分析完成之后,我们需要对样本做关联分析,通常来讲,大部分的样本都是有通信行为的,在关联分析的时候,通常就是以请求的域名、ip等信息进行关联)
- 溯源分析(更深层次的对攻击者进行一个挖掘)
- 总结分析报告
当然,对待不同类型的样本,也有着不同的处理方式。在接下来的文章中,我将先分别结合实际的样本介绍几种分析方法,最后再按照这个流程,进行详细的分析。
0x02 IDA分析
现在我们通过IDA加载这个样本
将样本拖入到32位的IDA中,会有如下窗口
这里可以看到,IDA自动识别到了该程序是一个PE文件,直接单击OK
样本通过IDA加载之后,会默认停在程序的入口点,这里是WinMain函数开头的地方。
我们在右边的代码窗口中按空格键,将流程图展示的代码转换为汇编展示的代码
导入表分析
同样的,先分析程序的导入表,打开名为IMPORTS的窗口
程序一共有57个导入表,大多数都是正常的,只是在最后看到了如下的导入表信息
通过对WindowsAPI的熟悉,我们可以知道InternetReadFile用于从互联网读取数据到内存,也就是可以实现下载功能
我们可以双击进入该函数,然后在InternetReadFile上按下X,查看交叉引用
从交叉引用的窗口中我们可以看到,前面两个引用地址都是sub_401170+xxx ,最后一个引用地址为.rdata:00407570
所以我们可以得知,该函数是在sub_401170调用了两次,由于rdata段属于资源段,这里不是代码区,所以不用管最后这个地址。
既然只在在sub_401170函数中调用,我们就直接选中第一条,然后点击ok跳转过去
然后往上滑动,找到这个函数开始的地方
可以看到,的确是sub_401170函数,且从右边绿色的字体中我们可以得知,该函数是直接在WinMain函数中调用的,我们继续按X交叉引用,回到在WinMain中的调用处:
回来发现是在00401B24这行调用的
此时我们可以通过两种方式进行标注
1是直接修改sub_401170函数的名字,使其有一定的标识度
修改函数名的话,我们直接选中sub_401170,然后按下 n 键即可重命名
我这里将其重命名为了Rename_InterNet_relevant,表示这个函数是我重命名的,然后是网络请求相关的函数。
接着单击ok,接下来sub_401170这个函数就被重命名为了Rename_InterNet_relevant
第二种方式是给这行代码添加注释
鼠标光标选中00401B42这一行,然后按下英文的 ; 键
然后在弹出的对话框中输入注释信息,点击ok
由于导入表中已经没有更多的可疑函数了,接下来,我们分析一下strings表
Strings分析
可疑通过快捷键shift + f12 打开strings表
strings一共有41个字符串,其中大部分是编译器所使用的,在strings表尾部可以看到有几个比较可疑的字符串。
我们在分析字符串的时候,是为了尽可能的查找到特殊的字符串。
在该样本的strings表中,开头的那几个字符串太短,也几乎没有什么意义所以可以暂时忽略,中间部分大部分是编译器生成的,或者使用到的公有库文件中的。一般来说,程序中带了两个下划线开头的,都是属于系统的内容,比如__GLOBAL_HEAP_SELECTED:
然后像”R6028- unable to initialize heap”这种,也属于正常的字符串:
再往后看,是程序使用到的一些API名或模块名。
最后部分出现了下载链接,然后本地的一个路径。
很明显,C:Program FilesuuseeUUSeePlayer.exe这个不是一个正常的程序路径。
结合后面的hxxp[:]//download.uusee.com/pop2/pc/UUSee_youzhan1_Setup_11.exe
我们可以推测程序会从后面这个下载链接下载文件到本地并保存到上面的那个路径。
我们先双击进入到下载链接这个字符串,然后交叉引用。
可以看到,唯一的引用位置就在WinMain函数中。
接着我们来看看存放地址的引用位置:
同样是在WinMain函数中:
且该地址作为参数,传入到了sub_401000函数中。
我们这里可以大胆的猜测,sub_401000函数用于执行这个路径的文件。
至此,字符串表也分析的差不多了,通过导入表和字符串表的分析,现在已经对这个样本有了一个大概的印象。
比如样本有可能会通过InternetReadFile函数从hxxp[:]//download.uusee.com/pop2/pc/UUSee_youzhan1_Setup_11.exe读取文件并保存到本地路径的C:Program FilesuuseeUUSeePlayer.exe,然后在sub_401000函数中启动这个文件。
接下来,我们对样本进行完整的代码分析,去验证我们的猜想。
WinMain代码分析
我们回到WinMain函数中,代码如下:
WinMain的第一行是
sub esp,224h
这里直接修改了esp的值,开辟了一个栈空间,然后call sub_401A00,这里可以看到,call sub_401A00之后,会通过test al,al 这个指令判断al的值是否为0,如果al寄存器不等于0,则通过jnz跳转到loc_401BB2处继续执行。
之前我们说过,当一个函数执行完之后,如果函数有返回值,默认情况下会将返回值保存到eax中。
由于al是eax的一部分,所以如果eax等于0,那么al肯定也是为0的。
如果返回值为0,jnz指令不执行,程序继续执行call sub_401960 ,同样的,该函数执行完之后,也会判断al的值是否为0,如果不为0则也跳转到loc_401BB2的地址继续执行。我们看一下loc_401BB2:
可以看到,当程序跳转到loc_401BB2执行后,WinMain执行retn指令,程序就退出了。
所以我们可以知道,刚才调用的sub_401A00 以及 sub_401960 都是一个初始的校验,或者说环境判断,如果说校验没通过,那么程序就不会继续执行了。
一般来说,攻击者在执行真正的恶意功能前,可能会对程序进行以下几个方面的校验
1 互斥体校验 此步操作是为了防止多开程序,从而导致在进程列表中有多个进程,被运维人员或者用户发现异常。
2 反调试校验 此步操作用于判断当前程序是否处于调试的状态
3 虚拟机校验 此步操作用于判断程序是否运行在虚拟机中
4 联网校验 此步操作用于判断用户是否可以连接互联网,或者是否可以连接外网
5 操作系统校验 此步操作用于判断用户计算机的操作系统位数,通常来说,进行此步校验的不会直接结束程序,会根据32位和64位的不同,分别执行不同的代码。
6 是否被其他攻击者攻陷 (查找是否存在其他攻击者留下的攻击痕迹,如果存在则退出)
7 是否值得后续的攻击(比如窃密的可能会判断用户计算机所在的地区、挖矿的会判断用户计算机的性能等)
暂时就想到这些,后面有想到的话再补充。总的来说,做这些校验,都是为了后续能够开展更好的攻击。
我们回到WinMain的头部代码,当sub_401A00 和 sub_401960校验完成之后,程序会call sub_401810函数,然后判断返回值,如果返回值不为0则跳转到下面的loc_401AD0处执行,否则直接通过ExitProcess函数退出进程。
这里我们无法直接判断sub_401810函数的功能,有可能也是在进行校验,也有可能是做了其他操作。
于是,为了搞清楚程序到底在做什么,我们进入到第一个call sub_401A00中:
我们之前讲过,双下划线开头的,一般都是系统的,所以我们直接对__except_handler3进行查询:
遇到这种就需要注意了,因为这里是在WinMain函数中,出现了处理异常的函数,我们需要小心的分析一下这部分代码。
因为是处理异常的代码,这里使用的也是不常见的汇编,我们可以尝试直接通过F5查看该含函数的伪代码:
这里提示401A92:positive sp value has been found
这种情况通常是表示堆栈不平衡,我们可以手动修改堆栈。
首先定位到401A92,光标停留在401A92这行,然后Alt + k 打开堆栈窗口
这里可以看到,Current SP value 是0x18,但是下面的值却是0x4,这里堆栈不平衡了,我们将下面的0x4修改为-0x18
然后单击ok,按下F5,还是提示无法反汇编
于是我们在00401A90这行继续按Alt + k
将这里的0x1C更改为0x4,然后确定,F5得到sub_401A00的C语言伪代码
这里只执行了一个__indword(0x568u)
我们查询一下这条语句
搜索引擎告诉我们,该语句可以用于反调试。通过查看搜索出来的文档,我们也可以基本确定这里是用于反调试,于是我们可以修改sub_401A00并对其进行标注。
接着我们来到WinMain中的第二个call ,sub_401960函数中:
我们还是直接F5,可以看到如下语句:
__asm { vpcext 7, 0Bh }
查询之后可以得知,该指令可能用于检测虚拟机
现在我们就知道WinMain开头的两个函数的具体功能了:
接着看第三个函数sub_401810:
sub_401810最开始的地方,声明了一个名为pe的结构体,类型是PROCESSENTRY32
我们查询一下PROCESSENTRY32可以得知该结构体存储的是快照进程信息
然后下面调用了CreateToolhelp32SnapshotAPI创建了一个线程快照
CreateToolhelp32Snapshot调用完之后,将eax中的值mov到esi中,然后将0FFFFFFFF和esi比较,如果不相等,则跳转到后面的loc_40184F:
loc_40184F这里,首先会调用Process32First来获取当前进程列表中第一个进程的句柄。
当函数成功调用后,程序会通过lstrcmp对比explorer.exe和pe.szExeFile
我们之前已经知道了pe是PROCESSENTRY32类型的结构体,所以我们到微软的官网中查看PROCESSENTRY32的结构,其中提到了szExeFile表示进程中可执行文件的文件名。
所以这里是判断是否找到了explorer.exe进程
如果没有找到,则调用Process32Next获取下一个进程的句柄,然后再次与explorer.exe比较 可以看到这里是一个循环,功能就是找到explorer进程。
如果找到的话,则跳转到loc_401881继续执行,将explorer.exe进程的信息存放到ebx和ebp中。
然后CloseHandle关闭句柄,最后retn返回。所以我们可以得知 WinMain里面的第三个函数sub_401810用于遍历当前所有的进程,找到explorer.exe
回到WinMain,当explorer成功找到之后,则会跳转到loc_401AD0处继续执行。
我们可以看到,在loc_401AD0处,程序首先通过 mov esi,offset aHttpDownloadUu 将我们之前看到的URL地址在内存中的地址赋值给了esi寄存器。此时esi寄存器将会指向这个下载地址。
结合程序通过lea edi, [esp+22Ch+var_104] 给edi寄存器赋值。这里的var_104在该函数开头有定义:
所以这里实际上是 lea,edi,[esp + 22c -104]
得到lea,edi,[esp + 128]
所以edi的值是 esp + 128
接着,程序通过
rep movsd
movsw
的指令,将esi的值赋值给edi,所以现在esp + 128 的地址,指向了下载链接。
接着,程序又进行了两次赋值,红框部分,然后将esp + 22c +Filename的值赋值给eax
其中Filename的值为-208h,所以这里eax为 esp + 22c -208 ,eax最后得到esp + 24
然后push eax,将这个值作为参数传递到sub_4018B0函数中
sub_4018B0的主要功能是获取windows的目录信息,可以看到通过GetSystemDirectoryA获取到系统路径之后,程序会将其存放到[esp+210h+Buffer],然后在下面通过循环,只取冒号之前的内容。 所以这里最后会返回一个系统的根路径。C:(分析到这里换了一个环境,所以IDA修改为白色了)
sub_4018B0函数调用成功后,[esp+230h+FileName]的值应该为C:
这里需要注意一点,由于在调用sub_4018B0的时候,执行了push eax的操作,所以esp的值会减4,所以之后想要请求现在存放的值,将会多增加4.
我们回到WinMain函数中,红框部分是qmemcpy的汇编实现,用于将aUuseeYouzhan1S拼接到[esp+230h+FileName]
所以红框部分执行完之后,[esp+230h+FileName]的值应该是C:UUSee_youzhan1_Setup_11.exe
同时可以看到,call sub_4018B0执行完之后,edi赋值为 [esp + 230h + FileName]
对比上面的[esp+22ch+FileName] 刚好多了4个字节,和我们推算的一致。
赋值成功之后,程序通过lea指令取了[esp+230h+FileName]和[esp+230h+var_104]的值分别存放到了ecx和edx中,然后将这两个寄存器作为参数传递到了之前我们分析的网络请求函数中。此时ecx存放的就是文件的保存路径C:UUSee_youzhan1_Setup_11.exe,edx存放的是请求下载的地址。
Rename_InterNet_relevant分析
我们进入到Rename_InterNet_relevant函数中,看看具体的实现。
Rename_InterNet_relevant函数最开始通过memset的方式对一些局部变量赋值。
如LibFileName、ProcName等
我们将所有的ascii转换为字符并拼接之后可以得到解码了如下的变量:
LibFileName = Winnet.dll
ProcName = InternetGetConnectedStats
[esp+107A4h+var_10144] = InternetOpenA
[esp+107A4h+var_1044F] = InternetOpenUrlA
[esp+107A4h+var_10248] = HttpQueryInfoA
[esp+107A4h+var_10450] = InternetReadFile
[esp+107A4h+var_10658] = InternetCloseHandle
变量赋值完成之后,程序首先通过LoadLibrary加载Winnet.dll
然后通过GetProcAddress获取ProcNam也就是InternetGetConnectedStats的地址
获取成功之后,程序call eax调用InternetGetConnectedStats,接着判断eax是否为0,如果不等于0则说明调用成功,跳转到loc_40158D继续执行,如果eax等于0说明函数调用失败,则在后面调用FreeLibrary释放Library然后退出函数。
跳转过来执行后,程序首先通过timeGetTime获取时间,以%s%ld作为格式化,然后尝试通过GetProcAddress获取var_10144的地址。
经过上面的分析,我们知道var_10144代表InternetOpenA,后面的代码基本上可以不用看了,就是非常非常正常的一个下载流程。
就是InternetReadFile,然后CreateFile,再通过WriteFile写入文件:
至此,网络请求函数分析完毕。
程序调用网络请求函数成功下载文件并保存到本地之后,程序会将存储路径作为参数传递到我们最开始分析的sub_401000函数中
sub_401000
我们来看一下sub_401000的具体实现:
00401000函数进来之后,通过lea edi, [esp+264h+var_24C]给EDi赋值,然后通过rep stosd将edi的前面11h大小的空间写为0。
然后在00401035处有一次给edi赋值,在0040108F处将0赋值到edi中,大小为3Dh。
且可以看到在0040103C处开始对局部变量LibFileName赋值。
我们可以把鼠标点到后面的6Bh上,然后按下键盘的R键,将十六进制数据转换为字符。
同时可以看到,这里首先将字符 e 赋值给dl,将字符 l 赋值给al,然后再下面通过访问dl 和 al 以访问对应的字符,避免了多次使用同一个字符串ascii编码。
将这里的数据组合一下不难看出这里LibFileName = kerne32.dll
接下来是给ProcName赋值。同样的,经过分析,不难看出ProcName = CreateProcessA
两个局部变量赋值完成之后,会将LibFileName(kerner32.dll)赋值到eax,然后push eax调用LoadLibraryA
通过LoadLibrary加载Kernel32.dll之后,程序还会通过GetProcAddress获取CreateProcessA的地址
然后通过call eax的方式,创建edx进程,dex来源于参数。由此可以得知,00401000函数的确用于创建进程,和我们最开始的猜想保持一致。
进程成功创建之后,通过CloseHandle关闭打开的句柄,然后退出00401000函数。
接着进行一段时间的休眠,然后将另外一个路径C:Program FilesuuseeUUSeePlayer.exe作为参数传递到sub_401000函数中执行。
这个路径就是我们最开始在行为分析中看到的路径,经过分析,我们发现在此样本中并为访问该路径,结合sleep函数,我们可以猜测,本样本通过网络请求下载回来的样本应该是第二阶段的Downloader或者Dropper,执行之后,会下载或释放第二个样本保存到C:Program FilesuuseeUUSeePlayer.exe,然后在这里通过sub_401000函数执行。
最后结束进程,该样本运行结束。
0x03 总结
至此,第二个简单的Downloader已经分析完成。
在下一小节,将结合行为分析的一些技巧,对最后一个Downloader进行快速的静态分析。