python爬蟲之破解javascript-obfuscator的混淆加密


接上一篇有關前端加密達到反爬的文章,是不是覺得用了javascript-obfuscator 就很安全了,那還真不一定啊,還是那句,反爬與反反爬一直在斗爭,沒有誰能絕對的壓制另一方,只有使用者技術的高低。以下就是一個大神的針對javascript-obfuscator庫的破解。

 

本篇文章轉載於 : https://mp.weixin.qq.com/s/aZmuc3DwtQvRluvKCKDedw

 

死代碼與花指令

 

在開始之前,我們先了解一下這種「在代碼中插入大量無用代碼以混淆視聽」的混淆方式吧。這種混淆方式有兩種叫法,或者說是兩種做法,它們分別是「死代碼」和「花指令」。

 

死代碼

死代碼一開始是被用來描述一些人寫代碼時寫出的沒有用到的代碼的,為了編譯后的文件盡可能地小,編譯器通常會對死代碼進行移除處理。

而在不知道什么時候開始,死代碼被安全工作者們用來作為一種混淆機制,以將代碼量變得極為龐大,使進行逆向工程的人難以找到主要邏輯

但死代碼有個很明顯的特征:它雖然看着代碼量很大,但實際卻完全不會在程序的正常代碼中被調用

如果你有興趣的話,可以對一些包含了死代碼的代碼進行聚類分析,你會發現死代碼和正常代碼之間涇渭分明,正常代碼都是互相關聯着的,而死代碼卻是孤零零的一塊或者多塊,並且正常代碼還完全不會與死代碼產生關聯。

花指令

花指令是以前被大量運用在木馬、病毒的免殺上的一種反反匯編手段,花指令中的“指令”通常指的是匯編中的 jmpcall 之類的調用、跳轉指令,而攻擊者們會將這些指令巧妙地插入到惡意代碼的執行邏輯中,使得靜態分析工具在分析到這個位置時無法正常反匯編。

花指令曾經的目的主要有兩個,一個是使殺毒軟件無法自動分析出惡意代碼,達到瞞天過海的效果;一個是給安全工作者在分析惡意軟件時設下層層阻攔,使安全工作者需要花費更多的時間才能理清代碼邏輯,達到拖延時間的效果。

同樣是不知道什么時候開始,花指令也被安全工作者們用來作為一種混淆機制。在這種應用場景下,花指令和死代碼其實很類似,它們都是用了大量無用代碼來混淆視聽,但花指令和死代碼最大的區別就是,花指令的無用代碼是會被混在正常代碼中進行執行的。相比於死代碼而言,花指令會造成一些性能損失,但同時也會讓進行逆向工程的人更加難以分析。

但花指令也不是無懈可擊的,為了不影響程序正常的執行,花指令不能干擾到程序的原有邏輯,舉個例子:

a = 1
b = 2
# 花指令開始,對變量進行了一通操作
a += 1
a += b
# 花指令結束,又把變量的值給變回去了
a -= ba -= 1 c(a, b)

 

所以其實只要你能看出這一通操作沒有任何意義,花指令也自然就沒法影響到你了。

小結

不管是死代碼還是花指令,其實都只需要我們仔細觀察就能將其剔除,它們並不是什么很難搞的東西,見得多了之后你甚至都不需要細看就能快速排除掉一些明顯不是正常代碼的部分,畢竟常見的混淆器中用到的代碼其實重合度是很高的,同樣的套路見多了之后自然很容易分辨。

更何況,代碼混淆是需要考慮性能損耗的,對方不可能為了防你逆向工程而無止盡地對代碼進行混淆,要不然人家正常業務也沒辦法進行了。

 

實戰

 

基礎知識了解完了,我們來進入實戰環節。

首先,我們打開 https://obfuscator.io/,這是 Obfuscator 的網頁版本,可以快速在網頁上進行混淆參數的配置,並且一鍵生成並導出混淆后的代碼。

順帶一提,Obfuscator 是一款非常優秀的 JavaScript 代碼混淆工具,但代碼結構都是固定的,如果想要更好的混淆效果,可將混淆后的代碼進行修改,從而讓別人更難分析和調試

