7GvZ6G.png

声明

本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!

本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【K哥爬虫】联系作者立即删除!

前言

最近巴黎奥运会,很多平台都搞起了免费饮品免单的活动,当然雪王也不例外,小程序与 App 同为 webview 类型的程序,起初该平台只有一个 sign 加密,最近压力上来了,更新了一个新参数 type_1286 ,不少粉丝也被这个参数难住了,本文仅对雪王的这个新参数进行逆向分析,仅供学习交流

7GaL96.png

逆向目标

  • 某雪冰城小程序、某雪冰城 App
  • I+Wwj+eoi+W6jzovL+icnOmbquWGsOWfji9hb1RwNU1zT0tJMHRHWWM=

构建调试框架

APP 端调试 webview

APP 端的 webview 调试主要借助 XP 模块,或者 LSP。然后导入 webview 模块即可调试, 以 LSP 为例,模拟器装好面具以及 LSP 后,将 webview 模块导入并且选择指定 APP:

7Gv6KL.png

然后重启模拟器,在浏览器输入chrome://inspect/#devices ,若无设备加载出来,则在 cmd 控制台多输入几次 adb devices 直到设备加载出来:

7GcLAP.jpg

然后点击 inspect 即可进入,出现正常的 F12 界面证明调试成功:

7Gc0Jw.jpg

小程序端调试 webview

  • 首先 USB 数据线连接手机进入调试模式;
  • 首先微信访问 http://debugxweb.qq.com/?inspector=true 确定是否可以用(能打开就能用);
  • 微信上打开你需要调试的页面;
  • 谷歌浏览器地址栏输入 chrome://inspect/#devices 等待一会儿 (浏览器需要具备翻强功能);
  • 点击对应网页或者小程序 inspect 即可出现调试栏,然后像正常调试页面即可 chrome://inspect/#devices

最后效果如 App 端开启调试的结果相同。

抓包分析

进入免单界面,点击领取,在开发者工具中即可查看到该接口,如下:

7GclB6.jpg

其主要是加密参数为 url 接口中的 type_1286,以及提交内容中的 sign,本文将重点讲 type_1286 参数的生成。

逆向分析

type_1286 参数

该参数为领取免单券接口的重要参数,我们在 App 或者小程序端输入口令点击确认,观察堆栈,从第一个堆栈进入:

7Gcjmw.jpg

然后我们在进入的地方下一个断点,然后继续点击确认,发现在此处断了下来,发现是一个大 OB 混淆,与普通的OB 还不太一样,经过分析可知 UL 参数即为我们需要逆向的参数:

 var UL = UE['Fu'](this[oA(P7.a)][-0x190d + -0x20e9 + 0x39f7])
  , UL = F0[oA(P7.A)](UH, UL, UV);

7Gcnh6.jpg

在俩个 UL 参数中下断点,再次刷新点击确认,成功在 UL 处断了下来,经过分析可知,第一个 UL 为一个 object 对象,然后将 UL,UV 参数传入UH中完成加密,生成最终的 url 如下:

7GcIrO.jpg

我们进入 UH 函数进行分析,发现它是一个 && 用法,最终通过 M['F6'](L, N)生成加密参数 type_128,也就是调用 F6 函数生成最终的加密参数:

7Gciwf.jpg

我们进入 F6 函数,观察它的生成逻辑:

7GcsSc.jpg

发现它也是被混淆的不成样子了,最后加密参数由以下代码块生成:

 (g += N),
                    (N = F[UJ(mS.F)](F[UJ(mS.Y)](F[UJ(mS.U)](F[UJ(mS.a)](F[UJ(mS.A)](M[UJ(mS.D)](g), '|'), (-0xfbf + 0x9 * -0x189 + 0x16 * 0x158,
                    m['n'])()), '|'), new Date()[UJ(mS.o)]()), '|1'),
                    g = E['FU']['ua'](N, !(0xbb4 + 0x1a49 * -0x1 + 0xe95)),
                    N = {}),
                    (N[M['F7'](L[UJ(mS.i)])] = g,
                    L[UJ(mS.y)] = (-0x2ff + 0xbe5 + -0x8e6,
                    H['Fa'])(L[UJ(mS.y)], N),
                    (0x3 * 0xe5 + -0x614 + 0x1 * 0x365,
                    H['FY'])(L))

所以我们想要拿下这个参数就要将这个 F6 函数拿下,这里我们讲 2 种方法补环境和算法还原,如果细分第二种办法又可以分为两种。

