Leakage SECOON Beginners CTF 2019 write up

超超beginnerな私がLeakageを解いた過程をここに残します.



実行してみると

usage: ./leakage flag


と出力. とりあえず適当な文字を引数に再度実行してみると

$ ./leakage a
wrong
$ ./leakage aaa
wrong
$ ./leakage 1234
wrong


次に, radare2main関数を逆アセンブルして呼び出してみました.

[0x00400660]> pdf
            ;-- main:
/ (fcn) main 111
|   main ();
|           ; var int local_10h @ rbp-0x10
|           ; var int local_4h @ rbp-0x4
|              ; DATA XREF from 0x0040051d (entry0)
|           0x00400660      55             push rbp
|           0x00400661      4889e5         mov rbp, rsp
|           0x00400664      4883ec10       sub rsp, 0x10
|           0x00400668      897dfc         mov dword [local_4h], edi
|           0x0040066b      488975f0       mov qword [local_10h], rsi
|           0x0040066f      837dfc01       cmp dword [local_4h], 1     ; rflags ; [0x1:4]=-1
|       ,=< 0x00400673      7f22           jg 0x400697
|       |   0x00400675      488b45f0       mov rax, qword [local_10h]
|       |   0x00400679      488b00         mov rax, qword [rax]
|       |   0x0040067c      4889c6         mov rsi, rax
|       |   0x0040067f      488d3dfd0500.  lea rdi, qword str.usage:__s_flag ; 0x400c83 ; "usage: %s flag\n" ; const char * format
|       |   0x00400686      b800000000     mov eax, 0
|       |   0x0040068b      e860feffff     call sym.imp.printf         ; int printf(const char *format)
|       |   0x00400690      b801000000     mov eax, 1
|      ,==< 0x00400695      eb36           jmp 0x4006cd
|      ||      ; JMP XREF from 0x00400673 (main)
|      |`-> 0x00400697      488b45f0       mov rax, qword [local_10h]
|      |    0x0040069b      4883c008       add rax, 8
|      |    0x0040069f      488b00         mov rax, qword [rax]
|      |    0x004006a2      4889c7         mov rdi, rax
|      |    0x004006a5      e83dffffff     call sym.is_correct
|      |    0x004006aa      85c0           test eax, eax
|      |,=< 0x004006ac      740e           je 0x4006bc
|      ||   0x004006ae      488d3dde0500.  lea rdi, qword str.correct  ; 0x400c93 ; "correct" ; const char * s
|      ||   0x004006b5      e806feffff     call sym.imp.puts           ; int puts(const char *s)
|     ,===< 0x004006ba      eb0c           jmp 0x4006c8
|     |||      ; JMP XREF from 0x004006ac (main)
|     ||`-> 0x004006bc      488d3dd80500.  lea rdi, qword str.wrong    ; 0x400c9b ; "wrong" ; const char * s
|     ||    0x004006c3      e8f8fdffff     call sym.imp.puts           ; int puts(const char *s)
|     ||       ; JMP XREF from 0x004006ba (main)
|     `---> 0x004006c8      b800000000     mov eax, 0
|      |       ; JMP XREF from 0x00400695 (main)
|      `--> 0x004006cd      c9             leave
\           0x004006ce      c3             ret

sym.is_correctを呼び出して, その結果次第でstr.correctstr.wrongに分かれています.

次にsym.is_correct の逆アセンブルの結果をみました.

