1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
|
import hashlib import math from typing import Any, Dict, List from Crypto.Util.number import * from pwn import *
rotate_amounts = [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]
constants = [int(abs(math.sin(i + 1)) * 2 ** 32) & 0xFFFFFFFF for i in range(64)]
functions = 16 * [lambda b, c, d: (b & c) | (~b & d)] + \ 16 * [lambda b, c, d: (d & b) | (~d & c)] + \ 16 * [lambda b, c, d: b ^ c ^ d] + \ 16 * [lambda b, c, d: c ^ (b | ~d)]
index_functions = 16 * [lambda i: i] + \ 16 * [lambda i: (5 * i + 1) % 16] + \ 16 * [lambda i: (3 * i + 5) % 16] + \ 16 * [lambda i: (7 * i) % 16]
def get_init_values(A: int = 0x67452301, B: int = 0xefcdab89, C: int = 0x98badcfe, D: int = 0x10325476) -> List[int]: return [A, B, C, D]
def left_rotate(x, amount): x &= 0xFFFFFFFF return ((x << amount) | (x >> (32 - amount))) & 0xFFFFFFFF
def padding_message(msg: bytes) -> bytes: """ 在MD5算法中,首先需要对输入信息进行填充,使其位长对512求余的结果等于448,并且填充必须进行,即使其位长对512求余的结果等于448。 因此,信息的位长(Bits Length)将被扩展至N*512+448,N为一个非负整数,N可以是零。 填充的方法如下: 1) 在信息的后面填充一个1和无数个0,直到满足上面的条件时才停止用0对信息的填充。 2) 在这个结果后面附加一个以64位二进制表示的填充前信息长度(单位为Bit),如果二进制表示的填充前信息长度超过64位,则取低64位。 经过这两步的处理,信息的位长=N*512+448+64=(N+1)*512,即长度恰好是512的整数倍。这样做的原因是为满足后面处理中对信息长度的要求。 """ orig_len_in_bits = (8 * len(msg)) & 0xffffffffffffffff msg += bytes([0x80]) while len(msg) % 64 != 56: msg += bytes([0x00]) msg += orig_len_in_bits.to_bytes(8, byteorder = 'little') return msg
def md5(message: bytes, A: int = 0x67452301, B: int = 0xefcdab89, C: int = 0x98badcfe, D: int = 0x10325476) -> int: message = padding_message(message) hash_pieces = get_init_values(A, B, C, D)[:] for chunk_ofst in range(0, len(message), 64): a, b, c, d = hash_pieces chunk = message[chunk_ofst:chunk_ofst + 64] for i in range(64): f = functions[i](b, c, d) g = index_functions[i](i) to_rotate = a + f + constants[i] + int.from_bytes(chunk[4 * g:4 * g + 4], byteorder = 'little') new_b = (b + left_rotate(to_rotate, rotate_amounts[i])) & 0xFFFFFFFF a, b, c, d = d, new_b, b, c for i, val in enumerate([a, b, c, d]): hash_pieces[i] += val hash_pieces[i] &= 0xFFFFFFFF return sum(x << (32 * i) for i, x in enumerate(hash_pieces))
def md5_to_hex(digest: int) -> str: raw = digest.to_bytes(16, byteorder = 'little') return '{:032x}'.format(int.from_bytes(raw, byteorder = 'big'))
def get_md5(message: bytes, A: int = 0x67452301, B: int = 0xefcdab89, C: int = 0x98badcfe, D: int = 0x10325476) -> str: return md5_to_hex(md5(message, A, B, C, D))
def md5_attack(message: bytes, A: int = 0x67452301, B: int = 0xefcdab89, C: int = 0x98badcfe, D: int = 0x10325476) -> int: hash_pieces = get_init_values(A, B, C, D)[:] for chunk_ofst in range(0, len(message), 64): a, b, c, d = hash_pieces chunk = message[chunk_ofst:chunk_ofst + 64] for i in range(64): f = functions[i](b, c, d) g = index_functions[i](i) to_rotate = a + f + constants[i] + int.from_bytes(chunk[4 * g:4 * g + 4], byteorder = 'little') new_b = (b + left_rotate(to_rotate, rotate_amounts[i])) & 0xFFFFFFFF a, b, c, d = d, new_b, b, c for i, val in enumerate([a, b, c, d]): hash_pieces[i] += val hash_pieces[i] &= 0xFFFFFFFF return sum(x << (32 * i) for i, x in enumerate(hash_pieces))
def get_init_values_from_hash_str(real_hash: str) -> List[int]: """ Args: real_hash: 真实的hash结算结果
Returns: 哈希初始化值[A, B, C, D]
""" str_list: List[str] = [real_hash[i * 8:(i + 1) * 8] for i in range(4)] return [int.from_bytes(int('0x' + s, 16).to_bytes(4, byteorder = 'little'), byteorder = 'big') for s in str_list]
def get_md5_attack_materials(origin_msg: bytes, key_len: int, real_hash: str, append_data: bytes) -> Dict[str, Any]: """ Args: origin_msg: 原始的消息字节流 key_len: 原始密钥(盐)的长度 real_hash: 计算出的真实的hash值 append_data: 需要添加的攻击数据
Returns: 发起攻击需要的物料信息 { 'attack_fake_msg': bytes([...]), 'attack_hash_value': str(a1b2c3d4...) }
""" init_values = get_init_values_from_hash_str(real_hash) fake_key: bytes = bytes([0xff for _ in range(key_len)]) finally_padded_attack_data = padding_message(padding_message(fake_key + origin_msg) + append_data) attack_hash_value = md5_to_hex(md5_attack(finally_padded_attack_data[len(padding_message(fake_key + origin_msg)):], A = init_values[0], B = init_values[1], C = init_values[2], D = init_values[3])) fake_padding_data = padding_message(fake_key + origin_msg)[len(fake_key + origin_msg):] attack_fake_msg = origin_msg + fake_padding_data + append_data return {'attack_fake_msg': attack_fake_msg, 'attack_hash_value': attack_hash_value}
if __name__ == '__main__': sh = remote(...) for Rounds in range(100): print(Rounds) sh.recvuntil(b"Round") sh.recvuntil(b"msg:") msg = long_to_bytes(int(sh.recvline().strip().decode(),16)) attack_data: bytes = b"SPARKLE" sh.recvuntil(b"sign:") h = sh.recvline().strip().decode() md5_value = h attack_materials = get_md5_attack_materials(msg, 32, md5_value, attack_data) newmsg = attack_materials['attack_fake_msg'] h = attack_materials['attack_hash_value'] sh.sendline(newmsg.hex().encode()) sh.sendline(h.encode())
sh.recvline() sh.recvline() print(sh.recvline())
|