未加星标

35c3CTF collection writeup

字体大小 | |
[开发(python) 所属分类 开发(python) | 发布者 店小二04 | 时间 2019 | 作者 红领巾 ] 0人收藏点击收藏

这次的c3系列CTF同往常一样具有非常高的质量,题目非常有意思,这道题目是pwn题里最为简单的一道,对于新手来说比较友好,可以娱乐玩一下,看看和国内libc题不一样的感觉。

python基础

对于这个题目来说,你需要了解python内部的一些基本原理,最好是看过源码,那样的话我觉得你就没有需要看wp的必要了hhh。

在我很久以前的文章里曾经介绍过相关的知识,需要的同学可以粗浅的看一下:

https://www.anquanke.com/post/id/86366

这里就不再赘述了。

题目基本分析

题目给了一个 python3.6 和一个 Collection.cpython-36m-x86_64-linux-gnu.so ,对于熟悉python的同学来讲,看到这个应该就能想到这个属于用C写的python的扩展。

python是一个非常方便使用C代码的语言,用C写的python扩展,在格式符合条件,文件名正确(像这个文件名的格式就是库的一种比较标准的格式),就可以像python代码一样直接进行import。其实这里如果不是很清楚也不是太关键,题目给了一个 test.py 防止你不知道应该怎么调用:

题目所有文件:

27 tree dist/ ~/c/c/3/p/c/wp dist/ ├── Collection.cpython-36m-x86_64-linux-gnu.so ├── libc-2.27.so ├── python3.6 ├── server.py └── test.py

test.py

import Collection a = Collection.Collection({"a":1337, "b":[1.2], "c":{"a":45545}}) print(a.get("a")) print(a.get("b")) print(a.get("c"))

这个文件里给出了这个python扩展的基本用法,看起来没什么太多特别的东西。

在 server.py 里,给出了在远程服务器运行的代码,是 py2 写的,所以明显这个不是攻击的目标。主要逻辑是加上了一个沙箱(在执行你给出的python代码前加入了一段代码):

加入的代码:

prefix = """ from sys import modules del modules['os'] import Collection keys = list(__builtins__.__dict__.keys()) for k in keys: if k != 'id' and k != 'hex' and k != 'print' and k != 'range': del __builtins__.__dict__[k] """

