知乎x-zse-96逆向-webpack与浏览器补环境

加密定位

直接控制台搜索 x-zse-96

image-20240703113738811

image-20240702103844435

image-20240702104538512

再看一下ed方法 signature: (0,tJ(ti).encrypt)(ty()(tp)) , ty方法应该是一个md5加密

image-20240702105116454

image-20240702105437960

image-20240702105446437

然后再看一下 tJ(ti).encrypt 方法

image-20240702105712230

webpack加载器

再往下走一步,具体的加密应该就在这个 te.O 方法中,这里是一大段控制流, 函数的开头是一个 1514的数字,说明这里就是用了webpack打包

image-20240702105943940

image-20240702181310638

我们再搜一下 1514 看一下加载器的位置,来到这里后 打上断点,重新刷新一下,断点进来,这里就是加载器的位置了

image-20240702181702241image-20240702181800849

我们把代码复制下来,顺便把我们加密的代码整个复制下来,就是所谓的模块代码,补上window,以及 我们在用个全局变量来接受 加载器函数,然后顺便打印一下,我们需要那几个模块函数

image-20240702183730208

image-20240702183648557

运行后会报错,记得将self去掉,用window来接收 webpackChunkheifetz 对象

image-20240702183216321

image-20240702183840797

没有报错后,会发现打印成功,调用了 1514 和 74185 两个模块函数image-20240702183911357

我们只需要保留这两个函数就可以了,其它的都可以删掉

image-20240702204248172

然后我们再回过头看一下加密流程

1
2
3
4
5
6
7
signature: (0,tJ(ti).encrypt)(ty()(tp))
tp = "101_3_3.0+/api/v4/search/customize+AEASvRSu2xiPTh3toCTlFzmkJRYmd9hN91k=|1719839335"
// ty() 是个md5加密 ty()(tp) = d6a59e6e075d5aea83bf9ad1f7fbffcd
// 加密代码就是
tJ(ti).encrypt("d6a59e6e075d5aea83bf9ad1f7fbffcd")
// tJ(ti).encrypt() 是 __g._encrypt()
// __g._encrypt 又是 te.O() 走到了webpack模块的位置

我们知道了 __g._encrypt() 是我们需要的加密代码,我们再看一下我们复制下来的js代码,能看到 ZP就是D方法,也就是我们需要的加密代码,我们尝试一下 用 tr[‘ZP’] 调用一下,发现每次加密结果都不一样,和浏览器中的结果也不一样,但是长度一样,猜测是因为时间戳或者随机数的原因

image-20240703082809250

image-20240702205925784

image-20240702210052524

我们在上面固定随机数后,发现结果固定了,然后在浏览器控制台在固定试一试,但是两者之间的加密结果不一样,那就说明有环境检测了

image-20240702210842993

image-20240702210921575

补环境

我们将代码分成三份

第一份是demo, 就是加载器和模块代码

image-20240703102936013

第二份是 补环境代码, 这里使用 Proxy 对全局遍历window、document、navigator等常见环境检测点进行代理,拦截代理对象的读取、函数调用等操作,并通过控制台输出,这样的话我们就能够实现检测环境自吐的功能,后续我们再针对吐出来的环境统一的进行补环境,这样就会方便的多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// env.js
window=global;
xx={};
Math.random=function(){return 0.1}

function getEnvs(proxyObjs) {
for (let i = 0; i < proxyObjs.length; i++) {
const handler = `{
get: function(target, property, receiver) {
console.log("方法:", "get ", "对象:", "${proxyObjs[i]}", " 属性:", property, " 属性类型:", typeof property, ", 属性值:", target[property], ", 属性值类型:", typeof target[property]);
return target[property];
},
// set: function(target, property, value, receiver) {
// console.log("方法:", "set ", "对象:", "${proxyObjs[i]}", " 属性:", property, " 属性类型:", typeof property, ", 属性值:", value, ", 属性值类型:", typeof target[property]);
// return Reflect.set(...arguments);
// }
}`;
eval(`try {
${proxyObjs[i]};
${proxyObjs[i]} = new Proxy(${proxyObjs[i]}, ${handler});
} catch (e) {
${proxyObjs[i]} = {};
${proxyObjs[i]} = new Proxy(${proxyObjs[i]}, ${handler});
}`);
}
}

proxyObjs = ['window', 'document', 'location', 'navigator', 'history', 'screen']
getEnvs(proxyObjs);

最后一个就是调用模块了,这个就不用动了

image-20240703103721452

运行后会发现有很多对象属性为 undefined 这就是我们需要补的环境,先从好补的开始补

image-20240703103934836

image-20240703103928714

1
2
3
navigator={
userAgent:'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36'
}

document.toString()

在浏览器控制台里面 document.toString() 返回的是 ‘[object HTMLDocument]’ 所以我们也返回这个就可以了

image-20240703104326023

1
2
3
document={
toString:function(){return '[object HTMLDocument]'}
}

image-20240703104824996

1
2
3
4
navigator={
userAgent:'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
toString:function(){return '[object Navigator]'},
}

順便把 window.name 给补了,先设个空

1
window.name=''

location.toString()

顺便把herf也加上

image-20240703105151484

1
2
3
4
location={
toString:function(){return 'https://www.zhihu.com/'},
href:'https://www.zhihu.com/'
}

