某备案查询网站 汉字点选逆向分析

查看 55|回复 9
作者:li63033   
声明
本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!
本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请联系作者立即删除!
目标
目标:点选人机验证逆向分析
网址:aHR0cHM6Ly9iZWlhbi5taWl0Lmdvdi5jbi8jL0ludGVncmF0ZWQvaW5kZXg=
流程分析
进入页面前,先打开F12开发者工具
[ol]

  • 上来就是个521大礼包,朋友们这可太熟悉了,妥妥的jsl,现在这东西逆向真的已经烂大街了(不排除有刚入坑的小伙伴,后期单独补上)


    1.png (26.17 KB, 下载次数: 3)
    下载附件
    2023-12-24 20:21 上传

  • 未进行查询验证前,Network中我们会看到一个/auth接口进行认证操作,请求传递了两个参数。
  • authKey:时间戳加盐后的MD5,需要逆向得到
  • timeStamp:当前时间戳



    2.png (28.18 KB, 下载次数: 0)
    下载附件
    2023-12-24 20:21 上传

    接口返回了后续请求中,header需要携带的token


    3.png (43.35 KB, 下载次数: 3)
    下载附件
    2023-12-24 20:21 上传

  • 点击查询验证后,出现了我们今天分析的主角:文字点选验证码,加载接口为/getCheckImagePoint,传递了一个参数

  • clientUid:设备id,需要逆向得到


    4.png (29.06 KB, 下载次数: 2)
    下载附件
    2023-12-24 20:21 上传


    接口返回了点选验证码的详细信息

  • bigImage:点选验证码上方的大图base64

  • secretKey:加密坐标时的密钥

  • smallImage:点选验证码下方的小图base64

  • uuid:此验证码的id

  • wordCount:需要点选的文字数量


    5.png (52.38 KB, 下载次数: 2)
    下载附件
    2023-12-24 20:21 上传


  • 点选完毕后,进行验证的接口为/checkImage,传递了相关点选的数据

  • clientUid:3中生成的clientUid

  • pointJson:点击坐标密文,需要逆向得到

  • secretKey:加密密钥

  • token:加载接口返回的验证码id


    6.png (53.66 KB, 下载次数: 2)
    下载附件
    2023-12-24 20:21 上传


    未通过验证,会刷新验证码,验证返回的数据如下


    7.png (29.6 KB, 下载次数: 1)
    下载附件
    2023-12-24 20:21 上传

    通过验证,会跳转查询请求,验证返回数据中会多一个sign字段


    8.png (43.25 KB, 下载次数: 1)
    下载附件
    2023-12-24 20:21 上传

  • 查询请求接口为/queryByCondition,需要再请求头中携带四个正确的字段,才能够请求成功

  • Cookie:jsl生成的cookie,需要逆向得到

  • Sign:点选验证码通过后,返回的sign

  • Token:第一次进入系统,auth接口的返回值

  • Uuid:点选验证码加载时的uuid


    9.png (122.7 KB, 下载次数: 2)
    下载附件
    2023-12-24 20:21 上传


    参数正确,返回的请求数据如下


    10.png (67.5 KB, 下载次数: 2)
    下载附件
    2023-12-24 20:21 上传

    [/ol]
    逆向分析
    1. authKey参数
    跟到这个参数很简单,直接全局搜就能看到了


    11.png (40 KB, 下载次数: 1)
    下载附件
    2023-12-24 20:21 上传

    2. clientUid参数
    这个参数也很简单,还是全局搜,我们会发现是已经生成好存在了localstorage中


    12.png (47.32 KB, 下载次数: 1)
    下载附件
    2023-12-24 20:21 上传

    所以我们干脆直接改为全局搜localStorage.getItem,就能定位到生成&存储的位置了


    13.png (45.57 KB, 下载次数: 2)
    下载附件
    2023-12-24 20:21 上传

    3. pointJson参数
    仍然是全局搜,很快就能定位到参数生成的位置,入口为h函数,跟进去我们就能看到,是个AES加密,密钥为接口返回的secretKey,加密模式为ECB,填充模式为Pkcs7


    14.png (79.2 KB, 下载次数: 1)
    下载附件
    2023-12-24 20:22 上传

    4. Cookie中的__jsluid_s
    这是一个辨识度很高的jsl防护,先埋个坑,后期单独出一期jsl的逆向
    点选识别
    其他所有流程步骤都分析完毕了,就差点选坐标的获取了。
    啰嗦两句,其实现在针对点选方式验证码的解决,比较成熟的方案为机器学习+识别推理,模型训练的方法也比较集中为目标检测+孪生神经网络。目标检测经过长时间的发展改进,现在已经挺成熟了,所以难点不在找字,而在于找对点击顺序。ok,现在方向有了,开干
    一、目标检测训练
    目的:
  • 识别大图中所有的文字位置
  • 识别小图中需要点击的文字位置

    步骤
    [ol]

  • 采集点选验证码的大图和小图
    这一步只要拿到了Token值,直接请求接口getCheckImagePoint就能拿到了


    15.png (274.57 KB, 下载次数: 2)
    下载附件
    2023-12-24 20:22 上传

  • 分别对小图和大图中出现的目标字进行标注
    这一步中,我使用的标注工具为labelImg,labelImg是一款非常优秀的图片数据标定工具,借助它我们能轻松完成数据集的标注。
    点击 Open Dir ,选择验证码存放的路径。点击 Change Save Dir,选择标注结果存放的路径。点击Pascal VOC,它会变成YOLO,保存的结果可以直接拿来用。勾选右侧Use default labtel,并在输入框填入text,这样接下来所有标注的文字都会打上text的标签


    16.png (246.18 KB, 下载次数: 3)
    下载附件
    2023-12-24 20:22 上传

    按 W 键创建一个标记框,框起来验证区域的字。标注完一张图片后,记得按 Ctrl + S 保存。按 D 进入下一张图片,按 A 进入前一张图片。

    我们在目标检测阶段只区分是不是字,不区分具体是什么字,所以标签只有一个 text。
    >
    > 下方点击顺序的四个字位置是固定的,所以后期我们统一进行自动化标注。



    17.png (139.55 KB, 下载次数: 1)
    下载附件
    2023-12-24 20:22 上传

  • 使用YOLOv8训练目标检测模型
    使用YOLOv8前,确保你的电脑有N卡,并安装好了CUDA+cudnn相关深度学习的环境,安装完上述环境,再安装Python包ultralytics即可。
    目标检测模型训练配置yaml
    # 指定训练集、验证集、测试集路径
    train: /path/to/your/train/images
    val: /path/to/your/valid/images
    test: /path/to/your/test/images
    # 有多少个分类
    nc: 1
    # 分类分别是什么
    names: ['text']
    目标检测模型训练代码
    from ultralytics import YOLO
    # 预训练模型下载 https://github.com/ultralytics/ultralytics?tab=readme-ov-file#models
    model = YOLO("./model/yolov8n.pt", task="detect")
    model.train(data="./dataset/detect.yaml", epochs=20, cache=True, imgsz=320, batch=16, workers=0, device=0)
    导出onnx模型
    model = YOLO('./path/to/your/train/output/best.pt', task='detect')
    model.export(format='onnx', imgsz=320, simplify=True)
    模型使用
    model = YOLO(model='./path/to/your/model/best.onnx', task='detect')
    results = model.predict(source='./path/to/your/test/dataset/images/folder', show=False, save=True, imgsz=500, device=0)
    print(f"total_标签名字:{results[0].names}")
    def y8_detect_xy(result):
       """
       输出坐标信息
       :param result:
       :return:
       """
       cls_xy = list()
       cls_dict = result.names
       cls_all = result.boxes.cls.tolist()
       print(f">>识别结果目标类 {len(cls_all)}个: {cls_all}")
       xyxy_all = result.boxes.xyxy.tolist()
       for i in range(len(cls_all)):
           label_name = cls_dict[int(cls_all)]
           box_xyxy = xyxy_all
           box_mid_xy = [(box_xyxy[0] + box_xyxy[2]) / 2, (box_xyxy[1] + box_xyxy[3]) / 2]
           # print(f"目标点{i}: 标签名字: {label_name}, 中心坐标:{box_mid_xy}, xyxy坐标:{box_xyxy}")
           cls_xy.append({
               "label_id": i, "label_name": label_name,
               "box_mid_xy": box_mid_xy, "xyxy": box_xyxy
           })
       print(f"==识别结果: {cls_xy}==")
       return cls_xy
    for result in results:
       y8_detect_xy(result)
    训练效果


    18.png (1.17 MB, 下载次数: 2)
    下载附件
    2023-12-24 20:22 上传

    [/ol]
    二、孪生神经网络训练
    目的:
  • 判断待点击字在大图中出现的位置
  • 在大图中按顺序找出小图中的文字

    步骤:
    [ol]

  • 进行目标检测,裁剪预测的结果小图
    剪裁小图代码
    def extract_correct_word():
       save_to = '/path/to/save/word'
       if not os.path.exists(save_to):
           os.mkdir(save_to)
       images = glob('./cut/images/*.png')
       for image_path in tqdm(images):
           results = model.predict(source=image_path, show=False, save=True, imgsz=500, device=0)
           num = image_path.split('/')[-1].split('.')[0].split('_')[1]
           im = np.asarray(Image.open(image_path).convert("RGB"))
           for result in results:
               labels = y8_detect_xy(result)
               for i, label in enumerate(tqdm(labels)):
                   x1, y1, x2, y2 = label["xyxy"]
                   word_im = im[math.floor(y1):math.ceil(y2), math.floor(x1):math.ceil(x2)]
                   cv2.imwrite(os.path.join(save_to, 'x-{}-{}.png'.format(num, i)), word_im)
    人工标注后整理,效果如下


    19.png (87.09 KB, 下载次数: 2)
    下载附件
    2023-12-24 20:22 上传

  • 进行孪生神经网络训练
    训练代码


    20.png (90.05 KB, 下载次数: 1)
    下载附件
    2023-12-24 20:22 上传

    预测结果


    21.png (177.24 KB, 下载次数: 0)
    下载附件
    2023-12-24 20:22 上传

    导出onnx模型
    def pth2onnx():
       device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
       model_path = r"/path/to/your/train/output/model.pth"
       model = Siamese((105, 105, 3))
       model.load_state_dict(torch.load(model_path, map_location=device))
       model.eval()
       dummy_input = [torch.randn(1, 3, 105, 105), torch.randn(1, 3, 105, 105)]
       torch.onnx.export(
           model, dummy_input, 'weights_self_12071503.onnx', verbose=True, input_names=['x1', 'x2'], output_names=['output']
       )
       print("Successful!")
    [/ol]
    三、模型融合使用
    目的:
  • 在验证点击区,依次找出待点击的文字

    核心代码:
    def judge_max_sim(self, cls_xys, img):
        targets = [(i["xyxy"], i["box_mid_xy"]) for i in cls_xys if i["label_name"] == "target"]
        target_chars = []
        # 将提供的顺序,按照x坐标轴从小到大排序,确保识别的字符顺序正确
        targets = sorted(targets, key=lambda x: x[0][0])
        for xyxy, _ in targets:
            target_chars.append(img.crop(xyxy))
        texts = [(i["xyxy"], i["box_mid_xy"]) for i in cls_xys if i["label_name"] == "text"]
        text_chars = []
        for xyxy, _ in texts:
            text_chars.append(img.crop(xyxy))
        # 获取点击顺序
        click_seq_result = []
        for m, target_img in enumerate(target_chars):
            slys = []
            if len(texts) == 0:
                break
            elif len(texts) == 1:
                slys_index = 0
            else:
                for n, text_img in enumerate(text_chars):
                    similarity = self.siam_model.predict(target_img, text_img)
                    slys.append(similarity)
                slys_index = slys.index(max(slys))
            click_seq_result.append(texts[slys_index][1])
            texts.pop(slys_index)
            text_chars.pop(slys_index)
            if len(texts) == 0:
                break
        return click_seq_result
    def get_xy_seq(self, img_path):
        """
        获取点击顺序
        :param img_path:
        :return:
        """
        img = self.open_image(img_path)
        # yolov8识别
        obj_result = self.yolo_v8_model_predict(img_path)[0]
        cls_xys = self.target_detection_xy(obj_result)
        # 孪生判断相似度
        xy_seq = self.judge_max_sim(cls_xys, img)
        return xy_seq
    结果验证


    22.png (452.02 KB, 下载次数: 3)
    下载附件
    2023-12-24 20:22 上传

    下载次数, 下载附件

  • goblack   

    前排留名。
    备案查询接口倒是容易找到,如果是部分行业,还能简单的正规取得官方接口对接(早几年在取得资质的时候就有要求对接联调)。
    本文的要点是过验证。
    我没用机器学习。类似的验证码用的是色彩。先判断背景色,再寻找在多大的范围内出现了连续的单色,然后对比得到位置。
    相对来说还是机器学习最终更简单粗暴。
    就类似动态识别号码,物品动态识别,最终都是落入机器学习模糊计算的流程了。。人工算法取巧既复杂,又容易受到干扰。机器学习在有一定数据量的情况下,识别验证码怕是比人工强更多
    yjn866y   

    厉害厉害,学习学习
    yuband   

    如果能拿到对应服务器大部分的验证图片,训练结果应该也是可靠的,不知道接口有没有验证码请求限制
    viply   

    好家伙,都用上yolo来识别了
    daitoudage   

    哈哈哈感谢楼主分享啊啊啊
    moka518   

    高级啊,能把源码发出来不
    soughing   

    厉害厉害,学习学习
    Quincy379   

    学到了,感谢分享!!!
    zyzoicq   

    高手啊~~学习了,没准哪天就用到了呢~~
    您需要登录后才可以回帖 登录 | 立即注册