Cython二进制逆向系列(一) 初识Cython

打印 上一主题 下一主题

主题 881|帖子 881|积分 2643

Cython二进制逆向系列(一) 初识Cython

  众所周知,Python类题目最难的一种就是使用Cython工具将py源码转换为二进制文件。此类题目相比于直接由Cpython编译而成的类字节码文件更复杂,且现在不存在能够将Cython编译后的二进制文件重新反编译成py源码的工具。Cython作为Python中通用的一个模块,其设计的本意是为了提高Python代码的运行效率。因此,在Cython转换py源代码时,会对源码进行一系列的调整,从而干扰整个文件的逆向。当然,也正是因为他是通用工具,其整体框架和对类似Python在字节码处理上也有肯定的规律。本系列将一步步拆解Cython生成的二进制文件/编译中间文件c语言文件,从而手撕Cython逆向。


一、什么是Cython?他与CPython有什么区别?

  我们知道Python作为依托于虚拟机的解释型动态语言,代码在运行时逐行解释。这种动态特性增加了运行开销。与编译型语言相比,编译后的代码已优化为机器码,执行效率更高。别的,Python 使用引用计数和垃圾回收机制管理内存。垃圾回收会在不定时触发的清理过程中消耗 CPU 时间,尤其是在大量对象创建和销毁时。高级数据结构(如列表、字典等)的实现灵活性较高,但其底层内存分配和操作效率不及低级语言中的数组和哈希表。
  Python的虚拟机由其他编译型语言编写,其中由C语言编写的解释器称为CPython。CPython 是 Python 的官方参考实现。CPython由于扩展性强、稳固、简朴的一系列优点,以及他强大的模块社区,使得CPython成为的Python现在应用最为广泛的解释器。

python虚拟机包括python编译器和python解释器
  传统的py代码执行需要经历以下步骤:首先交由编译器将py源代码编译成类字节码文件,然后解释器再按照类字节码文件中存储的数据逐行执行。这样的步骤,导致每次py源代码都要经历编译这一步骤。因此,为了提升py源码的运行效率,进而使得Python也能够处理高并发环境下大概高性能要求的问题,Cython由此诞生。类似于编译型语言,Cython会将py源码转换为c语言代码,然后通过c语言编译器将代码编译成二进制文件,这样py源码每次在执行时,就不需要Python编译器编译出类字节码文件,而是直接使用已经由c语言编译好的二进制文件调用Py解释器的相干接口,这大大提高了Python的执行效率。
  CPython和Cython的雷同点是都能够处理py源代码,但他们是两个截然不同的东西。Cython 是一种工具,主要用于编译和优化 Python 代码,使其更接近 C 的运行效率,并答应调用 C/C++ 函数。


二、使用Cython编译二进制文件

  现在假设项目的根目录下有待编译的py源代码文件test.py,我们只写一行代码:
  1. print("hello world")
复制代码
  然后在项目根目录(test.py同级目录)新建setup.py文件
  1. from distutils.core import setup
  2. from Cython.Build import cythonize
  3. setup(ext_modules=cythonize("test.py")) #这里是待编译文件的名字
复制代码
  然后在终端运行命令
  1. python setup.py build_ext --inplace
复制代码
  就会在同级目录下生成.c的C语言代码文件和.pyd的二进制模块库,以及build文件夹(存储了编译过程中的中间文件)。想要使用此模块,于其他模块雷同,只需import该模块的模块名即可。
  可能会遇到的问题:1.setup文件报错找不到合适的distutils/setup版本。办理方法:切换python版本。笔者用的是3.8.10
           2.终端编译时报错找不到vs build。着实是找不到c语言编译器。办理办法:下载visual studio。
  到此,我们通过正向的方式得到了Cython产生的二进制文件。本节浅分析一下产生的.c文件代码。


