漁滒 发表于 2021-8-3 22:21

某网站心跳包参数加密的wasm分析

@(某网站心跳包参数加密的wasm分析)
## js层动态分析
网站地址:aHR0cHM6Ly9saXZlLmJpbGliaWxpLmNvbS8=

首先打开任意直播,然后进行抓包


这里可以看到,s参数就是加密的,不多说,直接下个XHR断点


这时s参数已经生成,发现有很多的异步,不要紧,一个一个调用堆栈往前找


在找到到这个调用堆栈的时候,这个函数名称引起了我的注意,在这行下一个断点,然后继续让代码执行


断点再次断下时,发现是调用的上面的函数,继续在关键的callbacks.sign处下断点


callbacks.sign来到这里继续在t.sign下断点


来到这里进行单步调试,可以发现r.spyder就是计算签名的函数,在控制台尝试运行一下,然后继续跟进去





这个名称,很明显就是调用了wasm,抓包中搜索wasm



搜索到有两个wasm,那么就在wasm的文本格式中继续搜索spyder这个关键词,因为这个是导出函数,一定会出现在wasm的文本格式中


其中一个可以搜索到结果,样品地址:aHR0cHM6Ly9pMC5oZHNsYi5jb20vYmZzL2xpdmUvZTc5MTU1NjcwNmY4OGQ4OGI0ODQ2YTYxYTU4M2IzMWRiMDA3ZjgzZC53YXNt

## nodejs调用wasm与绕过dom环境检测
这篇文章将不直接分析算法,而是先尝试使用nodejs调用wasm的方式来获取结果

一般的加载顺序是先找到导入函数,然后加载wasm,最后获取导出函数



导入函数直接白给,那么就复制粘贴好了,初步代码如下

