啊哈友 發表於 2019-11-5 16:43:00

Python逆向(二)—— pyc文件结构分析

<h1 id="一前言">一、前言</h1>
<p>上一节我们知道了pyc文件是python在编译过程中出现的主要中间过程文件。pyc文件是二进制的,可以由python虚拟机直接执行的程序。分析pyc文件的文件结构对于实现python编译与反编译就显得十分重要。</p>
<p>Python代码的编译结果就是PyCodeObject对象。PyCodeObject对象可以由虚拟机加载后直接运行,而pyc文件就是PyCodeObject对象在硬盘上的保存形式。因此我们先分析PyCodeObject对象的结构,随后再涉及pyc文件的二进制结构。</p>
<h1 id="二pycodeobject对象结构分析">二、PyCodeObject对象结构分析</h1>
<pre><code>typedef struct {
    PyObject_HEAD
    int co_argcount;      /* 位置参数个数 */
    int co_nlocals;         /* 局部变量个数 */
    int co_stacksize;       /* 栈大小 */
    int co_flags;   
    PyObject *co_code;      /* 字节码指令序列 */
    PyObject *co_consts;    /* 所有常量集合 */
    PyObject *co_names;   /* 所有符号名称集合 */
    PyObject *co_varnames;/* 局部变量名称集合 */
    PyObject *co_freevars;/* 闭包用的的变量名集合 */
    PyObject *co_cellvars;/* 内部嵌套函数引用的变量名集合 */
    /* The rest doesn’t count for hash/cmp */
    PyObject *co_filename;/* 代码所在文件名 */
    PyObject *co_name;      /* 模块名|函数名|类名 */
    int co_firstlineno;   /* 代码块在文件中的起始行号 */
    PyObject *co_lnotab;    /* 字节码指令和行号的对应关系 */
    void *co_zombieframe;   /* for optimization only (see frameobject.c) */
} PyCodeObject;
</code></pre>
<p>上面就是PyCodeObject对象一般情况下包含的属性名称及数据类型,每个属性在虚拟机执行pyc文件时都有其作用,随后在编译与反编译的过程中我们会对上述出现的属性一一分析。</p>
<h1 id="三pyc文件生成">三、pyc文件生成</h1>
<p>python中使用marshal.dump的方法将PyCodeObject对象转化为对应的二进制文件结构。每个字段在二进制文件中的结构如下图:</p>
<p><img src="https://img2018.cnblogs.com/blog/1391592/201911/1391592-20191105164042497-1316179961.png" alt="" loading="lazy"></p>
<p>byte表示占用1个字节,long表示占用4个字节,bytes表示该字段可能占用1到多个字节。需要说明的是,PyCodeObject对象中的每一个属性及值都会按照一定的顺序表示在二进制文件里。</p>
<p>pyc文件结构主要包括两部分:pyc文件头部表示和PyCodeObject对象部分。上面对PyCodeObject对象的二进制部分已经有了了解,pyc文件头部比较简单,在python2中只占用4个字节包含两个字段magic和mtime,完整的pyc文件结构见下图:</p>
<p><img src="https://img2018.cnblogs.com/blog/1391592/201911/1391592-20191105164109121-2118007080.png" alt="" loading="lazy"></p>
<h1 id="四实例分析">四、实例分析</h1>
<p>上面我们对pyc文件结构已经有了理论上的了解,接下来通过一个实例对实际的二进制文件进行分析。<br>
源文件test.py</p>
<pre><code>s = "hello"                                                                                                                                                   
def func():
    a = 3
    print s
func()
</code></pre>
<p>通过执行python2 -m py_compile test.py 可以生成编译好的pyc文件test.pyc。</p>
<p><img src="https://img2018.cnblogs.com/blog/1391592/201911/1391592-20191105164143127-12748161.png" alt="" loading="lazy"></p>
<p>我们使用二进制编辑器打开test.pyc</p>
<p><img src="https://img2018.cnblogs.com/blog/1391592/201911/1391592-20191105164207813-995473195.png" alt="" loading="lazy"></p>
<ul>
<li><strong>pyc文件头部</strong>:
<ul>
<li>前4个字节:03f3 0d0a,表示python版本</li>
<li>5-8个字节:0e6b 905d,表示pyc文件修改时间</li>
</ul>
</li>
<li><strong>PyCodeObject对象二进制编译结果</strong>:
<ul>
<li>第9字节:63,TYPE_CODE字段,也就是字符c,值为99,即0x63,表示接下为是一个PyCodeObject对象</li>
</ul>
</li>
<li><strong>PyCodeObject对象----全局参数</strong>:
<ul>
<li>然后4个字节是0x00 0000 00,code block的位置参数个数co_argument,这里是0;</li>
<li>再接着4个字节是0x00 0000 00, code block中的局部变量个数co_nlocals,这里是0;</li>
<li>再接着4个字节是0x01 0000 00, code block需要的栈空间co_stacksize,这里是1;</li>
<li>再接着4个字节是0x40 0000 00, co_flags,这里是64;</li>
</ul>
</li>
<li><strong>PyCodeObject对象----code block</strong>:
<ul>
<li>1个字节0x73为TYPE_CODE字段, 表示该字段为string格式;</li>
<li>4个字节0x1a00 0000表示code block段的数据部分占用0x1a个字节,即长度为26;</li>
<li>接下来26个字节6400 ...... 6402 0053为该TYPE_CODE字段(数据类型string)部分,也就是pyc文件中包含的字节码指令</li>
</ul>
</li>
<li><strong>再往下的逐个TYPE_CODE字段都是重复结构的,用来表示PyCodeObject对象中的一些其他参数</strong></li>
</ul><br><br>
来源:https://www.cnblogs.com/blili/p/11799483.html
頁: [1]
查看完整版本: Python逆向(二)—— pyc文件结构分析