計算 CRC32 的逆函式

黃志斌發表於2020-03-22

本月初我寫了一篇文章“加密的壓縮包”,說是一道 CTF 題需要根據已知的 CRC32 值反向破解文字檔案(6 bytes)的內容。當時是暴力破解。總共需要破解 3 個 CRC32 值,暴力破解 1 個 CRC32 值平均需要 5 個多小時,還好我的計算機有 4 個邏輯 CPU,可以同時破解這 3 個 CRC32 值。

後來,根據我同事的解題方法,知道可以計算 CRC32 的逆函式。我們把明文儲存在一個長度為 6 的 byte 陣列中,該陣列的前 2 個位元組遍歷所有可能的明文字元,然後根據給定的 CRC32 值(以及該陣列的前 2 個位元組)計算逆函式,填入該陣列的後 4 個位元組,然後判斷它們是否在明文字元範圍內。這個方法只需要 0.1 秒。

  • 暴力破解的計算量:656 = 75,418,890,625
  • 計算逆函式計算量:652 = 4225

這個方法如下所示:

$ csc reversecrc32.cs ~/src/apps/lib/Crc32.cs
$ mono reversecrc32.exe 4B10DEBA
$ mono reversecrc32.exe 1FD8A07A
$ mono reversecrc32.exe E7F7E18C

當然,明文的長度不必須是 6 個字元,不論是上篇文章的暴力破解,還是這篇文章的計算 CRC32 的逆函式,都很容易修改以適應其他明文長度,或者明文有特定的字首或字尾等情況。

下面是相應的 C# 源程式:

reversecrc32.cs:

using System;
using System.Text;
using System.Collections.Generic;
using Skyiv.Utils;

static class ReverseCrc32
{
  static readonly Encoding ascii = new ASCIIEncoding();
  static readonly HashSet<byte> a = new HashSet<byte>();

  static ReverseCrc32()
  {
    for (var i = '0'; i <= '9'; i++) a.Add((byte)i);
    for (var i = 'A'; i <= 'Z'; i++) a.Add((byte)i);
    for (var i = 'a'; i <= 'z'; i++) a.Add((byte)i);
    a.Add((byte)'+'); a.Add((byte)'/'); a.Add((byte)'=');
  }

  static bool Valid(byte[] bs)
  {
    foreach (var b in bs) if (!a.Contains(b)) return false;
    return true;
  }

  static void Main(string[] args)
  {
    var z = Convert.ToUInt32(args[0], 16);
    var bs = new byte[6];
    foreach (var i in a)
      foreach (var j in a) {
        bs[0] = i; bs[1] = j;
        bs.ReverseCrc32(z, 2);
        if (Valid(bs)) Console.WriteLine("{0}", ascii.GetString(bs));
      }
  }
}

Crc32.cs:

using System;
using System.Linq;
using System.Collections.Generic;

namespace Skyiv.Utils
{
  public static class Crc32
  {
    static readonly uint[] t1 = new uint[256];
    static readonly uint[] t2 = new uint[256];

    static Crc32()
    {
      uint p = 0xEDB88320;
      for (uint f, r, j, i = 0; i < t1.Length; t1[i] = f, t2[i] = r, i++)
        for (f = i, r = i << 24, j = 8; j > 0; j--) {
          f = ((f & 1) == 0) ? (f >> 1) : ((f >> 1) ^ p);
          r = ((r & 0x80000000) == 0) ? (r << 1) : (((r ^ p) << 1) | 1);
        }
    }

    public static void ReverseCrc32(this byte[] bs, uint crc, int n)
    {
      if (bs.Length < n + 4) return;
      uint z = 0xFFFFFFFF;
      for (int i = 0; i < n; i++) z = (z >> 8) ^ t1[(z ^ bs[i]) & 0xFF];
      Array.Copy(BitConverter.GetBytes(z), 0, bs, n, 4);
      z = crc ^ 0xFFFFFFFF;
      for (int i = bs.Length-1; i >= n; i--) z = (z<<8)^t2[z>>24]^bs[i];
      Array.Copy(BitConverter.GetBytes(z), 0, bs, n, 4);
    }

    public static uint GetCrc32<T>(this IEnumerable<T> bs)
    {
      return ~bs.Aggregate(0xFFFFFFFF, (r, b) =>
        (t1[(r & 0xFF) ^ Convert.ToByte(b)] ^ (r >> 8)));
    }
  }
}

參考資料

  1. GitHub: CRC32 tools: reverse, undo/rewind, and calculate hashes
  2. Daniel's Software Blog: Calculating Reverse CRC

相關文章