前言
在安全攻防戰場中,前端程式碼都是公開的,那麼對前端進行加密有意義嗎?可能大部分人的回答是,毫無意義
,不要自創加密演算法,直接用HTTPS吧。但事實上,即使不瞭解密碼學,也應知道是有意義
的,因為加密前
和解密後
的環節,是不受保護的。HTTPS只能保護傳輸層,此外別無用處。
而加密環節又分:
- 傳輸加密(對抗鏈路破解)
- 資料加密(對抗協議破解)
- 程式碼加密(隱藏演算法、反除錯...)
本文主要列舉一些我見到的,我想到的一些加密方式,其實確切的說,應該叫混淆,不應該叫加密。
那麼,程式碼混淆的具體原理是什麼?其實很簡單,就是去除程式碼中儘可能多的有意義的資訊,比如註釋、換行、空格、程式碼負號、變數重新命名、屬性重新命名(允許的情況下)、無用程式碼的移除等等。因為程式碼是公開的,我們必須承認沒有任何一種演算法可以完全不被破解,所以,我們只能儘可能增加攻擊者閱讀程式碼的成本。
語法樹AST混淆
在保證程式碼原本的功能性的情況下,我們可以對程式碼的AST按需進行變更,然後將變更後的AST在生成一份程式碼進行輸出,達到混淆的目的,我們最常用的uglify-js就是這樣對程式碼進行混淆的,當然uglify-js
的混淆只是主要進行程式碼壓縮,即我們下面講到的變數名混淆。
變數名混淆
將變數名混淆成閱讀比較難閱讀的字元,增加程式碼閱讀難度,上面說的uglify-js
進行的混淆,就是把變數混淆成了短名(主要是為了進行程式碼壓縮),而現在大部分安全方向的混淆,都會將其混淆成類16進位制變數名,效果如下:
var test = 'hello';
複製程式碼
混淆後:
var _0x7deb = 'hello';
複製程式碼
注意事項:
-
eval語法,eval函式中可能使用了原來的變數名,如果不對其進行處理,可能會執行報錯,如下:
var test = 'hello'; eval('console.log(test)'); 複製程式碼
如果不對eval中的console.log(test)進行關聯的混淆,則會報錯。不過,如果eval語法超出了靜態分析的範疇,比如:
var test = 'hello'; var variableName = 'test'; eval('console.log(' + variableName + ')'); 複製程式碼
這種咋辦呢,可能要進行遍歷AST找到其執行結果,然後在進行混淆,不過貌似成本比較高。
-
全域性變數的編碼,如果程式碼是作為SDK進行輸出的,我們需要儲存全域性變數名的不變,比如:
<script> var $ = function(id) { return document.getElementById(id); }; </script> 複製程式碼
$
變數是放在全域性下的,混淆過後如下:<script> var _0x6482fa = function(id) { return document.getElementById(id); }; </script> 複製程式碼
那麼如果依賴這一段程式碼的模組,使用
$('id')
呼叫自然會報錯,因為這個全域性變數已經被混淆了。
常量提取
將JS中的常量提取到陣列中,呼叫的時候用陣列下標的方式呼叫,這樣的話直接讀懂基本不可能了,要麼反AST處理下,要麼一步一步除錯,工作量大增。
以上面的程式碼為例:
var test = 'hello';
複製程式碼
混淆過後:
var _0x9d2b = ['hello'];
var _0xb7de = function (_0x4c7513) {
var _0x96ade5 = _0x9d2b[_0x4c7513];
return _0x96ade5;
};
var test = _0xb7de(0);
複製程式碼
當然,我們可以根據需求,將陣列轉化為二位陣列、三維陣列等,只需要在需要用到的地方獲取就可以。
常量混淆
將常量進行加密處理,上面的程式碼中,雖然已經是混淆過後的程式碼了,但是hello
字串還是以明文的形式出現在程式碼中,可以利用JS中16進位制編碼會直接解碼的特性將關鍵字的Unicode進行了16進位制編碼。如下:
var test = 'hello';
複製程式碼
結合常量提取得到混淆結果:
var _0x9d2b = ['\x68\x65\x6c\x6c\x6f'];
var _0xb7de = function (_0x4c7513) {
_0x4c7513 = _0x4c7513 - 0x0;
var _0x96ade5 = _0x9d2b[_0x4c7513];
return _0x96ade5;
};
var test = _0xb7de('0x0');
複製程式碼
當然,除了JS特性自帶的Unicode自動解析以外,也可以自定義一些加解密演算法,比如對常量進行base64編碼,或者其他的什麼rc4等等,只需要使用的時候解密就OK,比如上面的程式碼用base64編碼後:
var _0x9d2b = ['aGVsbG8=']; // base64編碼後的字串
var _0xaf421 = function (_0xab132) {
// base64解碼函式
var _0x75aed = function(_0x2cf82) {
// TODO: 解碼
};
return _0x75aed(_0xab132);
}
var _0xb7de = function (_0x4c7513) {
_0x4c7513 = _0x4c7513 - 0x0;
var _0x96ade5 = _0xaf421(_0x9d2b[_0x4c7513]);
return _0x96ade5;
};
var test = _0xb7de('0x0');
複製程式碼
運算混淆
將所有的邏輯運算子、二元運算子都變成函式,目的也是增加程式碼閱讀難度,讓其無法直接通過靜態分析得到結果。如下:
var i = 1 + 2;
var j = i * 2;
var k = j || i;
複製程式碼
混淆後:
var _0x62fae = {
_0xeca4f: function(_0x3c412, _0xae362) {
return _0x3c412 + _0xae362;
},
_0xe82ae: function(_0x63aec, _0x678ec) {
return _0x63aec * _0x678ec;
},
_0x2374a: function(_0x32487, _0x3a461) {
return _0x32487 || _0x3a461;
}
};
var i = _0x62fae._0e8ca4f(1, 2);
var j = _0x62fae._0xe82ae(i, 2);
var k = _0x62fae._0x2374a(i, j);
複製程式碼
當然除了邏輯運算子和二元運算子以外,還可以將函式呼叫、靜態字串進行類似的混淆,如下:
var fun1 = function(name) {
console.log('hello, ' + name);
};
var fun2 = function(name, age) {
console.log(name + ' is ' + age + ' years old');
}
var name = 'xiao.ming';
fun1(name);
fun2(name, 8);
複製程式碼
var _0x62fae = {
_0xe82ae: function(_0x63aec, _0x678ec) {
return _0x63aec(_0x678ec);
},
_0xeca4f: function(_0x92352, _0x3c412, _0xae362) {
return _0x92352(_0x3c412, _0xae362)
},
_0x2374a: 'xiao.ming',
_0x5482a: 'hello, ',
_0x837ce: ' is ',
_0x3226e: ' years old'
};
var fun1 = function(name) {
console.log(_0x62fae._0x5482a + name);
};
var fun2 = function(name, age) {
console.log(name + _0x62fae._0x837ce + age + _0x62fae._0x3226e);
}
var name = _0x62fae._0x2374a;
_0x62fae._0xe82ae(fun1, name);
_0x62fae._0xeca4f(fun2, name, 0x8);
複製程式碼
上面的例子中,fun1和fun2內的字串相加也會被混淆走,靜態字串也會被前面提到的字串提取
抽取到陣列中(我就是懶,這部分程式碼就不寫了)。
需要注意的是,我們每次遇到相同的運算子,需不需要重新生成函式進行替換,這就按個人需求了。
語法醜化
將我們常用的語法混淆成我們不常用的語法,前提是不改變程式碼的功能。例如for換成do/while,如下:
for (i = 0; i < n; i++) {
// TODO: do something
}
var i = 0;
do {
if (i >= n) break;
// TODO: do something
i++;
} while (true)
複製程式碼
動態執行
將靜態執行程式碼新增動態判斷,執行時動態決定運算子,干擾靜態分析。
如下:
var c = 1 + 2;
複製程式碼
混淆過後:
function _0x513fa(_0x534f6, _0x85766) { return _0x534f6 + _0x85766; }
function _0x3f632(_0x534f6, _0x534f6) { return _0x534f6 - _0x534f6; }
// 動態判定函式
function _0x3fa24() {
return true;
}
var c = _0x3fa24() ? _0x513fa(1, 2) : _0x3f632(1, 2);
複製程式碼
流程混淆
對執行流程進行混淆,又稱控制流扁平化,為什麼要做混淆執行流程呢?因為在程式碼開發的過程中,為了使程式碼邏輯清晰,便於維護和擴充套件,會把程式碼編寫的邏輯非常清晰。一段程式碼從輸入,經過各種if/else分支,順序執行之後得到不同的結果,而我們需要將這些執行流程和判定流程進行混淆,讓攻擊者沒那麼容易摸清楚我們的執行邏輯。
控制流扁平化又分順序扁平化、條件扁平化,
順序扁平化
顧名思義,將按順序、自上而下執行的程式碼,分解成數個分支進行執行,如下程式碼:
(function () {
console.log(1);
console.log(2);
console.log(3);
console.log(4);
console.log(5);
})();
複製程式碼
流程圖如下:
混淆過後程式碼如下:
(function () {
var flow = '3|4|0|1|2'.split('|'), index = 0;
while (!![]) {
switch (flow[index++]) {
case '0':
console.log(3);
continue;
case '1':
console.log(4);
continue;
case '2':
console.log(5);
continue;
case '3':
console.log(1);
continue;
case '4':
console.log(2);
continue;
}
break;
}
}());
複製程式碼
混淆過後的流程圖如下:
流程看起來扁
了。
條件扁平化
條件扁平化的作用是把所有if/else分支的流程,全部扁平到一個流程中,在流程圖中擁有相同的入口和出口。
如下面的程式碼:
function modexp(y, x, w, n) {
var R, L;
var k = 0;
var s = 1;
while(k < w) {
if (x[k] == 1) {
R = (s * y) % n;
}
else {
R = s;
}
s = R * R % n;
L = R;
k++;
}
return L;
}
複製程式碼
如上程式碼,流程圖是這樣的
控制流扁平化後程式碼如下:
function modexp(y, x, w, n) {
var R, L, s, k;
var next = 0;
for(;;) {
switch(next) {
case 0: k = 0; s = 1; next = 1; break;
case 1: if (k < w) next = 2; else next = 6; break;
case 2: if (x[k] == 1) next = 3; else next = 4; break;
case 3: R = (s * y) % n; next = 5; break;
case 4: R = s; next = 5; break;
case 5: s = R * R % n; L = R; k++; next = 1; break;
case 6: return L;
}
}
}
複製程式碼
混淆後的流程圖如下:
直觀的感覺就是程式碼變扁
了,所有的程式碼都擠到了一層當中,這樣做的好處在於在讓攻擊者無法直觀,或通過靜態分析的方法判斷哪些程式碼先執行哪些後執行,必須要通過動態執行才能記錄執行順序,從而加重了分析的負擔。
需要注意的是,在我們的流程中,無論是順序流程還是條件流程,如果出現了塊作用域的變數宣告(const/let),那麼上面的流程扁平化將會出現錯誤,因為switch/case內部為塊作用域,表示式被分到case內部之後,其他case無法取到const/let的變數宣告,自然會報錯。
不透明謂詞
上面的switch/case的判斷是通過數字(也就是謂詞)的形式判斷的,而且是透明的,可以看到的,為了更加的混淆視聽,可以將case判斷設定為表示式,讓其無法直接判斷,比如利用上面程式碼,改為不透明謂詞:
function modexp(y, x, w, n) {
var a = 0, b = 1, c = 2 * b + a;
var R, L, s, k;
var next = 0;
for(;;) {
switch(next) {
case (a * b): k = 0; s = 1; next = 1; break;
case (2 * a + b): if (k < w) next = 2; else next = 6; break;
case (2 * b - a): if (x[k] == 1) next = 3; else next = 4; break;
case (3 * a + b + c): R = (s * y) % n; next = 5; break;
case (2 * b + c): R = s; next = 5; break;
case (2 * c + b): s = R * R % n; L = R; k++; next = 1; break;
case (4 * c - 2 * b): return L;
}
}
}
複製程式碼
謂詞用a、b、c三個變數組成,甚至可以把這三個變數隱藏到全域性中定義,或者隱藏在某個陣列中,讓攻擊者不能那麼輕易找到。
指令碼加殼
將指令碼進行編碼,執行時 解碼 再 eval 執行如:
eval (…………………………..……………. ……………. !@#$%^&* ……………. .…………………………..……………. )
複製程式碼
但是實際上這樣意義並不大,因為攻擊者只需要把alert或者console.log就原形畢露了
改進方案:利用Function / (function(){}).constructor
將程式碼當做字串傳入,然後執行,如下:
var code = 'console.log("hellow")';
(new Function(code))();
複製程式碼
如上程式碼,可以對code進行加密混淆,例如aaencode,原理也是如此,我們舉個例子
alert("Hello, JavaScript");
複製程式碼
利用aaencode混淆過後,程式碼如下:
゚ω゚ノ= /`m´)ノ ~┻━┻ //*´∇`*/ ['_']; o=(゚ー゚) =_=3; c=(゚Θ゚) =(゚ー゚)-(゚ー゚); (゚Д゚) =(゚Θ゚)= (o^_^o)/ (o^_^o);(゚Д゚)={゚Θ゚: '_' ,゚ω゚ノ : ((゚ω゚ノ==3) +'_') [゚Θ゚] ,゚ー゚ノ :(゚ω゚ノ+ '_')[o^_^o -(゚Θ゚)] ,゚Д゚ノ:((゚ー゚==3) +'_')[゚ー゚] }; (゚Д゚) [゚Θ゚] =((゚ω゚ノ==3) +'_') [c^_^o];(゚Д゚) ['c'] = ((゚Д゚)+'_') [ (゚ー゚)+(゚ー゚)-(゚Θ゚) ];(゚Д゚) ['o'] = ((゚Д゚)+'_') [゚Θ゚];(゚o゚)=(゚Д゚) ['c']+(゚Д゚) ['o']+(゚ω゚ノ +'_')[゚Θ゚]+ ((゚ω゚ノ==3) +'_') [゚ー゚] + ((゚Д゚) +'_') [(゚ー゚)+(゚ー゚)]+ ((゚ー゚==3) +'_') [゚Θ゚]+((゚ー゚==3) +'_') [(゚ー゚) - (゚Θ゚)]+(゚Д゚) ['c']+((゚Д゚)+'_') [(゚ー゚)+(゚ー゚)]+ (゚Д゚) ['o']+((゚ー゚==3) +'_') [゚Θ゚];(゚Д゚) ['_'] =(o^_^o) [゚o゚] [゚o゚];(゚ε゚)=((゚ー゚==3) +'_') [゚Θ゚]+ (゚Д゚) .゚Д゚ノ+((゚Д゚)+'_') [(゚ー゚) + (゚ー゚)]+((゚ー゚==3) +'_') [o^_^o -゚Θ゚]+((゚ー゚==3) +'_') [゚Θ゚]+ (゚ω゚ノ +'_') [゚Θ゚]; (゚ー゚)+=(゚Θ゚); (゚Д゚)[゚ε゚]='\\'; (゚Д゚).゚Θ゚ノ=(゚Д゚+ ゚ー゚)[o^_^o -(゚Θ゚)];(o゚ー゚o)=(゚ω゚ノ +'_')[c^_^o];(゚Д゚) [゚o゚]='\"';(゚Д゚) ['_'] ( (゚Д゚) ['_'] (゚ε゚+(゚Д゚)[゚o゚]+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ ((゚ー゚) + (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+ (c^_^o)+ (゚Д゚)[゚ε゚]+(゚ー゚)+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚Θ゚)+ (c^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ ((゚ー゚) + (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ ((゚ー゚) + (o^_^o))+ (゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚ー゚)+ (c^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚Θ゚)+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ ((o^_^o) +(o^_^o))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) - (゚Θ゚))+ (o^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ (o^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ (c^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚ー゚)+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+ (゚Θ゚)+ (゚Д゚)[゚o゚]) (゚Θ゚)) ('_');
複製程式碼
這段程式碼看起來很奇怪,不像是JavaScript程式碼,但是實際上這段程式碼是用一些看似表情的符號,宣告瞭一個16位的陣列(用來表示16進位制位置),然後將code當做字串遍歷,把每個程式碼符號通過string.charCodeAt
取這個16位的陣列下標,拼接成程式碼。大概的意思就是把程式碼當做字串,然後使用這些符號的拼接代替這一段程式碼(可以看到程式碼裡有很多加號),最後,通過(new Function(code))('_')
執行。
仔細觀察上面這一段程式碼,把程式碼最後的('_')
去掉,在執行,你會直接看到原始碼,然後Function.constructor
存在(゚Д゚)
變數中,感興趣的同學可以自行檢視。
除了aaencode,jjencode原理也是差不多,就不做解釋了,其他更霸氣的jsfuck,這些都是對程式碼進行加密的,這裡就不詳細介紹了。
反除錯
由於JavaScript自帶debugger
語法,我們可以利用死迴圈性的debugger
,當頁面開啟除錯皮膚的時候,無限進入除錯狀態。
定時執行
在程式碼開始執行的時候,使用setInterval
定時觸發我們的反除錯函式。
隨機執行
在程式碼生成階段,隨機在部分函式體中注入我們的反除錯函式,當程式碼執行到特定邏輯的時候,如果除錯皮膚在開啟狀態,則無限進入除錯狀態。
內容監測
由於我們的程式碼可能已經反除錯了,攻擊者可以會將程式碼拷貝到自己本地,然後修改,除錯,執行,這個時候就需要新增一些檢測進行判定,如果不是正常的環境執行,那讓程式碼自行失敗。
程式碼自檢
在程式碼生成的時候,為函式生成一份Hash,在程式碼執行之前,通過函式 toString 方法,檢測程式碼是否被篡改
function module() {
// 篡改校驗
if (Hash(module.toString()) != 'JkYxnHlxHbqKowiuy') {
// 程式碼被篡改!
}
}
複製程式碼
環境自檢
檢查當前指令碼的執行環境,例如當前的URL是否在允許的白名單內、當前環境是否正常的瀏覽器。
如果為Nodejs環境,如果出現異常環境,甚至我們可以啟動木馬,長期跟蹤。
廢程式碼注入
插入一些永遠不會發生的程式碼,讓攻擊者在分析程式碼的時候被這些無用的廢程式碼混淆視聽,增加閱讀難度。
廢邏輯注入
與廢程式碼相對立的就是有用的程式碼,這些有用的程式碼代表著被執行程式碼的邏輯,這個時候我們可以收集這些邏輯,增加一段判定來決定執行真邏輯還是假邏輯,如下:
(function(){
if (true) {
var foo = function () {
console.log('abc');
};
var bar = function () {
console.log('def');
};
var baz = function () {
console.log('ghi');
};
var bark = function () {
console.log('jkl');
};
var hawk = function () {
console.log('mno');
};
foo();
bar();
baz();
bark();
hawk();
}
})();
複製程式碼
可以看到,所有的console.log都是我們的執行邏輯,這個時候可以收集所有的console.log,然後製造假判定來執行真邏輯程式碼,收集邏輯注入後如下:
(function(){
if (true) {
var foo = function () {
if ('aDas' === 'aDas') {
console.log('abc');
} else {
console.log('def');
}
};
var bar = function () {
if ('Mfoi' !== 'daGs') {
console.log('ghi');
} else {
console.log('def');
}
};
var baz = function () {
if ('yuHo' === 'yuHo') {
console.log('ghi');
} else {
console.log('abc');
}
};
var bark = function () {
if ('qu2o' === 'qu2o') {
console.log('jkl');
} else {
console.log('mno');
}
};
var hawk = function () {
if ('qCuo' !== 'qcuo') {
console.log('jkl');
} else {
console.log('mno');
}
};
foo();
bar();
baz();
bark();
hawk();
}
})();
複製程式碼
判定邏輯中生成了一些字串,在沒有使用字串提取的情況下,這是可以通過程式碼靜態分析來得到真實的執行邏輯的,或者我們可以使用上文講到的動態執行來決定執行真邏輯,可以看一下使用字串提取和變數名編碼後的效果,如下:
var _0x6f5a = [
'abc',
'def',
'caela',
'hmexe',
'ghi',
'aaeem',
'maxex',
'mno',
'jkl',
'ladel',
'xchem',
'axdci',
'acaeh',
'log'
];
(function (_0x22c909, _0x4b3429) {
var _0x1d4bab = function (_0x2e4228) {
while (--_0x2e4228) {
_0x22c909['push'](_0x22c909['shift']());
}
};
_0x1d4bab(++_0x4b3429);
}(_0x6f5a, 0x13f));
var _0x2386 = function (_0x5db522, _0x143eaa) {
_0x5db522 = _0x5db522 - 0x0;
var _0x50b579 = _0x6f5a[_0x5db522];
return _0x50b579;
};
(function () {
if (!![]) {
var _0x38d12d = function () {
if (_0x2386('0x0') !== _0x2386('0x1')) {
console[_0x2386('0x2')](_0x2386('0x3'));
} else {
console[_0x2386('0x2')](_0x2386('0x4'));
}
};
var _0x128337 = function () {
if (_0x2386('0x5') !== _0x2386('0x6')) {
console[_0x2386('0x2')](_0x2386('0x4'));
} else {
console[_0x2386('0x2')](_0x2386('0x7'));
}
};
var _0x55d92e = function () {
if (_0x2386('0x8') !== _0x2386('0x8')) {
console[_0x2386('0x2')](_0x2386('0x3'));
} else {
console[_0x2386('0x2')](_0x2386('0x7'));
}
};
var _0x3402dc = function () {
if (_0x2386('0x9') !== _0x2386('0x9')) {
console[_0x2386('0x2')](_0x2386('0xa'));
} else {
console[_0x2386('0x2')](_0x2386('0xb'));
}
};
var _0x28cfaa = function () {
if (_0x2386('0xc') === _0x2386('0xd')) {
console[_0x2386('0x2')](_0x2386('0xb'));
} else {
console[_0x2386('0x2')](_0x2386('0xa'));
}
};
_0x38d12d();
_0x128337();
_0x55d92e();
_0x3402dc();
_0x28cfaa();
}
}());
複製程式碼
求值陷阱
除了注入執行邏輯以外,還可以埋入一個隱蔽的陷阱,在一個永不到達
且無法靜態分析
的分支裡,引用該函式,正常使用者不會執行,而 AST 遍歷求值時,則會觸發陷阱!陷阱能幹啥呢?
- 日誌上報,及時瞭解情況
- 在本地儲存隱寫特徵,長期跟蹤
- 釋放CSRF漏洞,獲得破解者的詳細資訊
- 開啟自殺程式(頁面崩潰、死迴圈、耗盡記憶體等)
加殼干擾
在程式碼用eval包裹,然後對eval引數進行加密,並埋下陷阱,在解碼時插入無用程式碼,干擾顯示,大量換行、註釋、字串等大量特殊字元,導致顯示卡頓。
結束
大概我想到的混淆就包括這些,單個特性使用的話,混淆效果一般,各個特性組合起來用的話,最終效果很明顯,當然這個看個人需求,畢竟混淆是個雙刃劍,在增加了閱讀難度的同時,也增大了指令碼的體積,降低了程式碼的執行效率。