Base64

转换步骤

  1. 将待转换的字符串,每 3 个字节分为一组,每个字节占 8 bit,共 24 个二进制位
  2. 将上面的 24 个二进制位,每 6 个字节做为一组,共分为 4 组 (若最后一组字符数不足三个,用 ‘=’ 补充)
  3. 在每组前面添加两个 0,每组由 6 个变为 8 个二进制位,总共 32 个二进制位,即 4 个字节
  4. 根据 Base64 编码对照表获得对应的值

Base64 算法解码过程
去掉所有的等号,查表将字符转为二进制的索引值,最后每 8 位一组计算 ASCii 码还原字符,不足 8 位则丢弃

原始 Base64 码表ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

Base64 & Base32 & Base16
Base64 就是用每 6 位二进制(2 的 6 次幂就是 64) 来表示一个字符
Base32 就是用每 5 位二进制(2 的 5 次幂就是 32) 来表示一个字符
Base16 就是用每 4 位二进制(2 的 4 次幂就是 16) 来表示一个字符

问:Base64 为什么使用 3 个字节作为一组呢?
因为 6 和 8 的最小公倍数为 24,三个字节正好 24 个二进制位,每 6 bit 为一组,恰好能够分为 4 组

特点

  1. Base64 要用到 Base64 码表,可以在程序中找到连续的字符串:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

  2. 根据 Base64 加密原理,代码中必然存在根据余数个数判断是否添加等号的代码
    字符 '=' 的 ASCii 码:61(0x3D),也有可能直接索引码表里面的 '='

  3. 识别代码中对数据的左移右移操作
    ((a[0] & 3) << 4) | (a[1] >> 4 )(16 * (a[0] & 3)) | (a[1] / 16) 是等价操作,都表示取 a[0] 后 2 位与 a[1] 前 4 位拼接,是 Base64 中的常见操作

  4. 最主要的是理解编码解码原理,比如编码时通常都会用 3 个字节一组来处理比特位数据


原理

以下图的表格为示例

加密算法的函数特征1.jpeg

具体分析一下整个过程:

  1. 第 1 步,根据 'M''a''n' 对应的 ASCii 码值分别为 77,97,110,对应的二进制值是:01001101、01100001、01101110,由此组成一个 24 位的二进制字符串
  2. 第 2 步,如图红色框,将 24 位每 6 位二进制位一组分成 4 组
  3. 第 3 步,在上面每一组前面补两个 0,扩展成 32 个二进制位:00010011、00010110、00000101、00101110
  4. 第 4 步,四组 8bit 分别对应的值 (Base64 编码索引) 为:19、22、5、46,在 Base64 编码表中进行查找,分别对应:'T''W''F''u',因此 “Man” 经过 Base64 编码之后就变为:"TWFu"

如果遇到位数不足的情况,位数不足用 ‘=’ 补充,总共有两种情况:

  1. 最后一组只有一个字符
  2. 最后一组有两个字符

加密算法的函数特征2.png

加解密代码

Python 版(简洁脚本)

  • 特点

    1. 可以更换加密的码表
    2. 快捷,直接使用即可
  • 代码

import base64

# enc表示更换码表后的待解密字符串
enc = "x2dtJEOmyjacxDemx2eczT5cVS9fVUGvWTuZWjuexjRqy24rV29q"

# new_table表示更换后的码表
new_table = "ZYXABCDEFGHIJKLMNOPQRSTUVWzyxabcdefghijklmnopqrstuvw0123456789+/"

# old_table表示原始码表
old_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

# 将enc还原成原始码表加密后的内容,存放在dec中
dec = enc.translate(str.maketrans(new_table, old_table))

# Base64解密,base64.b64decode()的结果为 bytes 类型
print(base64.b64decode(dec))

Python 版(完整系统)

  • 特点

    1. 可以更换加密的码表
    2. 支持加密和解密
  • 代码

# coding:utf-8  
s = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"  # 原始码表  
  
  
def my_base64_encode(inputs):  
    try:  
        # 将字符串转化为2进制  
        bin_str = []  
        for i in inputs:  
            x = str(bin(ord(i))).replace('0b', '')  
            bin_str.append('{:0>8}'.format(x))  
        # print(bin_str)  
        # 输出的字符串  
        outputs = ""  
        # 不够三倍数,需补齐的次数  
        nums = 0  
        while bin_str:  
            # 每次取三个字符的二进制  
            temp_list = bin_str[:3]  
            if len(temp_list) != 3:  
                nums = 3 - len(temp_list)  
                while len(temp_list) < 3:  
                    temp_list += ['0' * 8]  
            temp_str = "".join(temp_list)  
            # print(temp_str)  
            # 将三个8字节的二进制转换为4个十进制  
            temp_str_list = []  
            for i in range(0, 4):  
                temp_str_list.append(int(temp_str[i * 6:(i + 1) * 6], 2))  
            # print(temp_str_list)  
            if nums:  
                temp_str_list = temp_str_list[0:4 - nums]  
  
            for i in temp_str_list:  
                outputs += s[i]  
            bin_str = bin_str[3:]  
        outputs += nums * '='  
        print("加密完成:\n%s " % outputs)  
    except Exception as e:  
        print(f"加密错误: \n{e}")  
  
  
def my_base64_decode(inputs):  
    try:  
        # 将字符串转化为2进制  
        bin_str = []  
        for i in inputs:  
            if i != '=':  
                x = str(bin(s.index(i))).replace('0b', '')  
                bin_str.append('{:0>6}'.format(x))  
        # print(bin_str)  
        # 输出的字符串  
        outputs = ""  
        nums = inputs.count('=')  
        while bin_str:  
            temp_list = bin_str[:4]  
            temp_str = "".join(temp_list)  
            # print(temp_str)  
            # 补足8位字节  
            if (len(temp_str) % 8 != 0):  
                temp_str = temp_str[0:-1 * nums * 2]  
            # 将四个6字节的二进制转换为三个字符  
            for i in range(0, int(len(temp_str) / 8)):  
                outputs += chr(int(temp_str[i * 8:(i + 1) * 8], 2))  
            bin_str = bin_str[4:]  
        print("解密完成:\n%s " % outputs)  
    except Exception as e:  
        print(f"解密错误: \n{e}")  
  
  
print("     可更换码表的 Base64 加解密系统       ")  
print("*************************************")  
select = input("是否更换加密的码表? (y or n) 你的选择: ")  
if select == "y" or select == "yes":  
    s = input("在这里输入码表: ")  
    print(".....done, 已更改!")  
else:  
    print("码表未做更改!")  
  
input_str = input("输入数据: ")  
print("*************************************")  
my_base64_encode(input_str)  
print("*************************************")  
my_base64_decode(input_str)  
print("*************************************", end='')

C++ 版(完整系统)

  • 特点

    1. 可以更换加密的码表
    2. 支持加密和解密
  • 代码

#include <stdio.h>  
#include <string.h>  
  
// Base64 字符表  
char base64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";  
  
// 编码一个字节  
char encode_byte(unsigned char b) {  
    return base64chars[b];  
}  
  
// Base64 编码  
void base64_encode(const char *input, size_t length) {  
    for (size_t i = 0; i < length; i += 3) {  
        // 取三个字节  
        unsigned int triplet = (input[i] << 16) | (i + 1 < length ? input[i + 1] << 8 : 0) | (i + 2 < length ? input[i + 2] : 0);  
  
        // 分割成四个六位字节并编码  
        printf("%c%c%c%c",  
               encode_byte((triplet >> 18) & 0x3F),  
               encode_byte((triplet >> 12) & 0x3F),  
               i + 1 < length ? encode_byte((triplet >> 6) & 0x3F) : '=',  
               i + 2 < length ? encode_byte(triplet & 0x3F) : '=');  
    }  
    printf("\n");  
}  
  
// 映射关系  
int decode_char(char ch) {  
    for (int i = 0; i < 64; i++) {  
        if (base64chars[i] == ch) {  
            return i;  
        }  
    }  
    return 0;  
}  
  
// Base64 解码  
void base64_decode(const char *input, size_t length) {  
    for (size_t i = 0; i < length; i += 4) {  
        // 取四个 Base64 字符  
        unsigned int quad = (decode_char(input[i]) << 18) | (decode_char(input[i + 1]) << 12) |  
                            (decode_char(i + 2 < length ? input[i + 2] : '=') << 6) |  
                            (decode_char(i + 3 < length ? input[i + 3] : '='));  
  
        // 分割成三个字节并输出  
        printf("%c%c%c",  
               (quad >> 16) & 0xFF,  
               i + 2 < length ? (quad >> 8) & 0xFF : 0,  
               i + 3 < length ? quad & 0xFF : 0);  
    }  
    printf("\n");  
}  
  
void start() {  
    printf("     可更换码表的 Base64 加解密系统       \n");  
    printf("*************************************\n");  
  
    char option;  
    printf("是否更换加密的码表? (y or n) 你的选择: ");  
    fflush(stdout);  
    scanf("%c", &option);  
  
    if(option == 'y' || option == 'Y') {  
        printf("在这里输入新的码表: ");  
        fflush(stdout);  
        scanf("%s", base64chars);  
        printf(".....done, 已更改!\n");  
    } else  
        printf("码表未做更改!\n");  
}  
  
int main() {  
    start();  
  
    char data[256];  
  
    printf("输入数据: ");  
    fflush(stdout);  
    scanf("%s", data);  
    size_t input_length = strlen(data);  
    printf("*************************************\n");  
    printf("加密结果: \n");  
    base64_encode(data, input_length);  
    printf("*************************************\n");  
    printf("解密结果: \n");  
    base64_decode(data, input_length);  
    printf("*************************************");  
  
    return 0;  
}

IDA 示例

char *__fastcall base64_encode(char *a1)
{
    int v1; // eax  
    int v2; // eax  
    int v3; // eax  
    int v4; // eax  
    int v5; // eax  
    int v6; // eax  
    int v8; // [rsp+1Ch] [rbp-54h]  
    int v9; // [rsp+20h] [rbp-50h]  
    int v10; // [rsp+24h] [rbp-4Ch]  
    int v11; // [rsp+24h] [rbp-4Ch]  
    int v12; // [rsp+24h] [rbp-4Ch]  
    int v13; // [rsp+28h] [rbp-48h]  
    int v14; // [rsp+2Ch] [rbp-44h]  
    char src[56]; // [rsp+30h] [rbp-40h]  
    unsigned __int64 v16; // [rsp+68h] [rbp-8h]  
    
    v16 = __readfsqword(0x28u);
    v1 = strlen(a1);    // a1为输入的字符串  
    v14 = v1 % 3;   // v14为输入字符串长度除3以后的余数  
    v13 = v1 / 3;   // v13为3个一组的字符组合数量  
    memset(src, 0, 0x30uLL);
    v10 = 0;
    v8 = 0;
    v9 = 0;
    while ( v8 < v13 )
    {
        v2 = v10;
        v11 = v10 + 1;
        // 第一个:第一个字符右移2位,取前6位作为索引值,查找对应字符
        src[v2] = base64_table[a1[v9] >> 2];    
        v3 = v11++;    
        // 第二个:第一个字符取后2位与第二个字符的前4位拼接    
        src[v3] = base64_table[(16 * (a1[v9] & 3)) | (a1[v9 + 1] >> 4)];    
        // 第三个:第二个字符取后4位与第三个字符的前2位拼接,查找对应字符    
        src[v11] = base64_table[(4 * (a1[v9 + 1] & 0xF)) | (a1[v9 + 2] >> 6)];  
        v4 = v11 + 1;    
        v10 = v11 + 2;    
        // 第四个:第三个字符取后6位作为索引,查找对应字符  
        src[v4] = base64_table[a1[v9 + 2] & 0x3F];    
        v9 += 3;    
        ++v8;  
    }  
    if ( v14 == 1 )
    {    // 余数为1,则需要添加两个等号    
        src[v10] = base64_table[a1[v9] >> 2];    
        src[v10 + 1] = base64_table[16 * (a1[v9] & 3)];    
        strcat(src, "==");  
    }
    else if ( v14 == 2 )
    {   // 余数为2,则需要添加1个等号      
        v5 = v10;    
        v12 = v10 + 1;    
        src[v5] = base64_table[a1[v9] >> 2];    
        v6 = v12++;    
        src[v6] = base64_table[(16 * (a1[v9] & 3)) | (a1[v9 + 1] >> 4)];    
        src[v12] = base64_table[4 * (a1[v9 + 1] & 0xF)];    
        src[v12 + 1] = '=';  
    }  
    strcpy(a1, src);
    return a1;
}

