JS截获动态代码:Hook eval与Function及注入方法详解

作者:霁月时间:2026-10-23 14:07:17浏览:84

本文介绍如何通过Hook JavaScript的eval和Function系统函数来截获动态执行的代码,以应对前端反爬机制。详细讲解了挂钩代码的编写细节,包括处理Function的变长参数和this指向。此外,还列举了多种注入方式,如油猴、代理工具(Fiddler、anyproxy)及Chrome DevTools Protocol,帮助开发者在页面加载早期注入监测脚本。

JS截获动态代码:Hook eval与Function及注入方法详解

本文重点讲解如何向JavaScript环境中注入eval和Function系统函数,从而截获动态生成的代码。文中提供了详细的示例代码,对学习和实际应用具有较高的参考价值。

正文

前端反爬与动态代码执行机制

当前许多网站采用了多种前端反爬策略,其核心手段通常是将包含反爬逻辑的前端JavaScript代码进行加密隐藏,并在运行时实时解密并动态执行。

动态执行JavaScript代码主要依赖eval和Function两种机制。无论网站的加密代码多么复杂,只要成功Hook这两个方法,即可获取解密后的可执行JavaScript源码。

需要注意的是,部分网站会检测eval和Function是否被修改,以判断其是否保持原生状态。因此,在实现Hook时需要采用一些技巧来规避检测。

Hook代码实现细节

挂钩代码

以下是针对eval的Hook代码示例:


(function() {
  if (window.__cr_eval) return
  window.__cr_eval = window.eval
  var myeval = function (src) {
    console.log("================ eval begin: length=" + src.length + ",caller=" + (myeval.caller && myeval.caller.name) + " ===============")
    console.log(src);
    console.log("================ eval end ================")
    return window.__cr_eval(src)
  }
  var _myeval = myeval.bind(null) // 注意:这句和下一句就是小花招本招了!
  _myeval.toString = window.__cr_eval.toString
  Object.defineProperty(window, 'eval', { value: _myeval })
  console.log(">>>>>>>>>>>>>> eval injected: " + document.location + " <<<<<<<<<<<<<<<<<<<")
})();

执行上述代码后,后续所有的eval操作都会在控制台打印出即将执行的JavaScript源码。

同理,可以编写针对Function的Hook代码:


(function() {
  if (window.__cr_fun) return
  window.__cr_fun = window.Function
  var myfun = function () {
    var args = Array.prototype.slice.call(arguments, 0, -1).join(","), src = arguments[arguments.length - 1]
    console.log("================ Function begin: args=" + args + ", length=" + src.length + ",caller=" + (myfun.caller && myfun.caller.name) + " ===============")
    console.log(src);
    console.log("================ Function end ================")
    return window.__cr_fun.apply(this, arguments)
  }
  myfun.toString = function() { return window.__cr_fun + "" } // 小花招
  Object.defineProperty(window, 'Function', { value: myfun })
  console.log(">>>>>>>>>>>>>> Function injected: " + document.location + " <<<<<<<<<<<<<<<<<<<")
})();

与eval不同,Function是一个支持变长参数的构造函数,因此在Hook时需要特别处理this指向问题。

此外,部分网站可能采用类似机制加密页面内容,并通过document.write输出动态解密后的内容。因此,同样可以Hook document.write,其方法与Hook eval类似,此处不再赘述。

代码注入方式

注入方式

另一个关键问题在于如何注入上述Hook代码。

最简单的方式是通过F12打开浏览器控制台,直接执行上述代码。但这种方式只能Hook住执行之后的eval调用。若希望从页面加载初期就注入Hook,可采用以下几种方式:

  • 油猴注入:油猴可以监听文档加载的不同状态,并在特定时刻执行JavaScript代码。具体实现请参考油猴官方手册。
  • 代理注入:通过修改服务器返回的响应数据,在<head>标签内的第一个位置插入<script>节点,确保在其他JavaScript加载执行前完成注入。Fiddler、anyproxy等工具均可编写外部规则实现此功能,具体方法见附录。
  • 使用chrome-devtools-protocol,通过Page.addScriptToEvaluateOnNewDocument方法注入外部JavaScript代码。
  • 附录

代理工具规则编写指南

对于不熟悉代理规则编写的读者,以下介绍Fiddler和anyproxy的规则编写方法:

1. 如何添加Fiddler代理规则

在Fiddler菜单中选择Rules > Customize Rules以打开脚本编辑器。

在脚本编辑器中找到OnBeforeResponse方法,并在该方法内添加以下C#代码:


if (oSession.oResponse.headers.ExistsAndContains("Content-Type", "html")){
  oSession.utilDecodeResponse(); // Remove any compression or chunking
  var b = System.Text.Encoding.UTF8.GetString(oSession.responseBodyBytes);
  var r = /<head[^>]*>/i;
  var js = "..."; // 要注入的js源码,见正文
  b = b.replace(r, "$0<script>" + js + "</script>");
  oSession.utilSetResponseBody(b); // Set the response body back
}

这样即可在所有HTML文档头部自动添加JavaScript代码。

2. 如何添加anyproxy代理规则

在anyproxy根目录下编辑一个rule.js文件,内容如下:


function injectEval() {
  if (window.__cr_eval) return
  ... // 见正文,此处略
  console.log(">>>>>>>>>>>>>> eval injected: " + document.location + " <<<<<<<<<<<<<<<<<<<")
}
module.exports = {
 summary: 'a rule to hook all eval',
 *beforeSendResponse(requestDetail, {response}) {
  if (response.header["Content-Type"].indexOf("text/html") >= 0) {
   response.body = (response.body + "").replace(/<head[^>]*>/i, `$&<script>(${injectEval})();</script>`)
   return {response}
  }
 },
};

启动anyproxy时带上规则参数。


anyproxy -r rule.js

可以看出,使用基于JavaScript的工具链具有天然优势,即注入的代码可以以源码形式与规则代码共存,从而利用IDE的语法检查、自动完成等功能,显著提高开发效率。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

作者最新文章
相关文章
更多