```javascript

const fs = require('fs');
let wasmfilename = 'bilibili2.wasm';


var importObject = {
    env: {
      __cargo_web_snippet_0d39c013e2144171d64e2fac849140a7e54c939a: function (r, t) {
            t = e.STDWEB_PRIVATE.to_js(t),
                e.STDWEB_PRIVATE.from_js(r, t.location)
      },
      __cargo_web_snippet_0f503de1d61309643e0e13a7871406891e3691c9: function (r) {
            e.STDWEB_PRIVATE.from_js(r, window)
      },
      __cargo_web_snippet_10f5aa3985855124ab83b21d4e9f7297eb496508: function (r) {
            return e.STDWEB_PRIVATE.acquire_js_reference(r) instanceof Array | 0
      },
      __cargo_web_snippet_2b0b92aee0d0de6a955f8e5540d7923636d951ae: function (r, t) {
            t = e.STDWEB_PRIVATE.to_js(t),
                e.STDWEB_PRIVATE.from_js(r, function () {
                  try {
                        return {
                            value: t.origin,
                            success: !0
                        }
                  } catch (e) {
                        return {
                            error: e,
                            success: !1
                        }
                  }
                }())
      },
      __cargo_web_snippet_461d4581925d5b0bf583a3b445ed676af8701ca6: function (r, t) {
            t = e.STDWEB_PRIVATE.to_js(t),
                e.STDWEB_PRIVATE.from_js(r, function () {
                  try {
                        return {
                            value: t.host,
                            success: !0
                        }
                  } catch (e) {
                        return {
                            error: e,
                            success: !1
                        }
                  }
                }())
      },
      __cargo_web_snippet_4c895ac2b754e5559c1415b6546d672c58e29da6: function (r, t) {
            t = e.STDWEB_PRIVATE.to_js(t),
                e.STDWEB_PRIVATE.from_js(r, function () {
                  try {
                        return {
                            value: t.protocol,
                            success: !0
                        }
                  } catch (e) {
                        return {
                            error: e,
                            success: !1
                        }
                  }
                }())
      },
      __cargo_web_snippet_614a3dd2adb7e9eac4a0ec6e59d37f87e0521c3b: function (r, t) {
            t = e.STDWEB_PRIVATE.to_js(t),
                e.STDWEB_PRIVATE.from_js(r, t.error)
      },
      __cargo_web_snippet_62ef43cf95b12a9b5cdec1639439c972d6373280: function (r, t) {
            t = e.STDWEB_PRIVATE.to_js(t),
                e.STDWEB_PRIVATE.from_js(r, t.childNodes)
      },
      __cargo_web_snippet_6fcce0aae651e2d748e085ff1f800f87625ff8c8: function (r) {
            e.STDWEB_PRIVATE.from_js(r, document)
      },
      __cargo_web_snippet_7ba9f102925446c90affc984f921f414615e07dd: function (r, t) {
            t = e.STDWEB_PRIVATE.to_js(t),
                e.STDWEB_PRIVATE.from_js(r, t.body)
      },
      __cargo_web_snippet_80d6d56760c65e49b7be8b6b01c1ea861b046bf0: function (r) {
            e.STDWEB_PRIVATE.decrement_refcount(r)
      },
      __cargo_web_snippet_897ff2d0160606ea98961935acb125d1ddbf4688: function (r) {
            var t = e.STDWEB_PRIVATE.acquire_js_reference(r);
            return t instanceof DOMException && "SecurityError" === t.name
      },
      __cargo_web_snippet_8c32019649bb581b1b742eeedfc410e2bedd56a6: function (r, t) {
            var n = e.STDWEB_PRIVATE.acquire_js_reference(r);
            e.STDWEB_PRIVATE.serialize_array(t, n)
      },
      __cargo_web_snippet_a466a2ab96cd77e1a77dcdb39f4f031701c195fc: function (r, t) {
            t = e.STDWEB_PRIVATE.to_js(t),
                e.STDWEB_PRIVATE.from_js(r, function () {
                  try {
                        return {
                            value: t.pathname,
                            success: !0
                        }
                  } catch (e) {
                        return {
                            error: e,
                            success: !1
                        }
                  }
                }())
      },
      __cargo_web_snippet_ab05f53189dacccf2d365ad26daa407d4f7abea9: function (r, t) {
            t = e.STDWEB_PRIVATE.to_js(t),
                e.STDWEB_PRIVATE.from_js(r, t.value)
      },
      __cargo_web_snippet_b06dde4acf09433b5190a4b001259fe5d4abcbc2: function (r, t) {
            t = e.STDWEB_PRIVATE.to_js(t),
                e.STDWEB_PRIVATE.from_js(r, t.success)
      },
      __cargo_web_snippet_b33a39de4ca954888e26fe9caa277138e808eeba: function (r, t) {
            t = e.STDWEB_PRIVATE.to_js(t),
                e.STDWEB_PRIVATE.from_js(r, t.length)
      },
      __cargo_web_snippet_cdf2859151791ce4cad80688b200564fb08a8613: function (r, t) {
            t = e.STDWEB_PRIVATE.to_js(t),
                e.STDWEB_PRIVATE.from_js(r, function () {
                  try {
                        return {
                            value: t.href,
                            success: !0
                        }
                  } catch (e) {
                        return {
                            error: e,
                            success: !1
                        }
                  }
                }())
      },
      __cargo_web_snippet_e8ef87c41ded1c10f8de3c70dea31a053e19747c: function (r, t) {
            t = e.STDWEB_PRIVATE.to_js(t),
                e.STDWEB_PRIVATE.from_js(r, function () {
                  try {
                        return {
                            value: t.hostname,
                            success: !0
                        }
                  } catch (e) {
                        return {
                            error: e,
                            success: !1
                        }
                  }
                }())
      },
      __cargo_web_snippet_e9638d6405ab65f78daf4a5af9c9de14ecf1e2ec: function (r) {
            r = e.STDWEB_PRIVATE.to_js(r),
                e.STDWEB_PRIVATE.unregister_raw_value(r)
      },
      __cargo_web_snippet_ff5103e6cc179d13b4c7a785bdce2708fd559fc0: function (r) {
            e.STDWEB_PRIVATE.tmp = e.STDWEB_PRIVATE.to_js(r)
      },
      __web_on_grow: A
    }
};

var wasmobject = new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array(fs.readFileSync(wasmfilename))), importObject);
console.log(wasmobject);
```

直接运行发现报错


那就直接给个空函数

```javascript

const fs = require('fs');
let wasmfilename = 'bilibili2.wasm';


var __fun = function(){};

var importObject = {
    env: {
      __cargo_web_snippet_0d39c013e2144171d64e2fac849140a7e54c939a: function (r, t) {
            t = e.STDWEB_PRIVATE.to_js(t),
                e.STDWEB_PRIVATE.from_js(r, t.location)
      },
      /*省略重复的代码*/
      __web_on_grow: __fun
    }
};

var wasmobject = new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array(fs.readFileSync(wasmfilename))), importObject);
var wasmmemory = wasmobject.exports.memory;
console.log(wasmobject);
console.log(wasmmemory);
```
这时已经可以加载成功,并且成功获取内存



