Skip to content

Instantly share code, notes, and snippets.

@ntddk
Last active August 16, 2016 08:15
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ntddk/29a63e9dc1cdc9221331ef38b2326676 to your computer and use it in GitHub Desktop.
Save ntddk/29a63e9dc1cdc9221331ef38b2326676 to your computer and use it in GitHub Desktop.

演習:サンドボックスをつくってみよう

c2.exe(QEMU検知あり)またはc2safe.exe(QEMU検知なし)を解析し,以下の情報を半自動的に抽出するしくみをつくってください:

  • 接続するURL一覧
  • 受信したコマンド
  • 実行されたコマンドハンドラのダンプ
  • 実行されなかったコマンドハンドラのダンプ

Google is your friend.

演習環境

sandbox/sandbox

概要は昨年の資料を参照のこと. 演習環境にインストール済.ビルドオプションはデフォルトです.ただし,URLDownloadFileWなどurlmon.dllのAPIをフックできるよう,オリジナルのDECAF (commit 32a937ebdcf7f1db2613156da46e61dfc07cdaba) を改変してあります:

--- windows_vmi.cpp.bak 2016-08-11 23:14:35.000000000 +0900
+++ windows_vmi.cpp     2016-08-11 22:55:49.871370930 +0900
@@ -90,7 +90,7 @@


 static const char * dll_modules_list[] = {
-       "ntdll.dll", "kernel32.dll", "ntoskrnl.exe", "hal.dll", "win32k.sys", "ndis.sys", "user32.dll", "advapi32.dll", "psapi.dll", "shell.dll", "ws2_32.dll",
+       "ntdll.dll", "kernel32.dll", "ntoskrnl.exe", "hal.dll", "win32k.sys", "ndis.sys", "user32.dll", "advapi32.dll", "psapi.dll", "shell.dll", "ws2_32.dll", "urlmon.dll",
 };

 static bool should_extract_symbol(const char *module_name)

DECAFを起動するには,ホームディレクトリから次のコマンドをタイプします.

~/decaf/i386-softmmu/qemu-system-i386 -monitor stdio -m 2048 ~/winxp.qcow2 -netdev user,id=mynet -device ne2k_pci,netdev=mynet

ゲストOSはマシンスペックを鑑みてWindows XPです. なお,ゲストOSのマウスカーソルがカクつかないよう,.bashrcに追記しています.zshなど他のシェルを使う方は参考にしてください.

echo export SDL_VIDEO_X11_DGAMOUSE=0 >> .bashrc

もしゲストOSにファイルを移動したい場合は,ホームディレクトリから次のコマンドをタイプします:

sudo bash mount.sh winxp.qcow2

ここで,ゲストOSのデスクトップは/mnt/Documents\ and\ Settings/IEUser/Desktop/に展開されます.ファイル操作が終了したら,DECAFを起動する前にホームディレクトリから次のコマンドをタイプしてください:

sudo bash unmount.sh

プラグインの雛形は

git clone https://github.com/ntddk/geteip

で入手できます../configure --decaf-path=/home/sandbox/decaf && makeでパスを設定していれば,ディレクトリはどこでも構いません.plugin_cmds.hがコマンド,geteip.cが本体です.共有ライブラリとしてDECAFのAPIを叩くしくみになっています.DECAFを起動し,

(qemu) load_plugin /home/sandbox/geteip/geteip.so
(qemu) geteip c2.exe
(qemu) unload_plugin

のように解析対象のプロセスを指定してから,手動でゲストOS上のexeを実行してください.

(qemu) ps
(qemu) help
(qemu) savevm hoge
(qemu) loadvm hoge

などの機能も適宜活用してください.APIフックは次のようにして書けます:

typedef struct{
    uint32_t call_stack[1]; // リターンアドレスがcall_stack[0], 引数がctx->call_stack[1]以降に入る
    DECAF_Handle hook_handle;
} IsDebuggerPresent_hook_context_t;

static void IsDebuggerPresent_ret(void *param){
    IsDebuggerPresent_hook_context_t *ctx = (IsDebuggerPresent_hook_context_t *)param;
    hookapi_remove_hook(ctx->hook_handle);
    DECAF_printf("EIP = %08x, EAX = %d\n", cpu_single_env->eip, cpu_single_env->regs[R_EAX]);
    free(ctx);
}

static void IsDebuggerPresent_call(void *opaque){
    DECAF_printf("IsDebuggerPresent ");
    IsDebuggerPresent_hook_context_t *ctx = (IsDebuggerPresent_hook_context_t*)malloc(sizeof(IsDebuggerPresent_hook_context_t));
    if(!ctx) return;
    DECAF_read_mem(NULL, cpu_single_env->regs[R_ESP], 4, ctx->call_stack); // 引数の数に応じて4, 4*2, 4*3...
    ctx->hook_handle = hookapi_hook_return(ctx->call_stack[0], IsDebuggerPresent_ret, ctx, sizeof(*ctx));
}

static void geteip_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;
        isdebuggerpresent_handle = hookapi_hook_function_byname("kernel32.dll", "IsDebuggerPresent", 1, target_cr3, IsDebuggerPresent_call, NULL, 0);  
        blockbegin_handle = DECAF_register_callback(DECAF_BLOCK_BEGIN_CB, &geteip_block_begin_callback, NULL);
    }
}

APIが用いる引数とスタックの対応を意識してください(スライド p.54).マクロを使うと次のようにも書けます.

typedef struct{
	uint32_t call_stack[1]; // リターンアドレス
	DECAF_Handle hook_handle;
} hook_context_t;

