吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2591|回复: 4
收起左侧

[Web逆向] 某初滑块逆向分析

  [复制链接]
buluo533 发表于 2025-8-11 18:40
本帖最后由 buluo533 于 2025-8-11 18:42 编辑

本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关.本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责
某初滑块滑块,难度适中,食用地址(好像更新了代码,更简单了,我会给出复杂一点的版本):
aHR0cHM6Ly9hcHAueWljaHVhcHAuY24vaW5kZXgvaW52aXRlL2luZGV4Lw==
工具站:
https://astexplorer.net/

https://babel.nodejs.cn/docs/babel-core

    本文主要是对滑块的思路进行分析讲解,同样分析简单ob混淆的ast还原以及滑块加密逻辑


                           

滑块情况

滑块情况


一、流程分析
  

debuggger

debuggger

              开局一个debugger,掏出多年的hook脚本,直接注入过掉(百度一搜就有,或者自己写一个)
              
           
let AAA = Function.prototype.constructor

Function.prototype.constructor = function (x) {
    console.log(x)
    if (X != "debugger") {
        return AAA(x)
    }
    ;
    return AAA.call(this, x)
}


           解决掉debugger我们就可以按照之前的经验来分析,我们需要进行滑动,来看需要处理的请求接口信息
        

滑动接口

滑动接口

         很明显,啥都没有,还反手送我一个debugger,还有一串混淆的代码,很明显,这个js代码是我们要处理的,但是我们没有看到校验的逻辑。我们就可以猜测了,因为之前还有假的滑块,先排除一下,会不会是cookie校验
      

cookie前

cookie前

      

cookie信息

cookie信息

       我们对比一下滑动前和滑动后的参数变化,发现有一个异常的参数guard_ret,简单注入hook一下
      
(function () {
    'use strict';
    var cookie_cache = document.cookie;
    Object.defineProperty(document, 'cookie', {
        get: function () {
            return cookie_cache;
        },
        set: function (val) {
            console.log('Setting cookie', val);
            if (val.indexOf('guard_ret') != -1) {
                debugger;
            }
            var cookie = val.split(";")[0];
            var ncookie = cookie.split("=");
            var flag = false;
            var cache = cookie_cache.split("; ");
            cache = cache.map(function (a) {
                if (a.split("=")[0] === ncookie[0]) {
                    flag = true;
                    return cookie;
                }
                return a;
            })
            cookie_cache = cache.join("; ");
            if (!flag) {
                cookie_cache += cookie + "; ";
            }
            return cookie_cache;
        }
    });
})();

            

cookie堆栈

cookie堆栈

           这里就是成功断住了,我们往上看看堆栈,分析产生
      

拼接cookie

拼接cookie

          我们发现这个js文件就是我们返回的那一个js文件,混淆很难看,直接ast一把梭哈
二、ast还原混淆代码逻辑分析
   拿到混淆代码先总览一边,看看还原思路。
      

混淆逻辑

混淆逻辑

                    很明显一个编码混淆。
                     

赋值调用

赋值调用

                    然后发现有很多_0xb0e1函数的一个赋值调用,符合ob混淆的特征,是一个解密函数,然后就是找准大数组和数组移位函数
               

大数组逻辑

大数组逻辑

              

                找准了大数组和解密函数,接下来就是开始写ast代码,先搭一个模板,理一下思路
                (1)将三要素,大数组,数组移位函数,解密函数,存进内存
                (2)编码混淆还原
                (3)解密函数调用还原
                (4)ob混淆特征大对象还原
                 一步一步来,找到对应的三要素位置,直接提取出来,存进内存。
               

语法树

语法树

         
let parse = require("@babel/parser").parse;
let traverse = require("@babel/traverse").default;
let generator = require("@babel/generator").default;
let fs = require("fs");
let types = require("@babel/types");

let jsc_code = fs.readFileSync("./混淆代码.js", encoding = "utf-8")
let ast = parse(jsc_code)
let init_ast = parse(jsc_code)

traverse(init_ast, {
    Program: function (path) {
        path.node.body = path.node.body.slice(0, 5)
        path.stop()
    }
})
let test_code = generator(init_ast, {minified: true}).code
eval(test_code)

          接下来做一个编码的还原,掏出俩件套
        
