|
1 | 1 | // ==UserScript==
|
2 | 2 | // @name jQuery Hook
|
3 |
| -// @namespace https://github.com/CC11001100/jQuery-hook |
4 |
| -// @version 0.3 |
| 3 | +// @namespace https://github.com/JSREI/jQuery-hook |
| 4 | +// @version 0.4 |
5 | 5 | // @description 用于快速定位使用jQuery绑定到DOM元素上的事件的代码的真实位置,辅助逆向分析。
|
6 |
| -// @document https://github.com/CC11001100/jQuery-hook |
| 6 | +// @document https://github.com/JSREI/jQuery-hook |
7 | 7 | // @author CC11001100
|
8 | 8 | // @match *://*/*
|
9 | 9 | // @run-at document-start
|
10 | 10 | // @grant none
|
| 11 | +// @require file://D:\workspace\jQuery-hook\jQuery-hook.js |
11 | 12 | // ==/UserScript==
|
12 | 13 | (() => {
|
13 | 14 |
|
14 |
| - // 尽量唯一有区分度即可 |
| 15 | + // 尽量唯一有区分度即可,您可自定义为自己的ID |
15 | 16 | const globalUniqPrefix = "cc11001100";
|
16 | 17 |
|
17 |
| - // 在第一次设置jquery的时候添加Hook,jQuery初始化的时候会添加一个名为$的全局变量 |
| 18 | + // 用于控制打印在控制台的消息的大小 |
| 19 | + const consoleLogFontSize = 12; |
| 20 | + |
| 21 | + // 在第一次设置jquery的时候添加Hook,jQuery初始化的时候会添加一个名为$的全局变量,在添加这个变量的时候对其动一些手脚 |
18 | 22 | Object.defineProperty(window, "$", {
|
19 | 23 | set: $ => {
|
20 | 24 |
|
21 | 25 | // 为jquery的各种方法添加Hook
|
22 | 26 | try {
|
23 | 27 | addHook($);
|
24 | 28 | } catch (e) {
|
25 |
| - console.error("为jQuery添加Hook时报错: " + e) |
| 29 | + const valueStyle = `color: black; background: #E50000; font-size: ${consoleLogFontSize}px; font-weight: bold;`; |
| 30 | + const normalStyle = `color: black; background: #FF6766; font-size: ${consoleLogFontSize}px;`; |
| 31 | + |
| 32 | + const message = [ |
| 33 | + |
| 34 | + normalStyle, now(), |
| 35 | + |
| 36 | + normalStyle, "jQuery Monitor: ", |
| 37 | + |
| 38 | + normalStyle, "add hook error, msg = ", |
| 39 | + |
| 40 | + valueStyle, `${e}`,]; |
| 41 | + console.log(genFormatArray(message), ...message); |
26 | 42 | }
|
27 | 43 |
|
28 |
| - // 删除set描述符拦截,恢复正常赋值 |
| 44 | + // 删除set描述符拦截,恢复正常赋值,假装啥都没发生过... |
29 | 45 | delete window["$"];
|
30 | 46 | window["$"] = $;
|
31 |
| - }, |
32 |
| - configurable: true |
| 47 | + }, configurable: true |
33 | 48 | });
|
34 | 49 |
|
35 | 50 | /**
|
|
38 | 53 | */
|
39 | 54 | function addHook($) {
|
40 | 55 |
|
| 56 | + addEventHook($); |
| 57 | + |
| 58 | + addAjaxHook($); |
| 59 | + |
| 60 | + const valueStyle = `color: black; background: #669934; font-size: ${consoleLogFontSize}px; font-weight: bold;`; |
| 61 | + const normalStyle = `color: black; background: #65CC66; font-size: ${consoleLogFontSize}px;`; |
| 62 | + |
| 63 | + const message = [ |
| 64 | + |
| 65 | + normalStyle, now(), |
| 66 | + |
| 67 | + normalStyle, "jQuery Monitor: ", |
| 68 | + |
| 69 | + normalStyle, "设置jQuery Hook成功!",]; |
| 70 | + console.log(genFormatArray(message), ...message); |
| 71 | + } |
| 72 | + |
| 73 | + /** |
| 74 | + * 增加Ajax Hook |
| 75 | + * |
| 76 | + * @param $ |
| 77 | + */ |
| 78 | + function addAjaxHook($) { |
| 79 | + if (!$["ajaxSetup"]) { |
| 80 | + const valueStyle = `color: black; background: #E50000; font-size: ${consoleLogFontSize}px; font-weight: bold;`; |
| 81 | + const normalStyle = `color: black; background: #FF6766; font-size: ${consoleLogFontSize}px;`; |
| 82 | + |
| 83 | + const message = [ |
| 84 | + |
| 85 | + normalStyle, now(), |
| 86 | + |
| 87 | + normalStyle, "jQuery Monitor: ", |
| 88 | + |
| 89 | + normalStyle, "$不是jQuery对象,没有 ajaxSetup 属性,因此不添加Ajax Hook",]; |
| 90 | + console.log(genFormatArray(message), ...message); |
| 91 | + return; |
| 92 | + } |
| 93 | + const oldAjaxSetUp = $.ajaxSetup; |
| 94 | + $.ajaxSetup = function () { |
| 95 | + try { |
| 96 | + if (arguments.length === 1) { |
| 97 | + const valueStyle = `color: black; background: #669934; font-size: ${consoleLogFontSize}px; font-weight: bold;`; |
| 98 | + const normalStyle = `color: black; background: #65CC66; font-size: ${consoleLogFontSize}px;`; |
| 99 | + |
| 100 | + const message = [ |
| 101 | + |
| 102 | + normalStyle, now(), |
| 103 | + |
| 104 | + normalStyle, "jQuery Monitor: ", |
| 105 | + |
| 106 | + normalStyle, "检测到ajaxSetup全局拦截器设置请求参数", |
| 107 | + |
| 108 | + normalStyle, `, code location = ${getCodeLocation("$.ajaxSetup")}`]; |
| 109 | + console.log(genFormatArray(message), ...message); |
| 110 | + console.log(arguments); |
| 111 | + } |
| 112 | + } catch (e) { |
| 113 | + console.error(e); |
| 114 | + } |
| 115 | + return oldAjaxSetUp.apply(this, arguments); |
| 116 | + } |
| 117 | + } |
| 118 | + |
| 119 | + /** |
| 120 | + * 增加事件Hook |
| 121 | + * |
| 122 | + * @param $ |
| 123 | + */ |
| 124 | + function addEventHook($) { |
41 | 125 | if (!$["fn"]) {
|
42 |
| - console.log("当前页面虽然声明了$变量,但并不是jQuery,因此忽略。"); |
| 126 | + const valueStyle = `color: black; background: #E50000; font-size: ${consoleLogFontSize}px; font-weight: bold;`; |
| 127 | + const normalStyle = `color: black; background: #FF6766; font-size: ${consoleLogFontSize}px;`; |
| 128 | + |
| 129 | + const message = [ |
| 130 | + |
| 131 | + normalStyle, now(), |
| 132 | + |
| 133 | + normalStyle, "jQuery Monitor: ", |
| 134 | + |
| 135 | + normalStyle, "$不是jQuery对象,没有 fn 属性,因此不添加 Event Hook",]; |
| 136 | + console.log(genFormatArray(message), ...message); |
43 | 137 | return;
|
44 | 138 | }
|
45 | 139 |
|
46 | 140 | // 一些比较通用的事件的拦截
|
47 |
| - const eventNameList = [ |
48 |
| - "click", "dblclick", "blur", "change", "contextmenu", "error", "focus", |
49 |
| - "focusin", "focusout", "hover", "holdReady", "proxy", "ready", "keydown", "keypress", |
50 |
| - "keyup", "live", "load", "mousedown", "mouseenter", "mouseleave", "mousemove", "mouseout", |
51 |
| - "mouseover", "mouseup" |
52 |
| - ]; |
| 141 | + 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"]; |
53 | 142 | for (let eventName of eventNameList) {
|
54 | 143 | const old = $.fn[eventName];
|
55 | 144 | $.fn[eventName] = function () {
|
56 | 145 | try {
|
57 | 146 | setEventFunctionNameToDomObjectAttribute(this, eventName, arguments[0]);
|
58 | 147 | } catch (e) {
|
59 |
| - console.error(`为jQuery添加${eventName}类型的事件的Hook时发生错误: ${e}`); |
| 148 | + const valueStyle = `color: black; background: #E50000; font-size: ${consoleLogFontSize}px; font-weight: bold;`; |
| 149 | + const normalStyle = `color: black; background: #FF6766; font-size: ${consoleLogFontSize}px;`; |
| 150 | + |
| 151 | + const message = [ |
| 152 | + |
| 153 | + normalStyle, now(), |
| 154 | + |
| 155 | + normalStyle, "jQuery Monitor: ", |
| 156 | + |
| 157 | + normalStyle, `为jQuery添加${eventName}类型的事件的Hook时发生错误: ${e}`,]; |
| 158 | + console.log(genFormatArray(message), ...message); |
60 | 159 | }
|
61 | 160 | return old.apply(this, arguments);
|
62 | 161 | }
|
|
79 | 178 | setEventFunctionNameToDomObjectAttribute(this, eventName, eventFunction);
|
80 | 179 | }
|
81 | 180 | } catch (e) {
|
82 |
| - console.error(`为jQuery添加on方法的Hook时发生错误: ${e}`); |
| 181 | + const valueStyle = `color: black; background: #E50000; font-size: ${consoleLogFontSize}px; font-weight: bold;`; |
| 182 | + const normalStyle = `color: black; background: #FF6766; font-size: ${consoleLogFontSize}px;`; |
| 183 | + |
| 184 | + const message = [ |
| 185 | + |
| 186 | + normalStyle, now(), |
| 187 | + |
| 188 | + normalStyle, "jQuery Monitor: ", |
| 189 | + |
| 190 | + normalStyle, `为jQuery添加on方法的Hook时发生错误: ${e}`,]; |
| 191 | + console.log(genFormatArray(message), ...message); |
83 | 192 | }
|
84 | 193 | return fnOnHolder.apply(this, arguments);
|
85 | 194 | }
|
86 | 195 |
|
87 | 196 | // TODO 还有delegate之类的比较隐晦的绑定事件的方式
|
88 | 197 |
|
89 |
| - console.log(`当前页面使用了jQuery,jQuery Hook已初始化完毕。`); |
90 | 198 | }
|
91 | 199 |
|
92 |
| - const addressIdGeneratorMap = {}; |
93 |
| - |
94 |
| - /** |
95 |
| - * 生成一个全局唯一的标识 |
96 |
| - * @param eventName |
97 |
| - */ |
98 |
| - function globalUnique(eventName) { |
99 |
| - const id = (addressIdGeneratorMap[eventName] || 0) + 1; |
100 |
| - addressIdGeneratorMap[eventName] = id; |
101 |
| - return `${globalUniqPrefix}_${eventName}_${id}`; |
102 |
| - } |
103 | 200 |
|
104 | 201 | /**
|
105 | 202 | * 为绑定了jquery事件的dom元素添加元素,提示所绑定的事件与对应的函数代码的全局变量的名称,只需要复制粘贴跟进去即可
|
|
110 | 207 | * @param eventFunction
|
111 | 208 | */
|
112 | 209 | function setEventFunctionNameToDomObjectAttribute(domObject, eventName, eventFunction) {
|
113 |
| - // TODO bug fix 注意,事件名可能会包含一些非法的字符 |
114 |
| - // cc11001100-jquery-$destroy-event-function |
115 |
| - eventName = safeSymbol(eventName); |
116 |
| - const eventFunctionGlobalName = globalUnique(eventName); |
117 |
| - window[eventFunctionGlobalName] = eventFunction; |
118 |
| - const attrName = `${globalUniqPrefix}-jQuery-${eventName}-event-function`; |
| 210 | + const {formatEventName, eventFuncGlobalName} = storeToWindow(eventName, eventFunction); |
| 211 | + const attrName = `${globalUniqPrefix}-jQuery-${formatEventName}-event-function`; |
119 | 212 | if (domObject.attr(attrName)) {
|
120 |
| - domObject.attr(attrName + "-" + new Date().getTime(), eventFunctionGlobalName); |
| 213 | + domObject.attr(attrName + "-" + new Date().getTime(), eventFuncGlobalName); |
121 | 214 | } else {
|
122 |
| - domObject.attr(attrName, eventFunctionGlobalName); |
| 215 | + domObject.attr(attrName, eventFuncGlobalName); |
123 | 216 | }
|
124 | 217 | }
|
125 | 218 |
|
| 219 | + // ----------------------------------------------- ----------------------------------------------------------------- |
| 220 | + |
| 221 | + // 用于缓存事件函数到全局变量的映射关系 |
| 222 | + // <事件函数, 全局变量> |
| 223 | + const eventFuncCacheMap = new Map(); |
| 224 | + |
| 225 | + /** |
| 226 | + * 为事件的函数绑定一个全局变量,如果之前已经绑定过了则返回之前的 |
| 227 | + * |
| 228 | + * @param eventName {string} |
| 229 | + * @param eventFunc {Function} |
| 230 | + * @return {{string, string}} 事件名和其对应的函数绑定到的全局变量 |
| 231 | + */ |
| 232 | + function storeToWindow(eventName, eventFunc) { |
| 233 | + if (eventFunc in eventFuncCacheMap) { |
| 234 | + return eventFuncCacheMap[eventFunc]; |
| 235 | + } |
| 236 | + // 注意,事件名可能会包含一些非法的字符,所以需要转义 |
| 237 | + // cc11001100-jquery-$destroy-event-function |
| 238 | + const formatEventName = safeSymbol(eventName); |
| 239 | + const eventFuncGlobalName = globalUnique(formatEventName); |
| 240 | + window[eventFuncGlobalName] = eventFunc; |
| 241 | + eventFuncCacheMap[eventFunc] = eventFuncGlobalName; |
| 242 | + return { |
| 243 | + formatEventName, eventFuncGlobalName, |
| 244 | + }; |
| 245 | + } |
| 246 | + |
126 | 247 | /***
|
| 248 | + * 将事件名称转为合法的变量名称 |
127 | 249 | *
|
128 | 250 | * @param name
|
129 | 251 | */
|
130 | 252 | function safeSymbol(name) {
|
131 | 253 | const replaceMap = {
|
132 |
| - ".": "_dot_", |
133 |
| - "$": "_dollar_", |
134 |
| - "-": "_dash_" |
| 254 | + ".": "_dot_", "$": "_dollar_", "-": "_dash_" |
135 | 255 | };
|
136 |
| - for (let key of Object.getOwnPropertyNames(replaceMap)) { |
137 |
| - name = name.replace(key, replaceMap[key]); |
| 256 | + let newName = ""; |
| 257 | + for (let c of name) { |
| 258 | + if (c in replaceMap) { |
| 259 | + newName += replaceMap[c]; |
| 260 | + } else if (isOkVarChar(c)) { |
| 261 | + newName += c; |
| 262 | + } |
| 263 | + } |
| 264 | + return newName; |
| 265 | + } |
| 266 | + |
| 267 | + /** |
| 268 | + * 判断字符是否是合法的变量名字符 |
| 269 | + * |
| 270 | + * @param c {string} |
| 271 | + * @returns {boolean} |
| 272 | + */ |
| 273 | + function isOkVarChar(c) { |
| 274 | + if (c >= 'a' && c <= 'z') { |
| 275 | + return true; |
| 276 | + } |
| 277 | + if (c >= 'A' && c <= 'Z') { |
| 278 | + return true; |
138 | 279 | }
|
139 |
| - return name; |
| 280 | + if (c >= '0' && c <= '9') { |
| 281 | + return true; |
| 282 | + } |
| 283 | + return false; |
| 284 | + } |
| 285 | + |
| 286 | + // ----------------------------------------------- ----------------------------------------------------------------- |
| 287 | + |
| 288 | + // 每个事件一个独立的自增id |
| 289 | + const addressIdGeneratorMap = {}; |
| 290 | + |
| 291 | + /** |
| 292 | + * 为给定的事件生成一个全局唯一的标识,这个标识中会带上事件类型以方便区分不同事件 |
| 293 | + * |
| 294 | + * @param eventName {string} |
| 295 | + */ |
| 296 | + function globalUnique(eventName) { |
| 297 | + const id = (addressIdGeneratorMap[eventName] || 0) + 1; |
| 298 | + addressIdGeneratorMap[eventName] = id; |
| 299 | + return `${globalUniqPrefix}__${eventName}__${id}`; |
| 300 | + } |
| 301 | + |
| 302 | + // ----------------------------------------------- ----------------------------------------------------------------- |
| 303 | + |
| 304 | + function now() { |
| 305 | + // 东八区专属... |
| 306 | + return "[" + new Date(new Date().getTime() + 1000 * 60 * 60 * 8).toJSON().replace("T", " ").replace("Z", "") + "] "; |
| 307 | + } |
| 308 | + |
| 309 | + function genFormatArray(messageAndStyleArray) { |
| 310 | + const formatArray = []; |
| 311 | + for (let i = 0, end = messageAndStyleArray.length / 2; i < end; i++) { |
| 312 | + formatArray.push("%c%s"); |
| 313 | + } |
| 314 | + return formatArray.join(""); |
| 315 | + } |
| 316 | + |
| 317 | + // ----------------------------------------------- ----------------------------------------------------------------- |
| 318 | + |
| 319 | + /** |
| 320 | + * 解析当前代码的位置,以便能够直接定位到事件触发的代码位置 |
| 321 | + * |
| 322 | + * @param keyword {string} |
| 323 | + * @returns {string} |
| 324 | + */ |
| 325 | + function getCodeLocation(keyword = "cc11001100") { |
| 326 | + const callstack = new Error().stack.split("\n"); |
| 327 | + while (callstack.length && callstack[0].indexOf(keyword) === -1) { |
| 328 | + callstack.shift(); |
| 329 | + } |
| 330 | + callstack.shift(); |
| 331 | + // callstack.shift(); |
| 332 | + |
| 333 | + return callstack[0].trim(); |
140 | 334 | }
|
141 | 335 |
|
142 | 336 | })();
|
|
0 commit comments