JS逆向之反调试入门

篇幅有限

完整内容及源码关注公众号: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

image-20210901213744851

防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.beval改为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即可拿到加密后的值

验证码

网易易盾腾讯防水墙极验,本文以网易易盾为例,实现验证码加密的逆向分析。

image-20210904094222693

抓包

通过抓包https://c.dun.163.com/api/v2/get生成验证码入参对比,token可有可无

image-20210904103515874

返回值为携带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

image-20210904105233073

滑动成功后将返回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断点跟踪~~

image-20210904105614374

在t参数中找到了fp已经被加密,根据右侧调用栈

image-20210904105751525

逐步向上追溯到fp定义的位置,即n.fingerprint

image-20210904110128098

在当前页面中搜索fingerprint,找到该参数赋值的地方,即 window.gdxidpyhxde

image-20210904110418447

由于cookie中有gdxidpyhxdE的值,清除缓存后,利用hook实现window.gdxidpyhxde的定位

image-20210904110848762

image-20210906073928073

控制台打印be的值正是gdxidpyhxde,接下来判断be是在哪里塞入fp的值,由于core.v2.15.2.min.js每次动态生成,所以每次都需要重新下断点跟踪。

image-20210906074857909

单步跟踪到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]] ||

image-20210906081026001

报错类型错误: Cannot set property 'cookie' of undefined,由于在加载滑块过程中没有被调用,直接干掉该方法,同理引用到的函数也一并干掉

image-20210906081713001

重新运行getfp()

image-20210906082001239

虽然有了结果,不过该值是不正确的,以下位置取的指纹信息,而v8执行是拿不到指纹的,所以直接把该值输出后替换到扣出的js中,var w = ["6762186695653", "39753334585035"];

image-20210906082059495

针对不同的站,host不一样,在W()中修改

1
2
var _host = "dun.163.com";
i[u[61]] = _host;

image-20210906083710504

接下来通过Initiator栈中f.src的fp的值由undefined改为上面的加密值编码后的结果

image-20210906084125778

image-20210906084333277

cb

Initiator断点,滑动滑块触发check后,再次调用get

image-20210907081958465

当滑动滑块时,第一次check完成,获取第二次get的调用栈中找到了cb的定义位置

image-20210907082548882

跟进s()后

1
2
3
4
function s() {
var e = X.uuid(32);
return P(e)
}

其中uuid抠出来可以直接运行调用

image-20210907083823230

其中P函数所在大function抠出来

文章作者: J
文章链接: http://onejane.github.io/2021/08/30/JS逆向之反调试入门/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 万物皆可逆向
支付宝打赏
微信打赏