算法分析:看雪CTF2019的一道逆向題目
1.查看程序的基本運(yùn)行邏輯

出現(xiàn)一個窗口,有一個提示標(biāo)簽password、一個輸入框,一個驗(yàn)證按鈕

當(dāng)再輸入框內(nèi)輸入一些字符按下驗(yàn)證按鈕后出現(xiàn)錯誤提示信息框
其運(yùn)行邏輯大概可視為下圖

2.查殼初探
MFC封裝vc寫的沒殼

3.使用IDA Pro進(jìn)行靜態(tài)分析
①拖入程序,先打開函數(shù)調(diào)用情況窗口(方便函數(shù)跟蹤)

②shift+F12查看引用字符
看到了剛才錯誤的提示字符“錯了”,還有嫌疑字符“pass!”,那我們找到pass字符所在的位置查看引用情況

③查看引用,找到引用函數(shù)
發(fā)現(xiàn)pass!字符的引用的函數(shù)是sub_401770,跟進(jìn)去

跟進(jìn)來看到的信息可以直接判斷sub_401770這個函數(shù)就是輸入提示我們輸入的password是正確的函數(shù)

④跟蹤函數(shù)調(diào)用情況,直至找到最終調(diào)用者
(1)做一個小提示:在ida中我們看到的函數(shù)都是一個以sub_函數(shù)地址值來命名這個函數(shù),不便于我們分析,所以我們只用將光標(biāo)放在函數(shù)名字上按下n鍵就可以更改為我們便于理解和記住的名字(不可以用中文)
先將sub_401770改名為Tip_Success,通過函數(shù)調(diào)用表點(diǎn)擊Caller字符函數(shù)名就會跳轉(zhuǎn)Instruction字段函數(shù)的對應(yīng)的調(diào)用者

(2)跟進(jìn)發(fā)現(xiàn)sub_4017F0函數(shù)中有關(guān)鍵邏輯

將Str1與"KanXueCTF2019JustForhappy"作對比,如果相等則執(zhí)行了提示成功的函數(shù)Tip_Success,那我們可以直接推測Str1就是Flag最后的變換值
(3)查找偽代碼中的關(guān)于Str1的邏輯語句
單機(jī)Str1看到了Str1是一個字符型數(shù)組局部變量,大小為28,經(jīng)歷了一個while中的計(jì)算

代碼中涉及了v4和a1,v4可以看到是一個初始化值為0的整型變量,a1則是一個該函數(shù)的形參,那說明此時(shí)我們要重點(diǎn)尋找a1.
分析循環(huán)體
?while ( *(_DWORD *)(a1 + 4 * v4) < 62 && *(_DWORD *)(a1 + 4 * v4) >= 0 )
? {
??? Str1[v4] = aAbcdefghiabcde[*(_DWORD *)(a1 + 4 * v4)];
??? ++v4;
? }
第二步我提到過Str1變量已經(jīng)可以確定是Flag最后的變換值
查看循環(huán)體內(nèi),其意思便是只要a1 + 4?v4這個地址里面的內(nèi)容小于62并且大于等于0則將a1 + 4?v4地址的值作為aAbcdefghiabcde數(shù)組的下標(biāo),然后將對應(yīng)的aAbcdefghiabcde數(shù)組內(nèi)容賦值給Str1
那么我們?nèi)ゲ榭匆幌耡Abcdefghiabcde數(shù)組的元素是什么?算擊aAbcdefghiabcde數(shù)組名字查看內(nèi)容
原來就是一串字符

此時(shí)分析到這里基本這個函數(shù)就分析完了,我們?nèi)绻^續(xù)分析則必須先找出a1到底是什么?
繼續(xù)通過函數(shù)調(diào)用表找到sub_4017F0(我們改名為Check_Encryption)函數(shù)的調(diào)用者
跟進(jìn)來以后看到了Check_Encryption函數(shù)中的形參就是此時(shí)函數(shù)sub_401890中的一個整型數(shù)組v5的首地址(該數(shù)組大小為26)

