漁滒 发表于 2022-9-11 15:11

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

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

## 前言

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



自然的,算法也就发生了一些改变。最明显最直接可见的变化就是长度边长了,并且相同的入参,输出结果并不相同。那么这篇文章就在原来2.0的基础上[【JS逆向系列】某乎x96参数与jsvmp初体验](https://www.52pojie.cn/thread-1619464-1-1.html),来分析一下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随机数的返回

```javascript
Math.random = function(){
    return 0.50
};
```
输入这段代码后再执行加密函数,此时就发现结果都是一样的了


那么此时就得到了一组样本,当随机数恒定返回0.5时。入参【a63da42088bd8d635961ede065daeb51】的正确结果为【t=V/NpKQqHpejG8nmTuCzIrXW+JszxwLVVyuy+8S0ak=pe1N4BRA6Qxz+LDn+Xyj】,那么接下在就真正可以开始补环境了。

首先安装依赖库

```bash
npm install jsdom
npm install canvas
```

然后在头部加上jsdom的代码

```javascript
const{JSDOM}=require("jsdom");
const dom=new JSDOM("<!DOCTYPE html><p>Hello world</p>");
window=dom.window;

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

结尾加上测试代码

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

开始测试运行



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

```javascript
document=window.document;
```

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

最后头部为

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

```

测试可以运行出结果


这个结果和样本明显不一样,说明还缺少了其他环境没有补到。

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

```javascript
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);
      return target
    }
});
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);
      return target
    }
});
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);
      return target
    }
});
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);
      return target
    }
});
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);
      return target
    }
});
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);
      return target
    }
});
```

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


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


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



确实是不一样的结果,所以这里就需要hook掉toString方法

```javascript
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 '';
    }
    return _temp;
};

```

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


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

```javascript
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和网页返回的不一样,继续补上

```javascript
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 '';
    }else if(this.constructor.name === 'CanvasRenderingContext2D'){
      return ''
    }
    return _temp;
};
```


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

```javascript
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未定义,和之前一样处理

```javascript
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一下看看

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



果然是,那么继续补上

```javascript
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() { }'
    }
    return _temp;
};
```


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

```javascript
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);
      return target
    }
});
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);
      return target
    }
});
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);
      return target
    }
});
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);
      return target
    }
});
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);
      return target
    }
});
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);
      return target
    }
});

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 '';
    }else if(this.constructor.name === 'CanvasRenderingContext2D'){
      return ''
    }
    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() { }'
    }
    return _temp;
};
```


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

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

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

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

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

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

```javascript
// 删除时间参数
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.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;
                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了。

```javascript
let cases_list = ;
traverse(ast, {
    SwitchCase(path){
      if(path.node.test){
            if(cases_list.includes(path.node.test.value)){
                path.remove()
            }
      }
    }
});
```


这时删除了多个case后,依然可以得到正确结果。

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


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

```javascript
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.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))
            }else{
                path.parentPath.replaceWith(t.numericLiteral(0))
            }
      }
    }
});
```


这样就转变成了字面量


接着例如case 50,下面的this.T实际就是上面的50,像这种也可以还原为字面量

```javascript
// 还原this.T
traverse(ast, {
    SwitchCase(path){
      if(path.node.test){
            if(path.node.consequent.length > 1 && path.node.consequent.expression.right.type === 'BinaryExpression'){
                let item = path.node.consequent.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);
                }
            }
      }
    }
});
```

最后是case 331的地方


这里的【V】变量只出现了一次,所以可以进行手动的处理


接着再将字面量的内容还原一下


前面会发现很多个都指向了352,那么这个352肯定是一个关键的地方


最后还有一些是有【+=】或者【-=】的,这种也可以顺带还原一下

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

那么来到这里,ast的部分就算完成了。


先看看这个case 352,它是指向case 300


然后case 300,又指向了case 368,那么如下图


如果把后面的两个节点合并成一个,那么就少了一个节点了,其实相当于把case 368的代码全部放到case 300的下面



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

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

```javascript
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);
```


可以看到字节码和字符串都解密出来了,接着就从case 352开始入手写代码,相对于2.0,代码逻辑基本没有变化太多,变化的都是因为控制流而影响的


相当于把2.0逻辑的所有函数展平,然后用控制流来执行,把之前的代码改一改,又能用起来,还原了初始化的代码如下

