记录黑客技术中优秀的内容,传播黑客文化,分享黑客技术精华

一款木马释放器的简单分析及IDA的骚操作

2020-11-30 19:54

本文为看雪论精华文章

看雪论坛作者ID:SSH山水画





前言



这是一款比较简单的木马,通过分析此样本可以训练分析思路、分析逻辑,并且加强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.VirtualFree00020004  7DD71856  kernel32.VirtualAlloc00020008  7DD789F1  kernel32.SetEnvironmentVariableW0002000C  7DD743E2  kernel32.GetWindowsDirectoryW00020010  7DD979D4  kernel32.Wow64GetThreadContext00020014  7DD734B0  kernel32.GetModuleHandleW00020018  7DD74950  kernel32.GetModuleFileNameW0002001C  7DD77A10  kernel32.ExitProcess00020020  7DD7103D  kernel32.CreateProcessW00020024  7DD73F5C  kernel32.CreateFileW00020028  7DD71410  kernel32.CloseHandle0002002C  7DD9828E  kernel32.lstrcatW00020030  0000000000020034  7DE90058  ntdll_12.ZwResumeThread00020038  7DE8FAC8  ntdll_12.ZwQueryInformationProcess0002003C  7DE8FC40  ntdll_12.ZwMapViewOfSection00020040  7DE8FF94  ASCII "窑"00020044  7DE8F9D0  ntdll_12.ZwClose00020048  7DE8FC70  ntdll_12.ZwUnmapViewOfSection0002004C  7DE8FD6C  ntdll_12.ZwDelayExecution00020050  0000000000020054  0000000000020058  000000000002005C  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处按,让IDA将其解释为一个函数头部:
这样就是我们最终弄好的结果,IDA可以正确解析这段代码,我们看的也会特别清晰。




0x5 第二层代码分析



按下F5观察伪源码:
这段代码就不详细说明了,大体逻辑就是判断系统位数,取出对应的系统文件,注入恶意代码,运行系统文件。
 
我们的重点在注入到系统文件中的那部分代码是什么,这里我们不分析第二层代码,采用一个更加便捷的方式。




0x6 第三层代码提取



我们此处依然采取dump 的方式进行提取,直接运行样本,不做任何阻拦,样本会创建一个被注入了恶意代码的svchost.exe进程,我们火绒剑选择svchost.exe,点击下方内存列表,右击0x20000这块内存,选择内存转储。
 
PS:若火绒剑无法保存,可以尝试使用其他工具进行保,需要注意的是svchost为64位程序,OD是用不了的,此处推荐PCHunter64。
 
拷贝出来的dump文件才用相同方式拖入IDA,更改基址,批量命名,此处省略。




0x7 第三层代码分析



如果操作没有错误,IDA解析后的代码应该如下:


到了这里样本的最终行为已经毫无遮拦了,我们甚至不用进入F5分析逻辑,只需简单的观察字符串即可分析出这个样本的大致行为逻辑。


注册表操作API+启动项目录字符串 = 添加开机自启。


网络操作相关API用于发送请求,可能是下载一个恶意程序亦或是上传一段用户隐私数据。




总结



整篇文章对于代码的分析很少,各位如果想要细致分析可以自行抠代码,没有了保护,所有写法已经是透明的了,因此继续逆向只是个体力活,我这里就不演示了。
 
主要是给初学的小白演示下这种多层代码释放嵌套的样本怎么处理,已经IDA的强大功能。



- End -



看雪ID:SSH山水画

https://bbs.pediy.com/user-home-867232.htm

  *本文由看雪论坛 SSH山水画 原创,转载请注明来自看雪社区。


好消息!!现在看雪《安卓高级研修班》线下班 & 网课(12月班)开始同步招生啦!以前没报上高研班的小伙伴赶快抓紧机会报名,升职加薪唾手可得!!



推荐文章++++

* Frida配合BurpSuite的Brida插件自动解密取证

* 利用Xposed对ollvm后的so中flag爆破

* Windows内存篇Ⅱ x64内核内存布局的迁移演变

* Linux kernel pwn:ROP & ret2usr

* 不用CR0或MDL修改内核非分页写保护内存的一种思路(x64)







公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com



求分享

求点赞

求在看


“阅读原文一起来充电吧!

知识来源: https://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458377189&idx=1&sn=d70dcbd5f5eff20dcfd5c47200774ecb&chksm=b180eb6f86f76279cf271d5134f76842a39198f5bff0f8bfa42c04dbfeaf8150453877f76c3d&scene=27&k

阅读:25101 | 评论:0 | 标签:木马

想收藏或者和大家分享这篇好文章→复制链接地址

“一款木马释放器的简单分析及IDA的骚操作”共有0条留言

发表评论

姓名:

邮箱:

网址:

验证码:

公告

❤人人都能成为掌握黑客技术的英雄⛄️

ADS

标签云