当前位置:首页 > 后端开发 > 正文内容

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

邻居的猫1个月前 (12-09)后端开发1914

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,咱们只写一行代码:

print("hello world")

  然后在项目根目录(test.py同级目录)新建setup.py文件

from distutils.core import setup
from Cython.Build import cythonize
setup(ext_modules=cythonize("test.py")) #这儿是待编译文件的姓名

  然后在终端运转指令

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行的常量赋值
static const char __pyx_k_main[] = "__main__";
static const char __pyx_k_name[] = "__name__";
static const char __pyx_k_test[] = "__test__";
static const char __pyx_k_print[] = "print";
static const char __pyx_k_Hello_World[] = "Hello World";
static const char __pyx_k_cline_in_traceback[] = "cline_in_traceback";
  • 坐落1958行的函数__Pyx_CreateStringTabAndInitStrings,效果是将字符串和变量/变量名联络在一起
static int __Pyx_CreateStringTabAndInitStrings(void) {
  __Pyx_StringTabEntry __pyx_string_tab[] = {
    {&__pyx_n_s_, __pyx_k_, sizeof(__pyx_k_), 0, 0, 1, 1},
    {&__pyx_kp_s_Hello_World, __pyx_k_Hello_World, sizeof(__pyx_k_Hello_World), 0, 0, 1, 0},
    {&__pyx_n_s_cline_in_traceback, __pyx_k_cline_in_traceback, sizeof(__pyx_k_cline_in_traceback), 0, 0, 1, 1},
    {&__pyx_n_s_end, __pyx_k_end, sizeof(__pyx_k_end), 0, 0, 1, 1},
    {&__pyx_n_s_file, __pyx_k_file, sizeof(__pyx_k_file), 0, 0, 1, 1},
    {&__pyx_n_s_main, __pyx_k_main, sizeof(__pyx_k_main), 0, 0, 1, 1},
    {&__pyx_n_s_name, __pyx_k_name, sizeof(__pyx_k_name), 0, 0, 1, 1},
    {&__pyx_n_s_print, __pyx_k_print, sizeof(__pyx_k_print), 0, 0, 1, 1},
    {&__pyx_n_s_test, __pyx_k_test, sizeof(__pyx_k_test), 0, 0, 1, 1},
    {0, 0, 0, 0, 0, 0, 0}
  };
  return __Pyx_InitStrings(__pyx_string_tab);
}
  • 坐落2916行的__Pyx_Print,获取print代码目标,并以arg_tuple为参数进行调用
static int __Pyx_Print(PyObject* f, PyObject *arg_tuple, int newline) {
    int i;
    if (!f) {
        if (!(f = __Pyx_GetStdout()))
            return -1;
    }
    Py_INCREF(f);
    for (i=0; i < PyTuple_GET_SIZE(arg_tuple); i++) {
        PyObject* v;
        if (PyFile_SoftSpace(f, 1)) {
            if (PyFile_WriteString(" ", f) < 0)
                goto error;
        }
        v = PyTuple_GET_ITEM(arg_tuple, i);
        if (PyFile_WriteObject(v, f, Py_PRINT_RAW) < 0)
            goto error;
        if (PyString_Check(v)) {
            char *s = PyString_AsString(v);
            Py_ssize_t len = PyString_Size(v);
            if (len > 0) {
                switch (s[len-1]) {
                    case ' ': break;
                    case '\f': case '\r': case '\n': case '\t': case '\v':
                        PyFile_SoftSpace(f, 0);
                        break;
                    default:  break;
                }
            }
        }
    }
  • 坐落3015行的__Pyx_PrintOne,print参数只要一个的状况
static int __Pyx_PrintOne(PyObject* stream, PyObject *o) {
    // ...
    PyObject* arg_tuple = PyTuple_Pack(1, o);
    // ...
    res = __Pyx_Print(stream, arg_tuple, 1);
  
  • 坐落2345行,调用print
if (__Pyx_PrintOne(0, __pyx_kp_s_Hello_World) < 0) __PYX_ERR(0, 1, __pyx_L1_error)

  _pyx_pymod_exec_hello_world__Pyx_PrintOne展开编进了函数中(都被指定了__attribute__((cold))扩展的函数),这儿调用首要是把__pyx_kp_s_Hello_World即字符串"Hello, World!"的 PyObject 打成一个 tuple,然后用PyObject_Call调用PyObject_GetAttr拿到的print函数的 PyCodeObject,完成了对print("Hello, World!")的调用。

  这也是一般函数的调用流程,有一个 tuple 存非关键字参数(args)、一个 dict 存关键字参数(kwargs),然后调用PyObject_Call,其三个参数分别是被调用函数的 PyCodeObject、args tuple、kwargs dict,这样就完成了对 Python 函数的调用。



四、Python的内存办理机制

  假如单纯考虑print函数的调用,以上代码100行足以。那么为什么整个c文件有长达几千行的代码呢?其间大部分是对Python目标内存的办理。
  咱们以978行的函数(宏界说函数)__Pyx_PyHeapTypeObject_GC_Del为例:

#define __Pyx_PyHeapTypeObject_GC_Del(obj)  {\
    PyTypeObject *type = Py_TYPE((PyObject*)obj);\
    assert(__Pyx_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE));\
    PyObject_GC_Del(obj);\
    Py_DECREF(type);\
}

  事实上,这正是python的引证计数内存办理机制。

  首要运用 Py_TYPE 宏获取传入目标 obj 的类型(PyTypeObject)。然后运用断语(assert)查看 type 是否具有 Py_TPFLAGS_HEAPTYPE 特性。宏 __Pyx_PyType_HasFeature判别 type 是否为堆分配的类型。调用 PyObject_GC_Del,从 Python 的废物收回体系中删去该目标。对类型目标削减引证计数。一般,当一个堆分配的目标被毁掉时,其类型的引证计数也需求削减。

  引证计数的中心原理为:每个目标都有一个 引证计数器,记载指向它的引证数量。当有新的变量引证该目标时(例如赋值操作),引证计数加 1。当引证被删去或超出效果域时,引证计数减 1。当引证计数变为 0 时,阐明该目标不再被运用,体系收回其占用的内存。选用这种方法办理内存,无需杂乱的废物收回算法。可是一起存在循环引证问题。假如两个目标彼此引证,引证计数永久不会降为 0,导致内存走漏。

