最近使用C++实现了一个fuzz。但是发现对应目标有认证流程,我的fuzz代码上也得加上这个加密认证逻辑才能正常工作。经过谷哥的帮助,在网上找到了一段python实现的加密认证逻辑。
让我自己用C++重写?那是不可能的,于是就走上了用C++调用python的踩坑之路。
0x01 执行简单Python代码
如果我们应用的场景并不复杂,比如知识想执行一段简单的python代码。那么你只需要了解以下内容
- 在头文件中包含Python.h头文件
- 使用
Py_Initialize()
初始化python解析器 - 使用
PyRun_SimpleString
执行python代码 Py_Finalize
释放python解析器
以下就是一个实现的demo
1 | //test.cpp |
注意在链接的时候加上对应的库,编译指令如下
1 | g++ test.cpp -I/usr/include/python3.8 -l python3.8 |
0x02 执行简单python脚本中的函数
2.1 无参数与返回值
有的时候我们需要调用python脚本中的函数来实现一些功能,假设这个时候我们有这样一个脚本
1 | #cat script/sayHello.py |
然后我们需要了解一些常用api
1 | //导入函数相关 |
我们可以像下面这样去加载调用模块,并调用指定的函数
1 |
|
2.2有参数与返回值
参数构建
在Python/C API中提供了Py_BuildValue()函数对数字和字符串进行转换处理,使之变成Python中相应的数据类型。其函数原型如下所示
1 | PyObject* Py_BuildValue( const char *format, ...) |
format对应的类型列表如下
1 | s(str或None)[char *] |
返回值
python函数的返回值也是PyObject类型,因此,在python脚本返回到C/C++之后,需要解构Python数据为C的类型,这样C/C++程序中才可以使用Python里的数据。但是,由于python的返回值有多种数据结构类型,因此,我们需要为每个类型进行转换。不过由于篇幅问题,我们只是介绍简单的整形和字符串类型的处理,其他类型的返回见文末的github链接,总体思路都是根据类型逐个从值从PyObject中提取。python提供了下面函数来完成这个功能
1 | int PyArg_Parse( PyObject *args, char *format, ...) |
释放资源
Python使用引用计数机制对内存进行管理,实现自动垃圾回收。在C/C++中使用Python对象时,应正确地处理引用计数,否则容易导致内存泄漏。在Python/C API中提供了Py_CLEAR()、Py_DECREF()等宏来对引用计数进行操作。
当使用Python/C API中的函数创建列表、元组、字典等后,就在内存中生成了这些对象的引用计数。在对其完成操作后应该使用Py_CLEAR()、Py_DECREF()等宏来销毁这些对象。其原型分别如下所示
1 | void Py_CLEAR(PyObject *o) |
下面是个简单的例子,在例子中会有输出和返回值
1 | /* |
2.3 调用类中的函数
大概流程是:
第一步,导入python文件,如前文所述
第二步,导入已经导入的模块的方法或类
第三步,使用导入的方法或类
第四步,释放资源
下面结合具体例子来分析
1 | Py_Initialize(); |
0x03 遇到的问题
类似于下图这种会出现内存泄漏
1 |
|
[解决方案](c++ - Memory leak when embedding python into my application - Stack Overflow)
0x04 参考
[C++调用python脚本 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/79896193#:~:text=C%2FC%2B%2B中调用,的所有初始化, 并销毁)