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

JS逆向100例】猿人学系列Web竞赛题5:JS混淆-乱码增强,详解

时间:2023-03-26 14:05:02 Python

逆向目标猿人学-反混淆刷机平台Web题5:JS混淆、乱码增强目标:CatchAll5直播间热度页面,计算前5个直播间热度之和首页:https://match.yuanrenxue.com/...接口:https://match.yuanrenxue.com/...反向参数:url请求参数:m,fCookie参数:m,RM4hZBv0dDon443M逆过程抓包分析进入网页,右键查看页面源代码,但在直播间找不到相关数据信息,证明数据通过ajax加载,ajax加载有一个特殊的请求类型XHR,打开开发者工具,刷新网页抓包,在Network过滤栏选择XHR,数据接口为5?m=XXX&f=XXX,可以看到第th个直播间的热度数据e响应预览:接口url有两个请求参数m和f,至今不知道怎么来的:这道题提示cookie的有效期只有50秒,也就是cookie值在变化动态地。经过对比分析,cookie中有两个动态变化的参数m和RM4hZBv0dDon443M,接下来需要定位其生成位置:逆向分析Cookie加密参数分析可以通过HookCookie定位参数位置,这里通过Fiddler编程cat插件钩子,K哥爬虫里面的相关插件♂发送【Fiddler插件】获取。Hook代码如下:(function(){'usestrict';varcookieTemp='';Object.defineProperty(document,'cookie',{set:function(val){if(val.indexOf('RM4hZBv0dDon443M')!=-1){debugger;}console.log('Hookcapturesthecookievalue->',val);cookieTemp=val;returnval;},get:function(){returncookieTemp;},});})();将以上代码写入插件,注入Hook:清空网页缓存,勾选开启框,打开Fiddler注入Hook,可以发现打断成功:从右边stackup跟随stack,就会发现已经关注了虚拟机VMXXX,点击右下角{}格式化,跳转到978行,代码部分如下:_0x3d0f3f[_$Fe]='R'+'M'+'4'+'h'+'Z'+'B'+'v'+'0'+'d'+'D'+'o'+'n'+'4'+'4'+'3'+'M='+_0x4e96b4['_$ss']+';\x20path=/';在此行打断点调试,控制台打印相关参数:_$Fe:cookie_0x4e96b4['_$ss']:RM4hZBv0dDon443M参数加密值前的字母为RM4hZBv0dDon443M=,这里是位置其中RM4hZBv0dDon443M参数被加密赋值给cookie,所以key加密的部分是_0x4e96b4['_$ss'],打印相关内容会发现_0x4e96b4是一个window对象,而window._$ss是加密值:直接搜索_$ss没有结果,也试试Hook,Hook代码:(function(){'usestrict'Object.defineProperty(window,'_$ss',{set:function(val){console.log('Hook捕获_$ss->',val);debugger;},});})();成功断开:顺着栈往上,找到它的定义位置,顺着它进入虚拟机,格式化后跳到1229行:_0x4e96b4['_$'+_$UH[0x348][0x1]+_$UH[0x353][0x1]]=_0x29dd83[_$UH[0x1f]]();在这一行打断点调试分析各自的含义:'_$s',_$UH[0x348][0x1],_$UH[0x353][0x1]combined:'_$ss'_$UH[0x1f]():toString()_0x29dd83[_$UH[0x1f]]():将_0x29dd83生成的值转换为字符串所以关键的加密位置必须在_0x29dd83。往上看,1225行定义了_0x29dd83,此时看到mode和padding这两个关键字。这里的概率是AES或者DES加密。代码去混淆替换后结果如下:_$Ww=_$Tk['enc']['utf-8']['parse'](_0x4e96b4['_$pr']['toString']()),_0x29dd83=_$Tk['AES'](_$Ww,_0x4e96b4['_$qF'],{'模式':_$Tk['模式']['ECB'],'填充':_$Tk['pad']['pkcs7']}),_0x4e96b4['_$ss']=_0x29dd83['toString']();现在很明显了,这里是AES加密,加密内容为_$Ww,密钥值为_0x4e96b4['_$qF'],加密模块为ECB,填充方式为pkcs7:CBC:CipherBlockChaining(密码块链接模式),这是一种循环模式。将上一组的密文和本组的明文进行异或加密后,这样做的目的是增加破解PKCS7的难度:填充时,先求出待填充的字节长度=块长度-(数据长度%块长度),填充字节序列中的所有字节填充需要填充的字节长度值。_$Ww的值从_0x4e96b4['_$pr']转换为字符串,然后以utf-8编码。和键值_0x4e96b4['_$qF']一样是一个数组,需要知道这两个数组是怎么生成的,先ctrl+f搜索_0x4e96b4['_$qF'],定义在第1444行,内容如下:_0x4e96b4['_$qF']=CryptoJS['enc']['Utf8'][_$UH[0xff]](_0x4e96b4['btoa'](_0x4e96b4['_$is'])['切片'](0x0,0x10));在这一行打断点,控制台打印分析:可以看出_0x4e96b4['_$qF']是通过CryptoJS库对字符串进行base64加密,取前16位的结果。搜索_0x4e96b4['_$is'],发现生成字符串的位置是由第674行的_$yw赋值的,在上一行可以看到熟悉的_$Fe,也就是cookie。发现cookie中的m参数定义在这里:_0x3d0f3f[_$Fe]='m='+_0x474032(_$yw)+';\x20path=/';参数m的值也和_$yw有关,m参数是通过_0x474032函数对_$yw进行处理得到的,后面会专门分析,_$yw定义在第672行:_$yw=_0x2d5f5b()[_$UH[0x1f]]();_$UH[0x1f]为“toString”,_$yw的值为_0x2d5f5b()函数的返回值转换为字符串,一直追到函数定义的位置。查找后发现在第279行,在控制台打印后发现这是时间戳,所以_$yw就是时间戳:因此_0x4e96b4['_$qF']的值就是使用base64加密时间戳并取前16位数字的结果。接下来只需要知道_0x4e96b4['_$pr']是怎么产生的就可以重现了RM4hZBv0dDon443M参数的加密过程出来,1224行断点调试发现_0x4e96b4['_$pr']数组包含五个值:现在我们要知道这五个值是从哪里传进来的,搜索_0x4e96b4['_$pr']看它赋值在哪里,每个分解一下,数组定义在270行:_0x4e96b4['_$pr']=new_0x4d2d2c();_0x4d2d2c在224行被定义为Array,所以这里创建了一个数组_0x4e96b4['_$pr']'],然后寻找传值的地方,继续断点调试。1717行断点运行四次,传入四个值:_0x4e96b4['_$pr']['push'](_0x474032(_$Wa));跟进_$Wa定义的位置,在1715行,_0x12eaf3函数生成的,跟进到这个函数的位置,第275行,去混淆后的返回值如下:Date['parse'](newDate());下一个调试断点会跳到868行,此时数组传入第五个值,_$yw是时间戳,因为m=_0x474032(_$yw),所以第五个值也是参数m,记住这里出现的_0x4e96b4['_$is']:_0x3d0f3f[_$Fe]='m='+_0x474032(_$yw)+';\x20path=/';_0x4e96b4['_$is']=_$yw;_0x4e96b4['_$pr']['推'](_0x474032(_$yw));已经找到数组值的生成位置,和m参数一样,传入的值已经经过_0x474032函数处理,所以需要跟进_0x474032函数,鼠标选中,点击跳转到该位置定义函数的地方:第455行,返回值为三目表达式:function_0x474032(_0x233f82,_0xe2ed33,_0x3229f9){return_0xe2ed33?_0x3229f9?v(_0xe2ed33,_0x233f82):y(_0xe2ed33,_0x233f82):_0x3229f9?_0x41873d(_0x233f82):_0x37614a(_0x233f82);}在return处设置断点调试,_0x233f82是传入的_$yw的值,即时间戳,最后两个参数未定义,所以你不妨简化功能:功能_0x474032(_0x233f82,_0xe2ed33,_0x3229f9){return_0x37614a(_0x233f82);}接下来需要跟进_0x37614a函数所在的位置:function_0x37614a(_0x32e7c1){return_0x499969(_0x41873d(_0x32e7c1));}这里需要跟进90_369函数还有90_0的内容,接下来就是扣分了,少什么,少什么,少什么,少什么,少什么,少什么,少什么,少什么。,例如:_$UH[0x6c]--->"length"或者写成键值对:_$UH={8:'prototype',15:'charCodeAt',31:'toString',108:'length'}值得注意的是,_0x11a7a2函数运行时会报错opisnotdefined,而op在第308行定义:op的值为26,这里可以直接定义为固定值,即varop=26;将_0x42fb36和b64pad写成固定值,即_0x42fb36=16;,b64pad=1;调试的时候还发现window['_$6_'],window['_$tT'],window['_$Jy']这些参数的值是动态变化的。如果不重写甚至注释掉相关部分,可以在本地node环境运行结果,但是如果用python调用会报错,证明前端会对这些参数进行校验。这些参数在_0x11a7a2函数中定义。追根溯源,这个函数最终是由_0x474032函数调用的。_0x474032函数处理_$yw的值,生成_0x4e96b4['_$pr']数组的最后一个值和m参数的值,所以如果这些参数的值匹配不正确,就会校验失败.我们只需要断点看m参数的值是什么时候产生的,这三个参数的值是多少,然后写成固定值:window['_$6_']=-389564586;窗口['_$tT']=-660478335;窗口['_$Jy']=-405537848;至此,cookie中RM4hZBv0dDon443M参数和m参数的生成逻辑已经梳理完毕,通过JavaScript复现如下://以下函数内容过长,此处省略//完整代码,关注GitHub:https://github.com/kgepachong/crawlervarCryptoJS=require('crypto-js');函数rm4Encrypt(_$??yw,pr){varvalue=Buffer.from(_$yw).toString('base64').slice(0,16);varsrcs=CryptoJS.enc.Utf8.parse(pr);varkey=CryptoJS.enc.Utf8.parse(value);varencrypted=CryptoJS.AES.encrypt(srcs,key,{mode:CryptoJS.mode.ECB,padding:CryptoJS.pad.Pkcs7});返回encrypted.toString();}var_$yw=newDate().valueOf().toString();var_$Wa=Date.parse(newDate())functionpr(){pr=[];for(i=1;i<5;i++){//_$Wa传入四个值pr.push(_0x474032(_$Wa))}//_$yw传入一个值pr.push(_0x474032(_$yw));returnpr.toString();}varRM4hZBv0dDon443M=rm4Encrypt(_$??yw,pr());//m为数组A最后传入的值varm=pr[4];console.log('TheRM4hZBv0dDon443M参数的加密值为:'+RM4hZBv0dDon443M)console.log('m参数的值为:'+m)运行结果:请求头参数解析完成,还有两个请求参数m和f未解析,从界面直接跟进stack,跟进Initiator的请求:点击下方的{}格式化后右上角会跳转到5:格式化文件的856行,参数m和f的定义位置可以在883行的列表中找到:"m":window._$is,"f":window.$_zw[23]m的值是window._$is,是不是觉得很眼熟,就是上面说的_0x4e96b4['_$is'],_0x4e96b4是window,所以这里m的值其实就是_$是的;f的值是window.$_zw[23],现在我们要知道$_zw[23]的值是怎么产生的,本地搜索$_zw会发现数组定义在第611行,然后回头看要查看数组中的第23行是什么,首先在控制台打印内容:第633行的内容是第6行,你会发现第23行的内容如下:$_aiding.$_zw.push($_t1);这里打个断点调试验证结果是否一样:接下来只需要找到$_t1的定义位置,ctrl+f在本地搜索$_t1,它的定义在第613行,是一个时间戳:让$_t1=Date.parse(newDate());Date.parse(newDate()):获取的时间戳,将毫秒改为000显示,如1662691102000newDate().valueOf():获取当前包含毫秒的时间戳,如1662691114310发现是符合_$Wa的定义。比较m和f这两个参数的值,会发现相差接近50秒,对应题中提示的cookie有效期只有50秒:在虚拟机中fileLine1975还有一个50秒的定时器:至此,所有参数生成的逻辑都调整清楚了。这道题不难,但是在推码的过程中需要注意的细节很多。猿人学习为您提供了一个优质的练习平台,做题也是一种很好的自我提升方式。完整代码bilibili关注K哥的爬虫,小助手动手视频教学:https://space.bilibili.com/16...GitHub关注K哥的爬虫,持续分享爬虫相关代码!欢迎加星!https://github.com/kgepachong/下面只是演示了部分关键代码,不能直接运行!JavaScript代码var_0x4e96b4=window={};var_0x1171c8=0x67452301;var_0x4dae05=-0x10325477;var_0x183a1d=-0x67452302;var_0xcfa373=0x10325476;var_0x=30bc/7/完整代码请关注GitHub:https://github.com/kgepachong/crawlervarCryptoJS=require('crypto-js');函数rm4Encrypt(_$??yw,pr){varvalue=Buffer.from(_$yw).toString('base64').slice(0,16);var_$Ww=CryptoJS.enc.Utf8.parse(pr);varkey=CryptoJS.enc.Utf8.parse(value);varencrypted=CryptoJS.AES.encrypt(_$??Ww,key,{模式:CryptoJS.mode.ECB,填充:CryptoJS.pad.Pkcs7});returnencrypted.toString();}functiongetParamers(){pr=[];对于(i=1;i<5;i++){var_$Wa=Date.解析(新日期());公关push(_0x474032(_$Wa))}var_$yw=newDate().的价值()。toString();pr.push(_0x474032(_$yw));cookie_m=pr[4];Cookie_rm4=rm4Encrypt(_$??yw,pr.toString());返回{“cookie_m”:cookie_m,“cookie_rm4”:cookie_rm4,“m”:_$yw,“f”:Date.parse(newDate()).toString()}}console.log(getParamers());Python代码#=======================#--*--编码:utf-8--*--#@Time:2022/9/8#@作者:微信:K哥笨虫#@FileName:yrx5.py#@Software:PyCharm#=======================importexecjsimportrequestsimportredefencrypt_yrx5():room_heat_all=[]forpage_numinrange(1,6):withopen('yrx5.js','r',encoding='utf-8')作为f:encrypt=f.read()encrypt_params=execjs.compile(encrypt).call('getParamers')headers={"user-agent":"yuanrenxue,project",}cookies={#填写自己的sessionid"sessionid":"yoursessionid","m":encrypt_params['cookie_m'],"RM4hZBv0dDon443M":encrypt_params['cookie_rm4']}params={"m":encrypt_params['m'],"f":encrypt_params['f']}url="https://match.yuanrenxue.com/api/match/5?page=%s"%page_numresponse=requests.get(url,headers=headers,cookies=cookies,params=params)foriinrange(10):value=response.json()['data'][i]room_heat=re.findall(r"'value':(.*?)}",str(value))[0]room_heat_all.append(room_heat)room_heat_all.sort(reverse=True)top_five_total=0foriinrange(5):top_five_total+=int(room_heat_all[i])打印(top_five_total)if__name__=='__main__':encrypt_yrx5()