篇幅有限
完整内容及源码关注公众号:ReverseCode,发送 冲
反爬对抗
过debugger
debugger行右键Edit breakpoint修改为false- 动态调试时,循环debugger可能有setInterval定时器,
- 根据调用栈找到第一次循环调用debugger的方法=function(){}
- 直接到栈底就是定时器中调用处的入参=function(){}
- 在定时器运行前打上断点,将setInterval=function(){}
- 拷贝到本地后,格式化并删除debugger,再运行如果卡死说明做了防格式化,可以在头部加上debugger,重新到浏览器运行,单步跟踪判断哪里利用防格式化卡死代码(toString后通过正则RegExp防止代码格式化),尝试在每个正则位置校验换行断点,返回值改为true。或者手动在RegExp下添加debugger检查对哪个函数做换行校验,将该函数还原一行即可。
- while(true)
- function x(){xx()} function xx(){x()}
反反混淆
判断window.info
在哪里设置的值时,可以通过在js顶部加上hook实现,根据堆栈找到设置info时的位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| (function () { 'use strict'; var a; Object.defineProperty(window, 'info', { set: function (val) { console.log('Hook捕获到setinfo设置->', val); debugger; a = val; return val; }, get: function (val) { console.log('Hook捕获到getinfo设置->', val); return a; }, }); })();
|
常规结构是参数+方法名+加密后数据
->垃圾代码
->解密代码+检测
->真实代码
->计时器检测
->死代码注入+初始化相关
抓包优先清空所有cookie
防DDOS
服务端返回503资源受限,浏览器为了提高响应速度自动清理缓存,被服务端状态码欺骗,导致所有参数都不会被搜索到,浏览器拿不到源码可以通过fiddler追寻历史发包记录,找到503、521记录大多返回包含混淆后的fuckjs中即可找到参数内容,逐段fuckjs(避免脱机)只可以通过浏览器自动运行解密识别,还原替换到原js中。
5s防护加头
从fiddler中的503、521请求返回中获取未直接返回的加密参数生成逻辑
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
| var ja_ = { value: "" //这个是取值用的 }; var location = { hash:"" //这个不用管,取值用的 }; var k_ = { innerHTML:"[k的那串fuck代码]" //k的那串fuck代码,带k值的5s防护 }; var document = { createElement: function(xd) { if (xd == 'div') { return { innerHTML: "", firstChild: { href: "http://www.itorrents.org/" //这个链接根据需要修改 } } } return {} }, getElementById: function(x) { if (x == 'jschl-answer') { //这个名字根据需要修改 return ja_; } if (x == 'challenge-form') {//这个名字根据需要修改 return { //这里是个form表单,根据需要修改 action: "", submit: function() {} } } else{ return k_ //这里就是多出来的k的值,带k值的5s防护 } return {} } };
// 补头 function getno5s(){ !function() { //setTimeout 里的代码 }() return ja_.value //这里取值用的 }
//自执行 !(function(){ ... t = document.createElement('div'); t.innerHTML='<a href='/'>x</a>; t = t.firstChild.href;r=t.match(/https?:\/\//)[0] t = t.substr(r.length); t = t.substr(0,t.length-1);k='cf-dn-tPaJV'; a = document.getElementById('jschl-answer'); f = document.getElementById('challenge-form'); ... a.value = ... f.action += location.hash; f.submit(); })()
|
加速乐
localtion.href
改为a.b
,eval
改为console.log
将解密后的代码拷贝出来补头
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
| var window = { addEventListener: function(){} } var document = { addEventListener:function(x,x1,x2){ if(x == "DOMContentLoaded") { x1(); } }, addEventListener:function(x,x1,x2){ if(x == "onreadystatechange") { x1(); } }, cookie:"" } var setTimeout = function(x,x1){}
// 补头 var _9 = function() { setTimeout('location.href=...',1500); document.cookie = '__jsl_clearance...' } if((function(){ try{ return !!window.addEventListener; } catch(e){ return false; } })()) { document.addEventListener('DOMContentLoaded', _9, false) } else { document.attachEvent('onreadystatechange', _9) }
|
document.cookie即可拿到加密后的值
验证码
网易易盾, 腾讯防水墙,极验,本文以网易易盾为例,实现验证码加密的逆向分析。
抓包
通过抓包https://c.dun.163.com/api/v2/get
生成验证码入参对比,token可有可无
返回值为携带token的json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| { "data": { "bg": [ "https://necaptcha.nosdn.127.net/92790c370aaa4e83ab122927096375d1.jpg", "https://nos.netease.com/necaptcha/92790c370aaa4e83ab122927096375d1.jpg" ], "front": [ "https://necaptcha.nosdn.127.net/d040ca01b02c4d5493b90cf50f066213.png", "https://nos.netease.com/necaptcha/d040ca01b02c4d5493b90cf50f066213.png" ], "token": "fa17e8710a7f40beb760d2398f3890a5", "type": 2, "zoneId": "CN31" }, "error": 0, "msg": "ok" }
|
通过抓包滑动验证码https://c.dun.163.com/api/v2/check
,其中的token参数为get包返回的fa17e8710a7f40beb760d2398f3890a5
滑动成功后将返回validate字符串
1 2 3 4 5 6 7 8 9 10
| { "data": { "result": true, "zoneId": "CN31", "token": "2ce6fe0684804245a795d584395487b5", "validate": "8gxMgwjCs36ABQyKiWXApPYj4qmeePZtNacdk+33/pWyyVk9xmp6Fe6etQ75HC/G/p4SqUksSPlt8+SstaDC3Eve43eTujSRtRjRkjwxQHpQ/lDjwKTb2VfYqnObIYge5b29Di6sbOKMlDTLiDqVGcyrENpDdPSiOkrTu3zS30w=" }, "error": 0, "msg": "ok" }
|
通过c.dun.163.com/api/v2
过滤生成和滑动验证码的包。
分析
fp
进入Initiator调用栈并打上断点,,滑动滑块进行check后重新获取图。该js动态生成因为追加时间戳可以通过xhr断点跟踪~~
在t参数中找到了fp已经被加密,根据右侧调用栈
逐步向上追溯到fp定义的位置,即n.fingerprint
在当前页面中搜索fingerprint
,找到该参数赋值的地方,即 window.gdxidpyhxde
由于cookie中有gdxidpyhxdE
的值,清除缓存后,利用hook实现window.gdxidpyhxde
的定位
控制台打印be的值正是gdxidpyhxde,接下来判断be是在哪里塞入fp的值,由于core.v2.15.2.min.js每次动态生成,所以每次都需要重新下断点跟踪。
单步跟踪到h赋值时,重新回到了hook函数,再观察h的值,有加密串:时间戳组成。找到了加密的大致流程,追溯上层调用栈后,以上的function都包含于于一个大自执行的多层自执行function,接下来就是把大外层的function扣出来了。
![GIF 2021-9-6 8-06-35](JS逆向之反调试入门/GIF 2021-9-6 8-06-35.gif)
去除自执行函数,将W()中的h结果返回出来就是我们所需要的的fp,接下来将代码拷贝到js引擎中运行。报错引用错误: window 未定义
,补头var window = this;
,报错类型错误: 无法读取属性 'cookie' of undefined
,直接干掉G[u[160]] ||
报错类型错误: Cannot set property 'cookie' of undefined
,由于在加载滑块过程中没有被调用,直接干掉该方法,同理引用到的函数也一并干掉
重新运行getfp()
虽然有了结果,不过该值是不正确的,以下位置取的指纹信息,而v8执行是拿不到指纹的,所以直接把该值输出后替换到扣出的js中,var w = ["6762186695653", "39753334585035"];
。
针对不同的站,host不一样,在W()中修改
1 2
| var _host = "dun.163.com"; i[u[61]] = _host;
|
接下来通过Initiator栈中f.src的fp的值由undefined改为上面的加密值编码后的结果
cb
Initiator断点,滑动滑块触发check后,再次调用get
当滑动滑块时,第一次check完成,获取第二次get的调用栈中找到了cb的定义位置
跟进s()后
1 2 3 4
| function s() { var e = X.uuid(32); return P(e) }
|
其中uuid抠出来可以直接运行调用
其中P函数所在大function抠出来