火猫直播源调试:简单的JS混淆

继优酷和斗鱼后,这是直播源调试的第三篇文章,简要记录博主获取火猫直播源时的调试过程。

火猫直播以游戏直播为主,没有斗鱼、虎牙那么有名,关注 Dota 2 和 CS:GO 的网友应该比较熟悉,经常直播一些重要赛事。调试初衷也来自 Jell 同学。

火猫直播源的调试过程比较简单,不像斗鱼直播源优酷直播源那样依赖 cookie 或使用各种位运算和 eval 函数加密,只用了 sojson 免费版的变量名混淆和编码。下面开始:

浏览器中调试

火猫直播源 PC 网页版与移动网页版的请求过程一样,为调试方便,本文以直播间“MarsTV官方02”的移动网页版为例,房间号 10519。

1、打开 Chrome 并启动开发者工具,进入直播间。查看“Network”选项卡,找到可播放的流媒体地址是这样的:

https://live-js-hls.huomaotv.cn/live/46mrLV_720p/index.m3u8?t=1589363030&r=517263133681&stream=46mrLV&rid=oubvc2y3v&token=be90bfac216d0373ba157aca3ec41b9e&url=http%3A%2F%2Flive-js-hls.huomaotv.cn%2Flive%2F46mrLV_720p%2Findex.m3u8&from=huomaomobileh5

来自这个请求的响应:

Request URL:https://m.huomao.com/swf/live_data
Request Method:POST
Form Data:cdns=1&streamtype=live&VideoIDS=CdvfGdaXzeGbPhddM1s&from=huomaomobileh5&time=1589363029&token=68366d9aa54d6376bbefb3be2a4ef3e0

返回的数据为 JSON 格式,其中 streamList 即流媒体地址,roomStatus 为直播间状态。

火猫直播真实流媒体地址

2、切换不同直播间测试,发现请求参数中 cdns、streamtype 和 from 固定不变;time 为 10 位时间戳;同一个直播间的 VideoIDS 固定; 每次请求的 token 不同。

cdns: 1
streamtype: live
VideoIDS: CdvfGdaXzeGbPhddM1s
from: huomaomobileh5
time: 1589363029
token: 68366d9aa54d6376bbefb3be2a4ef3e0

3、定位请求发起点

这里火猫直播与其他平台有点不同:直接全局搜索 swf/live_data 或 Streamtype、VideoIDS、token 等参数名都找不到赋值。但搜索 VideoIDS 的值“CdvfGdaXzeGbPhddM1s”时,在直播页的 HTML 源码里找到了定义处,即 stream:

火猫直播VideoIDS参数

同时看到有很多编码过的变量名,解码后的代码是这样的:

var stream = "CdvfGdaXzeGbPhddM1s";
var t;
var image = "https://static.huomao.com/upload/web/images/channel/2020/19/20200507150539o52kYU9D.jpg";
var is_mobile_player = true;
var _0xb483 = ["_decode", "http://www.sojson.com/javascriptobfuscator.html"];
(function(_0xd642x1) {
    _0xd642x1[_0xb483[0]] = _0xb483[1]
})(window);
var __Ox73971 = ["stream", "parse", "roomStatus", "err", "error", "0", "length", "streamList", "default", "list_hls", "type", "HD", "<video poster="", "" preload width="100%" preload="metadata" webkit-playsinline playsinline="true" x5-playsinline x-webkit-airplay="true" id="videolive"   controls  name="media" style="width: 100%;display: block;"><source  src="", "url", ""></video>", "prepend", ".video-box", "init"];