属于一个典型的python沙箱,而且看起来还是比较强的,只留下了 id , hex , print,和 range ,连 len`都没有。但是熟悉python沙箱的同学来讲可能会考虑到这种沙箱依然会存在缺陷,我们后面也会用到这样的缺陷。现在我们继续把题目的基本内容弄明白。

下一步就是需要逆向了,既然是一个库,里边的一些符号是不能删除的,所以函数名字之类的大多还保留着,对于我们逆向来说还是比较友好的。但是同样由于是使用到python的动态库,在函数上大量使用了python的一些函数,参数也是python定义的参数,比如我们看一下入口:

__int64 PyInit_Collection() { __int64 v0; // rax __int64 v1; // rbx if ( (int)PyType_Ready((__int64)&CollectionTypeObject) < 0 ) return 0LL; v0 = PyModule_Create2(&collection_module_def, 0x3F5LL); v1 = v0; if ( v0 ) { ++CollectionTypeObject.ob_base.ob_base.ob_refcnt; PyModule_AddObject(v0, (__int64)"Collection", (__int64)&CollectionTypeObject); mprotect((void *)0x439000, 1uLL, 7); MEMORY[0x43968F] = _mm_load_si128((const __m128i *)&xmmword_27E0); MEMORY[0x43969F] = MEMORY[0x43968F]; mprotect((void *)0x439000, 1uLL, 5); init_sandbox(); } return v1; }

从函数名可以看出来这里是入口,当然另外一个比较推荐的做这种题的方法是去了解一些背景知识,这样可以理解的更全面一些。对于这道题来讲就是去了解用c写的python库的一些特点,这样对于找入口之类的事会更加得心应手一些。在这个函数里用到了例如 PyType_Ready 之类的python的函数,可以在python源码或者文档里搜到签名 int PyType_Ready (PyTypeObject *type) ,传入的参数是一个 PyTypeObject ,我们需要定义出这个结构体才能够更好的去逆向,因为可能需要查看他其中的一些参数。在linux内核驱动的一些题目当中也会碰到类似的问题,但是那个明显比这种情况更复杂一些(因为参数比较多,很容易出现配置不同,导致虽然代码相同,但是编译出来的结果不一样),于是在这里我借鉴了以前在逆向驱动时候用到的技巧,就是直接将python源码进行编译,然后把编译后的带有debug信息的二进制用ida打开,利用ida的导出类型到c header的方法去把类型直接从python中提取出来。

这个方法中需要注意的是,对于正常的debug编译来说是没有类型信息的,需要使用 -g3 的debug编译等级,需要在编译python的时候:

./configure CFLAGS=-g3 make -j4

这样就可以编译出带有type的python,然后用ida打开,选择 file -> produce file -> create C header 就可以导出到header,在逆向so库的这边把header导入就可以得到类型信息了。

在恢复完类型信息之后,我们就可以看看so库里定义的一些关键的接口了:

.data:00000000002041E0 dq offset CollectionTypeMethod; tp_methods ... .data:00000000002041E0 dq offset CollectionInit; tp_init .data:00000000002041E0 dq 0 ; tp_alloc .data:00000000002041E0 dq offset CollectionNew ; tp_new

在methods里:

.data:00000000002041A0 CollectionTypeMethod dq offset collection_get_name; ml_name .data:00000000002041A0 ; DATA XREF: .data:CollectionTypeObject↓o .data:00000000002041A0 dq offset collection_get; ml_meth ; "get" ... .data:00000000002041A0 dd 1 ; ml_flags .data:00000000002041A0 db 4 dup(0) .data:00000000002041A0 dq offset CollectionGetDoc; ml_doc

所以说这个库基本就是定义了 Collection.Collection 对象的初始化,和他的 get 方法。

在有了类型信息之后,接下来的逆向并不困难,我就不再详细去描述了,基本方法就是看到python的函数,去查找签名,把类型改对。几个重要的自定义类型:

struct PyCollectionObject { PyObject ob_base; MyTypeHandler *handler; void *slots[32]; }; struct MyTypeHandler { MyList *list; int maybe_ref; }; struct MyList { MyNode *head; MyNode *last; int num; }; struct MyNode { MyRecord *record; struct MyNode *next; }; struct MyRecord { char *name; int type; };

总结一下题目的基本逻辑:

server中:设置py沙箱,打开flag,设置fd为1023然后启动用户的python程序。设置沙箱:这一步导致我们根本不用考虑去绕过python层的沙箱了,因为在 import Collection 的时候有 init_sandbox 操作,加入了seccomp,只能使用白名单,我主要在意了白名单里有 write 和 readv ,但是没有open。 有 Collection.Collection 对象,和该对象上的 .get 方法。对象初始化接受一个dict,dict的key必须为字符串,然后value为数值/list/dict中的一种。 .get 接受一个字符串,然后返回初始化时传入的内容。 在初始化时会建立一个 handler ,相当于key的缓存,会保存下传入的dict的key的内容(字符串内容)和类型(是整数还是列表还是字典),建立之后会存入缓存的handler里,如果存在“一样”的handler,就会直接使用该handler,而不新建。 handler的“一样”的比较,是将两个handler按照字典序排序,之后比较两个handler相应位置的key和类型是不是都一样,如果完全一样则一样,否则则不同 在 .get 的时候,首先从 handler 里找到对应key所在的索引,然后从对象里的 slots 里取出内容返回,如果是整数,还需要进行一次转换,将整数转换为python的整数对象类型。 漏洞点

漏洞点就在题目的逻辑里。因为在比较的时候两个handler是经过排序的,排序之后认为相同,则就使用现有的handler了,但是事实上两个handler相同之后,他们的顺序可能是不同的,而后在 .get 的时候又用到了这个顺序,不同的顺序对应的索引肯定不同。

举个例子:

假设第一个对象的dict为 {"a": 1, "b":[1]}
第二个为 {"b":[1], "a": 1} ,

那么两个对象的handler是肯定一样的,因为他们经过排序之后的类型和key字符串都是相同的,但是存储在对象中的具体顺序并不同:

第一个对象中的slots:

slots[0] ==> 1 slots[1] ==> [1]

第二个对象中的slots:

slots[0] ==> [1] slots[1] ==> 1

现在我们要去取第二个对象的 a :

index of 'a': 0 (因为与第一个handler一样,而直接使用了第一个对象的handler,所以取到的索引也是第一个handler里的) 取slots[0] ==> [1]

明显出现了问题。

而且这里由于类型不同,就会导致python类型混淆,把整数当做list的地址,或是dict的地址来处理,这样就给了我们利用的前提了。

利用思路

接下来我们要思考如何把漏洞的控制能力放大。我个人比较喜欢使用这样的思路去思考利用,就是关注控制能力,例如任意读写就比溢出的控制能力更强,从任意读写可以随意构造溢出,但是反过来就比较麻烦,所以思路就是一步一步提升控制能力。(这里纯属个人瞎掰,姑妄言之姑妄听之)

我们现在对漏洞的控制能力是我们可以做到不同对象的混淆,但是只有混淆还不够。

由于list或者dict在slots中的体现都是地址,而整数则是直接的数,所以利用混淆,我们可以将任意位置当做list或者是dict来处理。这样就相当于我们从混淆,做到了将任意位置当做list或dict,嗯控制更强了一些。但是还不够。

对于这种情况,一般我们会考虑去做到任意地址读写,一方面在于这样的能力非常强,可以做到很多事情,几乎等于完成利用,另一方面由于我们有python本身在运行,内部使用了很多数据结构,我们有很多目标可以选择,所以做到任意读写的概率也比较大。

接下来就需要阅读一些python的代码了,因为我们需要去找合适的混淆目标。

首先是list:

typedef struct { PyObject_VAR_HEAD /* Vector of pointers to list elements. list[0] is ob_item[0], etc. */ PyObject **ob_item; /* ob_item contains space for 'allocated' elements. The number * currently in use is ob_size. * Invariants: * 0 <= ob_size <= allocated * len(list) == ob_size * ob_item == NULL implies ob_size == allocated == 0 * list.sort() temporarily sets allocated to -1 to detect mutations. * * Items must normally not be NULL, except during construction when * the list is not yet visible outside the function that builds it. */ Py_ssize_t allocated; } PyListObject;

还有dict:

typedef struct { PyObject_HEAD /* Number o

本文开发(python)相关术语:python基础教程 python多线程 web开发工程师 软件开发工程师 软件开发流程

代码区博客精选文章
分页:12
转载请注明
本文标题:35c3CTF collection writeup
本站链接:https://www.codesec.net/view/627728.html


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 开发(python) | 评论(0) | 阅读(244)