发票查验平台JS混淆文件反编译原理分析兼谈obfuscator反编译步骤分析
时间:2023-05-04 05:07:00
发票检查平台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));
二. 分类几种加密形式
根据大数组,结束 ]; 不同的,可以判断哪种类型的加密,总共可以总结出两种类型,四种加密函数加密。
- 类型一
eab23.js:
... , 'EgnNC2i', 'W4mboWug', 'WPbecc0', 'CCo7W5/cLa', 'z2v0vgLTzq']; var _0x5e21 = function(_0x4d2ad2, _0x5e214d) { _0x4d2ad2 = _0x4d2ad2 - 0x0;
- 类型二
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文件的反编译基本完成。
五、总结
需要注意的几点
- 对于字符串中,带括号 ( 的处理,要注意,如果不考虑括号,会出错。
- EFG公式化后,参数的分割是一个问题,主要是 括号的配对问题。
- 反编译出来的js没有格式化(美化),替换后的多余的代码也没有删除。
- 本文只针对发票查验平台的js举例分析,对于其他js没有进行过测试。
- 本分析开源代码是 delphi 编写。只能在delphi10 下编译,delphi7下因为,js中return decodeURIComponent(_0x4828e5); delphi7中对应的函数有问题,但delphi10没有问题。
- 本文所反编译出来的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文件不可使用,不要抱怨…,但本文所给出来的分析方法是完全可行的。