前言 

现在一些网站对 JavaScript 代码采取了一定的保护措施,比如变量名混淆、执行逻辑混淆、反调试、核心逻辑加密等,有的还对数据接口进行了加密,这次案例是通过补环境过加密。

声明

本文章中所有内容仅供学习交流,相关链接做了脱敏处理,若有侵权,请联系我立即删除!

案例分析

目标网址:

aHR0cHM6Ly9tb2JpbGUueWFuZ2tlZHVvLmNvbS8=

数据接口:

aHR0cHM6Ly9tb2JpbGUueWFuZ2tlZHVvLmNvbS9wcm94eS9hcGkvc2VhcmNoX3N1Z2dlc3Q=

以上均做了脱敏处理,Base64 编码及解码方式:

import base64
# 编码
# result = base64.b64encode('待编码字符串'.encode('utf-8'))
# 解码
result = base64.b64decode('待解码字符串'.encode('utf-8'))
print(result)

常规 JavaScript 逆向思路

一般情况下,JavaScript 逆向分为三步:

  • 寻找入口:逆向在大部分情况下就是找一些加密参数到底是怎么来的,关键逻辑可能写在某个关键的方法或者隐藏在某个关键的变量里,一个网站可能加载了很多 JavaScript 文件,如何从这么多的 JavaScript 文件的代码行中找到关键的位置,很重要;
  • 调试分析:找到入口后,我们定位到某个参数可能是在某个方法中执行的了,那么里面的逻辑是怎么样的,调用了多少加密算法,经过了多少赋值变换,需要把整体思路整理清楚,以便于断点或反混淆工具等进行调试分析;
  • 模拟执行:经过调试分析后,差不多弄清了逻辑,就需要对加密过程进行逻辑复现,以拿到最后我们想要的数据

接下来开始正式进行案例分析:

寻找入口

打开开发者人员工具,进行抓包,从响应预览中可以看到搜索内容的下拉列表的接口为 search_suggest?pdduid=XXX&query=XXX:

负载中可以看到一些接口 url 的参数,query 就是搜索栏中输入的内容,很明显,anti_content 参数经过加密,接下来就需要对这个参数进行逆向分析:

逆向分析

anti_content 是接口 url 中的参数,且下拉数据是通过 ajax 加载的,所以通过 Hook 或者 XHR 断点的方式都能成功定位,不过大道至简,这里直接全局搜索 anti_content,因为只有一个 js 文件中包含这个关键字:

点击 SearchViewUI.js 文件跟进去,然后通过左下角的 { } 对其进行格式化操作,ctrl + f 局部搜索 anti_content 关键字,只有一个结果,在第1949 行,在 第1939 行打下断点调试:

可以看到 f 即 anti_content 参数的值,f 定义在第 1938 行,f = e.sent,此时加密后的参数已经生成了,所以需要向上跟栈,看看是哪加密的:

跟到 vendors_xxx 文件的第 19158 行,这里就是个异步 Promise,在 19619 行打下断点会发现此时的 e 还是 undefined,没生成加密参数:

XMLHttpRequest 这种异步的都很麻烦跳来跳去,类似如下几个地方,跟栈需要耐心:

var l = p(e, t, n);
return this._invoke(t, e)
return e.apply(this, arguments)
getAntiContent
initRiskCntroller
messagePackSync

跟到后面会跳转到一个叫 react_anti_XXX 的文件,这个文件还记录了鼠标移动轨迹:

在该文件的第 1791 行是 anti_content 参数的关键加密位置,这部分经过混淆,不过也没必要解混淆:

控制台打印看看,Lt() 即返回这个值的函数: 

滑到开头,会发现这是个通过 webpack 打包了的 js 文件,框出来的部分就是模块加载器,有个 webpack 很明显的标志:

return t[e].call(o.exports, o, o.exports, r)

webpack 相关推荐看看这篇文章:JavaScript 之 webpack 加密代码扣取

这里为文件本身的加载器调用,删掉,后面调用加载器的时候再传值进去就行了:

现在只需要把加载器导出为全局变量,然后将该函数改为自执行调用即可:

!(function (t) {
    var n = {};
    function r(e) {
        if (n[e])
            return n[e].exports;
        var o = n[e] = {
            i: e,
            l: !1,
            exports: {}
        };
        return t[e].call(o.exports, o, o.exports, r),
            o.l = !0,
            o.exports
    };

    window.rose = r;
})
...
...
...
let anti_content = window.rose(4);
result = new anti_content();

将此时的 js 代码放到浏览器环境中运行,在 result 处打断点,断住后跟进到第 1830 行:

会跳转到如下代码处:

var Vt = new Bt;
t[a(831, "C0uu")] = function() {
    var t = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}
        , n = a;
    return t[w] && X && Vt[n(548, "YD8i")](t[w]),
    Vt
}

Vt 继承了 Bt 的方法,返回值部分 t[w] 是一个时间戳,w 为 serverTime:

跟到 Vt[n(548, "YD8i")] 中看看,这里是通过 now() 方法生成的时间戳:

 所以可以写成如下样式:

let anti_content = window.rose(4);
result = new anti_content({ serverTime: new Date().getTime() });

Vt 原型下的 messagePack 函数调用了刚刚的加密函数 Lt():

所以来试试 result 调用 messagePack 函数能否得到加密值:

在控制台中成功打印出了加密结果,证明加密方法就是这样的,接下补环境即可,整理后的 js 文件如下:

!(function (t) {
    var n = {};
    function r(e) {
        if (n[e])
            return n[e].exports;
        var o = n[e] = {
            i: e,
            l: !1,
            exports: {}
        };
        return t[e].call(o.exports, o, o.exports, r),
            o.l = !0,
            o.exports
    };

    window.rose = r;

})([function (t, n, r) {

    // 此部分与原 js 文件一样, 在此省略

])

let anti_content = window.rose(4);
result = new anti_content({ serverTime: new Date().getTime() });

 补完环境后即可在 node 环境下成功得到加密结果:

python 调用:

欢迎提供更多需要 anti_content 参数的接口 ~ 

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