首页
社区
课程
招聘
[转帖][推荐]Python的decompile问题(1),(2),(3) by Team509
发表于: 2006-12-8 22:31 16091

[转帖][推荐]Python的decompile问题(1),(2),(3) by Team509

2006-12-8 22:31
16091
原文点我

我的一个朋友讲,他可以直接看出一个pyc和pyo的源代码来,也就是说可以他可以作为一个人肉的python的decompiler,这项功夫兄弟羡慕得紧,想要讨教,却不知从何问起,在google上搜索,居然没什么文章介绍这方面的东西,原来有人做过这方面的工具,后来居然不发布了,靠这个作为服务来收钱了。好在最近有朋友给了我一些资料,我从本周开始了这方面的”研究”工作,有了一点点的心得,特记录在这里。这项工作我希望最后能做成一个decompiler的工具。

好了,言归正传,我们正式开练,下面我们从”hello world”开始:

Hello.py

Print “hello world”

好了,我们先编译它。

c:Python24python

Python 2.4.3 (#69, Mar 29 2006, 17:35:34) [MSC v.1310 32 bit (Intel)] on win32

Type "help", "copyright", "credits" or "license" for more information.

>>> import compiler

>>> compiler.compileFile("c:temphello.py")

>>>
我们生成了hello.pyc,下面是文件内容:

Offset      0  1  2  3  4  5  6  7   8  9 a b c d e f

00000000   6D F2 0D 0A 36 1B 79 45  63 00 00 00 00 00 00 00   m?.6.yEc.......

00000016   00 01 00 00 00 40 00 00  00 73 09 00 00 00 64 01   .....@...s....d.

00000032   00 47 48 64 00 00 53 28  02 00 00 00 4E 73 0B 00   .GHd..S(....Ns..

00000048   00 00 68 65 6C 6C 6F 20  77 6F 72 6C 64 28 00 00   ..hello world(..

00000064   00 00 28 00 00 00 00 28  00 00 00 00 28 00 00 00   ..(....(....(...

00000080   00 73 10 00 00 00 63 3A  5C 74 65 6D 70 5C 68 65   .s....c: emphe

00000096   6C 6C 6F 2E 70 79 73 08  00 00 00 3C 6D 6F 64 75   llo.pys....<modu

00000112   6C 65 3E 01 00 00 00 74  00 00 00 00               le>....t....

好,我们开始分析这个文件的内容:

0x00 ? 0x03

6D F2 0D 0A代表的是编译器的版本,(2.4版本),是特征值

同理,其它版本的特征值如下:

99 4e 0d 0a  python 1.5

Fc c4 0d 0a  python 1.6

87 c6 0d 0a  python 2.0

2a eb 0d 0a  python 2.1

2d ed 0d 0a python 2.2

3b f2 0d 0a  python 2.3

6d f2 0d 0a python 2.4

B3 f2 0d 0a python 2.5

0x04 ? 0x07

36 1b 79 45

这个文件生成的时间,是小端在前的格式。

好了,下面进入了我们的核心内容了,

0x8

63 -à ‘c’

这个代表什么?我们有必要下载一份python的源代码来看看了,在python/marshal.c中,你会看到:

#define TYPE_NULL     '0'

#define TYPE_NONE    'N'

#define TYPE_FALSE    'F'

#define TYPE_TRUE     'T'

#define TYPE_STOPITER    'S'

#define TYPE_ELLIPSIS   '.'

#define TYPE_INT 'i'

#define TYPE_INT64    'I'

#define TYPE_FLOAT   'f'

#define TYPE_COMPLEX    'x'

#define TYPE_LONG    'l'

#define TYPE_STRING 's'      //A-1

#define TYPE_INTERNED   't'

#define TYPE_STRINGREF  'R'

#define TYPE_TUPLE   '('      //A-3

#define TYPE_LIST      '['

#define TYPE_DICT     '{'

#define TYPE_CODE    'c'    //A-2

#define TYPE_UNICODE     'u'

#define TYPE_UNKNOWN   '?'

对了,这个’c’代表TYPE_CODE (A-2 ),那么我们看看这个TYPE_CODE的结构是如何的,同样,在marshal.c中我们找到:

                     int argcount = r_long(p); // B-1

                     int nlocals = r_long(p);  // B-2

                     int stacksize = r_long(p); // B-3

                     int flags = r_long(p);  // B - 4

                     PyObject *code = r_object(p); // B ? 5

                     PyObject *consts = r_object(p); //B-6

                     PyObject *names = r_object(p); //B-7

                     PyObject *varnames = r_object(p);

                     PyObject *freevars = r_object(p);

                     PyObject *cellvars = r_object(p);

                     PyObject *filename = r_object(p);

                     PyObject *name = r_object(p);

                     int firstlineno = r_long(p);

                     PyObject *lnotab = r_object(p);

那么,我们知道了,

0x9 ? 0xc 字节: 00 00 00 00是argcount (B-1),  

0xd-0x10 : 00 00 00 00是nlocals,(B-2)

0x11 ? 0x14 : 01 00 00 00 是 stacksize,(B-3)

0x15 ? 0x18 : 40 00 00 00 是 flags, (B-4)

下面又是一个object, 0x73 = ‘s’ ,是另一个标志符了,看看上面,你就知道这代表TYPE_STRING(A-1),我们来看看TYPE_STRING是如何处理的:

case TYPE_STRING:

              n = r_long(p); //C-1

              if (n < 0) {

                     PyErr_SetString(PyExc_ValueError, "bad marshal data");

                     return NULL;

              }

              v = PyString_FromStringAndSize((char *)NULL, n);

              if (v != NULL) {

                     if (r_string(PyString_AS_STRING(v), (int)n, p) != n) {

                            Py_DECREF(v);

                            v = NULL;

                            PyErr_SetString(PyExc_EOFError,

                                   "EOF read where object expected");

                     }

              }

              if (type == TYPE_INTERNED) {

                     PyString_InternInPlace(&v);

                     PyList_Append(p->strings, v);

              }

              return v;

看到了吧?0x1a  -  0x 1d  09 00 00 00是代表string的长度(C-1),

0x1e ? 0x26  64 01 00 47 48 64 00 00 53 是 code的 内容,

从B-6可以知道,下面我们要读入consts了,这个标志符是0x28 = ‘(‘,

代表他是一个TYPE_TUPLE(A-3),我们来看看如何处理这个TYPE_TUPLE,

n = r_long(p); //D-1

              if (n < 0) {

                     PyErr_SetString(PyExc_ValueError, "bad marshal data");

                     return NULL;

              }

              v = PyTuple_New((int)n);

              if (v == NULL)

                     return v;

              for (i = 0; i < n; i++) {

                     v2 = r_object(p); //D-2

                     if ( v2 == NULL ) {

                            if (!PyErr_Occurred())

                                   PyErr_SetString(PyExc_TypeError,

                                          "NULL object in marshal data");

                            Py_DECREF(v);

                            v = NULL;

                            break;

                     }

                     PyTuple_SET_ITEM(v, (int)i, v2);

              }

              return v;

从D-1,D-2我们知道这个tuple有两个元素,第一个的标志符是0x4e, Type_None,

第二个是0x73 = ‘s’ ,字符串型,长度为0x0b,内容就是”hello world”,

下面我们从B-7知道应该是names了,是tuple 类型,长度为0,

同理,varnames = freevars = cellvars = (),

Filename = “c:temphello.py”

Name = “<module>”

Firstlineno = 1

Lnotab的标志符为0x74, TYPE_INTERNED,这种类型的跟TYPE_STRING是一样处理的。

好了这整个文件我们都过了一遍了,很显然接下来要把重点放到code的内容上来,这个内容一共9个字符:64 01 00 47 48 64 00 00 53 , 什么意思???

请先阅读这段东西:

http://www.python.org/workshops/1998-11/proceedings/papers/aycock-211/aycock211.html

显然这段东西应该是opcode 加上操作数,opcode的定义在哪里???

找啊找,我们找到了,在/include下面有opcode.h的说明,我们对着说明来看看,

#define LOAD_CONST 100  /* Index in const list */

第一个是0x64 = 100 ,100对应得是LOAD_CONST ,后面有注明是index in const list,后面

那个参数是00 01 (小端在前) ,我们看看前面的consts的第二个内容是”hello world”,对不对?

所以这个就是 64 01 00 == LOAD_CONST “ hello world”

第二个是0x47 = 71,  没参数的, #define PRINT_ITEM      71 , 没错,就是它 ,

第三个是0x48 = 72  没参数的, #define PRINT_NEWLINE       72 没错,就是它

第4个又是0x64 = 100,那个参数是00 00 (小端在前) ,我们看看前面的consts的第1个内容是None,对不对?

第5个是0x53 = 83,#define RETURN_VALUE 83 , 哈哈,全对,我们没有指定返回值, 翻译成python的汇编就是:

              LOAD_CONST “ hello world”

              PRINT_ITEM

              PRINT_NEWLINE

              LOAD_CONST None

              RETURN_VALUE

好,我们终于实现了pyc 和 pyo 的disassemble, 但离我们的decompiler好像还有很长的距离要走,没关系,慢慢来

(待序)

[课程]Android-CTF解题方法汇总!

收藏
免费 7
支持
分享
最新回复 (27)
雪    币: 398
活跃值: (343)
能力值: (RANK:650 )
在线值:
发帖
回帖
粉丝
2
Team509的作品和文章,都不是讲“破解”的,却是比“破解”更透彻
建议段老板做个他们的专题,整理一下他们的作品和文章
2006-12-8 22:34
0
雪    币: 116
活跃值: (220)
能力值: ( LV12,RANK:370 )
在线值:
发帖
回帖
粉丝
3
学习
2006-12-8 23:31
0
雪    币: 846
活跃值: (221)
能力值: (RANK:570 )
在线值:
发帖
回帖
粉丝
4
“我们在网络安全这个行业也混了好多年了”

年轻的强人叫大大,他们那些混了好多年的, 就是老大。
2006-12-8 23:53
0
雪    币: 44229
活跃值: (19960)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
5
最初由 shoooo 发布
Team509的作品和文章,都不是讲“破解”的,却是比“破解”更透彻
建议段老板做个他们的专题,整理一下他们的作品和文章


发现Team509中人确实是牛,共享精神也很值得学习,如果shoooo 能把他们请到论坛,现场给大家指指路,那真是一大幸事
2006-12-9 10:09
0
雪    币: 263
活跃值: (10)
能力值: ( LV9,RANK:210 )
在线值:
发帖
回帖
粉丝
6
是啊
高级的看过了
简单的受益了
比如{
逆向工程的秘密-中文版
}
菜鸟编写CRME宝刀
2006-12-9 10:49
0
雪    币: 5275
活跃值: (451)
能力值: (RANK:1170 )
在线值:
发帖
回帖
粉丝
7
好啊,python
2006-12-9 12:07
0
雪    币: 258
活跃值: (230)
能力值: ( LV12,RANK:770 )
在线值:
发帖
回帖
粉丝
8
shoooooooooooooooooo
我要注册.....
2006-12-9 12:46
0
雪    币: 1241
活跃值: (120)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
9
python 的 decompiler 叫 decompyle ,其利用的扫描和解析模块叫 spark。这个模块在 python 中使用相当广泛。作者非常厉害。
decompyle 支持到 2.3 后不再免费提供,而是在网上搞了个反编译服务,反编译一个软件要好多欧元。#-(
不过在他的 decompyle 基础上通过只修改规则定义就可以支持到 2.4 ,当然对 python 2.4 及 2.5 以后出现新特性需要自己定义规则,另外这种简单方法修改得到的 decompyle 对后来 python 出现的一些结构优化不能很好的解决。所以如果想弄个比较好的 decompiler 就需要自己处理优化出现的问题了。
另外曾有个老外在2.3源码基础之上修订过一个版本使其支持到了2.4,在网上 emaillist中公布过,我通过狗狗搜索到了,不过我看了他的代码之后,很遗憾的是虽然他的代码N长,但并没有很好的解决问题。
2006-12-9 17:14
0
雪    币: 1241
活跃值: (120)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
10
另外,想补充说一点的是 python 是开源的,decompyle也曾是开源的,这些源代码都很有研究价值。
2006-12-9 17:31
0
雪    币: 331
活跃值: (56)
能力值: ( LV13,RANK:410 )
在线值:
发帖
回帖
粉丝
11
对写自己的脚本很有帮助,thx 4 share
2006-12-9 20:39
0
雪    币: 367
活跃值: (42)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
最初由 ljtt 发布
python 的 decompiler 叫 decompyle ,其利用的扫描和解析模块叫 spark。这个模块在 python 中使用相当广泛。作者非常厉害。
decompyle 支持到 2.3 后不再免费提供,而是在网上搞了个反编译服务,反编译一个软件要好多欧元。#-(
不过在他的 decompyle 基础上通过只修改规则定义就可以支持到 2.4 ,当然对 python 2.4 及 2.5 以后出现新特性需要自己定义规则,另外这种简单方法修改得到的 decompyle 对后来 python 出现的一些结构优化不能很好的解决。所以如果想弄个比较好的 decompiler 就需要自己处理优化出现的问题了。
另外曾有个老外在2.3源码基础之上修订过一个版本使其支持到了2.4,在网上 emaillist中公布过,我通过狗狗搜索到了,不过我看了他的代码之后,很遗憾的是虽然他的代码N长,但并没有很好的解决问题。

都被你说到了,不过我没有找到这个老外的源码
那些自定义的规则有什么规律和要求没有?
2006-12-9 21:02
0
雪    币: 10
活跃值: (130)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
13
最初由 ljtt 发布
python 的 decompiler 叫 decompyle ,其利用的扫描和解析模块叫 spark。这个模块在 python 中使用相当广泛。作者非常厉害。
decompyle 支持到 2.3 后不再免费提供,而是在网上搞了个反编译服务,反编译一个软件要好多欧元。#-(
不过在他的 decompyle 基础上通过只修改规则定义就可以支持到 2.4 ,当然对 python 2.4 及 2.5 以后出现新特性需要自己定义规则,另外这种简单方法修改得到的 decompyle 对后来 python 出现的一些结构优化不能很好的解决。所以如果想弄个比较好的 decompiler 就需要自己处理优化出现的问题了。
另外曾有个老外在2.3源码基础之上修订过一个版本使其支持到了2.4,在网上 emaillist中公布过,我通过狗狗搜索到了,不过我看了他的代码之后,很遗憾的是虽然他的代码N长,但并没有很好的解决问题。

能否共享一份 decompyle 的源码?我最近也在写一款脚本的 decompiler,在跳转处理上出现了一些问题,想参考一下它是怎么处理的。还有,PB的程序也是以中间代码出现的吗?老大是如何解决条件跳转判断问题的?一般跳转到的地方都是二次扫描后再填充的。先谢过了~
2006-12-10 01:30
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
惭愧,惭愧,我们在很多方面(特别是软件破解这方面)跟坛子里的很多老大比起来,水平还差得远了,指路什么的,完全谈不上,说句实话,我们当年也是全靠看雪论坛指路,才能对这个方面有所了解.
今后还得请坛子里的老大们多多指教,
多谢ljtt兄指路,那个decompyle我们现在正在研究,
希望可以尽快的作出对2.4,2.5的版本来.
hermit, DarkNess0ut 兄,这东西可在这里下载:
http://ftp.debian.org/debian/pool/main/d/decompyle/decompyle_2.3.2.orig.tar.gz

最初由 kanxue 发布
发现Team509中人确实是牛,共享精神也很值得学习,如果shoooo 能把他们请到论坛,现场给大家指指路,那真是一大幸事
2006-12-13 17:48
0
雪    币: 367
活跃值: (42)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
最初由 wooshi 发布
惭愧,惭愧,我们在很多方面(特别是软件破解这方面)跟坛子里的很多老大比起来,水平还差得远了,指路什么的,完全谈不上,说句实话,我们当年也是全靠看雪论坛指路,才能对这个方面有所了解.
今后还得请坛子里的老大们多多指教,
多谢ljtt兄指路,那个decompyle我们现在正在研究,
希望可以尽快的作出对2.4,2.5的版本来.
hermit, DarkNess0ut 兄,这东西可在这里下载:
........

谢谢,decompyle_2.3.2很早就已经下载到了
我要的是ljtt说的2.4的修改版,对那些自定义的规则不是很了解
2006-12-13 18:20
0
雪    币: 217
活跃值: (15)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
16
ljtt 我的偶像级人物啊
2006-12-13 18:25
0
雪    币: 146
活跃值: (33)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
17
这样的站很喜欢.......
2006-12-13 18:48
0
雪    币: 146
活跃值: (33)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
18
Forbidden
You don't have permission to access /download/docs/programming/ on this server.
2006-12-13 18:55
0
雪    币: 146
活跃值: (33)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
19

虽然不懂,
但是想持续一下关注..
2006-12-14 21:12
0
雪    币: 217
活跃值: (15)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
20
这个页憷荇憷牦问题,洳槟ie7,扉浏览,靓憷韩冼懋体,晕啊

这个页是不是有问题,我用ie7,一浏览,就是韩文字体,晕啊
2006-12-15 17:42
0
雪    币: 44229
活跃值: (19960)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
21
转帖:Python的decompile问题(2)
URL:http://www.team509.com/modules.php?name=News&file=article&sid=45


上文我们讲到我们已经明白disassemble是怎么产生的了,现在我们来讨论如何从disassemble到decompile的转化,事实上在python 2.3以前是有一个工具来完成这个转化,decompyle , 这个工具可以从这里下载:


http://ftp.debian.org/debian/pool/main/d/decompyle/decompyle_2.3.2.orig.tar.gz


遗憾的是,2.3版本以后这个工具就不支持了,我们先研究一下decompyle,然后再实现我们的支持2.3以后的版本。首先声明一下,我对python和编译原理都不是很熟悉,所以文章中可以谬误难免,如果发现错误,请及时地通知我,我好做修改。




研究decompyle,就不得不研究spark,spark是decompyle的核心,decompyle事实上是spark的一个应用。Spark是一个用python写的,为小型语言准备的编译器的框架(framework),这样说起来真拗口,你把它理解为是一个python写的,功能主要是lex&yacc的功能,基本上就对了,spark主要分成四个部分:词法分析,语法分析,语义分析,代码生成,下面我们分别简单的介绍一下这四个部分:


词法分析:GenericScanner 类,你要用这个类的时候,需要为这个类写一个子类,同时定义一堆以”t_”开头的函数来实现具体的词法,比如说你要定义一个 digit , 在 lex中是这样:

digit [0-9] , 在spark中需要定义成这样:




class DigitScanner(GenericScanner):


def __init__(self):


GenericScanner.__init__(self)


def t_digit(self,s):


r’d’


t= Token(type=’digit’,attr=s)


self.rv.append(t)





语法分析:这个是整个spark最为古怪的部分了,因为它把所有的语法规则都作为相应函数的__doc__,这个前两天我看decompile的src的时候,把我给搞糊涂了,因为我在整个程序代码里没有找到语法规则,后来用debugger跟了半天才发现这个秘密,看spark文档的时候才特别对这个问题注意了,举一例说明之:


def p_funcdef(self, args):


'''


stmt ::= funcdef


funcdef ::= mkfunc designator


load_closure ::= load_closure LOAD_CLOSURE


load_closure ::= LOAD_CLOSURE


'''


语法分析与词法分析类似,你要加你自己的语法规则的话,需要写一个类,作为GenericParser的子类,同时有定义语法规则的函数都以”p_”开始。语法分析的主要目的是建立AST树





语义分析:在spark中,这个功能的framework由GenericASTTraversal实现,主要用来遍历语法分析生成的ast树,做语法检查。同词法分析,语法分析一样,你要使用它,需要写一个GenericASTTraversal的子类,处理函数需要以”n_”开头。





代码生成:这个含义很明显了,生成最终的代码,在spark中,和语义分析一样,都是由GenericASTTraversal实现,方法都一样,遍历语法分析生成的ast树,但主要用途不一样而已。





好了,我们简单的介绍到这里,有不明白的看这里:


http://pages.cpsc.ucalgary.ca/~aycock/spark/paper.pdf


下面我们看看decompyle是怎样利用spark生成python的decompile的。


首先,我们要有个概念,我们只要能成功的定义出词法分析规则和语法分析规则,那么


我们就可以利用spark来实现decompile.


我们来看看如何定出这些规则,下面以例子的形式来说明(decompyle的一个例子):


def kwfunc(**kwargs):


print kwargs.items()





def argsfunc(*args):


print args





def no_apply(*args, **kwargs):


print args


print kwargs.items()


argsfunc(34)


foo = argsfunc(*args)


argsfunc(*args)


argsfunc(34, *args)


kwfunc(**kwargs)


kwfunc(x=11, **kwargs)


no_apply(*args, **kwargs)


no_apply(34, *args, **kwargs)


no_apply(x=11, *args, **kwargs)


no_apply(34, x=11, *args, **kwargs)


no_apply(42, 34, x=11, *args, **kwargs)





no_apply(1,2,4,8,a=2,b=3,c=5)





编译后利用decompyle 进行 disassemble得到:


6 LOAD_CONST 'code_object kwfunc'


9 MAKE_FUNCTION_0 ''


12 STORE_NAME 'kwfunc'


18 LOAD_CONST 'code_object argsfunc'


21 MAKE_FUNCTION_0 ''


24 STORE_NAME 'argsfunc'


30 LOAD_CONST 'code_object no_apply'


33 MAKE_FUNCTION_0 ''


36 STORE_NAME 'no_apply'


42 LOAD_NAME 'no_apply'


45 LOAD_CONST 1


48 LOAD_CONST 2


51 LOAD_CONST 4


54 LOAD_CONST 8


57 LOAD_CONST 'a'


60 LOAD_CONST 2


63 LOAD_CONST 'b'


66 LOAD_CONST 3


69 LOAD_CONST 'c'


72 LOAD_CONST 5


75 CALL_FUNCTION_772 ''


78 POP_TOP ''


79 LOAD_CONST ''


82 RETURN_VALUE ''





6 LOAD_FAST 'kwargs'


9 LOAD_ATTR 'items'


12 CALL_FUNCTION_0 ''


15 PRINT_ITEM ''


16 PRINT_NEWLINE ''


17 LOAD_CONST ''


20 RETURN_VALUE ''





6 LOAD_FAST 'args'


9 PRINT_ITEM ''


10 PRINT_NEWLINE ''


11 LOAD_CONST ''


14 RETURN_VALUE ''





6 LOAD_FAST 'args'


………………………………………………….





220 CALL_FUNCTION_VAR_KW_258 ''


223 POP_TOP ''


224 LOAD_CONST ''


227 RETURN_VALUE ''





可以看到,disassemble的指令的格式基本上是:





Opcode


或 opcode+一个参数





Decompyle为了简便起见,把一些opcode做了一些变形,比如说上面标有220的这行:


220 CALL_FUNCTION_VAR_KW_258 ''


事实上应该是 220 CALL_FUNCTION_VAR_KW 258





在Decompyle中,每一个token都有四个属性:opname ,oparg,pattr,offset, opname就是每一行前面那个词,比如LOAD_CONST, LOAD_FAST,oparg就是后面跟的那个short型的整数了,pattr是把有些opcode用到的局部变量,名称从pyc或pyo中查出来,填到这个地方,还有就是计算跳转型的opcode)的跳转地址计算出来填进去,具体例子可以看这系列文章的(1)的做法,offset是这条opcode在这条指令流的偏移地址,比如说上面这个(220 CALL_FUNCTION_VAR_KW 258)的220就是offset.





词法分析规则是相当的简单,因为一条opcode无非是1 byte 或 3 bytes长,一个字节一个字节的读进来,有参数的(opcode > 90) 再读两字节,然后按照不同的opcode属性,比如说用到局部变量的,查查局部变量表,填到pattr里去,我们就得到了排列好的tokens了。





下面要进行的工作就是把一维的tokens列表,变成2维的AST树,这个除了语法分析规则要我们提供以外,其他的工作都由spark帮我们做好了,我们考虑语法分析规则就行了。


Decompiler的语法分析规则其实就是确定哪些opcode的组合对应哪一条高级语言的语句,比如说:


LOAD_CONST ‘hello world’


PRINT_ITEM


PRINT_NEWLINE


你就知道是 print “ hello world”


我们把它抽象出来,用spark的定义语义规则形式就是:


def p_print(self, args):


'''


stmt ::= print_stmt


stmt ::= print_stmt_nl


stmt ::= print_nl_stmt


print_stmt ::= expr PRINT_ITEM


print_nl_stmt ::= PRINT_NEWLINE


print_stmt_nl ::= print_stmt print_nl_stmt


'''





遇到这样的opcode组合后,代表这部分的AST树可归约成: print_stmt_nl,然后在decompyle的walk.py中你会发现:


'print_stmt_nl': ( '%|print %[0]C ', (0,1, None) ),





有了这条规则,decompyle在输出的就会直接把print_stmt_nl这个node输出为 print 加上参数。





好了,我们大概知道decompyle是怎么干的了,如果我们要给decompyle做一个扩展,让它能够符合2.4,2.5的话,那么有哪些地方需要修改呢?


1. Dis部分,这部分可能要把2.4,2.5新出现的opcode(有么?),加进去,简单。


2. 词法分析阶段,跟Dis部分差不多,新出现的opcode对应得token处理加进去,简单。


3. 语法分析阶段,这个复杂了,pyc应该和以前的差不多,但如果python采用了新的优化技术的话,pyo的处理(语法规则)可能很多要重新写过了,这个比较花时间了。


4. Output(代码生成),简单,小修改即可。





下面我们就研究研究2.4,2.5跟2.3,2.2的处理有什么不同吧。


(待续)
2006-12-19 09:10
0
雪    币: 44229
活跃值: (19960)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
22
Python的decompile问题(3)
URL:http://www.team509.com/modules.php?name=News&file=article&sid=46

Python的decompile问题(3)

现在我们开始修改decompyle,来使它能够符合python2.4, 2.5.




(1) 用decompyle-2.3.2.orig这个版本为基础进行修改,首先在magics.py中加入(一看就知道加到magics.py的什么地方):


__build_magic(62061): '2.4',


__build_magic(62131): '2.5',


在dis_files.py中加入:


'2.4': dis('2.4’, 'dis_24'),


'2.5': dis('2.5', 'dis_25'),


在marshal_files.py中加入:





'2.4': 'marshal_24',


'2.5': 'marshal_25',





分别从安装2.4,2.5版本的python 的lib目录下(c:python24lib,c:python25lib)把dis.py和opcode.py拷贝过来,命名为dis_24.py,opcode_24.py, dis_25.py,opcode_25.py,并把两个dis文件里的import opcode做相应修改。


(2) 把python2.4,python2.5的src包里的marshal.c拷贝过来,命名为marshal_24.c, marshal_25.c,并照着marshal_23.c格式修改其中内容,就是最后3行啦。再修改setup.py中的内容,把marshal_24.c, marshal_25.c加进去。


(3) 下面就是修改parser.py和walker.py,这个内容比较多,我试举一例来说明如何修改,先安装前面修改的这个decompyle到python2.5,再用python2.5编译decompyle的test_import.py,到test_import.pyc, 用我们新修改的decompyle试一下,发现不能用了,


错误是:Syntax error at or near `IMPORT_NAME' token at offset 12


我们看看错误周围的test_import.pyc的asm:


6 LOAD_CONST -1


9 LOAD_CONST ''


12 IMPORT_NAME 'sys'


15 STORE_NAME 'sys'


我们再比较2.2的相应得asm:


15 LOAD_CONST ''


18 IMPORT_NAME 'sys'


21 STORE_NAME 'sys'


发现一个import sys对应得asm多了一条:LOAD_CONST -1,


看来我们需要在parser.py中的p_import20函数中添加:


stmt ::= importstar25


stmt ::= importstmt25


stmt ::= importfrom25





importstmt25 ::= LOAD_CONST LOAD_CONST import_as


importstar25 ::= LOAD_CONST LOAD_CONST IMPORT_NAME IMPORT_STAR


importfrom25 ::= LOAD_CONST LOAD_CONST IMPORT_NAME importlist2 POP_TOP


就是把原来每条实用规则前面多加一条LOAD_CONST就行了。


再在walker.py的importstmt2规则下面添加:


'importstmt25': ( '%|import %c ', 2),


'importstar25': ( '%|from %[2]{pattr} import * ', ),


'importfrom25': ( '%|from %[2]{pattr} import %c ', 3 ),


Hehe,照着上面的importstmt2,还是很容易写的。


好了,把修改过的parser.py,walker.py存盘,运行decompyle 来 decompile test_import.pyc,OK,这次一点错都没有了。





这个修改过程还比较多,一个完整的版本大家还得等一段时间了。不过,照上面修改过的版本对一般简单的pyc,pyo已经可以用了。
2006-12-19 09:11
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
谢谢楼主的介绍,很有意思
2006-12-19 09:42
0
雪    币: 217
活跃值: (15)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
24
高淫啊,佩服啊
2006-12-19 09:50
0
雪    币: 10
活跃值: (130)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
25
这工作相当于自己写一款脚本引擎了~

思路非常有创意,俺咋木有想到涅~
2006-12-19 13:59
0
游客
登录 | 注册 方可回帖
返回
//