当前位置: 首页 > 后端技术 > Python

【JS 逆向百例】猿人学系列 web 比赛第二题:js 混淆 - 动态 cookie,详细剖析

时间:2023-03-26 19:45:43 Python

【JS反推100例】猿类人类学系列网络竞赛第二题:js混淆-动态cookie,详解5页公布每日热度值,计算所有值之和首页:https://match.yuarenxue.com/...接口:https://match.yuanrenxue.com/...反向参数:Cookie参数:mReverse过程抓包分析进入网页,右键查看源码页面,但是在直播间找不到相关的数据信息,证明数据是通过ajax加载的,ajax加载了一个特殊请求类型的XHR,打开开发者工具,刷新网页抓包后,它会跳转到虚拟机并进入无限调试器。无限调试器的传递方式在之前的文章中已经详细介绍过了。如果你有兴趣,你可以阅读和学习它。在这里,右键单击调试器行并选择从不在此处暂停。然后可以下一个断点:在Network的过滤栏中选择XHR,数据界面为2。在响应预览中,可以在当前页面看到各手机发布日期的热度:点击第二页在此时,会弹出提示框:cookie无效,正在重置页面,证明cookie是时效性的,将验证:cookie中有一个关键的加密参数m,其内容如下:通过hookcookie中的m参数进行逆向分析定位和hook的方式有很多种。详细介绍可以看K哥之前的文章。这里我们使用编程猫Fiddler插件进行hook。发送【Fiddler插件】获取。Hook代码如下:(function(){'usestrict';varcookieTemp='';Object.defineProperty(document,'cookie',{set:function(val){if(val.indexOf('m')!=-1){debugger;}console.log('Hookcapturescookiesettings->',val);cookieTemp=val;returnval;},get:function(){returncookieTemp;},});})();勾选打开的框,启动Fiddler进行hook注入:刷新网页,如果进入无限调试器,按上面解决,但是直接通过m参数定位不是最好的方案,因为里面还有其他参数包含字母m的cookie。如果位置不正确,网页将被刷新。这里,break成功的地方是m参数的值产生的位置:顺着栈一直走到_0xdad69f(2:18),然后点击左下角的{}进行格式化,代码会跳转到4943行2:格式化后的文件,如下:document[$dbsm_0x42c3(qqLQOq,iOiqII)+$dbsm_0x42c3(q1IoqQ,QQlLlq)]=_0x5500bb['\x4e\x74\x44'+'\x72\x43'](_0x5500bb[$dbsm_0x42c3(qqqQoq,oqQiiO)+'\x6d\x65'](_0x5500bb[$dbsm_0x42c3(Ioo0ql,olq0Oq)+'\x6d\x65Q'](_0x5500bb[$dbsm_0xO42q3(,OOqI72)+4xI72'](_0x5500bb[$dbsm_0x42c3(Q1qoqQ,lILOOq)+'\x72\x44'](_0x5500bb[$dbsm_0x42c3(qOO1Q0,oiqlQQ)+'\x72\x44'](Ql1OO0,_0x5500bb['\x7a\x76\x67'+'\x6c\x77'](_0x3c9ca8)),Qoqq0I),_0x5500bb[$dbsm_0x42c3(iqOiQ0,QOiq0Q)+'\x47\x6b'](_0x313b78,_0x160e3a)lOo0QQ),_0x160e3a),_0x5500bb[$dbsm_0x42c3(qiOOiO,liQIoQ)+'\x4e\x5a']),aftertheconsoleprints,wecanseethatthisisthelocationwherethemparametervalueinthecookieisgenerated:furtherprintandanalyzeotherpartsintheconsole含义:m参数值的格式如下:0ef478cf61e0749d7444c7997c917679|1663213224000可以依此将代码进行简化:_0x5500bb[$dbsm_0x42c3(iqOiQ0,QOiq0Q)+'\x47\x6b'](_0x313b78,_0x160e3a)+lOo0QQ+_0x160e3a控制台打印验证,theresultmatches:Next,followupto_0x5500bb[$dbsm_0x42c3(iqOiQ0,QOiq0Q)+'\x47\x6b'],selectitwiththemouseandclicktoenter:inthe3911thlineofthefile,thecontentisasfollows:_0x434ddb[$dbsm_0x42c3(Iooo0l,Qq1oqI)+'\x47\x6b']=function(_0x105ffe,_0x733be0){return_0x105ffe(_0x733be0);}Thereturnvalueis_0x105ffe(_0x733be0),andtheparameterspassedinbythisfunctionare_0x313_a78and,soitcanbefurtherrewritten:_0x313b78(_0x160e3a)+lOo0QQ+_0x160e3a_0x160e3aisthetimestamp,sothevalueofthemparameterisobtainedbypassingthetimestampasaparameterintothe_0x313b78functionandthenencryptingit,soitneedstobefurtherfolloweduptothedefinitionofthe_0x313b78functionPosition,alsoselectwiththemouse,clicktojumptoline4933,debuginthenodeenvironment,thepreliminarycodeis:function_0x313b78(_0x575158,_0x1fa91a,_0x1cf5de){//下面部分太长这里省略//完整代码遵循GitHub:https://github.com/kgepachong/crawler}var_0x160e3a=Date.parse(newDate());varm=_0x313b78(_0x160e3a)+lOo0QQ+_0x160e3a;console.log(m);运行后会提示_0x5500bb未定义,到原文件ctrl+f在本地搜索此函数,在第3940行:_0x5500bb=_0x434ddbmake运行后会提示_0x434ddb未定义定义,搜索后,发现在第2817行定义了_0x434ddb为空对象,后面传入了很多值,类似于一个大数组。不能只填_0x434ddb={};,传值部分要填,否则后面运行的时候会出现一些错误。经过测试,有些部分是可以省略的,但是小删减很麻烦。直接全部凑齐就行了。这个很多,从2817行到3939行,补全然后运行程序,这个时候提示$dbsm_0x42c3是undefined,然后搜索它的定义位置,在94行,填完之后提示OooIi1是undefined,在第209行,需要填写从第209行到第2816行的所有行,否则会提示其中有一行是undefined。同样的,有些虽然调试后不需要,但是一一调整很麻烦,也没有必要。完成后运行,会提示$dbsm_0x123cisundefined:itisinthe22ndOK,it'salargearray.填好后运行程序,发现卡住了。过了一会儿,报错:这个错误可能是内存资源耗尽导致程序崩溃。将这部分代码复制到浏览器中进行调试,并打开一个Openanewpage,打开开发者工具,在Sources中选择Snippets,新建一个脚本,把已经扣好的代码粘贴进去,在第一个里面写debugger线;手动断点调试,ctrl+s保存文件点击右下角按钮运行脚本会停在第一行:点击单步调试一步步看问题出在哪里:点击几步后卡住,跳到2711行,这是一个for循环,右边出现红框报错,代表潜在的内存崩溃,也就是单步调试断线的时候到这里,程序就接近内存崩溃了:再单步调试,会发现一直在2712行和2713行来回执行,直到后来连浏览器都崩溃了,所以问问题在于WxzuQr对象中存在无限循环,直到内存资源耗尽:这部分在$dbsm_0x42c3函数中。接下来,我们需要研究崩溃的原因。右边入栈入栈,前面两步通过构造函数创建了两个实例对象WjJIeN和vnuqco,WjJIeN的部分如下:_0x11a714['prototype']['WjJIeN']=函数(_0x4859ef){如果(!布尔值(~_0x4859ef)){返回_0x4859ef;}returnthis['WxzuQr'](this['yewpLt']);}这里是一个if判断,~是按位取反,意思是如果!Boolean(~_0x4859ef)的值为false,就会执行WxzuQr的死循环行为,直到程序Crash,再跟进vnuqco部分,查看_0x4859ef是什么,判断是什么:_0x11a714['prototype']['vnuqco']=function(){_0x2940ac=newRegExp(this['PuKGlh']+this['CTXIfT']),_0x3fba94=_0x2940ac['test'](this['XxpyjG']['toString']())?--this['yHmSUE'][0x1]:--this['yHmSUE'][0x0];返回这个['WjJIeN'](_0x3fba94);}返回值中传递给WjJIeN的参数是_0x3fba94,定义在2699行,是一个三元表达式:_0x2940ac['test'](this['XxpyjG']['toString']())?--this['yHmSUE'][0x1]:--this['yHmSUE'][0x0];将输出打印到控制台以查看每一部分的含义:--this['yHmSUE'][0x1]的值固定为-1,this['yHmSUE'][0x0]的值每运行一次减一:console.log(!Boolean(~-1))//trueconsole.log(!Boolean(~-2))//false所以只有当_0x2940ac['test'](this['XxpyjG']['toString']())的值为true时才不会进入死循环,在control中打印this['XxpyjG']['toString']()的部分:这个函数在第2689行,看看是怎么判断的,跟进到_0x2940ac的定义位置,在第2698,是正则表达式对象。在控制台打印后可以知道表达式为:/\w+*\(\)*{\w+*['|"].+['|"];?*}//.../:正则表达式限制器,直写,反转义,如果正则表达式中间部分有\d,则\d的\不会作为转义字符*\(\):匹配零个或多个括号,\为转义符*{,*}:匹配前后花括号\w+:匹配一个或多个字母数字字符。+:贪心匹配任意字符['|"]:匹配单个ordoublequotes;?:匹配零个或一个分号,所以匹配样式大致如下:XXX(??){XXX'XXX';},不匹配换行符、制表符、空格等,未格式化的代码会被压缩成一个行,所以这里比较适合格式化检测,因为一开始就进行了格式化操作,判断结果为false,从而进入死循环,导致程序崩溃,所以只需要将这部分内容压缩成一行,检查一下:没有格式化后打印结果为真,即不会调用WxzuQr对象,因此en进入无限循环。修改后,再次运行程序。结束了吗?当然不是。行,内容如下:_0x5500bb[$dbsm_0x42c3(QoLq0i,q0Oqqo)+'\x5a\x49']然后在浏览器中调试,在这一行输入debugger;然后运行脚本,停止后打印分析:'\x5a\x49'是'ZI',QoLq0i,q0Oqqo是固定值,所以问题出在$dbsm_0x42c3函数,其实如果理解OB混淆,就会知道这种混淆方法有一些特点,其中之一就是它一般由三部分组成:一个大数组,一个shift自执行函数,一个解密字符串函数。我们之前已经找到了大数组,就是$dbsm_0x123c,$dbsm_0x42c3是一个解密字符串函数。这里有班次自执行功能。如果少了东西,结果就会出错,需要自己找回来补上。从第23行到第93行,夹在$dbsm_0x123c和$dbsm_0x42c3之间。会后报错:第27行报错,放在浏览器中调试,或者在开头标上debugger;运行后,下台执行,点击几个熟悉的卡住,然后跳转到第24行的for循环:右边出现熟悉的warning提示,证明又进入死循环了。果不其然,过一会浏览器页面就崩溃了:根据之前的经验,看看是不是有别的格式检测导致了这个在循环中,果不其然,在第55行:这里是removeCookie处代码的格式检测,以及同样把函数体写成一行:'removeCookie':function(){return'dev';},然后运行,提示_0x3c9ca8未定义,ctrl+f本地搜索找到函数定义位置扣上。运行后提示_0x1316f4未定义。这个推完之后,记得把下面自执行的括号删掉,然后会提示_0x12a78eisundefined,推的时候记得把最后的括号删掉,然后就没有什么特别需要注意的了,填上即可缺少哪个功能,然后会提示navigatorisundefined,只需填写浏览器环境即可。在node环境下,窗口设置为全局:varwindow=global;window.navigator={};自然不会这么轻易结束。运行后会提示_0x184fb0未定义。和以前一样,搜一扣就可以了。之后还有一个漫长的功能补充过程。没有别的技巧,就是需要耐心,手都酸了,直到报如下错误:错误信息historyisundefined,这是一个浏览器对象,显示在console.log中。console.log行断点调试,运行到这里,会跳转到虚拟机,代码如下:将执行history.pushState,但是我们没有history环境,所以会报错。添加history环境后,发现程序一直卡死。仔细看代码,我们发现了一个while循环。最离谱的是里面的for循环设置了1,100,000次,差点就ok了。据说还在持续测试中,等到猴年过去了。在这里,您可以直接将console.log赋值给一个变量并替换它。记得放在前面:varresult=console.log;至此,终于结束了!成功打印出m参数的值:这道题不难,逆序思路也很清晰,但是推码过程复杂,坑多,还是值得练习的。完整代码bilibili关注K哥爬虫,小助手动手视频教学:https://space.bilibili.com/16...GitHub关注K哥爬虫,持续分享爬虫相关代码!欢迎加星!https://github.com/kgepachong/下面只是演示了部分关键代码,不能直接运行!JavaScript代码varwindow=global;window.navigator={};varresult=console.log;//下面部分太长这里省略//完整代码关注GitHub:https://github.com/kgepachong/crawlerfunction_0x313b78(_0x575158,_0x1fa91a,_0x1cf5de){如果(_0x5500bb[$dbsm_0x42c3(QoLq0i,q0Oqqo)+'\x5a\x49'](_0x5500bb[$dbsm_0x42c3(LQOI0Q,QqOI00)+'\x73\x42'],_0x5500bb[x04dbsm)+'\x5a\x76'])){VWQQuv['\x6f\x4f\x61'+'\x68\x47'](debuggerProtection,Q0LiqQ);}else{_0x5500bb[$dbsm_0x42c3(i1lQqq,q110Lq)+'\x62\x45'](_0x3c9ca8);返回_0x1fa91a?_0x1cf5de?_0x5500bb[$dbsm_0x42c3(iqqLQO,LoOOOq)+'\x4b\x6b'](_0x21cf21,_0x1fa91a,_0x575158):_0x5500bb['\x72\x71\x75'+'\x4b\x51'](y,_0x1fa95):_0x1cf5de?_0x5500bb[$dbsm_0x42c3(qLQQ1q,I1oOQ1)+'\x4d\x6e'](_0x443ca7,_0x575158):_0x5500bb[$dbsm_0x42c3(qLLoQi,iO0OQo)+'\x4d\x6e'](_0x184fb);}}functiongetCookieM(){var_0x160e3a=Date.parse(newDate());varm=_0x313b78(_0x160e3a)+lOo0QQ+_0x160e3a;returnm;}//var_0x160e3a=Date.parse(newDate());//varm=_0x313b78(_0x160e3a)+lOo0QQ+_0x160e3a;//result(m);Python代码#=======================#--*--coding:utf-8--*--#@Time:2022/9/8#@Author:微信:K哥笨虫#@FileName:yrx5.py#@Software:PyCharm#=======================importexecjsimportrequestsimportredefget_cookie_m():heat_total=0forpage_numinrange(1,6):withopen('yrx2.js','r',encoding='utf-8')asf:encrypt=f.read()cookie_m=execjs.compile(encrypt).call('getCookie')headers={"user-agent":"yuanrenxue,project",}cookies={"sessionid":"填入自己的sessionid","m":cookie_m}url="https://match.yuanrenxue.com/api/match/2?page=%s"%page_numresponse=requests.get(url,headers=headers,cookies=cookies)foriinrange(10):value=response.json()['data'][i]heat=re.findall(r"'value':(.*?)}",str(value))[0]heat_total+=int(heat)print(heat_total)if__name__=='__main__':get_cookie_m()