然后就是根据js的逻辑,来调用wasm即可。不过这个wasm有两个坑,一个是数据类型的处理,另一个是dom环境的检测

第一点:在prepare_any_arg函数里面有个from_js函数

这里可以看到对传进来的数据类型不同,会做不同的处理。例如字符串的类型是4,数值的类型是2或者3,null的类型为1,void 0 的类型为0,布尔值为5或者6,以及其他类型,所以要改写一下js处理数据的逻辑,同时修改倒数函数中的函数名,下面是数据处理的相关函数

```javascript
var to_js = function (r) {
    var t = (new Uint8Array(wasmmemory.buffer));
    if (t !== 0){
      if(t === 1){
            return null;
      }else if(t === 2){
            return (new Int32Array(wasmmemory.buffer));
      }else if(t === 3){
            return (new Float64Array(wasmmemory.buffer));
      }else if(t === 4){
            var _ = (new Uint32Array(wasmmemory.buffer))
                , n = (new Uint32Array(wasmmemory.buffer))[(r + 4) / 4];
            return to_js_string(_, n)
      }else if(t === 5){
            return !1;
      }else if(t === 6){
            return !0;
      }else if(t === 7){
            console.log('返回数组。未实现')
      }else if(t === 8){
            console.log('返回对象。未实现')
      }else if(t === 9){
            return id_to_ref_map[(new Int32Array(wasmmemory.buffer))]
      }else {
            console.log('返回其他。未实现')
      }
    }

};

var to_js_string = function (r, _) {
    return (new Buffer.from((new Uint8Array(wasmmemory.buffer)).subarray(r, r + _))).toString()
};

var to_utf8_string = function (t, _) {
    var n = new Uint8Array(new Buffer.from(_, 'utf8'));
    var a = n.length;
    var c = wasmobject.exports.__web_malloc(a);
    var R = new Uint8Array(wasmmemory.buffer, c, a);
    R.set(n);
    (new Uint32Array(wasmmemory.buffer)) = c;
    (new Uint32Array(wasmmemory.buffer))[(t + 4) / 4] = a;
};

var acquire_rust_reference = function (r) {
    var c = last_refid++;
    id_to_ref_map = r;
    return c
};

var serialize_array = function (r, t) {
    var _ = t.length
      , n = wasmobject.exports.__web_malloc(16 * _);
    (new Uint8Array(wasmmemory.buffer)) = 7;
    (new Uint32Array(wasmmemory.buffer)) = n;
    (new Uint32Array(wasmmemory.buffer))[(r + 4) / 4] = _;
    for (var a = 0; a < _; ++a)
      from_js(n + 16 * a, t)
};


var from_js = function (r, t) {
    var _ = Object.prototype.toString.call(t);
    if ("" === _){
      (new Uint8Array(wasmmemory.buffer)) = 4;
      to_utf8_string(r, t);
    }
    else if ("" === _)
      t === (0 | t) ? ((new Uint8Array(wasmmemory.buffer)) = 2,
            (new Int32Array(wasmmemory.buffer)) = t) : ((new Uint8Array(wasmmemory.buffer)) = 3,
            (new Float64Array(wasmmemory.buffer)) = t);
    else if (null === t)
      (new Uint8Array(wasmmemory.buffer)) = 1;
    else if (void 0 === t)
      (new Uint8Array(wasmmemory.buffer)) = 0;
    else if (!1 === t)
      (new Uint8Array(wasmmemory.buffer)) = 5;
    else if (!0 === t)
      (new Uint8Array(wasmmemory.buffer)) = 6;
    else if ("" === _) {
      var n = register_raw_value(t);
      e.HEAPU8 = 15,
            e.HEAP32 = n
    } else {
      var a = acquire_rust_reference(t);
      (new Uint8Array(wasmmemory.buffer)) = 9;
      (new Int32Array(wasmmemory.buffer)) = a;
    }
};

var prepare_any_arg = function(r){
    var t = wasmobject.exports.__web_malloc(16);
    from_js(t, r);
    return t
};
```

第二点:然后设置参数尝试进行调用

