跳转至

常見加密算法和編碼識別

前言

在對數據進行變換的過程中,除了簡單的字節操作之外,還會使用一些常用的編碼加密算法,因此如果能夠快速識別出對應的編碼或者加密算法,就能更快的分析出整個完整的算法。CTF 逆向中通常出現的加密算法包括base64、TEA、AES、RC4、MD5等。

Base64

Base64 是一種基於64個可打印字符來表示二進制數據的表示方法。轉換的時候,將3字節的數據,先後放入一個24位的緩衝區中,先來的字節佔高位。數據不足3字節的話,於緩衝器中剩下的比特用0補足。每次取出6比特(因爲 {\displaystyle 2^{6}=64}),按照其值選擇ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/中的字符作爲編碼後的輸出,直到全部輸入數據轉換完成。

通常而言 Base64 的識別特徵爲索引表,當我們能找到 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ 這樣索引表,再經過簡單的分析基本就能判定是 Base64 編碼。

當然,有些題目 base64 的索引表是會變的,一些變種的 base64 主要 就是修改了這個索引表。

Tea

密碼學中,微型加密算法(Tiny Encryption Algorithm,TEA)是一種易於描述和執行塊密碼,通常只需要很少的代碼就可實現。其設計者是劍橋大學計算機實驗室大衛·惠勒羅傑·尼達姆

參考代碼:

#include <stdint.h>

void encrypt (uint32_t* v, uint32_t* k) {
    uint32_t v0=v[0], v1=v[1], sum=0, i;           /* set up */
    uint32_t delta=0x9e3779b9;                     /* a key schedule constant */
    uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3];   /* cache key */
    for (i=0; i < 32; i++) {                       /* basic cycle start */
        sum += delta;
        v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
        v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);  
    }                                              /* end cycle */
    v[0]=v0; v[1]=v1;
}

void decrypt (uint32_t* v, uint32_t* k) {
    uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i;  /* set up */
    uint32_t delta=0x9e3779b9;                     /* a key schedule constant */
    uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3];   /* cache key */
    for (i=0; i<32; i++) {                         /* basic cycle start */
        v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
        v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
        sum -= delta;                                   
    }                                              /* end cycle */
    v[0]=v0; v[1]=v1;
}

在 Tea 算法中其最主要的識別特徵就是 擁有一個 magic number :0x9e3779b9 。當然,這 Tea 算法也有魔改的,感興趣的可以看 2018 0ctf Quals milk-tea。

RC4

密碼學中,RC4(來自Rivest Cipher 4的縮寫)是一種流加密算法,密鑰長度可變。它加解密使用相同的密鑰,因此也屬於對稱加密算法。RC4是有線等效加密(WEP)中採用的加密算法,也曾經是TLS可採用的算法之一。

void rc4_init(unsigned char *s, unsigned char *key, unsigned long Len) //初始化函數
{
    int i =0, j = 0;
    char k[256] = {0};
    unsigned char tmp = 0;
    for (i=0;i<256;i++) {
        s[i] = i;
        k[i] = key[i%Len];
    }
    for (i=0; i<256; i++) {
        j=(j+s[i]+k[i])%256;
        tmp = s[i];
        s[i] = s[j]; //交換s[i]和s[j]
        s[j] = tmp;
    }
 }

void rc4_crypt(unsigned char *s, unsigned char *Data, unsigned long Len) //加解密
{
    int i = 0, j = 0, t = 0;
    unsigned long k = 0;
    unsigned char tmp;
    for(k=0;k<Len;k++) {
        i=(i+1)%256;
        j=(j+s[i])%256;
        tmp = s[i];
        s[i] = s[j]; //交換s[x]和s[y]
        s[j] = tmp;
        t=(s[i]+s[j])%256;
        Data[k] ^= s[t];
     }
} 

通過分析初始化代碼,可以看出初始化代碼中,對字符數組s進行了初始化賦值,且賦值分別遞增。之後對s進行了256次交換操作。通過識別初始化代碼,可以知道rc4算法。

其僞代碼表示爲:

初始化長度爲256的S盒。第一個for循環將0到255的互不重複的元素裝入S盒。第二個for循環根據密鑰打亂S盒。

  for i from 0 to 255
     S[i] := i
 endfor
 j := 0
 for( i=0 ; i<256 ; i++)
     j := (j + S[i] + key[i mod keylength]) % 256
     swap values of S[i] and S[j]
 endfor

下面i,j是兩個指針。每收到一個字節,就進行while循環。通過一定的算法((a),(b))定位S盒中的一個元素,並與輸入字節異或,得到k。循環中還改變了S盒(©)。如果輸入的是明文,輸出的就是密文;如果輸入的是密文,輸出的就是明文。

 i := 0
 j := 0
 while GeneratingOutput:
     i := (i + 1) mod 256   //a
     j := (j + S[i]) mod 256 //b
     swap values of S[i] and S[j]  //c
     k := inputByte ^ S[(S[i] + S[j]) % 256]
     output K
 endwhile

