吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 11200|回复: 176
收起左侧

[Web逆向] 某验反爬滑块逆向分析

    [复制链接]
gouzi123 发表于 2024-7-3 17:23
本帖最后由 gouzi123 于 2024-7-5 15:35 编辑

url:[滑动模式 (geetest.com)](https://www.geetest.com/demo/slide-float.html)

一些废话

最近在论坛看来很多大佬的文章,不愧是精华,非常详细,当然自己在这期间也遇到很多问题,不过好在成功了。
写篇文章看看能否骗个精华,混点CB。如果分享的内容已经有人写过,或者没啥技术含量,还望各位坛友多多包涵{:301_974:}

免责声明:本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关. 本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请联系作者立即删除.

链接: aHR0cHM6Ly93d3cuZ2VldGVzdC5jb20vZGVtby9zbGlkZS1mbG9hdC5odG1s

一、抓包分析

1.1 验证流程分析

1.1.1 register-slide-official:(有用)

image-20240617114958912.png

image-20240617115216999.png
第一次刷新页面,发现就有这个包,register-slide-official:

    • 表示向极验平台进行请求发送,注册/申请一个验证码
    • url:https://www.geetest.com/demo/gt/register-slide?t=1718596177195
    • 参数:t,表示的是一个实时的时间戳
    • 响应数据:
    • challenge和gt,表示向极验注册/申请到的验证码的唯一标识。并且这两个参数会在后期经常被用到。

1.1.2  gettype:(有用)

image-20240617123353309.png

  • 该请求的目的是获取极验安全校验的核心js文件fullpage.js和极验提供的各类型验证码对应的js文件。该请求流程是必须要有的。

1.1.3  js/fullpage.9.1.9-devcs9.js(核心加密文件)

image-20240617123615797.png

    • 该js文件就是极验验证码的核心文件,相关的验证算法、指纹校验模拟等都被包含在该核心文件中。后期逆向时,主要针对就是该文件中相关的js代码。

1.1.4 get.php:(有用)

image-20240617123639258.png
image-20240617123655899.png

  • 获取之前注册/申请好的验证码的数据包。其中,请求参数w需要进行逆向。

1.2 点击验证码后数据包捕获:

image-20240617123735485.png

1.2.1 ajax.php:(有用)

image-20240617123826036.png

  • ajax请求,用于向极验请求指定类型的验证码,会发现响应数据中有result:slider就表示请求滑块类型验证码,然后status:success表示请求成功。

  • 其中请求参数w是需要逆向的。callback参数是geetest结合时间戳的形式

1.2.2 xxx.webp:

image-20240617123909665.png

  • 发现是被打乱顺序的验证码图片, 跟jmcomic中分割图片类似,可以找它的切割算法,然后用python中PIL解决

1.2.3 get.php:(有用)

image-20240617124017974.png

  • 用于加载还原顺序后的滑动验证码的数据包。其中并没有需要逆向的请求请求参数,不过其中的callback参数是geetest结合时间戳的形式。

1.3 滑动滑块后数据包捕获

1.3.1 ajax.php:(有用)

  • 滑动状态验证的数据包,验证滑动行为是否合法是否滑动成功。响应数据中就会有message和success的值表示是否验证成功。如果滑动成功还会出现一个validate的键值对,表示滑动验证成功后的标识,后面只要携带该标识进行其他请求发送,即可表示为验证成功后的请求发送行为。

  • 请求参数中有一个w需要逆向,该w就表示用户滑动的轨迹加密后的内容。

image-20240619163219793.png
image-20240618200533849.png
注意:这里的challenge已经发生变化,多了几位,如果不细心根本发现不了,还以为逆向结果不对,非常ex,具体的后面再聊吧

既然分析完了,就开始愉(恶)快(心)的逆向之旅吧~

二、第一次逆向W

2.1 register-slide-official

这个包比较简单,没啥逆向参数,很容易就请求到了结果,先安稳一下你的心情

import requests
import time

session = requests.Session()
t = str(int(time.time() * 1000))

headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Edg/126.0.0.0',
}

params = {
    't': t,
}

#================register-slide数据包请求===========================
response_text = session.get(
    'https://www.geetest.com/demo/gt/register-slide-official',
    params=params,
    headers=headers,
).json()
gt = response_text['gt']#咱最关键要的是gt和challenge这两个参数
challenge = response_text['challenge']
print(gt,challenge)

image-20240619165803828.png

2.2 gettype.php

需要携带这两参数,好说

image-20240619170244894.png

#================gettype数据包请求===========================
t = str(int(time.time() * 1000))
url = 'https://api.geetest.com/gettype.php'
params = {
    'gt':gt,
    'callback':"geetest_"+t
}
get_ret = session.get(url=url,headers=headers,params=params).text

image-20240619170344697.png

2.3 fullpage.9.1.9-devcs9.js

这个就是咱核心的js文件,本来以为是动态的,结果多刷新几次发现参数都基本上没变,可能是手下留情了,那就不需要进行请求

2.4  get.php

image-20240619170901512.png
好,这个就是咱第一次的重点要关注的东西了,看到这复杂的载荷,你看看这w,多长啊,而且还有恶心的混淆,不说了,考验咱逆向功底的时候到了,八仙过海,各显神通!

2.4.1 寻找加密位置

那么如何寻找加密位置呢?首先想的就是w全局搜索,但结果嘛,emmm,不用想,肯定混淆过了

image-20240619171220505.png
ps:很多大佬直接搜索定位"\u0077" ,这也是最方便的地方,不过为了提高一下自己js逆向的水平,我准备换一种方式

搜一下interceptor

image-20240619171617146.png
拦截器肯定也是混淆过了

那hook能不能hook住它加密的位置呢?也就是最后执行JSON.stringfy的位置,来到最开始的地方打上断点

image-20240619171757545.png
不行,说明它内部可能又重新给这些函数重写了,或者换了个名字,诶

那试一下内存漫游(ast-hook-for-js-RE-master)

这里是志远大佬录制的视频教程,建议观看此视频教程安装:https://www.bilibili.com/video/BV1so4y1o7qr/

注意一下启动的是proxy-server就行

启动之后直接搜索值就行

image-20240619173214133.png
image-20240619173227649.png
image-20240619173300219.png
发现是在这里,记住一些明显的特征,方便以后搜索,"\u0077"   i + r  t[$_CEGDT(1040)]

然后就可以切换回来了,直接全局搜索

image-20240619173646575.png
注意关一下那啥正则表达式之类的东西,然后切换一下关键词搜索,直到找到像这样就一两个结果的舒服

分别进去打上断点,然后刷新看看断到哪了

image-20240619173832429.png
发现就在这里,芜湖,原来藏在这,混淆太ex了,用用文本替换+AST来解一下混淆先

2.4.2 解混淆

首先,是把所有的字符串还原成人可以看到的吧,比如那些unicode全都给它弄一下,这个简单,网上工具很多,都不用自己写AST代码

这里说一下最常用的吧,就是V神的v-jstool, 网上很容易搜索到,就普通解混淆就行

image-20240619174428460.png

image-20240619174453678.png
这样看着顺眼点,但是还不够,我要那些t[xxx]也还原成字符串,该怎么办呢? 毕竟大括号里的内容都不一样,有的的GDT、GEK这类的

提出这个想法的非常好,那就不妨先看看该函数开头,这些值是哪来的

image-20240619174737117.png
image-20240619175148374.png

var $_CEGDT = LIuDu.$_Ca;//这个是一个函数
var $_CEGCS = ["$_CEGGw"].concat($_CEGDT); //concat方法用于合并两个或多个数组,$_CEGCS = ["$_CEGGw","$_CEGDT"]
var $_CEGEk = $_CEGCS[1];//取出该数组的第一个值 $_CEGEk = $_CEGDT
$_CEGCS.shift();//删除第一个元素,并返回该元素的值 $_CEGCS = [$_CEGDT]
var t = this;//全局变量,里面一大堆的函数

/**
通过上诉的分析可以知道,$_CEGDT=$_CEGCS=$_CEGEk=LIuDu.$_Ca=LIuDu.$_Ca,用人话说就是这些带$的都是同一个值
可以看到,极验很多函数都有这个操作,这下不得不用AST来干它了
我的思路是把这前面四行删掉,然后让一个变量等于LIuDu.$_Ca这个函数,然后将所有带$的给它统一命名这个变量即可

**/
  • 去除前面四段无效代码
//删除前四行无效代码

//var $_CJDe = LIuDu.$_Ca;
traverse(ast, {
    VariableDeclaration: function (path) {
        if (path.node.declarations.length === 1 && path.node.declarations[0].init) {
            if (path.node.declarations[0].init.computed === false && path.node.declarations[0].id.name !== "bobo") { //注意这里不能删除刚刚赋值的变量
                //删除即可

                path.remove()

            }
        }
    }
})
//var $_CJCW = ["$_CJGo"].concat($_CJDe);
traverse(ast, {
    VariableDeclaration: function (path) {
        if (path.node.declarations.length === 1 && path.node.declarations[0].init) {
            if (path.node.declarations[0].init.callee && path.node.declarations[0].init.callee.computed === false) {
                //删除即可
                //console.log(path.node.declarations[0].init.callee.property.name)
                path.remove()

            }
        }
    }
})

//var $_CJER = $_CJCW[1];
traverse(ast, {
    VariableDeclaration: function (path) {
        if (path.node.declarations.length === 1 && path.node.declarations[0].init) {
            if (path.node.declarations[0].init.computed === true && path.node.declarations[0].init.property.type === "NumericLiteral") {
                //删除即可
                if (path.node.declarations[0].init.property.value === 1) {
                    path.remove()
                }

            }
        }
    }
})

//$_DAHHO.shift();
traverse(ast, {
    ExpressionStatement: function (path) {
        if (path.get("expression.type").node === "CallExpression" && path.get("expression.callee.type").node === "MemberExpression") {
            if (path.get("expression.callee.computed").node === false && path.get("expression.callee.property.name").node === "shift") {
                //删除即可
                //console.log(path.get("expression.callee.property.name").node)
                path.remove()
            }

        }
    }
})
  • 变量名替换

    traverse(ast, {
      CallExpression: {
          exit:function (path) {
          if(path.node.arguments.length === 1 && path.node.callee.type === "Identifier"){
              if(path.node.arguments[0].type === "NumericLiteral"){
                  path.get("callee").replaceInline(types.memberExpression(types.Identifier("bobo"), types.Identifier("$_DDIBY"), false)) //将函数名替换成bobo
              }
          }
      }
      }
    })
    
  • 将LIuDu.$_AD函数写入内存,同时改写函数名为bobo

    //LIuDu.$_AD写入到内存当中
    traverse(ast, {
      ExpressionStatement: function (path) {
          if (path.get("expression.operator").node === "=" && path.get("expression.left.computed").node === false) {
              if (path.node.expression.left.object && path.node.expression.left.property) {
                  if (path.node.expression.left.property.name === "$_AD") {
                      //
                      path.get("expression.left").replaceWith(types.Identifier("bobo"))
                      //console.log(path.toString())
                      eval(path.toString())
                  }
              }
    
          }
    
      }
    })
    console.log(bobo)

    image-20240620222657050.png

  • 去除控制流平坦化

    image-20240621154458083.png
    进过对比之后发现,删除之后body会发生变化,原来是var和for变成一连串的Fun

    image-20240621155041159.png
    image-20240621155116556.png
    我们需要做的就是把consequent下一系列东西,放到BlockStatement下的body当中即可

    /*
    var $_DDDCy = LIuDu.$_DV()[12][18];
          for (; $_DDDCy !== LIuDu.$_DV()[8][17];) {
            switch ($_DDDCy) {
              case LIuDu.$_DV()[8][18]:}}
    
    经过观察发现,几乎很多无效的控制流都包括LIuDu.$_DV(),且是从上到下依次执行的,所以咱的思路就是先删除VariableDeclaration
    然后再判断ForStatement是不是也包含 LIuDu.$_DV
    
    */traverse(ast, {
          FunctionDeclaration(path) {
              let varDeclaration = null;
              let forLoop = null;
              let switchStatements = [];
    
              // Traverse the function body to find the specific pattern
              path.traverse({
                  //var $_DCGCv = LIuDu.$_DV()[8][18];
                  VariableDeclarator(path) {
                      if (path.node.init && path.node.init.type === "MemberExpression" && path.node.init.computed === true &&
                          path.node.init.object.object && path.node.init.object.object.type === "CallExpression"
                      ) {
                          if (path.node.init.object.object.callee.type==="MemberExpression"&&path.node.init.object.object.callee.object && path.node.init.object.object.callee.object.name === 'LIuDu') {
                              varDeclaration = path.parentPath;
                          }
                      }
    
                  },
                  //for (; $_DCGCv !== LIuDu.$_DV()[4][16];) {
                  ForStatement(path) {
                      if (path.node.test && types.isBinaryExpression(path.node.test) && path.node.test.right.type === "MemberExpression"&&path.node.test.right.object.object) {
                          if (path.node.test.right.object.object.type === "CallExpression"&&path.node.test.right.object.object.callee && path.node.test.right.object.object.callee.object.name === 'LIuDu') {
                              forLoop = path;
                              // path.get("test").remove()
                          }
    
                      }
                  },
                  SwitchCase(path) {
                      if (path.parentPath && path.parentPath.parentPath && path.node.consequent) {
                          for(let i = 0; i < path.node.consequent.length; i++){
                              if(path.node.consequent[i].type !== "BreakStatement"){
                                  //console.log(path.node.consequent[i])
                                  switchStatements.push(path.node.consequent[i]);
                              }
    
                          }
    
                      }
                  }
              });
    
              // If the pattern matches, process the switch cases and replace the function body
              if (varDeclaration && forLoop && switchStatements.length > 0) {
                  const newBody = types.blockStatement(switchStatements);
                  path.get('body').replaceWith(newBody);
              }
          }
      }
    );
    
  • 执行bobo.$_DDIBY函数

    //执行bobo函数
    traverse(ast, {
      CallExpression: function (path) {
          if (path.node.callee.object && path.node.callee.object.name === "bobo") {
              //console.log(path.toString())
              result = eval(path.toString())
              if (typeof result === "string"){
                  path.replaceInline({type:"StringLiteral", value: result})
              }
    
          }
      }
    })

image-20240620230549418.png
这样调试就非常舒服了

2.4.3 开始逆向

如果不能断住,可能是找不到LIuDu,可以在函数头上添加bobo = LIuDu.$_AD

可能有几个break没删干净,手动删一下就可以

var t = this;
        var n = t["$_EI_"];
        if (!n["gt"] || !n["challenge"]) return G(I("config_lack", t));
        var e = t["$_BJDj"]["$_BIBN"]();
        t["$_CCF_"] = e;
        t["$_EJK"]["cc"] = n["cc"];
        t["$_EJK"]["ww"] = n["supportWorker"];
        t["$_EJK"]["i"] = e;
        var r = t["$_CCGr"]();
        var o = $_BFc()["encrypt1"](de["stringify"](t["$_EJK"]), t["$_CCHC"]());
        var i = p["$_HET"](o);
        var s = {
          "gt": t["$_EJK"]["gt"],
          "challenge": t["$_EJK"]["challenge"],
          "lang": n["lang"],
          "pt": t["$_BJHm"],
          "client_type": t["$_BJIk"],
          "w": i + r
        };

/**
我们可以很舒服的看到, w是i和r的值拼接形成的

r是直接调用t["$_CCGr"]函数

i是通过执行p["$_HET"]函数,传进去参数o得到的,而参数o则是上面某个加密函数生成的

而其它的值可以给它还原一下,比如那些this啥的不好改写

**/

image-20240621112652490.png
var n = t["$EI"]; 这个是一系列大的字典,里面一大堆的值,先全部复制下来,然后看看有啥需要替换的吧

var n = {
    "$_BJBl": 1718940058915,//时间戳
    "protocol": "https://",
    "gt": "019924a82c70bb123aae90d483087f94", //这两个是需要返回的,动态的
    "challenge": "a6eddf7c0bc30c8325c1ad94e703b736",
    "offline": false,
    "new_captcha": true,
    "product": "float",
    "width": "300px",
    "https": true,
    "api_server": "apiv6.geetest.com",
    "type": "fullpage",
    "static_servers": [
        "static.geetest.com/",
        "static.geevisit.com/"
    ],
    "beeline": "/static/js/beeline.1.0.1.js",
    "voice": "/static/js/voice.1.2.4.js",
    "click": "/static/js/click.3.1.0.js",
    "fullpage": "/static/js/fullpage.9.1.9-devcs9.js",
    "slide": "/static/js/slide.7.9.2.js",
    "geetest": "/static/js/geetest.6.0.9.js",
    "aspect_radio": {
        "slide": 103,
        "click": 128,
        "voice": 128,
        "beeline": 50
    },
    "cc": 20,
    "supportWorker": true,
    "$_FFW": {
        "pt": 0
    }
};

好像除了时间戳和gt、challenge需要变,其它的都是固定的

image-20240621112549286.png
这个if判断返回false,可以直接删掉

image-20240621113031751.png
下面这个e值,似乎也是固定的,多试几次发现确实如此,那就直接写死就🆗

下面就是需要咱逆向的东西了

2.4.4 r值进行逆向

var r = t["$_CCGr"]

image-20240621113236990.png
进入到 t["$_CCGr"]函数当中,我们知道,外部调用的时候,没有带任何参数,所以e=undefined,先是调用函数,得到一个aeskey然后执行encrypt函数进行AES加密,返回得到结果,

image-20240621113525476.png
注意: 这是第一个坑,如果出现这种情况,说明此时,该网站有缓存之类的,记得清空

image-20240621114622091.png
image-20240621114746653.png
这样才对,我们的aeskey其实是te()函数返回的,所以可以直接将this["$_CCHC"]改写为te函数即可

来到te函数,发现是四个随机数拼接返回的值,直接扣下来就可以

image-20240621125126701.png

function e() {
    return (65536 * (1 + Math["random"]()) | 0)["toString"](16)["substring"](1);
}

function te() {
    return e() + e() + e() + e();
};

key值搞定后,跟进加密的地方看看,看到熟悉的RSA,可恶,它的aeskey竟然是迷惑人的,实际上是RSA,差点被骗

image-20240621191951256.png
两种方式,一种是直接用RSA的加密库node-jsencrypt,当然你要改一下它的源码,因为它里面的加密函数没有暴露出来

也就是说要在最后把这个module.exports改成以下格式才行

module.exports = {
    'JSEncrypt':JSEncryptExports.JSEncrypt,
    'RSAKey':JSEncryptExports.RSAKey
};

另一种的话就是硬扣+稍微一点的补环境+删除无效代码,比如那些检测MouseEvent事件的,之前没删干净的 LIuDu.$_DV()[0]的东西

然后补上window和navigator,最后调用内部函数

这里也有很多坑,比如说最容易弄错的地方吧

image-20240622112849808.png
就是这里,对应原来的这个地方,可能AST还原的时候不精准,把v(0)变成了"stringfy",v(1)变成了"prototype",肯定不止这一个地方,记得网站上也改一下

image-20240622113030029.png
诶,找了一上午原来是这个错误,然后差不多弄弄就应该出来了,扣了500多行

image-20240622113220884.png
芜湖!终于把r值弄出来了

2.4.5 o值进行逆向

var o = $_BFc()["encrypt1"](de["stringify"](t["$_EJK"]), t["$_CCHC"]());

//参数t["$_EJK"](已知)、t["$_CCHC"](未知)
//函数$_BFc()["encrypt1"](未知)、de["stringify"](这不就是JSON.stringify嘛,还想迷惑我)

image-20240622114105981.png
进到t["$_CCHC"]函数当中,wc,好熟悉,这不就是刚才弄的te

先别开心,注意,如果这个时候直接弄te的话,后面会出现decrypt error,必须保持我们这个时候的key和之前r值的key要保持一致才可以,那么如何才能让key一致呢?

很明显,把它生成的值先赋值到全局变量,然后再调用就可以了

image-20240629110802871.png
我就不信这次的encrypt还不是AES加密,跟进去看看

image-20240622114249326.png
iv、ciphertext、words、sigBytes看看,多nice,这不就铁是AES,实在不放心也可以硬扣,但我要赌一把!

function encrypt1(word, key0) {
    var key = CryptoJS.enc.Utf8.parse(key0);  //十六位十六进制数作为密钥
    var iv = CryptoJS.enc.Utf8.parse("0000000000000000");   //十六位十六进制数作为密钥偏移量
    let srcs = CryptoJS.enc.Utf8.parse(word);
    let r = CryptoJS.AES.encrypt(srcs, key, {iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7});
        var o = r['ciphertext']['words'];
    var i = r['ciphertext']['sigBytes'];

    var s = [];
    var a = 0;
    for (; a < i; a++) {
        var _ = o[a >>> 2] >>> 24 - a % 4 * 8 & 255;
        s['push'](_);
    }
    return s;
}

2.4.6 i值进行逆向

var i = p["$_HET"](o);

直接进入函数瞅一眼

image-20240622123649623.png
硬扣吧,也不是啥标准库

image-20240622115035485.png
然后就是缺啥补啥就行了,注意this中的很多函数没有,记得改写,比如o["$_GGd"]

function $_GJQ(e) {
        var t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789()";
        return e < 0 || e >= t["length"] ? "." : t["charAt"](e);
      }
function $_HBB(e, t) {
        return e >> t & 1;
      }
function $_HCO(e, o) {
        var i = this;
        o || (o = i);
        for (var t = function (e, t) {
            for (var n = 0, r = 24 - 1; 0 <= r; r -= 1) if (1 === $_HBB(t, r)) {
              n = (n << 1) + $_HBB(e, r);
            }
            return n;
          }, n = "", r = "", s = e["length"], a = 0; a < s; a += 3) {
          var _;
          if (a + 2 < s) {
            _ = (e[a] << 16) + (e[a + 1] << 8) + e[a + 2];
            n += $_GJQ(t(_, 7274496)) + $_GJQ(t(_, 9483264)) + $_GJQ(t(_, 19220)) + $_GJQ(t(_, 235));
          } else {
            var c = s % 3;
            if (2 == c) {
              _ = (e[a] << 16) + (e[a + 1] << 8);
              n += $_GJQ(t(_, 7274496)) + $_GJQ(t(_, 9483264)) + $_GJQ(t(_, 19220));
              r = '.';
            } else {
              if (1 == c) {
                _ = e[a] << 16;
                n += $_GJQ(t(_, 7274496)) + $_GJQ(t(_, 9483264));
                r = "..";
              }
            }
          }
        }
        return {
          "res": n,
          "end": r
        };
      }
function $_HET(e) {
        var t = $_HCO(e);
        return t["res"] + t["end"];
      }

2.4.7  排除this问题

本来以为可以直接运行了,结果报错说undefined,不用想就知道,肯定是this的问题,现在this指向的是window

image-20240622115614061.png
所以咱直接在window上给它一个$_EJK就行了

window.$_EJK = {
    "gt": "019924a82c70bb123aae90d483087f94",
    "challenge": "4b538439964403c397e52c4653043893",
    "offline": false,
    "new_captcha": true,
    "product": "float",
    "width": "300px",
    "https": true,
    "api_server": "apiv6.geetest.com",
    "protocol": "https://",
    "type": "fullpage",
    "static_servers": [
        "static.geetest.com/",
        "static.geevisit.com/"
    ],
    "beeline": "/static/js/beeline.1.0.1.js",
    "voice": "/static/js/voice.1.2.4.js",
    "click": "/static/js/click.3.1.0.js",
    "fullpage": "/static/js/fullpage.9.1.9-devcs9.js",
    "slide": "/static/js/slide.7.9.2.js",
    "geetest": "/static/js/geetest.6.0.9.js",
    "aspect_radio": {
        "slide": 103,
        "click": 128,
        "voice": 128,
        "beeline": 50
    },
    "cc": 20,
    "ww": true,
    "i": "-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1"
}
function get_f_w(time, gt, challenge) {
    var t = this;
    var n = {
        "$_BJBl": time,
        "protocol": "https://",
        "gt": gt,
        "challenge": challenge,
        "offline": false,
        "new_captcha": true,
        "product": "float",
        "width": "300px",
        "https": true,
        "api_server": "apiv6.geetest.com",
        "type": "fullpage",
        "static_servers": [
            "static.geetest.com/",
            "static.geevisit.com/"
        ],
        "beeline": "/static/js/beeline.1.0.1.js",
        "voice": "/static/js/voice.1.2.4.js",
        "click": "/static/js/click.3.1.0.js",
        "fullpage": "/static/js/fullpage.9.1.9-devcs9.js",
        "slide": "/static/js/slide.7.9.2.js",
        "geetest": "/static/js/geetest.6.0.9.js",
        "aspect_radio": {
            "slide": 103,
            "click": 128,
            "voice": 128,
            "beeline": 50
        },
        "cc": 20,
        "supportWorker": true,
        "$_FFW": {
            "pt": 0
        }
    };
    var e = "-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1"
    t["$_CCF_"] = e;
    t["$_EJK"]["cc"] = n["cc"];
    t["$_EJK"]["ww"] = n["supportWorker"];
    t["$_EJK"]["i"] = e;
    var r = $_CCGr();
    var o = encrypt1(JSON["stringify"](t["$_EJK"]), te());

    var i = $_HET(o);
    var s = {
        "gt": gt,
        "challenge": challenge,
        "lang": n["lang"],
        "pt": t["$_BJHm"],
        "client_type": t["$_BJIk"],
        "w": i + r
    };
    return s
}
//console.log(get_f_w(1718940058915, "019924a82c70bb123aae90d483087f94", "a6eddf7c0bc30c8325c1ad94e703b736"))

image-20240629123614980.png

2.4.8 python调用

咱废话不多说,直接用python调用试试

# ================get.php数据包请求===========================
f = open('第一个w值.js', 'r', encoding='utf-8')
first_js = execjs.compile(f.read())
ret_dic = first_js.call('get_f_w',  gt, challenge)
print(ret_dic)
w = ret_dic['w']
print(w)
url = 'https://api.geetest.com/get.php'
params = {
    "gt": gt,
    "challenge": challenge,
    "lang": "zh-cn",
    "pt": "0",
    "client_type": "web",
    "callback": 'geetest_%s' % t,
    'w': w
}
get_ret = session.get(url=url, headers=headers, params=params).text
print(get_ret)

image-20240629124240196.png
成功!芜湖,过了第一个w

三、第二个W逆向

第二个需要逆向的w出现在点击了【点击按钮进行验证】后出现的第一个ajax.php数据包中。

image-20240701065801489.png

3.1 ajax.php

3.1.1 找w值

注意,这里如果直接搜索的话,记得换一些关键词多试一下,比如"w、["w 、"w": 之类的

或者用之前讲的内存漫游、跟堆栈都行,这里我就直接搜索吧

image-20240701085730318.png
位置就是在这里,发现是$_CEAo传过来的值,那就再搜索

image-20240701085825933.png
发现是这个东西给它赋值的,那就先对它进行改写吧

i["$_CCHC"]这个函数可以跟进去看一下,发现其实就是生成aeskey的地方,咱之前已经生成过了,那就直接传进来就可以

function get_sec_w(gt, challenge, aeskey) {
    var n = {};
    var r = get_r(gt,challenge);
    n["gt"] = gt;
    n["challenge"] = challenge;
    n["lang"] = "zh-cn";
    n["pt"] = 0;
    n["client_type"] = "web";
    n["w"] = p["$_HET"](c["encrypt"](r, aeskey))
    return n
}

发现缺少的就是r值了,以及encrypt函数(之前扣过)

3.1.2 逆向r值

很明显,r值是需要我们去进行逆向的,毕竟往上瞅一眼就知道这家伙绝对不是常量

在变量r处设置断点,刷新后断点停留

r = "{" + i["$_CECC"] + "\"captcha_token\":\"" + n(o["toString"]() + n(n["toString"]()) + n(e["toString"]())) + "\",\"w6nw\":\"fkplx5pf\"}";
  • 先看如下三个toString,o['toString']()、n['toString']()和e['toString']()是什么?

其中o一个自运行函数,n是自运行函数里面的一个内置函数定义,e是var t = ["bbOy"]里的第0个值。

因此o['toString']()是将函数o的定义转换成字符串,n['toString']()是将函数n的定义转换成字符串,e['toString']()是将参数e的值转换成字符串。

注意:在改写get_r函数的时候,已经将函数o和n进行了反混淆,因此这两个函数的实现已经被我们改变了,因此直接基于改变后的o和n函数进行toString()操作得到的函数字符串就和浏览器的toString()结果不一样了。

image-20240701090834395.png
因此,我们就需要换一个没解混淆的浏览器,找一个好用的关键词,然后把o['toString']()、n['toString']()和e['toString']()设置成定值

image-20240701091007458.png
image-20240701091232190.png

3.1.3 i["$_CECC"]

image-20240701091549564.png
现在未知的就是i["$_CECC"],那么我们就去找找看到底在哪里生成的这个值

还是老方法,直接搜索

image-20240701091636298.png

var i = this;
var e = i["$_CAAS"]["$_BIBN"]();
var t = i["$_CAAS"]["$_BICN"]();
var n = i["$_BJDj"]["$_BICN"]();
var r = i["$_BDHU"]["$_BIBN"]();
var o = i["$_EI_"];
var s = $_Gt() - rt;
i["$_CECC"] = "";
for (var a = [
    ["lang", o["lang"] || "zh-cn"], ["type", "fullpage"], 
    ["tt", function(e, t, n) {
    if (!t || !n)
        return e;
    var r;
    var o = 0;
    var i = e;
    var s = t[0];
    var a = t[2];
    var _ = t[4];
    while (r = n["substr"](o, 2)) {
        o += 2;
        var c = parseInt(r, 16);
        var l = String["fromCharCode"](c);
        var u = (s * c * c + a * c + _) % e["length"];
        i = i["substr"](0, u) + l + i["substr"](u);
    }
    return i;
}(e, o["c"], o["s"]) || -1], 
    ["light", r || -1], ["s", H(p["$_HDn"](t))], 
    ["h", H(p["$_HDn"](n))], ["hh", H(n)], 
    ["hi", H(i["$_CCF_"])], 
    ["vip_order", i["vip_order"] || -1], 
    ["ct", i["ct"] || -1], 
    ["ep", i["$_CEDJ"]() || -1], 
    ["passtime", s || -1], 
    ["rp", H(o["gt"] + o["challenge"] + s)]
], _ = 0; _ < a["length"]; _++)
    i["$_CECC"] += "\"" + a[_][0] + "\":" + de["stringify"](a[_][1]) + ",";

很好,又是一大堆的参数,e、t、r、s、p、h等等,歪日, 没办法,慢慢来吧

  • e值进行逆向

image-20240701092431064-17197970721123.png
进入到e函数当中,发现$_BGJ中的东西赋值给了e,然后进过某个不为人知的操作,返回,那先看看这是啥

image-20240701092536383.png
wc,这不就是鼠标轨迹嘛,数组每一个元素就是一个具体的轨迹坐标和触发该坐标的时间,根据上图会发现,基本上是每几毫秒就会记录一个轨迹坐标。此处需要注意:轨迹点和轨迹点之间一定是连续的,要满足人为使用鼠标连续滑动的行为。

那不就好办了,毕竟每个人移动的鼠标轨迹都不同,你总不可能会搜集所有人的轨迹吧?那不就累死。所以为了让参数短一点,就直接模拟那些手速快的,咋的?你不可能因为我手速比别人快就封我吧

image-20240701093231722.png
PS:想练习手速的可以试试,这边最高纪录是6个值,还是单身多年的结果

function() {
    var e = this["$_BGJ"];//将this['$_BGJ']也就是鼠标轨迹赋值给e
    return this["$_BGJ"] = [],//修改成空,且该值后面没用到,可以直接将其删除
        this["$_HDn"](this["$_BHIQ"](e));
}
//下面就需要搞定参数this["$_BHIQ"](e)和函数this["$_HDn"]

进入到this['$_BHIQ']函数当中:

image-20240701155317661.png
查看this'$_BHIQ'\的值:将滑动轨迹被$_BHIQ进行处理,处理方式是获取e中的滑动坐标和每两次滑动时间戳之间的差值。因此,this'$BHIQ'的值可以直接复制即可。(PS:当然,你想硬扣也可以,锻炼一些JS逆向的能力,反正它这东西也不校验,你写死无所谓)

再看看this["$_HDn"]函数

image-20240701155603494.png
发现非常长,你可以选择直接固定返回值,也可以把这个函数扣下来,说不定后面也会用到

记得把那些没删干净的$_DDGIe = LIuDu.$_DV()[4][17]去掉,不然报错麻烦

function $_HDn(e) {
    var p = {
        "move": 0,
        "down": 1,
        "up": 2,
        "scroll": 3,
        "focus": 4,
        "blur": 5,
        "unload": 6,
        "unknown": 7
    };

    function h(e, t) {
        for (var n = String(e)["toString"](2), r = "", o = n["length"] + 1; o <= t; o += 1)
            r += "0";
        return n = r + n;
    }

    function f(e) {
        var t = [];
        var n = e["length"];
        var r = 0;
        while (r < n) {
            var o = e[r];
            var i = 0;
            while (1) {
                if (16 <= i)
                    break;
                var s = r + i + 1;
                if (n <= s)
                    break;
                if (e[s] !== o)
                    break;
                i += 1;
            }
            r = r + 1 + i;
            var a = p[o];
            if (0 != i) {
                t["push"](8 | a);
                t["push"](i - 1);
            } else {
                t["push"](a);
            }
        }
        for (var _ = h(32768 | n, 16), c = "", l = 0, u = t["length"]; l < u; l += 1)
            c += h(t[l], 4);
        return _ + c;
    }

    function c(e, t) {
        for (var n = [], r = 0, o = e["length"]; r < o; r += 1)
            n["push"](t(e[r]));
        return n;
    }

    function d(e, t) {
        e = function _(e) {
            var t = 32767;
            var n = (e = c(e, function (e) {
                return t < e ? t : e < -t ? -t : e;
            }))["length"];
            var r = 0;
            var o = [];
            while (r < n) {
                var i = 1;
                var s = e[r];
                var a = Math["abs"](s);
                while (1) {
                    if (n <= r + i)
                        break;
                    if (e[r + i] !== s)
                        break;
                    if (127 <= a || 127 <= i)
                        break;
                    i += 1;
                }
                1 < i ? o["push"]((s < 0 ? 49152 : 32768) | i << 7 | a) : o["push"](s);
                r += i;
            }
            return o;
        }(e);
        var n;
        var r = [];
        var o = [];
        c(e, function (e) {
            var t = Math["ceil"](function n(e, t) {
                return 0 === e ? 0 : Math["log"](e) / Math["log"](t);
            }(Math["abs"](e) + 1, 16));
            0 === t && (t = 1);
            r["push"](h(t - 1, 2));
            o["push"](h(Math["abs"](e), 4 * t));
        });
        var i = r["join"]("");
        var s = o["join"]("");
        return t ? n = c(function a(e, t) {
            var n = [];
            return c(e, function (e) {
                if (t(e)) {
                    n["push"](e);
                }
            }),
                n;
        }(e, function (e) {
            return 0 != e && e >> 15 != 1;
        }), function (e) {
            return e < 0 ? "1" : "0";
        })["join"]("") : n = "",
        h(32768 | e["length"], 16) + i + s + n;
    }

    return function (e) {
        for (var t = [], n = [], r = [], o = [], i = 0, s = e["length"]; i < s; i += 1) {
            var a = e[i];
            var _ = a["length"];
            t["push"](a[0]);
            n["push"](2 === _ ? a[1] : a[2]);
            3 === _ && (r["push"](a[1][0]),
                o["push"](a[1][1]));
        }
        var c = f(t) + d(n, !1) + d(r, !0) + d(o, !0);
        var l = c["length"];
        return l % 6 != 0 && (c += h(0, 6 - l % 6)),
            function u(e) {
                for (var t = "", n = e["length"] / 6, r = 0; r < n; r += 1)
                    t += "()*,-./0123456789:?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"["charAt"](window["parseInt"](e["slice"](6 * r, 6 * (r + 1)), 2));
                return t;
            }(c);
    }(e);
}

image-20240701155910909.png
搞定!

  • t值进行逆向

image-20240701160341458.png

//在console中查看i['$_BJJL']['$_BICE']函数的定义,对其进行反混淆和逆向
function get_t_value() {
    /*
        此处打上断点,在console中查看 this['$_BGC']是一个空数组[]
        而this['$_HDz']就是前面逆向好的$_HDz函数,因此下代码可以改为:
    */
    // return this['$_HDz'](this['$_BGC']);
    return $_HDn([]); //$_HDn之前也扣过了
}
  • n值进行逆向

image-20240701163144932.png

function(e, t) {
    var n = this;
    var r = n["$_BGJ"];
    var o = [];
    return new $_DJs(n["$_BJAQ"]())["$_EBo"](function(e) {
        var t = r[e];
        o["push"](n["$_BIHp"](t) ? n["$_BIDR"] : t);
    }),
        o["join"]("magic data");
}

image-20240701163345085.png
image-20240701163557648.png
n["$_BIDR"] 和 r是一个定值,那下面的就是需要\$_DJs、$_BJAQ、$_EBo函数

进入到BJAQ函数内部

image-20240701165816312.png
image-20240701165853821.png
BIIQ是一个大数组,好像是固定的,那就直接扣

image-20240701170015515.png
image-20240701170223186.png
扣成这样就可以了,然后运行一下,看看跟网站的结果是不是一样的

image-20240701170317344.png
接着是$_BIHp函数

返回一个布尔值

function $_DJs(e) {
    this["$_BAEj"] = e || [];
}
function $_BJAQ() {
    var $_BIIQ = [
        "A",
        "ARTICLE",
        "ASIDE",
        "AUDIO",
        "BASE",
        "BUTTON",
        "CANVAS",
        "CODE",
        "IFRAME",
        "IMG",
        "INPUT",
        "LABEL",
        "LINK",
        "NAV",
        "OBJECT",
        "OL",
        "PICTURE",
        "PRE",
        "SECTION",
        "SELECT",
        "SOURCE",
        "SPAN",
        "STYLE",
        "TABLE",
        "TEXTAREA",
        "VIDEO"
    ];
    var $_BIJu = [
        "DIV",
        "P",
        "UL",
        "LI",
        "SCRIPT"
    ]
    return ["textLength", "HTMLLength", "documentMode"]["concat"]($_BIIQ)["concat"](["screenLeft", "screenTop", "screenAvailLeft", "screenAvailTop", "innerWidth", "innerHeight", "outerWidth", "outerHeight", "browserLanguage", "browserLanguages", "systemLanguage", "devicePixelRatio", "colorDepth", "userAgent", "cookieEnabled", "netEnabled", "screenWidth", "screenHeight", "screenAvailWidth", "screenAvailHeight", "localStorageEnabled", "sessionStorageEnabled", "indexedDBEnabled", "CPUClass", "platform", "doNotTrack", "timezone", "canvas2DFP", "canvas3DFP", "plugins", "maxTouchPoints", "flashEnabled", "javaEnabled", "hardwareConcurrency", "jsFonts", "timestamp", "performanceTiming", "internalip", "mediaDevices"])["concat"]($_BIJu)["concat"](["touchEvent"]);

}

function $_EBo(e) {
    var t = [
        "textLength",
        "HTMLLength",
        "documentMode",
        "A",
        "ARTICLE",
        "ASIDE",
        "AUDIO",
        "BASE",
        "BUTTON",
        "CANVAS",
        "CODE",
        "IFRAME",
        "IMG",
        "INPUT",
        "LABEL",
        "LINK",
        "NAV",
        "OBJECT",
        "OL",
        "PICTURE",
        "PRE",
        "SECTION",
        "SELECT",
        "SOURCE",
        "SPAN",
        "STYLE",
        "TABLE",
        "TEXTAREA",
        "VIDEO",
        "screenLeft",
        "screenTop",
        "screenAvailLeft",
        "screenAvailTop",
        "innerWidth",
        "innerHeight",
        "outerWidth",
        "outerHeight",
        "browserLanguage",
        "browserLanguages",
        "systemLanguage",
        "devicePixelRatio",
        "colorDepth",
        "userAgent",
        "cookieEnabled",
        "netEnabled",
        "screenWidth",
        "screenHeight",
        "screenAvailWidth",
        "screenAvailHeight",
        "localStorageEnabled",
        "sessionStorageEnabled",
        "indexedDBEnabled",
        "CPUClass",
        "platform",
        "doNotTrack",
        "timezone",
        "canvas2DFP",
        "canvas3DFP",
        "plugins",
        "maxTouchPoints",
        "flashEnabled",
        "javaEnabled",
        "hardwareConcurrency",
        "jsFonts",
        "timestamp",
        "performanceTiming",
        "internalip",
        "mediaDevices",
        "DIV",
        "P",
        "UL",
        "LI",
        "SCRIPT",
        "touchEvent"
    ]
    if (t["map"])
        return new $_DJs(t["map"](e));
    for (var n = [], r = 0, o = t["length"]; r < o; r += 1)
        n[r] = e(t[r], r, this);
    return new $_DJs(n);
}

$_DJs.prototype.$_EBo = $_EBo;//它new对象是在原型链上的函数

function $_BIHp(e) {
    return void 0 === e;
}

function $_BICN2(e, t) {
    var r = {};
    var o = [];

    return new $_DJs($_BJAQ())["$_EBo"](function (e) {
        var t = r[e];
        o["push"]($_BIHp(t) ? -1 : t);
    }),
        o["join"]("magic data");
}

image-20240701171826325.png

  • r值进行逆向

image-20240701171902530.png
image-20240701172621300.png

this["$_BGJ"]是固定的数组而y、b、x都是false,直接改成false即可

function $_BIBN2() {
    var e = ["DIV_0"] || [];
    return this["$_BGJ"] = [],
        this["$_BGIn"] = 0,
        this["$_BGJK"] = [],
        false,
        e["join"]("|");

}
  • o值是一个固定的字典(当然,可能有像challenge是变化的,咱先固定)

image-20240701175026148.png

  • s值进行逆向

s = $_GI() - rt;

进入到_GI函数内部

image-20240701180300282.png
image-20240701180336278.png

/*
    1、分析s的生成原理。在console中查看$_GI是一个函数,该函数最终返回值是:
    return new Date()['getTime']()表示当前时间戳。
    rt在console中查看是另一个较早之前的时间戳。
    因此s = 当前时间戳 - 较早之前的时间戳,所以s用来表示用户在滑动验证时,某两个行为之间的时间差。
    这个时间差肯定无法再服务器端进行固定校验,因此可以给s赋当下s表示的时间差值或者随机值都行。
*/
  • HDn相关的函数

注意,如果直接搜索

function $_HDn(e) {
    var p = {
        "move": 0,
        "down": 1,
        "up": 2,
        "scroll": 3,
        "focus": 4,
        "blur": 5,
        "unload": 6,
        "unknown": 7
    };

    function h(e, t) {
        for (var n = String(e)["toString"](2), r = "", o = n["length"] + 1; o <= t; o += 1)
            r += "0";
        return n = r + n;
    }

    function f(e) {
        var t = [];
        var n = e["length"];
        var r = 0;
        while (r < n) {
            var o = e[r];
            var i = 0;
            while (1) {
                if (16 <= i)
                    break;
                var s = r + i + 1;
                if (n <= s)
                    break;
                if (e[s] !== o)
                    break;
                i += 1;
            }
            r = r + 1 + i;
            var a = p[o];
            if (0 != i) {
                t["push"](8 | a);
                t["push"](i - 1);
            } else {
                t["push"](a);
            }
        }
        for (var _ = h(32768 | n, 16), c = "", l = 0, u = t["length"]; l < u; l += 1)
            c += h(t[l], 4);
        return _ + c;
    }

    function c(e, t) {
        for (var n = [], r = 0, o = e["length"]; r < o; r += 1)
            n["push"](t(e[r]));
        return n;
    }

    function d(e, t) {
        e = function _(e) {
            var t = 32767;
            var n = (e = c(e, function (e) {
                return t < e ? t : e < -t ? -t : e;
            }))["length"];
            var r = 0;
            var o = [];
            while (r < n) {
                var i = 1;
                var s = e[r];
                var a = Math["abs"](s);
                while (1) {
                    if (n <= r + i)
                        break;
                    if (e[r + i] !== s)
                        break;
                    if (127 <= a || 127 <= i)
                        break;
                    i += 1;
                }
                1 < i ? o["push"]((s < 0 ? 49152 : 32768) | i << 7 | a) : o["push"](s);
                r += i;
            }
            return o;
        }(e);
        var n;
        var r = [];
        var o = [];
        c(e, function (e) {
            var t = Math["ceil"](function n(e, t) {
                return 0 === e ? 0 : Math["log"](e) / Math["log"](t);
            }(Math["abs"](e) + 1, 16));
            0 === t && (t = 1);
            r["push"](h(t - 1, 2));
            o["push"](h(Math["abs"](e), 4 * t));
        });
        var i = r["join"]("");
        var s = o["join"]("");
        return t ? n = c(function a(e, t) {
            var n = [];
            return c(e, function (e) {
                if (t(e)) {
                    n["push"](e);
                }
            }),
                n;
        }(e, function (e) {
            return 0 != e && e >> 15 != 1;
        }), function (e) {
            return e < 0 ? "1" : "0";
        })["join"]("") : n = "",
        h(32768 | e["length"], 16) + i + s + n;
    }

    return function (e) {
        for (var t = [], n = [], r = [], o = [], i = 0, s = e["length"]; i < s; i += 1) {
            var a = e[i];
            var _ = a["length"];
            t["push"](a[0]);
            n["push"](2 === _ ? a[1] : a[2]);
            3 === _ && (r["push"](a[1][0]),
                o["push"](a[1][1]));
        }
        var c = f(t) + d(n, !1) + d(r, !0) + d(o, !0);
        var l = c["length"];
        return l % 6 != 0 && (c += h(0, 6 - l % 6)),
            function u(e) {
                for (var t = "", n = e["length"] / 6, r = 0; r < n; r += 1)
                    t += "()*,-./0123456789:?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"["charAt"](window["parseInt"](e["slice"](6 * r, 6 * (r + 1)), 2));
                return t;
            }(c);
    }(e);
}
  • 分析H函数

image-20240701202225465.png
这个的话直接扣,然后把那些 LIuDu.$_DV删掉

  function H(e) {
      function _(e, t) {
          return e << t | e >>> 32 - t;
      }

      function c(e, t) {
          var n;
          var r;
          var o;
          var i;
          var s;
          return o = 2147483648 & e,
              i = 2147483648 & t,
              s = (1073741823 & e) + (1073741823 & t),
              (n = 1073741824 & e) & (r = 1073741824 & t) ? 2147483648 ^ s ^ o ^ i : n | r ? 1073741824 & s ? 3221225472 ^ s ^ o ^ i : 1073741824 ^ s ^ o ^ i : s ^ o ^ i;
      }

      function t(e, t, n, r, o, i, s) {
          return c(_(e = c(e, c(c(function a(e, t, n) {
              return e & t | ~e & n;
          }(t, n, r), o), s)), i), t);
      }

      function n(e, t, n, r, o, i, s) {
          return c(_(e = c(e, c(c(function a(e, t, n) {
              return e & n | t & ~n;
          }(t, n, r), o), s)), i), t);
      }

      function r(e, t, n, r, o, i, s) {
          return c(_(e = c(e, c(c(function a(e, t, n) {
              return e ^ t ^ n;
          }(t, n, r), o), s)), i), t);
      }

      function o(e, t, n, r, o, i, s) {
          return c(_(e = c(e, c(c(function a(e, t, n) {
              return t ^ (e | ~n);
          }(t, n, r), o), s)), i), t);
      }

      function i(e) {
          var t;
          var n = "";
          var r = "";
          for (t = 0; t <= 3; t++)
              n += (r = "0" + (e >>> 8 * t & 255)["toString"](16))["substr"](r["length"] - 2, 2);
          return n;
      }

      var s;
      var a;
      var l;
      var u;
      var p;
      var h;
      var f;
      var d;
      var g;
      var v;
      for (s = function m(e) {
          var t;
          var n = e["length"];
          var r = n + 8;
          var o = 16 * (1 + (r - r % 64) / 64);
          var i = Array(o - 1);
          var s = 0;
          var a = 0;
          while (a < n) {
              s = a % 4 * 8;
              i[t = (a - a % 4) / 4] = i[t] | e["charCodeAt"](a) << s;
              a++;
          }
          return s = a % 4 * 8,
              i[t = (a - a % 4) / 4] = i[t] | 128 << s,
              i[o - 2] = n << 3,
              i[o - 1] = n >>> 29,
              i;
      }(e = function x(e) {
          e = e["replace"](/\r\n/g, "\n");
          for (var t = "", n = 0; n < e["length"]; n++) {
              var r = e["charCodeAt"](n);
              if (r < 128) {
                  t += String["fromCharCode"](r);
              } else {
                  127 < r && r < 2048 ? t += String["fromCharCode"](r >> 6 | 192) : (t += String["fromCharCode"](r >> 12 | 224),
                      t += String["fromCharCode"](r >> 6 & 63 | 128));
                  t += String["fromCharCode"](63 & r | 128);
              }
          }
          return t;
      }(e)),
               f = 1732584193,
               d = 4023233417,
               g = 2562383102,
               v = 271733878,
               a = 0; a < s["length"]; a += 16) {
          d = o(d = o(d = o(d = o(d = r(d = r(d = r(d = r(d = n(d = n(d = n(d = n(d = t(d = t(d = t(d = t(u = d, g = t(p = g, v = t(h = v, f = t(l = f, d, g, v, s[a + 0], 7, 3614090360), d, g, s[a + 1], 12, 3905402710), f, d, s[a + 2], 17, 606105819), v, f, s[a + 3], 22, 3250441966), g = t(g, v = t(v, f = t(f, d, g, v, s[a + 4], 7, 4118548399), d, g, s[a + 5], 12, 1200080426), f, d, s[a + 6], 17, 2821735955), v, f, s[a + 7], 22, 4249261313), g = t(g, v = t(v, f = t(f, d, g, v, s[a + 8], 7, 1770035416), d, g, s[a + 9], 12, 2336552879), f, d, s[a + 10], 17, 4294925233), v, f, s[a + 11], 22, 2304563134), g = t(g, v = t(v, f = t(f, d, g, v, s[a + 12], 7, 1804603682), d, g, s[a + 13], 12, 4254626195), f, d, s[a + 14], 17, 2792965006), v, f, s[a + 15], 22, 1236535329), g = n(g, v = n(v, f = n(f, d, g, v, s[a + 1], 5, 4129170786), d, g, s[a + 6], 9, 3225465664), f, d, s[a + 11], 14, 643717713), v, f, s[a + 0], 20, 3921069994), g = n(g, v = n(v, f = n(f, d, g, v, s[a + 5], 5, 3593408605), d, g, s[a + 10], 9, 38016083), f, d, s[a + 15], 14, 3634488961), v, f, s[a + 4], 20, 3889429448), g = n(g, v = n(v, f = n(f, d, g, v, s[a + 9], 5, 568446438), d, g, s[a + 14], 9, 3275163606), f, d, s[a + 3], 14, 4107603335), v, f, s[a + 8], 20, 1163531501), g = n(g, v = n(v, f = n(f, d, g, v, s[a + 13], 5, 2850285829), d, g, s[a + 2], 9, 4243563512), f, d, s[a + 7], 14, 1735328473), v, f, s[a + 12], 20, 2368359562), g = r(g, v = r(v, f = r(f, d, g, v, s[a + 5], 4, 4294588738), d, g, s[a + 8], 11, 2272392833), f, d, s[a + 11], 16, 1839030562), v, f, s[a + 14], 23, 4259657740), g = r(g, v = r(v, f = r(f, d, g, v, s[a + 1], 4, 2763975236), d, g, s[a + 4], 11, 1272893353), f, d, s[a + 7], 16, 4139469664), v, f, s[a + 10], 23, 3200236656), g = r(g, v = r(v, f = r(f, d, g, v, s[a + 13], 4, 681279174), d, g, s[a + 0], 11, 3936430074), f, d, s[a + 3], 16, 3572445317), v, f, s[a + 6], 23, 76029189), g = r(g, v = r(v, f = r(f, d, g, v, s[a + 9], 4, 3654602809), d, g, s[a + 12], 11, 3873151461), f, d, s[a + 15], 16, 530742520), v, f, s[a + 2], 23, 3299628645), g = o(g, v = o(v, f = o(f, d, g, v, s[a + 0], 6, 4096336452), d, g, s[a + 7], 10, 1126891415), f, d, s[a + 14], 15, 2878612391), v, f, s[a + 5], 21, 4237533241), g = o(g, v = o(v, f = o(f, d, g, v, s[a + 12], 6, 1700485571), d, g, s[a + 3], 10, 2399980690), f, d, s[a + 10], 15, 4293915773), v, f, s[a + 1], 21, 2240044497), g = o(g, v = o(v, f = o(f, d, g, v, s[a + 8], 6, 1873313359), d, g, s[a + 15], 10, 4264355552), f, d, s[a + 6], 15, 2734768916), v, f, s[a + 13], 21, 1309151649), g = o(g, v = o(v, f = o(f, d, g, v, s[a + 4], 6, 4149444226), d, g, s[a + 11], 10, 3174756917), f, d, s[a + 2], 15, 718787259), v, f, s[a + 9], 21, 3951481745);
          f = c(f, l);
          d = c(d, u);
          g = c(g, p);
          v = c(v, h);
      }
      return (i(f) + i(d) + i(g) + i(v))["toLowerCase"]();
      return e << t | e >>> 32 - t;
      var n;
      var r;
      var o;
      var i;
      var s;
      return o = 2147483648 & e,
          i = 2147483648 & t,
          s = (1073741823 & e) + (1073741823 & t),
          (n = 1073741824 & e) & (r = 1073741824 & t) ? 2147483648 ^ s ^ o ^ i : n | r ? 1073741824 & s ? 3221225472 ^ s ^ o ^ i : 1073741824 ^ s ^ o ^ i : s ^ o ^ i;
      return c(_(e = c(e, c(c(function a(e, t, n) {
          return e & t | ~e & n;
      }(t, n, r), o), s)), i), t);
      return c(_(e = c(e, c(c(function a(e, t, n) {
          return e & n | t & ~n;
      }(t, n, r), o), s)), i), t);
      return c(_(e = c(e, c(c(function a(e, t, n) {
          return e ^ t ^ n;
      }(t, n, r), o), s)), i), t);
      return c(_(e = c(e, c(c(function a(e, t, n) {
          return t ^ (e | ~n);
      }(t, n, r), o), s)), i), t);
      var t;
      var n = "";
      var r = "";
      for (t = 0; t <= 3; t++)
          n += (r = "0" + (e >>> 8 * t & 255)["toString"](16))["substr"](r["length"] - 2, 2);
      return n;
  }
  • 分析i[]相关变量的值

    在该函数执行之前打上断点

image-20240701203129339.png
image-20240701203451448.png

i['$_CCFB'] == '-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1'

i['vip_order'] == undefined
i['ct'] == undefined

//i["$_CEDJ"]是一个函数,函数调用后返回的结果是一种显卡设备检测的相关内容,相对可以固定下来
$_CEDJ = {
    "v": "9.1.9-devcs9",
    "te": false,
    "$_BBE": true,
    "ven": "Google Inc. (Intel)",
    "ren": "ANGLE (Intel, Intel(R) Iris(R) Xe Graphics (0x000046A6) Direct3D11 vs_5_0 ps_5_0, D3D11)",
    "fp": null,
    "lp": null,
    "em": {
        "ph": 0,
        "cp": 0,
        "ek": "11",
        "wd": 1,
        "nt": 0,
        "si": 0,
        "sc": 0
    },
    "tm": {
        "a": 1719837041982,
        "b": 1719837042512,
        "c": 1719837042512,
        "d": 0,
        "e": 0,
        "f": 1719837041985,
        "g": 1719837041985,
        "h": 1719837041985,
        "i": 1719837041985,
        "j": 1719837041985,
        "k": 0,
        "l": 1719837041992,
        "m": 1719837042500,
        "n": 1719837042509,
        "o": 1719837042513,
        "p": 1719837042988,
        "q": 1719837042988,
        "r": 1719837043069,
        "s": 1719837043171,
        "t": 1719837043171,
        "u": 1719837043171
    },
    "dnf": "dnf",
    "by": 0
}

然后把de.stringfy改成json.stringfy

image-20240701205156368.png
大功告成,至此_$_CECC逆向结束

这样咱也可以得到r值了

image-20240701205344113.png

3.1.4 c["encrypt"]函数

image-20240701205518117.png
又是AES

function encrypt(word, key0) {
    var key = CryptoJS.enc.Utf8.parse(key0);  //十六位十六进制数作为密钥
    var iv = CryptoJS.enc.Utf8.parse("0000000000000000");   //十六位十六进制数作为密钥偏移量
    let srcs = CryptoJS.enc.Utf8.parse(word);
    let r = CryptoJS.AES.encrypt(srcs, key, {iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7});
        var o = r['ciphertext']['words'];
    var i = r['ciphertext']['sigBytes'];

    var s = [];
    var a = 0;
    for (; a < i; a++) {
        var _ = o[a >>> 2] >>> 24 - a % 4 * 8 & 255;
        s['push'](_);
    }
    return s;
}

3.1.5 p["$_HET"]函数

image-20240701205703004.png
这个跟之前的扣的一模一样,开心,直接cv大法

image-20240701210519262.png

3.1.6 简单排除一些错误

就是把那些CryptoJS引入呀,然后传入gt, challenge

image-20240701211523338.png

3.1.7 python调用

image-20240701211827187.png

# ================ajax.php数据包请求===========
first_ajax_url = 'https://api.geevisit.com/ajax.php'
second_f = open('第二个w值.js', 'r', encoding='utf-8')
second_js = execjs.compile(second_f.read())
second_datas = second_js.call('get_sec_w', gt, challenge, aeskey)

params = {
    "gt": gt,
    "challenge": challenge,
    "lang": second_datas['lang'],
    "pt": "0",
    "client_type": "web",
    'w': second_datas["w"],
    'callback': 'geetest_%d' % int(time.time() * 1000)
}
first_ajax_ret = session.get(url=first_ajax_url, params=params, headers=headers).text
print(first_ajax_ret)

成功!至此,get_second_w函数逆向结束!

3.2 get.php数据包请求

该数据包用于加载滑动验证码的数据包。其中并没有需要逆向的请求请求参数,不过返回的响应数据中包含了下次请求需要的一些参数。

此时特别注意:该数据包返回的数据中包含了challenge参数,该参数值发生了变化,会在原先challenge值后面多了两位变化的字符,需要我们单独将新的challenge从该数据包返回的数据中提取出来。

image-20240701212426473.png
image-20240701212402695.png
多了两个数字

def jsonp_op(s):
    #用正则匹配进行处理成字典类型
    jsonp_re = re.compile(r"\((?P<code>.*)\)",re.S)
    jsonp_str = jsonp_re.search(s,re.S).group('code')
    return json.loads(jsonp_str)

get_php_url = 'https://api.geevisit.com/get.php'
params = {
    "is_next": "true",
    "type": "slide3",
    "gt": gt,
    "challenge": challenge,
    "lang": "zh-cn",
    "https": "true",
    "protocol": "https://",
    "offline": "false",
    "product": "embed",
    "api_server": "api.geevisit.com",
    "isPC": "true",
    "autoReset": "true",
    "width": "100%",
    "callback": 'geetest_%s'%t
}
get_php_ret = jsonp_op(session.get(get_php_url,headers=headers,params=params).text)
print(get_php_ret)
#注意,此处获取的challenge比之前就得challenge最后随机多了两位
new_challenge = get_php_ret['challenge']
bg_url = get_php_ret['bg']
slice_url = get_php_ret['slice']
#这两个之后会用到
c0 = get_php_ret['c']
s0 = get_php_ret['s']

image-20240701213049940.png

3.3 验证码图片下载与还原

image-20240701214350831.png
看看,这图片都被切割的它妈都不认识了,去找找它的切割算法吧

直接在事件监听断点这里找到动画(Canvas),然后打上断点

然后刷新,注意:此时如果断住绝对不是我们的图片生成位置,别被绕进去了
image-20240702093411106.png

点击验证码后发现断到了这里,注意到了这个很像是某一种算法,简单解一下混淆以及去除无用控制流代码后

