首页 > 其他分享 >【验证码逆向专栏】某某邮政滑块逆向分析

【验证码逆向专栏】某某邮政滑块逆向分析

时间:2024-09-02 10:26:52浏览次数:10  
标签:逆向 滑块 cv2 验证码 value 0x49863c 0x3d7e8f tp 0x238244

1.png

声明

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

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

前言

粉丝答疑又双叒来了!最近仍有不少粉丝咨询逆向相关问题,K哥会尽力回复,不过毕竟精力有限,不可能说是面面俱到(忙忘了@.@)。当然,还是会尽可能多筛选些典型的案例,产出相关文章,以供大伙学习交流。本期分析的验证码,如果没有相关业务,可能很多人不会接触到,也算是较为新颖,话不多说,开始正文:

2.png

逆向目标

目标:xx 邮政滑块逆向分析

网站:aHR0cHM6Ly9wYXNzcG9ydC4xMTE4NS5jbi9jYXMvbG9naW4=

3.png

抓包分析

抓包分析,先看图片接口 gen:

4.png

返回的内容比较多,后面估计会用到,我们留意一下就行,clientUid 后文分析:

5.png

再来看验证接口,即发验证码的接口:

6.png

  • id : 图片接口返回;
  • bgImageHeight: 写死即可;
  • bgImageWidth:写死即可;
  • clientUid:同图片接口参数 clientUid;
  • deviceId:图片接口返回 deviceId;
  • enmiid:需要分析;
  • entSlidingTime:结束验证时间,模拟一下,校验不严;
  • print:需要分析;
  • sliderImageHeight:写死即可;
  • sliderImageWidth:写死即可;
  • startSlidingTime:开始验证时间,模拟一下,校验不严;
  • trackList:轨迹列表。

验证结果:

  • 失败

7.png

  • 成功

8.png

逆向分析

gen 接口

该接口有个 clientUid 参数,目测是 uuid 类型, 我们全局搜索一下,发现只在 main.js 文件中存在,进去后发现被 ob 混淆:

9.png

又是经典的 ob 混淆, 这里直接借用 v佬 的插件快速还原,工具地址如下:

Github 仓库:https://github.com/cilame/v_jstools

在线分析工具:https://astexplorer.net

还原之前还需要稍微的预处理一下,比如下面这样一堆常量的花指令,以及十六进制形式的数字:

比如下面这样一堆常量的花指令,以及十六进制形式的数字

10.png

附上简单的预处理 AST 代码:

// 字符串美化
const simplifyLiteral = {
    NumericLiteral({ node }) {
        if (node.extra && /^0[obx]/i.test(node.extra.raw)) {
        node.extra = undefined;
        }
    },
    StringLiteral({ node }) {
        if (node.extra && /\\[ux]/gi.test(node.extra.raw)) {
        node.extra = undefined;
        }
    },
}

traverse(ast, simplifyLiteral);



let my_obj = {};


const merge_odj = {
    VariableDeclarator(path){
        let {init} = path.node;

        if (!types.isObjectExpression(init)) return;

        let {properties} = init;

        properties.forEach(property => {
            // 确保属性值是字面量
            if (types.isLiteral(property.value)) {
              my_obj[property.key.name] = property.value;
            }
        });
       
    }
};

traverse(ast, merge_odj);


const clearObjectofValue = {
    MemberExpression(path){
        let {object, property} = path.node
        if (!property || !types.isIdentifier(property) || !types.isIdentifier(object)) return;
        let key = property.name
        let value = my_obj[key]
        if (!value) return;
        console.log(`${path.toString()} --> ${value.value}`)
        path.replaceWithSourceString(value.value)
    }
}

traverse(ast, clearObjectofValue);

ast = parser.parse(generator(ast,{compact:true}).code)


remove_no_citation_obj = {
    VariableDeclarator(path){
        let {init,id} = path.node
        if (!types.isObjectExpression(init) && !types.isIdentifier(id)) return;
        let {scope} = path
        let binding = scope.getBinding(id.name)
        if (binding.referencePaths.length !== 0) return;
        path.remove()
    }
}

// 删除未被引用的对象
traverse(ast,remove_no_citation_obj)

然后再利用 v_jstools 插件中的解密高级 ob 混淆即可还原了,good:

11.png

利用浏览器的替换功能,替换 main.js 文件,注意将代码压缩替换,我们再来搜索 clientUid 看看:

12.png

返回 _0x349b8e,我们再进去到 _0x236e93 函数中看看:

13.png

发现确实是 uuid 的构造,这边引库或者扣代码都行:

  • clientUid
var _0x5e9294;
var _0x31a6e3 = new Uint8Array(16);

function _0x2f762f() {
    if (!_0x5e9294 && !(_0x5e9294 = typeof crypto != "undefined" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto) || typeof msCrypto != "undefined" && typeof msCrypto.getRandomValues == "function" && msCrypto.getRandomValues.bind(msCrypto))) {
        throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
    }
    return _0x5e9294(_0x31a6e3);
}

for (var _0x22667e = function (_0x37736f) {
    return typeof _0x37736f == "string" && /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i.test(_0x37736f);
}, _0x49863c = [], _0x18d3c6 = 0; _0x18d3c6 < 256; ++_0x18d3c6) {
    _0x49863c.push((_0x18d3c6 + 256).toString(16).substr(1));
}

function _0x19f649(_0x3d7e8f) {
    var _0x238244 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
    var _0x313ed4 = (_0x49863c[_0x3d7e8f[_0x238244 + 0]] + _0x49863c[_0x3d7e8f[_0x238244 + 1]] + _0x49863c[_0x3d7e8f[_0x238244 + 2]] + _0x49863c[_0x3d7e8f[_0x238244 + 3]] + "-" + _0x49863c[_0x3d7e8f[_0x238244 + 4]] + _0x49863c[_0x3d7e8f[_0x238244 + 5]] + "-" + _0x49863c[_0x3d7e8f[_0x238244 + 6]] + _0x49863c[_0x3d7e8f[_0x238244 + 7]] + "-" + _0x49863c[_0x3d7e8f[_0x238244 + 8]] + _0x49863c[_0x3d7e8f[_0x238244 + 9]] + "-" + _0x49863c[_0x3d7e8f[_0x238244 + 10]] + _0x49863c[_0x3d7e8f[_0x238244 + 11]] + _0x49863c[_0x3d7e8f[_0x238244 + 12]] + _0x49863c[_0x3d7e8f[_0x238244 + 13]] + _0x49863c[_0x3d7e8f[_0x238244 + 14]] + _0x49863c[_0x3d7e8f[_0x238244 + 15]]).toLowerCase();
    if (!_0x22667e(_0x313ed4)) {
        throw TypeError("Stringified UUID is invalid");
    }
    return _0x313ed4;
}

function _0x236e93(_0x5bbf46, _0x3b7cda, _0x21cf16) {
    var _0x574141 = (_0x5bbf46 = _0x5bbf46 || {}).random || (_0x5bbf46.rng || _0x2f762f)();
    _0x574141[6] = _0x574141[6] & 15 | 64;
    _0x574141[8] = _0x574141[8] & 63 | 128;
    if (_0x3b7cda) {
        _0x21cf16 = _0x21cf16 || 0;
        for (var _0x3503fc = 0; _0x3503fc < 16; ++_0x3503fc) {
            _0x3b7cda[_0x21cf16 + _0x3503fc] = _0x574141[_0x3503fc];
        }
        return _0x3b7cda;
    }
    return _0x19f649(_0x574141);
};


console.log(_0x236e93());

验证接口

重点就是 enmiid、print 参数的生成,我们通过搜索 enmiid、print 即可定位到:

14.png

_0x5e712c 对象中取的值,我们可以搜索 _0x5e712c 的赋值操作 "_0x5e712c =", 即可定位到目标位置:

15.png

可以看到目标的参数以及都生成好了,我们可以从前往后开始分析:

16.png

_0x5b4cc9 就是图片返回的内容,取了 webAName、miid、rc, 然后拼接了一个 weba.js 请求这个链接,通过三次 then 得到 _0x383eb4 这个对象,进去到 _0x383eb4 对象的方法或者去看 weba.js 这个文件就可以发现又是 WebAssembly 与 JavaScript 之间进行交互,跟上篇某东 m 端的 wasm 大差不差,敢兴趣的可以去看看,我们接着向下分析,通过 _0x383eb4 对象下的函数对 _0x2d1fb0_0x354e64 进行一系列的操作最终得到了目标参数。

_0x2d1fb0 就是取的 rc_0x354e64 是一个对象,里面的 hitId 对应 miid、deviceId 对应图片接口返回的 deviceId_print 通过 _0x468795(_0x34c930) 返回,_0x34c930 对应 miid,然后进入到 _0x468795 函数:

17.png

