日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

GIVE_A_TRY.exe 逆向(NCK逆向初级第9,10,11课作业)

發布時間:2025/3/21 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 GIVE_A_TRY.exe 逆向(NCK逆向初级第9,10,11课作业) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

這個程序好像是一道CTF的題,對我這樣的新手來說難度很大,解題過程中遇到了不少坑,還學到了新的反調試技巧。下面我將記錄下我逆向這個程序的過程。

一、去花指令,過反調試,分析TLS回調函數功能

這個程序用到了TLS反調試技術,很遺憾,我用的編程達人OD和X64DBG都有反反調試插件,直接把TLS過了,所以我剛開始甚至沒意識到反調試的存在。
然而,如果您使用無插件的OD,那么您運行該程序可能會崩潰,又或者即使輸入了正確密鑰,程序也會提示密鑰錯誤,這是因為,程序會檢測當前是否正在被調試,然后會根據這個判斷結果修改隨機數種子。如果正在被調試,那么生成的種子也是錯的。
關于TLS反調試,我也寫了一篇博客記錄。https://blog.csdn.net/Kwansy/article/details/108570075

要逆向這個程序,首先必須了解TLS的原理,TLS回調函數會在主函數調用前由操作系統調用,作者把一些關鍵操作放在TLS回調里做了,所以我們必須跟進TLS函數,看看作者干了些啥。

打開StrongOD插件選項,勾上“在TLS斷下”

這樣,就能跟進第一個TLS回調函數。


關于TLS函數的位置,我們可以打開PE工具查看

然后我們會發現作者弄了很多花指令,模板有兩種,第一種作者喜歡放到函數開頭附近,402006 的call就是一個花指令,是通過一系列的 CALL, ADD [ESP] 和 RET 來實現的,分析清楚他的模板之后,可以NOP掉了。

后面還有很多花指令,基本都是CALL的模板,全部NOP掉,分析第一個TLS回調,我在OD和IDA都分析過了,直接說結論,第一個TLS的功能是反調試,和檢測調試狀態,最后,動態修復第二個TLS回調函數的地址。

void __stdcall TlsCallback_0(int a1, int a2, int a3) {void (__stdcall *TlsCallback_1)(int, int, int); // ediunsigned int v4; // et0void *hThread; // eaxvoid *hProcess; // eaxunsigned int v7; // [esp+0h] [ebp-10h]if ( a2 == 1 ){TlsCallback_1 = ::TlsCallback_1;v4 = __readeflags();v7 = v4;if ( v4 & 0x100 )TlsCallback_1 = 0;__writeeflags(v7);if ( *(_DWORD *)&NtCurrentPeb()->InheritedAddressSpace & 0x10000 )TlsCallback_1 = 0;hThread = (void *)GetCurrentThread();NtSetInformationThread(hThread, ThreadHideFromDebugger, 0, 0);hProcess = (void *)GetCurrentProcess();NtQueryInformationProcess(hProcess, ProcessDebugPort, &isdebug, 4u, 0);if ( isdebug )TlsCallback_1 = 0;dword_404036 = (int)TlsCallback_1; // 動態patch第二個TLS回調} }

只要我們把反調試過掉,第二個TLS回調就有了,同樣的道理,跟進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;} }

第二個TLS回調函數的功能就是修改了isdebug這個全局變量的值,然后給TLS數組的第三個位置填0,所以就沒有第三個TLS回調了。
到這里為止,我們完成了去花指令,反反調試的工作,下一步就是分析算法。

二、分析算法

怎么找到密鑰檢驗算法就略過了,用IDA反編譯函數,根據功能分析函數,重命名一些變量后,得到如下代碼:

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反匯編的代碼有錯誤,我已經在下文的代碼注釋中標明了。
如果你不明白上面這句話的含義,你可以拿IDA生成的代碼和匯編對照,就能明白了,
.text:004011D8 mul edx
.text:004011DA div ecx
.text:004011DC mov eax, edx
IDA F5插件無視了這三行,莫名其妙。。。

校驗函數用到了隨機數,我第一反應是懵逼的,甚至有點害怕。實際上,當srand的種子確定后,rand的結果也是國定的,可以看到,種子是 isdebug ^ keySum 計算得來。isdebug是在第二個TLS回調函數里設置的,固定就是 0x31333359,而這個 keySum 是密鑰每一位累加得來,也就是說,每次輸入不同的密鑰,keySum 都不同。
我到這一步就不會做了,下面是其他牛人的解題思路:
首先,我們知道密鑰是以"flag{"開頭的(程序里有字符串提示),那么前5次循環的key[i]是確定的,我們希望 if ( v6 % 0xFAC96621 * (unsigned __int64)v21 % 0xFAC96621 != dword_4030B4[i] ) 這個判斷是相等的,v21是通過v6計算得來,所以唯一不確定的是v6,而v6有key[i]和rand()共同確定,前5次key[i]已知,不確定rand(),前五次rand()由srand種子決定,所以只需要暴力枚舉種子,讓前5個密鑰字符滿足比較條件即可。種子的暴力枚舉不需要從0到0xFFFFFFFF,只需要枚舉 42個字符的累加和這個范圍就行了。直接上代碼:

// 如果知道 key 的累加和,就可以得到隨機數種子 // 要想計算得到 key 的累加和,只能通過 v6 = (unsigned __int8)key[i] * rand(); 反推,反推的方法是暴力枚舉srand種子 // 已知key以"flag"開頭,則key[0] - key[3] 都是確定的,那么只要找到一個種子,使前4個字符計算后和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插件漏了這行,大坑// 還有一個坑,(unsigned __int64)v6 不要省略強轉,IDA怎么生成就怎么抄// 因為在vs里,64位的求余根本沒用div指令,不強轉就算不出來了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;} } }

爆破得到keySum后,馬上就能算出前42次隨機數的值

void SetRand() {srand(seed); // 爆破得到的種子for (int i = 0; i < 42; i++){Rand[i] = rand();} }

三、爆破密碼

// lession09.cpp : 此文件包含 "main" 函數。程序執行將在此處開始并結束。 // #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; // 過掉反調試后得出的值,用于計算種子 DWORD seed; // 隨機種子 DWORD Rand[42]; // 偽隨機數組,因為種子已經爆破得到,所以42個隨機數已經確定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, "密碼長度!=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 的累加和,就可以得到隨機數種子 // 要想計算得到 key 的累加和,只能通過 v6 = (unsigned __int8)key[i] * rand(); 反推,反推的方法是暴力枚舉srand種子 // 已知key以"flag"開頭,則key[0] - key[3] 都是確定的,那么只要找到一個種子,使前4個字符計算后和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插件漏了這行,大坑// 還有一個坑,(unsigned __int64)v6 不要省略強轉,IDA怎么生成就怎么抄// 因為在vs里,64位的求余根本沒用div指令,不強轉就算不出來了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(); // 設置偽隨機數組// 爆破密碼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("密碼錯誤\n");//}return 0; }

四、去花腳本

手動去除花指令吃力不討好,下面附上去除花指令的腳本。

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 《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀

總結

以上是生活随笔為你收集整理的GIVE_A_TRY.exe 逆向(NCK逆向初级第9,10,11课作业)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。