【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶

    • 前言
    • 初看js代码
    • 补环境方案
    • 修改字节码方案(反混淆与反汇编)
    • 算法还原
    • 后记

前言

距离上一次的某乎jsvmp也过了好一段时间,现在也从2.0的版本升级到了3.0的版本

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第1张图片
【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第2张图片
自然的,算法也就发生了一些改变。最明显最直接可见的变化就是长度边长了,并且相同的入参,输出结果并不相同。那么这篇文章就在原来2.0的基础上【JS逆向系列】某乎x96参数与jsvmp初体验,来分析一下3.0版本变难了多少,算法又要怎么还原出来。

初看js代码

至于参数如何查找这篇文章就跳过了,相关内容可以查看前一篇,这篇从【__g._encrypt】开始。两个版本的入口是相同的,都是从【__g._encrypt】进入到jsvmp内部代码,入参也都是一个md5结果的16进制字符串。

某乎的jsvmp与其他的略有不同,一般的jsvmp是堆栈式的,而某乎的这个是寄存器式的。也是也之前一样,是有vm的初始化,这次3.0的对象是【l】对象

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第3张图片

结构上和之前还是很想的,不过多了不少参数,有几个关键的参数需要注意

参数 映射含义
this.c 通用寄存器
this.s pc寄存器
this.S 栈帧
this.i 数组缓存
this.Q 跳转标志位
this.G 操作码数组
this.D 字符串数组
this.w 控制流出口
this.g 异常跳转
this.a 时间检测参数
this.e 3字节操作码
this.T 控制流入口
this.U 时间检测参数
this.M 常量虚假指令

以上的仅仅是我个人的理解,不一定正确,仅供参考。

补环境方案

还是和之前一样,首先试试能不能通过补环境得出相同的结果,首先在网页上拿一组样本。
【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第4张图片
这里入参是【a63da42088bd8d635961ede065daeb51】结果是【RiO+y9AqW9KuaS+8vShliRMUs8LvryJRSxJinhVvmy+JvR5Xel5Uv5psmxAcilNl】,按照之前的办法,就是补环境使得到相同的结果,但是对于3.0版本就会出现问题。这里发现,相同的入参,多次执行,结果是不一样的。

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第5张图片
这就不好办了,那么即使补环境出来的结果,也不知道是不是对的。一般这种情况下,就是计算涉及到的随机数或者时间。而这里就是包含的随机数,所以需要hook随机数的返回

Math.random = function(){
    return 0.50
};

输入这段代码后再执行加密函数,此时就发现结果都是一样的了

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第6张图片
那么此时就得到了一组样本,当随机数恒定返回0.5时。入参【a63da42088bd8d635961ede065daeb51】的正确结果为【t=V/NpKQqHpejG8nmTuCzIrXW+JszxwLVVyuy+8S0ak=pe1N4BRA6Qxz+LDn+Xyj】,那么接下在就真正可以开始补环境了。

首先安装依赖库

npm install jsdom
npm install canvas

然后在头部加上jsdom的代码

const{JSDOM}=require("jsdom");
const dom=new JSDOM("

Hello world

"
); window=dom.window; Math.random = function(){ return 0.50 };

结尾加上测试代码

console.log(D('a63da42088bd8d635961ede065daeb51'));
console.log('t=V/NpKQqHpejG8nmTuCzIrXW+JszxwLVVyuy+8S0ak=pe1N4BRA6Qxz+LDn+Xyj');

开始测试运行

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第7张图片

提示缺少【document】,那么就加上这个定义

document=window.document;

继续运行,后面还有类似的报错,继续补全。

最后头部为

const{JSDOM}=require("jsdom");
const dom=new JSDOM("

Hello world

"
); window=dom.window; document=window.document; navigator=window.navigator; location=window.location; history=window.history; screen=window.screen; Math.random = function(){ return 0.50 };

测试可以运行出结果

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第8张图片
这个结果和样本明显不一样,说明还缺少了其他环境没有补到。

那么接下来就得对前面的环境变量上代理,看看还用到了什么属性和方法

window = new Proxy(window, {
    set(target, property, value, receiver) {
        console.log("设置属性set window", property, typeof value);
        return Reflect.set(...arguments);
    },
    get(target, property, receiver) {
        console.log("获取属性get window", property, typeof target[property]);
        return target[property]
    }
});
document = new Proxy(document, {
    set(target, property, value, receiver) {
        console.log("设置属性set document", property, typeof value);
        return Reflect.set(...arguments);
    },
    get(target, property, receiver) {
        console.log("获取属性get document", property, typeof target[property]);
        return target[property]
    }
});
navigator = new Proxy(navigator, {
    set(target, property, value, receiver) {
        console.log("设置属性set navigator", property, typeof value);
        return Reflect.set(...arguments);
    },
    get(target, property, receiver) {
        console.log("获取属性get navigator", property, typeof target[property]);
        return target[property]
    }
});
location = new Proxy(location, {
    set(target, property, value, receiver) {
        console.log("设置属性set location", property, typeof value);
        return Reflect.set(...arguments);
    },
    get(target, property, receiver) {
        console.log("获取属性get location", property, typeof target[property]);
        return target[property]
    }
});
history = new Proxy(history, {
    set(target, property, value, receiver) {
        console.log("设置属性set history", property, typeof value);
        return Reflect.set(...arguments);
    },
    get(target, property, receiver) {
        console.log("获取属性get history", property, typeof target[property]);
        return target[property]
    }
});
screen = new Proxy(screen, {
    set(target, property, value, receiver) {
        console.log("设置属性set screen", property, typeof value);
        return Reflect.set(...arguments);
    },
    get(target, property, receiver) {
        console.log("获取属性get screen", property, typeof target[property]);
        return target[property]
    }
});

同时,整个大逻辑被一个try代码块包裹着

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第9张图片
那么如果报错的话,我们也看不到,不方便补环境,所以去掉try代码块,只保留try里面的内容。

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第10张图片
可以看到读取了不少属性,最后运行到【获取属性get document Symbol(Symbol.toStringTag) string】这一步就退出了,那么看看这一步的结果是不是和网页不一样

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第11张图片
【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第12张图片
确实是不一样的结果,所以这里就需要hook掉toString方法

var Object_toString = Object.prototype.toString;
Object.prototype.toString = function () {
    let _temp = Object_toString.call(this, arguments);
    console.log(this);
    console.log("Object.prototype.toString: " + _temp);
    if(this.constructor.name === 'Document'){
        return '[object HTMLDocument]';
    }
    return _temp;
};

再次运行后,日志内容比之前更加长了,说明补的内容有效了,同时得到的加密结果也不一样了

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第13张图片
这里最后是location对象出现问题,那么在jsdom上面,就需要补上url链接,那么就会自动补全location对象,开头部分的代码就修改为

const{JSDOM}=require("jsdom");
const dom=new JSDOM("

Hello world

"
,{url:'https://www.zhihu.com/search'}); window=dom.window; document=window.document; navigator=window.navigator; location=window.location; history=window.history; screen=window.screen; Math.random = function(){ return 0.50 };

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第14张图片

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第15张图片
这里canvas和网页返回的不一样,继续补上

var Object_toString = Object.prototype.toString;
Object.prototype.toString = function () {
    let _temp = Object_toString.call(this, arguments);
    console.log(this);
    console.log("Object.prototype.toString: " + _temp);
    if(this.constructor.name === 'Document'){
        return '[object HTMLDocument]';
    }else if(this.constructor.name === 'CanvasRenderingContext2D'){
        return '[object CanvasRenderingContext2D]'
    }
    return _temp;
};

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第16张图片
又继续往下跑了,这次是检测了window下的_resourceLoader,浏览器上是undefined,但是node上返回对象。还有后面的_sessionHistory,一起补上。

