首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
Android安全
发新帖
2
4
[原创]看懂身份验证中的活体检测(三):静默活体检测
发表于: 2026-7-30 16:34
451
[原创]看懂身份验证中的活体检测(三):静默活体检测
deltasee
2026-7-30 16:34
451
之前讲了动作活体要用户配合“表演”:眨眼、摇头、张嘴。三色活体又多了一位灯光师:屏幕要依次发出不同颜色。 本篇拆解的静默活体链把这两件事都拿掉了:它直接观察当前帧的人脸区域,再输出一个外观分类分数。 所谓“静默”,只是**交互方式更安静**。本文这份实现回答的仍是:**当前这块普通人脸图像,在模型看来更接近哪一侧。** 前两篇分别讲了[动作活体如何使用 Face 的时序特征](https://bbs.kanxue.com/thread-291933.htm),以及[三色活体如何计算人脸区域的光照响应](https://bbs.kanxue.com/thread-292054.htm)。 这一篇只沿另一条线往下走:`SilentLiveEngine`、`libLiveness.so` 与 ncnn 如何把**一帧人脸 crop**变成静默活体分。 本文只解释检测机制与防御边界,不提供攻击复现;FaceAISDK 仅作为公开项目样本,不作漏洞结论。 ## 1. 先看全图:一帧人脸怎样变成静默活体分  这张图的前半段负责准备输入,后半段才是本文重点:native 怎样裁出人脸、让模型打分并把结果送回 Java。下面只用最少代码接上前两篇,再把篇幅留给 `libLiveness.so`。 ## 2. 节点①:引擎和模型怎样就位 状态机创建时,只要活体检测没有关闭,就会创建 `SilentLiveEngine`。包装层随后加载 so、读取本地配置并让 native 加载模型: ```kotlin [F3:101] if (livenessType != FaceLivenessType.NONE) { // 活体检测未关闭时准备静默引擎 [F3:102] val engine = SilentLiveEngine(context.assets) // 把 App 资源交给包装层 [F3:104] engine.init() // 开始加载 so、配置和模型 [SEJ1:Component.<init>] System.loadLibrary(LiveDetector.tag) // tag 固定为 "Liveness",加载 libLiveness.so [SEJ1:LiveDetector.loadModel] val configs = parseConfig(assetManager) // 解析 assets/liveness/config.json [SEJ1:LiveDetector.loadModel] return nativeLoadModel(assetManager, configs) // 把资源管理器和配置列表交给 JNI/native ``` 这里知道一件事就够了:模型随 SDK 资源在端侧加载,`config.json` 决定 native 加载哪个模型以及怎样准备输入。 ## 3. 节点②—③:从前两篇接过当前帧和人脸框 CameraX 取帧、Bitmap 转换、ML Kit 生成 `Face` 以及多脸时选择最大脸的完整过程,已经在[第一篇:动作检测](https://bbs.kanxue.com/thread-291933.htm)中展开;[第二篇:三色 / 炫彩 / RGB 检测](https://bbs.kanxue.com/thread-292054.htm)也复用了同一条输入链,本篇不再重复贴代码。 没看过前文只需记住:`Face` 是 ML Kit 的人脸检测结果,不是活体结论;`boundingBox` 只标出脸在当前 Bitmap 中的位置。 无脸、距离和多脸检查都发生在静默模型打分之前。 ## 4. 节点④—⑤:Java 怎样把当前帧送进 libLiveness.so 前两篇留下的是当前 Bitmap 和 `Face.boundingBox`。Java 侧只做三步:包装脸框、转换图像、调用引擎。 ```java [F5:200-F5:205] SilentFaceBox box = new SilentFaceBox(left, top, right, bottom, 0.0f); // 把 boundingBox 包装成 native 可读的脸框 [F5:207-F5:208] byte[] nv21 = LibYuv.bitmapToNv21(fullBmp, width, height); // 把当前整帧 Bitmap 转成 NV21 [F5:210-F5:211] float raw = state.OooOOOo.detectLive(nv21, width, height, 0, box); // 输入整帧、尺寸、方向值和脸框 ``` 包装层只校验 NV21 长度并展开脸框,随即进入 JNI: ```kotlin [SEJ1:LiveDetector.detect] if (width * height * 3 / 2 != yuv.size) throw IllegalArgumentException("Invalid yuv data") // 只校验 NV21 长度 [SEJ1:LiveDetector.detect] return nativeDetectYuv(yuv, width, height, orientation, left, top, right, bottom) // 进入 JNI 并返回分数 ``` 进入 `libLiveness.so` 的输入只有四类:**整帧 NV21、帧尺寸、方向值和脸框边界**。 ## 5. 节点⑥—⑧:libLiveness.so 里发生了什么 以下地址化伪代码由作者根据当前 ARM64 `libLiveness.so` 样本的反汇编结果整理,不是上游 C++,地址也只绑定当前样本。 ### native 读到哪些模型资产 `assets/liveness/` 中有两组 ncnn 模型;关键配置按原字段重排后是: ```jsonc [ {"name":"liveness_model_1","scale":2.7,"width":80,"height":80}, // 较小观察范围 {"name":"liveness_model_2","scale":4.0,"width":80,"height":80} // 较大观察范围 ] ``` 两个模型都会把各自的 crop 缩放为 `80×80` 后再推理。 ### 一次调用怎样从 NV21 走到分数 ```cpp [N2:0x1698AC] nativeDetectYuv(nv21, w, h, angle, left, top, right, bottom) // native 接收单帧 NV21、尺寸、方向和人脸框 [N2:0x169960] NV21_to_RGB(nv21, w, h, rgb) // 把 NV21 转成连续 RGB 颜色缓冲 [N2:0x16999C] swap(rgb.R, rgb.B) // 逐像素交换第 1、3 通道,把 RGB 缓冲改成 BGR [N2:0x169D84] maxSafeScale = min((frameW - 1) / faceW, (frameH - 1) / faceH) // 算出不越出画面的最大扩边倍数 [N2:0x169E60] effectiveScale = min(config.scale, maxSafeScale) // 目标尺度过大时,以画面边界允许值为准 [N2:0x169F08] Mat::from_pixels_crop_resize(bgr, PIXEL_BGR, cropRect, config.width, config.height) // 裁出扩边脸区并缩放到配置尺寸 [N2:0x16A048] extractor = net.create_extractor() // 为当前模型创建一次 ncnn 推理 extractor [N2:0x16A07C] extractor.input("data", mat) // 把处理后的人脸 crop 放入名为 data 的输入 [N2:0x16A0AC] extractor.extract("softmax", out) // 读取名为 softmax 的模型输出 [N2:0x16A0B4] live_value = out[1] // 只读取输出数组的第二个元素 [N2:0x169D98] score_sum += live_value // 把当前模型的第二个输出累加进总分 [N2:0x16A140] return score_sum / model_count // 用累加值除以参与推理的模型数量并返回平均值 ``` 这位于总图的**节点⑥ native 预处理层、节点⑦模型层和节点⑧聚合层**。 这段 native 代码可以压成三步: 1. **换颜色格式:**NV21 → RGB → BGR。 2. **裁人脸区域:**按脸框扩边,靠近画面边缘时自动缩小范围。 3. **计算分数:**crop 送入 ncnn,每个模型取 `out[1]`,最后求平均。 ### crop 到底是什么 `crop` 不是一种新图片格式,也不是模型的神秘中间结果。它就是**根据人脸框,从整帧画面中裁出来的模型输入区域**。 | crop | 画面里保留什么 | | --- | --- | | `2.7` 倍 | 人脸占比更大,更集中于脸部 | | `4.0` 倍 | 保留更多头发、轮廓、颈部和周围背景 | 两个 crop 都以脸框中心为基准;靠近画面边缘时,实际观察范围会缩小。 ### 模型真正读的是哪些人脸信号,又怎样算出静默分 先看“画面怎样变成一个分数”:  > 模型读入的是两块人脸 crop 的像素,不是姓名、身份证号、身份模板或真实深度图;每个模型最终输出 3 个 Softmax 类别值。 它不是用固定 RGB 阈值判断肤色,而是可能从 crop 中综合观察这些线索: | 信号 | 画面里可能看到什么 | 不能误读成 | | --- | --- | --- | | 色泽与光照 | 颜色分布、明暗过渡、屏幕发光、材质反光 | 肤色深浅等于真假 | | 纹理 | 皮肤连续性、打印网点、屏幕像素、摩尔纹、压缩痕迹 | 当前权重一定依赖某一种纹理 | | 立体外观 | 鼻梁、眼窝、面颊的轮廓、阴影与遮挡 | 测得真实物理深度 | | 区域一致性 | 脸、头发、耳朵、颈部、边缘和背景的衔接 | 证明画面来自可信摄像头 | | 成像痕迹 | 扭曲、异常光影、重采样、边缘重复 | 某一种痕迹就能单独决定真假 | 现有模型结构与开源测试支持把 `out[1]` 解释为真人/活体方向。上面的 native 代码已经给出打分算法,换成公式就是: ```text 静默分 =(模型 1 当前 crop 的真人类别值 + 模型 2 当前 crop 的真人类别值)÷ 2 ``` | 模型 | 三分类输出示意 | 代码实际取值 | | --- | --- | --- | | model_1 | `[0.12, 0.78, 0.10]` | `out[1] = 0.78` | | model_2 | `[0.08, 0.86, 0.06]` | `out[1] = 0.86` | | 最终静默分 | `(0.78 + 0.86) ÷ 2` | `0.82` | > `0.82` 只是计算示意,不是实测结果,也不等于“系统有 82% 把握证明镜头前是真人”。 ## 6. 节点⑨—⑩:静默分怎样进入最终判断 模型返回后,Java 保存三位小数静默分,再交给上层 Demo: ```java [F5:213] state.OooOOo0 = Math.round(raw * 1000f) / 1000f; // 四舍五入后保存为三位小数静默分 [F5:820] callback.onLivenessDetected(OooOOo0, bitmap); // SDK 把静默分和当前 Bitmap 交给上层 [F1:102] public void onLivenessDetected(float livenessValue, Bitmap bitmap) { // Demo 接收最终活体回调 [F1:104] if (livenessValue > 0.75) { // Demo 用自己的实现常量消费这个静默分 [F1:111] finishFaceVerify(10, R.string.liveness_detection_done, livenessValue); // 把结果和静默分交给完成流程 [F1] } // 结束 Demo 的结果判断 [F1] } // 结束最终活体回调 ``` `OooOOo0` 是静默活体类别分,不是身份分、三色 `gainScore` 或校准概率;回调不再推理,只把它交给业务层。 三色链路可回看[第二篇](https://bbs.kanxue.com/thread-292054.htm)。 ## 7. 从三个商业样本看静默活体的现实形态 FaceAISDK 把一个端侧单帧模型讲得很清楚,但它还不能代表完整商业认证系统。基于作者对三个匿名 Android 商业身份核验样本的代码与运行链路观察,至少可以看到三种不同组合: | 脱敏样本 | 观察到的检测形态 | 给第三篇的启发 | | --- | --- | --- | | 样本 A | 端侧分类和多任务反伪模型被放进多帧、光照响应状态机 | 单帧模型分可能只是流程中的一个状态信号 | | 样本 B | RGB 反伪模型之外,还出现纹理统计、多帧一致性和可选近红外(NIR)路径 | 商业检测会把模型、传统图像特征和多模态能力组合起来 | | 样本 C | 端侧负责人脸质量、流程与素材采集,服务端继续结合风险信号和身份比对裁决 | 端侧检测通过不等于整条认证链已经通过 | > **“静默”首先描述用户体验,而不是统一的算法名称。**三个样本共同说明:本文的端侧单帧模型只可能是商业认证系统的一层,其他实现还可能组合多帧、可选多模态、设备风险、云端反伪和身份比对;这些是方向性观察,不代表所有厂商。 ## 8. 这一层在防御体系中的位置 | 问题 | 结论 | | --- | --- | | 它补了什么 | 无需用户动作或屏幕发光,也能为当前人脸 crop 输出一条外观分类分数 | | 它没覆盖什么 | 不证明身份、真实深度、采集来源或摄像头到 SDK 的链路完整性 | | 防御方怎样使用 | 分层记录输入质量、静默分、其他检测分、最终结果、失败原因和版本 | ## 9. 小结 静默活体之所以“静默”,是因为用户不必完成动作,屏幕也不必为这一层打三色光;不是因为模型已经越过图像,直接知道了“真人是谁、画面从哪里来”。 从这份代码证据看,它的完整路径是: > 当前帧与人脸框 → NV21 与 `SilentFaceBox` → `libLiveness.so` → crop / resize → ncnn → 累加 `out[1]` 并平均 → 三位小数 `OooOOo0` → `onLivenessDetected` → Demo 业务判断。 把这条链路读准确,防御方才能既不低估静默活体,也不把一个单帧外观分类分数抬成整条身份可信结论。 下一篇,我们把动作、三色 / RGB 与静默三层重新放回同一条画面信任链,讨论一个更贴近现实的问题:**为什么 AI 生成视频可能同时挑战这三层活体防护?** 这是前三篇的综合收尾,只讲三层共享的信任假设与防御意义,不做攻击复现。 --- 代码索引说明:文中的 `[F*]`、`[SEJ1]` 与 `[N2]` 标记来自作者对 FaceAISDK 公开流程、包装层和当前 ARM64 样本的防御侧整理,只用于定位流程证据;本文不包含攻击实现、复现路径或攻击样本。
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
收藏
・
2
点赞
・
4
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
欲拭泪
为你点赞!
2026-7-30 18:21
东方玻璃
感谢你的积极参与,期待更多精彩内容!
2026-7-30 17:09
New对象处
非常支持你的观点!
2026-7-30 17:00
Yangser
为你点赞!
2026-7-30 16:54
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
5
)
Yangser
雪 币:
1953
活跃值:
(4173)
能力值:
( LV4,RANK:50 )
在线值:
发帖
11
回帖
129
粉丝
217
关注
私信
Yangser
2
楼
cy
2026-7-30 16:54
0
New对象处
雪 币:
361
活跃值:
(8070)
能力值:
( LV3,RANK:30 )
在线值:
发帖
6
回帖
182
粉丝
32
关注
私信
New对象处
3
楼
太强了,心里有个疑问,人脸失败这个结果太含糊了,是怎么排除
2026-7-30 17:04
0
温泉划水鱼
雪 币:
84
能力值:
( LV1,RANK:0 )
在线值:
发帖
6
回帖
224
粉丝
28
关注
私信
温泉划水鱼
4
楼
感谢分享,很巧妙的设计
2026-7-30 19:50
0
西瓜帅
雪 币:
1825
活跃值:
(2963)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
76
粉丝
1
关注
私信
西瓜帅
5
楼
叽里咕噜说啥呢 ,直接让ai一顿乱改
2026-7-30 20:06
0
deltasee
雪 币:
226
能力值:
( LV1,RANK:0 )
在线值:
发帖
3
回帖
1
粉丝
9
关注
私信
deltasee
6
楼
New对象处
太强了,心里有个疑问,人脸失败这个结果太含糊了,是怎么排除 [em_004]
人脸失败是最终结果,就像你的环境被检测一样,还是要拆开来看,所以我把这个Demo拆分开3块来讲的,人脸验证的成功与否就是在这三块的结果汇总。
2026-8-6 15:04
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
deltasee
3
发帖
1
回帖
0
RANK
关注
私信
他的文章
[原创]看懂身份验证中的活体检测(三):静默活体检测
451
[原创]看懂身份验证中的活体检测(二):三色/炫彩/RGB检测
1388
[原创]看懂身份验证中的活体检测(一):动作活体
1730
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部