首页
论坛
课程
招聘
[原创]看雪.TSRC 2017CTF秋季赛第五题WP
2017-11-2 15:21 2150

[原创]看雪.TSRC 2017CTF秋季赛第五题WP

2017-11-2 15:21
2150

看雪.TSRC 2017CTF秋季赛第五题WP

说来惭愧,这题只能写写我的做法,好多我也没有分析出来,看得好心烦。

查找重点流程

看图标就知道MFC写的窗体程序。拖入IDA,入口都不想去翻,直接查看字符串,好多。稍微翻下能看到KanXueCrackMe2017字样和一串类似base64的字串,追到sub_406FC3,像是验证算法。
再往上追,到sub_4071FD,这时我比较确信这是主流程,而下层那个应该就是算法,看伪代码:

signed int __thiscall sub_4071FD(_DWORD *this)
{
  _DWORD *v1; // esi
  signed int result; // eax

  v1 = this;
  CWnd::UpdateData(1);
  result = sub_406FC3(v1);
  switch ( result )
  {
    case 1:
      return CWnd::MessageBoxW(v1, (LPCWSTR)&unk_44BE10, &Caption, 0);// 成功
    case 0:
      return CWnd::MessageBoxW(v1, (LPCWSTR)&unk_44BE18, &Caption, 0);// 失败
    case 2:
      result = CWnd::MessageBoxW(v1, &Text, &Caption, 0);// 很遗憾,离成功还差一小步@¥@
      break;
  }
  return result;
}

主流程根据算法流程的返回不同弹出三个不同的弹窗。

算法流程

直接贴上算法流程的伪代码,菜鸡如我,只会f5。

signed int __thiscall sub_406FC3(_DWORD *this)
{
  _DWORD *v1; // ebx
  _WORD *v2; // eax
  int v3; // esi
  int i; // ecx
  size_t v5; // kr00_4
  signed int result; // eax
  signed int j; // [esp+24h] [ebp-D9F8h]
  int v9[48]; // [esp+28h] [ebp-D9F4h]
  char v11; // [esp+D9A8h] [ebp-74h]
  char v12; // [esp+D9D0h] [ebp-4Ch]
  __int16 v13; // [esp+D9E2h] [ebp-3Ah]
  char v14[28]; // [esp+D9E4h] [ebp-38h]
  CPPEH_RECORD ms_exc; // [esp+DA04h] [ebp-18h]

  v1 = this;
  sub_407307(v9);
  strcpy(&v12, "KanXueCrackMe2017");
  v13 = 0;
  sub_4084A3((int)v9, &v12);
  v2 = (_WORD *)v1[38];
  v3 = *((_DWORD *)v2 - 3);
  for ( i = 0; i < v3; ++v2 )
  {
    if ( i < 0 || i > v3 )
      ATL::AtlThrowImpl(0x80070057);
    v14[i++] = *v2;
  }
  v14[i] = 0;
  __asm { int     2Dh; Windows NT - debugging services: eax = type }
  ms_exc.registration.TryLevel = 0;
  v5 = strlen(v14);
  for ( j = 0; j < 1000; ++j )
  {
    sub_4031E6((int)&v11, (int)v14, v5);       //base64
    memcpy_0(v14, &v11, v5);
  }
  if ( !strcmp("Vm0wd2QyUXlVWGw==", &v11) )
  {
    result = 1;
    ms_exc.registration.TryLevel = -2;
  }
  else
  {
    ms_exc.registration.TryLevel = -2;
    result = 0;
  }
  return result;
}

先不说具体算法,先看流程。
似乎最后strcmp("Vm0wd2QyUXlVWGw==", &v11)这比较成功,就行了。

 

但再看中间的__asm { int 2Dh; Windows NT - debugging services: eax = type }这段。显示的调试坑啊。为了静态方便看正确的流程,我patch下代码,至于动态,网上说加了插件的OD直接过,我是把int 2D改成int3,再设置pass int3过的,不然过不了,哎。

