---
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)