Hash扩展长度攻击及Hashdump的使用

3 篇文章 0 订阅
3 篇文章 0 订阅

Hash扩展长度攻击及Hashdump的使用

一、Hash扩展长度攻击

1.1 SHA1原理

​ 首先,当hash函数拿到需要被hash的字符串后,先将其字节长度整除64,取得余数。如果该余数正好等于56,那么就在该字符串最后添加上8个字节的长度描述符(具体用bit表示)。如果不等于56,就先对字符串进行长度填充,填充时第一个字节为hex(80),其他字节均用hex(00)填充,填充至余数为56后,同样增加8个字节的长度描述符(该长度描述符为需要被hash的字符串的长度,不是填充之后整个字符串的长度)。以上过程,称之为补位。

image-20231122214258314

​ 补位完成后,字符串以64位一组进行分组(因为上面的余数为56,加上8个字节的长度描述符后,正好是64位,凑成一组)。字符串能被分成几组就会进行多少次“复杂的数学变化”。每次进行“复杂的数学变化”都会生成一组新的registers值供下一次“复杂的数学变化”来调用。第一次“复杂的数学变化”会调用程序中的默认值。当后面已经没有分组可以进行数学变化时,该组生成的registers值就是最后的hash值。

在sha1的运算过程中,为确保同一个字符串的sha1值唯一,所以需要保证第一次registers的值也唯一。所以在sha1算法中,registers具有初始值。如上图中的registers值0。

​ Hash值的随机性完全依赖于进行“复杂的数学变化”时输入的registers值和该次运算中字符串分组的数据。如果进行“复杂数学变化”时输入的registers值和该次运算的字符串分组相同,那么他们各自生成的新的registers值也相同。

1.2 举例分析

​ 当需要被hash的字符串为str_a = ”123456”,程序首先判断,len(str_a) % 64 == 56是否成立。这里很明显不成立。那么程序就进行补位操作。首先补位成余数为56的长度。

image-20231122214543027

​ 如上图,蓝色字体就为程序对该字符串进行补位的数据。当满足len(str_a) % 64 == 56后,程序就在该字符串的后面添加8个字节的长度描述符。注意,此处的长度为原始需要被hash的长度。也就是len(str_a) = 6字节*8bit/字节= 48bit=0x30bit。

image-20231122214625170

​ 补位+长度描述符=64个字节,正好是一个分组。所以此处只要进行一次复杂的数学变化就可以了。程序根据该64个字节的数据和registers值0生成新的registers值1。那么该新的registers值1就是str_a的sha1值

1.3 扩展攻击如何利用

简单来说,就是服务器上会生成一个salt值,该salt值你是不可预测的。但是你又知道了sha1(salt+data)的值,该data的值你也是知道的。假设此处的data的值report.pdf,最后sha1的值为:0a8d538b724c6f2b4288526eb540ee7c。为了方便理解,我们继续假设salt的长度为16位。

image-20231122214954991

​ 将上图的字符串进行sha1操作时,同样先进行整除,然后取余。最后再补上8位的长度描述符。补位+添加长度描述符后的字符串如下图:

image-20231122215036424

​ 该长度也就满足了64位的分组,只需要进行一次“复杂的数学运算”就可以得到最后的sha1值了。

​ 下面请各位看官思考如何进行下面一个字符串的sha1操作。

image-20231122215117563

​ 同样,还是先进行分组。由于该字符串的长度大于64个字节,且小于128个字节,所以要分成两组,需要进行两次“复杂的数学运算”。这个时候我们发现,第一个分组的数据和上图中补码后的数据完全一样,又因为他们都是第一个分组,初始的registers值也一样。那么经过第一轮“复杂的数学运算”,他们各自生成的registers值也同样是相同的。唯一不同的是,由于上面的长度小于64字节,所以只需要进行一轮运算便得到了最后的sha1值。然后这里的字符串有两个分组,需要将第一轮更新的registers值(也就是第一轮运算出来的sha1值)作为第二轮“复杂的数学运算”的registers值,然后才能得出最终的sha1值。

根据上面例子就说明,如果salt的值你不知道,但是你知道长度,又知道sha1(salt),那么就也就可以知道sha1(salt+“填充数据”+“任意可控数据”)。这里的salt+“填充数据”就是对salt进行sha1时所补全的数据+最后8位的长度描述符。一般来说,salt+”填充数据”的长度就是64字节,正好是一个分组。如果salt的长度就大于了56个字节,那么加入填充数据后的长度应该是N个64字节,等于N个分组。

​ 为什么?你可以想象,sha1程序再对(salt+“填充数据”+“任意可控数据”)进行hash时,只需要进行第二轮及第二轮以后的运算。因为第一轮运算后的registers值就是sha1(salt)的值,该值你已经知道了。

一点微不足道在CTF中的变形理解(个人理解,可忽略)

