以前分析恶意样本关注的是样本的行为和功能,这个通过监控就可以了解个大概,而本文主要分享的是恶意样本中一些精心构造的东西。有的时候工具太高级,反而会让你错过一些逆向中的乐趣。
0x1 一些准备
异常和栈的使用,在平时的编程中一般不会被人注意,异常更多的用来调试程序使其更加稳定,而堆栈都交由编译器控制,而就有“坏人”利用这两点铺垫了一条通向邪恶的执行流程。异常的使用可以起到反调试的作用,当然如果你用StrongOD或忽略异常来调试就不存在了;栈的使用可能会带来一些免杀的效果。
0x1-1 异常编号998【0地址的妙用】
GetLastError是获取函数调用返回的一个错误结果,其中内存分配访问失效的编号是998(十六进制标示:0x3E6),看“坏人”是如果制造出这个值来得。
FindClose(0)就是这个恶意样本中利用的API+参数组合,简单分析下这个组合是怎么触发异常的。
FindClose(HANDLE hHandle)函数的功能是查找一个句柄并关闭,句柄简单地理解就是一个指针,指向内存中一个可读区域的起始。如果想访问不可读的区域就会产生异常,例如0地址空间。
## FindClose API的起始反汇编代码7C80EE67 > push 0x24
7C80EE69 push kernel32.7C80EF00 7C80EE6E call kernel32.7C8024D6 7C80EE73 mov esi,dword ptr ss:[ebp+0x8] ;ebp+0x8处是传入的句柄 ;将句柄值放入esi ==0 7C80EE76 xor eax,eax 7C80EE78 inc eax 7C80EE79 cmp esi,eax 7C80EE7B je Xkernel32.7C80EEF2 7C80EE7D cmp esi,-0x1 7C80EE80 je kernel32.7C835863 7C80EE86 and dword ptr ss:[ebp-0x4],0x0 7C80EE8A mov dword ptr ss:[ebp-0x24],esi 7C80EE8D lea edi,dword ptr ds:[esi+0x14] ;句柄[0]+0x14的值放入edi ;edi == 0x14 7C80EE90 push edi ;将edi作为参数传入 7C80EE91 call dword ptr ds:[<&ntdll.RtlEnterCriticalSection>] |
以上是FindClose中NULL值的传递过程,真正产生异常的地方在接下来调用的ntdll.RtlEnterCriticalSection中。
##ntdll.RtlEnterCriticalSection API起始反汇编代码7C921000 n> mov ecx,dword ptr fs:[0x18]
7C921007 mov edx,dword ptr ss:[esp+0x4] ;esp+0x4指向传入的参数 ;edx == 0x14 7C92100B cmp dword ptr ds:[edx+0x14],0x0 ;edx+0x14 == 0x00000028 ;访问0x00000028空间时造成异常 |
对于0地址的使用,这个恶意样本中还使用了一个API:
SCardForgetCardTypeW(0,0)
产生异常的原理跟FindClose(0)一样,但这个API在直接运行时不会被触发,这里就不赘述了。
0x1-2 经典的PUSH/RETN
熟悉壳或免杀的同学对这经典组合不会陌生,通过PUSH修改栈的数据,RETN返回到要去的地方。用VC内嵌汇编做个小实验。
#include<stdio.h>#include<stdlib.h>
void foo() { printf(“Hello Wolrd!!”); exit(0); } int main() { int PiaPia = (int)foo; __asm { push PiaPia retn } return 0; } |
运行结果是:Hello World!!。两句关键汇编的解释如下:
push PiaPia è 将PiaPia压到当前使用栈的栈顶,esp指向栈顶
retn è 将esp的内容赋给eip,eip告诉cpu从这执行
0x2 恶意样本中的组合利用
恶意样本来自一个邮件的下载链接,用pdf图标、scr后缀做伪装,但这些都不是本文的主角,只分析main函数里用到的loader代码,其用到的关键技巧在前面都已经解释了。
## main函数反汇编00401700 push ebp
00401701 mov ebp,esp 00401703 sub esp,0x1C 00401706 push ebx 00401707 push esi 00401708 push edi 00401709 mov word ptr ss:[ebp-0x1C],0x0 0040170F xor eax,eax 00401711 mov dword ptr ss:[ebp-0x1A],eax 00401714 mov dword ptr ss:[ebp-0x16],eax 00401717 mov dword ptr ss:[ebp-0x12],eax 0040171A mov word ptr ss:[ebp-0xE],ax 0040171E mov [local.3],0x0 00401725 mov [local.1],0x0 0040172C mov [local.2],SignedDo.EXS //将主功能函数地址赋值给局部变量 00401733 mov word ptr ss:[ebp-0x1C],0x10E7 00401739 push 0x0 //利用NULL制造异常 0040173B call dword ptr ds:[<&KERNEL32.FindClose>] 00401741 cmp eax,0x14A 00401746 jnz XSignedDo.0040174B 00401748 push [local.2] 0040174B lea ecx,[local.7] 0040174E push ecx 0040174F call dword ptr ds:[<&KERNEL32.GetLocalTime>] 00401755 push [local.2] //将主功能函数地址放栈顶 00401758 mov edx,[local.7] 0040175B and edx,0xFFFF 00401761 cmp edx,0x7D0 00401767 jle XSignedDo.0040177F 00401769 call dword ptr ds:[<&KERNEL32.GetLastError>] 0040176F cmp eax,0x3E6 // 只要998!!! 00401774 jnz XSignedDo.00401777 00401776 retn // 顺利返回到主功能函数的地址 00401777 push 0x0 // 以下代码不会被执行 00401779 call dword ptr ds:[<&KERNEL32.GetThreadPriority>] 0040177F push 0x0 00401781 push 0x0 00401783 call <jmp.&WinSCard.SCardForgetCardTypeW> 00401788 xor eax,eax 0040178A pop edi 0040178B pop esi 0040178C pop ebx 0040178D mov esp,ebp 0040178F pop ebp 00401790 retn 0x10 |
用颜色标注的代码是实现功能的代码,其他都属于无用代码,那么现在将它还原成C代码,更清晰地看看这几个技巧是如何组合的。
#include <stdlib.h>#include <windows.h>
void foo() { //Do Something…… exit(0); }
int main(int argc, char* argv[]) { int PiaPia = (int)foo; FindClose(NULL); if ( 998 == GetLastError() ) { __asm { push PiaPia retn } } return 0; } |
恶意代码作者这么写可能是用来对抗一些AV,发现写出C代码后就觉得没啥技术含量了,权当给一些初学者参考吧。
样本下载地址:infected.zip
原文地址:http://www.aptno1.com/YC/184.html
标签: 恶意样本, 木马, 病毒