【验证码逆向专栏】某安登录流程详解与验证码逆向分析与识别

news/2024/11/21 0:25:54/文章来源:https://www.cnblogs.com/ikdl/p/18345672

783JYG.png

声明

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

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

前言

最近知识星球有粉丝表示自己在逆向某安的过程中有一些疑惑,过来咨询,K 哥一向会尽力满足粉丝需求。本文就对某安进行深入研究,包括登录接口逆向、验证码识别、风控等方面进行全方位的分析。

逆向目标

  • 目标:某安登录界面
  • 网址:aHR0cHM6Ly9hY2NvdW50cy5iaW5hbmNlLmNvbS96aC1DTi9sb2dpbg==

抓包分析

进入登录页,随便输入账号和密码,打开开发者工具,点击下一步,会弹出九宫格点选验证码,抓包如下:

78k6Pm.jpg

通过返回值我们可以看见,该接口给我们返回的验证码的类型以及 sessionId 还有 validateId 参数,接下来是获取图片的接口,将我们上个接口获得的 validateId 传入:

78k2K5.jpg

然后该接口给我们返回图片地址,盐值 salt,还有 sig 以及点选问题 tag:

78ks9j.jpg

接着是验证接口,需要提交 validateId、sig、data 参数,验证成功则会返回 token 值:

78B0Pa.jpg
然后携带 token 值在接口 check/result 传入进行校验:

78BQML.jpg

若通过则返回如下参数:

78B7NI.jpg

该参数可以在后续登录接口进行使用,如通过 valid 参数判断账号是否注册等:

78BEaV.jpg

该网站主要验证码类型为九宫格,本文将主要对九宫格进行详细的剖析:
78Bq0J.jpg

验证码逆向分析

data 参数

九宫格验证接口中,data 参数为唯一加密参数,定位方式有很多种,这里我们直接采用跟栈的方式进行定位:

78BKbb.jpg
从第一个堆栈进入,在 t.apply 这一行打上断点,点击验证按钮,成功断了下来,如下:

78BbPe.jpg

继续向前跟栈,找到 T 函数跟进去:

78BgFP.jpg

然后继续向前跟,找到参数 D 生成的地方,即为 data 参数加密的位置:

78BGa6.jpg

发现这个 JS 被混淆了,我们可以用 v_jstools 进行简单的变量还原,替换进去即可,最终我们分析可得,data 参数是由 ek 和轨迹明文加密得到的,这里我们跟进加密函数 h 中,看看 data 是如何生成的:

78BA3Q.jpg

很明显这是平坦流,经过分析可知将传入的参数 ek 进行处理后,同 w 参入传入 m 函数进行处理:

78BxMO.jpg

最终通过 return f[n("0x916", "h1Kz")](m, w, t) 将处理结果进行返回,即生成最终的 data 参数,所以我们需要将 e 函数与 m 函数成功拿下,即可复现 data 参数的生成流程,接下来我们将用俩种扣代码的方法实现 data 参数复现。

第一种方法,我们可以首先将大数组、移位函数、解密函数这三个模块拿下来:

78BV5f.jpg

然后我们将大函数 h 整体拿下来,然后我们仿照源代码
var D = i[n("0x1f2", "3Izv")](h, JSON[n("0x564", "W73O")](K), w[n("0x962", "tz(W")].ek)
自己封装一个加密函数,对这个 h 函数进行调用,如下:

function encode(am, ek) {word = JSON.stringify(am);ms = h(word, ek)return ms
}

然后会提示,v 或者 f 不存在:

78BWCc.jpg

我们在网页相同的地方下断点,将 v 与 f 扣下来补到我们刚刚的 js 文件中。
接着继续调试,同样还会提示各种函数不存在:

78BvB3.jpg

我们同上的方法将缺失模块找到放到 js 文件种进行调试,最终 data 参数即可生成:

78Babj.jpg

这种方法最后全部必要的 js 代码拿下来的话,代码 800 行左右。

第二种方法,我们可以跟进主要的加密函数进行代码分析,将主要代码扣下来,在大函数 h 中,首先对 ek 进行了翻转,如下:

78BDR5.jpg

复现如下:

 var i = ek["split"]("")["reverse"]()["join"]("")

紧接着将翻转后的 ek 传入var t = f[n("0xc1", "8s2n")](i, f[n("0x930", "8Sjv")](e, i, 4)); 进行处理,我们可以看见翻转后的 ek 为 i ,同 4 被传入 e 中进行处理,跟进 e 中,看看他进行了哪些操作:

78BOZm.jpg

同样这部分操作也进行了平坦流的混淆,经过分析可知, 这个算法将输入字符串 w 按照参数 c 分块,对每个块的字符进行 Unicode 编码累加,乘以参数 e(默认 31),然后取模字符串 i 的长度,从中选出对应字符,最终生成一个新的字符串返回。 最终算法如下:

function eee(w, c, e) {var i = "abcdhijkxy";var t = 1;var x = [];for (var r = 0; r < c; r++) {var D = 0;var s = r * t;var K = r == (c - 1) ? (t + w.length % c) : t;for (var C = 0; C < K; C++) {var O = s + C;if (O < w.length) {D += w.charCodeAt(O);}}D = D * (e || 31);x.push(i.charAt(D % i.length));}return x.join("");
}

同网站测试结果相同,说明我们算法正确:

78BIDI.gif

最终我们再来分析 m 函数,进入 m 函数中,发现同样也是吃相极其难看的代码:

78BUq4.jpg

经过分析可知,m 函数初步流程为使用密钥 c 进行简单的编码,复现如下:

if (!w) {return "";}var e = w;var t = "";for (var x = 0; x < e["length"]; x++) {t += String.fromCharCode(e["charCodeAt"](x)^ c["charCodeAt"](x%c["length"]));}

最终传入 d 函数进行最后的加密,最后我们进入 d 函数进行分析:

78B1Dh.jpg

发现有我们很熟悉关键字 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",最终证实 d 函数确实是一个 base64 加密,只是通过平坦流将代码拆分,变成我们不熟悉的样子,至此所以加密流程分析完毕,将全部代码进行整合,即可复现 data 参数的生成:

78BPf9.jpg

发现代码加密流程仅 40 行,相比之前的 800 多行,发现算法跟踪下来,我们生成的加密算法更加简洁,不同方法适应不同的人,总会找到一个适合你的方法。

九宫格模型训练

针对模型的训练,相信之前的文章大家也都了解的很多了,九宫格可以采用纯分类实现,也可以使用检测+分类去实现,具体实现方法参考往期文章:

import os
import shutil
import random
import tensorflow as tf
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.applications.resnet50 import ResNet50
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Dense, Flatten
from tensorflow.keras.optimizers import SGD# 参数配置
config = {'train_data': './data','val_data': './data','input_shape': (60, 60, 3),'batch_size': 32,'nb_epoch': 100,'workers': 1,'learning_rate': 0.001,'momentum': 0.9,'reduce_lr_patience': 4,'early_stopping_patience': 10,'model_name': 'tuili','base_model_path': '', # 预训练模型  为空就用 ResNet50
}def split_dataset(dataset_dir, train_dir, val_dir, val_ratio=0.2):"""划分数据集为训练集和验证集:param dataset_dir: str, 数据集文件夹路径:param train_dir: str, 训练集文件夹路径:param val_dir: str, 验证集文件夹路径:param val_ratio: float, 验证集比例:return:"""if not os.path.isdir(train_dir):os.makedirs(train_dir)if not os.path.isdir(val_dir):os.makedirs(val_dir)for class_name in os.listdir(dataset_dir):class_dir = os.path.join(dataset_dir, class_name)if not os.path.isdir(class_dir):continuetrain_class_dir = os.path.join(train_dir, class_name)val_class_dir = os.path.join(val_dir, class_name)if not os.path.isdir(train_class_dir):os.makedirs(train_class_dir)if not os.path.isdir(val_class_dir):os.makedirs(val_class_dir)img_list = os.listdir(class_dir)img_num = len(img_list)val_num = int(val_ratio * img_num)random.shuffle(img_list)val_list = img_list[:val_num]train_list = img_list[val_num:]for img_name in val_list:src_path = os.path.join(class_dir, img_name)dst_path = os.path.join(val_class_dir, img_name)shutil.copyfile(src_path, dst_path)for img_name in train_list:src_path = os.path.join(class_dir, img_name)dst_path = os.path.join(train_class_dir, img_name)shutil.copyfile(src_path, dst_path)print('Split dataset into train and val successfully!')def prepare_data(data_dir, val_data_dir, input_shape, batch_size):"""数据预处理和加载"""train_datagen = ImageDataGenerator(rescale=1.0 / 255)val_datagen = ImageDataGenerator(rescale=1.0 / 255)train_ds = train_datagen.flow_from_directory(data_dir,target_size=input_shape[:2],batch_size=batch_size,class_mode='categorical',seed=123)val_ds = val_datagen.flow_from_directory(val_data_dir,target_size=input_shape[:2],batch_size=batch_size,class_mode='categorical',seed=123)return train_ds, val_dsdef build_model(input_shape, num_classes, base_model_path=None):"""构建模型"""if base_model_path:model = tf.keras.models.load_model(base_model_path)else:base_model = ResNet50(weights='imagenet', include_top=False, input_shape=input_shape)x = base_model.outputx = Flatten()(x)predictions = Dense(num_classes, activation='softmax')(x)model = Model(inputs=base_model.input, outputs=predictions)model.compile(optimizer=SGD(learning_rate=config['learning_rate'], momentum=config['momentum']),loss='categorical_crossentropy',metrics=['accuracy'])return modeldef main():# 数据集划分(如果需要)# split_dataset(config['dataset_dir'], config['train_dir'], config['val_dir'])train_ds, val_ds = prepare_data(config['train_data'], config['val_data'], config['input_shape'],config['batch_size'])# 获取类别数num_classes = len(train_ds.class_indices)# 构建模型model = build_model(config['input_shape'], num_classes, config['base_model_path'])# 模型摘要打印model.summary()# 设置回调callbacks = [tf.keras.callbacks.ModelCheckpoint(filepath=config['model_name'], monitor='val_accuracy', save_best_only=True,mode='max'),tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', patience=config['reduce_lr_patience']),tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=config['early_stopping_patience']),]# 训练模型model.fit(train_ds,epochs=config['nb_epoch'],validation_data=val_ds,callbacks=callbacks,workers=config['workers'])if __name__ == '__main__':main()

