Rakuten BIG s 提取原厂 boot_a 与解除 APN 新增限制
这次实测的设备是 Rakuten BIG s,型号 3917JR,代号 gaea,Android 10,ARM64、A/B 动态分区,当前槽位是 a,Bootloader 已解锁。本文只记录两件事:提取本机当前固件的原厂 boot_a,以及恢复 APN 页面新增 APN 的功能。
下面的命令、分区名和实测数值都来自这台 3917JR。换一台手机后不能盲抄,必须先确认自己的槽位和分区布局。
提取 boot_a
普通原厂系统里的 ADB shell 没有权限直接读取 boot_a。这里的前提是手机已经进入一个能够执行 su 0 的临时 rooted Android 环境。
用 DSU 启动临时 root 系统
实际是通过 Android 10 的 DSU 启动一个临时 rooted GSI。它只作为临时系统运行,不覆盖原厂 boot_a。这台 3917JR 的 DSU 入口被厂商隐藏了,但后端仍能工作。
我最初试过 ArrowOS 的 Android 10 ARM64 A/B GSI。它能安装进 DSU,启动后却一直卡在开机动画,ADB 也显示未授权,没法在临时系统的屏幕上完成授权,最后放弃。这是因为这台设备上那条路径没有拿到可用 shell。
最终使用的是 phhusson 官方 Android 10 v222 的 system-quack-arm64-ab-vanilla.img.xz:
- Release:https://github.com/phhusson/treble_experimentations/releases/tag/v222
- 下载:https://github.com/phhusson/treble_experimentations/releases/download/v222/system-quack-arm64-ab-vanilla.img.xz
下载后先解压 .xz。得到的 .img 是 Android sparse image,不能直接交给 DSU;要先转换成非 sparse 的 raw ext4 镜像,例如输出为 system.raw。本机离线检查这个 raw 镜像,确认其中存在 phh-su 和 /system/xbin/su,并且构建属性是 userdebug。这一步很重要,不能只因为文件名里有 GSI 就默认它能 root。
预先写入 ADB 公钥
前一次 DSU 虽然已经跑到 ADB,但主机无法授权。处理方法是在电脑上复制一份 system.raw 为 system-patched.raw,只改副本。随后在 Linux 的 Docker 容器中用 debugfs 将本机 adbkey.pub 写入镜像文件系统根目录的 /adb_keys。
/adb_keys 要设置为 UID/GID 0/0、权限 0644,SELinux xattr 为 u:object_r:adb_keys_file:s0;写入后执行 e2fsck -fn 只读检查。核心命令如下,/work 是容器中挂载的工作目录:
cp /work/system.raw /work/system-patched.raw
debugfs -w /work/system-patched.raw <<'EOF'rm /adb_keyswrite /work/adbkey.pub /adb_keysset_inode_field /adb_keys uid 0set_inode_field /adb_keys gid 0set_inode_field /adb_keys mode 0100644ea_set /adb_keys security.selinux u:object_r:adb_keys_file:s0close -aEOF
e2fsck -fn /work/system-patched.raw目的是让临时 GSI 启动后,电脑能直接使用这把已有的 ADB key,不会卡在临时系统的授权弹窗。这里必须放在运行时根目录 /adb_keys,不要放到 /system/etc/security/adb_keys;后一个位置不是这条 Android 10 adbd 读取授权 key 的可靠目标。
确认镜像没有问题后,把 system-patched.raw gzip 压缩为 system-patched.raw.gz。KEY_SYSTEM_SIZE 必须是未压缩 raw 的字节数。
安装并启动 DSU
这台手机需要先打开被隐藏的 DSU 功能旗标:
adb shell setprop persist.sys.fflag.override.settings_dynamic_system=true把压缩后的镜像推送到手机:
adb push .\system-patched.raw.gz /sdcard/Download/system-patched.raw.gz然后通过 Android 10 的 VerificationActivity 发起安装。本机 raw system 的准确大小是 1774190592 bytes,userdata 使用 8 GiB,即 8589934592 bytes:
adb shell am start-activity --user 0 ` -n com.android.dynsystem/com.android.dynsystem.VerificationActivity ` -a android.os.image.action.START_INSTALL ` -d file:///storage/emulated/0/Download/system-patched.raw.gz ` --el KEY_SYSTEM_SIZE 1774190592 ` --el KEY_USERDATA_SIZE 8589934592按手机上的锁屏凭据确认,等待安装完成。通知出现 Restart 后,必须由用户在通知里手动点 Restart;不要假定 ADB 命令可以替代这一步。
重启进入临时 GSI 后,先确认 ADB 已连接,再检查 root:
adb get-stateadb shell "su 0 id"本机正是通过这一步得到可用的 su 0,才继续读取原厂 boot_a。其他 Android 版本和机型的 DSU 支持、GSI 信任校验、启动结果和 ADB 行为都可能不同;DSU、GSI root 和 ADB key 注入不能直接照搬。
先确认连接、root 和分区
先看当前槽位,:
adb shell getprop ro.boot.slot_suffix本机返回 _a,所以后面读取 boot_a。接着依次检查 ADB、root 权限、分区实际路径和大小:
adb get-stateadb shell "su 0 id"adb shell "su 0 sh -c 'readlink -f /dev/block/by-name/boot_a; blockdev --getsize64 /dev/block/by-name/boot_a'"本机的实际结果是:
/dev/block/sde11100663296也就是 boot_a 实际指向 /dev/block/sde11,大小为 100663296 bytes,也就是 96 MiB。其他固件和机型应当以自己的查询结果为准。
两条走不通的路
我先在 fastbootd 中试过直接读取分区:
fastboot fetch boot_a boot_a.img实测失败,fastbootd 和原厂 Recovery 都不能用来读取这个分区。
进入临时 rooted Android 环境后,我最初假设 /sdcard/Download 存在,直接执行 dd,结果同样失败:
dd: /sdcard/Download/boot_a-stock.img: No such file or directory继续检查后发现,这个临时环境里连 /storage/emulated/0 和 /data/local/tmp 都没有。与其继续猜手机上哪个目录能写,不如完全绕过手机临时文件,直接把 boot_a 的二进制流写到 Windows。这样也避开了 PowerShell 5.1 文本重定向可能损坏二进制的问题。
用 Python 直接写入 Windows
把下面的脚本保存为 pull-boot-a.py。输出文件固定为脚本同目录下的 boot_a-stock.img,传输过程中先写入 .part 临时文件,完成返回码、大小和 SHA256 校验后才改成正式文件名。已有同名正式文件或临时文件时,脚本会拒绝覆盖。
import hashlibimport osimport subprocessfrom pathlib import Path
DEVICE = "/dev/block/by-name/boot_a"OUTPUT = Path(__file__).with_name("boot_a-stock.img")PART = OUTPUT.with_suffix(OUTPUT.suffix + ".part")
def adb_text(*args: str) -> str: result = subprocess.run( ["adb", *args], capture_output=True, text=True, check=True ) return result.stdout.strip()
state = adb_text("get-state")if state != "device": raise SystemExit(f"ADB device is not ready: {state!r}")
size_text = adb_text("exec-out", "su", "0", "blockdev", "--getsize64", DEVICE)expected_size = int(size_text)if expected_size != 100663296: raise SystemExit(f"Unexpected boot_a size: {expected_size} bytes")
if OUTPUT.exists() or PART.exists(): raise SystemExit(f"Refusing to overwrite existing output: {OUTPUT} or {PART}")
hasher = hashlib.sha256()written = 0process = subprocess.Popen( ["adb", "exec-out", "su", "0", "cat", DEVICE], stdout=subprocess.PIPE, stderr=subprocess.PIPE,)
assert process.stdout is not Nonewith PART.open("xb") as destination: while chunk := process.stdout.read(1024 * 1024): destination.write(chunk) hasher.update(chunk) written += len(chunk)
stderr = process.stderr.read().decode(errors="replace") if process.stderr else ""return_code = process.wait()if return_code != 0: raise SystemExit(f"adb failed with exit code {return_code}: {stderr}")if written != expected_size: raise SystemExit(f"Size mismatch: expected {expected_size}, received {written}")
os.replace(PART, OUTPUT)print(f"Saved: {OUTPUT}")print(f"Size: {written}")print(f"SHA256: {hasher.hexdigest().upper()}")脚本中的 100663296 是这台 3917JR 当前固件的设备专用断言。别的固件或机型要按实际查询结果修改,或者移除这条断言,不能照搬这个数字。
在 PowerShell 中进入脚本所在目录后执行:
python .\pull-boot-a.py本机输出如下:
Saved: E:\Downloads\gsi-cloud-patch\boot_a-stock.imgSize: 100663296SHA256: 626B5A3C44263A9E97778130A117C6525FEE502B7CEB6FF12BBBCA8BFA99A37B导出的文件头是 ANDROID!,手机分区哈希与 Windows 本地文件哈希一致。Windows 上可以再检查文件大小和哈希:
Get-Item .\boot_a-stock.imgGet-FileHash .\boot_a-stock.img -Algorithm SHA256这个大小和哈希只代表我手上这台手机当时的固件,不能拿来当作其他设备的标准。原厂 boot 必须从自己的设备和当前固件提取,不要分享或刷入别人的 boot。
解除 APN 新增限制
这一部分的前提是原厂系统已经取得 Magisk root,本文不展开 Magisk 的安装。
我实测使用的是 Vodafone UK,受到手机的Vodafone限制,无法新增和编辑APN。
下面用到的MCC/MNC 为 23415,carrierId 为 28,当时的 subId 是 3。固件中的文件:
/product/priv-app/CarrierConfig/CarrierConfig.apk其 assets/carrier_config_carrierid_28_Vodafone.xml 把配置设成了:
read_only_apn_types_string_array=[*]也就是所有 APN 类型都被设为只读,设置页面随之隐藏了“+”。实测原因在固件内置的运营商配置,不是 SIM 卡或企业策略。
先查当前 subId
subId 不能写死。先查询 SIM 信息:
adb shell "su -c 'content query --uri content://telephony/siminfo --projection _id:sim_id'"从活动 SIM 对应的行中读取 _id。本机当时类似:
Row: 2 _id=3, sim_id=0这里要使用的是 _id=3,所以本机的 SUB_ID 为 3。换卡或系统重新分配后,这个数字可能改变。
写入运行时覆盖
把下面命令中的 SUB_ID 替换成刚才查到的实际数字,再执行:
adb shell "su -c 'service call carrier_config 2 i32 SUB_ID i32 1 i32 96 i32 1279544898 i32 1 s16 read_only_apn_types_string_array i32 14 i32 1 s16 dun'"然后检查覆盖是否已经进入 mOverrideConfigs:
adb shell "su -c 'dumpsys carrier_config'"在输出的 mOverrideConfigs 下应当看到:
read_only_apn_types_string_array = [dun]本机实测结果很直接:APN 页面里的“+”恢复,可以新增 APN。原来 user_editable=0 的 VOXI 行仍可能不能直接编辑,本文只解决新增限制,不承诺能够编辑原有行。
这个覆盖只存在于运行时。重启手机或者重启 com.android.phone 后会消失;换卡后 subId 也可能改变。因此失效后要重新查询 subId,再执行覆盖命令。
但是已经增加的APN选项会一直在手机上保留。
Magisk 自动模块
已经把重新查询活动 subId、调用同一条运行时覆盖,以及监控 carrier_config 和 com.android.phone 重启后重新施加的逻辑做成 Magisk 模块。它不修改 CarrierConfig.apk、APN 数据库或 user_editable,只处理重启后 APN 页面“+”又消失的问题。
仓库链接
仓库文件:
https://github.com/John10240/Rakuten-BIG-s-3917JR
以上。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!




















