黑客猜奇偶升级版
付佳伟 17 级
原版做法想必都知道了,直接按F12
找到文字框,删掉readonly
属性并清空文本框中的内容,然后要猜的MD5就是已知的MD5,看最后一个数字选上就行了。重复30次,没什么技术含量。
升级版不能再发送空的text了,会被替换成不允许为空
,网上查阅资料发现未知原字符串的情况下MD5碰撞是不可能的,只能搜别的办法了。Google一会发现有个东西叫Length Extension Attack,仔细研究发现,MD5会把数据按照64字节(512位)分组,如果不足会进行padding,并且每一组计算出的结果会用于下一组的计算。这意味着页面给出的MD5就是已知的字符串经过padding之后计算出的MD5,可以将其用于下一组的计算。然后就开始找MD5的实现,找到两三个坏掉的代码后发现了HashPump这个小工具,是专门针对几种常见的Hash进行LEA的。
先考虑一下可能用到的工具并准备齐全:
~ $ mkdir guess && cd guess
~/guess $ sudo apt install curl git libssl-dev grep sed
~/guess $ git clone https://github.com/bwall/HashPump
~/guess $ cd HashPump
~/guess/HashPump $ make
~/guess/HashPump $ cd ..
~/guess $
在浏览器中试几次,发现每次都以POST方式发送text, choice和submit三个字段,其中submit的内容永远是URL Encode之后的提交
两个字,决定直接硬编码,而text则是文本框中的随机文字,choice是0或1,对应猜测偶数和奇数。
先用curl
来试一下返回页面的结构
curl --cookie cookie.txt --cookie-jar cookie.txt "http://hack.lug.ustc.edu.cn/dynamic/4/" -X POST -d "submit=%E6%8F%90%E4%BA%A4"
看起来不错,页面结构固定,但是里面包含了至少三个MD5字符串,我们需要提取用来猜测的那个。首先确定一下行号:
curl --cookie cookie.txt --cookie-jar cookie.txt "http://hack.lug.ustc.edu.cn/dynamic/4/" -X POST -d "submit=%E6%8F%90%E4%BA%A4" | cat -n
很好,48行就是我要的那个MD5,grep
提取出来备用
HASH=$(curl --cookie cookie.txt --cookie-jar cookie.txt "http://hack.lug.ustc.edu.cn/dynamic/4/" -X POST -d "submit=%E6%8F%90%E4%BA%A4" | sed -n "48p" | grep -oE '[0-9a-f]{32}')
简单起见,在LEA的补充数据部分,我就补一个a
就够了,反正是任意数据。接下来调试HashPump,发现直接指定data为空的时候还是要求输入data,果断给它一个EOF (Ctrl + D
),然后猜测一下试一试
~/guess $ ./hashpump -k 32 -s "$HASH" -d "" -a "a"
Input Data: a1158d0f03312db755457561a39582aa
\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00a
~/guess $ curl --cookie cookie.txt --cookie-jar cookie.txt "http://hack.lug.ustc.edu.cn/dynamic/4/" -X POST -d "text=%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%01%00%00%00%00%00%00a&choice=0&submit=%E6%8F%90%E4%BA%A4"
非常好,和HashPump算出来的MD5一模一样。
准备工作完毕,下面开始写代码:
#!/bin/bash
COOKIE=cookie.txt
URL=http://hack.lug.ustc.edu.cn/dynamic/4/
LINE=48
T_TEXT=%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%01%00%00%00%00%00%00a
T_CHOICE=0
T_SUBMIT=%E6%8F%90%E4%BA%A4
submit() {
curl --cookie $COOKIE --cookie-jar $COOKIE "$URL" -X POST -d "text=$T_TEXT&choice=$T_CHOICE&submit=$T_SUBMIT" 2>/dev/null
}
work() {
FORM="$(submit)"
FLAG=$(echo "$FORM" | grep -oE "flag\{[^{}]+\}")
if [ $? -eq 0 ]; then
echo "$FLAG"
exit 0
fi
KEY=$(echo "$FORM" | sed -n "${LINE}p" | grep -oE "[0-9a-f]{32}")
HASH=$(:|hashpump -k 32 -s "$KEY" -d "" -a "a" | grep -oE "[0-9a-f]{32}")
HASHNUM=${HASH:31:1}
case $HASHNUM in
0|2|4|6|8|a|c|e) T_CHOICE=0;;
1|3|5|7|9|b|d|f) T_CHOICE=1;;
*) :;;
esac
}
while :; do work; done
运行一会后就输出了flag:
flag{8fc66cd05d6bc2bc251182e08fefbfc9}
CancerGary 17级 校外同学
MD5原理 利用分块计算的特性构造特殊的字符串使得已知MD5参与下一块计算
代码:
(md5原版实现搬运自https://github.com/thereal1024/python-md5-collision/blob/master/md5.py)
#!/usr/bin/env python3
"""An implementation of MD5 that exposes internals and is directly built up
from mathematical primitives from the MD5 specification.
It achieves about 500KB/s, or 1/1000x of GNU md5sum.
Thus, this is not an implementation great for larges amounts of hashing.
Instead, the point is access to internals."""
__date__ = '2015-07-02'
__version__ = 0.8
import math
import binascii
# util
bin_to_words = lambda x: [x[4 * i:4 * (i + 1)] for i in range(len(x) // 4)]
words_to_bin = lambda x: b''.join(x)
word_to_int = lambda x: int.from_bytes(x, 'little')
int_to_word = lambda x: x.to_bytes(4, 'little')
bin_to_int = lambda x: list(map(word_to_int, bin_to_words(x)))
int_to_bin = lambda x: words_to_bin(map(int_to_word, x))
mod32bit = lambda x: x % 2 ** 32
rotleft = lambda x, n: (x << n) | (x >> (32 - n))
# initial state
IHV0_HEX = '0123456789abcdeffedcba9876543210'
IHV0 = bin_to_int(binascii.unhexlify(IHV0_HEX.encode()))
# parameters
BLOCK_SIZE = 64 # 512 bits (64 bytes)
ROUNDS = BLOCK_SIZE
# addition constants
AC = [int(2 ** 32 * abs(math.sin(t + 1))) for t in range(ROUNDS)]
# rotation constants
RC = [7, 12, 17, 22] * 4 + [5, 9, 14, 20] * 4 + [4, 11, 16, 23] * 4 + [6, 10, 15, 21] * 4
# non-linear functions
F = lambda x, y, z: (x & y) ^ (~x & z)
G = lambda x, y, z: (z & x) ^ (~z & y)
H = lambda x, y, z: x ^ y ^ z
I = lambda x, y, z: y ^ (x | ~z)
Fx = [F] * 16 + [G] * 16 + [H] * 16 + [I] * 16
# data selection
M1 = lambda t: t
M2 = lambda t: (1 + 5 * t) % 16
M3 = lambda t: (5 + 3 * t) % 16
M4 = lambda t: (7 * t) % 16
Mx = [M1] * 16 + [M2] * 16 + [M3] * 16 + [M4] * 16
Wx = [mxi(i) for i, mxi in enumerate(Mx)]
# iterations and function composition
RoundQNext = lambda w, q, i: mod32bit(
q[0] + rotleft(mod32bit(Fx[i](q[0], q[1], q[2]) + q[3] + AC[i] + w[Wx[i]]), RC[i]))
DoRounds = lambda w, q, i: DoRounds(w, [RoundQNext(w, q, i)] + q[:3], i + 1) if (i < ROUNDS) else q
MD5CompressionInt = lambda ihvs, b: [mod32bit(ihvsi + qi) for ihvsi, qi in zip(ihvs, DoRounds(bin_to_int(b), ihvs, 0))]
arrSh = lambda x: [x[1], x[2], x[3], x[0]]
arrUs = lambda x: [x[3], x[0], x[1], x[2]]
MD5Compression = lambda ihv, b: arrUs(MD5CompressionInt(arrSh(ihv), b))
class MD5:
"""Implementation of MD5
Expected outputs:
>>> MD5(b'').hexdigest()
'd41d8cd98f00b204e9800998ecf8427e'
>>> MD5(b'a').hexdigest()
'0cc175b9c0f1b6a831c399e269772661'
>>> MD5(b'abc').hexdigest()
'900150983cd24fb0d6963f7d28e17f72'
>>> MD5(b'message digest').hexdigest()
'f96b697d7cb7938d525a2f31aaf161d0'
>>> MD5(b'abcdefghijklmnopqrstuvwxyz').hexdigest()
'c3fcd3d76192e4007dfb496cca67e13b'
>>> MD5(b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789').hexdigest()
'd174ab98d277d9f5a5611c2c9f419d9f'
>>> MD5(b'12345678901234567890123456789012345678901234567890123456789012345678901234567890').hexdigest()
'57edf4a22be3c955ac49da2e2107b67a'
"""
def __init__(self, data=None,IHV0_HEX="0123456789abcdeffedcba9876543210"):
self._ihv = bin_to_int(binascii.unhexlify(IHV0_HEX.encode()))
self.bits = 0
self.buf = b''
if data:
self.update(data)
def update(self, data):
self.bits += len(data) * 8
self.buf += data
while len(self.buf) >= BLOCK_SIZE:
to_compress, self.buf = self.buf[:BLOCK_SIZE], self.buf[BLOCK_SIZE:]
self._ihv = MD5Compression(self._ihv, to_compress)
def set_ihv(self,str):
self._ihv = bin_to_int(binascii.unhexlify(str[:32].encode()))
def digest(self):
# total reseved bytes
total_bytes = (self.bits // 8)
# we deduct 1 extra byte for the 1 bit from the zero pading length
zerolen = (56 - (total_bytes + 1)) % 64
pad = bytes([0x80] + [0] * zerolen) + (total_bytes * 8).to_bytes(8, 'little')
temp = MD5()
temp._ihv = self._ihv
temp.update(self.buf + pad)
digest_value = temp._ihv
return int_to_bin(digest_value)
def digest2(self):
# total reseved bytes
# we deduct 1 extra byte for the 1 bit from the zero pading length
zerolen = (56 - ((self.bits // 8) + 1)) % 64
#pad = bytes([0x80] + [0] * zerolen) + (total_bytes * 8).to_bytes(8, 'little')
pad=b''
temp = MD5()
temp._ihv = self._ihv
temp.update(self.buf + pad)
digest_value = temp._ihv
return int_to_bin(digest_value)
def hexdigest(self):
return binascii.hexlify(self.digest()).decode()
def hexdigest2(self):
return binascii.hexlify(self.digest2()).decode()
def ihv(self):
return int_to_bin(self._ihv)
def hexihv(self):
"""Get the current IHV in hex
>>> MD5().hexihv() == IHV0_HEX
True
>>> MD5(b'test').hexihv() == IHV0_HEX
True
>>> MD5(b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!?').hexihv()
'9d39fa2529070110ab7f132e7a9cacf3'
"""
return binascii.hexlify(self.ihv()).decode()
if __name__ == '__main__':
import requests,bs4,re
s=requests.Session()
for i in range(0,30):
res=s.get("http://hack.lug.ustc.edu.cn/dynamic/4/")
soup=bs4.BeautifulSoup(res.text,'lxml')
code=soup.find_all("code")[-1].text
# origin_code=b'GoCjI2uWceMlf7BJKEbAF1GHPl86s8j9'
# code=MD5(origin_code).hexdigest()
# print(code)
q1 = b'\x80' + b'\x00' * 23 + (32 * 8).to_bytes(8, 'little')
q2= b'\x80'+b'\x00' * 55+(64 * 8).to_bytes(8, 'little')
# m1 = MD5(origin_code + q1)
# m1r=m1.hexdigest2()
# print(m1r)
#
# mm=MD5(origin_code+q1)
# print(mm.hexdigest())
m2=MD5(q2,code)
m2r=m2.hexdigest2()
# print(m2r)
if (m2r[-1].isalpha()):
choice=(ord(m2r[-1])-97)%2
else:
choice=int(m2r[-1])%2
ans_res=s.post("http://hack.lug.ustc.edu.cn/dynamic/4/",data={"text":q1,"choice":choice,"submit":"提交"})
print(re.search('Combo:.*?<',ans_res.text))
res=s.get("http://hack.lug.ustc.edu.cn/dynamic/4/")
print(res.text)