创作不易,希望点个赞!
如果本文有任何问题错误,欢迎评论指正!
(点个赞!点个赞!点个赞!点个赞!点个赞!点个赞!)
前面文章:
爬虫(js逆向)网络基础协议与抓包原理-chrome开发工具-fiddler抓包-重放攻击(1)
爬虫(js逆向)js基础-函数进阶-原型链(prototype、proto、构造函数-this绑定对象(2)
爬虫(js逆向)非指纹built-in函数-js进阶-混淆与伪代码-常见反爬措施-爬虫逆向方法论(3)
爬虫(js逆向)调试干扰-处理debugger-调试检测-内存爆破-JS逆向举例(4)
对于前面文章的补充:
关于之前讲述的控制台打开检测原理
看到一个页面,并非单存的是通过html组成
实际上是:html+css+javascript 组成
关于html、css文章,我的博客有非常详细的介绍
html_后端工程师必备知识-这些你都懂了吗?大概3w多字
CSS_后端工程师必备知识-从入门到劝退详解-呕心沥血撰写(滑稽)写了大概4W多字,自认为非常详细
而javascript的知识内容在前面博客有提及,包含我的有道云笔记(简写常用)(详细的可以看w3c教程)
Autoresponseder 替换注意事项
Autoresponseder 是fiddler的一个功能,使用方式在第一篇js逆向文章已经非常的详细讲述
实际上,比如直接打开百度,看到的页面源代码,html+css+js,可以通过fiddler拦截其中比如js,编写自己的js规则
关于js的一些作用域问题
负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。
var a = 1;引擎会在定义这个变量的局部中寻找,比如定义在某个函数内,在函数内没有查找到,那么会接着查找,直到找到
否则报错
LHS 与 RHS作用域查询机制是什么?
var a = 1; // LHS查询:赋值操作左侧的查询,LHS查询试图找到变量的容器本身,,从而对其赋值。
console.log(a) // RHS查询:赋值操作右侧的查询,可以理解为“取到某某的值”
相关文章:
https://www.h5w3.com/57138.html
https://www.h5w3.com/62830.html
https://www.h5w3.com/70576.html
https://www.h5w3.com/34296.html
https://www.h5w3.com/32056.html
https://juejin.cn/post/6844904033308655623
Hook 技术又叫做钩子函数,在系统没有调用该函数之前,钩子程序就先捕获该消息,钩子函数先得到控制权,这时钩子函数既可以加工处理(改变)该函数的执行行为,还可以强制结束消息的传递。
简单来说,就是把系统的程序
拉出来变成我们自己执行代码片段。
在js中,系统程序可以指浏览器API,也可以指代码中实现的一些方法等
分类:手动hook\自动hook
客户端拥有js的最高解释权,可以决定在任何时候注入js而服务器无法左右
只能通过检测和混淆手段令hook难度加大,但是却无法阻止。
js hook目的是找到函数入口以及一些参数变化,便于分析js逻辑
但是 hook的能力远不及此。我们只借助其寻找函数入口。
func
为需要处理的函数,赋值给一个新的备份变量old_func
,重写需要处理的函数方法func
,arguments
为函数传入的参数
mytask
为处理业务的代码,打印,也可以写一个debugger
方便调试,然后return old_func.apply(arguments)
,返回这个函数的调用
old_func = func
func = function(arguments){
mytask;
return old_func.apply(arguments)
}
那么hook的原理是什么呢?
第三篇文章,提及了原型链,重写了函数的方法,那么会发生改变
以eval
举例,先看原生的eval
:
修改后,可以明显的发现,其原型链指向的内容发生变化
那么通过上面的eval
举例,实际上为了应对反爬措施(开发业务代码的,判断原型链是否发生了变化),利用了hook,可以伪装没有被修改,需要抹除被hook的痕迹
,也就是需要生成对应的 伪造指纹,下面第三节的第3小节有讲述evalhook完毕需要伪造正常的指纹,避免被发现
func.prototype..... = .......
func :要hook的函数
相关文章1
相关文章2(这篇文章直白易懂)
基本语法: Object.defineProperty(obj, prop, descriptor)
- 参数(obj): 被操作的对象本身
- 参数(prop): 属性名称, 指定对象要修改或者新增的属性名
- 参数(descriptor): 对属性的描述(如 value指定属性的值)
descriptor 属性详解
- value: 需要设置属性的值(默认为 undefined)
- writable: 该属性是否为可修改的(默认为 false)
- configurable:是否可以配置descriptor中的属性是否可以修改(默认为 false 除了 value和writable属性外)
- enumerable:该属性是否可枚举(默认为false)
- set(): 该属性修改时会调用此函数(默认为undefined)
- get(): 该属性获取时 会调用此函数(默认为undefined)
实际上是借助上面提到的 defineProperty方法,obj.attr
某属性进行复制一个新的变量old_attr
old_attr = obj.attr
Object.defineProperty(obj, 'attr', {
get: function() {
console.log(cookie_cache);
return old_attr
},
set: function(val) {
return ......
}
通过案例说明公式原理,实际上,复制了浏览器document
对象cookie
值为1,然后将其赋值给一个新变量document.cookie_bak
,结合defineProperty
中descriptor
属性get
,方法,当访问document
中cookie
的属性时候,会执行此函数,经过cookie_bak
,伪装没有修改过,那么此时,访问document.cookie
,时候,因为函数中编写了debugger
,那么会进入到debugger
状态,这样就成功了,那么调用堆栈,就可以找蛛丝马迹呢~~~
document.cookie = '1'
document.cookie_bak = document.cookie
Object.defineProperty(document,'cookie',{
get:function(){
debugger;
return document.cookie_bak
}
})
document.cookie
进入到debugger
状态
st 2、9
bs2、10
下面新增set,调用创建的的适合进入debugger状态
Object.defineProperty(document,'cookie',{
get:function(){
debugger;
return ;
},
set:function(){
debugger;
return;
}
})
调试前,打开script监听
提前打开控制台,点击需要调试的网页,可以看到网页在加载且进入,只有触发F8,F10,F11这些才会加载script
关闭script事件监听,并且单步F10调试,慢慢按,遇到一个debugger,右键行号,点击neve paue here,过debugger
修改下hook代码
Object.defineProperty(document,'cookie',{
get:function(){
debugger;
return ;
},
set:function(val){
debugger;
return;
}
})
经过注入代码,单步调试后
debugger,有些并不会直接写出来,而是通过eval混淆调用,是看不到名称的,如果在解决了常规debugger情况,有些东西还是拿不到,就要考虑打开网站之前就要做debugger测试hook,是否是eval型debugger
构建eval方式的hook,且伪造指纹
eval_bk = eval
eval = function(val){
debugger;
return eval_bk(val)
}
eval.toString = function(){
return " function eval() [native code]"
}
注入hook,经过调试(其中测试的时候,我发现chrome无痕调试比较准确能够)
向上找堆栈,发现是在这里进入的第二层,为此处调用的eval
继续F10 往下执行,进入了第二层代码,函数入口
(1)函数hook
一般情况下不会出现hook失败的情况,只有可能是 proto 模拟的不好导致被检测到了。
(2)属性hook
当所有网站的逻辑都采用Object.defineProperty绑定后,属性hook就会失效。暂时没有发现好的解决方案。
第一次执行绑定没有问题,第二次绑定会报错,如果整个网站都用这个绑定的时候,代码绑定之前,可以hook,绑定之后无处理。
有些网站会自己写hook,如果尝试hook,hook失败的话,会写一个蜜罐逻辑,让调试者调试失效
(3)局部hook
,原理是一样的,只不过需要在进入作用域的那一刻进行hook。
如果不想手动写脚本,比如自定义通用脚本,可以借助油猴
chrome网上应用商店
https://www.tampermonkey.net/faq.php?version=4.13&ext=dhdg&updated=true
红色的很重要
@name :脚本名,你爱叫什么叫什么。
@namespace :脚本的命名空间。(一般就是写个url,告诉自己用在哪里)。
@version:版本,你爱写多少写多少。
@author:作者,你爱写谁写谁。
@description:描述,你爱怎么描述怎么描述。反正别人也看不懂。
@homepage, @homepageURL, @website and @source: 一般正常人都不写这几个参数。从脚本名称链接到给定页面的作者主页。
@icon, @iconURL and @defaulticon:一般正常人都不写这几个参数,低分辨率脚本图标。
@icon64 and @icon64URL:一般正常人都不写这几个参数,高分辨率脚本图标。
@updateURL: 更新检查的url,@version参数开启,会向url检查更新。
@downloadURL:定时监测到更新会自动下载url内容,若为 none则不检查
@include @match @exclude:匹配规则相关。支持精确匹配与正则匹配。@exclude是排除,不写相当于脚本白写
@require @resource :导包,支持url引入。比如引入 jquery,可以使用$
@connect:标记定义域,感觉很有用,但是我菜,所以很少用
。
@grant:白名单函数:比如:@grant window.close 。很强大,如果默认不写则会对一些原生函数如:eval等进行保护,显然这不是我们所期待的,所以就直接 @grant none了
@antifeature :官方文档的意思:开发人员是否允许别人把脚本货币化。
@noframes:在主页上运行而不在iframes上运行。
@unwrap:卵用没有,官方文档的意思:在chrome上,不需要它,自动被忽略了(淦!)
@nocompat:一般默认,官方文档写了好长,大概的意思指支持标记浏览器。如:@nocompat Chrome,这样就不能在火狐浏览器运行它了。
逆向中最重要的参数!!!
@run-at: 指定油猴脚本在什么时候执行,默认是在所有js加载完成后执行。(那hook脚本还有个屁用啊!)
参数:
@run-at document-start :脚本尽快注入(相当于script断点之后,进入页面一瞬间注入)
@run-at document-body :如果页面body元素存在,则注入(所以练习平台第二题就注不进去了)
@run-at document-end :脚本将在DOMContentLoaded事件发生注入。
@run-at document-idle:默认值。脚本将在DOMContentLoaded事件发生之后才注入
@run-at context-menu:如果在浏览器上下文菜单中单击脚本(仅限于基于桌面Chrome的浏览器),则会注入脚本。
示例网站:
http://cpquery.cnipa.gov.cn/
将代码放入进去ctrl+s保存
// ==UserScript==
// @name 万能hook eval函数
// @namespace http://tampermonkey.net/
// @version 0.1
// @description eval-everything
// @author An-lan
// @include *
// @grant none
// @run-at document-start
// ==/UserScript==
// ==UserScript==
// @name 万能hook eval函数
// @namespace http://tampermonkey.net/
// @version 0.1
// @description eval-everything
// @author An-lan
// @include *
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
alert('hook success');
var eval_bk = eval;
eval = function(val){
debugger;
console.log(val);
return eval_bk(val);
};
eval.toString = function(){
return "function eval() { [native code] }"
};
eval.length = 1;
})();
// ==UserScript==
// @name 万能hook eval函数
// @namespace http://tampermonkey.net/
// @version 0.1
// @description eval-everything
// @author An-lan
// @include *
// @grant none
// @run-at document-start
// ==/UserScript==
alert('hook success');
var eval_bk = eval;
eval = function(val){
debugger;
console.log(val);
return eval_bk(val);
};
eval.toString = function(){
return "function eval() { [native code] }"
};
eval.length = 1;
gif 动态演示,保存编辑好脚本后,打开网页,F5刷新,可以看到自动跳转到了debugger地方,这个时候就可以查看堆栈了
(滑稽,一开始漏写debugger语句进脚本,不会自动调到脚本,TMD找了15分钟原因)
调试的网址,不贴出来了!!!
开启油猴脚本后,在目标网址打开控制台,F5刷新网页,就能进来,接着就可以分析堆栈了~~
function a() {
function b(a) {
return a
}
// hook b的 arguments a 为 5的时候打断点
b_bk = b;
b = function (val) {
if (val === 5) {
debugger;
}
return b_bk(val)
}
// 下面为业务逻辑代码
b(1);
b(2);
eval('b(' + 5 + ')');
}
a()
如何hooke到d呢?暂时没有思路。。。
function c() {
let d = 2;
}
Object.defineProperty(window, 'hook', {
set: function () {
debugger;
}
})
实际上查看String相关的方法,可以通过hook下面的方式,得到非常多想要的内容
在前面已经提及了,如何编写hook
所有验证码都是基于以下原理,验证码本质上就是交互过程与模拟过程
交互过程
:可以机器学习方式实现等等模拟过程
:如果难一点纯走协议,就需要逆向js等等利用fiddler抓包工具
通过base64解密,验证码图是在img中的链接中加密请求
那么经过人为点击处理完毕验证码,会携带上面的cookie,以及下面的图片点击的坐标,请求点击后的接口,那么服务端就会校验,成功的话会返回一个成功的cookie(此时会删除之前的cookie)或者其它参数,表示用户通过,前端js缓存的内容可能会存在校验
市面一线的验证码,都会将请求的参数进行加密,不可能像上面的的案例看到数字,后端会去再将加密的解码。
捕获了如下请求
在用户操作成功后,再请求一个接口,返回了token,通过这个token,当前页面,会将这个token会传向后端就会二次验证,整个过程就是如此
补充session和cookie的关系:
首先cookie是服务端识别客户的唯一标识的依据,客户在访问网站时候,服务端为了记住这个客户,会在服务端按照它的规则制作一个cookie数据,会将这个cookie数据保留在服务端一段时间,同时会给客户的一份它自己保留,这样就无需每次都要登录来认证自己了。
先来了解几个概念。
1、无状态的HTTP协议:
协议是指计算机通信网络中两台计算机之间进行通信所必须共同遵守的规定或规则,超文本传输协议(HTTP)是一种通信协议,它允许将超文本标记语言(HTML)文档从Web服务器传送到客户端的浏览器。
2、HTTP协议是无状态的协议。
一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的连接。这
就意味着服务器无法从连接上跟踪会话。
3、会话(Session)跟踪:会话,指用户登录网站后的一系列动作,比如浏览商品添加到购物车并购买。会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话。常用的会话跟踪技术
是Cookie与Session。Cookie通过在客户端记录信息确定用户身份,Session通过在服务器端记录信息确定
4. 从爬虫(验证码)角度来看,是以set-cookie建立了连接(以抓包情况演示验证码) 【当然了,不一定是以set-cookie的形式,还有可能是以接口API形式返回,反正无论如何,必须让服务器给你标记一下】
抠代码的三个阶段:
A. 缺啥补啥,稳扎稳打
B. 见文知义,化繁为简
C. 了然于胸,如履平地
优点:
缺点:
优点:
简单网站,相对于抠代码,对熟练度要求更低
缺点:
JavaScript RPC 的简单实现过程是:
在客户端JavaScript脚本中,将要调用的服务端PHP函数名和函数参数(本地的JavaScript变量值)作为要传输的数据,通过Ajax传输到服务端,同时,Ajax设置一个回调函数,以便使用服务端函数的返回结果。在服务端PHP脚本中,接收Ajax传输的数据,从中取出要执行的函数名和函数参数。然后执行指定的函数,并将执行函数的返回值作为传输的数据,直接输出到浏览器,以此作为响应AJax的请求。客户端的Ajax在接受服务端的响应后,把返回的数据传递给AJax的回调函数。到此完成了一个RPC的流程。
st2,bs2
直接请求这个网址,返回的是script脚本
写一个hook,捕获cookie
Object.defineProperty(document,'cookie',{
set:function(val){
debugger;
return val
}
} )
查看右边堆栈,定位到了代码
document[_$ob('0x35')] = a[_$ob('0x47')](a[_$ob('0x1')](a['NpWYd'](a['gpbdE'](a[_$ob('0x5e')](a[_$ob('0x2')](a[_$ob('0x48')], Math[_$ob('0x1c')](a[_$ob('0x11')](c, 0x3e8))), '~'), token), '|'), md), a['zQTqr']);
核心步骤:定义一个变量去接收,创建一个test.js
文件存放代码
var cookie = a[_$ob('0x47')](a[_$ob('0x1')](a['NpWYd'](a['gpbdE'](a[_$ob('0x5e')](a[_$ob('0x2')](a[_$ob('0x48')], Math[_$ob('0x1c')](a[_$ob('0x11')](c, 0x3e8))), '~'), token), '|'), md), a['zQTqr']);
扣代码,就是缺啥补啥
引入眼帘的是a
,是一个对象,那么还是不知道a
是什么,继续向上看a
搜索var a
,定位当定义a
的方法,( 可以其它方法搜索,查找,能定位到就是好方法)
现在查看 a
里面缺什么(采用深度优先,需要解决a及其相关,再考虑广度优先),现在解决_$ob
定位到此处,复制到test.js
可以看到_$oa
是啥不知道,那么需要重复上面的步骤,定位复制
_$oa
是一个数组
搜索_$oa
,在上面可以看到,是将_$oa
进行数组移位生成的
静态(直接复制数组):
动态(复制逻辑代码):
到这一步感觉上是都扣代码完成了,那么可以直接运行脚本,发现c
没有定义
定位到了c
,并将其复制test.js
再次运行,发现token没有定义,复制粘贴,一运行发现window
没有定义,这是因为node.js没有
再次运行,不难发现,window中没有btoa,即每页base64方法
base64个人是建议手写出来,但是注意是否被魔改(没有魔改),安装nodejs中btoa包
npm install btoa
也可以pycharm手动安装
导入包
再次运行 发现md
没有定义
继续扣~~~
继续
继续~。。。
继续扣,最后运行,不报错!!!nice
打印一下cookie看看,逻辑完成
但是还不够,希望的是,python去调用cookie,写一个python脚本,调用js,运行代码,成功打印页面html,而并非是js代码~~~,芜湖~~~通过!!!!
猿人学大赛第二题
注入控制台hook脚本
Object.defineProperty(document,'cookie',{
set:function(val){
debugger;
return val
}
} )
然后就是看堆栈,扣代码~~~省略步骤说明!!!!
通过堆栈确定是这些
查找 _0xe54e90
,可以看到,这个变量是由_0x2b293a
定义而成
最后补完后,一运行,报错
错误发现跟本地运行一样的错误,得出结论(格式化检测,并不是指纹检测)
将代码复制继续排查是否内存泄漏,不过现在,添加个debugger语句
回车运行后,F11调试,找到内存溢出的地方,可以看到疯狂的在这运行,进入死循环
进入死循环原因是,这个分支判断,进行调用了setcookie方法
最简单的方式,是直接修改这个,但是,可能会产生一些问题,可能会影响后续,分支判断
继续往上找,不能发现是一个正则处理方法,利用这个方法,test方法调用toString方法
那么手动修改removeCookie,实际上是检测这个有没有被格式化,将其手动调整不要格式化
此时运行还是错误状态,继续在console。调试,又进入循环,
实际上这是一个数组移位问题
跳过这个,不然F11半天
接着F11调试的时候,浏览器卡死,内存溢出!!!!!!!!!!!!
对应在此处打上debugger;
可以看到,又有格式化检测,并且RegExp
再次运行,发现终于没有卡死了,出现了未定,未定义好解决呀。。继续扣代码
去掉debugger,浏览器又出现了卡死,在下面地方继续debugger;找出卡死原因,即调用这个函数地方上面打上debugger
删除上一个debugger后,在卡死上方继续debugger,发现这里一运行就灰色卡死(缩小了范围)
后面步骤省略,都是重复性,利用debugger缩小方位,扣代码
唯一提及的 global
,看到这个就要想到是否重写了函数,实际的确重写了函数
1、如何编写hook函数(一般hook、对象属性hook)
2、油猴脚本使用方式
3、扣JavaScript代码流程
4、逐步利用debugger;去缩小出现内存溢出地方
5、浏览器指纹置空
6、global关键字,要思考是否内置方法被重写
补环境、jsRPC之后文章写
bs(1、2、5、6、9)