js-hooker

166 阅读3分钟

         该项目是对开源项目进行二开,改写的hook脚本,新增了cookie添加形式的捕获,若有侵权,请联系博主删除。

        下面贴运行截图:

                ​编辑解释:       绿色: 新增cookie

                  红色 : 删除cookie

                  黄色 : cookie更改

可以根据cookie状态跟踪目标cookie进行分析。

        const debuggerRules = [{"add|update": "epssw"}];

这里是debugger的规则,
支持以下几种格式:

            "add": true, "update": true, "delete": true, "read": true,
形式为规则:cookie名,会在触发的时候下断。

​编辑

下附代码:

// ==UserScript==
// @name         JS-Cooker
// @description  用于监控js对cookie的修改,或者在cookie符合给定条件时进入断点
// @author       allenyang
// @match       *://*/*
// @run-at      document-start
// @grant       none
// ==/UserScript==

(() => {
        const debuggerRules = [{"add|update": "epssw"}];
        const enableEventDebugger = {
            "add": true, "update": true, "delete": true, "read": true,
        }
        const consoleLogFontSize = 12;
        const ignoreUpdateButNotChanged = false;
        let realDocumentCookieProperty = null;
        const definePropertyIsMe = "allenyang-js-hooker";
        (function () {
            Object.defineProperty = new Proxy(Object.defineProperty, {
                apply: function (target, thisArg, argArray) {
                    const isMe = argArray && argArray.length >= 3 && argArray[2] && definePropertyIsMe in argArray[2];
                    const isDocumentCookie = argArray && argArray.length >= 2 && argArray[0] === document && "cookie" === argArray[1];
                    if (!isMe && isDocumentCookie) {
                        if (argArray && argArray.length >= 3) {
                            realDocumentCookieProperty = argArray[2];
                            return;
                        }
                    }
                    return target.apply(thisArg, argArray);
                }
            });

            Object.defineProperty.toString = function () {
                return "function defineProperty() { [native code] }";
            }
            Object.defineProperties = new Proxy(Object.defineProperties, {
                apply: function (target, thisArg, argArray) {
                    const isDocumentCookie = argArray && argArray.length >= 2 && document === argArray[0] && "cookie" in argArray[1];
                    if (isDocumentCookie) {
                        realDocumentCookieProperty = argArray[1]["cookie"];
                        delete argArray[1]["cookie"];
                        if (!Object.keys(argArray[1]).length) {
                            return;
                        }
                    }
                    return target.apply(thisArg, argArray);
                }
            });

            Object.defineProperties.toString = function () {
                return "function defineProperties() { [native code] }";
            }

        })();
        (function addCookieHook() {
            const handler = {
                get: () => {
                    delete document.cookie;
                    try {
                        if (realDocumentCookieProperty && "get" in realDocumentCookieProperty) {
                            return realDocumentCookieProperty["get"].apply(document, arguments);
                        } else {
                            return document.cookie;
                        }
                    } finally {
                        addCookieHook();
                    }

                }, set: newValue => {
                    allenyang_onSetCookie(newValue);
                    delete document.cookie;

                    try {
                        if (realDocumentCookieProperty && "set" in realDocumentCookieProperty) {
                            realDocumentCookieProperty["set"].apply(document, [newValue]);
                        } else {
                            document.cookie = newValue;
                        }
                    } finally {
                        addCookieHook();
                    }

                }, configurable: true, enumerable: false,
            };
            handler[definePropertyIsMe] = true;
            Object.defineProperty(document, "cookie", handler);
        })();

        function allenyang_onSetCookie(newValue) {
            const cookiePair = parseSetCookie(newValue);
            const currentCookieMap = getCurrentCookieMap();
            if (cookiePair.expires !== null && new Date().getTime() >= cookiePair.expires) {
                onDeleteCookie(newValue, cookiePair.name, cookiePair.value || (currentCookieMap.get(cookiePair.name) || {}).value);
                return;
            }
            if (currentCookieMap.has(cookiePair.name)) {
                onUpdateCookie(newValue, cookiePair.name, currentCookieMap.get(cookiePair.name).value, cookiePair.value);
                return;
            }
            onAddCookie(newValue, cookiePair.name, cookiePair.value);
        }

        function onReadCookie(cookieOriginalValue, cookieName, cookieValue) {

        }

        function onDeleteCookie(cookieOriginalValue, cookieName, cookieValue) {
            const valueStyle = `color: black; background: #E50000; font-size: ${consoleLogFontSize}px; font-weight: bold;`;
            const normalStyle = `color: black; background: #FF6766; font-size: ${consoleLogFontSize}px;`;

            const message = [

                normalStyle, now(),

                normalStyle, "JS Cookie Monitor: ",

                normalStyle, "delete cookie, cookieName = ",

                valueStyle, `${cookieName}`,

                ...(() => {
                    if (!cookieValue) {
                        return [];
                    }
                    return [normalStyle, ", value = ",

                        valueStyle, `${cookieValue}`,];
                })(),

                normalStyle, `, code location = ${getCodeLocation()}`];
            console.log(genFormatArray(message), ...message);

            testDebuggerRules(cookieOriginalValue, "delete", cookieName, cookieValue);
        }

        function onUpdateCookie(cookieOriginalValue, cookieName, oldCookieValue, newCookieValue) {

            const cookieValueChanged = oldCookieValue !== newCookieValue;

            if (ignoreUpdateButNotChanged && !cookieValueChanged) {
                return;
            }

            const valueStyle = `color: black; background: #FE9900; font-size: ${consoleLogFontSize}px; font-weight: bold;`;
            const normalStyle = `color: black; background: #FFCC00; font-size: ${consoleLogFontSize}px;`;

            const message = [

                normalStyle, now(),

                normalStyle, "JS Cookie Monitor: ",

                normalStyle, "update cookie, cookieName = ",

                valueStyle, `${cookieName}`,

                ...(() => {
                    if (cookieValueChanged) {
                        return [normalStyle, `, oldValue = `,

                            valueStyle, `${oldCookieValue}`,

                            normalStyle, `, newValue = `,

                            valueStyle, `${newCookieValue}`]
                    } else {
                        return [normalStyle, `, value = `,

                            valueStyle, `${newCookieValue}`,];
                    }
                })(),

                normalStyle, `, valueChanged = `,

                valueStyle, `${cookieValueChanged}`,

                normalStyle, `, code location = ${getCodeLocation()}`];
            console.log(genFormatArray(message), ...message);

            testDebuggerRules(cookieOriginalValue, "update", cookieName, newCookieValue, cookieValueChanged);
        }

        function onAddCookie(cookieOriginalValue, cookieName, cookieValue) {
            const valueStyle = `color: black; background: #669934; font-size: ${consoleLogFontSize}px; font-weight: bold;`;
            const normalStyle = `color: black; background: #65CC66; font-size: ${consoleLogFontSize}px;`;

            const message = [

                normalStyle, now(),

                normalStyle, "JS Cookie Monitor: ",

                normalStyle, "add cookie, cookieName = ",

                valueStyle, `${cookieName}`,

                normalStyle, ", cookieValue = ",

                valueStyle, `${cookieValue}`,

                normalStyle, `, code location = ${getCodeLocation()}`];
            console.log(genFormatArray(message), ...message);

            testDebuggerRules(cookieOriginalValue, "add", cookieName, cookieValue);
        }

        function now() {
            return "[" + new Date(new Date().getTime() + 1000 * 60 * 60 * 8).toJSON().replace("T", " ").replace("Z", "") + "] ";
        }

        function genFormatArray(messageAndStyleArray) {
            const formatArray = [];
            for (let i = 0, end = messageAndStyleArray.length / 2; i < end; i++) {
                formatArray.push("%c%s");
            }
            return formatArray.join("");
        }

        function getCodeLocation() {
            const callstack = new Error().stack.split("\n");
            while (callstack.length && callstack[0].indexOf("allenyang") === -1) {
                callstack.shift();
            }
            callstack.shift();
            callstack.shift();

            return callstack[0].trim();
        }

        function parseSetCookie(cookieString) {
            // uuid_tt_dd=10_37476713480-1609821005397-659114; Expires=Thu, 01 Jan 1025 00:00:00 GMT; Path=/; Domain=.csdn.net;
            const cookieStringSplit = cookieString.split(";");
            const {key, value} = splitKeyValue(cookieStringSplit.length && cookieStringSplit[0])
            const map = new Map();
            for (let i = 1; i < cookieStringSplit.length; i++) {
                let {key, value} = splitKeyValue(cookieStringSplit[i]);
                map.set(key.toLowerCase(), value);
            }
            // 当不设置expires的时候关闭浏览器就过期
            const expires = map.get("expires");
            return new CookiePair(key, value, expires ? new Date(expires).getTime() : null)
        }

        function splitKeyValue(s) {
            let key = "", value = "";
            const keyValueArray = (s || "").split("=");

            if (keyValueArray.length) {
                key = decodeURIComponent(keyValueArray[0].trim());
            }

            if (keyValueArray.length > 1) {
                value = decodeURIComponent(keyValueArray.slice(1).join("=").trim());
            }

            return {
                key, value
            }
        }

        function getCurrentCookieMap() {
            const cookieMap = new Map();
            if (!document.cookie) {
                return cookieMap;
            }
            document.cookie.split(";").forEach(x => {
                const {key, value} = splitKeyValue(x);
                cookieMap.set(key, new CookiePair(key, value));
            });
            return cookieMap;
        }

        class DebuggerRule {

            constructor(eventName, cookieNameFilter, cookieValueFilter) {
                this.eventName = eventName;
                this.cookieNameFilter = cookieNameFilter;
                this.cookieValueFilter = cookieValueFilter;
            }

            test(eventName, cookieName, cookieValue) {
                return this.testByEventName(eventName) && (this.testByCookieNameFilter(cookieName) || this.testByCookieValueFilter(cookieValue));
            }

            testByEventName(eventName) {
                if (!enableEventDebugger[eventName]) {
                    return false;
                }
                // 事件不设置则匹配任何事件
                if (!this.eventName) {
                    return true;
                }
                return this.eventName === eventName;
            }

            testByCookieNameFilter(cookieName) {
                if (!cookieName || !this.cookieNameFilter) {
                    return false;
                }
                if (typeof this.cookieNameFilter === "string") {
                    return this.cookieNameFilter === cookieName;
                }
                if (this.cookieNameFilter instanceof RegExp) {
                    return this.cookieNameFilter.test(cookieName);
                }
                return false;
            }

            testByCookieValueFilter(cookieValue) {
                if (!cookieValue || !this.cookieValueFilter) {
                    return false;
                }
                if (typeof this.cookieValueFilter === "string") {
                    return this.cookieValueFilter === cookieValue;
                }
                if (this.cookieValueFilter instanceof RegExp) {
                    return this.cookieValueFilter.test(cookieValue);
                }
                return false;
            }

        }
        (function standardizingRules() {

            const ruleConfigErrorMessage = [];

            const newRules = [];
            while (debuggerRules.length) {
                const rule = debuggerRules.pop();
                if (typeof rule === "string" || rule instanceof RegExp) {
                    newRules.push(new DebuggerRule(null, rule, null));
                    continue;
                }
                for (let key in rule) {
                    let events = null;
                    let cookieNameFilter = null;
                    let cookieValueFilter = null;
                    if (key === "events") {
                        events = rule["events"] || "add | delete | update";
                        cookieNameFilter = rule["name"]
                        cookieValueFilter = rule["value"];
                    } else if (key !== "name" && key !== "value") {
                        events = key;
                        cookieNameFilter = rule[key];
                        cookieValueFilter = rule["value"];
                    } else {
                        continue;
                    }
                    if (!cookieNameFilter) {
                        const errorMessage = `必须为此条规则 ${JSON.stringify(rule)} 配置一个Cookie Name匹配条件`;
                        ruleConfigErrorMessage.push(errorMessage);
                        continue;
                    }
                    events.split("|").forEach(eventName => {
                        eventName = eventName.trim();
                        if (eventName !== "add" && eventName !== "delete" && eventName !== "update") {
                            const errorMessage = `此条规则 ${JSON.stringify(rule)} 的Cookie事件名字配置错误,必须为 add、delete、update 三种之一或者|分隔的组合,您配置的是 ${eventName},仅忽略此无效事件`;
                            ruleConfigErrorMessage.push(errorMessage);
                            return;
                        }
                        newRules.push(new DebuggerRule(eventName, cookieNameFilter, cookieValueFilter));
                    })
                }
            }

            if (ruleConfigErrorMessage.length) {
                const errorMessageStyle = `color: black; background: #FF2121; font-size: ${Math.round(consoleLogFontSize * 1.5)}px; font-weight: bold;`;
                let errorMessage = now() + "JS Cookie Monitor: 以下Cookie断点规则配置错误,已忽略: \n ";
                for (let i = 0; i < ruleConfigErrorMessage.length; i++) {
                    errorMessage += `${i + 1}. ${ruleConfigErrorMessage[i]}\n`;
                }
                console.log("%c%s", errorMessageStyle, errorMessage);
            }
            for (let rule of newRules) {
                debuggerRules.push(rule);
            }
        })();
        function testDebuggerRules(setCookieOriginalValue, eventName, cookieName, cookieValue, cookieValueChanged) {
            for (let rule of debuggerRules) {
                if (rule.test(eventName, cookieName, cookieValue)) {
                    debugger;
                }
            }
        }
        class CookiePair {
            constructor(name, value, expires) {
                this.name = name;
                this.value = value;
                this.expires = expires;
            }

        }

    }

)();

       建议配合油猴食用效果更佳~