li63033 发表于 2023-12-24 20:25

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

本帖最后由 li63033 于 2024-3-5 14:35 编辑

## 声明

---

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

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

## 目标

---

目标:点选人机验证逆向分析

网址:`aHR0cHM6Ly9iZWlhbi5taWl0Lmdvdi5jbi8jL0ludGVncmF0ZWQvaW5kZXg=`

## 流程分析

---

进入页面前,先打开F12开发者工具

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

   

2. 未进行查询验证前,Network中我们会看到一个`/auth`接口进行认证操作,请求传递了两个参数。

   - authKey:时间戳加盐后的MD5,需要逆向得到
   - timeStamp:当前时间戳

   

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

   

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

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

   

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

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

   - secretKey:加密坐标时的密钥

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

   - uuid:此验证码的id

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

   

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

   - clientUid:3中生成的clientUid

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

   - secretKey:加密密钥

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

   

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

   

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

   

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

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

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

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

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

   

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

   

## 逆向分析

---

### 1. `authKey`参数

跟到这个参数很简单,直接全局搜就能看到了



### 2. `clientUid`参数

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



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



### 3. `pointJson`参数

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



### 4. Cookie中的`__jsluid_s`

这是一个辨识度很高的jsl防护,先埋个坑,后期单独出一期jsl的逆向

## 点选识别

---

其他所有流程步骤都分析完毕了,就差点选坐标的获取了。

啰嗦两句,其实现在针对点选方式验证码的解决,比较成熟的方案为机器学习+识别推理,模型训练的方法也比较集中为目标检测+孪生神经网络。目标检测经过长时间的发展改进,现在已经挺成熟了,所以难点不在找字,而在于找对点击顺序。ok,现在方向有了,开干

### 一、目标检测训练

目的:

- 识别大图中所有的文字位置
- 识别小图中需要点击的文字位置

步骤

1. **采集点选验证码的大图和小图**

   这一步只要拿到了`Token`值,直接请求接口`getCheckImagePoint`就能拿到了

   

2. **分别对小图和大图中出现的目标字进行标注**

   这一步中,我使用的标注工具为[`labelImg`](https://github.com/HumanSignal/labelImg),`labelImg`是一款非常优秀的图片数据标定工具,借助它我们能轻松完成数据集的标注。

   点击 `Open Dir` ,选择验证码存放的路径。点击 `Change Save Dir`,选择标注结果存放的路径。点击`Pascal VOC`,它会变成`YOLO`,保存的结果可以直接拿来用。勾选右侧`Use default labtel`,并在输入框填入`text`,这样接下来所有标注的文字都会打上`text`的标签

   

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

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

   

3. **使用YOLOv8训练目标检测模型**

   使用YOLOv8前,确保你的电脑有N卡,并[安装](https://blog.csdn.net/qq_50677040/article/details/132131346)好了`CUDA`+`cudnn`相关深度学习的环境,安装完上述环境,再安装Python包`ultralytics`即可。

   目标检测模型训练配置yaml

   ```yaml
   # 指定训练集、验证集、测试集路径
   train: /path/to/your/train/images
   val: /path/to/your/valid/images
   test: /path/to/your/test/images
   # 有多少个分类
   nc: 1
   # 分类分别是什么
   names: ['text']
   ```

   目标检测模型训练代码

   ```python
   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模型

   ```python
   model = YOLO('./path/to/your/train/output/best.pt', task='detect')
   model.export(format='onnx', imgsz=320, simplify=True)
   ```

   模型使用

   ```python
   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.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)]
         box_xyxy = xyxy_all
         box_mid_xy = [(box_xyxy + box_xyxy) / 2, (box_xyxy + box_xyxy) / 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)
   ```

   训练效果

   

### 二、孪生神经网络训练

目的:

- 判断待点击字在大图中出现的位置
- 在大图中按顺序找出小图中的文字

步骤:

1. **进行目标检测,裁剪预测的结果小图**

   剪裁小图代码

   ```python
   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('.').split('_')
         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
                   cv2.imwrite(os.path.join(save_to, 'x-{}-{}.png'.format(num, i)), word_im)
   ```

   人工标注后整理,效果如下

   

2. **进行孪生神经网络训练**

   训练代码

   

   预测结果

   

   导出onnx模型

   ```python
   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.onnx.export(
         model, dummy_input, 'weights_self_12071503.onnx', verbose=True, input_names=['x1', 'x2'], output_names=['output']
       )
       print("Successful!")
   ```

### 三、模型融合使用

目的:

- 在验证点击区,依次找出待点击的文字

核心代码:

```python
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)
    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)
      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)
    cls_xys = self.target_detection_xy(obj_result)
    # 孪生判断相似度
    xy_seq = self.judge_max_sim(cls_xys, img)
    return xy_seq
```

## 结果验证

---

goblack 发表于 2023-12-25 12:53

前排留名。
备案查询接口倒是容易找到,如果是部分行业,还能简单的正规取得官方接口对接(早几年在取得资质的时候就有要求对接联调)。

本文的要点是过验证。
我没用机器学习。类似的验证码用的是色彩。先判断背景色,再寻找在多大的范围内出现了连续的单色,然后对比得到位置。
相对来说还是机器学习最终更简单粗暴。
就类似动态识别号码,物品动态识别,最终都是落入机器学习模糊计算的流程了。。人工算法取巧既复杂,又容易受到干扰。机器学习在有一定数据量的情况下,识别验证码怕是比人工强更多

yjn866y 发表于 2023-12-25 14:46

厉害厉害,学习学习

yuband 发表于 2023-12-25 16:19

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

viply 发表于 2023-12-25 16:52

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

daitoudage 发表于 2023-12-25 20:59

哈哈哈感谢楼主分享啊啊啊{:1_893:}

moka518 发表于 2023-12-26 00:58

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

soughing 发表于 2023-12-26 07:18

厉害厉害,学习学习

Quincy379 发表于 2023-12-26 08:30

学到了,感谢分享!!!{:1_921:}

zyzoicq 发表于 2023-12-26 08:45

高手啊~~学习了,没准哪天就用到了呢~~
页: [1] 2 3 4 5 6
查看完整版本: 某备案查询网站 汉字点选逆向分析