-
-
[推荐]看雪·安恒 2020 KCTF 春季赛 | 第七题设计思路及解析
-
发表于: 2020-5-6 18:25 6549
-


4月28日,金左手战队的 ccfer 率先攻破题目,遥遥领先。
受到激励,其他战队也不甘心落后,紧追猛赶,辣鸡战队、雨落星辰等战队陆续攻破此题。


赛题点评

出题团队简介
本题出题战队 void(0):

团队简介:

设计思路

简介
故事背景简介
春夏秋冬,寒来暑往,日复一日,这人间循环往复空换时光流逝,似乎少了点乐趣。 无所事事便没有好事。 不知从哪儿传出的“巳蛇将出”的谣言,传说巳蛇亦蛇亦弓,入水为蛇,踪迹全无,出水为弓,毙敌百步。竟也有人捕风捉影、信以为真,一传十,十传百,事实变得更加扑朔迷离,人心惶惶。 遂智者言,地势坤、风入松、松如浪,一景一画皆有其意,善用者,良马也。
脱壳
脱壳
VM识别解析
VM识别解析
模拟存储识别
模拟存储识别
1. 代码空间
1. 代码标识 CD 8字节
2. 代码sizeof 8字节
2. 代码大小 8字节
2. 环境寄存器[9]
#define TE 0 //Text End
#define HS 1 //Heap Start
#define HE 2 //Heap End
#define SS 3 //Stack Start
#define TOP 4 //Stack End
//3个指针寄存器
#define IP 5 //指令指针 (指令运行后必然使用 ER[5])
#define SP 6 //栈指针
#define FP 7 //过程调用帧栈指针
...
3. 寄存器[10];
类型 unsigned long long
寄存器[1] - 寄存器[9]
GR[1] - GR[9]
指令解析
指令解析
ps:(有些操作会随着SetProperty属性更改而进行修改)
指令操作 [默认编号0]:LEA(取地址) [默认编号1]:SetProperty 寄存器,属性值(立即数)()更改不同属性的值 [默认编号2]:SetProperty 寄存器,属性值(立即数)()更改不同属性的值 [默认编号3]:LOADDI [默认编号4]:LOADQI [默认编号5]:LOADF4I [默认编号6]:LOADF8I [默认编号7]:LOADB [默认编号8]:LOADW [默认编号9]:LOADD [寄存器],[内存] [默认编号10]:LOADQ [默认编号11]:取地址 [默认编号12]:- [默认编号13]:STOREBI [默认编号14]:STOREWI [默认编号15]:STOREDI[寄存器],立即数 [默认编号16]:STOREQI [默认编号17]:STOREF4I [默认编号18]:STOREF8I [默认编号19]:STORERB [内存][内存] SetProperty 属性为准 [默认编号20]:STORERW [默认编号21]:STORERD [默认编号22]:STORERQ [默认编号23]:-待扩展 [默认编号24]:-待扩展 [默认编号25]:MOVB [内存][内存] [默认编号26]:MOVW [默认编号27]:MOVD [默认编号28]:MOVQ [默认编号29]:-待扩展 [默认编号30]:-待扩展 [默认编号31]:MOVRR [寄存器][寄存器] [默认编号32]:MOVRF4 [默认编号33]:MOVRF8 [默认编号34]:PUSHFP [默认编号35]:PUSHB [默认编号36]:PUSHW [默认编号37]:PUSHD [默认编号38]:PUSHQ [默认编号39]:PUSHF4 [默认编号40]:PUSHF8 [默认编号41]:POPFP [默认编号42]:POPB [默认编号43]:POPW [默认编号44]:POPD [默认编号45]:POPQ [默认编号46]:待扩展 [默认编号47]:待扩展 [默认编号48]:INCR [默认编号49]:DECR [默认编号50]:ADDR [默认编号51]:SUBR [默认编号52]:MULR [默认编号53]:DIVR [默认编号54]:-- [默认编号55]:-- [默认编号56]:-- [默认编号57]:-- [默认编号58]:-- [默认编号59]:-- [默认编号60]:-- [默认编号61]:-- [默认编号62]:AND [默认编号63]:OR [默认编号64]:XOR [默认编号65]:NOT [默认编号66]:SHRA [默认编号67]:SHRL [默认编号68]:SHL [默认编号69]:CALL [默认编号70]:RET [默认编号71]:MOVSF [默认编号72]:LDFPOB [默认编号73]:LDFPOW [默认编号74]:LDFPOD [默认编号75]:LDFPOQ [默认编号76]:JMPI [默认编号77]:JNZ [默认编号78]:JNS [默认编号79]:JNL [默认编号80]:JZ [默认编号81]:JS [默认编号82]:JL [默认编号83]:COMP [默认编号84]:COMPI [默认编号85]:OUTO [默认编号86]:OUTD [默认编号87]:OUTH [默认编号88]:OUTC [默认编号89]:-- [默认编号90]:-- [默认编号91]:NOP
例子strlen:
LEA R1,HELLO MOVRR R3,R1 L1: LOADB R2,R1 JZ END INCR R1 JMPI L1 END: SUBR R1,R3 OUTD R1 HALT ; 字符串 AA AA AA 00 HELLO: DB AA AA AA 00
二进制解析CODE:
unsigned AnsiChar data[70] = { //标识符 0x43, 0x44, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, //code 大小 0x2A, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, //data 数据大小 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //lea 寄存器[1],地址 0x00, 0x01, [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], //MOVRR 寄存器3,寄存器1 0x1F, 0x03, 0x01, //loadb 寄存器2,寄存器1 0x07, 0x02, 0x01, //jz 地址 0x50, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, //INCR 0x30, 0x01, //jmp跳转 0x4C, 0x0D, [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,] //SUBR 寄存器1,寄存器3 0x33, 0x01, 0x03, //OUTD 0x56,0x01, //结束符 0x5C, //数据字符串 0xAA, 0xAA, 0xAA, 0x00 };
(1)非混淆代码会 与外界交互
(2)前面会执行多个pushd 指令 popd指令结束
算法破解
密码算法描述
明文预处理
密钥预处理
加密
其中m为明文的分组组数
R为加密得到的密文数据块
n为输入的明文和密钥个数
offset为加密时生成的随机数
其中n为输入的明文和密钥个数
t为密钥的分组组数
解密
出题
crackme运行过程

解析过程


文章分析的是修复多解的版本。
程序有反调试(检测Context,NtQueryInformationProcess等),有壳,反调试可以用sharpod插件绕过,直接在GetSystemTimeAsFileTime下断运行,回溯到OEP,再手动修IAT,脱壳即可。脱壳以后直接拉IDA分析。
int __cdecl main(int argc, const char **argv, const char **envp) { int v3; // eax int v4; // eax int v5; // et2 int v6; // ecx int result; // eax char *v8; // eax int *v9; // esi int v10; // edi unsigned __int8 v11; // cl unsigned __int8 v12; // cl char v13; // bl unsigned __int8 v14; // bl char v15; // dl unsigned __int8 v16; // dl char v17; // dl unsigned __int8 v18; // dl char v19; // dl unsigned __int8 v20; // dl char v21; // dl unsigned __int8 v22; // dl char v23; // dl unsigned __int8 v24; // dl char v25; // dl unsigned __int8 v26; // dl __int128 *v27; // edi __int64 v28; // kr00_8 int v29; // eax int v30; // ebx char *v31; // esi int v32; // eax char *v33; // ecx int v34; // ecx int v35; // eax int i; // eax bool v37; // zf int v38; // edx int v39; // esi int v40; // ecx int v41; // ecx int v42; // [esp-Ch] [ebp-2F84h] int v43; // [esp-8h] [ebp-2F80h] unsigned __int8 v44; // [esp+10h] [ebp-2F68h] int v45; // [esp+10h] [ebp-2F68h] unsigned __int8 v46; // [esp+14h] [ebp-2F64h] unsigned __int8 v47; // [esp+18h] [ebp-2F60h] unsigned __int8 v48; // [esp+1Ch] [ebp-2F5Ch] unsigned __int8 v49; // [esp+20h] [ebp-2F58h] int v50; // [esp+28h] [ebp-2F50h] int v51; // [esp+2Ch] [ebp-2F4Ch] int v52; // [esp+3Ch] [ebp-2F3Ch] char v53[21]; // [esp+44h] [ebp-2F34h] __int64 v54; // [esp+59h] [ebp-2F1Fh] int v55; // [esp+61h] [ebp-2F17h] __int16 v56; // [esp+65h] [ebp-2F13h] char v57; // [esp+67h] [ebp-2F11h] DWORD vmenv[40]; // [esp+68h] [ebp-2F10h] __int128 v59; // [esp+120h] [ebp-2E58h] __int128 v60; // [esp+130h] [ebp-2E48h] __int64 v61; // [esp+140h] [ebp-2E38h] char v62[80]; // [esp+148h] [ebp-2E30h] char v63[1000]; // [esp+198h] [ebp-2DE0h] _OWORD vmcode[671]; // [esp+580h] [ebp-29F8h] v53[4] = 0; v54 = 0i64; *(_OWORD *)&v53[5] = 0i64; v55 = 0; v56 = 0; v57 = 0; memset(v63, 0, sizeof(v63)); printf(aPleaseInputUse); scanf(aS, &v53[4]); printf(aPleaseInputSer); scanf(aS, v63); initusernamekey(&v53[4]); // username生成usernamekey v3 = strlen(v63); v5 = v3 % 24; v4 = v3 / 24; v6 = v4; v51 = v4; if ( v5 ) { printf(aSorryTheSerial); LABEL_3: system(aPause); result = 0; } else { if ( v4 > 0 ) { v8 = &v63[1]; v50 = v6; v9 = serial; do { v10 = 3; do { v11 = *(v8 - 1); if ( v11 <= 0x39u ) v12 = v11 - 48; else v12 = v11 - 55; v13 = *v8; if ( (unsigned __int8)*v8 <= 0x39u ) v14 = v13 - 48; else v14 = v13 - 55; v15 = v8[1]; if ( (unsigned __int8)v15 <= 0x39u ) v16 = v15 - 48; else v16 = v15 - 55; v44 = v16; v17 = v8[2]; if ( (unsigned __int8)v17 <= 0x39u ) v18 = v17 - 48; else v18 = v17 - 55; v46 = v18; v19 = v8[3]; if ( (unsigned __int8)v19 <= 0x39u ) v20 = v19 - 48; else v20 = v19 - 55; v47 = v20; v21 = v8[4]; if ( (unsigned __int8)v21 <= 0x39u ) v22 = v21 - 48; else v22 = v21 - 55; v48 = v22; v23 = v8[5]; if ( (unsigned __int8)v23 <= 0x39u ) v24 = v23 - 48; else v24 = v23 - 55; v49 = v24; v25 = v8[6]; if ( (unsigned __int8)v25 <= 0x39u ) v26 = v25 - 48; else v26 = v25 - 55; v8 += 8; *v9 = v26 | (16 * (v49 | (16 * (v48 | (16 * (v47 | (16 * (v46 | (16 * (v44 | (16 * (v14 | (16 * v12))))))))))))); ++v9; --v10; } while ( v10 ); --v50; } while ( v50 ); v6 = v51; } // hexstrtodata v27 = &xmmword_45AA50; v52 = v6 - 8; v28 = 4i64 * v6 - 32; v29 = 0; v45 = 0; LABEL_35: v30 = *(_DWORD *)v27; v31 = &aVm[v29]; // 字符串跟username生成的数据拼接到一起,加密 v32 = strlen(&aVm[v29]); v31[v32 + 3] = v30; v33 = &v31[v32]; v33[2] = BYTE1(v30); v33[1] = BYTE2(v30); *v33 = HIBYTE(v30); v33[4] = 0; v34 = 0; v35 = 0; while ( v31[v35] ) { if ( !v31[v35 + 1] ) { ++v34; break; } if ( !v31[v35 + 2] ) { v34 += 2; break; } if ( !v31[v35 + 3] ) { v34 += 3; break; } if ( !v31[v35 + 4] ) { v34 += 4; break; } v35 += 5; v34 += 5; if ( v35 >= 2000 ) break; } encrypt(v31, (int)constkey, v34); initvmcode(vmcode); // 初始化虚拟机代码 vmenv[4] = 0; memset(&vmenv[8], 0, 0x48u); memset(&vmenv[26], 0, 0x50u); v59 = 0i64; v60 = 0i64; v61 = 0i64; memset(v62, 0, sizeof(v62)); initenv((int)vmenv, vmcode, 10737); // 初始化虚拟机环境 vmenv[28] = (DWORD)serial; vmenv[29] = (int)serial >> 31; vmenv[30] = (DWORD)constkey; vmenv[31] = (int)constkey >> 31; vmenv[32] = (DWORD)usernamekey; vmenv[33] = (int)usernamekey >> 31; vmenv[34] = (DWORD)text; // 虚拟机输出 vmenv[35] = (int)text >> 31; vmenv[38] = (DWORD)flagresult; // 虚拟机输出 vmenv[39] = (int)flagresult >> 31; vmstart(vmenv); // 执行虚拟机解密 xordecrypt(v42, v43, v52); // text xor flagresult 结果存放到key encrypt(key, (int)encresult, v28); for ( i = strlen(key) - 1; i > 0; --i ) { v37 = key[i] == (char)0x80; // 末尾的0x80替换为0 key[i] = 0; if ( v37 ) break; } v38 = strlen(key); v39 = 0; v40 = 0; if ( v38 > 0 ) // 比较 { while ( key[v40] == truekey[v45 + v40] ) { if ( ++v40 >= v38 ) goto LABEL_55; } v39 = 1; } LABEL_55: v41 = 0; while ( flagresult[v41] == encresult[v41] ) { ++v41; if ( v41 >= 8 ) { if ( v39 == 1 ) break; if ( vmenv[7] ) j_j_j___free_base((void *)vmenv[7]); v27 = (__int128 *)((char *)v27 + 4); v29 = v45 + 256; v45 += 256; if ( (int)v27 >= 0x45AA5C ) { printf(aCongratulation); goto LABEL_3; } goto LABEL_35; } } printf(aSorryTheSerial); system(aPause); if ( vmenv[7] ) j_j_j___free_base((void *)vmenv[7]); result = 0; } return result; }
(一共三轮加密,分别使用"这杀软好多呀,好像是个VM","这机器里文件修改时间分布广","伊娃找到了理想植物"),再加密生成constkey。
抓取"KCTF"相关的usernamekey和constkey,人肉004016EC处的虚拟机即可。

004016EC的解密流程转写成python大致如下:
def testdecrypt(roundnum): constroundkey = constkey[roundnum] for i in range(5): r = (serial[i * 3] + serial[i * 3 + 1] * constroundkey[i] + ((constroundkey[i]**2) * serial[i * 3 + 2])) % 0xFFFFFFFB r ^= usernamekey[i] text.append(r) for i in range(5, 13): r = (serial[i * 3] + serial[i * 3 + 1] * constroundkey[i % 8] + ((constroundkey[i % 8]**2) * serial[i * 3 + 2])) % 0xFFFFFFFB r ^= usernamekey[i] flagresult.append(r)
text和flagresult均为虚拟机输出,可以在后面的流程找到真正的结果。
修改程序流程,在00401705 下断点,输入KCTF和官方提供的真码,停下后,0045B270替换为正确的text("我是个任务管理器","找包含关键词的文件","赶紧带回去给船长"),末尾的一个"00"要改成"80",步过00401712,在0045A230就能看到flagresult了。

结合"KCTF"的usernamekey和constkey,可以列出下面的方程。
m = flagresult[i]^text[i]^usernamekey[i] (xi + yi * constkey[r][i%8] + zi * constkey[r][i%8]**2) mod 0xFFFFFFFB == m
其中i表示当前位置,r表示轮数,xi=serial[3i],yi=serial[3i+1],zi=serial[3i+2]
(x0 + y0 0x835904E1 + z0 0x835904E1**2) mod 0xFFFFFFFB == 0x1CC4FA98
写代码列出三轮的方程组,python代码如下,测试testdecrypt的代码也包含在里面了。
# KCTF serial = [ 0xe84de727, 0xb4c7223f, 0xc4c8b34f, 0x9d4e4221, 0x225dd4a2, 0x6a95e624, 0xe5eb4526, 0x9de64c0a, 0x9ed50b44, 0xbba723f9, 0x878e4b4d, 0xd8841263, 0x453dc515, 0x7b401d6b, 0x5af4f140, 0x1193faa1, 0x433ada48, 0x145a358a, 0xbce1b843, 0x9c7f5d39, 0x0c31987e, 0x39bb056e, 0x1a21b92b, 0x8de2358e, 0xcec1ff6c, 0x206cf8c1, 0x7c46c891, 0x44ba8da0, 0xec483438, 0x9ffa54b3, 0x2c8d3174, 0xe97ac3c2, 0x024783d0, 0xdfdc2bc9, 0x524b9c81, 0xb40f78f2, 0xe184a49b, 0x2292b4d7, 0x9a58ef0b ] constkey = [[ 0x835904e1, 0xc834944b, 0x027a19e0, 0xfeb308a3, 0x621ff195, 0x4b705c5c, 0x3eb5d9d0, 0x9a5c73f4 ], [ 0xccffa00f, 0x490c46ed, 0xf78be5a1, 0x81a56274, 0x165deb5c, 0xf6f46796, 0x44de5146, 0x00e4984c ], [ 0xdf77dfa8, 0xae0ed8d8, 0x064da354, 0x4c8b95cc, 0xf934ca39, 0xc4e9de04, 0x18ee2793, 0x945ac9c2 ]] usernamekey = [ 0xd9c3e463, 0x11c9af78, 0x6485bf9e, 0xff4bd05d, 0x65769726, 0xf5c38988, 0xbf3a2423, 0x4b718cc0, 0xc70d8f49, 0xdfc73315, 0x74470070, 0x94b89f71, 0x0e60f6b1, 0x2051a122, 0x1f061047, 0x4ced9e38 ] textr = [[3305578235L, 3274609060L, 2824850596L, 382260195L, 2418685203L], [2670343418L, 3143641187L, 3172232216L, 4193180192L, 2540967463L], [3311948515L, 4048345354L, 1943049539L, 810068865L, 2159988552L]] flagresultr = [[198562876L, 2077776234L, 1722709368L, 3592754452L, 271201555L, 1745403541L, 14394681L, 417773631L], [1257800710L, 30183867L, 32037074L, 1277826276L, 730502695L, 1053176226L, 2658935662L, 3990671698L], [2108157719L, 1169449682L, 3145018811L, 2229540901L, 12504904L, 855930893L, 1706174636L, 1402764800L]] def testdecrypt(roundnum): constroundkey = constkey[roundnum] for i in range(5): r = (serial[i * 3] + serial[i * 3 + 1] * constroundkey[i] + ((constroundkey[i]**2) * serial[i * 3 + 2])) % 0xFFFFFFFB r ^= usernamekey[i] text.append(r) for i in range(5, 13): r = (serial[i * 3] + serial[i * 3 + 1] * constroundkey[i % 8] + ((constroundkey[i % 8]**2) * serial[i * 3 + 2])) % 0xFFFFFFFB r ^= usernamekey[i] flagresult.append(r) def testencrypt(roundnum): constroundkey = constkey[roundnum] for i in range(5): r = textr[roundnum][i] ^ usernamekey[i] rc.append(r) for i in range(8): r = flagresultr[roundnum][i] ^ usernamekey[i + 5] rc.append(r) for j in range(3): text = [] flagresult = [] truetext = [] rc = [] testdecrypt(j) for i in range(5): truetext.append(text[i] ^ flagresult[i]) truetextstr = '' for i in truetext: truetextstr += hex(i)[2:-1] # print truetextstr testencrypt(j) for i in range(len(rc)): print ('(x%d + y%d * 16^^%08X + z%d * 16^^%08X ^2) == 16^^%08X ,') % ( i, i, constkey[j][i % 8], i, constkey[j][i % 8], rc[i])

x0 == 3897419559 && x1 == 2639151649 && x10 == 747450740 &&
x11 == 3755748297 && x12 == 3783566491 && x2 == 3857401126 &&
x3 == 3148293113 && x4 == 1161676053 && x5 == 294910625 &&
x6 == 3168909379 && x7 == 968557934 && x8 == 3468820332 &&
x9 == 1153076640 && y0 == 3032949311 && y1 == 576574626 &&
y10 == 3917136834 && y11 == 1380686977 && y12 == 580039895 &&
y2 == 2649115658 && y3 == 2274249549 && y4 == 2067799403 &&
y5 == 1127930440 && y6 == 2625592633 && y7 == 438417707 &&
y8 == 544012481 && y9 == 3964154936 && z0 == 3301487439 &&
z1 == 1788208676 && z10 == 38241232 && z11 == 3020912882 &&
z12 == 2589519627 && z2 == 2664762180 && z3 == 3632534115 &&
z4 == 1526001984 && z5 == 341456266 && z6 == 204576894 &&
z7 == 2380412302 && z8 == 2085013649 && z9 == 2683983027
E84DE727B4C7223FC4C8B34F9D4E4221225DD4A26A95E624E5EB45269DE64C0A9ED50B44BBA723F9878E4B4DD8841263453DC5157B401D6B5AF4F1401193FAA1433ADA48145A358ABCE1B8439C7F5D390C31987E39BB056E1A21B92B8DE2358ECEC1FF6C206CF8C17C46C89144BA8DA0EC4834389FFA54B32C8D3174E97AC3C2024783D0DFDC2BC9524B9C81B40F78F2E184A49B2292B4D79A58EF0B