被魔改 md5 加密坑了?某网站魔改 md5 加密逆向还原 (多种语言还原)

这是「进击的Coder」的第 806 篇技术分享

作者:TheWeiJun

来源:逆向与爬虫的故事

阅读本文大概需要 13 分钟。

大家好,我是 TheWeiJun;最近由于工作太忙好久没有更新了。静下心来,突然很想念各位读者朋友,所以晚上抽空更新一篇。今天分享一篇关于魔改 md5 实现的加密算法逆向分析,本文将用多种语言还原加密算法,解决不同语言还原加密算法的难题。希望各位朋友能够多多提出宝贵意见,在阅读的同时记得给我一个 star!

特别声明:本公众号文章只作为学术研究,不作为其它不法用途;如有侵权请联系作者删除。

5eeb3030d6ccc972409e7cf208dce3fd.gif

立即加星标

43ec315b2260ba53b01bdbeb32f7f383.png

每天看好文

 目录


一、前言介绍

二、参数分析

三、堆栈调试

四、算法分析

五、思路总结


趣味模块

小军是一名工程师,最近小军遇到了一个棘手的问题:小军想要还原一个加密算法,他不想和往常一样通过 Python 调用 JS 的方式去实现算法还原;而是选择通过 Python、Go、Java 语言去实现算法还原。这篇文章中,我们将解决小军遇到的困境,让我们一起去看小军遇到的难题并通过多种语言去实现算法还原吧!


一、前言介绍

1、什么是 md5 加密?