补环境

关于补环境的话,我们直接将代码全部拿下,放到浏览器里跑一下试试:

7Gc2s3.jpg

没错,浏览器卡死了,甚至电脑的风扇都开始转个不停:

7GcF7j.jpg

返回网页 js,我们发现在 F3 函数中存在格式化检测:

7GcZW5.jpg

我们将代码压缩,放到 node 环境中执行一次,看看能不能正常报错:

7Gcdjm.jpg

发现我们的代码已经可以正常跑起来了,接下来就到缺啥补啥的环境了,还是老样子,将代理挂上:

memory = {
    'Proxy': true,
    'random': 0.5,
}

// Math.random = function(){return memory['random']};

memory.proxy = (function() {
    memory.Object_sx = ['Date'];
    memory.Function_sx = []//['Array', 'Object', 'Function', 'String', 'Number', 'RegExp', 'Symbol', 'Error', 'EvalError', 'RangeError', 'ReferenceError', 'SyntaxError', 'TypeError', 'URIError', 'Uint8Array'];
    memory.setFun = [];
    memory.getObjFun = [];
    memory.color = {
        'set': [3, 101, 100],
        'get': [255, 140, 0],
        'has': [220, 87, 18],
        'apply': [107, 194, 53],
        'ownKeys': [147, 224, 255],
        'deleteProperty': [199, 21, 133],
        'defineProperty': [179, 214, 110],
        'construct': [200, 8, 82],
        'getPrototypeOf': [255, 255, 255],

        'object': [147, 224, 255],
        'function': [147, 224, 255],
        'number': [255, 224, 0],
        'array': [147, 224, 0],
        'string': [255, 224, 255],
        'undefined': [255, 52, 4],
        'boolean': [76, 180, 231],
    };
    memory.log = console.log;
    memory.log_order = 0;
    memory.proxy_Lock = 0;
    // 文本样式
    function styledText(text, styles) {
        let styledText = text;
        // RGB颜色
        if (styles.color) {
            styledText = `\x1b[38;2;${styles.color[0]};${styles.color[1]};${styles.color[2]}m${styledText}\x1b[0m`;
        }
        // 背景颜色
        if (styles.bgColor) {
            styledText = `\x1b[48;2;${styles.bgColor[0]};${styles.bgColor[1]};${styles.bgColor[2]}m${styledText}\x1b[0m`;
        }
        // 粗体
        if (styles.bold) {
            styledText = `\x1b[1m${styledText}\x1b[0m`;
        }
        // 斜体
        if (styles.italic) {
            styledText = `\x1b[3m${styledText}\x1b[0m`;
        }
        // 下划线
        if (styles.underline) {
            styledText = `\x1b[4m${styledText}\x1b[0m`;
        }
        // 返回带样式的文本
        return styledText
    }
    // 文本填充
    function limitStringTo(str, num) {
        str = str.toString()
        if (str.length >= num) {
            return str + ' '
        } else {
            const spacesToAdd = num - str.length;
            const padding = ' '.repeat(spacesToAdd);
            // 创建填充空格的字符串
            return str + padding;
        }
    }
    // 进行代理
    function new_obj_handel(target, target_name) {
        if(memory.Proxy == false){return target};

        let name = target_name.indexOf('.') != -1 ? target_name.split('.').slice(-1)[0]: target_name;
        if (target['isProxy'] || memory.Object_sx.includes(name)) {
            return target;
        }else{
            return new Proxy(target,my_obj_handler(target_name))
        }
    }
    function new_fun_handel(target, target_name) {
        if(memory.Proxy == false){return target}
        let name = target_name.indexOf('.') != -1 ? target_name.split('.').slice(-1)[0]: target_name;
        if (memory.Function_sx.includes(name)) {
            return target;
        }else{
            return new Proxy(target,my_fun_handler(target_name))
        }
    }
    // 获取数据类型
    function get_value_type(value) {
        if (Array.isArray(value)) {
            return 'array'
        }
        if (value == undefined) {
            return 'undefined'
        }
        return typeof value;
    }
    // 函数与对象的代理属性
    function my_obj_handler(target_name) {
        return {
            set: function (obj, prop, value) {
                if(memory['proxy_Lock']){
                    return Reflect.set(obj, prop, value);
                };

                const value_type = get_value_type(value);
                const tg_name = `${target_name}.${prop.toString()}`;
                const text = limitStringTo(++memory['log_order'],5) + limitStringTo('setter',20) + limitStringTo(`hook->${tg_name};`,50)

                // 如果设置到的属性是对象 --> 输出值对象
                // 如果设置到的属性是方法 --> 输出值function
                // 其他的就全部输出值
                if (value && value_type === "object") {
                    memory.log(styledText(text, {
                        color: memory.color['set'],
                    }), styledText('value->',{
                        color: memory.color['set'],
                    }),value)
                }
                else if (value_type === "function") {
                    memory.setFun.push(tg_name)
                    memory.log(styledText(text , {
                        color: memory.color['set'],
                    }),styledText(`value->`, {
                        color: memory.color['set'],
                    }),styledText(`function`, {
                        color: memory.color[value_type],
                    }))
                }
                else {
                    memory.log(styledText(text, {
                        color: memory.color['set'],
                    }),styledText(`value->`, {
                        color: memory.color['set'],
                    }),styledText(`${value}`, {
                        color: memory.color[value_type],
                    }))
                }

                return Reflect.set(obj, prop, value);
            },
            get: function (obj, prop) {
                if(memory['proxy_Lock']){
                    return Reflect.get(obj, prop)
                };
                if (prop === "isProxy") {
                    return true;
                }

                const value = Reflect.get(obj, prop);
                const tg_name = `${target_name}.${prop.toString()}`;
                const value_type = get_value_type(value);

                const text = limitStringTo(++memory['log_order'],5) + limitStringTo('getter',20) + limitStringTo(`hook->${tg_name};`,50)
                // 如果获取到的属性是对象 --> 对其getter和setter进行代理
                // 如果获取到的属性是方法 --> 对其caller进行代理
                // 其他的就全部输出值
                if (value_type === 'object') {
                    if (memory.getObjFun.indexOf(tg_name) == -1){
                        memory.log(styledText(text, {
                            color: memory.color['get'],
                        }), styledText('value->',{
                            color: memory.color['get'],
                        }),value)
                        memory.getObjFun.push(tg_name)
                    }
                    return new_obj_handel(value,tg_name)
                }
                else if(value_type === "function"){
                    if (memory.getObjFun.indexOf(tg_name) == -1){
                        memory.log(styledText(text , {
                            color: memory.color['get'],
                        }),styledText(`value->`, {
                            color: memory.color['get'],
                        }),styledText(`function`, {
                            color: memory.color[value_type],
                        }))
                        memory.getObjFun.push(tg_name)
                    }
                    return new_fun_handel(value,tg_name);
                }
                else{
                    memory.log(styledText(text , {
                            color: memory.color['get'],
                        }),styledText( `value->` , {
                            color: memory.color['get'],
                        }),styledText( `${value}` , {
                            color: memory.color[value_type],
                        }))
                    return value
                }
            },
            has: function(obj, prop) {
                if(memory['proxy_Lock']){
                    return Reflect.has(obj, prop)
                }

                const value = Reflect.has(obj, prop);
                const value_type = get_value_type(value);
                const text = limitStringTo(++memory['log_order'],5) + limitStringTo('in',20) + limitStringTo(`hook->"${prop.toString()}" in ${target_name};`,50)
                memory.log(styledText(text, {
                        color: memory.color['has'],
                    }), styledText(`value->`, {
                        color: memory.color['has'],
                    }), styledText(`${value}`, {
                        color: memory.color[value_type],
                    }))

                return value;
            },
            ownKeys:function(obj){
                if(memory['proxy_Lock']){
                    return Reflect.ownKeys(obj);
                }

                const value = Reflect.ownKeys(obj);
                const value_type = get_value_type(value);
                const text = limitStringTo(++memory['log_order'],5) + limitStringTo('ownKeys',20) + limitStringTo(`hook->${target_name};`,50)
                memory.log(styledText(text, {
                        color: memory.color['ownKeys'],
                    }), styledText(`value->`, {
                        color: memory.color['ownKeys'],
                    }), styledText(`${value}`, {
                        color: memory.color[value_type],
                    }));

                return value
            },
            deleteProperty:function(obj, prop) {
                if(memory['proxy_Lock']){
                    return Reflect.deleteProperty(obj, prop);
                }

                const value = Reflect.deleteProperty(obj, prop);
                const tg_name = `${target_name}.${prop.toString()}`;
                const value_type = get_value_type(value);
                const text = limitStringTo(++memory['log_order'],5) + limitStringTo('delete',20) + limitStringTo(`hook->${tg_name};`,50)
                memory.log(styledText(text, {
                        color: memory.color['deleteProperty'],
                    }), styledText(`value->`, {
                        color: memory.color['deleteProperty'],
                    }), styledText(`${value}`, {
                        color: memory.color[value_type],
                    }));

                return value;
            },
            defineProperty: function (target, property, descriptor) {
                if(memory['proxy_Lock']){
                    return Reflect.defineProperty(target, property, descriptor);
                };

                const value = Reflect.defineProperty(target, property, descriptor);
                const tg_name = `${target_name}.${property.toString()}`;
                const text = limitStringTo(++memory['log_order'],5) + limitStringTo('defineProperty',20) + limitStringTo(`hook->${tg_name};`,50)
                memory.log(styledText(text, {
                        color: memory.color['defineProperty'],
                    }), styledText('value->',{
                        color: memory.color['defineProperty'],
                    }),descriptor)

                return value;
            },
            getPrototypeOf(target) {
                if(memory['proxy_Lock']){
                    return Reflect.getPrototypeOf(target);
                }

                var value = Reflect.getPrototypeOf(target);
                const text = limitStringTo(++memory['log_order'],5) + limitStringTo('getPrototypeOf',20) + limitStringTo(`hook->${target_name};`,50)
                memory.log(styledText(text, {
                        color: memory.color['getPrototypeOf'],
                    }), styledText('value->',{
                        color: memory.color['getPrototypeOf'],
                    }),value)

                return value;
            }
        };
    }
    function my_fun_handler(target_name) {
        return  {
            apply:function(target, thisArg, argumentsList){
                if(memory['proxy_Lock']){
                    return Reflect.apply(target, thisArg, argumentsList);
                };

               if(memory.setFun.indexOf(target_name) != -1 || memory.setFun.includes(target_name.split('.')[0])){
                    // 扣的代码触发
                    var value = Reflect.apply(target, thisArg, argumentsList);
                    memory.setFun.push(`log_${memory['log_order'] + 1}`)
                }
                else{
                    // 补的环境触发的分支
                    memory['proxy_Lock'] = 1
                    var value = Reflect.apply(target, thisArg, argumentsList);
                    memory['proxy_Lock'] = 0

                }

                const value_type = get_value_type(value);
                const text = limitStringTo(++memory['log_order'],5) + limitStringTo('caller',20) + limitStringTo(`hook->log_${memory['log_order']} = ${target_name}();`,50);
                memory.log(styledText(text, {
                        color: memory.color['apply'],
                    }),styledText('arguments->',{
                        color: memory.color['apply'],
                    }),argumentsList, styledText('returnValue->',{
                        color: memory.color[value_type],
                    }),value)

                if(value_type == 'object'){
                    return new_obj_handel(value,`log_${memory['log_order']}`);
                }
                else if(value_type == 'function'){
                    return new_fun_handel(value,`log_${memory['log_order']}`);
                }
                return  value;
            },
            construct: function (target, args, newTarget) {
                if(memory['proxy_Lock']){
                    return Reflect.construct(target, args, newTarget)
                }

                if(memory.setFun.indexOf(target_name) != -1 || memory.setFun.includes(target_name.split('.')[0])){
                    var value = Reflect.construct(target, args, newTarget);
                    memory.setFun.push(`log_${memory['log_order'] + 1}`)
                }
                else{
                    memory['proxy_Lock'] = 1
                    var value = Reflect.construct(target, args, newTarget);
                    memory['proxy_Lock'] = 0
                }


                const text = limitStringTo(++memory['log_order'],5) + limitStringTo('new',20) + limitStringTo(`hook->log_${memory['log_order']} = new ${target_name}();`,50)
                memory.log(styledText(text, {
                        color: memory.color['construct'],
                    }), styledText('arguments->',{
                        color: memory.color['construct'],
                    }),args, styledText('returnValue->',{
                        color: memory.color['construct'],
                    }),value);
                return new_obj_handel(value,  `log_${memory['log_order']}`);
            },
        }
    }
    // 返回进行对象代理
    return new_obj_handel
}());

其中,检测最多的就是 createElement 校验了对标签的创建,以及标签下面的属于,检测较深但不严:

7Gv98h.jpg

全部补完大概在 400 行代码左右,最后运行代码没有报错,那么我们的环境就已经补好了,如下:

7Gvkt9.jpg

那么我们应该如何调用加密函数呢?还记得刚刚的 F6 函数吗?我们将代码全部放到 Notepad 中,将代码进行改写,将 F6 函数导出:

7GvBeY.jpg

然后将代码全部复制,放到在线 js 代码压缩网站中进行压缩,将压缩后的 js 代码放到我们刚刚补的环境下面,打印 console.log(window.kk) 看看我们的函数有没有导出:

7GvpSZ.jpg

最后将参数我们传入到加密函数中,不出所谓,打印出了正确的结果:

7GvLsU.jpg

补环境的话需要考虑的地方很多,对某些节点检测甚至有 3-4 层的深度,全部拿下的话代码行数在 8000 行左右,下面我们用算法还原的方式将整个算法进行剖析。

算法还原 1

我们还是回到加密函数 F6 中,看看它具体是通过哪些步骤进行加密的:

7Gv3Eq.jpg

首先将解密函数赋值给了 UJ,然后将 L 传入 H['FY'] 中进行取值,跟进 H['FY'] 看看它做了什么:

7GvlWs.jpg

最终 var g = L["FW"] + L["hash"];

然后 g += N,接着:

N = F[UJ(mS.F)](F[UJ(mS.Y)](F[UJ(mS.U)](F[UJ(mS.a)](F[UJ(mS.A)](M[UJ(mS.D)](g), '|'), (-0xfbf + 0x9 * -0x189 + 0x16 * 0x158,
                    m['n'])()), '|'), new Date()[UJ(mS.o)]()), '|1')

前面仍然是混淆的 + 函数,最后分析可得:

N = (((((sig(g) + '|') + 0) + '|') + new Date()["getTime"]()) + '|1')

然后调用 E['FU']['ua'](N, !(0xbb4 + 0x1a49 * -0x1 + 0xe95)) 完成最后的加密:

7Gv7ja.jpg

所以分析可知,我们需要先拿下 sig 函数,进到 sig 中,发现其结构如下:

 'sig': function(L) {
                    var Ub = Uc;
                    for (var N = 0xa52 + 0x1499 + 0x1 * -0x1eeb, g = F[Ub(mn.F)](encodeURIComponent, L), B = 0x129f + 0x7 * 0x3d + -0x144a; F[Ub(mn.Y)](B, g[Ub(mn.U)]); B++)
                        N = F[Ub(mn.a)](F[Ub(mn.A)](F[Ub(mn.D)](F[Ub(mn.o)](N, 0xb7a * -0x2 + 0x25c7 * 0x1 + -0xecc), N), -0x1bf6 + -0xb06 * -0x1 + 0x127e), g[Ub(mn.i)](B)),
                        N |= 0x6b * 0x35 + 0x1349 * 0x2 + -0x3cb9 * 0x1;
                    return N;
                }

