Learn learn Cython Reverse

非系统学习pyd逆向

pyd生成

  • pyd文件生成:

    • 编写pyx文件

        #test.pyx
        def say_hello_world(name):
        print("Hello world" % name)
      
    • 编写setup

        #setup.py
        from distutils.core import setup
        from Cython.Build import cythonize
        setup(name='Hello world app', ext_modules=cythonize("test.pyx"))
      
    • 生成pyd文件终端执行(也可以直接在pycharm中含参运行)

      python3 .\setup.py build_ext --inplace

  • 终端执行报错

      error: Microsoft Visual C++ 14.0 or greater is required. Get it with "Microsoft C++ Build Tools": https://visualstudio.microsoft.com/visual-cpp-build-tools/
    
    • 分析目的:目的就是安装一个Visual Studio Build Tools,他可以在不需要完整Visual Studio IDE的情况下获得编译和构建环境.
      这时候就有一个问题,我又不是没有python或者c的编译环境,为什么会这样?
    • 原因: 笔者安装的是Mingw环境,而报错安装所需要的环境是MSVC.而安装MSVC的最好方法就是使用Visual Studio Build Tools.,并且在Windows上,Python 官方发行版是使用 MSVC 编译的,所以setup.py编译拓展模块(c)一般需要MSVC.
  • 最后生成文件test.cp311-win_amd64.pyd

  • 上面的方法好像会丢失符号表,直接用py文件生成pyd文件,并且生成pdb表,运行方法同上

      from setuptools import setup, Extension
      from Cython.Build import cythonize
    
      ext_module = [
          Extension(
              name="www",
              sources=["www.py"],
              extra_compile_args=["/Zi"],
              extra_link_args=["/DEBUG"]
          )
      ]
    
      setup(
          name = "www",
          ext_modules = cythonize(ext_module,annotate=True)
      )
    

    其中/Zi是MSVC(Microsoft Visual C++ 编译器)的编译选项,用于生成调试符号(程序数据库文件,PDB).

  • 注意在使用自己生成的pyd修复原程序符号表的时候需要版本对应.

  • python3 .\setup.py build_ext --inplace

  • pyd实例:

简单分析pyd

  • 将.pyd文件放在当前工作目录便可以import,或者将.pyd文件放在lib/site-packages.如果无法运行,可能是缺少库导致的,或者版本不匹配(检测库的时候会显示python版本,更改版本即可)

      import pefile
    
      pe = pefile.PE("your_file.pyd")
      for entry in pe.DIRECTORY_ENTRY_IMPORT:
      print(entry.dll.decode("utf-8"))
    
  • 代码(IDE),而终端不需要加print(),因为解释器,按步骤解释会将内容输出

      import cy
    
      help(cy)
      a = cy.QOOQOOQOOQOOOQ() //cy.QOOQOOQOOQOOOQ()表示生成一个instance,而cy.QOOQOOQOOQOOOQ则是一个将a当作引用
      print(dir(a))
      print(a.get_key())
    
  • 当dir(cy.QOOQOOQOOQOOOQ)时会有输出,函数名都存在,但是好像缺少了变量
    img

通过注入class,获取运算过程

import cy

class Symbol:
    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return self.name

    def __rshift__(self, other):
        if isinstance(other, Symbol):
            expression = Symbol(f"({self.name} >> {other.name})")
        else:
            expression = Symbol(f"({self.name} >> {other})")
        return expression

    def __lshift__(self, other):
        if isinstance(other, Symbol):
            expression = Symbol(f"({self.name} << {other.name})")
        else:
            expression = Symbol(f"({self.name} << {other})")
        return expression

    def __rxor__(self, other):
        if isinstance(other, Symbol):
            expression = Symbol(f"({self.name} ^ {other.name})")
        else:
            expression = Symbol(f"({self.name} ^ {other})")
        return expression

    def __xor__(self, other):
        if isinstance(other, Symbol):
            expression = Symbol(f"({self.name} ^ {other.name})")
        else:
            expression = Symbol(f"({self.name} ^ {other})")
        return expression

    def __add__(self, other):
        if isinstance(other, Symbol):
            expression = Symbol(f"({self.name} + {other.name})")
        else:
            expression = Symbol(f"({self.name} + {other})")
        return expression

    def __and__(self, other):
        if isinstance(other, Symbol):
            expression = Symbol(f"({self.name} & {other.name})")
        else:
            expression = Symbol(f"({self.name} & {other})")
        return expression

