记录《加密与解密》中一道很脑洞的RE题

首先,这道题在《加密与解密》中有完整的分析,那我为什么要再写一遍? 因为这题的脑洞你不自己做过是感受不到的。

文章位于《加密与解密》的5.5 KeyFile保护

文件:http://pan.baidu.com/s/1c1QBKWw 密码:vweu

界面:

首先,上面的那个编辑框不是给你输入用的,所以考虑是从注册表,ini或是其他类型文件读入。

观察程序的api调用,

发现有ReadFile,双击过去下断,上面发现CreateFileA,发现是从一个叫做KwazyWeb.bit的文件读入。

这里我没有书上那么好的水平,我用ida分析。(以下代码已经重命名过)

  • 在call 0x4012CF中有如下一段:

从而得知,注册文件由三部分组成,一字节的len_name,len_name字节的name,18字节的key。

  • 以下为calc_name()中的代码:

得知,name_calc为name每一位所对应的ascii数相加以后与0xFF做&(与)运算。

  • 以下为sub_4010C9check函数的代码:

这里的string1和string2都是一串很长的字符串,这里我把他dump出来:

1
****************C*......*...****.*.****...*....*.*..**********.*..*....*...*...**.****.*.*...****.*....*.*******..*.***..*.....*.*..***.**.***.*...****....*X..*****************

后面call sub_40101D,我把代码也粘上:

代码很好懂,用name_calc对key每一位异或加密。

来看下面的循环,首先i = 0,后面有++i != 18,大循环一共18次;然后对i累加的条件是j = 0,而j = 8,j -= 2,所以小循环是4次,一共循环18*4 = 72次。

(key[i] >> j) & 3是传入key的每2bits。传入的数据只有0,1,2,3四种可能。

  • 再看show_msg()函数:

根据传入的choice,pt_long_str的值会±1,±16。如果指针指向的字符为’*‘,直接失败;如果指针指向的字符为’X’,则成功。后面两句赋值无关紧要。

代码逆向分析完了,
首先我们根据show_msg函数的判断逆出异或后的key_enc,然后根据自己输入的name算出name_calc,然后得到key = name_calc ^ key_enc。然后按照格式创建注册信息文件。

难就难在如何逆出key_enc。一开始我的思路断了。后来在思考为什么是±1和±16的时候恍然大悟,我们只要将之前那一长串字符串按照16位一组然后回车换行,我们就会得到:

1
2
3
4
5
6
7
8
9
10
11
****************
C*······*···****
·*·****···*····*
·*··**********·*
··*····*···*···*
*·****·*·*···***
*·*····*·*******
··*·***··*·····*
·*··***·**·***·*
···****····*X··*
****************

这就是一个迷宫游戏,从c走到x。1就是向右走,-1就是向左走,16就是向下,-16就是向上。

再根据判断条件编码一下,得到顺序:

1
move=[2,2,2,1,2,2,2,3,2,2,1,1,0,1,0,0,1,1,1,0,0,3,3,3,0,3,0,0,1,1,1,1,1,2,1,1,0,1,1,2,1,1,1,2,2,3,3,2,3,3,0,3,3,2,2,2,3,2,2,1,1,1,0,0,1,1,1,1,2,2,3,3]

剩下的就轻松多了,直接上python脚本:

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
def calc_name(name_str):
tmp = 0
for ch in name_str:
tmp+=ord(ch)
return tmp&255

def generate_key_enc():
key_e_list=[]
move=[2,2,2,1,2,2,2,3,2,2,1,1,0,1,0,0,1,1,1,0,0,3,3,3,0,3,0,0,1,1,1,1,1,2,1,1,0,1,1,2,1,1,1,2,2,3,3,2,3,3,0,3,3,2,2,2,3,2,2,1,1,1,0,0,1,1,1,1,2,2,3,3]

for i in range(0,len(move),4):
key_e_list.append((move[i]<<6 | move[i+1]<<4 | move[i+2]<<2 | move[i+3])&255)

return key_e_list

def generate_key(key_enc,name_calc):
key=[]
for i in range(len(key_enc)):
key.append(key_enc[i] ^ name_calc)
return key


key_len = 18
name='veritas501'
len_name = len(name)
name_calc = calc_name(name)

key_enc = generate_key_enc()
key = generate_key(key_enc,name_calc)

key_asc=''
for i in range(len(key)):
key_asc+=chr(key[i])

content = chr(len_name)+name+key_asc

fp = open('KwazyWeb.bit','wb')
fp.write(content)
fp.close()

破解成功!

这次逆向最开脑洞的地方就是如何把数据隐藏起来不被轻松dump,之前我做到题都是用算法来隐藏,而这题的思路非常创新,值得我学习。