RC4

RC4 是对称加密算法,通过密钥 key 和 S 盒生成密钥流,明文逐字节异或 S 盒,同时 S 盒也会发生改变
加密与解密使用了相同的函数和密钥 K,加密的强度主要来源于密钥的安全性,密钥泄露能直接解密出明文

相关 Writeup 见 《【攻防世界】crypt》、《【攻防世界】ereere

特点

  1. RC4 加密算法属于流加密算法,包括初始化函数加解密函数

  2. 初始化函数中有两个 256 循环,第一个循环给 s 盒初始化为 0 - 255,第二个循环根据密钥 key 对 s 盒 swap

  3. 加解密函数中有一个 256 循环,使明文和 s 盒异或生成密文


原理

初始化部分

初始化长度为 256 的 S 盒。第一个 for 循环将 0 到 255 的互不重复的元素装入 S 盒;第二个 for 循环根据密钥 key 打乱 S 盒,i 确保 S-box 的每个元素都得到处理,j 保证 S-box 的搅乱是随机的

不同的 S-box 在经过伪随机子密码生成算法的处理后可以得到不同的子密钥序列,将 S-box 和明文进行 xor 运算,得到密文,解密过程也完全相同

void rc4_init(unsigned char *s, unsigned char *key, unsigned long key_Len)
{
    int i = 0, j = 0;
    unsigned char k[256] = {0};    //临时向量 k
    unsigned char tmp = 0;
    for(i = 0; i < 256; i++) {
        s[i] = i;
        k[i] = (unsigned char) key[i % key_Len];    //Len = strlen(key),密钥的长度
    }
    for(i = 0; i < 256; i++) {    //打乱s表
        j = (j + s[i] + k[i]) % 256;
        tmp = s[i];
        s[i] = s[j];    //交换s[i]和s[j]
        s[j] = tmp;
    }
}

加解密部分

每收到一个字节,就进行循环。通过一定的算法定位 S 盒中的一个元素,并与输入字节异或,得到 k;同时,循环中还改变了 S 盒

如果输入的是明文,输出的就是密文;如果输入的是密文,输出的就是明文

void rc4_crypt(unsigned char *s, unsigned char *Data, unsigned long Data_Len)
{
    int i = 0, j = 0, t = 0;
    unsigned long k = 0;
    unsigned char tmp;
    for(k = 0; k < Data_Len; k++)
    {
        i = (i + 1) % 256;
        j = (j + s[i]) % 256;
        tmp = s[i];
        s[i] = s[j];    //交换s[i]和s[j]
        s[j] = tmp;
        t = (s[i] + s[j]) % 256;
        Data[k] ^= s[t];
    }
}

加解密代码

Python 版(简洁脚本)

  • 特点

    1. 利用 Python 库快捷实现 RC4 算法
    2. Key 表示 RC4 的密钥,Str 表示待加解密的内容 (bytes 型)
  • 代码

from Crypto.Cipher import ARC4

Str = b''
Key = ""

flag = ARC4.new(bytes(Key, encoding='utf-8')).decrypt(Str)  
print(flag)
  • 示例
Str = [100, 217, 79, 52, 168, 29, 108, 198, 213, 33, 14, 114, 152, 198, 117, 185, 143]  # 待加解密的内容  
Key = "SecretKey"  # 密钥key  
  
Str = bytes(Str)

''' 输出:
b'password123456789'
'''

Python 版(具体实现)

  • 特点

    1. 包含具体实现流程
  • 代码

Str = ""    # 待加解密的内容
Key = ""    # 密钥key
flag = ""   # 存放加解密后的结果

# ---------- rc4_init ----------
s_box = []  # 定义 s 盒
for i in range(256):    # 生成初始 s 盒
    s_box.append(i)
    #   T[i] = K[i mod len(Key)]    # 这个算法里没有 T[i],下面会解释
t = 0
j = 0
for i in range(256):    # 打乱 s 盒顺序
    tmp = s_box[i]
    j = (j + s_box[i] + ord(Key[t])) & 0xff     # j = (j + S[i] + T[i]) mod 256
    s_box[i] = s_box[j]
    s_box[j] = tmp
    t = t + 1               # 这里引入的 t 加一个 if 条件其实就是为了做 t = i % len(Key)
    if t >= len(Key):       # Key[t] 配合 t = i % len(Key) 就是实现了 T[i] = K[i mod len(Key)]
        t = 0               # 小细节写法不同而已,大致思路是一样的

# ---------- rc4_crypt ----------
i = 0
j = 0
for k in range(len(Str)):
    i = (i + 1) & 0xff
    j = (j + s_box[i]) & 0xff      # & 0xff 是为了做 % 256,两者效果相同
    tmp = s_box[i]
    s_box[i] = s_box[j]
    s_box[j] = tmp
    t = (s_box[i] + s_box[j]) & 0xff    # & 0xff 是为了做 % 256,两者效果相同
    flag += chr(Str[k] ^ s_box[t])      # 明文异或得密文,密文异或得明文

print("解密结果: " + flag)
  • 示例
Str = [100, 217, 79, 52, 168, 29, 108, 198, 213, 33, 14, 114, 152, 198, 117, 185, 143]  # 待加解密的内容  
Key = "SecretKey"  # 密钥key  
flag = ""  # 存放加解密后的结果

''' 输出:
解密结果: password123456789
'''

C++ 版(具体实现)

  • 特点

    1. 包含具体实现流程
  • 代码

#include <stdio.h> 


void rc4_init(unsigned char *s, unsigned char *key, unsigned long key_Len)  
{  
    int i = 0, j = 0;  
    unsigned char k[256] = {0};    //临时向量 k
    unsigned char tmp = 0;  
    for(i = 0; i < 256; i++) {  
        s[i] = i;  
        k[i] = (unsigned char) key[i % key_Len];    //Len = strlen(key),密钥的长度  
    }  
    for(i = 0; i < 256; i++) {    //打乱s表  
        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 Data_Len)  
{  
    int i = 0, j = 0, t = 0;  
    unsigned long k = 0;  
    unsigned char tmp;  
    for(k = 0; k < Data_Len; k++)  
    {  
        i = (i + 1) % 256;  
        j = (j + s[i]) % 256;  
        tmp = s[i];  
        s[i] = s[j];    //交换s[i]和s[j]  
        s[j] = tmp;  
        t = (s[i] + s[j]) % 256;  
        Data[k] ^= s[t];  
    }  
}
  • 示例一
#include <string.h>

int main() 
{  
	unsigned char data[] = "password123456789";
    unsigned char key[] = "SecretKey";   // 密钥 key      
  
    // 获取密钥长度  
    unsigned long key_len = strlen((char *)key);  
  
    // 初始化 RC4 状态向量  
    unsigned char state[256];  
    rc4_init(state, key, key_len);  
  
    // 加密数据  
    rc4_crypt(state, data, strlen((char *)data));  
    printf("加密后: %s\n", data);  
  
    // 重新初始化 RC4 状态向量  
    rc4_init(state, key, key_len);  
  
    // 解密数据  
    rc4_crypt(state, data, strlen((char *)data));  
    printf("解密后: %s\n", data);  
  
    return 0;  
}

/* 输出:
加密后: d貽4�l普!r樒u箯
解密后: password123456789
*/
  • 示例二
#include <string.h>

int main() 
{
    unsigned char data[] = {0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39};
    unsigned char key[] = "SecretKey";   // 密钥 key

    // 获取数据长度
    unsigned long data_len = sizeof(data) / sizeof(data[0]);
    
    // 获取密钥长度
    unsigned long key_len = strlen((char *)key);

    // 初始化 RC4 状态向量
    unsigned char state[256];
    rc4_init(state, key, key_len);

    // 加密数据
    rc4_crypt(state, data, data_len);
    printf("加密后: %s\n", data);

    // 重新初始化 RC4 状态向量
    rc4_init(state, key, key_len);

    // 解密数据
    rc4_crypt(state, data, data_len);
    printf("解密后: %s\n", data);

    return 0;
}

/* 输出:
加密后: d貽4�l普!r樒u箯D{�
解密后: password123456789D{�
*/

IDA 示例

__int64 __fastcall rc4_init(_DWORD *a1, __int64 a2, int a3)
{
  __int64 result; // rax
  int i; // [rsp+0h] [rbp-28h]
  int j; // [rsp+0h] [rbp-28h]
  int v6; // [rsp+4h] [rbp-24h]
  int v7; // [rsp+8h] [rbp-20h]
  int v8; // [rsp+Ch] [rbp-1Ch]
  _DWORD *v9; // [rsp+10h] [rbp-18h]

  *a1 = 0;
  a1[1] = 0;
  v9 = a1 + 2;
  for ( i = 0; i < 256; ++i )
    v9[i] = i;    //循环给 s 盒赋值
  v6 = 0;
  result = 0i64;
  LOBYTE(v7) = 0;
  for ( j = 0; j < 256; ++j )    //循环根据密钥 key 对 s 盒进行 swap
  {    //Ⅰ、Ⅱ、Ⅲ 交换v9[j]和v9[v7]的值
    v8 = v9[j];    //Ⅰ
    v7 = (*(a2 + v6) + v8 + v7);
    v9[j] = v9[v7];    //Ⅱ
    v9[v7] = v8;    //Ⅲ
    if ( ++v6 >= a3 )
      v6 = 0;
    result = (j + 1);
  }
  return result;
}


_DWORD *__fastcall rc4_crypt(_DWORD *a1, __int64 a2, int a3)
{
  _DWORD *result; // rax
  int i; // [rsp+0h] [rbp-28h]
  int v5; // [rsp+4h] [rbp-24h]
  int v6; // [rsp+8h] [rbp-20h]
  int v7; // [rsp+Ch] [rbp-1Ch]
  int v8; // [rsp+10h] [rbp-18h]
  _DWORD *v9; // [rsp+18h] [rbp-10h]

  v5 = *a1;
  v6 = a1[1];
  v9 = a1 + 2;
  for ( i = 0; i < a3; ++i )
  {    //Ⅰ、Ⅱ、Ⅲ、Ⅳ 交换v9[v5]和v9[v6]的值
    v5 = (v5 + 1);    //Ⅰ
    v7 = v9[v5];
    v6 = (v7 + v6);
    v8 = v9[v6];    //Ⅱ
    v9[v5] = v8;    //Ⅲ
    v9[v6] = v7;    //Ⅳ
    *(a2 + i) ^= LOBYTE(v9[(v8 + v7)]);
  }
  *a1 = v5;
  result = a1;
  a1[1] = v6;
  return result;
}

TEA

TEA 算法全称微型加密算法,最初是由剑桥计算机实验室的 David Wheeler 和 Roger Needham 在 1994 年设计的。 TEA 算法使用 64 位的明文分组和 128 位的密钥,它使用 Feistel 分组加密框架,需要进行 64 轮迭代,尽管作者认为 32 轮已经足够了,32 轮迭代加密后得到的密文就是 64 位

该算法使用了一个神秘常数 δ(Delta) 作为倍数,它来源于黄金比率,以保证每一轮加密都不相同。但 δ(Delta) 的精确值似乎并不重要,这里 TEA 把它定义为 δ =「(√5 - 1)231」(也就是程序中的 0x9e3779b9

相关 Writeup 见 《【GDOUCTF 2023】Tea

特点

  1. 密钥为 128 位 (一般分为 4 个 32 位子密钥)明文为 64 位 (一般分为 2 个 32 位明文),主要做了 32 轮变换,每轮变换中都涉及移位和变换操作

  2. TEA 算法中有一个固定的常数 δ(Delta),通常为 0x9e3779b9 或者 0x61c88647,当然也可能魔改

  3. 有对常数 δ(Delta) 进行的操作,在 TEA 算法加密过程中,δ = 0x9e3779b9 时代码中会出现累加,δ = 0x61c88647 时代码中会出现累减 (也就是说,加密过程中:sum -= 0x61c88647sum += 0x9e3779b9,这两个值是等价的)

  4. δ = 0x9e3779b9 时,解密的初始值 sun = num_rounds * DELTA;当 δ = 0x61c88647 时,解密的初始值 sun = - num_rounds * DELTA

  5. 涉及到的移位操作通常是左移 4 位、右移 5 位

  6. 在 TEA 算法中取密钥 key 的时候是固定下标取的


原理

  1. 将明文按 64 位(8 字节)分组,每组视为一个 64 位的二进制数,同时将这 64 位的二进制数分成两个相等长度的部分 v0v1(两个 32 位明文数据)
  2. 将 128 位的密钥分成 4 个 32 位的子密钥,分别记为 k0、k1、k2、k3(四个 32 位密钥)
  3. 设定一个 32 位的常数 delta,其值为 0x9E3779B9,(也可以为 0x61c88647
  4. 定义 32 轮加密迭代,每轮中右半部分会经过一个运算,包括左移、异或、加等操作,并且每轮的密钥是不同的
  5. 最后得到经过加密算法加密的密文 v0'v1'

加密流程如图:

加密算法的函数特征3.png


加解密代码

C++ 版

  • 特点

    1. 包含具体实现流程
    2. 支持快速修改加密轮数 num_roundsdelta 的值
  • 代码

#include <stdio.h> 
#include <stdint.h>  
#define DELTA 0x9e3779b9   // delta值


//加密函数  
void tea_encrypt(unsigned int num_rounds, uint32_t* v, uint32_t* key) 
{ 
    uint32_t v0 = v[0], v1 = v[1];   // 初始化参数   
    uint32_t sum = 0;   // 初始sum为0  
    uint32_t k0 = key[0], k1 = key[1], k2 = key[2], k3 = key[3];   // key值  
    for (int i = 0; i < num_rounds; i++) {   // 一般为32轮加密  
        sum += DELTA;  
        v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);  
        v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);  
    }  
    v[0] = v0; v[1] = v1;   // 将结果存回原密钥数组  
}  


//解密函数  
void tea_decrypt(unsigned int num_rounds, uint32_t* v, uint32_t* key) 
{  
    uint32_t v0 = v[0], v1 = v[1];   // 初始化参数   
    uint32_t sum = num_rounds * DELTA;   // 初始sum为delta*加密轮数, 如果dalta为0x9e3779b9,加密32轮,则这个值为0xC6EF3720  
    uint32_t k0 = key[0], k1 = key[1], k2 = key[2], k3 = key[3];   // key值  
  
    for (int i = 0; i < num_rounds; i++) {   // 一般为32轮解密  
        v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);  
        v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);  
        sum -= DELTA;  
    }  
    v[0] = v0; v[1] = v1;  
}
  • 示例一
