为了账号安全,请及时绑定邮箱和手机立即绑定

【AI 逆向专栏】某象刮刮卡验证码逆向分析

标签:
Python

NhgLNO.png

声明

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

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

前言

在人工智能技术飞速迭代的今天,AI 大模型已突破传统技术边界,从基础的模式识别、数据解析,升级为具备自主学习、逻辑推理、复杂问题拆解能力的核心赋能工具,其强大的算力与智能分析能力,正重构各行业的技术路径,逆向工程领域也不例外。

曾经,协议逆向、接口解析、产品反编译等工作,往往依赖工程师长期的经验积累、繁琐的手工调试,不仅耗时耗力,更易在复杂代码、加密协议中陷入瓶颈,难以实现高效突破。

而如今,AI 大模型的介入,彻底改变了逆向工程的实战逻辑 —— 它能快速挖掘代码中的隐藏规律、自动解析未知协议的交互逻辑,借助 AI 工具的辅助,可大幅降低逆向门槛、提升分析效率,甚至能完成传统人工难以企及的复杂逆向任务。

在技术竞争日趋激烈、产品迭代不断加速的当下,逆向工程的核心诉求已从“能完成”转向“高效、精准、快速”,而拥抱 AI、善用 AI 大模型,不再是可选的加分项,而是立足行业、实现技术突破的必由之路。

(怪不得加入 OpenAI,属实给 “龙虾之父” 用爽了)

NgT9Bb.png

逆向目标

  • 目标:某象刮刮卡验证码
  • 网址:aHR0cHM6Ly93d3cuZGluZ3hpYW5nLWluYy5jb20vYnVzaW5lc3MvY2FwdGNoYQ==
  • AI 模型:GPT-5.5

抓包分析

进入网站,就是旗下产品样例在线体验页面,其中有个较为新颖的验证码类型 —— 刮刮卡,一开始会展示图案位置,需要刮出完整图案才能验证通过:

NgBeCt.png

/api/a 接口响应返回 sid 参数值及验证码背景图片链接,type 2 即刮刮卡验证:

NgTBRP.png

不过此时的背景图片是乱序的,需要还原,后文分析:

NgTTZw.png

c1 接口请求了两次,一次 GET,一次 POST,第二次响应返回的 data 值就是最终验证接口的请求参数 c,两次请求都有个加密参数 Param,一长一短,后文分析:

NgEGbG.png

/api/v3 为校验接口,ak 为验证码标识,c、sid 均由上文接口返回,aid 和主要加密参数 ac 后文分析:

NgTLDO.png

  • 验证通过:{“success”:true,“token”:“xxxxx”,“msg”:null,“tp”:null,“sv”:null,“retry”:0,“ot”:null}
  • 识别错误:{“success”:false,“token”:null,“msg”:“retry”,“tp”:null,“sv”:null,“retry”:0,“ot”:null}
  • 算法异常:{“success”:false,“token”:null,“msg”:“验证过程出现异常”,“tp”:null,“sv”:null,“retry”:0,“ot”:null}

AI & 古法逆向分析

乱序图片还原

接口返回的图片链接,下载下来是乱序的,想要识别目标图案位置,肯定是需要还原出完整图片的。网站也一样,后端返回乱序图片,前端根据正确的还原顺序,绘制出完整图片,然后渲染到页面上。

因此,我们需要找到还原顺序,从而复现出还原算法。验证码图片大概率是通过 canvas 绘制的,可以下个画布断点尝试下,重新获取验证码,成功断下了:

NgTAgs.png

代码有点混淆,解完之后内容如下:

function d(n, t, r, e) {
    var o, i = n.options.lineWidth || p.default_line_width, d = p["cover_color"], v = t["getElementsByTagName"]("canvas")[0]["getContext"]("2d");
    return v["fillStyle"] = d,
        v.fillRect(0, 0, r, e),
        v.globalCompositeOperation = "destination-out",
        v["lineWidth"] = i,
        v.lineCap = "round",
        v
}

将这部分丢给 AI 分析下,AI 直接看出了这是在绘制刮刮卡:

destination-out 的作用是:后续绘制的地方会把已有像素“擦掉”,不是画上颜色。所以这常见于验证码滑块、刮刮卡、手势擦除、canvas 遮罩揭露这类场景。

Ngm7sV.png

根据 AI 的分析,可以判断,这块是在绘制遮罩层,并非乱序还原部分。因此,F8,跳到下一个断点位置看看,如下图所示:

NgmQEL.png

同样,解下混淆,可以让 AI 实现:

function _(n, t, i, e, r) {
    var o = n.getContext("2d");
    o["drawImage"](t, 0, 0, i, e);
    var a = Math.floor(i / r['length']);
    x(r, (function (n, i) {
        var r = n * a
            , c = a;
        o.drawImage(t, r, 0, c, e, i * a, 0, c, e)
    }
    ))
}

还是直接让 AI 分析一下:

NgmNWJ.png

NgmqjG.png

综上可知,这部分就是我们要找的 “乱序切块还原” 部分,但是测试发现,还原顺序并不是定值,那是如何生成的呢?照以往,得开始往上跟栈分析了,但是大人,时代变了,现在直接交给 AI,然后去泡杯茶,看看新闻,欣赏 AI 的表演(描述需求,发网站链接,算法所在文件,AI 会自动调用 mcp 加载页面进行分析):

Ng7aoZ.png

AI 不仅正确找到了相关算法的生成位置,还将还原逻辑用 python 复现了,表现的十分优秀,主要逻辑如下,相关算法会分享到知识星球中:

# uses the image URL filename stem, e.g. "5d6bbb8c70d449b7940f9b5d878b760a".

def generate_ranges(seed: str) -> list[int]:
    order: list[int] = []
    for index, char in enumerate(seed):
        if index == 32:
            break

        value = ord(char)
        while value % 32 in order:
            value += 1
        order.append(value % 32)

    if len(order) != 32:
        raise ValueError(f"seed must generate 32 ranges, got {len(order)}")
    return order

aid

/api/a 接口的加密参数,这个直接手动扣就行了,从该接口的堆栈处,跟到 oneclick-Captcha-js.js 文件中,直接搜索 aid = 定位,下断,刮刮卡的 R 为 3,跟到函数 I 中,将算法扣下来用 python 还原即可:

NgEL0h.png

timestamp = int(time.time() * 1000)
random_part = random.randint(0, 99999999)
aid = f"dx-{timestamp}-{random_part}-3"

_t

str(int(time.time() * 1000))[-6:-1]

Param

先从第一个 c1 的堆栈处,跟到 index.js 中(动态的,建议固定一套调试),下断,清空缓存,刷新网页,断住后往上跟栈到下图处,此时 Param 参数的值已经生成:

NgN3yZ.png

再往上跟一个栈就能找到生成位置,第二个 c1 的 Param 也一样:

NgNlUU.png

按照以往的思路,定位到算法生成的位置,就该开始漫长且枯燥的扣代码了,感谢新时代,接下来交给 AI 就行了(其实上述过程都不需要,跟 AI 描述你需要哪个接口中哪个参数的生成算法,他就能直接分析出来),定位到了正确的位置:

NgND9Q.png

最后,直接让 AI 用 python 复现算法(爽),根据 AI 分析及验证,两个 Param 的生成算法是一样的,入参不同所以长度有差别:

NgNOHf.png

第二个 Param 的入参包含大量的环境校验,以下为 AI 分析结果:

field_sources = {
    "can": "canvasFP,Canvas 指纹",
    "cpt": "canPlayType,音视频格式支持",
    "web": "webglFP,WebGL 指纹 hash",
    "gi": "webgl,WebGL 原始信息",
    "pr": "devicePixelRatio",
    "dm": "navigator.deviceMemory",
    "jf": "fonts,字体探测",
    "hc": "navigator.hardwareConcurrency",
    "ua": "navigator.userAgent",
    "uad": "navigator.userAgentData",
    "np": "navigator.platform",
    "lug": "navigator.language",
    "ce": "navigator.cookieEnabled",
    "netType": "navigator.connection.effectiveType",
    "ts": "touch,触摸能力",
    "tz": "Intl.DateTimeFormat().resolvedOptions().timeZone",
    "to": "new Date().getTimezoneOffset()",
    "ls": "localStorage 支持",
    "ss": "sessionStorage 支持",
    "ind": "indexedDB 支持",
    "od": "openDatabase 支持",
    "cd": "screen.colorDepth",
    "res": "screen.width/height",
    "ar": "screen.availWidth/availHeight",
    "vs": "viewportSize",
    "ws": "windowSize",
    "rp": "navigator.plugins",
    "adb": "adblock 检测",
    "hl": "history.length",
    "cl": "cookieLength",
    "st": "startTime",
    "dt": "document.title",
    "url": "location.href",
    "bl": "bodyLength",
    "hdl": "headLength",
    "dr": "document.referrer",
    "xp": "xpath",
    "hsl": "performance.memory.jsHeapSizeLimit",
    "in": "incognito 检测",
    "qu": "storage quota",
    "hev": "UA high entropy values",
    "bt": "beginTime",
    "ct": "collectTime",
    "hlb": "hasLiedBrowser",
    "hll": "hasLiedLanguages",
    "hlo": "hasLiedOs",
    "hlr": "hasLiedResolution",
    "db": "debugger 检测",
    "sm": "simulator 检测",
}

固定入参,将 AI 算法生成的值与网页生成的对比验证一下,结果一致(稳!):

NgNS1c.png

版本号、自定义 base64 字符表匹配(动态 js)及相关算法,都会分享到知识星球中,以供学习交流。

ac

最后,来分析下某象最关键的加密参数 —— ac。

还是先从堆栈处下断,往上跟一下,定位到了下图位置:

NgKTps.png

M.getUA() 跟进去,就会跳转到 greenseer.js 文件中,知道大致的范围了,接下来,描述给 AI,看他发挥就行了(学习 AI 的思路),有问题调教一下,有些点还是会分析的不对,需要纠正:

NgKD94.png

NgKSP9.png

AI 还能将各关键算法的位置全部清晰的罗列出来,便于自行分析:

NgKt0q.png

顺便让 AI 解混淆(控制流平坦化还原、Webpack 模块拆分、静态调用解码等等),还原 greenseer.js 文件,代码同步到知识星球:

NghQQY.png

刮刮乐需要先检测出目标物品的位置,生成对应的轨迹,才能计算出正确的 ac 值,先让 AI 写出识别算法,找到目标物体的位置(已分享到知识星球):

NghYIf.png

研究轨迹,可以插桩分析:

(() => {
  if (!window.__scratchStartTime || n.type === "mousedown" || n.type === "touchstart") {
    window.__scratchStartTime = Date.now();
    window.__scratchData = [];
  }

  const item = {
    x: Y(n),
    y: H(n),
    t: Date.now() - window.__scratchStartTime
  };

  window.__scratchData.push(item);
  return JSON.stringify(window.__scratchData);
})()

// copy(JSON.stringify(window.__scratchData))

结果验证

Nghz53.png

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号

举报

0/150
提交
取消