KCTF 2021 Q3 声名远扬
👢

KCTF 2021 Q3 声名远扬

Tags
Reverse
CTF
Published
December 3, 2021
Author
Mason
考察点正好是之前做到过的32位至64位模式切换,只不过表现形式有所出入。在C++程序中存在的大量虚函数一定程度上提高了静态分析的难度,算是入门C++逆向的一个切入点。值得学习。
考察C++逆向。
总体思路:动态调试,黑盒测试。
Windows 32位程序,无壳。
注:以下分析如未作特殊说明,默认基址为0x251000
题目存在一些花指令,不多做阐述,nop修复即可。
比较多的虚函数,同时能看到关键词DuiLib
notion image
有关DuiLib能够从网上找到N篇文章介绍切入点,随便挂一个
通过虚函数跳转最终定位到按钮回调函数sub_26D2D0
简单调试分析后,能够发现首先是进行了变表的base64编码,具体位置在0x26E530,伪代码分析特征还是比较明显的
notion image
当然最大的特征当属编码表,其特征位于函数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; }
下面进行验证
动态调试得到编码串
notion image
尝试变表解密
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},验证成功
进入下一步
经过调试分析确定总体逻辑如下图
notion image
单步调试过程中会发现在verify运行至一个段间跳转时出现异常。
notion image
反复调试后,最终发现突破点在于其进行远跳转前,32位至64位模式的切换。
只不过在这里的表现形式与往常有所不同
notion image
关于32位程序至64位程序的切换,先前也发过文章
🥅
东华杯2021大学生网安邀请赛
知晓其模式切换后,强制指定PE64标识
notion image
放入IDA,为方便定位函数,Rebase Segment,基址设为0
notion image
通过调试得到调用函数地址,减去基址后得到偏移量0x146f0
notion image
定位函数,得到伪代码后,看到了函数调用
notion image
汇编下形式为call rdi
notion image
翻找之后能够知道rdi来自于指令mov rdi, [rsp+arg_0]
向上分析
notion image
确定函数偏移为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 = 0 v23 = 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: break print(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 = 44 v28 = b'' for xmm in xmmArr: v28 += int.to_bytes(xmm, length=16, byteorder="little") v28 = bytearray(v28) v4 = 0 while 1: v5 = v4 - 52 v6 = v4 v4 += 1 v28[v6] ^= v5 & 0xff if v4 >= v27: break v28[v27] = 0 print(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())