python逆向之pyc文件


最近做题老是遇到python逆向,没有经验,查了一些资料,记录一下

 

 

Python是一门解释性语言,没有严格意义上的编译和汇编过程

解释型语言没有严格编译汇编过程,由解释器将代码块按需要变运行边翻译给机器执行。因此解释型语言一度存在运行效率底,重复解释的问题。

但是通过对解释器的优化!可以提高解释型语言的运行效率。Python就属于这一种编程语言。

一、pyc文件

1、pyc文件概述

Pyc文件是py编译过程中产生的字节码文件,可以由虚拟机直接执行,是python将目标源码编译成字节码以后在磁盘上的文件形式

 

2、pyc文件结构(python 2.6.2 和 python3.8)

 

/* Bytecode object */

typedef struct {

    PyObject_HEAD

    int co_argcount;            /*  Code Block的位置参数个数,比如说一个函数的位置参数个数*/

    int co_nlocals;             /*  Code Block中局部变量的个数,包括其中位置参数的个数 */

    int co_stacksize;           /* 执行该段Code Block需要的栈空间 */

    int co_flags;               /* CO_..., see below */

    PyObject *co_code;          /* Code Block编译所得的字节码指令序列。以PyStingObjet的形式存在 */

    PyObject *co_consts;        /* PyTupleObject对象,保存CodeBlock中的所常量 */

    PyObject *co_names;         /* PyTupleObject对象,保存CodeBlock中的所有符号 */

    PyObject *co_varnames;      /* Code Block中的局部变量名集合 */

    PyObject *co_freevars;      /* Python实现闭包需要用的东西 */

    PyObject *co_cellvars;      /* Code Block中内部嵌套函数所引用的局部变量名集合 */

    /* The rest doesn't count for hash/cmp */

    PyObject *co_filename;      /* Code Block所对应的.py文件的完整路径 */

    PyObject *co_name;          /* Code Block的名字,通常是函数名或类名 */

    int co_firstlineno;         /* Code Block在对应的.py文件中起始行 */

    PyObject *co_lnotab;        /* 字节码指令与.py文件中source code行号的对应关系,以PyStringObject的形式存在 */

    void *co_zombieframe;     /* for optimization only (see frameobject.c) */

} PyCodeObject;

 python3.8:

typedef struct {
    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.
} PyCodeObject;

 

抄自  https://basicbit.cn/2018/12/20/2018-12-20-Python%E5%8F%8D%E7%BC%96%E8%AF%91%EF%BC%9F%E5%85%88%E8%81%8A%E8%81%8Apyc%E7%BB%93%E6%9E%84%E5%90%A7/

Python代码的编译结果就是PyCodeObject对象

每个pyCodeObject代表一个CodeBlock ,也可以称之为作用域,一个pyc文件中不止一个Code Block,一个文件,函数,类,都会对应一个Code Block

 

(这张图针对python2

long :四个字节

byte :1个字节

bytes :多个字节

 

Pyc文件结构在内存中的分布

https://www.cnblogs.com/blili/p/11799483.html

 

 

3、pyc文件的生成

 

Python -m compileall .py文件

 

 

 

 

4、实例

 

 

 

 

用二进制工具打开:

 

 

 

0x00h ~ 0x03h :03 F3 0D 0A是魔数,用于区分不同版本的python低字0d0a就是\r\n

0x04h ~ 0x07h :BB 67 F2 61 代表着修改时间

 

0x08: 0x63 ,他是一个的标识(TYPE_CODE)

0x09h ~ 0x0ch :00 00 00 00 代表全局 code block的位置的参数数量(co_argument)

0x0dh ~ 0x10h :00 00 00 00 代表code block的局部变量参数个数(co_nlocals)

0x11h ~ 0x14h :01 00 00 00 代表栈空间,该codeblock的栈空间为1(co_stacksize)

0x15h ~ 0x18h :40 00 00 00 (co_flags)

 

紧接着

0x19h : 0x73  PyStringObject(TYPE_STRING)的标志,在这之后就是字节码了,PyCodeObject的字节码序列是用PyStringObject对象来保存的,

0x1ah :  0x27 代表字节码的大小,也就是0x27大小

这表示从 0x1bh ~ 0x41h 即是为该TYPE_CODE字段(数据类型string)部分,也就是pyc文件中包含的字节码指令

 

证明:

 

 

先介绍几个函数

marshal模块:

 

 该模块经常用于处理python的反序列化

Load函数

 

 

 

Co_consts函数:

 

 

Dis模块

 

dis函数

 

 

 

结合函数我们可以发现我们字节码的大小正好是39(0x27)

 

 

Co_code 在0x44的位置结束了,从0x45的位置开始就是 co_consts 的内容了 这里保存了code block的常量

 

0x45h 对应的0x28应该是标识符

0x46h ~ 0x49h : 04 00 00 00 是元素个数

0x4ah :0x73  TYPE_CODE为 0x74,数据类型是PyStringObject

后面的4个字节为字符串长度:

0x4bh ~ 0x4eh : 09 00 00 00  代表着 PyStringObject的字符长度为9

0x4fh ~ 0x57h : 48 65 6c 6c 6f 20 70 79 63 即是那长度为9的字符串

0x58h : 0x69  TYPE_CODE 为 0x69 则数据类型为int ,后面的4个字节为内容

0x59h ~ 0x5ch  : 0A 00 00 00 即为内容

0x5dh : 0x63 ,TYPE_CODE为0x63,数据类型为PyCodeObject ,与上面一样分析

……

剩下的就不分析了,都差不多

 

 

二、python字节码解读

 

 

 

 python3 中每个字节码的大小都为2,(一字节操作数,一字节参数)

快速查看字节码的方法:python2 -m dis ./py

1、 常量

 

常量用 LOAD_CONST表示

2、局部变量

 

 

LOAD_FAST一般加载局部变量的值,也就是读取值,用于计算或者函数调用传参等。
STORE_FAST一般用于保存值到局部变量。

 

 

3、全局变量

 

LOAD_GLOBAL用来加载全局变量,包括指定函数名,类名,模块名等全局符号。

STORE_GLOBAL用来给全局变量赋值。

 

4、数据类型list

 

 

5、数据类型dict

 

 

6、数学运算

 

 

7、for循环

 

SETUP_LOOP用于开始一个循环。SETUP_LOOP 25 (to 28)28表示循环退出点

GET_ITER 表示开始迭代

FOR_ITER 表示继续iter开始下一个

8、if

 

POP_JUMP_IF_FALSEJUMP_FORWARD一般用于分支判断跳转。POP_JUMP_IF_FALSE表示条件结果为FALSE就跳转到目标偏移指令。JUMP_FORWARD直接跳转到目标偏移指令。

9、函数调用

 

 

参考:

https://basicbit.cn/2018/12/20/2018-12-20-Python%E5%8F%8D%E7%BC%96%E8%AF%91%EF%BC%9F%E5%85%88%E8%81%8A%E8%81%8Apyc%E7%BB%93%E6%9E%84%E5%90%A7/

https://www.cnblogs.com/blili/p/11799398.html

https://bbs.pediy.com/thread-246683.htm#msg_header_h3_6


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM