知乎x-zse-96逆向-webpack与浏览器补环境
加密定位
直接控制台搜索 x-zse-96



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



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

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


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


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


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


没有报错后,会发现打印成功,调用了 1514 和 74185 两个模块函数
我们只需要保留这两个函数就可以了,其它的都可以删掉

然后我们再回过头看一下加密流程
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"
tJ(ti).encrypt("d6a59e6e075d5aea83bf9ad1f7fbffcd")
|
我们知道了 __g._encrypt() 是我们需要的加密代码,我们再看一下我们复制下来的js代码,能看到 ZP就是D方法,也就是我们需要的加密代码,我们尝试一下 用 tr[‘ZP’] 调用一下,发现每次加密结果都不一样,和浏览器中的结果也不一样,但是长度一样,猜测是因为时间戳或者随机数的原因



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


补环境
我们将代码分成三份
第一份是demo, 就是加载器和模块代码

第二份是 补环境代码, 这里使用 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
| 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);
|
最后一个就是调用模块了,这个就不用动了

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


navigator.userAgent
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]’ 所以我们也返回这个就可以了

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

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 给补了,先设个空
location.toString()
顺便把herf也加上

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

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

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

因为我们不知道它要返回的是什么,所以我们可以输出一下,发现需要返回的是 canvas
1 2 3 4
| document={ toString:function(){return '[object HTMLDocument]'}, createElement:function(res){return res}, }
|

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

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


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

1 2 3
| canvas={ getContext:function(){} }
|
补上后就发现没有返回值了,我们打印一下,看下需要返回什么
1 2 3
| canvas={ getContext:function(res){console.log(res)} }
|

返回了一个2d 这是个什么东西
我们在 mdn上搜索,发现是 需要补上 CanvasRenderingContext2D
这个对象
HTMLCanvasElement.getContext() - Web API | MDN (mozilla.org)
1 2 3 4 5 6
| canvas={ getContext:function(){ return CanvasRenderingContext2D } } CanvasRenderingContext2D ={
}
|
因为不知道 CanvasRenderingContext2D 需要补什么,我们把 CanvasRenderingContext2D 也加入代理对象中看一下

发现检测的还是 toString
1 2 3
| CanvasRenderingContext2D ={ toString:function(){return 'function CanvasRenderingContext2D() { [native code] }'} }
|
代码问题
补完后就是返回了一大堆东西,继续看下哪些能补,有webdriver 还有 document 和 window的一些属性没补

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 后 会发现有两个结果

第一个是在加载器里面,我们就先不管
第二个在模块代码里面,我们试着把 try catch删除


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

alert
1
| window.alert=function(){}
|
document.getElementById()

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

1 2 3 4 5 6 7
| document={ toString:function(){return '[object HTMLDocument]'}, createElement:function(){return canvas}, getElementById:function(){}, getElementsByClassName:function(){}
}
|
结束
补到这里的话,就会很神奇的发现,加密结果与浏览器一致了



完整的环境代码如下
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
| 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);
|