使用OpCode绕过Python沙箱的方法详解

0x01 OpCode

opcode又称为操作码,是将python源代码进行编译之后的结果,python虚拟机无法直接执行human-readable的源代码,因此python编译器第一步先将源代码进行编译,以此得到opcode。例如在执行python程序时一般会先生成一个pyc文件,pyc文件就是编译后的结果,其中含有opcode序列。

如何查看一个函数的OpCode?

?
1
2
3
4
5
def a():
 if 1 == 2:
  print("flag{****}")
print "Opcode of a():",a.__code__.co_code.encode('hex')

通过此方法我们可以得到a函数的OpCode

Opcode of a(): 6401006402006b020072140064030047486e000064000053

我们可以通过dis库获得相应的解析结果。

?
1
2
3
import dis
dis.dis('6401006402006b020072140064030047486e000064000053'.decode('hex'))

得到反编译的结果

0 LOAD_CONST          1 (1)
      3 LOAD_CONST          2 (2)
      6 COMPARE_OP          2 (==)
      9 POP_JUMP_IF_FALSE    20
     12 LOAD_CONST          3 (3)
     15 LOAD_BUILD_CLASS
     16 YIELD_FROM    
     17 JUMP_FORWARD        0 (to 20)
>>   20 LOAD_CONST          0 (0)
     23 RETURN_VALUE

常见的字节码指令

为了进一步研究OpCode,我们可以对dis的disassemble_string函数进行patch

在124行加入

?
1
print hex(op).ljust(6),

可以查看具体的字节码。

0 LOAD_CONST           0x64       1 (1)
      3 LOAD_CONST           0x64       2 (2)
      6 COMPARE_OP           0x6b       2 (==)
      9 POP_JUMP_IF_FALSE    0x72      20
     12 LOAD_CONST           0x64       3 (3)
     15 LOAD_BUILD_CLASS     0x47 
     16 YIELD_FROM           0x48 
     17 JUMP_FORWARD         0x6e       0 (to 20)
>>   20 LOAD_CONST           0x64       0 (0)
     23 RETURN_VALUE         0x53

变量

指令名 操作
LOAD_GLOBAL 读取全局变量
STORE_GLOBAL 给全局变量赋值
LOAD_FAST 读取局部变量
STORE_FAST 给局部变量赋值
LOAD_CONST 读取常量

IF

指令名 操作
POP_JUMP_IF_FALSE 当条件为假的时候跳转
JUMP_FORWARD 直接跳转

CMP_OP

?
1
cmp_op = ('<', '<=', '==', '!=', '>', '>=', 'in', 'not in', 'is','is not', 'exception match', 'BAD')

其余的指令参考OpCode源码

0x02 利用OpCode改变程序运行逻辑

在Python中,我们可以对任意函数的__code__参数进行赋值,通过对其进行赋值,我们可以改变程序运行逻辑。

Example1

?
1
2
3
def a():
 if 1 == 2:
  print("flag{****}")

在沙箱环境中我们需要调用这个函数,但是此函数我们无法执行到print语句。因此我们需要通过某种方法得到flag

Solution 1

我们直接获取a.__code__.co_consts,查看所有的常量。即可知道flag

?
1
(None, 1, 2, 'flag{****}')

Solution 2

更改程序运行逻辑

CodeType构造函数

?
1
2
3
def __init__(self, argcount, nlocals, stacksize, flags, code,
     consts, names, varnames, filename, name,
     firstlineno, lnotab, freevars=None, cellvars=None):

上述函数其余参数均可通过__code.__.co_xxx获得

因此我们

?
1
2
3
4
5
6
def a():
 if 1 == 2:
  print("flag{****}")
for name in dir(a.__code__):
 print name,getattr(a.__code__,name)

输出

co_argcount 0
co_cellvars ()
co_code ddkrdGHndS
co_consts (None, 1, 2, 'flag{****}')
co_filename example1.py
co_firstlineno 1
co_flags 67
co_freevars ()
co_lnotab

co_name a
co_names ()
co_nlocals 0
co_stacksize 2
co_varnames ()

构造相应目标代码

?
1
2
3
4
5
def a():
 if 1 != 2:
  print("flag{****}")
print "Opcode of a():",a.__code__.co_code.encode('hex')

得到code

6401006402006b030072140064030047486e000064000053

构造payload

?
1
2
3
4
5
6
7
8
9
10
def a():
 if 1 == 2:
  print("flag{****}")
newcode = type(a.__code__)
code = "6401006402006b030072140064030047486e000064000053".decode('hex')
code = newcode(0,0,2,67,code,(None, 1, 2, 'flag{****}'),(),(),"xxx","a",1,"")
a.__code__ = code
a()

即可输出flag

Example 2

?
1
2
3
4
5
6
7
def target(flag):
 def printflag():
  if flag == "":
   print flag
 return printflag
flag = target("flag{*******}")

