软件沙箱技术 – 安全分析沙箱Cuckoo Sandbox

1.Cockoo的功能

Cockoo Sandbox是开源安全沙箱,基于GPLv3。目的是恶意软件(malware analysis)分析。使用的时候将待分析文件丢到沙箱内,分析结束后输出报告。很多安全设备提供商所谓云沙箱是同类技术,一些所谓Anti-APT产品也是这个概念。和传统AV软件的静态分析相比,Cuckoo动态检测。扔到沙箱的可执行文件会被执行,文档会被打开,运行中检测。和不少开源软件学而优则仕一样,Cookoo搞了一个产品,Red Dragon。这种方式也是挺好的一种做开源软件的运营方式,Snort的SourceFire不就是被思科十几亿收购了吗。

 

Cuckoo它可以分析的内容有:

1.Windows可执行文件,DLL文件

2.PDF,MSOffice文档

3.URL和HTML文档

4.PHP脚本,VB脚本

5.CPL文件

6.ZIP文件,Jar文件

7.Python文件

可以看出,只是Windows平台,外加一些脚本和文件。

 

Cuckoo的分析结果包含如下内容:

1.函数以及API调用的Call Trace

2.应用拷贝和删除的文件

3.选定进程的内存镜像

4.分析机的full memory dump

5.恶意软件执行时的截屏

6.分析机产生的网络流量

 

2.部署和使用

 

下图是Cuckoo的部署,其实很简单,分为host和guests。

 

 

Host(管理机)

负责

管理guests

启动分析工作

网络流量收集等。

 

host依赖一些开源软件,例如

tcpdump用于Guest网络拦截

Volatility用于内存的dump

 

Guest(虚拟机)

Guest是通用的虚拟机,Xen、VirtualBox等。它运行Cuckoo的Agent,接收Host发过来的任务(文件)运行后获取信息。

Agent是跨平台的(就是Python脚本)可以运行在Windows、Linux和MAC OS上。它实际是是一个XMLRPC server,等待连接。

 

Cuckoo使用

Cuckoo提供了python命令行工具。

1.使用cuckoo.py启动引擎。

2.使用submit.py像cuckoo提交待分析应用。

3.引擎会和虚拟机中的Agent通信,运行应用。

4.分析结束后,结果输出到特定目录。

 

3.Hook引擎

 

cuckoo使用了通用虚拟机做Guest,本身也没有隔离机制和访问控制机制。它特殊的地方就是Hook机制。核心模块cuckoomon(Cuckoo Sandbox Monitor)的源码在这里。它的作用是Hook在可执行程序上,拦截执行流程。 形式上它是一个DLL,会被inject到待分析的恶意软件和它创建的所有进程中。所谓动态分析,就是Hook后的API,在线收集的运行时信息。主要有两个步骤:

 

3.1 DLL注入

注入是使用Python实现的,流程如下:

1.使用CreateProcess(CREATE SUSPENDED)启动应用

2.使用CreateRemoteThread和QueueUserAPC API调用LoadLibrary注入cuckoomon.dll。

3.Resume进程的主线程

 