此算法保證每256次循環中S盒的每個元素至少被交換過一次

python解密腳本

對應例題:《從 0 到 1》RE 篇——BabyAlgorithm

題目鏈接

import base64
def rc4_main(key = "init_key", message = "init_message"):
    print("RC4解密主函數調用成功")
    print('\n')
    s_box = rc4_init_sbox(key)
    crypt = rc4_excrypt(message, s_box)
    return crypt
def rc4_init_sbox(key):
    s_box = list(range(256))
    print("原來的 s 盒:%s" % s_box)
    print('\n')
    j = 0
    for i in range(256):
        j = (j + s_box[i] + ord(key[i % len(key)])) % 256
        s_box[i], s_box[j] = s_box[j], s_box[i]
    print("混亂後的 s 盒:%s"% s_box)
    print('\n')
    return s_box
def rc4_excrypt(plain, box):
    print("調用解密程序成功。")
    print('\n')
    plain = base64.b64decode(plain.encode('utf-8'))
    plain = bytes.decode(plain)
    res = []
    i = j = 0
    for s in plain:
        i = (i + 1) % 256
        j = (j + box[i]) % 256
        box[i], box[j] = box[j], box[i]
        t = (box[i] + box[j]) % 256
        k = box[t]
        res.append(chr(ord(s) ^ k))
    print("res用於解密字符串,解密後是:%res" %res)
    print('\n')
    cipher = "".join(res)
    print("解密後的字符串是:%s" %cipher)
    print('\n')
    print("解密後的輸出(沒經過任何編碼):")
    print('\n')
    return cipher
a=[] #cipher
key=""
s=""
for i in a:
    s+=chr(i)
s=str(base64.b64encode(s.encode('utf-8')), 'utf-8')
rc4_main(key, s)

MD5

MD5消息摘要算法(英語:MD5 Message-Digest Algorithm),一種被廣泛使用的密碼散列函數,可以產生出一個128位(16字節)的散列值(hash value),用於確保信息傳輸完整一致。MD5由美國密碼學家羅納德·李維斯特(Ronald Linn Rivest)設計,於1992年公開,用以取代MD4算法。這套算法的程序在 RFC 1321 中被加以規範。

僞代碼表示爲:

/Note: All variables are unsigned 32 bits and wrap modulo 2^32 when calculating
var int[64] r, k

//r specifies the per-round shift amounts
r[ 0..15]:= {7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22} 
r[16..31]:= {5,  9, 14, 20,  5,  9, 14, 20,  5,  9, 14, 20,  5,  9, 14, 20}
r[32..47]:= {4, 11, 16, 23,  4, 11, 16, 23,  4, 11, 16, 23,  4, 11, 16, 23}
r[48..63]:= {6, 10, 15, 21,  6, 10, 15, 21,  6, 10, 15, 21,  6, 10, 15, 21}

//Use binary integer part of the sines of integers as constants:
for i from 0 to 63
    k[i] := floor(abs(sin(i + 1)) × 2^32)

//Initialize variables:
var int h0 := 0x67452301
var int h1 := 0xEFCDAB89
var int h2 := 0x98BADCFE
var int h3 := 0x10325476

//Pre-processing:
append "1" bit to message
append "0" bits until message length in bits ≡ 448 (mod 512)
append bit length of message as 64-bit little-endian integer to message

//Process the message in successive 512-bit chunks:
for each 512-bit chunk of message
    break chunk into sixteen 32-bit little-endian words w[i], 0 ≤ i ≤ 15

    //Initialize hash value for this chunk:
    var int a := h0
    var int b := h1
    var int c := h2
    var int d := h3

    //Main loop:
    for i from 0 to 63
        if 0 ≤ i ≤ 15 then
            f := (b and c) or ((not b) and d)
            g := i
        else if 16 ≤ i ≤ 31
            f := (d and b) or ((not d) and c)
            g := (5×i + 1) mod 16
        else if 32 ≤ i ≤ 47
            f := b xor c xor d
            g := (3×i + 5) mod 16
        else if 48 ≤ i ≤ 63
            f := c xor (b or (not d))
            g := (7×i) mod 16

        temp := d
        d := c
        c := b
        b := leftrotate((a + f + k[i] + w[g]),r[i]) + b
        a := temp
    Next i
    //Add this chunk's hash to result so far:
    h0 := h0 + a
    h1 := h1 + b 
    h2 := h2 + c
    h3 := h3 + d
End ForEach
var int digest := h0 append h1 append h2 append h3 //(expressed as little-endian)

其鮮明的特徵是:

    h0 = 0x67452301;
    h1 = 0xefcdab89;
    h2 = 0x98badcfe;
    h3 = 0x10325476;