'); }

2021羊城杯_EasySmc


主函数

int __cdecl main()
{
  int v1; // [esp+18h] [ebp-10h]

  sub_80486BB();
  v1 = loc_8048F45();
  *(_DWORD *)(v1 + 32) = &unk_804B0C0;
  sub_80491C8();                                // 处理对象是代码段,说明是SMC自解密
  ((void (__cdecl *)(int))loc_80487A8)(v1);
  return 0;
}

动调让程序运行解密代码,运行完成后,再重新构建一下main函数

先u然后c,再之后p;也可以将最后main函数结尾构建到retn那里,不然会导致不能f5

f5之后,就能看见加密函数内的伪代码,比较好分析。

首先让你输入值,然后会读取共64位的数据到v95数组中

  v95[0] = xmmword_7FF7FD76E340;
  v95[1] = xmmword_7FF7FD76E350;
  v95[2] = xmmword_7FF7FD76E360;
  v95[3] = xmmword_7FF7FD76E370;

之后根据输入长度判断是否进行do while中的循环(只运行一遍的循环

后面还有一大串,未列举出来

image-20210917091529744

但是我们实际上不用管这里面做了什么,因为后面会将这里覆盖掉

  v13 = 16 * v12 + 1;                       // v12=0,v13=16*0+1=1
  v71 = (unsigned int)(v13 - 1);              // v71=0
  for ( i = 3 * (int)v71; v71 < v11; v94[v73 + 3] = v74 )// 从零开始,三个一组,类似于base64
    {
      v73 = 4 * (int)v71;
      v71 = (unsigned int)(v71 + 1);
      v74 = input.m128i_i8[i + 1];
      v75 = 16 * (input.m128i_i8[i] & 3);
      v94[v73] = *((_BYTE *)v95 + (input.m128i_i8[i] >> 2)) ^ 0xA6;// v95是len=64的数字集,说明这里是进行魔改后的base64加密
      v76 = input.m128i_i8[i + 2];
      i += 3i64;
      v94[v73 + 1] = *((_BYTE *)v95 + ((v74 >> 4) | v75)) ^ 0xA3;
      LOBYTE(v75) = *((_BYTE *)v95 + ((v76 >> 6) | (4 * (v74 & 0xF)))) ^ 0xA9;
      LOBYTE(v74) = *((_BYTE *)v95 + (v76 & 0x3F)) ^ 0xAC;
      v94[v73 + 2] = v75;
    }

这里对值进行了覆盖,所以上面do while循环并没有作用。然后根据剩余个数,往末尾填值

  v77 = v9 - 3 * v11;
  if ( v77 == 1 )                               // 当还剩一位时,前两位算法填充,后两位:14,最后补个0
  {
    v78 = input.m128i_i8[3 * v11];
    v94[4 * v11 + 2] = '1';
    v94[4 * v11 + 3] = '4';
    v79 = *((_BYTE *)v95 + (unsigned __int8)(16 * (v78 & 3))) ^ 0xA3;
    v94[4 * v11] = *((_BYTE *)v95 + (v78 >> 2)) ^ 0xA6;
    v94[4 * v11 + 1] = v79;
    v94[4 * v11 + 4] = 0;
  }
  else if ( v77 == 2 )                          // 当还剩两位时,前三位算法填充,后一位:4,最后再加个0
  {
    v80 = input.m128i_i8[3 * v11 + 1];
    v81 = input.m128i_i8[3 * v11];
    v82 = *((_BYTE *)v95 + 4 * (v80 & 0xFu)) ^ 0xA9;
    v83 = *((_BYTE *)v95 + ((v80 >> 4) | (16 * (v81 & 3)))) ^ 0xA3;
    v94[4 * v11] = *((_BYTE *)v95 + (v81 >> 2)) ^ 0xA6;
    v94[4 * v11 + 1] = v83;
    v94[4 * v11 + 2] = v82;
    v94[4 * v11 + 3] = '4';
    v94[4 * v11 + 4] = 0;
  }
  else
  {                                             // 没有剩下填充0
    v94[4 * v11] = 0;
  }

可以发现,末尾都会额外填零,填零是为了后面和值比较时,确定停止的位置

ida中数据末尾会自动填零,确保将数据分割开来

我们再来看比较值

v84 = "H>oQn6aqLr{DH6odhdm0dMe`MBo?lRglHtGPOdobDlknejmGI|ghDb<4";// 比较值

最后两位为<4,说明最后剩余两位。(也可以通过比较值的长度获取输入值的长度

看完整个算法过程,就可以开始快乐的搓脚本了

import base64
base64_="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
fake_base64=[0xE4, 0xC4, 0xE7, 0xC7, 0xE6, 0xC6, 0xE1, 0xC1, 0xE0, 0xC0, 
  0xE3, 0xC3, 0xE2, 0xC2, 0xED, 0xCD, 0xEC, 0xCC, 0xEF, 0xCF, 
  0xEE, 0xCE, 0xE9, 0xC9, 0xE8, 0xC8, 0xEB, 0xCB, 0xEA, 0xCA, 
  0xF5, 0xD5, 0xF4, 0xD4, 0xF7, 0xD7, 0xF6, 0xD6, 0xF1, 0xD1, 
  0xF0, 0xD0, 0xF3, 0xD3, 0xF2, 0xD2, 0xFD, 0xDD, 0xFC, 0xDC, 
  0xFF, 0xDF, 0x95, 0x9C, 0x9D, 0x92, 0x93, 0x90, 0x91, 0x96, 
  0x97, 0x94, 0x8A, 0x8E]
Xor=[0xA6,0xA3,0xA9,0xAC]
str_cmp="H>oQn6aqLr{DH6odhdm0dMe`MBo?lRglHtGPOdobDlknejmGI|ghDb<4"
tmp=""
for i in range(len(str_cmp)-1):              #减一是因为最后一位填充数并不符合算法
    tmp+=base64_[fake_base64.index(ord(str_cmp[i])^Xor[i%4])]
tmp+="4"
strflag=base64.b64decode(tmp).decode("utf-8")[:-1]
print(strflag)

得到flag:

SangFor{XSAYT0u5DQhaxveIR50X1U13M-pZK5A0}

image-20210917101249037


  目录