history.toString()

image-20240703105300775

1
2
3
history={
toString:function(){return '[object History]'}
}

screen.toString()

image-20240703105341619

1
2
3
screen={
toString:function(){return '[object Screen]'}
}

document.createElement()

image-20240703110219023

因为我们不知道它要返回的是什么,所以我们可以输出一下,发现需要返回的是 canvas

1
2
3
4
document={
toString:function(){return '[object HTMLDocument]'},
createElement:function(res){return res},
}

image-20240703110548027

补上后顺带把 canvas 对象也加上

image-20240703110622286

canvas

补上后发现 没有返回结果了,发现是 canvas 没有加入到 代理对象中

image-20240703110737701

image-20240703111112867

加上后在运行发现需要补 canvas.getContext

image-20240703111150981

1
2
3
canvas={
getContext:function(){}
}

补上后就发现没有返回值了,我们打印一下,看下需要返回什么

1
2
3
canvas={
getContext:function(res){console.log(res)}
}

image-20240703111524034

返回了一个2d 这是个什么东西

我们在 mdn上搜索,发现是 需要补上 CanvasRenderingContext2D 这个对象

HTMLCanvasElement.getContext() - Web API | MDN (mozilla.org)image-20240703111605638

1
2
3
4
5
6
canvas={
getContext:function(){ return CanvasRenderingContext2D }
}
CanvasRenderingContext2D ={

}

因为不知道 CanvasRenderingContext2D 需要补什么,我们把 CanvasRenderingContext2D 也加入代理对象中看一下

image-20240703111934140

发现检测的还是 toString

1
2
3
CanvasRenderingContext2D ={
toString:function(){return 'function CanvasRenderingContext2D() { [native code] }'}
}

代码问题

补完后就是返回了一大堆东西,继续看下哪些能补,有webdriver 还有 document 和 window的一些属性没补

image-20240703112300934

1
2
3
4
5
navigator={
userAgent:'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
toString:function(){return '[object Navigator]'},
webdriver:false
}

还有 global 和 Buffer 之类的,浏览器环境是不存在的,我们先删掉

1
2
delete global;
delete Buffer;

然后还有能补的不太好补了,我们再去看一下demo里面的代码,优先看一下有没有 try catch之类的,我们把try catch去掉,让代码有异常就主动报错

搜索 try 后 会发现有两个结果

image-20240703112829568

第一个是在加载器里面,我们就先不管

第二个在模块代码里面,我们试着把 try catch删除

image-20240703112851740

image-20240703112927046

在运行的话就会发现报错 alert is not defined

image-20240703113028486

alert

1
window.alert=function(){}

document.getElementById()

image-20240703113314286

1
2
3
4
5
6
document={
toString:function(){return '[object HTMLDocument]'},
createElement:function(){return canvas},
getElementById:function(){},

}

document.getElementsByClassName()

image-20240703113349678

1
2
3
4
5
6
7
document={
toString:function(){return '[object HTMLDocument]'},
createElement:function(){return canvas},
getElementById:function(){},
getElementsByClassName:function(){}

}

结束

补到这里的话,就会很神奇的发现,加密结果与浏览器一致了

image-20240703113533420

image-20240703113539188

image-20240703113600208

完整的环境代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// env.js
window=global;
xx={};
delete global;
delete Buffer;
Math.random=function(){return 0.1}
window.name=''
window.alert=function(){}
navigator={
userAgent:'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
toString:function(){return '[object Navigator]'},
webdriver:false
}
document={
toString:function(){return '[object HTMLDocument]'},
createElement:function(){return canvas},
getElementById:function(){},
getElementsByClassName:function(){}

}
canvas={
getContext:function(){ return CanvasRenderingContext2D }
}
CanvasRenderingContext2D ={
toString:function(){return 'function CanvasRenderingContext2D() { [native code] }'}
}

location={
toString:function(){return 'https://www.zhihu.com/'},
href:'https://www.zhihu.com/'
}

history={
toString:function(){return '[object History]'}
}
screen={
toString:function(){return '[object Screen]'}
}

function getEnvs(proxyObjs) {
for (let i = 0; i < proxyObjs.length; i++) {
const handler = `{
get: function(target, property, receiver) {
console.log("方法:", "get ", "对象:", "${proxyObjs[i]}", " 属性:", property, " 属性类型:", typeof property, ", 属性值:", target[property], ", 属性值类型:", typeof target[property]);
return target[property];
},
// set: function(target, property, value, receiver) {
// console.log("方法:", "set ", "对象:", "${proxyObjs[i]}", " 属性:", property, " 属性类型:", typeof property, ", 属性值:", value, ", 属性值类型:", typeof target[property]);
// return Reflect.set(...arguments);
// }
}`;
eval(`try {
${proxyObjs[i]};
${proxyObjs[i]} = new Proxy(${proxyObjs[i]}, ${handler});
} catch (e) {
${proxyObjs[i]} = {};
${proxyObjs[i]} = new Proxy(${proxyObjs[i]}, ${handler});
}`);
}
}

proxyObjs = ['window', 'document', 'location', 'navigator', 'history', 'screen','canvas','CanvasRenderingContext2D']
getEnvs(proxyObjs);