# 一梯退三步 by IKangarooM
###### tags: `Ghidra` `CTF` `reverse`
## 題目內容
點開題目格~~然後被嘲諷了一番~~後,有個`crackme`的執行檔可以下載,
下載回來之後先以`./crackme`執行看看:

哎呀,又要猜密碼了嗎?~~這個我最會了:D~~
隨便輸入按下Enter試試:

嗚嗚,被打槍惹yo。(~~謎之音:猜這什麼鬼?~~)
既然不是隨便猜就能對的話,我們就得先反觀察一下這個執行檔了...
---
## 解題過程
### 1. 以string看看執行檔內容
執行`strings crackme`後會得到一連串的程式細節,其中我們發現了以下這一段內容:

出現了!剛剛的==Input your password==跟==Don't touch me!==,以及一個非常關鍵的句子:==You get the flag!==
因此我們先大膽猜測這附近看起來不像是系統程序的字串有可能就是密碼,先後輸入了==AWAVA==、==AUATL==、==\[\]A\A]A^A_== 以及 ==;\*3$"==,然而...

全數慘遭打槍。可惡!
沒有一個是You get the flag ~~,還敢出來啊冰鳥~~!
但正因為有You get the flag於strings的結果中,因此我們認為或許祕密被藏在更細的地方,例如說++執行檔的組合語言++中,~~看來只能使用那一招了呢...翻開覆蓋的陷阱卡!++Ghidra++!~~
---
### 2. 以Ghidra反組譯執行檔
把`crackme`丟進Ghidra後,得到如下的畫面:

由於程式的第一個執行點應該是在main函數中,因此我們先透過左邊的Symbol tree快速切到main函數所在位置:

接著快速的往下滑動,找到字串==You get the flag!== 的同時,我們也發現了上面呼叫了一個不認識的函數==challenge==。
將右邊的Decompile視窗開啟,觀察一下函數內容:
```C
undefined8 main(void)
{
int iVar1;
long in_FS_OFFSET;
undefined local_38 [40];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
setbuf(stdin,(char *)0x0);
setbuf(stdout,(char *)0x0);
setbuf(stderr,(char *)0x0);
printf("Input your password:");
__isoc99_scanf(&DAT_004008f9,local_38);
iVar1 = challenge(local_38);
if (iVar1 == 0) {
puts("\n\nDon\'t touch me!");
}
else {
puts("\n\nYou get the flag!");
}
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return 0;
}
```
嗯?我們input的數字local_38被抓到challenge這個函數裡做運算了!而且回傳為0的話就會被打槍!
為了破解這個謎團,我們透過Symbol Tree來到challenge裡面,並觀察decompile後的程式碼:
```C
undefined8 challenge(long lParm1)
{
uint local_c;
local_c = 0;
while( true ) {
if (0x11 < local_c) {
return 1;
}
if (((int)*(char *)(lParm1 + (long)(int)local_c) ^ local_c) !=
*(uint *)(secret + (long)(int)local_c * 4)) break;
local_c = local_c + 1;
}
return 0;
}
```
什...什麼!有個看起來非常複雜的東西要處理啊!
仔細觀察一下,想要成功返回1的條件是要讓起始值為0的local_c大於0x11,在這邊0x11應為十六進制的17,換言之==要讓這個迴圈跑18次才有可能得到flag==。
那麼,什麼情況下迴圈不會跑滿18次呢?
我們發現第13行有個break,且break的下一行local_c才會+1進入下一圈,因此我們的任務變成==要想辦法不讓break發生==,因此我們回過頭來看看這一行:
```C
if (((int)*(char *)(lParm1 + (long)(int)local_c) ^ local_c) !=
*(uint *)(secret + (long)(int)local_c * 4)) break;
```
lParm1指的是我們給的Input,而local_c指的是一個介在0~18的值,經過一些化簡步驟後,我們得到下面這個句子:
```C
if(lParm1[local_c] ^ local_c != secret[local_c]) break;
```
看起來簡單多了!但問題來了:==這個secret,是什麼東西啦\=A\===
讓我們再回去Ghidra找找,我們找到Symbol Tree裡的secret,他的bytes code如右所示:

```
45 00 00 00 44 00 00 00 4c 00 00 00 50 00 00 00 7f 00 00 00 6f 00 00 00 6f 00 00 00 34 00 00 00 3a 00 00 00 62 00 00 00 3d 00 00 00 6a 00 00 00 79 00 00 00 39 00 00 00 6f 00 00 00 37 00 00 00 23 00 00 00 6c 00 00 00
```
這...這玩意看得我好眼熟啊...八個數字一組的東西,難道不就是十六進制的數字表示法(eg. 0xffffffff)嗎!?
再仔細往下看,有18組這樣的數對,果然不錯!看來這就是上面提到要比對長度為18的secret了!
但是...這貨的寫法...==是Little Endian還是Big Endian呢...?==
為了確保Endian的正確性,我們用了`readelf -h crackme`去翻找內容,果然讓我們找到了:

確定是little endian後,我們就可以確定他的寫法是==由前至後==,因此也可以開始把這些字轉回去了。
回憶上面我們簡化過的code,由於程式還有把值做了local_c次方的計算,我們乾脆手動寫了一個簡單的程式`secret.c`:
```C
#include <stdio.h>
int main(){
unsigned int sec[18] = {0x45, 0x44, 0x4c, 0x50, 0x7f, 0x6f, 0x6f, 0x34, 0x3a, 0x62, 0x3d, 0x6a, 0x79, 0x39, 0x6f, 0x37, 0x23, 0x6c};
for(int i=0;i<18;i++){
printf("%c", sec[i]^i);
}
printf("\n");
return 0;
}
```
執行完成後會印出一串神奇的字串,~~然後這個字串令人意外的是竟然開頭是EENS...~~
將這個字串再次塞回到`crackme`裡面,我們就解決這個謎團了!