[0x004005e7]> pdf
/ (fcn) sym.is_correct 121
|   sym.is_correct ();
|           ; var int local_18h @ rbp-0x18
|           ; var int local_5h @ rbp-0x5
|           ; var int local_4h @ rbp-0x4
|              ; CALL XREF from 0x004006a5 (main)
|           0x004005e7      55             push rbp
|           0x004005e8      4889e5         mov rbp, rsp
|           0x004005eb      4883ec20       sub rsp, 0x20
|           0x004005ef      48897de8       mov qword [local_18h], rdi
|           0x004005f3      488b45e8       mov rax, qword [local_18h]
|           0x004005f7      4889c7         mov rdi, rax                ; const char * s
|           0x004005fa      e8d1feffff     call sym.imp.strlen         ; size_t strlen(const char *s)
|           0x004005ff      4883f822       cmp rax, 0x22               ; '"' ; 34
|       ,=< 0x00400603      7407           je 0x40060c
|       |   0x00400605      b800000000     mov eax, 0
|      ,==< 0x0040060a      eb52           jmp 0x40065e
|      ||      ; JMP XREF from 0x00400603 (sym.is_correct)
|      |`-> 0x0040060c      c745fc000000.  mov dword [local_4h], 0
|      |,=< 0x00400613      eb3e           jmp 0x400653
|      ||      ; JMP XREF from 0x00400657 (sym.is_correct)
|     .---> 0x00400615      8b45fc         mov eax, dword [local_4h]
(省略)

sym.imp.strlenコマンドライン引数の大きさを計算し, それと0x22(34)を比較してます.


デバッグモードで再度引数をctf4b{aaaaaaaaaaaaaaaaaaaaaaaaaaa}(34文字)にして実行してみました.

            ;-- rip:
            0x004005e7      55             push rbp
            0x004005e8      4889e5         mov rbp, rsp
            0x004005eb      4883ec20       sub rsp, 0x20
            0x004005ef      48897de8       mov qword [rbp - 0x18], rdi
            0x004005f3      488b45e8       mov rax, qword [rbp - 0x18]
            0x004005f7      4889c7         mov rdi, rax
            0x004005fa      e8d1feffff     call sym.imp.strlen         ;[1]
            0x004005ff      4883f822       cmp rax, 0x22               ; '"' ; 34
        ,=< 0x00400603      7407           je 0x40060c                 ;[2]
        |   0x00400605      b800000000     mov eax, 0
       ,==< 0x0040060a      eb52           jmp 0x40065e                ;[3]
       |`-> 0x0040060c      c745fc000000.  mov dword [rbp - 4], 0
       |,=< 0x00400613      eb3e           jmp 0x400653                ;[4]
      .---> 0x00400615      8b45fc         mov eax, dword [rbp - 4]
      :||   0x00400618      4863d0         movsxd rdx, eax
      :||   0x0040061b      488d053e0600.  lea rax, qword obj.enc_flag    ; 0x400c60
      :||   0x00400622      0fb60402       movzx eax, byte [rdx + rax]
      :||   0x00400626      0fb6c0         movzx eax, al
      :||   0x00400629      89c7           mov edi, eax
      :||   0x0040062b      e8a0000000     call sym.convert            ;[5]
      :||   0x00400630      8845fb         mov byte [rbp - 5], al
      :||   0x00400633      8b45fc         mov eax, dword [rbp - 4]
      :||   0x00400636      4863d0         movsxd rdx, eax
      :||   0x00400639      488b45e8       mov rax, qword [rbp - 0x18]
      :||   0x0040063d      4801d0         add rax, rdx                ; '('
      :||   0x00400640      0fb600         movzx eax, byte [rax]
      :||   0x00400643      3845fb         cmp byte [rbp - 5], al      ; [0x2:1]=255 ; 2
     ,====< 0x00400646      7407           je 0x40064f                 ;[6]
     |:||   0x00400648      b800000000     mov eax, 0
    ,=====< 0x0040064d      eb0f           jmp 0x40065e                ;[3]
    |`----> 0x0040064f      8345fc01       add dword [rbp - 4], 1
    | :|`-> 0x00400653      837dfc21       cmp dword [rbp - 4], 0x21    ; [0x21:4]=-1 ; '!' ; 33
    | `===< 0x00400657      7ebc           jle 0x400615                ;[7]
    |  |    0x00400659      b801000000     mov eax, 1
    `--`--> 0x0040065e      c9             leave

すると, sym.convertの後

cmp byte [rbp - 5], al      ; [0x2:1]=255 ; 2


ここの結果次第で, mainに戻るか戻らないか分かられていたので, ここのレジスタの中身を見ました.

[0x00400643]> px @ rbp - 5
- offset -       0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x7ffe9568e0cb  6300 0000 00f0 e068 95fe 7f00 00aa 0640  c......h.......@
(省略)


下位1byteは'c'ですね.

[0x00400643]> dr al
0x00000063

0x03ASCIIコードではc よって一致しています.
このような比較を1文字ずつ, 計34文字比較します. すると

[0x00400643]> px @ rbp - 5
- offset -       0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x7ffe9568e0cb  6c06 0000 00f0 e068 95fe 7f00 00aa 0640  l......h.......@

下位1byteはlですね.

[0x00400643]> dr al
0x00000061

0x61ASCIIコードではa

不一致である為wrongと返されます. そこで, alの値を0x6c(l)に変更します.

このようにalの値をbyte [rbp - 5]に揃えていく操作を最後まで続けます.


すると, 最終的にflag

ctf4b{le4k1ng_th3_f1ag_0ne_by_0ne}

のゲットです!お疲れ様でした!


[追伸] 他の方のwrite upを読んでると, angr

github.com

という自動解析ツールでさらっと(?)解いてますね.

次回までに習得しておこうと思います.