BUUCTF Dig the way
文章目錄
- 題目類型
- 查殼
- 拖進ida
- 整體邏輯
- 文件讀取函數
- fseek函數
- ftell函數
- fread函數(補充)
- 分析三個func
- func0
- func1
- func2
- 解決方法分析
- 分析
題目類型
這道題是一道棧溢出的題目,有點意思。
查殼
無殼
拖進ida
int __cdecl main(int argc, const char **argv, const char **envp) {int result; // eax@2int v4; // ebx@6size_t v5; // eax@8int v6; // ebx@11int v7; // [sp+1Ch] [bp-48h]@6int v8; // [sp+30h] [bp-34h]@1signed int v9; // [sp+34h] [bp-30h]@1signed int v10; // [sp+38h] [bp-2Ch]@1signed int v11; // [sp+3Ch] [bp-28h]@1int v12; // [sp+40h] [bp-24h]@1int v13; // [sp+44h] [bp-20h]@1int (__cdecl *v14)(int, int, int); // [sp+48h] [bp-1Ch]@1int (__cdecl *v15)(int, int, int); // [sp+4Ch] [bp-18h]@1int (__cdecl *v16)(int, int, int); // [sp+50h] [bp-14h]@1__int32 v17; // [sp+54h] [bp-10h]@3__int32 v18; // [sp+58h] [bp-Ch]@3FILE *v19; // [sp+5Ch] [bp-8h]@1__main();v14 = func0;v15 = func1;v16 = func2;v8 = 0;v9 = 1;v10 = 2;v11 = 3;v12 = 3;v13 = 4;v19 = fopen("data", "rb");if ( v19 ){fseek(v19, 0, 2);v18 = ftell(v19);fseek(v19, 0, 0);v17 = ftell(v19);if ( v17 ){puts("something wrong");result = 0;}else{for ( i = 0; i < v18; ++i ){v4 = i;*((_BYTE *)&v7 + v4) = fgetc(v19);}v5 = strlen((const char *)&v7);if ( v5 <= v18 ){v18 = v11;i = 0;v17 = v13;while ( i <= 2 ){v6 = i + 1;*(&v8 + v6) = (*(&v14 + i))(&v8, v12, v13);v12 = ++i;v13 = i + 1;}if ( v11 ){result = -1;}else{get_key(v18, v17);system("PAUSE");result = 0;}}else{result = -1;}}}else{result = -1;}return result; }如何獲取到flag?
需要執行get_key函數,如果要執行,那么需要保證v11等于0。那么接下來我們來理理整體邏輯,
整體邏輯
v14 = func0;v15 = func1;v16 = func2;v14,v15,v16三個函數指針,分別指向func0,func1,func2,讀取data文件到v7,v7數組只有20個字節的大小,如果文件內容超過20字節,那么就會依次向后(也就是說把v8,v9,v10等等)進行覆蓋
while ( i <= 2 ){v6 = i + 1;*(&v8 + v6) = (*(&v14 + i))((int)&v8, v12, v13);v12 = ++i;v13 = i + 1;}v8的起始值是0,v6是1,所以這里func0(v14指向的函數)的返回值是賦給v9;func1(v15指向的函數)的的返回值是賦給v10;func2(v16指向的函數)的的返回值是賦給v11(這里是不是找到關鍵點了???func2的返回值給v11,繼續往下觀察)
文件讀取函數
fseek函數
FILE *fp = fopen(model_path, "rb"); fseek(fp, 0, SEEK_END)用 法: int fseek(FILE *stream, long offset, int fromwhere);
描 述: 函數設置文件指針stream的位置。如果執行成功,stream將指向以fromwhere為基準,偏移offset個字節的位置。如果執行失敗(比如offset超過文件自身大小),則不改變stream指向的位置。
返回值: 成功,返回0,否則返回其他值。
參數:
SEEK_SET: 文件開頭
SEEK_CUR: 當前位置
SEEK_END: 文件結尾
其中SEEK_SET,SEEK_CUR和SEEK_END和依次為0,1和2;
例子:
ftell函數
FILE *fp = fopen(model_path, "rb"); fseek(fp, 0, SEEK_END); //fp指針移到文件尾部 int model_len = ftell(fp);用 法: long ftell(FILE *fp);
描 述: 返回當前文件指針位置。這個位置是當前文件指針相對于文件開頭的位移量。
返回值:返回文件指針的位置,若出錯則返回-1L。
參數:文件指針。
fread函數(補充)
fread(buffer,100,1,fp)用 法: size_t fread( void *buffer, size_t size, size_t count, FILE *stream ) ;
描 述: fread()用來從文件流中讀取數據。參數stream為已打開的文件指針,參數buffer指向欲存放讀取進來的數據空間,讀取的字節數以參數size * count來決定。
返回值: 返回實際讀取到的count數目,如果此值比參數count來得小,則代表可能讀到了文件尾了或者有錯誤發生(前者幾率大),這時必須用feof()或ferror()來決定發生什么情況。
參數:
分析三個func
func0
signed int __cdecl func0(int a1, int a2, int a3) {int v3; // ST0C_4@1v3 = *(_DWORD *)(4 * a2 + a1);*(_DWORD *)(a1 + 4 * a2) = *(_DWORD *)(4 * a3 + a1);*(_DWORD *)(a1 + 4 * a3) = v3;return 1; }func0一看就是利用一個temp把兩個東西交換一下。
func1
int __cdecl func1(int a1, int a2, int a3) {int v3; // eax@1v3 = (*(_DWORD *)(4 * a2 + a1) + *(_DWORD *)(4 * a3 + a1)) >> 31;return (v3 ^ (*(_DWORD *)(4 * a2 + a1) + *(_DWORD *)(4 * a3 + a1)))- v3- (((*(_DWORD *)(4 * a3 + a1) >> 31) ^ *(_DWORD *)(4 * a3 + a1))- (*(_DWORD *)(4 * a3 + a1) >> 31))- abs(*(_DWORD *)(4 * a2 + a1))+ 2; }func1的return值化簡后為4a2+a1-abs(4a2+a1)+2(注意,這里我把右移31位直接看成0了,32位的數最高位為1被輸入的話很難遇到,直接忽略。)
4a2+a1-abs(4a2+a1)+2也就是y=x-|x|+2的函數圖像,如下:
這里返回值的分情況,有小于0,有大于0,有等于0
func2
int __cdecl func2(int a1, int a2, int a3) {int v3; // ecx@1v3 = (*(_DWORD *)(4 * a3 + a1) + *(_DWORD *)(4 * a2 + a1)) >> 31;return ((*(_DWORD *)(4 * a3 + a1) >> 31) ^ *(_DWORD *)(4 * a3 + a1))- (*(_DWORD *)(4 * a3 + a1) >> 31)- ((v3 ^ (*(_DWORD *)(4 * a3 + a1) + *(_DWORD *)(4 * a2 + a1)))- v3)+ abs(*(_DWORD *)(4 * a2 + a1))+ 2; }func2的return值化簡后為-4a2-a1+abs((4a2 + a1))+2(注意,這里我把右移31位直接看成0了,32位的數最高位為1被輸入的話很難遇到,直接忽略。)
-4a2-a1+abs((4a2 + a1))+2也就是y=-x+|x|+2的函數圖像,如下:
也就是說返回值恒大于0
解決方法分析
第一種方法,我們所畫出的函數恒為正,直接pass
第二種方法,如何改變呢?這里func0本來就是用來交換的,直接在調用func0函數時傳func1函數的函數指針v15和func2函數的函數指針v16進行交換即可。交換之后,v16函數指針指向的函數就是func1,func1的返回值可正可負可為零,返回值賦給了v11,然后就可以對flag進行打印輸出。
分析
signed int __cdecl func0(int a1, int a2, int a3) {int v3; // ST0C_4v3 = *(_DWORD *)(4 * a2 + a1);*(_DWORD *)(a1 + 4 * a2) = *(_DWORD *)(4 * a3 + a1);*(_DWORD *)(a1 + 4 * a3) = v3;return 1; } *(&v8 + v6) = (*(&v14 + i))((int)&v8, v12, v13);觀察func0,此時func0函數中參與運算的起始地址是v8,偏移分別是v12(3)和v13(4),也就是說,此時func0交換的參數是v11和v12。要使參數變為v15和v16,就需要把偏移改為7和8,也就是把v12的值改為7,v13 的值改為8(其實顛倒過來也行)
v7有20個字節,v8~v11是4個int,也就是4x4=16個字節,于是data文件需要從第36個字節開始,將v12和v13覆蓋為7和8。
此時僅僅只是交換了兩個函數指針,如何保證func1的返回值為0呢?
v15執行func2函數,v16執行func1函數,而在循環中v12和v13由i賦值,當執行v16(func1)函數時,v12和v13的值分別為2和3
4a2+a1-abs(4a2+a1)+2,v10等于2,要使返回的值為0,則需v11為-1,于是同樣利用程序data讀文件的漏洞將v11覆蓋為-1
總結
以上是生活随笔為你收集整理的BUUCTF Dig the way的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: BUUCTF 特殊的BASE64
- 下一篇: [NPUCTF2020]芜湖(Base6