AST反混淆實戰(zhàn)(一)

AST簡介

抽象語法樹Abstract Syntax Tree,AST),或簡稱語法樹(Syntax tree),是源代碼語法結構的一種抽象表示。它以樹狀的形式表現編程語言的語法結構润樱,樹上的每個節(jié)點都表示源代碼中的一種結構。之所以說語法是“抽象”的羡棵,是因為這里的語法并不會表示出真實語法中出現的每個細節(jié)壹若。

使用到的第三方庫

esprima:代碼轉語法樹
estraverse:AST遍歷輔助庫(深度優(yōu)先搜索),包含遍歷及替換節(jié)點的功能
escodegen:語法樹轉代碼
jsdom:獲取模擬瀏覽器window對象(很多js會檢測環(huán)境)

相關文件

混淆源文件待上傳后改鏈接
反混淆腳本待上傳后改鏈接

反混淆前后對比

-反混淆前

    _0x1dc76b[_0x1972('0xed')](typeof window, _0x1dc76b[_0x1972('0x229')]) && _0x1dc76b[_0x1972('0x147')](typeof window[_0x1972('0x38e')], _0x1dc76b[_0x1972('0x229')]) ? _0x1dc76b[_0x1972('0x44c')](typeof window[_0x1972('0x38e')][_0x1972('0x131')], _0x1dc76b[_0x1972('0x299')]) ? /phantomjs/[_0x1972('0x348')](window[_0x1972('0x38e')][_0x1972('0x131')][_0x1972('0x39e')]()) ? ss = _0x1dc76b[_0x1972('0x64')](ss, _0x1dc76b[_0x1972('0x13')](0x1, 0xf)) : window[_0x1972('0x38e')][_0x1972('0x301')] ? ss = _0x1dc76b[_0x1972('0x64')](ss, _0x1dc76b[_0x1972('0x311')](0x1, 0xf)) : ss = _0x1dc76b[_0x1972('0x64')](ss, _0x1dc76b[_0x1972('0x36c')](0x1, 0x1)) : _0x1dc76b[_0x1972('0x44c')](typeof window[_0x1972('0x38e')][_0x1972('0x3bd')], _0x1dc76b[_0x1972('0x21f')]) ? ss = _0x1dc76b[_0x1972('0x64')](ss, _0x1dc76b[_0x1972('0x36c')](0x1, 0xf)) : !window[_0x1972('0x38e')][_0x1972('0x3bd')] ? ss = _0x1dc76b[_0x1972('0x64')](ss, _0x1dc76b[_0x1972('0x323')](0x1, 0xf)) : ss = _0x1dc76b[_0x1972('0x28c')](ss, _0x1dc76b[_0x1972('0x323')](0x1, 0xf)) : ss = _0x1dc76b[_0x1972('0x27c')](ss, _0x1dc76b[_0x1972('0x323')](0x1, 0xf));
    _0x1dc76b[_0x1972('0x44c')](typeof window, _0x1dc76b[_0x1972('0x229')]) && _0x1dc76b[_0x1972('0x26')](typeof window[_0x1972('0x433')], _0x1dc76b[_0x1972('0x229')]) ? _0x1dc76b[_0x1972('0x20e')](_0x1dc76b[_0x1972('0x1d8')](window[_0x1972('0x433')][_0x1972('0x1da')], window[_0x1972('0x433')][_0x1972('0x176')]), 0x0) ? ss = _0x1dc76b[_0x1972('0x27c')](ss, _0x1dc76b[_0x1972('0x323')](0x1, 0x2)) : ss = _0x1dc76b[_0x1972('0x370')](ss, _0x1dc76b[_0x1972('0x323')](0x1, 0x10)) : ss = _0x1dc76b[_0x1972('0x34d')](ss, _0x1dc76b[_0x1972('0x323')](0x1, 0x10));
    _0x1dc76b[_0x1972('0x26')](typeof document, _0x1dc76b[_0x1972('0x229')]) && _0x1dc76b[_0x1972('0x26')](typeof document[_0x1972('0x208')], fff) ? !document[_0x1972('0x208')](_0x1dc76b[_0x1972('0x454')]) ? ss = _0x1dc76b[_0x1972('0x2c6')](ss, _0x1dc76b[_0x1972('0x323')](0x1, 0x3)) : ss = _0x1dc76b[_0x1972('0x3da')](ss, _0x1dc76b[_0x1972('0x323')](0x1, 0x11)) : ss = _0x1dc76b[_0x1972('0x3da')](ss, _0x1dc76b[_0x1972('0x1a1')](0x1, 0x11));
    _0x1dc76b[_0x1972('0x119')](typeof window, _0x1dc76b[_0x1972('0x229')]) ? _0x1dc76b[_0x1972('0x119')](typeof window[_0x1972('0x410')], _0x1dc76b[_0x1972('0x21f')]) ? ss = _0x1dc76b[_0x1972('0x3da')](ss, _0x1dc76b[_0x1972('0x1a1')](0x1, 0x4)) : ss = _0x1dc76b[_0x1972('0x3da')](ss, _0x1dc76b[_0x1972('0x18d')](0x1, 0x12)) : _0x1dc76b[_0x1972('0x32e')](typeof window, _0x1dc76b[_0x1972('0x229')]) && _0x1dc76b[_0x1972('0x363')](typeof window[_0x1972('0x197')], _0x1dc76b[_0x1972('0x21f')]) ? ss = _0x1dc76b[_0x1972('0x3da')](ss, _0x1dc76b[_0x1972('0x18d')](0x1, 0x12)) : ss = _0x1dc76b[_0x1972('0x3da')](ss, _0x1dc76b[_0x1972('0x18d')](0x1, 0x12));