这一次因为是通过变量传入参数,我们无法通过上一次读co_consts获得变量。但是我们这次依旧可以通过重写code获得flag。

构造替代函数

?
1
2
3
4
5
6
7
8
9
def target(flag):
 def printflag():
  if flag != "":
   print flag
 return printflag
a = target("xxx")
import types
code = a.__code__.co_code.encode('hex')
print code

EXP

?
1
2
3
4
5
newcode = type(flag.__code__)
code = "8800006401006b030072140088000047486e000064000053".decode('hex')
code = newcode(0,0,2,19,code,(None, ''),(),(),"example2.py","printflag",2,"",('flag',),())
flag.__code__ = code
flag()

➜  python example2exp.py
8800006401006b030072140088000047486e000064000053
➜  python example2.py  
flag{*******}

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对脚本之家的支持。

(0)

相关推荐

  • CTF竞赛密码学之 LFSR

    概述: 线性反馈移位寄存器(LFSR)归属于移位寄存器(FSR),除此之外还有非线性移位寄存器(NFSR).移位寄存器是流密码产生密钥流的一个主要组成部分. $GF(2)$上一个n级反馈移位寄存器由n ...

  • 记一次CTF实战练习(RE/PWN)

    这是Hgame_CTF第二周的题目,一共有四周.相对来说,比第一周难(HgameCTF(week1)-RE,PWN题解析).这次的有一道逆向考点也挺有意思,得深入了解AES的CBC加密模式才能解题.还 ...

  • 编写高质量代码:改善Python程序的91个建议.1

    人生苦短,睡觉最好! -U 是--upgrade的缩写,如果以已经安装就升级到最新版 先得安装一下 输出的没毛病 我们实验一下 我提前把代码改过 pep8 --show-source --show-p ...

  • Python 为什么引入这两个关键词

    啥是 global 和 nonlocal Python 支持的关键词里,global 和 nonlocal 初学者接触的少,不知道是做什么用的:一些人虽然知道它们的作用,但对为什么要引入这两个关键词则 ...

  • 第54天:Python 多线程 Event

    Event(事件) Event 是一个事务处理的机制,用于通知多个异步任务某个事件已经发生了.比如在交通红绿灯中多辆在行驶中的汽车可以表示成程序中的多个异步任务,红绿灯的亮起可以表示成一个通知,红灯通 ...

  • Dataset之MNIST:自定义函数mnist.load_mnist根据网址下载mnist数据集(四个ubyte.gz格式数据集文件)

    Dataset之MNIST:自定义函数mnist.load_mnist根据网址下载mnist数据集(四个ubyte.gz格式数据集文件) 下载结果 运行代码 mnist.py文件 # coding: ...

  • 对Python通过pypyodbc访问Access数据库的方法详解

    看书上通过ODBC访问数据库的案例,想实践一下在Python 3.6.1中实现access2003数据库的链接,但是在导入odbc模块的时候出现了问题,后来查了一些资料就尝试着使用pypyodbc,最 ...

  • Python中的魔术方法详解

    介绍 在Python中,所有以"__"双下划线包起来的方法,都统称为"Magic Method",中文称『魔术方法』,例如类的初始化方法 __init__ ,P ...

  • Python魔法方法详解

    据说,Python 的对象天生拥有一些神奇的方法,它们总被双下划线所包围,他们是面向对象的 Python 的一切. 他们是可以给你的类增加魔力的特殊方法,如果你的对象实现(重载)了这些方法中的某一个, ...

  • C# DataTable使用方法详解

    在项目中常常常使用到DataTable,假设DataTable使用得当,不仅能使程序简洁有用,并且可以提高性能,达到事半功倍的效果,现对DataTable的使用技巧进行一下总结. 1.添加引用 ? 1 ...

  • 英语连读方法详解,看这一个视频就够了!

    英语连读方法详解,看这一个视频就够了!

  • 小学四年级语文:修辞方法详解及练习题(附万能答题公式)

    一. 比喻 (一)什么是比喻 比喻就是"打比方",即利用不同事物之间的某些相似之处,用一个事物来比方另一个事物.多用一些具体的,浅显的.熟知的事物来说明抽象的.深奥的.生疏的事物. ...

  • OpenSchema 方法详解

    OpenSchema 方法详解 来源(www.guzubo.cn) From: http://www.guzubo.cn/3f91b447-0a3f-4de9-9e11-68f46fe136cd608 ...

  • 油罐附件及维护方法详解

    油罐附件是油罐的重要组成部分,在这些附件中,有些是为了完成油品收发作业和便于生产管理而设置的:有些则是为了保障油罐使用安全,防止或消除各类油罐事故而设置的.掌握油罐附件的分类.作用.工作原理.故障及维 ...

  • 成本均线能清晰设别主力机构资金动向,四大分析软件设置方法详解

    成本均线能清晰设别主力机构资金动向,四大分析软件设置方法详解