function a() {
    var _0xffa4x2 = {};
    _0xffa4x2[__Ox73971[0x0]] = stream;
    aes[__Ox73971[0x12]](_0xffa4x2, t, function(_0xffa4x3) {
        var _0xffa4x4 = JSON[__Ox73971[0x1]](_0xffa4x3);
        var _0xffa4x5 = _0xffa4x4[__Ox73971[0x2]];
        if (_0xffa4x4[__Ox73971[0x3]] || _0xffa4x4[__Ox73971[0x4]]) {
            return false
        };
        if (_0xffa4x5 == __Ox73971[0x5]) {
            closeRecmmend(cid)
        } else {
            for (var _0xffa4x6 = 0; _0xffa4x6 < _0xffa4x4[__Ox73971[0x7]][__Ox73971[0x6]]; _0xffa4x6++) {
                if (_0xffa4x4[__Ox73971[0x7]][_0xffa4x6][__Ox73971[0x8]]) {
                    var _0xffa4x7 = _0xffa4x4[__Ox73971[0x7]][_0xffa4x6][__Ox73971[0x9]];
                    for (var _0xffa4x6 = 0; _0xffa4x6 < _0xffa4x7[__Ox73971[0x6]]; _0xffa4x6++) {
                        if (_0xffa4x7[_0xffa4x6][__Ox73971[0xa]] == __Ox73971[0xb]) {
                            $(__Ox73971[0x11])[__Ox73971[0x10]](__Ox73971[0xc] + image + __Ox73971[0xd] + _0xffa4x7[_0xffa4x6][__Ox73971[0xe]] + __Ox73971[0xf]);
                            return
                        }
                    }
                }
            }
        }
    })
}

虽然变量名经过混淆,但还是可以看出这段 JS 的作用是在页面里初始化播放器,变量 _0xffa4x3 即上步请求返回的数据,并用 JSON.parse 函数处理后,提取了真实流媒体地址。

火猫直播sea.js加密

火猫直播源码中的video标签

在 _0xffa4x3 前下个断点,单步执行,跳到了 https://m.huomao.com/static/web/js/sea.js,这也是一个混淆和编码了变量名的JS,上面还“贴心”的提示了“var __encode = 'sojson.com'”,加密方法来自sojson。

当然也可先添加一个 XHR Breakpoint 或查看第 2 步请求的 Initiator,能定位到 https://m.huomao.com/static/web//html5/js/jquery-1.8.2.min.js 中的代码块:

return {
    send: function(e, f) {
        var g, h, i = c.xhr();
        c.username ? i.open(c.type, c.url, c.async, c.username, c.password) : i.open(c.type, c.url, c.async);
        if (c.xhrFields) for (h in c.xhrFields) i[h] = c.xhrFields[h];
        c.mimeType && i.overrideMimeType && i.overrideMimeType(c.mimeType),
        !c.crossDomain && !e["X-Requested-With"] && (e["X-Requested-With"] = "XMLHttpRequest");
        try {
            for (h in e) i.setRequestHeader(h, e[h])
        } catch(j) {}
        i.send(c.hasContent && c.data || null),
        d = function(a, e) {
        }
    }
}

其中 c.data 就是我们发送的参数,再查看 send 断点处的调用栈,也可找到 sea.js。

火猫直播请求参数

还原 sea.js

4、sea.js 解码后是这样的:

var __encode = 'sojson.com',
    _a = {},
    _0xb483 = ["_decode", "http://www.sojson.com/javascriptobfuscator.html"];
