首页
社区
课程
招聘
[原创] 京东-看雪-2018 春季赛 第七题 密室逃脱
2018-6-29 11:17 2590

[原创] 京东-看雪-2018 春季赛 第七题 密室逃脱

aqs 活跃值
5
2018-6-29 11:17
2590

拿到的是一个 win32 程序,跑一下看看

Only 'a'-'z','A'-'Z','0'-'9' accepted.
Only 16 chars accepted.
Serial:zzzzzzzzzzzzzzzz

serial error

serial 是16byte 长度的字符串,根据题目描述,每个byte对应一个初始密钥
看一下程序逻辑

int __cdecl main(int argc, const char **argv, const char **envp)
{
  printf("Only 'a'-'z','A'-'Z','0'-'9' accepted.\n");
  printf("Only 16 chars accepted.\n");
  printf("Serial:");
  scanf_s("%s", input_450B80, 17);
  main_logic_401050();
  system("PAUSE");
  return 0;
}

逻辑不复杂,前面output 一些信息,输入 序列号,然后进入main_logic 函数,main_logic 函数只有一层实现,下面分段看一下

  helpset[0] = 0x10101;
  helpset[1] = 0;
  helpset[2] = 0;
  helpset[3] = 0;
  helpset[4] = 0x1000001;
  helpset[5] = 1;
  helpset[6] = 0;
  helpset[7] = 0;
  helpset[8] = 1;
  helpset[9] = 0x10100;
  helpset[10] = 0;
  helpset[11] = 0;
  helpset[12] = 0x100;
  helpset[13] = 0x1000100;
  helpset[14] = 0;
  helpset[15] = 0;
  helpset[16] = 0x100;
  helpset[17] = 0x10001;
  helpset[18] = 0;
  helpset[19] = 0;
  helpset[20] = 0x1010000;
  helpset[21] = 0;
  helpset[22] = 1;
  helpset[23] = 0;
  helpset[24] = 0x10000;
  helpset[25] = 1;
  helpset[26] = 256;
  helpset[27] = 0;
  helpset[28] = 0x1000000;
  helpset[29] = 0x1000000;
  helpset[30] = 0x10000;
  helpset[31] = 0;
  helpset[32] = 0;
  helpset[33] = 256;
  helpset[34] = 1;
  helpset[35] = 1;
  helpset[36] = 0;
  helpset[37] = 0x10000;
  helpset[38] = 0x1000000;
  helpset[39] = 256;
  helpset[40] = 0;
  helpset[41] = 0x1000000;
  helpset[42] = 0;
  helpset[43] = 257;
  helpset[44] = 0;
  helpset[45] = 0;
  helpset[46] = 16777472;
  helpset[47] = 0x10000;
  helpset[48] = 0;
  helpset[49] = 0;
  helpset[50] = 65537;
  helpset[51] = 0x10000;
  helpset[52] = 0;
  helpset[53] = 0;
  helpset[54] = 65792;
  helpset[55] = 0x1000000;
  helpset[56] = 0;
  helpset[57] = 0;
  helpset[58] = 0x1000000;
  helpset[59] = 16777217;
  helpset[60] = 0;
  helpset[61] = 0;
  helpset[62] = 0;
  helpset[63] = 16843008;

首先会写入一个 16x16 的表
在内存中是这个样子的

这个表在后面会用到,主要是用来确定操作的index 的
先看一下后面的逻辑

 do
  {                                             // 找到每个key 对应的 index
    for ( offset = 0; ; ++offset )
    {
      if ( offset >= 64 )
      {                                         
        printf("input error\n");
        system("PAUSE");
        exit(-1);
      }
      if ( serial_set_40DA68[offset] == (unsigned __int8)input_450B80[index] )
        break;
    }                                           // 找到每个byte 对应的 offset
    input_index[index++] = offset;             
  }
  while ( index < 16 );

首先根据输入的每个byte在一个 table 中找到对应的 index

abcdefghijklmnopqrstuvwxyz+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789

后面对 变换之后的 index table 进行加密

  do
  {
    pchr1_2 = &input_index[change_num];
    level_key = &input_index[change_num];
    room_index = 0;
    do
    {
      v6 = &v16;
      v7 = (int)&pchr1_2[v15];                  // v7==2,3,4 ,5 ....
      index_4 = 4;
      do
      {
        v8 = (v7 - 2) % 16;                     // 2-2=0..
        if ( *((_BYTE *)&helpset[room_index] + v8) )
          *v6++ = *((_BYTE *)&index_4 + v8 + level);// index_4=4
        v9 = (v7 - 1) % 16;                     // 2-1=1...
        if ( *((_BYTE *)&helpset[room_index] + v9) )// man
          *v6++ = *((_BYTE *)&index_4 + v9 + level);
        if ( *((_BYTE *)&helpset[room_index] + v7 % 16) )// v7==2..
          *v6++ = *((_BYTE *)&index_4 + v7 % 16 + level);
        v10 = (v7 + 1) % 16;                    // 2+1==3...
        if ( *((_BYTE *)&helpset[room_index] + v10) )
          *v6++ = *((_BYTE *)&index_4 + v10 + level);// char[index]
        v7 += 4;
        --index_4;
      }                                         // 根据 room set change
      while ( index_4 );                        // 4 个byte 
      *level_key = changset1_40FEF0[0x40 * (v17 + (v16 << 6)) + v18];// 最后成为一个 index
      room_index += 4;
      pchr1_2 = level_key++ + 1;
    }
    while ( room_index < 64 );                  // 遍历 room set
    v15 -= 16;
    change_num = level + 16;
    level = change_num;
  }                                             // 一共是 20 层的内容
  while ( change_num < 320 );                   // 这个就是所谓的楼层的意思是吧

代码被自己改的有点难看,大概逻辑是:

  • 找到 变换之后的 16 byte table
  • 根据 前面的 16x16 的表找到对应的 index
  • 得到的 3 个 index 变换成一个 index 并在一个 63x63x63 大小的表里面找到对应的数字
  • 将得到的数字 保存好作为下一次循环的 index table
  • 循环 20次

这么说有点模糊,调试一下,在 0x0401287 这里下个断点,ecx -0x10 就是 table 的位置

这里传入的是 abcdefghijklmnop , 可以看到 index 变换之后变成了 index 0-0xf
接下来在0x0401393, 也就是获取到下一个table 的一个byte回填的时候

00401393                 mov     dl, changset1_40FEF0[edx+ecx]

找到 三个参数

可以发现是 0 1 2,结合前面的 16x16 的表来看 index 是符合的
但是顺序没搞清楚是怎么变换的,只有 16 个byte,调试看看就行,得到的每次 变换的 三个参数是 对应的 index 是

0 1 2
3 4 0
5 6 0
5 7 1
4 6 1
8 2 3
9 2 4
7 a 3
8 c 5
b d 6
c d 7
b e 9
e 8 a
f 9 a
f b c
f d e

变换 之后将最后得到的密钥和 一个固定的串进行比较

  do
  {                                             // 将最后得到的 serial 进行比较
    if ( (unsigned __int8)change_serial[index_2] != cmp_serial_40FEE0[index_2] )
      return printf("serial error\n");
    ++index_2;
  }
  while ( index_2 < 16 );
  do                                            
    printf("%c", (unsigned __int8)serial_set_40DA68[(unsigned int)(unsigned __int8)v22[index_1++] >> 1]);
  while ( index_1 < 16 );
  return printf("\n");

如果一致就过,还会根据 变换得到的 19 个加密串中间的一个来生成一段字符串
到了这里,整理一下

  • 自己可以输入 16 byte 的字符串
  • 字符串进行 20-1 轮一样的变换 得到一个最终的密钥
  • 最终密钥如下
    14h, 22h, 1Eh, 10h, 38h, 30h, 18h, 10h, 4, 1Ah, 24h 8, 2, 26h, 38h, 2Ah
    

