破解Kyocera KY-42C
摘要
一篇关于解锁引导加载程序和改造Kyocera KY-42C功能手机的详细博客文章,该手机在固件更新后被认为无法获取root权限。作者研究了联发科(MediaTek)的启动过程和漏洞利用。
<p><a href="https://lobste.rs/s/xvzbwu/hacking_kyocera_ky_42c">评论</a></p>
查看缓存全文
缓存时间: 2026/07/06 20:08
# 破解 Kyocera KY-42C
来源:https://itssho.my/blog/article/unlocking-kyocera-ky42c
发布于 2026 年 7 月 6 日,作者:Shomy
## 引言
在这篇文章中,我将分享解锁 bootloader 和修改 Kyocera KY-42C(DIGNO Keitai 4)的经验。这是一款日本功能手机,社区里都知道这款设备在固件更新后被认为是无法 root 的,但一如既往,这并不能阻止有人尝试 :)。
*Kyocera A202KC,与 KY-42C 类似的设备,图片来自 garahowiki (https://garahowiki.com/phones:kyocera_digno_keitai_4:start)*
在我发布关于 Carbonara(一个 MediaTek Download Agent 漏洞)的博文之后,一位名为 @760ceb3b9c0ba4872cadf3ce35a7a494 (https://github.com/760ceb3b9c0ba4872cadf3ce35a7a494) 的用户留言询问,在固件更新后(据称使其无法 root),是否有可能解锁 KY-42C 的 bootloader。我对这个挑战很感兴趣,于是决定进行调查。
## 背景
KY-42C 搭载 MediaTek MT6761 处理器,市场名称为“Helio A22”。这是一款 MediaTek 设备(而且是旧款),因此我心中已经有了一些想法。我之前曾处理过解锁 MediaTek 设备的工作,所以对如何入手比较熟悉。
在深入讨论设备的具体细节之前,有必要提及一些关于 MediaTek 设备的概念。MediaTek 设备包含一种 USB 救援模式,用于刷写固件,该模式存在于 `Preloader` 和 `BROM` 中。Brom 是芯片中的掩膜 ROM,是设备开机时首先运行的代码。它负责初始化基本硬件,并加载 `Preloader`——第二级引导加载程序,其职责是初始化其余硬件(如 DRAM)并加载主引导加载程序(LK),后者再加载 Android 操作系统。
MediaTek 启动流程:BootROM 和 Preloader 都允许加载 `Download Agent`(DA),这是一种二进制文件,允许通过专用工具(如 `SP Flash Tool`、`Penumbra` 或 `mtkclient`)刷写固件。要了解更多关于 Download Agent 的信息,建议阅读 R0rt1z2 关于 heapb8 的博文 (https://blog.r0rt1z2.com/posts/exploiting-mediatek-datwo/#download-agents) 以及 penumbra 文档 (https://penumbra.itssho.my/Mediatek/Common/DA/Download-Agent)。
通常 OEM 厂商不提供设备所需的 DA,即使提供,也通常锁定为仅允许刷写签名固件。此外,由于 kamakiri (https://penumbra.itssho.my/Mediatek/Exploits/Kamakiri) 或 linecode 等 BROM 漏洞的存在,OEM 厂商已开始全面封锁 BROM USBDL,只允许通过 Preloader 刷写。KY-42C 就是这种情况,它的 BROM 被锁定,并且没有可用的 DA 来刷写固件。
## 调查
对于 KY-42C,我们关心的是,该设备在 1.090XX 固件更新后,锁定了 BootROM USBDL。这意味着我们无法使用任何 BROM 漏洞来引导未签名的 Download Agent,只能使用 Preloader 下载模式。但我有一种感觉,BROM USBDL 并非真正永久锁定,有可能暂时访问它。
MediaTek 实现了两种方式来阻止该模式:一种是通过 efuse 永久禁用,不可逆转;另一种是在 Preloader GFH 头中设置一个标志。在启动过程中,BootROM 通过检查几个标志来确定是否允许 USBDL:
```c
bool check_brom_cmd_disabled(void) {
int iVar1;
GFH_BROM_SEC_CFG_v1 *brom_sec_cfg;
GFH_BROM_CFG_v3 *brom_cfg;
ushort code2;
ushort uVar2;
bool locked;
code2 = 0;
uVar2 = 0;
iVar1 = efuse_is_brom_cmd_disabled();
locked = iVar1 != 0;
if (locked) {
code2 = 0x8000;
}
/* Preloader header 由多个结构体组成,统称为 GFH(通用文件头)。
* 每个 GFH 结构体有一个用途,我们关心的有 GFH_BROM_SEC_CFG 和 GFH_BROM_CFG。
*/
brom_sec_cfg = get_bl_gfh(GFH_BROM_SEC_CFG);
if ((brom_sec_cfg != NULL) && (brom_sec_cfg->m_cmd_mode_permanent_dis == 0xc975e033)) {
code2 = code2 | 0x4000;
locked = true;
}
/*
* GFH_BROM_CFG 允许禁用特定的通信模式,此处为 UART1 和 USB。
* 这意味着即使启用了 BROM USBDL,也只能通过 USB 以外的方式进行。
*/
brom_cfg = get_bl_gfh(GFH_BROM_CFG);
if ((brom_cfg != NULL) && (brom_cfg->m_brom_cmd_via_uart1_disable_magic == 'R')) {
code2 = code2 | 1;
}
if ((brom_cfg != NULL) && (brom_cfg->m_brom_cmd_via_usb_disable_magic == 'U')) {
code2 = code2 | 2;
}
...
iVar1 = efuse_is_brom_cmd_disabled();
if (iVar1 != 0) {
code2 = code2 | 0x8000;
locked = true;
}
cmd_dis_bitfield = code2 | uVar2 | cmd_dis_bitfield;
if ((cmd_dis_bitfield & 0x3fff) == 3) {
cmd_dis_bitfield = cmd_dis_bitfield & 0xfffe;
}
...
return locked;
}
```
随后,BootROM 会检查是否允许 USBDL,以及允许哪种通信模式。
我的感觉是 efuse 本身并未熔断,而是通过 Preloader 标志禁用了下载模式。为了证实我的理论,我请 760ceb3b9c0ba4872cadf3ce35a7a494 运行一个小脚本,从 Preloader USBDL 中转储熔丝(是的,MediaTek 允许这样做)。
```python
# 脚本基于 moto-experiments:
# https://github.com/R0rt1z2/moto-experiments
...
device = Device(args.port)
logging.info('Waiting for device...')
device.find_device()
if not args.skip_handshake:
device.handshake()
device.identify()
hrid = device.read32(0x11c50000, 0x1000)
with open('efuse.bin', 'wb') as f:
f.write(hrid)
```
感谢 MTK 将 `devinfo` 基址列入 `read32` 命令的白名单,我们成功转储了整个 efuse 区域,尤其是偏移 `0x60` 处的 `sec-ctrl` 熔丝。
sec-ctrl 熔丝
通过查看 BootROM 代码,我们可以轻松确定禁用 BROM USBDL 的位是哪个。
```c
uint efuse_is_brom_cmd_disabled(void) {
return (uint)(_DAT_11c50060) >> 8 & 1;
}
```
所以,这个位是位 8,在我们的例子中计算结果为 0!也就是说,efuse 并未熔断,因此理论上我们只需擦除 Preloader 或短路测试点,就可以利用 `linecode` 漏洞加载未签名的 DA。不幸的是,经过多次尝试定位测试点,并且由于设备 fastboot 被锁定而无法擦除 Preloader,只剩下一个选项:希望找到一种通过软件解锁 bootloader 的方法,最好是利用 Preloader 或 lk 漏洞。
## 固件分析
我首先开始分析 lk,760ceb3b9c0ba4872cadf3ce35a7a494 从旧备份中找到了它。凭借我偶尔为 kaeru (https://github.com/R0rt1z2/kaeru) 贡献而分析 lk 镜像的经验,我很快找到了 `fastboot_init`,所有 fastboot 命令都在这里注册。我寻找某种解锁命令,但看起来 Kyocera 将其完全移除了:
```c
...
fastboot_register("flash:",0x4802b461,1,0);
iVar1 = codecheck();
if (iVar1 == 0) {
fastboot_register("erase:",0x4802b1f1,1,1);
} else {
fastboot_register("erase:",0x4802b1f1,1,0);
}
fastboot_register("oem printk-ratelimit",0x48025175,1,0);
iVar1 = codecheck();
if (iVar1 == 0) {
fastboot_register("continue",&LAB_4802508c+1,0,0);
} else {
fastboot_register("continue",&LAB_4802508c+1,1,0);
}
fastboot_register("download:",0x4802b899,1,0);
fastboot_publish("max-download-size",0x480baa9c);
FUN_48000188();
iVar1 = codecheck();
if (iVar1 == 0) {
fastboot_register("oem p2u",0x4802522d,1,1);
} else {
fastboot_register("oem p2u",0x4802522d,1,0);
}
fastboot_register("oem dump_pllk_log",0x480250fd,1,0);
fastboot_register("oem off-mode-charge",&LAB_48025388+1,0,0);
iVar1 = codecheck();
if (iVar1 == 0) {
fastboot_register("oem key",0x4802b905,1,1);
} else {
fastboot_register("oem key",0x4802b905,1,0);
}
iVar1 = codecheck();
if (iVar1 == 0) {
fastboot_register("oem lks",0x4802b961,1,1);
} else {
fastboot_register("oem lks",0x4802b961,1,0);
}
...
fastboot_register("oem ultraflash:",0x4802b4cd,1,1);
fastboot_register("oem ultraflash_en",0x4802b5b9,1,1);
fastboot_register("ultraflash:",0x4802b4cd,1,1);
```
而且不幸的是,大多数 oem 命令都被 `chkcode` 分区中的一个标志所阻挡,导致安全设备无法访问。
```c
int codecheck(void) {
uint uVar1;
int local_810;
int local_80c;
if (ALLOWED? < 0) {
uVar1 = partition_read("chkcode",0x800,0,0,&local_810,0x800);
if (((uVar1 == 0) || (local_810 != 0x544f4f4c)) || (local_80c != 0x4b434642)) {
ALLOWED? = 0;
return 0;
}
ALLOWED? = 1;
}
return ALLOWED?;
}
```
这再加上严格的安全策略,缩小了在 lk 中利用漏洞的可能性(至少是我愿意投入时间研究的)。因此,是时候转向 Preloader 了。
获取 Preloader 镜像相当具有挑战性,因为 760ceb3b9c0ba4872cadf3ce35a7a494 的转储中不包含它。我等待着他们提供继续所需的文件。他们最终从 OTA 转储中获取了新旧两个镜像,这对于差异分析非常完美。
有了这两个镜像,我首先确认了我最初的推测,即 Preloader 确实是禁用 BROM USBDL 的元凶。
```
# 旧 preloader BROM_SEC_CFG (v1, 48 bytes)
JTAG: disabled
Debug: disabled
BROM Cmd Perm Dis: no
# 新 preloader BROM_SEC_CFG (v1, 48 bytes)
JTAG: disabled
Debug: disabled
BROM Cmd Perm Dis: yes
```
然后,我开始将两个镜像加载到 Ghidra 中,寻找可利用的路径。
## 利用 Preloader
在 Preloader 的 USBDL 模式中,设备会进入一个无限循环,等待来自主机的命令。命令通过 USB 发送,在 switch 语句中匹配并执行相应函数。
```c
// 来自 GitHub 上的 `download.c`
int usbdl_handler(struct bldr_comport *comport, u32 hshk_tmo_ms) {
u8 cmd = 0;
...
#if CFG_PRELOADER_AS_DA
usbdl_init_image();
#endif
while (1) {
platform_wdt_kick();
usbdl_get_byte(&cmd);
if (cmd != CMD_GET_BL_VER)
usbdl_put_byte(cmd);
switch (cmd) {
...
case CMD_SEND_DA:
DBGMSG("%s CMD_SEND_DA\n", MOD);
usbdl_send_da();
break;
case CMD_JUMP_DA:
DBGMSG("%s CMD_JUMP_DA\n", MOD);
usbdl_jump_da();
break;
#if CFG_PRELOADER_AS_DA
case CMD_SEND_IMAGE:
DBGMSG("%s CMD_SEND_IMAGE\n", MOD);
usbdl_send_image();
break;
case CMD_BOOT_IMAGE:
DBGMSG("%s CMD_BOOT_IMAGE\n", MOD);
usbdl_boot_image();
break;
#endif
case CMD_READ32:
DBGMSG("%s CMD_READ32\n", MOD);
usbdl_read32(FALSE);
break;
case CMD_WRITE32:
DBGMSG("%s CMD_WRITE32\n", MOD);
usbdl_write32(TRUE);
break;
...
default:
DBGMSG("%s Unhandled CMD 0x%x\n", MOD, cmd);
break;
}
}
return 0;
}
```
在查看 Preloader 代码和我在 Ghidra 中加载的代码时,我被“CMD_SEND_IMAGE\n”调试消息吸引住了。这个命令只有在定义 `CFG_PRELOADER_AS_DA` 时才可用,而 KY-42C 似乎定义了它。根据我的经验,这个命令和 `CMD_BOOT_IMAGE` 通常不会出现在生产版 Preloader 中。因此,我想,既然 MTK 默认不启用这些命令,并且还决定完全移除它们,也许是有原因的。
阅读代码后,我意识到,正如我所怀疑的,MediaTek 忘记在这些命令中实现签名验证检查:
```c
static void usbdl_send_image(void) {
u32 img_addr = 0;
u32 img_len = 0;
image_index_t id;
u16 status = 0;
u8 img_name[64] = {0};
u32 checksum32 = 0;
u32 my_checksum32 = 0;
u8 debug_buf[32] = {0};
usbdl_get_data(img_name, 64);
usbdl_get_dword(&img_len);
for (id = IMAGE_LK_ID; id < IMAGE_MAX_NUM; id++) {
if ((!strcmp(img_name, g_image_list[id].partition_name)) && (img_len > 0) && (img_len <= g_image_list[id].length)) {
pal_log_info("%s SEND: Verify PASS\n", MOD);
if(id == IMAGE_ATF_ID)
img_addr = (u32)tee1_buf;
else
img_addr = g_image_list[id].start_addr;
break;
}
}
if (!img_addr) {
pal_log_err("%s Unknown image\n", MOD);
}
usbdl_put_word(status);
/* 这里设备从主机下载镜像数据,并且
* 只进行校验和验证,从未验证签名。
*/
usbdl_get_data((u8 *)img_addr, img_len);
my_checksum32 = checksum_plain((u8 *)img_addr, img_len);
usbdl_get_dword(&checksum32);
if (my_checksum32 != checksum32) {
pal_log_err("%s checksum mismatch!\n", MOD);
return;
}
if(id == IMAGE_ATF_ID) {
// 重定位 ATF 和 TEE
}
}
static void usbdl_boot_image(void) {
u8 img_name[64] = {0};
u32 jump_arg;
u16 status = 0;
usbdl_get_data(img_name, 64);
trustzone_pre_init();
g_boot_mode = FASTBOOT;
platform_set_boot_args();
trustzone_post_init();
jump_arg = (u32)&bootarg;
if (!strcmp(img_name, lk)) {
usbdl_put_word(status);
pal_log_err("%s Jump to LK\n", MOD);
/* 嗨,MTK,谢谢这个 */
bldr_jump(g_image_list[IMAGE_LK_ID].start_addr + PART_HDR_BUF_SIZE, jump_arg, sizeof(boot_arg_t));
} else if (!strcmp(img_name, atf)) {
usbdl_put_word(status);
pal_log_err("%s Jump to ATF\n", MOD);
bldr_jump64(g_image_list[IMAGE_LK_ID].start_addr + PART_HDR_BUF_SIZE, jump_arg, sizeof(boot_arg_t));
} else {
status = 1;
usbdl_put_word(status);
pal_log_err("%s Unknown Jump\n", MOD);
}
}
```
这意味着我们只需几行 Python 代码就能在 `EL3` 上实现任意代码执行:
```python
path = 'bin/unlock.bin'
with open(path, 'rb') as f:
data = f.read()
device = Device(None)
logging.info('Waiting for device...')
device.find_device()
device.handshake()
device.identify()
device.send_image('lk', data)
device.boot_image('lk')
```
*值得一提的是,此漏洞过去已在 lgk10exploit (https://github.com/arturkow2000/lgk10exploit) 中公开使用过,因此并非新鲜事,而且现在已经被修补了好几年。不过,在发现这个项目之前,自己摸索出这一点还是挺有意思的。*
## 解锁 Bootloader
最初,我们尝试引导一个打过补丁的 lk 镜像,但不幸的是,设备几秒钟后就会崩溃,我们无法找出原因。因此,再加上这个 lk 中根本没有包含 `sec_set_lock_state` 函数,我决定制作自己的 payload,使用我的 mtk-payloads (https://github.com/shomykohai/mtk-payloads) 项目框架。你可以在此处查看 payload 的完整代码 (https://github.com/shomykohai/kyocera-ky-42c-unlock/blob/main/payload/src/main.c),但主要思路是创建一个新的 seccfg,加密其哈希并写入 `seccfg` 分区,同时解锁 `chkcode` 中的 fastboot 功能:
```c
int main(void) {
...
seccfg.lock_state = LKS_UNLOCK;
seccfg.dm_verity = DM_VERITY_OK;
sha256_hash(hash, &seccfg, 0x1C);
params.length = HASH_SZ;
params.anti_clone = true;
params.encrypt = true;
sp_sej_enc(hash, hash, params);
memcpy(seccfg.hash, hash, HASH_SZ);
mmc_write_block(&g_mmc_dev, seccfg_start, &seccfg);
u32 chkcode_block = gpt_get_start(&gpt, "chkcode") + 4;
mmc_read_block(&g_mmc_dev, chkcode_block, chkcode_buf);
*(u32 *)&chkcode_buf[0] = CHKCODE_MAGIC1;
*(u32 *)&chkcode_buf[4] = CHKCODE_MAGIC2;
mmc_write_block(&g_mmc_dev, chkcode_block, chkcode_buf);
}
```
我将 payload 和 Python 脚本发送给了 760ceb
相似文章
VMC2040安全摄像头的Root操作
本篇博客文章是系列教程的第一部分,内容涵盖硬件检查、UART发现以及初始Bootloader分析,目标是对Arlo VMC2040安全摄像头进行Root操作。
使用Claude Code对Android恶意软件进行逆向工程
本文详细介绍了作者如何使用Claude Code对预装在廉价Android投影仪上的恶意软件进行逆向工程,识别并禁用了可疑的软件包。
usbliter8 - Apple A12/A13 bootROM漏洞利用
本文详细介绍了苹果A12/A13 SoC中一种新颖的bootROM漏洞,该漏洞利用USB控制器中的硬件缺陷和配置缺陷,从而破坏了启动链。文中还提供了一个概念验证。
我把一台80美元的RK3562安卓平板变成了Debian Linux工作站
一个构建系统和预发布的Debian 12映像,适用于Doogee U10安卓平板(Rockchip RK3562),无需解锁引导加载程序即可实现完整的Linux功能,从零开始逆向工程。
本田思域与邪恶泊车员
一位安全研究人员揭示了本田思域车载信息娱乐系统(headunit)会接受使用公开已知的AOSP测试密钥进行的未签名更新,从而使得通过物理USB访问可以实现“邪恶泊车员”(evil valet)攻击。还发布了如ota-builder和apk-rebuilder等工具,以方便进一步的逆向工程。