恶意样本中的998和栈使用 - APT防御产品

恶意样本中的998和栈使用


以前分析恶意样本关注的是样本的行为和功能,这个通过监控就可以了解个大概,而本文主要分享的是恶意样本中一些精心构造的东西。有的时候工具太高级,反而会让你错过一些逆向中的乐趣。

 

0x1 一些准备

异常和栈的使用,在平时的编程中一般不会被人注意,异常更多的用来调试程序使其更加稳定,而堆栈都交由编译器控制,而就有“坏人”利用这两点铺垫了一条通向邪恶的执行流程。异常的使用可以起到反调试的作用,当然如果你用StrongOD或忽略异常来调试就不存在了;栈的使用可能会带来一些免杀的效果。

 

0x1-1 异常编号998【0地址的妙用】

GetLastError是获取函数调用返回的一个错误结果,其中内存分配访问失效的编号是998(十六进制标示:0x3E6),看“坏人”是如果制造出这个值来得。

viewfile.jpg

      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


转载请注明出处 APT防御产品 » 恶意样本中的998和栈使用

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址