亚洲国产精品乱码一区二区,美景房屋2免费观看,哎呀哎呀在线观看视频高清国语,从镜子里看我是怎么C哭你

Article / 文章中心

極驗第四代滑塊驗證碼破解(一):AST還原混淆JS

發(fā)布時間:2022-06-06 點擊數(shù):5634

首先,極驗第四代驗證碼加密方式和極驗第三代差不多。所以,有看過我之前極驗三系列破解方式的小伙伴,破解起來還是比較輕松的。

本系列文章為極驗第四代滑塊驗證碼破解,內(nèi)容會對比極驗三代的破解過程,會有些冗余,但很細致,小伙伴們仔細學習哦。

 

一、環(huán)境安裝

 

環(huán)境安裝,請查看極驗三破解鏈接:極驗滑塊驗證碼破解與研究(一):AST還原混淆JS

 

二、AST還原混淆JS

 

注意:極驗三代、四代js代碼混淆方式一模一樣,所以用極驗三的AST還原函數(shù)改一下即可。

AST還原函數(shù)詳解,請查看極驗三破解鏈接:極驗滑塊驗證碼破解與研究(一):AST還原混淆JS

 

1. 需要還原的js代碼鏈接

https://static.geetest.com/v4/static/v1.4.4/js/gcaptcha4.js

2. AST還原源碼

const parser = require("@babel/parser");

const traverse = require("@babel/traverse").default;

const t = require("@babel/types");

const generator = require("@babel/generator").default;

const fs = require("fs");

 

// #######################################

// 還原需要用到的js源碼

// #######################################

khCTZ.$_AY = function() {

    // 從gcaptcha4.js源碼中摳出來

}();

khCTZ.$_BS = function() {

    // 從gcaptcha4.js源碼中摳出來

}();

khCTZ.$_Cl = function() {

    // 從gcaptcha4.js源碼中摳出來

};

khCTZ.$_DX = function() {

    // 從gcaptcha4.js源碼中摳出來

};

function khCTZ() {}

 

// #######################################

 

 

// #######################################

// AST解析函數(shù)

// #######################################

// 刪除節(jié)點中的extra屬性(二進制、Unicode等編碼 -> utf-8)

function replace_unicode(path) {

    let node = path.node;

    if (node.extra === undefined)

        return;

    delete node.extra;

}

 

// 定義一個全局變量,存放待替換變量名

let name_array = [];

 

function get_name_array(path) {

    let {kind, declarations} = path.node

    if (kind !== 'var'

        || declarations.length !== 3

        || declarations[0].init === null

        || declarations[0].init.property === undefined)

        return;

    if (declarations[0].init.property.name !== "$_Cl")

        return;

    // 獲取待替換節(jié)點變量名

    let name1 = declarations[0].id.name

    // 獲取待輸出變量名

    let name2 = declarations[2].id.name

    // 將變量名存入數(shù)組

    name_array.push(name1, name2)

 

    // 刪除下一個節(jié)點

    path.getNextSibling().remove()

    // 刪除下一個節(jié)點

    path.getNextSibling().remove()

    // 刪除path節(jié)點

    path.remove()

}

 

function replace_name_array(path) {

    let {callee, arguments} = path.node

    if (callee === undefined || callee.name === undefined)

        return;

    // 不在name_array中的節(jié)點不做替換操作

    if (name_array.indexOf(callee.name) === -1)

        return;

    // 調(diào)用khCTZ.$_Cl函數(shù)獲取結果

    let value = khCTZ.$_Cl(arguments[0].value);

    // 創(chuàng)建節(jié)點并替換結果

    let string_node = t.stringLiteral(value)

    path.replaceWith(string_node)

}

 

function replace_$_Cl(path) {

    let {arguments, callee} = path.node

    // 解析arguments參數(shù)

    if (arguments.length !== 1) return;

    if (arguments[0].type !== 'NumericLiteral') return;

 

    // 解析callee

    if (callee.type !== 'MemberExpression') return;

    let {object, property} = callee;

    if (object.type !== 'Identifier' || property.type !== 'Identifier') return;

 

    if (property.name === '$_Cl') {

        // 計算值

        let value = khCTZ.$_Cl(arguments[0].value);

        // 創(chuàng)建節(jié)點并替換

        let string_node = t.stringLiteral(value)

        path.replaceWith(string_node)

    }

}

 

// 控制流平坦化

