您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
用于快速定位使用jQuery绑定到DOM元素上的事件的代码的真实位置,辅助逆向分析。
当前为
- // ==UserScript==
- // @name jQuery Hook
- // @namespace https://github.com/JSREI/jQuery-hook
- // @version 0.4
- // @description 用于快速定位使用jQuery绑定到DOM元素上的事件的代码的真实位置,辅助逆向分析。
- // @document https://github.com/JSREI/jQuery-hook
- // @author CC11001100
- // @match *://*/*
- // @run-at document-start
- // @grant none
- // ==/UserScript==
- (() => {
- // 可自定义的一个变量前缀,尽量唯一有区分度即可,可以替换为为自己的ID
- const globalUniqPrefix = "cc11001100";
- // 用于控制打印在控制台的消息的大小
- const consoleLogFontSize = 12;
- // ----------------------------------------------- -----------------------------------------------------------------
- /**
- * 用于统一构建待颜色的日志输出,采用构建者模式
- *
- * from: https://github.com/JSREI/js-color-log
- */
- class ColorLogBuilder {
- /**
- * 创建一条日志,调用show()方法将其打印到控制台
- *
- * 因为认为字体颜色是没有区分度的,所以这里就不支持指定字体的颜色,字体恒定为黑色
- *
- * @param normalTextBackgroundColor {string} 此条日志中普通文本的背景色
- * @param highlightTextBackgroundColor {string} 此条日志中要高亮的文本的背景色
- * @param _consoleLogFontSize {string} 日志的大小
- */
- constructor(normalTextBackgroundColor = "#FFFFFF", highlightTextBackgroundColor = "#FFFFFF", _consoleLogFontSize = consoleLogFontSize) {
- this.normalTextBackgroundColor = normalTextBackgroundColor;
- this.highlightTextBackgroundColor = highlightTextBackgroundColor;
- this.consoleLogFontSize = _consoleLogFontSize;
- this.messageArray = [];
- // 每天日志都使用统一的前缀,在创建的时候就设置好
- // 先是一个日期,然后是插件的名字,以便与其它工具的输出相区分
- // 此处的统一前缀自行修改,因为使用的时候都是拷贝过去的
- this.append(`[${this.nowTimeString()}] `).append("jQuery Hook: ");
- }
- /**
- * 往日志中追加普通类型的信息
- *
- * @param msg {string}
- * @return {ColorLogBuilder}
- */
- append(msg) {
- this.appendNormal(msg);
- return this;
- }
- /**
- * 往日志中追加普通类型的信息
- *
- * @param msg {string}
- * @return {ColorLogBuilder}
- */
- appendNormal(msg) {
- this.messageArray.push(`color: black; background: ${this.normalTextBackgroundColor}; font-size: ${this.consoleLogFontSize}px;`);
- this.messageArray.push(msg);
- return this;
- }
- /**
- * 往日志中追加高亮的内容
- *
- * @param msg {string}
- */
- appendHighlight(msg) {
- this.messageArray.push(`color: black; background: ${this.highlightTextBackgroundColor}; font-size: ${this.consoleLogFontSize}px; font-weight: bold;`);
- this.messageArray.push(msg);
- return this;
- }
- /**
- * 把当前这条日志打印出来
- */
- show() {
- console.log(this.genFormatArray(this.messageArray), ...this.messageArray);
- }
- nowTimeString(fmt = "yyyy-MM-dd HH:mm:ss") {
- const now = new Date();
- let o = {
- "M+": now.getMonth() + 1, "d+": now.getDate(), //日
- "H+": now.getHours(), //小时
- "m+": now.getMinutes(), //分
- "s+": now.getSeconds(), //秒
- "q+": Math.floor((now.getMonth() + 3) / 3), //季度
- "S": now.getMilliseconds() //毫秒
- };
- if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (now.getFullYear() + "").substr(4 - RegExp.$1.length));
- for (let k in o) if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
- return fmt;
- }
- genFormatArray(messageAndStyleArray) {
- const formatArray = [];
- for (let i = 0, end = messageAndStyleArray.length / 2; i < end; i++) {
- formatArray.push("%c%s");
- }
- return formatArray.join("");
- }
- }
- // ----------------------------------------------- -----------------------------------------------------------------
- // 在第一次初始化jQuery的时候添加Hook,jQuery初始化的时候会添加一个名为$的全局变量,在添加这个变量的时候对其动一些手脚
- Object.defineProperty(window, "$", {
- set: $ => {
- // 为jquery的各种方法添加Hook
- try {
- addHook($);
- } catch (e) {
- new ColorLogBuilder("#FF6766", "#E50000")
- .append("add hook error, msg = ")
- .appendHighlight(e)
- .show();
- }
- // 删除set描述符拦截,恢复正常赋值,假装啥都没发生过,但实际上已经狸猫换太子了...
- delete window["$"];
- window["$"] = $;
- }, configurable: true
- });
- /**
- * 为jquery添加一些hook,等会儿使用jquery为dom元素绑定事件的话就会被捕获到
- * @param $
- */
- function addHook($) {
- addEventHook($);
- addAjaxHook($);
- new ColorLogBuilder("#65CC66", "#669934")
- .append("在当前页面上检测到jQuery的加载,添加jQuery Hook完成")
- .show();
- }
- /**
- * 增加Ajax Hook
- *
- * @param $
- */
- function addAjaxHook($) {
- if (!$["ajaxSetup"]) {
- new ColorLogBuilder("#FF6766", "#E50000")
- .appendHighlight("$不是jQuery对象,没有 ajaxSetup 属性,因此不添加Ajax Hook")
- .show();
- return;
- }
- const oldAjaxSetUp = $.ajaxSetup;
- $.ajaxSetup = function () {
- try {
- if (arguments.length === 1) {
- const {formatEventName, eventFuncGlobalName} = storeToWindow("ajaxSetup", arguments[0]);
- new ColorLogBuilder("#65CC66", "#669934")
- .append("检测到ajaxSetup全局拦截器设置请求参数,已经挂载到全局变量:")
- .appendHighlight(eventFuncGlobalName)
- .show();
- }
- } catch (e) {
- console.error(e);
- }
- return oldAjaxSetUp.apply(this, arguments);
- }
- }
- /**
- * 增加事件Hook
- *
- * @param $
- */
- function addEventHook($) {
- if (!$["fn"]) {
- new ColorLogBuilder("#FF6766", "#E50000")
- .appendHighlight("$不是jQuery对象,没有 fn 属性,因此不添加 Event Hook")
- .show();
- return;
- }
- // 一些比较通用的事件的拦截
- const eventNameList = ["click", "dblclick", "blur", "change", "contextmenu", "error", "focus", "focusin", "focusout", "hover", "holdReady", "proxy", "ready", "keydown", "keypress", "keyup", "live", "load", "mousedown", "mouseenter", "mouseleave", "mousemove", "mouseout", "mouseover", "mouseup"];
- for (let eventName of eventNameList) {
- const old = $.fn[eventName];
- $.fn[eventName] = function () {
- try {
- setEventFunctionNameToDomObjectAttribute(this, eventName, arguments[0]);
- } catch (e) {
- new ColorLogBuilder("#FF6766", "#E50000")
- .appendHighlight(`为jQuery添加${eventName}类型的事件的Hook时发生错误: ${e}`)
- .show();
- }
- return old.apply(this, arguments);
- }
- }
- // on,不仅是内置事件类型,还有可能有一些自定义的事件类型
- // https://api.jquery.com/on/
- const fnOnHolder = $.fn.on;
- $.fn.on = function () {
- try {
- const eventName = arguments[0];
- let eventFunction = undefined;
- for (let x of arguments) {
- if (x instanceof Function) {
- eventFunction = x;
- break;
- }
- }
- if (eventFunction instanceof Function) {
- setEventFunctionNameToDomObjectAttribute(this, eventName, eventFunction);
- }
- } catch (e) {
- new ColorLogBuilder("#FF6766", "#E50000")
- .appendHighlight(`为jQuery添加on方法的Hook时发生错误: ${e}`)
- .show();
- }
- return fnOnHolder.apply(this, arguments);
- }
- // TODO 还有delegate之类的比较隐晦的绑定事件的方式
- }
- /**
- * 为绑定了jquery事件的dom元素添加元素,提示所绑定的事件与对应的函数代码的全局变量的名称,只需要复制粘贴跟进去即可
- * 注意,有可能会为同一个元素重复绑定相同的事件
- *
- * @param domObject
- * @param eventName
- * @param eventFunction
- */
- function setEventFunctionNameToDomObjectAttribute(domObject, eventName, eventFunction) {
- eventName.split(' ').map((eventName) => {
- const {formatEventName, eventFuncGlobalName} = storeToWindow(eventName, eventFunction);
- const attrName = `${globalUniqPrefix}-jQuery-${formatEventName}-event-function`;
- if (domObject.attr(attrName)) {
- domObject.attr(attrName + "-" + new Date().getTime(), eventFuncGlobalName);
- } else {
- domObject.attr(attrName, eventFuncGlobalName);
- }
- })
- }
- // ----------------------------------------------- -----------------------------------------------------------------
- // 用于缓存事件函数到全局变量的映射关系
- // <事件函数, 全局变量>
- const eventFuncCacheMap = new Map();
- /**
- * 为事件的函数绑定一个全局变量,如果之前已经绑定过了则返回之前的
- *
- * @param eventName {string}
- * @param eventFunc {Function}
- * @return {{string, string}} 事件名和其对应的函数绑定到的全局变量
- */
- function storeToWindow(eventName, eventFunc) {
- if (eventFunc in eventFuncCacheMap) {
- return eventFuncCacheMap[eventFunc];
- }
- // 注意,事件名可能会包含一些非法的字符,所以需要转义
- // cc11001100-jquery-$destroy-event-function
- const formatEventName = safeSymbol(eventName);
- const eventFuncGlobalName = globalUnique(formatEventName);
- window[eventFuncGlobalName] = eventFunc;
- eventFuncCacheMap[eventFunc] = eventFuncGlobalName;
- return {
- formatEventName, eventFuncGlobalName,
- };
- }
- /***
- * 将事件名称转为合法的变量名称
- *
- * @param name
- */
- function safeSymbol(name) {
- const replaceMap = {
- ".": "_dot_",
- "$": "_dollar_",
- "-": "_dash_",
- " ": "_whitespace_"
- };
- let newName = "";
- for (let c of name) {
- if (c in replaceMap) {
- newName += replaceMap[c];
- } else if (isOkVarChar(c)) {
- newName += c;
- }
- }
- return newName;
- }
- /**
- * 判断字符是否是合法的变量名字符
- *
- * @param c {string}
- * @returns {boolean}
- */
- function isOkVarChar(c) {
- return (/^[a-zA-Z0-9]$/).test(c);
- }
- // ----------------------------------------------- -----------------------------------------------------------------
- // 每个事件一个独立的自增id
- const addressIdGeneratorMap = {};
- /**
- * 为给定的事件生成一个全局唯一的标识,这个标识中会带上事件类型以方便区分不同事件
- *
- * @param eventName {string}
- */
- function globalUnique(eventName) {
- const id = (addressIdGeneratorMap[eventName] || 0) + 1;
- addressIdGeneratorMap[eventName] = id;
- return `${globalUniqPrefix}__${eventName}__${id}`;
- }
- // ----------------------------------------------- -----------------------------------------------------------------
- /**
- * 解析当前代码的位置,以便能够直接定位到事件触发的代码位置
- *
- * @param keyword {string}
- * @returns {string}
- */
- function getCodeLocation(keyword = "cc11001100") {
- const callstack = new Error().stack.split("\n");
- while (callstack.length && callstack[0].indexOf(keyword) === -1) {
- callstack.shift();
- }
- callstack.shift();
- // callstack.shift();
- return callstack[0].trim();
- }
- })();