有人在2018年的时候就分析过了恶意样本分析,相关资料可以查看参考部分。由于里边有很多可以学习和总结的脱壳技术,因此重新分析这个样本,这篇文章暂时不做加密和勒索实际功能分析,如果要看分析样本形成勒索的过程以及相关分析报告,可以直接跳到参考部分,这篇文章主要关注的是脱壳相关的一些技术细节的分析,由于文章是我个人的一些分析和总结,因此会有一些不足,比较趋向于像我这样刚接触恶意软件分析,由于很多样本在分析前需要脱壳之后才能看到核心功能,因此这里重点关注脱壳过程,主要内容如下
解密并dump出shellCode
解密并dump出PE1
解密并dump出encrpt.dll
使用IDA打开病毒母体,来到入口函数,经过分析后程序会解密出资源文件的内容
调用VirtualProtect
函数修改shell_code
的属性为可读写,这里的VirtualPtotect
函数是通过动态获取的函数指针
之后调用函数sub_401014
对shell_code
解密并调用
为了能进一步分析shell_code
,需要在动态调试阶段dump
下来,因此这里使用x32dbg
调试母体病毒,在程序调用shell_code
的时候,单步进入后dump
下来,如下是来到执行调用shell_code
的位置
此时选择单步进入,来到shell_code
的入口处,如下
此时将内存dump
下,步骤为在内存窗口点击右键-->选择 在内存布局中转到
之后跳转到内存布局,之后选中对应的内存位置一般使用这个方式跳转到内存布局文件,默认会选择指定的内存
,然后右键保存为文件,之后命名为shell_code.bin
,如下
使用ida分析shell_code
的功能。
现在主要对shellcode的功能分析,这部分主要会完成如下功能
在执行解密PE1这个文件之前,需要弄清楚几个问题
[ebp-0A0h]
保存的内容是什么kernel32.dll
函数是如何保存的[ebp-0A0h]
保存的内容是什么
在动态调试阶段注意如下指令
mov eax, [ebp-8Ch]
mov ecx, [ebp-3Ch]
lea eax, [ecx+eax-38h]
mov [ebp-8], eax
mov eax, [ebp-8]
mov [ebp-0A0h], eax ;这里赋值给[ebp-0xA0]
动态调试阶段内内存内容如下
根据内存中的内容可以知道,这里保存的是一个PE文件,不过是被加密了在后面会执行解密,在执行解密前,程序动态保存了kernel32.dll GetProcAddress
函数在栈上使用。如下
通过这个手段,分别将需要的函数保存在了当前的栈上,主要获取的函数包括
mov dword ptr ss:[ebp-38],eax ;VirtualProtect
mov dword ptr ss:[ebp-48],eax ;VirutalAlloc
mov dword ptr ss:[ebp-24],eax ;VirutalProtect
mov dword ptr ss:[ebp-5c],eax ;VirutalFree
mov dword ptr ss:[ebp-44],eax ;GetVersionExA
mov dword ptr ss:[ebp-38],eax ;TerminateProcess
这些字符串在dump出的shell_code中静态分析显示如下
查看字符串内容
由此可以知道这个主要是为了避免杀毒软件所查杀,因此在解密shellc_code并运行后,动态阶段解密后导入。
继续往下来到如下指令
push dword ptr [ebp-44h] ; ptr_GetVersionExA
push 100000h
push dword ptr [ebp-0A8h] ; buff_1
call sub_4497
这里的[ebp-0x0A8]
实际指向的是新的一个程序块,这部分的内容还没有被修复
dump下这个文件命名为stage2.bin
拖入ida分析后保存,这部分的节区等信息没有被填充,如下
这部分也是需要在后面的修复中使用的。
kernel32.dll
函数是如何保存的根据上面的分析shell_code
在运行阶段动态导入了需要使用的API保存在栈上。
在前面的准备阶段完成后开始调用VirtualAlloc
分配一块内存
将返回的内存指针保存在了[ebp-0x10]
,这里命名内存块为new_pe1_buff
,然后调用sub_4170
函数执行解密
执行解密前的new_pe1_buff
的内存内容
解密后
此时将内存的数据保存解密后的内存为PE1.exe
,这样就能获取解密后的PE1数据了。
下面的过程主要是分析内存修复PE文件的过程
往下继续分析,在解密PE1文件后,接着调用VirtualProtect
函数修改了[ebp-0xA8]
内存属性为0x40
,对照了MSDN
的文档后知道,修改为可读写属性,如下
#参考https://docs.microsoft.com/zh-cn/windows/win32/memory/memory-protection-constants
PAGE_EXECUTE_READWRITE
0x40
修改完属性后就将[ebp-0xA8]
的数据清空,目的是接下来就会对这个内存重新导入新的数据块 call sub_435D
函数实际是调用了memset
函数,如下
执行后清空掉这块内存,目的是后续重组一个PE文件
重组一个PE文件,需要的数据包括
开始组装新的PE文件,调用sub_3EE函数
复制头部信息,内容保存在[ebp-0xA8]
push dword ptr [eax+14h];指向section->PointerToRawData
push dword ptr [ebp-34h];指向了 new_pe1_dos_header
push dword ptr [ebp-90h];这里指向的[ebp-0xA8]
call sub_43EE
这里的[eax+0x14]
是.text节区在文件内的大小映射到[ebp-0xA8]
内。如下是动态调试时参数
之后完成后就将[ebp-0x10]的节区按照文件大小映射到内存中,执行前
执行复制函数后,
这里就将复制完了第一部分内容,后面还要继续复制节区数据和IAT修复。
复制section
在复制完header部分后后面,接着开始修复节区和IAT表。首先获取节区大小
根据[ebp-0xB0]
当做节区索引,如果小于当前的节区表大小,则跳入复制节区表
读取下一个节区
重复复制完成后,开始修复IAT表
修复IAT
设置完成导入表和导出表后,执行读取符号
这里将[ebp-0x90]命名为payload.dll
修复完成所有的函数后,最后跳转执行PE1.exe
,来到最后跳转位置
这个过程其实不用去过分关注,因为程序的核心程序并不在后面的节区复制和修复IAT过程,我们dump出的PE1.exe已经可以用作静态分析和动态调试了,经过分析后发现在解密完成PE1.exe之后的操作属于扰乱分析的一个手段实际程序会跳转到PE1.exe内执行解密encrypt.dll,这个在后面的动态调试PE1.exe中可以得到证明,分析的好处就是对内存加载PE以及修复PE的过程有了更熟悉了一些。
根据前面的分析可以知道,shell_code的主要功能是解密出PE1程序然后执行,释放的PE1并没有被写入到本地,这样就可以减少被查杀的概率,直接是无文件方式执行,同样的encrypt.dll也不会被写入到本地中,而是解密后反射运行,如下是dump的过程。
首先程序先解密出encrypt.dll
程序
之后获取dll内的ReflectLoader
函数
利用反射调用的方式执行encrypt.dll
,关于反射调用可以看stephenfewer开源的项目,和一般的利用注射器(进程注入dll)方式利用VirtualAlloc,WriteProcessMemory,CreateRemoteThread,LoadLibrary
等当时的注入不同,注射器方式需要写入dll到本地然后利用LoadLibrary
函数加载dll触发dll内的DllMain
会被执行,反射调用会在当前运行的进程加载dll然后执行。
在获取到ReflectiveLoader
之后调用VirtualProtect
函数修改encrypt.dll
的属性为可执行
这里就完成了PE1
的核心功能
ReflectiveLoader
函数然后执行encyrpt.dll接下来就是核心encrypt.dll
的分析,在分析之前需要dump出这个dll文件。这一步我在分析和调试的时候走了很多弯路,例如一直调试母体病毒,分析shell_code
的解密功能,包括解密出了PE1
之后的程序,不过在多次调试和分析中,对这种内存解密和加载PE文件的方式也熟悉了不少。
dump核心的encrypt.dll
可以建立在第二阶段获取的PE1.exe
这个文件内,直接使用x32dbg调试PE1.exe
文件,在分析PE1.exe
时可以知道,程序要执行encrypt.dll
前会调用VirtualProtect
函数修改内存属性,由此可以知道此事的encrypt.dll
是解密完成了,因此我们直接在调试的PE1.exe
的VirtualProtect
函数上设置断点,需要注意的是这个函数会被调用两次,因此我们只需要在第一次命中断点后选择dump下来即可,设置断点的选择在如下位置方便获取到参数
设置好断点后运行程序,在第一次断下来后查看内存,如下
此时选择dump下来保存,即可获取到了核心的encrypt.dll
首先程序会解密出shellCode
来解密出第一阶段的PE1
程序,PE1
负责解密出encrypt.dll
,之后通过反射调用的方式来运行恶意行为。从这个过程中可以看出这个过程是一个无文件方式,相比较于注射器来完成的进程注入方式,反射调用更加隐蔽。
关键内存Api断点
如果是进程注入的方式会选择在VirtualProtect,WriteProcessMemeory VirtualAlloc
函数设置断点分别获取解密的PE文件,这种脱壳方式可以称为关键内存Api断点;例如上面的脱壳过程,可以设置断点在VirtualProtect,VirtualAlloc
上,程序运行后分别关注每个次断点内存中的内存,然后每次都dump下来,然后做分析即可,可以不用去关心实际的一些解密过程。
ESP定律
如果是压缩壳类的,可以考虑ESP定律手段,这个资料比较多,也容易上手,这个我会在分析两个压缩壳的样本。
来源
基本分析
详细分析1
详细分析2