在本系列文章中,我们将为读者深入讲解三星手机的内核防护技术。在上一篇文章中,我们为读者介绍了本研究所使用的平台,如何在两种平台上面提取二进制文件,如何获取相关的符号/日志字符串,简要介绍了管理程序的框架,最后,详细说明了三种公用的结构体。在本文中,将继续为读者呈现更多精彩内容!
(接上文)
系统初始化
实际上,uH/RKP是由S-Boot(Samsung Bootloader)加载到内存中的。S-Boot通过命令安全监视程序(运行在EL3)在其指定的地址开始执行管理程序的代码,来跳转到EL2入口点。
uint64_t cmd_load_hypervisor() {
// ...
part = FindPartitionByName("UH");
if (part) {
dprintf("%s: loading uH image from %d..\n", "f_load_hypervisor", part - >block_offset);
ReadPartition( & hdr, part - >file_offset, part - >block_offset, 0x4C);
dprintf("[uH] uh page size = 0x%x\n", (((hdr.size - 1) >> 12) + 1) << 12);
total_size = hdr.size + 0x1210;
dprintf("[uH] uh total load size = 0x%x\n", total_size);
if (total_size > 0x200000 || hdr.size > 0x1FEDF0) {
dprintf("Could not do normal boot.(invalid uH length)\n");
// ...
}
ret = memcmp_s( & hdr, "GREENTEA", 8);
if (ret) {
ret = -1;
dprintf("Could not do uh load. (invalid magic)\n");
// ...
} else {
ReadPartition(0x86FFF000, part - >file_offset, part - >block_offset, total_size);
ret = pit_check_signature(part - >partition_name, 0x86FFF000, total_size);
if (ret) {
dprintf("Could not do uh load. (invalid signing) %x\n", ret);
// ...
}
load_hypervisor(0xC2000400, 0x87001000, 0x2000, 1, 0x87000000, 0x100000);
dprintf("[uH] load hypervisor\n");
}
} else {
ret = -1;
dprintf("Could not load uH. (invalid ppi)\n");
// ...
}
return ret;
}
void load_hypervisor(...) {
dsb();
asm("smc #0");
isb();
}
注意:在最新的三星设备上,监视程序代码(基于ATF-ARM可信固件)已经不再以纯文本的形式出现在S-Boot二进制文件中,取而代之的是一个加密的blob。现在,我们需要利用三星的可信操作系统实现(TEEGRIS)的漏洞,才能转储到纯文本形式的监视程序代码。
void
default(...) {
// ...
if (get_current_el() == 8) {
// Save registers x0 to x30, sp_el1, elr_el2, spsr_el2
// ...
memset( & rkp_bss_start, 0, 0x1000);
main(saved_regs.x0, saved_regs.x1, &saved_regs);
}
asm("smc #0");
}
在这里,代码是从default函数开始执行的。在调用main之前,这个函数将先检查是否在EL2中运行。一旦main返回,它就会进行SMC,大概是为了把控制权还给S-Boot。
int32_t main(int64_t x0, int64_t x1, saved_regs_t * regs) {
// ...
// Setting A=0 (Alignment fault checking disabled)
//SA=0 (SP Alignment check disabled)
set_sctlr_el2(get_sctlr_el2() & 0xFFFFFFF5);
if (!initialized) {
initialized = 1;
// Check if loading address is as expected
if ( & hyp_base != 0x87000000) {
uh_log('L', "slsi_main.c", 326, "[-] static s1 mmu mismatch");
return - 1;
}
set_ttbr0_el2( & static_s1_page_tables_start__);
s1_enable();
uh_init(0x87000000, 0x200000);
if (vmm_init())
return - 1;
uh_log('L', "slsi_main.c", 338, "[+] vmm initialized");
set_vttbr_el2( & static_s2_page_tables_start__);
uh_log('L', "slsi_main.c", 348, "[+] static s2 mmu initialized");
s2_enable();
uh_log('L', "slsi_main.c", 351, "[+] static s2 mmu enabled");
}
uh_log('L', "slsi_main.c", 355, "[*] initialization completed");
return 0;
}
在禁用对齐检查并确保将该二进制文件加载到预期地址(0x87000000)后,main函数会把TTBR0_EL2设置为其初始页表,并调用s1_enable函数。
void s1_enable() {
// ...
cs_init( & s1_lock);
// Setting Attr0=0xff (Normal memory, Outer & Inner Write-Back Non-transient,
//Outer & Inner Read-Allocate Write-Allocate)
//Attr1=0x00 (Device-nGnRnE memory)
//Attr2=0x44 (Normal memory, Outer & Inner Write-Back Transient,
//Outer & Inner No Read-Allocate No Write-Allocate)
set_mair_el2(get_mair_el2() & 0xFFFFFFFFFF000000 | 0x4400FF);
// Setting T0SZ=24 (TTBR0_EL2 region size is 2^40)
//IRGN0=0b11 && ORGN0=0b11
//(Normal memory, Outer & Inner Write-Back
//Read-Allocate No Write-Allocate Cacheable)
//SH0=0b11 (Inner Shareable)
//PAS=0b010 (PA size is 40 bits, 1TB)
set_tcr_el2(get_tcr_el2(); & 0xFFF8C0C0 | 0x23F18);
flush_entire_cache();
sctlr_el2 = get_sctlr_el2();
// Setting C=1 (data is cacheable for EL2)
//I=1 (instruction access is cacheable for EL2)
//WXN=1 (writeable implies non-executable for EL2)
set_sctlr_el2(sctlr_el2 & 0xFFF7EFFB | 0x81004);
invalidate_entire_s1_el2_tlb();
// Setting M=1 (EL2 stage 1 address translation enabled)
set_sctlr_el2(sctlr_el2 & 0xFFF7EFFA | 0x81005);
}
函数s1_enable的主要任务是设置MAIR_EL2、TCR_EL2和SCTLR_EL2等与缓存相关的字段;但它最重要的任务,却是启用EL2的MMU。然后,main函数将调用uh_init函数,并将uH的内存范围传给它。
我们可以看到,Gal Beniamini提出的第二项设计改进,也就是将WXN位设置为1,也已经被三星的KNOX团队实现了。
int64_t uh_init(int64_t uh_base, int64_t uh_size) {
// ...
memset( & uh_state.base, 0, sizeof(uh_state));
uh_state.base = uh_base;
uh_state.size = uh_size;
static_heap_initialize(uh_base, uh_size);
if (!static_heap_remove_range(0x87100000, 0x40000) || !static_heap_remove_range( & hyp_base, 0x87046000 - &hyp_base) ||
!static_heap_remove_range(0x870FF000, 0x1000)) {
uh_panic();
}
memory_init();
uh_log('L', "main.c", 131, "================================= LOG FORMAT =================================");
uh_log('L', "main.c", 132, "[LOG:L, WARN: W, ERR: E, DIE:D][Core Num: Log Line Num][File Name:Code Line]");
uh_log('L', "main.c", 133, "==============================================================================");
uh_log('L', "main.c", 134, "[+] uH base: 0x%p, size: 0x%lx", uh_state.base, uh_state.size);
uh_log('L', "main.c", 135, "[+] log base: 0x%p, size: 0x%x", 0x87100000, 0x40000);
uh_log('L', "main.c", 137, "[+] code base: 0x%p, size: 0x%p", &hyp_base, 0x46000);
uh_log('L', "main.c", 139, "[+] stack base: 0x%p, size: 0x%p", stacks, 0x10000);
uh_log('L', "main.c", 143, "[+] bigdata base: 0x%p, size: 0x%p", 0x870FFC40, 0x3C0);
uh_log('L', "main.c", 152, "[+] date: %s, time: %s", "Feb 27 2020", "17:28:58");
uh_log('L', "main.c", 153, "[+] version: %s", "UH64_3b7c7d4f exynos9610");
uh_register_commands(0, init_cmds, 0, 5, 1);
j_rkp_register_commands();
uh_log('L', "main.c", 370, "%d app started", 1);
system_init();
apps_init();
uh_init_bigdata();
uh_init_context();
memlist_init( & uh_state.dynamic_regions);
pa_restrict_init();
uh_state.inited = 1;
uh_log('L', "main.c", 427, "[+] uH initialized");
return 0;
在将参数保存到一个我们命名为uh_state的全局控制结构体中后,uh_init函数将调用static_heap_initialize函数。这个函数首先将其参数保存到全局变量中,然后,将双向堆块链表初始化为一个横跨整个uH静态内存范围的空闲块。
接下来,uh_init函数将调用heap_remove_range函数,从内存中删除静态堆分配器可以返回的3个重要地址范围(从而有效地将原来的内存块分割成多个块):
- 日志区域。
- uH(代码/数据/bss/栈)区域。
- “bigdata”(分析)区域。
之后,uh_init将调用memory_init函数。
int64_t memory_init() {
memory_buffer = 0x87100000;
memset(0x87100000, 0, 0x40000);
cs_init( & memory_cs);
clean_invalidate_data_cache_region(0x87100000, 0x40000);
memory_buffer_index = 0;
memory_active = 1;
return s1_map(0x87100000, 0x40000, UNKN3 | WRITE | READ);
}
这个函数将日志区域清零,并将其映射到EL2页表中。这个区域将会用于*printf字符串打印函数,后者会在uh_log函数内部调用。
之后,uh_init函数将使用uh_log来记录各种信息 (这些信息可以从设备上的/proc/uh_log中获取)。然后,uh_init函数会调用uh_register_commands和 rkp_register_commands函数(它也调用uh_register_commands,但参数不同)。
int64_t uh_register_commands(uint32_t app_id,
int64_t cmd_array,
int64_t cmd_checker,
uint32_t cmd_count,
uint32_t flag) {
// ...
if (uh_state.inited)
uh_log('D', "event.c", 11, "uh_register_event is not permitted after uh_init : %d", app_id);
if (app_id >= 8)
uh_log('D', "event.c", 14, "wrong app_id %d", app_id);
uh_state.cmd_evtable[app_id] = cmd_array;
uh_state.cmd_checkers[app_id] = cmd_checker;
uh_state.cmd_counts[app_id] = cmd_count;
uh_state.cmd_flags[app_ip] = flag;
uh_log('L', "event.c", 21, "app_id:%d, %d events and flag(%d) has registered", app_id, cmd_count, flag);
if (cmd_checker)
uh_log('L', "event.c", 24, "app_id:%d, cmd checker enforced", app_id);
return 0;
}
uh_register_commands函数的参数为应用程序ID、一个命令处理程序数组、一个可选的命令“检查器”函数、数组中的命令数量和一个调试标志。这些值将存储在 uh_state结构体的cmd_evtable、cmd_checkers、cmd_counts和cmd_flags字段中。
根据内核源代码来看,虽然仅定义了3个应用程序,但是uH最多允许定义8个应用程序。
// from include/linux/uh.h
#define APP_INIT0
#define APP_SAMPLE1
#define APP_RKP2
#define UH_PREFIXUL(0xc300c000)
#define UH_APPID(APP_ID)((UL(APP_ID) & UL(0xFF)) | UH_PREFIX)
enum __UH_APP_ID {
UH_APP_INIT = UH_APPID(APP_INIT),
UH_APP_SAMPLE = UH_APPID(APP_SAMPLE),
UH_APP_RKP = UH_APPID(APP_RKP),
};
接下来,uh_init函数将会调用system_init和apps_init函数。
uint64_t system_init() {
// ...
memset( & saved_regs, 0, sizeof(saved_regs));
res = uh_handle_command(0, 0, &saved_regs);
if (res)
uh_log('D', "main.c", 380, "system init failed %d", res);
return res;
}
uint64_t apps_init() {
// ...
memset( & saved_regs, 0, sizeof(saved_regs));
for (i = 1; i != 8; ++i) {
if (uh_state.cmd_evtable[i]) {
uh_log('W', "main.c", 393, "[+] dst %d initialized", i);
res = uh_handle_command(i, 0, &saved_regs);
if (res)
uh_log('D', "main.c", 396, "app init failed %d", res);
}
}
return res;
}
这些函数将调用命令#0、APP_INIT的system_init,以及所有其他注册应用程序的app_init。在我们的例子中,它最终会调用init_cmd_init和rkp_cmd_init函数,我们将在稍后看到这一点。
int64_t uh_handle_command(uint64_t app_id, uint64_t cmd_id, saved_regs_t * regs) {
// ...
if ((uh_state.cmd_flags[app_id] & 1) != 0)
uh_log('L', "main.c", 441, "event received %lx %lx %lx %lx %lx %lx", app_id, cmd_id, regs - >x2, regs - >x3, regs - >x4,
regs - >x5);
cmd_checker = uh_state.cmd_checkers[app_id];
if (cmd_id && cmd_checker && cmd_checker(cmd_id)) {
uh_log('E', "main.c", 448, "cmd check failed %d %d", app_id, cmd_id);
return - 1;
}
if (app_id >= 8)
uh_log('D', "main.c", 453, "wrong dst %d", app_id);
if (!uh_state.cmd_evtable[app_id])
uh_log('D', "main.c", 456, "dst %d evtable is NULL\n", app_id);
if (cmd_id >= uh_state.cmd_counts[app_id])
uh_log('D', "main.c", 459, "wrong type %lx %lx", app_id, cmd_id);
cmd_handler = uh_state.cmd_evtable[app_id][cmd_id];
if (!cmd_handler) {
uh_log('D', "main.c", 464, "no handler %lx %lx", app_id, cmd_id);
return - 1;
}
return cmd_handler(regs);
}
并且,函数uh_handle_command会打印出应用程序ID、命令ID及其参数(如果设置了调试标志的话),如果指定了命令检查器函数,则会调用相应的命令处理程序。
之后,uh_init函数将调用uh_init_bigdata和uh_init_context函数。
int64_t uh_init_bigdata() {
if (!bigdata_state)
bigdata_state = malloc(0x230, 0);
memset(0x870FFC40, 0, 960);
memset(bigdata_state, 0, 560);
return s1_map(0x870FF000, 0x1000, UNKN3 | WRITE | READ);
}
其中,函数uh_init_bigdata用于分配和清零分析功能使用的缓冲区。它还让bigdata区域在EL2页表中变为可读和可写的。
int64_t * uh_init_context() {
// ...
uh_context = malloc(0x1000, 0);
if (!uh_context)
uh_log('W', "RKP_1cae4f3b", 21, "%s RKP_148c665c", "uh_init_context");
return memset(uh_context, 0, 0x1000);
}
同时,函数uh_init_context也将分配并清零一个缓冲区,这个缓冲区在平台重置时用来存储管理程序的寄存器(我们不知道它被用在哪里,也许是处理某些事件时,被监控器用来恢复管理程序的状态)。
之后,函数uh_init调用memlist_init函数来初始化uh_state结构体中的dynamic_regions memlist结构体,然后,将调用pa_restrict_init函数。
int64_t pa_restrict_init() {
memlist_init( & protected_ranges);
memlist_add( & protected_ranges, 0x87000000, 0x200000);
if (!memlist_contains_addr( & protected_ranges, rkp_cmd_counts))
uh_log('D', "pa_restrict.c", 79, "Error, cmd_cnt not within protected range, cmd_cnt addr : %lx", rkp_cmd_counts);
if (!memlist_contains_addr( & protected_ranges, (uint64_t) & protected_ranges))
uh_log('D', "pa_restrict.c", 84, "Error protect_ranges not within protected range, protect_ranges addr : %lx",
&protected_ranges);
return uh_log('L', "pa_restrict.c", 87, "[+] uH PA Restrict Init");
}
函数pa_restrict_init将初始化protected_ranges memlist结构体,并将uH内存区域添加到其中。此外,它还会检查rkp_cmd_counts和protected_ranges结构体是否包含在该memlist结构体中。
之后,函数uh_init将返回main函数,然后,main函数将调用函数vmm_init。
int64_t vmm_init() {
// ...
uh_log('L', "vmm.c", 142, ">>vmm_init<<");
cs_init( & stru_870355E8);
cs_init( & panic_cs);
set_vbar_el2( & vmm_vector_table);
// Setting TVM=1 (EL1 write accesses to the specified EL1 virtual
//memory control registers are trapped to EL2)
hcr_el2 = get_hcr_el2() | 0x4000000;
uh_log('L', "vmm.c", 161, "RKP_398bc59b %x", hcr_el2);
set_hcr_el2(hcr_el2);
return 0;
}
函数vmm_init会将VBAR_EL2寄存器设置为管理程序所使用的异常向量,并允许对EL1级别的虚拟内存控制寄存器的写入操作进行捕获。
然后,uh_init将VTTBR_EL2寄存器设置为初始页表,该表将用于第二阶段地址转换,实现EL1级别的内存访问。最后,在返回之前,它将调用s2_enable函数。
void s2_enable() {
// ...
cs_init( & s2_lock);
// Setting T0SZ=24 (VTTBR_EL2 region size is 2^40)
//SL0=0b01 (Stage 2 translation lookup start at level 1)
//IRGN0=0b11 && ORGN0=0b11
//(Normal memory, Outer & Inner Write-Back
//Read-Allocate No Write-Allocate Cacheable)
//SH0=0b11 (Inner Shareable)
//TG0=0b00 (Granule size is 4KB)
//PS=0b010 (PA size is 40 bits, 1TB)
set_vtcr_el2(get_vtcr_el2() & 0xFFF80000 | 0x23F58);
invalidate_entire_s1_s2_el1_tlb();
// Setting VM=1
set_hcr_el2(get_hcr_el2() | 1);
lock_start = 1;
}
s2_enable函数将配置第二级地址转换寄存器并启用它。
应用程序的初始化
前面说过,uh_init函数将为每个已注册的应用程序调用命令#0。下面,我们以两个应用程序APP_INIT(有点像init进程)和APP_RKP为例,看看它们将执行哪些操作。
APP_INIT
为APP_INIT注册的命令处理程序包括:
让我们来看看uH在启动过程中调用的命令#0(command #0)。
int64_t init_cmd_init(saved_regs_t * regs) {
// ...
if (!uh_state.fault_handler && regs - >x2) {
uh_state.fault_handler = rkp_get_pa(regs - >x2);
uh_log('L', "main.c", 161, "[*] uH fault handler has been registered");
}
return 0;
}
APP_INIT的命令#0处理程序其实很简单:它首先会设置uh_state的fault_handler字段。这个结构体包含一个内核函数的地址,当管理程序检测到故障时,就会调用这个函数。
当uH调用这个命令时,它实际上啥都不做,因为寄存器都被设置为0。但是,这个命令以后还会被内核所调用,这一点可以init/main.c文件中的rkp_init函数中可以看到。
// from init/main.c
static void __init rkp_init(void)
{
uh_call(UH_APP_INIT, 0, uh_get_fault_handler(), kimage_voffset, 0, 0);
// ...
}
// from include/linux/uh_fault_handler.h
typedef struct uh_registers {
u64 regs[31];
u64 sp;
u64 pc;
u64 pstate;
}
uh_registers_t;
typedef struct uh_handler_data {
esr_t esr_el2;
u64 elr_el2;
u64 hcr_el2;
u64 far_el2;
u64 hpfar_el2;
uh_registers_t regs;
}
uh_handler_data_t;
typedef struct uh_handler_list {
u64 uh_handler;
uh_handler_data_t uh_handler_data[NR_CPUS];
}
uh_handler_list_t;
// from init/uh_fault_handler.c
void uh_fault_handler(void)
{
unsigned int cpu;
uh_handler_data_t * uh_handler_data;
u32 exception_class;
unsigned long flags;
struct pt_regs regs;
spin_lock_irqsave( & uh_fault_lock, flags);
cpu = smp_processor_id();
uh_handler_data = &uh_handler_list.uh_handler_data[cpu];
exception_class = uh_handler_data - >esr_el2.ec;
if (!exception_class_string[exception_class]
|| exception_class > esr_ec_brk_instruction_execution)
exception_class = esr_ec_unknown_reason;
pr_alert("=============uH fault handler logging=============\n");
pr_alert("%s", exception_class_string[exception_class]);
pr_alert("[System registers]\n", cpu);
pr_alert("ESR_EL2: %x\tHCR_EL2: %llx\tHPFAR_EL2: %llx\n",
uh_handler_data - >esr_el2.bits,
uh_handler_data - >hcr_el2, uh_handler_data - >hpfar_el2);
pr_alert("FAR_EL2: %llx\tELR_EL2: %llx\n", uh_handler_data - >far_el2,
uh_handler_data - >elr_el2);
memset( & regs, 0, sizeof(regs));
memcpy( & regs, &uh_handler_data - >regs, sizeof(uh_handler_data - >regs));
do_mem_abort(uh_handler_data - >far_el2, (u32) uh_handler_data - >esr_el2.bits, ®s);
panic("%s", exception_class_string[exception_class]);
}
u64 uh_get_fault_handler(void)
{
uh_handler_list.uh_handler = (u64) & uh_fault_handler;
return (u64) & uh_handler_list;
}
它们是APP_INIT的2个命令,用于处理初始化相关的事情(尽管它们的命令ID并非#0)。它们不是由内核调用的,而是在加载/执行内核之前由S-Boot所调用的。
int64_t dtb_update(...) {
// ...
dtb_find_entries(dtb, "memory", j_uh_add_dynamic_region);
sprintf(path, "/reserved-memory");
offset = dtb_get_path_offset(dtb, path);
if (offset < 0) {
dprintf("%s: fail to get path [%s]: %d\n", "dtb_update_reserved_memory", path, offset);
} else {
heap_base = 0;
heap_size = 0;
dtb_add_reserved_memory(dtb, offset, 0x87000000, 0x200000, "el2_code", "el2,uh");
uh_call(0xC300C000, 4, &heap_base, &heap_size, 0, 0);
dtb_add_reserved_memory(dtb, offset, heap_base, heap_size, "el2_earlymem", "el2,uh");
dtb_add_reserved_memory(dtb, offset, 0x80001000, 0x1000, "kaslr", "kernel-kaslr");
if (get_env_var(FORCE_UPLOAD) == 5)
rmem_size = 0x2400000;
else
rmem_size = 0x1700000;
dtb_add_reserved_memory(dtb, offset, 0xC9000000, rmem_size, "sboot", "sboot,rmem");
}
// ...
}
int64_t uh_add_dynamic_region(int64_t addr, int64_t size) {
uh_call(0xC300C000, 2, addr, size, 0, 0);
return 0;
}
void uh_call(...) {
asm("hvc #0");
}
S-Boot将为DTB中的每一个内存节点调用命令#2(HVC参数是内存区域的地址和大小),然后调用命令#4(参数是两个指向S-Boot局部变量的指针)。然后,它将调用命令#4(参数是两个指向S-Boot局部变量的指针)。
int64_t init_cmd_add_dynamic_region(saved_regs_t * regs) {
// ...
if (uh_state.dynamic_heap_inited || !regs - >x2 || !regs - >x3)
return - 1;
return memlist_add( & uh_state.dynamic_regions, regs - >x2, regs - >x3);
}
命令#2,我们将其命名为add_dynamic_region,用于向dynamic_regions memlist中添加一个内存范围,将来会从中划出uH的“动态堆”区域。S-Boot还会通知管理程序,当DDR完成初始化后,它可以访问哪些物理内存区域。
int64_t init_cmd_initialize_dynamic_heap(saved_regs_t * regs) {
// ...
if (!regs - >x2 || !regs - >x3)
return - 1;
PHYS_OFFSET = memlist_get_min_addr( & uh_state.dynamic_regions);
base = virt_to_phys_el1(regs - >x2);
check_kernel_input(base);
size = virt_to_phys_el1(regs - >x3);
check_kernel_input(size);
if (!base || !size) {
uh_log('L', "main.c", 188, "Wrong addr in dynamicheap : base: %p, size: %p", base, size);
return - 1;
}
if (uh_state.dynamic_heap_inited)
return - 1;
uh_state.dynamic_heap_inited = 1;
dynamic_heap_base = *base;
if (!regs - >x4) {
memlist_merge_ranges( & uh_state.dynamic_regions);
memlist_dump( & uh_state.dynamic_regions);
some_size1 = memlist_get_some_size( & uh_state.dynamic_regions, 0x100000);
set_robuf_size(some_size1 + 0x600000);
some_size2 = memlist_get_some_size( & uh_state.dynamic_regions, 0x100000);
some_size3 = memlist_get_some_size( & uh_state.dynamic_regions, 0x200000);
dynamic_heap_size = (some_size1 + some_size2 + some_size3 + 0x7FFFFF) & 0xFFE00000;
} else {
dynamic_heap_size = *size;
}
if (!dynamic_heap_base) {
dynamic_heap_base = memlist_get_region_of_size( & uh_state.dynamic_regions, dynamic_heap_size, 0x200000);
} else {
if (memlist_remove( & uh_state.dynamic_regions, dynamic_heap_base, dynamic_heap_size)) {
uh_log('L', "main.c", 281, "[-] Dynamic heap address is not existed in memlist, base : %p", dynamic_heap_base);
return - 1;
}
}
dynamic_heap_initialize(dynamic_heap_base, dynamic_heap_size);
uh_log('L', "main.c", 288, "[+] Dynamic heap initialized base: %lx, size: %lx", dynamic_heap_base, dynamic_heap_size);
* base = dynamic_heap_base;
* size = dynamic_heap_size;
mapped_start = dynamic_heap_base;
if ((s2_map(dynamic_heap_base, dynamic_heap_size_0, UNKN1 | WRITE | READ, &mapped_start) & 0x8000000000000000) != 0) {
uh_log('L', "main.c", 299, "s2_map returned false, start : %p, size : %p", mapped_start, dynamic_heap_size);
return - 1;
}
sparsemap_init("physmap", &uh_state.phys_map, &uh_state.dynamic_regions, 0x20, 0);
sparsemap_for_all_entries( & uh_state.phys_map, protected_ranges_add);
sparsemap_init("ro_bitmap", &uh_state.ro_bitmap, &uh_state.dynamic_regions, 1, 0);
sparsemap_init("dbl_bitmap", &uh_state.dbl_bitmap, &uh_state.dynamic_regions, 1, 0);
memlist_init( & uh_state.page_allocator.list);
memlist_add( & uh_state.page_allocator.list, dynamic_heap_base, dynamic_heap_size);
sparsemap_init("robuf", &uh_state.page_allocator.map, &uh_state.page_allocator.list, 1, 0);
allocate_robuf();
regions_end_addr = memlist_get_max_addr( & uh_state.dynamic_regions);
if ((regions_end_addr >> 33) <= 4) {
s2_unmap(regions_end_addr, 0xA00000000 - regions_end_addr);
s1_unmap(regions_end_addr, 0xA00000000 - regions_end_addr);
}
return 0;
}
命令#4,我们将其命名为initialize_dynamic_heap,用于最终确定内存区域列表并初始化动态堆分配器。S-Boot在使用前面的命令添加了所有物理内存区域后就会调用它。这个函数处理的事情非常多(有些细节我们还不是很清楚),包括:
- 设置PHYS_OFFSET(内核起始的物理地址)。
- 如果动态堆基地址作为第一个参数,它将使用动态堆;否则,它将尝试找到一个足够大的内存区域。
- 从dynamic_regions memlist中删除所选区域。
- 调用dynamic_heap_initialize,就像静态堆一样,保存全局变量中的值,并初始化堆块列表,类似于静态堆分配器。
- 将动态堆区域添加到第二阶段的转换表中。
- 用dynamic_regions memlist初始化3个sparsemaps physmap、ro_bitmap和dbl_bitmap。
- 将physmap sparsemap的所有bitmap缓冲区添加到protected_ranges memlist中。
- 初始化一个名为robuf_regions的新memlist,并将动态堆区域添加到其中。
- 用robuf_regions memlist初始化一个名为robuf的新sparsemap。
- 调用allocate_robuf,我们将在下文详细介绍该函数。
- 最后,对于第一和第二阶段中映射到区域列表范围的结束地址和0xA00000000之间的内存,全部解除映射(我们还不知道它为什么要这么做)。
int64_t allocate_robuf() {
// ...
if (!uh_state.dynamic_heap_inited) {
uh_log('L', "page_allocator.c", 84, "Dynamic heap needs to be initialized");
return - 1;
}
robuf_size = uh_state.page_allocator.robuf_size & 0xFFFFF000;
robuf_base = dynamic_heap_alloc(uh_state.page_allocator.robuf_size & 0xFFFFF000, 0x1000);
if (!robuf_base)
dynamic_heap_alloc_last_chunk( & robuf_base, &robuf_size);
if (!robuf_base) {
uh_log('L', "page_allocator.c", 96, "Robuffer Alloc Fail");
return - 1;
}
if (robuf_size) {
offset = 0;
do {
zero_data_cache_page(robuf_base + offset);
offset += 0x1000;
} while ( offset < robuf_size );
}
return page_allocator_init( & uh_state.page_allocator, robuf_base, robuf_size);
}
函数allocate_robuf首先会尝试从刚刚初始化的动态堆分配器中分配一个大小为robuf_size的内存区域,如果分配失败,它将获取分配器中可用的最后一个连续内存块。然后,它将此内存区域作为参数调用page_allocator_init函数。page_allocator_init函数将初始化sparsemap和内存页分配器将使用的所有内容。内存页分配器/“robuf”区域是RKP将只读内存页移交给内核过程中使用的内存区域(例如,用于数据保护功能)。
小结
在本系列文章中,我们将为读者深入讲解三星手机的内核防护技术。在本文中,我们为读者介绍了系统的初始化过程,以及应用程序的初始化过程,在后续的文章中,会有更多精彩内容呈现给大家,敬请期待!
(未完待续)