--- tags: 資訊安全實務 --- # HW8 ## SecureContainProtect 這題用IDA打開按下F5後就基本上甚麼都出來了,沒有stripped,也沒有任何來干擾我們逆向分析的東西,基本上就是很裸,那就先來上一下IDA裡面最核心的CODE ```cpp __isoc99_scanf("%39s", s); for ( k = 0; k <= 6014; ++k ) { v0 = byte_202E00[k]; v1 = byte_202020[k % 81]; byte_202E00[k] = v1 ^ s[k % strlen(s)] ^ v0; v6 += byte_202E00[k]; } if ( v6 != 257498 ) puts( "\n" "\n" "You are not the agent.....\n" "Are you trying to steal our secret?\n" "A well-trained killer is coming to your place for a cup of tea..."); else puts(byte_202E00); exit(0); ``` 上面code的byte_202020就是數獨的部分,byte_202E00就是一個...對就是一個陣列,在經過上面的運算後如果我們輸入的key是正確的就能xor出正確的ascii art。所以下一步我就是把完成的數獨寫到一個檔案裡面,再把那6015大小的陣列也進去記憶體把值拿出來寫到檔案裡面,這樣我之後再寫自己的script時,就能直接透過去讀那些data了。 剩下就是就是要怎麼還原ascii art了,這部分我在解的時候做了幾個猜想 * ascii art的組成一定是ascii可見字元 * 然後一定要有換行,要不然我就只會看到一坨大便 * 所以經過一連串的xor後,每個字元的範圍我就大膽猜測在32到126之間,或者是10(換行) * 然後我們能輸入的key的範圍則是在33到126之間,不包含32是因為我們無法輸入空白 有了上面的猜想,就能夠將可能的key的候選都找出來,也能順便找出key的長度,以下是找輸入候選的code,找的方法是針對輸入第x個字元,且在假設的長度下能不能讓輸出可能會變成ascii art ```cpp map<int, vector<int>> key[39]; map<int, vector<int>> key_sum[39]; for (int pos = 0; pos < 39; pos++) { map<int, vector<int>> key_tmp; map<int, vector<int>> key_sum_tmp; for (int ascii = 33; ascii <= 126; ascii++) //exclude space { for (int len = 1; len <= 39; len++) { bool ascii_art = true; unsigned int sum = 0; for (int i = pos; i < 6015; i += len) { int temp = a[i] ^ ascii ^ sudoku[i % 81]; sum += temp; //10 --> new line if (!((temp >= 32 && temp <= 126) || temp == 10)) { ascii_art = false; break; } } if (ascii_art) { key_tmp[len].push_back(ascii); key_sum_tmp[len].push_back(sum); } } } for (auto it = key_tmp.begin(); it != key_tmp.end(); ++it) { key[pos][it->first] = it->second; key_sum[pos][it->first] = key_sum_tmp[it->first]; } } ``` 上面的code能讓```key[pos][len]```存者可能的輸入候選,那我這個map又可以來運用在找到key的長度是多少,先不說廢話上code ```cpp for (int i = 0; i < 39; i++) { cout << "SET" << i + 1 << "------------------------" << endl; for (auto it = key[i].begin(); it != key[i].end(); ++it) { cout << it->first << " "; } cout << endl; } ``` 上面這code在做甚麼呢?我把```key[pos]```當作是一個集合,集合1就代表key的第一個字元,集合輸出的是在第x字元下,長度多少可能會產生ascii art,都輸出之後只要找出這39個集合的交集就能夠找出長度,最後找到長度是32。 那接下來助教可能會想,key_sum這個map到底是幹嘛的,原本的程式裡面要驗證我們的輸入對不對是利用xor後的數字來做驗證,若不等於257498就代表我們輸入的key是錯的,那我在找候選的時候順便把各個位置的sum都存起來,到時候再把每個位置的sum加起來看看是不是257498就知道哪個是答案了,但候選實在太多了,所以我就想對這個key_sum做一下排序,讓一定會大於257498的組合直接刪掉,順帶一提,其實這個排序我好像不用自己手刻= = ```cpp //sort for (int i = 0; i < 32; i++) { for (int j = 0; j < key_sum[i][32].size() - 1; j++) { for (int k = 0; k < key_sum[i][32].size() - 1 - j; k++) { if (key_sum[i][32][k] > key_sum[i][32][k + 1]) { int temp = key_sum[i][32][k]; key_sum[i][32][k] = key_sum[i][32][k + 1]; key_sum[i][32][k + 1] = temp; temp = key[i][32][k]; key[i][32][k] = key[i][32][k + 1]; key[i][32][k + 1] = temp; } } } } ``` 然後奇蹟就發生了,排序完將最小的sum都加起來後答案就出來了 ```cpp int sum = 0, index = 0; string temp = "", ans = ""; for (int i = 0; i < 32; i++) { temp += (char)(key[i][32][0]); sum += key_sum[i][32][0]; } //wtf!? sum == 257498 for (int i = 0; i < 6015; i++) { ans += (char)(temp[i % temp.length()] ^ a[i] ^ sudoku[i % 81]); } cout << ans << endl; ``` FLAG ``` FLAG{oh_my_g0d_hoo0ow_did_you_decrypt_this???} ``` ## wishMachine 這題雖然有很多(好像也沒有很多)干擾我們逆向分析的東西,但我覺得抓到感覺後甚至比上題還簡單,我最後發現就是一堆pointer在指來指去,然後有一個超大的陣列存者很多很重要的內容,那跟上題一樣,我先把那個超大陣列的內容存到一個檔案裡面,到時候還原演算法時就能直接去讀它。 ```cpp string filename = "D:\\CTF\\hw\\hw8\\magic.txt"; vector<unsigned int> dword_6D5120; while (file >> t1 >> t2 >> t3 >> t4) { stringstream ss; unsigned int temp = 0; //little endian ss << t4 << t3 << t2 << t1; ss >> std::hex >> temp; dword_6D5120.push_back(temp); } ``` 然後我就先把它debug detect的部分給修掉,用gdb去trace它那些function pointer到底會去call哪些function,就發現其實那些function都超簡單,這題感覺有點難敘述,我就直接上code來解釋,下面這部分的code就是在計算這些pointer要指向哪裡 ```cpp unsigned int dword_8A1070 = 0; unsigned int unk_6D5114 = 0x3f; unsigned int unk_6D5118 = 0x01; unsigned int unk_6D511C = 0x15f2ac48; unsigned int unk_6D5110 = 0x6fb8; unsigned long int unk_6D5100 = 0x3fa21e; func_pointer_map[0x4011D6] = &chec1; func_pointer_map[0x40102D] = &chec2; func_pointer_map[0x401138] = &chec3; func_pointer_map[0x400FBE] = &chec4; func_pointer_map[0x4010C8] = &chec5; for (int i = 0; i < 1000; i++) { for (int j = 0; j < 70; j += var_pointer_map["dword_8A2118"]) { //dword_8A2114 = unk_6D5114 + 10 * dword_8A1070; var_pointer_map["dword_8A2114"] = dword_8A1070 == 0 ? unk_6D5114 : dword_6D5120[-2 + 10 * dword_8A1070 - 1]; //dword_8A2118 = unk_6D5118 + 10 * dword_8A1070; var_pointer_map["dword_8A2118"] = dword_8A1070 == 0 ? unk_6D5118 : dword_6D5120[-1 + 10 * dword_8A1070 - 1]; //dword_8A211C = unk_6D511C + 10 * dword_8A1070; var_pointer_map["dword_8A211C"] = dword_8A1070 == 0 ? unk_6D511C : dword_6D5120[10 * dword_8A1070 - 1]; //dword_8A2120 = dword_6D5120[dword_8A1070 * 10]; var_pointer_map["dword_8A2120"] = dword_6D5120[dword_8A1070 * 10]; //dword_8A2110 = unk_6D5110 + 10 * dword_8A1070; var_pointer_map["dword_8A2110"] = dword_8A1070 == 0 ? unk_6D5110 : dword_6D5120[-3 + 10 * dword_8A1070 - 1]; unsigned long int temp = dword_8A1070 > 0 ? (dword_6D5120[10 * dword_8A1070 - 7] << 64) + dword_6D5120[10 * dword_8A1070 - 8] : 0; qword_8A2100 = dword_8A1070 == 0 ? unk_6D5100 + var_pointer_map["dword_8A2110"] : temp + var_pointer_map["dword_8A2110"]; func_pointer_map[qword_8A2100](); dword_8A1070++; } string coin = input; file1 << coin << endl; } ``` 上面有用到兩個全域變數,是用來模擬IDA中的指標運算 * map<int, void(*)()> func_pointer_map; * map<string, unsigned int> var_pointer_map; 那接下來就是function pointer到底call了甚麼,經過我的trace後我發現它會去call 5個function,那5個function會拿我們的input去做某種運算後去跟存在記憶體裡面的答案去做對比,但因為運算都非常的簡單,能夠利用答案反推回我們的輸入,所以以下的code就是我把這5個function實作出來後反推回輸入的實作 ```cpp void chec1() { for (int i = 0, j; i < var_pointer_map["dword_8A2118"]; i++) { int pos = var_pointer_map["dword_8A2114"] + i; unsigned int ans = i == 0 ? var_pointer_map["dword_8A211C"] : var_pointer_map["dword_8A2120"]; unsigned int f0 = 0, f1 = 1, guess = 0; for (j = 0; guess != ans ; j++) { guess = f0 + f1; f0 = f1; f1 = guess; } input[pos] = (char)j; } } void chec2() { for (int i = 0, j; i < var_pointer_map["dword_8A2118"]; i++) { int pos = var_pointer_map["dword_8A2114"] + i; unsigned int ans = i == 0 ? var_pointer_map["dword_8A211C"] : var_pointer_map["dword_8A2120"]; int guess = 0; for (j = 0; guess != ans; j++) { if (j & 1) guess += 2; else guess += 11; } input[pos] = (char)j; } } void chec3() { for (int i = 0, j; i < var_pointer_map["dword_8A2118"]; i++) { int pos = var_pointer_map["dword_8A2114"] + i; unsigned int ans = i == 0 ? var_pointer_map["dword_8A211C"] : var_pointer_map["dword_8A2120"]; signed int guess = -88035316; for (j = 0; guess != ans; j++) { if (j & 1) guess -= 120; else guess -= 30600; } input[pos] = (char)j; } } void chec4() { for (int i = 0, j; i < var_pointer_map["dword_8A2118"]; i++) { int pos = var_pointer_map["dword_8A2114"] + i; unsigned int ans = i == 0 ? var_pointer_map["dword_8A211C"] : var_pointer_map["dword_8A2120"]; //(unsigned int)(input[pos] * 135) == ans input[pos] = (char)(ans / 135); } } void chec5() { for (int i = 0, j; i < var_pointer_map["dword_8A2118"]; i++) { int pos = var_pointer_map["dword_8A2114"] + i; unsigned int ans = i == 0 ? var_pointer_map["dword_8A211C"] : var_pointer_map["dword_8A2120"]; //(unsigned int)(input[pos] ^ 0x52756279u) == ans input[pos] = (char)(ans ^ 0x52756279u); } } ``` 上面的input一樣是一個全域變數的長度71 char陣列,這些function再配合前面貼的那個pointer運算的code,就能夠成功找到1000個coin,最後我在把它寫進一個檔案裡面,都寫完後把它複製下來貼到題目的程式後就成功解出。 FLAG ``` FLAG{7hes3_func710n_ptrs_g1v3_m3_l0t_0o0of_f4n_I_w4n7_m00r3_11!!l1!|!} ``` [最後在附上我寫進的1000個coin與超大array的檔案連結](https://drive.google.com/drive/folders/1gSZzGJWFdo9g3ornkLY_6pLTApVtoaMp?usp=sharing)