Files
reverse-skills-max/vendor-new/xbsReverseSkill/web-js-env-patcher/references/hook-templates.md

7.1 KiB
Raw Blame History

浏览器 Hook 模板

本文件提供授权调试时用于定位加密参数的最小 Hook 模板。原则:只观察、不篡改、不批量请求;命中后用调用栈回到源码确认。

使用规则

  • 只在用户授权的目标页面中使用。
  • Hook 前先记录目标 API、参数名和页面 URL。
  • Hook 代码只输出调用栈和关键值,不修改请求内容。
  • 命中后尽快移除 Hook,避免影响目标页面逻辑。
  • 不用 Hook 绕过登录、验证码、MFA 或风控。

fetch

(() => {
  const targetParam = "sign";
  const apiKeyword = "/api/";
  const rawFetch = window.fetch;
  window.fetch = function patchedFetch(input, init) {
    const url = typeof input === "string" ? input : (input && input.url) || "";
    const body = init && init.body;
    if (url.includes(apiKeyword) || String(url).includes(targetParam) || String(body || "").includes(targetParam)) {
      console.group("[fetch 命中]", url);
      console.log("input =", input);
      console.log("init =", init);
      console.trace("fetch 调用栈");
      console.groupEnd();
      debugger;
    }
    return rawFetch.apply(this, arguments);
  };
})();

XMLHttpRequest

(() => {
  const targetParam = "sign";
  const apiKeyword = "/api/";
  const rawOpen = XMLHttpRequest.prototype.open;
  const rawSend = XMLHttpRequest.prototype.send;
  const rawSetHeader = XMLHttpRequest.prototype.setRequestHeader;

  XMLHttpRequest.prototype.open = function patchedOpen(method, url) {
    this.__reverse_method = method;
    this.__reverse_url = String(url || "");
    return rawOpen.apply(this, arguments);
  };

  XMLHttpRequest.prototype.setRequestHeader = function patchedSetRequestHeader(name, value) {
    if (String(name).includes(targetParam) || String(value).includes(targetParam)) {
      console.group("[XHR Header 命中]", this.__reverse_url);
      console.log(name, value);
      console.trace("setRequestHeader 调用栈");
      console.groupEnd();
      debugger;
    }
    return rawSetHeader.apply(this, arguments);
  };

  XMLHttpRequest.prototype.send = function patchedSend(body) {
    const url = this.__reverse_url || "";
    if (url.includes(apiKeyword) || url.includes(targetParam) || String(body || "").includes(targetParam)) {
      console.group("[XHR send 命中]", this.__reverse_method, url);
      console.log("body =", body);
      console.trace("send 调用栈");
      console.groupEnd();
      debugger;
    }
    return rawSend.apply(this, arguments);
  };
})();

URLSearchParams / FormData

(() => {
  const targetParam = "sign";
  for (const Ctor of [URLSearchParams, FormData].filter(Boolean)) {
    for (const method of ["append", "set"]) {
      const raw = Ctor.prototype[method];
      if (typeof raw !== "function") continue;
      Ctor.prototype[method] = function patchedParamWrite(name, value) {
        if (String(name) === targetParam || String(value).includes(targetParam)) {
          console.group(`[${Ctor.name}.${method} 命中]`, name);
          console.log("value =", value);
          console.trace("参数写入调用栈");
          console.groupEnd();
          debugger;
        }
        return raw.apply(this, arguments);
      };
    }
  }
})();
(() => {
  const targetParam = "sign";
  const proto = Document.prototype;
  const desc = Object.getOwnPropertyDescriptor(proto, "cookie");
  if (!desc || !desc.set || !desc.get) return;
  Object.defineProperty(document, "cookie", {
    configurable: true,
    enumerable: desc.enumerable,
    get() {
      const value = desc.get.call(document);
      if (String(value).includes(targetParam)) {
        console.trace("document.cookie 读取命中");
      }
      return value;
    },
    set(value) {
      if (String(value).includes(targetParam)) {
        console.group("[cookie 写入命中]");
        console.log(value);
        console.trace("cookie 写入调用栈");
        console.groupEnd();
        debugger;
      }
      return desc.set.call(document, value);
    }
  });
})();

localStorage / sessionStorage

(() => {
  const keys = ["token", "sign", "device", "fingerprint"];
  for (const storageName of ["localStorage", "sessionStorage"]) {
    const storage = window[storageName];
    if (!storage) continue;
    for (const method of ["getItem", "setItem", "removeItem"]) {
      const raw = storage[method];
      storage[method] = function patchedStorage(key, value) {
        if (keys.some(k => String(key).includes(k))) {
          console.group(`[${storageName}.${method} 命中]`, key);
          console.log("value =", value);
          console.trace("Storage 调用栈");
          console.groupEnd();
        }
        return raw.apply(this, arguments);
      };
    }
  }
})();

时间、随机数和动态代码

(() => {
  for (const [owner, name] of [[Date, "now"], [Math, "random"]]) {
    const raw = owner[name];
    owner[name] = function patchedTimeRandom() {
      const ret = raw.apply(this, arguments);
      console.log(`[${name}]`, ret);
      console.trace(`${name} 调用栈`);
      return ret;
    };
  }

  const rawGetRandomValues = crypto && crypto.getRandomValues;
  if (rawGetRandomValues) {
    crypto.getRandomValues = function patchedGetRandomValues(arr) {
      const ret = rawGetRandomValues.apply(this, arguments);
      console.log("[crypto.getRandomValues]", Array.from(arr));
      console.trace("crypto 随机数调用栈");
      return ret;
    };
  }

  const rawFunction = Function;
  window.Function = new Proxy(rawFunction, {
    construct(target, args) {
      console.log("[new Function]", args);
      console.trace("动态函数调用栈");
      debugger;
      return Reflect.construct(target, args);
    },
    apply(target, thisArg, args) {
      console.log("[Function]", args);
      console.trace("动态函数调用栈");
      debugger;
      return Reflect.apply(target, thisArg, args);
    }
  });
})();

WebSocket、事件和 postMessage

(() => {
  const rawWS = window.WebSocket;
  if (rawWS) {
    window.WebSocket = new Proxy(rawWS, {
      construct(target, args) {
        console.log("[WebSocket 创建]", args);
        console.trace("WebSocket 调用栈");
        return Reflect.construct(target, args);
      }
    });
  }

  const rawPostMessage = window.postMessage;
  if (rawPostMessage) {
    window.postMessage = function patchedPostMessage(message, targetOrigin, transfer) {
      console.log("[postMessage]", message, targetOrigin);
      console.trace("postMessage 调用栈");
      return rawPostMessage.apply(this, arguments);
    };
  }
})();

证据输出要求

命中 Hook 后至少记录:

  • 命中的 API 或参数名。
  • 调用栈截图或文本。
  • 所在 JS 文件、行列号、chunk 名。
  • 写入前后请求对象。
  • 是否可复现。
  • 是否需要 sourcemap 或动态 chunk。