Hash(salt)              =>Hash(salt+"填充数据"+"任意可控数据")
Hash("未知密钥"+"abcdef")=>Hash("未知密钥"+"abcdef"+"填充数据"+"其他字符串")
salt=="未知密钥"+"abcdef"

二、Hashdump的使用

HashPump是一个借助于OpenSSL实现了针对多种散列函数的攻击的工具,支持针对MD5、CRC32、SHA1、SHA256和SHA512等长度扩展攻击。而MD2、SHA224和SHA384算法不受此攻击的影响,因其部分避免了对状态变量的输出,并不输出全部的状态变量。

2.1 环境需要

loguru==0.6.0

2.2 Hashdump

import base64
import hashlib
import hmac
import struct
import sys
import time
import urllib.parse

from common.md5_manual import md5_manual
from loguru import logger
from common.crypto_utils import CryptoUtils
# print(len("patrilic_is_good"))

class HashExtAttack:
    """
    哈希长度扩展攻击,解决 hashpump 在win下使用困难的问题
    目前仅支持md5,如果你对认证算法有了解可以手动改写str_add中的字符串拼接方式
    """

    def __init__(self):
        self.know_text = b""
        self.know_text_padding = b""
        self.new_text = b""
        self.rand_str = b''
        self.know_hash = b"3c5a36dd888251601d36bbc184648717"
        self.key_length = 15

    def _padding_msg(self):
        """填充明文"""
        logger.debug("填充明文")
        self.know_text_padding = md5_manual.padding_str(self.know_text)
        logger.debug(f"已知明文填充:{self.know_text_padding}")

    def _gen_new_plain_text(self):
        """生成新明文"""
        self.new_text = self.know_text_padding + self.rand_str  # b'80' + 55 * b'\x00' + struct.pack("<Q", 512 + len(self.rand_str) *8)
        logger.debug(f"new_text: {self.new_text}")

    def split_hash(self, hash_str: bytes):
        by_new = CryptoUtils.trans_str_origin2_bytes(hash_str.decode())
        return struct.unpack("<IIII", by_new)

    def _guess_new_hash(self) -> tuple:
        """生成新hash"""
        # 第一步先生成新的字符串
        # 对已知明文进行填充
        self._padding_msg()
        # 第二步 生成新明文
        self._gen_new_plain_text()
        # 第三步 生成新hash(基于已知hash进行计算)
        # 3.1 hash拆分成4个分组
        hash_block = self.split_hash(hash_str=self.know_hash)
        md5_manual.A, md5_manual.B, md5_manual.C, md5_manual.D = hash_block
        tmp_str = md5_manual.padding_str(self.new_text)
        logger.debug(f"新明文填充tmp_str({len(tmp_str)}): {tmp_str}")
        logger.debug(f"参与手工分块计算的byte:{tmp_str[-64:]}")
        md5_manual.solve(tmp_str[-64:])
        self.new_hash = md5_manual.hex_digest()

        return self.new_text, self.new_hash

    def run(self, know_text, know_hash, rand_str, key_len) -> tuple:
        # self.know_text = input("请输入已知明文:")
        self.know_text = ("*" * key_len + know_text).encode()  # 密钥拼接
        self.know_hash = know_hash.encode()
        self.rand_str = rand_str.encode()

        self._guess_new_hash()
        logger.info(f"已知明文:{self.know_text[key_len:]}")
        logger.info(f"已知hash:{self.know_hash}")
        logger.debug(f"任意填充:{self.rand_str}")
        logger.info(f"新明文:{self.new_text[key_len:]}")
        logger.info(f"新明文(url编码):{urllib.parse.quote(self.new_text[key_len:], safe='&=')}")
        # logger.debug(f"新明文:{base64.b64encode(self.new_text[key_len:])}")
        logger.info(f"新hash:{self.new_hash}")
        return self.new_text[key_len:], self.new_hash

    def input_run(self):
        time.sleep(0.2)
        self.run(input("请输入已知明文:"), input("请输入已知hash: "), input("请输入扩展字符: "),
                 int(input("请输入密钥长度:")))

    def test(self):
        self.run(
            "order_id=70&buyer_id=17&good_id=38&buyer_point=300&good_price=888&order_create_time=1678236217.799935",
            "178944d4a39e4e4af6522c6de6cb24c5", "&good_price=1", 50)


hash_ext_attack = HashExtAttack()

if __name__ == '__main__':

    logger.remove()
    logger.add(sys.stderr, level="INFO")
    hash_ext_attack.input_run()

2.3 使用方法

交互式:

  1. 运行 hash_ext_attack.py 脚本

  2. 按照提示输入

  3. 得到新的明文和新的hash

调用式:

在不知道密钥长度需要爆破或者批量生成时有用。按照hash_ext_attack.test() 方法中的实例,在其他脚本中调用hash_ext_attack类中run方法

三、CTF题目

传送门:Admins Only!

四、参考

参考1:hash扩展长度攻击及hashdump使用

参考1:hash-ext-attack

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值