function replace_ForStatement(path) {

    var node = path.node;

 

    // 獲取上一個節(jié)點,也就是VariableDeclaration

    var PrevSibling = path.getPrevSibling();

    // 判斷上個節(jié)點的各個屬性,防止報錯

    if (PrevSibling.type === undefined

        || PrevSibling.container === undefined

        || PrevSibling.container[0].declarations === undefined

        || PrevSibling.container[0].declarations[0].init === null

        || PrevSibling.container[0].declarations[0].init.object === undefined

        || PrevSibling.container[0].declarations[0].init.object.object === undefined)

        return;

    if (PrevSibling.container[0].declarations[0].init.object.object.callee.property.name !== '$_DX')

        return;

 

    // SwitchStatement節(jié)點

    var body = node.body.body;

    // 判斷當前節(jié)點的body[0]屬性和body[0].discriminant是否存在

    if (!t.isSwitchStatement(body[0]))

        return;

    if (!t.isIdentifier(body[0].discriminant))

        return;

 

    // 獲取控制流的初始值

    var argNode = PrevSibling.container[0].declarations[0].init;

    var init_arg_f = argNode.object.property.value;

    var init_arg_s = argNode.property.value;

    var init_arg = khCTZ.$_DX()[init_arg_f][init_arg_s];

 

    // 提取for節(jié)點中的if判斷參數(shù)的value作為判斷參數(shù)

    var break_arg_f = node.test.right.object.property.value;

    var break_arg_s = node.test.right.property.value;

    var break_arg = khCTZ.$_DX()[break_arg_f][break_arg_s];

 

    // 提取switch下所有的case

    var case_list = body[0].cases;

    var resultBody = [];

 

    // 遍歷全部的case

    for (var i = 0; i < case_list.length; i++) {

        for (; init_arg != break_arg;) {

 

            // 提取并計算case后的條件判斷的值

            var case_arg_f = case_list[i].test.object.property.value;

            var case_arg_s = case_list[i].test.property.value;

            var case_init = khCTZ.$_DX()[case_arg_f][case_arg_s];

 

            if (init_arg == case_init) {

                //當前case下的所有節(jié)點

                var targetBody = case_list[i].consequent;

 

                // 刪除break節(jié)點,和break節(jié)點的上一個節(jié)點的一些無用代碼

                if (t.isBreakStatement(targetBody[targetBody.length - 1])

                    && t.isExpressionStatement(targetBody[targetBody.length - 2])

                    && targetBody[targetBody.length - 2].expression.right.object.object.callee.object.name == "khCTZ") {

 

                    // 提取break節(jié)點的上一個節(jié)點AJgjJ.EMf()后面的兩個索引值

                    var change_arg_f = targetBody[targetBody.length - 2].expression.right.object.property.value;

                    var change_arg_s = targetBody[targetBody.length - 2].expression.right.property.value;

 

                    // 修改控制流的初始值

                    init_arg = khCTZ.$_DX()[change_arg_f][change_arg_s];

 

                    targetBody.pop(); // 刪除break

                    targetBody.pop(); // 刪除break節(jié)點的上一個節(jié)點

                }

                //刪除break

                else if (t.isBreakStatement(targetBody[targetBody.length - 1])) {

                    targetBody.pop();

                }

                resultBody = resultBody.concat(targetBody);

                break;

            } else {

                break;

            }

        }

    }

    //替換for節(jié)點,多個節(jié)點替換一個節(jié)點用replaceWithMultiple

    path.replaceWithMultiple(resultBody);

 

    //刪除上一個節(jié)點

    PrevSibling.remove();

}

 

// 刪除無關函數(shù)

function delete_func(path) {

    let {expression} = path.node

    if (expression === undefined

        || expression.left === undefined

        || expression.left.property === undefined)

        return;

    if (expression.left.property.name === '$_AY'

        || expression.left.property.name === '$_Cl'

        || expression.left.property.name === '$_BS'

        || expression.left.property.name === '$_DX'

    ) {

        path.remove()

    }

}

 

// #######################################

 

 

// #######################################

// AST還原流程

// #######################################

// 需要解碼的文件位置

let encode_file = "gcaptcha4.js"

// 解碼后的文件位置

let decode_file = "gcaptcha4_decode.js"

 

// 讀取需要解碼的js文件, 注意文件編碼為utf-8格式

let jscode = fs.readFileSync(encode_file, {encoding: "utf-8"});

 

// 將js代碼修轉(zhuǎn)成AST語法樹

let ast = parser.parse(jscode);

// AST結構修改邏輯

const visitor = {

    StringLiteral: {

        enter: [replace_unicode]

    },

    VariableDeclaration: {

        enter: [get_name_array]

    },

    CallExpression: {

        enter: [replace_name_array, replace_$_Cl]

    },

    ForStatement: {

        enter: [replace_ForStatement]

    },

    ExpressionStatement: {

        enter: [delete_func]

    },

}

 

// 遍歷語法樹節(jié)點,調(diào)用修改函數(shù)

traverse(ast, visitor);

 

// 將ast轉(zhuǎn)成js代碼,{jsescOption: {"minimal": true}} unicode -> 中文

let {code} = generator(ast, opts = {jsescOption: {"minimal": true}});

// 將js代碼保存到文件

fs.writeFile(decode_file, code, (err) => {

});