```javascript
var arge1 = '{"id":"","device":"[\\"AUTO1716277899447614\\",\\"3fc2a589-9cb7-42bf-85c9-c2af590c2fad\\"]","ets":1627808068,"benchmark":"seacasdgyijfhofiuxoannn","time":60,"ts":1627808222759,"ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4580.0 Safari/537.36"}';
var arge2 = new Array();
arge2.push(2);
arge2.push(5);
arge2.push(1);
arge2.push(4);

var wasmr = prepare_any_arg(arge1);
var wasmt = prepare_any_arg(arge2);
console.log(wasmr);
console.log(wasmt);
wasmobject.exports.spyder.apply(null, );
```


此时可以发现其对bom和dom都有检测,这个问题不大,简单的补头就可以解决,报错什么就补什么

```javascript
window = {
    location: {
      host: "live.bilibili.com",
      hostname: "live.bilibili.com",
      href: "https://live.bilibili.com/",
      origin: "https://live.bilibili.com",
      pathname: "/blanc/",
      protocol: "https:"
    }
};

document = {
    body: {
      childNodes:
    }
};
```
加上上面的代码,就可以绕过检测,最终的完整代码

```javascript

const fs = require('fs');
let wasmfilename = 'bilibili2.wasm';

window = {
    location: {
      host: "live.bilibili.com",
      hostname: "live.bilibili.com",
      href: "https://live.bilibili.com/",
      origin: "https://live.bilibili.com",
      pathname: "/blanc/",
      protocol: "https:"
    }
};

document = {
    body: {
      childNodes:
    }
};


var tmp;
var id_to_ref_map = {};
var last_refid = 1;

var __fun = function(){};

var importObject = {
    env: {
      __cargo_web_snippet_0d39c013e2144171d64e2fac849140a7e54c939a: function(r, t) {
            t = to_js(t);
            from_js(r, t.location)
      },
      __cargo_web_snippet_0f503de1d61309643e0e13a7871406891e3691c9: function(r) {
            from_js(r, window)
      },
      __cargo_web_snippet_10f5aa3985855124ab83b21d4e9f7297eb496508: function(r) {
            return id_to_ref_map instanceof Array | 0;
      },
      __cargo_web_snippet_2b0b92aee0d0de6a955f8e5540d7923636d951ae: function(r, t) {
            t = to_js(t),
                from_js(r, function() {
                  try {
                        return {
                            value: t.origin,
                            success: !0
                        }
                  } catch (e) {
                        return {
                            error: e,
                            success: !1
                        }
                  }
                }())
      },
      __cargo_web_snippet_461d4581925d5b0bf583a3b445ed676af8701ca6: function(r, t) {
            t = to_js(t),
                from_js(r, function() {
                  try {
                        return {
                            value: t.host,
                            success: !0
                        }
                  } catch (e) {
                        return {
                            error: e,
                            success: !1
                        }
                  }
                }())
      },
      __cargo_web_snippet_4c895ac2b754e5559c1415b6546d672c58e29da6: function(r, t) {
            t = to_js(t),
                from_js(r, function() {
                  try {
                        return {
                            value: t.protocol,
                            success: !0
                        }
                  } catch (e) {
                        return {
                            error: e,
                            success: !1
                        }
                  }
                }())
      },
      __cargo_web_snippet_614a3dd2adb7e9eac4a0ec6e59d37f87e0521c3b: __fun,
      __cargo_web_snippet_62ef43cf95b12a9b5cdec1639439c972d6373280: function(r, t) {
            t = to_js(t),
                from_js(r, t.childNodes)
      },
      __cargo_web_snippet_6fcce0aae651e2d748e085ff1f800f87625ff8c8: function(r) {
            from_js(r, document)
      },
      __cargo_web_snippet_7ba9f102925446c90affc984f921f414615e07dd: function(r, t) {
            t = to_js(t),
                from_js(r, t.body)
      },
      __cargo_web_snippet_80d6d56760c65e49b7be8b6b01c1ea861b046bf0: __fun,
      __cargo_web_snippet_897ff2d0160606ea98961935acb125d1ddbf4688: __fun,
      __cargo_web_snippet_8c32019649bb581b1b742eeedfc410e2bedd56a6: function(r, t) {
            var _ = id_to_ref_map;
            serialize_array(t, _);
      },
      __cargo_web_snippet_a466a2ab96cd77e1a77dcdb39f4f031701c195fc: function(r, t) {
            t = to_js(t),
                from_js(r, function() {
                  try {
                        return {
                            value: t.pathname,
                            success: !0
                        }
                  } catch (e) {
                        return {
                            error: e,
                            success: !1
                        }
                  }
                }())
      },
      __cargo_web_snippet_ab05f53189dacccf2d365ad26daa407d4f7abea9: function(r, t) {
            t = to_js(t),
                from_js(r, t.value)
      },
      __cargo_web_snippet_b06dde4acf09433b5190a4b001259fe5d4abcbc2: function(r, t) {
            t = to_js(t),
                from_js(r, t.success)
      },
      __cargo_web_snippet_b33a39de4ca954888e26fe9caa277138e808eeba: function(r, t) {
            t = to_js(t),
                from_js(r, t.length)
      },
      __cargo_web_snippet_cdf2859151791ce4cad80688b200564fb08a8613: function(r, t) {
            t = to_js(t),
                from_js(r, function() {
                  try {
                        return {
                            value: t.href,
                            success: !0
                        }
                  } catch (e) {
                        return {
                            error: e,
                            success: !1
                        }
                  }
                }())
      },
      __cargo_web_snippet_e8ef87c41ded1c10f8de3c70dea31a053e19747c: function(r, t) {
            t = to_js(t),
                from_js(r, function() {
                  try {
                        return {
                            value: t.hostname,
                            success: !0
                        }
                  } catch (e) {
                        return {
                            error: e,
                            success: !1
                        }
                  }
                }())
      },
      __cargo_web_snippet_e9638d6405ab65f78daf4a5af9c9de14ecf1e2ec: __fun,
      __cargo_web_snippet_ff5103e6cc179d13b4c7a785bdce2708fd559fc0: function(r) {
            tmp = to_js(r)
      },
      __web_on_grow: __fun
    }
};

var wasmobject = new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array(fs.readFileSync(wasmfilename))), importObject);
var wasmmemory = wasmobject.exports.memory;

var to_js = function (r) {
    var t = (new Uint8Array(wasmmemory.buffer));
    if (t !== 0){
      if(t === 1){
            return null;
      }else if(t === 2){
            return (new Int32Array(wasmmemory.buffer));
      }else if(t === 3){
            return (new Float64Array(wasmmemory.buffer));
      }else if(t === 4){
            var _ = (new Uint32Array(wasmmemory.buffer))
                , n = (new Uint32Array(wasmmemory.buffer))[(r + 4) / 4];
            return to_js_string(_, n)
      }else if(t === 5){
            return !1;
      }else if(t === 6){
            return !0;
      }else if(t === 7){
            console.log('返回数组。未实现')
      }else if(t === 8){
            console.log('返回对象。未实现')
      }else if(t === 9){
            return id_to_ref_map[(new Int32Array(wasmmemory.buffer))]
      }else {
            console.log('返回其他。未实现')
      }
    }

};

var to_js_string = function (r, _) {
    return (new Buffer.from((new Uint8Array(wasmmemory.buffer)).subarray(r, r + _))).toString()
};

var to_utf8_string = function (t, _) {
    var n = new Uint8Array(new Buffer.from(_, 'utf8'));
    var a = n.length;
    var c = wasmobject.exports.__web_malloc(a);
    var R = new Uint8Array(wasmmemory.buffer, c, a);
    R.set(n);
    (new Uint32Array(wasmmemory.buffer)) = c;
    (new Uint32Array(wasmmemory.buffer))[(t + 4) / 4] = a;
};

var acquire_rust_reference = function (r) {
    var c = last_refid++;
    id_to_ref_map = r;
    return c
};

var serialize_array = function (r, t) {
    var _ = t.length
      , n = wasmobject.exports.__web_malloc(16 * _);
    (new Uint8Array(wasmmemory.buffer)) = 7;
    (new Uint32Array(wasmmemory.buffer)) = n;
    (new Uint32Array(wasmmemory.buffer))[(r + 4) / 4] = _;
    for (var a = 0; a < _; ++a)
      from_js(n + 16 * a, t)
};


var from_js = function (r, t) {
    var _ = Object.prototype.toString.call(t);
    if ("" === _){
      (new Uint8Array(wasmmemory.buffer)) = 4;
      to_utf8_string(r, t);
    }
    else if ("" === _)
      t === (0 | t) ? ((new Uint8Array(wasmmemory.buffer)) = 2,
            (new Int32Array(wasmmemory.buffer)) = t) : ((new Uint8Array(wasmmemory.buffer)) = 3,
            (new Float64Array(wasmmemory.buffer)) = t);
    else if (null === t)
      (new Uint8Array(wasmmemory.buffer)) = 1;
    else if (void 0 === t)
      (new Uint8Array(wasmmemory.buffer)) = 0;
    else if (!1 === t)
      (new Uint8Array(wasmmemory.buffer)) = 5;
    else if (!0 === t)
      (new Uint8Array(wasmmemory.buffer)) = 6;
    else if ("" === _) {
      var n = register_raw_value(t);
      e.HEAPU8 = 15,
            e.HEAP32 = n
    } else {
      var a = acquire_rust_reference(t);
      (new Uint8Array(wasmmemory.buffer)) = 9;
      (new Int32Array(wasmmemory.buffer)) = a;
    }
};

var prepare_any_arg = function(r){
    var t = wasmobject.exports.__web_malloc(16);
    from_js(t, r);
    return t
};


var arge1 = '{"id":"","device":"[\\"AUTO1716277899447614\\",\\"3fc2a589-9cb7-42bf-85c9-c2af590c2fad\\"]","ets":1627808068,"benchmark":"seacasdgyijfhofiuxoannn","time":60,"ts":1627808222759,"ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4580.0 Safari/537.36"}';
var arge2 = new Array();
arge2.push(2);
arge2.push(5);
arge2.push(1);
arge2.push(4);

var wasmr = prepare_any_arg(arge1);
var wasmt = prepare_any_arg(arge2);

wasmobject.exports.spyder.apply(null, );

console.log(tmp);
```