(function(_0xd642x1) {
    _0xd642x1[_0xb483[0]] = _0xb483[1]
})(_a);
var __Ox7f0a3 = ["length", "AES_ExpandKey: Only key lengths of 16, 24 or 32 bytes allowed!", "slice", "concat", "", "fromCharCode", "hmh5", "initdata", "undefined", "huomaomobileh5", "huomaoh5room", "encode", "POST", "stream", "/CdnVerification/stream_refresh", "parse", "status", "success", "time", "data", "getTime", "url", "ajax", "live", "cid", "province", "country", "/swf/live_data", "log", "删除", "版本号,js会定期弹窗,", "还请支持我们的工作", "sojs", "on.com"];
var aes = (function() {
    var _0x58d6x2;
    var _0x58d6x3;
    var _0x58d6x4;

    function _0x58d6x5() {
        _0x58d6x2 = new Array(256);
        for (var _0x58d6x6 = 0; _0x58d6x6 < 256; _0x58d6x6++) {
            _0x58d6x2[_0x58d6x13[_0x58d6x6]] = _0x58d6x6
        };
        _0x58d6x3 = new Array(16);
        for (var _0x58d6x6 = 0; _0x58d6x6 < 16; _0x58d6x6++) {
            _0x58d6x3[_0x58d6x14[_0x58d6x6]] = _0x58d6x6
        };
        _0x58d6x4 = new Array(256);
        for (var _0x58d6x6 = 0; _0x58d6x6 < 128; _0x58d6x6++) {
            _0x58d6x4[_0x58d6x6] = _0x58d6x6 << 1;
            _0x58d6x4[128 + _0x58d6x6] = (_0x58d6x6 << 1) ^ 0x1b
        }
    }

    function _0x58d6x7() {
        delete _0x58d6x2;
        delete _0x58d6x3;
        delete _0x58d6x4
    }

    function _0x58d6x8(_0x58d6x9) {
        var _0x58d6xa = _0x58d6x9[__Ox7f0a3[0x0]],
            _0x58d6xb, _0x58d6xc = 1;
        switch (_0x58d6xa) {
            case 16:
                _0x58d6xb = 16 * (10 + 1);
                break;
            case 24:
                _0x58d6xb = 16 * (12 + 1);
                break;
            case 32:
                _0x58d6xb = 16 * (14 + 1);
                break;
            default:
                alert(__Ox7f0a3[0x1])
        };
        for (var _0x58d6x6 = _0x58d6xa; _0x58d6x6 < _0x58d6xb; _0x58d6x6 += 4) {
            var _0x58d6xd = _0x58d6x9[__Ox7f0a3[0x2]](_0x58d6x6 - 4, _0x58d6x6);
            if (_0x58d6x6 % _0x58d6xa == 0) {
                _0x58d6xd = new Array(_0x58d6x13[_0x58d6xd[0x1]] ^ _0x58d6xc, _0x58d6x13[_0x58d6xd[0x2]], _0x58d6x13[_0x58d6xd[0x3]], _0x58d6x13[_0x58d6xd[0x0]]);
                if ((_0x58d6xc <<= 1) >= 256) {
                    _0x58d6xc ^= 0x11b
                }
            } else {
                if ((_0x58d6xa > 24) && (_0x58d6x6 % _0x58d6xa == 16)) {
                    _0x58d6xd = new Array(_0x58d6x13[_0x58d6xd[0x0]], _0x58d6x13[_0x58d6xd[0x1]], _0x58d6x13[_0x58d6xd[0x2]], _0x58d6x13[_0x58d6xd[0x3]])
                }
            };
            for (var _0x58d6xe = 0; _0x58d6xe < 4; _0x58d6xe++) {
                _0x58d6x9[_0x58d6x6 + _0x58d6xe] = _0x58d6x9[_0x58d6x6 + _0x58d6xe - _0x58d6xa] ^ _0x58d6xd[_0x58d6xe]
            }
        }
    }

    function _0x58d6xf(_0x58d6x10, _0x58d6x9) {
        var _0x58d6x11 = _0x58d6x9[__Ox7f0a3[0x0]];
        _0x58d6x18(_0x58d6x10, _0x58d6x9[__Ox7f0a3[0x2]](0, 16));
        for (var _0x58d6x6 = 16; _0x58d6x6 < _0x58d6x11 - 16; _0x58d6x6 += 16) {
            _0x58d6x15(_0x58d6x10, _0x58d6x13);
            _0x58d6x1a(_0x58d6x10, _0x58d6x14);
            _0x58d6x1d(_0x58d6x10);
            _0x58d6x18(_0x58d6x10, _0x58d6x9[__Ox7f0a3[0x2]](_0x58d6x6, _0x58d6x6 + 16))
        };
        _0x58d6x15(_0x58d6x10, _0x58d6x13);
        _0x58d6x1a(_0x58d6x10, _0x58d6x14);
        _0x58d6x18(_0x58d6x10, _0x58d6x9[__Ox7f0a3[0x2]](_0x58d6x6, _0x58d6x11))
    }

    function _0x58d6x12(_0x58d6x10, _0x58d6x9) {
        var _0x58d6x11 = _0x58d6x9[__Ox7f0a3[0x0]];
        _0x58d6x18(_0x58d6x10, _0x58d6x9[__Ox7f0a3[0x2]](_0x58d6x11 - 16, _0x58d6x11));
        _0x58d6x1a(_0x58d6x10, _0x58d6x3);
        _0x58d6x15(_0x58d6x10, _0x58d6x2);
        for (var _0x58d6x6 = _0x58d6x11 - 32; _0x58d6x6 >= 16; _0x58d6x6 -= 16) {
            _0x58d6x18(_0x58d6x10, _0x58d6x9[__Ox7f0a3[0x2]](_0x58d6x6, _0x58d6x6 + 16));
            _0x58d6x22(_0x58d6x10);
            _0x58d6x1a(_0x58d6x10, _0x58d6x3);
            _0x58d6x15(_0x58d6x10, _0x58d6x2)
        };
        _0x58d6x18(_0x58d6x10, _0x58d6x9[__Ox7f0a3[0x2]](0, 16))
    }
    var _0x58d6x13 = new Array(99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171, 118, 202, 130, 201, 125, 250, 89, 71, 240, 173, 212, 162, 175, 156, 164, 114, 192, 183, 253, 147, 38, 54, 63, 247, 204, 52, 165, 229, 241, 113, 216, 49, 21, 4, 199, 35, 195, 24, 150, 5, 154, 7, 18, 128, 226, 235, 39, 178, 117, 9, 131, 44, 26, 27, 110, 90, 160, 82, 59, 214, 179, 41, 227, 47, 132, 83, 209, 0, 237, 32, 252, 177, 91, 106, 203, 190, 57, 74, 76, 88, 207, 208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, 81, 163, 64, 143, 146, 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, 205, 12, 19, 236, 95, 151, 68, 23, 196, 167, 126, 61, 100, 93, 25, 115, 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94, 11, 219, 224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, 231, 200, 55, 109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174, 8, 186, 120, 37, 46, 28, 166, 180, 198, 232, 221, 116, 31, 75, 189, 139, 138, 112, 62, 181, 102, 72, 3, 246, 14, 97, 53, 87, 185, 134, 193, 29, 158, 225, 248, 152, 17, 105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223, 140, 161, 137, 13, 191, 230, 66, 104, 65, 153, 45, 15, 176, 84, 187, 22);
    var _0x58d6x14 = new Array(0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, 1, 6, 11);

    function _0x58d6x15(_0x58d6x16, _0x58d6x17) {
        for (var _0x58d6x6 = 0; _0x58d6x6 < 16; _0x58d6x6++) {
            _0x58d6x16[_0x58d6x6] = _0x58d6x17[_0x58d6x16[_0x58d6x6]]
        }
    }

    function _0x58d6x18(_0x58d6x16, _0x58d6x19) {
        for (var _0x58d6x6 = 0; _0x58d6x6 < 16; _0x58d6x6++) {
            _0x58d6x16[_0x58d6x6] ^= _0x58d6x19[_0x58d6x6]
        }
    }

    function _0x58d6x1a(_0x58d6x16, _0x58d6x1b) {
        var _0x58d6x1c = new Array()[__Ox7f0a3[0x3]](_0x58d6x16);
        for (var _0x58d6x6 = 0; _0x58d6x6 < 16; _0x58d6x6++) {
            _0x58d6x16[_0x58d6x6] = _0x58d6x1c[_0x58d6x1b[_0x58d6x6]]
        }
    }

    function _0x58d6x1d(_0x58d6x16) {
        for (var _0x58d6x6 = 0; _0x58d6x6 < 16; _0x58d6x6 += 4) {
            var _0x58d6x1e = _0x58d6x16[_0x58d6x6 + 0],
                _0x58d6x1f = _0x58d6x16[_0x58d6x6 + 1];
            var _0x58d6x20 = _0x58d6x16[_0x58d6x6 + 2],
                _0x58d6x21 = _0x58d6x16[_0x58d6x6 + 3];
            var _0x58d6x1c = _0x58d6x1e ^ _0x58d6x1f ^ _0x58d6x20 ^ _0x58d6x21;
            _0x58d6x16[_0x58d6x6 + 0] ^= _0x58d6x1c ^ _0x58d6x4[_0x58d6x1e ^ _0x58d6x1f];
            _0x58d6x16[_0x58d6x6 + 1] ^= _0x58d6x1c ^ _0x58d6x4[_0x58d6x1f ^ _0x58d6x20];
            _0x58d6x16[_0x58d6x6 + 2] ^= _0x58d6x1c ^ _0x58d6x4[_0x58d6x20 ^ _0x58d6x21];
            _0x58d6x16[_0x58d6x6 + 3] ^= _0x58d6x1c ^ _0x58d6x4[_0x58d6x21 ^ _0x58d6x1e]
        }
    }

    function _0x58d6x22(_0x58d6x16) {
        for (var _0x58d6x6 = 0; _0x58d6x6 < 16; _0x58d6x6 += 4) {
            var _0x58d6x1e = _0x58d6x16[_0x58d6x6 + 0],
                _0x58d6x1f = _0x58d6x16[_0x58d6x6 + 1];
            var _0x58d6x20 = _0x58d6x16[_0x58d6x6 + 2],
                _0x58d6x21 = _0x58d6x16[_0x58d6x6 + 3];
            var _0x58d6x1c = _0x58d6x1e ^ _0x58d6x1f ^ _0x58d6x20 ^ _0x58d6x21;
            var _0x58d6x23 = _0x58d6x4[_0x58d6x1c];
            var _0x58d6x24 = _0x58d6x4[_0x58d6x4[_0x58d6x23 ^ _0x58d6x1e ^ _0x58d6x20]] ^ _0x58d6x1c;
            var _0x58d6x25 = _0x58d6x4[_0x58d6x4[_0x58d6x23 ^ _0x58d6x1f ^ _0x58d6x21]] ^ _0x58d6x1c;
            _0x58d6x16[_0x58d6x6 + 0] ^= _0x58d6x24 ^ _0x58d6x4[_0x58d6x1e ^ _0x58d6x1f];
            _0x58d6x16[_0x58d6x6 + 1] ^= _0x58d6x25 ^ _0x58d6x4[_0x58d6x1f ^ _0x58d6x20];
            _0x58d6x16[_0x58d6x6 + 2] ^= _0x58d6x24 ^ _0x58d6x4[_0x58d6x20 ^ _0x58d6x21];
            _0x58d6x16[_0x58d6x6 + 3] ^= _0x58d6x25 ^ _0x58d6x4[_0x58d6x21 ^ _0x58d6x1e]
        }
    }
    var _0x58d6x26 = new Array(32);
    var _0x58d6x27 = [];

    function _0x58d6x28() {
        var _0x58d6x29 = [232, 48, 164, 196, 9, 249, 30, 45, 33, 85, 62, 235, 143, 78, 88, 15, 57, 48, 69, 50, 52, 51, 69, 66, 49, 65, 70, 55, 51, 54, 56, 53];
        _0x58d6x5();
        _0x58d6x26[__Ox7f0a3[0x0]] = 32;
        for (var _0x58d6x6 = 0; _0x58d6x6 < 32; _0x58d6x6++) {
            _0x58d6x26[_0x58d6x6] = _0x58d6x6
        };
        _0x58d6x8(_0x58d6x26);
        _0x58d6xf(_0x58d6x29, _0x58d6x26);
        _0x58d6x27 = _0x58d6x29
    }
    var _0x58d6x2a = __Ox7f0a3[0x4];

    function _0x58d6x2b() {
        if (!_0x58d6x2e) {
            _0x58d6x2e = true;
            var _0x58d6x2c = _0x58d6x27;
            _0x58d6x12(_0x58d6x2c, _0x58d6x26);
            _0x58d6x12(_0x58d6x2c, _0x58d6x26);
            var _0x58d6x2d = __Ox7f0a3[0x4];
            for (var _0x58d6x6 = 0; _0x58d6x6 < _0x58d6x2c[__Ox7f0a3[0x0]]; _0x58d6x6++) {
                _0x58d6x2d += String[__Ox7f0a3[0x5]](_0x58d6x2c[_0x58d6x6])
            };
            _0x58d6x2a = _0x58d6x2d
        };
        _0x58d6x2f();
        return _0x58d6x2a
    }
    var _0x58d6x2e = false;

    function _0x58d6x2f() {
        _0x58d6x7()
    }
    _0x58d6x28();

    function _0x58d6x30(_0x58d6x31, _0x58d6x32, _0x58d6x33) {
        if (window[__Ox7f0a3[0x6]] && window[__Ox7f0a3[0x6]][__Ox7f0a3[0x7]]) {
            var _0x58d6x34 = __Ox7f0a3[0x4];
            if (typeof is_mobile_player != __Ox7f0a3[0x8]) {
                _0x58d6x34 = __Ox7f0a3[0x9]
            } else {
                _0x58d6x34 = __Ox7f0a3[0xa]
            };
            _0x58d6x31 = Base64[__Ox7f0a3[0xb]](_0x58d6x31);
            $[__Ox7f0a3[0x16]]({
                type: __Ox7f0a3[0xc],
                data: {
                    'VideoIDS': window[__Ox7f0a3[0x6]][__Ox7f0a3[0x7]][__Ox7f0a3[0xd]],
                    'url': _0x58d6x31,
                    'time': _0x58d6x32,
                    'from': _0x58d6x34,
                    'token': md5(window[__Ox7f0a3[0x6]][__Ox7f0a3[0x7]][__Ox7f0a3[0xd]] + _0x58d6x34 + _0x58d6x31 + _0x58d6x32 + _0x58d6x2b())
                },
                url: __Ox7f0a3[0xe],
                success: function(_0x58d6x35) {
                    var _0x58d6x36;
                    try {
                        _0x58d6x36 = JSON[__Ox7f0a3[0xf]](_0x58d6x35)
                    } catch (error) {};
                    if (_0x58d6x36) {
                        if (_0x58d6x36[__Ox7f0a3[0x10]] == __Ox7f0a3[0x11]) {
                            _0x58d6x33(_0x58d6x36[__Ox7f0a3[0x13]][__Ox7f0a3[0x12]], new Date()[__Ox7f0a3[0x14]](), _0x58d6x36[__Ox7f0a3[0x13]][__Ox7f0a3[0x15]])
                        }
                    }
                }
            })
        }
    }

    function _0x58d6x37(_0x58d6x38, _0x58d6x32, _0x58d6x33) {
        var _0x58d6x39 = __Ox7f0a3[0x4];
        if (typeof is_mobile_player != __Ox7f0a3[0x8]) {
            _0x58d6x39 = __Ox7f0a3[0x9]
        } else {
            _0x58d6x39 = __Ox7f0a3[0xa]
        };
        var _0x58d6x3a = md5(_0x58d6x38[__Ox7f0a3[0xd]] + _0x58d6x39 + _0x58d6x32 + _0x58d6x2b());
        $[__Ox7f0a3[0x16]]({
            type: __Ox7f0a3[0xc],
            data: {
                'cdns': 1,
                'streamtype': __Ox7f0a3[0x17],
                'cid': _0x58d6x38[__Ox7f0a3[0x18]],
                'VideoIDS': _0x58d6x38[__Ox7f0a3[0xd]],
                'district': _0x58d6x38[__Ox7f0a3[0x19]],
                'country': _0x58d6x38[__Ox7f0a3[0x1a]],
                'from': _0x58d6x39,
                'time': _0x58d6x32,
                'token': _0x58d6x3a
            },
            url: __Ox7f0a3[0x1b],
            success: function(_0x58d6x35) {
                _0x58d6x33(_0x58d6x35)
            }
        })
    }
    return {
        "refresh": _0x58d6x30,
        "init": _0x58d6x37
    }
})();;;
(function(_0x58d6x3b, _0x58d6x3c, _0x58d6x3d, _0x58d6x3e, _0x58d6x3f, _0x58d6x40) {
    _0x58d6x40 = __Ox7f0a3[0x8];
    _0x58d6x3e = function(_0x58d6x41) {
        if (typeof alert !== _0x58d6x40) {
            alert(_0x58d6x41)
        };
        if (typeof console !== _0x58d6x40) {
            console[__Ox7f0a3[0x1c]](_0x58d6x41)
        }
    };
    _0x58d6x3d = function(_0x58d6x42, _0x58d6x3b) {
        return _0x58d6x42 + _0x58d6x3b
    };
    _0x58d6x3f = _0x58d6x3d(__Ox7f0a3[0x1d], _0x58d6x3d(__Ox7f0a3[0x1e], __Ox7f0a3[0x1f]));
    try {
        _0x58d6x3b = __encode;
        if (!(typeof _0x58d6x3b !== _0x58d6x40 && _0x58d6x3b === _0x58d6x3d(__Ox7f0a3[0x20], __Ox7f0a3[0x21]))) {
            _0x58d6x3e(_0x58d6x3f)
        }
    } catch (e) {
        _0x58d6x3e(_0x58d6x3f)
    }
})({})

