---
# System prepended metadata

title: HW8
tags: [資訊安全實務]

---

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