// x##_[文字列]でxと文字列の結合,#xでxが"x"に,\で改行.
#define CALL_HOOK(x) static void x##_call(void *opaque){ \
	DECAF_printf("%s ", #x); \
	hook_context_t *ctx = (hook_context_t*)malloc(sizeof(hook_context_t)); \
	if(!ctx) return; \
	DECAF_read_mem(NULL, cpu_single_env->regs[R_ESP], 4, ctx->call_stack); \
	ctx->hook_handle = hookapi_hook_return(ctx->call_stack[0], x##_ret, ctx, sizeof(*ctx));\
} \
static DECAF_Handle x##_handle = DECAF_NULL_HANDLE

#define RET_HOOK(x,y) static void x##_ret(void *param) { \
	hook_context_t *ctx = (hook_context_t *)param; \
	hookapi_remove_hook(ctx->hook_handle); \
	free(ctx); \
	y;\
}
#define HOOK_FUNC(x,y) x##_handle = hookapi_hook_function_byname(#y, #x, 1, target_cr3, x##_call, NULL, 0)

RET_HOOK(URLDownloadToFileW, cpu_single_env->regs[R_EAX]);
CALL_HOOK(URLDownloadToFileW);

static void tracer_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;
		HOOK_FUNC(URLDownloadToFileW, urlmon.dll);
		blockbegin_handle = DECAF_register_callback(DECAF_BLOCK_BEGIN_CB, &tracer_block_begin_callback, NULL);
		insn_handle = DECAF_register_callback(DECAF_INSN_BEGIN_CB, &tracer_insn_callback, NULL);
	}
}

/home/sandbox/bak/に予備のゲストOSイメージがあります.もし環境を壊してしまったら使ってください.

演習環境にインストール済.angrはシンボリック実行やプログラムスライシングを通して,プログラムの特定位置に到達するための入力値を抽出する静的解析ツールです.ただし,プログラムの規模が大きい場合は解析に時間を要します.したがって,解析対象のどこまでをDECAFで解析し,どこまでをangrで解析するかが,効率のよい自動化の肝となってきます.

#!/usr/bin/env python
#-*- coding:utf-8 -*-
import sys
import angr
import simuvex # シンボリック実行エンジン

# 解析対象を指定
p = angr.Project(sys.argv[1]) 

# 制御フローグラフの生成
cfg = p.analyses.CFG()
print [x for x in cfg.functions.iteritems()]

# シンボルを取得
target_addr = p.loader.main_bin.get_symbol("main").addr

# パス分析クラスのインスタンス
pg = p.factory.path_group()

# シンボルへのパスを分析
pg.explore(find = target_addr)

# 分析対象はアドレスでも指定可能
# findが到達したいアドレス,avoidが到達したくないアドレス
# angrはfindに到達する入力値を探索してくれる
a = p.surveyors.Explorer(find= FIND_ADDR, avoid= AVOID_ADDR).run()

# 実行結果のダンプ
a.found[0].state.posix.dumps(1)

プログラムが複雑な場合,エントリーポイントを起点とした制御フローグラフの作成やパス分析は失敗してしまうことがありますが,angrはプログラムの途中の入力を起点に解析を始めることができます.

p = angr.Project(sys.argv[1])

# 解析の起点となるアドレスを指定
state = p.factory.blank_state(addr = START_ADDR)
# その地点までプログラムを実行したときのスタックの状態を指定
state.regs.ebp = BASE_ADDR
state.regs.esp = STACK_ADDR

# 入力値の設定
for i in range(INPUT_LENGTH):
    s = state.se.BVS('Var[{}]'.format(i), 32, explicit_name=True)
    state.memory.store(INPUT_ADDR + i * 4, s)

path = p.factory.path(state)
a = p.surveyors.Explorer(start = path, find= FIND_ADDR, avoid= AVOID_ADDR)
a.found[0].state.posix.dumps(1)

必要に応じてインストールしてください.

wget https://github.com/aquynh/capstone/archive/3.0.4.tar.gz
tar xvzf 3.0.4.tar.gz && cd capstone-3.0.4 && bash ./make.sh && sudo bash ./make.sh install

DECAFでアドレスを読み取ったあと,逆アセンブルに使えます.

#include <stdio.h>
#include <inttypes.h>

#include <capstone/capstone.h>

#define CODE "\x55\x48\x8b\x05\xb8\x13\x00\x00"

int main(void)
{
	csh handle;
	cs_insn *insn;
	size_t count;

	if (cs_open(CS_ARCH_X86, CS_MODE_64, &handle) != CS_ERR_OK)
		return -1;
	count = cs_disasm_ex(handle, CODE, sizeof(CODE)-1, 0x1000, 0, &insn);
	if (count > 0) {
		size_t j;
		for (j = 0; j < count; j++) {
			printf("0x%"PRIx64":\t%s\t\t%s\n", insn[j].address, insn[j].mnemonic,
					insn[j].op_str);
		}

		cs_free(insn, count);
	} else
		printf("ERROR: Failed to disassemble given code!\n");

	cs_close(&handle);

	return 0;
}

Capstoneを用いる場合は<capstone/capstone.h>をincludeし,gccのオプションに-Wall -lcapstoneを付加してください. Programming with C language – Capstone – The Ultimate Disassemblerが参考になるでしょう.

UnicornはQEMUとcapstoneをベースとしたユーザーランドのエミュレータで,多言語バインディングを備えています.しかしながら,PEファイルのエミュレーションには未だ難点があり,今回の演習では用いません.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment