目标vs的使用恶作剧1:1+1=3终于找到了...折腾下str吧找到str拼接的关键点啦总结
目标今天的目标很简单:让python的计算出现奇奇怪怪的"错误".
从今天起,我们正式跨入python虚拟机 运行时部分 的恶搞.
要阅读python庞大的源码,你得拥有足够的耐心,因为他的函数调用特别复杂,甚至多达几十层才能实现一个在我们眼中特别简单的功能(如今天的恶搞对象:加减乘除).
这时候,我们的工具就得派上用场了:vs的查找.
这里可不是普普通通的查找哦,我们在一个单词上右键,然后点击Find all references,也就是"找到所有引用".下方就会显示出所有引用了这种对象的列表,如下:
因为vs的某些局限,你无法直接ctrl+单击查看函数的实现,这里为了搞事情我们得找到加法处理的函数,所以只能查找所有引用,然后找到代表着"实现声明式"的那一个,就像上图标蓝的.根据常识,我们知道其他的都是调用这个函数,因此找到实现只能这么办!
(或者用眼睛找个半个小时也不是不行,何况我自己找这个函数已经找了半个小时了)
不想看过程的话,直接看最后,节约时间吧如果不想了解具体的话:传送
今天的练手恶搞啦.我们知道加法的英文,ADD,因此在我半个小时劳动成果的文件Python/ceval.c中查找字符串"ADD",(记得打开大写锁定,也就是说Add不会被搜索).
跟着直觉走,1+1应该属于二元运算,也就是BINARY,这么说,给出结果吧:Python/ceval.c:2068.相关case如下
PyObject *right = POP(); PyObject *left = TOP(); PyObject *sum; if (PyUnicode_CheckExact(left) && PyUnicode_CheckExact(right)) { sum = unicode_concatenate(tstate, left, right, f, next_instr); } else { sum = PyNumber_Add(left, right); Py_DECREF(left); } Py_DECREF(right); SET_TOP(sum); if (sum == NULL) goto error; DISPATCH();
最主要的是那个if判断句.前半部分,应该是字符串的拼接,后文会讲到.先看else,应该是处理数字相关(复数,浮点数和整数,都应该算在内).看到他调用了PyNumber_Add函数,也足以证明这是数字加法.
根据前一节讲到的 *** 作技巧,一步步找出这个函数的实现,在Objects/abstract.c:1069,如下:
PyNumber_Add(PyObject *v, PyObject *w) { PyObject *result = BINARY_OP1(v, w, NB_SLOT(nb_add), "+"); if (result != Py_NotImplemented) { return result; } Py_DECREF(result); PySequenceMethods *m = Py_TYPE(v)->tp_as_sequence; if (m && m->sq_concat) { result = (*m->sq_concat)(v, w); assert(_Py_CheckSlotResult(v, "+", result != NULL)); return result; } return binop_type_error(v, w, "+"); }
关注到那个if (result != Py_NotImplemented),猜着就是正常的情况,因为有个not,然后是不等于,因此最常用的分支应该在这里了,也就是正常返回.result是个PyObject*,仍然一步步进去,找到BINARY_OP1宏展开式:(同文件)
找到binary_op1在同文件的L863函数体开始
简单翻译一下,就是"槽函数"(学过qt的,眼不眼熟?),通过对象的类型动态查找相应的方法,然后变成函数指针并调用,那么下面的任务就简单了,针对我们需要的1+1对象改掉它的槽函数不就完啦!
L865有一段,应该是数字处理的:
slotv = NB_BINOP(Py_TYPE(v)->tp_as_number, op_slot);
找到tp_as_number到底是个啥:object.h:208,发现是个PyNumberMethods指针.顾名思义,Methods后缀的结构体应该就是把这个对象的内置方法函数指针都存起来,然后供给虚拟机调用.跳到这个结构体的定义,在object.h:104
我们只关心第一个字段:nb_add(就是number 的 add 方法 的意思)
这里来个特殊手段,不能直接访问这个字段,而且我们也不应该这么做.为什么?因为我们要的是针对long的这个域,得想办法找到处理long的add方法.那怎么办?找引用呗.可以看见结果差不多是这样:
稍加思考,static静态的结构体,约摸着存的就是方法映射表.按照它的命名,xxx_as_number就是xxx类型的number方法映射表.很见到找到long的部分:
转跳到了Objects/longobject.c:5585看来很有希望嘛,你们看第一个字段填的啥?没错!long_add处理方法!
找到它的实现,如下,在Objects/longobject.c:3064.根据实践咱只用看最开头的那个if成立部分:
return PyLong_FromLong(MEDIUM_VALUE(a) + MEDIUM_VALUE(b));
看出来没?那么大个加号诶!ok开始动手脚.比如说…加个+1?
来吧看看效果:
可见整型运算确实…哈哈哈字符串不会受影响.这时候把这解释器打包给朋友人家还以为电脑抽风呢…(使坏)
终极目标:在每个字符串的拼接处加一个"!#!"
还是一样,不想看过程跳最后:转跳
看到刚刚发现的那个BINARY_ADD case,没讲到的那个if分支就是处理UTF-8拼接的.转跳到Python/ceval.c:6313的unicode_concatenate函数.不管前面的switch,那是特殊情况.只看后面主调用PyUnicode_Append(&res, w)
这个函数体我看了下,只需要关注下面的部分Objects/unicodeobject.c:11850
把这个函数名翻译一下就知道怎么做了(滑稽);多跳几次函数调用,找到根在这里呢,同文件的L1567,“复制字符(s,复数)”
看到关键的L1620,memcpy调用.这个大家都熟悉吧,memcpy(dst, src, cnt)把src开始的cnt个字节复制到dst处
在这里动手脚,思路是加一段把"!#!"复制到to的结尾,然后指针后推三个字节,再复制from.但是经过实际测试我发现啊,这样做根本行不通.因为如果你在这里加个printf就会发现啊,python虚拟机在初始化的时候也会调用这个函数特别多次,那么如果我们在这里植入漏洞,就会害惨了自己,所以还是跳过这个部分吧…
今天还算是成功,虽然str拼接的漏洞没玩成功,但int加法自动+1导致的1+1=3已经足够好玩了(笑).今天利用这个机会,我们第一次深入python运行时,大致在寻找一层层的函数调用中,摸索出虚拟机运行的原理和约定(当然如果你跳过细节就会错过这些).在把1+1变成3的过程中,我们学会vs的一个重要功能:查找引用;也对python虚拟机源码有了一个更感性的认识.
点击查看上一章
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)