使用AST进行JavaScript反混淆(2022年增值税发票查验js)

背景

多年前学过龙书,一来当时本身也没看懂,二来时间也长也差不多都忘记了。直到最近有 deobfuscate 问题才看了下AST。

说实话,一旦稍微了解AST和熟悉了 Babel 接口,deobfuscate 实在不是啥难事。

反混淆总结放前面。

注意事项

最重要的就2条:

  1. 开源/简单的混淆方案,现有的基本上都能解决;如:https://deobfuscator.kuizuo.cn/ 和 https://github.com/Tsaiboss/decodeObfuscator
  2. 其它混淆方案,特别是商用的或者是变形后的 deobfuscate 需要注意以下几点:
    1. 务必要处理掉反逆向逻辑,如:死循环这类;
    2. 使用 astexplorer 务必分析好特征,一般来讲开源的处理不了基本上是因为特征变了,其核心是一样的。一旦你补上了对应的特征基本上使用前面的开源项目就达到目的了。
    3. 必要的动态调试(浏览器debug)少不了,因为可以非常方便进行对比验证;

JS混淆总的来讲是提升 deobfuscate 难度,和其它安全类产品类似。JS毕竟是运行在客户端,想要完全的避免是不可能的。甚至是Native APP VM加壳这种都不能完全避免。

实战

证书问题