```javascript
(function () {
let local_0 = ;
let local_1 = ;
let local_2 = ;
let local_3 = new Array(32);
let local_23 = ;
let local_24 = ;
let local_25 = new Array(4);
let local_26 = new Array(36);
let local_6 = (local_2 & 255) << 24;
let local_7 = (local_2 & 255) << 16;
let local_8 = (local_2 & 255) << 8;
let local_9 = local_2 & 255;
local_25 = local_6 | local_7 | local_8 | local_9;
local_6 = (local_2 & 255) << 24;
local_7 = (local_2 & 255) << 16;
local_8 = (local_2 & 255) << 8;
local_9 = local_2 & 255;
local_25 = local_6 | local_7 | local_8 | local_9;
local_6 = (local_2 & 255) << 24;
local_7 = (local_2 & 255) << 16;
local_8 = (local_2 & 255) << 8;
local_9 = local_2 & 255;
local_25 = local_6 | local_7 | local_8 | local_9;
local_6 = (local_2 & 255) << 24;
local_7 = (local_2 & 255) << 16;
local_8 = (local_2 & 255) << 8;
local_9 = local_2 & 255;
local_25 = local_6 | local_7 | local_8 | local_9;
local_26 = local_25 ^ local_24;
local_26 = local_25 ^ local_24;
local_26 = local_25 ^ local_24;
local_26 = local_25 ^ local_24;
local_9 = 0;

while (local_9 < 32) {
    let local_27 = local_26;
    let local_28 = local_26;
    let local_29 = local_26;
    let local_30 = local_27 ^ local_28 ^ local_29 ^ local_23;
    let local_16 = new Array(4);
    let local_5 = new Array(4);
    local_16 = 255 & local_30 >>> 24;
    local_16 = 255 & local_30 >>> 16;
    local_16 = 255 & local_30 >>> 8;
    local_16 = 255 & local_30;
    local_5 = local_0 & 255];
    local_5 = local_0 & 255];
    local_5 = local_0 & 255];
    local_5 = local_0 & 255];
    local_6 = (local_5 & 255) << 24;
    local_7 = (local_5 & 255) << 16;
    local_8 = (local_5 & 255) << 8;
    local_9 = local_5 & 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_26 ^ local_31;
    local_3 = local_26;
    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】内部的逻辑

```javascript
function _0x2068(parameter) {
    let local_47 = Date["now"]();
    let local_48 = ;
    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"]("") == -1) {
                                                                                    local_44 = 48;
                                                                                    }

                                                                                    if (!(window["__proto__"]["constructor"]["toString"]()["indexOf"]("") == -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_34 ^ local_48 ^ 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 ^ 58 >>> local_58 & 255;
      local_58 = 8 * (local_56 % 4);
      local_56 = local_56 + 1;
      local_59 = local_59 | (local_53 ^ 58 >>> local_58 & 255) << 8;
      local_58 = 8 * (local_56 % 4);
      local_56 = local_56 + 1;
      local_59 = local_59 | (local_53 ^ 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寄存器的值修改到第二次跳转的后面


3.0的跳转代码在case 443,第一次出现跳转时,pc寄存器的值为【1284】,这里的this.b就是要转跳的值,在第一次跳转的时候暂时不用管。


接着进入到第二次跳转,此时的pc寄存器位【1288】,并且记录一下this.b为【2066】。这时就可以尝试修改。但是这里有一个逗号表达式,不方便我们修改,那么手动把它搞成一个一个语句。


但是这样还不对,因为环境检测还不是一个正确的值,那么直接设置为检验通过的值


那么加上检测校验正确的值,那么就应该可以了,前面仅仅保留一些必要的代码

```javascript
window=global;

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




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

## 算法还原

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

```python
def x_zse_96_b64encode(md5_bytes: bytes):

    h = {
      "zk": ,
      "zb": ,
      "zm":
    }

    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']], h['zb']], h['zb']]]
      r = struct.unpack(">i", bytes(n))
      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)))
       ^ G(n ^ n ^ n ^ h['zk'])) for r in range(32)]
      return list(struct.pack(">i", n) + struct.pack(">i", n) + struct.pack(">i", n) + struct.pack(">i", n))

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

    local_48 =
    local_50 = bytes() + md5_bytes# 随机数0 是环境检测通过
    local_50 = x_zse_96_V3.pad(bytes(local_50))
    local_34 = local_50[:16]
    local_35 = ^ local_48 ^ 42 for local_11 in range(16)]
    local_36 = g_r(local_35)
    local_38 = local_50
    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 ^ Unsigned_right_shift(58, local_58) & 255
      local_58 = 8 * (local_56 % 4)
      local_56 = local_56 + 1
      local_59 = local_59 | (local_53 ^ 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 ^ Unsigned_right_shift(58, local_58) & 255) << 16
      local_57 = local_57 + local_55
      local_57 = local_57 + local_55
      local_57 = local_57 + local_55
      local_57 = local_57 + local_55
    return local_57