那我們此時(shí)就重點(diǎn)跟蹤v5的值了

紅框框住的就是一些mfc組件的代碼,不用管,可以看到,v5的變化初始化和變化來自于Str數(shù)組,
那么Str又是什么呢,仔細(xì)看從上往下第一個箭頭指向的地方GetBuffer函數(shù),這個函數(shù)會返回一個緩沖區(qū)指針,Str來接收了,說明Str很有可能就是我們輸入的password所在的地址,再往GetBuffer下看一行遍看到了一個if語句
判斷Str長度是否為0,也就是判斷是否空,空的話就執(zhí)行了return語句,不為空的話就進(jìn)行了一系列關(guān)于v5和Str的計(jì)算,那么此時(shí)可以直接肯定Str就是我們輸入的字符所在地址

通過代碼又可以看出我們輸入的password存到Str后進(jìn)行了一系列if判斷后計(jì)算賦值給了v5,而v5的值又作為了Check_Encryption函數(shù)的唯一實(shí)參
?if ( strlen(Str) )
? {
??? for ( i = 0; Str[i]; ++i )
??? {
????? if ( Str[i] > 57 || Str[i] < 48 )
????? {
??????? if ( Str[i] > 122 || Str[i] < 97 )
??????? {
????????? if ( Str[i] > 90 || Str[i] < 65 )
??????????? sub_4017B0();
????????? else
??????????? v5[i] = Str[i] - 29;
??????? }
??????? else
??????? {
????????? v5[i] = Str[i] - 87;
? ??????}
????? }
????? else
????? {
??????? v5[i] = Str[i] - 48;
????? }
??? }
??? result = Check_Encryption((int)v5);
? }
? else
? {
??? result = CWnd::MessageBoxA(v8, "請輸入pass!", 0, 0);
? }
(4)梳理大概邏輯

5)我們按照從頭到尾的順序來分析這個程序
既然我們知道了我們輸入的password是被Str指向(可以理解為Str是個字符數(shù)組存了我們輸入的password),那么我們試試是否可以從sub_401890這個函數(shù)推出Str

