演算法分析:看雪CTF2019的一道逆向題目
1.檢視程式的基本執行邏輯
出現一個視窗,有一個提示標籤password、一個輸入框,一個驗證按鈕
當再輸入框內輸入一些字元按下驗證按鈕後出現錯誤提示資訊框
其執行邏輯大概可視為下圖
2.查殼初探
MFC封裝vc寫的沒殼
3.使用IDA Pro進行靜態分析
拖入程式,先開啟函式呼叫情況視窗(方便函式跟蹤)
shift+F12檢視引用字元
看到了剛才錯誤的提示字元“錯了”,還有嫌疑字元“pass!”,那我們找到pass字元所在的位置檢視引用情況
檢視引用,找到引用函式
發現pass!字元的引用的函式是sub_401770,跟進去
跟進來看到的資訊可以直接判斷sub_401770這個函式就是輸入提示我們輸入的password是正確的函式
跟蹤函式呼叫情況,直至找到最終呼叫者
(1)做一個小提示:在ida中我們看到的函式都是一個以sub_函式地址值來命名這個函式,不便於我們分析,所以我們只用將游標放在函式名字上按下n鍵就可以更改為我們便於理解和記住的名字(不可以用中文)
先將sub_401770改名為Tip_Success,透過函式呼叫表點選Caller字元函式名就會跳轉Instruction欄位函式的對應的呼叫者
(2)跟進發現sub_4017F0函式中有關鍵邏輯
將Str1與"KanXueCTF2019JustForhappy"作對比,如果相等則執行了提示成功的函式Tip_Success,那我們可以直接推測Str1就是Flag最後的變換值
(3)查詢虛擬碼中的關於Str1的邏輯語句
單機Str1看到了Str1是一個字元型陣列區域性變數,大小為28,經歷了一個while中的計算
程式碼中涉及了v4和a1,v4可以看到是一個初始化值為0的整型變數,a1則是一個該函式的形參,那說明此時我們要重點尋找a1.
分析迴圈體
while ( *(_DWORD *)(a1 + 4 * v4) < 62 && *(_DWORD *)(a1 + 4 * v4) >= 0 )
{
Str1[v4] = aAbcdefghiabcde[*(_DWORD *)(a1 + 4 * v4)];
++v4;
}
第二步我提到過Str1變數已經可以確定是Flag最後的變換值
檢視迴圈體內,其意思便是隻要a1 + 4 v4這個地址裡面的內容小於62並且大於等於0則將a1 + 4 v4地址的值作為aAbcdefghiabcde陣列的下標,然後將對應的aAbcdefghiabcde陣列內容賦值給Str1
那麼我們去檢視一下aAbcdefghiabcde陣列的元素是什麼?算擊aAbcdefghiabcde陣列名字檢視內容
原來就是一串字元
此時分析到這裡基本這個函式就分析完了,我們如果要繼續分析則必須先找出a1到底是什麼?
繼續透過函式呼叫表找到sub_4017F0(我們改名為Check_Encryption)函式的呼叫者
跟進來以後看到了Check_Encryption函式中的形參就是此時函式sub_401890中的一個整型陣列v5的首地址(該陣列大小為26)
那我們此時就重點跟蹤v5的值了
紅框框住的就是一些mfc元件的程式碼,不用管,可以看到,v5的變化初始化和變化來自於Str陣列,
那麼Str又是什麼呢,仔細看從上往下第一個箭頭指向的地方GetBuffer函式,這個函式會返回一個緩衝區指標,Str來接收了,說明Str很有可能就是我們輸入的password所在的地址,再往GetBuffer下看一行遍看到了一個if語句
判斷Str長度是否為0,也就是判斷是否空,空的話就執行了return語句,不為空的話就進行了一系列關於v5和Str的計算,那麼此時可以直接肯定Str就是我們輸入的字元所在地址
透過程式碼又可以看出我們輸入的password存到Str後進行了一系列if判斷後計算賦值給了v5,而v5的值又作為了Check_Encryption函式的唯一實參
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是個字元陣列存了我們輸入的password),那麼我們試試是否可以從sub_401890這個函式推出Str
閱讀程式碼,可以得到Str[i]在此時可以有三種情況,所以此時我們不可以確定Str的唯一性,也就是我們不可以在這裡得到Str(我們輸入的)具體是多少,只能得到範圍,那麼就繼續分析後面一步,跟進Check_Encryption函式
(6)分析Check_Encryption關鍵程式碼
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)的作用便是遍歷上一個函式v5的每一個元素,若每一個元素滿足小於62並且大於0的話則將v5對應的每一個元素作為aAbcdefghiabcde陣列的下角標,將對應的aAbcdefghiabcde元素賦值給Str1,之前提過Str1是最後的Flag變換結果
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();
作者設計,如果最後的Flag遍結果等於“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;
}
}
此時我們既然得到了v5的值那麼就可以去分析上一個函式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;
}
}
這個迴圈結構就是判斷Strp[i]是否在一個規定的範圍,透過Str[i]所屬的範圍來進行加密或者推=退出程式,因為v5是Str[i]-常數得到的,那麼我們就可以用v5+常數來判斷Str[i]的範圍以及確卻數值了,反推程式碼如下,input陣列就是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;
相關文章
- bugku一道逆向題目分析2022-04-27
- 一道關於逆向的實戰CTF題目分析2024-07-12
- 看雪安卓研修班,安卓逆向2020-12-21安卓
- 演算法分析:BUUCTF-2019全國賽的一道逆向題2022-04-13演算法
- 一道演算法題的分析2021-09-09演算法
- 2020 看雪SDC議題回顧 | 麒麟框架:現代化的逆向分析體驗2020-11-03框架
- 2020 看雪SDC 議題預告 | 麒麟框架:現代化的逆向分析體驗2020-10-12框架
- 從一道題目看二維陣列的地址2012-09-21陣列
- 從一道Promise執行順序的題目看Promise實現2018-03-10Promise
- 一道無聊的題目2024-06-24
- 對VM逆向的分析(CTF)(比較經典的一個虛擬機器逆向題目)2021-06-04虛擬機
- 一道以前看過的面試題2010-11-17面試題
- 看雪安全程式設計培訓目錄2018-03-10程式設計
- 一道騷面試題目2022-03-13面試題
- 『看雪眾測』這次的眾測物件是看雪!2018-03-21物件
- 一道面試題的分析2018-10-28面試題
- 一道面試題目引發的思考2017-09-08面試題
- 一道演算法題2018-03-15演算法
- 從itpub看來的一道組合題2017-04-05
- 一道面試題看 HashMap 的儲存方式2014-05-18面試題HashMap
- 分享一道有趣的 Leetcode 題目2019-11-16LeetCode
- ssd上一道題目引發的思考2012-11-08
- 看雪安卓容器2019-01-14安卓
- 一道簡單的題目引發的思考2016-02-01
- 一道前端演算法題2018-12-11前端演算法
- 一道關於隨機數生成的題目2015-03-04隨機
- 看雪安全網站2015-11-15網站
- 開啟演算法之路,還原題目,用debug除錯搞懂每一道題2021-02-21演算法除錯
- 一道位運算的演算法題2014-10-15演算法
- 連結串列演算法題二,還原題目,用debug除錯搞懂每一道題2021-02-26演算法除錯
- 通過一道題來看React事件模型2022-06-20React事件模型
- 看雪論壇版主座談 | 看雪2017安全開發者峰會2017-12-09
- 一道有意思的面試演算法題2019-01-04面試演算法
- 記一道毫無思路的演算法題2014-10-23演算法
- 一道關於筆試的多執行緒題目2015-09-03筆試執行緒
- 「每日一道演算法題」Reverse Integer2019-01-03演算法
- [原創]看雪CTF2017第二題lelfeiCM的writeup2019-02-25TF2
- 一道java面試題分析及思考2017-11-01Java面試題