这里断点到的 _0x58d6x37 函数中有我们要找的 POST 过程中的各种参数:

火猫直播的token值

其中 token=_0x58d6x3a=md5(_0x58d6x38[__Ox7f0a3[0xd]] + _0x58d6x39 + _0x58d6x32 + _0x58d6x2b());是一个 md5 加密,参数由 4 个字符串拼接而来。

_0x58d6x38[__Ox7f0a3[0xd]]="CdvfGdaXzeGbPhddM1s",即第 3 步中确定的 VideoIDS ;

_0x58d6x39="huomaomobileh5"为请求参数中的固定值“from”;

_0x58d6x32=1589384663,10 位时间戳;

_0x58d6x2b()="6FE26D855E1AEAE090E243EB1AF73685"也像是个 md5,继续往上查找 _0x58d6x2b() 函数的定义处:

function _0x58d6x2b() {
    if (!_0x58d6x2e) {
        _0x58d6x2e = true;
        var _0x58d6x2c = _0x58d6x27;
        _0x58d6x12(_0x58d6x2c, _0x58d6x26);
        _0x58d6x12(_0x58d6x2c, _0x58d6x26);
        var _0x58d6x2d = __Ox7f0a3[0x4];
        for (var _0x58d6x6 = 0; _0x58d6x6 < _0x58d6x2c[__Ox7f0a3[0x0]]; _0x58d6x6++) {
            _0x58d6x2d += String[__Ox7f0a3[0x5]](_0x58d6x2c[_0x58d6x6])
        }
        ;_0x58d6x2a = _0x58d6x2d
    }
    ;_0x58d6x2f();
    return _0x58d6x2a
}