MD5 消息摘要算法 (MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个 128 位(16 字节)的散列值(hash value),用于确保信息传输完整一致。MD5 加密是一种不可逆的加密算法,不可逆加密算法的特征是加密过程中不需要使用密钥,输入明文后由系统直接经过加密算法处理成密文,这种加密后的数据是无法被解密的,只有重新输入明文,并再次经过同样不可逆的加密算法处理,得到相同的加密密文并被系统重新识别后,才能真正解密。

2、md5 是如何加密的?

MD5 算法的原理可简要的叙述为:MD5 码以 512 位分组来处理输入的信息,且每一分组又被划分为 16 个 32 位子分组,经过了一系列的处理后,算法的输出由四个 32 位分组组成,将这四个 32 位分组级联后将生成一个 128 位散列值。

总体流程如下图所示,每次的运算都由前一轮的 128 位结果值和当前的 512bit 值进行运算 。

51102537a2f70884a352979ba21f8bfc.png

了解了 md5 加密后,接下来我们去实战中分析 md5 是如何实现魔改并进行加密运算的。


二、参数分析

1、首先打开我们今天要模拟的网站,刷新当前页面,使用 fn+F12 打开开发者界面,直接定位我们要获取的接口,截图如下所示:

29c7751fffc5cc074c8c828471618947.png

2、我们确定好获取的接口后,点击payload查看该请求参数,截图如下所示:

563244140c9160f57610cd1fab5b526d.png

3、标红的参数就是我们本次要还原的加密参数,接下来,我们对该接口各个参数进行初判断及整理分析:

Data 参数分析:

  • formDataSign     初步怀疑是 md5 加密,长度 32 位

  • formDataStr       搜索的关键字

  • jsVer                   JS 

    发版时间

  • timestamp          当前时间戳,长度 10 位

Headers 参数分析:

说明:由于 headers 参数没有重要参数影响,故不作说明。

ormData


三、断点调试

1、使用最简单的方式,查询指定关键字、加密方法,定位加密参数具体坐标文件,截图如下:

7d99bb747f0a38c6059596e804eaf87e.png

说明:经过查询,我们可以肯定的是代码中没有用到这个变量名,然后我们去搜索加密方法,发现能搜到结果,但是和我们的加密参数关联不大,截图如下:

34d6b60fe010020d73a54ec841931834.png

2、接下来,我们还是使用 XHR 打断点,回溯堆栈的方式查找吧,截图如下:

9ff8251a9e104f67e7503f0f25629aa5.png

3、然后刷新当前页面,进行堆栈查找,截图如下:

798965cda87f9866cfd03fcab116c8e4.png

总结:很明显此刻加密参数已经生成,我们需要定位参数生成的位置,就需要学会查看堆栈信息,接下里进行堆栈回溯。

4、通过 Call Stack 进行堆栈回溯,截图如下所示:

472512304a393cdc2623c65f92ddde95.png

5、由于堆栈回溯流程环节较多,我们直接快进定位到加密参数位置,截图如下:

f96ebb633e6b76ffc030387b6702483d.png

说明:此刻我们可以看到 t 参数为 timetamp 参数拼接 salt 参数,然后进行下面参数运行即可得到第一次加密的密文,截图如下所示:

eb7b86ae9fb2c011b1050f32a72278cd.png

总结:此刻我们验证下前面的猜想:是否是 md5 加密,将明文信息粘贴到 md5 在线生成工具中验证,结果和 js 生成的值不匹配,截图如下:

70cf5702d0eafd6c6a18691a32e91a7f.png

6、继续执行断点,我们可以看到第二次加密运行截图如下图所示:

9b867c1911bf42d98c5c2e1640c8978f.png

总结:此刻我们可以看到第二次加密运行的入参为:formDataStr 拼接刚刚加密运行得到密文的 32 位字符串。继续执行断点,截图如下图所示:

dcdcc70f14bd3a169ac916b601afbe4b.png

7、将 JS 断点调试生成的最终加密值,与xhr请求时发送的 formDataSign 加密值对比,截图如下:

12164846f1af19988be3f641023d3c0f.png

总结:我们可以看到 formDataSign 的值是经过两轮 js 自定义魔改算法而生成的,接下来我们通过还原 js 加密算法去验证该网站是否使用的魔改 md5。


四、算法还原

1、先将本次分析的 js 代码抠出来使用 Nodejs 运行,去掉一些无用代码后,完整代码如下:

function encrypt(e) {
    function h(a, b) {
        var c, d, e, f, g;
        e = a & 2147483648;
        f = b & 2147483648;
        c = a & 1073741824;
        d = b & 1073741824;
        g = (a & 1073741823) + (b & 1073741823);
        return c & d ? g ^ 2147483648 ^ e ^ f : c | d ? g & 1073741824 ? g ^ 3221225472 ^ e ^ f : g ^ 1073741824 ^ e ^ f : g ^ e ^ f
    }


    function k(a, b, c, d, e, f, g) {
        a = h(a, h(h(b & c | ~b & d, e), g));
        return h(a << f | a >>> 32 - f, b)
    }


    function l(a, b, c, d, e, f, g) {
        a = h(a, h(h(b & d | c & ~d, e), g));
        return h(a << f | a >>> 32 - f, b)
    }


    function m(a, b, d, c, e, f, g) {
        a = h(a, h(h(b ^ d ^ c, e), g));
        return h(a << f | a >>> 32 - f, b)
    }


    function n(a, b, d, c, e, f, g) {
        a = h(a, h(h(d ^ (b | ~c), e), g));
        return h(a << f | a >>> 32 - f, b)
    }


    function p(a) {
        var b = "",
            d = "",
            c;
        for (c = 0; 3 >= c; c++) d = a >>> 8 * c & 255, d = "0" + d.toString(16), b += d.substr(d.length - 2, 2);
        return b
    }


    var f = [],
        q, r, s, t, a, b, c, d;
    e = function (a) {
        a = a.replace(/\\r\\n/g, "\\n");
        for (var b = "", d = 0; d < a.length; d++) {
            var c = a.charCodeAt(d);
            128 > c ? b += String.fromCharCode(c) : (127 < c && 2048 > c ? b += String.fromCharCode(c >> 6 | 192) : (b += String.fromCharCode(c >> 12 | 224), b += String.fromCharCode(c >> 6 & 63 | 128)), b += String.fromCharCode(c & 63 | 128))
        }
        return b
    }(e);
    f = function (b) {
        var a, c = b.length;
        a = c + 8;
        for (var d = 16 * ((a - a % 64) / 64 + 1), e = Array(d - 1), f = 0, g = 0; g < c;) a = (g - g % 4) / 4, f = g % 4 * 8, e[a] |= b.charCodeAt(g) << f, g++;
        a = (g - g % 4) / 4;
        e[a] |= 128 << g % 4 * 8;
        e[d - 2] = c << 3;
        e[d - 1] = c >>> 29;
        return e
    }(e);
    a = 271733878;
    b = 2562383102;
    c = 4023233417;
    d = 1732584193;
    for (e = 0; e < f.length; e += 16) q = a, r = b, s = c, t = d, a = k(a, b, c, d, f[e + 0], 7, 3614090360), d = k(d, a, b, c, f[e + 1], 12, 3905402710), c = k(c, d, a, b, f[e + 2], 17, 606105819), b = k(b, c, d, a, f[e + 3], 22, 3250441966), a = k(a, b, c, d, f[e + 4], 7, 4118548399), d = k(d, a, b, c, f[e + 5], 12, 1200080426), c = k(c, d, a, b, f[e + 6], 17, 2821735955), b = k(b, c, d, a, f[e + 7], 22, 4249261313), a = k(a, b, c, d, f[e + 8], 7, 1770035416), d = k(d, a, b, c, f[e + 9], 12, 2336552879), c = k(c, d, a, b, f[e + 10], 17, 4294925233), b = k(b, c, d, a, f[e + 11], 22, 2304563134), a = k(a, b, c, d, f[e + 12], 7, 1804603682), d = k(d, a, b, c, f[e + 13], 12, 4254626195), c = k(c, d, a, b, f[e + 14], 17, 2792965006), b = k(b, c, d, a, f[e + 15], 22, 1236535329), a = l(a, b, c, d, f[e + 1], 5, 4129170786), d = l(d, a, b, c, f[e + 6], 9, 3225465664), c = l(c, d, a, b, f[e + 11], 14, 643717713), b = l(b, c, d, a, f[e + 0], 20, 3921069994), a = l(a, b, c, d, f[e + 5], 5, 3593408605), d = l(d, a, b, c, f[e + 10], 9, 38016083), c = l(c, d, a, b, f[e + 15], 14, 3634488961), b = l(b, c, d, a, f[e + 4], 20, 3889429448), a = l(a, b, c, d, f[e + 9], 5, 568446438), d = l(d, a, b, c, f[e + 14], 9, 3275163606), c = l(c, d, a, b, f[e + 3], 14, 4107603335), b = l(b, c, d, a, f[e + 8], 20, 1163531501), a = l(a, b, c, d, f[e + 13], 5, 2850285829), d = l(d, a, b, c, f[e + 2], 9, 4243563512), c = l(c, d, a, b, f[e + 7], 14, 1735328473), b = l(b, c, d, a, f[e + 12], 20, 2368359562), a = m(a, b, c, d, f[e + 5], 4, 4294588738), d = m(d, a, b, c, f[e + 8], 11, 2272392833), c = m(c, d, a, b, f[e + 11], 16, 1839030562), b = m(b, c, d, a, f[e + 14], 23, 4259657740), a = m(a, b, c, d, f[e + 1], 4, 2763975236), d = m(d, a, b, c, f[e + 4], 11, 1272893353), c = m(c, d, a, b, f[e + 7], 16, 4139469664), b = m(b, c, d, a, f[e + 10], 23, 3200236656), a = m(a, b, c, d, f[e + 13], 4, 681279174), d = m(d, a, b, c, f[e + 0], 11, 3936430074), c = m(c, d, a, b, f[e + 3], 16, 3572445317), b = m(b, c, d, a, f[e + 6], 23, 76029189), a = m(a, b, c, d, f[e + 9], 4, 3654602809), d = m(d, a, b, c, f[e + 12], 11, 3873151461), c = m(c, d, a, b, f[e + 15], 16, 530742520), b = m(b, c, d, a, f[e + 2], 23, 3299628645), a = n(a, b, c, d, f[e + 0], 6, 4096336452), d = n(d, a, b, c, f[e + 7], 10, 1126891415), c = n(c, d, a, b, f[e + 14], 15, 2878612391), b = n(b, c, d, a, f[e + 5], 21, 4237533241), a = n(a, b, c, d, f[e + 12], 6, 1700485571), d = n(d, a, b, c, f[e + 3], 10, 2399980690), c = n(c, d, a, b, f[e + 10], 15, 4293915773), b = n(b, c, d, a, f[e + 1], 21, 2240044497), a = n(a, b, c, d, f[e + 8], 6, 1873313359), d = n(d, a, b, c, f[e + 15], 10, 4264355552), c = n(c, d, a, b, f[e + 6], 15, 2734768916), b = n(b, c, d, a, f[e + 13], 21, 1309151649), a = n(a, b, c, d, f[e + 4], 6, 4149444226), d = n(d, a, b, c, f[e + 11], 10, 3174756917), c = n(c, d, a, b, f[e + 2], 15, 718787259), b = n(b, c, d, a, f[e + 9], 21, 3951481745), a = h(a, q), b = h(b, r), c = h(c, s), d = h(d, t);
    return (p(a) + p(b) + p(c) + p(d)).toLowerCase()
};
var params = "16774142142710df14fccd4ef73ee9d59451a6c2fb";
var first_md5 = encrypt(params);
var keyword = '{"keyword":"三只羊"}' + first_md5;
var last_md5 = encrypt(keyword);
console.log(last_md5);

1.1 代码运行后截图如下:

356bd5688e4b38603c17d5aa45746ff5.png

总结:通过还原 js 代码,我们已经能够解决小军提到的问题。大家肯定很好奇,为啥我知道该网站使用的算法是魔改 md5 加密算法,很简单的一步操作就是先看常量 (a、b、c、d),再看码表 K。很明显这个地方的 a、b、c、d 四个常量转为 16 进制后,是经过了特殊的魔改而来。接下来让我们用其他语言来实现该算法吧!

2、经过上面的深度分析后,我通过修改 md5 源码实现了一版 Python 魔改的 md5 算法,完整代码如下:

# -*- coding: utf-8 -*-
# -------------------------
# @author : 逆向与爬虫的故事
# -------------------------
def magic_md5(message: str) -> bytes:
    # 定义常量,用于初始化128位变量,注意字节顺序,A=0x01234567,这里低值存放低字节,
    # 即01 23 45 67,所以运算时A=0x67452301,其他类似。
    # 用字符串的形势,是为了和hex函数的输出统一,hex(10)输出为'0xA',注意结果为字符串。
    h0 = 0x10325476
    h1 = 0x98badcfe
    h2 = 0xefcdab89
    h3 = 0x67452301


    # 定义每轮中循环左移的位数,用元组表示 4*4*4=64
    R = (7, 12, 17, 22) * 4 + (5, 9, 14, 20) * 4 + \
        (4, 11, 16, 23) * 4 + (6, 10, 15, 21) * 4
    # 定义常数K 64
    # K[i] = (int(abs(math.sin(i + 1)) * 2 ** 32)) & 0xffffffff
    K = (0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
         0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8,
         0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193,
         0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, 0x265e5a51,
         0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
         0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905,
         0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681,
         0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60,
         0xbebfbc70, 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
         0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244,
         0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92,
         0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314,
         0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391)


    # 定义每轮中用到的函数。L为循环左移,
    # 左移之后可能会超过32位,所以要和0xffffffff做与运算,确保结果为32位。
    F = lambda x, y, z: ((x & y) | ((~x) & z))
    G = lambda x, y, z: ((x & z) | (y & (~z)))
    H = lambda x, y, z: (x ^ y ^ z)
    I = lambda x, y, z: (y ^ (x | (~z)))


    L = lambda x, n: ((x << n) | (x >> (32 - n))) & 0xffffffff
    # 小端  0x12,0x34,0x56,0x78 -> 0x78563412
    # 将四个8位无符号数转化为一个32位无符号数
    W = lambda i4, i3, i2, i1: (i1 << 24) | (i2 << 16) | (i3 << 8) | i4
    # 字节翻转 0x12345678 -> 0x78563412 将一个32位无符号数的高位和低位进行对换
    reverse = lambda x: (x << 24) & 0xff000000 | (x << 8) & 0x00ff0000 | \
                        (x >> 8) & 0x0000ff00 | (x >> 24) & 0x000000ff




    # 对每一个输入先添加一个'0x80',即'10000000', 即128
    ascii_list = list(map(lambda x: x, message.encode()))
    msg_length = len(ascii_list) * 8
    ascii_list.append(128)


    # 补充0
    while (len(ascii_list) * 8 + 64) % 512 != 0:
        ascii_list.append(0)


    # 最后64为存放消息长度,以小端数存放。
    # 例如,消息为'a',则长度是8,则添加'0x0800000000000000'
    for i in range(8):
        ascii_list.append((msg_length >> (8 * i)) & 0xff)


    # print(ascii_list)
    # print(len(ascii_list)//64)
    # 对每一消息块进行迭代
    for i in range(len(ascii_list) // 64):
        # print(ascii_list[i*64:(i+1)*64])
        # 对每一个消息块进行循环,每个消息块512bits=16*32bits=64*8bits
        a, b, c, d = h0, h1, h2, h3
        for j in range(64):
            # 64轮的主循环
            if 0 <= j <= 15:
                f = F(b, c, d) & 0xffffffff
                g = j
            elif 16 <= j <= 31:
                f = G(b, c, d) & 0xffffffff
                g = ((5 * j) + 1) % 16
            elif 32 <= j <= 47:
                f = H(b, c, d) & 0xffffffff
                g = ((3 * j) + 5) % 16
            else:
                f = I(b, c, d) & 0xffffffff
                g = (7 * j) % 16
            aa, dd, cc = d, c, b
            # 第i个chunk,第g个32-bit
            s = i * 64 + g * 4
            w = W(ascii_list[s], ascii_list[s + 1], ascii_list[s + 2], ascii_list[s + 3])
            bb = (L((a + f + K[j] + w) & 0xffffffff, R[j]) + b) & 0xffffffff
            a, b, c, d = aa, bb, cc, dd
            # print(b)
        h0 = (h0 + a) & 0xffffffff
        h1 = (h1 + b) & 0xffffffff
        h2 = (h2 + c) & 0xffffffff
        h3 = (h3 + d) & 0xffffffff
    h0, h1, h2, h3 = reverse(h0), reverse(h1), reverse(h2), reverse(h3)
    digest = (h0 << 96) | (h1 << 64) | (h2 << 32) | h3
    return hex(digest)[2:].rjust(32, '0')

2.1 代码运行后截图如下:

5b32268b5c918146b3c68ff3822aecad.png

3、为了满足小军的需求,我们又实现了一版 Go 语言版本的魔改 md5 算法,完整代码如下:

package md5


import (
  "crypto"
  "encoding/binary"
  "errors"
  "hash"
)


func init() {
  crypto.RegisterHash(crypto.MD5, New)
}


// The size of an MD5 checksum in bytes.
const Size = 16


// The blocksize of MD5 in bytes.
const BlockSize = 64


const (
  init0 = 0x10325476
  init1 = 0x98badcfe
  init2 = 0xefcdab89
  init3 = 0x67452301
)


// digest represents the partial evaluation of a checksum.
type digest struct {
  s   [4]uint32
  x   [BlockSize]byte
  nx  int
  len uint64
}


func (d *digest) Reset() {
  d.s[0] = init0
  d.s[1] = init1
  d.s[2] = init2
  d.s[3] = init3
  d.nx = 0
  d.len = 0
}


const (
  magic         = "md5\x01"
  marshaledSize = len(magic) + 4*4 + BlockSize + 8
)


func (d *digest) MarshalBinary() ([]byte, error) {
  b := make([]byte, 0, marshaledSize)
  b = append(b, magic...)
  b = appendUint32(b, d.s[0])
  b = appendUint32(b, d.s[1])
  b = appendUint32(b, d.s[2])
  b = appendUint32(b, d.s[3])
  b = append(b, d.x[:d.nx]...)
  b = b[:len(b)+len(d.x)-d.nx] // already zero
  b = appendUint64(b, d.len)
  return b, nil
}


func (d *digest) UnmarshalBinary(b []byte) error {
  if len(b) < len(magic) || string(b[:len(magic)]) != magic {
    return errors.New("crypto/md5: invalid hash state identifier")
  }
  if len(b) != marshaledSize {
    return errors.New("crypto/md5: invalid hash state size")
  }
  b = b[len(magic):]
  b, d.s[0] = consumeUint32(b)
  b, d.s[1] = consumeUint32(b)
  b, d.s[2] = consumeUint32(b)
  b, d.s[3] = consumeUint32(b)
  b = b[copy(d.x[:], b):]
  b, d.len = consumeUint64(b)
  d.nx = int(d.len % BlockSize)
  return nil
}


func appendUint64(b []byte, x uint64) []byte {
  var a [8]byte
  binary.BigEndian.PutUint64(a[:], x)
  return append(b, a[:]...)
}


func appendUint32(b []byte, x uint32) []byte {
  var a [4]byte
  binary.BigEndian.PutUint32(a[:], x)
  return append(b, a[:]...)
}


func consumeUint64(b []byte) ([]byte, uint64) {
  return b[8:], binary.BigEndian.Uint64(b[0:8])
}


func consumeUint32(b []byte) ([]byte, uint32) {
  return b[4:], binary.BigEndian.Uint32(b[0:4])
}


// New returns a new hash.Hash computing the MD5 checksum. The Hash also
// implements encoding.BinaryMarshaler and encoding.BinaryUnmarshaler to
// marshal and unmarshal the internal state of the hash.
func New() hash.Hash {
  d := new(digest)
  d.Reset()
  return d
}


func (d *digest) Size() int { return Size }


func (d *digest) BlockSize() int { return BlockSize }


func (d *digest) Write(p []byte) (nn int, err error) {
  // Note that we currently call block or blockGeneric
  // directly (guarded using haveAsm) because this allows
  // escape analysis to see that p and d don't escape.
  nn = len(p)
  d.len += uint64(nn)
  if d.nx > 0 {
    n := copy(d.x[d.nx:], p)
    d.nx += n
    if d.nx == BlockSize {
      if haveAsm {
        block(d, d.x[:])
      } else {
        blockGeneric(d, d.x[:])
      }
      d.nx = 0
    }
    p = p[n:]
  }
  if len(p) >= BlockSize {
    n := len(p) &^ (BlockSize - 1)
    if haveAsm {
      block(d, p[:n])
    } else {
      blockGeneric(d, p[:n])
    }
    p = p[n:]
  }
  if len(p) > 0 {
    d.nx = copy(d.x[:], p)
  }
  return
}


func (d *digest) Sum(in []byte) []byte {
  // Make a copy of d so that caller can keep writing and summing.
  d0 := *d
  hash := d0.checkSum()
  return append(in, hash[:]...)
}


func (d *digest) checkSum() [Size]byte {
  // Append 0x80 to the end of the message and then append zeros
  // until the length is a multiple of 56 bytes. Finally append
  // 8 bytes representing the message length in bits.
  //
  // 1 byte end marker :: 0-63 padding bytes :: 8 byte length
  tmp := [1 + 63 + 8]byte{0x80}
  pad := (55 - d.len) % 64                             // calculate number of padding bytes
  binary.LittleEndian.PutUint64(tmp[1+pad:], d.len<<3) // append length in bits
  d.Write(tmp[:1+pad+8])


  // The previous write ensures that a whole number of
  // blocks (i.e. a multiple of 64 bytes) have been hashed.
  if d.nx != 0 {
    panic("d.nx != 0")
  }


  var digest [Size]byte
  binary.LittleEndian.PutUint32(digest[0:], d.s[0])
  binary.LittleEndian.PutUint32(digest[4:], d.s[1])
  binary.LittleEndian.PutUint32(digest[8:], d.s[2])
  binary.LittleEndian.PutUint32(digest[12:], d.s[3])
  return digest
}


// Sum returns the MD5 checksum of the data.
func Sum(data []byte) [Size]byte {
  var d digest
  d.Reset()
  d.Write(data)
  return d.checkSum()
}

3.1 main 函数完整代码如下:

package main


import (
  "collyx-spider/utils/md5"
  "fmt"
)


func main() {
  firstText := "16774142142710df14fccd4ef73ee9d59451a6c2fb"
  signBytes := md5.Sum([]byte(firstText))
  sign := fmt.Sprintf("%x", signBytes)
  fmt.Println(sign)
  secondText := `{"keyword":"三只羊"}` + sign
  sign2Bytes := md5.Sum([]byte(secondText))
  lastSign := fmt.Sprintf("%x", sign2Bytes)
  fmt.Println(lastSign)
}

3.2 代码运行后,截图如下所示:

550951ddc40044ee715de5f5e6247404.png

总结:观察 Goland 生成的加密值,我们可以确定和前面计算的结果一致,接下来我们再研究下 java 版本魔改 md5 如何实现。

4、作者通过 Java 语言实现的魔改 md5 完整代码如下:

import java.util.Arrays;


public class MagicMd5 {
    private String content;
    private String md5;




    public String md5(String text) {
        content = text;
        this.Reset();
        return md5;
    }


    private void Reset() {
        int[] init = new int[]{0x10325476, 0x98badcfe, 0xefcdab89, 0x67452301};


        byte[] bytes = new byte[64];
        byte[] tail = new byte[0];
        int len = 0;
        long size = 0;
        byte[] src = content.getBytes();
        int n = src.length / 64;
        for (int i = 0; i < n; i++) {
            bytes = Arrays.copyOfRange(src, i * 64, i * 64 + 64);
            init = md5_2(bytes, init);
        }
        size = src.length * 8;
        tail = Arrays.copyOfRange(src, src.length - src.length % 64, src.length);




        if (tail.length < 56) {
            bytes = Arrays.copyOf(tail, 64);
            bytes[tail.length] = -128;
        } else {
            bytes = Arrays.copyOf(tail, 64);
            bytes[tail.length] = -128;
            init = md5_2(bytes, init);
            bytes = Arrays.copyOf(new byte[]{}, 64);
        }
        for (int i = 0; i < 8; i++) {
            bytes[56 + i] = new Long(size >>> i * 8).byteValue();
        }
        init = md5_2(bytes, init);
        md5 = int2string(init[0]) + int2string(init[1]) + int2string(init[2]) + int2string(init[3]);
    }


    public int[] md5_2(byte[] bytes, int[] before) {
        int A = before[0], B = before[1], C = before[2], D = before[3];
        int a = A, b = B, c = C, d = D;
        int s[] = {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};
        int[] k = {0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf,
                0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af,
                0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e,
                0x49b40821, 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
                0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6,
                0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8,
                0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122,
                0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
                0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039,
                0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244, 0x432aff97,
                0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d,
                0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
                0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391};


        for (int i = 0; i < 64; i++) {
            int f, g;
            if (i < 16) {
                f = (b & c) | (~b & d);
                g = i;
            } else if (i < 32) {
                f = (b & d) | (~d & c);
                g = (5 * i + 1) % 16;
            } else if (i < 48) {
                f = b ^ c ^ d;
                g = (3 * i + 5) % 16;
            } else {
                f = c ^ (~d | b);
                g = 7 * i % 16;
            }


            int m = byteArr2Int(Arrays.copyOfRange(bytes, 4 * g, 4 * g + 4));
            int b_temp = b;
            b = b + Integer.rotateLeft(a + f + m + k[i], s[i]);
            a = d;
            d = c;
            c = b_temp;
        }
        A += a;
        B += b;
        C += c;
        D += d;


        int[] res = new int[4];
        res[0] = A;
        res[1] = B;
        res[2] = C;
        res[3] = D;


        return res;
    }


    public String int2string(int n) {
        String res = "";
        for (int i = 0; i < 4; i++) {
            String s = (Integer.toHexString((n >>> (8 * i)) & 0xff));
            if (s.length() < 2) {
                s = "0" + s;
            }
            res += s;
        }
        return res;
    }


    private int byteArr2Int(byte[] bytes) {
        int res = 0;
        for (int i = 3; i >= 0; i--) {
            res = (res << 8);
            res += (int) bytes[i] & 0xff;
        }
        return res;
    }




    @Override
    public String toString() {
        return "MD5 [content=" + content + "]";
    }


    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((content == null) ? 0 : content.hashCode());
        return result;
    }


    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        MagicMd5 other = (MagicMd5) obj;
        if (content == null) {
            if (other.content != null)
                return false;
        } else if (!content.equals(other.content))
            return false;
        return true;
    }


    public static void main(String[] args) {
        // 运行魔改md5加密算法
        MagicMd5 magicmd5 = new MagicMd5();
        String firstSign = magicmd5.md5("16774142142710df14fccd4ef73ee9d59451a6c2fb");
        String keyword = "{\"keyword\":\"三只羊\"}";
        System.out.println(firstSign);
        String lastSign = magicmd5.md5(keyword+firstSign);
        System.out.println(lastSign);
    }


}

4.1 代码实现后,我们将运行后的代码截图如下所示:

8daf9a06979f21e65f5a7efec5b5109e.png

总结:本篇文章到这里,我们已经能够通过 Js、Python、Go、Java 语言去实现魔改 md5 算法还原了,小军遇到的难题我们已经迎刃而解,整篇文章字数有点多,感谢大家耐心观看!


五、思路总结

回顾整个分析流程,本次难点主要概括为以下几点:

  • 如何快速确定位加密参数的位置

  • 堆栈回源如何过滤无用代码

  • Js、Python 还原加密算法实现

  • Go、Java 还原加密算法实现

  • 熟练掌握 Md5 算法及加密运算过程

本篇分享到这里就结束了,欢迎大家关注下期,我们不见不散☀️☀️😊

24666334f2a23821a1734863778eadcc.jpeg

10eb2af1fae30745b7e48c1486c01c59.gif

点分享

ad9e0503a985c568ea22309f355a754a.gif

点收藏

da17c2ed9f7aa246d907926a7d1dc489.gif

点点赞

214cbaaac30dddaf8607d10675654e32.gif

点在看

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值