GIVE_A_TRY.exe 逆向(NCK逆向初级第9,10,11课作业)
這個(gè)程序好像是一道CTF的題,對(duì)我這樣的新手來說難度很大,解題過程中遇到了不少坑,還學(xué)到了新的反調(diào)試技巧。下面我將記錄下我逆向這個(gè)程序的過程。
一、去花指令,過反調(diào)試,分析TLS回調(diào)函數(shù)功能
這個(gè)程序用到了TLS反調(diào)試技術(shù),很遺憾,我用的編程達(dá)人OD和X64DBG都有反反調(diào)試插件,直接把TLS過了,所以我剛開始甚至沒意識(shí)到反調(diào)試的存在。
然而,如果您使用無(wú)插件的OD,那么您運(yùn)行該程序可能會(huì)崩潰,又或者即使輸入了正確密鑰,程序也會(huì)提示密鑰錯(cuò)誤,這是因?yàn)?#xff0c;程序會(huì)檢測(cè)當(dāng)前是否正在被調(diào)試,然后會(huì)根據(jù)這個(gè)判斷結(jié)果修改隨機(jī)數(shù)種子。如果正在被調(diào)試,那么生成的種子也是錯(cuò)的。
關(guān)于TLS反調(diào)試,我也寫了一篇博客記錄。https://blog.csdn.net/Kwansy/article/details/108570075
要逆向這個(gè)程序,首先必須了解TLS的原理,TLS回調(diào)函數(shù)會(huì)在主函數(shù)調(diào)用前由操作系統(tǒng)調(diào)用,作者把一些關(guān)鍵操作放在TLS回調(diào)里做了,所以我們必須跟進(jìn)TLS函數(shù),看看作者干了些啥。
打開StrongOD插件選項(xiàng),勾上“在TLS斷下”
這樣,就能跟進(jìn)第一個(gè)TLS回調(diào)函數(shù)。
關(guān)于TLS函數(shù)的位置,我們可以打開PE工具查看
然后我們會(huì)發(fā)現(xiàn)作者弄了很多花指令,模板有兩種,第一種作者喜歡放到函數(shù)開頭附近,402006 的call就是一個(gè)花指令,是通過一系列的 CALL, ADD [ESP] 和 RET 來實(shí)現(xiàn)的,分析清楚他的模板之后,可以NOP掉了。
后面還有很多花指令,基本都是CALL的模板,全部NOP掉,分析第一個(gè)TLS回調(diào),我在OD和IDA都分析過了,直接說結(jié)論,第一個(gè)TLS的功能是反調(diào)試,和檢測(cè)調(diào)試狀態(tài),最后,動(dòng)態(tài)修復(fù)第二個(gè)TLS回調(diào)函數(shù)的地址。
只要我們把反調(diào)試過掉,第二個(gè)TLS回調(diào)就有了,同樣的道理,跟進(jìn)TLS2,去掉花指令,分析代碼。
void __stdcall TlsCallback_1(int a1, int a2, int a3) {void *hProcess; // eaxif ( a2 == 1 ){hProcess = (void *)GetCurrentProcess();NtQueryInformationProcess(hProcess, ProcessDebugPort, &isdebug, 4u, &isdebug);isdebug ^= 0x31333337u;dword_40403A = 0;isdebug ^= *(unsigned __int8 *)start;} }第二個(gè)TLS回調(diào)函數(shù)的功能就是修改了isdebug這個(gè)全局變量的值,然后給TLS數(shù)組的第三個(gè)位置填0,所以就沒有第三個(gè)TLS回調(diào)了。
到這里為止,我們完成了去花指令,反反調(diào)試的工作,下一步就是分析算法。
二、分析算法
怎么找到密鑰檢驗(yàn)算法就略過了,用IDA反編譯函數(shù),根據(jù)功能分析函數(shù),重命名一些變量后,得到如下代碼:
int __stdcall CheckKey(char *key) {int result; // eaxint v2; // ediunsigned __int8 v3; // alchar *v4; // esiunsigned int i; // ebxunsigned int v6; // eaxunsigned int v7; // edxunsigned int v8; // edxunsigned int v9; // edxunsigned int v10; // edxunsigned int v11; // edxunsigned int v12; // edxunsigned int v13; // edxunsigned int v14; // edxunsigned int v15; // edxunsigned int v16; // edxunsigned int v17; // edxunsigned int v18; // edxunsigned int v19; // edxunsigned int v20; // edxunsigned int v21; // edxif ( strlen(key) != 42 )return MessageBoxA(0, aThinkAgain, 0, 0);v2 = 0;v3 = *key;v4 = key + 1;while ( v3 ){v2 += v3;v3 = *v4++;}srand(isdebug ^ v2);for ( i = 0; i != 42; ++i ){v6 = (unsigned __int8)key[i] * rand();v7 = v6 * (unsigned __int64)v6 % 0xFAC96621;v8 = v7 * (unsigned __int64)v7 % 0xFAC96621;v9 = v8 * (unsigned __int64)v8 % 0xFAC96621;v10 = v9 * (unsigned __int64)v9 % 0xFAC96621;v11 = v10 * (unsigned __int64)v10 % 0xFAC96621;v12 = v11 * (unsigned __int64)v11 % 0xFAC96621;v13 = v12 * (unsigned __int64)v12 % 0xFAC96621;v14 = v13 * (unsigned __int64)v13 % 0xFAC96621;v15 = v14 * (unsigned __int64)v14 % 0xFAC96621;v16 = v15 * (unsigned __int64)v15 % 0xFAC96621;v17 = v16 * (unsigned __int64)v16 % 0xFAC96621;v18 = v17 * (unsigned __int64)v17 % 0xFAC96621;v19 = v18 * (unsigned __int64)v18 % 0xFAC96621;v20 = v19 * (unsigned __int64)v19 % 0xFAC96621;v21 = v20 * (unsigned __int64)v20 % 0xFAC96621;if ( v6 % 0xFAC96621 * (unsigned __int64)v21 % 0xFAC96621 != dword_4030B4[i] )break;}if ( i >= 0x2A )result = MessageBoxA(0, aCorrect, aCongrats, 0);elseresult = MessageBoxA(0, aIncorrect, 0, 0);return result; }要注意IDA F5反匯編的代碼有錯(cuò)誤,我已經(jīng)在下文的代碼注釋中標(biāo)明了。
如果你不明白上面這句話的含義,你可以拿IDA生成的代碼和匯編對(duì)照,就能明白了,
.text:004011D8 mul edx
.text:004011DA div ecx
.text:004011DC mov eax, edx
IDA F5插件無(wú)視了這三行,莫名其妙。。。
校驗(yàn)函數(shù)用到了隨機(jī)數(shù),我第一反應(yīng)是懵逼的,甚至有點(diǎn)害怕。實(shí)際上,當(dāng)srand的種子確定后,rand的結(jié)果也是國(guó)定的,可以看到,種子是 isdebug ^ keySum 計(jì)算得來。isdebug是在第二個(gè)TLS回調(diào)函數(shù)里設(shè)置的,固定就是 0x31333359,而這個(gè) keySum 是密鑰每一位累加得來,也就是說,每次輸入不同的密鑰,keySum 都不同。
我到這一步就不會(huì)做了,下面是其他牛人的解題思路:
首先,我們知道密鑰是以"flag{"開頭的(程序里有字符串提示),那么前5次循環(huán)的key[i]是確定的,我們希望 if ( v6 % 0xFAC96621 * (unsigned __int64)v21 % 0xFAC96621 != dword_4030B4[i] ) 這個(gè)判斷是相等的,v21是通過v6計(jì)算得來,所以唯一不確定的是v6,而v6有key[i]和rand()共同確定,前5次key[i]已知,不確定rand(),前五次rand()由srand種子決定,所以只需要暴力枚舉種子,讓前5個(gè)密鑰字符滿足比較條件即可。種子的暴力枚舉不需要從0到0xFFFFFFFF,只需要枚舉 42個(gè)字符的累加和這個(gè)范圍就行了。直接上代碼:
爆破得到keySum后,馬上就能算出前42次隨機(jī)數(shù)的值
void SetRand() {srand(seed); // 爆破得到的種子for (int i = 0; i < 42; i++){Rand[i] = rand();} }三、爆破密碼
// lession09.cpp : 此文件包含 "main" 函數(shù)。程序執(zhí)行將在此處開始并結(jié)束。 // #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <windows.h> #include <stdio.h> using namespace std;DWORD dword_4030B4[42] = {0x63B25AF1, 0x0C5659BA5, 0x4C7A3C33, 0x0E4E4267, 0x0B611769B,0x3DE6438C, 0x84DBA61F, 0x0A97497E6, 0x650F0FB3, 0x84EB507C,0x0D38CD24C, 0x0E7B912E0, 0x7976CD4F, 0x84100010, 0x7FD66745,0x711D4DBF, 0x5402A7E5, 0x0A3334351, 0x1EE41BF8, 0x22822EBE,0x0DF5CEE48, 0x0A8180D59, 0x1576DEDC, 0x0F0D62B3B, 0x32AC1F6E,0x9364A640, 0x0C282DD35, 0x14C5FC2E, 0x0A765E438, 0x7FCF345A,0x59032BAD, 0x9A5600BE, 0x5F472DC5, 0x5DDE0D84, 0x8DF94ED5,0x0BDF826A6, 0x515A737A, 0x4248589E, 0x38A96C20, 0x0CC7F61D9,0x2638C417, 0x0D9BEB996 };DWORD isdebug = 0x31333359; // 過掉反調(diào)試后得出的值,用于計(jì)算種子 DWORD seed; // 隨機(jī)種子 DWORD Rand[42]; // 偽隨機(jī)數(shù)組,因?yàn)榉N子已經(jīng)爆破得到,所以42個(gè)隨機(jī)數(shù)已經(jīng)確定BOOL __stdcall CheckKey(char *key) {int keySum; // ediunsigned __int8 c; // alchar *ptr; // esiunsigned int i; // ebxunsigned int v6; // eaxunsigned int v7; // edxunsigned int v8; // edxunsigned int v9; // edxunsigned int v10; // edxunsigned int v11; // edxunsigned int v12; // edxunsigned int v13; // edxunsigned int v14; // edxunsigned int v15; // edxunsigned int v16; // edxunsigned int v17; // edxunsigned int v18; // edxunsigned int v19; // edxunsigned int v20; // edxunsigned int v21; // edxunsigned int v22; // IDA 漏了這句if (strlen(key) != 42){MessageBoxA(0, "密碼長(zhǎng)度!=42", "", MB_OK);return FALSE;}keySum = 0;c = *key;ptr = key + 1;while (c){keySum += c;c = *ptr++;}srand(isdebug ^ keySum); // isdebug == 0x31333359for (i = 0; i != 42; ++i){v6 = (unsigned __int8)key[i] * rand();v7 = v6 * (unsigned __int64)v6 % 0xFAC96621;v8 = v7 * (unsigned __int64)v7 % 0xFAC96621;v9 = v8 * (unsigned __int64)v8 % 0xFAC96621;v10 = v9 * (unsigned __int64)v9 % 0xFAC96621;v11 = v10 * (unsigned __int64)v10 % 0xFAC96621;v12 = v11 * (unsigned __int64)v11 % 0xFAC96621;v13 = v12 * (unsigned __int64)v12 % 0xFAC96621;v14 = v13 * (unsigned __int64)v13 % 0xFAC96621;v15 = v14 * (unsigned __int64)v14 % 0xFAC96621;v16 = v15 * (unsigned __int64)v15 % 0xFAC96621;v17 = v16 * (unsigned __int64)v16 % 0xFAC96621;v18 = v17 * (unsigned __int64)v17 % 0xFAC96621;v19 = v18 * (unsigned __int64)v18 % 0xFAC96621;v20 = v19 * (unsigned __int64)v19 % 0xFAC96621;v21 = v20 * (unsigned __int64)v20 % 0xFAC96621;v22 = v21 * (unsigned __int64)v21 % 0xFAC96621; // IDA漏了這句if (v6 % 0xFAC96621 * (unsigned __int64)v22 % 0xFAC96621 != dword_4030B4[i])break;}if (i >= 0x2A)return TRUE;elsereturn FALSE; }// 如果知道 key 的累加和,就可以得到隨機(jī)數(shù)種子 // 要想計(jì)算得到 key 的累加和,只能通過 v6 = (unsigned __int8)key[i] * rand(); 反推,反推的方法是暴力枚舉srand種子 // 已知key以"flag"開頭,則key[0] - key[3] 都是確定的,那么只要找到一個(gè)種子,使前4個(gè)字符計(jì)算后和dword_4030B4[i]相等 void CalcKeySum() {char key[5] = "flag";// 爆破keySumfor (unsigned int keySum = 0; keySum < 255*42; keySum++){srand(keySum ^ isdebug);int i;for (i = 0; i < 4; i++){unsigned int v6 = (unsigned __int8)key[i] * rand();unsigned int v7 = v6 * (unsigned __int64)v6 % 0xFAC96621;unsigned int v8 = v7 * (unsigned __int64)v7 % 0xFAC96621;unsigned int v9 = v8 * (unsigned __int64)v8 % 0xFAC96621;unsigned int v10 = v9 * (unsigned __int64)v9 % 0xFAC96621;unsigned int v11 = v10 * (unsigned __int64)v10 % 0xFAC96621;unsigned int v12 = v11 * (unsigned __int64)v11 % 0xFAC96621;unsigned int v13 = v12 * (unsigned __int64)v12 % 0xFAC96621;unsigned int v14 = v13 * (unsigned __int64)v13 % 0xFAC96621;unsigned int v15 = v14 * (unsigned __int64)v14 % 0xFAC96621;unsigned int v16 = v15 * (unsigned __int64)v15 % 0xFAC96621;unsigned int v17 = v16 * (unsigned __int64)v16 % 0xFAC96621;unsigned int v18 = v17 * (unsigned __int64)v17 % 0xFAC96621;unsigned int v19 = v18 * (unsigned __int64)v18 % 0xFAC96621;unsigned int v20 = v19 * (unsigned __int64)v19 % 0xFAC96621;unsigned int v21 = v20 * (unsigned __int64)v20 % 0xFAC96621;unsigned int v22 = v21 * (unsigned __int64)v21 % 0xFAC96621; // 注意,IDA的F5插件漏了這行,大坑// 還有一個(gè)坑,(unsigned __int64)v6 不要省略強(qiáng)轉(zhuǎn),IDA怎么生成就怎么抄// 因?yàn)樵趘s里,64位的求余根本沒用div指令,不強(qiáng)轉(zhuǎn)就算不出來了if ((unsigned __int64)v6 % 0xFAC96621 * (unsigned int)v22 % 0xFAC96621 != dword_4030B4[i]){break;} }if (i == 4){printf("keySum = %X\n", keySum);seed = keySum ^ isdebug;break;} } }void SetRand() {srand(seed); // 爆破得到的種子for (int i = 0; i < 42; i++){Rand[i] = rand();} }int main() {CalcKeySum(); // 爆破種子(0xE61)SetRand(); // 設(shè)置偽隨機(jī)數(shù)組// 爆破密碼for (int i = 0; i < 42; i++){for (unsigned char ch = 0; ch < 0xFF; ch++){unsigned int v6 = (unsigned __int8)ch * Rand[i];unsigned int v7 = v6 * (unsigned __int64)v6 % 0xFAC96621;unsigned int v8 = v7 * (unsigned __int64)v7 % 0xFAC96621;unsigned int v9 = v8 * (unsigned __int64)v8 % 0xFAC96621;unsigned int v10 = v9 * (unsigned __int64)v9 % 0xFAC96621;unsigned int v11 = v10 * (unsigned __int64)v10 % 0xFAC96621;unsigned int v12 = v11 * (unsigned __int64)v11 % 0xFAC96621;unsigned int v13 = v12 * (unsigned __int64)v12 % 0xFAC96621;unsigned int v14 = v13 * (unsigned __int64)v13 % 0xFAC96621;unsigned int v15 = v14 * (unsigned __int64)v14 % 0xFAC96621;unsigned int v16 = v15 * (unsigned __int64)v15 % 0xFAC96621;unsigned int v17 = v16 * (unsigned __int64)v16 % 0xFAC96621;unsigned int v18 = v17 * (unsigned __int64)v17 % 0xFAC96621;unsigned int v19 = v18 * (unsigned __int64)v18 % 0xFAC96621;unsigned int v20 = v19 * (unsigned __int64)v19 % 0xFAC96621;unsigned int v21 = v20 * (unsigned __int64)v20 % 0xFAC96621;unsigned int v22 = v21 * (unsigned __int64)v21 % 0xFAC96621; // IDA漏了這句if (v6 % 0xFAC96621 * (unsigned __int64)v22 % 0xFAC96621 == dword_4030B4[i])putchar((char)ch);}}//char key[] = "flag{wh3r3_th3r3_i5_@_w111-th3r3_i5_@_w4y}";//if (CheckKey(key))//{// printf("密碼正確\n");//}//else//{// printf("密碼錯(cuò)誤\n");//}return 0; }四、去花腳本
手動(dòng)去除花指令吃力不討好,下面附上去除花指令的腳本。
find eip,#E80000000081042417000000C3576174636820757220737465702100# cmp $RESULT,0 je exit mov [$RESULT],#90909090909090909090909090909090909090909090909090909090#find eip,#E80000000081042425000000C354686520666C616720626567696E7320776974682022666C61677B2200# cmp $RESULT,0 je exit mov [$RESULT],#909090909090909090909090909090909090909090909090909090909090909090909090909090909090#loop:find eip, #E801000000??????????C3#cmp $RESULT,0je exitmov [$RESULT],#9090909090909090909090# jmp loopexit: MSG "bye\r\n" ret 《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的GIVE_A_TRY.exe 逆向(NCK逆向初级第9,10,11课作业)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: TLS调试检测和反调试
- 下一篇: SEH反调试(SetUnhandledE