|
待分析样本之两个exe样本
v>分析样本一:Keyboard Test Portable.exe
1.基础信息
文件哈希值是恶意代码的指纹,通过用它来确认文件是否被篡改,也可以通过hash值查找
恶意样本,一般我们也可以使用多种hash值验证文件的唯一性。
hash校验值
MD5: 04FCF1A858EFC08D49459C859DA97C8B
SHA1: C46AAEDADBFF7E13B5FE7AB8B9BF19A54F76B0C6
SHA256:c1c7fd5ab25bc316e675ba6a8d737cc2b7ad32a8b9bbbb23cf30b5992c782f3f
CRC32: B3FF47D5
查壳,没有壳
节表信息这个文件的创建时间是2009年12月12日
界面如下:像是模拟键盘输入的一个软件
2.云沙箱
在微步在线云沙箱对该样本进行检测,检出率为9/25。且检测出来自expiro木马家族。
导入表信息:哈勃分析系统:奇安信威胁情报中心3.行为分析
行为检测的工具推荐使用工SysTracer.exe、火绒剑、Procmon、sysmon等。
此外,还有一个工具:fireeye的开源工具:fakenet:https://github.com/fireeye/flare-fake
net-ng
在联网的虚拟机中安装该工具后,将会构建一个虚拟网络,此时,通过主机产生的所有网
络请求、流量将不会发送出去,而是发送到fakenet中。此外,fakenet还会根据请求的内容
进行对应的响应,可用于必须使用网络才可以运行的恶意样本。
1.SysTracer.exe
这个工具我没有找到,所以暂时先不用这个工具分析
2.火绒剑
火绒剑是现在比较流行的一款行为分析工具,官方也提供了该工具的单独下载。
火绒剑安装好之后,以管理员身份启动,开启监控,然后进行过滤设置:然后看到这里的时候,发现jux1a师傅是这么说的,
可以把注册表监控取消勾选,因为正常情况下来讲,一个程序运行之后,哪怕什么都不做,都会大幅度
的进行注册表操作。
于是在动作过滤处,去掉注册表监控,
通过上述监控发现,文件监控的动作最多,然后发现它会将C盘下所有目录下的文件都
fileopen一次。如下图大量的fileopen操作。接下来看网络请求,发现有外联ip:
对这几个外联ip进行反查:
121.32.254.175,来自广东佛山的数据中心34.128.82.12,是谷歌云,来自美国的数据中心,ssbzmoy.biz/xhgxkqiphkxj域名对应的ip
是34.128.82.12
cvgrf.biz域名对应的ip是104.198.2.251,也是来自美国的谷歌云。
行为监控,按照英语翻译,推测是执行提取的文件。
执行进程:3.Procmon
procmon是微软官方提供的行为监控工具,打开之后默认会检测所有的进程:
可以选择如下图标或者Ctrl+L进行过滤:
根据个人选择,第一个框可以根据ProcessName对指定的进程进行过滤第二个下拉框有如下的过滤条件:
然后在输入框中填入需要过滤的ProcessName,点击Add,然后选择OK:此时窗口将只会剩下我们选择的进程的相关行为:由于是微软自己开发的行为监控工具,Procmon可以说是非常详细,检测能力比其他来说
也更全面,但是procmon在使用的时候,会多出很多系统相关的行为,这里需要分析的时候
自行鉴别。
通过procmon.exe的进程树进行分析,可以看见下面会继续执行keyboardtest.exe。
首先我们看到,这个程序会和外联ip建立tcp和http连接:
然后就是大量的文件操作和注册表操作,包括:creatfile、readfile、closefile、
regquerykey、regopenkey、regqueryvalue、regsetinfokey。
4.sysmon
Sysmon是一款轻量级的监视工具。属于Windows Sysinternals出品的一款Sysinternals系列
中的工具,用来监视和记录系统活动,并记录到windows事件日志。
Sysmon相比前面几款工具,安装要稍微麻烦一些,但同时也具备了一些优势。官网下载地址:https://docs.microsoft.com/en-us/sysinternals/downloads/sysmon
下载好之后,需要根据github上的配置文件进行配置。
git地址:https://github.com/SwiftOnSecurity/sysmon-config
安装过程如下
1 使用管理员启动cmd
2 进入到下载的sysmon目录下
3 将git下载下来的配置文件和sysmon放在同一个目录下
4 执行命令Sysmon64.exe -accepteula -i z-AlphaVersion.xml
安装教程也可以参照:https://zhuanlan.zhihu.com/p/623351222?utm_id=0
sysmon64.exe -accepteula -i sysmonconfig-export.xml
我自己本地安装出现了一点问题(如上图),但是记录下这个工具的基本用法。
将sysmon启动起来之后,win+r 运行eventvwr打开日志管理器然后在:
应用程序和服务日志->Microsoft->Windows目录下会有一个SysMon文件夹
然后就能看到启动sysmon之后的日志。
新版本的sysmon有了dns查询功能,非常实用
启动sysmon之后,启动刚刚的exe程序,就可以监控该样本产生的一些行为。
4.静态分析
首先将样本拖动到IDA中,IDA自动识别这是一个PE文件。直接点击ok进入下一步。
在弹出的选项都单击ok,最后成功加载后默认显示如下:
导入表分析
通过IDA加载样本之后,默认会停在样本的入口点,即如果程序没有做特殊的处理,IDA默认
停留的地方,就是程序运行时的入口点。此时不要急着直接分析代码,可以先通过一些其他
信息来对该样本进行一个初步的判断。首先是导入表分析,在函数未加壳的情况下,导入表中会包含程序所使用到的一些系统
API,通过这些API,我们通常可以对样本的基本行为有个大概的了解。
默认情况下,导入表会自动打开
如果不小心关掉了,可以通过alt+6的快捷键重新打开。Windows的设计比较人性化,
WindowsAPI的命名也倾向于与函数的功能相关联,所以通过导入函数的命名通常便可以推
断这些函数的大概功能是什么,不过想要了解详细信息如参数、返回值等,还是要查询手
册。
这里的WriteFile函数很明显是写入数据到文件。
CreateFileA用于创建文件。
CreateThread用于创建线程。
同时可以看到有很多关于注册表的操作函数,其中:
1. RegOpenKeyExA:该函数用于打开指定的注册表键。它接受参数来指定要打开
的键的句柄、键的名称和访问权限等。
2. RegQueryValueExA:该函数用于查询指定注册表键中的值。它接受参数来指
定要查询的键的句柄、值的名称和缓冲区来接收查询结果等。
3. RegCreateKeyExA:该函数用于创建一个新的注册表键或打开一个现有的键。
如果指定的键已经存在,则打开该键;否则,创建一个新的键。它接受参数来
指定父键的句柄、新键的名称和访问权限等。
4. RegSetValueExA:该函数用于设置指定注册表键的值。它接受参数来指定要
设置的键的句柄、值的名称、值的类型和数据等。
5. RegCloseKey:该函数用于关闭一个打开的注册表键。它接受参数来指定要关
闭的键的句柄。在该程序中,导入表比分析的真实样本还要更多,其中也包含了一些比较奇怪的API,所以
在分析导入表的时候,只能对程序起到一个初步分析的作用,让我们心里明白 程序大概会
执行哪些操作。
如果实在想搞清楚这些API在哪里使用了,用来干什么了,我们可以通过IDA的交叉引用来
确定。
就以我们分析的这个恶意样本为例,查看RegOpenKeyExA的交叉引用结果,双击该API的
名称:
接着单击框起来的地方,然后按X,弹出的对话框就会显示所以用到了这个函数的地方。
单击ok,跟进过去。向上找,我们定位到调用RegOpenKeyExADE的函数名为sub_40B9A3,我们继续对
sub_40B9A3按X查看交叉引用,进来之后,sub_40B9A3函数前后的代码如下:
继续向上找,看看是在哪个函数中,看到这样的汇编代码,我们即可知道回到了函数的头
部,函数名为sub_40BF8C继续对sub_40BF8C进行交叉引用:
然后对sub_40CBB26进行交叉引用,然后找到函数头部,发现是sub_40CC1F
继续对sub_40CC1F进行交叉引用,到了sub_40D399sub_40D399进行交叉引用,回到了start函数这里,就是我们最开始看到的IDA默认停留的
入口点。所以我们现在通过交叉引用搞清楚了regOpenKeyExA这个API的调用链,也基本确
定了这个函数是由用户代码编写调用的,以及函数的调用位置。
注意:有很多API是正常程序、恶意程序都会使用到的,我们不能看到程序的导入表
中有某个API,就说程序一定有某个功能。
字符串分析
字符串表的快捷键是shift + f12
字符串表中会显示出IDA对该样本提取的所有字符串。在程序未加壳或是混淆的情况下,我
们可以通过Strngs Window 查看程序中所用到的字符串,在该样本中如下:这里查出来的字符串还是比较多的,一共有238个,其中大部分是编译器所使用的,在
strings表尾部可以看到有几个比较可疑的字符串。
我们在分析字符串的时候,是为了尽可能的查找到特殊的字符串。
在该样本的strings表中,开头的那几个字符串太短,也几乎没有什么意义所以可以暂时忽
略,中间部分大部分是编译器生成的,或者使用到的公有库文件中的。一般来说,程序中带
了两个下划线开头的,都是属于系统的内容,比如__GLOBAL_HEAP_SELECTED。
然后像”R6028- unable to initialize heap”这种,也属于正常的字符串。
再往后看,是程序使用到的一些API名或模块名。然后这里有几个注册表键路径以及文件名的字符串:
注册表路径:
Software\\Microsoft\\Windows\\CurrentVersion 是一个注册表键路径(Registry Key
Path),用于访问 Windows 操作系统的当前版本信息和相关配置。
Software\\WinRAR SFX 是一个注册表键路径(Registry Key Path),用于访问 WinRAR
SFX(
Self-Extracting Archive)相关的设置和配置信息
.rdata:00412688 0000002A C
Software\\Microsoft\\Windows\\CurrentVersion
.rdata:00412634 00000014 C Software\\WinRAR SFX
.rdata:00412768 00000019 C winrarsfxmappingfile.tmp
.rdata:0041382C 00000039 C - UTF-8
d:\\Projects\\WinRAR\\SFX\\build\\sfxrar32\\Release\\sfxrar.pdb
.rsrc:004250D6 0000001E C KeyboardTest\\##Attributes.ini我们对上述注册表路径进行交叉引用分析,以下面这段字符串为例,这是一个注册表路径,
用于访问 Windows 操作系统的当前版本信息和相关配置。
.rdata:00412688
0000002A
C
Software\\Microsoft\\Windows\\CurrentVersion
双击想要查看的字符串,
然后选中前面这个变量名,按X
然后对这里的sub_40BF8C进行交叉引用,到了sub_40CB26
对sub_40CB26交叉引用,到sub_40CC1F对sub_40CC1F交叉引用,到sub_40D399
对sub_40D399交叉引用,又回到了start函数中,且距离刚刚分析的regOpenKeyExA函数不
远。
代码分析(侧重于函数功能分析)
经过导入表和字符串表的分析,对这个样本有了一个大致的了解。现在对其代码进行分析:
首先是start函数,比较简短,先调用sub_40A6B3函数,在通过xor eax eax 的方式将寄存器
eax的值清零。
接着push了四个eax(0)入栈,然后通过call指令调用了sub_40D399函数。sub_40A6B3函数
我们先跟进到sub_40A6B3函数,先把这个函数的完整汇编代码贴过来,然后一点点进行分
析
.text:0040A6B3 ; =============== S U B R O U T I N E
=======================================
.text:0040A6B3
.text:0040A6B3 ; Attributes: bp-based frame
.text:0040A6B3
.text:0040A6B3 sub_40A6B3 proc near ; CODE XREF:
start↓p
.text:0040A6B3
.text:0040A6B3 var_20 = dword ptr -20h
.text:0040A6B3 var_1C = dword ptr -1Ch
.text:0040A6B3 var_18 = dword ptr -18h
.text:0040A6B3 var_14 = dword ptr -14h
.text:0040A6B3 var_10 = dword ptr -10h
.text:0040A6B3 var_C = dword ptr -0Ch
.text:0040A6B3 var_8 = dword ptr -8
.text:0040A6B3 var_4 = dword ptr -4
.text:0040A6B3
.text:0040A6B3 push ebp
.text:0040A6B4 mov ebp, esp
.text:0040A6B6 sub esp, 20h
.text:0040A6B9 push offset unk_41FDC0
.text:0040A6BE push 101h
.text:0040A6C3 call sub_40A6AE
.text:0040A6C8 pop ecx
.text:0040A6C9 pop ecx
.text:0040A6CA mov [ebp+var_4], eax
.text:0040A6CD cmp [ebp+var_4], 0
.text:0040A6D1 jz short loc_40A6E0
.text:0040A6D3 mov ecx, [ebp+var_4]
.text:0040A6D6 call loc_522000
.text:0040A6DB mov [ebp+var_14], eax
.text:0040A6DE jmp short loc_40A6E4
.text:0040A6E0 ; --------------------------------------------------
-------------------------
.text:0040A6E0
.text:0040A6E0 loc_40A6E0: ; CODE XREF:
sub_40A6B3+1E↑j.text:0040A6E0 and [ebp+var_14], 0
.text:0040A6E4
.text:0040A6E4 loc_40A6E4: ; CODE XREF:
sub_40A6B3+2B↑j
.text:0040A6E4 push offset uExitCode
.text:0040A6E9 push 0Ch
.text:0040A6EB call sub_40A6AE
.text:0040A6F0 pop ecx
.text:0040A6F1 pop ecx
.text:0040A6F2 mov [ebp+var_8], eax
.text:0040A6F5 cmp [ebp+var_8], 0
.text:0040A6F9 jz short loc_40A708
.text:0040A6FB mov ecx, [ebp+var_8]
.text:0040A6FE call sub_4032B4
.text:0040A703 mov [ebp+var_18], eax
.text:0040A706 jmp short loc_40A70C
.text:0040A708 ; --------------------------------------------------
-------------------------
.text:0040A708
.text:0040A708 loc_40A708: ; CODE XREF:
sub_40A6B3+46↑j
.text:0040A708 and [ebp+var_18], 0
.text:0040A70C
.text:0040A70C loc_40A70C: ; CODE XREF:
sub_40A6B3+53↑j
.text:0040A70C push offset unk_414C88
.text:0040A711 push 14h
.text:0040A713 call sub_40A6AE
.text:0040A718 pop ecx
.text:0040A719 pop ecx
.text:0040A71A mov [ebp+var_C], eax
.text:0040A71D cmp [ebp+var_C], 0
.text:0040A721 jz short loc_40A730
.text:0040A723 mov ecx, [ebp+var_C]
.text:0040A726 call sub_409271
.text:0040A72B mov [ebp+var_1C], eax
.text:0040A72E jmp short loc_40A734
.text:0040A730 ; --------------------------------------------------
-------------------------
.text:0040A730
.text:0040A730 loc_40A730: ; CODE XREF:
sub_40A6B3+6E↑j.text:0040A730 and [ebp+var_1C], 0
.text:0040A734
.text:0040A734 loc_40A734: ; CODE XREF:
sub_40A6B3+7B↑j
.text:0040A734 push offset byte_41A830
.text:0040A739 push 4AE0h
.text:0040A73E call sub_40A6AE
.text:0040A743 pop ecx
.text:0040A744 pop ecx
.text:0040A745 mov [ebp+var_10], eax
.text:0040A748 cmp [ebp+var_10], 0
.text:0040A74C jz short loc_40A75B
.text:0040A74E mov ecx, [ebp+var_10]
.text:0040A751 call sub_402C55
.text:0040A756 mov [ebp+var_20], eax
.text:0040A759 jmp short locret_40A75F
.text:0040A75B ; --------------------------------------------------
-------------------------
.text:0040A75B
.text:0040A75B loc_40A75B: ; CODE XREF:
sub_40A6B3+99↑j
.text:0040A75B and [ebp+var_20], 0
.text:0040A75F
.text:0040A75F locret_40A75F: ; CODE XREF:
sub_40A6B3+A6↑j
.text:0040A75F leave
.text:0040A760 retn
.text:0040A760 sub_40A6B3 endp
看以看到首先是定义了一些变量,然后是下面三条汇编代码,用于开辟当前函数的栈空间。
push ebp
mov ebp, esp
sub esp, 20h
接下来我们就重点关注会调用那些函数,逐个跟进并总结每个函数大致用途:
.text:0040A6C3 call sub_40A6AE
.text:0040A6FE call sub_4032B4
.text:0040A726 call sub_409271
.text:0040A751 call sub_402C551.sub_40A6AE,将esp+arg_4的值赋值给eax寄存器,然后返回
2.sub_4032B4,将ecx的值移动到eax寄存器中,接下来将ecx寄存器的值清零,将ecx的值移
动到eax和eax+4指向的内存地址,相当于将eax和eax+4内存中的值清零,将值1移动到
eax+8寄存器指向的内存地址中,这里使用byte ptr表示将值视为一个字节,将cl寄存器
中的值移动到eax+9寄存器指向的内存地址中,mov [eax+0Ah], cl:将cl寄存器中的值
移动到eax+0Ah寄存器指向的内存地址中。最后返回。
3.sub_409271 ,比较简单,和上面类似,不赘述了
4. sub_402C55,将esi压栈,然后将ecx的值赋给esi,调用sub_407238函数,接下来
清空eax寄存器的值,并将esi的值重新赋值给ecx寄存器,然后做了五次赋值操
作,接下来调用sub_402B95,然后将esi的值赋值eax,最后esi出栈,从这个函数中
返回。先看 sub_407238,直接跟进调用的两个函数,sub_40E01D和sub_4071b0,
sub_40E01D直接返回了一个参数,sub_4071b0调用了sub_40A27C,再无向下调用函数。
再看sub_402B95函数,会继续向下调用sub_4029E6,sub_40A65B,sub_40DDDC.方便起见,我绘制了下面的函数调用表,从前到后就是表示函数由浅到深的调用过程,最终
调用到了内存管理的相关函数。
sub_40D399函数
接下来跟进sub_40D399函数,直接分析调用函数,我们可以很清晰的看到有:
.text:0040D3A5 call ds:OleInitialize //初始化
COM(
Component Object Model)库。OM 是一种面向组件的编程模型,用于在
Windows 平台上实现组件之间的交互和通信。
.text:0040D3B0 call sub_40E4D2
.text:0040D3B5 call ds:GetCommandLineA //用于获取
当前进程的命令行参数字符串
.text:0040D3C2 call sub_40B478
.text:0040D3D7 call ds:OpenFileMappingA //用于在当
前进程中获取对已命名文件映射对象的句柄。
.text:0040D3ED call ds:MapViewOfFile//用于将一个文
件映射对象映射到当前进程的地址空间中的一段虚拟内存。这样,进程就可以直接访问和操作
这段内存,实现与文件的交互。
.text:0040D3FF call ds:SetEnvironmentVariableA
//用于设置当前进程的环境变量的值。
.text:0040D406 call ds:UnmapViewOfFile //释放相应
的虚拟内存区域
.text:0040D40D call ds:CloseHandle //用于关闭一个内
核对象的句柄。
.text:0040D41B call ds:SetEnvironmentVariableA
.text:0040D42E call ds:GetModuleFileNameA//用于获
取指定模块的文件名。
.text:0040D43A call ds:SetEnvironmentVariableA
.text:0040D440 call sub_406E98
.text:0040D450 call ds:GetModuleFileNameW //用于
获取指定模块的文件名
.text:0040D461 call ds:GetModuleHandleA //用于获
取已加载模块的句柄.text:0040D472 call dsoadIconA //用于加载指定模块
中的图标资源
.text:0040D485 call dsoadBitmapA //用于加载指定模
块中的位图资源
.text:0040D493 call sub_41163C
.text:0040D49E call sub_4098D3
.text:0040D4A6 call sub_40646C
.text:0040D4BF call sub_40AC94
.text:0040D4E4 call dsialogBoxParamA //用于创建
并显示一个模态对话框
.text:0040D4FF call sub_406482
.text:0040D50F call sub_4116B4
.text:0040D51C call sub_40B71C
.text:0040D52C call sub_40A27C
.text:0040D53F call sub_40A1F8
.text:0040D550 call esi ; DeleteObject //用于删除
由 GDI (图形设备接口)创建的图形对象,例如位图、画刷、字体等。
.text:0040D55C call esi ; DeleteObject
.text:0040D578 call sub_40324F
.text:0040D591 call sub_40B6DD
.text:0040D5A2 call ds:ExitProcess //用于终止当前进
程并返回给操作系统。这里涉及到的函数还是比较多的,除了上述一些已知的函数,我们跟进每个sub函数并且绘
制函数调用表格,绘制起来发现这个函数调用过程实在太多了,手绘还是很麻烦,可以直接
使用ida逐个去查看,下面是我汇总的不完整的函数调用。
使用ida直接查看函数调用关系还是挺方便的,可以参考下面这个文章:https://blog.csdn.n
et/u014602228/article/details/122091620没有绘制完的,接下来使用ida自带的功能展示函数调用:
sub_409286
sub_4096A2
sub_40AC94sub_40B71C
sub_40A1F8sub_40B6DD
伪代码分析(整体性分析)
start函数
对应某个函数直接一键F5,先是start函数,直接就是调用了sub_40A6B3函数和sub_40D399
函数两个函数。
sub_40A6B3
跟进sub_40A6B3函数,先是声明了四个变量,接下来调用sub_40A6AE,参数是257,
&unk_41FDC0, sub_40A6AE函数会直接返回第二个参数值,即v0=&unk_41FDC0,接下来
判断v0是否为真,若为真(也就是不为0),就会去执行loc_522000处的代码.接下来又是让
v1=&uExitCode,若v1是非零字符或数字,则调用sub_4032B4,继续向下跟进,发现
sub_4032B4经过一些运算,返回一个值。其他他的类似,最终返回result。sub_40D399
再跟进sub_40D399函数,伪代码如下:OleInitialize(0) 表示以默认方式初始化OLE库,调用sub_40E4D2 ,并将 unk_41FDC0
的地址作为参数传递给该函数。跟进sub_40E4D2 看一看,首先声明了三个变量,分别为一
个名为 v1 的无符号整数变量、一个名为 result 的布尔型变量、一个名为 CPInfo 的
_cpinfo 结构体变量,调用 GetCPInfo 函数,将参数 0(表示默认的当前代码页)和
CPInfo 的地址作为参数传递给它。该函数用于获取指定代码页的信息。将
CPInfo.MaxCharSize > 1 的结果存储在 this 数组的第 256 个元素中。如果
MaxCharSize 大于 1,则返回值为 true;否则返回值为 false。进入一个循环,循环条件为v1 小于 0x100。在循环内部,调用 IsDBCSLeadByte 函数,将 v1 作为参数传递给它,用
于判断指定字节是否为双字节字符集(DBCS)的首字节。将 result 的值存储在 this 数
组的第 v1 个元素中。将 v1 的值自增 1。重复步骤这个内部循环直到 v1 达到 0x100。返
回 result。
这个函数的目的可能是初始化一个 _BYTE 类型的数组,并根据系统的代码页信息判断每个
字节是否为双字节字符集的首字节,并存储在数组中。
接着回到sub_40D399函数,
获取当前进程的命令行参数,并将其保存在变量 CommandLineA 中,然后将 CommandLineA
的值赋给变量 v1。接下来,代码检查 CommandLineA 是否非空(即命令行参数存在)。如
果存在命令行参数,则执行以下操作:1. 调用 sub_40B478 函数,将 CommandLineA 作为参数传递给它。
2. 检查 byte_419F91 的值,它可能是一个标志位或变量。如果 byte_419F91 为真
(非零),则执行以下操作:
调用 OpenFileMappingA 函数,尝试打开一个命名的共享内存文件映射
对象,名称为 "winrarsfxmappingfile.tmp"。
如果成功打开文件映射对象,将返回的句柄保存在 v2 变量中。
调用 MapViewOfFile 函数,将文件映射对象映射到当前进程的地址空
间,并返回指向映射视图的指针 v4。
如果 v4 非空,则调用 SetEnvironmentVariableA 函数,将环境变量
"sfxcmd" 的值设置为 v4 指向的字符串。
调用 UnmapViewOfFile 函数,解除对映射视图的映射。
关闭文件映射对象句柄 v3。
3. 如果 byte_419F91 的值为假(零),则执行以下操作:
调用 SetEnvironmentVariableA 函数,将环境变量 "sfxcmd" 的值设置
为 v1 指向的字符串(即命令行参数字符串)。
接下来,调用 GetModuleFileNameA 函数,获取当前模块(当前可执行文件)的文件名,
并将结果保存在 Value 变量中。调用 SetEnvironmentVariableA 函数,将环境变量
"sfxname" 的值设置为 Value。调用sub_406E98,该函数伪代码下图已给出,这个函数的作
用是为了获取操作系统的平台和版本信息,并根据平台为 Windows NT 时返回主要版本
号。若成功获取到该操作系统的平台和版本信息,则调用 GetModuleHandleA 函数,获取
当前模块的句柄,并将结果保存在 ModuleHandleA 变量中。若没有获取到,则让
filename=0。继续向下,调用 GetModuleHandleA 函数,获取当前模块的句柄,并将结果保存在
ModuleHandleA 变量中。将 ModuleHandleA 的值赋给 hInstance 变量。调用 LoadIconA
函数,根据指定的句柄和资源 ID 加载一个图标资源,并将结果转换为 LPARAM 类型,并将
结果保存在 ho 变量中。调用 LoadBitmapA 函数,根据指定的句柄和资源 ID 加载一个位图
资源,并将结果转换为 LPARAM 类型,并将结果保存在 dword_4192D0 变量中。
然后是调用了四个函数,我们逐个函数进行查看,
sub_41163C,声明了一些局部变量,包括 LibraryA、v3、InitCommonControlsEx 和
v6。将 this[1] 和 *this 的值都设置为0,相当于将它们初始化为NULL。调用
LoadLibraryA 函数,加载 "riched32.dll" 库,并将返回的句柄保存在 *this 中。调用
LoadLibraryA 函数,加载名为 "riched20.dll" 的动态链接库,并将返回的句柄保存在
this[1] 中。调用 InitCommonControls 函数,初始化公共控件。设置 v6 数组的值为8和
2047。调用 LoadLibraryA 函数,加载名为 "COMCTL32.DLL" 的动态链接库,并将返回的
句柄保存在 LibraryA 中。如果成功加载了 "COMCTL32.DLL",则继续执行以下操作:调用
GetProcAddress 函数,获取名为 "InitCommonControlsEx" 的函数地址,并将结果赋给
InitCommonControlsEx 变量。如果成功获取到了 InitCommonControlsEx 函数地址,调
用该函数,传递 v6 数组的地址作为参数,执行初始化公共控件的扩展操作。调用
FreeLibrary 函数,释放之前加载的 "COMCTL32.DLL" 动态链接库。调用 SHGetMalloc
函数,获取一个内存分配器的接口,并将结果保存在 ppMalloc 变量中(可能是全局变
量)。返回 this 指针。sub_4098D3(&unk_414C88, (LONG)Value);这里面向下调用的函数比较多,不在这里展开
说,
sub_40646C(v7),这个函数的作用是将一段内存区域的值初始化为0,推测该函数可能用于
初始化某个对象或结构体的成员变量
继续向下,调用sub_40AC94函数,这个函数使用 __stdcall 调用约定,声明了两个 HDC
(设备上下文)类型的变量 DC 和 v2。首先,检查全局变量 dword_41F318 的值是否为0。
如果为0,则表示之前没有获取过设备信息,需要进行获取。调用 GetDC 函数,获取屏幕的
设备上下文,并将结果保存在 DC 变量中。将 DC 的值赋给 v2 变量。如果成功获取到了设
备上下文 (DC 不为NULL),则执行以下操作:调用 GetDeviceCaps 函数,传递设备上下文
DC 和参数 88(代表设备的水平像素密度,即每英寸水平像素数),并将返回的结果保存在
全局变量 dword_41F318 中。调用 ReleaseDC 函数,释放之前获取的设备上下文 DC。最
后,函数返回计算结果,即将参数 a1 乘以全局变量 dword_41F318,再除以 96。
该函数的作用是根据设备的像素密度对给定的参数 a1 进行缩放计算。继续向下,调用 DialogBoxParamA 函数显示一个对话框,传递了以下参数:
ModuleHandleA: 模块句柄,表示对话框资源所在的模块。
"STARTDLG": 对话框资源的标识符或名称。
0: 父窗口的句柄,此处为0表示没有父窗口。
sub_40CC1F: 对话框过程函数,用于处理对话框消息。
0: 传递给对话框过程函数的附加参数。
将 hWndParent 变量的值设置为0,表示没有父窗口。将 lpParam 变量的值设置为0,表示
没有附加参数。将 dword_418ECC 变量的值设置为0。
接着就是调用了三个函数,sub_406482, sub_4116B4函数伪代码如下,综合两个函数的功
能,我们可以推断出这段代码的作用是在特定条件下调用 OleUninitialize 函数,释放加
载的库,并通过 ppMalloc 接口释放内存。
sub_406482
接收一个 _BYTE* 类型的指针参数 this。
检查 this 指针偏移为20的字节的值是否为非零。
如果该值非零,调用 OleUninitialize 函数。sub_4116B4
接收一个 HMODULE* 类型的指针参数 this。
检查 this 指针所指向的内容是否为非空。
如果非空,调用 FreeLibrary 函数,释放指向的库。
将 this[1] 的值赋给 v2 变量。
检查 v2 是否为非空。
如果非空,调用 FreeLibrary 函数,释放指向的库。
调用 ppMalloc->lpVtbl->Release(ppMalloc),通过 ppMalloc 接口调用
Release 方法释放内存。
接下来是很多条件语句,如果 byte_419F88 的值为非零,调用 sub_40B71C 函数。调用
sub_40A27C 函数,传递了 &String、0 和 128 作为参数。如果 dword_41A824 的值为非
零,调用 sub_40A1F8 函数,传递了 dword_41A818 作为参数。调用 DeleteObject 函
数,释放 ho 所表示的 GDI 对象。如果 dword_4192D0 的值为非零,调用 DeleteObject
函数,释放 dword_4192D0 所表示的 GDI 对象。如果 uExitCode[0] 的值为零且
dword_419F78 的值为非零,调用 sub_40324F 函数,传递了 uExitCode 的地址和 255 作
为参数。将 dword_419F78 的值设置为2。如果 hHandle 的值非空,调用 sub_40B6DD 函
数,传递了 hHandle 作为参数。接下来就是释放与 OLE 初始化相关的资源,然后终止当前进程的执行,并以指定的退出代
码退出。uExitCode[0] 变量的值决定了进程的退出代码。
sub_4098D3
分析样本二:no9TeYAoL030bliKeYsvJcB.exe
1.基础信息
hash校验值
MD5: 312AD3B67A1F3A75637EA9297DF1CEDB
SHA1: 7D922B102A52241D28F1451D3542DB12B0265B75
SHA256:3b4c1d0a112668872c1d4f9c9d76087a2afe7a8281a6cb6b972c95fb2f4eb28e
CRC32: 360F1798
节表信息:创建时间为2022年7月29日
查壳,也是没有壳的
2.云沙箱
微步在线云沙箱:腾讯哈勃分析:
奇安信情报沙箱:根据沙箱我们基本对这个样本已经有了全方位的认识,不过我们还是接下来通过常用的手段
对这个样本进行分析。3.行为分析
直接使用procman进行分析,先使用processname进行匹配,过滤该样本执行后的所有动
作:
文件操作,发现了大量创建文件、读取文件、关闭文件的动作。同时也有网络外联请求:
创建线程
4.静态分析
导入表分析
下面罗列了该样本导入表中的所有函数,可以看到有创建线程、创建文件、获取文件大小、
设置环境变量的值等等。
GetCurrentProcess (KERNEL32):获取当前进程的句柄。
VirtualAlloc (KERNEL32):分配或保留进程的虚拟内存空间。
lstrcatA (KERNEL32):将两个字符串连接起来。
GetModuleHandleA (KERNEL32):获取指定模块的句柄。
SetCurrentDirectoryA (KERNEL32):设置当前进程的当前工作目录。
Sleep (KERNEL32):使当前线程暂停执行一段时间。
LoadLibraryA (KERNEL32):加载指定的动态链接库(DLL)文件。
DeleteFileA (KERNEL32):删除指定的文件。
lstrcpyA (KERNEL32):将字符串复制到目标缓冲区。
CreateThread (KERNEL32):创建一个新的线程。
GetProcAddress (KERNEL32):获取动态链接库中导出函数的地址。
GetFileSize (KERNEL32):获取文件的大小。GetConsoleWindow (KERNEL32):获取控制台窗口的句柄。
GetLastError (KERNEL32):获取最近一次发生的错误代码。
GetModuleHandleW (KERNEL32):获取指定模块的句柄。
HeapFree (KERNEL32):释放先前通过堆分配的内存块。
lstrlenA (KERNEL32):获取字符串的长度。
LoadLibraryW (KERNEL32):加载指定的动态链接库(DLL)文件。
GetProcessHeap (KERNEL32):获取当前进程的堆句柄。
WriteConsoleW (KERNEL32):向控制台窗口写入字符或文本。
CloseHandle (KERNEL32):关闭打开的对象句柄。
CreateFileW (KERNEL32):创建或打开文件或设备。
SetFilePointerEx (KERNEL32):设置文件指针的位置。
GetConsoleMode (KERNEL32):获取控制台输入模式和输出模式。
GetConsoleOutputCP (KERNEL32):获取当前控制台输出的代码页。
FlushFileBuffers (KERNEL32):刷新文件缓冲区。
HeapReAlloc (KERNEL32):重新分配先前通过堆分配的内存块。
HeapSize (KERNEL32):获取指定堆上的内存块大小。
GetStringTypeW (KERNEL32):获取字符串中每个字符的类型。
SetStdHandle (KERNEL32):设置标准输入、输出或错误设备的句柄。
GetFileType (KERNEL32):获取文件的类型。
SetEnvironmentVariableW (KERNEL32):设置环境变量的值。
FreeEnvironmentStringsW (KERNEL32):释放先前分配的环境字符串块。
GetEnvironmentStringsW (KERNEL32):获取当前进程的环境字符串块。
WideCharToMultiByte (KERNEL32):将宽字符转换为多字节字符。
MultiByteToWideChar (KERNEL32):将多字节字符转换为宽字符。
GetCommandLineW (KERNEL32):获取命令行参数的宽字符版本。
GetCommandLineA (KERNEL32):获取命令行参数的多字节字符版本。
GetCPInfo (KERNEL32):获取代码页的信息。
GetOEMCP (KERNEL32):获取当前OEM字符集的代码页标识。
GetACP (KERNEL32):获取当前ANSI字符集的代码页标识。
IsValidCodePage (KERNEL32):检查给定的代码页标识是否有效。
FindNextFileW (KERNEL32):在搜索操作中查找下一个文件或子目录。
FindFirstFileExW (KERNEL32):在搜索操作中查找第一个文件或子目录。
FindClose (KERNEL32):关闭与指定搜索句柄关联的搜索操作。
HeapAlloc (KERNEL32):从堆中分配内存块。
GetTimeZoneInformation (KERNEL32):获取当前时区的信息。
LCMapStringW (KERNEL32):将一个字符串转换为另一种格式的字符串,根据指定的区域
设置进行地域特定的映射。
CompareStringW (KERNEL32):比较两个字符串的排序顺序,根据指定的区域设置进行排
序。
WriteFile (KERNEL32):向文件或设备写入数据。
GetStdHandle (KERNEL32):获取标准输入、输出或错误设备的句柄。
GetModuleFileNameW (KERNEL32):获取指定模块的完整路径和文件名。GetModuleHandleExW (KERNEL32):获取指定模块的句柄,允许指定模块的别名。
ExitProcess (KERNEL32):终止当前进程并返回退出代码。
EncodePointer (KERNEL32):对指针进行编码,以增加指针的安全性。
LoadLibraryExW (KERNEL32):加载指定的动态链接库(DLL)文件,并提供更多加载选
项。
UnhandledExceptionFilter (KERNEL32):用于处理未被应用程序显式捕获的异常的过
滤器函数。
SetUnhandledExceptionFilter (KERNEL32):设置全局未处理异常过滤器函数,用于
处理未被应用程序显式捕获的异常。
TerminateProcess (KERNEL32):终止指定进程。
IsProcessorFeaturePresent (KERNEL32):检查特定处理器功能是否存在。
IsDebuggerPresent (KERNEL32):检查当前进程是否正在被调试。
GetStartupInfoW (KERNEL32):获取当前进程的启动信息。
GetCurrentProcessId (KERNEL32):获取当前进程的标识符(ID)。
GetCurrentThreadId (KERNEL32):获取当前线程的标识符(ID)。
InitializeSListHead (KERNEL32):初始化一个单向链表头。
RaiseException (KERNEL32):引发一个异常。
SetLastError (KERNEL32):设置最近一次发生的错误代码。
EnterCriticalSection (KERNEL32):进入临界区,用于线程同步。
LeaveCriticalSection (KERNEL32):离开临界区,用于线程同步。
DeleteCriticalSection (KERNEL32):删除临界区对象。
RtlUnwind (KERNEL32):从当前函数位置开始执行异常处理,直到找到匹配的异常处理程
序。
InitializeCriticalSectionAndSpinCount (KERNEL32):初始化临界区对象,并指
定自旋次数。
TlsAlloc (KERNEL32):分配一个新的线程局部存储(
TLS)索引。
TlsGetValue (KERNEL32):获取指定线程的TLS值。
TlsSetValue (KERNEL32):设置指定线程的TLS值。
TlsFree (KERNEL32):释放先前分配的线程局部存储(
TLS)索引。
FreeLibrary (KERNEL32):释放指定动态链接库(DLL)的加载。
DecodePointer (KERNEL32):对先前通过EncodePointer编码的指针进行解码。
ShowWindow (USER32):显示或隐藏窗口。
QueryPerformanceCounter (api-ms-win-core-profile-l1-1-0):获取高精度性
能计数器的计数值。
GetSystemTimeAsFileTime (api-ms-win-core-sysinfo-l1-1-0):获取系统时间
作为文件时间。字符串分析
通过快捷键shift + f12 打开strings表。
查看字符串,共有307个,
存在url的字符串
winMain函数分析
默认情况下,vc编译的应用程序入口点在WinMain函数。
通过IDA加载的时候,如果程序有WinMain,也会默认停留在该函数,按下空格键将其转换
为正常汇编代码显示
首先看前四句,是函数执行前,为函数执行开辟栈空间的一个过程:push ebp //保存调用函数之前的EBP值,以便在函数结束后进行恢复
mov ebp, esp //将栈指针(ESP)的值复制给基址指针(EBP)。这将创建一个新的基址
指针,用于在函数内部引用栈上的局部变量和参数。
and esp, 0FFFFFFF0h //将栈指针与0FFFFFFF0h进行按位与操作。这个操作将栈指针向
下对齐到16字节边界。对齐栈指针可以提高内存访问的效率。
sub esp, 48h //从栈指针中减去48字节,为局部变量和临时数据分配空间。
接下来,将esi和edi的值压栈保存,方便后续使用。将0推入栈里,这是为了作为
nCmdShow参数传递给后面的函数调用,调用 GetConsoleWindow 函数,获取当前进程的
控制台窗口句柄。该函数返回值将存储在 eax 寄存器中。将 eax 寄存器的值推送到栈上。
这是为了作为 hWnd 参数传递给后面的函数调用。接下来会调用showwindows函数,这是
一个用于显示或者隐藏窗口的函数,BOOL ShowWindow(HWND hWnd, int nCmdShow);,如
今获取到窗口句柄(HWND),即指定了要显示或隐藏的窗口,并设置nCmdShow的值为0,
即隐藏窗口。
.text:00406949 push esi
.text:0040694A push edi
.text:0040694B push 0 ; nCmdShow
.text:0040694D call ds:GetConsoleWindow
.text:00406953 push eax ; hWnd
.text:00406954 call ds:ShowWindow
.text:0040695A mov esi, dsoadLibraryA
.text:00406960 lea eax, [esp+50h+LibFileName]
ncmdShow参数含义:
nCmdShow:指定窗口的显示状态。可以使用以下常量之一:
SW_HIDE (0):隐藏窗口。
SW_SHOWNORMAL (1):正常显示窗口。
SW_SHOWMINIMIZED (2):以最小化的方式显示窗口。
SW_SHOWMAXIMIZED (3):以最大化的方式显示窗口。
SW_SHOWNOACTIVATE (4):显示窗口,但不激活窗口。
SW_SHOW (5):显示窗口。
SW_MINIMIZE (6):最小化窗口。
SW_SHOWMINNOACTIVE (7):以最小化的方式显示窗口,但不激活窗口。
SW_SHOWNA (8):显示窗口,保持当前活动状态。
SW_RESTORE (9):恢复窗口的大小和位置,如果窗口最小化或最大化,则还原
为原始状态。
SW_SHOWDEFAULT (10):根据窗口的显示属性显示窗口。接下来我们结合伪代码一起看,将特定的值存储到 LibFileName 和 v33 数组中,并使用
SSE 指令 _mm_xor_si128 来执行 128 位宽的异或操作,对对 LibFileName 数组和 v33 数
组中的数据进行按位异或操作。使用修改后的 LibFileName 数组作为参数,调用
LoadLibraryA 函数来加载 DLL。可能是在动态加载 DLL 的过程中进行一些转换或保护操
作。
继续向下,若LibraryA非空,即成功加载了DLL,则对v33数组进行赋值,也对v34数组进行
赋值,并且对v33数组中的数据与v34进行按位异或操作。如果加载了第二个 DLL(通过
LoadLibraryA(v33) 的返回值非空),继续对v33和v34进行赋值并进行异或。使用修正后
的 v33 数组作为参数,再次调用 LoadLibraryA 函数来加载 DLL。如果加载了第三个 DLL
(通过 LoadLibraryA(v33) 的返回值非空),则调用sub_4013C0(LibraryA) 函数。
到这一步我想使用ollydbg对这个程序进行调试,看看这一步到底是在做什么,但是加载失
败了那我们继续从IDA Pro 里面分析,接下来到了sub_4013C0函数。
使用 hModule 和字符串 "GetCurrentProcess" 作为参数,调用 GetProcAddress 函数以获
取 "GetCurrentProcess" 函数的地址。然而,此处并未保存函数地址的返回值,可能是因为
后续没有使用到。接下来是一些重复的操作,不断地赋值,不断地通过 GetProcAddress
函数获取对应函数的地址,使用LoadLibraryA函数加载"Shell32.dll"库,并将其句柄存储在
变量LibraryA中。将变量result设置为通过GetProcAddress函数获取的LibraryA库中v5的低
位字节(m128i_i8)所表示的函数的地址。将result的整数值转换为int类型,并存储在变量
dword_424744中。返回result的值。
(
Shell32.dll 是 Windows 操作系统中的一个动态链接库(DLL)文件。它包含与 Windows
Shell 相关的各种功能和资源,为操作系统提供图形用户界面(GUI)。包括文件和文件夹
管理、shell操作、用户界面组件等等)
可以大致推测出,现在的操作就是为了能够为后续执行命令作准备又回到WinMain函数,使用GetCurrentProcess()函数获取当前进程的句柄并赋值给
CurrentProcess。接下来是一个循环,遍历一个链表并执行特定的操作。通过访问
NtCurrentPeb()->Ldr->InLoadOrderModuleList.Flink,将变量i初始化为链表的起始节点。
进入一个无限循环,每次迭代时,将变量i更新为当前节点的下一个节点(
i = i->Flink)。
从当前节点中提取一些值,计算得到变量v8。检查变量v8是否与当前节点相同。如果不相
同,则继续执行以下步骤;否则,跳转到LABEL_13。从变量v8中获取一个指针v9。如果v9
不为空(非零),则跳出循环。跳转到LABEL_13。计算变量v10的值,它是一个指针。进
入另一个无限循环,每次迭代时,根据v10的值计算出另一个指针v11。对v11进行一系列操
作,包括计算哈希值v12。如果哈希值v12等于1620655005,则跳出循环。更新变量v10和
Flink的值。如果v9为空(零),则跳转到LABEL_13。最后,通过函数指针调用一个函数,传递CurrentProcess和0x8000作为参数。
继续向下,
调用 CreateThread 函数创建一个新线程,其中 StartAddress 是线程的起始地址。该线
程在后台运行,没有指定堆栈大小和创建标志,参数为 0。进入一个无限循环,直到
byte_424740 变量为非零为止。在循环中,通过遍历一个链表来查找某些条件满足的节
点。循环内部,通过访问 NtCurrentPeb()->Ldr->InLoadOrderModuleList.Flink,将变
量 j 初始化为链表的起始节点。进入一个内部循环,在内部循环中,从当前节点中提取一
些值,计算得到变量 v17。检查变量 v17 是否与当前节点相同。如果不相同,则继续执行
以下步骤;否则,跳转到 LABEL_23。从变量 v17 中获取一个指针 v18。如果 v18 不为空
(非零),则跳出内部循环。跳转到 LABEL_23。计算变量 v19 的值,它是一个指针。进
入另一个内部循环,在内部循环中,根据 v19 的值计算出另一个指针 v21。对 v21 进行一系列操作,包括计算哈希值 v20。如果哈希值 v20 等于 799419560,则跳出内部循环。更
新变量 v19 和 v16 的值。如果 v18 为空(零),则跳转到 LABEL_23。最后,通过函数指
针调用一个函数,传递参数 175,并执行特定的操作。循环回到第 2 步,直到
byte_424740 变量为非零。返回 0。
拓展1.恶意样本执行前的校验一般来说,攻击者在执行真正的恶意功能前,可能会对程序进行以下几个方面的校验
1 互斥体校验 此步操作是为了防止多开程序,从而导致在进程列表中有多个进程,被运维
人员或者用户发现异常。
2 反调试校验 此步操作用于判断当前程序是否处于调试的状态
3 虚拟机校验 此步操作用于判断程序是否运行在虚拟机中
4 联网校验 此步操作用于判断用户是否可以连接互联网,或者是否可以连接外网
5 操作系统校验 此步操作用于判断用户计算机的操作系统位数,通常来说,进行此步校验
的不会直接结束程序,会根据32位和64位的不同,分别执行不同的代码。
6 是否被其他攻击者攻陷 (查找是否存在其他攻击者留下的攻击痕迹,如果存在则退出)
7 是否值得后续的攻击(比如窃密的可能会判断用户计算机所在的地区、挖矿的会判断用
户计算机的性能等)
暂时就想到这些,后面有想到的话再补充。总的来说,做这些校验,都是为了后续能够开
展更好的攻击。
参考文章
恶意代码分析之行为分析及样本收集(
https://www.anquanke.com/post/id/208208#h3-3)
从"新"开始学习恶意代码分析——静态分析(
https://www.anquanke.com/post/id/207594#h
2-5)
从"新"开始学习恶意代码分析——再静态分析(
https://www.anquanke.com/post/id/207799
#h3-10)
IDA 中怎么查看函数的调用关系(
https://blog.csdn.net/u014602228/article/details/12209
1620)
|
|