const{JSDOM}=require("jsdom");
const dom=new JSDOM("

Hello world

"
,{url:'https://www.zhihu.com/search'}); window=dom.window; document=window.document; navigator=window.navigator; location=window.location; history=window.history; screen=window.screen; window._resourceLoader = undefined; window._sessionHistory = undefined; Math.random = function(){ return 0.50 };

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第17张图片
出现alert未定义,和之前一样处理

const{JSDOM}=require("jsdom");
const dom=new JSDOM("

Hello world

"
,{url:'https://www.zhihu.com/search'}); window=dom.window; document=window.document; navigator=window.navigator; location=window.location; history=window.history; screen=window.screen; alert=window.alert; window._resourceLoader = undefined; window._sessionHistory = undefined; Math.random = function(){ return 0.50 };

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第18张图片
结果还是不一样,并且获取了window的原型后就没有了,那么这种情况很有可能检测了原型链和函数或者tostring,那么hook一下看看

var Function_toString = Function.prototype.toString;
Function.prototype.toString = function () {
    let _temp = Function_toString.call(this, arguments);
    console.log(this);
    console.log("Function.prototype.toString: " + _temp);
    return _temp;
};

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第19张图片
果然是,那么继续补上

var Function_toString = Function.prototype.toString;
Function.prototype.toString = function () {
    let _temp = Function_toString.call(this, arguments);
    console.log(this);
    console.log("Function.prototype.toString: " + _temp);
    if(this.name === 'Window'){
        return 'function Window() { [native code] }'
    }
    return _temp;
};

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第20张图片
漂亮,终于得到一样的结果,那么这里补环境就完成了,总结一下我们补了什么

const{JSDOM}=require("jsdom");
const dom=new JSDOM("

Hello world

"
,{url:'https://www.zhihu.com/search'}); window=dom.window; document=window.document; navigator=window.navigator; location=window.location; history=window.history; screen=window.screen; alert=window.alert; window._resourceLoader = undefined; window._sessionHistory = undefined; Math.random = function(){ return 0.50 }; window = new Proxy(window, { set(target, property, value, receiver) { console.log("设置属性set window", property, typeof value); return Reflect.set(...arguments); }, get(target, property, receiver) { console.log("获取属性get window", property, typeof target[property]); return target[property] } }); document = new Proxy(document, { set(target, property, value, receiver) { console.log("设置属性set document", property, typeof value); return Reflect.set(...arguments); }, get(target, property, receiver) { console.log("获取属性get document", property, typeof target[property]); return target[property] } }); navigator = new Proxy(navigator, { set(target, property, value, receiver) { console.log("设置属性set navigator", property, typeof value); return Reflect.set(...arguments); }, get(target, property, receiver) { console.log("获取属性get navigator", property, typeof target[property]); return target[property] } }); location = new Proxy(location, { set(target, property, value, receiver) { console.log("设置属性set location", property, typeof value); return Reflect.set(...arguments); }, get(target, property, receiver) { console.log("获取属性get location", property, typeof target[property]); return target[property] } }); history = new Proxy(history, { set(target, property, value, receiver) { console.log("设置属性set history", property, typeof value); return Reflect.set(...arguments); }, get(target, property, receiver) { console.log("获取属性get history", property, typeof target[property]); return target[property] } }); screen = new Proxy(screen, { set(target, property, value, receiver) { console.log("设置属性set screen", property, typeof value); return Reflect.set(...arguments); }, get(target, property, receiver) { console.log("获取属性get screen", property, typeof target[property]); return target[property] } }); var Object_toString = Object.prototype.toString; Object.prototype.toString = function () { let _temp = Object_toString.call(this, arguments); console.log(this); console.log("Object.prototype.toString: " + _temp); if(this.constructor.name === 'Document'){ return '[object HTMLDocument]'; }else if(this.constructor.name === 'CanvasRenderingContext2D'){ return '[object CanvasRenderingContext2D]' } return _temp; }; var Function_toString = Function.prototype.toString; Function.prototype.toString = function () { let _temp = Function_toString.call(this, arguments); console.log(this); console.log("Function.prototype.toString: " + _temp); if(this.name === 'Window'){ return 'function Window() { [native code] }' } return _temp; };

当需要运行的时候,可以把代码部分的代码注释掉,因为这部分只是方便我们查看以及补环境,不影响最终的结果

修改字节码方案(反混淆与反汇编)

在修改字节码之前,要么需要详细分析字节码的逻辑,又或者反汇编字节码到类似js代码的方式。再来看能不能通过修改字节码的方案来绕过环境检测。

例如之前2.0部分的代码,是先进行环境检测,检测完成后才进行真正的加密,所以才可以修改字节码,使得它跳过了环境检测的部分,直接开始核心的加密函数。如果3.0也是沿用之前的逻辑,先进行了检测再加密,那么这种方案就是可行的。

但是3.0没有办法直接进行反汇编,因为相对于2.0的代码来说,增加了控制流的代码,那么最好是先尝试还原了控制流,再做后续处理。

首先是 按照前面说的去掉try代码块)
首先肯定是处理反调试,3.0也是有时间检测,但是时间检测被放到了jsvmp内部了,不好直接干掉,那么就把初始化里面关于时间的都干掉

// 删除时间参数
traverse(ast, {
    SwitchCase(path){
        if(path.node.test){
            if(path.node.test.value === 300){
                path.node.consequent.splice(0, 1)
            }else if(path.node.test.value === 360){
                path.node.consequent.splice(0, 1)
            }else if(path.node.test.value === 368){
                path.node.consequent[0].expression.right.test = t.booleanLiteral(false)
            }
        }
    },
    FunctionDeclaration(path){
        if(path.node.id && path.node.id.name === 'l'){
            for (let i = path.node.body.body.length - 1; i >= 0; i--) {
                let item = path.node.body.body[i];
                if(item.expression.left.property.name === 'a' || item.expression.left.property.name === 'U'){
                    path.node.body.body.splice(i, 1)
                }
            }
        }
    }
});

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第21张图片
此时再运行,依然可以得到相同的结果,那么就说明这里的时间和2.0是一样,只是用来反调试,与加密逻辑无关。

接下来也不知道怎么入手,那么就来点暴力点的,这么多个case,有没有可能有一些是没有用到的呢?那么在所有的case前面都下一个断点

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第22张图片
然后调试运行,当在断点停下的时候,取消断点再运行下去,直到结束。那么下载来还有断点的case,就是不会运行到的case了。

let cases_list = [27, 34, 41, 48, 101, 117, 124, 147, 258, 283, 380, 400, 449, 459, 468, 469, 473, 479, 481, 485, 491, 496, 506];
traverse(ast, {
    SwitchCase(path){
        if(path.node.test){
            if(cases_list.includes(path.node.test.value)){
                path.remove()
            }
        }
    }
});

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第23张图片
这时删除了多个case后,依然可以得到正确结果。

继续往后调试,会发现一些控制流的分支是虚假分支,也就是在运行的时候,是恒真或者恒假的分支,这种最好是可以将它还原掉,方便后面真实分支的case合并。

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第24张图片
分析发现,例如在case 331中存在赋值的【this.M[21] = 8;】,这里就可以把这个值记录下来,其他任何没有出现赋值的,都是null了