現在,我們用它給出的樣例代碼來進行混淆。樣例代碼如下:

 

// Paste your JavaScript code here
function hi() {
 console.log("Hello World!");
}
hi();

 

注意,我們需要勾選以下選項:

 

 

 

 

 

這三個選項的效果分別是:

•Compact code將代碼中的換行符全部去掉,使得代碼看起來毫無結構性。也就是所謂的代碼壓縮。•Self Defending在代碼中插入自檢代碼,用來干擾逆向工程的人對代碼進行格式化、變量重命名操作,如果代碼被格式化了就會無法正常運行。•Dead Code Injection在代碼中插入死代碼,也就是本文的重點。

配置好參數后點擊 Obfuscate 按鈕,即可生成按配置混淆后的代碼,我生成的代碼是這樣的(長圖警告):

 

 

 

 

 

 

 

 

可以看到,原本短短的幾行代碼,在經過混淆后變成了這么多。而且這個代碼還是經過壓縮的,完全看不出層級。

當然,這個代碼是可以正常運行的,我們用NodeJS跑一遍看看:

 

 

 

 

看起來混淆並沒有影響到正常的代碼邏輯,我們再把這一坨代碼給格式化一下看看:

 

 

 

 

果不其然,格式化后的代碼直接就沒法運行了。在平時我們遇到這種情況時要記住,原代碼可以正常運行但格式化之后不行,那么這個報錯肯定是跟格式化代碼有關系的,至於它報錯的內容具體是啥意思其實並不重要。

那么怎么辦呢?我們來靜態分析一下它的代碼就知道了。

先來看看第一段代碼:

 

 

 

 

 

 

 

定義了一個數組並初始化,顯然不可能造成什么問題。

接着看看第二段代碼(長圖警告):

 

 

 

 

這是一個自執行的函數,沒有返回值。但是注意,它的第一個實參是 _0x2831,也就是之前定義的那個數組,對應的形參是 _0x528cba。我們可以根據這個來判斷它對 _0x2831 做了些什么。

現在我們來一段一段地分析這第二大段代碼中的每一段代碼,首先是第一段代碼:

var _0x1b0e99 = function(_0x5beb46) {
       while (--_0x5beb46) {
           _0x528cba['push'](_0x528cba['shift']());
       }
   };

 

 

這么短的代碼相信大家都應該能看懂,是對 _0x528cba 進行 shift 操作,而 _0x528cba 是自執行函數的形參,實參是 _0x2831。換句話說,它就是對實參進行 shift 操作。不過這里它只是聲明,並沒有調用,所以還不會去改變實參。

然后是第二段代碼和第三段代碼,這里因為代碼量太大就不整個貼出來了,之前已經貼過完整代碼了。

第二段代碼是定義了一個函數,而第三段代碼則是調用這個函數,因此我們主要分析這第二段代碼即可。

如果你不會分析,可以跳過它聲明的語句,它真正開始執行的是這行代碼:

 

var _0x53c9b6 = _0x1d1bc5['updateCookie']();

 

不要看它這段代碼里面既有 setCookie,又有 getCookie,其實它跟 cookie 沒有半毛錢關系,它只是一個 object 的 key 值,僅此而已。

因此,我們只需要關注它有沒有改變實參,有沒有改變全局變量。整個代碼全局變量只有一個 _0x2831,它也是實參,也就是說只需要關心這個 _0x2831 即可。

通過上面的分析我們可以知道,它的第一段代碼定義了一個函數,確實改變了實參,但是沒有調用。因此,我們得找找看它在哪里被調用的,直接搜函數名 _0x1b0e99,定位到這里:

_0x4c51d1(_0x1b0e99, _0x283138);

 

 

這時,_0x1b0e99是第一個實參,第二個實參 _0x283138 則是自執行函數的形參,它對應的實參是 0x1bf,是一個整形的數值。這下,我們只需要看看 _0x4c51d1 的函數聲明即可:

 

var _0x4c51d1 = function(_0x3d5743, _0x3c21e0) {
    _0x3d5743(++_0x3c21e0);
};

 

這么大一段代碼,其實真正改變實參的只有這里。我們將這兩行代碼結合一下,就會變成這樣:

 

