乐于分享
好东西不私藏

安卓中的 ASLR 实现与关闭

安卓中的 ASLR 实现与关闭

什么是 ASLR

由 Linux 内核在 2001 年引入的一项内核补丁技术,旨在为防止堆栈指针泄漏引发的重定向攻击。通过 ASLR(Address Space Layout Randomization)随机化内存布局,使得指针偏移难以预测,从而防范攻击。

在最新的 Android 版本中,ASLR 已经作为系统安全的一部分被强制开启。并且不可关闭。各版本 ASLR 对比:

Android 版本
ASLR 状态
随机化范围
PIE 要求
2.2 (Froyo) 及以下
不支持
2.3 (Gingerbread)
可选
栈、共享库
4.0 (ICS)
默认开启
栈、堆、mmap、共享库
可选支持
4.1-4.4 (JB/KK)
默认开启
增强随机化
建议使用
5.0-7.1 (L-M)
强制开启
完整随机化
强制 PIE
8.0+ (Oreo+)
强制开启
高熵 ASLR
强制 PIE

ASLR 的实现

ASLR 的实现在安卓中共分为两部分,分别是内核态与用户态。

内核态

ASLR 的开启与关闭由进程的 personality 属性或全局变量 randomize_va_space 控制。当一个进程 execve(init) 启动时,在内核中经由以下链条:

sys_execve -> do_execve -> do_execveat_common -> bprm_execve -> exec_binpr -> search_binary_handler -> load_binary -> load_elf_binary

在 load_elf_binary 装载 ELF 的过程中会设置 PF_RANDOMIZE 标志。

