吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2324|回复: 28
上一主题 下一主题
收起左侧

[Web逆向] 某天御滑块逆向分析

  [复制链接]
跳转到指定楼层
楼主
buluo533 发表于 2025-7-2 08:15 回帖奖励
本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关.本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责
用产品体验网站做某天御滑块的纯算分析,难度适中,使用地址:aHR0cHM6Ly90aWFueXUuMzYwLmNuLyMvZ2xvYmFsL2RldGFpbHMvc2xpZGluZy1wdXp6bGU=
参考文章:
RSA密钥格式解析&转换------aHR0cHM6Ly9tcC53ZWl4aW4ucXEuY29tL3MvcHcycEFEYU1QaWJpTlJvQ3dhOUlzZz9zY2VuZT0x
加密的诗篇:RSA与Padding技术的艺术融合------aHR0cHM6Ly9tcC53ZWl4aW4ucXEuY29tL3MvaU0xS1lldy13R1VORXBwWk1YTG5vdw==
【密码学】分组密码模式------aHR0cHM6Ly9tcC53ZWl4aW4ucXEuY29tL3MvNzZjNXNyWVZrakdIS203MmN4TU1jUQ==


代码使用了混淆加密,可以参考蔡老板文章和视频做ast还原处理,我就不献丑,只做一个简单的处理。


一、流程分析

       打开控制台就是一个debugger,直接选择永不停留简单过掉,或者hook也行。
                     
                      完成一次完整的滑块滑动,发现就两个接口,一个是前置请求接口,一个是校验接口,我们进行多次滑动来分析固定值和可疑的加密参数
                  
                     
     发现一共有七个可疑的变化参数。
     
                我们查看前置接口的返回值,发现captchaId,token是由前置接口返回生成的,所以不需要处理,接下来就是背景图和滑块图的信息都在接口当中
     
                 

                好家伙,背景图被切成了32条,重新排序的,但是他在接口中返回就是乱序,说明后期在前端也进行了重新组合处理。
    所以我们需要处理的内容有:
   1、前置请求变化参数timestamp,nonce,sign
    2、背景图重新贴图处理,识别距离
     3、校验接口report,length参数




二、参数逆向分析和还原
  1、前置接口还原分析
   
                我们直接跟进去下断点,可以简单翻看一下源码,大体上是个ob混淆,有很多十六进制数字和编码的字符串,可以简单用一个ast插件处理替换
   
[JavaScript] 纯文本查看 复制代码
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;
        }
    }
});

           这次滑块加密有一个特点,很多加密都是在本地作用域实现的,有看作用域习惯的大佬可能上手嘎嘎快
   
           
                 
          每次根据本地变量名搜索,很快能定位到加密生成的位置,我们下断点,重新加载滑块
   
       先简单在控制台测试一下,发现确实是函数生成的逻辑,我们分析一下大致的流程
  
[JavaScript] 纯文本查看 复制代码
_0x888e9f[_0x37f37d(0x3eb, '\x30\x32\x4a\x4a')](          _0x374653,
          _0x888e9f[_0x37f37d(0x534, '\x55\x35\x6a\x4d')],
        )(_0x52dfa5);
      
     
   这样可以看出来 _0x374653是一个函数名,传入参数是'setRequestBody',他们的返回值也是一个函数,传入的参数是_0x52dfa5,我们直接跟进去看看
   

  进来之后很明显就看到了两个要处理的参数,我们直接下断点分析一下逻辑
  
