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运算。
相关推荐
全宝5 分钟前
用css做一枚拟物风格的按钮
前端·css·svg
IT_陈寒13 分钟前
3年Java开发经验总结:提升50%编码效率的7个核心技巧与实战案例
前端·人工智能·后端
yqcoder20 分钟前
vue2 和 vue3 生命周期的区别
前端·javascript·vue.js
excel31 分钟前
前端人必备的 JavaScript API 全面指南(含 postMessage、File、Stream、Web 组件等)
前端
m0_738120726 小时前
CTFshow系列——命令执行web53-56
前端·安全·web安全·网络安全·ctfshow
Liu.7748 小时前
uniappx鸿蒙适配
前端
山有木兮木有枝_9 小时前
从代码到创作:探索AI图片生成的神奇世界
前端·coze
言兴9 小时前
秋招面试---性能优化(良子大胃袋)
前端·javascript·面试
WebInfra10 小时前
Rspack 1.5 发布:十大新特性速览
前端·javascript·github
雾恋11 小时前
我用 trae 写了一个菜谱小程序(灶搭子)
前端·javascript·uni-app