前言
这是一款比较简单的木马,通过分析此样本可以训练分析思路、分析逻辑,并且加强IDA的使用.本人安全公司实习生,刚入圈几个月,所以文章中会有说的不对的地方,欢迎指出.择优吸收.
样本信息:
File: virus.exe
SHA1: 71b7322291b5a89d227b5cfe82106fce51036da2
SHA256: b48ebc54b9717bbe3a9de3fd5744c8ad1fdd3a26c7b47f6170b98f8abde9a744
LsHashS: 1155293a7b9091923b1d1511a8d9c17589bb1b55b4613bd55313ba7b3594b899
MD5: 00877507f0b812599868a647330d0630
分析环境 Windows7_Service Pack 1
分析工具 OD IDA 火绒剑 PEID
0x1.初步观察了解行为
拖入PEID查一下有没有壳子
yoda's Protector v1.02 (.dll,.ocx) -> Ashkbiz Danehkar (h) [Overlay] *
不了解,只知道有个加密壳
右键查看文件属性
属性为空,至少确定了不是个正规公司开发的程序,直接上火绒剑吧
过滤FILE_open REG_openkey REG_getval FILE_read
行为(个人习惯,因为这几个行为基本没卵用)
不得不说,火绒剑
真是方便,程序行为十分清晰了
样本启动了svchost.exe
后直接结束了
而下方的行为显然不是svchost.exe
应该有的,目测会有一段注入ShellCode的代码
到这里,这个程序基本上可以报毒了 : 静默运行+启动svchost.exe执行敏感代码+无任何文件描述信息
我们接下来要对这个样本进行稍微详细的分析,看看他具体干了什么
0x2.第一层代码分析
将样本拖入IDA32,分别查看字符串+导入表
发现字符串有很多URL
还有请求头信息,
我们随便选一个:http://morphed.ru/static.php
去VT查一下
很好,非常不正常!我们又可以为他扣掉几分(所以到现在还是在做黑白鉴定的工作 --_-- )
该步入正题了,上面查看文件属性,文件大小不是很大,字符串和导入表信息也不多,因此我们决定从入口点开始进行分析,在IDA中找到入口点按下F5分析伪源码 (个人比较喜欢F5,但是这是不好的,遇到强度高的样本,F5反而会增加你的工作量)
可以看到上来就开始开辟内存,拷贝0x00401000
处的数据,我们去看看401000
存了些啥
一段函数代码+一堆未知数据,暂时记住这个地方,后面可能会用到
简单起个名后,继续分析,我们看到memcpy下面的sub_407AC4
紧接着就对这块内存进行了操作,跟进去看看函数内部情况
没有再调用函数,反而进行了很多数据的运算,而一般这种情况不是加密就是解密,结合上面的行为分析结果,加上调用407AC4
这个函数后并没有接收这个函数的返回值,推测此处对ShellCode进行了解密一类的操作
回到入口函数,观察下一行代码sub_407B02(pAlloc_, 0x490, LoadLibraryA, GetProcAddress);
参数有很明显的特点:
- 一段ShellCode的首地址
- 一个未知的常量
- 两个经典远线程注入需要用到的函数地址
进入函数内部,为参数命名后,看一下代码
哦吼,又是没有函数调用,全部在对数据进行操作,两层循环嵌套,循环内调用了GetProcAddress和LoadLibrary
,根据传入的参数401000 490
我们去看看401490
都存了些啥
定位到401490
按D
让其以字节显示,观察附近的数据,有了很明显的发现
下面是一个个函数名字符串,我们按A Alt+A
把这些数据以字符串进行展示,观察结果
根据401490
附近的字符串,我们最终可以推测出sub_407B02
处的函数用于加载函数,模拟导入表功能,将其命名FixImport
回到主函数继续分析:return ((int (__stdcall *)(_DWORD))(pAlloc_0 + 0xC0))(sub_407992);
可以看到这行代码用于执行4010C0
出的函数,传入参数为407992
,一个函数地址
我们之前记得401000
向下是由一段数据+一段函数代码组成
而这行代码以函数指针的形式调用了4010C0
的代码,却不是直接写成函数
由此我们验证了上文对401000
处数据为ShellCode的猜想,接下来我们则需要知道4010C0
干了什么
0x3.第二层代码提取
进入4010C0
,在4010C0
处按P
,使IDA识别为函数
按F5,解析为伪源码,发现失败,提示call的地址找不到?这是怎么回事?
还记得上文中有两个函数分别对401000
附近的数据进行了解密+修复导入函数一类的操作吗?
没错,这段代码只有在401000
段被正确修复后才能执行,至此,我们需要使用OD进行动态调试来dump出401000处的数据
打开OD,拖入样本,在地址0040798E
处下断点 (4010C0处为函数头部,在40798E处调用了这个函数)
观察OD各个窗口的值
我们上文一直按照401000
分析的,因为IDA静态分析我们并不知道VirtualAlloc分配到了哪里,所以我们只能忽略入口函数的那句memcpy
,但是现在我们动态调试,就可以查看分配的那块内存了:eax = 0x200c0
反汇编窗口跳转到0x200C0
,数据窗口跳转到0x20000
(4010C0之前有一段未知数据,我们要弄清楚那段数据)
可以看到,call处的地址都变了,而且OD已经正确识别出了各个函数的名字,这也是为什么IDA无法F5的原因,这里的call的地址都是通过解密函数+地址修复函数进行处理过的
仔细观察,call后面的地址都是小于200C0
的,正好对应上了我们20000~200C0
这一段未知数据
数据窗口右键->长型->地址
一切都变得清晰了,从20000~200C0
是样本自身维护的一张导入表,以供ShellCode使用,400C0~42000
包含了一些可执行的代码
点击M
右键20000
内存段->数据->备份->保存数据到文件
这样,ShellCode这段代码我们就dump下来了,接下来我们要对这段ShellCode进行分析
将dump下来的数据(后文称之为1.mem)拖入IDA32
点击Edit->Segments->Rebase Program进行基地址的修改
由于OD中内存申请地址为0x20000
我们也要在IDA中设置为0x20000
以保证IDA能正确解析汇编代码
点击OK,观察代码变化
不再是一堆堆的字节数据了,变成了可读性较高的汇编代码,但是还有一处瑕疵
20000~200C0
处应该有一些函数的,IDA这里没有为我们解析,这会让我们分析工作量增加,我们接下来要使用IDA的脚本执行功能:
0x4.IDA脚本批量为Dump中的函数命名
点击File->Produce FIle->Dump database to IDC file...->保存
此时,我们dump文件同目录下会生成一个与dump文件同名的idc文件:1.idc
用notepad++将其打开
static main(void)
{
// set 'loading idc file' mode
set_inf_attr(INF_GENFLAGS, INFFL_LOADIDC|get_inf_attr(INF_GENFLAGS));
GenInfo(); // various settings
Segments(); // segmentation
Enums(); // enumerations
Structures(); // structure types
ApplyStrucTInfos(); // structure type infos
Patches(); // manual patches
SegRegs(); // segment register values
Bytes(); // individual bytes (code,data)
Functions(); // function definitions
// clear 'loading idc file' mode
set_inf_attr(INF_GENFLAGS, ~INFFL_LOADIDC&get_inf_attr(INF_GENFLAGS));
}
我们会看到这样一段代码,把函数内容全部删除,只留下Bytes(); // individual bytes (code,data)
static main(void)
{
Bytes(); // individual bytes (code,data)
}
接下来Ctrl+F
搜索Bytes
,我们看看这个Bytes
函数干了什么
static Bytes(void) {
Bytes_0();
end_type_updating(UTP_STRUCT);
}
可以看到Bytes
调用了Bytes_0
,我们再搜索Bytes_0
去看看他的内容
static Bytes_0(void) {
auto x;
#define id x
update_extra_cmt (0X20000, E_PREV + 0, "; File Name : G://1.mem");
update_extra_cmt (0X20000, E_PREV + 1, "; Format : Binary file");
update_extra_cmt (0X20000, E_PREV + 2, "; Base Address: 0000h Range: 0000h - 2000h Loaded length: 2000h");
create_dword (0X20000);
create_dword (0X20004);
create_dword (0X20008);
create_dword (0X2000C);
create_dword (0X20010);
create_dword (0X20014);
create_dword (0X20018);
create_dword (0X2001C);
create_dword (0X20020);
create_dword (0X20024);
create_dword (0X20028);
create_dword (0X2002C);
create_word (0X20036);
create_word (0X2003A);
create_dword (0X2003C);
make_array (0X2003C, 0X9);
create_strlit (0X20060, 0X20068);
set_name (0X20060, "a753");
create_strlit (0X20068, 0X20094);
set_name (0X20068, "aSystem32Wuaucl");
create_strlit (0X20094, 0X200C0);
set_name (0X20094, "aSyswow64Svchos");
create_insn (0X200C0);
create_insn (x=0X200C3);
op_hex (x, 1);
create_insn (0X2014F);
create_insn (0X203C9);
set_cmt (0X2041D, "Trap to Debugger", 0);
create_insn (x=0X2041D);
op_hex (x, 0);
create_insn (0X2041E);
create_insn (0X20424);
create_insn (0X2042A);
create_insn (0X20430);
create_insn (0X20436);
create_insn (0X2043C);
create_insn (0X20442);
create_insn (0X20448);
create_insn (0X2044E);
create_insn (0X20454);
create_insn (0X2045A);
create_insn (0X20460);
create_insn (0X20466);
create_insn (0X2046C);
create_insn (0X20472);
create_insn (0X20478);
create_insn (0X2047E);
create_insn (0X20484);
create_insn (0X2048A);
create_dword (x=0X20490);
op_plain_offset (x, 0, 0);
op_plain_offset (x, 128, 0);
create_dword (0X20494);
make_array (0X20494, 0X2);
create_byte (0X2049D);
make_array (0X2049D, 0X3);
create_dword (x=0X204A0);
op_plain_offset (x, 0, 0);
op_plain_offset (x, 128, 0);
create_dword (x=0X204A4);
op_plain_offset (x, 0, 0);
op_plain_offset (x, 128, 0);
make_array (0X204A8, 0X8);
make_array (0X204B2, 0X2);
create_dword (0X204B4);
make_array (0X204B4, 0X6);
create_dword (x=0X204CC);
op_plain_offset (x, 0, 0);
op_plain_offset (x, 128, 0);
create_dword (x=0X204D0);
op_plain_offset (x, 0, 0);
op_plain_offset (x, 128, 0);
create_byte (0X204D5);
make_array (0X204D5, 0X3);
make_array (0X204DA, 0X2);
make_array (0X204DE, 0X2);
make_array (0X204E2, 0X2);
create_dword (x=0X204E4);
op_plain_offset (x, 0, 0);
op_plain_offset (x, 128, 0);
create_dword (x=0X204E8);
op_plain_offset (x, 0, 0);
op_plain_offset (x, 128, 0);
make_array (0X204EE, 0X2);
make_array (0X204F2, 0X2);
make_array (0X204F6, 0X2);
make_array (0X204FA, 0X6);
create_dword (x=0X20500);
op_plain_offset (x, 0, 0);
op_plain_offset (x, 128, 0);
create_dword (x=0X20504);
op_plain_offset (x, 0, 0);
op_plain_offset (x, 128, 0);
create_byte (0X20509);
make_array (0X20509, 0X3);
create_dword (x=0X2050C);
op_plain_offset (x, 0, 0);
op_plain_offset (x, 128, 0);
make_array (0X20512, 0X2);
create_byte (0X20515);
make_array (0X20515, 0X3);
make_array (0X20519, 0X7);
create_strlit (0X20522, 0X20533);
set_name (0X20522, "aNtdelayexecuti");
create_strlit (0X20536, 0X2053E);
set_name (0X20536, "aZwclose");
create_byte (0X2053F);
create_strlit (0X20541, 0X20550);
set_name (0X20541, "aWcreatesection");
create_strlit (0X20552, 0X20565);
set_name (0X20552, "aZwmapviewofsec");
create_strlit (0X20569, 0X20582);
set_name (0X20569, "aWqueryinformat");
create_word (0X20582);
create_strlit (0X20585, 0X20593);
set_name (0X20585, "aWresumethread");
create_strlit (0X20597, 0X205AB);
set_name (0X20597, "aWunmapviewofse");
create_strlit (0X205AC, 0X205B6);
set_name (0X205AC, "aNtdllDll");
create_strlit (0X205B8, 0X205C4);
set_name (0X205B8, "aClosehandle");
create_strlit (0X205C7, 0X205D2);
set_name (0X205C7, "aReatefilew");
create_strlit (0X205D5, 0X205E3);
set_name (0X205D5, "aReateprocessw");
create_strlit (0X205E7, 0X205F2);
set_name (0X205E7, "aXitprocess");
create_word (0X205F2);
create_strlit (0X205F4, 0X20607);
set_name (0X205F4, "aGetmodulefilen");
create_byte (0X20609);
make_array (0X20609, 0X3);
create_strlit (0X2060C, 0X2061B);
set_name (0X2060C, "aTmodulehandlew");
create_strlit (0X2061E, 0X2062F);
set_name (0X2061E, "aGetthreadconte");
create_strlit (0X20633, 0X20647);
set_name (0X20633, "aEtwindowsdirec");
create_strlit (0X2064B, 0X20662);
set_name (0X2064B, "aEtenvironmentv");
create_strlit (0X20664, 0X20671);
set_name (0X20664, "aVirtualalloc");
create_byte (0X20673);
create_strlit (0X20674, 0X20680);
set_name (0X20674, "aVirtualfree");
create_strlit (0X20682, 0X2068B);
set_name (0X20682, "aLstrcatw");
create_strlit (0X2068C, 0X20699);
set_name (0X2068C, "aKernel32Dll");
make_array (0X20699, 0X3);
create_dword (0X2069C);
make_array (0X2069C, 0X25A);
create_dword (x=0X21004);
op_plain_offset (x, 0, 0);
op_plain_offset (x, 128, 0);
create_word (0X2100A);
create_dword (0X2100C);
make_array (0X2100C, 0X3FD);
}
可以看到一大堆函数代码,不要管,全部删除
static Bytes_0(void) {
}
接下来我们要用到一个函数 set_name (地址,"名字");
这是IDA提供的接口,用于为地址命名
我们需要将20000~200C0
处的函数全部命名
回到OD,数据窗口定位到20000
,复制全部函数数据
00020000 7DD7186E kernel32.VirtualFree
00020004 7DD71856 kernel32.VirtualAlloc
00020008 7DD789F1 kernel32.SetEnvironmentVariableW
0002000C 7DD743E2 kernel32.GetWindowsDirectoryW
00020010 7DD979D4 kernel32.Wow64GetThreadContext
00020014 7DD734B0 kernel32.GetModuleHandleW
00020018 7DD74950 kernel32.GetModuleFileNameW
0002001C 7DD77A10 kernel32.ExitProcess
00020020 7DD7103D kernel32.CreateProcessW
00020024 7DD73F5C kernel32.CreateFileW
00020028 7DD71410 kernel32.CloseHandle
0002002C 7DD9828E kernel32.lstrcatW
00020030 00000000
00020034 7DE90058 ntdll_12.ZwResumeThread
00020038 7DE8FAC8 ntdll_12.ZwQueryInformationProcess
0002003C 7DE8FC40 ntdll_12.ZwMapViewOfSection
00020040 7DE8FF94 ASCII "窑"
00020044 7DE8F9D0 ntdll_12.ZwClose
00020048 7DE8FC70 ntdll_12.ZwUnmapViewOfSection
0002004C 7DE8FD6C ntdll_12.ZwDelayExecution
00020050 00000000
00020054 00000000
00020058 00000000
0002005C 00000000
将上面这段数据以set_name (xxx,"xxx");
的格式进行替换,具体替换方法可以手动替换,也可以正则替换,看各位喜好
替换完的结果:
set_name (0x00020000, "VirtualFree");
set_name (0x00020004, "VirtualAlloc");
set_name (0x00020008, "SetEnvironmentVariableW");
set_name (0x0002000C, "GetWindowsDirectoryW");
set_name (0x00020010, "Wow64GetThreadContext");
set_name (0x00020014, "GetModuleHandleW");
set_name (0x00020018, "GetModuleFileNameW");
set_name (0x0002001C, "ExitProcess");
set_name (0x00020020, "CreateProcessW");
set_name (0x00020024, "CreateFileW");
set_name (0x00020028, "CloseHandle");
set_name (0x0002002C, "lstrcatW");
set_name (0x00020034, "ZwResumeThread");
set_name (0x00020038, "ZwQueryInformationProcess");
set_name (0x0002003C, "ZwMapViewOfSection");
set_name (0x00020044, "ZwClose");
set_name (0x00020048, "ZwUnmapViewOfSection");
set_name (0x0002004C, "ZwDelayExecution");
//记得别忘了后面的;和地址的0x 否则IDA执行脚本时会报错
将这段数据复制到Bytes_0
函数体中,结果如下
static Bytes_0(void) {
set_name (0x00020000, "VirtualFree");
set_name (0x00020004, "VirtualAlloc");
set_name (0x00020008, "SetEnvironmentVariableW");
set_name (0x0002000C, "GetWindowsDirectoryW");
set_name (0x00020010, "Wow64GetThreadContext");
set_name (0x00020014, "GetModuleHandleW");
set_name (0x00020018, "GetModuleFileNameW");
set_name (0x0002001C, "ExitProcess");
set_name (0x00020020, "CreateProcessW");
set_name (0x00020024, "CreateFileW");
set_name (0x00020028, "CloseHandle");
set_name (0x0002002C, "lstrcatW");
set_name (0x00020034, "ZwResumeThread");
set_name (0x00020038, "ZwQueryInformationProcess");
set_name (0x0002003C, "ZwMapViewOfSection");
set_name (0x00020044, "ZwClose");
set_name (0x00020048, "ZwUnmapViewOfSection");
set_name (0x0002004C, "ZwDelayExecution");
}
保存,退出,回到IDA,按Alt+F7
弹出脚本选择框
选择1.idc
,点打开
可以神奇的发现,20000
开始的地址已经被IDA正确解析,在200C0
处按P
,让IDA将其解释为一个函数头部
这样就是我们最终弄好的结果,IDA可以正确解析这段代码,我们看的也会特别清晰
0x5.第二层代码分析
按下F5观察伪源码
这段代码就不详细说明了,大体逻辑就是判断系统位数,取出对应的系统文件,注入恶意代码,运行系统文件
我们的重点在注入到系统文件中的那部分代码是什么,这里我们不分析第二层代码,采用一个更加便捷的方式
0x6.第三层代码提取
我们此处依然采取dump 的方式进行提取,直接运行样本,不做任何阻拦,样本会创建一个被注入了恶意代码的svchost.exe
进程,我们火绒剑选择svchost.exe,点击下方内存列表,右击0x20000
这块内存,选择内存转储
PS:若火绒剑无法保存,可以尝试使用其他工具进行保存,需要注意的是svchost为64位程序,OD是用不了的,此处推荐PCHunter64
拷贝出来的dump文件才用相同方式拖入IDA,更改基址,批量命名,此处省略
0x7.第三层代码分析
如果操作没有错误,IDA解析后的代码应该如下:
到了这里样本的最终行为已经毫无遮拦了,我们甚至不用进入F5分析逻辑,只需简单的观察字符串即可分析出这个样本的大致行为逻辑
注册表操作API+启动项目录字符串 = 添加开机自启
网络操作相关API用于发送请求,可能是下载一个恶意程序亦或是上传一段用户隐私数据
总结
整篇文章对于代码的分析很少,各位如果想要细致分析可以自行抠代码,没有了保护,所有写法已经是透明的了,因此继续逆向只是个体力活,我这里就不演示了
主要是给初学的小白演示下这种多层代码释放嵌套的样本怎么处理,已经IDA的强大功能
择优吸收