[JavaScript] 纯文本查看 复制代码
timestamp: Math.round(            new Date()[_0x2639ff(0x3e3, '\x69\x4a\x58\x55')](),
          ),
          nonce: _0x1b59d6.sCtwF(
            Math[_0x2639ff(0x5ac, '\x64\x6d\x6e\x5d')](
              new Date()[_0x2639ff(0x183, '\x28\x67\x62\x55')](),
            ),
            Math[_0x2639ff(0x5df, '\x74\x52\x38\x79')](
              0x5f5e100 * Math[_0x2639ff(0x290, '\x79\x5d\x51\x64')](),
            ),

        我们对混淆借助控制台进行处理后,还原代码为
      
[Asm] 纯文本查看 复制代码
    var timestamp = Math.round(new Date()['getTime']());
    var nonce = Math['floor'](100000000 * Math['random']())

这两个参数我们就处理完了,接下来就是找sign的位置和加密逻辑,我们直接在返回值位置下断点,看看整体的流程。
  
       很明显发现了sign的赋值位置和大致的逻辑
[JavaScript] 纯文本查看 复制代码
_0x1b59d6[
            _0x2639ff(0x147, '\x5e\x33\x58\x44')
          ](md5, _0x3ca1de))

         
       很明显能看出加密的逻辑,第一个参数是一个函数名,对第二参数进行加密,我们可以看到的函数名是md5,传入参数是_0x3ca1de,我们需要先简单测试一下是不是标准算法

    所以sign核心是一个md5加密,我们再看看_0x3ca1de的生成逻辑
  
         
         大体上就是对前面的请求头的参数转化为字符串拼接,所以这一部分我们整体用python还原
         
[Python] 纯文本查看 复制代码
from hashlib import md5
import time

def sign_md5(en_data):
    return md5(en_data.encode()).hexdigest()

def nonce_get():
    t1 = round(time.time() * 1000)  
    t2 = random.randrange(100000000) 
    return t1 + t2

def first_response():
    timestamp = str(int(time.time() * 1000))
    nonce = nonce_get()
    headers = {

    }
    data = {
        "appId": "dc1db94ea7b3843c",
        "dc": 0,
        "ec": 0,
        "hc": 0,
        "nonce": nonce,
        "os": 3,
        "pc": 0,
        "phone": 10000000000,
        "pn": "com.web.tianyu",
        "rc": 0,
        "sdkName":脱敏处理,
        "timestamp": timestamp,
        "type": 1,
        "ui": 'null',
        "version": "2.0.0",
        "xc": 0,
    }
    encode_sign = ''.join(f"{key}{value}" for key, value in data.items())
    sign = sign_md5(encode_sign)
    data['sign'] = sign
    response = requests.post(脱敏处理, headers=headers, data=data).json()

   2、校验接口还原分析
      完成前置接口的加密逻辑,我们可以提取的参数有背景图地址,滑块地址,captchaId,token,接下来就是分析校验参口的加密逻辑report参数
   
           
   
               还是同理跟着作用域可以很快找到加密的地址,都是明文的(说明直接搜也可以找到),然后就是来分析他的加密逻辑

[JavaScript] 纯文本查看 复制代码
_0x1b59d6[_0x34f404(0x169, '\x35\x69\x66\x73')](            _0x204684,
            _0x1b59d6.sQorU,
          )(
            JSON[_0x34f404(0x424, '\x7a\x5b\x6d\x49')](
              _0x1022c0[_0x34f404(0x4f5, '\x5a\x69\x26\x6a')],
            ),
          ),

            还是一样的,我们通过控制台进行还原
        
      
       可以看到前面一部分是通过 _0x204684(_0x1b59d6.sQorU)返回一个函数,后面轨迹的值就是传入的参数
  
      很明显他的加密逻辑就是对轨迹的处理,我们跟进去看看。
  
  这里的嵌套比较多,我们要按照代码的执行顺序来分析。
  
[JavaScript] 纯文本查看 复制代码
_0x145c35[_0x1426e3(0x571, '\x46\x59\x43\x6f')](
              _0x1b59d6[_0x1426e3(0x348, '\x69\x4b\x4c\x39')](
                atob,
                _0x4070dc.vConfig.k,
              ),
            )

     
   这样我们就有了一个比较清晰的逻辑,还有一个大胆的猜测,这是一个rsa加密 ,setPublicKey(atob(_0x4070dc.vConfig.k))


    典型的pkcs8密钥格式(不清楚的看文章,这个很重要)

