Android XX验证码SDK验证流程分析

写在前面

验证码是我们做攻防对抗过程中经常会遇到的东西,在大部分核心场景,包括用户登陆,支付等环节,简单的会做个无感验证,但是如果发现异常用户,则会弹出图形验证码等形式。最近因为工作需要,对GeeTest验证码做了分析,今天抽空把整套流程再重新梳理一遍,供有兴趣的同学参考。

整体流程

XX验证码验证流程如下图:

暂时无法在飞书文档外展示此内容

  1. 发出注册请求Register.php,由服务端生成cid,challenge等参数,其中challenge是主要id,用于后续验证。
  2. 拉取验证码信息(get.php),根据注册时获取到的cid,challenge等信息,加密生成参数w,再将w作为固定请求的body数据,验证码服务器在接收到注册请求时,解密w数据,得到cid以及challenge,并生成验证码数据,再将验证码数据返回到客户端。抓包如下图,上面内容为request信息,下面内容为response信息。
  1. 验证码结果验证(ajax.php),客户端将用户处理的验证结果以及challenge,cid等信息一起加密生成参数w,再将参数w作为固定的body数据,返回到验证码服务器进行验证。
  2. 客户端会保存好challenge(具备有一定时效性,超过一定时间将会失效),后续的业务请求中会将challenge值一起携带到后端,后端再根据challenge到验证码服务器进行查询,确认第三部中验证是否合法。如果合法,则正常返回;否则,将返回相应错误码。

w参数计算

java层逆向,通过逐步调试分析,发现最终在调用com.geetest.sdk.a.a函数后,生成的值会作为参数"w"。然而该函数是个native函数,因此需要继续分析so层。(sdk版本:4.0.8)

直接hook native函数com.geetest.sdk.a.a可以发现,get.php请求时的w参数内容为:

json 复制代码
{"challenge":"ffac10aa5b0937bae3cbd895b3ec39a4","cid":"0c326bd5edd071ba9b9dd10df201013f","client_type":"android","lang":"zh-CN"}

无感验证模式下,ajax.php请求时w参数内容为:

python 复制代码
             
{"cid":"0c326bd5edd071ba9b9dd10df201013f","challenge":"ffac10aa5b0937bae3cbd895b3ec39a4","client_type":"android","light":"","serial":"xxxxxxxx","insight":"{"build":"1","release":"1.0","br":"100.0","bs":{"br":"1.0","bs":"5","plugState":"1","health":"2"},"coun":"CN","dh":"2203","dm":"google","dw":"1080","dn":"Pixel6","lang":"zh","mems":"7.90GB","ostype":"android","osver":"13","py":"1","ts":"1706768800000","vendor":"xxxxxx","app":"xxxx","gt3":"4.0.8","uuid":"xxxxxx"}","accessibility":{"voice":0}}

通过frida hook函数RegisterNatives可以知道对应的函数地址,偏移为0x54f4(不同版本sdk的偏移有所不同)。

yaml 复制代码
[INFO][fridaRegstNtv]: name: a, signature: (Ljava/lang/String;)Ljava/lang/String;, fnPtr: 0x78ae9114f4, modulename: libgee-lib.so -> base: 0x78ae90c000, offset: 0x54f4

ida逆向分析,发现可以直接F5,不过代码看着是做了一些简单的混淆,但是大体上逻辑还是能够猜得出来,入口函数如下:

  1. 随机生成16字节密钥。进入61行的函数,可以看出来gen_rand_array16函数是随机生成16字节大小的字符。

  1. 明文数据填充16字节对齐,对于长度少于16个字节,需要补满16个字节,补(16-len)个(16-len),从这特征可以看出来大概率是AES加密。
  1. 上图76行发现对前面随机生成的aes_key做加密处理,并且返回结果为128字节,由于RSA加密结果就是128字节,所以大概率就是用的RSA加密,之后结合动态hook方式获取RSA关键密钥即可。
  2. 字符串拼接,将加密后的数据以及RSA的128位密钥拼一起,再做base64运算。
相关推荐
90后的晨仔2 分钟前
Puppeteer 与 Playwright 深度实战指南:从零到精通,全面提升开发效率
前端
sunphp开发者9 分钟前
阿里云CDN加速配置问题
前端·阿里云·云计算
小小数媒成员15 分钟前
存储器层级结构
java·服务器·前端
单线程121381 小时前
从案例分析 Vue3 Tokenizer 源码
前端·javascript·vue.js
明月_清风1 小时前
🖥️ Electron 三进程 Host 实战:从架构设计到生产落地的完整指南
前端·electron·客户端
GIS数据转换器2 小时前
智慧林草“一张图“平台
java·大数据·服务器·前端·javascript·数据库·人工智能
2401_885885042 小时前
国际语音php接口代码示例:PHP使用cURL快速调用语音发送API
android·开发语言·前端·人工智能·python·php·语音识别
leoZ2312 小时前
AI+前端提效-08 AI自动化文档:前端组件、接口、项目文档自动生成
前端·人工智能·深度学习·神经网络·目标检测·自然语言处理·自动化
lilian2332 小时前
HarmonyOS 7 新特性(五)|ContainerReader 容器断点与自适应布局
前端·华为·harmonyos
计算机魔术师2 小时前
Google DeepMind 将 AI 科学家 Co-Scientist 扩展为实验室集成研究伙伴
前端