逆向中的Hash类算法

简介

Hash 类算法是一种摘要算法,摘要结果是不可逆的。所以一般在逆向中我们通常碰到 Hash 算法要通过它给出的一些信息来进行碰撞爆破。

下面我们首先了解一下常见的 Hash 算法。

算法特征

MD5

MD5(Message-Digest Algorithm 5)是信息学中使用广泛的哈希算法

这个算法具有很多性质:

  1. 压缩性:对于任意长度的输入,输出长度总是相同的

  2. 抗修改性:对原数据的一点点修改都会导致最终结果的最大变化。

  3. 抗碰撞性:已知原数据和 MD5 值很难生成与原数据不同但 MD5 值相同的数据。

可以理解为:生成任意一段数据的 "数字指纹",对文件或数据的微小改动都会之间导致数字指纹的巨大变化。

Hash 算法常见一般有两种形式的调用:

  • 封装成函数
复制代码
uint8_t digest[16];
uint8_t input[]="xxxxxx";
​
MD5_hash(input,sizeof(input)-1,digest);
  • 分三步完成哈希
复制代码
MD5_CTX ctx;
uint8_t digest[16];
uint8_t input[]="xxxxxx";
​
MD5_Init(&ctx);
MD5_Update(&ctx,input,sizeof(input)-1);
MD5_Final(&ctx,digest);

openssl源码的函数实现中,可以在靠近开头的位置看到四个固定常量。

复制代码
A = 0x67452301
B = 0xEFCDAB89
C = 0x98BADCFE
D = 0X10325476

识别特征:

  • 四个固定的常量(初始化向量)

  • 不论输入是多长,输出永远是16字节

  • 符合上述两种调用方式之一

SHA1

SHA1 算法与 MD5 类似,只不过 SHA1 有五个常量:

复制代码
H0 = 0x67452301
H1 = 0xEFCDAB89
H2 = 0X98BADCFE
H3 = 0X10325476
H4 = 0XC3D2E1F0

识别特征:

  • 五个固定的常量(初始化向量)

  • 不论输入是多长,输出永远是 20 字节

  • 符合上述两种调用方式之一

SHA256

SHA256 则有八个常量:

复制代码
H0 = 0x67452301
H1 = 0xEFCDAB89
H2 = 0x98BADCFE
H3 = 0x10325476
H4 = 0xC3D2E1F0
H5 = 0x9b05688c
H6 = 0x1f83d9ab
H7 = 0x5be0cd19

识别特征:

  • 八个固定的常量(初始化向量)

  • 不论输入是多长,输出永远是32字节

  • 符合上述两种调用方式之一

逆向方式

算法分析

通过 ida 的 findcrypt 插件我们可以直接查找到程序中的 Hash 常量来判断加密算法,但是这通常只对自己实现或静态链接openssl库才有作用。如果使用openssl链接库编译动态链接,那么 findcrypt 是找不到的。

也不能说找不到,当动态调试程序的时候使用 findcrypt 插件进行查找可以找到整个openssl链接库的常量。这样我们根本无法分析程序是哪个加密算法。

在使用动态链接的情况下,通常可以通过 ida 函数窗口的函数分析出加密算法。

看到下例中的md5字样,我们可以判断程序为md5加密算法。

网站查询

通过在线网站数据库查询并 "解密" Hash值。

md5:md5在线解密破解,md5解密加密

脚本爆破

通过编写脚本进行碰撞爆破。

通用脚本:

复制代码
import string
import hashlib
import itertools
​
#设置目标哈希值
target = bytes.fromhex("")  
​
#设置字符集(可以根据需要修改,以下为大小写字母和数字)
charset = (string.ascii_letters + string.digits).encode()
​
#根据选择的哈希算法创建哈希对象
hash_type = 'sha256'
​
if hash_type == 'md5':
    hash_func = hashlib.md5
elif hash_type == 'sha1':
    hash_func = hashlib.sha1
elif hash_type == 'sha256':
    hash_func = hashlib.sha256
else:
    print("Unsupported hash type")
    exit()
​
for i in itertools.product(charset, repeat=6): #这里的6是字符的长度,可以修改为任何你想要的长度
    guess = bytes(i)
    hashed = hash_func(guess).digest()
    if hashed == target:
        print(f"Found match: {guess.decode()}")
        break

hashcat

使用 hashcat 这个工具进行碰撞爆破。

常用参数:

  • -m 指定哈希类型

  • -a 指定破解模式

  • -V 查看版本信息

  • -o 将输出结果储存到指定文件

  • --force 忽略警告

  • --show 仅显示破解的 hash 密码和对应的明文

  • --remove 从源文件中删除破解成功的 hash

  • --username 忽略 hash表 中的用户名

  • -b 测试计算机破解速度和相关硬件信息

  • -O 限制密码长度

  • -T 设置线程数

  • -r 使用规则文件

  • -1 自定义字符集 -1 0123asd ?1={0123asd}

  • -2 自定义字符集 -2 0123asd ?2={0123asd}

  • -3 自定义字符集 -3 0123asd ?3={0123asd}

  • -i 启用增量破解模式

  • --increment-min 设置密码最小长度

  • --increment-max 设置密码最大长度

破解模式介绍:

  • 0 straight 字典破解

  • 1 combination 将字典中密码进行组合(1 2>11 22 12 21)

  • 3 brute-force 使用指定掩码破解

  • 6 Hybrid Wordlist + Mask 字典+掩码破解

  • 7 Hybrid Mask + Wordlist 掩码+字典破解

集成的字符集:

  • ?l 代表小写字母

  • ?u 代表大写字母

  • ?d 代表数字

  • ?s 代表特殊字符

  • ?a 代表大小写字母、数字以及特殊字符

  • ?b 0x00-0xff

例题

[SUCTF 2018 招新赛]hash

  • 分析

ida 反编译分析。看到了主函数中的 flag 前缀,下面使用 findcrypt 分析算法。

使用 findcrypt 后我们搜索到了程序中的 MD5 常量,可以判断程序使用的是 MD5 算法。

结合上面信息分析程序代码逻辑。

程序要求输入字符的前 6 个字符为SUCTF{,最后一个字符为}sub_4011A0函数使用了 MD5 的常量,应该是加密函数。最后将结果存到v18,因为下面的代码疑似以两位十六进制打印了v18变量,这可能是存在函数返回值的变量。

下面有一个用于判断的 MD5 值,与其判断的Buffer在与其判断之前。将符合i&1==0条件的字符跟 1 进行了异或,i&1即结果是偶数位的字符,所以我们爆破它之前需要跟 1 再次进行异或。

写出逆向脚本:

复制代码
md5="bf772f6ed89838b9gb9f7abf3cc09413"
md5=list(md5)
for i in range(len(md5)):
    if i&1 == 0:
        md5[i]=chr(ord(md5[i])^1)
result="".join(md5)
print(result)
#cf673f7ee88828c9fb8f6acf2cb08403

然后我们可以先在 md5 查询网站查询。

查询结果为:birthday

接下来我们继续逆向剩下的代码程序。

将加密后的buffer与 md5 密文进行比较,如果相等则执行第二个if语句部分的代码。

下面我们跟进分析sub_401A90函数。

分析函数代码,首先将取输入的长度。然后以输入长度为界限进行迭代处理。将每个字符根据下标进行i*2+1偏移。i就是字符串的下标。

根据代码逻辑,对比较的字符串进行逆向处理。

复制代码
enc="`ut9t;"
result=""
for i in range(len(enc)):
    n=i*2+1
    result+=chr(ord(enc[i])-n)
print(result)
#_ro2k0

将第一段 flag 和 第二段 flag 拼接起来得:SUCTF{birthday_ro2k0}

2024极客大挑战 blasting_master

  • 分析

分析主函数代码,发现读取了输入并去除换行符。然后由byte_42DE作为标志决定是否执行sub_1464函数,函数参数为我们输入的内容。然后再对指定大小内存进行比较检查对错。

根据以上逻辑我们可以判断程序默认是执行sub_1464函数的,这个函数极可能就是加密函数。而unk_2020以及unk_4060可能就是存储密文的变量以及存储加密结果的变量。dword_4040很明显就是比较长度了。

我们先根据代码逻辑进行函数及变量重命名方便理解。

重命名后:

接下来我们首先使用 findcrypt 进行算法特征查找,但是最后 findcrypt 并没有找到算法特征。

但是我们在函数窗口发现了算法特征,可以看到动态链接函数中的 MD5 字样。我们可以由此判断encrypt加密算法是 MD5 算法。

进入encrypt函数分析,可以看到代码逻辑非常乱。

我们先根据代码逻辑修改变量和函数类型。

修改后的:

这里v3变量获取了我们的输入,之后将v3作为参数传入了sub_13E2参数,并且将dec数组作为第二个参数传入函数。

这里根据前面的逻辑判断,sub_13E2大概就是封装的 MD5 加密函数。dec是存放结果的数组。

下面的逻辑就是对加密结果进行混淆的逻辑了。

跟进sub_13E2函数查看,验证我们的判断是正确的。

我们接下来分析函数中对加密结果进行混淆的逻辑。

以上代码循环执行了十六次,即混淆了十六次。而混淆的数据以及存储的位置每次又都是不同的。我们可以判断这里我们的 md5 加密同样进行了十六次,而我们的 flag 前缀确定的是SYS{,所以我们可以将密文数据分为 16 组进行爆破。

  • exp
复制代码
import hashlib 
​
cipher = [0xB2, 0x50, 0xA0, 0xBC, 0x3A, 0x7F, 0x54, 0x6D, 0x96, 0x07, 0x0F, 0x71, 0x9A, 0x72, 0xEB, 0xA5, 0xA0, 0xB5, 0x71, 0xA4, 0x6A, 0xB8, 0xBA, 0xFA, 0xE4, 0x31, 0xC3, 0x71, 0x54, 0x29, 0xA7, 0x59, 0x20, 0x2B, 0x13, 0x21, 0xBD, 0x67, 0x5F, 0x8D, 0x65, 0x3A, 0x02, 0x27, 0x08, 0x4F, 0x92, 0x9C, 0xB5, 0x7C, 0xDF, 0x69, 0x34, 0xB8, 0x82, 0x2D, 0xF6, 0xCA, 0x7A, 0x65, 0x98, 0x63, 0xDC, 0x51, 0x2A, 0x34, 0x97, 0x4F, 0xF8, 0xBC, 0x23, 0x1F, 0x38, 0xA8, 0xA6, 0x2F, 0xA9, 0x0D, 0x64, 0x4C, 0xAC, 0x2F, 0xF9, 0xF5, 0x2D, 0xB1, 0x91, 0xA8, 0xD5, 0x76, 0xD9, 0x2D, 0xC6, 0xAC, 0x2E, 0x69, 0x32, 0xD5, 0x64, 0x1D, 0xC1, 0x3C, 0xEC, 0xF5, 0x2C, 0x90, 0xED, 0xF4, 0x17, 0x8B, 0x55, 0x4C, 0xE4, 0x6C, 0x3B, 0xB3, 0xDA, 0x29, 0xC0, 0x7B, 0x39, 0xDF, 0x92, 0x73, 0xFC, 0xC9, 0xC2, 0xA8, 0x68, 0x11, 0x22, 0x2B, 0x64, 0x3F, 0x12, 0x9B, 0x95, 0x73, 0x2A, 0x05, 0xD3, 0x3F, 0x2E, 0x33, 0xF1, 0x85, 0xED, 0x07, 0x7B, 0x86, 0x8F, 0x62, 0x2D, 0x79, 0x03, 0xAC, 0x80, 0xCE, 0xF5, 0xB2, 0xA0, 0x0C, 0xF7, 0xE1, 0xC5, 0x0E, 0x63, 0x27, 0xD1, 0x65, 0x23, 0xEA, 0x5A, 0x1C, 0x02, 0x0B, 0x32, 0xBA, 0x1F, 0xE5, 0xC7, 0x22, 0xA5, 0x66, 0x77, 0xEA, 0x5B, 0xE4, 0x64, 0xAB, 0x8B, 0x60, 0xB6, 0xDF, 0x00, 0xDC, 0xF7, 0x6D, 0x93, 0xEC, 0x2F, 0x2F, 0x68, 0x07, 0x50, 0xE0, 0xD1, 0x1A, 0x3F, 0xC6, 0x4E, 0x2E, 0xC6, 0xBB, 0xAE, 0x08, 0x40, 0xD8, 0x5B, 0x11, 0xB5, 0xDC, 0x15, 0x35, 0x7F, 0x63, 0x49, 0x3E, 0x5B, 0x9C, 0x0D, 0xFC, 0x0D, 0xB6, 0x80, 0xB7, 0x2B, 0x00, 0xEF, 0x3C, 0x0C, 0x2F, 0xEB, 0x86, 0x44, 0x57, 0x74, 0x9E, 0x5F, 0x1F, 0x8B, 0xA1, 0xC9, 0x01, 0xF1, 0xD8, 0xF4, 0x92, 0x82, 0x95, 0x6F, 0x85, 0xD2, 0x15, 0x22, 0x1F, 0xF0, 0x9F, 0xD1, 0xAB, 0x51, 0x39, 0x9A, 0xB6, 0xC4, 0xDA, 0xFB, 0x38, 0x8D, 0xE6, 0x8C, 0x57, 0x19, 0x5E, 0x94, 0xDA, 0x57, 0xCC, 0xF0, 0xB9, 0x0A, 0x4A, 0x17, 0x82, 0xFC, 0xC5, 0x4F, 0x4B, 0x5A, 0xA5, 0xF4, 0xE5, 0x3E, 0xFA, 0x3A, 0x0A, 0xF4, 0xB4, 0x8E, 0x7F, 0x25, 0x84, 0x75, 0x90, 0xCD, 0x35, 0x87, 0xEB, 0xC3, 0xCE, 0x81, 0x2B, 0x86, 0xC9, 0x16, 0x7E, 0x85, 0x68, 0x2D, 0xF1, 0xDB, 0x8E, 0x74, 0x15, 0xCF, 0x95, 0x51, 0x07, 0x88, 0x5E, 0x1B, 0xE9, 0x37, 0xC9, 0x5B, 0xBA, 0x61, 0xEB, 0x9F, 0x7B, 0xE4, 0x89, 0x10, 0xF0, 0x6E, 0xCD, 0x75, 0x71, 0xAD, 0x09, 0x74, 0x58, 0x49, 0xA3, 0xF5, 0x33, 0x83, 0x75, 0x22, 0x95, 0x1B, 0xE3, 0x3C, 0x48, 0x05, 0x5C, 0xAD, 0xA8, 0x6B, 0xFD, 0x41, 0xEB, 0xAF, 0xC6, 0x02, 0x28, 0xC6, 0x5E, 0xCF, 0x36, 0xAE, 0x50, 0xCE, 0x93, 0xF2, 0x70, 0x88, 0x9D, 0x3F, 0x4A, 0x9F, 0x86, 0xE7, 0x67, 0x64, 0xB0, 0x02, 0x96, 0x0C, 0xAB, 0x9F, 0xEB, 0x4B, 0x03, 0x44, 0x92, 0xDE, 0x6C, 0xF4, 0xCE, 0x32, 0x4F, 0x4F, 0x38, 0xE2, 0x52, 0x59, 0xCA, 0x95, 0x4A, 0x11, 0xD8, 0x30, 0xA2, 0x7B, 0xD5, 0x3A, 0xE6, 0x11, 0xDA, 0x3A, 0x4A, 0x33, 0x61, 0x39, 0x65, 0x26, 0xD2, 0x78, 0xBC, 0xED, 0xBD, 0xA5, 0x8B, 0x2B, 0x87, 0x4C, 0x95, 0x47, 0x25, 0x02, 0xBA, 0x83, 0x3D, 0xDC, 0xE4, 0x6A, 0xAD, 0x67, 0xDD, 0x22, 0xB1, 0xBD, 0x2B, 0x7C, 0x53, 0x11, 0x3C, 0xD9, 0x23, 0x06, 0x3D, 0x20, 0xBA, 0x28, 0xC8, 0x2D, 0x89, 0x51, 0x57, 0x63, 0x82, 0xA0, 0xC8, 0xA8, 0xDE, 0x29, 0x61, 0xC1, 0x53, 0x51, 0xB0, 0xBC, 0x37, 0x04, 0xEE, 0xC9, 0x35, 0x8A, 0xA8, 0xA2, 0x66, 0xBA, 0x6F, 0x24, 0xB6, 0x3F, 0x62, 0x41, 0x6D, 0x10, 0x46, 0xCB, 0x06, 0x12, 0x39, 0xD9, 0x0E, 0xF9, 0xDC, 0x19, 0xA7, 0x65, 0xB8, 0xC0, 0x40, 0xBE, 0xF6, 0x99, 0x9A, 0xAF, 0x02, 0x16, 0x37, 0x4D, 0xA5, 0x75, 0x4C, 0x42, 0x4B, 0x1A, 0xF0, 0x52, 0xDA, 0x38, 0xF3, 0x6B, 0xA9, 0x1A, 0xDC, 0xFA, 0x80, 0xB0, 0x60, 0xB1, 0xFD, 0x73, 0x7B, 0x78, 0xD9, 0x62, 0x83, 0x26, 0xBF, 0x16, 0x33, 0x71, 0x79, 0x6F, 0x11, 0x2F, 0xE9, 0xA7, 0xBB, 0x46, 0x46, 0xD6, 0x8F, 0xF6, 0x21, 0x7E, 0xFC, 0x68, 0x12, 0x86, 0x6B, 0xFC, 0x51, 0xC9, 0x70, 0x7A, 0x74, 0xBC, 0x8F, 0x6E, 0x0B, 0x86, 0x42, 0x6F, 0x5C, 0xFD, 0xF7, 0x4E, 0x27, 0x71, 0xFE, 0x37, 0xE6, 0xC8, 0x62, 0x47, 0xFC, 0xD5, 0x6C, 0xBA, 0x5C, 0xD9, 0x29, 0x5A, 0x73, 0xAE, 0xC3, 0x8F, 0xF0, 0x46, 0x95, 0x32, 0x42, 0x2D, 0xD0]
​
md5_list = lambda input_str: list(hashlib.md5(input_str.encode()).digest())
​
cip = [cipher[16 * i: 16 * (i + 1)] for i in range(40)]
​
data1 = 'S'  # 初始化 data1 为 'S'
data2 = 'Y'  # 初始化 data2 为 'Y'
data3 = 'C'  # 初始化 data3 为 'C'
​
print("SYC", end='')  # 打印 "SYC"
​
for i in range(40): 
    # x 从 32 到 127,表示所有字符的 ASCII 码
    for x in range(32, 128): 
        input_str = data1 + data2 + data3 + chr(x)
        md5_lis = md5_list(input_str) 
​
        # 检查 cip[i][j] 是否符合解密条件
        if all(cip[i][j] == ((md5_lis[j] ^ (j + 42)) * 7 + (j % 15) * 82) % 256 for j in range(16)): 
            # 如果所有字节都匹配,打印字符 x,并更新 data1, data2 和 data3
            print(chr(x), end='') 
            data1 = data2 
            data2 = data3 
            data3 = chr(x)
​
            if data3 == '}': 
                exit(0)
相关推荐
高洁012 小时前
AI智能体搭建(2)
人工智能·深度学习·算法·机器学习·知识图谱
木易 士心2 小时前
加密与编码算法全解:从原理到精通(Java & JS 实战版)
java·javascript·算法
福楠2 小时前
C++ | 继承
c语言·开发语言·数据结构·c++·算法
华如锦2 小时前
MongoDB作为小型 AI智能化系统的数据库
java·前端·人工智能·算法
机器学习之心HML2 小时前
GSABO(通常指混合了模拟退火SA和天牛须搜索BAS的改进算法)与BP神经网络结合,用于爆破参数优选
人工智能·神经网络·算法·爆破参数优选
轻微的风格艾丝凡2 小时前
数织求解脚本技术文档
算法·matlab
你怎么知道我是队长2 小时前
C语言---强制类型转换
c语言·开发语言·算法
_OP_CHEN2 小时前
【算法基础篇】(四十六)同余方程终极攻略:从基础转化到实战破解
c++·算法·蓝桥杯·数论·同余方程·扩展欧几里得算法·acm/icpc
程序员泡椒4 小时前
二分查找Go版本实现
数据结构·c++·算法·leetcode·go·二分