let cases_dict = {};
// 数组虚假分析
traverse(ast, {
    MemberExpression(path){
        if(path.node.object.type === 'ThisExpression' && path.node.property.type === 'Identifier' && path.node.property.name === 'M' && path.parent.type === 'MemberExpression'){
            if(path.parentPath.parent.type === 'AssignmentExpression' && path.parentPath.parent.right.type === 'NumericLiteral'){
                cases_dict[path.parentPath.parent.left.property.value] = path.parentPath.parent.right.value;
                path.parentPath.parentPath.parentPath.remove()
            }
        }
    }
});
traverse(ast, {
    MemberExpression(path){
        if(path.node.object.type === 'ThisExpression' && path.node.property.type === 'Identifier' && path.node.property.name === 'M' && path.parent.type === 'MemberExpression'){
            if(cases_dict.hasOwnProperty(path.parent.property.value)){
                path.parentPath.replaceWith(t.numericLiteral(cases_dict[path.parent.property.value]))
            }else{
                path.parentPath.replaceWith(t.numericLiteral(0))
            }
        }
    }
});

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第25张图片
这样就转变成了字面量

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第26张图片
接着例如case 50,下面的this.T实际就是上面的50,像这种也可以还原为字面量

// 还原this.T
traverse(ast, {
    SwitchCase(path){
        if(path.node.test){
            if(path.node.consequent.length > 1 && path.node.consequent[path.node.consequent.length - 2].expression.right.type === 'BinaryExpression'){
                let item = path.node.consequent[path.node.consequent.length - 2].expression.right;
                if(item.left.type === 'BinaryExpression' && item.left.right.type === 'MemberExpression' && item.left.right.property.type === 'Identifier' && item.left.right.property.name === 'T'){
                    item.left.right = t.numericLiteral(path.node.test.value);
                }
            }
        }
    }
});

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第27张图片
最后是case 331的地方

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第28张图片
这里的【V】变量只出现了一次,所以可以进行手动的处理

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第29张图片
接着再将字面量的内容还原一下

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第30张图片
前面会发现很多个都指向了352,那么这个352肯定是一个关键的地方

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第31张图片
最后还有一些是有【+=】或者【-=】的,这种也可以顺带还原一下

traverse(ast, {
    SwitchCase(path){
        if(path.node.test){
            if(path.node.consequent[path.node.consequent.length - 2].expression.right.type === 'NumericLiteral'){
                if(path.node.consequent[path.node.consequent.length - 2].expression.operator === '+='){
                    path.node.consequent[path.node.consequent.length - 2].expression.operator = '=';
                    path.node.consequent[path.node.consequent.length - 2].expression.right.value = path.node.consequent[path.node.consequent.length - 2].expression.right.value + path.node.test.value;
                }else if(path.node.consequent[path.node.consequent.length - 2].expression.operator === '-='){
                    path.node.consequent[path.node.consequent.length - 2].expression.operator = '=';
                    path.node.consequent[path.node.consequent.length - 2].expression.right.value = path.node.test.value - path.node.consequent[path.node.consequent.length - 2].expression.right.value;
                }
            }
        }
    }
});

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第32张图片
那么来到这里,ast的部分就算完成了。

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第33张图片
先看看这个case 352,它是指向case 300

在这里插入图片描述
然后case 300,又指向了case 368,那么如下图

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第34张图片
如果把后面的两个节点合并成一个,那么就少了一个节点了,其实相当于把case 368的代码全部放到case 300的下面

在这里插入图片描述

换完以后,再次运行一下代码,也能得到相同的结果,那就说明这么修改是没有问题的,没有影响原来的执行流程。

接着就可以开始分析vmp的代码逻辑了。根据之前2.0的经验以及分析,case 331就是解码那段base64字符串到字节码的逻辑

let b64_code = 'ABt7CAAUSAAACADfSAAACAD1SAAACAAHSAAAC......';  //内容太长省略了


let opcode_list = [];
let text_list = [];

function decrypt_text(item){
    let U = 66;
    let M = [];
    for (let b = 0; b < item.length; b++) {
        M.push(String.fromCharCode(24 ^ item.charCodeAt(b) ^ U));
        U = 24 ^ item.charCodeAt(b) ^ U;
    }
    return M.join("")
}

(function () {
    let D = atob(b64_code);
    let w = D.charCodeAt(0) << 16 | D.charCodeAt(1) << 8 | D.charCodeAt(2);

    for (var k = 3; k < w + 3; k += 3) {
        opcode_list.push(D.charCodeAt(k) << 16 | D.charCodeAt(k + 1) << 8 | D.charCodeAt(k + 2));
    }

    for (let V = w + 3; V < D.length;) {
        let E = D.charCodeAt(V) << 8 | D.charCodeAt(V + 1);
        let T = D.slice(V + 2, V + 2 + E);
        text_list.push(decrypt_text(T));
        V += E + 2;
    }
})();

console.log(opcode_list);
console.log(text_list);

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第35张图片
可以看到字节码和字符串都解密出来了,接着就从case 352开始入手写代码,相对于2.0,代码逻辑基本没有变化太多,变化的都是因为控制流而影响的

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第36张图片
相当于把2.0逻辑的所有函数展平,然后用控制流来执行,把之前的代码改一改,又能用起来,还原了初始化的代码如下

