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的代码:
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