文章分析的是修复多解的版本。
程序有反调试(检测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; }
程序大致流程为:将serial从hex字符串解码,username加密生成usernamekey,usernamekey的一部分添加到三个字符串的尾部
(一共三轮加密,分别使用"这杀软好多呀,好像是个VM","这机器里文件修改时间分布广","伊娃找到了理想植物"),再加密生成constkey。再把usernamekey,serial,constkey作为vm虚拟机的参数,执行虚拟机解密流程。text跟flagresult进行异或,最后比较text和flagresult是否与预设结果相同。
usernamekey,constkey跟username有关,不过我们只要求出"KCTF"的序列号就行。
抓取"KCTF"相关的usernamekey和constkey,人肉004016EC处的虚拟机即可。
0040E3D6 为vmdispatcher,edi为vmenv,跟踪handler,观察vmenv的变化,就能分析出vm的大致流程了。有些handler有反调试,所以脱壳了也要开着sharpod。
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了。
拿到这三轮解密的flagresult,再异或对应轮数的text(超过text下标的则忽略这步),得到异或前的text,再异或对应位置的usernamekey,得到m。
结合"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]
当i=0,r=0时,方程为:
(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])
输出的方程改改,丢给wolfram
解得
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
整理得到flag
E84DE727B4C7223FC4C8B34F9D4E4221225DD4A26A95E624E5EB45269DE64C0A9ED50B44BBA723F9878E4B4DD8841263453DC5157B401D6B5AF4F1401193FAA1433ADA48145A358ABCE1B8439C7F5D390C31987E39BB056E1A21B92B8DE2358ECEC1FF6C206CF8C17C46C89144BA8DA0EC4834389FFA54B32C8D3174E97AC3C2024783D0DFDC2BC9524B9C81B40F78F2E184A49B2292B4D79A58EF0B
附件是脱壳的程序,方便分析。
[培训]科锐逆向工程师培训班38期--远程教学预课班将于 2020年5月28日 正式开班!
最后于 5小时前 被梦游枪手编辑 ,原因: