前言
GKCTF 2021所以題目均以開源,下面所說的一切思路可以自行通過源碼對比IDA進行驗證。
Github項目地址:https://github.com/w4nd3r-0/GKCTF2021
出題及解題思路
QQQQT
Enigma Virtual Box打包的QT程序,可以解包其實也可以不解包,因為這里並沒有對字符串做隱藏,按鈕事件函數可以很塊通過flag字符串定位,按根據鈕事件的邏輯也十分好猜,base系列加密特征明顯,對base系列算法稍有了解的即可識別。若還是無法識別,建議查看一些base系列算法C實現,自行積累一些識別特征的方法。
最終是用了一個base58進行加密,加密字符串也是寫臉上,可以直接百度或谷歌在線網站解密即可。
flag: flag{12t4tww3r5e77}
PS:學弟出的這個題目沒有對字符串做隱藏,使得選手不用了解QT任何機制就能解出來。
Crash
可根據字符串信息也可根據段gopclntab判別是golang elf程序。符號表可以在IDA7.5中通過IDAGolanHelper(該插件近一個月有更新,可以支持高版本go符號還原)還原符號。
有了符號信息后靜態審代碼就非常清晰明了,用了3DES CBC, SHA256, SHA512, MD5,對四部分數據進行驗證,而對應的密文也都可以簡單的提取出來。
3DES CBC的密鑰為一個txt文件,利用golang新版特性附加到來了二進制文件中,可以方便的找到,因此直接解密即可。
hash系列函數都是4字節爆破,用python的itertools可以快速爆破。
from Crypto.Cipher import DES3
import base64
import itertools
import string
import hashlib
def des3_cbc_decrypt(secret_key, secret_value, iv):
unpad = lambda s: s[0:-ord(s[-1])]
res = DES3.new(secret_key.encode("utf-8"), DES3.MODE_CBC, iv)
base64_decrypted = base64.b64decode(secret_value.encode("utf-8"))
encrypt_text = res.decrypt(base64_decrypted)
result = unpad(encrypt_text.decode())
return result
def sha256crash(sha256enc):
code = ''
strlist = itertools.product(string.ascii_letters + string.digits, repeat=4)
for i in strlist:
code = i[0] + i[1] + i[2] + i[3]
encinfo = hashlib.sha256(code.encode()).hexdigest()
if encinfo == sha256enc:
return code
break
def sha512crash(sha256enc):
code = ''
strlist = itertools.product(string.ascii_letters + string.digits, repeat=4)
for i in strlist:
code = i[0] + i[1] + i[2] + i[3]
encinfo = hashlib.sha512(code.encode()).hexdigest()
if encinfo == sha256enc:
return code
break
def md5crash(sha256enc):
code = ''
strlist = itertools.product(string.ascii_letters + string.digits, repeat=4)
for i in strlist:
code = i[0] + i[1] + i[2] + i[3]
encinfo = hashlib.md5(code.encode()).hexdigest()
if encinfo == sha256enc:
return code
break
if __name__ == '__main__':
key = "WelcomeToTheGKCTF2021XXX"
iv = b"1Ssecret"
cipher = "o/aWPjNNxMPZDnJlNp0zK5+NLPC4Tv6kqdJqjkL0XkA="
part1 = des3_cbc_decrypt(key,cipher,iv)
part2 = sha256crash("6e2b55c78937d63490b4b26ab3ac3cb54df4c5ca7d60012c13d2d1234a732b74")
part3 = sha512crash("6500fe72abcab63d87f213d2218b0ee086a1828188439ca485a1a40968fd272865d5ca4d5ef5a651270a52ff952d955c9b757caae1ecce804582ae78f87fa3c9")
part4 = md5crash("ff6e2fd78aca4736037258f0ede4ecf0")
flag = "GKCTF{" + part1 + part2 + part3 + part4 + "}"
# GKCTF{87f645e9-b628-412f-9d7a-e402f20af940}
print (flag)
app-debug
這個題目比賽過程中出來一些情況,這里對造成不便的師傅說一聲道歉。
安卓逆向題目,主要驗證邏輯在native層,因此直接進入native層即可。對輸入進行tea加密驗證,delta是0x458BCD42,並且有利用TracerPid的反調試,當發現調試器時,會使用假的key,只要沒有檢測到調試器才會使用真key。
因為key是一個全局變量可以通過引用找到在哪里替換為了真key,再找到比對的密文后即可進行解密函數的編寫。
#include <stdio.h>
#include <stdint.h>
void TeaDecode(uint32_t* v, uint32_t* k) {
uint32_t delta=0x458BCD42;
uint32_t v0=v[0], v1=v[1], sum=delta*32, i;
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3];
for (i=0; i<32; i++) {
v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
sum -= delta;
}
v[0]=v0; v[1]=v1;
}
int main()
{
uint32_t enc[]={4121530355,2719511459, 0};
uint32_t key[] = {9, 7, 8, 6};
TeaDecode(enc, key);
puts(enc); //GKcTFg0
return 0;
}
KillerAid
程序結構分為兩部分,一個C# 前端GUI,用於獲取ID和Code,並將ID用Code進行循環異或處理,最后比對ID。需要獲得ID的前提必須有正確的Code,因此必須先解出Code。
Code的驗證邏輯在一個用C++編寫的KillerAid.Core.dll中。Core.dll中主要由兩部分組成,一部分是用於反調試的檢測代碼,一部分是基於AES-CBC的一個簡單對稱加密體系算法。
反調試檢測代碼主要有三部分,一部分是利用WIN32 API以及一些Windows下比較常規的反調試技巧;另一部分是通過便於隱藏特征的動態CRC32算法對ntdll、Core.dll、exe的代碼段進行冗余碼校驗;最后一部分是,基於xd4d大佬一篇解析Net內核調試機制的C++代碼實現方案,用於切斷.net 內核調試線程(即殺死調試線程實例)與dnSpy和IDA這類托管調試器的通信。
調試啟動的手段通過C++ 語言機制,用於一個全局委托類進行構造,它會在dll被加載時,十分早的調用委托類的構造函數,而所以反調試手段都是通過調用win32 API創建一個反調試線程進行檢測。
這期為了實現這些功能所調用的所有Win 32 api以及一些native api都是定義成為函數指針集成到一個代理類中,調用進行動態函數地址獲取,可以比較有效隱藏win32 api的調用以及一部分抗靜態分析的效果。
由於反調試比較多,雖然可找到一個反調試的主調用函數,進行文件patch但最簡單的方法仍然是將反調試線程掛起。但沒有做好的一點是動態crc32的調用時機相對於exe的運行時機來說還是太滯后了,后面想考慮加一個隱蔽的使用文件CRC32進行檢測,但由於時間原因並沒有加上。
至於加密函數的設計,是一個基於AES-CBC模式設計的簡單對稱加密體系,具體設計如下圖所示:
具體的解密部分並沒有寫多少,但可以參照AES的解密思路,一個道理,這里不多做贅述。
AES算法的初始key和iv向量都是定義為全局變量,並使用rand函數動態獲取。由於iv向量是定位全局變量,dll一加載即有數據了,所以這里即便沒過反調試,也可通過實際加載的ImageBase計算偏移獲得key和iv向量的地址(PE的知識),提取數據即可。
至於解密函數,可在源碼中dllmain.cpp中找到,當然這里也貼出來了。
#ifdef _DEBUG
uint8_t* AES_DecryptPro(uint8_t* encData, size_t sizeofData, uint8_t* key, uint8_t* iv, uint32_t rounds)
{
uint8_t* Ivs = nullptr;
uint8_t* Keys = nullptr;
struct AES_ctx ctx;
Ivs = new uint8_t[rounds * AES_BLOCKLEN]();
Keys = new uint8_t[rounds * AES_BLOCKLEN]();
// 迭代出所有 k 與 iv
for (size_t i = 0; i < rounds; i++)
{
memcpy(Ivs + i * AES_BLOCKLEN, iv, AES_BLOCKLEN);
memcpy(Keys + i * AES_BLOCKLEN, key, AES_BLOCKLEN);
// 對 iv 進行 sbox 替代 后 k 用 iv 異或更新
SubBytes((state_t*)iv);
XorWithIv(key, iv);
// 對 k 進行 sbox 替代 后 iv 用 k 異或更新
SubBytes((state_t*)key);
XorWithKey(iv, key);
}
// 解密流
for (size_t i = 1; i <= rounds; i++)
{
key = (uint8_t*)(Keys + (rounds - i) * AES_BLOCKLEN);
iv = (uint8_t*)(Ivs + (rounds - i) * AES_BLOCKLEN);
AES_init_ctx_iv(&ctx, key, iv);
AES_CBC_decrypt_buffer(&ctx, encData, sizeofData);
}
delete[] Ivs;
delete[] Keys;
return encData;
}
#define DecryptPro(encData, sizeofData, key, iv, rounds) AES_DecryptPro(encData, sizeofData, key, iv, rounds)
#else
#define DecryptPro(encData, sizeofData, key, iv, rounds)
#endif // _DEBUG
SoMuchCode
這個題目的混淆思路十分簡單,,即再真實邏輯中插入大量的有引用的垃圾代碼,用來將真實的邏輯變得更加復雜難看,其實從CFG圖中可以看出,並沒有任何復雜分支,基本是一條流程走到底,而具體垃圾代碼的插入的實現思路是使用編譯器預處理的宏展開機制進行的。
程序的原始邏輯十分簡單,獲取輸入,進行xxtea加密,與密文比較。
本題希望選手能夠在大量的垃圾邏輯中抓住關鍵部分進行逆向,因此xxtea加密函數中進行一部分混淆,但關鍵特征並沒有進行混淆,只要通過長長的程序流程追蹤到了加密函數,即可識別出是xxtea加密,后續解題思路也會明了起來。
具體解密函數可參考源碼給出的實現。