close


前言

之前在學習家用路由器這本書以及看網上大佬寫相關文章 的時候,總感覺有些關鍵細節一筆帶過,有時候給我造成了很大的困擾,鑑於這個原因,我想到把自己的一些思考以及實際操作經驗寫出來給後來者,希望他們不要再走我走過的彎路。

引爆內存崩潰

首先看源代碼:

#include <stdio.h>#include <sys/stat.h>#include <unistd.h>void do_system(int code,char *cmd){ char buf[255]; //sleep(1); system(cmd);}void main(){ char buf[256]={0}; char ch; int count = 0; unsigned int fileLen = 0; struct stat fileData; FILE *fp; if(0 == stat("passwd",&fileData)) fileLen = fileData.st_size; else return 1; if((fp = fopen("passwd","rb")) == NULL) { printf("Cannot open file passwd!n"); exit(1); } ch=fgetc(fp); while(count <= fileLen) { buf[count++] = ch; ch = fgetc(fp); } buf[--count] = 'x00'; if(!strcmp(buf,"adminpwd")) { do_system(count,"ls -l"); } else { printf("you have an invalid password!n"); } fclose(fp);}

將vuln_system.c 拷貝至對應目錄下:

執行如下命令:

root@ricard-virtual-machine:~/my_file# /root/my_file/buildroot1/buildroot/output/host/bin/mips-linux-gcc vuln_system.c -static -o vuln_systemroot@ricard-virtual-machine:~/my_file# python -c "print 'A'*600">passwdroot@ricard-virtual-machine:~/my_file# qemu-mips vuln_system

而後會出現錯誤:

程序引發了一段故障,使用如下命令重新執行:

root@ricard-virtual-machine:~/my_file# qemu-mips vuln_system `python -c "print 'A'*600"`

這裡直接運行,發生崩潰就退出了;

加-g是等待調試的:

root@ricard-virtual-machine:~/my_file# qemu-mips -g 1234 ./vuln_system `python -c "print 'A'*600"`

執行完這條指令之後,使用IDA進行附加調試:

附加之後,在IDA裡面按F9鍵(書裡面寫的是F5,,這是錯的!)可以看到程序在試圖執行0x41414141的時候崩潰了,如下圖所示:

這是因為0x41414141將原來的返回地址給覆蓋了,程序在返回的時候返回的是0x41414141這個無效地址而不是原來的地址,故會崩潰.

劫持流程

計算偏移

通過閱讀vuln_system.c的源碼可以知道,main函數裡面,在讀取完passwd這個文件之後,將passwd文件裡面的所有數據存入堆棧的局部變量buf裡面,而buf的大小僅為256字節,而passwd文件有600字節大小的數據寫入buf,導致了緩衝區溢出;

通過靜態分析發現,如果要使緩衝區溢出,並控制到堆棧中的返回地址saved_ra,需要覆蓋的數據大小應該達到0x1A0-0x04即0x19c字節;作者這裡運用這個公式的依據是什麼呢?讓我們回顧一下X86架構下的情形:

偏移不就是找buf和ra之間的偏移麼,ra是存儲於棧裡面的(有點類似於x86裡面的ret指令),buf指向棧裡面,只要計算出buf的初始位置和ra之間的偏移,就可以計算出有多少個字節就可以溢出到ra了!

尋找偏移

上圖是主函數裡面的一開始的部分,為了進一步分析出偏移,筆者將相關匯編指令謄寫並注釋如下:

addiu $sp, -0x1D0 //sp <==sp-0x1D0 sw $ra, 0x1D0+var_4($sp) //將ra裡面的值存放於堆棧裡面,其偏移值為0x1D0+var_4 sw $fp, 0x1D0+var_8($sp) //將fp裡面的值存放於堆棧裡面,其偏移為0x1D0+var_8 move $fp, $sp //fp<== sp li $gp, 0x4291A0 //li指令:將一個立即數存放於寄存器裡面 sw $gp, 0x1D0+var_1C0($sp) //將gp裡面的值存放於堆棧裡面,其偏移為0x1D0+var_1C0 addiu $v0, $fp, 0x1D0+var_1A0 //v0用於存放函數函數返回值 li $v0, 0x100 //將立即數0x100傳入v0 move $a2, $v1 //MIPS架構中一般使用a0-a3作為函數的前4個參數 move $a1, $zero //zero寄存器裡面永遠為0 move $a0, $v0 //a0=v0 la $v0, memset //複製memset地址至至v0中 move $t9, $v0 //$t0-$t9供匯編程序使用的臨時變量 bal memset //無條件轉移,並且將轉移指令後面的第二條地址作為返回值存放於Ra裡面 nop

結合源代碼看,可以發現主函數裡面調用的第一個函數為memset函數,貌似源代碼裡面沒有這個函數,怎麼回事,繼續看,發現在調用這個函數之前是調用了3個參數的,分別用到a0,a1,和a2這幾個寄存器,(在MIPS架構裡面,是用a0-a3這幾個寄存器來傳參的),而函數memset的原型為void* memset(void* s,int ch,unsigned n),其主要功能為:在內存空間裡以s為起始的地方,將開始的n個字節設為指定值;可以發現傳給a2的值為v1,而傳給$v1的為0x100(0x100實際上就是十進制256),分析到這裡,大家應該會清晰了吧,這裡在做的事情其實是內存初始化,通過這個函數將內存裡面的256個字節初始化為0,而這裡的內存初始地址是通過指令addiu $v0,$fp,0x1D0+var_1A0來確定的,顯然,0x1D0+var_1A0就是我們要找的buf的起始偏移,到這裡,我們才能確定:需要覆蓋的數據大小應該為0x1D0+var_1A0-0x1D0-var_4即0x19c字節;

驗證

root@ricard-virtual-machine:~/my_file# python -c "print 'A'*0x19c + 'BBBB'+'CCCC'">passwdroot@ricard-virtual-machine:~/my_file# qemu-mips -g 1234 vuln_system

輸入指令之後,程序就會處於等待調試的狀態; 而後利用IDA附加該進程(此過程在前面已經敘述過);

由於這裡使用附加調試的效果不是太好,我這裡使用的運行時調試的方法,讀者可以參考這本書即可;

在主函數結尾的地方下斷點,按F9運行程序,會在0x004006CC這個地址斷下:

雙擊0x004006D0這行;來到返回地址在棧空間0x40800104(也可以利用SP+0x1D0+VAR_4得到)處,如下圖 所示:

查看HEX VIEW-1窗口,發現返回地址已經被覆蓋為0x42424242,如下圖所示,此時緩衝區已經被輸入的數據所覆蓋,並且越界後覆蓋了堆棧上的其他數據;

繼續按F8鍵執行指令jr $ra,程序就會跳往0x42424242出執行,如下圖所示:

小結

在這一節裡面,主要學習的知識點是如何計算偏移達到覆蓋返回地址的目的,這裡總結出一個公式:

偏移=函數返回地址-緩衝區首地址;

(注:在堆棧中,一般函數返回地址處於高地址,緩衝區地址處於低地址),今天就暫時寫到這裡,後面有時間我會帶來更多的分享.


- 結尾 -
精彩推薦
【技術分享】FRIDA-API使用篇:rpc、Process、Module、Memory使用方法及示例
【技術分享】【CTF攻略】第三屆XCTF——鄭州站ZCTF第一名戰隊Writeup
【技術分享】淺析xml之xinclude & xslt
戳「閱讀原文」查看更多內容
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()