TL;DR BasicBlockを実行する度にゲストOS判定を行う。 init_taskのアドレスなどからshared/kernelinfo/procinfo_generic/procinfo.iniデータベースを使ってOSの種類、バージョンを特定しProcInfoに格納。init_taskのアドレスによって特定している(これでディストリビューション,バージョン分けきれるのか?) また.ini内のts_xxxなどはtask_struct構造体のxxxメンバのオフセットを表している。 また、OSのバージョン名.initをkernelinfo/old_stuff/lib_confから検索し、glibcの関数,RVAの組み合わせを読み込む。 ゲストOSの判定が完了したらTLBが実行される度に
APIフック_bynameでは指定された関数名を、上記の方法で取得したfunc,RVAの組み合わせよりアドレスを得てフックをセットする。
APIフック情報はload/saveによる一時的な保存が可能。(いわゆるrecord and replayだと思われる) これにはqemuのsave_vmを"hookapi"の名前などで使っている。 hook場所とhook関数をsaveし、load時はこれらをDECAF_registerOptimizedBlockBeginCallbackで登録しなおしている。 DECAFではplugin<-->callback, callback<-->QEMUの2ステージに分かれており、上記含むインターフェースは全て前者(stage 2)となる。 stage2では要は、callback_list_heads[type]にフックハンドラを登録していくだけ。 callback_list_heads[DECAF_INSN_END_CB]のような感じで種類ごとにリストを持っている。 後者のstage1はTCGにヘルパー関数を追加してこれらcallback_list_headに登録されたフックハンドラを挿入していくようにしている。
(vl.c)
|c| // AVB, This opens the device for sleuthkit to read DECAF_blocks_init(); DECAF_init(); ||<
(shared/DECAF_main.c)
|c| void DECAF_init(void) { DECAF_callback_init(); //file: shared/DECAF_callback.c tainting_init(); //file: shared/tainting.c DECAF_virtdev_init();//file: shared/DECAF_main.c register_savevm(NULL, "DECAF", 0, 1, DECAF_save, DECAF_load, NULL ); DECAF_vm_compress_init(); //file: shared/DECAF_vm_compress.c function_map_init();//file: shared/function_map.cpp init_hookapi(); #ifndef CONFIG_VMI_ENABLE procmod_init(); #else VMI_init(); #endif } ||<
(shared/DECAF_callback.c)
|c| void DECAF_callback_init(void) { int i;
for(i=0; i<DECAF_LAST_CB; i++) LIST_INIT(&callback_list_heads[i]);
pOBBTable = CountingHashtable_new(); pOBBPageTable = CountingHashtable_new();
pOBEFromPageTable = CountingHashtable_new(); pOBEToPageTable = CountingHashtable_new(); pOBEPageMap = CountingHashmap_new();
bEnableAllBlockBeginCallbacks = 0; enableAllBlockBeginCallbacksCount = 0; bEnableAllBlockEndCallbacks = 0; enableAllBlockEndCallbacksCount = 0; } ||<
テイント領域の初期化 (shared/tainting/tainting.c)
|c| void tainting_init(void) { #ifdef CONFIG_TCG_TAINT /* AWH - Taint tracking IR setup (testing) / do_enable_tainting_internal(); #endif / CONFIG_TCG_TAINT */ } ||<
(shared/tainting/taint_memory.c)
|c| void do_enable_tainting_internal(void) { if (!taint_tracking_enabled) { CPUState *env = cpu_single_env ? cpu_single_env : first_cpu; DECAF_stop_vm(); //file: shared/DECAF_main.c <=> vm_stop(RUN_STATE_PAUSED) tb_flush(env); allocate_taint_memory_page_table(); //file: shared/tainting/taint_memory.c taint_tracking_enabled = 1; DECAF_start_vm(); } } ||<
テイント領域は恐らくページテーブルのような物で管理されている(?) テーブルの初期化 (shared/tainting/taint_memory.c)
|c| static void allocate_taint_memory_page_table(void) { if (taint_memory_page_table) return; // AWH - Don't allocate if one exists taint_memory_page_table_root_size = ram_size >> (BITPAGE_LEAF_BITS + BITPAGE_MIDDLE_BITS); taint_memory_page_table = (tbitpage_middle_t *) g_malloc0(taint_memory_page_table_root_size * sizeof(void)); allocate_leaf_pool(); //file: shared/tainting/taint_memory.c allocate_middle_pool();//file: shared/tainting/taint_memory.c middle_nodes_in_use = 0; leaf_nodes_in_use = 0; } ||<
メモリのテイントは__taint_ldb_raw(addr,vaddr)(shared/tainting/taint_memory.c)をTCGからホストマシン語に変換される際挿入している。 DECAF_WRITE_TAINTMEM_CBコールバック関数はhelper_DECAF_invoke_write_taint_mem(shared/DECAF_callback.c)から呼ばれる。この関数が__taint_ldb_rawなどから呼ばれる仕組み。
また指定された命令が実行された際
レジスタのテイント伝搬などのinstrumentは(target-i386/translate.c)でoptimize_taint(shared/tainting/tcg_taint.c)を呼び出すことで行っている。optimize_taintはgen_taintcheck_insn(shared/tainting/tcg_taint.c)を呼ぶ。
結局キーストロークのテイントしか実装されてない...?
キーストロークはhw/ps2.cからフック関数DECAF_keystroke_place(shared/DECAF_main.c)を呼んでhelper_DECAF_invoke_keystroke_callback(shared/DECAF_callback.c)から登録したコールバックが呼ばれる。この際、DECAF_main.cにあるtaint_keystroke_enabledのアドレスがコールバック関数の引数に渡される。例えばkeyloggerプラグインはコールバックでこのtaint_keystroke_enabledに1を設定している。 これが1だと(hw/ps2.c)のps2_put_keycodeあらps2_put_keycode_taintが呼ばれる。
(hw/ps2.c)
|c| static void ps2_queue_taint(void *opaque, int b) { PS2State *s = (PS2State *)opaque; PS2Queue *q = &s->queue;
if (q->count >= PS2_QUEUE_SIZE)
return;
q->data[q->wptr] = b;
q->taint_data[q->wptr] = 0xFF; [*]
if (++q->wptr == PS2_QUEUE_SIZE)
q->wptr = 0;
q->count++;
s->update_irq(s->update_arg, 1);
}
static void ps2_put_keycode_taint(void *opaque, int keycode) { PS2KbdState *s = opaque;
/* XXX: add support for scancode set 1 */
if (!s->translate && keycode < 0xe0 && s->scancode_set > 1) {
if (keycode & 0x80) {
ps2_queue_taint(&s->common, 0xf0);
}
if (s->scancode_set == 2) {
keycode = ps2_raw_keycode[keycode & 0x7f];
} else if (s->scancode_set == 3) {
keycode = ps2_raw_keycode_set3[keycode & 0x7f];
}
}
ps2_queue_taint(&s->common, keycode);
} uint32_t ps2_read_data(void *opaque) { ... index = q->rptr - 1; if (index < 0) index = PS2_QUEUE_SIZE - 1; val = q->data[index]; DECAF_keystroke_read(q->taint_data[index]); } ||<
キーボード入力はPS2Queueに追加されるが、このPS2Queue構造体に新たにtaint_dataというメンバーが追加されており、[*]で0xffに設定されている。このtaint_dataは同ps2_read_dataからDECAF_keystroke_read(DECAF_main.c)が呼ばれる際に引数として渡されている。
|c| void DECAF_keystroke_read(uint8_t taint_status) { #ifdef CONFIG_TCG_TAINT if (taint_keystroke_enabled) { cpu_single_env->tempidx = taint_status; cpu_single_env->tempidx = cpu_single_env->tempidx & 0xFF; } #endif /CONFIG_TCG_TAINT/ } ||<
cpu_single_env->tempidxは(?) またテイントされたレジスタ情報はcpu_single_env->taint_regsに格納される。 taint_regsには(shared/tainting/helper_i386_check.h)内でテイント伝搬される。
テイント領域は3層のページテーブル構造で管理されている。ある(物理)アドレスをテイントする際はそのアドレスを3つに分けて最も末端のleaf_nodeにテイント情報を格納する。(ページングと同じ要領) DECAF_READ_TAINTMEM_CBはload命令が実行される度にコールバックされる。 DECAF_WRITE_TAINTMEM_CBはstore命令が実行される度にコールバックされる。
(shared/tainting/taint_memory.c)
|c| void REGPARM __taint_stb_raw_paddr(ram_addr_t addr,gva_t vaddr) { if (!taint_memory_page_table || addr >= ram_size) return;
uint16_t before, after;
char changed = 0;
tbitpage_leaf_t *leaf_node = taint_st_general_i32(addr,
cpu_single_env->tempidx & 0xFF); [*]
if (leaf_node) {
before = *(uint8_t *) (leaf_node->bitmap + (addr & LEAF_ADDRESS_MASK));
*(uint8_t *) (leaf_node->bitmap + (addr & LEAF_ADDRESS_MASK)) =
cpu_single_env->tempidx & 0xFF;
after = *(uint8_t *) (leaf_node->bitmap + (addr & LEAF_ADDRESS_MASK));
if ((before != after) || (cpu_single_env->tempidx & 0xFF)) changed = 1;
} if ( changed && DECAF_is_callback_needed( DECAF_WRITE_TAINTMEM_CB) ) helper_DECAF_invoke_write_taint_mem(vaddr,addr,1,(uint8_t ) (leaf_node->bitmap + (addr & LEAF_ADDRESS_MASK))); [] return; } ||<
このstoreにより、テイント情報が0から変わらず0ならchangedは0だが、それ以外は1になりコールバックが呼ばれる。 [*]のtaint_st_general_i32は第二引数が指定されている場合は、指定されたアドレスのエントリを新たに確保して返す。NULLならエントリーを返す。
この __taint_stb_raw_paddrも(tcg/i386/tcg-target.c)でTCGがホストマシン語に変換される際にtaint_stb_cbを通して挿入される。
DECAF_initに戻る
ログの出力(?)
|c| void DECAF_virtdev_init(void) { int res = register_ioport_write(0x68, 1, 1, DECAF_virtdev_write_data, NULL ); if (res) { fprintf(stderr, "failure on initializing DECAF virtual device\n"); exit(-1); } if (!(guestlog = fopen("guest.log", "w"))) { fprintf(stderr, "failure on opening guest.log \n"); exit(-1); } }
(shared/DECAF_vm_compress.c)
|c| void DECAF_vm_compress_init(void) { } ||<
(shared/function_map.cpp)
|c| static void function_map_save(QEMUFile * f, void opaque) { / Nothing here if we are loading from guest.log */ //Aravind }
static int function_map_load(QEMUFile * f, void opaque, int version_id) { //Aravind start / Loading the entries from guest.log.
- This only works if TEMU_loadvm has executed (guest.log is generated).
- Ideal would be to serialize, compress and checkpoint the maps in the image and then restore. */ FILE *fp = fopen("guest.log", "r"); char line[1024] = {'\0'}; while( fgets (line, 1024, fp)) { if(line[0] != 'F') continue; parse_function(line); } fclose(fp); return 0; //end }
void function_map_init() { register_savevm(NULL, "funmap", 0, 1, function_map_save, function_map_load, NULL); }
||<
(shared/hookapi.cpp)
|c| static void hookapi_save(QEMUFile *f, void opaque) { ...
for(i = 0; i < HOOKAPI_HTAB_SIZE; i++) { QLIST_FOREACH(hrec, &hookapi_record_heads[i], link) { qemu_put_be32(f, hrec->eip); []
if(dladdr((void *)hrec->fnhook, &info) == 0) { [*]
fprintf(stderr, "%s\n", dlerror());
return;
}
qemu_put_buffer(f, (uint8_t*)info.dli_fname, len); //module name [*]
qemu_put_be32(f, (uint32_t)((uintptr_t)hrec->fnhook - (uintptr_t)info.dli_fbase)); //relative address
...
}
}
qemu_put_be32(f, 0); //terminator
//TODO: save fun_to_hook }
[]でeipを、[]で関数アドレスから所属するモジュール名を解決し[*]で保存している。
static int hookapi_load(QEMUFile *f, void *opaque, int version_id) { hookapi_remove_all(); ... while((eip = qemu_get_be32(f))) { len = qemu_get_be32(f); if(len < 1 || len > PATH_MAX) return -EINVAL;
qemu_get_buffer(f, (uint8_t *)mod_name, len);
handle = dlopen(mod_name, RTLD_NOLOAD); [*]
...
void *sym;
if ((sym = dlsym(handle, "_init"))) {
Dl_info dli;
if (dladdr(sym, &dli))
base = (uintptr_t) dli.dli_fbase; [*]
}
...
record->cbhandle = DECAF_registerOptimizedBlockBeginCallback(&hookapi_check_hook, NULL, record->eip, OCB_CONST); [*]
...
hookapi_insert(record); [*]
dlclose(handle);
} return 0; } []でdlopenし[]で_initシンボルを使ってベースアドレスを算出している。([?]_initって廃止されてなかったっけ..) [*]でhookapi_check_hookをコールバック(stage 2)登録している。hookapi_check_hookはhookapi_record_heads[index(pc)]に登録されているコールバックを全て呼び出す。つまり同じアドレスに複数のフックがある場合を想定している。
要はhookapi_loadは、QEMUFileに保存されているフックポイントeip一覧を一つずつ取得してそれぞれにhookapi_check_hookを置いていく処理。
コールバックのインターフェースは2ステージに分かれている (Stage 1) QEMU <---> callback(ヘルパー関数をTCGに追加する) (Stage 2) callback <---> plugin (callback_structリストcallback_list_heads)
(shared/DECAF_callback.c)
|c| //data structures for storing the userspace callbacks (stage 2) typedef struct callback_struct{ int *enabled; //the following are used by the optimized callbacks //BlockBegin only uses from - to is ignored //blockend uses both from and to gva_t from; gva_t to; OCB_t ocb_type;
DECAF_callback_func_t callback;
LIST_ENTRY(callback_struct) link;
}callback_struct_t; ||<
(shared/DECAF_callback.c)
|c| DECAF_Handle DECAF_registerOptimizedBlockBeginCallback( DECAF_callback_func_t cb_func, int *cb_cond, gva_t addr, OCB_t type) { callback_struct_t * cb_struct = (callback_struct_t *)malloc(sizeof(callback_struct_t)); if (cb_struct == NULL) { return (DECAF_NULL_HANDLE); } ... //pre-populate the info cb_struct->callback = cb_func; cb_struct->enabled = cb_cond; cb_struct->from = addr; cb_struct->to = INV_ADDR; cb_struct->ocb_type = type;
...
//insert it into the list LIST_INSERT_HEAD(&callback_list_heads[DECAF_BLOCK_BEGIN_CB], cb_struct, link);[*]
return ((DECAF_Handle)cb_struct); } ||< [*]でcb_structをcallback_list_headsに登録している。
callback_list_heads[CALLBACK_TYPE]
- callback_list_heads + | | |callback_struct->callback| ------> |hookapi_check_hook|
|c| void init_hookapi(void) { int i; for (i = 0; i < HOOKAPI_HTAB_SIZE; i++) QLIST_INIT(&hookapi_record_heads[i]);
//LOK: We no longer register for ALL basic block callbacks //block_begin_handle = DECAF_register_callback(DECAF_BLOCK_BEGIN_CB, hookapi_check_hook, NULL);
register_savevm(NULL, "hookapi", 0, 2, hookapi_save, hookapi_load, NULL); } ||<
hookapi_record_headsはhookapi_recordのリスト。hookapi_record_heads[pc]はアドレスpcに登録されたフックリストの先頭ポインタを持っている。
(shared/hookapi.cpp)
|c| typedef struct hookapi_record{ uint32_t eip; int is_global; uint32_t esp; //for hooking function return uint32_t cr3; //for hooking function return hook_proc_t fnhook; void *opaque; uint32_t sizeof_opaque; //LOK: Added an entry for the DECAF callback handle DECAF_Handle cbhandle; QLIST_ENTRY(hookapi_record) link; } hookapi_record_t; ||<
load_pluginコマンドの流れ: -monオプションでmon_init_funcを呼び出す。
(vl.c)
|c| static int mon_init_func(QemuOpts *opts, void *opaque) { CharDriverState *chr; const char *chardev; const char *mode; int flags;
mode = qemu_opt_get(opts, "mode");
...
chardev = qemu_opt_get(opts, "chardev");
chr = qemu_chr_find(chardev);
monitor_init(chr, flags); [*]
return 0;
} ||< mon_init_funcではオプションをパースして[*]のmonitor_initを呼び出す。
(monitor.c)
|c| void monitor_init(CharDriverState *chr, int flags) { static int is_first_init = 1; Monitor *mon; ... mon = g_malloc0(sizeof(*mon));
mon->chr = chr;
mon->flags = flags;
...
} else {
qemu_chr_add_handlers(chr, monitor_can_read, monitor_read,
monitor_event, mon); [*]
}
QLIST_INSERT_HEAD(&mon_list, mon, entry); [*]
...
} ||< []でchrキャラクターデバイスに対するフックハンドラを登録している。 []でモニターを登録している。
(monitor.c)
|c| static void monitor_read(void *opaque, const uint8_t *buf, int size) { Monitor *old_mon = cur_mon; int i;
cur_mon = opaque;
if (cur_mon->rs) {
for (i = 0; i < size; i++)
readline_handle_byte(cur_mon->rs, buf[i]);
} else {
if (size == 0 || buf[size - 1] != 0)
monitor_printf(cur_mon, "corrupted command\n");
else
handle_user_command(cur_mon, (char *)buf); [*]
}
cur_mon = old_mon;
} ||< 入力されたコマンドを[*]で処理している。
(monitor.c)
|c| static void handle_user_command(Monitor *mon, const char *cmdline) { QDict *qdict; const mon_cmd_t *cmd;
qdict = qdict_new();
cmd = monitor_parse_command(mon, cmdline, qdict); [*]
if (handler_is_async(cmd)) {
user_async_cmd_handler(mon, cmd, qdict);
} else if (handler_is_qobject(cmd)) {
QObject *data = NULL;
/* XXX: ignores the error code */
cmd->mhandler.cmd_new(mon, qdict, &data);
assert(!monitor_has_error(mon));
if (data) {
cmd->user_print(mon, data);
qobject_decref(data);
}
} else {
cmd->mhandler.cmd(mon, qdict);
}
out: QDECREF(qdict); } ||<
monitor_parse_command->monitor_find_commandでコマンド名からmon_cmd_tを検索して返す。
(monitor.c)
|c| static const mon_cmd_t *monitor_find_command(const char *cmdname) { const mon_cmd_t *cmd; // AWH - search the standard monitor cmds first cmd = search_dispatch_table(mon_cmds, cmdname); if (cmd) return cmd;
//LOK: Now search the DECAF's default commands if (DECAF_mon_cmds != NULL) { cmd = search_dispatch_table(DECAF_mon_cmds, cmdname); [*] if (cmd != NULL) { return cmd; } } ... // AWH - if you can't find it, try to search the plugin term cmds if (decaf_plugin) { if (decaf_plugin->mon_cmds) { cmd = search_dispatch_table(decaf_plugin->mon_cmds, cmdname); if (cmd) return cmd; } if (decaf_plugin->info_cmds) { cmd = search_dispatch_table(decaf_plugin->info_cmds, cmdname); if (cmd) return cmd; } }
return NULL; // No plugin, no cmd found } ||< []のsearch_dispatch_tableでmon_cmd_tのリストであるディスパッチリストからコマンドを探す。 []でDECAF_mon_cmds(shared/DECAF_mon_cmds.h)を検索し、コマンド名からmon_cmd_tを見つける。
(shared/DECAF_mon_cmds.h)
|c| { .name = "load_plugin", .args_type = "filename:F", .params = "filename", .help = "Load a DECAF plugin", .mhandler.cmd_new = do_load_plugin, }, ||<
do_load_plugin
VMI_InitでゲストOSの判定を行うコードを挿入する。 (shared/vmi.cpp)
|c| void VMI_init() { #ifdef CONFIG_VMI_ENABLE monitor_printf(default_mon, "inside vmi init \n"); insn_handle_c = DECAF_register_callback(DECAF_TLB_EXEC_CB, block_end_cb, NULL); [*] #endif
} ||<
[*]でDECAF_TLB_EXEC_CB(ってなんだよ)のフックにblock_end_cbを登録している
(shared/vmi.cpp)
|c| static void block_end_cb(DECAF_Callback_Params* temp) { static long long count_out = 0x8000000000L; // detection fails after 1000 basic blocks int found_guest_os = 0;
if ((count_out & 0xff) != 0) [*]
goto _skip_probe;
for(size_t i=0; i<sizeof(handle_funds_c)/sizeof(handle_funds_c[0]); i++)
{
if(handle_funds_c[i].find(temp->ie.env, insn_handle_c) == 1)
{
GuestOS_index_c = i; [*]
found_guest_os = 1;
}
}
if(found_guest_os)
{
DECAF_unregister_callback(DECAF_TLB_EXEC_CB, insn_handle_c); [*]
handle_funds_c[GuestOS_index_c].init(); [*]
}
_skip_probe:
if (count_out-- <= 0) {// does not find
DECAF_unregister_callback(DECAF_TLB_EXEC_CB, insn_handle_c);
monitor_printf(default_mon, "oops! guest OS type cannot be decided. \n");
}
} ||<
block_end_cbはBasicBlockが実行される度に呼ばれるがゲストOS判定は[]で、0xff回ごとに行う(毎度行うとコストが高すぎる) []でhandle_funcs_cのfindを使ってOS判定を行う。handle_funcs_cおよびos_handle_cは以下のようになっている。
(shared/vmi.h)
|c| typedef struct os_handle_c{ GUEST_OS_C os_info; int (*find)(CPUState *env,uintptr_t insn_handle); void (*init)(); } os_handle_c; ||<
(shared/vmi.cpp)
|c| static os_handle_c handle_funds_c[] = { #ifdef TARGET_I386 { WINXP_SP2_C, &find_winxpsp2, &win_vmi_init, }, { WINXP_SP3_C, &find_winxpsp3, &win_vmi_init, }, { WIN7_SP0_C, &find_win7sp0, &win_vmi_init, }, { WIN7_SP1_C, &find_win7sp1, &win_vmi_init, }, #endif { LINUX_GENERIC_C, &find_linux, &linux_vmi_init,}, }; ||<
LinuxOSの判定はfind_linuxを用いている(後述) またblock_end_cb初めフック関数に渡すパラメーターDECAF_Callback_Paramsは以下のようになっている。
(shared/DECAF_callback_common.h)
|c| typedef struct _DECAF_Callback_Params { DECAF_Handle cbhandle; union{ DECAF_Block_Begin_Params bb; DECAF_Block_End_Params be; DECAF_Insn_Begin_Params ib; DECAF_Insn_End_Params ie; DECAF_Mem_Read_Params mr; DECAF_Mem_Write_Params mw; DECAF_EIP_Check_Params ec; DECAF_Keystroke_Params ks; DECAF_Nic_Rec_Params nr; DECAF_Nic_Send_Params ns; DECAF_Opcode_Range_Params op; DECAF_Tlb_Exec_Params tx; DECAF_Read_Taint_Mem rt; DECAF_Write_Taint_Mem wt; #ifdef CONFIG_TCG_LLVM DECAF_Block_Trans_Params bt; #endif /* CONFIG_TCG_LLVM */ }; } DECAF_Callback_Params;
typedef void (DECAF_callback_func_t)(DECAF_Callback_Params); ||<
フックの種類ごとにunionでパラメーターを変えている。
さてfind_linuxを見ていく。
|c| // to see whether this is a Linux or not, // the trick is to check the init_thread_info, init_task int find_linux(CPUState env, uintptr_t insn_handle) { target_ulong _thread_info = DECAF_getESP(env) & ~ (guestOS_THREAD_SIZE - 1); [] static target_ulong _last_thread_info = 0;
// if current address is tested before, save time and do not try it again
if (_thread_info == _last_thread_info || _thread_info <= 0x80000000)
return 0;
_last_thread_info = _thread_info;
if(0 != load_proc_info(env, _thread_info, OFFSET_PROFILE)) [*]
{
return 0;
}
monitor_printf(default_mon, "swapper task @ [%08x] \n", OFFSET_PROFILE.init_task_addr);
// load library function offset
load_library_info(OFFSET_PROFILE.strName); [*]
// load_proc_info(OFFSET_PROFILE, temp_offset_profile.init_task_addr);
//printProcInfo(&OFFSET_PROFILE);
VMI_guest_kernel_base = 0xc0000000;
return (1);
} ||<
[*]でOFFSET_PROFILEに現在のプロセス(ここではinit_process)の情報をiniファイルから読み込み格納する。
(shared/linux_procinfo.cpp)
|c| // infer init_task_addr, use the init_task_addr to search for the corresponding // section in procinfo.ini. If found, fill the fields in ProcInfo struct.
int load_proc_info(CPUState * env, gva_t threadinfo, ProcInfo &pi) { static bool bProcinfoMisconfigured = false; const int CANNOT_FIND_INIT_TASK_STRUCT = -1; const int CANNOT_OPEN_PROCINFO = -2; const int CANNOT_MATCH_PROCINFO_SECTION = -3; target_ulong tulInitTaskAddr;
if(bProcinfoMisconfigured) { return CANNOT_MATCH_PROCINFO_SECTION; }
// find init_task_addr tulInitTaskAddr = findTaskStructFromThreadInfo(env, threadinfo, &pi, 0); [*]
string sProcInfoPath; boost::property_tree::ptree pt; get_procinfo_directory(sProcInfoPath); [] sProcInfoPath += "procinfo.ini"; monitor_printf(default_mon, "\nProcinfo path: %s\n",sProcInfoPath.c_str()); // read procinfo.ini if (0 != access(sProcInfoPath.c_str(), 0)) { monitor_printf(default_mon, "can't open %s\n", sProcInfoPath.c_str()); return CANNOT_OPEN_PROCINFO; } boost::property_tree::ini_parser::read_ini(sProcInfoPath, pt); []
// find the match section using previously found init_task_addr int iSectionNum = find_match_section(pt, tulInitTaskAddr); [] ... _load_one_section(pt, iSectionNum, pi); [] monitor_printf(default_mon, "Match %s\n", pi.strName); return 0; } ||<
[*]でthread_infoからtask_structを取得する。
|c| gva_t findTaskStructFromThreadInfo(CPUState * env, gva_t threadinfo, ProcInfo* pPI, int bDoubleCheck) { int bFound = 0; target_ulong i = 0; target_ulong j = 0; gva_t temp = 0; gva_t temp2 = 0; gva_t candidate = 0; gva_t ret = INV_ADDR;
if (pPI == NULL) { return (INV_ADDR); }
//iterate through the thread info structure for (i = 0; i < MAX_THREAD_INFO_SEARCH_SIZE; i+= sizeof(target_ptr)) { temp = (threadinfo + i); candidate = 0; // candidate = (get_target_ulong_at(env, temp)); DECAF_read_ptr(env, temp, &candidate); //if it looks like a kernel address if (isKernelAddress(candidate)) [] { //iterate through the potential task struct for (j = 0; j < MAX_TASK_STRUCT_SEARCH_SIZE; j+= sizeof(target_ptr)) { temp2 = (candidate + j); target_ulong val = 0; DECAF_read_ptr(env, temp2, &val); if (val == threadinfo) [] { pPI->ti_task = i; [*] pPI->ts_stack = j; ret = candidate;
if (!bDoubleCheck)
{
return (ret);
}
else
{
//printk(KERN_INFO "TASK STRUCT @ [0x%"T_FMT"x] FOUND @ offset %"T_FMT"d\n", candidate, j);
bFound = 1;
}
}
}
}
} return (ret); } ||<
thread_infoのメンバを走査していき、[]でtask_structのアドレスがカーネル空間ならtask_structのメンバ走査を始める。[]でtask_structが確かにthread_infoを指していたら(冗長チェック)[*]でProcInfoに、それぞれのメンバのインデックスを格納してtask_structのアドレスを返す。
次に[]でboost::property_treeを使ってiniファイルを読み込みパース結果をptに入れる。[]のfind_match_sectionでtask_structのアドレスとiniファイルの.init_task_addrを照合し、該当するOSのバージョンを検出する。(shared/kernelinfo/procinfo_generic/procinfo.ini) [*]の_load_one_sectionで指定されたセクション番号の情報を読み込む。
(shared/linux_procinfo.cpp)
|c| #define FILL_TARGET_ULONG_FIELD(field) pi.field = pt.get(sSectionNum + #field, INVALID_VAL) void _load_one_section(const boost::property_tree::ptree &pt, int iSectionNum, ProcInfo &pi) { string sSectionNum;
sSectionNum = boost::lexical_cast<string>(iSectionNum);
sSectionNum += ".";
// fill strName field
string sName;
const int SIZE_OF_STR_NAME = 32;
sName = pt.get<string>(sSectionNum + "strName");
strncpy(pi.strName, sName.c_str(), SIZE_OF_STR_NAME);
pi.strName[SIZE_OF_STR_NAME-1] = '\0'; [*]
const target_ulong INVALID_VAL = -1;
// fill other fields
FILL_TARGET_ULONG_FIELD(init_task_addr ); [*]
FILL_TARGET_ULONG_FIELD(init_task_size );
...
} ||<
[]でProcInfo.strnameにiniファイルのstrName属性値を代入している。 []のFILL_TARGET_ULONG_FIELDを使ってproperty_tree(つまりiniファイル属性)からProcInfoへ値をコピーしている。
ProcInfoが取得できたので次に[*]のload_library_infoでバージョン名からライブラリ情報を読み込む。
(shared/linux_procinfo.cpp)
|c| class LibraryLoader { public: LibraryLoader(const char strName) { if(init_property_tree(strName)) [] { load(); [*] } } private:
void load() { // load every section int cntSection = m_pt.get("info.total", 0); string sSectionNum;
monitor_printf(default_mon, "Lib Configuration Total Sections: %d\n", cntSection);
for(int i = 1; i<=cntSection; ++i)
{
sSectionNum = boost::lexical_cast<string>(i);
m_cur_libpath = m_pt.get<string>(sSectionNum + "." + LIBPATH_PROPERTY_NAME); [*]
m_cur_section = &m_pt.get_child(sSectionNum);
load_cur_section(); [*]
}
}
void load_cur_section() { monitor_printf(default_mon, "loading lib conf for %s\n", m_cur_libpath.c_str()); // traverse the section BOOST_FOREACH(boost::property_tree::ptree::value_type &v, m_cur_section) { if(!v.first.compare(LIBPATH_PROPERTY_NAME)) { continue; } // insert function target_ulong addr = m_cur_section->get<target_ulong>(v.first); [] funcmap_insert_function(m_cur_libpath.c_str(), v.first.c_str(), addr, 0); [*] } } };
const string LibraryLoader::LIBPATH_PROPERTY_NAME = "decaf_conf_libpath";
void load_library_info(const char strName) { LibraryLoader loader(strName); [] } ||<
[]でLibraryLoaderのコンストラクタを呼び出す。[]でkernelinfo/old_stuff/lib_confの該当するiniファイルのboost::property_treeを初期化する。 []のloadで実際にglibcの関数情報を読み込む。[]でglibcのパスを読み、[]で現在のiniセクションに対してパースを開始する。 []で各関数のアドレスをaddrに代入し、[*]のfuncmap_inesrt_functionで関数,アドレスの組を
(shared/function_map.cpp)
|c| void funcmap_insert_function(const char *module, const char *fname, uint32_t offset, uint32_t inode_number) { char key[64]; sprintf(key, "%u_%s", inode_number, module);
map<string, map<string, uint32_t> >::iterator iter = map_function_offset.find(key);
if (iter == map_function_offset.end()) {
map<string, uint32_t> func_offset;
func_offset[fname] = offset;
map_function_offset[key] = func_offset;
} else {
iter->second.insert(pair<string, uint32_t>(string(fname), offset));
}
map<string, map<uint32_t, string> >::iterator iter2 = map_offset_function.find(key);
if (iter2 == map_offset_function.end()) {
map<uint32_t, string> offset_func;
offset_func[offset] = fname;
map_offset_function[key] = offset_func;
} else
iter2->second.insert(pair<uint32_t, string>(offset, fname));
} ||<
map_function_offset[inode+モジュール名]->func_offset[関数名]->関数のRVA またmap_offset_functionはこの逆。
+--map_function_offset--+ | inode+module | ---> +--func_offset--+ | .... | | fname | ---> offset +-----------------------+ +---------------+
ゲストOSの判定が完了すれば[*]のinitを使って初期化を行う。
(shared/linux_vmi.cpp)
|c| void Linux_tlb_call_back(DECAF_Callback_Params *temp) { CPUState *ourenv = temp->tx.env; uint32_t vaddr = temp->tx.vaddr; uint32_t pgd = -1; process proc = NULL; bool found_new = false; pgd = DECAF_getPGD(ourenv); []
//TODO: kernel modules are not retrieved in the current implementation.
if (DECAF_is_in_kernel(ourenv)) {
//proc = kernel_proc;
}
else if ( (proc = VMI_find_process_by_pgd(pgd)) == NULL) {
found_new = ((proc = find_new_process(ourenv, pgd)) != NULL); [*]
}
if (proc) {
if ( !is_vm_page_resolved(proc, vaddr) ) { [*]
char task_comm[SIZEOF_COMM];
if ( !found_new
&& !DECAF_read_mem(ourenv, proc->EPROC_base_addr + OFFSET_PROFILE.ts_comm, SIZEOF_COMM, task_comm)
&& strncmp(proc->name, task_comm, SIZEOF_COMM) ) {
strcpy(proc->name, task_comm);
//message_p(proc, '^');
}
get_new_modules(ourenv, proc); [*]
//If this page still cannot be resolved, we give up.
if (!is_vm_page_resolved(proc, vaddr)) {
int attempts = unresolved_attempt(proc, vaddr);
if (attempts > 200)
proc->resolved_pages.insert(vaddr>>12);
}
}
}
} ... // when we know this is a linux void linux_vmi_init() {
DECAF_register_callback(DECAF_TLB_EXEC_CB, Linux_tlb_call_back, NULL); [*]
recon_timer = qemu_new_timer_ns(vm_clock, check_procexit, 0);
qemu_mod_timer(recon_timer,qemu_get_clock_ns(vm_clock) + get_ticks_per_sec() * 20); [*]
} ||<
[*]でTLB実行時のコールバックLinux_tlb_call_backを登録し、ゲストOSのIntrospectionコードを実行している。
(shared/linux_vmi.cpp)
|c| static process * find_new_process(CPUState *env, uint32_t cr3) { uint32_t task_pid = 0, ts_parent_pid = 0, proc_cr3 = -1; const int MAX_LOOP_COUNT = 1024; // maximum loop count when trying to find a new process (will there be any?) process *right_proc = NULL;
target_ulong next_task, ts_real_parent, mm, task_pgd;
next_task = OFFSET_PROFILE.init_task_addr;
// avoid infinite loop
for (int count = MAX_LOOP_COUNT; count > 0; --count)
{
BREAK_IF(DECAF_read_ptr(env,
next_task + (OFFSET_PROFILE.ts_tasks + sizeof(target_ptr)),
&next_task) < 0); [*]
next_task -= OFFSET_PROFILE.ts_tasks; [*]
BREAK_IF(DECAF_read_ptr(env,
next_task + OFFSET_PROFILE.ts_tgid,
&task_pid) < 0);
BREAK_IF(DECAF_read_ptr(env,
next_task + OFFSET_PROFILE.ts_mm,
&mm) < 0); [*]
if (mm != 0)
{ // for user-processes
// we read the value of active_mm into mm here
BREAK_IF(DECAF_read_ptr(env,
next_task + OFFSET_PROFILE.ts_mm + sizeof(target_ptr),
&mm) < 0
||
DECAF_read_ptr(env,
mm + OFFSET_PROFILE.mm_pgd,
&task_pgd) < 0); [*]
proc_cr3 = DECAF_get_phys_addr(env, task_pgd); [*]
}
else
{ // for kernel threads
proc_cr3 = -1;// when proc_cr3 is -1UL, we cannot find the process by findProcessByCR3(), but we still can do findProcessByPid()
}
if (!VMI_find_process_by_pgd(proc_cr3)) { [*]
// get parent task's base address
...
process* pe = new process(); [*]
pe->pid = task_pid;
pe->parent_pid = ts_parent_pid;
pe->cr3 = proc_cr3;
pe->EPROC_base_addr = next_task; // store current task_struct's base address
BREAK_IF(DECAF_read_mem(env,
next_task + OFFSET_PROFILE.ts_comm,
SIZEOF_COMM, pe->name) < 0);
VMI_create_process(pe); [*]
//monitor_printf(default_mon, "new proc = %s, pid = %d, parent_pid = %d \n", pe->name, pe->pid, pe->parent_pid);
if (cr3 == proc_cr3) {
right_proc = pe; [*]
}
}
}
//last_task_pid = _last_task_pid;
return right_proc;
} ||<
[]でtask_struct->tasks(list_head)メンバを読みだし、次のプロセス(next_task)を読む。(1ループ目からnextを読んでいるのはリストの先頭のswapperをスキップするため) []でnext_taskからtasksメンバのオフセットを引いてtask_structの先頭アドレスを取得している。これでtask_structのアドレスが分かったのでpid,mmのアドレスを[]で取得。また[]でmm->pgd(仮想アドレスGVA)を取得し、[]のDECAF_get_phys_addrでQEMUのSoftMMUを使ってGPA(?)に変換する。 []のVMI_find_process_by_pgd(shared/vmi.cpp)によりprocess_mapからprocessクラス(shared/vmi.h)を検索して返す。
+--process_map--+ | cr3_addr | -----> +---process---+ +---------------+
もしまだprocess_map内にない場合は新しいプロセスを発見したとし、[*]でprocess構造体を確保してpid,ppid,comm(実行コマンド)などの情報を設定した後、VMI_create_process関数を呼ぶ。
(shared/vmi.cpp)
|c| int VMI_create_process(process *proc) {
VMI_Callback_Params params;
proc->modules_extracted = true;
params.cp.cr3 = proc->cr3;
params.cp.pid = proc->pid;
params.cp.name = proc->name;
unordered_map < uint32_t, process * >::iterator iter =
process_pid_map.find(proc->pid);
if (iter != process_pid_map.end()){
VMI_remove_process(proc->pid); [*]
}
unordered_map < uint32_t, process * >::iterator iter2 =
process_map.find(proc->cr3);
if (iter2 != process_map.end()) {
VMI_remove_process(iter2->second->pid); [*]
}
process_pid_map[proc->pid] = proc; [*]
process_map[proc->cr3] = proc;
SimpleCallback_dispatch(&VMI_callbacks[VMI_CREATEPROC_CB], ¶ms); [*]
return 0;
} ||<
[],[]でprocess_pid_map(pid->process)およびprocess_map(cr3->process)に既に同じprocessが存在する場合は取り除く。そして[]で新たに設定する。 []のSimpleCallback_dispatch(shared/util/SimpleCallback.c)でVMI_callbacks[VMI_CREATEPROC_CB]につながっているフックリストを全て実行する。これで登録したコールバック関数が全て呼び出される。
ではLinux_tlb_call_backに戻って[*]のis_vm_page_resolvedを使ってすでにvaddrにマップされたモジュールがわかっているか判定する。
(shared/linux_vmi.cpp)
|c| static inline bool is_vm_page_resolved(process *proc, uint32_t addr) { return (proc->resolved_pages.find(addr >> 12) != proc->resolved_pages.end()); } ||<
process->resolved_pagesはvaddr<-->マップされたmoduleの対応が既に解決されているページの一覧を示す。(shared/vmi.h) まだ解決していない場合は[*]のget_new_modulesで新たにモジュールのマッピングを検索する。 get_new_modulesはゲストがi386の場合はget_new_modules_x86である。
(shared/linux_vmi.cpp)
|c|
||<
hookapi_hook_function_byname: モジュール名,API名を渡してフックfnhookを呼び出す
(shared/hookapi.cpp)
|c| uintptr_t hookapi_hook_function_byname(const char *mod_name, const char fun_name, int is_global, target_ulong cr3, hook_proc_t fnhook, void opaque, uint32_t sizeof_opaque) { target_ulong pc = funcmap_get_pc(mod_name, fun_name, cr3); [] return (hookapi_hook_function(is_global, pc, cr3, fnhook, opaque, sizeof_opaque)); [] } ||<
[*]でpc取得。
(shared/function_map.cpp)
|c| target_ulong funcmap_get_pc(const char *module_name, const char function_name, target_ulong cr3) { target_ulong base; module mod = VMI_find_module_by_name(module_name, cr3, &base); [] if(!mod) return 0; VMI_extract_symbols(mod,base); []
char key[64];
sprintf(key, "%u_%s", mod->inode_number, mod->name);
map<string, map<string, uint32_t> >::iterator iter = map_function_offset.find(key);
if(iter == map_function_offset.end())
return 0;
map<string, uint32_t>::iterator iter2 = iter->second.find(function_name);
if(iter2 == iter->second.end())
return 0;
return iter2->second + base;
} ||<
[*]でモジュール名,pgdアドレスからハンドラ取得。baseにモジュールのベースアドレス格納。
(shared/vmi.cpp)
|c| module * VMI_find_module_by_name(const char *name, target_ulong pgd, target_ulong *base) { unordered_map < uint32_t, process * >::iterator iter_p = process_map.find(pgd); process *proc = iter_p->second;
if(!proc->modules_extracted)
traverse_mmap(cpu_single_env, proc);
unordered_map< uint32_t, module * >::iterator iter;
for (iter = proc->module_list.begin(); iter != proc->module_list.end(); iter++) {
module *mod = iter->second;
if (strcasecmp(mod->name, name) == 0) {
*base = iter->first; [*]
return mod;
}
}
return NULL;
} ||<
proc->module_listからモジュール一覧を取得して、ベースアドレスが一致する物を取得。
さて次に[*]のhookapi_hook_functionで
(shared/hookapi.cpp)
|c| uintptr_t hookapi_hook_function( int is_global, target_ulong pc, target_ulong cr3, hook_proc_t fnhook, void *opaque, uint32_t sizeof_opaque ) { hookapi_record_t *record = (hookapi_record_t *)g_malloc(sizeof(hookapi_record_t));
if (record == NULL) return 0;
record->eip = pc; record->cr3 = cr3; record->is_global = is_global; record->fnhook = fnhook; record->sizeof_opaque = sizeof_opaque; record->opaque = opaque; record->esp = 0; //esp is only used for return hook record->cbhandle = DECAF_registerOptimizedBlockBeginCallback(&hookapi_check_hook, NULL, pc, OCB_CONST); [*] if (record->cbhandle == DECAF_NULL_HANDLE) { g_free(record); return (0); }
hookapi_insert(record); [*] return (uintptr_t)record; } ||<
[],[]で上でも説明したcb_structの登録をしている。
さてstage2インターフェースの登録までは見たので、次にstage1(QEMU<->コールバック)を見ていく。 stage1はtarget-***系のTCGにDECAFのヘルパー関数を追加してVMIコードを入れるプロセスを言う。
ゲストOS判定後 (shared/linux_vmi.cpp)
|c| void Linux_tlb_call_back(DECAF_Callback_Params *temp) { CPUState *ourenv = temp->tx.env; uint32_t vaddr = temp->tx.vaddr; uint32_t pgd = -1; process *proc = NULL; bool found_new = false; pgd = DECAF_getPGD(ourenv);
//TODO: kernel modules are not retrieved in the current implementation.
if (DECAF_is_in_kernel(ourenv)) {
//proc = kernel_proc;
}
else if ( (proc = VMI_find_process_by_pgd(pgd)) == NULL) {
found_new = ((proc = find_new_process(ourenv, pgd)) != NULL);
}
if (proc) { // we are not scanning modules for kernel threads, since kernel thread's cr3 is -1UL, the proc should be null
if ( !is_vm_page_resolved(proc, vaddr) ) {
char task_comm[SIZEOF_COMM];
if ( !found_new
&& !DECAF_read_mem(ourenv, proc->EPROC_base_addr + OFFSET_PROFILE.ts_comm, SIZEOF_COMM, task_comm)
&& strncmp(proc->name, task_comm, SIZEOF_COMM) ) {
strcpy(proc->name, task_comm);
//message_p(proc, '^');
}
get_new_modules(ourenv, proc);
//If this page still cannot be resolved, we give up.
if (!is_vm_page_resolved(proc, vaddr)) {
int attempts = unresolved_attempt(proc, vaddr);
if (attempts > 200)
proc->resolved_pages.insert(vaddr>>12);
}
}
// retrieve symbol information here
//retrive_symbols(env, proc);
}
} ||<
(shared/linux_procinfo.cpp)
|c| int populate_kernel_offsets(CPUState env, gva_t threadinfo, ProcInfo pPI) { ... if ((taskstruct = findTaskStructFromThreadInfo(env, threadinfo, pPI, 0)) == INV_ADDR) [] return (-1); if ((mmstruct = findMMStructFromTaskStruct(env, taskstruct, pPI, 0)) == INV_ADDR) [] return (-1);
if (findTaskStructListFromTaskStruct(env, taskstruct, pPI, 0) == INV_ADDR) [*] return (-1);
if (findRealParentGroupLeaderFromTaskStruct(env, taskstruct, pPI) == INV_ADDR) [*] return (-1);
gl = get_target_ulong_at(env, taskstruct + pPI->ts_group_leader); ret = findCommFromTaskStruct(env, gl, pPI);
//don't forget to to get back to the head of the task struct // by subtracting ts_tasks offset tempTask = get_target_ulong_at(env, gl + pPI->ts_tasks) - pPI->ts_tasks; while ((ret == INV_ADDR) && (tempTask != gl) && (isKernelAddress(tempTask))) { ret = findCommFromTaskStruct(env, tempTask, pPI); //move to the next task_struct tempTask = get_target_ulong_at(env, tempTask + pPI->ts_tasks) - pPI->ts_tasks; }
if (ret != INV_ADDR) { //printk(KERN_INFO "Comm offset is = %"T_FMT"d, %s \n", pPI->ts_comm, (char*)(taskstruct + pPI->ts_comm)); } else { return (-1); }
if (findCredFromTaskStruct(env, taskstruct, pPI) == INV_ADDR) return (-1); //printk(KERN_INFO "real_cred = %"T_FMT"d, cred = %"T_FMT"d \n", pPI->ts_real_cred, pPI->ts_cred);
if (findPIDFromTaskStruct(env, taskstruct, pPI) == INV_ADDR) return (-1); //printk(KERN_INFO "pid = %"T_FMT"d, tgid = %"T_FMT"d \n", pPI->ts_pid, pPI->ts_tgid);
//For this next test, I am just going to use the task struct lists if (findThreadGroupFromTaskStruct(env, pPI->init_task_addr, pPI) == INV_ADDR) return (-1); //printk(KERN_INFO "Thread_group offset is %"T_FMT"d\n", pPI->ts_thread_group);
realcred = get_target_ulong_at(env, taskstruct + pPI->ts_real_cred); if (populate_cred_struct_offsets(env, realcred, pPI) != 0) return (-1);
isOffsetPopulated = true; return (0); } ||<
[*]のfindMMStructFromTaskStructで
toggle-kvm (-enable-kvm時SEGV→DECAF_kvm_enabledとkvm_enabled()を同時にtrueにしなければSEGV→-enable-kvmのみで動くよう修正)
(save/load)vmでスナップショットを取る。qcow2(およびスナップショット)をTSKで解析するようなプラグイン開発が行えるようにする。