(function () {
  let local_0 = [20, 223, 245, 7, 248, 2, 194, 209, 87, 6, 227, 253, 240, 128, 222, 91, 237, 9, 125, 157, 230, 93, 252, 205, 90, 79, 144, 199, 159, 197, 186, 167, 39, 37, 156, 198, 38, 42, 43, 168, 217, 153, 15, 103, 80, 189, 71, 191, 97, 84, 247, 95, 36, 69, 14, 35, 12, 171, 28, 114, 178, 148, 86, 182, 32, 83, 158, 109, 22, 255, 94, 238, 151, 85, 77, 124, 254, 18, 4, 26, 123, 176, 232, 193, 131, 172, 143, 142, 150, 30, 10, 146, 162, 62, 224, 218, 196, 229, 1, 192, 213, 27, 110, 56, 231, 180, 138, 107, 242, 187, 54, 120, 19, 44, 117, 228, 215, 203, 53, 239, 251, 127, 81, 11, 133, 96, 204, 132, 41, 115, 73, 55, 249, 147, 102, 48, 122, 145, 106, 118, 74, 190, 29, 16, 174, 5, 177, 129, 63, 113, 99, 31, 161, 76, 246, 34, 211, 13, 60, 68, 207, 160, 65, 111, 82, 165, 67, 169, 225, 57, 112, 244, 155, 51, 236, 200, 233, 58, 61, 47, 100, 137, 185, 64, 17, 70, 234, 163, 219, 108, 170, 166, 59, 149, 52, 105, 24, 212, 78, 173, 45, 0, 116, 226, 119, 136, 206, 135, 175, 195, 25, 92, 121, 208, 126, 139, 3, 75, 141, 21, 130, 98, 241, 40, 154, 66, 184, 49, 181, 46, 243, 88, 101, 183, 8, 23, 72, 188, 104, 179, 210, 134, 250, 201, 164, 89, 216, 202, 220, 50, 221, 152, 140, 33, 235, 214];
  let local_1 = [120, 50, 98, 101, 99, 98, 119, 100, 103, 107, 99, 119, 97, 99, 110, 111];
  let local_2 = [100, 51, 100, 50, 101, 55, 50, 54, 102, 53, 48, 100, 52, 49, 48, 49];
  let local_3 = new Array(32);
  let local_23 = [462357, 472066609, 943670861, 1415275113, 1886879365, 2358483617, 2830087869, 3301692121, 3773296373, 4228057617, 404694573, 876298825, 1347903077, 1819507329, 2291111581, 2762715833, 3234320085, 3705924337, 4177462797, 337322537, 808926789, 1280531041, 1752135293, 2223739545, 2695343797, 3166948049, 3638552301, 4110090761, 269950501, 741554753, 1213159005, 1684763257];
  let local_24 = [2746333894, 1453994832, 1736282519, 2993693404];
  let local_25 = new Array(4);
  let local_26 = new Array(36);
  let local_6 = (local_2[0] & 255) << 24;
  let local_7 = (local_2[0 + 1] & 255) << 16;
  let local_8 = (local_2[0 + 2] & 255) << 8;
  let local_9 = local_2[0 + 3] & 255;
  local_25[0] = local_6 | local_7 | local_8 | local_9;
  local_6 = (local_2[4] & 255) << 24;
  local_7 = (local_2[4 + 1] & 255) << 16;
  local_8 = (local_2[4 + 2] & 255) << 8;
  local_9 = local_2[4 + 3] & 255;
  local_25[1] = local_6 | local_7 | local_8 | local_9;
  local_6 = (local_2[8] & 255) << 24;
  local_7 = (local_2[8 + 1] & 255) << 16;
  local_8 = (local_2[8 + 2] & 255) << 8;
  local_9 = local_2[8 + 3] & 255;
  local_25[2] = local_6 | local_7 | local_8 | local_9;
  local_6 = (local_2[12] & 255) << 24;
  local_7 = (local_2[12 + 1] & 255) << 16;
  local_8 = (local_2[12 + 2] & 255) << 8;
  local_9 = local_2[12 + 3] & 255;
  local_25[3] = local_6 | local_7 | local_8 | local_9;
  local_26[0] = local_25[0] ^ local_24[0];
  local_26[1] = local_25[1] ^ local_24[1];
  local_26[2] = local_25[2] ^ local_24[2];
  local_26[3] = local_25[3] ^ local_24[3];
  local_9 = 0;

  while (local_9 < 32) {
    let local_27 = local_26[local_9 + 1];
    let local_28 = local_26[local_9 + 2];
    let local_29 = local_26[local_9 + 3];
    let local_30 = local_27 ^ local_28 ^ local_29 ^ local_23[local_9];
    let local_16 = new Array(4);
    let local_5 = new Array(4);
    local_16[0] = 255 & local_30 >>> 24;
    local_16[0 + 1] = 255 & local_30 >>> 16;
    local_16[0 + 2] = 255 & local_30 >>> 8;
    local_16[0 + 3] = 255 & local_30;
    local_5[0] = local_0[local_16[0] & 255];
    local_5[1] = local_0[local_16[1] & 255];
    local_5[2] = local_0[local_16[2] & 255];
    local_5[3] = local_0[local_16[3] & 255];
    local_6 = (local_5[0] & 255) << 24;
    local_7 = (local_5[0 + 1] & 255) << 16;
    local_8 = (local_5[0 + 2] & 255) << 8;
    local_9 = local_5[0 + 3] & 255;
    let local_17 = local_6 | local_7 | local_8 | local_9;
    let local_13 = 32 - 13;
    let local_14 = (local_17 & 4294967295) << 13;
    let local_15 = local_14 | local_17 >>> local_13;
    let local_18 = local_15;
    local_13 = 32 - 23;
    local_14 = (local_17 & 4294967295) << 23;
    local_15 = local_14 | local_17 >>> local_13;
    let local_19 = local_15;
    let local_20 = local_17 ^ local_18 ^ local_19;
    let local_31 = local_20;
    local_26[local_9 + 4] = local_26[local_9] ^ local_31;
    local_3[local_9] = local_26[local_9 + 4];
    local_9 = local_9 + 1;
  }

  window["__ZH__"]["zse"]["zk"] = local_3;
  window["__ZH__"]["zse"]["zb"] = local_0;
  window["__ZH__"]["zse"]["zm"] = local_1;

  function _0x2068(parameter) {}

  __g["_encrypt"] = _0x2068;
})();

可以看到这里是初始化了一大堆固定值,然后复制给了【zk】、【zb】、【zm】这三个对象,那么它前面的一大堆计算其实也不用管,因为也没有涉及到时间和随机数,也就是说计算前是定值,那么计算后,也一定是一个定值,那么我们直接用计算后的定值就可以了。最后两句是定义了一个函数,赋值给了【__g】的【_encrypt】属性,这一点和2.0是一模一样的。