扫描二维码推送至手机访问。

版权声明:本文由51Blog发布,如需转载请注明出处。

本文链接:https://www.51blog.vip/?id=157

分享给朋友:

“Cython二进制逆向系列(一) 初识Cython” 的相关文章

php向下取整,phpstudy官网下载

在PHP中,向下取整可以通过使用`floor`函数来实现。这个函数会返回小于或等于给定数值的最大整数。例如:```php```在这个例子中,`floor`返回5,因为它是最接近5.6且不大于5.6的整数。 PHP 向下取整函数详解在PHP编程中,向下取整是一个常见的操作,它可以帮助我们获取一个浮点数...

疯狂GO,华硕京东超值狂欢GO,轻薄本盛宴来袭!

疯狂GO,华硕京东超值狂欢GO,轻薄本盛宴来袭!

《疯狂GO》是韩国男子组合BigBang成员GDragon(权志龙)演唱的一首歌曲。该歌曲由GDragon填词,GDragon、???、???谱曲,收录在GDragon的专辑《首个个人世界巡回演唱会ONE OF A KIND》中。这首歌曲于2013年4月1日发行,其MV于2013年3月17日开始拍摄...

c语言程序设计,基础入门与进阶指南

好的,请问您想了解C语言程序设计的哪个方面?例如语法、数据结构、算法、编程实践等。我会尽力为您提供相关的信息。C语言程序设计:基础入门与进阶指南C语言作为一种历史悠久且广泛使用的编程语言,在计算机科学领域占据着举足轻重的地位。本文将为您介绍C语言程序设计的基础知识,帮助您从入门到进阶,掌握这一强大的...

c语言gets函数,使用、风险与替代方案

`gets` 函数是 C 语言标准库中的一个函数,用于从标准输入读取一行文本,直到遇到换行符或文件结束符。它的原型如下:```cchar gets;```其中,`str` 是一个字符数组,用于存储读取的字符串。`gets` 函数会读取直到换行符或文件结束符,然后将换行符替换为字符串终止符 `0`,并...

java单元测试, 单元测试框架的选择

java单元测试, 单元测试框架的选择

Java单元测试:全面解析与实战指南在软件开发过程中,单元测试是确保代码质量的重要手段。Java作为一种广泛使用的编程语言,其单元测试框架也相当丰富。本文将全面解析Java单元测试,包括测试框架的选择、测试用例的编写、测试结果的解析以及单元测试的最佳实践。 单元测试框架的选择在Java中,常用的单...

java项目实例,基于Spring Boot的在线书店系统开发实战

java项目实例,基于Spring Boot的在线书店系统开发实战

1. 微信小程序开发(前端 后端) 项目介绍:该项目利用Java作为后端语言,配合前端技术,开发微信小程序。适合对微信小程序开发感兴趣的开发者。 源码地址:2. SpringBoot Vue.js搭建图书管理系统 项目介绍:该项目使用SpringBoot作为后端框架,Vue.js作...