```

运行一下测试代码


结果也是完全正确的,那么算法还原的方案也完成了。

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

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

```python
class x_zse_96_V3(object):

    local_48 =
    local_55 = "6fpLRqJO8M/c3jnYxFkUVC4ZIG12SiH=5v0mXDazWBTsuw7QetbKdoPyAl+hN9rgE"
    h = {
      "zk": ,
      "zb": ,
      "zm":
    }

    @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']], cls.h['zb']], cls.h['zb']]]
      r = struct.unpack(">i", bytes(n))
      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)))
       ^ cls.G(n ^ n ^ n ^ cls.h['zk'])) for r in range(32)]
      return list(struct.pack(">i", n) + struct.pack(">i", n) + struct.pack(">i", n) + struct.pack(">i", n))

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

    @classmethod
    def g_x(cls, e, t):
      n = []
      i = 0
      for _ in range(len(e), 0, -16):
            o = e
            a = ^ t 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
            a = cls.re_g_r(o)
            t = ^ t 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() + md5_bytes# 随机数0 是环境检测通过
      local_50 = cls.pad(bytes(local_50))
      local_34 = local_50[:16]
      local_35 = ^ cls.local_48 ^ 42 for local_11 in range(16)]
      local_36 = cls.g_r(local_35)
      local_38 = local_50
      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 ^ 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 ^ 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 ^ cls.Unsigned_right_shift(58, local_58) & 255) << 16
            local_57 = local_57 + cls.local_55
            local_57 = local_57 + cls.local_55
            local_57 = local_57 + cls.local_55
            local_57 = local_57 + cls.local_55
      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) << 18) + (cls.local_55.index(x_zse_96) << 12) + (cls.local_55.index(x_zse_96) << 6) + cls.local_55.index(x_zse_96)
            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 = ^ cls.local_48 ^ 42 for local_11 in range(16)]
      local_50 = cls.unpad(bytes(local_34 + local_38))
      return {
            'seed': local_50,
            'device': local_50,
            'md5_bytes': local_50
      }
```


非常好的,得到了相同的结果,说明解码函数没有问题了。

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

漁滒 发表于 2022-9-11 16:42

hlrlqy 发表于 2022-9-11 16:00
看到了,您使用ast生成代码的方式很高明,比起直接在case中插桩输出伪代码实现类似trace的效果提供了更大 ...

是的,插桩也是一个很好的方法。两种方法可以互补来更加有效的分析

白云点缀的蓝 发表于 2022-9-11 15:29

话说这编程是不是只有英文,没任何意义???{:17_1065:}{:17_1065:}

漁滒 发表于 2022-9-11 15:34

白云点缀的蓝 发表于 2022-9-11 15:29
话说这编程是不是只有英文,没任何意义???

只有英文是什么意思?没太理解

hlrlqy 发表于 2022-9-11 15:40

请问反汇编是如何操作的呢?

漁滒 发表于 2022-9-11 15:42

hlrlqy 发表于 2022-9-11 15:40
请问反汇编是如何操作的呢?

可以先看看前面一篇的分析

coderyzh 发表于 2022-9-11 15:59

get感谢楼主分享

hlrlqy 发表于 2022-9-11 16:00

漁滒 发表于 2022-9-11 15:42
可以先看看前面一篇的分析

看到了,您使用ast生成代码的方式很高明,比起直接在case中插桩输出伪代码实现类似trace的效果提供了更大的想象空间

Barbara 发表于 2022-9-11 17:17

分析的好详细,默默学习一个,谢谢楼主,某乎整的好复杂啊

bdcpc 发表于 2022-9-11 18:26

好复杂,真的看不懂(#-.-)
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: 【JS逆向系列】某乎x96参数3.0版本与jsvmp进阶