官网(https://inv-veri.chinatax.gov.cn/)使用自签证书,如果没有安装跟证书会出现:证书错误
浏览器解决方案有下面几种:

  • 第一种是不管,直接点击继续访问。但是各省市的网站需要都点击一次。比较麻烦。
  • 第二种是安装跟证书,参考:https://inv-veri.chinatax.gov.cn/zdaz.html ,Windows平台建议选择自动安装。
  • 第三种浏览器里面设置忽略证书验证,设置浏览器启动参数 -ignore-certificate-errors ,参考:https://www.likecs.com/show-304407.html 忽略证书

接口分析

整个功能就2个接口,一个验证码接口一个发票查验接口,对应如下:

  1. yzmQuery:获取验证码。 eb23.js 文件。
  2. vatQuery:获取发票数据。被 eval 隐藏,请求在 90a1c.js 文件。

我们分别对这2个接口进行分析。

代码分析见后面的 源码整理。

验证码接口 yzmQuery

先在浏览器中抓个包,接口如下:

https://fpcy.guangdong.chinatax.gov.cn/NWebQuery/yzmQuery?callback=jQuery36108117432652732104_1665738094937&fpdm=224420000000&fphm=00234125&r=0.8586908933148021&v=2.0.11_060&nowtime=1665738219455&publickey=1665738219455&key9=6ed669ba8da29e71cd3f582907f3840a&_=1665738094939&flwq39=eoyleUZchlfg6YBgVcLURuRzQ3Z8GRI148s3JKhei5SSK%2BfibNFq7YxxZT6NO2aXSzrimz7GoKWos%2BVh2bdBiAT4tMlYqyQB%2BQY1%2BD5AmUv1nfu2stTeQCJVCHzhRcmHFoV2VPTq%2BBegLJwOatgO4yYY59uStSsc1nT6qvBcaf4%3D

具体参数如下:

callback: jQuery36108117432652732104_1665738094937
fpdm: 224420000000
fphm: 00234125
r: 0.8586908933148021
v: 2.0.11_060
nowtime: 1665738219455
publickey: 1665738219455
key9: 6ed669ba8da29e71cd3f582907f3840a
_: 1665738094939
flwq39: eoyleUZchlfg6YBgVcLURuRzQ3Z8GRI148s3JKhei5SSK+fibNFq7YxxZT6NO2aXSzrimz7GoKWos+Vh2bdBiAT4tMlYqyQB+QY1+D5AmUv1nfu2stTeQCJVCHzhRcmHFoV2VPTq+BegLJwOatgO4yYY59uStSsc1nT6qvBcaf4=

此接口返回内容下:

jQuery36108117432652732104_1665738094937({
    "key1": "xxx",  // 验证码的base64编码
    "key2": "2022-10-14 16:44:47",  // 时间,发票数据接口要使用
    "key3": "dda81561f442e25c978cb7d5b2139928", // key,发票数据接口要使用
    "key4": "01", // 颜色
    "key5": "2",
    "key6": "8ae8f8fe838974270183d5bc260e5ae3" // key,发票数据接口要使用
})

验证码接口返回
我们对上面参数进行分类:

第一类是jsoup请求参数:callback_

callback_, 前面是随机字符串,可要可不要。不要的化服务器会直接返回json格式数据。_ 参数是当前时间(参考后面!)。不要callback参数

发票参数: fpdmfphm

fpdmfphm 发票代码和发票号码。注意新的电子发票是 20位发票号码,需要拆分。

获取当前时间参考

环境参数: nowtimer , v, publickey

这4个参数中只有 v是版本号的意思,在 validate.js 中,自己可以打开 https://inv-veri.chinatax.gov.cn/js/validate.js?0.3320348734641909 看。 r 参数是随机值,可以自己取。 nowtimepublickey 和 都是当前时间,参考前面的 showTime() 接口。

加密参数:key9flwq39

这重点。

参数 key9 是 算出来的,来源参考:key9参数
这个参数的三个参数分别是 发票代码、发票号码、时间。我们自己可以验证。
验证key9参数
跟踪$.nnyd.yzm 函数可以发现对应位置,方式如下:

  1. 控制台输入函数名称 $.nnyd.yzm
  2. 点击呈现的代码。
    输入函数,定位代码
    在新窗口中格式化代码并搜索关键字。
    搜索yzm关键字

flwq39参数:这个参数是放到 ajaxSetup中,如下所示:

    $.ajaxSetup({
        "beforeSend": function (_0xc6fbc4, _0x5a4363) {
            if (_0x5a4363.url.indexOf("127.0.0.1") == -1) {
                _0x5a4363.url += _0x5a4363.url.match(/\?/) ? "&" : "?";
                if (_0x5a4363.url.indexOf("yzmQuery") >= 0) {
                    _0x5a4363.url += "flwq39=" + _0x3ed54a(_0x5a4363);
                    dlqee = new Date().getTime() + 2000;
                }
                if (_0x5a4363.url.indexOf("vatQuery") >= 0) {
                    var _0x61b5fc = new Date().getTime() < dlqee;
                    _0x5a4363.url += "flwq39=" + _0x51824e(_0x5a4363, _0x61b5fc);
                }
            }
        }
    });

查验接口 vatQuery

如果你看前面的验证码接口,那么发现此接口和之前基本上是一样的。

看实际地址:

https://fpcy.guangdong.chinatax.gov.cn/NWebQuery/vatQuery?callback=jQuery36108117432652732104_1665738094937&key1=224420000000&key2=00234125&key3=20221013&key4=52200&fplx=09&yzm=zsj&yzmSj=2022-10-14%2016%3A45%3A04&index=e4c1e5acc4313342aa2e62cbb6dffdbd&key6=8ae8f8fe8389744a0183d5bc662d6b1e&publickey=2022-10-14%2016%3A45%3A04&key9=1065daa3d1a4b8a8b85e8ec531d13bac&_=1665738094940&flwq39=lYNyPq6FOGowj1WkJsBztPN8Gq%2FS4tfy9STQYrQoDbV7ac1KrlFeU8MopI9BGNo7Xf7VaCRDKeE6UNpps2UlMcC1IlYabqVzRog0VFh%2FNDrXPgylod3E4UjLDE3l6mk5A%2B57SMzNe%2BV7gSH7a9%2BhYTJ42B5wRTPT2ph2hNJoff4%3D

对应参数:

callback: jQuery36108117432652732104_1665738094937
key1: 224420000000
key2: 00234125
key3: 20221013
key4: 52200
fplx: 09
yzm: zsj
yzmSj: 2022-10-14 16:45:04
index: e4c1e5acc4313342aa2e62cbb6dffdbd
key6: 8ae8f8fe8389744a0183d5bc662d6b1e
publickey: 2022-10-14 16:45:04
key9: 1065daa3d1a4b8a8b85e8ec531d13bac
_: 1665738094940
flwq39: 
lYNyPq6FOGowj1WkJsBztPN8Gq/S4tfy9STQYrQoDbV7ac1KrlFeU8MopI9BGNo7Xf7VaCRDKeE6UNpps2UlMcC1IlYabqVzRog0VFh/NDrXPgylod3E4UjLDE3l6mk5A+57SMzNe+V7gSH7a9+hYTJ42B5wRTPT2ph2hNJoff4=
第一类是jsoup请求参数:callback_

见验证码接口

发票参数: key1key2key3key4
  • key1:发票代码
  • key2 发票号码。注意新的电子发票是 20位发票号码,需要拆分。
  • key3 开票日期
  • key4 发票金额,注意不同类型的发票这个值不一样,可能是校验码,也可能是 不含税进金额,也可能是含税金额。
  • yzm 验证码
  • fplx 发票类型,由 alxd 函数获取。如下所示:
    获取发票类型
    alxd 代码获取请参考前面的 $.nnyd.yzm
上下文参数: yzmSjindexpublickeykey6
  • yzmSj 验证码接口返回的
  • index 验证码接口返回的 key3
  • publickey 验证码接口返回的时间。
  • key6 验证码接口返回的key6
加密参数:key9flwq39

擦看前面的方式可以发现, key9$.nnyd.cy 接口返回的,如下所示。
查验接口参数
flwq39 参数见前面。

部分结果源码整理

// 获取发票的接口。
function getYzmXx() {
    $.pricode.clearA();
    show_yzm = "1";
    var _0x436381 = $("#fpdm").val().trim();
    if ("" === _0x436381) {
        _0x436381 = $("#fphm").val().trim();
    }
    var _0x58c97a = getSwjg(_0x436381, 0);
    if (!_0x58c97a[1]) {
        jAlert("<div id='popup_message'>请输入正确的代码号码!</div>", "提示");
        return;
    }
    var _0x3bab81 = _0x58c97a[1] + "/yzmQuery";
    var _0x469bdb = showTime().toString();
    var _0x4c4952 = $("#fpdm").val().trim();
    var _0x5699c9 = $("#fphm").val().trim();
    var _0x43c245 = $("#kjje").val().trim();
    var _0x4b069d = Math.random();
    var _0x1d0e71 = _0x58c97a[2];
    if (_0x5699c9.length === 20) {
    	// 处理20位发票号码,前面12位分割成发票代码,后面8位分割位发票号码。
        _0x4c4952 = _0x5699c9.substring(0, 12);
        _0x5699c9 = _0x5699c9.substring(12);
    }
    var _0x28b87b = {
        "fpdm": _0x4c4952,
        "fphm": _0x5699c9,
        "r": _0x4b069d,
        "v": VVV,
        "nowtime": _0x469bdb,
        "area": _0x1d0e71,
        "publickey": _0x469bdb,
        "key9": $.nnyd.yzm(_0x4c4952, _0x5699c9, _0x469bdb)
    };
    $.ajaxSetup({
        "cache": false
    });
    yzmFlag = 1;
     $.ajax({
        "type": "post",
        "url": _0x3bab81,
        "data": _0x28b87b,
        "dataType": "jsonp",
        "jsonp": "callback",
        "success": function (_0x165bcc) {
            // 省略....
       },
       "timeout": 5000,
        "error": function (_0x283219, _0x4d7b1f, _0xb6705a) {
            if (retrycount == 9) {
                jAlert("系统繁忙,请稍后重试!", "提示");
            } else {
            	// 省略....
            }
  });
  
function showTime() {
    const _0x2969c0 = new Date();
    return _0x2969c0.getTime();
}

发票查验接口所在代码:

            $("#checkfp").hide();
            $("#uncheckfp").show();
            var _0x2e8cad = 5;
            var _0x320d7e = $("#yzm").val().trim();
            _0x2e8cad = 6;
            var _0x53ae8a;
            var _0x2730af = '';
            _0x53ae8a = 8;
            var _0x23d188 = null;
            var _0x49fc66 = '';
            if (avai(fplx)) {
                if (fplx == '01' || fplx == '02' || fplx == '03' || fplx == '08' || fplx == '09' || fplx == '83' || fplx == '61') {
                    var _0x472fbd;
                    var _0x312fb7 = _0x57615f.indexOf('.');
                    _0x472fbd = 11;
                    if (_0x312fb7 > 0) {
                        var _0x1d99cd = _0x57615f.split('.');
                        if (_0x1d99cd[1] == '00' || _0x1d99cd[1] == '0') {
                            _0x57615f = _0x1d99cd[0];
                        } else {
                            if (_0x1d99cd[1].charAt(1) == '0') {
                                _0x57615f = _0x1d99cd[0] + '.' + _0x1d99cd[1].charAt(0);
                            }
                        }
                    }
                }

                if (_0x277a13.length === 20) {
                    _0x3e827f = _0x277a13.substring(0, 12);
                    _0x277a13 = _0x277a13.substring(12);
                }

                var _0x181fe9;
                var _0x1220aa = _0x5f273a[1];
                _0x181fe9 = 11;
                _0x49fc66 = _0x1220aa + "/vatQuery";
                _0x23d188 = {
                    'key1': _0x3e827f,
                    'key2': _0x277a13,
                    'key3': _0x57f456,
                    'key4': _0x57615f,
                    'fplx': fplx,
                    'yzm': _0x320d7e,
                    'yzmSj': yzmSj,
                    'index': jmmy,
                    'area': _0x3fb76c,
                    'key6': key6,
                    'publickey': yzmSj,
                    'key9': $.nnyd.cy(_0x3e827f, _0x277a13, yzmSj)
                };

                if (oldweb == 1) {
                    _0x49fc66 = _0x1220aa + "/invQuery";
                    _0x23d188 = {
                        'fpdm': _0x3e827f,
                        'fphm': _0x277a13,
                        'kprq': _0x57f456,
                        'fpje': _0x57615f,
                        'fplx': fplx,
                        'yzm': _0x320d7e,
                        'yzmSj': yzmSj,
                        'index': jmmy,
                        'area': _0x3fb76c,
                        'key6': key6,
                        'publickey': yzmSj,
                        'key9': $.nnyd.cy(_0x3e827f, _0x277a13, yzmSj)
                    };
                }

                delayMessage = "发票查验请求失败!";
                showTime();
                var _0x201fbe = 'N';
                $.ajax({
                    'type': "post",
                    'url': _0x49fc66,
                    'dataType': "jsonp",
                    'data': _0x23d188,
                    'jsonp': "callback",
                    // 省略....
                    });

重点算法分析

涉及的接口 $.nnyd.yzm$.nnyd.cyflw39
涉及的算法有:md5、RSA、Base64。

流程大体如下:

  1. 对发票代码、发票号号码、当前时间分别组合进行 Base64 ;
  2. 分别组合算MD5;
  3. 对MD5进行RSA加密; 92da1b9c13d7432c8eae5aa66e641262.js 文件
  4. 对RSA结果进行URL编码;

除此之外还有其它的东西:

  1. 统计识别失败次数;
  2. 检查环境;常见爬虫的特征检查。

参考及工具

  • https://juejin.cn/post/7045604250126123015
  • https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md#builders
  • https://astexplorer.net/
  • https://lzc6244.github.io/2021/07/27/Babel-AST%E5%85%A5%E9%97%A8.html
  • https://steakenthusiast.github.io/
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值