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));
根据大数组,末尾 ]; 的不同,可以判断出来是哪种类型加密,一共可以归纳出来2种类型,4种加密函数加密。
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
先分析类型二,为什么先分析类型二,因为,本人最开始分析的时候,是按照类型二,而在实际反编译每个js文件时,发现有类型一,所以,先从,类型二开始分析。
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 参数一个是 1个,一个是2个,这个地方涉及反编译时,解密算法的不同。
在反编译时,这个变量_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文件的反编译基本完成。
需要注意的几点
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文件不可使用,不要抱怨…,但本文所给出来的分析方法是完全可行的。