当今互联网时代,JavaScript已经成为了web前端开发的重点技术之一。其中,JavaScript代码的安全性问题一直是关注的焦点。为了保护JavaScript代码的安全性,很多人对其进行加密处理,众所周知,对于单纯的加密算法,通过反向工程或逆向分析也能够破解。在此情况下,JavaScript代码混淆技术成为了一种应对加密破解的有效措施。
提示:以下是本篇文章正文内容,下面案例可供参考
JavaScript混淆是一种将JavaScript代码转换成难以阅读和理解的形式的过程。这可以通过压缩、重命名变量和函数、删除注释和空格、使用特殊字符和添加无用代码等方式实现。混淆后的代码在计算机中仍然可以运行,但在人类眼中很难理解和修改。这可以有效地保护JavaScript代码免受未经授权的访问和篡改。
二、js混淆作用
JavaScript混淆是保护JavaScript代码和数据安全的重要技术。为了防止爬虫通过分析js内容,轻易的还原出网站的加密逻辑,而做出的安全防御手段之一,常常将一个逻辑简单的可能十几行算法代码思路,通过变量混淆替换、增加冗余无效代码、增加无限debugger反调试功能、增加多层的套娃式if~else代码、以及埋陷阱等混淆成几千行的js代码,给人第一印象难读,对入门分析者造成恐惧心理。
三、JavaScript 混淆技术主要有以下几种:
-
变量混淆 将带有含意的变量名、方法名、常量名随机变为无意义的类乱码字符串,降低代码可读性,如转成单个字符或十六进制字符串。
-
字符串混淆 将字符串阵列化集中放置、并可进行 MD5 或 Base64 加密存储,使代码中不出现明文字符串,这样可以避免使用全局搜索字符串的方式定位到入口点。
-
属性加密 针对 JavaScript 对象的属性进行加密转化,隐藏代码之间的调用关系。
-
控制流平坦化 打乱函数原有代码执行流程及函数调用关系,使代码逻变得混乱无序。
-
僵尸代码 随机在代码中插入无用的僵尸代码、僵尸函数,进一步使代码混乱。
-
调试保护 基于调试器特性,对当前运行环境进行检验,加入一些强制调试 debugger 语句,使其在调试模式下难以顺利执行 JavaScript 代码。
-
多态变异 使 JavaScript 代码每次被调用时,将代码自身即立刻自动发生变异,变化为与之前完全不同的代码,即功能完全不变,只是代码形式变异,以此杜绝代码被动态分析调试。
-
锁定域名 使 JavaScript 代码只能在指定域名下执行。
-
反格式化 如果对 JavaScript 代码进行格式化,则无法执行,导致浏览器假死。
-
特殊编码 将 JavaScript 完全编码为人不可读的代码,如表情符号、特殊表示内容等等
四、常见的混淆
URL:JS加密,JS 安全加密, AAencode 加密 —在线加密 (sojson.com)
1)、OB混淆
OB
混淆全称 Obfuscator,Obfuscator 其实就是混淆的意思,官网:https://obfuscator.io/ ,其作者是一位叫 Timofey Kachalov 的俄罗斯JavaScript
开发工程师,早在 2016 年就发布了第一个版本。
OB 混淆具有以下特征:
1、一般由一个大数组或者含有大数组的函数、一个自执行函数、解密函数和加密后的函数四部分组成;
2、函数名和变量名通常以 _0x
或者 0x
开头,后接 1~6 位数字或字母组合;
3、自执行函数,进行移位操作,有明显的 push、shift 关键字;
例如在上面的例子中,_0x3ed1()
方法就定义了一个大数组,自执行函数里有 push、shift 关键字,主要是对大数组进行移位操作,_0x2e12()
就是解密函数,hi()
就是加密后的函数
。
(function(_0x1cbb42, _0x27ca96) {
var _0x35e2ac = _0x2e12
, _0x22ba7c = _0x1cbb42();
while (!![]) {
try {
var _0x108d6f = -parseInt(_0x35e2ac(0x1e6)) / 0x1 + parseInt(_0x35e2ac(0x1eb)) / 0x2 + parseInt(_0x35e2ac(0x1e5)) / 0x3 * (-parseInt(_0x35e2ac(0x1ef)) / 0x4) + parseInt(_0x35e2ac(0x1ed)) / 0x5 + parseInt(_0x35e2ac(0x1ee)) / 0x6 + parseInt(_0x35e2ac(0x1e9)) / 0x7 * (parseInt(_0x35e2ac(0x1e7)) / 0x8) + -parseInt(_0x35e2ac(0x1ea)) / 0x9 * (parseInt(_0x35e2ac(0x1ec)) / 0xa);
if (_0x108d6f === _0x27ca96)
break;
else
_0x22ba7c['push'](_0x22ba7c['shift']());
} catch (_0x55c1fe) {
_0x22ba7c['push'](_0x22ba7c['shift']());
}
}
}(_0x3ed1, 0x4cc24));
function _0x2e12(_0x57166b, _0x567174) {
var _0x3ed14f = _0x3ed1();
return _0x2e12 = function(_0x2e123b, _0xa39d85) {
_0x2e123b = _0x2e123b - 0x1e5;
var _0x532ba1 = _0x3ed14f[_0x2e123b];
return _0x532ba1;
}
,
_0x2e12(_0x57166b, _0x567174);
}
function _0x3ed1() {
var _0x5dda6c = ['1115632iBYQbo', '54eYZLaO', '767914jEsBIU', '744360nxCbIq', '13950Avaqin', '2375400wOmjhe', '60CEfoUI', '20127fpZoZw', '239744ExCnkT', '16trzBpq', 'log'];
_0x3ed1 = function() {
return _0x5dda6c;
}
;
return _0x3ed1();
}
function hi() {
var _0xf7fbc1 = _0x2e12;
console[_0xf7fbc1(0x1e8)]('Hello\x20World!');
}
hi();
2)、eval混淆
- 定义: eval(string) ,可计算某个字符串,并执行其中的的 JavaScript 代码。 有返回值
- 举例
// eval()函数使用举例
a=eval('(function xxx(){return "hello world"})()');
console.log(a) // hello world
// 将上边的eval字体进行base64加密
xxx=btoa('(function xxx(){return "hello world"})()');
console.log(xxx) // KGZ1bmN0aW9uIHh4eCgpe3JldHVybiAiaGVsbG8gd29ybGQifSkoKQ==
// 然后混淆的时候,可能就会放入加密的东西
console.log(eval(atob('KGZ1bmN0aW9uIHh4eCgpe3JldHVybiAiaGVsbG8gd29ybGQifSkoKQ==\n')));
- 缺点 :想要运行字符串的值,只能通过明文的eval()函数执行,然后里面的字符串可能是多层加密的,我们通常在eval处下断点。
想要运行字符串的值,只能通过明文的eval()函数执行,然后里面的字符串可能是多层加密的,我们通常在eval处下断点。
3)、AAEncode混淆(表情包加密)
- js支持Unicode,因此支持Unicode里收录的所有国家语言,如果我们有的语言和我们定义的变量长得很相似(例如0和o),那么因此有了这种混淆。
示例:console.log("hello world!");
- 解决方法:
- 控制台,增删代码,让其报错,即可在虚拟机(VM)处看见源码
- 在线调试,在 AAEncode 代码第一行下断点,然后一步一步执行,最终也会在虚拟机(VM)里看到源码
4)、jsfuck混淆
- 思想: jsfuck源于一门编程语言brainfuck,其主要的思想就是只使用8种特定的符号来编写代码。而jsfuck也是沿用了这个思想,它仅仅使用6种符号来编写代码。它们分别是
(、)、+、[、]、!
- 示例:console.log("hello world!");
-
解决方法:
- 如果是非虚拟机的数字/字符串加密使用解密工具:JS 不可逆加密,JS fuck 加密、JavaScript在线加密
- 上述解决不了只能一段一段手动控制台复制得结果
- 如果是虚拟机加密,让其强制报错
5)、JJEncode混淆
- 示例:
alert("Hello, JavaScript" );
6)、sojson混淆
它可以为js代码添加加密混淆
, 压缩成一行
, 防止格式化
, 死代码注入
等属性。
- 加密混淆:将代码中的所有标识符 (变量名, 函数名) 替换成没有意义的以下划线开始的十六进制数字,如 _0x546d, _0x28239d等。
- 压缩成一行: 将原本多行的代码都写到一行。
- 防止格式化:如果调试者希望借助 IDE 对代码进行格式化, 代码将无法正常运行, 陷入卡死状态。
- 死代码注入:如果调试这对代码进行了格式化, 陷入的卡死状态就是注入的死代码导致的
示例:
JS原文:
(function(w, d) {
w.update = "2023年07月16日15:24:29更新";
d.info = "这个是一个本站对JavaScript 脚本的一个最牛加密,兼容性适中,解密难度极大";
})(window, document);
js混淆后:
var version_ = 'jsjiami.com.v7';
function _0x56f8(_0x239c5e, _0x4296e5) {
var _0x342bf4 = _0x342b();
return _0x56f8 = function(_0x56f859, _0x432900) {
_0x56f859 = _0x56f859 - 0xeb;
var _0x2e680a = _0x342bf4[_0x56f859];
if (_0x56f8['OGfwbD'] === undefined) {
var _0x2c3003 = function(_0x116e29) {
var _0x4e35db = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';
var _0x32ef41 = ''
, _0x3c2092 = '';
for (var _0x4f8508 = 0x0, _0x28e3e8, _0x3c5243, _0x56e51d = 0x0; _0x3c5243 = _0x116e29['charAt'](_0x56e51d++); ~_0x3c5243 && (_0x28e3e8 = _0x4f8508 % 0x4 ? _0x28e3e8 * 0x40 + _0x3c5243 : _0x3c5243,
_0x4f8508++ % 0x4) ? _0x32ef41 += String['fromCharCode'](0xff & _0x28e3e8 >> (-0x2 * _0x4f8508 & 0x6)) : 0x0) {
_0x3c5243 = _0x4e35db['indexOf'](_0x3c5243);
}
for (var _0x49fa53 = 0x0, _0x151cea = _0x32ef41['length']; _0x49fa53 < _0x151cea; _0x49fa53++) {
_0x3c2092 += '%' + ('00' + _0x32ef41['charCodeAt'](_0x49fa53)['toString'](0x10))['slice'](-0x2);
}
return decodeURIComponent(_0x3c2092);
};
var _0x1ca9f8 = function(_0x57c253, _0x3c21bc) {
var _0x2a0b7d = [], _0x4cb5fb = 0x0, _0x4d43fe, _0x99a74b = '';
_0x57c253 = _0x2c3003(_0x57c253);
var _0x149167;
for (_0x149167 = 0x0; _0x149167 < 0x100; _0x149167++) {
_0x2a0b7d[_0x149167] = _0x149167;
}
for (_0x149167 = 0x0; _0x149167 < 0x100; _0x149167++) {
_0x4cb5fb = (_0x4cb5fb + _0x2a0b7d[_0x149167] + _0x3c21bc['charCodeAt'](_0x149167 % _0x3c21bc['length'])) % 0x100,
_0x4d43fe = _0x2a0b7d[_0x149167],
_0x2a0b7d[_0x149167] = _0x2a0b7d[_0x4cb5fb],
_0x2a0b7d[_0x4cb5fb] = _0x4d43fe;
}
_0x149167 = 0x0,
_0x4cb5fb = 0x0;
for (var _0x244006 = 0x0; _0x244006 < _0x57c253['length']; _0x244006++) {
_0x149167 = (_0x149167 + 0x1) % 0x100,
_0x4cb5fb = (_0x4cb5fb + _0x2a0b7d[_0x149167]) % 0x100,
_0x4d43fe = _0x2a0b7d[_0x149167],
_0x2a0b7d[_0x149167] = _0x2a0b7d[_0x4cb5fb],
_0x2a0b7d[_0x4cb5fb] = _0x4d43fe,
_0x99a74b += String['fromCharCode'](_0x57c253['charCodeAt'](_0x244006) ^ _0x2a0b7d[(_0x2a0b7d[_0x149167] + _0x2a0b7d[_0x4cb5fb]) % 0x100]);
}
return _0x99a74b;
};
_0x56f8['Dtwoep'] = _0x1ca9f8,
_0x239c5e = arguments,
_0x56f8['OGfwbD'] = !![];
}
var _0x2898ad = _0x342bf4[0x0]
, _0x5dd41f = _0x56f859 + _0x2898ad
, _0x3ba330 = _0x239c5e[_0x5dd41f];
return !_0x3ba330 ? (_0x56f8['lYcCWy'] === undefined && (_0x56f8['lYcCWy'] = !![]),
_0x2e680a = _0x56f8['Dtwoep'](_0x2e680a, _0x432900),
_0x239c5e[_0x5dd41f] = _0x2e680a) : _0x2e680a = _0x3ba330,
_0x2e680a;
}
,
_0x56f8(_0x239c5e, _0x4296e5);
}
(function(_0x57cb6, _0x4314f7, _0x12aca8, _0x16b392, _0x1875bd, _0x432797, _0x394cbe) {
return _0x57cb6 = _0x57cb6 >> 0x1,
_0x432797 = 'hs',
_0x394cbe = 'hs',
function(_0x7b10f6, _0x1a79f7, _0xd8cd20, _0x3f28ea, _0x385c4a) {
var _0x2eb8f7 = _0x56f8;
_0x3f28ea = 'tfi',
_0x432797 = _0x3f28ea + _0x432797,
_0x385c4a = 'up',
_0x394cbe += _0x385c4a,
_0x432797 = _0xd8cd20(_0x432797),
_0x394cbe = _0xd8cd20(_0x394cbe),
_0xd8cd20 = 0x0;
var _0x7b2355 = _0x7b10f6();
while (!![] && --_0x16b392 + _0x1a79f7) {
try {
_0x3f28ea = parseInt(_0x2eb8f7(0xfa, 'Iw8r')) / 0x1 + -parseInt(_0x2eb8f7(0xf8, 'mhb8')) / 0x2 + parseInt(_0x2eb8f7(0xf0, 'lK2M')) / 0x3 * (-parseInt(_0x2eb8f7(0xf5, 'og&u')) / 0x4) + -parseInt(_0x2eb8f7(0xec, '$CGx')) / 0x5 + parseInt(_0x2eb8f7(0xed, 'T^LQ')) / 0x6 + -parseInt(_0x2eb8f7(0xf4, 'sW9l')) / 0x7 * (-parseInt(_0x2eb8f7(0xf3, 'g]XJ')) / 0x8) + -parseInt(_0x2eb8f7(0xf1, 'sW9l')) / 0x9 * (-parseInt(_0x2eb8f7(0xeb, 'QMK%')) / 0xa);
} catch (_0x495302) {
_0x3f28ea = _0xd8cd20;
} finally {
_0x385c4a = _0x7b2355[_0x432797]();
if (_0x57cb6 <= _0x16b392)
_0xd8cd20 ? _0x1875bd ? _0x3f28ea = _0x385c4a : _0x1875bd = _0x385c4a : _0xd8cd20 = _0x385c4a;
else {
if (_0xd8cd20 == _0x1875bd['replace'](/[eWyXFGnhNPAERSqkCKLrg=]/g, '')) {
if (_0x3f28ea === _0x1a79f7) {
_0x7b2355['un' + _0x432797](_0x385c4a);
break;
}
_0x7b2355[_0x394cbe](_0x385c4a);
}
}
}
}
}(_0x12aca8, _0x4314f7, function(_0x28f1b0, _0x2ae9e5, _0x343433, _0x14fb7b, _0x8bf826, _0x11755b, _0x537a44) {
return _0x2ae9e5 = '\x73\x70\x6c\x69\x74',
_0x28f1b0 = arguments[0x0],
_0x28f1b0 = _0x28f1b0[_0x2ae9e5](''),
_0x343433 = '\x72\x65\x76\x65\x72\x73\x65',
_0x28f1b0 = _0x28f1b0[_0x343433]('\x76'),
_0x14fb7b = '\x6a\x6f\x69\x6e',
(0x134e3c,
_0x28f1b0[_0x14fb7b](''));
});
}(0x18e, 0x6c4ae, _0x342b, 0xc9),
_0x342b) && (version_ = _0x342b);
(function(_0x1c9e5b, _0x5351dd) {
var _0x275977 = _0x56f8
, _0xd0dfd9 = {
'PQflt': '这个是一个本站对JavaScript\x20脚本的一个最牛加密,兼容性适中,解密难度极大'
};
_0x1c9e5b['update'] = _0x275977(0xf7, 'sW9l'),
_0x5351dd[_0x275977(0xfc, '7k)1')] = _0xd0dfd9['PQflt'];
}(window, document));
function _0x342b() {
var _0x407a63 = (function() {
return [version_, 'EFyjWshNKjGFSiraCqmXeinP.cEoPAWmggk.RvL7==', 'pSonW4VdLxvJdSosW4md', 'WQaKmSosnSkIWPruFSoYW6u', 'ivvAWQCpWPNdUmo0W63cQq', 'jNxdLSkfnvNcPmon', 'WQqRnCoDmCkfWQHrsSohW7q'].concat((function() {
return ['WO/dShRcRCkiz8oaW6VcU8os', 'ymkZWOKevGZcL8orW7ddO8klua', 'WQaJnSoz5BM2W7pdHEACSchcKUAvUfxcKXHdWQhcUqldV+AzUEAuGG', 'W6NcSNBdRConWOlcSgDjd8kby8kg', 'WQ5Ww8k8iSkHkmklDd4b', 'WRSeWQtdTCoaWPaGW4pcLCodySky', 'iIXhWRGRbt3dOW'].concat((function() {
return ['ohVcPJu', 'W7alF3jzWO5wdSkAWRu', 'W6pdRLhdRr4eW550f8kPW6r2W68', 'W5PrWOSgW6mNv1NcNYdcUZm', 'WRSgWQxdVSkDW4K7W4lcV8ov', 'W5ldR3ahD8kCzmo6WPtcSSoPW6OQ'];
}()));
}()));
}());
_0x342b = function() {
return _0x407a63;
}
;
return _0x342b();
}
;var version_ = 'jsjiami.com.v7';
总结
以上,我们就介绍了接口加密技术和 JavaScript 的压缩、混淆和加密技术,知己知彼方能百战不殆,了解了原理,我们才能更好地去实现 JavaScript 的逆向。
参考文献
- https://www.ruanyifeng.com/blog/2017/09/asmjs_emscripten.html
- https://juejin.im/post/5cfcb9d25188257e853fa71c#heading-23
- https://www.jianshu.com/p/326594cbd4fa
- https://github.com/javascript-obfuscator/javascript-obfuscator
- https://obfuscator.io/
- https://www.sojson.com/jjencode.html
- http://dean.edwards.name/packer/