Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ If you downloaded the `target_toolkit_linux` or `target_toolkit_windows` zip fil
3. **Linux:** Run `bash build.sh`. **Windows:** Run `build.bat`.
4. The script extracts and cracks the ABL, outputting `ABL.efi` (fake re-lock) and `ABL_original.efi` (original). `BDS.efi` is bundled. Check `patch_log.txt` - if it says "Warning: Failed to patch ABL GBL", the ABL lacks the vulnerability and the `abl` partition must be downgraded to an older ABL with it.

Then complete the install manually (see the [Wiki](https://github.com/superturtlee/gbl_root_canoe/wiki) for full steps): copy `ABL.efi` into `/mnt/vendor/persist/efisp/`, create `BOOTENTRIES`, `sync`, and flash `BDS.efi` to `efisp` (`dd if=BDS.efi of=/dev/block/by-name/efisp bs=4M`).
For Xiaomi/HyperOS region override, explicitly run `bash build.sh --hwcountry-global` (Linux), `build.bat --hwcountry-global` (Windows), or `sh build.sh --hwcountry-global` (Android toolkit). This redirects the supported ABL's HwCountry getvar and display references to `GLOBAL`, adapting the feature from [HyperCanoe](https://github.com/StevenWin818/GBL_Root_HyperCanoe). The default build keeps the hardware country unchanged. Unsupported or ambiguous layouts and unavailable PE tail padding fail without producing a patched file. The patcher also accepts `patch_abl ABL_original.efi boot.efi --hwcountry-global` directly; the module's automatic installer does not enable this option.

Then complete the install manually (see the [Wiki](https://github.com/superturtlee/gbl_root_canoe/wiki) for full steps): copy the generated `efisp/boot.efi` into `/mnt/vendor/persist/efisp/`, use the toolkit's `BOOTENTRIES`, `sync`, and flash `BDS.efi` to `efisp` (`dd if=BDS.efi of=/dev/block/by-name/efisp bs=4M`). `boot.efi` is a chain-loaded EFI application: do not flash it to the `abl` or `efisp` partition. Static patch validation does not establish that a device will boot; the installed ABL/BDS chain must be compatible.

### 3. OTA Upgrade
Before rebooting for an OTA update, use the module WebUI to flash and retain the old ABL version. "Update efisp" is enabled by default; for a major version upgrade keep it on, otherwise the device may get stuck on the first boot screen.
Expand Down
4 changes: 3 additions & 1 deletion README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@
3. **Linux 平台:** 开启终端执行 `bash build.sh`。**Windows 平台:** 双击运行 `build.bat`。
4. 脚本会提取并破解 ABL,输出 `ABL.efi`(假回锁)和 `ABL_original.efi`(原版),`BDS.efi` 已附带。请查看 `patch_log.txt`,若显示 "Warning: Failed to patch ABL GBL",则该 ABL 没有漏洞,需将 `abl` 分区降级为带有 GBL 漏洞的旧版本 ABL。

随后手动完成安装(完整步骤见 [Wiki](https://github.com/superturtlee/gbl_root_canoe/wiki)):将 `ABL.efi` 复制到 `/mnt/vendor/persist/efisp/`,创建 `BOOTENTRIES`,`sync`,再将 `BDS.efi` 刷入 `efisp`(`dd if=BDS.efi of=/dev/block/by-name/efisp bs=4M`)。
小米 / HyperOS 跨区功能需显式启用:Linux 执行 `bash build.sh --hwcountry-global`,Windows 执行 `build.bat --hwcountry-global`,Android 工具包执行 `sh build.sh --hwcountry-global`。此功能移植自 [HyperCanoe](https://github.com/StevenWin818/GBL_Root_HyperCanoe),将支持的 ABL 中 HwCountry 的 getvar 和显示指针重定向为 `GLOBAL`;默认不修改硬件地区。布局不支持、引用不唯一或没有可用的 PE 段尾填充时会失败,不生成修补文件。也可直接执行 `patch_abl ABL_original.efi boot.efi --hwcountry-global`;模块的自动安装流程不启用此选项。

随后手动完成安装(完整步骤见 [Wiki](https://github.com/superturtlee/gbl_root_canoe/wiki)):将生成的 `efisp/boot.efi` 复制到 `/mnt/vendor/persist/efisp/`,使用工具包的 `BOOTENTRIES`,`sync`,再将 `BDS.efi` 刷入 `efisp`(`dd if=BDS.efi of=/dev/block/by-name/efisp bs=4M`)。`boot.efi` 是通过启动链加载的 EFI 应用,不能刷到 `abl` 或 `efisp` 分区。静态补丁校验不能证明实机一定能启动,现有 ABL / BDS 启动链还需匹配。

### 3. OTA 升级
重启进行 OTA 更新前,使用模块 WebUI 刷写以保留旧版本 ABL。“更新 efisp”默认开启;跨版本升级时请保持开启,否则可能卡一屏。
Expand Down
6 changes: 6 additions & 0 deletions submodules/patcher/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,9 @@ build_android:
build_windows:
mkdir -p build
x86_64-w64-mingw32-gcc $(CFLAGS) -o build/patch_abl.exe $(SRCS)

.PHONY: test
test:
mkdir -p build
gcc $(CFLAGS) -o build/test_hwcountry tests/test_hwcountry.c src/patchs/xiaomi/hwcountry.c src/arm64_inst/arm64_inst_decoder.c src/arm64_inst/utils.c
./build/test_hwcountry
9 changes: 9 additions & 0 deletions submodules/patcher/include/patchs/xiaomi/hwcountry.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#ifndef PATCHS_XIAOMI_HWCOUNTRY_H
#define PATCHS_XIAOMI_HWCOUNTRY_H

#include <stdint.h>

/* Returns 0 on success, -1 if the supported HwCountry layout is absent. */
int32_t patch_hwcountry_global(char* buffer, int32_t size);

#endif
14 changes: 11 additions & 3 deletions submodules/patcher/src/patch_abl.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#include "patchs/core.h"
#include "patchs/xiaomi/hwcountry.h"
//FILE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
/* ==================== main ==================== */
int32_t read_file(const char* filename, char** data, int32_t* size) {
FILE* file = fopen(filename, "rb");
Expand All @@ -18,8 +20,9 @@ int32_t read_file(const char* filename, char** data, int32_t* size) {
return 0;
}
int32_t main(int32_t argc, char* argv[]) {
if (argc != 3) {
printf("Usage: %s <input_file> <output_file>\n", argv[0]);
bool hwcountry_global = argc == 4 && strcmp(argv[3], "--hwcountry-global") == 0;
if (argc != 3 && !hwcountry_global) {
printf("Usage: %s <input_file> <output_file> [--hwcountry-global]\n", argv[0]);
return EXIT_FAILURE;
}
char* data = NULL;
Expand All @@ -28,6 +31,11 @@ int32_t main(int32_t argc, char* argv[]) {
printf("Failed to read file: %s\n", argv[1]);
return EXIT_FAILURE;
}
if (hwcountry_global && patch_hwcountry_global(data, size) != 0) {
printf("Error: Failed to patch HwCountry -> GLOBAL\n");
free(data);
return EXIT_FAILURE;
}
if (!PatchBuffer(data,size))
{
printf("Patching failed\n");
Expand All @@ -50,4 +58,4 @@ int32_t main(int32_t argc, char* argv[]) {
free(data);
printf("Saved to %s\n", argv[2]);
return EXIT_SUCCESS;
}
}
2 changes: 1 addition & 1 deletion submodules/patcher/src/patchs/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ int32_t source_callback(char* buffer, int32_t size, int32_t now_offset, int8_t c
#endif
#ifndef DISABLE_PATCH_5
int32_t fwd = track_forward_patch_strb(buffer, size, now_offset, current_target, anchor_offset);
if (fwd <= 0) {
if (fwd != SUCCESS) {
printf("Warning: sink STRB not found after anchor 0x%X\n", anchor_offset);
return -1;
}
Expand Down
198 changes: 198 additions & 0 deletions submodules/patcher/src/patchs/xiaomi/hwcountry.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
/* HwCountry redirection adapted from StevenWin818/GBL_Root_HyperCanoe. */
#include "arm64_inst/utils.h"
#include "patchs/xiaomi/hwcountry.h"

static int32_t find_cstring(const char* buffer, int32_t size, const char* needle) {
int32_t length = (int32_t)strlen(needle) + 1;
for (int32_t i = 0; i <= size - length; ++i)
if (memcmp(buffer + i, needle, length) == 0) return i;
return -1;
}

static int32_t find_reference(const char* buffer, int32_t size, int64_t target) {
int32_t result = -1;
for (int32_t i = 0; i <= size - 8; i += 4) {
if (calc_adrl_file_offset(buffer, i, 0) != target) continue;
if (result >= 0) {
printf("HwCountry: ambiguous ADRP+ADD references to 0x%llX\n",
(unsigned long long)target);
return -1;
}
result = i;
}
return result;
}

static uint16_t read_u16(const char* buffer, int32_t off) {
uint16_t value;
memcpy(&value, buffer + off, sizeof(value));
return value;
}

static bool in_range(int64_t target, int32_t start, int32_t length) {
return target >= start && target < (int64_t)start + length;
}

static bool slack_is_referenced(const char* buffer, int32_t size,
int32_t start, int32_t length) {
for (int32_t i = 0; i <= size - 8; i += 4) {
if (in_range(calc_adrl_file_offset(buffer, i, 0), start, length)) return true;
uint32_t raw = read_instr(buffer, i);
if ((raw & 0x9F000000u) == 0x10000000u) { /* ADR */
int32_t imm = ((raw >> 29) & 3) | (((raw >> 5) & 0x7FFFF) << 2);
if (imm & 0x100000) imm -= 0x200000;
if (in_range((int64_t)i + imm, start, length)) return true;
}
if ((raw & 0x3B000000u) == 0x18000000u) { /* LDR literal */
if (in_range((int64_t)i + decode_imm19(raw), start, length)) return true;
}
if ((raw & 0x7E000000u) == 0x36000000u) { /* TBZ/TBNZ */
int32_t imm = (raw >> 5) & 0x3FFF;
if (imm & 0x2000) imm -= 0x4000;
if (in_range((int64_t)i + imm * 4, start, length)) return true;
}
DecodedInst d = decode_at(buffer, i);
int64_t target;
if (get_JUMP_target(&d, i, &target) && in_range(target, start, length)) return true;
}
return false;
}

static int32_t find_slack(const char* buffer, int32_t size, int32_t need) {
/* Only use mapped, readable .text tail padding in a flat ARM64 PE. */
if (size < 64 || memcmp(buffer, "MZ", 2) != 0) return -1;
uint32_t pe = read_instr(buffer, 0x3C);
if (pe > (uint32_t)size - 24 || memcmp(buffer + pe, "PE\0\0", 4) != 0
|| read_u16(buffer, pe + 4) != 0xAA64) return -1;
uint16_t count = read_u16(buffer, pe + 6);
uint16_t optional_size = read_u16(buffer, pe + 20);
int64_t sections = (int64_t)pe + 24 + optional_size;
if (count == 0 || optional_size < 160 || sections + (int64_t)count * 40 > size
|| read_u16(buffer, pe + 24) != 0x20B
|| read_instr(buffer, pe + 48) != 0 || read_instr(buffer, pe + 52) != 0)
return -1;

int32_t slack = -1;
for (int32_t i = 0; i < count; ++i) {
int32_t section = (int32_t)sections + i * 40;
uint32_t virtual_size = read_instr(buffer, section + 8);
uint32_t rva = read_instr(buffer, section + 12);
uint32_t raw_size = read_instr(buffer, section + 16);
uint32_t raw = read_instr(buffer, section + 20);
if (raw_size == 0) continue;
if (raw != rva || (int64_t)raw + raw_size > size) return -1;
if (memcmp(buffer + section, ".text\0\0\0", 8) != 0) continue;
uint32_t mapped_size = virtual_size < raw_size ? virtual_size : raw_size;
if (slack >= 0 || mapped_size < (uint32_t)need
|| !(read_instr(buffer, section + 36) & 0x40000000u)) return -1;
slack = (int32_t)(raw + mapped_size - need);
}
if (slack < 0) return -1;
for (int32_t i = 0; i < need; ++i)
if (buffer[slack + i] != 0) return -1;
if (slack_is_referenced(buffer, size, slack, need)) return -1;

/* Reject padding which the PE loader would rewrite through relocations. */
uint32_t directory_count = read_instr(buffer, pe + 24 + 108);
if (directory_count > 5) {
uint32_t reloc = read_instr(buffer, pe + 24 + 152);
uint32_t reloc_size = read_instr(buffer, pe + 24 + 156);
int64_t end = (int64_t)reloc + reloc_size;
if (end > size) return -1;
for (int64_t off = reloc; off < end;) {
if (end - off < 8) return -1;
uint32_t page = read_instr(buffer, (int32_t)off);
uint32_t block_size = read_instr(buffer, (int32_t)off + 4);
if (block_size < 8 || (block_size & 1) || off + block_size > end) return -1;
for (int64_t entry = off + 8; entry < off + block_size; entry += 2) {
uint16_t relocation = read_u16(buffer, (int32_t)entry);
if ((relocation >> 12) == 0) continue;
int64_t target = (int64_t)page + (relocation & 0xFFF);
if (target < (int64_t)slack + need && target + 8 > slack) return -1;
}
off += block_size;
}
}
return slack;
}

static void redirect_reference(char* buffer, int32_t off, int32_t target) {
DecodedInst pair = decode_at(buffer, off);
int64_t delta = ((int64_t)(target & ~0xFFF) - (off & ~0xFFF)) / 4096;
uint32_t imm21 = (uint32_t)delta & 0x1FFFFF;
uint32_t adrp = 0x90000000u | ((imm21 & 3) << 29)
| ((imm21 >> 2) << 5) | pair.rt;
uint32_t add = 0x91000000u | ((uint32_t)(target & 0xFFF) << 10)
| ((uint32_t)pair.rt << 5) | pair.rt;
printf("HwCountry: redirect ADRP+ADD at 0x%X -> file:0x%X\n", off, target);
write_instr(buffer, off, adrp);
write_instr(buffer, off + 4, add);
}

int32_t patch_hwcountry_global(char* buffer, int32_t size) {
const char value[] = "GLOBAL";
const char line[] = "HwCountry: GLOBAL";
if (!buffer || size < 8) return -1;

int32_t format = find_cstring(buffer, size, "HwCountry: %a");
if (format < 0) {
printf("HwCountry: format string not found\n");
return -1;
}
int32_t display = find_reference(buffer, size, format);
if (display < 0) {
printf("HwCountry: format reference not found or ambiguous\n");
return -1;
}

/* The format call receives the country buffer in X3. */
int32_t base_reg = -1;
uint32_t country_offset = 0;
for (int32_t i = display; i <= size - 4 && i - display <= 32; i += 4) {
DecodedInst d = decode_at(buffer, i);
if (d.type == INST_ADD_X_IMM && d.rt == 3 && d.rn != 31) {
base_reg = d.rn;
country_offset = d.imm;
break;
}
}
if (base_reg < 0) {
printf("HwCountry: country argument in X3 not found\n");
return -1;
}

int64_t country = -1;
for (int32_t i = display; i >= 0; i -= 4) {
DecodedInst d = decode_at(buffer, i);
if (d.type == INST_PACIASP || d.type == INST_RET) break;
if (d.type != INST_ADRP || d.rt != base_reg) continue;
int64_t base = calc_adrl_file_offset(buffer, i, 0);
if (base >= 0) {
country = base + country_offset;
break;
}
}
if (country < 0 || country >= size) {
printf("HwCountry: country buffer not found in current function\n");
return -1;
}
int32_t getvar = find_reference(buffer, size, country);
if (getvar < 0 || getvar == display) {
printf("HwCountry: getvar reference not found or ambiguous\n");
return -1;
}

int32_t slack = find_slack(buffer, size, sizeof(value) + sizeof(line));
if (slack < 0) {
printf("HwCountry: no verified ARM64 PE .text tail padding for replacement strings\n");
return -1;
}

/* All sites are validated before changing the buffer. */
memcpy(buffer + slack, value, sizeof(value));
memcpy(buffer + slack + sizeof(value), line, sizeof(line));
redirect_reference(buffer, getvar, slack);
redirect_reference(buffer, display, slack + sizeof(value));
printf("HwCountry -> GLOBAL applied\n");
return 0;
}
106 changes: 106 additions & 0 deletions submodules/patcher/tests/test_hwcountry.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#include "arm64_inst/utils.h"
#include "patchs/xiaomi/hwcountry.h"
#ifdef NDEBUG
#undef NDEBUG
#endif
#include <assert.h>
#include <stdlib.h>

enum { SIZE = 0x2000, SECTION = 0x188, DISPLAY = 0x310, GETVAR = 0x1300,
FORMAT = 0x900, COUNTRY = 0xB00, SLACK = 0x1000 - 25 };

static void put16(char* data, int32_t off, uint16_t value) {
memcpy(data + off, &value, sizeof(value));
}

/* Only the PE header and the instructions used by the patch are populated. */
static void fixture(char* data) {
memset(data, 0, SIZE);
memcpy(data, "MZ", 2);
write_instr(data, 0x3C, 0x80);
memcpy(data + 0x80, "PE\0\0", 4);
put16(data, 0x84, 0xAA64);
put16(data, 0x86, 2);
put16(data, 0x94, 0xF0);
put16(data, 0x98, 0x20B);
memcpy(data + SECTION, ".text", 5);
write_instr(data, SECTION + 8, 0xE00);
write_instr(data, SECTION + 12, 0x200);
write_instr(data, SECTION + 16, 0xE00);
write_instr(data, SECTION + 20, 0x200);
write_instr(data, SECTION + 36, 0x60000020);
memcpy(data + SECTION + 40, ".data", 5);
write_instr(data, SECTION + 48, 0x1000);
write_instr(data, SECTION + 52, 0x1000);
write_instr(data, SECTION + 56, 0x1000);
write_instr(data, SECTION + 60, 0x1000);
write_instr(data, SECTION + 76, 0xC0000040);
memcpy(data + FORMAT, "HwCountry: %a", 14);
write_instr(data, 0x300, 0xD503233F); /* PACIASP */
write_instr(data, 0x304, 0x90000014); /* ADRP X20, page 0 */
write_instr(data, 0x308, 0x91280294); /* ADD X20, X20, #0xA00 */
write_instr(data, DISPLAY, 0x90000002);
write_instr(data, DISPLAY + 4, 0x91240042); /* X2 -> format */
write_instr(data, DISPLAY + 8, 0x91040283); /* ADD X3, X20, #0x100 */
write_instr(data, GETVAR, 0xF0FFFFE0); /* ADRP X0, previous page */
write_instr(data, GETVAR + 4, 0x912C0000); /* ADD X0, X0, #0xB00 */
assert(calc_adrl_file_offset(data, GETVAR, 0) == COUNTRY);
}

static void expect_failure_unchanged(char* data) {
char before[SIZE];
memcpy(before, data, SIZE);
assert(patch_hwcountry_global(data, SIZE) == -1);
assert(memcmp(before, data, SIZE) == 0);
}

int main(void) {
char data[SIZE];
fixture(data);
char before[SIZE];
memcpy(before, data, SIZE);
assert(patch_hwcountry_global(data, SIZE) == 0);
assert(calc_adrl_file_offset(data, GETVAR, 0) == SLACK);
assert(calc_adrl_file_offset(data, DISPLAY, 0) == SLACK + 7);
assert(strcmp(data + SLACK, "GLOBAL") == 0);
assert(strcmp(data + SLACK + 7, "HwCountry: GLOBAL") == 0);
for (int32_t i = 0; i < SIZE; ++i) {
if ((i >= GETVAR && i < GETVAR + 8) || (i >= DISPLAY && i < DISPLAY + 8)
|| (i >= SLACK && i < SLACK + 25)) continue;
assert(data[i] == before[i]);
}

fixture(data);
data[FORMAT] = 'X';
expect_failure_unchanged(data);

fixture(data);
data[0xFFF] = 1; /* Occupied tail must not be overwritten. */
expect_failure_unchanged(data);

fixture(data);
memcpy(data + GETVAR + 16, data + GETVAR, 8); /* Ambiguous getvar. */
expect_failure_unchanged(data);

fixture(data);
write_instr(data, SECTION + 12, 0x400); /* Non-flat PE mapping. */
expect_failure_unchanged(data);

fixture(data);
write_instr(data, 0x500, 0x90000005);
write_instr(data, 0x504, 0x913F9CA5); /* Existing reference to tail padding. */
assert(calc_adrl_file_offset(data, 0x500, 0) == SLACK);
expect_failure_unchanged(data);

fixture(data);
write_instr(data, 0x104, 6); /* NumberOfRvaAndSizes. */
write_instr(data, 0x130, 0x1800); /* Base relocation directory. */
write_instr(data, 0x134, 10);
write_instr(data, 0x1800, 0);
write_instr(data, 0x1804, 10);
put16(data, 0x1808, 0xA000 | SLACK); /* DIR64 relocation into padding. */
expect_failure_unchanged(data);

puts("HwCountry tests passed (success and six rejected layouts)");
return EXIT_SUCCESS;
}
Loading