接下来就是核心逻辑的处理。
[JavaScript] 纯文本查看 复制代码
_0x1b59d6[_0x1426e3(0x466, '\x55\x58\x4a\x5b')](              _0x145c35[_0x1426e3(0x5ff, '\x66\x40\x31\x4c')](_0x42739f),
              _0x1b59d6[_0x1426e3(0x640, '\x4e\x4d\x56\x30')](
                md5,
                _0x1b59d6[_0x1426e3(0x452, '\x4b\x34\x26\x57')](
                  _0x4070dc[_0x1426e3(0x37e, '\x26\x50\x47\x72')][
                    _0x1b59d6.priWD
                  ],
                  _0x4070dc[_0x1b59d6[_0x1426e3(0x4fe, '\x26\x50\x47\x72')]][
                    _0x1b59d6[_0x1426e3(0x141, '\x51\x57\x47\x76')]
                  ],
                ),
              ),
            )

   这是整个抠出来的混淆逻辑,先进行第一步的处理
  
[Asm] 纯文本查看 复制代码
_0x1b59d6["PjQNt"](_0x145c35["encryptLong"](_0x42739f), _0x1b59d6["pmrAl"](md5, _0x1b59d6['vazGS'](_0x4070dc["vConfig"][_0x1b59d6.priWD], _0x4070dc["VVGww"][_0x1b59d6['FCsae']],),),)

   这样我们可以得到一个比较清晰的代码,我们再将这段代码的执行逻辑给分别执行,不再嵌套(不能嫌麻烦,嫌麻烦就学ast)。
   
[JavaScript] 纯文本查看 复制代码
const configPriWD = '431f916e31dc956477dea02c973d4cd9';  // captchaIdconst vvgwwFCsae = '074c78f4703210e56886940734b014b6';   // token
const vazGSResult = configPriWD + vvgwwFCsae
const pmrAlResult = md5(vazGSResult)
const encryptedLong = _0x145c35["encryptLong"](_0x42739f); // 轨迹 _0x42739f  rsa
const finalResult = encryptedLong + pmrAlResult

               这样我们就能愉快的分析加密逻辑了(先简单写死),很明显就是一个rsa结合md5然后再拼接,这样我们report参数整体就实现了,接下来就是python和密码学的结合了。
  首先要注意的几点
(1)rsa加密的长度有限制,参考文章分组密码模式,采用ecb模式处理
(2)pck8转化pck1格式,参考文章RSA密钥格式解析&转换(借助python偷懒做兼容)
(3)公钥存储的换行符处理
  有兴趣可以看一下文章,给一个参考代码:
  
[Python] 纯文本查看 复制代码
import base64from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5

def get_max_chunk_size(rsa_value):
    key_size_bytes = rsa_value.size_in_bytes()
    return key_size_bytes - 11


def format_pem_key():
    with open('public.pem', 'r') as f:
        key_str = f.read()
    base64_content = key_str.split('-----BEGIN PUBLIC KEY-----')[1].split('-----END PUBLIC KEY-----')[0]
    formatted_base64 = "\n".join(
        base64_content[i:i + 64] for i in range(0, len(base64_content), 64)
    )
    standard_pem = (
            "-----BEGIN PUBLIC KEY-----\n" +
            formatted_base64 + "\n" +
            "-----END PUBLIC KEY-----"
    )
    with open("public.pem", "w") as pem_file:
        pem_file.write(standard_pem)


def rsa_encrypt(rsa_key, enc_data):
    key = base64.b64decode(rsa_key).decode('utf-8')
    with open('public.pem', 'w') as f:
        f.write(key)
    format_pem_key()
    with open('public.pem', 'r') as f:
        key_value = f.read()

    pk = RSA.import_key(key_value)
    cipher = PKCS1_v1_5.new(pk)
    max_chunk_size = get_max_chunk_size(pk)
    text_bytes = enc_data.encode('utf-8')
    encrypted_chunks = []
    # 分段加密
    for i in range(0, len(text_bytes), max_chunk_size):
        chunk = text_bytes[i:i + max_chunk_size]
        encrypted_chunk = cipher.encrypt(chunk)
        encrypted_chunks.append(encrypted_chunk)

    # 合并所有分段并 Base64 编码
    encrypted_bytes = b''.join(encrypted_chunks)
    encrypted_base64 = base64.b64encode(encrypted_bytes).decode('utf-8')
    return encrypted_base64

   3、滑块处理分析
              