閱讀代碼,可以得到Str[i]在此時(shí)可以有三種情況,所以此時(shí)我們不可以確定Str的唯一性,也就是我們不可以在這里得到Str(我們輸入的)具體是多少,只能得到范圍,那么就繼續(xù)分析后面一步,跟進(jìn)Check_Encryption函數(shù)
(6)分析Check_Encryption關(guān)鍵代碼
? v4 = 0;
? v3 = 0;
? while ( *(_DWORD *)(a1 + 4 * v4) < 62 && *(_DWORD *)(a1 + 4 * v4) >= 0 )
? {
??? Str1[v4] = aAbcdefghiabcde[*(_DWORD *)(a1 + 4 * v4)];
??? ++v4;
? }
閱讀代碼可知,(_DWORD?)(a1 + 4 * v4)的作用便是遍歷上一個函數(shù)v5的每一個元素,若每一個元素滿足小于62并且大于0的話則將v5對應(yīng)的每一個元素作為aAbcdefghiabcde數(shù)組的下角標(biāo),將對應(yīng)的aAbcdefghiabcde元素賦值給Str1,之前提過Str1是最后的Flag變換結(jié)果
while ( *(_DWORD *)(a1 + 4 * v4) < 62 && *(_DWORD *)(a1 + 4 * v4) >= 0 )
? {
??? Str1[v4] = aAbcdefghiabcde[*(_DWORD *)(a1 + 4 * v4)];
??? ++v4;
? }
? Str1[v4] = 0;
? if ( !strcmp(Str1, "KanXueCTF2019JustForhappy") )
??? result = Tip_Success();
? else
result = sub_4017B0();
作者設(shè)計(jì),如果最后的Flag遍結(jié)果等于“KanXueCTF2019JustForhappy”則提示成功,那由上我們就可以寫腳本得到v5的值
char encryption[] = "KanXueCTF2019JustForhappy";
??????? char text[] = "abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ";
??????? for (int i = 0; i <= strlen(encryption); i++)
??????? {
??????????????? for (int j = 0; j <= strlen(text); j++)
??????????????? {
??????????????????????? if (encryption[i] == text[j])
??????????????????????? {
??????????????????????????????? //printf("%d--", j);
??????????????????????????????? v5[i] = j;
??????????????????????? }
??????????????? }
此時(shí)我們既然得到了v5的值那么就可以去分析上一個函數(shù)sub_401890得到Str(我們輸入的password)了
?for ( i = 0; Str[i]; ++i )
??? {
????? if ( Str[i] > 57 || Str[i] < 48 )
????? {
??????? if ( Str[i] > 122 || Str[i] < 97 )
??????? {
????????? if ( Str[i] > 90 || Str[i] < 65 )
??????????? sub_4017B0();
????????? else
??????????? v5[i] = Str[i] - 29;
??????? }
??????? else
??????? {
????????? v5[i] = Str[i] - 87;
??????? }
????? }
????? else
????? {
??????? v5[i] = Str[i] - 48;
????? }
??? }
這個循環(huán)結(jié)構(gòu)就是判斷Strp[i]是否在一個規(guī)定的范圍,通過Str[i]所屬的范圍來進(jìn)行加密或者推=退出程序,因?yàn)関5是Str[i]-常數(shù)得到的,那么我們就可以用v5+常數(shù)來判斷Str[i]的范圍以及確卻數(shù)值了,反推代碼如下,input數(shù)組就是Str(用戶輸入)
for (int i = 0; i <= 26; i++)
??????? {
??????????????? if (48 <= (v5[i] + 48) && (v5[i] + 48) <= 57)
??????????????? {
??????????????????????? input[i] = v5[i]+48;
??????????????? }
??????????????? if (97 <= (v5[i] + 87) && (v5[i] + 87) <= 122)
??????????????? {
??????????????????????? input[i] = v5[i]+87;
??????????????? }
??????????????? if (65 <= (v5[i] + 29) && (v5[i] + 29) <= 90)
??????????????? {
??????????????????????? input[i] = v5[i] + 29;
??????????????? }
??????? }
最后寫出第一輪和第二輪的解密代碼,即可得到flag
#include
#include
int main()
{
??????? int input[26] = { 1 };
??????? int v5[26] = {1};
??????? int Str[26] = { 1 };
??????? char encryption[] = "KanXueCTF2019JustForhappy";
??????? char text[] = "abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ";
??????? for (int i = 0; i <= strlen(encryption); i++)
??????? {
?????? ?????????for (int j = 0; j <= strlen(text); j++)
??????????????? {
??????????????????????? if (encryption[i] == text[j])
??????????????????????? {
??????????????????????????????? //printf("%d--", j);
??????????????????????????????? v5[i] = j;
???????????? ???????????}
??????????????? }
??????? }
??????? for (int i = 0; i <= 26; i++)
??????? {
??????????????? if (48 <= (v5[i] + 48) && (v5[i] + 48) <= 57)
??????????????? {
??????????????????????? input[i] = v5[i]+48;
??????????????? }
??????????????? if (97 <= (v5[i] + 87) && (v5[i] + 87) <= 122)
??????????????? {
??????????????????????? input[i] = v5[i]+87;
??????????????? }
??????????????? if (65 <= (v5[i] + 29) && (v5[i] + 29) <= 90)
??????????????? {
??????????????????????? input[i] = v5[i] + 29;
???????? ???????}
??????? }
??????? for (int i = 0; i < 26; i++)
??????? {
??????????????? printf("%c",input[i]);
??????? }
??????? return 0;