int main()
{
    uint32_t data[] = {0xDEADBEEF, 0xFACEB00C, 0xDAC0FFEE, 0xABADBABE};
    uint32_t key[] = {0x11111111, 0x22222222, 0x33333333, 0x44444444};
    unsigned int r = 32;   // 轮数
    int data_len = sizeof(data) / sizeof(data[0]);   // data 的长度

    for (int i = 0; i < data_len; i = i + 2)   // 2 个 4 字节为一组
        tea_encrypt(r, &data[i], key);
    printf("加密后:0x%x 0x%x 0x%x 0x%x \n", data[0], data[1], data[2], data[3]);

    for (int i = 0; i < data_len; i = i + 2)   // 2 个 4 字节为一组
        tea_decrypt(r, &data[i], key);
    printf("解密后:0x%x 0x%x 0x%x 0x%x \n", data[0], data[1], data[2], data[3]);

    return 0;
}

/* 输出:
加密后:0x99ad7ec2 0x938c4882 0x10ad3961 0xe971209e 
解密后:0xdeadbeef 0xfaceb00c 0xdac0ffee 0xabadbabe 
*/
  • 示例二
#include <cstring>

int main()
{
    char data[] = "password123456789";
    uint32_t key[] = {0x11111111, 0x22222222, 0x33333333, 0x44444444};
    unsigned int r = 32;   // 轮数
    int data_len = strlen(data);   // data 的长度

    for (int i = 0; i < data_len; i = i + 8)   // 8 字节为一组
        tea_encrypt(r, (uint32_t*) &data[i], key);
    printf("加密后:%s\n", data);

    for (int i = 0; i < data_len; i = i + 8)   // 8 字节为一组
        tea_decrypt(r, (uint32_t*) &data[i], key);
    printf("解密后:%s\n", data);

    return 0;
}

/* 输出:
加密后:(乱码)
解密后:password123456789
*/

TEA 算法可能的魔改:

  1. 修改 delta 值,不再为 0x9e3779b9 或者 0x61c88647
  2. 修改加密过程,在每一轮迭代中增加可逆运算,例如加上一个 ^ (sum + i)
for (int i = 0; i < num_rounds; i++) { // 一般为 32 轮加密  
sum += DELTA;  
v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1) ^ (sum + i);  
v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3) ^ (sum + i);  
}

那么解密时,则修改为:

for (int i = 0; i < num_rounds; i++) {   // 一般为32轮解密
v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3) ^ (sum + (num_rounds - 1 - i)); 
v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1) ^ (sum + (num_rounds - 1 - i)); 
sum -= DELTA;  
}
  1. 在最后赋值时,添加可逆运算:
v[0] = v0 ^ 0x12; v[1] = v1 ^ 0x34;

IDA 示例

__int64 __fastcall tea_encrypt(unsigned int a1, unsigned int *a2, unsigned int *a3)
{
  __int64 result; // rax
  unsigned int v4; // [rsp+18h] [rbp-20h]
  unsigned int v5; // [rsp+1Ch] [rbp-1Ch]
  int v6; // [rsp+20h] [rbp-18h]
  unsigned int i; // [rsp+24h] [rbp-14h]

  v4 = *a2;
  v5 = a2[1];
  v6 = 0;
  for ( i = 0; a1 > i; ++i )
  {
    v6 -= 1640531527;   // 即:0x61C88647
    v4 += (v5 + v6) ^ (16 * v5 + *a3) ^ ((v5 >> 5) + a3[1]);
    v5 += (v4 + v6) ^ (16 * v4 + a3[2]) ^ ((v4 >> 5) + a3[3]);
  }
  *a2 = v4;
  result = v5;
  a2[1] = v5;
  return result;
}

XTEA

由于 TEA 算法被发现存在缺陷,作为回应,设计者提出了一个 TEA 的升级版本——XTEA

XTEA 是 TEA 的扩展,也称做 TEAN,它使用与 TEA 相同的简单运算,同样是一个 64 位块的 Feistel 密码,使用 128 位密钥,建议 64 轮,但四个子密钥采取不正规的方式进行混合以阻止密钥表攻击

特点

  1. 基本上 TEA 的加密特点 XTEA 也都有,例如:存在 DELTA 值

  2. 同样,涉及到的移位操作通常是左移 4 位、右移 5 位;不同的是,对于 sum 的累加或累减操作位于 v0v1 两个数据处理之间

  3. 与 TEA 不同的是,在 XTEA 中密钥 key 的下标是通过计算得来的,分别是 key[sum & 3]key[(sum >> 11) & 3]


原理

与 TEA 的思想基本类似,对于密钥 key 的取法与 TEA 有所不同

加密流程如图:

加密算法的函数特征4.png


加解密代码

C++ 版

  • 特点

    1. 包含具体实现流程
    2. 支持快速修改加密轮数 num_roundsdelta 的值
  • 代码

#include <stdio.h>  
#include <stdint.h>  
#define DELTA 0x9e3779b9   // DELTA值  


//加密函数
void xtea_encrypt(unsigned int num_rounds, uint32_t* v, uint32_t* key) 
{  
    uint32_t v0 = v[0], v1 =v [1];  
    uint32_t sum = 0;  
    for (int i = 0; i < num_rounds; i++) {  
        v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);  
        sum += DELTA;  
        v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);  
    }  
    v[0] = v0; v[1] = v1;  
}  


//解密函数
void xtea_decrypt(unsigned int num_rounds, uint32_t* v, uint32_t* key) 
{  
    uint32_t v0 = v[0], v1 = v[1];  
    uint32_t sum = DELTA * num_rounds;  
    for (int i = 0; i < num_rounds; i++) {  
        v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);  
        sum -= DELTA;  
        v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);  
    }  
    v[0] = v0; v[1] = v1;  
}
  • 示例一
int main()  
{  
    uint32_t data[]={  
            0x168F8672, 0x02DBD824, 0xCF647FCA, 0xE6EFA7EF, 0x4AE016F0, 0xC5832E1D, 0x455C0A05, 0xFFEB8140,  
            0xBE9561EF, 0x7F819E23, 0x3BC04269, 0xC68B825B, 0xE6A5B1F0, 0xBD03CBBD, 0xA9B3CE0E, 0x6C85E6E7,  
            0x9F5C71EF, 0x3BE4BD57  
    };  
    uint32_t key[] = {0xDEADBEEF, 0x87654321, 0xFACEB00C, 0xCAFEBABE};  
    unsigned int r = 32;   // 轮数  
    int data_len = sizeof(data) / sizeof(data[0]);   // data 的长度  
  
    printf("解密后:");  
    for (int i = 0; i < data_len; i = i + 2) {   // 2 个 4 字节为一组  
        xtea_decrypt(r, &data[i], key);  
        printf("%s", &data[i]);   // 这种方式输出无需转换小端序  
    }  
  
    printf("加密后:");  
    for (int i = 0; i < data_len; i = i + 2) {   // 2 个 4 字节为一组  
        xtea_encrypt(r, &data[i], key);  
        printf("%s", &data[i]);  
    }  
  
    return 0;  
}

/* 输出:
解密后:DASCTF{Don't_forget_to_drink_tea}
加密后:(乱码)
*/
  • 示例二
#include <cstring>  
  
int main()
{
    char data[] = "DASCTF{Don't_forget_to_drink_tea}";
    uint32_t key[] = {0x11111111, 0x22222222, 0x33333333, 0x44444444};
    unsigned int r = 32;  // 轮数
    int data_len = strlen(data);   // data 的长度

    printf("加密后:\n");
    for (int i = 0; i < data_len; i = i + 8)   // 8 字节为一组
        xtea_encrypt(r, (uint32_t*) &data[i], key);
    printf("%s\n", data);

    printf("解密后:\n");
    for (int i = 0; i < data_len; i = i + 8)   // 8 字节为一组
        xtea_decrypt(r, (uint32_t*) &data[i], key);
    printf("%s\n", data);

    return 0;
}

/* 输出:
加密后:(乱码)
解密后:DASCTF{Don't_forget_to_drink_tea}
*/

IDA 示例

