最近跟着老师做一个关于decaf的项目,因此对读的文章做点笔记。
- 简介
decaf是一个虚拟机,为开发者提供了许多调用接口,可以在运行时Hook。使用这些接口可以完全在客户机之外检索OS-level semantics(操作系统级别的语义),例如进程、系统API、按键、网络等。 - 样例插件
以下插件可以指定一个你想要追踪的进程,并在进程开始时打印进程名。
当DECAF加载该插件,它将首先调用init_plugin(void)
,可以通过plugin_interface_t
(DECAF_main.h) 定义插件的行为。但最重要的是指定plugin_cleanup
接口,通常用这个接口释放分配给该插件的资源,不这么做DECAF可能会崩。plugin_interface_t
还可以为DECAF定义你自己的命令,以便运行时与插件交互。
#include "DECAF_types.h"
#include "DECAF_main.h"
#include "DECAF_callback.h"
#include "DECAF_callback_common.h"
#include "vmi_callback.h"
#include "utils/Output.h"
#include "DECAF_target.h"
//basic stub for plugins
static plugin_interface_t my_interface;
static DECAF_Handle processbegin_handle = DECAF_NULL_HANDLE;
char targetname[512];
// 当客户系统中开始一个新进程时callback被调用
static void my_loadmainmodule_callback(VMI_Callback_Params params) {
if(strcmp(params->cp.name,targetname)==0)
DECAF_printf("Process %s you spcecified starts \n",params->cp.name);
}
// 执行monitor_proc命令的处理器
void do_monitor_proc(Monitor* mon, const QDict* qdict) {
//复制被监测进程的名字
if ((qdict != NULL) && (qdict_haskey(qdict, "procname"))) {
strncpy(targetname, qdict_get_str(qdict, "procname"), 512);
}
targetname[511] = '\0';
}
static int my_init(void) {
DECAF_printf("Hello World\n");
//为创建进程与移除进程注册
processbegin_handle =VMI_register_callback(VMI_CREATEPROC_CB, &my_loadmainmodule_callback, NULL);
if (processbegin_handle == DECAF_NULL_HANDLE){
DECAF_printf("Could not register for the create or remove proc events\n");
}
return (0);
}
// 当插件卸载时调用该函数
static void my_cleanup(void) {
DECAF_printf("Bye world\n"); // 注销进程的启动与退出的调用
if (processbegin_handle != DECAF_NULL_HANDLE) {
VMI_unregister_callback(VMI_CREATEPROC_CB, processbegin_handle);
processbegin_handle = DECAF_NULL_HANDLE;
}
}
// 支持插件的命令,在plugin_cmds.h中
static mon_cmd_t my_term_cmds[] = {
{.name = "monitor_proc",
.args_type = "procname:s?",
.params = "[procname]",
.help = "Run the tests with program [procname]" },
{NULL, NULL, }, };
//该函数通过DECAF注册插件接口(plugin_interface)。该接口用于注册自定义命令,让DECAF清楚插件卸载后调用哪个卸载函数,等等
plugin_interface_t init_plugin(void) {
my_interface.mon_cmds = my_term_cmds;
my_interface.plugin_cleanup = &my_cleanup;
my_init();
return (&my_interface);
}
在函数init_plugin(void)
中,我们用my_term_cmds
定义自己的命令。在my_term_cmds
,我们指定命令名、命令处理程序,命令参数和帮助信息。plugin_cleanup
也由my_cleanup
定义。
在my_init()
中我们注册VMI_CREATEPROC_CB
调用以及它的处理器my_loadmainmodule_callback
。故当一个进程开始时,DECAF会调用my_loadmainmodule_callback()
,并且从它的参数中可以得到进程PID、名称和CR3。故你可以检查该检查是否是你用monitor_proc命令指定的进程,若是则打印进程名。当然,我们还可以做许多其他事,例如可以在这里注册DECAF_INSN_BEGIN_CB回调与其处理程序。在它的处理程序中,我们检查它是否属于指定的进程,并使用下面的代码打印出指令。
uint32_t target_cr3;
static void my_insn_begin_callback(DECAF_Callback_Params* params) {
if(params->ib.env->cr[3]==target_cr3) {
DECAF_printf("EIP 0x%08x \n",params->ib.env->eip);
}
}
//当客户系统中开始一个新进程时该callback被调用
static void my_loadmainmodule_callback(VMI_Callback_Params params) {
if(strcmp(params->cp.name,targetname)==0){
DECAF_printf("Process %s you spcecified starts \n",params->cp.name);
target_cr3=params->cp.cr3;
DECAF_register_callback(DECAF_INSN_BEGIN_CB, &my_insn_begin_callback,NULL);
}
}
有一个需要注意的是VMI_register_callback的第三个参数,如果它是NULL,这意味着这个回调将在整个过程中一直被调用。如果指针指向1,那么这个回调将一直被调用。如果指针指向0,则此回调被禁用。其他类型的回调注册函数也遵循此约定。
现在就已经知道了如何注册或注销,启用或禁用回调了。
3. Hook API
对恶意软件分析时,api跟踪对于理解恶意软件的行为是至关重要的。传统的分析工具通过在不同的容易绕过的(尤其是被Rootkit绕过)层次上hook api来实现api跟踪。DECAF可以在虚拟机外更可靠得跟踪API。你可以在你的插件中hook任意的API或者EIP。在hookapitest插件中展现了如何hook api以及检索api参数。
现在,我们修改上面的样例代码来hook api NtCreateFile
并从栈中检索它的参数。首先,当目标进程开始时(my_loadmainmodule_callback
被调用时),我们通过hookapi_hook_function_byname注册了api hook。在下面的代码中,当客户系统调用NtCreateFile
时,NtCreateFile_call
将会被调用。对于被标记为“IN”的参数,你可以在栈中检索到它,但是对于“OUT”参数,当NtCreateFile返回时它才被赋值。为了处理这种情况,我们使用hookapi_hook_return
hook api 的返回值。当NtCreateFile_call
被调用,NtCreateFile返回的返回地址在EBP,我们应该把这个返回值传递给hookapi_hook_return
函数,另外,你可以通过hookapi_hook_return
的第三个、第四个参数把数据传递给NtCreateFile_ret
。在NtCreateFile_ret
中,我们在栈上检索文件句柄。在返回时,EBP存储第一个参数——文件句柄的地址,我们使用DECAF_read_mem
从EBP中读取该文件句柄。你还可以在hookapitests/custom_handlers.c中找到更复杂的参数检索函数,但它们的基本原理都是相同的。
正确检索参数的关键在于正确地理解“地址”。地址有三种:客户操作系统的虚拟地址、客户操作系统的物理地址和主机操作系统的虚拟地址。EBP的值为客户操作系统的虚拟地址。DECAF_read_mem
获取客户机操作系统内存中指定虚拟地址存放的内容。有时,参数的API是一个指针,你需要先读这个指针的值,然后使用decaf_read_mem
读取指针指向的内存。
另一件需要注意的事情是字符集问题。Windows内部使用unicode字符,如果你得到一些不可读的编码,你可能需要将其转换为可读的字符集。
以下为代码
DECAF_handle ntcreatefile_handle;
typedef struct {
uint32_t call_stack[12]; //参数与返回地址
DECAF_Handle hook_handle;
}NtCreateFile_hook_context_t;
/* NTSTATUS NtCreateFile( Out PHANDLE FileHandle, In ACCESS_MASK DesiredAccess, In POBJECT_ATTRIBUTES ObjectAttributes, Out PIO_STATUS_BLOCK IoStatusBlock, _In_opt_ PLARGE_INTEGER AllocationSize, In ULONG FileAttributes, In ULONG ShareAccess, In ULONG CreateDisposition, In ULONG CreateOptions, In PVOID EaBuffer, In ULONG EaLength ); */
static void NtCreateFile_ret(void *param) {
NtCreateFile_hook_context_t *ctx = (NtCreateFile_hook_context_t *)param;
DECAF_printf("NtCreateFile exit:");
hookapi_remove_hook(ctx->hook_handle);
uint32_t out_handle;
DECAF_read_mem(NULL, ctx->call_stack[1], 4, &out_handle);
DECAF_printf("out_handle=%08x\n", out_handle);
free(ctx);
}
static void NtCreateFile_call(void *opaque) {
DECAF_printf("NtCreateFile entry\n");
NtCreateFile_hook_context_t ctx = (NtCreateFile_hook_context_t) malloc(sizeof(NtCreateFile_hook_context_t));
if(!ctx) //run out of memory return;
DECAF_read_mem(NULL, cpu_single_env->regs[R_ESP], 12*4, ctx->call_stack);
ctx->hook_handle = hookapi_hook_return(ctx->call_stack[0],NtCreateFile_ret, ctx, sizeof(*ctx));
}
static void my_loadmainmodule_callback(VMI_Callback_Params* params) {
if(strcmp(params->cp.name,targetname)==0){
DECAF_printf("Process %s you spcecified starts \n",params->cp.name);
target_cr3=params->cp.cr3;
/// @ingroup hookapi
/// install a hook at the function entry by specifying module name and function name
/// @param mod module name that this function is located in
/// @param func function name
/// @param is_global flag specifies if this hook should be invoked globally or only in certain execution context (when should_monitor is true)
/// @param cr3 the memory space that this hook is installed. 0 for all memory spaces.
/// @param fnhook address of function hook
/// @param opaque address of an opaque structure provided by caller (has to be globally allocated)
// @param sizeof_opaque size of the opaque structure (if opaque is an integer, not a pointer to a structure, sizeof_opaque must be zero)
/// @return a handle that uniquely identifies this hook
/// Note that the handle that is returned, might not actually be active yet - you can check the eip value of the handle to find out
/// the default value is 0.
ntcreatefile_handle = hookapi_hook_function_byname( "ntdll.dll", "NtCreateFile", 1, target_cr3, NtCreateFile_call, NULL, 0);
}
}