traverse(ast, {
    StringLiteral({node}) {
        if (node.extra && /\\[ux]/gi.test(node.extra.raw)) {
            node.extra = undefined;
        }
    }
});
traverse(ast, {
    NumericLiteral({node}) {
        if (node.extra && /^0[obx]/i.test(node.extra.raw)) {
            node.extra = undefined;
        }
    }
});

        完成这一步之后我们可以调用解密函数测试一下,有没有存进内存。
         

解密函数调用

解密函数调用

         因为我们有很多赋值调用的过程,我们把赋值的函数名给收集起来,然后统一替换成解密函数_0xb0e1,执行替换结果,代码如下。
      
      

     
let decode_name = []
traverse(ast, {
    VariableDeclarator(path) {
        const {id, init} = path.node;
        if (init && (init.type === 'Identifier' || init.type === 'FunctionDeclaration') ) {
            let name = id.name
            decode_name.push(name)
            path.remove()
        }

    }
});

traverse(ast, {
    CallExpression: {
        exit: function (path) {
            let {node} = path
            let func_name = node.callee.name
            if (!decode_name.includes(func_name)) {
                return
            }
            if (!node.arguments || node.arguments.length !== 1) {
                return;
            }
            let eval_path = path.toString().replace(func_name, "_0xb0e1")
            let result = eval(eval_path)
            path.replaceWith(types.valueToNode(result))
        }

    }
});

          这样就可以把所有解密函数调用的部分还原,可以看一下结果,还是非常的清晰
         

解密函数还原结果

解密函数还原结果

            接下来就是处理最难的部分,ob混淆的大数组处理。我的大致思路如下
           1、收集所有的符合条件的对象
           2、分成两部分处理,一部分是函数和运算符的还原,一部分是字符串的还原

           3、通过函数调用和作用域去还原,比较特定,不通用
             第一部分是收集所有的符合条件的对象
         
         
            
let obj_name = []
traverse(ast, {
    VariableDeclarator: function (path) {
        let {parentPath, node, scope} = path
        if (!parentPath.isVariableDeclaration()) {
            return;
        }
        let {id, init} = node
        if (!types.isObjectExpression(init)) {
            return;
        }
        if (!id.name && id.name === undefined) return;
        let name = id.name
        obj_name.push(name)
    }
})

           在最开始写代码建议使用某一个对象去测试,然后去处理函数和运算符的还原
      
traverse(ast, {
    CallExpression: {
        exit(path) {
            if (path.get("callee.object").node && obj_name.includes(path.get("callee.object").node.name)) {
                let property = path.get("callee.property").node.value
                let argument_path_array = path.get("arguments")
                let name = path.get("callee.object").node.name
                path.scope.getBinding(name).scope.path.traverse({
                    ObjectExpression: function (path_Expression) {
                        let {node, parentPath} = path_Expression
                        if (!parentPath.isVariableDeclarator()) {
                            return
                        }
                        if (parentPath.node.id.name !== name) {
                            return;
                        }
                        let properties = node.properties
                        for (i of properties) {
                            if (i.key.value && i.key.value === property) {
                                let value = i.value
                                switch (value.type) {
                                    case 'FunctionExpression':
                                        let return_path = value.body.body[0].argument
                                        if (value.body.body[0].argument.type === 'BinaryExpression') {
                                            let operator = return_path.operator
                                            let left = argument_path_array[0].node
                                            let right = argument_path_array[1].node
                                            path.replaceInline(types.binaryExpression(operator, left, right))
                                        } else if (value.body.body[0].argument.type === 'CallExpression') {
                                            let func = argument_path_array[0].node
                                            if (!types.isCallExpression(path) && !path.arguments) {
                                                return;
                                            }
                                            let func_arguments = path.node.arguments.slice(1)
                                            path.replaceInline(types.callExpression(func, func_arguments))
                                        }
                                        break
                                }
                            }
                        }
                        path_Expression.scope.crawl();
                    }
                });
            }
        }
    }
})

           第三步是字符串对应的还原
           
traverse(ast, {
    MemberExpression:
        function (path) {
            if (obj_name.includes(path.get("object.name").node) && path.get('property').isStringLiteral()) {
                let _string = path.get('property').node.value
                let name = path.get("object.name").node
                path.scope.getBinding(name).scope.path.traverse({
                    ObjectExpression: function (path_Expression) {
                        let {node, parentPath} = path_Expression
                        if (!parentPath.isVariableDeclarator()) {
                            return
                        }
                        if (parentPath.node.id.name !== name) {
                            return;
                        }
                        let properties = node.properties
                        for (i of properties) {
                            let key_value = i.key.value
                            let value = i.value
                            if (key_value === _string) {
                                switch (value.type) {
                                    case 'StringLiteral':
                                        let real_value = value.value
                                        path.replaceWith(types.valueToNode(real_value))
                                        break
                                }
                            }
                        }
                        path_Expression.scope.crawl();

                    }

                });

            }
        }
})

         
            总体还原思路是从函数调用入手,去匹配他的值和对象里的键这样去实现还原。现在更新的加密更加简单(需要处理定时器的逻辑),没有了对象的还原,简单写了一个,仅供参考
      
