picoCTF 2018: quackme

問題

問題文

Can you deal with the Duck Web? Get us the flag from this program. You can also find the program in /problems/quackme_1_374d85dc071ada50a08b36597288bcfd.

Hints:

Objdump or something similar is probably a good place to start.

問題概要

x86 の ELF ファイルが与えられる.

解答例

指針

  • rev は気合

解説

まずは objdump を使って逆アセンブルをしてみる.

main の逆アセンブルコードを読むと, do_magic という関数が呼ばれているのが分かる.

$ objdump -d -M intel main
...
(snip)
...
08048715 <main>:
8048715:       8d 4c 24 04             lea    ecx,[esp+0x4]
8048719:       83 e4 f0                and    esp,0xfffffff0
804871c:       ff 71 fc                push   DWORD PTR [ecx-0x4]
804871f:       55                      push   ebp
8048720:       89 e5                   mov    ebp,esp
8048722:       51                      push   ecx
8048723:       83 ec 04                sub    esp,0x4
8048726:       a1 44 a0 04 08          mov    eax,ds:0x804a044
804872b:       6a 00                   push   0x0
804872d:       6a 02                   push   0x2
804872f:       6a 00                   push   0x0
8048731:       50                      push   eax
8048732:       e8 79 fd ff ff          call   80484b0 <setvbuf@plt>
8048737:       83 c4 10                add    esp,0x10
804873a:       83 ec 0c                sub    esp,0xc
804873d:       68 f0 87 04 08          push   0x80487f0
8048742:       e8 29 fd ff ff          call   8048470 <puts@plt>
8048747:       83 c4 10                add    esp,0x10
804874a:       e8 f3 fe ff ff          call   8048642 <do_magic>
804874f:       83 ec 0c                sub    esp,0xc
8048752:       68 bb 88 04 08          push   0x80488bb
8048757:       e8 14 fd ff ff          call   8048470 <puts@plt>
804875c:       83 c4 10                add    esp,0x10
804875f:       b8 00 00 00 00          mov    eax,0x0
8048764:       8b 4d fc                mov    ecx,DWORD PTR [ebp-0x4]
8048767:       c9                      leave
8048768:       8d 61 fc                lea    esp,[ecx-0x4]
804876b:       c3                      ret
804876c:       66 90                   xchg   ax,ax
804876e:       66 90                   xchg   ax,ax
...
(snip)
...

同様に, do_magic の逆アセンブルコードを読もうとするが結構長くて読むのがとても大変.

IDA Pro というリバースエンジニアリングフレームワークを使うと, かなり解析が楽になるので今回はそれを使う.

IDA で do_magic を逆アセンブルさせ Graph View で表示させた結果が下の画像である.

f:id:kira000:20181225025356p:plain

以下の処理を先頭から読んでいく.

まず [ebp+var_18][ebp+var_10] を比べて分岐している.

[ebp+var_18], [ebp+var_10] がどのような値かどうかを調べていく.

do_magic の先頭から読んでいくと,

var_18= dword ptr -18h
var_10= dword ptr -10h

とあり, var_18 および var_10 の初期値が分かる.

また,

call    _strlen
add     esp, 10h
mov     [ebp+var_10], eax

とあるので, [ebp+var_10] の値が入力された文字列の長さであると分かる. (関数の戻り値は EAX レジスタに入る)

[ebp+var_18] の値は一番分かりやすい. 直前に以下の命令があるので, 0 だと分かる.

mov     [ebp+var_18], 0

また後半に以下のような命令がありインクリメントされているので, ループ用の変数として機能していると推測できる.

loc_8048707:
add     [ebp+var_18], 1

次に loc_80486BD が何をしているのかを解析していく.

アセンブリ命令の右側に命令の意味をコメントとして記載した.

loc_80486BD:
mov     eax, [ebp+var_18]    ; eax = 0
add     eax, 8048858h        ; eax += 0x8048858
movzx   ecx, byte ptr [eax]  ; ecx = [eax] が指す値から 1 Bytes 分の値
mov     edx, [ebp+var_18]    ; edx = 0
mov     eax, [ebp+var_14]    ; eax = (入力文字列の先頭アドレス)
add     eax, edx             ; eax += edx
movzx   eax, byte ptr [eax]  ; eax = [eax] が指す値から 1 Bytes 分の値
xor     eax, ecx             ; eax = eax xor ecx
mov     [ebp+var_1D], al     ; [ebp+var_1D] = al(EAXの下位 1 Bytes)
mov     edx, greetingMessage ; edx = greetingMessage ('You have now entered the Duck Web, and you')
mov     eax, [ebp+var_18]    ; eax = 0
add     eax, edx             ; eax += [greetingMessage を指すポインタ]
movzx   eax, byte ptr [eax]  ; eax = [eax] が指す値から 1 Bytes 分の値
cmp     al, [ebp+var_1D]     ; eaxの下位1Bytes と [ebp+var_1D] を比較
jnz     short loc_80486EF

[ebp+var_14] の値は do_magic の序盤に以下の命令があることから, 入力された文字列の先頭を指すポインタであると分かる.

call    read_input
mov     [ebp+var_14], eax

0x8048858 の指す値から 0x0 が現れるまでの値は以下の通りである. (何も考えずボタンをポチポチすると出てくる)

29 06 16 4F 2B 35 30 1E 51 1B 5B 14 4B 08 5D 2B 53 10 54 51 43 4D 5C 54 5D

var_18 がインクリメントされることを考慮しつつ上記のアセンブリ命令を C言語で表現することを試みる.

int i = 0;
int p[] = 0x29, 0x06, 0x16, 0x4F, 0x2B, 0x35, 0x30, 0x1E, 0x51, 0x1B,
          0x5B, 0x14, 0x4B, 0x08, 0x5D, 0x2B, 0x53, 0x10, 0x54, 0x51,
          0x43, 0x4D, 0x5C, 0x54, 0x5D;

char greetingMessage[] = "You have now entered the Duck Web, and you";
char input[];

input(input);
int cnt = 0;
for (i = 0; i < 0x19; i++) {
    int a;
    a = input[i]^p[i];
    if (a == greetingMessage[i]) {
        cnt++;
    }
}
if (cnt == 0x19) {
    puts("Your are winner\n");
}

input[i] ^ p[i] = greetingMessage[i] より

input[i] ^ p[i] ^ p[i] = greetingMessage[i] ^ p[i] <=> input[i] = greetingMessage[i] ^ p[i]

よって, greetingMessage と 0x8048858 が指す値の XOR を取ることで, input の値がわかる.

#!/usr/bin/env python3
greetingMessage = "You have now entered the Duck Web, and you"
p = [0x29, 0x06, 0x16, 0x4F, 0x2B, 0x35, 0x30, 0x1E, 0x51, 0x1B,
     0x5B, 0x14, 0x4B, 0x08, 0x5D, 0x2B, 0x53, 0x10, 0x54, 0x51,
     0x43, 0x4D, 0x5C, 0x54, 0x5D]

flag = ""

for x, y in zip(greetingMessage, p):
    flag += chr(ord(x)^y)

print (flag)
  • 実行結果
$ python solve.py
picoCTF{qu4ckm3_6b15c941}

flag: picoCTF{qu4ckm3_6b15c941}