セキュリティキャンプ講義「仮想化技術を用いたマルウェア解析」にチャレンジしてみた(プラグイン開発編)
前回のエントリでDECAFをコンパイル、仮想マシンの用意・起動とプラグインを読み込ませるところまでやった。
今回は講義の本題であるプラグインの開発をやっていく。
私自身はQemu, DECAFや関数Hook,PEB構造体とかも全く知らず,マルウェア解析の経験もない状態で始めてみたが,何とかなったので興味があるならチャレンジしてみれば楽しいと思う.
最終的なプラグインはpoppycompass/red · GitHubに置いてある.適宜参照してほしい.
プラグイン基礎
プラグインの基本をIsDebuggerPresent
をHookするプラグインgeteip
を使って説明する.
Hookとは,プログラムの特定の箇所にユーザが定義した処理を追加することらしい.
今回で言うと,IsDebuggerPresent
が実行される前と後に任意の処理を追加する関数Hookのことを指す.
前回のエントリのコマンドを一通り試した人は<your_path>/DECAF/decaf/plugins/geteip
以下にあるはずである.
まだ持っていない人は
$ git clone https://github.com/ntddk/geteip
でダウンロードすること.
このプラグインの主なファイルはplugin_cmds.h
とgeteip.c
である.
これからそれぞれの内容を見ていきたい.
なお,解説は現在の私の理解であるため,誤り等が含まれることがある.見つけたら遠慮なく指摘していただけるとありがたい.
plugin_cmds.h
プラグインに実装した機能はqemuのコマンドとして実行する.
このコマンド名はmon_cmd_t
で設定する.具体的な設定は以下の通り.
{ .name = "geteip", // コマンド名 .args_type = "procname:s?", // 引数タイプ.今回は特定のプロセス名を引数に取る .mhandler.cmd = do_monitor_proc, // コマンド実行時に実行する関数名 .params = "[procname]", // ???用途不明??? .help = "tracking EIP of [procname] as block"// コメント },
このプラグインではplugin_cmds.h
で構造体を定義し,geteip.c
にて
static mon_cmd_t geteip_term_cmds[] = { #include "plugin_cmds.h" {NULL, NULL, }, };
とインクルードしている.当然のことながらgeteip.c
の方に埋め込むこともできる.
コマンド名は自由に変えることができるが,変更したら*.c
の名前やソースの関数名も合わせて変更しないと混乱する.
geteip.c
全部説明すると長いので,Hook関連の場所だけを抜粋して説明する.
// Hook用のハンドル static DECAF_Handle isdebuggerpresent_handle = DECAF_NULL_HANDLE; typedef struct { uint32_t call_stack[1]; //paramters and return address, 今回は引数がないのでreturn adddressが入る. DECAF_Handle hook_handle; } IsDebuggerPresent_hook_context_t; // 関数Hookには関数ごとにこの構造体を用意する. /* * BOOL IsDebuggerPresent(VOID); */ // IsDebuggerPresent終了時に呼び出される 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); } // IsDebuggerPresent開始時に呼び出される 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); 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; // Hookしたい関数を登録 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); } }
プラグインをロードするとinit_plugin
が呼び出される.geteip_init
ではVMI_register_callback
関数で監視対象プロセス作成時にgeteip_loadmainmodule_callback
を呼び出すよう登録している.
実行
作成したプラグインはコンパイルしてから起動した仮想マシンにロードする.
コンパイル
$ pwd /home/<user>/DECAF/decaf/plugins/geteip $ ./configure --decaf-path=/home/<user>/DECAF/decaf $ make
geteip.so
が作成されていれば成功.
ロード
(qemu)
のプロンプトが出ている状態で
(qemu) load_plugin /home/<user>/DECAF/decaf/plugins/geteip/geteip.so (qemu) geteip blue.exe
のように使う.今回であればロード時にHello, World!
のメッセージがでるはず.
注意点
Anti-Qemu trick回避
IsDebuggerPresent Hook
IsDebuggerPresent
はデバッグ
* されている : 0以外
* されていない: 0
を返す関数.
今回だと,デバッグしていることを隠したいからIsDebuggerPresent
終了時に戻り値を0に書き換えてやるとblue.exe
はデバッグされていないと勘違いさせることができる.
geteip
のプラグインは関数Hookまで書いてあるから,戻り値を書き換えるだけで良い.
関数終了時の処理を少しだけいじる.戻り値はqemuのEAX
に入っている.
// IsDebuggerPresent終了時に呼び出される static void IsDebuggerPresent_ret(void *param) { IsDebuggerPresent_hook_context_t *ctx = (IsDebuggerPresent_hook_context_t *)param; hookapi_remove_hook(ctx->hook_handle); cpu_single_env->regs[R_EAX] = 0; // 追加 DECAF_printf("EIP = %08x, EAX = %d\n", cpu_single_env->eip, cpu_single_env->regs[R_EAX]); free(ctx); }
これでIsDebuggerPresent
の戻り値は常に0
となる.
ここからようやく課題スタート.
課題プログラム
一つ一つのLevelが回避できることを確認していきたい人は,これからは下に書いたコードをgeteip.c
に書き加えていけば各Levelを回避していけるはず.
未検証なので,回避できた・ここが足りなかったなどを教えてもらえると嬉しい.
前回のエントリのコマンドを一通りやっていれば/home/<user>DECAF/VMs
にblue
というディレクトリがあるはず.
やっていない人は
$ git clone https://github.com/ntddk/blue.git
でダウンロードする.
blue/Release/blue.exe
がプログラム本体で,blue/blue/blue.cpp
がソースコード.
構造は極めて簡単で,
if (IsDebuggerPresent() == FALSE && Blue() == 0 && Charlie() == 0 && Delta() == 0 && Echo() == 0) Flag();
の通り,各関数の戻り値を0
になるようにする.一つでも回避できないと即停止.
Level1: Blue
static inline int Blue() { printf("\n[Level 1] Blue ..."); Sleep(360000); DWORD time1 = GetTickCount(); Sleep(500); if ((GetTickCount() - time1) < 450) Detected(); else return 0; }
仕組みはまずSleep
で6分潜伏したあと2回GetTickCount
を呼び出して,Sleepがちゃんと実行されているかを確認しているような感じ.
毎回6分も待つのは辛いので真面目に回避することにする.
やるべきはSleep
の引数を1
にして,2回のGetTickCount
の戻り値の差を450以上にすること.
これらは以下のコードで実現できる.
// 関数Hook用の構造体を定義 typedef struct { uint32_t call_stack[2]; // return address and parameters ([0]: ret addr, [1]: time) DECAF_Handle hook_handle; } Sleep_hook_context_t; typedef struct { uint32_t call_stack[1]; // return address only -> VOID DECAF_Handle hook_handle; } GetTickCount_hook_context_t; // 構造体定義終わり /* * BOOL Sleep(DWORD dwMilliseconds); */ static void Sleep_ret(void *param) { // DECAF_printf("Sleep exit\n"); Sleep_hook_context_t *ctx = (Sleep_hook_context_t *)param; hookapi_remove_hook(ctx->hook_handle); free(ctx); } static void Sleep_call(void *opaque) { // DECAF_printf("Sleep entry\n"); Sleep_hook_context_t *ctx = (Sleep_hook_context_t*)malloc(sizeof(Sleep_hook_context_t)); if(!ctx) return; // bypass Sleep // Sleepの引数を ctx->call_stackに読み込み(call_stack[0]: ret addr, call_stack[1]: dwMilliseconds) DECAF_read_mem(NULL, cpu_single_env->regs[R_ESP], 2*4, ctx->call_stack); // 引数の書き換え.1ミリ秒だけ待機するようにした. ctx->call_stack[1] = 1; // call_stackをメモリに書き込む.これで変更が反映される. DECAF_write_mem(NULL, cpu_single_env->regs[R_ESP], 2*4, ctx->call_stack); ctx->hook_handle = hookapi_hook_return(ctx->call_stack[0], Sleep_ret, ctx, sizeof(*ctx)); } /* Sleep end */ /* * DWORD GetTickCount(VOID) */ static void GetTickCount_ret(void *param) { // GetTickCountは二回呼び出される.最初の呼び出しの時だけ戻り値を変更したい. // 静的変数 flag を使うことで実装した. static int flag = 0; // DECAF_printf("GetTickCount exit\n"); GetTickCount_hook_context_t *ctx = (GetTickCount_hook_context_t *)param; hookapi_remove_hook(ctx->hook_handle); // 最初の呼び出しでは戻り値は0,二回目は正しい値が返る. // 呼び出し間の差が450以上になれば良いので,最初(450), 2回目(900)とかやって遊ぶのも面白い.オーバフローには注意 if (!flag) { // ture is only first call cpu_single_env->regs[R_EAX] = 0; // return 0 flag = 1 } else { flag = 0; } // DECAF_printf("EIP = %08x, EAX = %d\n", cpu_single_env->eip, cpu_single_env->regs[R_EAX]); free(ctx); } static void GetTickCount_call(void *opaque) { // DECAF_printf("GetTickCount entry\n"); GetTickCount_hook_context_t *ctx = (GetTickCount_hook_context_t*)malloc(sizeof(GetTickCount_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], GetTickCount_ret, ctx, sizeof(*ctx)); } /* GetTickCount end */ // 監視対象の関数を登録する.'[+]'の行を付け加えるだけ. static void red_loadmainmodule_callback(VMI_Callback_Params* params) { [+] sleep_handle = hookapi_hook_function_byname("kernel32.dll", "Sleep", 1, target_cr3, Sleep_call, NULL, 0); [+] gettickcount_handle = hookapi_hook_function_byname("kernel32.dll", "GetTickCount", 1, target_cr3, GetTickCount_call, NULL, 0); }
ctx->callstackには関数呼び出し時は[ret_addr, arg0, arg1, ...]となっていて,関数終了時にはret_addrなどは消える.
編集が終わったら,
$ pwd /home/<user>/DECAF/decaf/plugins/geteip $ make
(qemu) unload_plugins # 既にプラグインを挿入している場合 (qemu) load_plugin /home/<user>/DECAF/decaf/plugins/geteip/geteip.so (qemu) geteip blue.exe
ここまでやったあと,Windows上でblue.exe
を動かしてみる.
Level2で検知されたらOK.
Level2: Charlie
コードは以下の通り.
static inline int Charlie() { printf("\n[Level 2] Charlie ..."); SYSTEM_INFO siSysInfo; GetSystemInfo(&siSysInfo); if (siSysInfo.dwNumberOfProcessors < 2) Detected(); else return 0; }
GetSystemInfo
関数は引数の_SYSTEM_INFO
構造体に各種値を入れる.構造体は9の変数を持っている.
typedef struct _SYSTEM_INFO { // sinf call_stack[0]: union { DWORD dwOemId; struct { WORD wProcessorArchitecture; WORD wReserved; }; }; call_stack[1]: DWORD dwPageSize; call_stack[2]: LPVOID lpMinimumApplicationAddress; call_stack[3]: LPVOID lpMaximumApplicationAddress; call_stack[4]: DWORD dwActiveProcessorMask; call_stack[5]: DWORD dwNumberOfProcessors; // プロセッサ数 call_stack[6]: DWORD dwProcessorType; call_stack[7]: DWORD dwAllocationGranularity; call_stack[8]_low: WORD wProcessorLevel; call_stack[8]_high: WORD wProcessorRevision; } SYSTEM_INFO;
_SYSTEM_INFO
構造体は6番目の要素にdwNumberOfProcessors
というプロセッサ数を格納する変数があり,これが1
だと仮想環境と判断し,停止する.
回避方法はシンプルで,こいつを2
以上に書き換えてやれば良い.
実装は以下の通り.使う関数などはLevel1と同じ.
static DECAF_Handle getsysteminfo_handle = DECAF_NULL_HANDLE; typedef struct { uint32_t call_stack[10]; // return address and parameters DECAF_Handle hook_handle; } GetSystemInfo_hook_context_t; /* * VOID GetSystemInfo(LPSYSTEM_INFO lpSystemInfo); */ static void GetSystemInfo_ret(void *param) { // DECAF_printf("GetSystemInfo exit\n"); GetSystemInfo_hook_context_t *ctx = (GetSystemInfo_hook_context_t *)param; hookapi_remove_hook(ctx->hook_handle); // 構造体を読みだす. DECAF_read_mem(NULL, cpu_single_env->regs[R_ESP], 10*4, ctx->call_stack); // 変数の書き換え ctx->call_stack[5] = 0x2; // SYSTEM_INFO.dwNumberOfProcessors is 0x2 // 反映 DECAF_write_mem(NULL, cpu_single_env->regs[R_ESP], 10*4, ctx->call_stack); // write back to memory // DECAF_printf("EIP = %08x, EAX = %d\n", cpu_single_env->eip, cpu_single_env->regs[R_EAX]); } static void GetSystemInfo_call(void *opaque) { // DECAF_printf("GetSystemInfo entry\n"); GetSystemInfo_hook_context_t *ctx = (GetSystemInfo_hook_context_t*)malloc(sizeof(GetSystemInfo_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], GetSystemInfo_ret, ctx, sizeof(*ctx)); } /* GetSystemInfo end */ static void red_loadmainmodule_callback(VMI_Callback_Params* params) { [+] getsysteminfo_handle = hookapi_hook_function_byname("kernel32.dll", "GetSystemInfo", 1, target_cr3, GetSystemInfo_call, NULL, 0); }
編集が終わったら,コンパイルする.
(qemu) unload_plugins # 既にプラグインを挿入している場合 (qemu) load_plugin /home/<user>/DECAF/decaf/plugins/geteip/geteip.so (qemu) geteip blue.exe
Level2が回避できたことを確認したらLevel3へ.
Level3: Delta
さて,ここまでは引数や戻り値をいじってやればよかったので,簡単だった.
Level3からはWinodwsについての知識が必要になってくるので複雑になってくる.
問題のコードは以下の通り.
static inline int Delta() { printf("\n[Level 3] Delta ..."); unsigned long NumberOfProcessors = 0; __asm { mov eax, fs:[0x30] mov eax, [eax + 0x64] mov NumberOfProcessors, eax } if (NumberOfProcessors & 0x1) Detected(); else return 0; }
やっていることはLevel2と同じくNumberOfProcessors
を確認している.
実はIsDebuggerPresent
もGetSystemInfo
もPEB(Process Environment Block)構造体というものの要素を参照する関数である.
だから問題としては関数がアセンブラになったと考えるとわかりやすいだろうか.
PEB構造体は各プロセスごとに作成される構造体で,ヒープやファイル,ロードしたDLLの内容などが入っている.
PEB構造体はTEB(Thread Environment Block)構造体という,スレッドごとに作成される構造体の先頭から0x30
バイト先にその先頭アドレスが入っている.
絵で書くとこんな感じか
TEB構造体 | 0x0 | | ... | | ... | | ... | PEB構造体 | 0x30(PEB) | -> | 0x0 | | ... | | ... | | 0x64 | -> value of NumberOfProcessors
決してプラグインに上のアセンブラを混ぜ返してやればいいなんて考えないように.エミュレータ上のレジスタとプラグインに書いたレジスタは別物だと思う.やる人はあまりいないと思うが,やって時間を浪費したアホがここにいるので一応書いておく.
このトリックの対処としてはPEB構造体の値そのものを書き換えてやればよい.実装は以下の通り.
// bypass level3, 引数はデバッグ用 static void level3(int line_num) { uint32_t NumberOfProcessors = 0; uint32_t base = 0, peb_addr = 0, peb = 0; // TEB構造体のアドレスを取得.FSレジスタに格納されている base = cpu_single_env->segs[R_FS].base; // PEB構造体の入っているアドレスを計算 peb_addr = base + 0x30; // PEB構造体のアドレスを読み込む DECAF_read_mem(NULL, peb_addr, 4, &peb); // NumberOfProcessorsを読み込む DECAF_read_mem(NULL, peb+0x64, 4, &NumberOfProcessors); //DECAF_printf("FS(TEB): 0x%08x, peb_addr: 0x%08x, peb: 0x%08x\n", base, peb_addr, peb); // 変数を変更して反映 NumberOfProcessors = 0x2; DECAF_write_mem(NULL, peb+0x64, 4, &NumberOfProcessors); // write back to memory } // level3 end static void red_loadmainmodule_callback(VMI_Callback_Params* params) { [+] level3(__LINE__); // replace NumberOfProcessors }
コンパイルして回避できたことを確認したら終了.
PEBやらTEB構造体を初めて知ったが,奥が深い.
PEB構造体は探せば中身が出てくるが,実際はUndocumentedらしい.
Level4: Echo
最後の課題.
多分今できているプラグインを読み込ませて何回かblue.exe
を実行するとLevel4が回避できる時と回避できない時があるはず.もしちゃんと検知してくれない時は何回か実行すると良い.
問題のコードは以下の通り.
static inline int Echo() { printf("\n[Level 4] Echo ..."); __try{__asm{cmpxchg8b fs:[0x1000]}} __except (1){Detected();} return 0; }
cmpxchg8b fs:[0x1000]
は
if ([EDX:EAX] == fs:[0x1000]) fs:[0x1000] = [ECX:EBX]; else [EDX:EAX] = fs:[0x1000]; // [EDX:EAX]はEDXが上位32ビットでEAXが下位32ビットとする64ビット値
といった動作をする.この命令自体F00F
バグと呼ばれるバグの原因となった厄介な機械語らしい.
これがどうしてQemu検知できるかというと,read-onlyのページに書き込みした場合,本物のCPUならばPage Faultが起こる.Qemuの場合はPage Faultが起こらないらしい.どうやらQemuの実装上の欠陥をついた方法のようだ.ソースは
https://www.symantec.com/avcenter/reference/Virtual_Machine_Threats.pdf
正直未だにこの検知の仕組みがよくわかっていない.整理できていない思考を書いておく.面倒な人は読み飛ばし推奨.真面目に読んだ人は意見が欲しい.
よくわからん!
- ソースの文章によると,メモリに書き込んでみて,Page Faultが起きなければ良いということなのだろう.しかし,Qemuだと例外が起こらないのだから,try文で検知できるのだろうか・・・?
[EDX:EAX]
は特に書き換えられていない.つまり[EDX:EAX]
はfs:[0x1000]
と関係がないから,[EDX:EAX] = fs:[0x1000]
の代入が起こるはず.これだとメモリがread-onlyだとかは関係がないんじゃなかろうか.- 加えて,このLevelは検知される時と検知されない時の原因はPEB構造体が
fs:[0x1000]
に配置されるか否かであることがわかった.
条件としては
配置される : 検知されない
配置されない: 検知される
Olly DebugのQ&Aを見てみると,WindowsXPのSP1以前はfs:[0x1000]
にPEB構造体が配置されることが約束されていたようだ.これがなにか関係があるのかとも思ったが,Qemu検知とは全く関係がない・・・はず.
Level4復帰
散々混乱した末に以下の結論に至った.
よくわからんが,PEB構造体は書き込み可である.fs:[0x1000]
にPEB構造体が配置されていない時はread-onlyなのだろう(未検証・予想・適当).
だから,fs:[0x1000]
に格納されているアドレスを書き込み可能なアドレスにしてしまえばいいと考えた.
Windowsのプログラムで書き込み可能フラグが立っているアドレスの調べ方がわからなかったので,手っ取り早くスタックの先頭アドレス(ESP)に書き換えることにした.これでうまく行ってしまったので,これが答えの一つなのだろう.他にもっと冴えたやり方あるのかな.
cmpxchg8b
が実行される時だけを検知して処理を行わないとプログラムがクラッシュする可能性がある.また,実行後はちゃんと元の値に戻さなければならない.
このあたりの検知は機械語を一つ実行するごとにHookしてcmpxchg8b
かどうかを判定した.これにはDECAF_INSN_BEGIN_CB
を使うことで実装することができる.
当然ながらこのやり方はとてつもなく重い.間違ってもこのHook中の処理にprintf
とか挟んではいけない.やらかした奴が言うんだから間違いない.
説明が長くなったが,実装は以下の通り.
// level4 static uint32_t save_peb = 0; static uint32_t save_val[2] = {0, 0}; static uint32_t save_base = 0; static void red_insn_begin_callback(DECAF_Callback_Params* params) { // detect cmpxchg8b and replace fs:[0x1000] uint32_t cur_insn = 0, cmpxchg8b = 0x00c70f64, // opcode of 'cmpxchg8b: 0x640fc7 ->(fix endian) 0x00c70f64 base, peb_addr, tmp_addr; // 3Byte読み込んで,比較 DECAF_read_mem(NULL, cpu_single_env->eip, 3, &cur_insn); if (cur_insn == cmpxchg8b) { // catch cmpxchg8b base = cpu_single_env->segs[R_FS].base; DECAF_read_mem(NULL, base+0x30, 4, &save_peb); // get peb if (base > save_peb) { // peb is rear base DECAF_read_mem(NULL, cpu_single_env->regs[R_ESP], 8, save_val); /* DECAF_printf("FS(TEB): 0x%08x, peb: 0x%08x\n", base, save_peb); */ /* DECAF_printf("edx:eax %08x:%08x, ecx:ebx %08x:%08x\n", cpu_single_env->regs[R_EDX], cpu_single_env->regs[R_EAX], cpu_single_env->regs[R_ECX], cpu_single_env->regs[R_EBX]); */ save_base = base; cpu_single_env->segs[R_FS].base = cpu_single_env->regs[R_ESP]-0x1000; // fs = ESP - 0x1000 } } } static void red_insn_end_callback(DECAF_Callback_Params* params) { return; } // level4 end static void red_loadmainmodule_callback(VMI_Callback_Params* params) { [+] insnbegin_handle = DECAF_register_callback(DECAF_INSN_BEGIN_CB, &red_insn_begin_callback, NULL); // level4 [+] insnend_handle = DECAF_register_callback(DECAF_INSN_END_CB, &red_insn_end_callback, NULL); // level4 } static void red_cleanup(void) { [+] if (insnbegin_handle != DECAF_NULL_HANDLE) [+] { [+] DECAF_unregister_callback(DECAF_INSN_BEGIN_CB, insnbegin_handle); [+] insnbegin_handle = DECAF_NULL_HANDLE; [+] } [+] if (insnend_handle != DECAF_NULL_HANDLE) [+] { [+] DECAF_unregister_callback(DECAF_INSN_END_CB, insnend_handle); [+] insnend_handle = DECAF_NULL_HANDLE; [+] } }
上で偉そうなことを行ったが,実はこのコード,アドレスを書き換えたあと戻していなかったりする.戻さなくてもクラッシュしないプログラムだったので,面倒くさいからそのままにしておいた.良い子のみんなはちゃんと戻そう.
これで全てのトリックを回避できるはず.
回避するとフラグの文字列が出力される.洋書のタイトルのようだ.
最後に
説明したいことが多くて,必要以上に長くなってしまった.ここまで読み進めた人はお疲れ様でした.
全てのヒントはスライドの中に紛れ込んでいたりする.探してみるのもおもしろい.
スライドの情報のみだと説明が足りないので,DECAFの公式サイトやソースコードを読むのが一番役に立った.大規模なプログラムのソースを読み散らかすのは楽しい経験だったと思う.
マルウェアの仮想化回避はLevel2とLevel3のように同じやり方でも少し変えるだけで全く別のものになってしまう.いたちごっこな雰囲気を強く感じる演習だった.これはセキュリティベンダが苦労するわけだと思う.
セキュリティキャンプ全国ではこれを数時間でやらせることを考えると結構鬼畜。出たかったな・・・。
最後に、黒米さんと愛甲さん,楽しい時間をありがとうございました.
裏ワザ
- 仮想マシン起動時に
-smp 2,maxcpus=2
をつけて割り当てるプロセッサを2つにするとLevel2,3は無条件でクリアできる.Level4は不安定だから場合によってはLevel1を回避しただけでフラグまでたどり着ける. - Level4とか面倒くさいから,該当するバイナリを
NOP
に書き換えるのもあり?(未検証)
なんだこれ
QemuのありえないHz.CPUが溶けないなら欲しい性能.
参考
Win32 Thread Information Block - Wikipedia, the free encyclopedia
https://www.symantec.com/avcenter/reference/Virtual_Machine_Threats.pdf