锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

发票查验平台JS混淆文件反编译原理分析兼谈obfuscator反编译步骤分析

时间:2023-05-04 05:07:00 703f振动变送器

发票检查平台JS混淆文件反编译原理分析obfuscator分析反编译步骤

    • 一. 特征
    • 二. 分类几种加密形式
    • 三、类型二分析
    • 四、类型一分析
    • 五、总结

发票检查平台JS混淆文件反编译原理分析obfuscator分析反编译步骤之所以带上 发票检查平台,因为,这个 发票检查平台的大多数JS是经过 obfuscator加密混淆的处理更适合我自己练习。 obfuscator混淆反编译的原试分析的情况下,混淆反编译的原理、方法和步骤动态调试分析。

obfuscator混淆的特征和几种格式。

为了让读者了解分析过程,主要有几个例子js例子文件。eab23.js,validate.js 属于一种类型。 eaPqe3.js, VM649.js 属于另一种类型,

阅读本文前请注意以下说明:

1. 主要用于本文 eab23.js 和 eaPqe3.js 中的 对变量名进行分析说明,否则,仅靠文字说明,很难理解。
2. 本文属于静态反编译JS对于反编译的文件,js文件,再次替换加载到网站时,会报错,查明原因: 可能是js语法有问题,我是对的js语法不精通,所以不是为了js语法问题,反编译js修改文件。
3. 本人使用EditPlus文本编辑软件,是的js文件进行 UTF8 -> ANSI,或 ANSI -> UTF8 格式转换。
4. 请在使用本文方法之前先将js文件存成ANSI编码格式,UTF8格式反编译可能出错,待反编译js使用成功后EditPlus转成UTF8格式。
5. 本文适用:无格式化(美化)js文件。假如你的js格式化(美化),请先处理为无格式化(美化)js为了便于阅读,反编译的文件正在格式化(美化)。

一. 特征

先看eab23.js文件头部定义的大数组,大概是这样的:(不是这种风格的头,不适合本文)

