在 Fedora 44 上运行 Redroid(Android 13)—— 一份实战指南
太长不看版 —— Fedora + SELinux 给 Redroid 埋了三颗隐形地雷。本文从干净部署开始,逐一记录我踩到的具体故障模式,以及最终让
sys.boot_completed=1的修复方法。已在 Fedora 44(GNOME Wayland)、内核 6.19.10、Docker 29.6.2、SELinux 强制模式、无 KVM 环境下验证通过。
目录
- 为什么选 Redroid?
- 第一部分 —— 如何部署
- 前置条件:内核、binderfs、Docker
- 拉取镜像
- 运行容器
- 用 adb 验证
- 工具链:frida + Burp + CA 证书
- 第二部分 —— 问题、原因与修复
- P1:容器不到 1 秒就退出(exit 129)
- P2:
vdc cryptfs init_user0永久挂起 - P3:所有 HAL 崩溃并报 "Could not register service"
- P4:podman 瞬间杀死容器
- P5:Android 应用无视全局代理(#p5)
- P6:重建容器导致一切丢失
- 附赠:“最新”镜像不一定最好
- 恢复检查清单(容器重建后)
- 常见错误速查表
为什么选 Redroid?
想为移动应用渗透测试准备一个 Android 模拟器,但:
- 机器是虚拟机:没有
/dev/kvm,没有 VT-x → Android Studio AVD 和 Genymotion 走不通(它们需要硬件虚拟化)。 - 手边没有物理设备。
Redroid 将 Android 作为一个普通 Docker 容器运行。不需要 KVM,不需要 GPU —— 非常适合无特权的虚拟机环境。它启动的是基于 LineageOS 的 Android 13,暴露 5555 端口的 adb,默认有 root 权限,而且没有反模拟器检测的包袱。
第一部分 —— 如何部署
前置条件
1. 内核 —— binderfs(必须)
Android 的 IPC(Binder)需要 binder 设备。检查:
ls /dev/binderfs/ # 期望看到:binder、hwbinder、vndbinder(三个都要有)
grep binder /proc/filesystems # 期望看到 "nodev binder"
如果缺失:
sudo modprobe binder_linux
# 持久化加载:
echo "binder_linux" | sudo tee /etc/modules-load.d/binder.conf
echo "binderfs /dev/binderfs binderfs nofail 0 0" | sudo tee -a /etc/fstab
不需要 ashmem —— Android 13 改用 memfd。
2. Docker(不要用 podman —— 见 P4)
sudo dnf install moby-engine # Fedora 的 Docker(CE)包
sudo systemctl enable --now docker
3. 拉取 Redroid 镜像
sudo docker pull redroid/redroid:13.0.0-latest
在受限网络(如中国)中,可在 /etc/docker/daemon.json 中添加镜像加速器:
{"registry-mirrors": ["https://dockerproxy.net", "https://docker.m.daocloud.io"]}
运行容器
sudo docker run -itd --name redroid --privileged \
-v /dev/binderfs:/dev/binderfs \
-p 5555:5555 \
redroid/redroid:13.0.0-latest \
androidboot.redroid_gpu_mode=guest \
ro.product.model="Pixel 6" ro.product.brand=google \
ro.product.name=redroid ro.product.device=redroid \
ro.product.manufacturer=Google \
ro.debuggable=1 ro.secure=0
关键参数:
--privileged—— Redroid 需要挂载文件系统和操作 binder。必须。-v /dev/binderfs:/dev/binderfs—— 将 binder 挂载点传入容器。-p 5555:5555—— adb 端口。ro.debuggable=1 ro.secure=0—— 使adb root生效。
用 adb 验证
adb connect 127.0.0.1:5555
adb devices # 期望看到 "device"
adb root # "adbd is already running as root"
adb shell getprop sys.boot_completed # 期望 1
adb shell getprop init.svc.zygote # 期望 running
adb shell getprop ro.build.version.release # 13
健康系统下,Android 13 在 60~90 秒内完成启动。
安装渗透测试工具链
frida-server(版本须与主机 frida-tools 匹配):
# 主机:pip install frida-tools objection
# 下载 frida-server-<ver>-android-x86_64,然后:
adb push frida-server-17.16.4-android-x86_64 /data/local/tmp/frida-server
adb shell "chmod 755 /data/local/tmp/frida-server"
adb shell "sh -c '/data/local/tmp/frida-server > /dev/null 2>&1 &'"
frida-ps -U # 验证进程列表是否显示
Burp 代理 + CA 证书(完整拦截链路):
adb reverse tcp:8080 tcp:8080 # 设备 → 主机 Burp
adb shell "settings put global http_proxy 127.0.0.1:8080"
# Redroid 特有问题:仅 settings 不够 —— 见 P5:
adb shell "setprop http.proxyHost 127.0.0.1; setprop http.proxyPort 8080;"
adb shell "stop && sleep 2 && start" # 重启框架
adb push 9a5ba575.0 /system/etc/security/cacerts/9a5ba575.0 # Burp CA(哈希命名)
adb shell "chmod 644 /system/etc/security/cacerts/9a5ba575.0"
验证:在模拟器内发起 HTTPS 请求(通过 frida 脚本在 system_server 中执行),然后观察 Burp 的 HTTP 历史中是否出现明文流量。已验证可行:UA=Dalvik/2.1.0 (Linux; U; Android 13; redroid13_x86_64),访问 https://www.baidu.com → HTTP 200 解密成功。
jadx 用于静态分析:sudo dnf install jadx(版本 1.5.0)。
第二部分 —— 问题、原因与修复
P1:容器不到 1 秒就退出(exit 129,日志为空)
现象
docker run 成功,然后容器约 0.15 秒内退出,代码 129(128+SIGHUP)。docker logs 完全为空。在 SELinux 强制模式的 Fedora 上,首次 dmesg -T | grep init 显示:
init: Failed to initialize property area
init: ... InitFatalReboot ...
可能原因 —— 有两层:
- Fedora 的 Docker 服务在 systemd 单元中硬编码了
--selinux-enabled。每个容器挂载都被打上了固定的 SELinux 上下文(system_u:object_r:container_file_t:s0:c1022,c1023),挂载点不再携带seclabel标签。Android 的 init 在/dev/__properties__/*上调用fsetxattr(security.selinux)时失败,返回EOPNOTSUPP(errno 95)。 - 即使解决了第一层,精简的主机 SELinux 策略缺少 Android 所需的约 263 个类型(如
properties_serial),导致上下文写入时返回 EINVAL。
修复(同时解决两层):
禁用 Docker 的 SELinux 集成:
# /etc/docker/daemon.json
{"selinux-enabled": false, "registry-mirrors": ["..."]}
# Fedora 在单元文件中硬编码了该标志 —— 通过 drop-in 文件覆盖:
sudo mkdir -p /etc/systemd/system/docker.service.d
# /etc/systemd/system/docker.service.d/selinux-off.conf
# [Service]
# ExecStart=
# ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock \
# --userland-proxy-path /usr/bin/docker-proxy --init-path /usr/bin/tini-static
sudo systemctl daemon-reload && sudo systemctl restart docker
docker info | grep -i selinux # 确认已消失
修补镜像中的 Android 属性上下文,将其改为主机策略实际允许的类型:
# 从主机策略中挑选真实存在的 file_type 类型(如 abrt_etc_t、bin_t)
sed -i 's/u:object_r:[a-z_0-9]*:s0/system_u:object_r:abrt_etc_t:s0/g' plat_property_contexts
echo "(default system_u:object_r:abrt_etc_t:s0)" >> plat_property_contexts
# 此外还需二进制字符串修补:
# /system/lib64/bootstrap/libc.so : "u:object_r:properties_serial:s0"
# /system/bin/init : "u:object_r:default_prop:s0"
通过 docker create + docker cp + docker commit 重新打包(不要用 docker import —— tar 包内含设备节点,containerd 无法解包):
CID=$(docker create --name redroid-fix redroid/redroid:13.0.0-latest)
docker cp plat_property_contexts $CID:/system/etc/selinux/plat_property_contexts
docker cp init $CID:/system/bin/init
docker cp libc.so $CID:/system/lib64/bootstrap/libc.so
docker commit $CID redroid-fixed:13
P2:vdc --wait cryptfs init_user0 挂起 —— 启动无法继续
现象: 容器存活,但 init 显示 Exec service is hung? Waited Ns without SIGCHLD。一个 PPID=1 的子进程(vdc --wait cryptfs init_user0)一直处于 nanosleep 状态。Zygote 和 adbd 永不启动。
可能原因: 镜像中没有 fstab → vold 从未初始化 → Redroid 的自定义 init.rc 在 post-fs-data 阶段无条件执行 init_user0(该内置命令会 fork 出 vdc 并等待 vold)→ 永久等待。检查:getprop ro.crypto.state 为空,/system/etc/fstab* 缺失。
修复:
# 1) 注释掉 init.rc 中的 init_user0 行
# grep -n init_user0 /system/etc/init/hw/init.rc → 用 sed 注释该行
sed -i 's/^\(.*init_user0.*\)$/#[FBE-skip] \1/' /system/etc/init/hw/init.rc
# 2) 提供一个最小的 fstab(注意:共 5 个字段!fs_mgr_flags 是第 5 个字段)
cat > fstab.redroid <<'EOF'
# Android fstab file.
#<src> <mnt_point> <type> <mnt_flags> <fs_mgr_flags>
/dev/block/vdc /data ext4 rw,nosuid,nodev,noatime,noauto_da_alloc,reservedsize=128M,quota formattable,check
EOF
# 复制到 /system/etc/fstab.redroid 和 /system/etc/fstab 两个位置
# 3) 告诉 zygote 分支该设备未加密:
printf 'ro.crypto.state=unencrypted\nro.crypto.type=none\n' > /system/etc/prop.default
# 重新提交镜像
提示:同时将 init.rc 中剩余的 5 行 exec ... vdc checkpoint/keymaster 替换为 /system/bin/true,否则它们也会阻塞。
P3:所有供应商 HAL 崩溃 —— "Could not register service (-2147483648)"
现象: 所有供应商 HAL 进程(keymaster、gatekeeper、wifi、hidl_memory)反复崩溃;surfaceflinger 报告 gralloc-mapper is missing;/var/log/audit/audit.log 显示:
avc: denied { set_context_mgr } scontext=system_u:system_r:spc_t:s0
tcontext=system_u:system_r:spc_t:s0 tclass=binder
可能原因: 精简的主机策略缺少 allow spc_t spc_t: binder set_context_mgr —— 因此 servicemanager / hwservicemanager 永远无法成为 binder 上下文管理器,所有服务注册失败。
修复 —— 为 spc_t 创建一个很小的许可型模块(比逐条添加 allow 规则更干净,一次性放通容器内部所有 avc 检查,主机策略保持不变):
# /tmp/redroid_perm.te
# module redroid_perm 1.0;
# require { type spc_t; }
# permissive spc_t;
sudo checkmodule -M -m -o /tmp/redroid_perm.mod /tmp/redroid_perm.te
sudo semodule_package -o /tmp/redroid_perm.pp -m /tmp/redroid_perm.mod
sudo semodule -i /tmp/redroid_perm.pp
注意:
checkmodule要求模块名与输出基名一致(redroid_perm⇔redroid_perm.mod)。
P4:podman 瞬间杀死容器(129)—— Docker 则正常
现象: 使用 podman(rootful)时,Redroid 容器在 0.15 秒内以 exit 129 退出,日志为空;而用 Docker 运行同一镜像则正常。
可能原因: podman 与 Android 的 pid-1 init 进程不兼容(信号处理 / conmon 交互问题 —— 向 Android init 发送了 SIGPHUP)。不值得深究。
修复: 使用真正的 Docker(moby-engine)。已确认:podman 下前台运行约 25 秒后退出,而 Docker 下长期稳定运行。
P5:settings put global http_proxy 被应用忽略
现象: 代理已设置,但 Java/OkHttp/WebView 流量从未到达 Burp;纯 HTTPS 请求超时。
可能原因: 在原生 Android 中,ConnectivityService 会将全局代理同步到 系统属性 http.proxyHost / http.proxyPort,Java 运行时在进程启动时会快照这些属性。而 Redroid 模拟的 ConnectivityService 从未执行该同步 —— getprop http.proxyHost 始终为空。
修复:
adb shell "setprop http.proxyHost 127.0.0.1; setprop http.proxyPort 8080;"
adb shell "stop && sleep 2 && start" # 重启框架,让新进程读取这些属性
同时配合 adb reverse tcp:8080 tcp:8080,使模拟器无需知道主机 IP 即可访问 Burp。
验证完整链路:在 system_server 内运行一个 frida 脚本,通过 HttpURLConnection 打开 https://... —— 它会遵循系统代理;观察 Burp 显示明文。
P6:重建容器导致 frida、CA 证书和代理全部丢失
现象: docker rm + 再次 run 之后,突然发现 frida-server 提示 No such file or directory,CA 证书丢失,代理设置消失。
可能原因: 你在运行中的容器内推送的所有东西都存在于其可写层中。删除容器即丢弃该层 —— 这里 Docker 没有持久化的 /data 卷。
修复: 将其视为 6 步恢复检查清单(见下文),或添加持久化卷:
-v ~/.redroid_data:/data
P7:“最新镜像不一定是最好的镜像”
这一点耗费了我不少时间:镜像迭代链最终停在 redroid-fixed9:13,但 fixed9 永远达不到 sys.boot_completed(zygote 持续重启),而 redroid-fixed2:13 却能干净启动,且整个 frida/Burp 链路全部通过。永远不要假设最新 tag 最健康 —— 在每个候选镜像上验证启动状态,固定使用能工作的那个(在命令中明确指定 tag,如 redroid-fixed2:13)。
恢复检查清单(容器重建后)
在 docker rm + docker run 之后,按顺序执行:
adb connect 127.0.0.1:5555 && sleep 45 # 等待启动
adb push frida-server-17.16.4-android-x86_64 /data/local/tmp/frida-server
adb shell "chmod 755 /data/local/tmp/frida-server && sh -c '/data/local/tmp/frida-server > /dev/null 2>&1 &'"
adb push 9a5ba575.0 /system/etc/security/cacerts/9a5ba575.0
adb shell "chmod 644 /system/etc/security/cacerts/9a5ba575.0"
adb reverse tcp:8080 tcp:8080
adb shell "settings put global http_proxy 127.0.0.1:8080"
adb shell "setprop http.proxyHost 127.0.0.1; setprop http.proxyPort 8080; setprop http.nonProxyHosts 'localhost|127.0.0.1|10.*|192.168.*'"
adb shell "stop && sleep 2 && start" # 使代理生效
常见错误速查表
| 现象 | 可能原因 | 修复方法 |
|---|---|---|
| 容器以 129 退出,日志为空 | SELinux 属性区域 init 失败 | 见 §P1(docker drop-in + 镜像上下文修补) |
启动卡在 vdc cryptfs init_user0 |
镜像内没有 fstab | 见 §P2:注释 init_user0,添加 fstab + prop.default |
| HAL 服务 SIGABRT + register -2147483648 | SELinux 拒绝 set_context_mgr |
见 §P3:为 spc_t 添加许可型模块 |
| podman 下立即退出 | podman / init 不兼容 | 使用 Docker(moby-engine) |
| 应用无视全局代理 | http.proxyHost 从未设置 |
见 §P5:setprop + 重启框架 |
| 重建后 frida/CA/代理丢失 | 状态仅存于可写层 | 恢复检查清单 + -v 挂载 /data 卷 |
镜像 N+x 上 sys.boot_completed 为空 |
后续镜像存在回归 | 固定使用已知良好的 tag |
已在以下环境测试:Fedora 44 Workstation(GNOME Wayland),内核 6.19.10,Docker 29.6.2(moby-engine),SELinux 强制模式并加载精简自定义策略,无 KVM。使用的镜像:redroid-fixed2:13(Android 13,基于 LineageOS),frida 17.16.4,Burp CA,端到端验证通过(含 HTTPS 解密)。
实用笔记:大文件下载可借助镜像代理(gh-proxy/dockerproxy 在受限网络可用);docker import 会损坏 redroid 镜像(请使用 create+cp+commit);iptables -L 不会显示控制器 NAT 规则(实际是 nftables);Redroid 内无 curl/浏览器 —— 可用 frida 脚本在 system_server 中测试连通性。
浙公网安备 33010602011771号