signed int __thiscall sub_406FC3(_DWORD *this)
{
  _DWORD *v1; // ebx
  _WORD *v2; // eax
  int v3; // esi
  int i; // ecx
  signed int v5; // eax
  signed int result; // eax
  int v7[48]; // [esp+28h] [ebp-D9F4h]
  int v8[13872]; // [esp+E8h] [ebp-D934h]
  char v9; // [esp+D9D0h] [ebp-4Ch]
  __int16 v10; // [esp+D9E2h] [ebp-3Ah]
  char v11[28]; // [esp+D9E4h] [ebp-38h]
  CPPEH_RECORD ms_exc; // [esp+DA04h] [ebp-18h]

  v1 = this;
  sub_407307(v7);
  strcpy(&v9, "KanXueCrackMe2017");
  v10 = 0;
  sub_4084A3((int)v7, &v9);
  v2 = (_WORD *)v1[38];
  v3 = *((_DWORD *)v2 - 3);
  for ( i = 0; i < v3; ++v2 )
  {
    if ( i < 0 || i > v3 )
      ATL::AtlThrowImpl(0x80070057);
    v11[i++] = *v2;
  }
  v11[i] = 0;
  ms_exc.registration.TryLevel = 1;
  sub_4084A3((int)v7, v11);
  v5 = 0;
  do
  {
    if ( v7[v5] != v8[v5] )
    {
      result = 0;
      goto LABEL_7;
    }
    ++v5;
  }
  while ( v5 < 48 );
  if ( sub_40680C(v11) == 1 && strlen(v11) == 12 )
  {
    result = 1;
LABEL_7:
    ms_exc.registration.TryLevel = -2;
    return result;
  }
  return 2;
}

还是先看流程。校验一共有两处:一是v7v8的48字节比较,不正确就返回0;二是v11长度是否为12sub_40680C(v11)是否返回1,若不是则返回2,是则返回1,即校验成功。

 

v7v8v11又是什么呢。下面开始理算法。

算法分析OR猜测

通过动静结合分析,v7v8sub_407307初始化的48字节数组,其16进制表示为:

00  01  02  03  04  05  06  07  08  09  0A  0B  0C  0D  0E  0F  
10  11  12  13  14  15  16  17  18  19  1A  1B  1C  1D  1E  1F  
20  21  22  23  24  25  26  27  28  29  2A  2B  2C  2D  2E  2F

v8后由sub_4084A3函数两次(实际上内部是多次)修改。而v11则是输入。

 

这里我们得到第一个比较明确的条件:

len(input) == 12

 

为了弄清楚v8的最后值与输入的关系,得先弄清楚sub_407307sub_4084A3这两个函数。

 

sub_407307除了初始化了v7v8,还通过v7另外6个数组生成了6个48x48变换矩阵。这6组数组如下:

m0=[
0x06, 0x07, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x08, 0x09, 0x0A, 0x0B, 0x22, 0x23, 0x24, 0x0F, 
0x0E, 0x11, 0x12, 0x13, 0x14, 0x15, 0x0C, 0x0D, 0x16, 0x17, 0x10, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 
0x20, 0x21, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F]

m1=[
0x10, 0x11, 0x12, 0x03, 0x04, 0x05, 0x06, 0x07, 0x0E, 0x0F, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
0x2C, 0x2D, 0x2E, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0x00, 0x01, 0x02, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x20, 0x21, 0x22, 0x2F]

m2=[
0x00, 0x01, 0x1A, 0x1B, 0x1C, 0x05, 0x06, 0x07, 0x08, 0x09, 0x02, 0x03, 0x04, 0x0D, 0x0E, 0x0F,
0x16, 0x17, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x18, 0x19, 0x2A, 0x2B, 0x2C, 0x1D, 0x1E, 0x1F,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x0A, 0x0B, 0x0C, 0x2D, 0x2E, 0x2F]

m3=[
0x00, 0x01, 0x02, 0x03, 0x24, 0x25, 0x26, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x04, 0x05, 0x06, 0x17, 0x1E, 0x1F, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D,
0x20, 0x21, 0x22, 0x23, 0x28, 0x29, 0x2A, 0x27, 0x14, 0x15, 0x16, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F]

m4=[
0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x0E, 0x0F, 0x28, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x2E, 0x2F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x00, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x06, 0x07,
0x26, 0x27, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x18, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x1E, 0x1F]