三、初识代码的调用逻辑

  打开.c文件,可以看到,一句简朴的print,转换后的c语言代码有4165行之多!足以证明其框架代码和处理代码之多。

  然而事实上,真正执行了print("hello world")的代码是以下部分

  • 位于1781行的常量赋值
  1. static const char __pyx_k_main[] = "__main__";
  2. static const char __pyx_k_name[] = "__name__";
  3. static const char __pyx_k_test[] = "__test__";
  4. static const char __pyx_k_print[] = "print";
  5. static const char __pyx_k_Hello_World[] = "Hello World";
  6. static const char __pyx_k_cline_in_traceback[] = "cline_in_traceback";
复制代码

  • 位于1958行的函数__Pyx_CreateStringTabAndInitStrings,作用是将字符串和变量/变量名联系在一起
  1. static int __Pyx_CreateStringTabAndInitStrings(void) {
  2.   __Pyx_StringTabEntry __pyx_string_tab[] = {
  3.     {&__pyx_n_s_, __pyx_k_, sizeof(__pyx_k_), 0, 0, 1, 1},
  4.     {&__pyx_kp_s_Hello_World, __pyx_k_Hello_World, sizeof(__pyx_k_Hello_World), 0, 0, 1, 0},
  5.     {&__pyx_n_s_cline_in_traceback, __pyx_k_cline_in_traceback, sizeof(__pyx_k_cline_in_traceback), 0, 0, 1, 1},
  6.     {&__pyx_n_s_end, __pyx_k_end, sizeof(__pyx_k_end), 0, 0, 1, 1},
  7.     {&__pyx_n_s_file, __pyx_k_file, sizeof(__pyx_k_file), 0, 0, 1, 1},
  8.     {&__pyx_n_s_main, __pyx_k_main, sizeof(__pyx_k_main), 0, 0, 1, 1},
  9.     {&__pyx_n_s_name, __pyx_k_name, sizeof(__pyx_k_name), 0, 0, 1, 1},
  10.     {&__pyx_n_s_print, __pyx_k_print, sizeof(__pyx_k_print), 0, 0, 1, 1},
  11.     {&__pyx_n_s_test, __pyx_k_test, sizeof(__pyx_k_test), 0, 0, 1, 1},
  12.     {0, 0, 0, 0, 0, 0, 0}
  13.   };
  14.   return __Pyx_InitStrings(__pyx_string_tab);
  15. }
复制代码

  • 位于2916行的__Pyx_Print,获取print代码对象,并以arg_tuple为参数进行调用
  1. static int __Pyx_Print(PyObject* f, PyObject *arg_tuple, int newline) {
  2.     int i;
  3.     if (!f) {
  4.         if (!(f = __Pyx_GetStdout()))
  5.             return -1;
  6.     }
  7.     Py_INCREF(f);
  8.     for (i=0; i < PyTuple_GET_SIZE(arg_tuple); i++) {
  9.         PyObject* v;
  10.         if (PyFile_SoftSpace(f, 1)) {
  11.             if (PyFile_WriteString(" ", f) < 0)
  12.                 goto error;
  13.         }
  14.         v = PyTuple_GET_ITEM(arg_tuple, i);
  15.         if (PyFile_WriteObject(v, f, Py_PRINT_RAW) < 0)
  16.             goto error;
  17.         if (PyString_Check(v)) {
  18.             char *s = PyString_AsString(v);
  19.             Py_ssize_t len = PyString_Size(v);
  20.             if (len > 0) {
  21.                 switch (s[len-1]) {
  22.                     case ' ': break;
  23.                     case '\f': case '\r': case '\n': case '\t': case '\v':
  24.                         PyFile_SoftSpace(f, 0);
  25.                         break;
  26.                     default:  break;
  27.                 }
  28.             }
  29.         }
  30.     }
复制代码

  • 位于3015行的__Pyx_PrintOne,print参数只有一个的情况
  1. static int __Pyx_PrintOne(PyObject* stream, PyObject *o) {
  2.     // ...
  3.     PyObject* arg_tuple = PyTuple_Pack(1, o);
  4.     // ...
  5.     res = __Pyx_Print(stream, arg_tuple, 1);
  6.   
复制代码

  • 位于2345行,调用print
[code]if (__Pyx_PrintOne(0, __pyx_kp_s_Hello_World) 

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

盛世宏图

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表