三星手机内核防护技术RKP深度剖析(三)

 

在本系列文章中,我们将为读者深入讲解三星手机的内核防护技术。在上一篇文章中,我们为读者介绍了本研究所使用的平台,如何在两种平台上面提取二进制文件,如何获取相关的符号/日志字符串,简要介绍了管理程序的框架,最后,详细说明了三种公用的结构体。在本文中,将继续为读者呈现更多精彩内容!

(接上文)

 

系统初始化

实际上,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个重要地址范围(从而有效地将原来的内存块分割成多个块):

  1. 日志区域。
  2. uH(代码/数据/bss/栈)区域。
  3. “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, &regs);

    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在使用前面的命令添加了所有物理内存区域后就会调用它。这个函数处理的事情非常多(有些细节我们还不是很清楚),包括:

  1. 设置PHYS_OFFSET(内核起始的物理地址)。
  2. 如果动态堆基地址作为第一个参数,它将使用动态堆;否则,它将尝试找到一个足够大的内存区域。
  3. 从dynamic_regions memlist中删除所选区域。
  4. 调用dynamic_heap_initialize,就像静态堆一样,保存全局变量中的值,并初始化堆块列表,类似于静态堆分配器。
  5. 将动态堆区域添加到第二阶段的转换表中。
  6. 用dynamic_regions memlist初始化3个sparsemaps physmap、ro_bitmap和dbl_bitmap。
  7. 将physmap sparsemap的所有bitmap缓冲区添加到protected_ranges memlist中。
  8. 初始化一个名为robuf_regions的新memlist,并将动态堆区域添加到其中。
  9. 用robuf_regions memlist初始化一个名为robuf的新sparsemap。
  10. 调用allocate_robuf,我们将在下文详细介绍该函数。
  11. 最后,对于第一和第二阶段中映射到区域列表范围的结束地址和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将只读内存页移交给内核过程中使用的内存区域(例如,用于数据保护功能)。

 

小结

在本系列文章中,我们将为读者深入讲解三星手机的内核防护技术。在本文中,我们为读者介绍了系统的初始化过程,以及应用程序的初始化过程,在后续的文章中,会有更多精彩内容呈现给大家,敬请期待!

(未完待续)

(完)