-
-
[原创] 京东-看雪-2018 春季赛 第七题 密室逃脱
-
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漏洞挖掘与利用;代码审计。