_0x1b0e99(++_0x283138);

 

所以,第二大段代碼這個自執行函數中的第二段代碼只有這一句是真正改變實參的地方,其他的全部是垃圾代碼,直接刪除即可。刪除后,這個自執行函數就變成了這樣:

(function(_0x528cba, _0x283138) {
   var _0x1b0e99 = function(_0x5beb46) {
       while (--_0x5beb46) {
           _0x528cba['push'](_0x528cba['shift']());
       }
   };
   _0x1b0e99(++_0x283138);
}(_0x2831, 0x1bf));

 

 

這樣看起來就清爽多了,運行試試看:

 

 

 

還是報同樣的錯誤,接着往下分析第三段代碼(長圖警告):

 

 

 

這是一個函數,可以看到,引用全局變量 _0x2831 的只有這一行:

var _0x1b0e99 = _0x2831[_0x528cba];

 

這是一個賦值語句,但是不會改變 _0x2831 這個變量,因此我們只需要重點關注它的返回值 _0x1b0e99 就好。

再來看看它最后賦值的地方,有兩處,一處在 if 語句里面:

_0x1b0e99 = _0x1b0e['SmClCt'](_0x1b0e99, _0x283138);

 

另外一處在 else 語句里面:

_0x1b0e99 = _0x309846;

 

那它到底是執行的那行代碼呢,來看看 if 語句的條件:

_0x309846 === undefined

 

繼續分析上面的代碼:

var _0x309846 = _0x1b0e['jZzRvK'][_0x528cba];

 

以及 _0x1b0e['jZzRvK'] 最近的定義的地方:

_0x1b0e['jZzRvK'] = {};

 

這樣就清楚了,_0x309846 === undefined 這個條件是成立的,所以 _0x1b0e99 最后賦值的地方是這里:

_0x1b0e99 = _0x1b0e['SmClCt'](_0x1b0e99, _0x283138);

 

函數 _0x1b0e['SmClCt'] 賦值在這里:

 

_0x1b0e['SmClCt'] = _0x5beb46;

 

而實參,則是在 _0x5beb46 函數之前定義過,因此只需要這兩行代碼提到 _0x5beb46 函數之后即可。注意,這里為了清楚一點,可以這樣操作:

if (_0x1b0e['DVdkAf'] === undefined) {
............................
       _0x1b0e['SmClCt'] = _0x5beb46;
       _0x1b0e['jZzRvK'] = {};
       _0x1b0e['DVdkAf'] = !![];
}
_0x1b0e99 = _0x1b0e['SmClCt'](_0x1b0e99, _0x283138);
return _0x1b0e99;

 

根據上面的思路繼續分析 hi 函數,同樣也注入了一些不會改變全局變量 _0x2831 的垃圾代碼,真正有效的代碼只有這一行:

console[_0x1b0e('0xc', '^G6o')](_0x1b0e('0xd', 'Bi36'));

 

刪除掉這些垃圾代碼后,我們就可以知道這份代碼原來長什么樣了。

 

 

 

BOOM!結果就是這么三行代碼:

 

function hi() {
    console[_0x1b0e('0xc', '^G6o')](_0x1b0e('0xd', 'Bi36'));
}

 

最后我們再運行一下試試看吧:

 

 

沒有報錯,代碼成功運行了~

總結

碰到大段代碼時不要慌,先試着分析一下,把無用代碼剔除掉之后其實最后剩下的可能就只有幾行而已。當然實際情況中你往往會碰到混淆參數更復雜的代碼,你需要讓程序來幫助你進行分析,而想要寫出這種程序又會使用到 AST 操作,所以說掌握 AST 操作還是很有必要的,建議學習一下。

 

 

 

以上為轉載的內容全文,怎么樣,一樣可以解出來,傻眼了吧,但是,也不是說javascript-obfuscator就完全沒用了,當然有用,必須有用,只是看怎么用,你想一下,如果讓這個混淆加密定期換一套呢?我換的速度難道還能比你解密的速度慢嗎?然后就算你能解密出來,我再加幾道關卡呢?讓你解的費勁,惡心你,擊垮你的信心,等等的,所以用處是很大的。只是看你怎么用了

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM