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运算。
相关推荐
神の愛1 小时前
左连接查询数据 left join
java·服务器·前端
小码哥_常2 小时前
解锁Android嵌入式照片选择器,让你的App体验丝滑起飞
前端
郑寿昌3 小时前
IIoT本体迁移的领域扩展机制
服务器·前端·microsoft
深海鱼在掘金4 小时前
Next.js从入门到实战保姆级教程(第十一章):错误处理与加载状态
前端·typescript·next.js
深海鱼在掘金4 小时前
Next.js从入门到实战保姆级教程(第十二章):认证鉴权与中间件
前端·typescript·next.js
energy_DT4 小时前
2026年十五五油气田智能增产装备数字孪生,CIMPro孪大师赋能“流动增产工厂”三维可视化管控
前端
龙猫里的小梅啊4 小时前
CSS(四)CSS文本属性
前端·css
MXN_小南学前端4 小时前
watch详解:与computed 对比以及 Vue2 / Vue3 区别
前端·javascript·vue.js
饭小猿人4 小时前
Flutter实现底部动画弹窗有两种方式
开发语言·前端·flutter
让学习成为一种生活方式5 小时前
pbtk v 3.5.0安装与使用--生信工具084
前端·chrome