traverse(ast, {
    CallExpression: {
        exit: function (path) {
            let {node} = path
            let func_name = node.callee.name
            if (func_name !== "_0x4de0") {
                return;
            }
            let result = eval(path.toString())
            path.replaceWith(types.valueToNode(result))
        }
    }
});

         剩下的就是处理垃圾代码的删除,就可以完成这个的还原。


三、加密逻辑分析
           

加密逻辑

加密逻辑

             还原后的代码一目了然,可以看到清晰的逻辑,找一下 _0x2b7a59的一个生成逻辑
         
 const _0x167271 = _0x39eb8c["getBoundingClientRect"](),
        _0x4b9064 = _0x255abf['getBoundingClientRect'](),
        _0x3f4be5 = _0x167271["left"] - _0x4b9064['left'],
        _0x32d3e9 = _0x167271["top"] - _0x4b9064["top"];
      x = Math["round"](_0x3f4be5);
      y = Math["round"](_0x32d3e9);
      var _0x2c1619 = _0x5ce987("guard"),
        _0x564781 = _0x2c1619["substr"](0, 8),
        _0x1499fd = _0x5cc496(x["toString"]() + 'x' + y["toString"](), _0x564781),
        _0x2b7a59 = _0x40fb99(_0x1499fd);
      document["cookie"] = "guardret=" + _0x2b7a59;

        
function _0x5ce987(_0x17f493) {
      const _0x5b6ec3 = document['cookie']["split"](';');
      let _0x119d4b = null;
      for (let _0x4de2de of _0x5b6ec3) {
          const [_0x1cc261, _0xeec935] = _0x4de2de["trim"]()['split']('=');
          if (_0x1cc261 === _0x17f493) {
            _0x119d4b = _0xeec935
          }
        }
      return _0x119d4b;
    } 

function _0x5cc496(_0x49fb4d, _0x45898e) {
    let _0x2f7fee = '';
    var _0x45898e = _0x45898e + "zVbhsiCROg";
    for (let _0x351fbd = 0; _0x351fbd < _0x49fb4d["length"]; _0x351fbd++) {
        const _0x37b77c = _0x49fb4d["charCodeAt"](_0x351fbd) ^ _0x45898e["charCodeAt"](_0x351fbd % _0x45898e["length"]);
        _0x2f7fee += String["fromCharCode"](_0x37b77c);
    }
    return _0x2f7fee;
}

function _0x40fb99(_0x20cad9) {
        return btoa(_0x20cad9) 
      }

     比较简单的一个逻辑,用python代码实现一下
def encrypt(data, key):
    result = []
    key += "zVbhsiCROg"
    key_length = len(key)

    for i in range(len(data)):   
        data_char_code = ord(data[i])
        key_char_code = ord(key[i % key_length])
        xor_result = data_char_code ^ key_char_code
        result.append(chr(xor_result))
    return ''.join(result)

   
四、总结
         这个站整体偏简单,没想到他竟然更新的更简单了,利用ast可以更快处理,还是用老版本做了一个示例,大佬们可以试试搞一下新版本,一把梭哈,cookie加密原理没有变{:1_932:} 给一个老版本混淆可以拿来练手 混淆代码.zip (12.46 KB, 下载次数: 4)

   

编码混淆

编码混淆

解密函数

解密函数

大数组

大数组

免费评分

参与人数 3威望 +1 吾爱币 +22 热心值 +2 收起 理由
涛之雨 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
gml857 + 1 + 1 我很赞同!
Yin07 + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

zhangyun_1 发表于 2025-8-13 09:11
不明觉厉,楼主编写辛苦了,更新后更简单的原因会不会是因为复杂影响了效率。
dandan946 发表于 2025-9-8 10:34
monica11 发表于 2025-10-13 12:44
zengNaNa 发表于 2025-10-14 14:50
感谢大佬分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-7-22 06:22

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表