1. 程式人生 > >記錄《加密與解密》中一道很腦洞的RE題

記錄《加密與解密》中一道很腦洞的RE題

首先,這道題在《加密與解密》中有完整的分析,那我為什麼要再寫一遍? 因為這題的腦洞你不自己做過是感受不到的。

文章位於《加密與解密》的5.5 KeyFile保護

介面:這裡寫圖片描述

首先,上面的那個編輯框不是給你輸入用的,所以考慮是從登錄檔,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出來:

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

off_403184是一個

後面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位一組然後回車換行,我們就會得到:

****************
C*······*···****
·*·****···*····*
·*··**********·*
··*····*···*···*
*·****·*·*···***
*·*····*·*******
··*·***··*·····*
·*··***·**·***·*
···****····*X··*
****************

這就是一個迷宮遊戲,從c走到x。1就是向右走,-1就是向左走,16就是向下,-16就是向上。

再根據判斷條件編碼一下,得到順序:

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指令碼:

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,之前我做到題都是用演算法來隱藏,而這題的思路非常創新,值得我學習。