m5=[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x12, 0x13, 0x14, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x1C, 0x1D, 0x1E, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x26, 0x27, 0x20, 0x1F,
0x0A, 0x21, 0x22, 0x23, 0x24, 0x25, 0x08, 0x09, 0x2E, 0x2F, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D]

再看sub_4084A3

void __stdcall sub_4084A3(int a1, const char *a2)
{
  signed int v2; // ebx
  int v3; // edi
  char v4; // al
  char *v5; // [esp+0h] [ebp-4h]
  signed int v6; // [esp+10h] [ebp+Ch]

  v5 = (char *)malloc(3 * strlen(a2));
  sub_40829C(a2, (int)v5);
  v6 = strlen(v5);
  v2 = 0;
  if ( v6 > 0 )
  {
    v3 = v6;
    do
    {
      v4 = v5[v2];
      if ( v4 < 48 || v4 > 57 )
      {
        if ( v4 >= 65 && v4 <= 72 )
          v3 = v4 - 55;
      }
      else
      {
        v3 = v4 - 48;
      }
      sub_4080DF(v3 / 3, v3 % 3 + 1, a1);
      ++v2;
    }
    while ( v2 < v6 );
  }
  free(v5);
}

粗跟下,sub_40829C似乎是一个转换,再将转换结果用作sub_4080DF的矩阵计算。
sub_4080DF的变换是以v3/3值为索引选择矩阵变换表,v3%3+1则是转换次数,变换目标为前面说的v8,变换实际上就是矩阵乘法。

 

sub_40829C的字符变换到底是一个什么过程呢。细跟了一次,没什么头绪,除了发现里面有点像是大数运算操作。

 

不断翻看代码。注意到矩阵运算前的v3是由转换后的字符算术运算得到的,运算规则是:

若字符为数字 则v3 = v4-48
若字符为A-H 则v3 = v4-55

 

那转换后的字符范围是0-9 A-H,v3范围是0-17。而在sub_40829C中也有类似的计算:

if ( v3 < 48 || v3 > 57 )
    {
      if ( v3 < 65 || v3 > 90 )
      {
        if ( v3 < 97 || v3 > 122 )
          v4 = -1;
        else
          v4 = v3 - 61;
      }
      else
      {
        v4 = v3 - 55;
      }
    }
    else
    {
      v4 = v3 - 48;
    }

其中的v3是取的输入字符,其范围是0-9 A-Z a-zv4范围是0-61

 

似乎转换后的字符是18进制,其它的还是没有头绪。后经人提醒,加上验证,输入字符应该是62进制字串,转换过程就是62进制到18进制的转换,这就与上面的代码全对上了。这个可以从1个字节、2个字节输入慢慢测出来。

 

开始看到两个校验,且两个校验的输入是全输入,就想能不能通过一个校验就能得出答案。通常有两个校验的情况不外乎这么两种:一是两个校验均不能得到唯一解,只能相互补充条件得到唯一解;二是其中一个校验是冗余校验,且难于反解出答案。如果此题是第一种情况,可以尝试爆破,从校验一结果中找到唯一解;如果是另一种情况,那只能先看看校验一能不能解了,反正校验二看了更蒙。

 

刚才说了校验一处的v8sub_4084A3分别以KanXueCrackMe2017和输入改变了两次。改变过程就是多次的矩阵变换,实际上就是由参数控制实现两次互逆的变换。

 

看了很多矩阵的相关知识,没找到办法。后来仔细分析了变换矩阵,原来此中的绝技。以第一个变换矩阵来说,它是由第一个变换数组得到的:

m0=[
0x06, 0x07, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x08, 0x09, 0x0A, 0x0B, 0x22, 0x23, 0x24, 0x0F, 
0x0E, 0x11, 0x12, 0x13, 0x14, 0x15, 0x0C, 0x0D, 0x16, 0x17, 0x10, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 
0x20, 0x21, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F]

看前8个元素,每变换一次,它们都是循环后移2个位置,m0[0:5]后移,m0[6:7]循环移动到前面,这样,每4次变换就是一个还原周期,其它位置的元素同样如此。

 

如:

>>> b*m0
matrix([[ 6,  7,  0,  1,  2,  3,  4,  5,  8,  9, 10, 11, 34, 35, 36, 15, 14,
         17, 18, 19, 20, 21, 12, 13, 22, 23, 16, 27, 28, 29, 30, 31, 32, 33,
         24, 25, 26, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47]])