var _0x4d2a = ['A8kXwSouyG', 'i2zWzg0', 'W6Smpta', 'jmoCyNb0', 'amkyBSoCW5y', 'W47dTLGTW4JcRNi', 'i3L6Bv91BNvZzv9PBwC', 'pYZdOsG', 'W4bvhtFdRXFcQGa', 'W67dRCojoG', 'mYldOsVdPG', 'rK9Hy0S', 'WORcPmkTW7y', ... ... , 'EgnNC2i', 'W4mboWug', 'WPbecc0', 'CCo7W5/cLa', 'z2v0vgLTzq']; var _0x5e21 = function(_0x4d2ad2, _0x5e214d) {     _0x4d2ad2 = _0x4d2ad2 - 0x0; 

再看eaPqe3.js文件头部定义的大数组,大概是这样的:(不是这种风格的头,不适合本文)

var _0xc03f = ['YnRuLXBpbmsgYnRuLXdoaXRl', 'd2hpdGU=', 'bm8tc2tpbiBza2luLTEgc2tpbi0yIHNraW4tMw==', 'YnRuLXByaW1hcnkgYnRuLXdoaXRl', 'c2tpbg==', 'bm8tc2tpbg==', 'c2tpbi0x', 'MnwxfDB8NHwz', 'LmJ0bg==', ... ... ... , 'YnRuLXN1Y2Nlc3M=', 'YnRuLWluZm8=', 'YnRuLXdhcm5pbmc=', 'YnRuLWRhbmdlcg==', 'bm8tYm9yZGVyIG1hcmdpbi0x', 'LnNpZGViYXItc2hvcnRjdXRzIC5idG4=']; (function(_0x4cd309, _0x19a697) {     var _0x4b7cf2 = function(_0x341b9a) {         while (--_0x341b9a) {             _0x4cd309['push'](_0x4cd309['shift();         }     }; 

有了这种风格的头,基本上可以肯定是 obfuscator混淆了。

这个变量在反编译中_0x4d2a或_0xc03f 定义成:ABC 大数组,

大数组数据正确加载:

eab23.js:定义的数组 _0x4d2a 可用于数组。

eaPqe3.js: 定义的数组 _0xc03f 通过以下函数,运算,成为可正常使用的数组。

(function(_0x4cd309, _0x19a697) {  ... ... ...     _0x328575(); } (_0xc03f, 0x114)); 

二. 分类几种加密形式

根据大数组,结束 ]; 不同的,可以判断哪种类型的加密,总共可以总结出两种类型,四种加密函数加密。

  1. 类型一

eab23.js:

... , 'EgnNC2i', 'W4mboWug', 'WPbecc0', 'CCo7W5/cLa', 'z2v0vgLTzq']; var _0x5e21 = function(_0x4d2ad2, _0x5e214d) {     _0x4d2ad2 = _0x4d2ad2 - 0x0; 
  1. 类型二

eaPqe3.js:

 ... , 'YnRuLXN1Y2Nlc3M=', 'YnRuLWluZm8=', 'YnRuLXdhcm5pbmc=', 'YnRuLWRhbmdlcg==', 'bm8tYm9yZGVyIG1hcmdpbi0x', 'LnNpZGViYXItc2hvcnRjdXRzIC5idG4=']; (function(_0x4cd309, _0x19a697) {     var _0x4b7cf2 = function(_0x341b9a) {         while (--_0x341b9a) {             _0x4cd309['push'](_0x4cd309['shift();         }     }; 

以上2种,区别在于,一个是紧跟var 一个紧跟 (function

首先分析类型2,为什么先分析类型2,因为,当我第一次分析时,是根据类型2,在实际反编译中js在文件中,发现了类型1,因此,从类型2开始分析。

三、类型二分析

eaPqe3.js 文件中有许多类似的内容:

'uMpwO': _0x4a4a('0x12'), 'Tgkgp': _0x4a4a('0x13'), 'jfBvM': function(_0x140b95, _0x146867) {     return _0x140b95(_0x146867); }, 'buukD': _0x4a4a('0x14'), 'DHwRy': function(_0x1ec21f, _0xf21777) {     return _0x1ec21f   _0xf21777; }, 

VM649.js 文件中有许多类似的内容:

     'BkoQV': _0x3d8c('0x5', '*#dB'),     'RZESQ': _0x3d8c('0x6', 'x5j6'),     'KIBiL': _0x3d8c('0x7', 'eHOs'),     'CqoPQ': function(_0x34bb7a, _0x101b41) {         return _0x34bb7a   _0x101b41;     },     'LLFZo': function(_0x48b5cf, _0x152e42) {         return _0x48b5cf == _0x152e42;     },

eaPqe3.js 和 VM649.js 区别是 _0x4a4a 和 _0x3d8c 参数一个是 一个,一个是两个,当涉及到反编译时,解密算法是不同的。

这个变量在反编译中_0x4a4a或_0x3d8c 定义成:CDE

_0x4a4a解密算法,在 eaPqe3.js中,是函数

var _0x4a4a = function(_0x130e4f, _0x2a0ae8) {     _0x130e4f = _0x130e4f - 0x0; ... ... ... 

_0x3d8c解密算法,在 VM649.js中,是函数

var _0x3d8c = function(_0x17ddb8, _0x230eed) { _0x17ddb8 = _0x17ddb8 - 0x0; ... ... ... 
 
  

二者的算法,有点区别,_0x3d8c 解密算法比 _0x4a4a 解密算法 多了

        for (var _0x4dde59 = 0x0; _0x4dde59 < 0x100; _0x4dde59++) {
            _0xa3b65e[_0x4dde59] = _0x4dde59;
        }
        for (_0x4dde59 = 0x0; _0x4dde59 < 0x100; _0x4dde59++) {
            _0x190871 = (_0x190871 + _0xa3b65e[_0x4dde59] + _0x1310de['charCodeAt'](_0x4dde59 % _0x1310de['length'])) % 0x100;
            _0x2884aa = _0xa3b65e[_0x4dde59];
            _0xa3b65e[_0x4dde59] = _0xa3b65e[_0x190871];
            _0xa3b65e[_0x190871] = _0x2884aa;
        }
        _0x4dde59 = 0x0;
        _0x190871 = 0x0;
        for (var _0x1da148 = 0x0; _0x1da148 < _0x4ae65f['length']; _0x1da148++) {
            _0x4dde59 = (_0x4dde59 + 0x1) % 0x100;
            _0x190871 = (_0x190871 + _0xa3b65e[_0x4dde59]) % 0x100;
            _0x2884aa = _0xa3b65e[_0x4dde59];
            _0xa3b65e[_0x4dde59] = _0xa3b65e[_0x190871];
            _0xa3b65e[_0x190871] = _0x2884aa;
            _0x520f67 += String['fromCharCode'](_0x4ae65f['charCodeAt'](_0x1da148) ^ _0xa3b65e[(_0xa3b65e[_0x4dde59] + _0xa3b65e[_0x190871]) % 0x100]);
        }
        return _0x520f67;

这部分。

经过解密后,

eaPqe3.js 文件中,变成:

    'uMpwO': 'function *\\( *\\)',
    'Tgkgp': '\\+\\+ *(?:_0x(?:[a-f0-9]){4,6}|(?:\\b|\\d)[a-z0-9]{1,4}(?:\\b|\\d))',
    'jfBvM': function(_0x140b95, _0x146867) {
        return _0x140b95(_0x146867);
    },
    'buukD': 'init',
    'DHwRy': function(_0x1ec21f, _0xf21777) {
        return _0x1ec21f + _0xf21777;
    },

VM649.js 文件中,变成:

    'BkoQV': 'vOTPz',
    'RZESQ': '1|3|6|4|5|2|0',
    'KIBiL': '7|9|13|5|8|0|14|3|11|1|4|10|12|6|2',
    'CqoPQ': function(_0x34bb7a, _0x101b41) {
        return _0x34bb7a + _0x101b41;
    },
    'LLFZo': function(_0x48b5cf, _0x152e42) {
        return _0x48b5cf == _0x152e42;
    },

在反编译时,这个变量_0x4a4a或_0x3d8c 的还原属于CDE解密替换过程。

反编译变量 EFG 的确定;

eaPqe3.js 文件中

var _0x435762 = {
        'QPfhe': _0x4a4a('0x2'),
        'lZKCQ': _0x4a4a('0x3'),
        'fTmMu': _0x4a4a('0x4'),
        'yBOsQ': _0x4a4a('0x5'),
        'QuKgT': _0x4a4a('0x6'),
        'pjvTz': _0x4a4a('0x7'),
        'XsKTp': _0x4a4a('0x8'),
        'ZJHnU': function(_0x2b15c4, _0x420291) {
            return _0x2b15c4 in _0x420291;
        },
        'DPxyT': _0x4a4a('0x9'),
... ... ...

VM649.js 文件中

var _0x23666d = {
        'Qhafz': function(_0x3e4d7e, _0x34ba06) {
            return _0x3e4d7e !== _0x34ba06;
        },
        'McUzn': _0x3d8c('0x0', '9Vq0'),
        'gNHuo': 'rFroG<;|t

这个变量 _0x435762 和 _0x23666d 就是反编译变量 EFG

EFG公式化及函数参数的计算 EFG公式化一般有2种类型,一个是常数,一个是函数公式。

将:

'buukD': 'init',
'DHwRy': function(_0x1ec21f, _0xf21777) {
    return _0x1ec21f + _0xf21777;
},

规整成:

buukD = 'init'
DHwRy = _0x1ec21f + _0xf21777

即可。

eaPqe3.js 文件中

      ace[_0x4a4a('0x9')][_0x435762[_0x4a4a('0x37')]] = ace[_0x4a4a('0x9')][_0x435762[_0x4a4a('0x38')]] || ace[_0x4a4a('0x9')][_0x435762[_0x4a4a('0x39')]];

经过替换后

       ace['vars']['non_auto_fixed'] = ace['vars']['android'] || ace['vars']['ios_safari'];

VM649.js 文件中

        if (_0x23666d[_0x3d8c('0xea', 'xXyQ')](_0x23666d[_0x3d8c('0xeb', 'f])s')], _0x23666d[_0x3d8c('0xec', 'Y(()')])) {

经过替换后

        if ('hWLpV' === 'hWLpV') {

至此js文件的反编译基本完成。

四、类型一分析

eab23.js 文件中,有很多这样相似的内容:

var _0x529034 = function(_0x5e14f6, _0x57ca28, _0x3e86a7, _0x840509, _0xed3c8) {
    return _0x41fe(_0x840509 - -0x2a0, _0x57ca28);
};
var _0x5b09eb = function(_0x11aaba, _0xd13acb, _0x136ee0, _0x1117f6, _0xb199c5) {
    return _0x41fe(_0x1117f6 - -0x2a0, _0xd13acb);
};
var _0x4fdc5c = function(_0x1f407e, _0x434aad, _0xc703f, _0x2949dc, _0x105708) {
    return _0x41fe(_0x2949dc - -0x2a0, _0x434aad);
};
var _0x43e376 = function(_0x414ff3, _0x1b482d, _0x2ea534, _0x59f6ba, _0x2c242b) {
    return _0x41fe(_0x59f6ba - -0x2a0, _0x1b482d);
};

validate.js 文件中,有很多这样相似的内容:

var _0x59b365 = function(_0x2e8c3d, _0x5f4d89, _0x4274ee, _0x4755bd, _0x1fdebd) {
    return _0x2edd(_0x2e8c3d - -0x1c9, _0x5f4d89);
};
var _0x145334 = function(_0x522d00, _0x40ed98, _0x1d37ad, _0x382dd0, _0x5373f5) {
    return _0x1355(_0x522d00 - -0x1c9, _0x40ed98);
};
var _0x540d8c = function(_0x494c38, _0x1b5d36, _0x1ccd46, _0x3c5c10, _0x5c4f79) {
    return _0x1355(_0x494c38 - -0x1c9, _0x1b5d36);
};
var _0x3285ed = function(_0x9ff47b, _0x323bd7, _0x31fff6, _0x376f73, _0x3d4f26) {
    return _0x1355(_0x9ff47b - -0x1c9, _0x323bd7);
};

eab23.js 和 validate.js 区别是 -0x2a0 和 -0x1c9 。

而 _0x41fe, _0x2edd, _0x1355 涉及 2个解密函数,

eab23.js 文件中

var _0x5e21 = function(_0x4d2ad2, _0x5e214d) {
    _0x4d2ad2 = _0x4d2ad2 - 0x0;
... ... ...
var _0x41fe = function(_0x4d2ad2, _0x5e214d) {
    _0x4d2ad2 = _0x4d2ad2 - 0x0;
... ... ...

validate.js 文件中

var _0x2edd = function(_0x35ac23, _0x2edd1b) {
    _0x35ac23 = _0x35ac23 - 0x0;
... ... ...
var _0x1355 = function(_0x35ac23, _0x2edd1b) {
    _0x35ac23 = _0x35ac23 - 0x0;
... ... ...

这个变量 _0x5e21 和 _0x2edd 反编译定义变量 CDE1
这个变量 _0x41fe 和 _0x1355 反编译定义变量 CDE2

_0x59b365, _0x145334, _0x59b365,等等 反编译中定义成: GHI

GHI也需要公式化及函数参数的计算 GHI公式化只有一种是函数公式。

例如:

  var _0x59b365 = function(_0x2e8c3d, _0x5f4d89, _0x4274ee, _0x4755bd, _0x1fdebd) {
        return _0x2edd(_0x2e8c3d - -0x1c9, _0x5f4d89);

处理成

_0x59b365 = _0x2edd(_0x2e8c3d - -0x1c9, _0x5f4d89)

与第二种类型不同的是,这里先替换 _0x59b365 GHI变量,最后,计算 _0x2edd CDE1或CDE2 函数。

eab23.js 文件中

   if (yzmWait == 0x0) {
        $(_0x542286( - 0x29f, -0x26e, -0x29a, -0x29a, -0x2a3))[_0x529034( - 0x2b7, '0ING', -0x27d, -0x299, -0x2de)]();
        $(_0x529034( - 0x2c2, '8@t@', -0x2d5, -0x298, -0x27a))[_0x4fdc5c( - 0x260, 'M!xk', -0x296, -0x297, -0x2c1)]();
        yzmWait = 0x3c;

经过替换后

if (yzmWait == 0x0) {
    $('#yzm_unuse_img')['hide']();
    $('#yzm_img')['show']();
    yzmWait = 0x3c;

validate.js 文件中

var _0x236b0b;
var _0x2d9f7a = new Date();
var _0xb2ea2 = _0x2d9f7a[_0x569190( - 0x2ce, -0x246, -0x262, -0x24d, -0x24f)]();
var _0x640a43 = _0xb2ea2[_0x524f06( - 0x1f4, -0x23d, '53V7', -0x20d, -0x24e)]();
var _0x3ff9b5 = _0x640a43[_0xd32495( - 0x218, -0x1d6, '@lGP', -0x2bf, -0x24d)](0x2);

经过替换后

var _0x236b0b;
var _0x2d9f7a = new Date();
var _0xb2ea2 = _0x2d9f7a['getFullYear']();
var _0x640a43 = _0xb2ea2['toString']();
var _0x3ff9b5 = _0x640a43['substring'](0x2);

至此js文件的反编译基本完成。

五、总结

需要注意的几点

  1. 对于字符串中,带括号 ( 的处理,要注意,如果不考虑括号,会出错。
  2. EFG公式化后,参数的分割是一个问题,主要是 括号的配对问题
  3. 反编译出来的js没有格式化(美化),替换后的多余的代码也没有删除。
  4. 本文只针对发票查验平台的js举例分析,对于其他js没有进行过测试。
  5. 本分析开源代码是 delphi 编写。只能在delphi10 下编译,delphi7下因为,js中return decodeURIComponent(_0x4828e5); delphi7中对应的函数有问题,但delphi10没有问题。
  6. 本文所反编译出来的js源码,适合对其中的算法进行算法分析,以利于移植成其他编程语言。

delphi 源码中

 procedure TDeObJs.AutoDeObJsCodeFile();
var
    strDeJsFile: string;
begin
    ReadJsFile(FJsFileName);
    FABC := ABC_GetABC();
    Log.i('ABC:' + FABC);
    if not string.IsNullOrEmpty(FABC) then
    begin
        ABC_SetListData();
        if FCryptType = 1 then
        begin
            FCDE := CDE_GetCDE();
            Log.i('CDE:' + FCDE);
            if not string.IsNullOrEmpty(FCDE) then
            begin
                CDE_ReplaceFuncData();
            end;

            Repeat
                FEFG := '';
                FEFG := EFG_GetEFG();
                Log.i('EFG:' + FEFG);
                if not string.IsNullOrEmpty(FCDE) and not string.IsNullOrEmpty(FEFG) then
                begin
                    EFG_SetListFuncData();
                    EFG_ReplaceFuncData();
                end;
            Until FEFG = '';
        end;

        if FCryptType = 2 then
        begin
            CDE_SetCDE12();

            Repeat
                FGHI := '';
                FGHI := GHI_GetGHI();
                Log.i('GHI:' + FGHI);
                if not string.IsNullOrEmpty(FCDE1) and not string.IsNullOrEmpty(FCDE2) and not string.IsNullOrEmpty(FGHI) then
                begin
                    GHI_SetListFuncData();
                    GHI_ReplaceFuncData();
                end;
            Until FGHI = '';

            if not string.IsNullOrEmpty(FCDE1) then
            begin
                CDE12_ReplaceFuncData(FCDE1);
            end;

            if not string.IsNullOrEmpty(FCDE2) then
            begin
                CDE12_ReplaceFuncData(FCDE2);
            end;

        end;


    end;
    strDeJsFile := ExtractFilePath(FJsFileName) + 'De_' + ExtractFileName(FJsFileName);
    WriteDeJsFile(strDeJsFile);

end;

源码地址 GItHUB 上 ,
源码下载
最后本人郑重说明,所反编译出来的js源码不能直接再次动态加载运行,其中,涉及JS语法问题,所以,如果你反编译出来的js文件不可使用,不要抱怨…,但本文所给出来的分析方法是完全可行的。

锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章