黑客猜奇偶升级版

付佳伟 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"

Image 1

看起来不错,页面结构固定,但是里面包含了至少三个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

Image 2

很好,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"

Image 3

非常好,和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)

results matching ""

    No results matching ""