__int64 __fastcall encode(unsigned int a1, unsigned int *a2, __int64 a3)
{
  __int64 v3; // r11
  __int64 result; // rax
  unsigned int v5; // [rsp+8h] [rbp-28h]
  unsigned int v6; // [rsp+Ch] [rbp-24h]
  unsigned int v7; // [rsp+10h] [rbp-20h]
  unsigned int i; // [rsp+14h] [rbp-1Ch]

  v7 = *a2;
  v6 = a2[1];
  v5 = 0;   // 初始 sum 为 0
  for ( i = 0; i < a1; ++i )   // a1 为迭代轮数
  {
    v7 += ~(*(_DWORD *)(a3 + 4LL * ~(~v5 | 0xFFFFFFFC)) + v5) & (v6 + (~(v6 >> 5) & (16 * v6) | ~(16 * v6) & (v6 >> 5))) | ~(v6 + (~(v6 >> 5) & (16 * v6) | ~(16 * v6) & (v6 >> 5))) & (*(_DWORD *)(a3 + 4LL * ~(~v5 | 0xFFFFFFFC)) + v5);
    v5 -= 1640531527;   // 即:0x61C88647
    v3 = (v5 >> 11) & ((v5 >> 11) ^ 0xFFFFFFFC);
    v6 += ~(*(_DWORD *)(a3 + 4 * v3) + v5) & (v7 + (~(v7 >> 5) & (16 * v7) | ~(16 * v7) & (v7 >> 5))) | ~(v7 + (~(v7 >> 5) & (16 * v7) | ~(16 * v7) & (v7 >> 5))) & (*(_DWORD *)(a3 + 4 * v3) + v5);
  }
  *a2 = v7;   // 将加密后的结果写回原数组
  result = v6;
  a2[1] = v6;   // 将加密后的结果写回原数组
  return result;
}

XXTEA

XXTEA,又称 Corrected Block TEA,是 XTEA 的升级版 ,支持块加密,设计者是 Roger Needham、David Wheeler,由于其代码实现非常简单,运算也是由异或等基本操作组成的,因此非常适合计算和存储能力吃紧的设备中使用

XXTEA 是一个非平衡 Feistel 网络分组密码,在可变长度块上运行,这些块是 32 位大小的任意倍数(最小 64 位),使用 128 位密钥,是目前 TEA 系列中最安全的算法,但性能较上两种有所降低

特点

  1. 分组长度可变,是任意 32 bit 为步长递增,最少 64 bit 的二进制字符串

  2. 密钥长度仍然为 128 位

  3. 加密轮数可变,取决于分组长度,分组越长轮数越少(最低 6 轮),分组越短轮数越多(最多 32 个完整轮数),加密轮数 = 明文总字节数 / 8

  4. 存在 DELTA 值


原理

加密算法的函数特征5.png


加解密代码

C++ 版

  • 特点

    1. 包含具体实现流程
    2. 支持快速修改 delta 的值
  • 代码

#include <stdio.h>
#include <stdint.h>
#define DELTA 0x9e3779b9   // DELTA值


//加密函数
void xxtea_encrypt(unsigned int num_rounds, uint32_t* v, uint32_t* key)
{
    uint32_t y, z, sum;
    uint32_t p, rounds, e;

    rounds = 6 + 52 / num_rounds;
    sum = 0;
    z = v[num_rounds - 1];
    do
    {
        sum += DELTA;
        e = (sum >> 2) & 3;
        for (p = 0; p < num_rounds - 1; p++)
        {
            y = v[p + 1];
            z = v[p] += (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)));
        }
        y = v[0];
        z = v[num_rounds - 1] += (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)));
    } while (--rounds);
}


//解密函数
void xxtea_decrypt(unsigned int num_rounds, uint32_t* v, uint32_t* key)
{
    uint32_t y, z, sum;
    uint32_t p, rounds, e;

    rounds = 6 + 52 / num_rounds;
    sum = rounds * DELTA;
    y = v[0];
    do
    {
        e = (sum >> 2) & 3;
        for (p = num_rounds - 1; p > 0; p--)
        {
            z = v[p - 1];
            y = v[p] -= (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)));
        }
        z = v[num_rounds - 1];
        y = v[0] -= (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)));
        sum -= DELTA;
    } while (--rounds);
}
  • 示例一
int main()
{
    uint32_t data[] = {0xDEADBEEF, 0xFACEB00C, 0xDAC0FFEE, 0xABADBABE};
    uint32_t key[] = {0x11111111, 0x22222222, 0x33333333, 0x44444444};
    int data_len = sizeof(data) / sizeof(data[0]);   // data 的长度
    unsigned int r = sizeof(data) / 8;   // 轮数

    for (int i = 0; i < data_len; i = i + 2)   // 2 个 4 字节为一组
        xxtea_encrypt(r, &data[i], key);
    printf("加密后:0x%x 0x%x 0x%x 0x%x\n", data[0], data[1], data[2], data[3]);

    for (int i = 0; i < data_len; i = i + 2)   // 2 个 4 字节为一组
        xxtea_decrypt(r, &data[i], key);
    printf("解密后:0x%x 0x%x 0x%x 0x%x\n", data[0], data[1], data[2], data[3]);

    return 0;
}

/* 输出:
加密后:0xf1560f7a 0x17cf17fa 0xf69c6e0c 0x3d09333e
解密后:0xdeadbeef 0xfaceb00c 0xdac0ffee 0xabadbabe
*/
int main()  
{  
    uint32_t data[] = {0xDEADBEEF, 0xFACEB00C, 0xDAC0FFEE, 0xABADBABE};  
    uint32_t key[] = {0x11111111, 0x22222222, 0x33333333, 0x44444444};  
    unsigned int r = sizeof(data) / 8;   // 轮数  
  
	// 加密  
	xxtea_encrypt(r, data, key);    
	printf("加密后:0x%x 0x%x 0x%x 0x%x\n", data[0], data[1], data[2], data[3]);    
	printf("加密后:%s\n", data);    
	  
	// 解密  
	xxtea_decrypt(r, data, key);    
	printf("解密后:0x%x 0x%x 0x%x 0x%x\n", data[0], data[1], data[2], data[3]);    
	printf("解密后:%s\n", data);  
  
    return 0;  
}  
  
/* 输出:  
加密后:0xf1560f7a 0x17cf17fa 0xdac0ffee 0xabadbabe  
加密后:(乱码)
解密后:0xdeadbeef 0xfaceb00c 0xdac0ffee 0xabadbabe  
解密后:(乱码)
*/
  • 示例二
#include <cstring>

int main()
{
    char data[] = "password123456789";
    uint32_t key[] = {0x11111111, 0x22222222, 0x33333333, 0x44444444};
    int data_len = strlen(data);   // data 的长度
    unsigned int r = strlen(data) / 8;   // 轮数

    printf("加密后:");
    for (int i = 0; i < data_len; i = i + 8)   // 8 字节为一组
        xxtea_encrypt(r, (uint32_t*) &data[i], key);
    printf("%s\n", data);

    printf("解密后:");
    for (int i = 0; i < data_len; i = i + 8)   // 8 字节为一组
        xxtea_decrypt(r, (uint32_t*) &data[i], key);
    printf("%s\n", data);

    return 0;
}

/* 输出:
加密后:(乱码)
解密后:password123456789
*/
#include <cstring>    
    
int main()    
{    
    char data[] = "password123456789";    
    uint32_t key[] = {0x11111111, 0x22222222, 0x33333333, 0x44444444};      
    unsigned int r = strlen(data) / 8;   // 轮数    
      
    xxtea_encrypt(r, (uint32_t*) &data, key);    
    printf("加密后:%s\n", data);     
    
    xxtea_decrypt(r, (uint32_t*) &data, key);    
    printf("解密后:%s\n", data);   
    
    return 0;    
}  
  
/* 输出:  
加密后:(乱码)123456789  
解密后:password123456789  
*/

IDA 示例

__int64 __fastcall xxtea_encrypt(unsigned int a1, _DWORD *a2, __int64 a3)
{
  unsigned int *v3; // rax
  _DWORD *v4; // rax
  __int64 result; // rax
  unsigned int v6; // [rsp+20h] [rbp-18h]
  unsigned int v7; // [rsp+24h] [rbp-14h]
  unsigned int i; // [rsp+28h] [rbp-10h]
  unsigned int v9; // [rsp+2Ch] [rbp-Ch]
  int v10; // [rsp+30h] [rbp-8h]
  unsigned int v11; // [rsp+34h] [rbp-4h]

  v9 = 0x34 / a1 + 6;
  v7 = 0;
  v6 = a2[a1 - 1];
  do
  {
    v7 -= 1640531527;
    v10 = (v7 >> 2) & 3;
    for ( i = 0; i < a1 - 1; ++i )
    {
      v11 = a2[i + 1];
      v3 = &a2[i];
      *v3 += ((v11 ^ v7) + (v6 ^ *(_DWORD *)(4LL * (v10 ^ i & 3) + a3))) ^ (((4 * v11) ^ (v6 >> 5))
                                                                          + ((v11 >> 3) ^ (16 * v6)));
      v6 = *v3;
    }
    v4 = &a2[a1 - 1];
    *v4 += ((*a2 ^ v7) + (v6 ^ *(_DWORD *)(4LL * (v10 ^ i & 3) + a3))) ^ (((4 * *a2) ^ (v6 >> 5))
                                                                        + ((*a2 >> 3) ^ (16 * v6)));
    result = (unsigned int)*v4;
    v6 = result;
    --v9;
  }
  while ( v9 );
  return result;
}

MD5

MD5(Message Digest Algorithm)又叫消息摘要算法,是单向散列算法(哈希算法)的一种,其对输入的任意长度的消息进行运算,产生一个 128 位的消息摘要(该过程通常不可逆)

将产生的 128 位消息摘要用十六进制表示,便是常见的 32 字符的 MD5 码

而所谓的 16 字符的 MD5 码,其实是这 32 字符中间的 16 个字符

加密算法的函数特征7.png

特点

  1. MD5 通常会出现四个初始化常量:
0x67452301   // 1732584193
0xEFCDAB89   // -271733879
0x98BADCFE   // -1732584194
0x10325476   // 271733878

在内存中小端序存放为:01 23 45 67 89 AB CD EF FE DC BA 98 76 54 32 10

  1. 通过 IDA 插件 Findcrypt 可以识别出 MD5

加密算法的函数特征6.png

  1. MD5 的实现通常包含三个阶段:MD5_InitMD5_UpdateMD5_Final

可能的魔改方式有:

  • 改变初始化所用到的 4 个常数
  • 改变填充的方法
  • 改变哈希变换的处理过程

原理

这里只提一些关键特征,详细原理见如下参考文献:

  1. MD5算法详解_md5 0x3f-CSDN博客
  2. [原创]了解常用加解密算法并简单逆向识别-软件逆向-看雪-安全社区|安全招聘|kanxue.com
  3. 学破解第115天,《C++之MD5消息摘要算法》学习 - 『脱壳破解区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn
  1. 为了后面填充 64 位的长度,需要先填充消息使其长度与 448 mod 512 同余

填充方法:附一个 1 在消息后面,然后用 0 填充,填充长度在 0 ~ 512 之间

  1. 最开始需要使用下面的数组进行初始化:
{ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 }
  1. 然后进行数据处理

需要使用左移数组,对应每轮处理的 4 * 16 = 64 步:

{ 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 }

还需要用到 64 个存放 32 位字节的加法常数数组,对应每组处理的 4 * 16 = 64 步:

{ 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 }