这时已经可以得到结果,经测试与浏览器计算结果一致

## IDA分析与python算法还原
关于wasm转o文件的操作在本篇就忽略了,详细内容参考上一篇:https://www.52pojie.cn/thread-1461335-1-1.html


IDA打开处理好的o文件,找到spyder方法,进入伪代码,函数的开头就出现了熟悉的case,没错,这个就是在js层设置的各种数据类型对应的数字。但是这个不太要管,根据前面nodejs调用wasm可知,前面会有一大段检测环境的代码,我们应该跳过这段检测的代码后再分析,进入图标概括,如下图



点击这一个块,然后再f5




一进去答案就出来了,连函数名都没有混淆,简直是白给
因为v160是小于等于5的,所以可以得出如下结论

|数值|算法|
|--|--|
|0|hmac_md5|
|1|hmac_sha1|
|2|hmac_sha256|
|3|hmac_sha224|
|4|hmac_sha512|
|5|hmac_sha384|

既然都是hmac的算法,那么只要得到密钥的需要摘要的内容,那么就可以直接用python的库完成签名

动态调试我是用的是Google Chrome 94.0.4596.0(正式版本)canary (64 位)

从spyder函数的第二个参数可知,首先计算的哈希函数是hmac_sha256,先简单说一下hmac的算法,根据文章(https://www.cnblogs.com/shoshana-kong/p/11497676.html)



hmac的算法实际上是计算了两次原本的哈希函数

1.首先将key填充至64字节,然后与i_pad进行异或,得到i key pad
2.还是将key填充至64字节,然后与o_pad进行异或,得到o key pad
3.将i key pad放在签名内容的前面,然后计算一次sha,得到hash sum 1
4.将步骤2中的o key pad放在步骤3的hash sum 1前面,再计算一次sha,得到hash sum 2

这时的hash sum 2就是最终hmac的结果,进入IDA中的hmac_sha256函数看一看





可以看到创建了两个sha256函数,进行了4次input,两次digest,与上面的算法是一致的,那么只需要直到input的内容,那么就可以还原算法了,在wasm中的hmac_sha256下一个断点,断下后,在函数内所有的input下一个断点

断下来后右下角的小按钮很有用,可以直接查看内存视图
根据OpenSSL的源码https://github.com/openssl/openssl/blob/master/crypto/hmac/hmac.c中的函数定义


第一个参数是结构体,第二个参数是内容,第三个参数是内容的长度

直接跳转到地址,可以看到这个就是64字节长度的i key pad,全部与0x36进行异或,就可以得到密钥为【seacasdgyijfhofiuxoannn】,实际就是参数的benchmark的值,继续执行

这时可以看到o key pad,继续往下

这时就可以看到实际签名的内容了,长度是199字节,是一段json的字符串,尝试使用python还原

```python
    def hmac_sha256(key, data):
      key = bytearray(key.encode())
      while len(key) < 64:
            key.append(0)
      i_key_pad = bytes()
      o_key_pad = bytes()
      sha256s = hashlib.sha256()
      sha256s.update(i_key_pad)
      sha256s.update(data.encode())
      hash_sum_1 = sha256s.digest()
      sha256s = hashlib.sha256()
      sha256s.update(o_key_pad)
      sha256s.update(hash_sum_1)
      return sha256s.hexdigest()
   
    data = {"platform":"web","parent_id":3,"area_id":163,"seq_id":13,"room_id":5887574,"buvid":"AUTO1716277899447614","uuid":"3fc2a589-9cb7-42bf-85c9-c2af590c2fad","ets":1627808068,"time":60,"ts":1627808222759}
    data = json.dumps(data, separators=(',', ':'))
    benchmark = 'seacasdgyijfhofiuxoannn'
    print(hmac_sha256(benchmark, data))
    print(HMAC.new(benchmark.encode(), data.encode(), SHA256).hexdigest())
```

运行发现,两个结果是一致的,都是【938e55c730b57bc3972ae88c1a45db62040a12d63e40d66d0ab8cbb09c121197】,如此类推,其他hmac函数都可以这样获取密钥和内容,最后可以得出签名的算法

```python
    data = {"platform":"web","parent_id":3,"area_id":163,"seq_id":13,"room_id":5887574,"buvid":"AUTO1716277899447614","uuid":"3fc2a589-9cb7-42bf-85c9-c2af590c2fad","ets":1627808068,"time":60,"ts":1627808222759}
    data = json.dumps(data, separators=(',', ':'))
    benchmark = 'seacasdgyijfhofiuxoannn'
    data = HMAC.new(benchmark, data.encode(), SHA256).hexdigest()
    data = HMAC.new(benchmark, data.encode(), SHA384).hexdigest()
    data = HMAC.new(benchmark, data.encode(), SHA1).hexdigest()
    s = HMAC.new(benchmark, data.encode(), SHA512).hexdigest()
    print(s)

```

后面就没有做请求测试了。理论上是没有问题的了,完结

howsk 发表于 2021-8-3 23:51

漁滒 发表于 2021-8-3 23:41
对的,查找参数来源或者有特殊意义的函数,例如文章中讲到的sign函数,看到的时候就应该多注意

嗯,那个看明白了,主要看一些比较敏感的函数。
我自己有时候也是看到了敏感的函数,问题不大就直接下断点来看,再一个一个排除。
现在就是麻烦的如果js的代码太多了,然后一个一个筛选去定位就太麻烦了,一下字来个几万行几十万行,那真的是体力活。

漁滒 发表于 2021-8-3 23:41

howsk 发表于 2021-8-3 23:39
XHR断点下的是URL包含,包含的是一个request的URL,然后断下来了之后再一点一点往上翻吗?

对的,查找参数来源或者有特殊意义的函数,例如文章中讲到的sign函数,看到的时候就应该多注意

hui-shao 发表于 2021-8-3 23:15

好帖!分析详尽!

howsk 发表于 2021-8-3 23:20

请教楼主,刚开始的s是加密的数据,请问如何快速定位下断点的位置呀。

漁滒 发表于 2021-8-3 23:30

howsk 发表于 2021-8-3 23:20
请教楼主,刚开始的s是加密的数据,请问如何快速定位下断点的位置呀。

下XHR断点,在函数调用堆栈中往回找

howsk 发表于 2021-8-3 23:39

漁滒 发表于 2021-8-3 23:30
下XHR断点,在函数调用堆栈中往回找

XHR断点下的是URL包含,包含的是一个request的URL,然后断下来了之后再一点一点往上翻吗?{:1_889:}

tbloy 发表于 2021-8-4 01:26

看起来不错,支持

famdo818 发表于 2021-8-4 02:42

感谢分享,论坛有你真好

lijisheng 发表于 2021-8-4 06:07

厉害,值得的学习
页: [1] 2 3 4 5 6 7 8 9
查看完整版本: 某网站心跳包参数加密的wasm分析