MTK Preloader 踩坑

背景

  • MT6737T
  • Android

前期readback 什么都都正常,也切出来了各个分区,并制作了scatter。

折腾的时候发现SP Flash Tool 加载preloader的时候有报错:

flash_tool_error

看日志

1
2
3
4
5
6
7
8
9
10
02/23/22 22:35:20.309 BROM_DLL[3848][1900]: DL_HANDLE()::Rom_Load(): ROM loaded, name = preloader (flashtool_handle_internal.cpp:4693)

02/23/22 22:35:20.309 BROM_DLL[3848][1900]: DEBUG: DL_HANDLE::UpdateRomFileInfoByPreloader(): UpdateRomFileInfoByPreloader get bbchiptype : 159 (flashtool_handle_internal.cpp:4359) //chip type 所以应该是强绑定的
02/23/22 22:35:20.309 BROM_DLL[3848][1900]: DL_HANDLE()::UpdateRomFileInfoByPreloader(): Loading SV5 BL, name = preloader (flashtool_handle_internal.cpp:4374)

02/23/22 22:35:20.309 BROM_DLL[3848][1900]: ERROR: DL_HANDLE(0x0BC38DE8)::UpdateRomFileInfoByPreloader(): [0]: preloader - Parse GFH_FILE_INFO error(0x00001008)! (flashtool_handle_internal.cpp:4385)
02/23/22 22:35:20.309 BROM_DLL[3848][1900]: ERROR: DL_HANDLE(0xB7B1A8AC)::File length not match with GFH specified file length (flashtool_handle_internal.cpp:4386) // 不匹配! GFH 指定的文件长度不匹配
02/23/22 22:35:20.309 BROM_DLL[3848][1900]: ERROR: File length (262160) / GFH specified file length (0) (flashtool_handle_internal.cpp:4387) // 这里,文件长度是xxx,但是GFH里指定的不对
02/23/22 22:35:20.309 BROM_DLL[3848][1900]: ERROR: DL_Rom_Load(): <ERR_CHECKPOINT>[232][error][5066]</ERR_CHECKPOINT> [S_DL_PC_BL_INVALID_GFH_FILE_INFO] (flashtool_handle.cpp:941)
02/23/22 22:35:20.309 BROM_DLL[3848][1900]: DL_Rom_Load(): DL_HANDLE->rwlock: WRITE_UNLOCK. (rwlock.cpp:476)

所以:

  • DL_HANDLE()::Rom_Load() 函数
  • 这个平台校验了size字段和实际preloader文件的size

逆向分析

1
2
$ file libflashtool.v1.so
libflashtool.v1.so: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, BuildID[sha1]=de4c47b0bb41c274fd42efb55ceb476bcc840d7a, not stripped

DL_HANDLE::UpdateRomFileInfoByPreloader 方法中找到了校验的逻辑:

1
2
3
4
5
6
if ( *((_DWORD *)var48 + 15) == 7 )
{
err_code = DL_HANDLE::UpdateRomFileInfoByPreloader(this, sys_index);// updateROM File!
if ( err_code )
return err_code;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
erro_code = ROM_ID_Class::LoadGFH((__int64)this + 112, *((_QWORD *)ROM + 123), 0, 
(__int64)&GFH);
if ( erro_code > 0xFFF )
{
r12_12 = (const char *)std::string::c_str(ROM);
rbx12 = g_hBROM_DEBUG;
MetaTrace::MetaTrace(
(MetaTrace *)var1030,
"FlashToolLib/source/common/handle/src/flashtool_handle_internal.cpp",
4417,
0xFFu,
" ERROR:");
MetaTrace::operator()(
var1030,
rbx12,
"DL_HANDLE(0x%08X)::UpdateRomFileInfoByPreloader(): [%u]: %s - Load GFH_FILE_INFO error(0x%08X)! ",
this,
rom_file_name,
r12_12,
erro_code);
MetaTrace::~MetaTrace((MetaTrace *)var1030);
return 5066;
}
if ( *((_DWORD *)GFH + 8) != *((_QWORD *)ROM + 0x7C) )// length check!!
{

//.....error log

}

ROM + 0x7C 是实际的文件大小
GFH + 8 是 解析preloader 中的GFH结构中的size字段

1
2
3
4
ROM_ID_Class::LoadGFH
GFH_Find(rom_buffer, type, (_QWORD *)st);
GFH_Internal_Parser(buff_addr, 0LL, type, GFG_st);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
__int64 __fastcall GFH_Internal_Parser(__int64 buf_addr, __int64 flag_0, int type, _QWORD *GFG_st)
{
__int64 result; // rax
__int64 st; // [rsp+20h] [rbp-30h]
__int64 v8; // [rsp+28h] [rbp-28h]
unsigned int cnt; // [rsp+3Ch] [rbp-14h]
unsigned int v10; // [rsp+40h] [rbp-10h]
unsigned int i; // [rsp+44h] [rbp-Ch]
unsigned int ret; // [rsp+48h] [rbp-8h]
unsigned int reta; // [rsp+48h] [rbp-8h]
char v14; // [rsp+4Eh] [rbp-2h]
char v15_0; // [rsp+4Fh] [rbp-1h]

v15_0 = 0;
v14 = 0;
if ( flag_0 )
v15_0 = 1;
ret = GFH_FILE_INFO_BasicCheck(buf_addr);
if ( ret > 0xFFF )
return ret;
cnt = *(_DWORD *)(buf_addr + 0x28);
for ( i = 0; i < cnt; i = v10 ) // parse sub struct?
{
st = buf_addr + i;
if ( (*(_DWORD *)st & 0xFFFFFF) != 5066061 )
return 0x1003LL;
v10 = i + *(unsigned __int16 *)(st + 4);
if ( cnt < v10 )
return 0x1005LL;
if ( v15_0 )
{
if ( *(_WORD *)(st + 6) <= 0x104u )
{
v8 = flag_0 + 24LL * *(unsigned __int16 *)(st + 6) + 8;
if ( *(_BYTE *)v8 )
{
reta = (*(__int64 (__fastcall **)(__int64, _QWORD))(v8 + 8))(st, *(_QWORD *)(v8 + 16));
if ( reta > 0xFFF )
return reta;
v14 = 1;
}
}
}
else if ( type == *(unsigned __int16 *)(st + 6) )
{
*GFG_st = st; // [1]
return 0LL;
}
}
if ( v15_0 && v14 )
result = 0LL;
else
result = 0x1003LL;
return result;
}

[1] 的位置 找到这个结构,然后把指针赋值,分析这段逻辑,其实就是文件头:

preloader_hexview

指定preloader文件大小是 0x26794,修改文件大小即可。

解决报错

所以只需要修改 preloader文件的长度为其 +0x24 处 4bytes代表size的字段即可
PS:不能修改这个长度字段

load_preloader_img

深入研究

之前搞的平台也没注意这个问题,也没报错,但是size对不上,所以需要探究文件格式和为什么检查

什么是preloader

介于boot rom 和 bootloader之间的桥梁,主要工作是初始化环境,包括c环境,timer,gpio,pmic,uart,i2c等以及装载lk镜像至DRAM中,建立起最基本的运行环境,最重要的就是初始化DRAM

  • 执行在 ARM EL3

preloader_birdview

工作原理 – 启动过程

mtk_boot

另一种情况是实现了ATF(Arm Trust Firmware)的时候:

boot_with_atf

ATF实现原理_chenying126的博客-CSDN博客_atf

  1. boot rom中执行boot code
  2. 把preloader加载到 ISRAM中
  3. 执行preloader,各种初始化的工作(DRAM初始化)
  4. 把bootloader(uboot, lk)加载到DRAM
  5. 跳转到lk执行
  6. lk执行
  7. 把Linux kernel 和 ramdisk加载到DRAM
  8. 跳转到kernel
  9. kernel执行
  10. 这是在Linux启动过程中使用的一个临时根

preloader 解析

preloader可以看成一个特定格式的可执行文件,所以需要找入口点。

/Users/muhe/Code/MTK6577/mediatek/platform/mt6577/preloader/src/init/init.s

github上找的一个可能是泄漏的基线代码来参考的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
.globl _start

_start :

b resethandler

bss_start:

.word _bss_start

bss_end:

.word _bss_end

stack :

.long sys_stack

stacksz:

.long sys_stack_sz


resethandler :
MOV r0, #0

MOV r1, #0

MOV r2, #0

MOV r3, #0

MOV r4, #0

MOV r5, #0

MOV r6, #0

MOV r7, #0

MOV r8, #0

MOV r9, #0

MOV r10, #0

MOV r11, #0

MOV r12, #0

MOV sp, #0

MOV lr, #0

这个特征还是很明显的,可以试试看:

preloader_init

上面这个0xEA应该是 b指令,可以借由这个搞定基地址

然后是到main.c,继续人肉找特征

preloader_main

找到了字符串,但是没有引用关系 :(

preloader_main 1

通过Ghrida的强制整个binary的分析,然后引用关系确定了main的位置,至此就可以往下看了,对比其他平台preloader的源码,能看个七七八八了。

PS : 基地址编译的时候可以指定的,比如在 linux/bootloader/preloader/platform/mt6735/link_descriptor.ld

1
2
3
4
5
6
7
8
9
10
11
OUTPUT_ARCH(arm)

ENTRY(_start)

romBase = 0x00201000;
ramBase = 0x00102180;

MEMORY {
ram : ORIGIN = ramBase, LENGTH = 0xBA80
rom : ORIGIN = romBase, LENGTH = 0x1F000
}

还有 : linux/bootloader/preloader/platform/mt6735/default.mak

github真是个好地方啊,还有一个完整的MT6737平台Linux based的基线代码,全套的环境和build产物都有的,可以看到:

mt6737_preloader_bin_list

  • 推测preloader应该是一个elf 经过copyobj之类的处理之后拼接上了特定的文件头
1
2
$ file *.elf
preloader_bd6737m_35g_b_m0.elf: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, with debug_info, not stripped

查看相关的makefile可以验证该猜想:

1
2
3
4
5
6
7
8
9
10
11
12
$(D_BIN)/preloader.elf: $(D_BIN)/$(PL_IMAGE_NAME).elf

$(OBJCOPY) -R .dram $(D_BIN)/$(PL_IMAGE_NAME).elf -O elf32-littlearm $(D_BIN)/preloader.elf


ifeq ($(CFG_PRELOADER_DRAM_USE), 1)

preloader_bin: $(D_BIN)/$(PL_DRAM_IMAGE_NAME).bin

$(D_BIN)/$(PL_DRAM_IMAGE_NAME).bin: $(D_BIN)/$(PL_IMAGE_NAME).elf

$(hide) $(OBJCOPY) ${OBJCFLAGS} $(OBJSECOND_FLAG) $(D_BIN)/$(PL_IMAGE_NAME).elf -O binary $(D_BIN)/$(PL_DRAM_IMAGE_NAME).bin

遂尝试:

1
2
3
4
PL_IMG_SECOND_PARTION_SECTION :=.pl_dram.text .pl_dram.data .pl_dram.rodata .pl_dram.start
OBJSECOND_FLAG := $(addprefix -j , $(PL_IMG_SECOND_PARTION_SECTION))

objcopy --gap-fill=0xff $OBJSECOND_FLAG input.elf -O binary output.bin

addprefix这个可以忽略

objcopy_test

当然,hash想一样还是想多了,毕竟编译环境都不一样,直接上diff:

一共两处:
diff1

这个是多了一个GFH结构(说好的 NO_GFH 难道只是说头没有)

diff2

  • preloader_bd6737m_35g_b_m0_LINKED.bin

    • output.bin 多了一个GFH在尾部
  • preloader_bd6737m_35g_b_m0_NO_GFH.bin

    • 中间与部分数据不一致
    • output.bin 多了一个GFH在尾部
  • preloader_bd6737m_35g_b_m0.binpreloader_bd6737m_35g_b_m0_NO_GFH.bin 又多了一个GFH头和尾部的签名数据

diff3

所以这里可以认定:

  • preloader是一个elf,通过copyobj处理后,头、尾添加GFH相关的数据,得到MTK平台的preloader
  • 然后MTK平台的preloader再添加EMMC BOOT头,就得到了从EMMC_BOOT_[1,2] 分区中得到的数据

结构

这里以EMMC为例:

EMMC_BOOT + GFH_INFO_EMMC + WTF1 + preloader_code + WTF2

  • EMMC_BOOT

    • MT6737/linux/bootloader/preloader/tools/gen-preloader-img.py
  • GFH_INFO_EMMC :

    • linux/bootloader/preloader/platform/mt6735/gfh/default/ns/GFH_INFO_EMMC.txt
  • GFH Part 2 : GFH 的另一部分,还会修改上面的size

    • linux/bootloader/preloader/tools/pbp/*
  • preloader_code

    • preloader.elf objcopy处理之后
  • WTF 2 :

    • ?

继续看Makefile来分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$(D_BIN)/$(PL_IMAGE_NAME).bin: $(D_BIN)/$(PL_IMAGE_NAME)_NO_GFH.bin $(GFH_INFO) $(GFH_HASH) $(PBP_TOOL)

@echo "[ Only for Non-Secure Chip ]"

@echo "============================================"

@echo "INI_GFH_GEN=NO"

@echo "[ Attach $(MTK_PLATFORM) GFH ]"

@echo "============================================"

@echo " : GFH_INFO - $(GFH_INFO)"

@echo " : GFH_HASH - $(GFH_HASH)"

cp -f $(GFH_INFO) $@

@chmod 777 $@

cat $< >> $@

cat $(GFH_HASH) >> $@

$(PBP_TOOL) $@

@echo "$(PBP_TOOL) pass !!!!"

// ...

$(D_BIN)/$(PL_IMAGE_NAME).bin: $(D_BIN)/$(PL_IMAGE_NAME)_LINKED.bin

cp -f $< $@

遂可以得到:

  • EMMC_BOOT
    • MT6737/linux/bootloader/preloader/tools/gen-preloader-img.py 生成
  • GFH_INFO_EMMC
    • **linux/bootloader/preloader/platform/mt6735/gfh/default/ns/GFH_INFO_EMMC.txt** 但是这个file size字段是0xffffffff,后续会处理
  • WTF1 : GFH_HASH
    • GFH_HASH.txt // GFH部分会由PBP_TOOL再次处理
  • preloader code
    • 编译的elf经过objcopy处理之后的代码数据
  • WTF2 :preloader extension

参考