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"という文字列と比較しているため、これがパスフレーズと推測できる。