CTF : バイナリ解析 - 1
参考書籍 : セキュリティコンテキストチャレンジブック CTFで学ぼう! 情報を守るための戦い方
1. 表層解析
・fileコマンドを用いて表層解析を行う
$ file ./share/UsaTest2.EXE ./share/UsaTest2.EXE: PE32 executable (GUI) Intel 80386, for MS Windows
PE32 executable より、実行ファイルの形式が分かり、32bit OS向けPEファイルフォーマットであると分かる
(GUI) より、GUIアプリケーションであることが、
Intel 80386, for MS Windows より対応しているCPUアーキテクチャとOSが分かる。
・stringsで文字列を抽出する
stringsコマンドでバイナリに含まれる文字列を一覧表示できる。
呼び出す関数の一覧や、CTFの場合はフラグのヒントが獲得できる。
文字列が少ない場合、パックされていると分かる。
2, 動的解析
・straceでトレースする
次のプログラムを解析する。
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> int main() { pid_t pid; char buf[16]; int user_input; pid = fork(); if (pid == 0) { puts("Segmentation Fault"); exit(0); } else { puts("Enter the pid of the child process."); fgets(buf, sizeof(buf), stdin); user_input = atoi(buf); if (pid == user_input) { puts("Congratulations! Your FLAG is ctf4b{7h15_15_s1mpl3_ltrace}"); } } return 0; }
$ strace ./main でシステムコールをトレースしながら実行する。
execve("./strace1", ["./strace1"], [/* 70 vars */]) = 0 brk(0) = 0x2263000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=91246, ...}) = 0 mmap(NULL, 91246, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f14a5263000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P \2\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=1857312, ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f14a5262000 mmap(NULL, 3965632, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f14a4c8f000 mprotect(0x7f14a4e4d000, 2097152, PROT_NONE) = 0 mmap(0x7f14a504d000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1be000) = 0x7f14a504d000 mmap(0x7f14a5053000, 17088, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f14a5053000 close(3) = 0 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f14a5260000 arch_prctl(ARCH_SET_FS, 0x7f14a5260740) = 0 mprotect(0x7f14a504d000, 16384, PROT_READ) = 0 mprotect(0x600000, 4096, PROT_READ) = 0 mprotect(0x7f14a527a000, 4096, PROT_READ) = 0 munmap(0x7f14a5263000, 91246) = 0 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f14a5260a10) = 25978
子プロセスを生成するシステムコール、clone()の戻り値が25978と分かるので、25978を入力しフラグを得る read(0, 25978 "25978\n", 1024) = 6 write(1, "Congratulations! Your FLAG is ct"..., 59Congratulations! Your FLAG is ctf4b{7h15_15_s1mpl3_ltrace} ) = 59 exit_group(0) ||< ・ltraceでトレースする 次のプログラムを解析する。 >|cpp| #include <stdio.h> #include <string.h> int main() { char buf[32]; char key[] = "d3m0_pr0gr4m_k3y"; puts("Please input the passphrase."); fgets(buf, sizeof(buf), stdin); strtok(buf, "\n"); if (!strcmp(buf, key)) { puts("Congratulations! Your flag is ctf4b{7h15_15_51mpl3_ltrace}"); } else { puts("Invalid inputs."); } return 0; }
$ ltrace ./main で標準ライブラリ関数のトレースをしながら実行する。
とりあえずtestと入力してみると、次の結果が得られる。
__libc_start_main(0x4006bd, 1, 0x7ffd2a1cba38, 0x400770 <unfinished ...> puts("Please input the passphrase."Please input the passphrase. ) = 29 fgets(test "test\n", 32, 0x7f8691682640) = 0x7ffd2a1cb920 strtok("test\n", "\n") = "test" strcmp("test", "d3m0_pr0gr4m_k3y") = 16 puts("Invalid inputs."Invalid inputs. ) = 16 +++ exited (status 0) +++
strcmpで"d3m0_pr0gr4m_k3y"という文字列と比較しているため、これがパスフレーズと推測できる。