李恒道 发表于 2021-9-22 17:12:55

[油猴脚本开发指南]Fetch劫持的第二种方式

# 前言

之前我们已经谈过了fetch劫持,但是由于那个代码相对较为复杂,cxxjackie提供了一个相对简单的方式,并未使用proxy劫持,我们这节课来分析一下。

```javascript
let oldfetch = fetch;
function fuckfetch() {
    return new Promise((resolve, reject) => {
      oldfetch.apply(this, arguments).then(response => {
            const oldJson = response.json;
            response.json = function() {
                return new Promise((resolve, reject) => {
                  oldJson.apply(this, arguments).then(result => {
                        result.hook = 'success';
                        resolve(result);
                  });
                });
            };
            resolve(response);
      });
    });
}
window.fetch = fuckfetch;
```

oldfetch保存了原fetch的引用

这时候我们对window.fetch挂载成我们的劫持函数,fuckfetch

因为fetch需要返回一个promise,所以这里我们通过

```
return new Promise((resolve, reject) => {})
```

包裹了一下,并且在原fetch函数内调用,并获取返回内容,对其进行一些处理并resolve返回过去。

附注:函数一旦返回一个Promise,我们只考虑输出相同的结果即可,而无须考虑Promise内处理过程的一致性。

```javascript
      oldfetch.apply(this, arguments).then(response => {
            const oldJson = response.json;
            response.json = function() {
                return new Promise((resolve, reject) => {
                  oldJson.apply(this, arguments).then(result => {
                        result.hook = 'success';
                        resolve(result);
                  });
                });
            };
            resolve(response);
      });
```

这里我们使用fetch的原函数,通过apply更改了this指针至自身,并且传入了参数。注意这点有一个需要注意的是,我们劫持函数的时候,由劫持函数调用原函数的过程中一定要使用call/apply进行修改this指针,来符合原来的调用过程。

在then后则是我们处理response的过程

```javascript
            const oldJson = response.json;
            response.json = function() {
                return new Promise((resolve, reject) => {
                  oldJson.apply(this, arguments).then(result => {
                        result.hook = 'success';
                        resolve(result);
                  });
                });
            };
```

这部分代码是针对某些特定的函数进行过滤,我们可以对网页进行分析以及调试,或去返回内容进行查看,来判断调用了哪些函数。

这里以json为例进行劫持

首先保存了原json的引用

然后修改json属性为一个劫持函数

由于json返回的是一个promise对象,所以我们这里也需要返回一个promise

在promise内依然是对其原json函数进行调用,并修改了this指向以及参数,最后对其结果进行一定的修改,然后通过resolve(result)进行返回。

那么这节课我们就了解到了一个相对较为轻量的fetch劫持方法!

# 结语

撒花~

懒男孩 发表于 2021-9-22 17:15:01

ggnba!!!

李恒道 发表于 2021-9-22 17:24:38

懒男孩 发表于 2021-9-22 17:15
ggnba!!!

好久没看到哥哥了

脚本体验师001 发表于 2021-9-22 21:30:03

按键精灵您肯定知道,他们很多导师都有自己的一套或几套插件,就是一个函数集啦。这对新手非常友好。甚至不需要去了解实现的原理。拖过来直接就可以用。能不能有针对性的做一些这种类似的东西,函数命名规范化。比如网络劫持类,这种叫釜底抽薪式。XMLHttpRequest那个叫红杏出墙式,等等

李恒道 发表于 2021-9-23 05:28:21

脚本体验师001 发表于 2021-9-22 21:30
按键精灵您肯定知道,他们很多导师都有自己的一套或几套插件,就是一个函数集啦。这对新手非常友好。甚至不 ...

这个是有计划的!其实
准备以后有机会慢慢搞,先集中精力给教程搞完...
呜呜呜,教程东西太他妈多了....

壹局 发表于 2021-11-30 20:35:22

脚本体验师001 发表于 2021-9-22 21:30
按键精灵您肯定知道,他们很多导师都有自己的一套或几套插件,就是一个函数集啦。这对新手非常友好。甚至不 ...

那是因为按键有自己的框架和编辑器,而油猴是开放的,源码也是开放的,如果在scriptcat基础上运行,倒是可以封装你提到的库或者API,但是需要时间和资金

steven026 发表于 2022-7-1 16:59:22

```
//劫持前fetch
window.hook_fetch=window.fetch

//劫持后fetch
window.fetch= async function(...args){
    let result = await hook_fetch(...args).then((e)=>{
      let res =e.clone() //克隆response
      e.text().then((ee)=>{
            console.log("第一次text()"+ee)
            //对劫持数据进行操作
      })
      return res //返回克隆的response
    })
    returnresult
}

//网站"原生"fetch
fetch("abc").then((e)=>{
    e.text().then((ee)=>{
      console.log("第二次text()"+ee)
      //网站"原生"操作
    })
})
```

话说response的stream虽然只能读一次,但是response本身能用.clone()的方式被克隆(这不是扯淡吗 那stream只读一次有什么用)
感觉可以利用response.clone()克隆response,直接把原始response复制一份给网站原生的then,然后我们对原本的response就可以爱怎么操作怎么操作了
随便写了个代码,GG看看有没有什么可以改进的(我异步不是很熟练 应该是这么写的吧)

!(data/attachment/forum/202207/01/165817mfk4yykyzfratwg5.png)

cxxjackie 发表于 2022-7-1 20:40:10

steven026 发表于 2022-7-1 16:59
```
//劫持前fetch
window.hook_fetch=window.fetch


你这个没有起到修改response的效果啊,只是返回了一份复制品,网站读到的response还是原来的,这个劫持的目的就是让返回值发生变化。

steven026 发表于 2022-7-1 22:02:38

cxxjackie 发表于 2022-7-1 20:40
你这个没有起到修改response的效果啊,只是返回了一份复制品,网站读到的response还是原来的,这个劫持的 ...

```
//劫持前fetch
window.hook_fetch=window.fetch

//劫持后fetch
window.fetch= async function(...args){
    return await hook_fetch(...args).then((e)=>{
      let res =e.clone() //克隆response
      e.text().then((ee)=>console.log("原始response内容",ee))
      res.text=()=>{return new Promise((resolve)=>{resolve("hooked")})} //修改返回内容
      return res //返回克隆并修改的response
    })
}

//网站"原生"fetch
fetch("https://bbs.tampermonkey.net.cn/").then((e)=>{
    e.text().then((ee)=>{
      console.log("修改后response内容",ee)
      //网站"原生"操作
    })
})
```
要修改返回内容的话,我写成了这个样子,GG看下这样会有什么问题吗?
!(data/attachment/forum/202207/01/220236q2nykwnng3xyznih.png)

cxxjackie 发表于 2022-7-1 22:24:52

steven026 发表于 2022-7-1 22:02
```
//劫持前fetch
window.hook_fetch=window.fetch


这样是覆盖,不能在原数据的基础上修改,或者把e.text().then挪到Promise里面,先取得原数据再修改返回就好了,不过这样就跟这篇的思路完全一样了,而且多克隆了一个response,可能产生额外的性能开销。
页: [1] 2
查看完整版本: [油猴脚本开发指南]Fetch劫持的第二种方式