理解爬虫HOOK技术

理解爬虫HOOK技术

前言

  工作中为了帮助其他部门同事更好的完成负担比较重的操作,研究过一些网站反爬的加密措施,但是工作中的例子不太适合展示。正巧之前在爬虫挑战题目当中,遇到了一个加密问题,[题目地址]{.ul}
,虽然不是第一次遇到。但是这次让我思考到了一件事情,在逆向网站的时候,我们能不能有一些比较方便的办法来更高效的解决这类问题呢?从百度上简单搜索了一下,发现了HOOK技术,比较符合我的想法,也让我开启了一个新世界的大门,原来还可以这样?综上正好把自己顺带的一些小经验跟大家唠唠。

开胃菜

  先思考这么一个问题?当我们在浏览页面的时候,有些网站(0几年的时候网页吧)老喜欢
alert(‘你看我烦不烦?’)你会不会感受到崩溃?假设这类的弹窗一直存在,会导致我们的用户体验极差,那么现在请你思考一下,运用你所掌握的技术,如何搞掉这个烦人的东西?

​ —–这里是5分钟的思考时间——

  如果你没有什么思路,那么这篇文章将对你有很大帮助。

  我们先将问题拆分一下,如果是一个我们自己的网页,要取消掉 alert
功能,我们可以这么做:

alert = function(message){

console.log(message)

}

复制代码

image-20200911153926607

  此时你会发现,这段代码执行完以后,页面上的 alert
功能已经失效了,转变成了 console.log 更加友好的打印出来了消息。

问题引申

经过前面 “开胃菜”
篇章,我们可以了解到,其实改写一个系统内置函数是相当方便的。那么我们是不是可以把之前的例子在写的好一些呢?答案是肯定的,考虑这段代码:

let myalert = alert;//备份alert函数

alert = function (message){//改写alert

console.log(‘拦截到的alert函数消息:’,message)

}

复制代码

有同学会有疑问:

  • 为什么要备份alert函数?

  • 只是 console 提示信息改了下而已?

先回答第一个问题:在我们修改任何的东西,养成备份是个好习惯,这仅仅是习惯问题。

第二个问题:其实是从这个 consolelog
的改写中,有没有想到一个问题?我们此刻改写了 alert
其实相当于是把它拦截掉了!没错,这就是我们要说的 HOOK
终于带入到我们的主题正式开始了!

何为HOOK

先来引用一段比较官话的定义:

Hook
技术又叫做钩子函数,在系统没有调用该函数之前,钩子程序就先捕获该消息,钩子函数先得到控制权,这时钩子函数既可以加工处理(改变)该函数的执行行为,还可以强制结束消息的传递。简单来说,就是把系统的程序拉出来变成我们自己执行代码片段。

官话一般都不讲人话,简单来说,我们举个例子。

你点了一个外卖:

  • 正常流程:下单-外卖小哥配送-收到送来食物

  • HOOK技术能做的事情:(hook做点事,比如让你别下单)-下单(可以下单前打个电话问朋友)-(hook做点事,比如取消订单)-外卖小哥配送(可以跟外卖小哥说已经取消了订单)-(hook做点事,比如拒绝这笔配送)-收到送来食物

根据这个例子,可以发现 Hook
技术就是在某个正常流程的中间某个时机,可以改变原本我们固定的一些操作,他就像个捣蛋鬼,可以给你的事情中间加点料。

第一个HOOK

我们回到刚刚最早说的 alert 想想,是不是就是做了我们说的这么一件事情?
alert 本身是用来弹窗的,这是我们正常的流程。

我们在弹窗前可以做个是否弹窗的确认按钮,然后接下来再确认要不要弹窗,这就是
hook,我们来实现完善一下:

let myalert = alert;//备份alert函数

alert = function (message){//改写alert

console.log(‘开始弹窗,这里可以做一些事情’);

console.log(‘拦截到的alert函数消息:’,message)

if(confirm(‘是否要进行弹窗?’)){

myalert(message)

}else{}

console.log(‘弹窗取消,拦截消息成功!’)

}

复制代码

在控制台跑一跑这个代码试试看,是不是能看到我们成功的 hook 住了这个 alert
功能?

控制台操作

发现有本电子书写的很好,可以移步[Chrome DevTools 使用技巧的介绍]{.ul}

里面有个没讲到的api:debug/undebug 可以自己研究下

如何hook别人网页?

大家都知道,自己的东西搞起来这种简单,无非就是加加代码搞搞即可,别人家的代码打开页面都执行完了,都没得玩了!其实不然,我们仔细想想,能否借助其他工具来实现呢?

  • 谷歌插件或者油猴注入,油猴可以监听文档加载的几种不同状态,并在特定时刻执行js代码。

  • 代理注入,修改应答数据,在标签内的第一个位置插入

  • 使用chrome-devtools-protocol,
    通过Page.addScriptToEvaluateOnNewDocument注入。

这几种办法都可以让代码提前注入到页面前面,就可以实现我们说的这种情况啦~

但是重点来了!之前介绍的这几种都是需要另外安装工具,我这里要推荐一种0安装的方法,浏览器就可以改写。你需要先准备一个
chrome 浏览器(我记得大概 85 版本以上)随后打开控制台面板:

  • 可以看到左侧有个 page 面板,里面包含了所有的此次页面加载资源

  • 在往旁边找,可以找到 overrides 面板。

image-20200914124016060

image-20200914124037752

我们主要就看着这两个面板即可,首先(首次打开需要指定一个本地文件夹目录)打开
overrides 点击 enable 启用 local overrides 功能。

再回到 page 面板里 对着需要改写的资源右键 save for overrides
选项,可以看到这样点击过后,会有一个小圆点在图标下方,这样说明我们成功改写了,控制台自带的编辑器就可以让我们改写内容了,我们将
head 标签里面加入我们需要放的 hook 函数,改完以后 ctrl +s
保存,刷新页面即可生效(注意不要关闭控制台)。

这种方式更加方便,发散下思维 network 里面的 get/post
请求是不是也可以通过这种方式拦截改写 response
呢?答案是肯定的!只不过你得转码一下请求路径里的特殊字符,例如:
模拟请求接口,如果接口携带参数 ? 用%3f来替换问号当做文件名。

详细的编码参考:
[www.w3school.com.cn/tags/html_r…]{.ul}


方法 网站 文件路径


POST [coolaf.com:1010/tool/ajaxgp]{.ul} 文件目录coolaf.com%3a1010toolajaxgp 其中 ajaxgp 是个文件

GET [www.baidu.com/s?wd=2131]{.ul} 文件目录
[www.baidu.coms%3fwd=2131]{.ul}
其中 s%3fwd=2131 是个文件


文件里面写自己想写的东西即可,不必在意后缀名(重要)。这样我们就可以实现在控制台改写内容的操作了。

谁也阻挡不了我们想要调试的想法了

常见的几种hook函数

首先我们来看第一种,先思考下,如何让控制台自动报告页面创建了哪些元素?

—–这里是5分钟的思考时间——

好了,思考结束我们来看看,如何实现这样的一个 hook 函数:

let create_element = document.createElement.bind(document);

document.createElement = function (_element) {

console.log(“创建DOM标签:”, _element);

return create_element(_element);

}

复制代码

这里要注意 document.createElement 这个方法,如果不使用 bind 指向
document 会产生非法调用的报错:

所以我们需要在引用方法的时候,bind 一下。

是否和我想的一样?不一样也没关系,可以在评论下给出自己的方式,条条大路通罗马。

那么如果再让你实现一个 eval 函数,是不是也类似更简单了?

let my_eval = eval;

eval = function (message) {

console.log(“eval:”, message);

my_eval(message);

}

复制代码

基本上使用这个思路,都能搞定hook函数。

检测页面变量

我们再深入思考一个问题,如何 hook 到页面上声明了哪些变量呢?

—–这里是5分钟的思考时间——

