Skip to content

Commit f0ebf15

Browse files
committed
支持ajax设置全局拦截器的检测
1 parent c1b1707 commit f0ebf15

File tree

2 files changed

+242
-46
lines changed

2 files changed

+242
-46
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ cc11001100_click_5
6161

6262
# 六、问题反馈
6363

64-
如果发现有Hook不到的情况,请在issue中反馈。
65-
64+
如果发现有Hook不到的情况,请在[issue](https://github.com/CC11001100/jQuery-hook/issues)中反馈。
6665

66+
# 七、TODO
67+
1. 对jQuery发出的请求进行监控和Hook
68+
如果希望有其它功能在[issue](https://github.com/CC11001100/jQuery-hook/issues)区留言讨论

jQuery-hook.js

Lines changed: 238 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,50 @@
11
// ==UserScript==
22
// @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
55
// @description 用于快速定位使用jQuery绑定到DOM元素上的事件的代码的真实位置,辅助逆向分析。
6-
// @document https://github.com/CC11001100/jQuery-hook
6+
// @document https://github.com/JSREI/jQuery-hook
77
// @author CC11001100
88
// @match *://*/*
99
// @run-at document-start
1010
// @grant none
11+
// @require file://D:\workspace\jQuery-hook\jQuery-hook.js
1112
// ==/UserScript==
1213
(() => {
1314

14-
// 尽量唯一有区分度即可
15+
// 尽量唯一有区分度即可,您可自定义为自己的ID
1516
const globalUniqPrefix = "cc11001100";
1617

17-
// 在第一次设置jquery的时候添加Hook,jQuery初始化的时候会添加一个名为$的全局变量
18+
// 用于控制打印在控制台的消息的大小
19+
const consoleLogFontSize = 12;
20+
21+
// 在第一次设置jquery的时候添加Hook,jQuery初始化的时候会添加一个名为$的全局变量,在添加这个变量的时候对其动一些手脚
1822
Object.defineProperty(window, "$", {
1923
set: $ => {
2024

2125
// 为jquery的各种方法添加Hook
2226
try {
2327
addHook($);
2428
} 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);
2642
}
2743

28-
// 删除set描述符拦截,恢复正常赋值
44+
// 删除set描述符拦截,恢复正常赋值,假装啥都没发生过...
2945
delete window["$"];
3046
window["$"] = $;
31-
},
32-
configurable: true
47+
}, configurable: true
3348
});
3449

3550
/**
@@ -38,25 +53,109 @@
3853
*/
3954
function addHook($) {
4055

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($) {
41125
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);
43137
return;
44138
}
45139

46140
// 一些比较通用的事件的拦截
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"];
53142
for (let eventName of eventNameList) {
54143
const old = $.fn[eventName];
55144
$.fn[eventName] = function () {
56145
try {
57146
setEventFunctionNameToDomObjectAttribute(this, eventName, arguments[0]);
58147
} 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);
60159
}
61160
return old.apply(this, arguments);
62161
}
@@ -79,27 +178,25 @@
79178
setEventFunctionNameToDomObjectAttribute(this, eventName, eventFunction);
80179
}
81180
} 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);
83192
}
84193
return fnOnHolder.apply(this, arguments);
85194
}
86195

87196
// TODO 还有delegate之类的比较隐晦的绑定事件的方式
88197

89-
console.log(`当前页面使用了jQuery,jQuery Hook已初始化完毕。`);
90198
}
91199

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-
}
103200

104201
/**
105202
* 为绑定了jquery事件的dom元素添加元素,提示所绑定的事件与对应的函数代码的全局变量的名称,只需要复制粘贴跟进去即可
@@ -110,33 +207,130 @@
110207
* @param eventFunction
111208
*/
112209
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`;
119212
if (domObject.attr(attrName)) {
120-
domObject.attr(attrName + "-" + new Date().getTime(), eventFunctionGlobalName);
213+
domObject.attr(attrName + "-" + new Date().getTime(), eventFuncGlobalName);
121214
} else {
122-
domObject.attr(attrName, eventFunctionGlobalName);
215+
domObject.attr(attrName, eventFuncGlobalName);
123216
}
124217
}
125218

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+
126247
/***
248+
* 将事件名称转为合法的变量名称
127249
*
128250
* @param name
129251
*/
130252
function safeSymbol(name) {
131253
const replaceMap = {
132-
".": "_dot_",
133-
"$": "_dollar_",
134-
"-": "_dash_"
254+
".": "_dot_", "$": "_dollar_", "-": "_dash_"
135255
};
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;
138279
}
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();
140334
}
141335

142336
})();

0 commit comments

Comments
 (0)