>>> b*m0*m0
matrix([[ 4,  5,  6,  7,  0,  1,  2,  3,  8,  9, 10, 11, 24, 25, 26, 15, 36,
         17, 18, 19, 20, 21, 34, 35, 12, 13, 14, 27, 28, 29, 30, 31, 32, 33,
         22, 23, 16, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47]])
>>> b*m0*m0*m0
matrix([[ 2,  3,  4,  5,  6,  7,  0,  1,  8,  9, 10, 11, 22, 23, 16, 15, 26,
         17, 18, 19, 20, 21, 24, 25, 34, 35, 36, 27, 28, 29, 30, 31, 32, 33,
         12, 13, 14, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47]])
>>> b*m0*m0*m0*m0
matrix([[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
         17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
         34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47]])

对其它组变换矩阵进行了测试,同样如此。
也就是说

m[i]*m[i]*m[i]*m[i] = I

那就好办了。

 

运行记录第一次的变换操作

00408516  COND: index = 00000004
0040851C  COND: count = 00000003
00408516  COND: index = 00000004
0040851C  COND: count = 00000002
00408516  COND: index = 00000003
0040851C  COND: count = 00000002
00408516  COND: index = 00000005
0040851C  COND: count = 00000003
00408516  COND: index = 00000004
0040851C  COND: count = 00000003
00408516  COND: index = 00000001
0040851C  COND: count = 00000002
00408516  COND: index = 00000001
0040851C  COND: count = 00000003
00408516  COND: index = 00000000
0040851C  COND: count = 00000001
00408516  COND: index = 00000004
0040851C  COND: count = 00000001
00408516  COND: index = 00000002
0040851C  COND: count = 00000002
00408516  COND: index = 00000001
0040851C  COND: count = 00000002
00408516  COND: index = 00000000
0040851C  COND: count = 00000002
00408516  COND: index = 00000005
0040851C  COND: count = 00000002
00408516  COND: index = 00000005
0040851C  COND: count = 00000003
00408516  COND: index = 00000001
0040851C  COND: count = 00000002
00408516  COND: index = 00000001
0040851C  COND: count = 00000002
00408516  COND: index = 00000000
0040851C  COND: count = 00000002
00408516  COND: index = 00000004
0040851C  COND: count = 00000003
00408516  COND: index = 00000000
0040851C  COND: count = 00000002
00408516  COND: index = 00000000
0040851C  COND: count = 00000002
00408516  COND: index = 00000003
0040851C  COND: count = 00000003
00408516  COND: index = 00000005
0040851C  COND: count = 00000003
00408516  COND: index = 00000002
0040851C  COND: count = 00000003
00408516  COND: index = 00000001
0040851C  COND: count = 00000002

整理下,列表中的前面数值表示变换矩阵序号,后面数值表示变换次数:

lop1 = [[4,3],[4,2],[3,2],[5,3],[4,3],[1,2],[1,3],[0,1],[4,1],
        [2,2],[1,2],[0,2],[5,2],[5,3],[1,2],[1,2],[0,2],[4,3],
        [0,2],[0,2],[3,3],[5,3],[2,3],[1,2]]

看着这个就能直接写出逆变换操作:

lop2 = [[1,2],[2,1],[5,1],[3,1],[0,2],[0,2],[4,1],[0,2],[1,2],
        [1,2],[5,1],[5,2],[0,2],[1,2],[2,2],[4,3],[0,3],[1,1],
        [1,2],[4,1],[5,1],[3,2],[4,2],[4,1]]

计算出来的输入位数太多。细看操作列表,其中有好一些是可以合并操作的,进行合并,得新的操作列表:

lop3 = [[1,2],[2,1],[5,1],[3,1],[4,1],[0,2],[5,3],[0,2],[1,2],
        [2,2],[4,3],[0,3],[1,3],[4,1],[5,1],[3,2],[4,3]]

出来结果校验直接通过,出乎意料,唯一结果。

 

校验二的代码现在是实在不想看,开始似乎看到了ecc,也是大数,如果有时间再补充吧。

 

最后给出部分代码:

import string

t18 = '0123456789ABCDEFGH'
t62 = string.digits+string.uppercase+string.lowercase

lop1 = [[4,3],[4,2],[3,2],[5,3],[4,3],[1,2],[1,3],[0,1],[4,1],
        [2,2],[1,2],[0,2],[5,2],[5,3],[1,2],[1,2],[0,2],[4,3],
        [0,2],[0,2],[3,3],[5,3],[2,3],[1,2]]

lop2 = [[1,2],[2,1],[5,1],[3,1],[4,1],[0,2],[5,3],[0,2],[1,2],[2,2],[4,3],[0,3],[1,3],[4,1],[5,1],[3,2],[4,3]]
#l=[[1,2],[2,1],[5,1],[3,1],[0,2],[0,2],[4,1],[0,2],[1,2],[1,2],[5,1],[5,2],[0,2],[1,2],[2,2],[4,3],[0,3],[1,1],[1,2],[4,1],[5,1],[3,2],[4,2],[4,1]]
#1,2 2,1 5,1 3,1 4,1 0,2 5,3 0,2 1,2 2,2 4,3 0,3 1,3 4,1 5,1 3,2 4,3

def t18to62(snum):
    sum=0
    for i in snum:
        sum = sum*18+t18.index(i)
    s = ''
    while sum:
        s += t62[sum%62]
        sum = sum/62
    return s


def t62to18(snum):  
    sum=0
    for i in snum:
        sum = sum*62+t62.index(i)
    s = ''
    while sum:
        s += t18[sum%18]
        sum = sum/18
    return s

if __name__ == '__main__':
    sn1 = ''
    for [i,j] in lop2:
        sn1 += t18[i*3+j-1]
    print t18to62(sn1[::-1])

[公告]名企招聘!

收藏
点赞0
打赏
分享
最新回复 (6)
雪    币: 12770
活跃值: 活跃值 (1857)
能力值: ( LV13,RANK:824 )
在线值:
发帖
回帖
粉丝
大帅锅 活跃值 4 2017-11-4 11:18
2
0
偶像  偶像!!    逆变换不能顺着写嘛  [4,3]对[4,1]  [4,2]对[4,2]    难道必须从尾向前写嘛?不能顺着写嘛,周期不是4嘛?   
雪    币: 561
活跃值: 活跃值 (14)
能力值: ( LV12,RANK:650 )
在线值:
发帖
回帖
粉丝
brichfire 活跃值 4 2017-11-4 11:38
3
0
大帅锅 偶像 偶像[em_2][em_2][em_2]!! 逆变换不能顺着写嘛 [4,3]对[4,1] [4,2]对[4,2] 难道必须从尾向前写嘛?不能顺着写嘛,周期不是4嘛?
矩阵乘法没有交换率,有结合律,从尾部开始乘以逆元依次消除得到单位矩阵。
雪    币: 12770
活跃值: 活跃值 (1857)
能力值: ( LV13,RANK:824 )
在线值:
发帖
回帖
粉丝
大帅锅 活跃值 4 2017-11-4 11:40
4
0
brichfire 矩阵乘法没有交换率,有结合律,从尾部开始乘以逆元依次消除得到单位矩阵。
了解
雪    币: 12770
活跃值: 活跃值 (1857)
能力值: ( LV13,RANK:824 )
在线值:
发帖
回帖
粉丝
大帅锅 活跃值 4 2017-11-4 20:29
5
0
原来那个用的ecx隐藏的传参,IDA没有分析出来,我就说为什么  不可以顺着写,我以为单位矩阵只有一个。
雪    币: 12005
活跃值: 活跃值 (238)
能力值: ( LV15,RANK:2427 )
在线值:
发帖
回帖
粉丝
poyoten 活跃值 22 2017-11-6 00:48
6
0
大帅锅 原来那个用的ecx隐藏的传参,IDA没有分析出来,我就说为什么 不可以顺着写,我以为单位矩阵只有一个。
这个就要跟一跟,或者看看汇编,有时候f5也会坑人的。
雪    币: 6817
活跃值: 活跃值 (153)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
聖blue 活跃值 2017-11-6 21:35
7
0
不错吆!
游客
登录 | 注册 方可回帖
返回