参考文章写了部分分支的AST代码,代码检测及算法部分的还原代码没有
打个日志断点,走到正确的流程,将寄存器pc值,操作数,case值放入数组,与本地node环境对比即可
使用AST还原代码执行逻辑生成伪代码(环境检测及参数加密),缺少环境会影响代码执行顺序(pc寄存器控制流程),还原不了加密位置代码时,还是需要补环境,走入正确的流程
注意全局变量中值的赋值,不仅要还原代码块,还要保存变量(全局,本地,调用栈),以便得到正确的流程
get_0x07_blockstatement函数初始化,注意入参是md5值,并且保存在my_k[0]
function get_0x07_blockstatement(pc, md5_value) {
let order = [];
let Stack = []; //保存代码块,用于生成伪代码
let Stack_2 = []; //用于运行代码,保存变量值
let blockstatement_0x07 = [];
let my_r = [0, 0, 0, 0]; //全局变量,改变pc需要用到
let my_k = [md5_value]; //本地变量,存放eval执行结果
let is_change_pc = false;
参考文章代码编写case1AST还原代码,注意保存变量即可,一个用于代码正常执行,一个用于生成伪代码
case 1:
t = (2048 & opcode[pc]) >> 11;
s = (1536 & opcode[pc]) >> 9;
i = 511 & opcode[pc];
h = 511 & opcode[pc];
//console.log('11111', 't=' + t, 's=' + s, 'i=' + i, 'h=' + h);
switch (t) {
case 0:
//e.r[s] = i; //e.r 全局变量
my_r[s] = i;
blockstatement_0x07.push(
types.expressionStatement(
types.assignmentExpression(
"=",
types.identifier('global_' + s),
types.numericLiteral(i) //types.identifier('i'),这里需要放一个数字
)));
pc++;
break;
case 1:
//e.r[s] = e.k[h]
my_r[s] = my_k[h];
blockstatement_0x07.push(types.expressionStatement(types.assignmentExpression("=", types.identifier('global_' + s), types.identifier('local_' + h))));
pc++;
}
break;
getOwnPropertyDescriptor方法浏览器返回undefined,node返回对象,hook此方法返回undefined,并且改写toString方法
Object.getOwnPropertyDescriptor = function getOwnPropertyDescriptor () {
return undefined;
}
toString = function () {
if (this.name == "getOwnPropertyDescriptor")
return "function getOwnPropertyDescriptor() { [native code] }"
return Function('prototype')['toString'].call(this)
}
在用到toString的地方改成自己的toString方法
case 8:
s = opcode[pc]>> 10 & 3;
i = opcode[pc] >> 2 & 255;
t = 3 & opcode[pc];
switch (t) {
case 1:
/*for (var r = e.f.pop(), i = [], o = 0; o < this.i; o++)
i.unshift(e.f.pop());
e.r[3] = e.r[this.s][r](i[0], i[1]);*/
var r = Stack.pop(),
r_2 = Stack_2.pop();
for (i_1 = [], i_2 = [], o = 0; o < i; o++){
i_1.unshift(Stack.pop());
i_2.unshift(Stack_2.pop());}
//console.log('zxcvb', my_r,r_2, r, 'ii', i_2, Stack_2, i)
if (my_r[0].name == 'toString')
my_r[0] = toString;
我还原的加密过程代码没有写成文章中的for循环,是按顺序执行,代码长度自然更长(但是代码是一样的),一共执行了11次(循环了11次),在调试过程中没有遇到for的地方,不知道原作者是否是自己手动写成for循环的,AST还原对逻辑的要求挺高,没有尝试写for的代码块
查看伪代码及结合浏览器调试能看出规律
在合适的位置下断点,找到正常流程时指令的执行顺序,注意在保存影响流程执行顺序的变量,缺少浏览器环境也会走到错误的分支,补环境不是必须的(但是是js逆向的基础),我在这里花了不少时间,明明是还原算法,在浏览器调试一波,打印关键日志信息(有关加密的赋值,计算的代码)即可,也不是非要看伪代码,伪代码也不是很直观
所有指令都是来自以下14组指令,case编号为倒数第二条指令的值,如case 453
352,300,368,360,426,453,440, //453分支下2条指令,即为453的子分支case 400, 440/445
352,300,368,360,426,458,254, //458 254/278/266
352,300,368,360,426,448,455, 471/455/465/475/ 第一次分析漏了461
352,300,368,360,426,418,
352,300,368,360,426,413,
352,300,368,360,426,483,350,
352,300,368,360,426,428,
352,300,368,360,426,423,108, 78/64/168/50/71/80/57/161/108/138/94/154/140/131/ 第一次分析漏了87/110 就这个分支多一些,16条
352,300,368,360,426,463,466, 476/466/ 第一次分析漏了486加密时走
352,300,368,360,426,438,
352,300,368,360,426,443,
352,300,368,360,426,433,
352,300,368,360,426,408,344
352,300,368,360,426,478,
既然补了环境,我还是记录一下,毕竟也是踩了不少坑。没有使用jsdom时,走到alert函数是报错了, 这个函数不会补,还忘大佬指教。
问题描述:Object.getPrototypeOf(window.alert)与浏览器不一致,不会给alert函数添加属性
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const dom = new JSDOM(`Hello world
`);
window = dom.window;
document = window.document;
window._resourceLoader = undefined;
window._sessionHistory = undefined;
alert = window.alert;
window.__ZH__ = {'zse':{}};
var createElement = function createElement(tag){
return canvas = {
'getContext': function(){
return {"direction": "ltr",
"toString": function(){
return "[object CanvasRenderingContext2D]"
}
}
}
};
}
Object.assign(document,{
'createElement': createElement,
"toString": function(){
return "[object HTMLDocument]"
}
});
navigator = {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36",
"webdriver": false,
"toString": function(){
return "[object Navigator]"
}
};
location = {
"href":"https://zhuanlan.zhihu.com/p/37359861",
"toString": function () {
return this.href
}
};
history = {
"toString": function(){
return "[object History]"
}
};
screen = {"toString": function(){
return "[object Screen]"
}};
window.__proto__.constructor.toString = function (){
return "function Window() { [native code] }"
}
补MD5,在旧版的33位基础上+1位随机值(随机值乘以127向下取整)放在0位置,补14个14(来源:34/16 16-2),一共48位
[
13, 0, 54, 51, 57, 98, 99, 56, 97, 48, 50,
101, 55, 49, 97, 53, 50, 49, 54, 101, 50, 53,
48, 101, 52, 99, 101, 55, 49, 50, 52, 101, 101,
51, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
14, 14, 14, 14
]
48位的前16位
413....... C_0 [
13, 0, 54, 51, 57, 98, 99, 56, 97, 48, 50,
101, 55, 49, 97, 53
] false
S[0] [
48, 53, 57, 48, 53, 51,
102, 55, 100, 49, 53, 101,
48, 49, 100, 55
]初始化时生成
465......... C_0 { x: [Function: x], r: [Function: r], _encrypt: false } r [
23, 31, 37, 41, 38, 123,
47, 37, 47, 43, 45, 42,
45, 42, 47, 40
]
返回值:C_3 [
240, 14, 73, 31, 20, 163,
8, 23, 192, 26, 63, 208,
233, 54, 248, 57
]
参数1:补全的48位MD5的后32位[
50, 49, 54, 101, 50, 53, 48, 101, 52,
99, 101, 55, 49, 50, 52, 101, 101, 51,
14, 14, 14, 14, 14, 14, 14, 14, 14,
14, 14, 14, 14, 14
]
参数2:__g.r的返回值[
240, 14, 73, 31, 20, 163,
8, 23, 192, 26, 63, 208,
233, 54, 248, 57
]
返回值
[
29, 1, 136, 145, 185, 79, 14, 246,
223, 35, 93, 10, 254, 58, 47, 239,
85, 157, 160, 72, 201, 36, 72, 82,
251, 39, 203, 207, 241, 10, 176, 214
]
[
240, 14, 73, 31, 20, 163, 8, 23, 192, 26,
63, 208, 233, 54, 248, 57, 29, 1, 136, 145,
185, 79, 14, 246, 223, 35, 93, 10, 254, 58,
47, 239, 85, 157, 160, 72, 201, 36, 72, 82,
251, 39, 203, 207, 241, 10, 176, 214
]
搞了好几天才能做到这个程度,现在是离职状态,又受疫情影响,这几天在乡坝头看电脑,虫就往电脑上,灯上飞,现在脸上过敏,嘴也是麻的,疫情也不好去医院看病,好在有一个也被困在村里的医生给了几颗药。
真的从外包辞职后,转行爬虫这个知识涉及面广的方向,现在看来真的不太好,不知道什么时候能好起来。