上面还有一个轨迹和length参数没有处理都是和滑块有关系的。我们知道背景图返回时就是乱的,说明在前端对背景图进行了处理,我们这里借助事件侦听断点进行分析
   
         我是菜狗用的中文的,大佬们英文界面就是canvas,然后刷新图片加载
  
      
       简单分析一下里面的逻辑,经常补环境的大佬应该熟悉这个方法,创建一个画布,应该是存放滑块的地方,说明这里还没有进行一个处理,继续往下看

   这后面跟着的是几个方法,我们直接都下上断点看看他的逻辑。

  很有意思,先获取了_0x251234的值,又将它传入下一个方法获得了一个32位的数组,我们知道滑块的条数是32,这个会不会是滑块的正确顺序。
  
       分析一下逻辑发现他是对一个地址进行了切片,这个还有点眼熟,好像就是滑块的背景图地址,取得格式前面的那一串字符
  
        这一部分搞定了,我们来看数组生成的逻辑
      
        _0x1ac65f( _0x18d30b[_0x3e1920(0x35a, '\x69\x4a\x58\x55')])(_0x251234)利用 _0x1ac65f( _0x18d30b[_0x3e1920(0x35a, '\x69\x4a\x58\x55')])函数对_0x251234也就是背景图的信息进行处理
跟进去看看逻辑。
  
[JavaScript] 纯文本查看 复制代码
_0x3834dc[_0x11c544(0x42c, '\x5b\x32\x33\x28')] = function(        _0x121c70,
        _0x10dcdd,
      ) {
        const _0x74b07a = _0x11c544;
        const _0x58b550 = {
          uKQaL: _0x74b07a(0x289, '\x6e\x74\x43\x4d'),
          LRljw(_0x52e5da, _0x58ed7b) {
            return _0x52e5da === _0x58ed7b;
          },
        };
        function _0x1c18e2(_0x2a7547, _0x2daf7e) {
          const _0x208f76 = _0x74b07a;
          if (_0x2a7547[_0x58b550[_0x208f76(0x36a, '\x4c\x41\x6f\x36')]])
            return _0x2a7547[_0x58b550[_0x208f76(0x36a, '\x4c\x41\x6f\x36')]](
              _0x2daf7e,
            );
          for (
            let _0x282371 = 0x0,
              _0xca62fe = _0x2a7547[_0x208f76(0x3ec, '\x66\x40\x31\x4c')];
            _0x282371 < _0xca62fe;
            _0x282371++
          )
            if (
              _0x58b550[_0x208f76(0x6c9, '\x5e\x33\x58\x44')](
                _0x2a7547[_0x282371],
                _0x2daf7e,
              )
            )
              return !0x0;
          return !0x1;
        }
        for (
          var _0x10dcdd = [], _0x66e9ff = 0x0;
          _0x187f8f[_0x74b07a(0x30c, '\x6f\x5e\x24\x79')](
            _0x66e9ff,
            _0x121c70[_0x187f8f.dxGbO],
          );
          _0x66e9ff++
        ) {
          let _0x285e49 = _0x121c70[
            _0x187f8f[_0x74b07a(0x5bb, '\x4a\x5b\x72\x57')]
          ](_0x66e9ff);
          if (_0x187f8f[_0x74b07a(0x5a4, '\x28\x67\x62\x55')](0x20, _0x66e9ff))
            break;
          for (
            ;
            _0x1c18e2(
              _0x10dcdd,
              _0x187f8f[_0x74b07a(0x2ec, '\x51\x57\x47\x76')](_0x285e49, 0x20),
            );

          )
            _0x285e49++;
          _0x10dcdd.push(_0x187f8f.yMCrD(_0x285e49, 0x20));
        }
        return _0x10dcdd;
      }

     还是一段混淆的代码,老规矩,直接处理掉混淆内容
  
