ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

从零拆解安卓虚拟摄像头:Xposed Hook 拦截相机 API 与 MediaCodec 硬解码实战笔记

从零拆解安卓虚拟摄像头:Xposed Hook 拦截相机 API 与 MediaCodec 硬解码实战笔记 从零拆解安卓虚拟摄像头Xposed Hook 拦截相机 API 与 MediaCodec 硬解码实战笔记【免费下载链接】android_virtual_camxposed安卓虚拟摄像头 android virtual camera on xposed hook项目地址: https://gitcode.com/gh_mirrors/an/android_virtual_cam如果你曾好奇过安卓虚拟摄像头到底是怎么实现的——为什么一个普通 App 可以把视频通话里的画面换成一段 mp4把拍照结果换成一张图片——这篇文章就是为你准备的。我会以第一人称复盘一次完整的实现从 Xposed Hook 在 Zygote 阶段的注入原理到两代相机接口的拦截思路再到 MediaCodec 硬解码流水线最后附上我踩过的坑与排查清单保证新手也能从头跟到尾。从一次给 App 换脸的需求说起讲个我亲身经历的场面。有阵子我在做自动化测试需要让被测 App 以为摄像头真的在工作——预览要出活生生的画面而不是黑屏。手头没有真实场景也懒得架设备于是问题就变成了能不能让一部手机对外假装它有一颗摄像头答案是可以。安卓虚拟摄像头这条路的核心就是在系统层面把相机 API 的调用结果偷换成我们准备好的内容预览画面换成 virtual.mp4 的视频流拍照结果换成预设图片。先别急着抠代码我们先把敌人的底细摸清楚。相机 API 拦截到底拦的是哪几扇门任何 App 想用摄像头最终都要走系统的相机服务。老一代 App 用 Camera1android.hardware.Camera新一代 App 用 Camera2android.hardware.camera2.*。它们 API 长得不一样但逃不开三个动作打开相机、设置预览输出、取帧或拍照。这就像一栋楼的三扇大门setPreviewTexture / setPreviewDisplayApp 告诉相机把画面画到这块屏幕上setPreviewCallback 系列App 说每来一帧给我一份数据takePictureApp 说拍一张存下来。虚拟摄像头的思路说白了就一句话在门口换货。把原本要交给真相机的目标Surface、回调、拍照结果悄悄换成我们的虚拟内容。至于怎么在门口动手脚那就是 Xposed 的活了。Xposed 是如何拦截相机 API 的Xposed 为什么能改系统方法因为它动手的时机足够早。配图建议此处适合放一张Xposed 注入时机的流程示意图——Zygote 孵化 App → 注入模块 → Hook 系统方法三步箭头图即可alt 文本可写为安卓虚拟摄像头原理之 Xposed Hook 拦截相机 API 的注入流程图Zygote 阶段的插队一切 Hook 的起点Android 里几乎所有 App 进程都是从一个叫 Zygote 的孵化器进程 fork 出来的。Xposed 做的第一件事就是在 Zygote 刚启动、还没开始孵化 App 时把自己的代码注入进去。这样一来后面每个被孵化的 App出生时就自带了这个后门。App 一启动我们的入口方法 handleLoadPackage 就会被回调从这里能拿到两样关键东西目标应用的 classLoader类加载器负责加载所有类以及包名等信息。有了 classLoader就能用 XposedHelpers.findAndHookMethod 去点名任何一个系统方法。划重点Hook 不是改系统源码而是在方法执行前或后插入一段我们自己的逻辑就像在系统大门前加装一个安检哨卡——进不进门你说了算但过不过这道卡得先看我脸色。一个最小的 Hook 长什么样拿 Camera1 的预览入口举例代码最小化后长这样XposedHelpers.findAndHookMethod( android.hardware.Camera, // 要拦截的类 lpparam.classLoader, // 目标应用的类加载器 setPreviewTexture, // 要拦截的方法名 SurfaceTexture.class, // 方法的参数类型 new XC_MethodHook() { Override protected void beforeHookedMethod(MethodHookParam param) { // 这里就是安检哨卡 // param.args[0] 是 App 传进来的预览纹理 // 把它换成我们的假纹理真相机就被晾在一边了 } });大白话解释findAndHookMethod 相当于对系统方法下达通缉令只要目标应用调用 setPreviewTexture就会先钻进 beforeHookedMethod。在 before 阶段改 param.args[0]就能在方法真正执行前把参数偷梁换柱。这个项目里会用一个假的 SurfaceTexture 顶替真纹理同时把真纹理保存下来后续由 MediaPlayer 把视频直接画到真正的预览目标上——真相机一次都没工作但 App 看到的画面却是完整的。小结一下Xposed Hook 实现的本质 找对类 找对方法 在恰当的时机改参数或改返回值。剩下的问题只有一个——该拦哪些方法两代相机 API 的围堵战术视频流替换的双线作战Camera1老接口五个 Hook 点打天下老一代接口的调用链比较直白项目里主要在五个位置设卡setPreviewTexture / setPreviewDisplay——预览输出入口把真纹理换成假纹理或者直接接管 SurfacesetPreviewCallback / setPreviewCallbackWithBuffer / setOneShotPreviewCallback——帧回调入口App 要的每一帧数据用我们解码出来的虚拟帧填充addCallbackBuffer——连缓冲区都换成全新数组防止真假数据串味takePicture——拍照入口JPEG 回调和 YUV 回调分别处理startPreview——真正开播的时机在这里把 MediaPlayer 绑到目标 Surface 上让视频循环播放。Camera2新接口更绕但套路一致Camera2 是流水线式的设计openCamera 打开设备createCaptureSession 建会话addTarget 给请求挂目标 Surfacebuild 提交请求。要拦截的点自然也多了一层openCamera 的 StateCallback 被记下来再去 Hook 回调里的 onOpened等相机真正打开后再下手addTarget / removeTarget 是关键——App 把预览 Surface 挂到 CaptureRequest 上时我们把它替换成一个虚拟 Surface让真相机对着虚拟 Surface 白干活createCaptureSession 及其各种变体createCaptureSessionByOutputConfigurations、createConstrainedHighSpeedCaptureSession 等也要拦把输出列表整体替换。看到这一堆方法名先别慌它们本质都是创建会话的不同重载Android 版本越新重载越多Hook 时只能逐个兼容。这正是这类项目版本地狱的来源之一。注意这里拦截的不是某一个方法而是一整条链路。任何一环漏了App 要么黑屏要么直接闪退。MediaCodec 解码流程分几步把视频变成一帧帧画面拦截只是换货货从哪来答案是把准备好的 virtual.mp4 硬解码成帧。这里用的是 MediaCodec——Android 官方的硬件加速编解码器俗称硬解。与之配合的还有 MediaExtractor负责读文件、挑轨道和 MediaFormat负责描述轨道参数。配图建议此处适合放一张解码流水线示意图——MediaExtractor 选轨 → MediaCodec 配置 → 循环取帧 → 输出 NV21 帧alt 文本可写为MediaCodec 硬解码流程分步示意用于安卓虚拟摄像头视频流替换整条流水线可以拆成四步第一步拆文件。MediaExtractor 打开视频文件逐个轨道看 MIME 类型找到以 video/ 开头的那个selectTrack 选中它。第二步配解码器。拿到轨道的 MediaFormat 后用 MediaCodec.createDecoderByType(mime) 创建对应解码器再把格式和输出目标 configure 进去。输出目标可以是 Surface直接上屏也可以是空拿原始帧数据。第三步循环喂数据。这是一个典型的生产者-消费者循环dequeueInputBuffer 拿空输入槽把 extractor.readSampleData 读到的压缩数据塞进去另一边 dequeueOutputBuffer 取解码结果有画面就渲染或取走原始帧。decoder.configure(mediaFormat, play_surf, null, 0); decoder.start(); while (!sawOutputEOS !stopDecode) { // 喂输入extractor.readSampleData - queueInputBuffer // 取输出dequeueOutputBuffer - 渲染或取帧 }第四步循环播放。这段视频播完输出 EOS后extractor.seekTo(0) 回到开头再播一遍实现无限循环——虚拟摄像头看起来就是永不中断的直播。这里有个细节值得留意解码出的原始帧是 YUV 系Y 亮度 UV 色度而不是 RGB。YUV 家族里常见三种格式NV21 是Y 平面 VU 交错是安卓相机预览的老朋友I420 是Y/U/V 三个平面分开放YUV_420_888 则是 Android Image API 提供的统一抽象可能带行距rowStride和像素间距pixelStride——说白了就是每一行可能有多余的填充字节。所以从 Image 里取数据时不能假设它是紧凑排列的得按行距跳着读。项目里 getDataFromImage 这段逻辑干的就是这件事把带行距的 Image 整理成紧凑的 NV21 或 I420 字节数组。划重点预览回调要的是 NV21解码器给的是 YUV_420_888 抽象格式中间必须有这层规整化转换否则画面不是花屏就是颜色错乱。拍照替换、权限重定向与那些开关文件拍照一张 1000.bmp 走天下拍照的拦截思路和预览一样Hook takePicture 之后再去 Hook 回调类的 onPictureTaken 方法在 before 阶段把字节数组参数换成我们准备的图片数据。JPEG 回调把 1000.bmp 压缩成 JPEG 字节流塞回去YUV 回调则先把 bmp 读成 Bitmap再按 RGB→YCbCr420 的公式换算成 YUV 字节流。这里有个小坑回调类是目标 App 自己定义的类名没法提前写死。所以代码里是拿到回调实例后动态地给它的类再下一个 Hook——先拿到 Class再 findAndHookMethod 这个 Class 的 onPictureTaken。这也是 Xposed 里很常用的一招对着实例反查类再 Hook 类的方法。权限不够把目录整个重定向Android 6.0 之后存储权限收紧目标 App 可能压根拿不到读取外部存储的权限自然读不到我们放在 DCIM/Camera1 里的视频。项目的处理很巧妙检测到权限不足时把素材目录重定向到该 App 自己的私有目录——Android/data/[包名]/files/Camera1/。私有目录 App 自己就能读写完全绕开权限问题。当然代价是这份素材只有这个 App 能用。一排开关文件零代码改配置这个项目最贴心的设计之一是用在素材目录里放特定文件来控制行为不用改代码、不用重启模块disable.jpg——临时停用替换全局实时生效no-silent.jpg——允许播放视频里的声音默认静音no_toast.jpg——关掉烦人的气泡提示force_show.jpg——强制重复显示目录重定向提示private_dir.jpg——强制所有 App 都走私有目录virtual.mp4 / 1000.bmp——替换预览和拍照的素材本体。为什么用文件而不是配置项因为 Hook 运行在目标 App 的进程里改配置得跨进程通信麻烦而文件系统是天然的共享黑板只要 Hook 代码每次读一下文件存在与否就能实时感知变化。️我踩过的坑与排查清单黑屏、花屏、方向不对这三大问题基本能覆盖 90% 的翻车现场。逐个说坑一黑屏。先检查素材路径。常见失误是目录套娃——把 virtual.mp4 放成了 DCIM/Camera1/Camera1/virtual.mp4多套了一层。另外系统相机这类应用通常难以替换成功不是你的问题是它走的路太特殊。坑二花屏。十有八九是分辨率不匹配。App 的预览回调会以气泡消息报出宽xxx 高xxx视频必须按这个尺寸制作。注意看 Toast——这个提示信息在排查时价值千金。坑三方向与变形。前置摄像头画面通常需要水平翻转并右旋 90 度而且处理后的分辨率要与提示的分辨率一致。用剪辑软件处理最省事别在代码里硬拗。坑四录像拦不住。MediaRecorder 的 setCamera 虽然有 Hook但目前的实现只做到发现录像行为并提示并不能真正替换录制内容。这是当前方案的能力边界遇到就认。坑五回调数据串味。帧回调里如果一直往同一个缓冲区写数据容易读到半新半旧的内容。项目里的做法是维护一个全局的当前帧字节数组回调时用 System.arraycopy 拷贝过去并处理长度不一致的情况。小结一下排查顺序先看 Toast 有没有报分辨率 → 检查目录层级 → 核对视频分辨率 → 再查权限重定向到了哪个目录。八成问题都能在这几步里解决。⚠️写在最后这套能力能拿去做什么复盘一下整条链路Xposed 在 Zygote 阶段注入负责换货两代相机接口的 Hook 点负责找准下手的门MediaCodec 硬解码负责造货文件系统负责配货。四件事串起来就是安卓虚拟摄像头原理的全貌。这门手艺的正当用途其实不少做应用兼容性测试时模拟各种摄像头分辨率验证某个 App 是否在后台偷偷调起摄像头或者研究应用如何处理相机数据流。作为安全研究者能亲手看穿相机 API 调用链本身就是很好的逆向练习。如果你想更深入可以从这几个方向继续给视频帧加实时滤镜、支持多路虚拟摄像头、降低 Hook 链路的性能开销或者做一套图形化配置界面替代开关文件。开源社区里有个叫 android_virtual_cam 的项目基于 Xposed 实现支持 Android 5.0代码量不大、注释直白很适合当作第一份虚拟摄像头源码来读。最后提醒一句这类能力天然带有双面性请务必把它用在合法合规的测试与研究场景里。技术本身没有立场用的人才有。【免费下载链接】android_virtual_camxposed安卓虚拟摄像头 android virtual camera on xposed hook项目地址: https://gitcode.com/gh_mirrors/an/android_virtual_cam创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表