image.png

反混淆后

typeof window == _0x1dc76b['WfJBW'] && typeof window['navigator'] == _0x1dc76b['WfJBW'] ? typeof window['navigator']['userAgent'] == _0x1dc76b['XaIwC'] ? /phantomjs/['test'](window['navigator']['userAgent']['toLowerCase']()) ? ss = ss | 1 << 15 : window['navigator']['webdriver'] ? ss = ss | 1 << 15 : ss = ss | 1 << 1 : typeof window['navigator']['appVersion'] == _0x1dc76b['DWvnG'] ? ss = ss | 1 << 15 : !window['navigator']['appVersion'] ? ss = ss | 1 << 15 : ss = ss | 1 << 15 : ss = ss | 1 << 15;
    typeof window == _0x1dc76b['WfJBW'] && typeof window['screen'] == _0x1dc76b['WfJBW'] ? 970 + 1680 > 0 ? ss = ss | 1 << 2 : ss = ss | 1 << 16 : ss = ss | 1 << 16;
    typeof document == _0x1dc76b['WfJBW'] && typeof document['getElementById'] == fff ? !document['getElementById'](_0x1dc76b['djBAH']) ? ss = ss | 1 << 3 : ss = ss | 1 << 17 : ss = ss | 1 << 17;
    typeof window == _0x1dc76b['WfJBW'] ? typeof window['callPhantom'] == _0x1dc76b['DWvnG'] ? ss = ss | 1 << 4 : ss = ss | 1 << 18 : typeof window == _0x1dc76b['WfJBW'] && typeof window['_phantom'] == _0x1dc76b['DWvnG'] ? ss = ss | 1 << 18 : ss = ss | 1 << 18;
image.png

說明

//類似這種調用方法皂冰,根據傳入參數算真實值(做了反混淆店展,會處理)
_0x1972('0x229')
//運算符封裝為方法的,都會進行反混淆
    _0x4d51e8['GNVJM'] = function (_0x399108, _0x2aefef) {
        return _0x399108 == _0x2aefef;
    };
//某個key等于字符串秃流,(這種沒處理赂蕴,因為不是很影響讀碼了,就沒處理舶胀。)
_0x4d51e8['WfJBW'] = 'object';

逆向過程

1.下載核心js文件概说,內容混淆嚴重。先做反混淆嚣伐。分析代碼糖赔,拆解為幾種混淆規(guī)則,針對每種規(guī)則反混淆轩端。這個文件梳理主要有3放典、4種混淆規(guī)則,這里做了主要的2種,已可以閱讀七七八八了(主要就是幾個混淆奋构,然后又互相嵌套壳影,AST遍歷是深度優(yōu)先遍歷,會從內到外依次反混淆弥臼,可以處理嵌套)宴咧。

  • 通過某個方法運算得到的string
var _0x1972 = function (_0x129385, _0x5b4f79) {
// ...............
//.............某些計算規(guī)則,返回根據傳入參數計算出來的string醋火。
//.................
return s;
}
//多次重復出現類似的代碼
_0x1972('0x49')