这些数值由 2 ^ 32 * (abs(sin(i))) 得出,i 的取值范围在 1 ~ 64 (这个表的存在是在 IDA 中确认 MD5 算法的关键

接下来是 MD5 算法最核心的环节,在这里对每一组消息进行 4 轮、每轮 16 步,总计 64 步的处理

以下是每次操作中用到的四个非线性函数(每轮一个):

// 第一轮逻辑函数
F(x, y, z) = (x & y) | ((~x) & z)
// 第二轮逻辑函数
G(x, y, z) = (x & y) | (y & (~z))
// 第三轮逻辑函数
H(x, y, z) = (x ^ y ^ z)
// 第四轮逻辑函数
I(x, y, z) = x ^ (x | (~z))

定义:

FF(a, b, c, d, Mj, s, ti) 操作为 a = b + ( (a + F(b, c, d) + Mj + ti) << s)  
GG(a, b, c, d, Mj, s, ti) 操作为 a = b + ( (a + G(b, c, d) + Mj + ti) << s)  
HH(a, b, c, d, Mj, s, ti) 操作为 a = b + ( (a + H(b, c, d) + Mj + ti) << s)  
II(a, b, c, d, Mj, s, ti) 操作为 a = b + ( (a + I(b, c, d) + Mj + ti) << s)

这里的 4 轮、每轮 16 步,总计 64 步的处理过程是:

// 第一轮  
FF(a ,b ,c ,d ,M0 ,7 ,0xd76aa478 )  
FF(d ,a ,b ,c ,M1 ,12 ,0xe8c7b756 )  
FF(c ,d ,a ,b ,M2 ,17 ,0x242070db )  
FF(b ,c ,d ,a ,M3 ,22 ,0xc1bdceee )  
FF(a ,b ,c ,d ,M4 ,7 ,0xf57c0faf )  
FF(d ,a ,b ,c ,M5 ,12 ,0x4787c62a )  
FF(c ,d ,a ,b ,M6 ,17 ,0xa8304613 )  
FF(b ,c ,d ,a ,M7 ,22 ,0xfd469501)  
FF(a ,b ,c ,d ,M8 ,7 ,0x698098d8 )  
FF(d ,a ,b ,c ,M9 ,12 ,0x8b44f7af )  
FF(c ,d ,a ,b ,M10 ,17 ,0xffff5bb1 )  
FF(b ,c ,d ,a ,M11 ,22 ,0x895cd7be )  
FF(a ,b ,c ,d ,M12 ,7 ,0x6b901122 )  
FF(d ,a ,b ,c ,M13 ,12 ,0xfd987193 )  
FF(c ,d ,a ,b ,M14 ,17 ,0xa679438e )  
FF(b ,c ,d ,a ,M15 ,22 ,0x49b40821 )  
// 第二轮  
GG(a ,b ,c ,d ,M1 ,5 ,0xf61e2562 )  
GG(d ,a ,b ,c ,M6 ,9 ,0xc040b340 )  
GG(c ,d ,a ,b ,M11 ,14 ,0x265e5a51 )  
GG(b ,c ,d ,a ,M0 ,20 ,0xe9b6c7aa )  
GG(a ,b ,c ,d ,M5 ,5 ,0xd62f105d )  
GG(d ,a ,b ,c ,M10 ,9 ,0x02441453 )  
GG(c ,d ,a ,b ,M15 ,14 ,0xd8a1e681 )  
GG(b ,c ,d ,a ,M4 ,20 ,0xe7d3fbc8 )  
GG(a ,b ,c ,d ,M9 ,5 ,0x21e1cde6 )  
GG(d ,a ,b ,c ,M14 ,9 ,0xc33707d6 )  
GG(c ,d ,a ,b ,M3 ,14 ,0xf4d50d87 )  
GG(b ,c ,d ,a ,M8 ,20 ,0x455a14ed )  
GG(a ,b ,c ,d ,M13 ,5 ,0xa9e3e905 )  
GG(d ,a ,b ,c ,M2 ,9 ,0xfcefa3f8 )  
GG(c ,d ,a ,b ,M7 ,14 ,0x676f02d9 )  
GG(b ,c ,d ,a ,M12 ,20 ,0x8d2a4c8a )  
// 第三轮  
HH(a ,b ,c ,d ,M5 ,4 ,0xfffa3942 )  
HH(d ,a ,b ,c ,M8 ,11 ,0x8771f681 )  
HH(c ,d ,a ,b ,M11 ,16 ,0x6d9d6122 )  
HH(b ,c ,d ,a ,M14 ,23 ,0xfde5380c )  
HH(a ,b ,c ,d ,M1 ,4 ,0xa4beea44 )  
HH(d ,a ,b ,c ,M4 ,11 ,0x4bdecfa9 )  
HH(c ,d ,a ,b ,M7 ,16 ,0xf6bb4b60 )  
HH(b ,c ,d ,a ,M10 ,23 ,0xbebfbc70 )  
HH(a ,b ,c ,d ,M13 ,4 ,0x289b7ec6 )  
HH(d ,a ,b ,c ,M0 ,11 ,0xeaa127fa )  
HH(c ,d ,a ,b ,M3 ,16 ,0xd4ef3085 )  
HH(b ,c ,d ,a ,M6 ,23 ,0x04881d05 )  
HH(a ,b ,c ,d ,M9 ,4 ,0xd9d4d039 )  
HH(d ,a ,b ,c ,M12 ,11 ,0xe6db99e5 )  
HH(c ,d ,a ,b ,M15 ,16 ,0x1fa27cf8 )  
HH(b ,c ,d ,a ,M2 ,23 ,0xc4ac5665 )  
// 第四轮  
II(a ,b ,c ,d ,M0 ,6 ,0xf4292244 )  
II(d ,a ,b ,c ,M7 ,10 ,0x432aff97 )  
II(c ,d ,a ,b ,M14 ,15 ,0xab9423a7 )  
II(b ,c ,d ,a ,M5 ,21 ,0xfc93a039 )  
II(a ,b ,c ,d ,M12 ,6 ,0x655b59c3 )  
II(d ,a ,b ,c ,M3 ,10 ,0x8f0ccc92 )  
II(c ,d ,a ,b ,M10 ,15 ,0xffeff47d )  
II(b ,c ,d ,a ,M1 ,21 ,0x85845dd1 )  
II(a ,b ,c ,d ,M8 ,6 ,0x6fa87e4f )  
II(d ,a ,b ,c ,M15 ,10 ,0xfe2ce6e0 )  
II(c ,d ,a ,b ,M6 ,15 ,0xa3014314 )  
II(b ,c ,d ,a ,M13 ,21 ,0x4e0811a1 )  
II(a ,b ,c ,d ,M4 ,6 ,0xf7537e82 )  
II(d ,a ,b ,c ,M11 ,10 ,0xbd3af235 )  
II(c ,d ,a ,b ,M2 ,15 ,0x2ad7d2bb )  
II(b ,c ,d ,a ,M9 ,21 ,0xeb86d391 )
  1. 如果 128 位消息已经处理完,则从低地址开始用 16 进制逐个输出字节便得到 32 字符的 MD5 码,取正中间的 16 字符就是 16 字符的 MD5 码

加密代码

Python 版

import hashlib

string = 'flag{welcome_to_uf4te!}'


def md5value(key):
    input_name = hashlib.md5()
    input_name.update(key.encode("utf-8"))
    print("大写的32位 : " + (input_name.hexdigest()).upper())
    print("大写的16位 : " + (input_name.hexdigest())[8:-8].upper())
    print("小写的32位 : " + (input_name.hexdigest()).lower())
    print("小写的16位 : " + (input_name.hexdigest())[8:-8].lower())


md5value(string)

''' 输出:
大写的 32 位 MD5 : B2E8ED4649266019056425F0E67A62A7
大写的 16 位 MD5 : 49266019056425F0
小写的 32 位 MD5 : b2e8ed4649266019056425f0e67a62a7
小写的 16 位 MD5 : 49266019056425f0
'''

IDA 示例

// MD5_Init
_DWORD *__cdecl sub_4012B0(_DWORD *a1)
{
  _DWORD *result; // eax

  result = a1;
  a1[5] = 0;
  a1[4] = 0;
  *a1 = 1732584193;
  a1[1] = -271733879;
  a1[2] = -1732584194;
  a1[3] = 271733878;
  return result;
}


// MD5_Update
int __cdecl sub_4012E0(int a1, int a2, unsigned int a3)
{
  unsigned int v3; // ecx
  int v4; // eax
  int v5; // ebx
  int v6; // ebp
  unsigned int i; // ebx

  v3 = *(a1 + 16) + 8 * a3;
  v4 = (*(a1 + 16) >> 3) & 0x3F;
  *(a1 + 16) = v3;
  if ( v3 < 8 * a3 )
    ++*(a1 + 20);
  *(a1 + 20) += a3 >> 29;
  v5 = 64 - v4;
  if ( a3 < 64 - v4 )
  {
    v6 = 0;
  }
  else
  {
    sub_401DF0(v4 + a1 + 24, a2, 64 - v4);
    sub_401400(a1, a1 + 24);
    v6 = v5;
    for ( i = v5 + 63; i < a3; v6 += 64 )
    {
      sub_401400(a1, a2 + i - 63);
      i += 64;
    }
    v4 = 0;
  }
  return sub_401DF0(v4 + a1 + 24, a2 + v6, a3 - v6);
}


int __cdecl sub_401400(int *a1, int a2)
{
  int v3; // edi
  int v4; // ebx
  int v5; // ebp
  unsigned __int64 v6; // kr00_8
  int v7; // eax
  unsigned __int64 v8; // kr08_8
  int v9; // ecx
  unsigned int v10; // ebx
  int v11; // edx
  int v12; // edi
  int v13; // edi
  unsigned int v14; // ecx
  int v15; // eax
  unsigned int v16; // edx
  int v17; // ecx
  unsigned int v18; // edi
  int v19; // edx
  unsigned int v20; // eax
  int v21; // edi
  unsigned int v22; // ecx
  int v23; // eax
  unsigned int v24; // edx
  int v25; // ecx
  unsigned int v26; // edi
  int v27; // edx
  unsigned int v28; // eax
  int v29; // edi
  unsigned int v30; // ecx
  int v31; // eax
  unsigned int v32; // edx
  int v33; // ecx
  unsigned int v34; // edi
  int v35; // edx
  unsigned int v36; // eax
  int v37; // edi
  unsigned int v38; // ecx
  int v39; // eax
  unsigned int v40; // edx
  int v41; // ecx
  unsigned int v42; // edi
  int v43; // edx
  unsigned int v44; // eax
  int v45; // edi
  unsigned int v46; // ecx
  int v47; // eax
  unsigned int v48; // edx
  int v49; // ecx
  unsigned int v50; // edi
  int v51; // edx
  unsigned int v52; // eax
  unsigned int v53; // edi
  unsigned int v54; // eax
  unsigned int v55; // ecx
  unsigned int v56; // ebx
  unsigned int v57; // edx
  unsigned int v58; // edi
  unsigned int v59; // eax
  unsigned int v60; // ecx
  unsigned int v61; // ebx
  unsigned int v62; // eax
  unsigned int v63; // edx
  unsigned int v64; // ebx
  unsigned int v65; // edi
  unsigned int v66; // edx
  unsigned int v67; // ecx
  unsigned int v68; // eax
  unsigned int v69; // ebx
  unsigned int v70; // edi
  unsigned int v71; // edx
  unsigned int v72; // ebx
  unsigned int v73; // ecx
  unsigned int v74; // eax
  unsigned int v75; // edi
  unsigned int v76; // edx
  unsigned int v77; // ebx
  unsigned int v78; // edi
  unsigned int v79; // ecx
  unsigned int v80; // ebx
  unsigned int v81; // eax
  unsigned int v82; // ecx
  unsigned int v83; // edx
  int v84; // eax
  unsigned int v85; // edi
  int v86; // edx
  unsigned int v87; // ebx
  int v88; // edi
  unsigned int v89; // ecx
  int v90; // ebx
  unsigned int v91; // eax
  int v92; // ecx
  unsigned int v93; // edx
  int v94; // eax
  unsigned int v95; // edi
  int v96; // edx
  unsigned int v97; // ebx
  int v98; // edi
  unsigned int v99; // ecx
  int v100; // ebx
  unsigned int v101; // eax
  int v102; // ecx
  unsigned int v103; // edx
  int v104; // eax
  unsigned int v105; // edi
  int v106; // edx
  unsigned int v107; // ebx
  int v108; // edi
  unsigned int v109; // ecx
  int v110; // ebx
  int v111; // ebp
  unsigned int v112; // eax
  int v113; // ecx
  int v114; // edx
  int v115; // ebx
  int v117; // [esp+10h] [ebp-40h] BYREF
  int v118; // [esp+14h] [ebp-3Ch]
  int v119; // [esp+18h] [ebp-38h]
  int v120; // [esp+1Ch] [ebp-34h]
  int v121; // [esp+20h] [ebp-30h]
  int v122; // [esp+24h] [ebp-2Ch]
  int v123; // [esp+28h] [ebp-28h]
  int v124; // [esp+2Ch] [ebp-24h]
  int v125; // [esp+30h] [ebp-20h]
  int v126; // [esp+34h] [ebp-1Ch]
  int v127; // [esp+38h] [ebp-18h]
  int v128; // [esp+3Ch] [ebp-14h]
  int v129; // [esp+40h] [ebp-10h]
  int v130; // [esp+44h] [ebp-Ch]
  int v131; // [esp+48h] [ebp-8h]
  int v132; // [esp+4Ch] [ebp-4h]
  int v133; // [esp+54h] [ebp+4h]
  int v134; // [esp+54h] [ebp+4h]
  int v135; // [esp+54h] [ebp+4h]
  int v136; // [esp+54h] [ebp+4h]
  int v137; // [esp+54h] [ebp+4h]
  int v138; // [esp+54h] [ebp+4h]
  int v139; // [esp+54h] [ebp+4h]
  int v140; // [esp+54h] [ebp+4h]

  v3 = a1[1];
  v4 = a1[2];
  v5 = a1[3];
  v133 = *a1;
  sub_401DA0(&v117, a2, 64);
  v6 = (v133 + v117 + (v3 & v4 | v5 & ~v3) - 680876936) << 7;
  v7 = v3 + (v6 | HIDWORD(v6));
  v8 = (v118 + (v7 & v3 | v4 & ~v7) + v5 - 389564586) << 12;
  v9 = v7 + (v8 | HIDWORD(v8));
  v10 = v4 + v119 + (v7 & v9 | v3 & ~v9) + 606105819;
  v11 = v9 + ((v10 << 17) | (v10 >> 15));
  v134 = v11
       + (((v3 + v120 + (v11 & v9 | v7 & ~v11) - 1044525330) >> 10) | ((v3 + v120 + (v11 & v9 | v7 & ~v11) - 1044525330) << 22));
  v12 = v121 + (v134 & v11 | v9 & ~v134);
  v13 = v134 + (((v7 + v12 - 176418897) << 7) | ((v7 + v12 - 176418897) >> 25));
  v14 = v9 + v122 + (v13 & v134 | v11 & ~v13) + 1200080426;
  v15 = v13 + ((v14 << 12) | (v14 >> 20));
  v16 = v11 + v123 + (v13 & v15 | v134 & ~v15) - 1473231341;
  v17 = v15 + ((v16 << 17) | (v16 >> 15));
  v135 = v17
       + (((v134 + v124 + (v17 & v15 | v13 & ~v17) - 45705983) >> 10) | ((v134
                                                                        + v124
                                                                        + (v17 & v15 | v13 & ~v17)
                                                                        - 45705983) << 22));
  v18 = v13 + v125 + (v135 & v17 | v15 & ~v135) + 1770035416;
  v19 = v135 + ((v18 << 7) | (v18 >> 25));
  v20 = v15 + v126 + (v19 & v135 | v17 & ~v19) - 1958414417;
  v21 = v19 + ((v20 << 12) | (v20 >> 20));
  v22 = v17 + v127 + (v19 & v21 | v135 & ~v21) - 42063;
  v23 = v21 + ((v22 << 17) | (v22 >> 15));
  v136 = v23
       + (((v135 + v128 + (v23 & v21 | v19 & ~v23) - 1990404162) >> 10) | ((v135
                                                                          + v128
                                                                          + (v23 & v21 | v19 & ~v23)
                                                                          - 1990404162) << 22));
  v24 = v19 + v129 + (v136 & v23 | v21 & ~v136) + 1804603682;
  v25 = v136 + ((v24 << 7) | (v24 >> 25));
  v26 = v21 + v130 + (v25 & v136 | v23 & ~v25) - 40341101;
  v27 = v25 + ((v26 << 12) | (v26 >> 20));
  v28 = v23 + v131 + (~v27 & v136 | v25 & v27) - 1502002290;
  v29 = v27 + ((v28 << 17) | (v28 >> 15));
  v137 = v29
       + (((v136 + v132 + (v29 & v27 | v25 & ~v29) + 1236535329) >> 10) | ((v136
                                                                          + v132
                                                                          + (v29 & v27 | v25 & ~v29)
                                                                          + 1236535329) << 22));
  v30 = v25 + v118 + (~v27 & v29 | v137 & v27) - 165796510;
  v31 = v137 + ((32 * v30) | (v30 >> 27));
  v32 = v27 + v123 + (v31 & v29 | v137 & ~v29) - 1069501632;
  v33 = v31 + ((v32 << 9) | (v32 >> 23));
  v34 = v29 + v128 + (v137 & v33 | v31 & ~v137) + 643717713;
  v35 = v33 + ((v34 << 14) | (v34 >> 18));
  v138 = v35
       + (((v137 + v117 + (v31 & v35 | v33 & ~v31) - 373897302) >> 12) | ((v137
                                                                         + v117
                                                                         + (v31 & v35 | v33 & ~v31)
                                                                         - 373897302) << 20));
  v36 = v31 + v122 + (v138 & v33 | v35 & ~v33) - 701558691;
  v37 = v138 + ((32 * v36) | (v36 >> 27));
  v38 = v33 + v127 + (v37 & v35 | v138 & ~v35) + 38016083;
  v39 = v37 + ((v38 << 9) | (v38 >> 23));
  v40 = v35 + v132 + (v138 & v39 | v37 & ~v138) - 660478335;
  v41 = v39 + ((v40 << 14) | (v40 >> 18));
  v139 = v41
       + (((v138 + v121 + (v37 & v41 | v39 & ~v37) - 405537848) >> 12) | ((v138
                                                                         + v121
                                                                         + (v37 & v41 | v39 & ~v37)
                                                                         - 405537848) << 20));
  v42 = v37 + v126 + (v139 & v39 | v41 & ~v39) + 568446438;
  v43 = v139 + ((32 * v42) | (v42 >> 27));
  v44 = v39 + v131 + (v43 & v41 | v139 & ~v41) - 1019803690;
  v45 = v43 + ((v44 << 9) | (v44 >> 23));
  v46 = v41 + v120 + (v139 & v45 | v43 & ~v139) - 187363961;
  v47 = v45 + ((v46 << 14) | (v46 >> 18));
  v140 = v47
       + (((v139 + v125 + (v43 & v47 | v45 & ~v43) + 1163531501) >> 12) | ((v139
                                                                          + v125
                                                                          + (v43 & v47 | v45 & ~v43)
                                                                          + 1163531501) << 20));
  v48 = v43 + v130 + (v140 & v45 | v47 & ~v45) - 1444681467;
  v49 = v140 + ((32 * v48) | (v48 >> 27));
  v50 = v45 + v119 + (v49 & v47 | v140 & ~v47) - 51403784;
  v51 = v49 + ((v50 << 9) | (v50 >> 23));
  v52 = v47 + v124 + (v140 & v51 | v49 & ~v140) + 1735328473;
  v53 = v51 + ((v52 << 14) | (v52 >> 18));
  v54 = v53
      + (((v140 + v129 + (v49 & v53 | v51 & ~v49) - 1926607734) >> 12) | ((v140
                                                                         + v129
                                                                         + (v49 & v53 | v51 & ~v49)
                                                                         - 1926607734) << 20));
  v55 = v49 + v122 + (v54 ^ v53 ^ v51) - 378558;
  v56 = v54 + ((16 * v55) | (v55 >> 28));
  v57 = v56
      + (((v51 + v125 + (v56 ^ v54 ^ v53) - 2022574463) << 11) | ((v51 + v125 + (v56 ^ v54 ^ v53) - 2022574463) >> 21));
  v58 = v57
      + (((v53 + v128 + (v56 ^ v54 ^ v57) + 1839030562) << 16) | ((v53 + v128 + (v56 ^ v54 ^ v57) + 1839030562) >> 16));
  v59 = v54 + v131 + (v56 ^ v58 ^ v57) - 35309556;
  v60 = v58 + ((v59 >> 9) | (v59 << 23));
  v61 = v56 + v118 + (v60 ^ v58 ^ v57) - 1530992060;
  v62 = v60 + ((16 * v61) | (v61 >> 28));
  v63 = v57 + v121 + (v62 ^ v60 ^ v58) + 1272893353;
  v64 = v62 + ((v63 << 11) | (v63 >> 21));
  v65 = v58 + v124 + (v62 ^ v60 ^ v64) - 155497632;
  v66 = v64 + ((v65 << 16) | HIWORD(v65));
  v67 = v66
      + (((v60 + v127 + (v62 ^ v66 ^ v64) - 1094730640) >> 9) | ((v60 + v127 + (v62 ^ v66 ^ v64) - 1094730640) << 23));
  v68 = v67
      + ((16 * (v62 + v130 + (v67 ^ v66 ^ v64) + 681279174)) | ((v62 + v130 + (v67 ^ v66 ^ v64) + 681279174) >> 28));
  v69 = v64 + v117 + (v68 ^ v67 ^ v66) - 358537222;
  v70 = v68 + ((v69 << 11) | (v69 >> 21));
  v71 = v66 + v120 + (v68 ^ v67 ^ v70) - 722521979;
  v72 = v70 + ((v71 << 16) | HIWORD(v71));
  v73 = v72 + (((v67 + v123 + (v68 ^ v72 ^ v70) + 76029189) >> 9) | ((v67 + v123 + (v68 ^ v72 ^ v70) + 76029189) << 23));
  v74 = v73
      + ((16 * (v68 + v126 + (v73 ^ v72 ^ v70) - 640364487)) | ((v68 + v126 + (v73 ^ v72 ^ v70) - 640364487) >> 28));
  v75 = v70 + v129 + (v74 ^ v73 ^ v72) - 421815835;
  v76 = v74 + ((v75 << 11) | (v75 >> 21));
  v77 = v72 + v132 + (v74 ^ v73 ^ v76) + 530742520;
  v78 = v76 + ((v77 << 16) | HIWORD(v77));
  v79 = v73 + v119 + (v74 ^ v78 ^ v76) - 995338651;
  v80 = v78 + ((v79 >> 9) | (v79 << 23));
  v81 = v74 + v117 + (v78 ^ (v80 | ~v76)) - 198630844;
  v82 = v80 + ((v81 << 6) | (v81 >> 26));
  v83 = v76 + v124 + (v80 ^ (v82 | ~v78)) + 1126891415;
  v84 = v82 + ((v83 << 10) | (v83 >> 22));
  v85 = v78 + v131 + (v82 ^ (v84 | ~v80)) - 1416354905;
  v86 = v84 + ((v85 << 15) | (v85 >> 17));
  v87 = v80 + v122 + (v84 ^ (v86 | ~v82)) - 57434055;
  v88 = v86 + ((v87 >> 11) | (v87 << 21));
  v89 = v82 + v129 + (v86 ^ (v88 | ~v84)) + 1700485571;
  v90 = v88 + ((v89 << 6) | (v89 >> 26));
  v91 = v84 + v120 + (v88 ^ (v90 | ~v86)) - 1894986606;
  v92 = v90 + ((v91 << 10) | (v91 >> 22));
  v93 = v86 + v127 + (v90 ^ (v92 | ~v88)) - 1051523;
  v94 = v92 + ((v93 << 15) | (v93 >> 17));
  v95 = v88 + v118 + (v92 ^ (v94 | ~v90)) - 2054922799;
  v96 = v94 + ((v95 >> 11) | (v95 << 21));
  v97 = v90 + v125 + (v94 ^ (v96 | ~v92)) + 1873313359;
  v98 = v96 + ((v97 << 6) | (v97 >> 26));
  v99 = v92 + v132 + (v96 ^ (v98 | ~v94)) - 30611744;
  v100 = v98 + ((v99 << 10) | (v99 >> 22));
  v101 = v94 + v123 + (v98 ^ (v100 | ~v96)) - 1560198380;
  v102 = v100 + ((v101 << 15) | (v101 >> 17));
  v103 = v96 + v130 + (v100 ^ (v102 | ~v98)) + 1309151649;
  v104 = v102 + ((v103 >> 11) | (v103 << 21));
  v105 = v98 + v121 + (v102 ^ (v104 | ~v100)) - 145523070;
  v106 = v104 + ((v105 << 6) | (v105 >> 26));
  v107 = v100 + v128 + (v104 ^ (v106 | ~v102)) - 1120210379;
  v108 = v106 + ((v107 << 10) | (v107 >> 22));
  v109 = v102 + v119 + (v106 ^ (v108 | ~v104)) + 718787259;
  v110 = v108 + ((v109 << 15) | (v109 >> 17));
  v111 = v110 + a1[2];
  v112 = v104 + v126 + (v108 ^ (v110 | ~v106)) - 343485551;
  v113 = v106 + *a1;
  v114 = v110 + ((v112 >> 11) | (v112 << 21));
  v115 = v108 + a1[3];
  a1[1] += v114;
  *a1 = v113;
  a1[2] = v111;
  a1[3] = v115;
  return sub_401E10(&v117, 0, 64);
}


// MD5_Final
int __cdecl sub_401390(int a1, int a2)
{
  unsigned int v2; // eax
  int v3; // ecx
  char v5[8]; // [esp+8h] [ebp-8h] BYREF

  sub_401D50(v5, a2 + 16, 8);
  v2 = (*(a2 + 16) >> 3) & 0x3F;
  v3 = 56;
  if ( v2 >= 0x38 )
    v3 = 120;
  sub_4012E0(a2, &unk_407030, v3 - v2);
  sub_4012E0(a2, v5, 8u);
  sub_401D50(a1, a2, 16);
  return sub_401E10(a2, 0, 88);
}


int __cdecl sub_4012E0(int a1, int a2, unsigned int a3)
{
  unsigned int v3; // ecx
  int v4; // eax
  int v5; // ebx
  int v6; // ebp
  unsigned int i; // ebx

  v3 = *(a1 + 16) + 8 * a3;
  v4 = (*(a1 + 16) >> 3) & 0x3F;
  *(a1 + 16) = v3;
  if ( v3 < 8 * a3 )
    ++*(a1 + 20);
  *(a1 + 20) += a3 >> 29;
  v5 = 64 - v4;
  if ( a3 < 64 - v4 )
  {
    v6 = 0;
  }
  else
  {
    sub_401DF0(v4 + a1 + 24, a2, 64 - v4);
    sub_401400(a1, a1 + 24);
    v6 = v5;
    for ( i = v5 + 63; i < a3; v6 += 64 )
    {
      sub_401400(a1, a2 + i - 63);
      i += 64;
    }
    v4 = 0;
  }
  return sub_401DF0(v4 + a1 + 24, a2 + v6, a3 - v6);
}

DES

DES 加密,即即数据加密标准(Data Encryption Standard),属于对称加密算法,是一种使用密钥加密的分组算法

参考文献:

  1. [原创]逆向中常见的Hash算法和对称加密算法的分析-密码应用-看雪-安全社区|安全招聘|kanxue.com
  2. [分享]DES加密-密码应用-看雪-安全社区|安全招聘|kanxue.com

特点

  1. Key 为 8 字节共 64 位(实际使用 56 位),明文或密文也为 8 字节 64 位

  2. DES 算法一般有两个关键点:加密算法和数据补位

  3. 存在 8 个置换盒 S1 ~ S8

int S1[] = {14,  4, 13,  1,  2, 15, 11,  8,  3, 10,  6, 12,  5,  9,  0,  7,
             0, 15,  7,  4, 14,  2, 13,  1, 10,  6, 12, 11,  9,  5,  3,  8,
             4,  1, 14,  8, 13,  6,  2, 11, 15, 12,  9,  7,  3, 10,  5,  0,
            15, 12,  8,  2,  4,  9,  1,  7,  5, 11,  3, 14, 10,  0,  6, 13};
 
int S2[] = {15,  1,  8, 14,  6, 11,  3,  4,  9,  7,  2, 13, 12,  0,  5, 10,
             3, 13,  4,  7, 15,  2,  8, 14, 12,  0,  1, 10,  6,  9, 11,  5,
             0, 14,  7, 11, 10,  4, 13,  1,  5,  8, 12,  6,  9,  3,  2, 15,
            13,  8, 10,  1,  3, 15,  4,  2, 11,  6,  7, 12,  0,  5, 14,  9};
 
int S3[] = {10,  0,  9, 14,  6,  3, 15,  5,  1, 13, 12,  7, 11,  4,  2,  8,
            13,  7,  0,  9,  3,  4,  6, 10,  2,  8,  5, 14, 12, 11, 15,  1,
            13,  6,  4,  9,  8, 15,  3,  0, 11,  1,  2, 12,  5, 10, 14,  7,
             1, 10, 13,  0,  6,  9,  8,  7,  4, 15, 14,  3, 11,  5,  2, 12};
 
int S4[] = { 7, 13, 14,  3,  0,  6,  9, 10,  1,  2,  8,  5, 11, 12,  4, 15,
            13,  8, 11,  5,  6, 15,  0,  3,  4,  7,  2, 12,  1, 10, 14,  9,
            10,  6,  9,  0, 12, 11,  7, 13, 15,  1,  3, 14,  5,  2,  8,  4,
             3, 15,  0,  6, 10,  1, 13,  8,  9,  4,  5, 11, 12,  7,  2, 14};
 
int S5[] = { 2, 12,  4,  1,  7, 10, 11,  6,  8,  5,  3, 15, 13,  0, 14,  9,
            14, 11,  2, 12,  4,  7, 13,  1,  5,  0, 15, 10,  3,  9,  8,  6,
             4,  2,  1, 11, 10, 13,  7,  8, 15,  9, 12,  5,  6,  3,  0, 14,
            11,  8, 12,  7,  1, 14,  2, 13,  6, 15,  0,  9, 10,  4,  5,  3};
 
int S6[] = {12,  1, 10, 15,  9,  2,  6,  8,  0, 13,  3,  4, 14,  7,  5, 11,
            10, 15,  4,  2,  7, 12,  9,  5,  6,  1, 13, 14,  0, 11,  3,  8,
             9, 14, 15,  5,  2,  8, 12,  3,  7,  0,  4, 10,  1, 13, 11,  6,
             4,  3,  2, 12,  9,  5, 15, 10, 11, 14,  1,  7,  6,  0,  8, 13};
 
int S7[] = { 4, 11,  2, 14, 15,  0,  8, 13,  3, 12,  9,  7,  5, 10,  6,  1,
            13,  0, 11,  7,  4,  9,  1, 10, 14,  3,  5, 12,  2, 15,  8,  6,
             1,  4, 11, 13, 12,  3,  7, 14, 10, 15,  6,  8,  0,  5,  9,  2,
             6, 11, 13,  8,  1,  4, 10,  7,  9,  5,  0, 15, 14,  2,  3, 12};
 
int S8[] = {13,  2,  8,  4,  6, 15, 11,  1, 10,  9,  3, 14,  5,  0, 12,  7,
             1, 15, 13,  8, 10,  3,  7,  4, 12,  5,  6, 11,  0, 14,  9,  2,
             7, 11,  4,  1,  9, 12, 14,  2,  0,  6, 10, 13, 15,  3,  5,  8,
             2,  1, 14,  7,  4, 10,  8, 13, 15, 12,  9,  0,  3,  5,  6, 11};

加密算法的函数特征11.png


原理

加密算法的函数特征10.png


加解密代码

Python 版(CBC 模式)

from pyDes import des, CBC, PAD_PKCS5  
import binascii  
  
key = 'hi_uf4te'  # 秘钥, 8 位  
iv = 'uf4te!!!'  # 偏移量 iv, 8 位  
  
  
def des_encrypt(s):  
    # key : 加密密钥, CBC : 加密模式,iv : 偏移, padmode : 填充  
    des_obj = des(key, CBC, iv, pad=None, padmode=PAD_PKCS5)  
    # 返回为字节  
    secret_bytes = des_obj.encrypt(s, padmode=PAD_PKCS5)  
    # 将字节对象转换为十六进制字符串  
    return binascii.b2a_hex(secret_bytes)  
  
  
def des_descrypt(s):  
    # key : 加密密钥, CBC : 加密模式,iv : 偏移, padmode : 填充  
    des_obj = des(key, CBC, iv, pad=None, padmode=PAD_PKCS5)  
    decrypt_str = des_obj.decrypt(binascii.a2b_hex(s), padmode=PAD_PKCS5)  
    return decrypt_str  
  
  
text = b'welcome_to_uf4te'  # 加解密内容  
  
enc = des_encrypt(text)  # DES 加密,结果为 16 进制字节流  
print(enc)  
  
dec = des_descrypt(enc)  # DES 解密  
print(dec)


''' 输出:
b'54576e252cc9973824e53ea43eb28489b3bb662a97e07165'
b'welcome_to_uf4te'
'''

Python 版(ECB 模式)

from Crypto.Cipher import DES
import binascii

key = b'hi_uf4te'  # 密钥 8 位或 16 位,必须为 bytes 类型
text = 'welcome_to_uf4te'


def pad(text):
    # 如果 text 不是 8 的倍数【加密文本 text 必须为 8 的倍数!】,补足为 8 的倍数
    while len(text) % 8 != 0:
        text += ' '
    return text


des = DES.new(key, DES.MODE_ECB)  # 创建 DES 实例
padded_text = pad(text)   # 填充

enc = des.encrypt(padded_text.encode('utf-8'))  # 加密,结果为 16 进制字节流
print(binascii.b2a_hex(enc))   # 将字节对象转换为十六进制字符串

dec = des.decrypt(enc).decode().rstrip(' ')  # 解密
print(dec)


''' 输出:
b'8c8641d37affbc2b251cbd968cb124a8'
welcome_to_uf4te
'''

AES

AES 加密,即高级加密标准(Advanced Encryption Standard),属于对称加密算法,基于数据块的加密方式:分组输入、分组输出,该算法已被用来替代 DES 算法,并在世界范围内广泛使用

AES 加密算法的速度比公钥加密等加密算法快很多,在很多场合都需要 AES 对称加密,主要缺点在于要求加密和解密双方都使用相同的密钥

参考文献:

  1. python实现AES加密解密 - Hello_wshuo - 博客园
  2. 逆向分析中的密码学—AES_0x01 0x02 0x04 0x08是什么意思-CSDN博客

特点

  1. AES 按照密钥的长度可以分为:AES-128、AES-192、AES-256 三种
密钥长度分组长度轮数
AES-128128 bit4 bit10 轮
AES-192192 bit4 bit12 轮
AES-256256 bit4 bit14 轮
  1. 加解密的字节替代过程中使用了 s_box 和逆 s_box 完成一个字节到另一个字节的映射

s 盒:

行/列0123456789ABCDEF
00x630x7c0x770x7b0xf20x6b0x6f0xc50x300x010x670x2b0xfe0xd70xab0x76
10xca0x820xc90x7d0xfa0x590x470xf00xad0xd40xa20xaf0x9c0xa40x720xc0
20xb70xfd0x930x260x360x3f0xf70xcc0x340xa50xe50xf10x710xd80x310x15
30x040xc70x230xc30x180x960x050x9a0x070x120x800xe20xeb0x270xb20x75
40x090x830x2c0x1a0x1b0x6e0x5a0xa00x520x3b0xd60xb30x290xe30x2f0x84
50x530xd10x000xed0x200xfc0xb10x5b0x6a0xcb0xbe0x390x4a0x4c0x580xcf
60xd00xef0xaa0xfb0x430x4d0x330x850x450xf90x020x7f0x500x3c0x9f0xa8
70x510xa30x400x8f0x920x9d0x380xf50xbc0xb60xda0x210x100xff0xf30xd2
80xcd0x0c0x130xec0x5f0x970x440x170xc40xa70x7e0x3d0x640x5d0x190x73
90x600x810x4f0xdc0x220x2a0x900x880x460xee0xb80x140xde0x5e0x0b0xdb
A0xe00x320x3a0x0a0x490x060x240x5c0xc20xd30xac0x620x910x950xe40x79
B0xe70xc80x370x6d0x8d0xd50x4e0xa90x6c0x560xf40xea0x650x7a0xae0x08
C0xba0x780x250x2e0x1c0xa60xb40xc60xe80xdd0x740x1f0x4b0xbd0x8b0x8a
D0x700x3e0xb50x660x480x030xf60x0e0x610x350x570xb90x860xc10x1d0x9e
E0xe10xf80x980x110x690xd90x8e0x940x9b0x1e0x870xe90xce0x550x280xdf
F0x8c0xa10x890x0d0xbf0xe60x420x680x410x990x2d0x0f0xb00x540xbb0x16

s 盒:

行/列0123456789ABCDEF
00x520x090x6a0xd50x300x360xa50x380xbf0x400xa30x9e0x810xf30xd70xfb
10x7c0xe30x390x820x9b0x2f0xff0x870x340x8e0x430x440xc40xde0xe90xcb
20x540x7b0x940x320xa60xc20x230x3d0xee0x4c0x950x0b0x420xfa0xc30x4e
30x080x2e0xa10x660x280xd90x240xb20x760x5b0xa20x490x6d0x8b0xd10x25
40x720xf80xf60x640x860x680x980x160xd40xa40x5c0xcc0x5d0x650xb60x92
50x6c0x700x480x500xfd0xed0xb90xda0x5e0x150x460x570xa70x8d0x9d0x84
60x900xd80xab0x000x8c0xbc0xd30x0a0xf70xe40x580x050xb80xb30x450x06
70xd00x2c0x1e0x8f0xca0x3f0x0f0x020xc10xaf0xbd0x030x010x130x8a0x6b
80x3a0x910x110x410x4f0x670xdc0xea0x970xf20xcf0xce0xf00xb40xe60x73
90x960xac0x740x220xe70xad0x350x850xe20xf90x370xe80x1c0x750xdf0x6e
A0x470xf10x1a0x710x1d0x290xc50x890x6f0xb70x620x0e0xaa0x180xbe0x1b
B0xfc0x560x3e0x4b0xc60xd20x790x200x9a0xdb0xc00xfe0x780xcd0x5a0xf4
C0x1f0xdd0xa80x330x880x070xc70x310xb10x120x100x590x270x800xec0x5f
D0x600x510x7f0xa90x190xb50x4a0x0d0x2d0xe50x7a0x9f0x930xc90x9c0xef
E0xa00xe00x3b0x4d0xae0x2a0xf50xb00xc80xeb0xbb0x3c0x830x530x990x61
F0x170x2b0x040x7e0xba0x770xd60x260xe10x690x140x630x550x210x0c0x7d
  1. 通过 IDA 插件 Findcrypt 可以识别出 AES

加密算法的函数特征9.png


原理

详细过程参考:

  1. 逆向中的常见密码的识别(持续更新中) - gaoyucan - 博客园
  2. 密码算法详解——AES - ReadingLover - 博客园

加密算法的函数特征8.png

AES 常用的加密工作模式有:ECB,CBC,OFB,CFB,CTR

模式含义偏移量 IV
ECB电子密码本模式
CBC密码分组链接模式
OFB输出反馈模式
CFB密码反馈模式
CTR计数器模式

最常用的是 ECB 和 CBC 模式,主要区别在于 ECB 无需偏移量 IV,CBC 需要偏移量


加解密代码

如果在 Windows 平台下,使用 from Crypto.Cipher import AES 遇到关于 Module Not Found Error : No module named 'Crypto' 的报错,可尝试如下办法解决

参考文献:

  1. 关于Module Not Found Error No module named Crypto解决_windows modulenotfounderror: no module named ‘cryp-CSDN博客
pip uninstall crypto
pip uninstall pycryptodome
pip install pycryptodome

Python 版(CBC 模式)

from Crypto.Cipher import AES  
import binascii  
  
key = b'1234567812345678'  # 秘钥,bytes 类型  
iv = b'1234567812345678'  # iv 偏移量,bytes 类型  
text = b'welcome_to_uf4te'  # 需要加解密的内容,bytes 类型  
  
aes = AES.new(key, AES.MODE_CBC, iv)  # 创建一个 AES 对象,AES.MODE_CBC 表示 CBC 模式  
den_text = aes.decrypt(text)  # 解密,bytes 类型  
print("解密:", binascii.b2a_hex(den_text))   # 将字节对象转换为十六进制字符串  
  
aes = AES.new(key, AES.MODE_CBC, iv)  # CBC 模式下解密需要重新创建一个 AES 对象,AES.MODE_CBC 表示 CBC 模式  
en_text = aes.encrypt(den_text)  # 加密,bytes 类型  
print("加密:", en_text)


''' 输出:
解密: b'576fab9d1e22110b39027d9d85f7417f'
加密: b'welcome_to_uf4te'
'''

Python 版(ECB 模式)

from Crypto.Cipher import AES  
import binascii  
  
key = b'1234567812345678'  # 秘钥,bytes 类型  
text = b'weclome_to_uf4te'  # 需要加解密的内容,bytes 类型  
  
aes = AES.new(key, AES.MODE_ECB)  # 创建一个 AES 对象,AES.MODE_ECB 表示模式是 ECB 模式  
  
den_text = aes.decrypt(text)  # 解密,bytes 类型  
print("解密:", binascii.b2a_hex(den_text))  
  
en_text = aes.encrypt(den_text)  # 加密,bytes 类型  
print("加密:", en_text)


''' 输出:
解密: b'914532f3c0677e44c5169a096ffccbd7'
加密: b'weclome_to_uf4te'
'''

IDA 示例

int __cdecl sub_401EC0(int *a1, int a2, int a3, int a4, int a5)
{
  int v5; // ebx
  int *v7; // ebp
  int v8; // eax
  char *v10; // esi
  int v11; // esi
  int *v12; // edi
  char *v13; // esi
  int v14; // eax
  int v15; // ecx
  int *v16; // eax
  char *v17; // edx
  _DWORD *v18; // ebp
  int v19; // edx
  int v20; // ecx
  int *v21; // eax
  char *v22; // edx
  bool v23; // cc
  int *v24; // edx
  int *v25; // ecx
  int v26; // esi
  int v27; // edi
  int v28; // edi
  _DWORD *v29; // esi
  _DWORD *v30; // edi
  int v31; // ebx
  bool v32; // zf
  int v33; // ebx
  int v34; // ecx
  int *v35; // edx
  int v36; // esi
  int v37; // edi
  int v38; // eax
  char v39[32]; // [esp+8h] [ebp-20h] BYREF
  int v40; // [esp+2Ch] [ebp+4h]
  _DWORD *v41; // [esp+30h] [ebp+8h]
  int v42; // [esp+30h] [ebp+8h]
  int v43; // [esp+34h] [ebp+Ch]
  char *v44; // [esp+38h] [ebp+10h]
  int v45; // [esp+3Ch] [ebp+14h]
  int v46; // [esp+3Ch] [ebp+14h]
  unsigned int v47; // [esp+3Ch] [ebp+14h]

  v5 = a3 / 4;
  if ( a3 / 4 != 4 && v5 != 6 && v5 != 8 )
    return 0;
  v7 = a1;
  *a1 = v5;
  a1[1] = v5 + 6;
  sub_401E80(a1, a2, a5);
  v8 = 4 * (v5 + 6) + 4;
  v43 = v8;
  if ( v5 > 0 )
  {
    v10 = v39;
    v45 = v5;
    do
    {
      *v10 = sub_4021A0(a4);
      v10 += 4;
      a4 += 4;
      --v45;
    }
    while ( v45 );
    v8 = 4 * (v5 + 6) + 4;
    qmemcpy(a1 + 3, v39, 4 * v5);
  }
  v46 = v5;
  if ( v5 < v8 )
  {
    v41 = &unk_4084DC;
    v11 = -8 - a1;
    v12 = &a1[v5 + 2];
    while ( 1 )
    {
      v13 = v12 + v11;
      v14 = sub_4021D0((*v12 >> 8) | (*v12 << 24));   // sub_4021D0 函数对 s_box 进行字节的映射
      v15 = 1;
      v12[1] = *v41 ^ *&v13[a1 + 12 + -4 * v5] ^ v14;
      if ( v5 > 6 )
      {
        v18 = v12 + 2;
        v44 = &v13[a1 + 16 + -4 * v5];
        do
        {
          if ( v15 + v46 >= v43 )
            break;
          v19 = *(v18++ - 1);
          ++v15;
          *(v18 - 1) = *v44 ^ v19;
          v44 += 4;
        }
        while ( v15 < 4 );
        if ( v46 + 4 < v43 )
          v12[5] = *&v13[a1 + 28 + -4 * v5] ^ sub_4021D0(v12[4]);
        v20 = 5;
        v21 = v12 + 6;
        v22 = &v13[a1 + 32 + -4 * v5];
        do
        {
          if ( v20 + v46 >= v43 )
            break;
          ++v20;
          *v21 = *v22 ^ *(v21 - 1);
          v22 += 4;
          ++v21;
        }
        while ( v20 < v5 );
      }
      else if ( v5 > 1 )
      {
        v16 = v12 + 2;
        v17 = &v13[a1 + 16 + -4 * v5];
        do
        {
          if ( v15 + v46 >= v43 )
            break;
          ++v15;
          *v16 = *v17 ^ *(v16 - 1);
          v17 += 4;
          ++v16;
        }
        while ( v15 < v5 );
      }
      v12 += v5;
      v8 = 4 * (v5 + 6) + 4;
      v23 = v5 + v46 < v43;
      v46 += v5;
      ++v41;
      if ( !v23 )
        break;
      v11 = -8 - a1;
    }
    v7 = a1;
  }
  v24 = &v7[v8 + 59];
  v25 = v7 + 3;
  v26 = 4;
  do
  {
    v27 = *v25++;
    *v24++ = v27;
    --v26;
  }
  while ( v26 );
  v28 = v8 - 4;
  v42 = v8 - 4;
  if ( v8 - 4 > 4 )
  {
    v29 = v7 + 7;
    v40 = &v7[v8 + 55];
    v47 = (v8 - 5) >> 2;
    do
    {
      v30 = v40;
      v31 = 4;
      do
      {
        *v30++ = sub_402270(*v29++);
        --v31;
      }
      while ( v31 );
      v32 = v47 == 1;
      v40 -= 16;
      --v47;
    }
    while ( !v32 );
    v8 = v43;
    v28 = v42;
  }
  if ( v28 < v8 )
  {
    v33 = 4 * v8;
    v34 = 4 * v28;
    v35 = &v7[v28 + 3];
    v36 = v8 - v28;
    do
    {
      v37 = *v35;
      v38 = v34 - v33;
      v34 += 4;
      ++v35;
      --v36;
      *(v7 + v38 + 268) = v37;
    }
    while ( v36 );
  }
  return 1;
}