通过 WebGL 在画布上绘制图形,并最终将绘制的内容转换为图像数据URL, 然后 _0x492d6a() 函数生成密文

最后再将密文与 _0x5ad970 + "|" + 密文得到我们最终返回的数据,我们断点进入到 _0x492d6a() 函数:

18.png

可能有大佬一眼就看出来是什么加密了,也可以根据长度判断,我们也可以找一下是谁调用的,在 249 中,

我们直接找 (249) ,就在下面,加密也写出来了,我们验证一下没有问题:

19.png

20.png

最后就是 _0x383eb4 对象了,我们可以仿照着写,先请求 weba.js 链接,注意 weba.js 链接是动态:

 "https://kks.11185.cn/static/front/" + webAName + "/bin/weba.js"

保存发现:

21.png

已经帮我们把对象给导出了,如下操作即可:

weba = require('./Weba.js');

window = global;

function ZxptRestKks(rc, _0x354e64){
    var result;
    var flag = false;
    weba().then(function(Module) {
        if (Module) {
            _0x2d1fb0 = rc;   // rc
            window[_0x354e64.deviceId] = function () {
              return _0xa2b2bc = Module.allocateUTF8(_0x2d1fb0);
            };
            // 访问 PromiseResult 下的属性
            _0x156e64 = Module.allocateUTF8(JSON.stringify(_0x354e64));
            _0x3e92b3 = Module._rotate(_0x156e64);
            _0x57276e = Module.UTF8ToString(_0x3e92b3);
            result = JSON.parse(_0x57276e);
            flag = true;
        }
    }).catch(function(error) {
        // 处理 Promise 被拒绝的情况
        console.error(error);
    });
    while (!flag) {
        require('deasync').sleep(100);
    }
    ;
    return result;
};

最后就是一些细节风控的处理,该验证码对距离的识别要求较高,轨迹要求不高,会封 ip 和 ua,速度快了,也会影响成功率,在 cv2 识别后,加上延迟即可,成功率 99%。附上处理代码:

  • cv2 识别
def identify_gap(bg, tp):
    """
    bg: 背景图片
    tp: 缺口图片
    out: 输出图片
    """
    # 读取背景图片和缺口图片
    bg_img = cv2.imdecode(np.frombuffer(bg, np.uint8), cv2.IMREAD_GRAYSCALE)
    tp_img = cv2.imdecode(np.frombuffer(tp, np.uint8), cv2.IMREAD_GRAYSCALE)  # 缺口图片
    yy = []
    xx = []
    for y in range(tp_img.shape[0]):
        for x in range(tp_img.shape[1]):
            r = tp_img[y, x]
            if r < 200:
                yy.append(y)
                xx.append(x)
    tp_img = tp_img[min(yy):max(yy), min(xx):max(xx)]
    # 识别图片边缘
    bg_edge = cv2.Canny(bg_img, 100, 200)
    tp_edge = cv2.Canny(tp_img, 100, 200)
    # 转换图片格式
    bg_pic = cv2.cvtColor(bg_edge, cv2.COLOR_GRAY2RGB)
    tp_pic = cv2.cvtColor(tp_edge, cv2.COLOR_GRAY2RGB)
    # 缺口匹配
    res = cv2.matchTemplate(bg_pic, tp_pic, cv2.TM_CCOEFF_NORMED)
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)  # 寻找最优匹配
    # # 绘制方框
    th, tw = tp_pic.shape[:2]
    tl = max_loc  # 左上角点的坐标
    br = (tl[0] + tw, tl[1] + th)  # 右下角点的坐标
    cv2.rectangle(bg_img, tl, br, (0, 0, 255), 2)  # 绘制矩形
    cv2.imwrite('distinguish.jpg', bg_img)  # 保存在本地
    # 返回缺口的X坐标
    print(f'value: {tl[0]}')
    return max_loc[0]


value = identify_gap(requests.get(backgroundImage).content, requests.get(sliderImage).content)
value = int(value * 0.55)
print(value)
time.sleep(1)
  • 轨迹构造
def get_sub_trajectory(trajectories, value):
    # 检查value是否在轨迹的x值中
    for trajectory in trajectories:
        if trajectory['x'] == value:
            # 如果找到,截取从轨迹开始到该点的子数组
            return [t for t in trajectories if t['x'] <= value]

    # 如果value不在x值中,找到最接近value的x值
    closest_x = None
    min_diff = float('inf')
    for trajectory in trajectories:
        if abs(trajectory['x'] - value) < min_diff:
            min_diff = abs(trajectory['x'] - value)
            closest_x = trajectory['x']

    # 截取从轨迹开始到最接近的x值的子数组
    return [t for t in trajectories if t['x'] <= closest_x]