我的方法是分好几步(你有更好的可以留言):

  • 第一步,先拿到一个空页面(注意浏览器不要装插件干扰) window
    对象底下的全部初始化对象,拿到 key
    数组。因为window是能记录下来所有的全局变量的,拿它作为突破口是必然的,缺点:只能拿到全局的,闭包内的就没办法了(老一辈常说:话不能说太满,有其他的方法欢迎留言)。

  • 第二步,拿这些去对比现在的页面上,看到底哪些变量是多出来的。

  • 第三步,对这些变量进行 hook 检测它们的动态。

好了,思路大概是这样,那么接下来改如何实现呢?我们一步一步来:

在确保所有插件和干扰元素都排除的情况下,我们用浏览器新开一个页面,然后记录下来这个页面目前的一些默认变量信息,秀操作:

var originKey = [];//数组保存window下默认变量

for(key in window){

originKey.push(key)

}

复制代码

可以看到,这些变量被我们收集好了。如果一个目标网页,去掉这些变量,剩下来的就是我们额外声明的变量。

用同样的方法去收集目标网页的变量,两个数组一对比,就可以知道了。空白页面是固定的,因此这个数组我们第一次跑完,第二次就可以接着用了。于是我们的代码长这样:

(()=>{

var originKey = [“parent”…省略很多系统变量”dispatchEvent”];

var moreKey = [];

for (key in window) {

if (window.hasOwnProperty(key) && !originKey.includes(key))

moreKey.push(key)

}

console.log(moreKey)

})()

复制代码

首先立即执行函数必须执行,如果暴露在了外面,会导致变量挂载到了window底下,就乱了。

那么接下来,我们来看如何检测一个变量从声明到被设置?

先了解两个 api 直接去看mdn上的介绍吧,写的非常好了:

非常简单的一段代码:

var temp = ‘’;

Object.defineProperty(window, ‘mytest’, {

set: function (value) {

console.log(‘发现变量此刻正在赋值!’);

temp = value;

},

get: function () {

console.log(‘发现变量此刻正在被人读取!’);

return temp;

}

})

复制代码

我们可以先试试看,是不是可以hook到它的行为?

可以看到,完全没问题。大家可能会想,为什么不用proxy,不是一个更前沿的
api 么?proxy无法代理一个未定义的变量,并且必须是对象。因此我们只能使用
Object.defineProperty
当然你也知道它的坏处,就是无法深入监听,有没有这种的办法呢?当然有!

看看我的例子,hook ‘axios’,’Vue’:

(function () {

var values = {};

function hooks(varName, values) {

Object.defineProperty(window, varName, {

set: function (value) {

console.log(“变量[“ + varName + “]:普通 正在被赋值:”, value);

values[varName] = value;

if (Object.prototype.toString.call(value).indexOf(‘object’)) {

values[varName] = new Proxy(values[varName], {

set: function (obj, prop, value) {

console.log(“变量[“ + varName + “]:对象 proxy设置属性”, prop,
“值:”, value)

obj[prop] = value

return value;

},

get: function (obj, prop) {

console.log(“变量[“ + varName + “]:对象 proxy读取属性”, prop,
“值:”, obj[prop])

return obj[prop];

}

})

}

},

get: function () {

console.log(“变量[“ + varName + “]:普通 正在直接读取”)

return values[varName]

}

})

}

hooks(“axios”, values);

hooks(“Vue”, values);

})();

复制代码

其实更加常见的 hook ,是我们在进行一些特殊的 api 调试,比如说 cookie
操作等,那就 hook cookie 。

监控某些特殊闭包内的变量

这个其实目前我的办法只能曲线救国,使用之前提到 hook
别人网页的操作,在它们的代码里面,使用 object.defineProperty 或者 proxy
即可!就当自己的代码改了就行!

为什么自己的浏览器可以,node或者其他语言脚本跑不行?

这里最主要的一个点在于,很多网站使用了浏览器检测。例如:检测当前环境
canvas
是否支持?检测常规的浏览器特性,可以参考[文章]{.ul}!因此我们在做hook的时候,也要注意一下如果脚本要放到其他语言里面去跑,要特别注意一下这个网站有没有检测过浏览器环境,不能一股脑的代码拷贝过来就用。

作者:Deno
链接:https://juejin.cn/post/6873365248510951431
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!