js逆向初探
JS 基础
瞎几把写就得,记录一下特性即可。
万物皆对象,了解一些内置函数和内置属性,比如
name, arguments
等var 有变量提升,let没有,闭包概念
匿名方法
const 匿名方法 = (functinn() {})
JS是弱类型语言
自主垃圾回收,引用计数
原型对象和实例对象,构造函数,打印原型对象得
.prototype
,原型对象和实例对象下面都有一个__proto__
属性,指向了其原型对象原型对象和实例对象的
this
指向是不同的,实例对象:谁调用的就是谁(比如window
),原型对象:指向的是原型对象的实例一个函数被new出来就变成了一个构造函数&原型对象,构造函数的属性和原型对象的属性不同
原型链:对于实例而言
a.__proto__
指向其原型对象,对于原型对象而言指向的其父原型对象,直到Object
原型对象一个例子:
1
2
3
4
5
6
7function pro() {
this.a = 1;
}
pro.a = 2;
var aa = new pro(); // new 出来一个 pro 对象,aa是实例,a是属性
// aa.__proto__ 指向pro对象,pro.prototype指向的也是pro对象,aa.__proto__.__proto__指向的是pro对象的父亲 Object 对象
// 如果要往 pro 对象里添加属性,可以用 pro.prototype.b = 2 或者 aa.__proto__.b = 2作用域:全局作用域、语句作用域(比如括号里面的)、块作用域(比如大括号中间的)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18var a = 1;
function xx() {
var a = 2;
console.log(a); // 输出的是 2,先从块作用域找,再从语句作用域找,再从全局找
}
/*
全局作用域 {
a = 1
语句作用域 {
块作用域 {
a = 2
console.log()
}
}
}
*/Object.defineProperty
在对象上定义一个新属性或修改一个对象的现有属性,并返回该对象,用于 hook。有三个参数obj, prop, descriptor
,分别是对象,定义或修改的属性的属性值symbol
,要定义或修改的属性的描述符(比如,可不可以被修改,可不可以被删除等等)可以通过
delete aa.a
删除 aa 实例的 a 属性,但是Object.freeze(aa)
可以冻结,不能修改和删除属性,Object.seal(aa)
不能删除属性但是可以改Object.prototype.__defineGetter__
,hook getter方法,比如aa.__defineGetter__("a", function(){console.log("1")});
就是只要aa
实例里的a
属性被访问,就会打印1
一个花指令的例子:
(0, a.request)
,这是一个函数调用,括号会返回最后一个元素的值,也就是这句话等价于a.request
工具使用
JS 逆向流程:抓包 -> 调试 -> 脱离浏览器环境(V8, node.js, pyv8等) ->
fiddler配置
版本: v5.0.20204.45441 for .NET 4.6.1 Built: 2020年11月3日
Tools -> HTTPS -> 全勾选 -> 安装证书 -> 管理员权限重启 fiddler
设置自定义端口号
用浏览器插件设置本地代理和端口号
修改 protocols 为
<client>; ssl2; ssl3; tls1.0; tls1.1; tls1.2
打开 Decode 快速解压缩
断点:捕获数据,Inspectors:放行数据
自动断点,在 request 之前/之后
修改 User Agent 地址
编程猫插件配置
编程猫插件注入 hook:开启,并删除掉地址就是匹配所有 URL。只能hook全局,局部变量是没法 hook 的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23//当前版本hook工具只支持Content-Type为html的自动hook
//下面是一个示例:这个示例演示了hook全局的cookie设置点
(function() {
//严谨模式 检查所有错误
'use strict';
//document 为要hook的对象 这里是hook的cookie
var cookieTemp = "";
Object.defineProperty(document, 'cookie', {
//hook set方法也就是赋值的方法
set: function(val) {
//这样就可以快速给下面这个代码行下断点
//从而快速定位设置cookie的代码
console.log('Hook捕获到cookie设置->', val);
cookieTemp = val;
return val;
},
//hook get方法也就是取值的方法
get: function()
{
return cookieTemp;
}
});
})();
浏览器工具
下断点
复制 JS 路径并直接修改本地的值
Event Listeners:绑定的事件列表,且可以筛选特定按钮的绑定事件
手机模式不是真的手机,没有手机的环境,浏览器的环境有 window,UA,Cookie等
微信 UA :可以通过微信抓包获取
F1:设置面板。取消Javascript source maps 功能(可能导致断不上) ,了解一下 webpack;NetWork:禁止输出,禁止清空缓存;NetWork Throttling Profiles:控制网速
XHR:XMLHttpRequest,JS 控制的一种发包方式(只能筛选JS发的包)
Performance:实时跟踪翻页时发送的包,以及时序,此外还能看到别的线程,和事件调用等(信息量有点大emm)
Application:网站储存的数据总览,Cookies 是本地文件,一般在缓存目录下,还有session Storage, LocalStorage, indexDB等
Security:是否支持 HTTPS 等
Sources:有个 Snippets ,临时脚本/片段
还有一堆功能藏在这三个点里,比如 Network request blocking等
断点:
- F9能进异步,F7不能
- 忽略断点按钮没法忽略DOM断点和异常断点
- 异常断点:try catch
- DOM断点:页面刷新的时候在HTML上下断
- 全局事件断点(生命周期断点):比如在加载的时候下断,滚动条下断,鼠标事件下断,XHR下断等
- 除了 XHR,还有 jsonp 的请求,请求后会返回的response会立即执行,且浏览器自己处理(XHR:用户请求用户处理,jsonp:用户请求浏览器处理)
- 代码行断点:跳过当前断点,编辑断点(条件断点:过滤断点,写上条件,让其在我们想要其断的时候再断,否则跳过)
- debugger:直接打上条件断点 false,其就不会执行了
- watch:监控变量的值,可以实时打印其值
- 方法栈:先运行的在下面,当前的在最上面。可以看出方法名,当前的代码行数,一些特殊的方法(比如定时器,XHR等方法),可以看出调用关系
- 作用域:Local(本地作用域),Closure(上层作用域,可能多个嵌套),Global(全局作用域),找一个变量:从当前开始找,然后一层一层网上找,直到全局
- 智能提示:会提示到当前的方法
- 下断点:方法开始,方法结束,想要的数据
send, ajax
是分装发 XHR,直接找他俩上面的栈帧- 本地替换:
- 浏览器本地替换
- DF本地替换,如果有个URL地址其
?
后面是个随机数,可以用FD替换,找到FD的Auto Responser
,也可以正则匹配(有个test按钮,点一下把想要匹配的目标URL贴进去就可以看出是否匹配成功),也可以自己写插件。如果替换的是 JS 文件,本地文件的后缀也需要是 JS 文件
反调试:
禁止控制台调试
写一个demo(比如一行代码
window.ep = 1;
放到一个新页面的 snippet中运行,发现有反调试
解决手动 debugger:直接本地替换进行修改,把debugger删掉即可
解决代码格式化检测:点击最高配置发现检测是有用的,格式化再运行会直接卡死
过检测:一般格式化检测利用的是这个原理:
1
2
3
4
5
6
7
8
9
10
11
12
13function xx() {
}
xx.toString() // 或者 xx + ""
/*
这种就会返回一个字符串,格式化由于空格,换行符的问题会导致长度不一致,这样用正则匹配一个未格式化的String,比如 funtion xx(){},如果代码格式化了,就肯定匹配不成功
那么过检测的方法就是先寻找特征
RegExp: 正则匹配
test: 正则匹配函数
toString: 字符串转化
然后下断看看会不会卡死
*/下面就是一个典型的格式化检测代码:
removeCookie
这个函数,先toString
,再test
和正则进行比较,如果发现其格式化了就返回一个检测值。那么一个有效的过检测手段就是先格式化,再把
return
的结果直接取反即可第二种方法是 hook:对
toString
方法进行替换1
_0x52e0da['removeCookie']['toString'] = function() {return "function(){return 'dev';}"}
当然也可以 hook 正则的
test
方法一个友好HOOK步骤:
1
2
3
4
5
6
7
8
9
10// 保存原函数
RegExp.prototype.test_ = RegExp.prototype.test;
// 替换原函数
RegExp.prototype.test = function() {
// 执行原函数,用 apply 执行
var ret = RegExp.prototype.test_.apply(this, arguments);
return !ret;
}
// 保护 toString (如果检测了就保护)
RegExp.prototype.test_.toString = RegExp.prototype.test.toString;第二种反调试:进虚拟机 VM
1
2// 1. eval
var a = eval("debugger;function xx(){return 2;} xx();");1
2
3// 2. Function 包裹语句
var a = Function("console.log(1)");
// 3. Blob 内存对象怎么过 Function:hook掉即可,Function的参数就是要执行的语句,直接把里面的debugger删掉即可。
1
2
3
4
5Function.prototype.constructor_ = Function.prototype.constructor;
Function.prototype.constructor = function(a) {
a = a.replace("debugger", "");
return Function.prototype.constructor_(a);
}对于一个登录页面来说,我们需要找到用户名,密码,加密逻辑
- 如果是 document 类型,就复制这个 API 的 URL 然后直接搜索,大概率能找到逻辑
- 如果是 xhr ,就直接从network里点击该包后的交叉引用,如果是 ajax,就找到其 caller,大概率能找到发包时的内容,再往上进行交叉引用,或查看栈帧找到其 caller 即可
- 如果是 jsonp,一般会有个回调函数,也是一样,找到其caller即可
异步调试:涉及到 promise
Promise标准:
拥有一个then方法
fulfill 成功时的操作,即resolve
reject 失败时的操作
padding 等待
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 有一个线程
var a = []; //定义一个数组,这个数组里放的都是方法
function Promise(p) {
a.push(p);
while(true) {
for(let i = 0; i < a.length; i++) {
if(a[i].z == -1) {
xxx
} elsec if(a[i].z == "pending") {
xxx
}
}
}
}
new Promise(); // 想要给 a 数组添加方法就要 new 这个对象,此时 执行构造方法,就是 Promise 方法,会立即执行
// 异步始终是在一个线程中的一个例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17const p = new Promise((resolve, reject) => {
setTimeout(function () {
// 用于模拟异步操作成功或失败
flag = 1
if (flag) {
resolve('异步操作成功啦~')
} else {
reject("异步操作失败啦~")
}
}, 2000)
})
p.then(value => {
console.log(value);
}, reason => {
console.log(reason);
})function.prototype.apply
:调用一个具有给定this
值的函数,以及以一个数组或类数组对象的形式提供的参数,o.apply(this, arguments ) 等价于 this.o(arguments)
,就是给 this 这个对象 bind 了一个方法 o 传入 argume nts一般会
new Promise(function(){})
,重点看里面的方法控制流平坦化:
wrap
,u.prev = u.next
,u.sent 等价于其上一个方法的返回值,方法就是找u.next
对应的前驱,把该代码块复制出来,去掉控制执行流的代码,再把u.sent 替换成上一个方法的返回值即可异步封装:
Callback 栈帧前三个一般都是发包的请求,然后看一些特殊的 js 代码
1
2
3
4
5
6
7while(b.length) {
a.then(b.shift(), b.shift());
} // shift可以理解为出栈 pop
// then 有两个参数,一个是成功回调一个是失败回调
// b 是一个数组,看其第一个元素即可
// 下断点在b.shift前面下
// 只有在异步中加密的才需要跟,在异步前后加密的可以直接看栈帧,如果看异步的时候发现执行时已经加密了,那就不用看异步了
HOOK:油猴插件/fiddler插件
油猴插件:原理是利用浏览器自带的API,直接调用这些API进行 HOOK,没有逃离浏览器的框架,不足是即使把运行时期改称了 document start,也可能 hook 不到(比如一些动态加载到js,在document刷新之前就会执行,这就hook不到),而且其会插入一段js代码,可能会被检测到
编程猫fiddler插件:基于FD代理,其脱离浏览器而存在,不被浏览器感知
hook 主要用于定位逻辑,常见的 hook 是对 LocalStorage的 hook
本地替换:override随意修改本地代码,浏览器自带的功能,但是这种方法会被检测,比如添加随机数校验,或者检查代码是否格式化,还有URL地址改变也会被识别成不同的文件
反调试
禁止控制台调试:
1
2
3
4
5
6
7
8
9
10
11// 1.检测控制台调试,用 while 或者定时器
setInterval("console.clear()", 1000);
// 修复:禁用console.clear,有个浏览器选项勾上即可
// 2.替换 console.log 为空
console.log = function(){}
// 修复:hook
console.log_ = console.log;
console.log = function() {};
// console.log被删掉以后
console.log = 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
35
36
37
38
39
40// 基于时间
function testDebuggerByTime() {
var cur = Date.now();
debugger;
if((Date.now() - cur) > 200) {
console.log("debugger detected");
}
}
testDebuggerByTime();
// 过检测,hook Date.now函数即可
Date.now = function() {return 1;}
//基于 DOM 检测
// 1. 监听F12键
document.addEventListener('keydown', function(event) {
// 检查是否是 F12 键
if (event.key === 'F12') {
console.log('F12 key was pressed!');
// 在这里可以执行其他操作,例如阻止默认行为
event.preventDefault();
}
});
// 2. 监听F8或者别的快捷键
// 3. Object.toString()
// 4. windows 宽纵比,控制台单独打开不要挤压原页面即可
// 5. proxy 拦截 log 执行,其实就是hook,在log中添加一些检测
// 6. 数据劫持,意思是如果没有打开控制台,就不会执行console.log语句,基于此我们可以打印一个document中的一个id值,或者class都可以,并重写其get方法,一旦执行console.log()函数,就会获得这个id值,进而触发检测代码,这个times就是检测被console.log几次
let times = 1;
var abc = document.createElement('div');
Object.defineProperty(abc, 'id', {
get: function() {
times++;
console.warn("控制台被打开了");
}
});
console.log(abc);
// 7.