//=================================================================================================================================
//反混淆核心代碼
var ast = esprima.parseScript(content.toString());
//_0x1972方法在前57行悠汽,這個通過eval函數加載這個方法
eval(content.split('\n').slice(0, 57).join(''));
ast = estraverse.replace(ast, {
    enter: function (node, parent) {
        if (node.type === "CallExpression") {
             //因為混淆文件會變化,變量名會變芥驳,函數名稱長度為7的只有_0x1972這個方法
            if (node.callee.name && node.callee.name.length === 7 && node.arguments.length === 1) {
                //如果是這個函數,則直接用計算后的結果替換
                let data = eval(`${node.callee.name}('${node.arguments[0].value}')`);
                if (data && data !== 'undefined' && data !== 'Number' && data !== 'NaN') {
                    return {
                        type: "Literal",
                        value: data,
                        raw: `'${data}'`
                    };
                }
            }
        }
    },
});

//=================================================================================================================================
//反混淆后_0x1972('0x49')變?yōu)橛嬎愫蟮闹礧fJBW
WfJBW
  • 運算符混淆
_0x615b4b['uUzso'] = function (_0x59febf, _0x3b43de) {
    return _0x59febf & _0x3b43de;
};
//中間有一次重命名過程
var _0x2bb501 = _0x615b4b;
//多次出現的類似代碼
_0x2bb501['uUzso'](_0xd72c68, 8)

//反混淆核心代碼
ast = estraverse.replace(ast, {
            enter: function (node, parent) {
                if (node.type === "CallExpression"
                    && node.arguments.length === 2
                    && node.callee
                    && node.callee.property
                    && node.callee.property.value
                ) {
                    let operator;
                    let rname;
                    let rvalue = node.callee.property.value;
                   //尋找重命名前的真實名稱
                    estraverse.traverse(ast, {
                        enter: function (find_node, parent) {

                            try {
                                if (find_node.type === "VariableDeclaration"
                                    && find_node.declarations[0].id.name === node.callee.object.name
                                ) {
                                    rname = find_node.declarations[0].init.name;
                                    this.break()
                                }
                            } catch (e) {
                                this.skip()
                            }
                        }
                    });
                    //尋找對應方法的運算符茬高,賦值給operator
                    estraverse.traverse(ast, {
                        enter: function (find_node, parent) {
                            try {

                                if (find_node.type === "AssignmentExpression"
                                    && find_node.left.object.name === rname
                                    && find_node.left.property.value === rvalue
                                ) {
                                    operator = find_node.right.body.body[0].argument.operator;
                                    if (operator) {
                                        this.break()
                                    }
                                    this.skip()
                                }
                            } catch (e) {
                                this.skip()
                            }
                        }
                    });
                    //替換為簡化的語法樹
                    if (operator) {
                        node = {
                            "type": "BinaryExpression",
                            "operator": operator,
                            "left": node.arguments[0],
                            "right": node.arguments[1]
                        };
                        return node
                    }
                }
            }


        }
    );


//反混淆后代碼
_0x615b4b['uUzso'] = function (_0x59febf, _0x3b43de) {
    return _0x59febf & _0x3b43de;
};
var _0x2bb501 = _0x615b4b;
//_0x2bb501['uUzso'](_0xd72c68, 8)     變?yōu)?           _0xd72c68 & 8
_0xd72c68 & 8;

2兆旬、代碼反混淆后已可以閱讀,找到核心代碼怎栽。

//加密入口函數
ABC['prototype']['z'](seed, ts)

//之后會用python調用丽猬,這里簡單封裝入口
function encrypt(seed, ts) {
    ts = Number(ts);
    return ABC['prototype']['z'](seed, ts);
}
//hook取兩個真實參數,真實結果應為971bzdrUsH6rIWHBVAmYXWgD3LfYvX/ci0ZvZl1DAdpLwgm8rTU6n4iFR5UFL3dliWHPzK1oNJkfepDbUYXKr4ZeMTBkmYrZcjuFh8K6Z6LiCgJwowN9mPggIR+336cmJH9B
encrypt('pA2EJB+wiUR5EXY0ZDTsTSh2z4oxxDfBIGHbOzpdB6A=', 1589961615404);

3、nodejs環(huán)境熏瞄,添加jsdom模擬瀏覽器環(huán)境,返回的結果總變脚祟。而在瀏覽器環(huán)境下,結果不變强饮,且與之前hook取到的結果一致由桌。判斷反混淆未影響代碼邏輯,但是代碼中有判斷瀏覽器環(huán)境

//添加window對象
const {JSDOM} = jsdom;
const {window} = new JSDOM(`...`)

//有一些判斷環(huán)境的代碼(這里給列出部分)
//如 global邮丰、process行您、child_process這些都是nodejs環(huán)境有,而瀏覽器沒有剪廉。有50處左右對比環(huán)境的娃循。
    if (typeof global != _0x3e1ab5['VMFpl'] || typeof process != _0x3e1ab5['VMFpl'] || typeof child_process != _0x3e1ab5['VMFpl'] || typeof Buffer != _0x3e1ab5['VMFpl'] || typeof sessionStorage == _0x3e1ab5['VMFpl']) {
        ss = ss | 1 << 12;
    }

