一道簡單Chacha20_RC4算法CTF題目

前言
本道逆向題涉及的知識點如下:
- 反調試
- 花指令
- chacha20加密
- rc4加密
這是今年12月份幫一朋友做的一道CTF題,看題目描述是某春秋平臺的,做這道題也花了2小時,因為以前沒遇到過chacha20加密,做題的時在論壇也沒有搜到chacha20算法,故而寫一篇文章記錄一下,供大家參考。
首先我們觀察下這道題目,解壓后如下,看樣子加密后的flag就在flag.enc中:

按照做題慣例,先查個殼,發現就是普通的32位程序,使用VC++編譯器編譯,沒殼:

我們直接拖到IDA中進行靜態分析。
靜態分析
初步函數流程的分析
進去之后,沒什么好說的,直接找main函數按F5開始分析整個程序邏輯;我們先大略的分析一下,見下圖注釋,函數名都還沒有修改,都是IDA自動生成的。

如上注釋,通過初步的分析,我們大致了解到,整個程序就是將flag讀入,然后經過中間一系列未識別到的函數,最后把文件改名為flag.enc并將加密后的內容覆蓋輸出到flag.enc。要注意在上圖的第23行有一個全局變量的字節序列,這個序列是在函數sub_401610生成的,這里的內容我們最后再分析,接下來先分析最主要的中間那三個函數。
加花函數的分析與還原
我們看到在22行,有一個名為loc_401450的函數,我們點進去看看為什么該函數沒有被IDA正確識別,如下圖:

點進去之后,發現左邊的地址部分一片紅,有經驗的逆向人員一看0x401468~0x40146C這部分,就是明顯的花指令特征,在地址0x40146A處的跳轉無論如何都會跳轉到0x40146F處,使得IDA未能識別這種跳轉破壞了函數的棧幀,因此IDA沒有將該部分正確識別為函數。
看過我另一篇文章的大家應該清楚花指令的還原,也可以用腳本,但是這里的花指令不多,故而我們直接手動來快速還原。將光標定位在地址0x40146E,然后直接按鍵盤上的D,即可將該部分轉換為數據。

彈出如下框,這是在IDA在質問我們人類智慧的一個警告,直接Yes就可以。

然后在正確的代碼地址0x40146F處按C鍵,使得該處的數據轉變為正確的代碼。

此外,由于0x40146A~0x40146E都是人工添加的無用代碼,我們可以直接將該部分數據全部轉為空指令nop,即0x90,如下圖:

將這5個無用的字節替換為0x90,然后點OK。

至此,上述去花操作完成。
然后,我們選中整個函數的部分0x401450~0x401566,然后按快捷鍵P,讓整個函數能被IDA正確識別。此時我們再次回到main函數的偽代碼窗口,看到該部分函數已經被正確識別了,如下圖:

剩下的loc_401940和loc_401AA0處的函數還原,和上述處理方法相同,需要注意的是函數的結尾一般是以retn結束。
chacha20算法的分析
接下來我們雙擊點進去sub_401450,開始分析該函數的算法,進去之后也是一臉茫然,看不明白;

繼續點進去sub_401380函數看看,我們發現其中有這樣一個字符串expand 32-byte k,如下圖:

