Cython二进制逆向系列(二)变量与数据结构
Cython二进制逆向系列(二)变量与数据结构
在这篇文章里,我们会讨论Cython是如何存储变量(整数、小数、字符串、布尔值)以及数据结构(列表、元组、集合、字典)。Cython 对变量存储的方式与 Python 相似,但在 Cython 中,可以使用 C 类型的变量来显著提高性能。此外,由于Cython仍然依托于Python的虚拟机运行,因此Cython编译后的文件在底层仍然离不开对虚拟机接口的调用。在逆向时,我们可以通过调用的接口函数来判断变量的类型。
Cython编译规则中会先把硬编码中的数字先解析了一遍,我们写的普通整数识别为long
类型,小数则是识别成了double
类型(无论长短),太长的整数识别为PyObject
类型,字符串也是PyObject
类型,布尔值是int
类型,列表、元组、字典都是PyObject
类型。
变量类型
整数
def integer():
var1=1
var2=437593479587349875983475987349587324895
var3=-1
integer()
编译后在c文件的2269行我们可以看到函数的实现:
static PyObject *__pyx_pf_4test_integer(CYTHON_UNUSED PyObject *__pyx_self) {
CYTHON_UNUSED long __pyx_v_var1;
CYTHON_UNUSED PyObject *__pyx_v_var2 = NULL;
CYTHON_UNUSED long __pyx_v_var3;
PyObject *__pyx_r = NULL;
__Pyx_RefNannyDeclarations
__Pyx_RefNannySetupContext("integer", 1);
/* "test.py":2
* def integer():
* var1=1 # <<<<<<<<<<<<<<
* var2=437593479587349875983475987349587324895
* var3=-1
*/
__pyx_v_var1 = 1;
/* "test.py":3
* def integer():
* var1=1
* var2=437593479587349875983475987349587324895 # <<<<<<<<<<<<<<
* var3=-1
* integer()
*/
__Pyx_INCREF(__pyx_int_0x149357046d142e1e6e1948884dc976fdf);
__pyx_v_var2 = __pyx_int_0x149357046d142e1e6e1948884dc976fdf;
/* "test.py":4
* var1=1
* var2=437593479587349875983475987349587324895
* var3=-1 # <<<<<<<<<<<<<<
* integer()
*/
__pyx_v_var3 = -1L;
/* "test.py":1
* def integer(): # <<<<<<<<<<<<<<
* var1=1
* var2=437593479587349875983475987349587324895
*/
/* function exit code */
__pyx_r = Py_None; __Pyx_INCREF(Py_None);
__Pyx_XDECREF(__pyx_v_var2);
__Pyx_XGIVEREF(__pyx_r);
__Pyx_RefNannyFinishContext();
return __pyx_r;
}
var1
被赋值为 1
,这是一个普通的 C 整数。它通过 long
类型存储。
var2
是一个非常大的整数,它的值是 437593479587349875983475987349587324895
。这个整数被赋值为 __pyx_int_0x149357046d142e1e6e1948884dc976fdf
,它表示 Cython 为这个大整数生成的 Python 对象。
var3
被赋值为 -1
,是一个 long
类型的整数。由此可知,无论正负,只要不超过long的范围,都会被当作long来存储。
而在1379行我们可以看到
__pyx_int_0x149357046d142e1e6e1948884dc976fdf = PyInt_FromString((char *)"0x149357046d142e1e6e1948884dc976fdf", 0, 0); if (unlikely(!__pyx_int_0x149357046d142e1e6e1948884dc976fdf)) __PYX_ERR(0, 1, __pyx_L1_error)
长整数在编译之后储存的是的字符串,在使用的时候再将字符串转为数字。
小数
写一个覆盖小数、字符串、布尔的函数
def decimalStringBool():
var1=1.012
var2="????"
var3=True
同样的我们找到函数的实现:
/* "test.py":2
* def decimalStringBool():
* var1=1.012 # <<<<<<<<<<<<<<
* var2="????"
* var3=True
*/
__pyx_v_var1 = 1.012;
由此看来,不管小数多长,都会被当作double来存储。然后在ida中,我们看一下反编译的效果:
好像并没有看到参数?
这是因为xmm0是浮点数寄存器,一般浮点数都会放入xmm系列寄存器中计算,而在正常的程序参数列表中没有xmm寄存器,所以ida没有识别出来,我们查看qword_180006CC8指向地址就可以看到我们输入的浮点数
字符串
全局字符串(以及函数名、一些具有特殊意义的字符串)赋值一般在_Pyx_CreateStringTabAndInitStrings
中,该函数中使用的字符串定义数组形如:
static const char __pyx_k__4[] = "?";
然后在函数的实现中,对局部变量var2进行赋值。
__Pyx_INCREF(__pyx_kp_s_);
__pyx_v_var2 = __pyx_kp_s_;
部分字符串变量也有通过__Pyx_StringTabEntry
的数组进行初始化的。
不管是反编译还是c的代码,其字符串都是通过偏移量的形式指向该字符串。在后续调用中,调用到“????”
字符串的地方都会用偏移量指代,这也是为什么对字符串交叉引用找不到真正调用地方的原因。
布尔值
布尔值就是直接赋值了。在c中是直接赋值的0或者1,在反编译的文件中是调用了接口。
数据结构
首先写一个能够覆盖所有数据结构的函数。
def dataStruct():
var = 123456789
var1 = [9999, 1.012, -437593479587349875983475987349587324895, 340759348759834759853842759, "hello", False, var]
var2 = []
var3 = (1, 2, 3)
var4 = {1, 2, 3, 4}
var5 = {"key1": "value1", "key2": "value2"}
print(var1, var2, var3, var4, var5)
dataStruct()
列表
列表中的内容全部在其他函数 __Pyx_InitConstants(void)
中进行了初始化,然后放入了off_180009668
数据块中统一管理
初始化元素:
static CYTHON_SMALL_CODE int __Pyx_InitConstants(void) {
if (__Pyx_CreateStringTabAndInitStrings() < 0) __PYX_ERR(0, 1, __pyx_L1_error);
__pyx_float_1_012 = PyFloat_FromDouble(1.012); if (unlikely(!__pyx_float_1_012)) __PYX_ERR(0, 1, __pyx_L1_error)
__pyx_int_1 = PyInt_FromLong(1); if (unlikely(!__pyx_int_1)) __PYX_ERR(0, 1, __pyx_L1_error)
__pyx_int_2 = PyInt_FromLong(2); if (unlikely(!__pyx_int_2)) __PYX_ERR(0, 1, __pyx_L1_error)
__pyx_int_3 = PyInt_FromLong(3); if (unlikely(!__pyx_int_3)) __PYX_ERR(0, 1, __pyx_L1_error)
__pyx_int_4 = PyInt_FromLong(4); if (unlikely(!__pyx_int_4)) __PYX_ERR(0, 1, __pyx_L1_error)
__pyx_int_9999 = PyInt_FromLong(9999); if (unlikely(!__pyx_int_9999)) __PYX_ERR(0, 1, __pyx_L1_error)
__pyx_int_0x119de994f9e3728a1652147 = PyInt_FromString((char *)"0x119de994f9e3728a1652147", 0, 0); if (unlikely(!__pyx_int_0x119de994f9e3728a1652147)) __PYX_ERR(0, 1, __pyx_L1_error)
__pyx_int_large_neg_43759347958734_xxx_475987349587324895 = PyInt_FromString((char *)"-437593479587349875983475987349587324895", 0, 0); if (unlikely(!__pyx_int_large_neg_43759347958734_xxx_475987349587324895)) __PYX_ERR(0, 1, __pyx_L1_error)
return 0;
__pyx_L1_error:;
return -1;
}
列表赋值:
/* "test.py":3
* def dataStruct():
* var = 123456789
* var1 = [9999, 1.012, -437593479587349875983475987349587324895, 340759348759834759853842759, "hello", False, var] # <<<<<<<<<<<<<<
* var2 = []
* var3 = (1, 2, 3)
*/
__pyx_t_1 = __Pyx_PyInt_From_long(__pyx_v_var); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 3, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_1);
__pyx_t_2 = PyList_New(7); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 3, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_2);
__Pyx_INCREF(__pyx_int_9999);
__Pyx_GIVEREF(__pyx_int_9999);
if (__Pyx_PyList_SET_ITEM(__pyx_t_2, 0, __pyx_int_9999)) __PYX_ERR(0, 3, __pyx_L1_error);
__Pyx_INCREF(__pyx_float_1_012);
__Pyx_GIVEREF(__pyx_float_1_012);
if (__Pyx_PyList_SET_ITEM(__pyx_t_2, 1, __pyx_float_1_012)) __PYX_ERR(0, 3, __pyx_L1_error);
__Pyx_INCREF(__pyx_int_large_neg_43759347958734_xxx_475987349587324895);
__Pyx_GIVEREF(__pyx_int_large_neg_43759347958734_xxx_475987349587324895);
if (__Pyx_PyList_SET_ITEM(__pyx_t_2, 2, __pyx_int_large_neg_43759347958734_xxx_475987349587324895)) __PYX_ERR(0, 3, __pyx_L1_error);
__Pyx_INCREF(__pyx_int_0x119de994f9e3728a1652147);
__Pyx_GIVEREF(__pyx_int_0x119de994f9e3728a1652147);
if (__Pyx_PyList_SET_ITEM(__pyx_t_2, 3, __pyx_int_0x119de994f9e3728a1652147)) __PYX_ERR(0, 3, __pyx_L1_error);
__Pyx_INCREF(__pyx_n_s_hello);
__Pyx_GIVEREF(__pyx_n_s_hello);
if (__Pyx_PyList_SET_ITEM(__pyx_t_2, 4, __pyx_n_s_hello)) __PYX_ERR(0, 3, __pyx_L1_error);
__Pyx_INCREF(Py_False);
__Pyx_GIVEREF(Py_False);
if (__Pyx_PyList_SET_ITEM(__pyx_t_2, 5, Py_False)) __PYX_ERR(0, 3, __pyx_L1_error);
__Pyx_GIVEREF(__pyx_t_1);
if (__Pyx_PyList_SET_ITEM(__pyx_t_2, 6, __pyx_t_1)) __PYX_ERR(0, 3, __pyx_L1_error);
__pyx_t_1 = 0;
__pyx_v_var1 = ((PyObject*)__pyx_t_2);
__pyx_t_2 = 0;
/* "test.py":4
* var = 123456789
* var1 = [9999, 1.012, -437593479587349875983475987349587324895, 340759348759834759853842759, "hello", False, var]
* var2 = [] # <<<<<<<<<<<<<<
* var3 = (1, 2, 3)
* var4 = {1, 2, 3, 4}
*/
__pyx_t_2 = PyList_New(0); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 4, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_2);
__pyx_v_var2 = ((PyObject*)__pyx_t_2);
__pyx_t_2 = 0;
首先PyList_New(7)
创建了一个长度为 7 的空列表,使用 __Pyx_PyList_SET_ITEM
为列表的每个位置设置值。对应的值通过 __Pyx_INCREF
和 __Pyx_GIVEREF
管理引用计数的不多解释。对于不同的数据类型,处理方式与上文差不多。比如长整数__pyx_int_large_neg_43759347958734_xxx_475987349587324895
表示一个大负数。布尔 使用 Py_False
和 Py_True
来表示 False
和 True
。字符串 "hello"
被转换为 __pyx_n_s_hello
静态字符串对象。
在ida里看一下就是先PyList_New(7)
然后直接用数组赋值
元组
元组和列表类似,但是会先打包成tuple,保证了元组内容不可变性(回忆上篇文章,在函数调用的时候)
/* "test.py":5
* var1 = [9999, 1.012, -437593479587349875983475987349587324895, 340759348759834759853842759, "hello", False, var]
* var2 = []
* var3 = (1, 2, 3) # <<<<<<<<<<<<<<
* var4 = {1, 2, 3, 4}
* var5 = {"key1": "value1", "key2": "value2"}
*/
__pyx_tuple_ = PyTuple_Pack(3, __pyx_int_1, __pyx_int_2, __pyx_int_3); if (unlikely(!__pyx_tuple_)) __PYX_ERR(0, 5, __pyx_L1_error)
__Pyx_GOTREF(__pyx_tuple_);
__Pyx_GIVEREF(__pyx_tuple_);
在打包前先拿到数据_pyx_int_1
_pyx_int_2
_pyx_int_3
_pyx_int_4
都是从长整型转换而来的,对应元组三个元素1,2,3 。这第四个元素是接下来集合用到的。(跟列表用的初始化函数是一个哦 包括下文集合的前三个元素也是1,2,3 所以直接拿来用 编译器又不傻,相同的工作做一次就好了)
static CYTHON_SMALL_CODE int __Pyx_InitConstants(void) {
if (__Pyx_CreateStringTabAndInitStrings() < 0) __PYX_ERR(0, 1, __pyx_L1_error);
__pyx_float_1_012 = PyFloat_FromDouble(1.012); if (unlikely(!__pyx_float_1_012)) __PYX_ERR(0, 1, __pyx_L1_error)
__pyx_int_1 = PyInt_FromLong(1); if (unlikely(!__pyx_int_1)) __PYX_ERR(0, 1, __pyx_L1_error)
__pyx_int_2 = PyInt_FromLong(2); if (unlikely(!__pyx_int_2)) __PYX_ERR(0, 1, __pyx_L1_error)
__pyx_int_3 = PyInt_FromLong(3); if (unlikely(!__pyx_int_3)) __PYX_ERR(0, 1, __pyx_L1_error)
__pyx_int_4 = PyInt_FromLong(4); if (unlikely(!__pyx_int_4)) __PYX_ERR(0, 1, __pyx_L1_error)
__pyx_int_9999 = PyInt_FromLong(9999); if (unlikely(!__pyx_int_9999)) __PYX_ERR(0, 1, __pyx_L1_error)
__pyx_int_0x119de994f9e3728a1652147 = PyInt_FromString((char *)"0x119de994f9e3728a1652147", 0, 0); if (unlikely(!__pyx_int_0x119de994f9e3728a1652147)) __PYX_ERR(0, 1, __pyx_L1_error)
__pyx_int_large_neg_43759347958734_xxx_475987349587324895 = PyInt_FromString((char *)"-437593479587349875983475987349587324895", 0, 0); if (unlikely(!__pyx_int_large_neg_43759347958734_xxx_475987349587324895)) __PYX_ERR(0, 1, __pyx_L1_error)
return 0;
__pyx_L1_error:;
return -1;
}
ida中也用的偏移量,不过没关系,上面给出了偏移量的赋值(偏移量最良心的一局)
集合
/* "test.py":6
* var2 = []
* var3 = (1, 2, 3)
* var4 = {1, 2, 3, 4} # <<<<<<<<<<<<<<
* var5 = {"key1": "value1", "key2": "value2"}
* print(var1, var2, var3, var4, var5)
*/
__pyx_t_2 = PySet_New(0); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 6, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_2);
if (PySet_Add(__pyx_t_2, __pyx_int_1) < 0) __PYX_ERR(0, 6, __pyx_L1_error)
if (PySet_Add(__pyx_t_2, __pyx_int_2) < 0) __PYX_ERR(0, 6, __pyx_L1_error)
if (PySet_Add(__pyx_t_2, __pyx_int_3) < 0) __PYX_ERR(0, 6, __pyx_L1_error)
if (PySet_Add(__pyx_t_2, __pyx_int_4) < 0) __PYX_ERR(0, 6, __pyx_L1_error)
__pyx_v_var4 = ((PyObject*)__pyx_t_2);
__pyx_t_2 = 0;
管你几个元素,新建的时候都是PySet_New(0)
新建一个空集合,然后挨个PySet_Add()
添加进集合中(为了集合元素的互异性)
字典
/* "test.py":7
* var3 = (1, 2, 3)
* var4 = {1, 2, 3, 4}
* var5 = {"key1": "value1", "key2": "value2"} # <<<<<<<<<<<<<<
* print(var1, var2, var3, var4, var5)
*
*/
__pyx_t_2 = __Pyx_PyDict_NewPresized(2); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 7, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_2);
if (PyDict_SetItem(__pyx_t_2, __pyx_n_s_key1, __pyx_n_s_value1) < 0) __PYX_ERR(0, 7, __pyx_L1_error)
if (PyDict_SetItem(__pyx_t_2, __pyx_n_s_key2, __pyx_n_s_value2) < 0) __PYX_ERR(0, 7, __pyx_L1_error)
__pyx_v_var5 = ((PyObject*)__pyx_t_2);
__pyx_t_2 = 0;
字典就没什么好说的了,,使用PyDict_New
创建空字典,PyDict_SetItem
函数是添加字典中的键值。