参照下面代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
class Process :
    "" "Windows process." ""
 
    def  inject ( self , dll = os . path . join ( "dll" , "cuckoomon.dll" ) , apc = False ) :
        "" "Cuckoo DLL injection.
       @param dll: Cuckoo DLL path.
       @param apc: APC use.
       " ""
        if self . pid == 0 :
            log . warning ( "No valid pid specified, injection aborted" )
            return False
 
        if not self . is_alive ( ) :
            log . warning ( "The process with pid %d is not alive, injection "
                        "aborted" % self . pid )
            return False
 
        dll = randomize_dll ( dll )
 
        if not dll  or not os . path . exists ( dll ) :
            log . warning ( "No valid DLL specified to be injected in process "
                        "with pid %d, injection aborted" % self . pid )
            return False
 
        arg = KERNEL32 . VirtualAllocEx ( self . h_process ,
                                      None ,
                                      len ( dll ) + 1 ,
                                      MEM_RESERVE | MEM_COMMIT ,
                                      PAGE_READWRITE )
 
        if not arg :
            log . error ( "VirtualAllocEx failed when injecting process with "
                      "pid %d, injection aborted (Error: %s)"
                      % ( self . pid , get_error_string ( KERNEL32 . GetLastError ( ) ) ) )
            return False
 
        bytes_written = c_int ( 0 )
        if not KERNEL32 . WriteProcessMemory ( self . h_process ,
                                           arg ,
                                           dll + "\x00" ,
                                           len ( dll ) + 1 ,
                                           byref ( bytes_written ) ) :
            log . error ( "WriteProcessMemory failed when injecting process "
                      "with pid %d, injection aborted (Error: %s)"
                      % ( self . pid , get_error_string ( KERNEL32 . GetLastError ( ) ) ) )
            return False
 
        kernel32_handle = KERNEL32 . GetModuleHandleA ( "kernel32.dll" )
        load_library = KERNEL32 . GetProcAddress ( kernel32_handle ,
                                               "LoadLibraryA" )
 
        config_path = os . path . join ( os . getenv ( "TEMP" ) , "%s.ini" % self . pid )
        with  open ( config_path , "w" ) as config :
            cfg = Config ( "analysis.conf" )
 
            config . write ( "host-ip={0}\n" . format ( cfg . ip ) )
            config . write ( "host-port={0}\n" . format ( cfg . port ) )
            config . write ( "pipe={0}\n" . format ( PIPE ) )
            config . write ( "results={0}\n" . format ( PATHS [ "root" ] ) )
            config . write ( "analyzer={0}\n" . format ( os . getcwd ( ) ) )
            config . write ( "first-process={0}\n" . format ( Process . first_process ) )
 
            Process . first_process = False
 
        if apc  or self . suspended :
            log . info ( "Using QueueUserAPC injection" )
            if not self . h_thread :
                log . info ( "No valid thread handle specified for injecting "
                         "process with pid %d, injection aborted" % self . pid )
                return False
 
            if not KERNEL32 . QueueUserAPC ( load_library , self . h_thread , arg ) :
                log . error ( "QueueUserAPC failed when injecting process "
                          "with pid %d (Error: %s)"
                          % ( self . pid ,
                             get_error_string ( KERNEL32 . GetLastError ( ) ) ) )
                return False
            log . info ( "Successfully injected process with pid %d" % self . pid )
        else :
            event_name = "CuckooEvent%d" % self . pid
            self . event_handle = KERNEL32 . CreateEventA ( None ,
                                                      False ,
                                                      False ,
                                                      event_name )
            if not self . event_handle :
                log . warning ( "Unable to create notify event.." )
                return False
 
            log . info ( "Using CreateRemoteThread injection" )
            new_thread_id = c_ulong ( 0 )
            thread_handle = KERNEL32 . CreateRemoteThread ( self . h_process ,
                                                        None ,
                                                        0 ,
                                                        load_library ,
                                                        arg ,
                                                        0 ,
                                                        byref ( new_thread_id ) )
            if not thread_handle :
                log . error ( "CreateRemoteThread failed when injecting " +
                    "process with pid %d (Error: %s)" % ( self . pid ,
                    get_error_string ( KERNEL32 . GetLastError ( ) ) ) )
                KERNEL32 . CloseHandle ( self . event_handle )
                self . event_handle = None
                return False
            else :
                KERNEL32 . CloseHandle ( thread_handle )
 
        return True

 

 

3.2 API Hook

 

1.上一步中主线程恢复后,因为APC callback,Cuckoo Monitor首先被执行。

2.初始化并且安装Hook

Cuckoo对ntdll.dll, kernel32.dll, advapi32.dll,shell32.dll,msvcrt.dll,user32.dll,wininet.dll,ws2_32.dll,mswsock.dll中的170+API进行hook

3.通知分析模块Analyzer(通过命名管道),应用启动。

4.Log将会通过实现配置好的TCP/IP端口发送给Host。

 

下面是添加Hook的代码:

int hook_api(hook_t *h, int type) { // table with all possible hooking types static struct { int(*hook)(hook_t *h, unsigned char *from, unsigned char *to); int len; } hook_types[] = { /* HOOK_JMP_DIRECT */ {&hook_api_jmp_direct, 5}, /* HOOK_NOP_JMP_DIRECT */ {&hook_api_nop_jmp_direct, 6}, /* HOOK_HOTPATCH_JMP_DIRECT */ {&hook_api_hotpatch_jmp_direct, 7}, /* HOOK_PUSH_RETN */ {&hook_api_push_retn, 6}, /* HOOK_NOP_PUSH_RETN */ {&hook_api_nop_push_retn, 7}, /* HOOK_JMP_INDIRECT */ {&hook_api_jmp_indirect, 6}, /* HOOK_MOV_EAX_JMP_EAX */ {&hook_api_mov_eax_jmp_eax, 7}, /* HOOK_MOV_EAX_PUSH_RETN */ {&hook_api_mov_eax_push_retn, 7}, /* HOOK_MOV_EAX_INDIRECT_JMP_EAX */ {&hook_api_mov_eax_indirect_jmp_eax, 7}, /* HOOK_MOV_EAX_INDIRECT_PUSH_RETN */ {&hook_api_mov_eax_indirect_push_retn, 7}, #if HOOK_ENABLE_FPU /* HOOK_PUSH_FPU_RETN */ {&hook_api_push_fpu_retn, 11}, #endif /* HOOK_SPECIAL_JMP */ {&hook_api_special_jmp, 7}, }; // is this address already hooked? if(h->is_hooked != 0) { return 0; } // resolve the address to hook unsigned char *addr = h->addr; if(addr == NULL && h->library != NULL && h->funcname != NULL) { addr = (unsigned char *) GetProcAddress(GetModuleHandleW(h->library), h->funcname); } if(addr == NULL) { return -1; } int ret = -1; // check if this is a valid hook type if(type >= 0 && type < ARRAYSIZE(hook_types)) { // determine whether we're running under win7, if so, we might have to // follow a short relative jmp and an indirect jump before reaching // the real address OSVERSIONINFO os_info = {sizeof(OSVERSIONINFO)}; if(GetVersionEx(&os_info) && os_info.dwMajorVersion == 6 && os_info.dwMinorVersion == 1) { // windows 7 has a DLL called kernelbase.dll which basically acts // as a layer between the program and kernel32 (and related?) it // allows easy hotpatching of a set of functions which is why // there's a short relative jump and an indirect jump. we want to // resolve the address of the real function, so we follow these // two jumps. if(!memcmp(addr, "\xeb\x05", 2) && !memcmp(addr + 7, "\xff\x25", 2)) { addr = **(unsigned char ***)(addr + 9); } // the following applies for "inlined" functions on windows 7, // some functions are inlined into kernelbase.dll, rather than // kernelbase.dll jumping to e.g. kernel32.dll. for these // functions there is a short relative jump, followed by the // inlined function. if(!memcmp(addr, "\xeb\x02", 2) && !memcmp(addr - 5, "\xcc\xcc\xcc\xcc\xcc", 5)) { // step over the short jump and the relative offset addr += 4; } } DWORD old_protect; // make the address writable if(VirtualProtect(addr, hook_types[type].len, PAGE_EXECUTE_READWRITE, &old_protect)) { if(hook_create_trampoline(addr, hook_types[type].len, h->tramp)) { hook_store_exception_info(h); uint8_t special = 0; if(h->allow_hook_recursion == 1) { special = 1; } hook_create_pre_tramp(h, special); // insert the hook (jump from the api to the // pre-trampoline) ret = hook_types[type].hook(h, addr, h->pre_tramp); // if successful, assign the trampoline address to *old_func if(ret == 0) { *h->old_func = h->tramp; // successful hook is successful h->is_hooked = 1; } } // restore the old protection VirtualProtect(addr, hook_types[type].len, old_protect, &old_protect); } } return ret; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
int hook_api ( hook_t * h , int type )
{
     // table with all possible hooking types
     static struct {
         int ( * hook ) ( hook_t * h , unsigned char * from , unsigned char * to ) ;
         int len ;
     } hook_types [ ] = {
         /* HOOK_JMP_DIRECT */ { & hook_api_jmp_direct , 5 } ,
         /* HOOK_NOP_JMP_DIRECT */ { & hook_api_nop_jmp_direct , 6 } ,
         /* HOOK_HOTPATCH_JMP_DIRECT */ { & hook_api_hotpatch_jmp_direct , 7 } ,
         /* HOOK_PUSH_RETN */ { & hook_api_push_retn , 6 } ,
         /* HOOK_NOP_PUSH_RETN */ { & hook_api_nop_push_retn , 7 } ,
         /* HOOK_JMP_INDIRECT */ { & hook_api_jmp_indirect , 6 } ,
         /* HOOK_MOV_EAX_JMP_EAX */ { & hook_api_mov_eax_jmp_eax , 7 } ,
         /* HOOK_MOV_EAX_PUSH_RETN */ { & hook_api_mov_eax_push_retn , 7 } ,
         /* HOOK_MOV_EAX_INDIRECT_JMP_EAX */
             { & hook_api_mov_eax_indirect_jmp_eax , 7 } ,
         /* HOOK_MOV_EAX_INDIRECT_PUSH_RETN */
             { & hook_api_mov_eax_indirect_push_retn , 7 } ,
#if HOOK_ENABLE_FPU
         /* HOOK_PUSH_FPU_RETN */ { & hook_api_push_fpu_retn , 11 } ,
#endif
         /* HOOK_SPECIAL_JMP */ { & hook_api_special_jmp , 7 } ,
     } ;
 
     // is this address already hooked?
     if ( h -> is_hooked != 0 ) {
         return 0 ;
     }
 
     // resolve the address to hook
     unsigned char * addr = h -> addr ;
 
     if ( addr == NULL && h -> library != NULL && h -> funcname != NULL ) {
         addr = ( unsigned char * ) GetProcAddress ( GetModuleHandleW ( h -> library ) ,
             h -> funcname ) ;
     }
     if ( addr == NULL ) {
         return - 1 ;
     }
 
     int ret = - 1 ;
 
     // check if this is a valid hook type
     if ( type >= 0 && type < ARRAYSIZE ( hook_types ) ) {
 
         // determine whether we're running under win7, if so, we might have to
         // follow a short relative jmp and an indirect jump before reaching
         // the real address
         OSVERSIONINFO  os_info = { sizeof ( OSVERSIONINFO ) } ;
         if ( GetVersionEx ( & os_info ) && os_info . dwMajorVersion == 6 &&
                 os_info . dwMinorVersion == 1 ) {
             // windows 7 has a DLL called kernelbase.dll which basically acts
             // as a layer between the program and kernel32 (and related?) it
             // allows easy hotpatching of a set of functions which is why
             // there's a short relative jump and an indirect jump. we want to
             // resolve the address of the real function, so we follow these
             // two jumps.
             if ( ! memcmp ( addr , "\xeb\x05" , 2 ) &&
                     ! memcmp ( addr + 7 , "\xff\x25" , 2 ) ) {
                 addr = * * ( unsigned char * * * ) ( addr + 9 ) ;
             }
 
             // the following applies for "inlined" functions on windows 7,
             // some functions are inlined into kernelbase.dll, rather than
             // kernelbase.dll jumping to e.g. kernel32.dll. for these
             // functions there is a short relative jump, followed by the
             // inlined function.
             if ( ! memcmp ( addr , "\xeb\x02" , 2 ) &&
                     ! memcmp ( addr - 5 , "\xcc\xcc\xcc\xcc\xcc" , 5 ) ) {
                 // step over the short jump and the relative offset
                 addr += 4 ;
             }
         }
 
         DWORD  old_protect ;
 
         // make the address writable
         if ( VirtualProtect ( addr , hook_types [ type ] . len , PAGE_EXECUTE_READWRITE ,
                 & old_protect ) ) {
 
             if ( hook_create_trampoline ( addr , hook_types [ type ] . len , h -> tramp ) ) {
 
                 hook_store_exception_info ( h ) ;
 
                 uint8_t  special = 0 ;
 
                 if ( h -> allow_hook_recursion == 1 ) {
                     special = 1 ;
                 }
 
                 hook_create_pre_tramp ( h , special ) ;
 
                 // insert the hook (jump from the api to the
                 // pre-trampoline)
                 ret = hook_types [ type ] . hook ( h , addr , h -> pre_tramp ) ;
 
                 // if successful, assign the trampoline address to *old_func
                 if ( ret == 0 ) {
                     * h -> old_func = h -> tramp ;
 
                     // successful hook is successful
                     h -> is_hooked = 1 ;
                 }
             }
 
             // restore the old protection
             VirtualProtect ( addr , hook_types [ type ] . len , old_protect ,
                 & old_protect ) ;
         }
     }
 
     return ret ;
}

 

 

4.躲避的应对

前面软件加壳技术介绍的时候有部分内容关于躲避。恶意软件会创建自进程,在子进程中做实际工作。对于类似Cuckoo这样的动态分析工具就要处理这种情况,在子进程中启动cuckoomon。下图就是应对流程,Monitor在监控到样本创建进程的时候,也会模拟初始化启动时候cuckoo做的事情,显示suspend,然后通知Analyzer。由后者进行DLL的注入。

From 【5】

 

前文也介绍过进程注入,也就是恶意软件利用周知进程,例如IE,运行自己的恶意逻辑,解决的方法类似。只是监控的不是CreateProcess而是OpenProcess。

 

【参考】

1.官方网站,http://www.cuckoosandbox.org/

2.Cuckoo Sandbox Book,http://cuckoo.readthedocs.org/en/latest/

3.Hooking functions with Cuckoobox’s hooking engine,http://living-security.blogspot.com/2014/07/hooking-functions-with-cuckooboxs.html

4.开源软件 cuckoo sandbox学习(二) 核心拦截模块源代码导读, http://sevemal.blog.51cto.com/8627322/1397666

5.Haow Sandbox, Cuckoo Sandbox Internal, http://recon.cx/2013/slides/recon2013-Jurriaan%20Bremer-Haow%20do%20I%20sandbox.pdf

你可能感兴趣的:(cuckoo)