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

前言

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

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

初看js代码

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

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

在这里插入图片描述

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

参数 映射含义
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 常量虚假指令

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

补环境方案

还是和之前一样,首先试试能不能通过补环境得出相同的结果,首先在网页上拿一组样本。
在这里插入图片描述
这里入参是【a63da42088bd8d635961ede065daeb51】结果是【RiO+y9AqW9KuaS+8vShliRMUs8LvryJRSxJinhVvmy+JvR5Xel5Uv5psmxAcilNl】,按照之前的办法,就是补环境使得到相同的结果,但是对于3.0版本就会出现问题。这里发现,相同的入参,多次执行,结果是不一样的。

在这里插入图片描述
这就不好办了,那么即使补环境出来的结果,也不知道是不是对的。一般这种情况下,就是计算涉及到的随机数或者时间。而这里就是包含的随机数,所以需要hook随机数的返回

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

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

在这里插入图片描述
那么此时就得到了一组样本,当随机数恒定返回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("<!DOCTYPE html><p>Hello world</p>");
window=dom.window;

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

结尾加上测试代码

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

开始测试运行

在这里插入图片描述

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

document=window.document;

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

最后头部为

const{
   JSDOM}=require("jsdom");
const dom=new JSDOM("<!DOCTYPE html><p>Hello world</p>");
window=dom.window;
document=window.document;
navigator=window.navigator;
location=window.location;
history=window.history;
screen=window.screen;

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]
    }
});

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

在这里插入图片描述
那么如果报错的话,我们也看不到,不方便补环境,所以去掉try代码块,只保留try里面的内容。

在这里插入图片描述
可以看到读取了不少属性,最后运行到【获取属性get document Symbol(Symbol.toStringTag) string】这一步就退出了,那么看看这一步的结果是不是和网页不一样

在这里插入图片描述
在这里插入图片描述
确实是不一样的结果,所以这里就需要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;
};

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

在这里插入图片描述
这里最后是location对象出现问题,那么在jsdom上面,就需要补上url链接,那么就会自动补全location对象,开头部分的代码就修改为

const{
   JSDOM}=require("jsdom");
const dom=new JSDOM("<!DOCTYPE html><p>Hello world</p>",{
   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
};

在这里插入图片描述

在这里插入图片描述
这里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;
};

在这里插入图片描述
又继续往下跑了,这次是检测了window下的_resourceLoader,浏览器上是undefined,但是node上返回对象。还有后面的_sessionHistory,一起补上。

const{
   JSDOM}=require("jsdom");
const dom=new JSDOM("<!DOCTYPE html><p>Hello world</p>",{
   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
};

在这里插入图片描述
出现alert未定义,和之前一样处理

const{
   JSDOM}=require("jsdom");
const dom=new JSDOM("<!DOCTYPE html><p>Hello world</p>",{
   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的原型后就没有了,那么这种情况很有可能检测了原型链和函数或者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;
};

在这里插入图片描述
果然是,那么继续补上

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;
};

在这里插入图片描述
漂亮,终于得到一样的结果,那么这里补环境就完成了,总结一下我们补了什么

const{
   JSDOM}=require("jsdom");
const dom=new JSDOM("<!DOCTYPE html><p>Hello world</p>",{
   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)
                }
            }
        }
    }
});

在这里插入图片描述
此时再运行,依然可以得到相同的结果,那么就说明这里的时间和2.0是一样,只是用来反调试,与加密逻辑无关。

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

在这里插入图片描述
然后调试运行,当在断点停下的时候,取消断点再运行下去,直到结束。那么下载来还有断点的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()
            }
        }
    }
});

在这里插入图片描述
这时删除了多个case后,依然可以得到正确结果。

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

在这里插入图片描述
分析发现,例如在case 331中存在赋值的【this.M[21] = 8;】,这里就可以把这个值记录下来,其他任何没有出现赋值的,都是null了

let cases_dict = {
   }
  • 18
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 17
    评论
要还原jsvmp-某乎_x-zes-96参数算法,首先需要了解该算法的具体逻辑和实现方式。接下来是一份手把手教学,帮助你进行还原。 1. 阅读文档:首先,找到jsvmp-某乎_x-zes-96参数算法的相关文档或说明,并仔细阅读。理解算法的目的、输入、输出和具体实现细节。 2. 分析算法:根据文档中的描述,仔细分析算法的各个步骤和计算逻辑。了解算法使用的数据结构、数学模型和函数等。 3. 实践案例:找到使用该算法的实践案例,或者自己创建一个简单的案例。通过这个案例,可以更好地理解算法的工作方式和参数调整的影响。 4. 调试工具:使用调试工具对代码进行逐行调试。在关键节点上加入断点,观察变量和数据的变化过程,以及函数的执行顺序。通过这种方式,深入了解算法的执行过程。 5. 修改参数:通过调试分析,找到算法中的参数和默认设置。尝试更改这些参数的数值,观察算法的输出结果的变化。通过不断修改参数和分析结果,逐渐掌握算法的运行规律。 6. 反向工程:通过对算法代码的逆向分析,尝试还原参数的计算过程。对研究的代码进行逆向工程,分析其中的数学运算和逻辑判断。通过逆向工程,可以更好地理解参数的计算方式。 7. 文档总结:将还原过程中的关键步骤和分析结果总结起来,形成详细的文档。这份文档可以帮助其他人了解算法的还原过程,同时也对自己的学习过程进行总结和回顾。 以上是一个基本的手把手教学,希望能够帮助你还原jsvmp-某乎_x-zes-96参数算法。请注意,由于算法的具体实现和难度因人而异,实际操作过程可能比上述步骤更加复杂和耗时,需要耐心和技术能力的支持。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值