# 调用函数
# trackList : 浏览器生成的最大距离轨迹
value = 128  # 假设我们要找的value是 128
sub_trajectory = get_sub_trajectory(trackList, value)
sub_trajectory[-1]['type'] = 'up'
print(sub_trajectory)

结果验证

22.png

标签:逆向,滑块,cv2,验证码,value,0x49863c,0x3d7e8f,tp,0x238244
From: https://www.cnblogs.com/ikdl/p/18392254

相关文章

  • zdppy+vue3+onlyoffice文档管理系统实战 20240901 上课笔记 基于验证码登录功能基本完
    遗留的问题1、点击切换验证码2、1分钟后自动切换验证码点击切换验证码实现步骤:1、点击事件2、调用验证码接口3、更新验证码的值点击事件给图片添加点击事件:<img:src="'data:image/png;base64,'+captchaImg"style="width:100%;height:50px;margin-top:10......
  • 学习爬虫day26-kanzhun请求加密与逆向解密
    找到入口之后,在主js文件中,模拟入口函数,生成需要的参数:functiontest(n){letM=window.loader(12210)//console.log(M._A())lets=M.A()//letn='{"cityCode":"","salaryId":"403","experienceId":"","pageNum......
  • 【阅己书城】逆向生成代码
    1、配置renren-generator#application.ymlserver:port:80#mysqlspring:datasource:type:com.alibaba.druid.pool.DruidDataSource#MySQL配置driverClassName:com.mysql.cj.jdbc.Driverurl:jdbc:mysql:///:3306/yueji_pms?useUnicode=true&......
  • JS逆向入门案例-xx志愿服务网encData-05
    文章目录概要整体架构流程技术细节小结概要提示:仅供学习,不得用做商业交易,如有侵权请及时联系!逆向:xx志愿服务网URL:aHR0cDovL2NoaW5hdm9sdW50ZWVyLm1jYS5nb3YuY24vc2l0ZS9wcm9qZWN0目标:表单数据中的encData参数整体架构流程提示:分析-调试-猜想-实现-执......
  • js逆向之常用算法
     [Python]encode&decodefromurllibimportparse#url进行编码与解码url='你好啊'url_encode=parse.quote(url)print('url编码后:',url_encode)url_decode=parse.unquote(url_encode)print('url解码后:',url_decode)url_encode......
  • 抖音旋转验证码角度识别
    一、简介上图是抖音最新的旋转验证码,和老款旋转验证码相比,现在新增了很多防御措施,比如内圈小图增加了白色花边,内外圈图片颜色有一定差异等等。所以给我们识别增加了很大难度。二、免费识别方法介绍经过我们大量的数据标注,我们终于完成了这款验证码的角度识别。我们可以完成......
  • 抖音旋转验证码角度识别
     一、简介上图是抖音最新的旋转验证码,和老款旋转验证码相比,现在新增了很多防御措施,比如内圈小图增加了白色花边,内外圈图片颜色有一定差异等等。所以给我们识别增加了很大难度。二、免费识别方法介绍经过我们大量的数据标注,我们终于完成了这款验证码的角度识别。我们可以完......
  • 什么是逆向思维?
     任何一句语言判断都会有相反的判断。我们说的每句话,与之对应平行的都有一个相反的陈述在那。就像人的影子,每个人都有影子,每句话都有影子。对于别人说的话,也想他的相反说法是什么,有没有证据支持。经过独立思考,往往能发现很多问题。一般人都要更坚强,要更强健,要有更多财富,但事......
  • js逆向之jsRpc
    github: https://github.com/jxhczhl/JsRpc 简介:通过远程调用(rpc)的方式免去抠代码补环境 原理:在网站的控制台新建一个WebScoket客户端链接到服务器通信,调用服务器的接口服务器会发送信息给客户端客户端接收到要执行的方法执行完js代码后把获得想要的内容发回给服务......
  • 逆向工程、Spring框架IOC、AOP学习
    系列文章目录第一章基础知识、数据类型学习第二章万年历项目第三章代码逻辑训练习题第四章方法、数组学习第五章图书管理系统项目第六章面向对象编程:封装、继承、多态学习第七章封装继承多态习题第八章常用类、包装类、异常处理机制学习第九章集合学习第......