那么接着还原一下【_0x2068】内部的逻辑

  function _0x2068(parameter) {
    let local_47 = Date["now"]();
    let local_48 = [48, 53, 57, 48, 53, 51, 102, 55, 100, 49, 53, 101, 48, 49, 100, 55];
    let local_44 = Date["now"]();

    if (typeof window == "undefined") {
      local_44 = 1;
    }

    if (!(typeof window == "undefined")) {
      if (typeof document == "undefined") {
        local_44 = 2;
      }

      if (!(typeof document == "undefined")) {
        if (typeof navigator == "undefined") {
          local_44 = 3;
        }

        if (!(typeof navigator == "undefined")) {
          if (typeof location == "undefined") {
            local_44 = 4;
          }

          if (!(typeof location == "undefined")) {
            if (typeof history == "undefined") {
              local_44 = 5;
            }

            if (!(typeof history == "undefined")) {
              if (typeof screen == "undefined") {
                local_44 = 6;
              }

              if (!(typeof screen == "undefined")) {
                if (typeof navigator["userAgent"] == "undefined") {
                  local_44 = 7;
                }

                if (!(typeof navigator["userAgent"] == "undefined")) {
                  if (window["name"] == "nodejs") {
                    local_44 = 8;
                  }

                  if (!(window["name"] == "nodejs")) {
                    if (document["toString"]()["indexOf"]("HTMLDocument") == -1) {
                      local_44 = 10;
                    }

                    if (!(document["toString"]()["indexOf"]("HTMLDocument") == -1)) {
                      if (navigator["toString"]()["indexOf"]("Navigator") == -1) {
                        local_44 = 11;
                      }

                      if (!(navigator["toString"]()["indexOf"]("Navigator") == -1)) {
                        if (location["toString"]()["indexOf"]("http") == -1) {
                          local_44 = 12;
                        }

                        if (!(location["toString"]()["indexOf"]("http") == -1)) {
                          if (history["toString"]()["indexOf"]("History") == -1) {
                            local_44 = 13;
                          }

                          if (!(history["toString"]()["indexOf"]("History") == -1)) {
                            if (screen["toString"]()["indexOf"]("Screen") == -1) {
                              local_44 = 14;
                            }

                            if (!(screen["toString"]()["indexOf"]("Screen") == -1)) {
                              if (navigator["userAgent"]["toLowerCase"]()["indexOf"]("headless") !== -1) {
                                local_44 = 15;
                              }

                              if (!(navigator["userAgent"]["toLowerCase"]()["indexOf"]("headless") !== -1)) {
                                let local_45 = Object["getOwnPropertyDescriptor"];

                                if (local_45["toString"]()["indexOf"]("native code") == -1) {
                                  local_44 = 16;
                                }

                                if (!(local_45["toString"]()["indexOf"]("native code") == -1)) {
                                  if (typeof document["createElement"] == "undefined") {
                                    local_44 = 17;
                                  }

                                  if (!(typeof document["createElement"] == "undefined")) {
                                    if (document["createElement"]("canvas")["getContext"]("2d")["toString"]()["indexOf"]("Canvas") == -1) {
                                      local_44 = 22;
                                    }

                                    if (!(document["createElement"]("canvas")["getContext"]("2d")["toString"]()["indexOf"]("Canvas") == -1)) {
                                      if (typeof window["buffer"] !== "undefined") {
                                        local_44 = 24;
                                      }

                                      if (!(typeof window["buffer"] !== "undefined")) {
                                        if (typeof window["emit"] !== "undefined") {
                                          local_44 = 25;
                                        }

                                        if (!(typeof window["emit"] !== "undefined")) {
                                          if (typeof window["callPhantom"] !== "undefined") {
                                            local_44 = 26;
                                          }

                                          if (!(typeof window["callPhantom"] !== "undefined")) {
                                            if (typeof window["__phantomas"] !== "undefined") {
                                              local_44 = 27;
                                            }

                                            if (!(typeof window["__phantomas"] !== "undefined")) {
                                              if (typeof window["_phantom"] !== "undefined") {
                                                local_44 = 28;
                                              }

                                              if (!(typeof window["_phantom"] !== "undefined")) {
                                                if (typeof window["WebPage"] !== "undefined") {
                                                  local_44 = 29;
                                                }

                                                if (!(typeof window["WebPage"] !== "undefined")) {
                                                  if (typeof window["fxdriver_id"] !== "undefined") {
                                                    local_44 = 30;
                                                  }

                                                  if (!(typeof window["fxdriver_id"] !== "undefined")) {
                                                    if (typeof window["__fxdriver_unwrapped"] !== "undefined") {
                                                      local_44 = 31;
                                                    }

                                                    if (!(typeof window["__fxdriver_unwrapped"] !== "undefined")) {
                                                      if (typeof window["domAutomation"] !== "undefined") {
                                                        local_44 = 32;
                                                      }

                                                      if (!(typeof window["domAutomation"] !== "undefined")) {
                                                        if (typeof window["ubot"] !== "undefined") {
                                                          local_44 = 33;
                                                        }

                                                        if (!(typeof window["ubot"] !== "undefined")) {
                                                          if (typeof window["CasperError"] !== "undefined") {
                                                            local_44 = 34;
                                                          }

                                                          if (!(typeof window["CasperError"] !== "undefined")) {
                                                            if (typeof window["casper"] !== "undefined") {
                                                              local_44 = 35;
                                                            }

                                                            if (!(typeof window["casper"] !== "undefined")) {
                                                              if (typeof window["patchRequire"] !== "undefined") {
                                                                local_44 = 36;
                                                              }

                                                              if (!(typeof window["patchRequire"] !== "undefined")) {
                                                                if (typeof document["$cdc_asdjflasutopfhvcZLmcfl_"] !== "undefined") {
                                                                  local_44 = 37;
                                                                }

                                                                if (!(typeof document["$cdc_asdjflasutopfhvcZLmcfl_"] !== "undefined")) {
                                                                  if (navigator["webdriver"] == true) {
                                                                    local_44 = 38;
                                                                  }

                                                                  if (!(navigator["webdriver"] == true)) {
                                                                    if (typeof document["__webdriver_script_fn"] !== "undefined") {
                                                                      local_44 = 39;
                                                                    }

                                                                    if (!(typeof document["__webdriver_script_fn"] !== "undefined")) {
                                                                      if (typeof window["_resourceLoader"] !== "undefined") {
                                                                        local_44 = 40;
                                                                      }

                                                                      if (!(typeof window["_resourceLoader"] !== "undefined")) {
                                                                        if (typeof window["_sessionHistory"] !== "undefined") {
                                                                          local_44 = 41;
                                                                        }

                                                                        if (!(typeof window["_sessionHistory"] !== "undefined")) {
                                                                          if (typeof window["global"] !== "undefined") {
                                                                            local_44 = 42;
                                                                          }

                                                                          if (!(typeof window["global"] !== "undefined")) {
                                                                            if (typeof Object["getPrototypeOf"](alert) !== "function") {
                                                                              local_44 = 43;
                                                                            }

                                                                            if (!(typeof Object["getPrototypeOf"](alert) !== "function")) {
                                                                              if (typeof document["getElementById"] == "undefined") {
                                                                                local_44 = 44;
                                                                              }

                                                                              if (!(typeof document["getElementById"] == "undefined")) {
                                                                                if (typeof Object["getPrototypeOf"](document["getElementById"]) !== "function") {
                                                                                  local_44 = 45;
                                                                                }

                                                                                if (!(typeof Object["getPrototypeOf"](document["getElementById"]) !== "function")) {
                                                                                  if (typeof document["getElementsByClassName"] == "undefined") {
                                                                                    local_44 = 46;
                                                                                  }

                                                                                  if (!(typeof document["getElementsByClassName"] == "undefined")) {
                                                                                    if (window["__proto__"]["constructor"]["toString"]()["indexOf"]("[native code]") == -1) {
                                                                                      local_44 = 48;
                                                                                    }

                                                                                    if (!(window["__proto__"]["constructor"]["toString"]()["indexOf"]("[native code]") == -1)) {
                                                                                      if (typeof window["__nightmare"] !== "undefined") {
                                                                                        local_44 = 49;
                                                                                      }

                                                                                      if (!(typeof window["__nightmare"] !== "undefined")) {
                                                                                        if (new Error()["stack"]["indexOf"]("localhost") !== -1) {
                                                                                          local_44 = 50;
                                                                                        }

                                                                                        if (!(new Error()["stack"]["indexOf"]("localhost") !== -1)) {
                                                                                          if (new Error()["stack"]["indexOf"]("puppeteer") !== -1) {
                                                                                            local_44 = 51;
                                                                                          }

                                                                                          if (!(new Error()["stack"]["indexOf"]("puppeteer") !== -1)) {
                                                                                            if (navigator["userAgent"]["toLowerCase"]()["indexOf"]("phantomjs") !== -1) {
                                                                                              local_44 = 52;
                                                                                            }

                                                                                            if (!(navigator["userAgent"]["toLowerCase"]()["indexOf"]("phantomjs") !== -1)) {
                                                                                              if (navigator["userAgent"]["toLowerCase"]()["indexOf"]("electron") !== -1) {
                                                                                                local_44 = 53;
                                                                                              }

                                                                                              if (!(navigator["userAgent"]["toLowerCase"]()["indexOf"]("electron") !== -1)) {
                                                                                                if (location["href"]["indexOf"]("localhost") !== -1) {
                                                                                                  local_44 = 54;
                                                                                                }

                                                                                                if (!(location["href"]["indexOf"]("localhost") !== -1)) {
                                                                                                  if (window["spawn"]) {
                                                                                                    local_44 = 56;
                                                                                                  }

                                                                                                  if (!window["spawn"]) {
                                                                                                    if (typeof window["_Selenium_IDE_Recorder"] !== "undefined") {
                                                                                                      local_44 = 57;
                                                                                                    }

                                                                                                    if (!(typeof window["_Selenium_IDE_Recorder"] !== "undefined")) {
                                                                                                      if (typeof window["_selenium"] !== "undefined") {
                                                                                                        local_44 = 58;
                                                                                                      }

                                                                                                      if (!(typeof window["_selenium"] !== "undefined")) {
                                                                                                        if (typeof window["__webdriver_evaluate"] !== "undefined") {
                                                                                                          local_44 = 59;
                                                                                                        }

                                                                                                        if (!(typeof window["__webdriver_evaluate"] !== "undefined")) {
                                                                                                          if (typeof window["__selenium_evaluate"] !== "undefined") {
                                                                                                            local_44 = 60;
                                                                                                          }

                                                                                                          if (!(typeof window["__selenium_evaluate"] !== "undefined")) {
                                                                                                            if (typeof window["__webdriver_script_function"] !== "undefined") {
                                                                                                              local_44 = 61;
                                                                                                            }

                                                                                                            if (!(typeof window["__webdriver_script_function"] !== "undefined")) {
                                                                                                              if (typeof window["__fxdriver_evaluate"] !== "undefined") {
                                                                                                                local_44 = 62;
                                                                                                              }

                                                                                                              if (!(typeof window["__fxdriver_evaluate"] !== "undefined")) {
                                                                                                                if (typeof window["__driver_unwrapped"] !== "undefined") {
                                                                                                                  local_44 = 63;
                                                                                                                }

                                                                                                                if (!(typeof window["__driver_unwrapped"] !== "undefined")) {
                                                                                                                  local_44 = 0;
                                                                                                                }
                                                                                                              }
                                                                                                            }
                                                                                                          }
                                                                                                        }
                                                                                                      }
                                                                                                    }
                                                                                                  }
                                                                                                }
                                                                                              }
                                                                                            }
                                                                                          }
                                                                                        }
                                                                                      }
                                                                                    }
                                                                                  }
                                                                                }
                                                                              }
                                                                            }
                                                                          }
                                                                        }
                                                                      }
                                                                    }
                                                                  }
                                                                }
                                                              }
                                                            }
                                                          }
                                                        }
                                                      }
                                                    }
                                                  }
                                                }
                                              }
                                            }
                                          }
                                        }
                                      }
                                    }
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }

    let local_49 = local_44;
    let local_41 = [];
    let local_42 = parameter["length"];
    let local_12 = 0;

    while (local_12 < local_42) {
      let local_43 = parameter["charCodeAt"](local_12);
      local_41["push"](local_43 & 255);
      local_12 = local_12 + 1;
    }

    let local_50 = local_41;
    let local_51 = Date["now"]() - local_47;

    if (local_51 > 10000) {
      local_49 = 126;
    }

    if (!(local_51 > 10000)) {}

    let local_52 = Math["floor"](Math["random"]() * 127);
    local_50["unshift"](local_49);
    local_50["unshift"](local_52);
    let local_21 = local_50["length"] % 16;
    let local_10 = 16 - local_21;
    let local_33 = 0;

    while (local_33 < local_10) {
      local_50["push"](local_10);
      local_33 = local_33 + 1;
    }

    let local_34 = local_50["slice"](0, 16);
    let local_35 = new Array(16);
    let local_11 = 0;

    while (local_11 < 16) {
      local_35[local_11] = local_34[local_11] ^ local_48[local_11] ^ 42;
      local_11 = local_11 + 1;
    }

    let local_36 = __g["r"](local_35);

    let local_37 = local_36["slice"]();
    let local_38 = local_50["slice"](16, local_50["length"]);

    let local_39 = __g["x"](local_38, local_36);

    local_37 = local_37["concat"](local_39);
    let local_53 = local_37;
    let local_54 = local_53["length"] % 3;

    if (local_54 == 1) {
      local_53["push"]("\0");
      local_53["push"]("\0");
    }

    if (!(local_54 == 1)) {}

    if (local_54 == 2) {
      local_53["push"]("\0");
    }

    if (!(local_54 == 2)) {}

    let local_55 = "6fpLR" + "qJO8M/c3j" + "nYxFkUV" + "C4ZIG12SiH=5v0mXDazWB" + "Tsuw7QetbKdoPyAl+hN9rgE";
    let local_56 = 0;
    let local_57 = "";
    let local_13 = local_53["length"] - 1;

    while (local_13 >= 0) {
      let local_58 = 8 * (local_56 % 4);
      local_56 = local_56 + 1;
      let local_59 = local_53[local_13] ^ 58 >>> local_58 & 255;
      local_58 = 8 * (local_56 % 4);
      local_56 = local_56 + 1;
      local_59 = local_59 | (local_53[local_13 - 1] ^ 58 >>> local_58 & 255) << 8;
      local_58 = 8 * (local_56 % 4);
      local_56 = local_56 + 1;
      local_59 = local_59 | (local_53[local_13 - 2] ^ 58 >>> local_58 & 255) << 16;
      local_57 = local_57 + local_55["charAt"](local_59 & 63);
      local_57 = local_57 + local_55["charAt"](local_59 >>> 6 & 63);
      local_57 = local_57 + local_55["charAt"](local_59 >>> 12 & 63);
      local_57 = local_57 + local_55["charAt"](local_59 >>> 18 & 63);
      local_13 = local_13 - 3;
    }

    return local_57;
  }

环境检测部分有点长,但是却发现,3.0的逻辑也是先进行环境检测,再进行加密函数的,那么到这里就可以确定,之前说的修改字节码的方法是可以实现的。

修改的核心思路那之前的基本相似,就是当第一次出现跳转指令的时候,把pc寄存器的值修改到第二次跳转的后面

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第37张图片
3.0的跳转代码在case 443,第一次出现跳转时,pc寄存器的值为【1284】,这里的this.b就是要转跳的值,在第一次跳转的时候暂时不用管。

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第38张图片
接着进入到第二次跳转,此时的pc寄存器位【1288】,并且记录一下this.b为【2066】。这时就可以尝试修改。但是这里有一个逗号表达式,不方便我们修改,那么手动把它搞成一个一个语句。

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第39张图片
但是这样还不对,因为环境检测还不是一个正确的值,那么直接设置为检验通过的值

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第40张图片
那么加上检测校验正确的值,那么就应该可以了,前面仅仅保留一些必要的代码

window=global;

Math.random = function(){
    return 0.50
};

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第41张图片

这时运行发现,结果也是一模一样的,那么说明修改字节码的方案完成。

算法还原

那么把vmp反汇编出来了,那么算法还原的难度也就基本没有了,相当于就是把js的逻辑写成其他语言的逻辑

def x_zse_96_b64encode(md5_bytes: bytes):

    h = {
        "zk": [1170614578, 1024848638, 1413669199, -343334464, -766094290, -1373058082, -143119608, -297228157, 1933479194, -971186181, -406453910, 460404854, -547427574, -1891326262, -1679095901, 2119585428, -2029270069, 2035090028, -1521520070, -5587175, -77751101, -2094365853, -1243052806, 1579901135, 1321810770, 456816404, -1391643889, -229302305, 330002838, -788960546, 363569021, -1947871109],
        "zb": [20, 223, 245, 7, 248, 2, 194, 209, 87, 6, 227, 253, 240, 128, 222, 91, 237, 9, 125, 157, 230, 93, 252, 205, 90, 79, 144, 199, 159, 197, 186, 167, 39, 37, 156, 198, 38, 42, 43, 168, 217, 153, 15, 103, 80, 189, 71, 191, 97, 84, 247, 95, 36, 69, 14, 35, 12, 171, 28, 114, 178, 148, 86, 182, 32, 83, 158, 109, 22, 255, 94, 238, 151, 85, 77, 124, 254, 18, 4, 26, 123, 176, 232, 193, 131, 172, 143, 142, 150, 30, 10, 146, 162, 62, 224, 218, 196, 229, 1, 192, 213, 27, 110, 56, 231, 180, 138, 107, 242, 187, 54, 120, 19, 44, 117, 228, 215, 203, 53, 239, 251, 127, 81, 11, 133, 96, 204, 132, 41, 115, 73, 55, 249, 147, 102, 48, 122, 145, 106, 118, 74, 190, 29, 16, 174, 5, 177, 129, 63, 113, 99, 31, 161, 76, 246, 34, 211, 13, 60, 68, 207, 160, 65, 111, 82, 165, 67, 169, 225, 57, 112, 244, 155, 51, 236, 200, 233, 58, 61, 47, 100, 137, 185, 64, 17, 70, 234, 163, 219, 108, 170, 166, 59, 149, 52, 105, 24, 212, 78, 173, 45, 0, 116, 226, 119, 136, 206, 135, 175, 195, 25, 92, 121, 208, 126, 139, 3, 75, 141, 21, 130, 98, 241, 40, 154, 66, 184, 49, 181, 46, 243, 88, 101, 183, 8, 23, 72, 188, 104, 179, 210, 134, 250, 201, 164, 89, 216, 202, 220, 50, 221, 152, 140, 33, 235, 214],
        "zm": [120, 50, 98, 101, 99, 98, 119, 100, 103, 107, 99, 119, 97, 99, 110, 111]
    }

    def left_shift(x, y):
        x, y = ctypes.c_int32(x).value, y % 32
        return ctypes.c_int32(x << y).value

    def Unsigned_right_shift(x, y):
        x, y = ctypes.c_uint32(x).value, y % 32
        return ctypes.c_uint32(x >> y).value

    def Q(e, t):
        return left_shift((4294967295 & e), t) | Unsigned_right_shift(e, 32 - t)

    def G(e):
        t = list(struct.pack(">i", e))
        n = [h['zb'][255 & t[0]], h['zb'][255 & t[1]], h['zb'][255 & t[2]], h['zb'][255 & t[3]]]
        r = struct.unpack(">i", bytes(n))[0]
        return r ^ Q(r, 2) ^ Q(r, 10) ^ Q(r, 18) ^ Q(r, 24)

    def g_r(e):
        n = list(struct.unpack(">iiii", bytes(e)))
        [n.append(n[r] ^ G(n[r + 1] ^ n[r + 2] ^ n[r + 3] ^ h['zk'][r])) for r in range(32)]
        return list(struct.pack(">i", n[35]) + struct.pack(">i", n[34]) + struct.pack(">i", n[33]) + struct.pack(">i", n[32]))

    def g_x(e, t):
        n = []
        i = 0
        for _ in range(len(e), 0, -16):
            o = e[16 * i: 16 * (i + 1)]
            a = [o[c] ^ t[c] for c in range(16)]
            t = g_r(a)
            n += t
            i += 1
        return n

    local_48 = [48, 53, 57, 48, 53, 51, 102, 55, 100, 49, 53, 101, 48, 49, 100, 55]
    local_50 = bytes([63, 0]) + md5_bytes  # 随机数  0 是环境检测通过
    local_50 = x_zse_96_V3.pad(bytes(local_50))
    local_34 = local_50[:16]
    local_35 = [local_34[local_11] ^ local_48[local_11] ^ 42 for local_11 in range(16)]
    local_36 = g_r(local_35)
    local_38 = local_50[16:]
    local_39 = g_x(local_38, local_36)
    local_53 = local_36 + local_39
    local_55 = "6fpLRqJO8M/c3jnYxFkUVC4ZIG12SiH=5v0mXDazWBTsuw7QetbKdoPyAl+hN9rgE"
    local_56 = 0
    local_57 = ""
    for local_13 in range(len(local_53) - 1, 0, -3):
        local_58 = 8 * (local_56 % 4)
        local_56 = local_56 + 1
        local_59 = local_53[local_13] ^ Unsigned_right_shift(58, local_58) & 255
        local_58 = 8 * (local_56 % 4)
        local_56 = local_56 + 1
        local_59 = local_59 | (local_53[local_13 - 1] ^ Unsigned_right_shift(58, local_58) & 255) << 8
        local_58 = 8 * (local_56 % 4)
        local_56 = local_56 + 1
        local_59 = local_59 | (local_53[local_13 - 2] ^ Unsigned_right_shift(58, local_58) & 255) << 16
        local_57 = local_57 + local_55[local_59 & 63]
        local_57 = local_57 + local_55[Unsigned_right_shift(local_59, 6) & 63]
        local_57 = local_57 + local_55[Unsigned_right_shift(local_59, 12) & 63]
        local_57 = local_57 + local_55[Unsigned_right_shift(local_59, 18) & 63]
    return local_57

运行一下测试代码

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第42张图片
结果也是完全正确的,那么算法还原的方案也完成了。

那么有了编码,会不会也有解码的方法呢?那肯定是有的,因为如果没有,服务器又怎么验证传上来的结果对不对呢。解码方法其实就是编码方法的逆运算。

例如加法和减法互为逆运算,因为一个数字我加上一个数,再减去这个数,还是得到原来的数字。
乘法和除法互为逆运算、异或和自身互为逆运算等等。那么如果要得到解码方法,相当于就是自己写一个逆运算的方法,按照前面的逻辑可以尝试编写

class x_zse_96_V3(object):

    local_48 = [48, 53, 57, 48, 53, 51, 102, 55, 100, 49, 53, 101, 48, 49, 100, 55]
    local_55 = "6fpLRqJO8M/c3jnYxFkUVC4ZIG12SiH=5v0mXDazWBTsuw7QetbKdoPyAl+hN9rgE"
    h = {
        "zk": [1170614578, 1024848638, 1413669199, -343334464, -766094290, -1373058082, -143119608, -297228157, 1933479194, -971186181, -406453910, 460404854, -547427574, -1891326262, -1679095901, 2119585428, -2029270069, 2035090028, -1521520070, -5587175, -77751101, -2094365853, -1243052806, 1579901135, 1321810770, 456816404, -1391643889, -229302305, 330002838, -788960546, 363569021, -1947871109],
        "zb": [20, 223, 245, 7, 248, 2, 194, 209, 87, 6, 227, 253, 240, 128, 222, 91, 237, 9, 125, 157, 230, 93, 252, 205, 90, 79, 144, 199, 159, 197, 186, 167, 39, 37, 156, 198, 38, 42, 43, 168, 217, 153, 15, 103, 80, 189, 71, 191, 97, 84, 247, 95, 36, 69, 14, 35, 12, 171, 28, 114, 178, 148, 86, 182, 32, 83, 158, 109, 22, 255, 94, 238, 151, 85, 77, 124, 254, 18, 4, 26, 123, 176, 232, 193, 131, 172, 143, 142, 150, 30, 10, 146, 162, 62, 224, 218, 196, 229, 1, 192, 213, 27, 110, 56, 231, 180, 138, 107, 242, 187, 54, 120, 19, 44, 117, 228, 215, 203, 53, 239, 251, 127, 81, 11, 133, 96, 204, 132, 41, 115, 73, 55, 249, 147, 102, 48, 122, 145, 106, 118, 74, 190, 29, 16, 174, 5, 177, 129, 63, 113, 99, 31, 161, 76, 246, 34, 211, 13, 60, 68, 207, 160, 65, 111, 82, 165, 67, 169, 225, 57, 112, 244, 155, 51, 236, 200, 233, 58, 61, 47, 100, 137, 185, 64, 17, 70, 234, 163, 219, 108, 170, 166, 59, 149, 52, 105, 24, 212, 78, 173, 45, 0, 116, 226, 119, 136, 206, 135, 175, 195, 25, 92, 121, 208, 126, 139, 3, 75, 141, 21, 130, 98, 241, 40, 154, 66, 184, 49, 181, 46, 243, 88, 101, 183, 8, 23, 72, 188, 104, 179, 210, 134, 250, 201, 164, 89, 216, 202, 220, 50, 221, 152, 140, 33, 235, 214],
        "zm": [120, 50, 98, 101, 99, 98, 119, 100, 103, 107, 99, 119, 97, 99, 110, 111]
    }

    @staticmethod
    def pad(data_to_pad):
        padding_len = 16 - len(data_to_pad) % 16
        padding = chr(padding_len).encode() * padding_len
        return data_to_pad + padding

    @staticmethod
    def unpad(padded_data):
        padding_len = padded_data[-1]
        return padded_data[:-padding_len]

    @staticmethod
    def left_shift(x, y):
        x, y = ctypes.c_int32(x).value, y % 32
        return ctypes.c_int32(x << y).value

    @staticmethod
    def Unsigned_right_shift(x, y):
        x, y = ctypes.c_uint32(x).value, y % 32
        return ctypes.c_uint32(x >> y).value

    @classmethod
    def Q(cls, e, t):
        return cls.left_shift((4294967295 & e), t) | cls.Unsigned_right_shift(e, 32 - t)

    @classmethod
    def G(cls, e):
        t = list(struct.pack(">i", e))
        n = [cls.h['zb'][255 & t[0]], cls.h['zb'][255 & t[1]], cls.h['zb'][255 & t[2]], cls.h['zb'][255 & t[3]]]
        r = struct.unpack(">i", bytes(n))[0]
        return r ^ cls.Q(r, 2) ^ cls.Q(r, 10) ^ cls.Q(r, 18) ^ cls.Q(r, 24)

    @classmethod
    def g_r(cls, e):
        n = list(struct.unpack(">iiii", bytes(e)))
        [n.append(n[r] ^ cls.G(n[r + 1] ^ n[r + 2] ^ n[r + 3] ^ cls.h['zk'][r])) for r in range(32)]
        return list(struct.pack(">i", n[35]) + struct.pack(">i", n[34]) + struct.pack(">i", n[33]) + struct.pack(">i", n[32]))

    @classmethod
    def re_g_r(cls, e):
        n = [0] * 32 + list(struct.unpack(">iiii", bytes(e)))[::-1]
        for r in range(31, -1, -1):
            n[r] = cls.G(n[r + 1] ^ n[r + 2] ^ n[r + 3] ^ cls.h['zk'][r]) ^ n[r + 4]
        return list(struct.pack(">i", n[0]) + struct.pack(">i", n[1]) + struct.pack(">i", n[2]) + struct.pack(">i", n[3]))

    @classmethod
    def g_x(cls, e, t):
        n = []
        i = 0
        for _ in range(len(e), 0, -16):
            o = e[16 * i: 16 * (i + 1)]
            a = [o[c] ^ t[c] for c in range(16)]
            t = cls.g_r(a)
            n += t
            i += 1
        return n

    @classmethod
    def re_g_x(cls, e, t):
        n = []
        i = 0
        for _ in range(len(e), 0, -16):
            o = e[16 * i: 16 * (i + 1)]
            a = cls.re_g_r(o)
            t = [a[c] ^ t[c] for c in range(16)]
            n += t
            t = o
            i += 1
        return n

    @classmethod
    def b64encode(cls, md5_bytes: bytes, device: int = 0, seed: int = 63) -> str:
        local_50 = bytes([seed, device]) + md5_bytes  # 随机数  0 是环境检测通过
        local_50 = cls.pad(bytes(local_50))
        local_34 = local_50[:16]
        local_35 = [local_34[local_11] ^ cls.local_48[local_11] ^ 42 for local_11 in range(16)]
        local_36 = cls.g_r(local_35)
        local_38 = local_50[16:]
        local_39 = cls.g_x(local_38, local_36)
        local_53 = local_36 + local_39
        local_56 = 0
        local_57 = ""
        for local_13 in range(len(local_53) - 1, 0, -3):
            local_58 = 8 * (local_56 % 4)
            local_56 = local_56 + 1
            local_59 = local_53[local_13] ^ cls.Unsigned_right_shift(58, local_58) & 255
            local_58 = 8 * (local_56 % 4)
            local_56 = local_56 + 1
            local_59 = local_59 | (local_53[local_13 - 1] ^ cls.Unsigned_right_shift(58, local_58) & 255) << 8
            local_58 = 8 * (local_56 % 4)
            local_56 = local_56 + 1
            local_59 = local_59 | (local_53[local_13 - 2] ^ cls.Unsigned_right_shift(58, local_58) & 255) << 16
            local_57 = local_57 + cls.local_55[local_59 & 63]
            local_57 = local_57 + cls.local_55[cls.Unsigned_right_shift(local_59, 6) & 63]
            local_57 = local_57 + cls.local_55[cls.Unsigned_right_shift(local_59, 12) & 63]
            local_57 = local_57 + cls.local_55[cls.Unsigned_right_shift(local_59, 18) & 63]
        return local_57

    @classmethod
    def b64decode(cls, x_zse_96: str) -> dict:
        local_56 = 0
        local_57 = []
        for local_13 in range(0, len(x_zse_96), 4):
            local_59 = (cls.local_55.index(x_zse_96[local_13 + 3]) << 18) + (cls.local_55.index(x_zse_96[local_13 + 2]) << 12) + (cls.local_55.index(x_zse_96[local_13 + 1]) << 6) + cls.local_55.index(x_zse_96[local_13])
            local_58 = 8 * (local_56 % 4)
            local_56 = local_56 + 1
            local_57.append((local_59 & 255) ^ cls.Unsigned_right_shift(58, local_58))
            local_58 = 8 * (local_56 % 4)
            local_56 = local_56 + 1
            local_57.append(((local_59 >> 8) & 255) ^ cls.Unsigned_right_shift(58, local_58))
            local_58 = 8 * (local_56 % 4)
            local_56 = local_56 + 1
            local_57.append(((local_59 >> 16) & 255) ^ cls.Unsigned_right_shift(58, local_58))
        local_36, local_39 = local_57[-16:][::-1], local_57[:-16][::-1]
        local_38 = cls.re_g_x(local_39, local_36)
        local_35 = cls.re_g_r(local_36)
        local_34 = [local_35[local_11] ^ cls.local_48[local_11] ^ 42 for local_11 in range(16)]
        local_50 = cls.unpad(bytes(local_34 + local_38))
        return {
            'seed': local_50[0],
            'device': local_50[1],
            'md5_bytes': local_50[2:]
        }

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第43张图片
非常好的,得到了相同的结果,说明解码函数没有问题了。

后记

这次的分析比2.0的更加详细,其中是因为3.0来说确实是难了一些
1.结果不是完全固定的
2.存在控制流
3.存在两个时间控制的反调试
4.从编码方法中推导出解码方法

更多内容欢迎加入我的星球学习

【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶_第44张图片

你可能感兴趣的:(javascript,python,javascript)