看雪·眾安 2021 KCTF秋季賽的第七題《聲名遠揚》已於今天中午12點截止答題,經統計,本題圍觀人數多達1179人,共計20支戰隊成功破解。
恭喜金左手用時4002秒拿下“一血”,接下來和我一起來看看該賽題的設計思路和相關解析吧~
出題團隊簡介
第七題《聲名遠揚》出題方 洋洋不得意 戰隊:
賽題設計思路
在win64中,程式碼段暫存器0x23和0x33所對應的GDT表項中CPU的模式分別為32位與64位。
具體原理參考連結:https://bbs.pediy.com/thread-221236.htm
設計思路:
1、exe程式編譯為32位程式,把核心判斷程式碼放入64位程式碼中。
2、加密用base64改了一下編碼表,編碼表特別好找,單步跟就能看到。
3、校驗flag的流程是線性的,沒有反除錯,加了一丟丟的垃圾指令(可能你們都注意不到)。只要找到onclick函式,一路單步就可以看到輸出的結果。
4、把關鍵字串隱藏起來,執行時解密出來。比如:"正確","錯誤",flag編碼後的字串。隱藏字串演算法是異或。
流程如下:
1、把輸入的flag進行base64編碼;
2、把編碼結果丟到64位程式碼中對比,接收對比結果;
3、把對比結果輸出。
破解思路:
1、找到64位程式碼中flag加密後的資料。
2、找到base64編碼表。
3、透過base64演算法還原flag。
賽題解析
本賽題解析由看雪論壇sunfishi給出:
考察C++逆向。
總體思路:動態除錯,黑盒測試。
Windows 32位程式,無殼。
注:以下分析如未作特殊說明,預設基址為0x251000。
題目存在一些花指令,不多做闡述,nop修復即可。
比較多的虛擬函式,同時能看到關鍵詞DuiLib。
有關DuiLib能夠從網上找到N篇文章介紹切入點,隨便掛一個。
Dump微信PC端的介面Duilib檔案-軟體逆向-看雪論壇-安全社群|安全招聘|bbs.pediy.com(https://bbs.pediy.com/thread-259443.htm)
透過虛擬函式跳轉最終定位到按鈕回撥函式sub_26D2D0。
簡單除錯分析後,能夠發現首先是進行了變表的base64編碼,具體位置在0x26E530,虛擬碼分析特徵還是比較明顯的。
當然最大的特徵當屬編碼表,其特徵位於函式0x26E250,虛擬碼如下:
int __cdecl base64Maps(int a1){ int *v1; // eax char v3[8]; // [esp+4h] [ebp-68h] BYREF int v4; // [esp+Ch] [ebp-60h] _BYTE base64[65]; // [esp+10h] [ebp-5Ch] BYREF unsigned int v6[2]; // [esp+51h] [ebp-1Bh] BYREF int v7; // [esp+68h] [ebp-4h] v4 = 0; sub_26D5D0((char *)v6 + 3, 8u); qmemcpy(base64, "prvo9CHSJOcPIb6xRVUXQz0qBGDE72LNZduaefYT5K_8-4FAhlimjkngt1yMWs3w!", sizeof(base64)); v1 = (int *)__FrameHandler3::TryBlockMap::TryBlockMap( (__FrameHandler3::TryBlockMap *)v3, (const struct _s_FuncInfo *)base64, (unsigned int)v6); sub_26EA90((char *)v6 + 3, *v1, v1[1]); v7 = 0; sub_26EAD0((void *)a1, (int)v6 + 3); v4 |= 1u; v7 = -1; sub_26EA70(); return a1;}
下面進行驗證。
動態除錯得到編碼串:
嘗試變表解密:
import base64 baseMaps = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="newMaps = "prvo9CHSJOcPIb6xRVUXQz0qBGDE72LNZduaefYT5K_8-4FAhlimjkngt1yMWs3w!" cipherText = "EHsnG0bjGT44BqIhETj!"cipherText = cipherText.translate(cipherText.maketrans(newMaps, baseMaps))print(base64.b64decode(cipherText.encode('utf-8')).decode())
解得lovectf{mas0n},驗證成功。
進入下一步。
經過除錯分析確定總體邏輯如下圖:
單步除錯過程中會發現在verify執行至一個段間跳轉時出現異常。
反覆除錯後,最終發現突破點在於其進行原跳轉前,32位至64位模式的切換。
只不過在這裡的表現形式與往常有所不同。
關於32位程式至64位程式的切換,可以參考文章:
CTF中32位程式呼叫64位程式碼的逆向方法 - 安全客,安全資訊平臺 (anquanke.com)(https://www.anquanke.com/post/id/171111)
知曉其模式切換後,強制指定PE64標識。
放入IDA,為方便定位函式,Rebase Segment,基址設為0。
透過除錯得到呼叫函式地址,減去基址後得到偏移量0x146f0。
定位函式,得到虛擬碼後,看到了函式呼叫。
彙編下形式為call rdi。
翻找之後能夠知道rdi來自於指令mov rdi, [rsp+arg_0]。
向上分析
確定函式偏移為0x145AC。
簡單分析虛擬碼,結合程式碼複用,能夠確定其check邏輯如下:
__int64 __fastcall sub_145AC(char *a1, __int64 a2){ unsigned int v4; // edx char v5; // al __int64 v6; // rcx int v7; // edx char v8; // al char *v9; // rcx __int64 v10; // rax unsigned int v11; // edx char v12; // al __int64 v13; // rcx char v14; // cl __int64 v15; // r8 int *v16; // rax char v17; // al __int64 v18; // rcx char v19; // cl __int64 v20; // r8 int *v21; // rax int v23; // [rsp+4h] [rbp-3Ch] BYREF char v24; // [rsp+8h] [rbp-38h] __int16 v25; // [rsp+9h] [rbp-37h] char v26; // [rsp+Bh] [rbp-35h] unsigned int v27; // [rsp+Ch] [rbp-34h] char v28[48]; // [rsp+10h] [rbp-30h] BYREF v27 = 44; v4 = 0; *(__m128i *)v28 = _mm_load_si128((const __m128i *)&xmmword_14408); *(__m128i *)&v28[32] = _mm_load_si128((const __m128i *)&xmmword_143F8); *(__m128i *)&v28[16] = _mm_load_si128(xmmword_14418); do { v5 = v4 - 52; v6 = v4++; v28[v6] ^= v5; } while ( v4 < v27 ); // 還原base64編碼串 v28[v27] = 0; v7 = 0; v8 = *a1; if ( *a1 ) { v9 = a1; do { if ( v8 != v9[v28 - a1] ) // strcmp break; ++v9; ++v7; v8 = *v9; } while ( *v9 ); } v10 = v7; v11 = 0; v24 = -48; if ( a1[v10] == v28[v10] ) // bingo { v23 = 0x78063019; // 正確 v25 = 0; v26 = 0; do { v12 = v11 - 52; v13 = v11++; *((_BYTE *)&v23 + v13) ^= v12; } while ( v11 < 4 ); v24 = 0; v14 = v23; if ( (_BYTE)v23 ) { v15 = a2 - (_QWORD)&v23; v16 = &v23; do { *((_BYTE *)v16 + v15) = v14; v16 = (int *)((char *)v16 + 1); v14 = *(_BYTE *)v16; } while ( *(_BYTE *)v16 ); } } else { v23 = 0x3C002078; // 錯誤 v25 = 0; v26 = 0; do { v17 = v11 - 52; v18 = v11++; *((_BYTE *)&v23 + v18) ^= v17; } while ( v11 < 4 ); v24 = 0; v19 = v23; if ( (_BYTE)v23 ) { v20 = a2 - (_QWORD)&v23; v21 = &v23; do { *((_BYTE *)v21 + v20) = v19; v21 = (int *)((char *)v21 + 1); v19 = *(_BYTE *)v21; } while ( *(_BYTE *)v21 ); } } return 0i64;}
簡單異或還原明文驗證猜想。
"""v23 = 0x78063019; v25 = 0; v26 = 0; do { v12 = v11 - 52; v13 = v11++; *((_BYTE *)&v23 + v13) ^= v12; } while ( v11 < 4 );""" v11 = 0v23 = bytearray(int.to_bytes(0x78063019, length=4, byteorder="little")) while 1: v12 = v11 - 52 v13 = v11 v11 += 1 v23[v13] ^= v12 & 0xff if v11 >= 4: breakprint(v23.decode('gbk'))# 正確
最終解題指令碼如下:
import base64 """ v27 = 44; v4 = 0; *(__m128i *)v28 = _mm_load_si128((const __m128i *)&xmmword_15408); *(__m128i *)&v28[32] = _mm_load_si128((const __m128i *)&xmmword_153F8); *(__m128i *)&v28[16] = _mm_load_si128(xmmword_15418); do { v5 = v4 - 52; v6 = v4++; v28[v6] ^= v5; } while ( v4 < v27 ); v28[v27] = 0;""" xmmArr = [0x0B3E38188BB9CBA9DBAFFB697ABA2948B, 0x0BFDBD9AAD6D4BCA1878490B0B5AE858C, 0x0F8D6D7A7BAB89480B78A94B9AE]v27 = 44v28 = b''for xmm in xmmArr: v28 += int.to_bytes(xmm, length=16, byteorder="little")v28 = bytearray(v28) v4 = 0while 1: v5 = v4 - 52 v6 = v4 v4 += 1 v28[v6] ^= v5 & 0xff if v4 >= v27: breakv28[v27] = 0print(v28)cipherText = v28.decode() baseMaps = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="newMaps = "prvo9CHSJOcPIb6xRVUXQz0qBGDE72LNZduaefYT5K_8-4FAhlimjkngt1yMWs3w!" cipherText = cipherText.translate(cipherText.maketrans(newMaps, baseMaps))print(base64.b64decode(cipherText.encode('utf-8')).decode())
往期解析
1、看雪·眾安 2021 KCTF 秋季賽 | 第二題設計思路及解析
2、看雪·眾安 2021 KCTF 秋季賽 | 第三題設計思路及解析
3、看雪·眾安 2021 KCTF 秋季賽 | 第四題設計思路及解析
4、看雪·眾安 2021 KCTF 秋季賽 | 第五題設計思路及解析
5、看雪·眾安 2021 KCTF 秋季賽 | 第六題設計思路及解析
第八題《群狼環伺》正在火熱進行中,
還在等什麼,快來參賽吧!
- End -
公眾號ID:ikanxue
官方微博:看雪安全
商務合作:wsc@kanxue.com