經過一番搜索,才知道這個加密函數是chacha20加密,找到了這個算法的C代碼實現,https://github.com/shiffthq/chacha20,算法大致先進行初始化,矩陣置換,然后再是輪函數,最后生成了密鑰流,以下是被調用加/解密函數接口:
void ChaCha20XOR(uint8_t key[32], uint32_t counter, uint8_t nonce[12], uint8_t *in, uint8_t *out, int inlen) { int i, j; uint32_t s[16]; uint8_t block[64]; //static void chacha20_init_state(uint32_t s[16], uint8_t key[32], uint32_t counter, uint8_t nonce[12]) chacha20_init_state(s, key, counter, nonce); for (i = 0; i < inlen; i += 64) { //static void chacha20_block(uint32_t in[16], uint8_t out[64], int num_rounds) chacha20_block(s, block, 20); s[12]++; for (j = i; j < i + 64; j++) { if (j >= inlen) { break; } out[j] = in[j] ^ block[j - i]; } }}
經過對比,我們發現這個函數就是chacha20的加解密算法,對比我們找到的源碼,把這個算法拿過來稍微改改,只要把原來函數的in[j]參數直接換作是out[j]即可和題目一樣,該參數既當作輸入又當作輸出。
注:我們使用的話,將github源碼下載下來,把cpp和h文件導入即可;ChaCha20是一種流密碼,可以將其理解為對稱加密算法。
RC4算法
然后剩下的兩個函數,如果有一定逆向題目積累的話,就不難猜測出這是RC4流密碼。該算法先是對S盒的一個初始化,然后進行加解密操作,對該密碼算法的詳情可參考文末附帶的鏈接。
分析隨機數生成序列
加解密算法分析完了,接下來我們呼應一下本題的題目名稱Random,看看前邊遺留的sub_401610()函數,該函數生成了一個加密密鑰,如下圖:

我們發現這是一個偽隨機生成的,關鍵是要知道其偽隨機生成的種子Seed,v0是根據當前時間的時間戳生成的,所以本題的一個坑點應該是在這里,當前的進程ID我可以猜測他的區間為1~9000。
做題的時候,剛開始我以為時間戳就是flag.enc文件的時間戳,后來發現怎么都出不來,于是從題目的出題時間開始算起(也就是? 2022?年?9?月?11?日,??23:22:02),寫了一個爆破,由于流密碼的速度非常快,所以我很快就爆破出來了。
寫wp代碼
根據以上分析,我們寫出其主要的wp代碼,完整wp代碼我放在了文末的附件,大家打開就能運行。
void get_flag(unsigned char* mykey, int v0, int pid){ unsigned char s[256] = { 0 }; unsigned char key[12] = "Encrypted!!"; char hexData[48] = { 0xFC, 0xD4, 0x19, 0x74, 0x51, 0x67, 0xED, 0x4B, 0x9C, 0x48, 0xC6, 0x5F, 0x9B, 0x5D, 0xB4, 0xF0, 0x44, 0x02, 0xAF, 0xAC, 0x66, 0x01, 0x06, 0xA5, 0xBE, 0xBC, 0xD0, 0x77, 0x29, 0x64, 0x8D, 0x5E, 0x41, 0xD4, 0x77, 0x31, 0x40, 0xB4, 0x92, 0x22, 0xF9, 0x9F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; //flag.enc字節序列 int enc_len = strlen(hexData); rc4_init(s, key, strlen((const char *)key)); rc4_crypt(s, (uint8_t *)hexData, enc_len); ChaCha20XOR((uint8_t *)mykey, 1, key, (uint8_t *)hexData, strlen(hexData)); if (hexData[0] == 'f' && hexData[1] == 'l' && hexData[2] == 'a') { //判定前三個字母是fla輸出即可 printf("timestamp:%d,pid:%d ", v0, pid); for (int i = 0; i < 48; i++){ printf("%c", hexData[i]); } printf(""); exit(0); }} int main() { unsigned char mykey[32]; int timestamp; DWORD Seed; timestamp = 1662973302; // time(0); 2022-09-12 17:01:42 for (int pid = 1; pid < 9000; pid++){ for (timestamp = 1662909722; timestamp <= 1662973302; timestamp++) { //最坑的點在這里,時間戳要從出題時間點開始算起 Seed = timestamp ^ pid; srand(Seed); for (int i = 0; i < 32; ++i) mykey[i] = (unsigned __int16)rand() >> 8; get_flag(mykey, timestamp, pid); //傳入timestamp和pid純屬好奇 } } printf("end"); return 0;}
解得flag
為了防止該題目再次出現,flag我就不以文本形式展現了。

小結
另外本文中對另外一個反調試的函數沒有進行過多分析,這類文章很多,大家搜一下就知道了,繞過方式也很簡單。因為本題目的難度還沒有用到動態分析。此外,雖是一道CTF題目,但是其中包含的反調試、ChaCha20、RC4流密碼、花指令以及函數的識別,也值得我們多多去學習。