本次逆向的目的主要是感受xhs的jsvmp,完善一下自己的补环境框架,并无相关业务,所有具体各个参数获取的流程以及cookie啥的就不分析了。上手之前,先找相关文章看看,所谓砍柴不费磨刀工功。目前有三种方式:RPC,补环境,纯算法还原,各种优缺点就不赘述了。
aHR0cHM6Ly93d3cueHh4LmNvbS9hcGkvc2VjL3YxL3NoaWVsZC93ZWJwcm9maWxl
X-s
, profileData
pip isntall node-vm2
: 因为本人用的vm2补环境的方案,此包运行补好的js;
node
: 我的node版本v18.15.0
npm install vm2
: vm2沙盒
因为看过相关文章。所有知道X-s
是window._webmsxyw
方法生产,profileData
是window.xhsFingerprintV3.getV18
。
// X-s
window._webmsxyw('/api/sns/web/v1/feed', {"source_note_id":"xxxxxxxx"});
//profileData
window.xhsFingerprintV3.getV18(function (params) {
console.log(params)
})
打开页面看看,果然都是各种异步任务,不管开干
既然是补环境就不用在乎这些异步细节,只要你用的XMLHttpRequest发请求,直接在框架里给你拦截下来就行。
下载一个html文件,让框架自行按顺序下载对应的js文件
执行所有的js文件,果然框架还有很多功能没去实现,xhr并没有捕获到任何请求, 看下框架还有哪些功能要完善的,mdn上查找一下,并在框架中简单实现具体的功能。
window.queueMicrotask
, 微任务队列,既然是一个存入微任务的队列,可以用async函数封装一下,然后丢给系统自行调用,或者保存起来js执行完后手动调用。这里用第二种方式实现。window.Crypto
, 直接将node的Crypto包引进vm2沙盒里,在框架里包装一下避免原型链检测window.AudioContext
, 简单补下原型链之类的,功能后面用到在说注意:以上我为了完善框架,并不代表检测点
ok,将这些以前没实现的功能实现下,继续跑js。我擦,xhr还没拦截到
经过一顿掉头发,发现我的js文件的执行流程和浏览器不一样,一顿无语,给浏览器下脚本断点,调整js文件执行顺序。搞定,成功拦截到了xhr请求。
用requests发送拦截的xhr请求参数,果不其然没通过检测,{"code":300015,"success":false,"msg":"浏览器异常,请尝试关闭/卸载风险插件或重启试试!","data":{}}
框架的Proxy
开起来,先观察一下都干了,为了避免干扰,手动调用window._webmsxyw('/api/sns/web/v1/feed', {"source_note_id":"xxxxxxxx"});
方法,
并将加密无关的js全部不执行,看看都检测了啥。
window._phantom
window.__nightmare
window._selenium
window.callPhantom
window.callSelenium
window._Selenium_IDE_Recorder
document.__webdriver_evaluate
document.__selenium_evaluate
document.__webdriver_script_function
document.__webdriver_script_func
document.__webdriver_script_fn
document.__fxdriver_evaluate
document.__driver_unwrapped
document.__webdriver_unwrapped
document.__driver_evaluate
document.__selenium_unwrapped
document.__fxdriver_unwrapped
我是vm2,肯定没问题,pass
PluginArray
new PluginArray
看是否有异常,passcookie
localStorage
sdt_source_storage_key
,去浏览器粘贴一个,passdiv
, ul
, li
, span
等之类的元素,实现了dom操作相关的功能 passdiv
元素的offsetHeight
检测div
,设置高度为20px
,然后获取offsetHeight
值,再添加到body
里面,然后再次获取offsetHeight
值, passoffsetWidth
,offsetHeight
检测mmmmmmmmmmlli
,设置字体大小72px
, 不停的换字体然后获取宽高之类的, passcanvas
相关的检测/vm|bootstrapNodeJSCore|tryModuleLoad|evalmachine|runInContext/g
这些关键词,框架早就已处理了,pass通过代理Proxy
日志,关键位置放置的debugger
,暂时分析出这些检测,再跑一边,没通过!我日**。
睡觉睡觉,反正没业务,明天再说。
第二回,jsvmp
调试,看到这vmp混淆就一顿烦躁,简单跟几步代码,整体规律还是挺明显,来来回回执行那么几个函数,找个位置插桩看下,随机数和时间戳啥的先不固定,代码和浏览器都跑一边插座,保存日志,对比分析一下发现检测环境结果的字符串有一个不一样:
x2=0|0|0|1|0|0|1|0|0|0|1|0|0|0|0
-> 浏览器中的结果;
x2=1|0|0|1|0|0|1|0|0|0|1|0|0|0|0
-> 代码中的结果。
条件断点到大致位置,我的返回是true,浏览器返回是false。具体执行了一下发现是一个appendChild
操作,具体分析发现检测是这样的:
div1 = document.createElement('div');
div2 = document.createElement('div');
div1.appendChild(div2);
// 浏览器中这句会抛一个异常,我的框架没有异常,大意了
div2.appendChild(div1);
将框架的bug补上,再跑一遍代码。通过了!通过了!通过了!
这个坑整了我好久!搞得我一直以为是我补的环境有问题,其实node跑出来的一直是正确的没问题的结果,偶然从调试控制台粘贴结果去跑才发现的,白掉了好多头发。
node-vm2
源码,发现这个包得原理就是起一个进程去调用node执行js代码,通过node的readline
读取进程的输入process.stdin
,与python进程的process.stdin
,process.stdout
实现交互。进一步对比分析发现,我的补环境框架使用的new vm2.VMScript(code, 'xxx.js');
对js进行了一个预处理,而node-vm2
包是直接vm.run(code)
node-vm2
源码,添加这步预处理操作vm2.VMScript
。这样就没问题了,至于VMScript
具体干了啥下次再去看吧。按照惯例是会放出成品demo的,但是国内这环境,大家还是自己动手吧,更有成就感。都看到这里了就随手帮我点个star吧!! https://github.com/conlin-huang/aio-scrapy
随着对抗强度的不断升级,爬虫要学的东西也越来越多了,web逆向,app逆向。爬虫也从最初的脚本小子,逐渐变成分工明确的团队,有专门做web逆向的,有专门做app端逆向的,做爬虫架构的,这似乎成了一个趋势。但是目前绝大多数公司就一个爬虫,然而人的精力是有限的,你我皆凡人,两个都研究太耗费心神了,除非天赋异禀,而且容易造成两个都是半吊子。所有在做规划的时候请选择一条路线,要么专精web,要么专精app。最后的最后,欢迎交流爬虫相关的知识,wx: h995018884。