最终在推理的时候,将模型转为 onnx (相关转换在上述付费文章中均有讲到),然后我们只需要将九宫格图片按照指定的大小切割出来去识别然后和标题对应就好了,关于打标的话,星球也有相关配套工具给大家使用:

78BMfV.gif

推理如下:

import onnxruntime
import numpy as np
import cv2
from PIL import Imageclass OCR():def __init__(self, model_path):self.ort_session = onnxruntime.InferenceSession(model_path)def inference(self, img_tensor):inputs = {self.ort_session.get_inputs()[0].name: img_tensor}outInfo = self.ort_session.run(None, inputs)[0]result_index = int(np.argmax(outInfo))class_names = ['大象', '熊猫', '狗', '猫', '自行车', '船', '车', '飞机', '鱼', '鸟']result = class_names[result_index]return resultdef preprocess_image(self, img):img = cv2.resize(img, (110, 110))img = np.asarray(img).astype(np.float32)img = np.expand_dims(img, axis=0)return imgdef split_image(img):content_box_list = []height, width = img.shape[:2]block_height = height // 3block_width = width // 3for i in range(3):for j in range(3):block = img[i * block_height:(i + 1) * block_height, j * block_width:(j + 1) * block_width]content_box_list.append(block)return content_box_listdef identify_grid_content(image_path, model_path):ocr = OCR(model_path)image = cv2.imread(image_path)content_box_list = split_image(image)result_list = []for idx, image in enumerate(content_box_list):img_tensor = ocr.preprocess_image(image)identified_item = ocr.inference(img_tensor)result_list.append((idx, identified_item))return result_list# Example usage
if __name__ == '__main__':image_path = '1.jpg'model_path = './mouan.onnx'results = identify_grid_content(image_path, model_path)print(results)

另外一种办法就是用 v8 去检测实现分类识别,关于相关文章在之前也有提过,训练完毕以后我们可以采用星球成员提出的方法进行推理部署:

基于 fastdeploy 的多方法图标推理:https://t.zsxq.com/Xndh3

不管哪种方法,只要能实现就是最好的办法,关于分类的数据集会整理上传到星球,星球成员可以自主下载数据集进行训练。

风控检测

风控会更新,以下是之前研究的时间段的测试结果,仅供参考。

环境正常,会弹九宫格,验证码类型随环境风险等级的提升而改变:

九宫格(bCaptcha2)、Cloudflare(turnstile)、reCaptcha(reCAPTCHA)、滑块(bCaptcha)

出现滑块,就是环境完全黑了,过了滑块也无法请求成功(网页端手动操作也一样)。

主要是三级风控:1. device-info;2. Cookies(BNC_FV_KEY_T、se_gsd 等等);3. 指纹,三级都黑了,就会风控区号(mobileCode),浏览器登录同样被限制(找朋友测试,区号黑了,地理位置、设备不同,登录都是一样的效果):

783XdB.png

风控限制和请求速度无关,与请求量有关,区号被风控后会逐步恢复,1 小时左右会解除限制。对爱尔兰的 ip 风控相对较松。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/778836.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Excel 根据单元格值设置行颜色

开始》条件格式》管理规则》新建格式规则》使用公式确定要设置格式的单元格 只为满足以下条件的单元格设置格式:=SEARCH("进行中",$E5)>0 ;(注释:此处筛选的是包含进行中的数据) 格式:选择满足条件的单元格设置什么格式 对某个单元格设置后【此示例使用的是…

怎么将回滚操作

点击禁用双击 install

RAG技术要点及英智未来的应用实践

RAG是检索增强生成(Retrieval-augmented Generation),概念是在2020年发表的论文《面向知识密集型NLP任务的检索增强生成》中提出的。 LLM的工作原理是基于预训练的历史数据进行推理生成文本,所以LLM在生成回答的时候可能会引入了过期的、不可预测的或者错误的信息,导致大语…

2024暑假集训测试18

前言比赛链接。这次有大量外校人员参加,\(90\) 来个人,T1 胡了个结论上去结果大小样例都过了,造 hack 还没 hack 了,索性交了,但是有捆绑感觉会爆零,没想到结论是对的,直接 A 了;打完 T1 就罚坐了,三个小时就弄出来 \(5\) 分,当时都绝望了,想到了很多东西。因为感觉…

智能加速计算卡设计原理图:628-基于VU3P的双路100G光纤加速计算卡 XCVU3P板卡

基于VU3P的双路100G光纤加速计算卡 一、板卡概述 基于Xilinx UltraScale+16 nm VU3P芯片方案基础上研发的一款双口100 G FPGA光纤以太网PCI-Express v3.0 x16智能加速计算卡,该智能卡拥有高吞吐量、低延时的网络处理能力以及辅助CPU进行网络功能卸载的能力,达到最大…

《数据资产管理核心技术与应用》读书笔记-第二章:元数据的采集与存储

《数据资产管理核心技术与应用》是清华大学出版社出版的一本图书,全书共分10章,第1章主要让读者认识数据资产,了解数据资产相关的基础概念,以及数据资产的发展情况。第2~8章主要介绍大数据时代数据资产管理所涉及的核心技术,内容包括元数据的采集与存储、数据血缘、数据质…

SchurVINS

SchurVINS: Schur Complement-Based Lightweight Visual Inertial Navigation System SchurVINS:基于Schur补的轻量级视觉惯性导航系统 Yunfei Fan, Tianyu Zhao, Guidong Wang 范云飞,赵天宇,王朝栋 ByteDance 字节跳动 {frank.01, zhaotianyu.1998, guidong.wang}@byteda…

帝国cms里栏目列表模板获取同级栏目的方法

帝国cms里栏目列表模板获取同级栏目的方法 [showclasstemp]$class_r[$GLOBALS[navclassid]][bclassid],3,0,0[/showclasstemp] 必须要先获取到当前栏目的父栏目ID 页面模板内容支持的变量下有 [!--bclass.id--] 但是这个不能在标签里使用 所以采用php代码来获取 $GLOBALS[navcl…

织梦DEDECMS怎么增加自定义属性

使用phpmyadmin方式添加自定义属性的方法步骤: 1、更改数据库前,先备份数据库,以免更改失败时,可以恢复到更改前的状态。 2、用phpmyadmin更改数据库比较直观,方便新手直接看到更改状况,所以建议新手使用这种方法。 3、进入phpmyadmin,打开dede_archives,点击“结构”,…

织梦dede怎么修改关键字长度?

dede文件修改默认关键字长度 第1步:找到并打开dede后台目录下的article_edit.php和article_add.php文件。 电脑维修技术网注:如果是修改专题认关键字的话,需要修改spec_add.php和spec_edit.php文件。 第2步:在文件中搜索"keywords",找到“$keywords = trim(cn_s…

织梦dedecms怎么更换模板

更换 Dedecms 模板是一个相对简单的过程,本指南将详细介绍如何操作。 步骤下载模板从 Dedecms 官方网站或其他可信来源下载所需的模板。上传模板解压缩下载的模板文件,并将所有文件上传到 Dedecms 安装目录中的 "templets" 文件夹。管理模板登录 Dedecms 后台,进入…