class AList:
    def __init__(self, nums):
        self.nums = [Symbol(str(num)) for num in nums]

    def __getitem__(self, key):
        return self.nums[key]

    def copy(self):
        return AList(self.nums)

    def __len__(self):
        return len(self.nums)

    def __setitem__(self, key, value):
        print(f"new_{self.nums[key]} = {value}")
        self.nums[key] = Symbol(f"new_{self.nums[key].name}")

    def __eq__(self, other):
        print(f"{self.nums} == {other}")
        return self.nums == other

inp = AList([f"a[{i}]" for i in range(32)])
res = cy.sub14514(inp)

if __name__ == '__main__':
    print(res)
  • 分析(........)

补一下python的内容

  • python的函数,类方法,静态方法,实例方法

    • 函数在类中,类外都可以实现只要加def关键字就行
    • 类方法,静态方法,实例方法都需要在类中

    类方法: @classmethod

    静态方法: @staticmethod

    实例方法: def fouth_func(self):

    • 类方法、实例方法其实都是method的对象,而函数和静态方法则都是funcation的对象,可以用type()检测一下
  • 为了能较全面的还原pyd的符号表,我想用dir检测method的方法,把所有的对应起来,发现一个很神奇的东西,一个函数有return和没有return会少用很多method,也就是说return包含了很多很多method,此题(ciscn2024 rand0m)中所有的method,用一个return就能实现.

修复符号表,导入结构体

  • __pyx_mstate_global 是一个__pyx_mstate类型的全局变量指针

  • 找到很多字符串的地方(_Pyx_CreateStringTabAndInitStrings()),其中off代表的是__pyx_mstate_global,而指向它的则是__pyx_StringTabEntry的p变量,以此将很多变量y成__pyx_StringTabEntry结构体(可以对比第二张图进行set type)
    img
    下面是一个标准的形式
    img

  • 对于off需要修改为__pyx_mstate *类型

  • 在转换的时候会收到这样的提示,convert to pointer表示转化为指针,而我们应选择set the type覆盖下面的内存组合成这个结构体.

    img

  • ida中编辑结构体local type界面(shift f1),可以在这个界面查看结构体,刚刚导入的头文件中的结构体可以在这里找到

  • diaphora后也会导入结构体,__pyx_mstate *这个结构体我们就可以直接用,哪里不合适改哪里就好,这样就不用自己导入头文件了,右键edit type或者ctrl+E进行编辑(选中C syntax)

    img

  • 因为diaphora导入的莫名的结构体也可能会影响别的函数,可以注意一下,有这种split的,就是导入的结构体,可以进入,将这个结构体删了,再看看

    img

  • f3功能

    img

  • 初始化整数_Pyx_InitConstants()所有常数都在这里面初始化,初始化后的变量会以结构体的形式取值,例如

    img
    这里把pyx_mstate_global[1].__pyx_n_s_i赋值2654435769,在使用的时候就这么使用

    img

  • 如何找python函数对应的c代码函数: 有这个字符串的就是pyx_name.method_name,一般会有两个函数,一个是加载这个方法,一个是方法的实现

  • 看运算的时候只看用到运算符号的,或者一个未知的函数(例如自己实现的左移右移而不是直接调用api)

  • 返回值是什么!

    img

  • 动态调试,如果创建了虚拟环境,需要选择c盘中的python程序(不知道为什么选虚拟环境中的python为什么不行.....)

结构体

__pyx_StringTabEntry结构体如下,至于__pyx_mstate可以直接用diaphora导入的,或者可以用下面系统学习pyd逆向中的方法

#ifndef SINGEL_FILE_CPYTHON_H
#define SINGEL_FILE_CPYTHON_H
struct __pyx_StringTabEntry{
    __int64 p,s,n,encoding;
    __int8 is_unicode,is_str,intern;
    __int8 b1,b2,b3,b4,b5;
};

如何找函数对应c代码的函数(如图)

含有这个字符串的函数

img

系统学习pyd逆向

参考文章(太详细了,我哭死): https://blog.csdn.net/qq_36791177/article/details/144567790

逆向思路

  • 注:我的博客具有一定的笔记性质,所以就直接搬了
  • 分析pyd进行信息搜集,确认python版本,函数名称等
  • linux编译一份相同python版本的so文件,ida载入,File->Produce File->Create C Header File导出结构体
  • 加载需要逆向的pyd,File->Load File->Parse C Header File,导入so文件导出xxx.h(有错误就修复),导入so文件导出的xxx.h是因为错误比较少,windows带调试符号导出的header错误较多比较难修复
  • windows编译一份带调试信息的pyd,ida导出idb,bindiff载入
  • 定位__Pyx_CreateStringTabAndInitStrings(),还原__pyx_mstate结构体(python变量名)
  • 定位_Pyx_InitConstants(),还原__pyx_mstate结构中的整数成员
  • 定位到核心函数,动态调试
  • 根据调用Cython api的库函数,重新定义变量类型为导入的结构体,来高效还原python代码

PyObject

参考文章: https://www.cnblogs.com/zhangxian/articles/4587770.html

python的所有对象都会被Python解释器表示为PyObject,PyObject结构包含Python对象的所有成员指针,并且对Python对象的类型信息和引用计数进行维护。所以要用c或者c++对python对象进行处理,就意味着要维护好一个PyObject结构.

引用计数

Py_INCREF(PyObject* obj) //新增引用计数
Py_DECREF(PyObject* obj) //减少引用计数

数据类型类型

文章写的超级详细,都有点不忍心copy了(侵权必删),早点看到能拿超级多的分数.

基础类型
//整数类型
PyObject* pInt = Py_BuildValue("i", 2003);
assert(PyInt_Check(pInt));
int i = PyInt_AsLong(pInt);
Py_DECREF(pInt);

// 浮点类型
PyObject* pFloat = Py_BuildValue("f", 3.14f);
assert(PyFloat_Check(pFloat));
float f = PyFloat_AsDouble(pFloat);
Py_DECREF(pFloat);

