声明

本文仅供学习参考,如有侵权可私信本人删除,请勿用于其他途径,违者后果自负! 如果觉得文章对你有所帮助,可以给博主点击关注和收藏哦!

插句个人内容:本人最近正在找工作,工作城市:广州。如果有合适的机会,希望有大佬可以内推,感激不尽。🙏 js逆向马马虎虎,app会弱一些。感兴趣的大佬可以私信本人要简历。

流程分析

本篇使用网站:

aHR0cHM6Ly93d3cuZ2VldGVzdC5jb20vZGVtby9zbGlkZS1mbG9hdC5odG1s

整个滑块验证码的流程如下,其中需要注意的接口都已经标记过。 image.png

分别解释一下用途。

  • 第一个接口

无加密参数参数,返回gtchallenge两个字段,这两个字段贯穿整个流程,十分重要。 image.png

  • 第二个接口

该接口会返回很多字段,能够使用到的也就是c,此时出现了第一个wimage.png

过去旧版的第一个w和第二个w都可以选择置空,但是现在这样做已经校验不过去了。 请求参数如下gtchallenge前面一个接口已经获得了,然后就是这个w了。 image.png

  • 第三个接口

返回了滑块类型,之前一直觉得这个接口没什么用,直接就忽略了,发现怎么都不能校验过去。 image.png 同时该接口的w值也不可忽略,还是要拿下,不可写死或置空处理。

  • 第四个接口

这个接口就是校验验证码缺口的接口了,成功了就会返回success,否则就是fail,当然还有其他的一些状态,可以根据返回的状态去排查对应的问题。

逆向分析

第一个w

第一个接口处,可以在Initiator中跳进js文件打下断点一步步跟栈。 image.png

image.png 或者直接搜索i + r,然后打下断点。 image.pngir对应生成的值拿下来就可以还原第一个w了。

先看一下r是什么内容。 image.png 先把这个函数直接拿下来,然后运行。 image.png 报错,yCtOu未定义,寻找一下是什么东西。 image.png 将需要的部分都拿下。 重新运行,继续报错。 image.png X没有定义,继续扣代码。这样做太慢了,可以直接把整个文件copy到本地然后慢慢补。 image.png 然后就可以很轻松的找到需要的内容了。 继续运行,window未定义。 image.png pe是什么不知道,回到网页中看一下 image.png image.png 缺少navigator补上就完了,需要的环境大致如下:

window = global;
navigator = {
    appName:'Netscape'
}
pe = navigator

image.png 报错复制到网页上观察一下。 image.png 看样子像一个随机数,找到函数位置。 image.png image.png 四位随机数生成四次然后进行字符串拼接。 最后结果就出来了。 image.png

o的代码:

o = $_BFo()[$_CEGDt(1108)](de[$_CEGEy(459)](t[$_CEGEy(369)]), t[$_CEGDt(1196)]())

手动解混淆:

o = $_BFo()['encrypt1'](JSON.stringify(t['$_EJV']), t["$_CCHU"])

t['$_EJV']是一个大的对象,除了gt和challenge之外,其他都可以固定。

{
    "gt": "传入gt",
    "challenge": "传入challenge",
    "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-r8k4eq.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"
}

t["$_CCHU"]则是上一次省的16位随机数,需要和生成r部分保持一致的值。

然后将$_BFo()函数拿下来就得到结果。 image.png

i就简单了,将o生成的数组变成字符串。 image.png 还是缺啥补啥,最终第一个w结果。 image.png

第二个w

同样使用Initiator追溯,向上寻找堆栈,慢慢分析也可以找到。 image.png 不过比较麻烦,可以直接搜索var n = {}。 调试发现断点没有经过t[$_CFEEo(1109)]();w还没有生成,所以w肯定在该函数之前生成了。 image.png image.png

函数很长,直接看return处。 最终定位到。 image.png

手动解混淆:

p['$_HEt'](c["encrypt"](r, i["$_CCHU"]()));

实际上和第一个w的加密一样 重点看一下r,r是一个字符串,反序列化

{
    "lang": "zh-cn",
    "type": "fullpage",
    "tt": "MZ(*((1(j(CM7((",
    "light": -1,
    "s": "c7c3e21112fe4f741921cb3e4ff9f7cb",
    "h": "321f9af1e098233dbd03f250fd2b5e21",
    "hh": "39bd9cad9e425c3a8f51610fd506e3b3",
    "hi": "09eb21b3ae9542a9bc1e8b63b3d9a467",
    "vip_order": -1,
    "ct": -1,
    "ep": {
        "v": "9.1.9-r8k4eq",
        "te": false,
        "$_BBp": true,
        "ven": "Google Inc. (Intel)",
        "ren": "ANGLE (Intel, Intel(R) Iris(R) Xe Graphics (0x000046A6) Direct3D11 vs_5_0 ps_5_0, D3D11)",
        "fp": [
            "move",
            744,
            147,
            1707040727652,
            "pointermove"
        ],
        "lp": [
            "up",
            672,
            82,
            1707040728689,
            "pointerup"
        ],
        "em": {
            "ph": 0,
            "cp": 0,
            "ek": "11",
            "wd": 1,
            "nt": 0,
            "si": 0,
            "sc": 0
        },
        "tm": {
            "a": 1707040721021,
            "b": 1707040721290,
            "c": 1707040721290,
            "d": 0,
            "e": 0,
            "f": 1707040721021,
            "g": 1707040721056,
            "h": 1707040721056,
            "i": 1707040721056,
            "j": 1707040721122,
            "k": 1707040721089,
            "l": 1707040721122,
            "m": 1707040721286,
            "n": 1707040721288,
            "o": 1707040721292,
            "p": 1707040721503,
            "q": 1707040721503,
            "r": 1707040721504,
            "s": 1707040721562,
            "t": 1707040721562,
            "u": 1707040721564
        },
        "dnf": "dnf",
        "by": 0
    },
    "passtime": 1190827,
    "rp": "6fc9d795969c2c344ce5bc11e50c9916",
    "captcha_token": "1265749785",
    "otpj": "jm4jwcx7"
}

经过测试发现,其他值均可固定,但是rp的加密过程passtime参与了,所以要注意保持一致。 再看rp如何生成,向上寻找。(代码比较混乱,我也找了半天o(╥﹏╥)o) image.png image.png

使用到了gt、challenge以及passtime,加密长度32位,盲猜一波md5。 ![image.png](https://cdn.nlark.com/yuque/0/2024/png/39047339/1707042842696-bb495516-f94b-4b69-b857-e2177b168e7b.png#averageHue=%23f6f5f4&clientId=u7beac4ef-06cb-4&from=paste&height=367&id=u837eeb7b&originHeight=459&originWidth=778&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=52709&status=done&style=stroke&taskId=uaad1ca3c-47d4-418b-892d-69fb067be2b&title=&width=622.4) 完全一致,rp就已经生成了。i"$_CCHU"依然是第一次生成的16位随机字符串,也需要使用同样的秘钥。不然会出现param decrypt error`的错误。

至此w生成完毕。

第三个w

这个w网上有很多现成的文章了,直接搜索"\u0077"即可定位,也可以选择上面的方法,跟堆栈。 image.png

这个w其实和上面的加密方式一样,没什么好说的,这里就不展开了。 说一下不同的地方。

{
    "lang": "zh-cn",
    "userresponse": "9777999999979766a",
    "passtime": 266,
    "imgload": 73,
    "aa": "K,,(!!Msssyyt!)!)ttusYsx(!!($)/(/0101010110$nd)c",
    "ep": {
        "v": "7.9.2",
        "$_BIE": false,
        "me": true,
        "tm": {
            "a": 1707046714483,
            "b": 1707046715148,
            "c": 1707046715148,
            "d": 0,
            "e": 0,
            "f": 1707046714484,
            "g": 1707046714533,
            "h": 1707046714533,
            "i": 1707046714533,
            "j": 1707046714614,
            "k": 1707046714574,
            "l": 1707046714614,
            "m": 1707046714724,
            "n": 1707046714741,
            "o": 1707046715150,
            "p": 1707046715369,
            "q": 1707046715369,
            "r": 1707046715370,
            "s": 1707046715434,
            "t": 1707046715434,
            "u": 1707046715435
        },
        "td": -1
    },
    "h9s9": "1816378497",
    "rp": "6ca7d8a547390ce95dc57399fd88f50d"
}

userresponse 、aa和rp是需要处理的部分,其他固定即可。

  • userresponse 部分

image.png

H(滑动距离,34位challenge)

将H函数扣下来就可以实现该算法了。

  • aa部分

image.png 此时的aa已经生成了,向上找一层。 image.png

n[$_DAAAU(985)][$_CJJJU(1075)]有三个参数,分别是轨迹加密得到的结果,接口的cs(和34位challenge为一个接口) 然后将剩余代码补齐即可得到结果。 十次成功率80%

image.png

参考

最后

最近开通了个人公众号,有一些被博客下架的文章后续也会同步上去。 大佬们可以关注一波。 微信公众号.png