image-20240702093954618.png

/*
    在console中查看i就是一个不显示的canvas标签:
        i ==》 <canvas width="312" height="160"></canvas>
    ["getContext"]("2d")表示可以绘制2d图像
    i["getContext"]("2d")表示创建了一个空白画布,画布宽高为312和160,可以在该画布上进行2d图像绘制,
        该画布用变量o来表示。
*/
var o = i["getContext"]("2d");
/*
    t就是<img src="https://static.geetest.com/pictures/gt/cd0bbb6fe/cd0bbb6fe.webp">
        查看src在浏览器中请求后的显示可知:t就表示乱序的背景图。
        t ===》乱序的背景图。
    o["drawImage"](t,0,0)表示即将要在o表示的画布上从起始坐标0,0位置绘制出t这个乱序的背景图。
        因此,画布o现在就是一张不可见的乱序背景图
 */
o["drawImage"](t, 0, 0);
/*
     在console中查看e就是一个显示/可见的canvas标签:
        e ==》<canvas class="xxx" height="160" width="260" style="display: block; opacity: 1;"></canvas>
    s = e["getContext"]("2d"):s就表示创建了一个宽高为260和160的可绘制2d图像的空白画布
*/
var s = e["getContext"]("2d");
e["height"] = 160, //r=160
e["width"] = 260;

for (var a = r / 2, _ = 0; _ < 52; _ += 1) {
    var c = Ut[_] % 26 * 12 + 1
      , u = 25 < Ut[_] ? a : 0
        //(c u 10 a) 依次表示:(初始x坐标 初始y坐标 宽 高)xy表示左上角坐标
      , l = o["getImageData"](c, u, 10, a);
    s["putImageData"](l, _ % 26 * 10, 25 < _ ? a : 0);
}

/*
    循环52次,根据Ut这个数组中指定的顺序将绘制了乱序背景图的o画布的数据获取然后重新写入到空白画布s中
    即可实现背景图乱序还原了。 
*/

将上述核心还原图片代码进行Python改写

image-20240702094059395.png

#将3个图片下载到本地
def download_img(name,src):
    img_data = session.get(src,headers=headers).content
    with open(name,'wb') as fp:
        fp.write(img_data)
download_img('bg.jpg',bg_url)
download_img('slice.jpg',slice_url)

def transform_back_bgImg(bgimgPath):
    from PIL import Image #好比是js中的canvas

    old_img = Image.open(bgimgPath) #获取乱序的背景图

    #创建一张空白的新图,用于存放还原后的背景图
    new_img = Image.new('RGB',(260,160)) #参数是颜色通道和尺寸大小

    #还原顺序数组
    Ut = [ 39, 38, 48, 49, 41, 40, 46, 47, 35, 34, 50, 51, 33, 32, 28, 29, 27, 26, 36, 37, 31, 30, 44, 45, 43, 42, 12, 13, 23, 22, 14, 15, 21, 20, 8, 9, 25, 24, 6, 7, 3, 2, 0, 1, 11, 10, 4, 5, 19, 18, 16, 17 ]

    r = 160
    a = r / 2
    for _ in range(52):
        c = Ut[_] % 26 * 12 + 1
        #u = 25 < Ut[_] ? a : 0
        if 25 < Ut[_]:
            u = a
        else:
            u = 0
        #l = o["getImageData"](left,upper,宽,高);
        #python:(left, upper, right, lower)
        l = old_img.crop((c,u,c+10,u+a)) #记得js和python坐标体系的转换

        x1 = _ % 26 * 10
        if 25 < _:
            y1 = a
        else:
            y1 = 0
        y1 = int(y1)
        new_img.paste(l,(x1,y1))

    new_img.save('new_bg_img.jpg')

 transform_back_bgImg('bg.jpg')

image-20240702094400433.png
搞定

四、最后一个w逆向

一切准备就绪,开始拖到滑块!

image-20240702094839236.png

ajax.php数据包分析

老规矩,先对slide.js进行解混淆操作

// https://www.python-spider.com/challenge/new/js

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

let fs = require("fs")
let js_code = fs.readFileSync("fw.js", "utf-8")

let init_ast = parse(js_code)
let ast = parse(js_code)

//删除前四行无效代码
//var $_CJDe = LIuDu.$_Ca;
traverse(ast, {
    VariableDeclaration: function (path) {
        if (path.node.declarations.length === 1 && path.node.declarations[0].init) {
            if (path.node.declarations[0].init.computed === false) {
                //删除即可
                path.remove()

            }
        }
    }
})
//var $_CJCW = ["$_CJGo"].concat($_CJDe);
traverse(ast, {
    VariableDeclaration: function (path) {
        if (path.node.declarations.length === 1 && path.node.declarations[0].init) {
            if (path.node.declarations[0].init.callee && path.node.declarations[0].init.callee.computed === false) {
                //删除即可
                //console.log(path.node.declarations[0].init.callee.property.name)
                path.remove()

            }
        }
    }
})

//var $_CJER = $_CJCW[1];
traverse(ast, {
    VariableDeclaration: function (path) {
        if (path.node.declarations.length === 1 && path.node.declarations[0].init) {
            if (path.node.declarations[0].init.computed === true && path.node.declarations[0].init.property.type === "NumericLiteral") {
                //删除即可
                if (path.node.declarations[0].init.property.value === 1) {
                    path.remove()
                }

            }
        }
    }
})

//$_DAHHO.shift();
traverse(ast, {
    ExpressionStatement: function (path) {
        if (path.get("expression.type").node === "CallExpression" && path.get("expression.callee.type").node === "MemberExpression") {
            if (path.get("expression.callee.computed").node === false && path.get("expression.callee.property.name").node === "shift") {
                //删除即可
                // console.log(path.toString())
                //console.log(path.get("expression.callee.property.name").node)
                path.remove()
            }

        }
    }
})
//变量名替换
//$_DCEIg(1337)    mwbxQ.$_Cg
traverse(ast, {
    CallExpression: {
        exit: function (path) {
            if (path.node.arguments.length === 1 && path.node.callee.type === "Identifier") {
                if (path.node.arguments[0].type === "NumericLiteral") {
                    path.get("callee").replaceInline(types.memberExpression(types.Identifier("bobo"), types.Identifier("$_DBGGJ"), false)) //将函数名替换成bobo
                }
            }
        }
    }
})

//$_DCFER(296)

//LIuDu.$_AD写入到内存当中
traverse(ast, {
    ExpressionStatement: function (path) {
        if (path.get("expression.operator").node === "=" && path.get("expression.left.computed").node === false) {
            if (path.node.expression.left.object && path.node.expression.left.property) {
                if (path.node.expression.left.property.name === "$_Au") {
                    //
                    path.get("expression.left").replaceWith(types.Identifier("bobo"))
                    //console.log(path.toString())
                    eval(path.toString())
                }
            }

        }

    }
})
// console.log(bobo)
// 去除控制流平坦化

// Traverse the AST and collect variable declarations from switch cases
// Traverse the AST and process functions with the specified conditions
traverse(ast, {
        FunctionDeclaration(path) {
            let varDeclaration = null;
            let forLoop = null;
            let switchStatements = [];

            // Traverse the function body to find the specific pattern
            path.traverse({
                //var $_DCGCv = LIuDu.$_DV()[8][18];
                VariableDeclarator(path) {
                    if (path.node.init && path.node.init.type === "MemberExpression" && path.node.init.computed === true &&
                        path.node.init.object.object && path.node.init.object.object.type === "CallExpression"
                    ) {
                        if (path.node.init.object.object.callee.type === "MemberExpression" && path.node.init.object.object.callee.object && path.node.init.object.object.callee.object.name === 'mwbxQ') {
                            varDeclaration = path.parentPath;
                        }
                    }

                },
                //for (; $_DCGCv !== LIuDu.$_DV()[4][16];) {
                ForStatement(path) {
                    if (path.node.test && types.isBinaryExpression(path.node.test) && path.node.test.right.type === "MemberExpression" && path.node.test.right.object.object) {
                        if (path.node.test.right.object.object.type === "CallExpression" && path.node.test.right.object.object.callee && path.node.test.right.object.object.callee.object.name === 'mwbxQ') {
                            forLoop = path;
                            // path.get("test").remove()
                        }

                    }
                },
                SwitchCase(path) {
                    if (path.parentPath && path.parentPath.parentPath && path.node.consequent) {
                        for (let i = 0; i < path.node.consequent.length; i++) {
                            if (path.node.consequent[i].type !== "BreakStatement") {
                                //console.log(path.node.consequent[i])
                                switchStatements.push(path.node.consequent[i]);
                            }

                        }

                    }
                }
            });

            // If the pattern matches, process the switch cases and replace the function body
            if (varDeclaration && forLoop && switchStatements.length > 0) {
                const newBody = types.blockStatement(switchStatements);
                path.get('body').replaceWith(newBody);
            }
        }
    }
);

//执行bobo函数
traverse(ast, {
    CallExpression: function (path) {
        if (path.node.callee.object && path.node.callee.object.name === "bobo") {
            //console.log(path.toString())
            result = eval(path.toString())
            if (typeof result === "string") {
                path.replaceInline({type: "StringLiteral", value: result})
            }

        }
    }
})

//去除mwbxQ.$_DW()[6][12];
traverse(ast, {
    ExpressionStatement: function (path) {
        if (path.get("expression.type").node === "AssignmentExpression" && path.get("expression.operator").node === "=") {
            if (path.node.expression.right.object && path.node.expression.right.object.type === "MemberExpression") {
                console.log(path.node.expression.right.object.object.callee)
                if (path.node.expression.right.object.object.callee && path.node.expression.right.object.object.callee.property.name === "$_DW") {
                    //删除即可
                    path.remove()
                }
            }

        }
    }
})
let decode_code = generate(ast, {minified: false}).code
fs.writeFileSync("output.js", decode_code)

先在函数顶部赋值个bobo变量,然后扔到V-jstools初步解混淆,最后用AST干它

记得文本替换的时候,手动把bobo还原成mwbxQ.$_Au,不然会报错

接着开启愉快的逆向之旅吧,再坚持一下,希望就在眼前!

4.1 寻找w值

直接搜索,不跟它墨迹

image-20240702102721123.png
发现就在这里,断点进去,发现断不到,同时报错,出现不了图像

image-20240702104349271.png
肯定是缺少了某部分,还是一样的操作,打开另一个没有解混淆的浏览器,然后找到该位置,看看缺少什么

image-20240702104623925.png
这样就可以了,然后在w位置打上断点

image-20240702104840064.png

var u = r["$_CCDm"]();
var l = V["encrypt"](gt["stringify"](o), r["$_CCEc"]());
var h = m["$_FEX"](l);
var w = h+u
//上面就是核心代码了,可以看到,无论是函数还是参数,几乎全是需要咱去扣的,咱一个个的去攻破,最简单的就是把gt改成JSON

4.2 $_CCDm函数

image-20240702105611439.png
很好,老朋友又见面了,第一个w里的加密函数换都不带换的,而咱已经有aeskey了,都不需要它生成,直接cv

function $_CCDm(aeskey) {
    var t = new X()["encrypt"](aeskey); //X()就是咱之前扣的RSA加密函数,然后把那些环境补一下或者复制过来就行
    while (!t || 256 !== t["length"]) t = new X()["encrypt"](aeskey);
    return t;
}

4.3 _CCEc函数

image-20240702110324117.png
这个返回的结果就是咱的aeskey,过

4.4 参数o的获取

往上翻一下发现o值里面也有一大堆的函数,其中已知的就是i["lang"] = "zh-cn",i["challenge"]

image-20240702110549839.png
image-20240702110917117.png
t、e、n都是作为参数传进来的,估计跟滑块相关,反正是要传进来的值,不用管

t:水平滑动距离
e:滑动轨迹加密后的结果
n:滑动总耗时

现在就还剩下H函数、imgload、$_CCCY函数这三个对象以及对e值处理的加密函数

4.4.1 H函数

image-20240702111530699.png