static int load_elf_binary(struct linux_binprm *bprm){  // ......  const int snapshot_randomize_va_space = READ_ONCE(randomize_va_space);    if (!(current->personality & ADDR_NO_RANDOMIZE) && snapshot_randomize_va_space)        current->flags |= PF_RANDOMIZE;    setup_new_exec(bprm);    retval = setup_arg_pages(bprm, randomize_stack_top(STACK_TOP),                 executable_stack);    // ......}

一旦 PF_RANDOMIZE 标志设置成功,后续的二进制程序、共享库、堆、栈内存都将开启随机化。对已经建立完成的内存布局,后续再修改标志也不会重排现有布局。

void setup_new_exec(struct linux_binprm * bprm){    /* Setup things that can depend upon the personality */struct task_struct *me = current;    arch_pick_mmap_layout(me->mm, &bprm->rlim_stack);  // ......}void arch_pick_mmap_layout(struct mm_struct *mm, const struct rlimit *rlim_stack){    unsigned long random_factor = 0UL;    if (current->flags & PF_RANDOMIZE)        random_factor = arch_mmap_rnd();    // ......}unsigned long arch_mmap_rnd(void){    return (get_random_u32() & MMAP_RND_MASK) << PAGE_SHIFT;}int setup_arg_pages(struct linux_binprm *bprm,            unsigned long stack_top,            int executable_stack){    // ......    if (current->flags & PF_RANDOMIZE)        stack_base += (STACK_RND_MASK << PAGE_SHIFT);    /* Make sure we didn't let the argument array grow too large. */    if (vma->vm_end - vma->vm_start > stack_base)        return -ENOMEM;    stack_base = PAGE_ALIGN(stack_top - stack_base);    stack_shift = vma->vm_start - stack_base;    mm->arg_start = bprm->p - stack_shift;    bprm->p = vma->vm_end - stack_shift; // ......}

用户态

用户态的 ASLR 是在 Linker 中强制开启的,这是安卓独有的特性。当共享库(so)被 Linker 加载时,在 find_libraries 阶段会将自身以及依赖的库加载顺序随机打乱。

bool find_libraries(android_namespace_t* ns,                    soinfo* start_with,                    const char* const library_names[],                    size_t library_names_count,                    soinfo* soinfos[],                    std::vector<soinfo*>* ld_preloads,                    size_t ld_preloads_count,                    int rtld_flags,                    const android_dlextinfo* extinfo,                    bool add_as_children,                    bool search_linked_namespaces){  // ......  // Step 2: Load libraries in random order (see b/24047022)  LoadTaskList load_list;  for (auto&& task : load_tasks) {    soinfo* si = task->get_soinfo();    auto pred = [&](const LoadTask* t) {      return t->get_soinfo() == si;    };    if (!si->is_linked() &&        std::find_if(load_list.begin(), load_list.end(), pred) == load_list.end() ) {      load_list.push_back(task);    }  }  shuffle(&load_list);    // ......  return linked;}static void shuffle(std::vector<LoadTask*>* v){  for (size_t i = 0, size = v->size(); i < size; ++i) {    size_t n = size - i;    size_t r = arc4random_uniform(n);    std::swap((*v)[n-1], (*v)[r]);  }}

同样在 ReserveAddressSpace 阶段也加入了 ASLR。

bool ElfReader::ReserveAddressSpace(address_space_params* address_space){  // ......  if (load_size_ > address_space->reserved_size) {    if (address_space->must_use_address) {      DL_ERR("reserved address space %zd smaller than %zd bytes needed for \"%s\"",             load_size_ - address_space->reserved_size, load_size_, name_.c_str());      return false;    }    start = ReserveAligned(load_size_, kLibraryAlignment);  }  load_start_ = start;  load_bias_ = reinterpret_cast<uint8_t*>(start) - addr;  return true;}//申请一块大内存,随机选择任意区间做为起始点。static void* ReserveAligned(size_t size, size_t align){ // ......  size_t mmap_size = align_up(size, align) + align - PAGE_SIZE;  uint8_t* mmap_ptr =      reinterpret_cast<uint8_t*>(mmap(nullptr, mmap_size, PROT_NONE, mmap_flags, -1, 0));  if (mmap_ptr == MAP_FAILED) {    return nullptr;  }  uint8_t* first = align_up(mmap_ptr, align);  uint8_t* last = align_down(mmap_ptr + mmap_size, align) - size;  size_t n = is_first_stage_init() ? 0 : arc4random_uniform((last - first) / PAGE_SIZE + 1);  uint8_t* start = first + n * PAGE_SIZE;  munmap(mmap_ptr, start - mmap_ptr);  munmap(start + size, mmap_ptr + mmap_size - (start + size));  return start;}

关闭 ASLR

在安卓中要做到完全关闭 ASLR,需要将内核态与用户态两层限制全部绕过。

内核态

在常规的系统中我们可以直接将 /proc/sys/kernel/randomize_va_space 设置为 0 即可关闭内核态的 ASLR。但是因为安卓 APP 的启动都是由 Zygote 进程孵化而来, 而 Zygote 进程是由 Init 在系统启动较早期启动。因此,这个方法在安卓中失效了。

通过上面的内核实现分析,我们已经得知进程的 PF_RANDOMIZE 标志一旦被设置,ASLR 将不可关闭。因此,我们只能在 Zygote 启动之前修改,才能实现内核态 ASLR 的关闭。

如此,我们可以借助 Apatch 提供的 post-fs-data 时机来运行脚本,完成对 randomize_va_space 的修改。

#!/system/bin/shsysctl -w kernel.randomize_va_space=0 2>/dev/nullresetprop -n ro.kernel.randomize_va_space 0echo 0 > /proc/sys/kernel/randomize_va_space

不过实测之后,这段脚本并没有生效。经过一番排查,在 Init 进程的启动过程中会将 randomize_va_space 重置为 2。

因此,我们必须在 Init 进程启动的过程中将 randomize_va_space 改回来。

通过上面的内核分析,我们知道 PF_RANDOMIZE 标志是在 load_elf_binary 函数中设置的。

这样就为我们提供了一个完美的 HOOK 时机,我们可以在 load_elf_binary 函数进入之前将 randomize_va_space 改为 0 。

KPM_NAME("kpm-aslr");KPM_VERSION("1.0.0");KPM_LICENSE("GPL v2");KPM_AUTHOR("lidongyooo");KPM_DESCRIPTION("KernelPatch Module ASLR Hook Example");static unsigned long load_elf_binary_addr;static int load_elf_binary_hooked;static int *randomize_va_space_addr;static void before_load_elf_binary(hook_fargs1_t *args, void *udata){    if (!args) return;    if (randomize_va_space_addr != NULL) {        *randomize_va_space_addr = 0;    }    logke("load_elf_binary enter current=%s bprm=%llx randomize_va_space=%d",          get_task_comm(current),          (unsigned long long)args->arg0, *randomize_va_space_addr);}static void after_load_elf_binary(hook_fargs1_t *args, void *udata){}static long inline_hook_demo_init(const char *args, const char *event, void *__user reserved){    logke("kpm aslr init\n");    randomize_va_space_addr = (int*)kallsyms_lookup_name("randomize_va_space");    load_elf_binary_addr = kallsyms_lookup_name("load_elf_binary");    if (!load_elf_binary_addr) {        logke("load_elf_binary not found\n");        return -ENOENT;    }    hook_err_t err = hook_wrap1((void *)load_elf_binary_addr, before_load_elf_binary, after_load_elf_binary, 0);    logke("hook err: %d\n", err);    if (err != HOOK_NO_ERR) return err;    load_elf_binary_hooked = 1;    return 0;}static long inline_hook_control0(const char *args, char *__user out_msg, int outlen){    pr_info("kpm control, args: %s\n", args);    return 0;}static long inline_hook_demo_exit(void *__user reserved){    if (load_elf_binary_hooked) {        hook_unwrap((void *)load_elf_binary_addr, before_load_elf_binary, after_load_elf_binary);        load_elf_binary_hooked = 0;    }    logke("kpm aslr  exit\n");    return 0;}KPM_INIT(inline_hook_demo_init);KPM_CTL0(inline_hook_control0);KPM_EXIT(inline_hook_demo_exit);

将这段编译为 KPM 模块,刷进 boot.img。再次启动时将会发现内存段的高位地址已经被固定。

redfin:/ # ldd /bin/sh                                                                                                                linux-vdso.so.1 => [vdso] (0x7ff7eae000)    libc.so => /apex/com.android.runtime/lib64/bionic/libc.so (0x7ff504a000)    libdl.so => /apex/com.android.runtime/lib64/bionic/libdl.so (0x7ff6b0b000)redfin:/ # ldd /bin/sh                                                                                                                linux-vdso.so.1 => [vdso] (0x7ff7eae000)    libc.so => /apex/com.android.runtime/lib64/bionic/libc.so (0x7ff56a0000)    libdl.so => /apex/com.android.runtime/lib64/bionic/libdl.so (0x7ff6b29000)

用户态

在上面的 Linker 源码分析中,我们知道 find_libraries、ReserveAddressSpace 都使用了 arc4random_uniform 作为随机因子。

因此,我们可以 HOOK arc4random_uniform 让其恒为 0,即可关闭 ASLR。

let linker = Process.findModuleByName('linker64')let __dl_arc4random_uniform_addr  = linker.findSymbolByName('__dl_arc4random_uniform')Interceptor.replace(__dl_arc4random_uniform_addr, new NativeCallback(function (range) {    return 0}, 'int', ['int']))let modules = Process.enumerateModules()for (const module of modules) {    console.log(JSON.stringify(module))}

测试

//第一次启动{"name":"base.odex","version":null,"base":"0x7bec200000","size":329334784,"path":"/data/app/~~_l2RJrouzXlrlwu9L_rZbQ==/com.example.app-3IT61D6SNh6GsSUiKRg5SA==/oat/arm64/base.odex"}{"name":"split_df_camera.odex","version":null,"base":"0x7cf9680000","size":24576,"path":"/data/app/~~_l2RJrouzXlrlwu9L_rZbQ==/com.example.app-3IT61D6SNh6GsSUiKRg5SA==/oat/arm64/split_df_camera.odex"}{"name":"split_df_im_bootfinish.odex","version":null,"base":"0x7c92e00000","size":1454080,"path":"/data/app/~~_l2RJrouzXlrlwu9L_rZbQ==/com.example.app-3IT61D6SNh6GsSUiKRg5SA==/oat/arm64/split_df_im_bootfinish.odex"}{"name":"split_df_kids_mode.odex","version":null,"base":"0x7cf9640000","size":24576,"path":"/data/app/~~_l2RJrouzXlrlwu9L_rZbQ==/com.example.app-3IT61D6SNh6GsSUiKRg5SA==/oat/arm64/split_df_kids_mode.odex"}{"name":"split_df_location.odex","version":null,"base":"0x7cf9600000","size":114688,"path":"/data/app/~~_l2RJrouzXlrlwu9L_rZbQ==/com.example.app-3IT61D6SNh6GsSUiKRg5SA==/oat/arm64/split_df_location.odex"}{"name":"libnpth_unwind.so","version":null,"base":"0x7c8d2c0000","size":270336,"path":"/data/app/~~_l2RJrouzXlrlwu9L_rZbQ==/com.example.app-3IT61D6SNh6GsSUiKRg5SA==/lib/arm64/libnpth_unwind.so"}{"name":"libnpth_dl.so","version":null,"base":"0x7cf94c0000","size":61440,"path":"/data/app/~~_l2RJrouzXlrlwu9L_rZbQ==/com.example.app-3IT61D6SNh6GsSUiKRg5SA==/lib/arm64/libnpth_dl.so"}//第二次启动{"name":"base.odex","version":null,"base":"0x7bec200000","size":329334784,"path":"/data/app/~~_l2RJrouzXlrlwu9L_rZbQ==/com.example.app-3IT61D6SNh6GsSUiKRg5SA==/oat/arm64/base.odex"}{"name":"split_df_camera.odex","version":null,"base":"0x7cf9680000","size":24576,"path":"/data/app/~~_l2RJrouzXlrlwu9L_rZbQ==/com.example.app-3IT61D6SNh6GsSUiKRg5SA==/oat/arm64/split_df_camera.odex"}{"name":"split_df_im_bootfinish.odex","version":null,"base":"0x7c92e00000","size":1454080,"path":"/data/app/~~_l2RJrouzXlrlwu9L_rZbQ==/com.example.app-3IT61D6SNh6GsSUiKRg5SA==/oat/arm64/split_df_im_bootfinish.odex"}{"name":"split_df_kids_mode.odex","version":null,"base":"0x7cf9640000","size":24576,"path":"/data/app/~~_l2RJrouzXlrlwu9L_rZbQ==/com.example.app-3IT61D6SNh6GsSUiKRg5SA==/oat/arm64/split_df_kids_mode.odex"}{"name":"split_df_location.odex","version":null,"base":"0x7cf9600000","size":114688,"path":"/data/app/~~_l2RJrouzXlrlwu9L_rZbQ==/com.example.app-3IT61D6SNh6GsSUiKRg5SA==/oat/arm64/split_df_location.odex"}{"name":"libnpth_unwind.so","version":null,"base":"0x7c8d2c0000","size":270336,"path":"/data/app/~~_l2RJrouzXlrlwu9L_rZbQ==/com.example.app-3IT61D6SNh6GsSUiKRg5SA==/lib/arm64/libnpth_unwind.so"}{"name":"libnpth_dl.so","version":null,"base":"0x7cf94c0000","size":61440,"path":"/data/app/~~_l2RJrouzXlrlwu9L_rZbQ==/com.example.app-3IT61D6SNh6GsSUiKRg5SA==/lib/arm64/libnpth_dl.so"}

可以发现,两次启动加载的类库地址完全一致。

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » 安卓中的 ASLR 实现与关闭

猜你喜欢

  • 暂无文章