一开始想到的是 有没有可能反向解密回去回去,但是发现用于变换的table 是 64x64x64 大小的,要变换回去感觉很难, 情况会有很多种
想了很久,后面发现,如果多加密几轮的话就有可能重新得到原来的输入
这就简单了,将 最后密钥多加密几轮,找到重新得到最后密钥的地方,就可以知道他是怎么来的了, 写了一个脚本测试了一下

#coding:utf-8

p=open('./Escape.exe','rb')
s=p.read()
p.close()
# 获取用于变换的表
change_table=[]

for i in s[0xe4f0:0xe4f0+262144]:
    change_table.append(ord(i))

# 最终秘钥
result=[0x14, 0x22, 0x1E, 0x10, 0x38, 0x30, 0x18, 0x10, 0x4, 0x1A, 0x24,0x8, 0x2, 0x26, 0x38, 0x2A]

# 数字转换成 3 个 index
def reverse(num):
    r1=(num>>6)>>6
    r2=(num>>6)-(r1*64)
    r3=(num)-((num>>6)*64)

    return [r1,r2,r3]

# 3 个 index 转换成一个数字
def realnum(n0,n1,n2):
    return (((n0<<6)+n1)<<6)+n2


# 字符串变换成 index table
def coven(n):
    charset='abcdefghijklmnopqrstuvwxyz+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
    test=[]
    for i in range(len(n)):
        for j in range(len(charset)):
            if n[i]==charset[j]:
                test.append(j)
                break
    return test


# index table 变回字符串
def reconven(n):
    t=''
    charset='abcdefghijklmnopqrstuvwxyz+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
    for i in n:
        t+=charset[i]
    return t


# 加密函数
def encrypto(n):
    test=[]
    test.append(change_table[realnum(n[0x0],n[0x1],n[0x2])])
    test.append(change_table[realnum(n[0x3],n[0x4],n[0x0])])
    test.append(change_table[realnum(n[0x5],n[0x6],n[0x0])])
    test.append(change_table[realnum(n[0x5],n[0x7],n[0x1])])
    test.append(change_table[realnum(n[0x4],n[0x6],n[0x1])])
    test.append(change_table[realnum(n[0x8],n[0x2],n[0x3])])
    test.append(change_table[realnum(n[0x9],n[0x2],n[0x4])])
    test.append(change_table[realnum(n[0x7],n[0xa],n[0x3])])
    test.append(change_table[realnum(n[0x8],n[0xc],n[0x5])])
    test.append(change_table[realnum(n[0xb],n[0xd],n[0x6])])
    test.append(change_table[realnum(n[0xc],n[0xd],n[0x7])])
    test.append(change_table[realnum(n[0xb],n[0xe],n[0x9])])
    test.append(change_table[realnum(n[0xe],n[0x8],n[0xa])])
    test.append(change_table[realnum(n[0xf],n[0x9],n[0xa])])
    test.append(change_table[realnum(n[0xf],n[0xb],n[0xc])])
    test.append(change_table[realnum(n[0xf],n[0xd],n[0xe])])
    return test

tmp=result
for i in range(0x200):
    print tmp
    tmp=encrypto(tmp)

test=[17, 43, 33, 5, 63, 1, 37, 11, 57, 19, 51, 9, 63, 57, 53, 37]

print reconven(test)

多循环几次,grep 一下,可以看到重复了,找到重复位置的前19 个密钥,变换一下就可以得到 serial

❯ python e2xp.py |grep '20, 34, 30, 16, 56, 48'               
[20, 34, 30, 16, 56, 48, 24, 16, 4, 26, 36, 8, 2, 38, 56, 42] 
[20, 34, 30, 16, 56, 48, 24, 16, 4, 26, 36, 8, 2, 38, 56, 42]

运行结果

Only 'a'-'z','A'-'Z','0'-'9' accepted.
Only 16 chars accepted.
Serial:rPFf9bJl3tXj93ZJ

yourserialisgood
请按任意键继续. . .

[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

最后于 2018-6-29 15:37 被aqs编辑 ,原因:
上传的附件:
收藏
点赞2
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回