function H(t, e) {
            for (var n = e["slice"](-2), r = [], i = 0; i < n["length"]; i++) {
                var o = n["charCodeAt"](i);
                if (57 < o) {
                    r[i] = o - 87;
                } else {
                    r[i] = o - 48;
                }
            }
            n = 36 * r[0] + r[1];
            var s;
            var a = Math["round"](t) + n;
            var _ = [[], [], [], [], []];
            var c = {};
            var u = 0;
            i = 0;
            for (var l = (e = e["slice"](0, -2))["length"]; i < l; i++)
                c[s = e["charAt"](i)] || (c[s] = 1,
                _[u]["push"](s),
                5 == ++u ? u = 0 : u = u);
            var h;
            var f = a;
            var d = 4;
            var p = "";
            var g = [1, 2, 5, 10, 50];
            while (0 < f)
                if (0 <= f - g[d]) {
                    h = parseInt(Math["random"]() * _[d]["length"], 10);
                    p += _[d][h];
                    f -= g[d];
                } else {
                    _["splice"](d, 1);
                    g["splice"](d, 1);
                    d -= 1;
                }
            return p;
        }

4.4.2 imgload

image-20240702111836817.png
r就是this,然后在r对象中$_CAGy是一个固定值,大概表示某个时间差,因此直接把该值写到程序中即可

4.4.3 e的加密函数

image-20240702112128742.png
往上翻一层堆栈,发现参数e就是变量l,对l逆向

l = n['$_CICO']['$_BBED'](n['$_CICO']['$_FD_'](), n['$_CJk']['c'], n['$_CJk']['s']);
/*
    其中n['$_CICO']是一个W对象,其中包含了 $_HCb这个轨迹数组 和 一些对象方法。 
    因此l可以改写为如下:
*/

l = $_BBED($_FD_(guiji), c, s)
//其中为函数,后面依次为函数的3个参数,因此需要对这3个参数进行逆向
  • $_FD_函数逆向

image-20240702112557985.png
image-20240702113005537.png

function $_FD_(guiji) {
    function n(t) {
        var e = "()*,-./0123456789:?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqr";
        var n = e["length"];
        var r = "";
        var i = Math["abs"](t);
        var o = parseInt(i / n);
        n <= o && (o = n - 1);
        o && (r = e["charAt"](o));
        var s = "";
        return t < 0 && (s += "!"),
            r && (s += "$"),
            s + r + e["charAt"](i %= n);
    }
    var t = function(t) {
        for (var e, n, r, i = [], o = 0, s = 0, a = t["length"] - 1; s < a; s++) {
            e = Math["round"](t[s + 1][0] - t[s][0]);
            n = Math["round"](t[s + 1][1] - t[s][1]);
            r = Math["round"](t[s + 1][2] - t[s][2]);
            0 == e && 0 == n && 0 == r || (0 == e && 0 == n ? o += r : (i["push"]([e, n, r + o]),
                                                                        o = 0));
        }
        return 0 !== o && i["push"]([e, n, o]),
            i;
    }(guiji);
        /* 1.this["$_HCb"]就是轨迹,因此,可以通过该函数的参数guiji将
        轨迹传递过来,替换this[HCB] */
    // }(this['$_HCb'])
    var r = [];
    var i = [];
    var o = [];
      /*
        2.逆向ct(t)['$_CAE']:
        new ct(t)表示ct是一个函数对象,调用该对象的$_CAE表示的函数。其中参数t
        因此先定位到ct的函数实现,让后给ct通过prototype添加$_CAE这个成员函数(该函数根据new ct(t)['$_CAE']进行定位)即可。具体操作如下一组代码:
     */
    return new ct(t)["$_CAE"](function(t) {
        var e = function(t) {
            for (var e = [[1, 0], [2, 0], [1, -1], [1, 1], [0, 1], [0, -1], [3, 0], [2, -1], [2, 1]], n = 0, r = e["length"]; n < r; n++)
                if (t[0] == e[n][0] && t[1] == e[n][1])
                    return "stuvwxyz~"[n];
            return 0;
        }(t);
        e ? i["push"](e) : (r["push"](n(t[0])),
                            i["push"](n(t[1])));
        o["push"](n(t[2]));
    }),
        r["join"]("") + "!!" + i["join"]("") + "!!" + o["join"]("");
}
function ct(t) {
    this['$_BCAO'] = t || [];
}
ct.prototype = {
    '$_CAE':function(t) {
        //此处的this就是ct
        var e = this['$_BCAO'];
        if (e['map'])
            return new ct(e['map'](t));
        for (var n = [], r = 0, i = e['length']; r < i; r += 1)
            n[r] = t(e[r], r, this);
        return new ct(n);
    }
}
  • n['$_CJk']['c']和n['$_CJk']['s']

image-20240702113723028.png
这两个参数一个是数组一个是数组,到底是哪里来的呢?

回想下,在之前处理点击了验证码后,出现的get.php(第二个get.php)包,返回的响应数据中就有c和s这两个变量的值,经过查看和此处的第二个和第三个参数的值一致!

image-20240702113752630.png

因此,可以在python程序将将c和s这两个值传递给BBED函数的第二个和第三个参数即可。

至此BBED函数的三个参数就处理完毕了,接下来处理BBED函数,然后调用该函数传入三个参数获取l的值,那么e的值也就获取了。

image-20240702113916626.png

/*
    注意该函数参数t就是$_FD_(guiji)函数的返回值
    参数e就是get.php数据包中的c
    参数n就是get.php中的s
*/
function $_BBED(t, e, n) {
    if (!e || !n)
        return t;
    var r, i = 0, o = t, s = e[0], a = e[2], _ = e[4];
    while (r = n['substr'](i, 2)) {
        i += 2;
        var c = parseInt(r, 16)
          , u = String['fromCharCode'](c)
          , l = (s * c * c + a * c + _) % t['length'];
        o = o['substr'](0, l) + u + o['substr'](l);
    }
    return o;
}

e的加密函数搞定!

4.4.4 $_CCCY函数

image-20240702114658320.png
['$_CCCY']()和第二个w中一样,可以先设计为固定值

这样我们上半部分的o值弄出来了,下面就是对o进行某种处理

4.4.5 try-catch代码分析

try {
    if (window['_gct']) { //if执行
        //1.创建了一个s对象,对象包含lang和ep两个属性值,两个属性值的来源于对象o
        //对象o是已知的,因此lang和ep两个属性值也是已知的
        var s = {
            'lang': o['lang'],
            'ep': o['ep']
        }
        , a = window['_gct'](s);//2.调用gct函数返回值给了变量a,a中包含了lang和ep
        //3.执行if
        if (a['lang']) {
            //4.该自运行函数返回_,_的值为'h9s9'
            var _ = function d(t) {
                for (var e in t)
                    if ('ep' !== e && 'lang' !== e)
                        return e;
            }(s)
            //5.下面的自运行函数返回值是由random操作生成的,进行断点调式后发现,该自运行函数会返回一个随机数且赋值给了变量c
            , c = function p(t, e, n) {
                for (var r = new t[('gg')][('f')](e, n), i = ['n', 's', 'e', 'es', 'en','w', 'wn', 'ws'], o = i['length'] - 2, s = 0; s < n['length']; s++) {
                    var a, _ = Math['abs'](n[s]['charCodeAt']() - 70)['toString']()[1];
                    a = o < _ ? t['gg'][i[1 + o]](r) : t['gg'][i[_]](r);
                    for (var c = Math['abs'](n[s]['charCodeAt']() - 70)['toString']()[0], u = 0; u < c; u++)
                        a['cc']();
                }
                return r['random']['join']('')['slice'](0, 10);
            }(a, s, _);
            //6.将随机数变量c赋值给了s对象的_表示的h9s9属性,此时s对象有了3个成员属性
            //分别是h9s9,ep和lang
            s[_] = c;
        }
        //7.经过断点分析,发现该自运行函数只会接受o和s两个参数后执行第一个if语句。
        //该if语句作用是将对象s合并到对象o中,s和o都存在ep和lang,除此之外s中还存在一个
        //h9s9属性,因此主要是将h9s9属性添加到了对象o中,且h9s9是一个随机值,则在程序中写写死即可

        !function g(t) {
            if ('function' == typeof Object['assign'])
                return Object['assign']['apply'](Object, arguments);
            /*if (null == t)
                    throw new Error('Cannot convert undefined or null to object');
                t = Object(t);
                for (var e = 1; e < arguments['length']; e++) {
                    var n = arguments[e];
                    if (null !== n)
                        for (var r in n)
                            Object['prototype']['hasOwnProperty']['call'](n, r) && (t[r] = n[r]);
                }
                return t;*/
        }(o, s);
    }
    } catch (v) {
        console.log(v);
    }

//因此try-catch整段代码直接改为:o['h9s9'] = "1816378497"; 即可,省的还要补环境麻烦

4.4.6 X函数

image-20240702171954577.png
现在我们还差这个函数

image-20240702172130575.png
PS:为了避免变量名重复,之前扣$_CCDm已经有个叫X的函数了,所以咱就让它叫X1

这样咱的o值也已经出来了,再坚持一下下,胜利就在眼前!

4.5 V["encrypt"]函数

image-20240702124424951.png
太棒了,又是老朋友,直接扣

function encrypt1(word, key0) {
    var key = CryptoJS.enc.Utf8.parse(key0);  //十六位十六进制数作为密钥
    var iv = CryptoJS.enc.Utf8.parse("0000000000000000");   //十六位十六进制数作为密钥偏移量
    let srcs = CryptoJS.enc.Utf8.parse(word);
    let r = CryptoJS.AES.encrypt(srcs, key, {iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7});
        var o = r['ciphertext']['words'];
    var i = r['ciphertext']['sigBytes'];

    var s = [];
    var a = 0;
    for (; a < i; a++) {
        var _ = o[a >>> 2] >>> 24 - a % 4 * 8 & 255;
        s['push'](_);
    }
    return s;
}

4.6 $_FEX函数

image-20240702124643287.png

芜湖,起飞,又是这个res、end

function $_GJQ(e) {
        var t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789()";
        return e < 0 || e >= t["length"] ? "." : t["charAt"](e);
      }
function $_HBB(e, t) {
        return e >> t & 1;
      }
function $_HCO(e, o) {
        var i = this;
        o || (o = i);
        for (var t = function (e, t) {
            for (var n = 0, r = 24 - 1; 0 <= r; r -= 1) if (1 === $_HBB(t, r)) {
              n = (n << 1) + $_HBB(e, r);
            }
            return n;
          }, n = "", r = "", s = e["length"], a = 0; a < s; a += 3) {
          var _;
          if (a + 2 < s) {
            _ = (e[a] << 16) + (e[a + 1] << 8) + e[a + 2];
            n += $_GJQ(t(_, 7274496)) + $_GJQ(t(_, 9483264)) + $_GJQ(t(_, 19220)) + $_GJQ(t(_, 235));
          } else {
            var c = s % 3;
            if (2 == c) {
              _ = (e[a] << 16) + (e[a + 1] << 8);
              n += $_GJQ(t(_, 7274496)) + $_GJQ(t(_, 9483264)) + $_GJQ(t(_, 19220));
              r = '.';
            } else {
              if (1 == c) {
                _ = e[a] << 16;
                n += $_GJQ(t(_, 7274496)) + $_GJQ(t(_, 9483264));
                r = "..";
              }
            }
          }
        }
        return {
          "res": n,
          "end": r
        };
      }

function $_HET(e) {
        var t = $_HCO(e);
        return t["res"] + t["end"];
      }

4.7 最后一个变量i的处理

var r = this;
var i = r["$_CJk"];

i["offline"] && (o["x"] = t);

//发现在get_third_w函数中,只有3处用到了变量i,且都是获取i中的lang、gt和challenge属性的值
//且lang是固定的zh-cn,gt和challenge可以通过get_third_w函数参数传递过来。

//因此可以将上面代码删除,修改为如下:
var i = {
    'lang':'zh-cn',
    'gt':gt,
    'challenge':challenge
};

