Await 做些相當庸俗的夢

RAR 5.0 密碼破解

實驗概述

在這個實驗中,我們嘗試不使用其他的解密函式庫,來破解一個 RAR5.0 格式的壓縮檔案。透過查閱資料可知,RAR 5.0 的破解沒有捷徑可循,只能使用嘗試的辦法進行測試。而提供的 RAR 檔案的密碼均為4位數。

因此,大致的想法就是使用猜測密碼的方式,遍歷 0000 - 9999 一共 10000 個密碼,驗證是否正確。

實驗過程

我的實驗過程大致分為以下幾個階段:

  1. RAR 5.0 檔案格式的瞭解。
  2. 對於幾個核心演算法(PBKDF2,HMAC,SHA-256)的思考。
  3. 具體程式設計。

對RAR 5.0 檔案格式的瞭解

由於不能使用外部的程式,我們必須用二進位制的方式讀取 RAR 檔案。使用 C++ 標準庫中的 ifstream可以方便地讀取二進位制檔案。另外寫了一個輔助函式,以便以 16 進位制逐位元組列印該壓縮檔案。

透過閱讀 RAR 官方的文件,可以瞭解到,RAR 檔案由 Archive blocks所組成,每一個 Block 儲存的資料與其他相對獨立。與解密壓縮檔案密碼最相關的是 Archive encryption header。其資料結構如下:

Content Data Type Description
Header CRC32 uint32
Header size vint
Header type vint 4
Header flags vint Flags common for all headers
Encryption version vint 0 for AES-256.
Encryption flags vint 0x0001   Password check data is present.
KDF count 1 byte Binary logarithm of iteration number for PBKDF2 function.
Salt 16 bytes Salt value used globally for all encrypted archive headers.
Check Value 12 bytes Value used to verify the password validity.

其中 vint 是 RAR 定義的變長整型資料型別。具體如何讀不在此贅述。由於我們的 RAR 檔案全部都是檔名加密的,因此,根據官方文件描述,在最開始表示 RAR 5.0 格式的 RAR 5.0 signature(8 bytes)之後,就是第一個 Archive encryption header。這也使得閱讀資料更簡單了。

如下是我讀到的前 48 bytes 的資料。

52 61 72 21 1a 07 01 00 da 47 1e fa 21 04 00 00
01 0f dd 9c 1c 17 d1 44 50 03 48 c4 d4 46 ae 17
70 e5 0e e1 7c bf 33 76 5d 58 34 6f cd 2a 61 65

根據文件所述,可以進行分析:

  1. RAR 5.0 Signature
    52 61 72 21 1a 07 01 00
    
  2. CRC32
    da 47 1e fa
    
  3. Size
    21
    
  4. Header Type
    04
    
  5. Header Flag
    00
    
  6. Encrypt Type
    00
    
  7. Encrypt Flag
    01
    
  8. KDF count
    0f
    
  9. Salt
    dd 9c 1c 17 d1 44 50 03 48 c4 d4 46 ae 17 70 e5
    
  10. PSWCheckValue(First 8 bytes)
    0e e1 7c bf 33 76 5d 58
    

在以上值中,最重要的是 Salt,KDF Count 和 PSWCheckValue。這幾個值將用來和密碼結合,並算出該密碼的 CheckValue,與檔案中的 PSWCheckValue 比較,驗證密碼是否能正確解密該檔案。

對於幾個核心演算法的思考

要得到 CheckValue,閱讀材料中的論文,可知需要實現 PBKDF2(其中的 PRF 函式使用 HMAC),而 HMAC 中使用的雜湊函式為 SHA-256。由於具體的演算法都有比較詳細的文件說明,我在這裡只寫了遇到的問題以及相關的思考。

  1. PBKDF2

    PBKDF2 是用來生成金鑰的演算法,迴圈使用上一次的輸出作為輸入,然後進行有限次的計算。

    迴圈次數由 KDF Count 決定,第一次使用的鹽是 Salt 拼接 0x0001,之後的使用的鹽依賴上一次的輸出結果。

    最後透過一些異或運算得到 8 bytes 的 CheckValue。

  2. HMAC

    使用 SHA-256 作為雜湊函式的 HMAC 接受兩個輸入,一個是 Password,另一個是 Message。一般的 HMAC 實現中,由於 Password 和 Message 都在變化,因此在輸入的時候分別輸入,同時進行計算。

    但是在此實驗的情況下,每次嘗試一個 Password 時,其值是不會變的。也就是因此計算出的臨時引數 $T_1$ 和 $T_2$ 在一次 CheckValue 計算中是不變的。故在實現一次 PBKDF2 時,我們可以把可預計算拆開計算,以避免重複。

  3. SHA-256

    SHA-256 是一個雜湊函式,輸入一條長度在 $(2^{64} -1)$ 以內的資訊,並輸出一條長度為 256 bit 的資訊摘要。其中比較重要的是資訊填充。由於輸入的資訊長度並不確定,需要填充成比較規範的形式才能進行下一步的計算。

    首先,在資訊後填一個1,及後繼的若干個0,使得連同資訊的位元長度與(512 - 64) 對 512 同餘。再在接下來的 64 bits 中填寫源資訊的長度。這樣完成資訊的填充。

    需要注意的一點是,這裡填充的長度均是大端序,也就是低位地址存高位位元組。而在常見的 x86 處理器均為小端序,在進行資訊填充的時候需要多一步處理,避免出錯。

程式設計實現

簡要的步驟如下:

  1. 掃描第一個Header,讀出 Salt,PswCheckValue,和 KDF Count。
  2. 將密碼用 UTF-8 表示。
  3. 實現 PBKDF2 函式,計算出 CheckValue,與檔案中的值進行比較。
  4. 如果不符,測試下一個密碼,迴圈 2 - 4 步。直至有匹配的密碼或者所有的密碼都測試完成。

其中有幾個問題需要注意:

  • 由於密碼均是數字,包含在 ASCII 中,而 ASCII 為 UTF-8 的真子集,故在 C++ 實現中不需要轉換編碼。
  • 小端到大端的轉換(在填充長度和置數 0x80000000 的時候需要注意)。
  • HMAC 的可預計算,計算好 T1 和 T2。

使用附件提供的 hmac_sha256_runhmac_sha256_pad 。我的理解是,先將 Password 填充為 512 bits 的長度的資訊,然後輸入 hmac_sha256_pad 中,計算出中間變數 T1 和 T2,之後,在迴圈時每次使用將每步的鹽再輸入到 hmac_sha256_run 中,之後僅迴圈後一部分即可。

實驗結果

成功的案例:

檔案錯誤:

實驗心得

本次實驗中,我對檔案如何在計算機上一個位元組一個位元組儲存有了比較清晰的認識,而且也理解了 C++ 中的指標的本質實際上是位元組地址。

指標的型別並不影響數值,而隻影響每次讀取地址所代表的值時的方式。比如 char* 型別的指標,取值時每次只讀取一個位元組,但是 unsigned int* 型別的指標,每次讀取 4 個位元組,並在 x86 機器上是以小端序的方式讀入的。

對於二進位制檔案如何讀取也有了一些經驗。

除此之外,瞭解到了加密的一些方法,對於 RAR 的加密部分的流程有了一定的認識。



可能相關的