还原以后为:

function sig(L) {
    for (var N = 0, g = encodeURIComponent(L), B = 0; B < g["length"]; B++)
        N = ((((N << 7) - N) + 398) + g["charCodeAt"](B)),
        N |= 0;
    return N;
}

接着,我们再进入主加密函数 ua 中,结构如下,依旧是吃相极其难看的代码:

7GvQp7.jpg

在代码中,我们看到了调用了解密函数,以及 uu 等函数调用,ua 分析后可得:

function ua(E, H) {
    var W = ["3", "4", "2", "1", "0"]
      , P = 0;
    while (!![]) {
        switch (W[P++]) {
        case '0':
            switch (M["length"] % 4) {
            default:
            case 0:
                return M;
            case 1:
                return (M + "===");
            case 2:
                return (M + '==');
            case 3:
                return (M + '=');
            }
        case '1':
            if (H)
                return M;
            continue;
        case '2':
            var M = uu(E, 6, function(L) {
                return V["uGGDj"].charAt(L);
            });
            continue;
        case '3':
            var K = {};
            K["uGGDj"] = "DGi0YA7BemWnQjCl4+bR3f8SKIF9tUz/xhr2oEOgPpac=61ZqwTudLkM5vHyNXsVJ";
            var V = K;
            continue;
        case '4':
            if (null === E)
                return '';
            continue;
        }
        break;
    }
}

接着进入 uu 函数中,一如既往的吃相更难看的代码:

7GvN8I.jpg

这就完了?不,还有:

7GvqtV.jpg

前面几个函数我们都是手动替换的解密后的字符串,所以解密函数就没有扣,那看看这个函数,你再手动替换试试看,手估计要费掉,所以必须将解密函数 F3 拿下,然后它就可以调用 az 自主完成字符串的解密。当然解密函数就是本文难点之一。