//get_third_w函数中额外添加gt和challenge两个参数
function get_third_w(aeskey, challenge, gt, t, n, c0, s0, guiji) {
    var i = {
        'lang': 'zh-cn',
        'gt': gt,
        'challenge': challenge
    };
    var $_CCCY = {
        "v": "7.9.2",
        "$_BIE": false,
        "me": true,
        "tm": {
            "a": 1719890639247,
            "b": 1719890639454,
            "c": 1719890639454,
            "d": 0,
            "e": 0,
            "f": 1719890639253,
            "g": 1719890639257,
            "h": 1719890639257,
            "i": 1719890639257,
            "j": 1719890639348,
            "k": 1719890639308,
            "l": 1719890639348,
            "m": 1719890639432,
            "n": 1719890639451,
            "o": 1719890639456,
            "p": 1719890639613,
            "q": 1719890639613,
            "r": 1719890639614,
            "s": 1719890640465,
            "t": 1719890640465,
            "u": 1719890640481
        },
        "td": -1
    }
    var o = {
        "lang": "zh-cn",
        "userresponse": H(t, challenge),
        "passtime": n,
        "imgload": 98,
        "aa": enc_e(c0, s0, guiji),
        "ep": $_CCCY
    };
    o['h9s9'] = "1816378497"
    i["offline"] && (o["x"] = t);
    o["rp"] = X1(i["gt"] + i["challenge"]["slice"](0, 32) + o["passtime"]);
    var u = $_CCDm(aeskey);
    var l = encrypt1(JSON["stringify"](o), aeskey);
    var h = $_HET(l);
    var w = h + u
    return w
}

似乎出值了,用python试一下

image-20240702173435256.png

五、轨迹模拟

下面是在网上找的大佬的轨迹代码,自己太菜了不会写QAQ

#获取滑动距离
def get_distance():
    import cv2
    #读取两张验证码图片
    bg = cv2.imread('new_bg_img.jpg')
    slice = cv2.imread('slice.jpg')
    #灰度化处理
    bg = cv2.cvtColor(bg,cv2.COLOR_BGR2GRAY)
    slice = cv2.cvtColor(slice,cv2.COLOR_BGR2GRAY)
    #图片边缘处理
    bg_can = cv2.Canny(bg,255,255)
    slice = cv2.Canny(slice,255,255)
    #匹配图像相似度
    r = cv2.matchTemplate(bg_can,slice,cv2.TM_CCOEFF_NORMED)
    #获取匹配度最好的结果
    minValue,MaxValue,minLoc,maxLoc = cv2.minMaxLoc(r)

    #测试滑动效果
    # x = maxLoc[0]
    # y = maxLoc[1]
    # bg = cv2.rectangle(bg,(x,y),(x+40,y+40),(255,255,255))
    # cv2.imshow('xxx',bg)
    # cv2.waitKey(0)
    # cv2.destroyAllWindows()

    return maxLoc[0] #返回匹配度最好的滑动距离结果
distance = get_distance()

#进行滑动轨迹的生成(算法)
def __ease_out_expo(sep):
    if sep == 1:
        return 1
    else:
        return 1 - pow(2, -10 * sep)
def get_slide_track(distance):
    import random
    '''
    根据滑动距离生成滑动轨迹
    :param distance:需要滑动的距离
    :return 滑动轨迹<type 'list'>:[[x,y,t],...]
        x:已滑动的横向距离
        y:已滑动的纵向距离,除起点外,均为0
        t:一次滑动消耗的时间,单位:毫秒
    '''
    if not isinstance(distance, int) or distance < 0:
        raise ValueError(f"distance类型必须是大于等于0的整数: distance: {distance}, type: {type(distance)}")
    #初始化滑动轨迹列表
    slide_track = [
        [random.randint(-50,-10),random.randint(-50,-10),0],
        [0,0,0],
    ]
    #共记录count次滑块位置信息
    count = 30 + int(distance/2)
    #初始化滑动时间
    t = random.randint(50,100)
    #记录上一次滑动的距离
    _x = 0
    _y = 0
    for i in range(count):
        #已滑动的横向距离
        x = round(__ease_out_expo(i / count) * distance)
        #滑动过程消耗的时间
        t += random.randint(10,20)
        if x == _x:
            continue
        slide_track.append([x,_y,t])
        _x = x
    slide_track.append([distance,0,t])

    return slide_track,t
#获取滑动轨迹和耗时
guiji,n = get_slide_track(distance)
print(guiji,n)

六、python调用w

有了轨迹和耗时n,下面就是见证奇迹的时候了

finall_f = open('第三个w值.js', 'r', encoding='utf-8')
finall_js = execjs.compile(finall_f.read())
print(distance,n,aeskey)
finall_w = finall_js.call('get_third_w', aeskey, new_challenge, gt, distance, n, c0, s0, guiji)#记得传入new_challenge,不然就会是fail
print(finall_w)
params = {
    "gt": gt,
    "challenge": new_challenge,
    "lang": "zh-cn",
    "$_BCN": "0",
    "client_type": "web",
    "w": finall_w,
    "callback": f"geetest_{t}"
}
response = session.get(url, headers=headers, params=params)

print(response.text)

image-20240703145530625.png
芜湖!成功!

再来一点废话:

不知道是改了多少次,吾爱这编辑器改图片真的累呀,传的时候还不按照顺序,得一个个慢慢去传然后放进来,日,这比扣代码还难受,那么多图片,用了几个小时,下次还是尽量用文字表诉吧{:301_972:}

感谢K哥爬虫等大佬的文章,自己去逆向就很累,还要写成文章分享,真的是为爱发电呀,感谢大佬们,希望自己也能成为大佬,那些rs、Akamai、5s一定搞出来!!!

免费评分

参与人数 68吾爱币 +62 热心值 +61 收起 理由
mtmakemoney + 1 用心讨论,共获提升!
笙若 + 1 + 1 谢谢@Thanks!
dahl97 + 1 + 1 谢谢@Thanks!
listeneast + 1 + 1 我很赞同!
gezhonglunta + 1 + 1 谢谢@Thanks!
wxyz0001 + 1 + 1 用心讨论,共获提升!
Pueritia + 1 + 1 谢谢@Thanks!
lengyan5i + 1 + 1 吾爱破解论坛有你更精彩!
Flytom + 1 我很赞同!
waiwai24 + 1 我很赞同!
瑾年Lee + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
散场 + 1 + 1 谢谢@Thanks!
罗大伊 + 1 + 1 谢谢@Thanks!
lanyun86 + 1 我很赞同!
xyddn + 1 + 1 谢谢@Thanks!
flyskyer + 1 + 1 学习了
放手一搏09 + 1 + 1 用心讨论,共获提升!
tianyalaike + 1 + 1 谢谢@Thanks!
死尸会跳舞 + 1 + 1 谢谢@Thanks!
liuruichengiu + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
nighv0120 + 1 我很赞同!
Vision、猎龙 + 1 + 1 谢谢@Thanks!
lyzrzjh + 1 我很赞同!
samwu + 1 + 1 我很赞同!
Carinx + 1 用心讨论,共获提升!
Mr.King + 1 + 1 谢谢@Thanks!
时崎-狂三 + 1 + 1 大佬牛逼
wu2343 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
cnszlijz + 1 + 1 用心讨论,共获提升!
a88289624 + 1 热心回复!
lylswb + 1 + 1 我很赞同!
rEtb1Te + 1 用心讨论,共获提升!
Akihi6 + 1 我很赞同!
muzi6360 + 1 + 1 我很赞同!
suncstary + 1 我很赞同!
success67567381 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
firepanda2305 + 1 + 1 我很赞同!
Nevzy + 1 + 1 我很赞同!
Fanqim + 1 + 1 我很赞同!
Qiaoyuexuan + 1 + 1 谢谢@Thanks!
mEIhUAlU123 + 1 + 1 热心回复!
gary2015 + 1 + 1 我很赞同!
Lich + 1 用心讨论,共获提升!
抱歉、 + 1 用心讨论,共获提升!
yixi + 1 + 1 谢谢@Thanks!
yjn866y + 1 + 1 热心回复!
尾叶 + 2 + 1 谢谢@Thanks!
2205 + 2 + 1 谢谢@Thanks!
by、不疼 + 1 前段时间按视频扣的JS最后不会搞轨迹,最后还是放弃了,对小白不友好。
西枫游戏 + 1 + 1 都给你了,以后多多出文章哈
yuyuxiaomin + 1 + 1 谢谢@Thanks!
x7032360 + 1 + 1 谢谢@Thanks!
lookfeiji + 1 + 1 给大佬磕一个
xhtdtk + 1 + 1 用心讨论,共获提升!
zss503 + 1 + 1 谢谢@Thanks!
Huibq120 + 1 我很赞同!
xyz349925756 + 1 + 1 谢谢@Thanks!
surepj + 1 + 1 用心讨论,共获提升!
allspark + 1 + 1 用心讨论,共获提升!
liuxuming3303 + 1 + 1 谢谢@Thanks!
冰茶荼 + 1 用心讨论,共获提升!
sophieqd + 1 + 1 谢谢@Thanks!
李恒道 + 2 + 1 谢谢@Thanks!
HongHu106 + 1 谢谢@Thanks!
iBaiYu + 2 + 1 用心讨论,共获提升!
lihuhu + 1 我很赞同!
Quincy379 + 2 + 1 谢谢@Thanks!
helian147 + 2 + 1 热心回复!

查看全部评分

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

外酥内嫩 发表于 2024-7-4 10:00
说的很详细了,不过提醒下,极验三代是标准RSA+标准AES,使用RSA加密AES密钥,再使用AES加密数据,这也是性能和安全兼顾的加密手法,这个是可以直接用Python还原的,只有AES最后的二进制数据转base64字符串是极验自己写的,这个可以直接抠代码,AST那块,其实可以通过寻找调用解混淆函数的引用来查找所有变量,运行出值再把调用解混淆函数的代码删了就行

免费评分

参与人数 2吾爱币 +2 热心值 +2 收起 理由
xiaogao66 + 1 + 1 热心回复!
gouzi123 + 1 + 1 谢谢@Thanks!

查看全部评分

hygzs 发表于 2024-7-13 15:41
你这前面都是node写的轨迹是py的我给你补上轨迹的node吧
[JavaScript] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function get_slide_track(distance) {
  if (typeof distance !== 'number' || distance < 0) {
    return `distance类型必须是大于等于0的整数: distance: ${distance}, type: ${typeof distance}`;
  }
  // 初始化轨迹列表
  const slide_track = [
    [Math.floor(Math.random() * 40) - 50, Math.floor(Math.random() * 40) - 50, 0],
    [0, 0, 0],
  ];
  // 共记录count次滑块位置信息
  const count = 30 + Math.floor(distance / 2);
  // 初始化滑动时间
  let t = Math.floor(Math.random() * 50) + 50;
  // 记录上一次滑动的距离
  let _x = 0;
  let _y = 0;
  for (let i = 0; i < count; i++) {
    // 已滑动的横向距离
    const x = Math.round(__ease_out_expo(i / count) * distance);
    // 滑动过程消耗的时间
    t += Math.floor(Math.random() * 10) + 10;
    if (x === _x) {
      continue;
    }
    slide_track.push([x, _y, t]);
    _x = x;
  }
  slide_track.push(slide_track[slide_track.length - 1]);
  return slide_track;
}


之前帮人写原的,然后开源在GitHub上了,共同进步鸭
https://github.com/JoeLeaf/oicq-node/blob/main/%E5%8E%9F%E7%9A%84%E4%B8%80%E4%BA%9B%E4%B8%9C%E8%A5%BF/%E6%9E%81%E9%AA%8C%E4%B8%89%E4%BB%A3%E6%BB%91%E5%9D%977.9.0%20%E5%8E%9F%E7%A5%9E%E5%8D%8F%E8%AE%AE%E7%89%88.js
wg521125 发表于 2024-7-4 07:18
Rainbow丶sugar 发表于 2024-7-4 10:06
厉害了大佬,看完学到了很多
xiaopacI 发表于 2024-7-4 10:31
学到了学到了
 楼主| gouzi123 发表于 2024-7-4 10:55
后面带了一大堆图片不知道咋删除
lies2014 发表于 2024-7-4 10:58
谢谢这么详细的教程,3.1.4节前后的地方乱掉了,能否编辑一下

2024-07-04_105504.jpg
腾讯QQ 发表于 2024-7-4 11:04
虽说看不懂,但是还是支持下
wh1tefi1sh 发表于 2024-7-4 14:11
感激之情无以言表 唯有铭记于心
 楼主| gouzi123 发表于 2024-7-4 19:33
lies2014 发表于 2024-7-4 10:58
谢谢这么详细的教程,3.1.4节前后的地方乱掉了,能否编辑一下

好,我看看,感谢提醒
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2025-4-1 10:38

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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