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_FalsePy_True 来表示 FalseTrue。字符串 "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函数是添加字典中的键值。


posted @ 2024-12-10 21:09  纸飞机低空飞行  阅读(39)  评论(0编辑  收藏  举报