函数最后返回 _0x58d6x2a,_0x58d6x2a 被赋值 _0x58d6x2d。而 _0x58d6x2d 的初始值为空,使用 String.fromCharCode() 方法将 _0x58d6x2c 的中的值转为字符串,继续向上查找可确定 _0x58d6x2c 为一个固定的 32 位数组,这意味着 _0x58d6x2b() 函数固定返回"6FE26D855E1AEAE090E243EB1AF73685",调试不同直播间时也验证了这点,现在 token 值计算过程也搞清楚了。

至此已确定所有参数的来源,最后附上 Python 代码:

Python 代码实现

要注意的是,和斗鱼直播一样,部分火猫直播间链接上的数字不是真实的房间 ID,需先在网页源代码中获取。

import requests
import time
import hashlib
import re

def get_real_url(rid):
    response = requests.get('http://m.huomao.com/mobile/mob_live/' + str(rid)).text
    videoids = re.findall(r'var stream = "([\w\W]+?)";', response)[0]
    tt = int(time.time())
    token = hashlib.md5((videoids + 'huomaoh5room' + str(tt) +
                         '6FE26D855E1AEAE090E243EB1AF73685').encode('utf-8')).hexdigest()
    post_data = {
            'cdns': 1,
            'streamtype': 'live',
            'VideoIDS': videoids,
            'from': 'huomaoh5room',
            'time': tt,
            'token': token
        }
    response = requests.post('https://m.huomao.com/swf/live_data', data=post_data).json()
    real_url = response.get('streamList')[-1].get('list')
    return real_url

后记:

1、其他各种直播平台的获取方法可查看博主的 GitHub 仓库:real-url

2、这个调试过程早前曾发在吾爱破解论坛上,后不知为何被版主给删了。

火猫直播源调试:简单的JS混淆》上有 2 条评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注