4、根據上面找出的對比瀏覽器環(huán)境的代碼斗蒋,寫出對應的處理代碼如:

//導入jsdom捌斧,有部分檢測window對象,及其屬性
const jsdom = require("jsdom");
const {JSDOM} = jsdom;
//檢測當前url
const {window} = new JSDOM(`...`,{
    url: "https://www.zhipin.com/job_detail/?query=",
});
// // or even
const {document} = (new JSDOM(`...`)).window;
//檢測top
top = window
//檢測setInterval方法toString的值泉沾,瀏覽器及nodejs環(huán)境有差異
setInterval['toString']= function () {
    return "function setInterval() { [native code] }"
}
//有檢測部分差異變量
global = undefined
process = undefined
child_process = undefined
Buffer = undefined
sessionStorage = {}

5捞蚂、還有部分不好在代碼上覆蓋,直接在反混淆后替換

//這里是調試時過無限debugger
    code = code.replace('debugger;', '')
//有一個eval函數內部也是無限debugger爆哑,這里用console.log替換eval洞难,直接輸出代碼不執(zhí)行
        .replace('eval', 'console.log')
//替換部分瀏覽器值(在4中代碼最上方覆蓋不生效,這里直接替換掉,待研究)
        .replace("window['screen']['availHeight']", '970')
        .replace("window['screen']['availWidth']", '1680')
        .replace("window['outerWidth']", '1680')
        .replace("window['innerWidth']", '480')
        .replace("window['outerHeight']", '967')
        .replace("window['innerHeight']", '856')

6队贱、終于在nodejs環(huán)境執(zhí)行后與瀏覽器環(huán)境一致色冀,也與hook到的值一致。
7柱嫌、這個混淆的js文件锋恬,會定期更新,各變量名及內部加密算法改變编丘。這里的4和5的處理都加入反混淆腳本与学,在js文件改變時,必須反混淆后才能出現5中的代碼嘉抓。(1.因為這里是直接調用索守,所以不關心加密算法是什么。2.因為上面5的部分代碼無法覆蓋抑片,所以新的文件還是要走反混淆這一步卵佛。如果5的部分可以類似于4的處理,那么之后新js文件可以不用反混淆敞斋,只需要將4部分放入js文件頭即可)

后記

看似很復雜的混淆截汪,其實都只是幾種混淆規(guī)則、互相嵌套造成的植捎⊙媒猓看著腦殼疼,其實只要分析一下焰枢,借助AST蚓峦,完成部分反混淆規(guī)則,即可正常閱讀代碼医咨。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末枫匾,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子拟淮,更是在濱河造成了極大的恐慌干茉,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件很泊,死亡現場離奇詭異角虫,居然都是意外死亡,警方通過查閱死者的電腦和手機委造,發(fā)現死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門戳鹅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人昏兆,你說我怎么就攤上這事枫虏。” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵隶债,是天一觀的道長腾它。 經常有香客問我,道長死讹,這世上最難降的妖魔是什么瞒滴? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮赞警,結果婚禮上妓忍,老公的妹妹穿的比我還像新娘。我一直安慰自己愧旦,他們只是感情好嘁圈,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布摩泪。 她就那樣靜靜地躺著岭辣,像睡著了一般押袍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上耕皮,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天,我揣著相機與錄音蝙场,去河邊找鬼凌停。 笑死,一個胖子當著我的面吹牛售滤,可吹牛的內容都是我干的罚拟。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼完箩,長吁一口氣:“原來是場噩夢啊……” “哼赐俗!你這毒婦竟也來了?” 一聲冷哼從身側響起弊知,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤阻逮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后秩彤,有當地人在樹林里發(fā)現了一具尸體叔扼,經...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年漫雷,在試婚紗的時候發(fā)現自己被綠了瓜富。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡降盹,死狀恐怖与柑,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤价捧,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布丑念,位于F島的核電站,受9級特大地震影響干旧,放射性物質發(fā)生泄漏渠欺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一椎眯、第九天 我趴在偏房一處隱蔽的房頂上張望挠将。 院中可真熱鬧,春花似錦编整、人聲如沸舔稀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽内贮。三九已至,卻和暖如春汞斧,著一層夾襖步出監(jiān)牢的瞬間夜郁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工粘勒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留竞端,地道東北人。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓庙睡,卻偏偏與公主長得像事富,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子乘陪,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355