前面我们说的算法分析可以粗略的分为俩种,其实就是解密函数的扣法可以分为俩种,我们进入解密函数 F3 中,观察其结构如下:

7GvHUG.jpg

它接收两个参数 a 和 A,通过对 a 进行复杂的算术操作来计算一个新的索引值 n:

7GvyyJ.jpg

然后从数组 F 中取出该索引处的元素,并返回这个元素。数组 U 也在计算过程中被用来获取中间值。特定值的函数。

将 F3 分析后复现如下:

F3 = function(a, A) {
        a = a - (-0x256c + -0x23 * -0x67 + -0x17f3 * -0x1);
        var r = U[a];
        var o = U[0x1b * 0xd3 + -0x2 * -0x1189 + 0xb77 * -0x5]
          , n = a + o
          , i = F[n];
            r = i;
        return r;
    }

所以我们可以将 U 和 F 拿下,U 数组很好拿下,因为他就是偏移量之后的数据,但是 F 就不一样了,如果你在很早之前就将 F 拿下,那么可能会造成 F 缺失的问题,会导致解密函数某些值解密不成功,因为F是一直在增加的

7GvK2B.jpg

所以我们如果想拿下完整的 F,就要在加密完成之前或者接近加密结束的时候进入 F3 中,将 F 全部控制台 copy 下来,这样整个解密函数将被彻底拿下:

7GvgEt.jpg

在 uu 函数中同样存在 lw 和 F 。同时 F 函数中需要用到 UZ 函数,我们只需定义一个空的 UZ 函数即可:

function UZ(a) {
}

最后整个加密流程整合,即可出现正确的结果:

7Gv8je.jpg

出现 OB 大数组所占的行数,整个算法也就几百行左右,比起之前的代码可谓是相当整洁。

算法还原 2

刚刚我们提到在扣解密函数的时候,可能会由于时机不正确,导致复制下来的 U 有所缺失,那么就会造成解密失败,如下:

7Gv5Gw.jpg

所以我们在网页中找到该位置对应的 js 代码,将解密函数失败的 az(lw.V) 这种值全部找出来,然后我们放到自己定义的一个大对象 KKK 中,如下:

KKK = {
    1719:"qMjri",
    1093:"BEYhV",
    267:"bdCZp",
    1463:"HXHPX",
    1016:"hzLDX",
    1364:"aBzMZ",
    507:"YFbJS",
    1118:"pow",
    1010:"Jwfww",
    1483:"wZcNG",
    1024:"UYeav",
    927:"tLwiW",
    230:"tQdqh",
    1880:"dOdjo",
    1614:"IlkJY",
    410:"PvxjR",
    1867:"tYdmC",
    1670:"llrMA",
    1577:"wEEdD",
    1593:"uhTrx",
    1860:"cbQeM",
    1734:"rfswT",
    594:"NUkoa",
    877:"charAt",
}

然后我们用代理器给我们的解密函数挂上代理, 通过代理(Proxy)对象为 F3 对象添加一个自定义的函数调用处理器。它利用 KKK 这个映射对象,在某些条件下替代函数的返回值:

// 定义处理函数 kk_handler
const kk_handler = {
    apply: function(target, thisArg, argumentsList) {
        // 原始函数调用
        let result = target(...argumentsList);

        // 检查结果是否为 undefined 并且参数是否存在于 KKK
        if (result === undefined && argumentsList in KKK) {
            return KKK[argumentsList];
        }

        // 返回原始函数调用的结果
        return result;
    }
};

代码详细解释如下:

  • target:被代理的目标函数,即 F3
  • thisArg:如果原始函数是作为对象方法调用的,那么它的 this 指向;
  • argumentsList:函数调用的参数列表;
  • 内部先调用目标函数并获取其返回值。如果返回值为 undefined 且参数列表存在于 KKK 中,返回对应的映射值,否则返回原始返回值;
  • 通过 Proxy 包装 F3javascript F3 = new Proxy(F3, kk_handler); 使用 Proxy 构造函数创建新的代理对象 F3,将 F3 和处理器 kk_handler关联起来。

最终就可以拦截解密函数,实现完整的解密,然后就同方法 1 调用加密函数即可完成 type_128 的加密,解决方法有很多种,遇到问题阅读相关 API 即可解决相关的问题,至此整个 type_128 参数就分析完毕了。