超超beginnerな私がLeakageを解いた過程をここに残します.
実行してみると
usage: ./leakage flag
と出力.
とりあえず適当な文字を引数に再度実行してみると
$ ./leakage a
wrong
$ ./leakage aaa
wrong
$ ./leakage 1234
wrong
次に, radare2
でmain
関数を逆アセンブルして呼び出してみました.
[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.correct
かstr.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
0x03
はASCIIコード
では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
0x61
はASCIIコード
ではa
不一致である為wrong
と返されます. そこで, al
の値を0x6c
(l
)に変更します.
このようにal
の値をbyte [rbp - 5]
に揃えていく操作を最後まで続けます.
すると, 最終的にflag
ctf4b{le4k1ng_th3_f1ag_0ne_by_0ne}
のゲットです!お疲れ様でした!
[追伸]
他の方のwrite upを読んでると, angr
github.com
という自動解析ツールでさらっと(?)解いてますね.
次回までに習得しておこうと思います.