shellcode检测——libemu原理分析

一、简介

        libemu是一款用C语言实现的基于x86的shellcode检测的库。

        它可以支持:

                 1、解析x86指令、寄存器模拟、FPU模拟

                 2、静态分析、动态分析、Win32API hook

         使用libemu你可以:

                 1、判断一段字串是不是shellcode

                 2、可以用libemu得到指令执行流程图(类似于IDA等调试工具)

          libemu可以被用于IDS、蜜罐等安全产品中


二、使用

         下面是一个使用libemu的例子 

/*libemu test*/
#include 
#include 
#include 

struct emu *emu;

char shellcode[] =  
    	"\xbe\x1f\x5e\x89\x76\x09\x31\xc0\x88\x46\x08\x89\x46\x0d\xb0\x0b"  
    	"\x89\xf3\x8d\x4e\x09\x8d\x56\x0d\xcd\x80\x31\xdb\x89\xd8\x40\xcd"  
    	"\x80\xe8\xdc\xff\xff\xff\x2f\x62\x69\x6e\x2f\x6c\x73\x00\xc9\xc3"; 

int main()
{
	emu = emu_new();
	if ( emu_shellcode_test(emu, (uint8_t *)shellcode, 48) >= 0 )
        {
        	fprintf(stderr, "suspecting shellcode\n");
        }
	emu_free(emu);
	return 0;
}

           在上例中,执行过程中会打印 suspecting shellcode,表示这个是一段可以字符串,而实际上这段字符串是linux下的shellcode,完成的功能是在当前路径下执行“/bin/ls”。


三、实现原理

        libemu是基于对x86汇编语言的解析和模拟执行。和Bochs、qemu不同的是libemu只是模拟器,不是虚拟机。只能完成对内存和CPU的简单模拟,不能完全模拟执行。

3.1 一个基本假设

        libemu的一个基本假设是,如果字串是一段shellcode,那么其中一定包含”call”(0xe8)或者“fnstenv”(0xd9)指令(GetPC code)。

        这里边涉及到shellcode的编写技巧,在shellcode的编写一般都要进行地址定位,而地址定位就很难绕过call/ret或者类fnstenv的浮点数指令。比如说:

        一个例子:

jmp    0x2a                popl   %esi               movl   %esi,0x9(%esi)      movb   $0x0,0x8(%esi)      movl   $0x0,0xd(%esi)      movl   $0xb,%eax           movl   %esi,%ebx           leal   0x9(%esi),%ecx      leal   0xd(%esi),%edx    int    $0x80              movl   $0x1, %eax 
movl   $0x0, %ebx          int    $0x80              call   -0x2f               .string \"/bin/ksh\"  
         上边这段汇编实际上就是执行了两个系统调用exec和exit,但是这执行过程中需要把字符串"/bin/ksh"的地址传递给exec,但是这个地址在编写shellcode的时候是不知道的。

这里就是利用了"call"指令的特性:call的时候push eip(实际上就是call指令的下一条指令的地址入栈,在这个shellcode中就是符串"/bin/ksh"的地址),然后跳转到"popl %esi"指令处,执行之后就是把刚刚push进栈的地址,pop给了esi寄存器。

         

         另一个例子:编写shellcode的另外一个技巧—Delta offset。一个shellcode写好之后,其中的指令和数据的相对位置是固定的。那么该shellcode在shellcode开发机和被攻击的机器上的不同在于,shellcode的加载位置不同(eip不同)。如果把shellcode的所用到的地址都硬编码为开发机中实际的地址,那么理论上上只需要知道Delta offset,就可以计算出被攻击机器的地址。如下图:

                                                                                          
               这时问题就被转化为如何获得被攻击机器的eip的值,可以这样做:        
       call delta 
       delta: 
            pop ebp 
               或者:
       fpu_addr: 
       fnop 
       call GetPhAddr 
       sub ebp,fpu_addr 
       GetPhAddr: 
       sub esp,16 
       fnstenv [esp-12] 
       pop ebp 
       add esp,12 
       ret

3.2 三个基本动作:
       1、模拟memory,模拟CPU
               memory:模拟两级页表(写时复制)。
                         
               CPU:模拟寄存器、段、当前指令及其描述。
       
       2、静态分析

               是否是合法的x86汇编格式:

                          

              梳理指令流程:顺序执行,跳转,条件跳转, 得到令执行流程图。

              判断指令的数据地址有没有超出”shellcode”的控制范围。

        3、动态执行

               判断在指令执行时,该指令所用到的寄存器、内存地址的值,是不是由shellcode控制的。这里需要用到静态分析中得到的指令执行流程图。

3.3、判断是不是shellcode的标准:

         这个字符串,是否能够作为一段汇编语言动态执行?


四、伪代码

        emu_shellcode_test(XXX,uint8_t *data, uint16_t size
        {
	       if(data不包含“0xe8”和“0xd9”)
	       {	
                     this is safe ; 
                     return;    
               }
               从data开始找第一个合法的汇编语句,并进行静态分析;
	       if(静态分析出错)//语法错误或者数据不可控
	       {	
                     this is safe;
                     return;   
               }
	       进行动态分析;
	       if(data中的汇编可连续动态执行N步)
	       {	
                     this is suspecting shellcode;
                     return;   
               }
         }

五、局限性

          平台:只局限于X86

          性能:比较低,libemu自己提供的nids demo 吞吐为2Mbps

          误报漏报:shellcode中不一定会有"call"

          检测手段:libeum实际上并没有真正的检测攻击

          编码/加密:libemu对编码或加密的数据无能为力

              


 

你可能感兴趣的:(黑防)