[JavaScript] 纯文本查看 复制代码
_0x3834dc = function (_0x121c70) {
    function _0x1c18e2(_0x2a7547, _0x2daf7e) {
        if (_0x2a7547["includes"]) {
            return _0x2a7547["includes"](_0x2daf7e);
        }
        for (let _0x282371 = 0, _0xca62fe = _0x2a7547.length; _0x282371 < _0xca62fe; _0x282371++) {
            if (_0x2a7547[_0x282371] === _0x2daf7e) {
                return true;
            }
        }
        return false;
    }

    var _0x10dcdd = [];
    for (var _0x66e9ff = 0; _0x66e9ff < 32; _0x66e9ff++) {
        let _0x285e49 = _0x121c70['charCodeAt'](_0x66e9ff);
        if (32 === _0x66e9ff) {
            break;
        }
        for (; _0x1c18e2(_0x10dcdd, _0x285e49 % 32);) {
            _0x285e49++;
        }
        _0x10dcdd.push(_0x285e49 % 32);
    }
    return _0x10dcdd;
};

     处理完成后我们可以简单测试一下结果是不是符合。


很好,非常符合,我们再用python简单实现一下同样的逻辑
[Python] 纯文本查看 复制代码
def canvas_list(input_string):    def list_contains(lst, value):
        return value in lst

    result = []
    for i in range(32):
        if i >= len(input_string):
            break
        char_code = ord(input_string[i])
        while list_contains(result, char_code % 32):
            char_code += 1
        result.append(char_code % 32)
    return result

     
   ok,搞定了,
   
三、总结

   轨迹算法和图像还原的内容我就不献丑,纯应付处理代码,论坛里大佬有很多这样的算法,可以去===借鉴借鉴===,整个加密核心就是rsa的处理,更多的需要去看文章。

免费评分

参与人数 12吾爱币 +9 热心值 +11 收起 理由
MissJz + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
allspark + 1 + 1 用心讨论,共获提升!
chuilishi + 1 + 1 用心讨论,共获提升!
tomoon + 1 热心回复!
pelephone + 1 + 1 我很赞同!
抱歉、 + 1 用心讨论,共获提升!
xiaofeng4929 + 1 谢谢@Thanks!
Issacclark1 + 1 谢谢@Thanks!
FitContent + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
tvbox007 + 1 + 1 我很赞同!
dandelion2sunny + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
悦来客栈的老板 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

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

推荐
linuxdev 发表于 2025-7-2 15:38
我遇到过很多次_0x开头的js加密,根据经验判断很有可能是obfuscator这个工具加密的,有现成的自动化解法,去除加密后逆向容易一些
3#
oscar4222001 发表于 2025-7-2 08:58
4#
earnm 发表于 2025-7-2 09:26
5#
 楼主| buluo533 发表于 2025-7-2 09:35 |楼主
earnm 发表于 2025-7-2 09:26
有混淆还是有一点难度的,越后面越看不懂

ast,可以看看蔡老板的视频和文章
6#
earnm 发表于 2025-7-2 09:43
buluo533 发表于 2025-7-2 09:35
ast,可以看看蔡老板的视频和文章

好的,谢谢您,请问有链接吗,可以提供一个不?
7#
 楼主| buluo533 发表于 2025-7-2 09:47 |楼主
earnm 发表于 2025-7-2 09:43
好的,谢谢您,请问有链接吗,可以提供一个不?

悦来客栈老板,小破站搜就行了
8#
earnm 发表于 2025-7-2 09:51
buluo533 发表于 2025-7-2 09:47
悦来客栈老板,小破站搜就行了

ok,谢了
9#
禁惹尘埃 发表于 2025-7-2 09:53
很详细的分析过程,值得学习
10#
zteuniv 发表于 2025-7-2 15:47
分析过程详细,值得学习
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2025-7-20 15:59

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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