首页
论坛
课程
招聘
[原创]2020KCTF春季赛 第七题 杯弓蛇影 WP
2020-4-30 14:08 4274

[原创]2020KCTF春季赛 第七题 杯弓蛇影 WP

2020-4-30 14:08
4274

文章分析的是修复多解的版本。
程序有反调试(检测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
附件是脱壳的程序,方便分析。


[看雪官方培训] Unicorn Trace还原Ollvm算法!《安卓高级研修班》2021年6月班火热招生!!

最后于 2020-4-30 14:10 被梦游枪手编辑 ,原因:
上传的附件:
收藏
点赞3
打赏
分享
最新回复 (1)
雪    币: 672
活跃值: 活跃值 (1131)
能力值: ( LV6,RANK:84 )
在线值:
发帖
回帖
粉丝
情话布墨 活跃值 2020-5-1 12:09
2
0
梦游师傅带带我好么~
游客
登录 | 注册 方可回帖
返回