Cython篇


Python逆向主要难度还是在pyd(或者说so)上,以下为Cython生成的可供python导入的模块的逆向手段。

Cython也能产生exe,使用—embed命令行参数,然后正常添加好include目录和libs目录和依赖就可以直接编译了。

开发的角度

项目创建和构建

在Windows上,直接通过setup.py的处理方法,编译出来的pyd没有符号文件产生,而且似乎也没有办法通过设置命令行,设置某些参数之类的方式来生成符号文件。我目前探索比较方便的方案是:

创建VS的动态链接库工程,项目属性里做如下工作:

  • (可选)可以在pre event里调用cython生成.c

  • 需要在项目依赖的include目录和lib目录,用到的lib依赖,三个地方都加上python的部分

  • 指定生成和本机python环境一样的pyd,比如都是x64的
  • 修改生成目标的dll后缀设置为pyd

然后项目文件的管理:

  • 不要把.py放项目的Source Files源文件)filter下,不然会被当成项目源码干扰编译过程,可以创建一个单独的filter来放.py文件,比如我现在的管理方式:

逆向的角度

  • 尽可能恢复符号,借用别的有符号的pyd,用bindiff进行恢复
  • 导出一部分结构体

__Pyx_AddTraceback对逆向有较大辅助作用

  • 确认下还有哪些符号有利于逆向分析

代码逆向定位

都在slots里,所以是挨着的,确定了一个就能确定另一个。函数实现也是挨着的。

  • __pyx_pymod_create
  • __pyx_pymod_exec_modulename
static PyModuleDef_Slot __pyx_moduledef_slots[] = {
  {Py_mod_create, (void*)},
  {Py_mod_exec, (void*)__pyx_pymod_exec_demo},
  {0, NULL}
};


// 在python的moduleobject.h里有定义
typedef struct PyModuleDef_Slot{
    int slot;
    void *value;
} PyModuleDef_Slot;

pyx和py的区别

其实就算不使用cython的语法特性,同样的代码放在py和pyx里,cython编译出来的结果并不相同。

__pyx_pymod_exec_modulename中的区别

自定义函数的区别

/*--- Execution code ---*/

其中自定义的函数部分,py生成的是

__pyx_t_1 = __Pyx_CyFunction_New(&__pyx_mdef_4util_1func, 0, __pyx_n_s_func, NULL, __pyx_n_s_util, __pyx_d, ((PyObject *)__pyx_codeobj__2)); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 1, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_1);
  if (PyDict_SetItem(__pyx_d, __pyx_n_s_func, __pyx_t_1) < 0) __PYX_ERR(0, 1, __pyx_L1_error)
  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;

pyx生成的是

  __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_4util_1func, NULL, __pyx_n_s_util); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 1, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_1);
  if (PyDict_SetItem(__pyx_d, __pyx_n_s_func, __pyx_t_1) < 0) __PYX_ERR(0, 1, __pyx_L1_error)
  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;

看下定义

#define PyCFunction_NewEx(ML, SELF, MOD) PyCMethod_New((ML), (SELF), (MOD), NULL)
PyAPI_FUNC(PyObject *) PyCMethod_New(PyMethodDef *, PyObject *,
                                     PyObject *, PyTypeObject *);
static PyObject *__Pyx_CyFunction_New(PyMethodDef *ml, int flags, PyObject* qualname,
                                      PyObject *closure, PyObject *module, PyObject* globals, PyObject* code) {
    PyObject *op = __Pyx_CyFunction_Init(
        PyObject_GC_New(__pyx_CyFunctionObject, __pyx_CyFunctionType),
        ml, flags, qualname, closure, module, globals, code
    );
    if (likely(op)) {
        PyObject_GC_Track(op);
    }
    return op;
}

其他区别

/* FetchCommonType.proto */
static PyTypeObject* __Pyx_FetchCommonType(PyTypeObject* type);

/* CythonFunctionShared.proto */
#define __Pyx_CyFunction_USED 1
#define __Pyx_CYFUNCTION_STATICMETHOD  0x01
#define __Pyx_CYFUNCTION_CLASSMETHOD   0x02
#define __Pyx_CYFUNCTION_CCLASS        0x04
#define __Pyx_CyFunction_GetClosure(f)\
    (((__pyx_CyFunctionObject *) (f))->func_closure)
#define __Pyx_CyFunction_GetClassObj(f)\
    (((__pyx_CyFunctionObject *) (f))->func_classobj)
#define __Pyx_CyFunction_Defaults(type, f)\
    ((type *)(((__pyx_CyFunctionObject *) (f))->defaults))
#define __Pyx_CyFunction_SetDefaultsGetter(f, g)\
    ((__pyx_CyFunctionObject *) (f))->defaults_getter = (g)
typedef struct {
    PyCFunctionObject func;
#if PY_VERSION_HEX < 0x030500A0
    PyObject *func_weakreflist;
#endif
    PyObject *func_dict;
    PyObject *func_name;
    PyObject *func_qualname;
    PyObject *func_doc;
    PyObject *func_globals;
    PyObject *func_code;
    PyObject *func_closure;
    PyObject *func_classobj;
    void *defaults;
    int defaults_pyobjects;
    size_t defaults_size;  // used by FusedFunction for copying defaults
    int flags;
    PyObject *defaults_tuple;
    PyObject *defaults_kwdict;
    PyObject *(*defaults_getter)(PyObject *);
    PyObject *func_annotations;
} __pyx_CyFunctionObject;
static PyTypeObject *__pyx_CyFunctionType = 0;
#define __Pyx_CyFunction_Check(obj)  (__Pyx_TypeCheck(obj, __pyx_CyFunctionType))
static PyObject *__Pyx_CyFunction_Init(__pyx_CyFunctionObject* op, PyMethodDef *ml,
                                      int flags, PyObject* qualname,
                                      PyObject *self,
                                      PyObject *module, PyObject *globals,
                                      PyObject* code);
static CYTHON_INLINE void *__Pyx_CyFunction_InitDefaults(PyObject *m,
                                                         size_t size,
                                                         int pyobjects);
static CYTHON_INLINE void __Pyx_CyFunction_SetDefaultsTuple(PyObject *m,
                                                            PyObject *tuple);
static CYTHON_INLINE void __Pyx_CyFunction_SetDefaultsKwDict(PyObject *m,
                                                             PyObject *dict);
static CYTHON_INLINE void __Pyx_CyFunction_SetAnnotationsDict(PyObject *m,
                                                              PyObject *dict);
static int __pyx_CyFunction_init(void);

/* CythonFunction.proto */
static PyObject *__Pyx_CyFunction_New(PyMethodDef *ml,
                                      int flags, PyObject* qualname,
                                      PyObject *closure,
                                      PyObject *module, PyObject *globals,
                                      PyObject* code);

__Pyx_InitGlobals —— 字面值初始化

包含大量变量初始化,这个变量呢,在python中其实是作为常量,或者说是字面值。比如python里的一个赋值a = 1,就会需要在__Pyx_InitGlobals里创建PyObject* PyInt_FromLong(1),然后python对象才能被赋值成1。

顺着这些变量的交叉引用,能找到def变成成的c函数。

__pyx_pymod_exec_模块名(PyObject *__pyx_pyinit_module)

会有个PyModule_GetDict()通过这个module获取字典,获得一个dict对象__pyx_d

  • 各种import,将import的结果保存在这个字典里。而且根据__Pyx_Import参数判断是直接import还是用了as当别名
  • 全局变量,直接在这里PyDict_SetItem()赋值了。

一些例子

// import array as arr
 __pyx_t_1 = __Pyx_Import(__pyx_n_s_array, 0, 0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 1, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_1);
  if (PyDict_SetItem(__pyx_d, __pyx_n_s_arr, __pyx_t_1) < 0) __PYX_ERR(0, 1, __pyx_L1_error)
  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;


// 全局变量赋值
  /* "util.py":7
 * DO_NOT_SET_FLAG = 0
 * SET_FLAG = 1
 * RAM_SIZE = 1024             # <<<<<<<<<<<<<<
 * RF_SIZE = 32
 * 
 */
  if (PyDict_SetItem(__pyx_d, __pyx_n_s_RAM_SIZE, __pyx_int_1024) < 0) __PYX_ERR(0, 7, __pyx_L1_error)

 
 // def 创建函数对象
  /* "util.py":11
 * 
 * 
 * def __init_memory():             # <<<<<<<<<<<<<<
 *     global mem
 *     mem = arr.array('I', [])
 */
  __pyx_t_1 = __Pyx_CyFunction_New(&__pyx_mdef_4util_1__init_memory, 0, __pyx_n_s_init_memory, NULL, __pyx_n_s_util, __pyx_d, ((PyObject *)__pyx_codeobj__15)); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 11, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_1);
  if (PyDict_SetItem(__pyx_d, __pyx_n_s_init_memory, __pyx_t_1) < 0) __PYX_ERR(0, 11, __pyx_L1_error)
  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; 

 
 // 全局会执行的代码
   /* "util.py":609
 * 
 * print('[-- Loaded --]')             # <<<<<<<<<<<<<<
 */
  __pyx_t_1 = __Pyx_PyObject_Call(__pyx_builtin_print, __pyx_tuple__95, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 609, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_1);
  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; 

从指针获取对象

memoryview()对于支持缓冲区协议的数据,可以允许Python代码访问。

python - Is it possible to dereference variable id’s? - Stack Overflow

介绍了gc.get_objects_ctypes.PyObj_FromPtr(obj_id)之类的方法。

PYC篇


常规逆向思路

先利其器:

https://github.com/rocky/python-uncompyle6

https://github.com/SycloverTeam/pycdc/(pycdc环境配置简单,可自行编译)

010Editor

pyc有时候可以直接import,import了之后导入了一些内容,可以进行侧信道攻击。

比如VNCTF2022的BabyMaze,本意应该是看dis内容,还原_map,以及wsad控制在迷宫中移动。

但我们可以直接import

可以进行个patch

可以偷窥下_map:

很大程度上能减轻逆向工作量

混淆

.pyc文件,可以加混淆,以及其他保护手段,加大逆向难度。

  • pyc去掉符号名称(换成空格),样本:

py10.pyc

Opcode的处理

主要是dis模块和marshal模块