// 字符串类型
PyObject* pString = Py_BuildValue("s", "Python");
assert(PyString_Check(pString);
int nLen = PyString_Size(pString);
char* s = PyString_AsString(pString);
Py_DECREF(pString);
元组
//创建元组对象
PyObject* pTuple = PyTuple_New(3);
assert(PyTuple_Check(pTuple));
assert(PyTuple_Size(pTuple) == 3);
// 初始化元组
PyTuple_SetItem(pTuple, 0, Py_BuildValue("i", 2003));
PyTuple_SetItem(pTuple, 1, Py_BuildValue("f", 3.14f));
PyTuple_SetItem(pTuple, 2, Py_BuildValue("s", "Python"));


// 解析元组
int i;
float f;
char *s;
if (!PyArg_ParseTuple(pTuple, "ifs", &i, &f, &s))
    PyErr_SetString(PyExc_TypeError, "invalid parameter");
// cleanup
Py_DECREF(pTuple);
列表
// 创建列表
PyObject* pList = PyList_New(3); // new reference
assert(PyList_Check(pList));
// pList[i] = i
for(int i = 0; i < 3; ++i)
    PyList_SetItem(pList, i, Py_BuildValue("i", i));
// 插入元素
PyList_Insert(pList, 2, Py_BuildValue("s", "inserted"));
// 追加元素
PyList_Append(pList, Py_BuildValue("s", "appended"));
// 排序数组
PyList_Sort(pList);
// 反转数组
PyList_Reverse(pList);
// 数组切片
PyObject* pSlice = PyList_GetSlice(pList, 2, 4); // new reference
for(int j = 0; j < PyList_Size(pSlice); ++j) {
PyObject *pValue = PyList_GetItem(pList, j);
assert(pValue);
}

Py_DECREF(pSlice);
Py_DECREF(pList);
字典
// 创建字典
PyObject* pDict = PyDict_New();
assert(PyDict_Check(pDict));
// pDict["first"] = 2003
PyDict_SetItemString(pDict, "first", Py_BuildValue("i", 2003));
// pDict["second"] = 3.14
PyDict_SetItemString(pDict, "second", Py_BuildValue("f", 3.14f));

// pDicts.Keys();
PyObject* pKeys = PyDict_Keys();
for(int i = 0; i < PyList_Size(pKeys); ++i) {
PyObject *pKey = PyList_GetItem(pKeys, i);
PyObject *pValue = PyDict_GetItem(pDict, pKey);
assert(pValue);
}
Py_DECREF(pKeys);
// 删除pDict["second"]
PyDict_DelItemString(pDict, "second");
Py_DECREF(pDict);

常见宏/函数

引用相关
#define __Pyx_GOTREF(obj)
// obj 引用计数+1,表示占用该对象

#define __Pyx_DECREF(obj)
// obj 引用计数-1

#define __Pyx_DECREF_SET(r, v)
// r引用计数-1,r = v

void _Py_Dealloc(PyObject *op)
//当Python对象op引用计数为0时,释放此对象内存
属性
#define __Pyx_GetModuleGlobalName(var, name)  (var) = __Pyx__GetModuleGlobalName(name)
// 从全局命名空间中获取模块对象,name是模块名称
// var = module对象

PyObject *PyObject_GetAttr(PyObject *o, PyObject *attr_name);
//从对象o检索名为attr_name的属性。成功返回属性值,失败返回Null。
//这相当于 Python 表达式o.attr_name.

PyObject * PyObject_GetAttrString( PyObject  *o , const char  *attr_name ) 
//从对象o中读取一个名为attr_name的属性。成功返回属性值,失败则返回NULL。这相当于Python表达式o.attr_name。

PyObject * PyObject_GenericGetAttr( PyObject  *o , PyObject  *name ) 
//通用的属性获取函数,在对象的__dict__中查找某个属性。
//正如实现描述器所述,数据优先于实例属性,失败触发触发AttributeError。

int PyObject_SetAttr( PyObject  *o , PyObject  *attr_name , PyObject  *v ) 
//这相当于Python语句。o.attr_name = v
//将对象o中名为attr_name的属性值设为v。失败时引发异常并返回-1;成功时返回返回0。
//如果v为NULL,属性将被删除,但此功能已被废弃,应改用PySequence_DelItem()。

int PyObject_SetAttrString( PyObject  *o , const char  *attr_name , PyObject  *v ) 
//这相当于Python语句。o.attr_name = v
//将对象o中名为attr_name的属性值设为v。失败时引发异常并返回-1;成功时返回返回0。
//如果v为NULL,该属性将被删除,但此功能已被废弃,应改用PySequence_DelItem()。

int PyObject_GenericSetAttr( PyObject  *o , PyObject  *name , PyObject  *value ) 
//通用的属性设置和删除函数,用于插入类型对象的tp_setattro槽。它在类的字典中(位于对象的MRO中)查找数据描述器,如果找到,则将比在实例字典中设置或删除属性优先执行。否则,该属性将在对象的__dict__中设置或删除。如果成功将返回0,否则将引发AttributeError并返回-1。

int PyObject_DelAttr( PyObject  *o , PyObject  *attr_name ) 
//删除对象o中名为attr_name的属性。失败时返回-1。这相当于Python语句。del o.attr_name

int PyObject_DelAttrString( PyObject  *o , const char  *attr_name ) 
//删除对象o中名为attr_name的属性。失败时返回-1。这相当于Python语句。del o.attr_name
函数
PyTypeObject PyMethod_Type;
//这个 PyTypeObject 实例代表 Python 方法类型。 它作为 types.MethodType 向 Python 程序公开。

int PyMethod_Check(PyObject *o)
//判断o是否是PyMethod_Type

PyObject *PyMethod_New(PyObject *func, PyObject *self)
//返回一个新的方法对象,func 应为任意可调用对象,self 为该方法应绑定的实例。 在方法被调用时 func 将作为函数被调用。 self 必须不为 NULL。

PyObject *PyMethod_Function(PyObject *meth)
//返回关联到方法 meth 的函数对象,相当于获取this指针

PyObject *PyMethod_GET_FUNCTION(PyObject *meth)
//宏版本的 PyMethod_Function(),略去了错误检测。

PyObject *PyMethod_Self(PyObject *meth)
//返回关联到方法 meth 的实例。

PyObject *PyMethod_GET_SELF(PyObject *meth)
//宏版本的 PyMethod_Self(),略去了错误检测。

PyObject * _PyObject_FastCallDict( PyObject  *callable , PyObject *const  *args , size_t  nargsf , PyObject  *kwdict ) 
//callable => 可调用的函数对象相当于this,可以通过PyMethod_GET_FUNCTION()获取
数字/运算
int PyNumber_Check ( PyObject * o ) 
//判断对象o是否为整数类型

PyObject * PyNumber_Add ( PyObject * o1 , PyObject * o2 )
PyObject * PyNumber_Subtract ( PyObject * o1 , PyObject * o2 )
PyObject * PyNumber_Multiply ( PyObject * o1 , PyObject * o2 )
PyObject * PyNumber_FloorDivide ( PyObject * o1 , PyObject * o2 )
//加减乘除

PyObject * PyNumber_InPlaceAdd ( PyObject * o1 , PyObject * o2 )
//o1 += o2
PyObject * PyNumber_InPlaceSubtract ( PyObject * o1 , PyObject * o2 )
//o1 -= o2
PyObject * PyNumber_InPlaceMultiply ( PyObject * o1 , PyObject * o2 )
//o1 *= o2
PyObject * PyNumber_InPlaceFloorDivide ( PyObject * o1 , PyObject * o2 )
//o1 /= o2


PyObject * PyNumber_Lshift ( PyObject * o1 , PyObject * o2 )
PyObject * PyNumber_Rshift ( PyObject * o1 , PyObject * o2 )
//左移右移

PyObject * PyNumber_And ( PyObject * o1 , PyObject * o2 )
PyObject * PyNumber_Xor ( PyObject * o1 , PyObject * o2 )
PyObject * PyNumber_Or ( PyObject * o1 , PyObject * o2 )
//逻辑运算

PyObject * PyNumber_Long ( PyObject * o )
//int(o)
PyObject * PyNumber_Float ( PyObject * o )
//float(o)
PyObject * PyNumber_Index ( PyObject * o )
//o转换为int,并且失败抛出异常
PyObject *PyNumber_ToBase(PyObject *n, int base)
//将字符串转换成2,8,16进制的字符串
//如果n不是整数,则会先调用PyNumber_Index(n)

type Py_ssize_t;	//有符号整数类型
Py_ssize_t PyNumber_AsSsize_t(PyObject *o, PyObject *exc)
//将o转换成有符号整数类型,如果失败则抛出异常exc
比较
PyObject *PyObject_RichCompare(PyObject *o1, PyObject *o2, int oper)
//比较对象函数相当于:o1 oper o2
//oper:0-5 
//		Py_LT, Py_LE, Py_EQ, Py_NE, Py_GT, Py_GE
//对应:<, <=, ==, !=, >, >=
//成功返回Py_True,失败返回Py_False

int PyObject_RichCompareBool(PyObject *o1, PyObject *o2, int opid)
//和上面一样,错误返回-1,成功1,失败0

cython逆向

可以参考上面的非系统学习pyd逆向,还是很全的.

cython api

网址: https://docs.python.org/3/c-api/object.html#c.PyObject_GetAttr

posted @   Un1corn  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示