福Star APP 门禁逆向思路
本文记录分析福Star APP - 身份码的逆向过程及分析思路。当然,本分析的结果大概是失败的,留待有志之士补完。
路由#
身份码的生成主要由三个路由组成,分别为 /member/getBizCardNo, /virtual-card/member/getCertificate 和 /member/generateQRCode,相应的域均为 [https://fsdqrcode.app.fjnu.edu.cn]。
getBizCardNo#
根据观察,该接口用处不大。在实践时,该接口总是返回{"code":-100,"message":"数据异常或数据为空"},判断在实际场景中该接口无效,应使用getCertificate及generateQRCode。
getCertificate#
接口路径:/virtual-card/member/getCertificate
请求类型:POST
Header:userToken(用户token)、sno(学号)
Body:
{"bizType":"1","pubKey":"..."}响应:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19{
"code": 0,
"message": "成功",
"data": {
"cert": {
"platformSign": "...",
"effectiveTime": ...,
"vCardNo": "...", // generateQRCode中使用的vCardNo
"vCardTypeSign": "...",
"publicKey": "...", // 即请求中发送的publicKey
"vCardTypeNo": "1002",
"vCardTypePublicKey": "...",
"version": "1.0",
"algorithm": 2,
"ts": ...
},
"vcardNo": null
}
}
此接口应为获取生成二维码所需的核心凭证。pubKey可以为任意内容,为空则会提示参数缺失。
逆向工作#
通过JADX对APP进行逆向。由于APP进行了加壳,使用Frida对代码进行了动态分析。最终得到以下结论:
- 业务入口: KabawHomePageModel.updateCertification() 方法为了请求接口,需要 pubKey。
- 获取公钥: 调用 VCertificationManager.getInstance().getVCardPublicKey() 来获取公钥字符串。
- 生成密钥别名: getVCardPublicKey 方法内部首先调用 getVCodeKeyName() 方法,将 用户Token + 业务类型Value + 用户ID 拼接成一个唯一的密钥别名 (Key Alias)。
- 路由分发: 接着,请求被交给 MsgSealRouter,它通过一个名为 AndroidRouter 的组件化路由框架,发起了一个路径为 “/getPublicKeyWithAlg” 的跨模块服务请求。
- 服务实现: 一个带有 @RouterPath(“/getPublicKeyWithAlg”) 注解的类接收到该请求,并将处理逻辑层层转发,依次经过 TsbApiServer -> NativeApiServices.TsbServer。
- JNI 接口: 最终,调用到达了 Java 层的终点——一个 native 方法:public static native String getPublicKeyWithAlg(int i, String str);。
最终指向 libmsgsealsdk.so 库中的 Java_com_msgseal_service_services_NativeApiServices_TsbServer_getPublicKeyWithAlg 函数,关于此处的工作没有再进行下去。
generateQRCode#
接口路径:/member/generateQRCode
请求类型:POST
Header:userToken(用户token)、sno(学号)
Body:
1
2
3
4
5{
"vCardNo":"...", // 上一步获取到的vCardNo
"studNo":"...", // 学号
"busNo":"..." // 学号
}响应
1
2
3
4
5
6
7{
"code": 0,
"message": "成功",
"data": {
"qrCode": "..."
}
}
客户端在获取到响应的qrCode后,将其转换为二维码显示在用户界面,也就是最终的身份码。
调试得出以下结论:
- 请求体中的studNo和busNo可以随意修改,但服务器并不会取信,只会使用header中经过验证的sno。
- vCardNo可以随意修改,但只有相应卡号存在于数据库中才能生成qrCode,否则会报错。
- 对于同一用户,其对应的vCardNo值(18位文本型整数)始终不变。
QRCode 不同部分含义#
| 部分 (Part) | 值 (Value) | 来源/含义分析 |
|---|---|---|
| 应用/业务标识 | FSQRFSTAE1010112 | [确认] 固定前缀。FSQRFSTAE 可能是应用名缩写,1010112 是具体的业务类型代码。 |
| 学号/业务号 | xxx | [新发现/确认] 直接来源于请求 Body 中的 studNo 和 busNo。 |
| 未知代码 | 33 | 含义仍不明确,可能是固定的业务子类型、校验码或者版本标识。 |
| 虚拟卡号 | xxxx | [确认] 来源于请求 Body 中的 vCardNo。 |
| 分隔符 | ** | [确认] 固定分隔符。 |
| 学号/业务号 (重复) | xxx | [确认] 再次使用了 studNo / busNo。 |
| 分隔符 | * | [确认] 固定分隔符。 |
| 过期时间戳 | 20250922144011 | [确认] 服务器生成的过期时间 (YYYYMMDDHHMMSS)。 |
| 有效时长 | 18000 | [确认] 很可能是有效时长(18000秒 = 5小时)。 |
| SM2 签名 | … | [确认] 服务器对前面所有数据生成的 SM2 签名。 |
| 版本号 | V1.0 | [确认] 固定的版本号。 |
猜测的后端处理流程#
- 基于userToken确认用户登录状态。
- 使用userToken在数据库中验证学号studNo。
- 对用户提交的vCardNo进行校验。
- 拼接二维码字符串。
- 签名并返回,得到qrCode。
细碎部分#
- 两个接口均可进行重放。在userToken不失效的情况下一直有效。
/virtual-card/member/getCertificate接口的作用似乎只是单纯为了获取到vCardNo,pubKey可以随意修改的操作推翻了我之前的部分设想。疑似为:客户端不知道或者不确定当前的 vCardNo,于是它调用 getCertificate 接口;服务器通过 userToken 识别用户,返回包含 vCardNo 的 cert 对象;客户端从 cert 对象中提取出 vCardNo,而 cert 对象中的其它信息在当下并未被直接使用。那么是否可以通过直接修改/member/generateQRCode接口的请求参数实现伪造?但经过测试,在调用/member/generateQRCode接口时,修改studNo、busNo不会影响到最终结果;修改vCardNo时可以正常生成带有不同vCardNo的qrCode,但是刷码时依然显示自己的名字。