首页
社区
课程
招聘
[原创]用 Python 反编译 Python 软件
发表于: 2010-4-21 16:28 45505

[原创]用 Python 反编译 Python 软件

2010-4-21 16:28
45505

【文章标题】: 用 Python 反编译 Python 软件
【文章作者】: Ptero
【软件名称】: ****
【下载地址】: ****
【加壳方式】: UPX
【保护方式】: 序列号,重启验证
【使用工具】: 7-zip, LordPE, Python, WinHex
【操作平台】: Windows, Linux
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪软件安全论坛, 转载请注明作者并保持文章的完整, 谢谢!

    论坛讨论 Python 的文章似乎比较少。此文权当抛砖引玉,不当之处请多指教。

    关于 Py 的反编译,网上流传的说法是,2.4 版本以后的比较困难。因为针对低版本,有一些开源代码可以实现反编译。到 2.4  以后,原作者要么不更新了,要么开始收费了。只有少数牛人能修改旧版的代码继续反编译下去。
    本文提供了一种方法,使得在没有反编译器的情况下,也能分析用 Python 写的软件。

    下面开始。

    试炼软件是用 upx 加壳的,upx -d 简单脱掉。
    再用 peid 查看, 发现如下信息:
Microsoft Visual C++ 7.0 Method2 [ZIP SFX]。
很少见。再用OD加载,查看字符串,发现是 py2exe 生成的。
这种文件,可以用 7zip 当作压缩包打开,里面可以看到一堆编译好的 pyc 和 pyo 文件。从名称来看,那些应该是 Python 自带的库文件,与程序无关。这里从库文件的名称可以看出是 Python 2.4 版本。

    程序自带了一个 pyo 文件,200 多 K。网上找到能免费反编译 Python 2.4 以后版本的,只有 decompyler 2.3 的一个修改版(2.3 开源,后续版本收费了), 还有就是 UnPyc。
    decompyler 的修改版,我没有找到。UnPyc,貌似只支持 Linux 的。在学校的 Linux 服务器上安装上,反编译却得到一堆错误。反汇编(生成 bytecode 的助记符,相当于汇编代码)倒是可以。

    在反汇编当中查找关键字符串,没找到。我把所有的函数名称看了一遍,也没有可疑的。看来那个 200 多 K 的文件,不是关键代码所在。还得另谋出路。

    关键代码不在附带的库里面,那还能在哪里呢?只能在程序自身了!一定是 py2exe 把那些代码编到 exe 里面了。
    有2种可能:1、编成 native code。2、编成 Python bytecode,通过 Python 虚拟机执行。在OD中跟踪一下,发现执行到关键代码的时候,已经处在虚拟机的代码之中了。所以排除 1 的可能性。

    下面,要找出关键代码的藏身之处。代码会藏在哪呢?.text 段?不大可能,因为那里都是 sfx 的代码。.data 段?也不可能,因为那里的数据只会被 sfx 用到。在 zip 压缩包里面?没找到可疑文件。那么,只剩下 .rsrc 了!

    用 LordPE 查看资源段(这里不用 Reshacker,因为它 dump 下来的不正确),找到 “Python24.dll”, “PZIB.PYD”,还有就是 “PYTHONSCRIPT”。光是看名字就很可疑了。把它 dump 下来,在 WinHex 当中查找关键字符串,果然找到了。
    下面就是要如何反编译这个 script 了。

    网上没有找到相关资料。于是下载了一份 py2exe 源码,找到这里:

>>>import marshal
>>>mylist=marshal.load(open("dumpfile", "r"))
>>>mylist
[<code object ? at 0xb75df650, file "D:\python24\lib\site-packages\py2exe\boot_common.py", line 44>, <code object ? at 0xb75df698, file "<install zipextimporter>", line 1>, <code object ? at 0xb75f4ad0, file "****.py", line 2>]
import dis as pydis
import types

code = None
def read(filename):
    f = open(filename)
    content = f.read()
    global code
    code = compile(content, filename, 'exec')
    f.close()

def find_code(code, name):
    for item in code.co_consts:
        if isinstance(item, types.CodeType):
            if item.co_name == name:
                return item
    return None
    
def dis(code_name=None):
    if code_name is None:
        co = code
        pydis.dis(co)
        return co
    names = code_name.split(".")
    co = code
    for name in names:
        co = find_code(co, name)
        if not co:
            print '%s is not a valid name' % code_name
    if co:
        print ("   byte code for %s  " % code_name).center(60, '*')
        pydis.dis(co)
>>>mylist[2].co_consts:
(1, None, ('gdi',), ('EnumProcesses',), ('Button',), ('Editbox',), ('Textin',), ('LOWORD', 'HIWORD', 'RGB', 'RECT'), ('Msg',), ('Listview',), ('Listbox',), ('Combobox',), ('Checkbox',), ('Radiobox',), ('Treeview',), ('StaticText',), ('Groupbox',), ('ContextMenu',), ('Menu',), ('SystemCursor',), ('GetVirtualScreenSize',), ('static',), ('Queue',), ('latin_1', 'gbk', 'utf_8', 'ascii', 'gb2312', 'gb18030'), ('StringIO',), ('dbapi2',), ('WinExec',), ('CF_TEXT', 'GHND'), 'Display_REG_Dialog', <code object Display_REG_Dialog at 0xb7cc9f50, file "****.py", line 50>, 'Display_INPUT_Dialog', <code object Display_INPUT_Dialog at 0xb7cd7188, file "****.py", line 96>, 'Display_INPUT_TIME_Dialog', <code object Display_INPUT_TIME_Dialog at 0xb7cd7380, file "****.py", line 134>, 'Setup_Find_Dialog', <code object Setup_Find_Dialog at 0xb7cd7530, file "****.py", line 168>, 'Get_yo2_user_Dialog', <code object Get_yo2_user_Dialog at 0xb7cd7770, file "****.py", line 209>, 'Edit_User_Dialog', <code object Edit_User_Dialog at 0xb7cd79b0, file "****.py", line 279>, 'Add_User_Dialog', <code object Add_User_Dialog at 0xb7cd7d10, file "****.py", line 332>, 'Add_Cate_Dialog', <code object Add_Cate_Dialog at 0xb7cd7f50, file "****.py", line 546>, 'Get_Pic_Dialog', <code object Get_Pic_Dialog at 0xb7cdd260, file "****.py", line 602>, 'Get_MPic_Dialog', <code object Get_MPic_Dialog at 0xb7cdd530, file "****.py", line 934>, 'Select_ExportType_Dialog', <code object Select_ExportType_Dialog at 0xb7cdd9b0, file "****.py", line 1276>, 'Setup_PROXY_Dialog', <code object Setup_PROXY_Dialog at 0xb7cddad0, file "****.py", line 1412>, 'Update_Dialog', <code object Update_Dialog at 0xb7cddba8, file "****.py", line 1564>, 'MyWindow', <code object MyWindow at 0xb7cef2f0, file "****.py", line 1698>, '')
>>>import sdis
>>>sdis.code = mylist[2]
>>>sdis.dis("MyWindow.__init__")
************   byte code for MyWindow.__init__  ************
(省略部分代码)
1732         439 LOAD_CONST               5 (0)
            442 STORE_FAST               8 (conf_user_tag)

1733         445 LOAD_GLOBAL             40 (os)
            448 LOAD_ATTR               35 (path)
            451 LOAD_ATTR               41 (isfile)
            454 LOAD_CONST              21 ('****.ini')
            457 CALL_FUNCTION            1
            460 JUMP_IF_FALSE           48 (to 511)
            463 POP_TOP             

1734         464 SETUP_EXCEPT            28 (to 495)

1735         467 LOAD_GLOBAL             42 (dict4ini)
            470 LOAD_ATTR               43 (DictIni)
            473 LOAD_CONST              21 ('****.ini')
            476 CALL_FUNCTION            1
            479 LOAD_FAST                0 (self)
            482 STORE_ATTR              44 (conf_user)

1736         485 LOAD_CONST              22 (1)
            488 STORE_FAST               8 (conf_user_tag)
            491 POP_BLOCK           
            492 JUMP_ABSOLUTE          518

1737     >>  495 POP_TOP             
            496 POP_TOP             
            497 POP_TOP             

1738         498 LOAD_CONST               5 (0)
            501 STORE_FAST               8 (conf_user_tag)
            504 JUMP_ABSOLUTE          518
            507 END_FINALLY         
            508 JUMP_FORWARD             7 (to 518)
        >>  511 POP_TOP             

1740         512 LOAD_CONST               5 (0)
            515 STORE_FAST               8 (conf_user_tag)

1741     >>  518 LOAD_FAST                8 (conf_user_tag)
            521 LOAD_CONST               5 (0)
            524 COMPARE_OP               2 (==)
            527 JUMP_IF_FALSE           50 (to 580)
            530 POP_TOP             

1742         531 LOAD_GLOBAL             42 (dict4ini)
            534 LOAD_ATTR               43 (DictIni)
            537 LOAD_CONST              21 ('****.ini')
            540 CALL_FUNCTION            1
            543 LOAD_FAST                0 (self)
            546 STORE_ATTR              44 (conf_user)

1743         549 LOAD_CONST              23 ('')
            552 LOAD_FAST                0 (self)
            555 LOAD_ATTR               44 (conf_user)
            558 LOAD_ATTR               45 (config)
            561 STORE_ATTR              46 (regnum)

1744         564 LOAD_FAST                0 (self)
            567 LOAD_ATTR               44 (conf_user)
            570 LOAD_ATTR               47 (save)
            573 CALL_FUNCTION            0
            576 POP_TOP             
            577 JUMP_FORWARD             1 (to 581)
        >>  580 POP_TOP             

1745     >>  581 LOAD_FAST                0 (self)
            584 LOAD_ATTR               44 (conf_user)
            587 LOAD_ATTR               48 (has_key)
            590 LOAD_CONST              24 ('config')
            593 CALL_FUNCTION            1
            596 JUMP_IF_TRUE            32 (to 631)
            599 POP_TOP             

1746         600 LOAD_CONST              23 ('')
            603 LOAD_FAST                0 (self)
            606 LOAD_ATTR               44 (conf_user)
            609 LOAD_ATTR               45 (config)
            612 STORE_ATTR              46 (regnum)

1747         615 LOAD_FAST                0 (self)
            618 LOAD_ATTR               44 (conf_user)
            621 LOAD_ATTR               47 (save)
            624 CALL_FUNCTION            0
            627 POP_TOP             
            628 JUMP_FORWARD             1 (to 632)
        >>  631 POP_TOP             

1748     >>  632 LOAD_FAST                0 (self)
            635 LOAD_ATTR               44 (conf_user)
            638 LOAD_ATTR               45 (config)
            641 LOAD_ATTR               48 (has_key)
            644 LOAD_CONST              25 ('regnum')
            647 CALL_FUNCTION            1
            650 JUMP_IF_TRUE            32 (to 685)
            653 POP_TOP             

1749         654 LOAD_CONST              23 ('')
            657 LOAD_FAST                0 (self)
            660 LOAD_ATTR               44 (conf_user)
            663 LOAD_ATTR               45 (config)
            666 STORE_ATTR              46 (regnum)

1750         669 LOAD_FAST                0 (self)
            672 LOAD_ATTR               44 (conf_user)
            675 LOAD_ATTR               47 (save)
            678 CALL_FUNCTION            0
            681 POP_TOP             
            682 JUMP_FORWARD             1 (to 686)
        >>  685 POP_TOP             

1751     >>  686 LOAD_CONST              23 ('')
            689 LOAD_FAST                0 (self)
            692 STORE_ATTR              49 (regno)

1752         695 LOAD_CONST              23 ('')
            698 LOAD_FAST                0 (self)
            701 STORE_ATTR              50 (regno2)

1753         704 LOAD_FAST                0 (self)
            707 LOAD_ATTR               51 (get_reg_no_true)
            710 CALL_FUNCTION            0
            713 LOAD_FAST                0 (self)
            716 STORE_ATTR              52 (reg_true)

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 8
支持
分享
最新回复 (11)
雪    币: 291
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
Python 能加密吗?还是混淆?
2010-4-21 17:57
0
雪    币: 370
活跃值: (15)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
3
如此强大,楼主不妨试着做个工具出来
Py写exe还是比较少见,脚本多用于管理,而不是商业软件
2010-4-21 18:30
0
雪    币: 202
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
那有那么麻烦,直接改成.zip,解开
dis一下,从字节码直接就可分析出来
如果想反成src,把字节码发给我,我帮你搞定
这个明显是没经过处理的,如果经过处理要麻烦一些
2010-4-22 12:51
0
雪    币: 452
活跃值: (72)
能力值: ( LV9,RANK:330 )
在线值:
发帖
回帖
粉丝
5
zip解开之后,得到的只是python的运行库文件,没多少用。
py2exe把关键代码编译到PE资源里面了,用zip解开是得不到的。
而且那个资源的格式和pyc还不一样,还要手动修改一下。
2010-4-22 12:57
0
雪    币: 202
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
用 Python 做商业软件的还是比较少见吧, 那个 QT 库太大了
2010-5-9 20:53
0
雪    币: 158
活跃值: (404)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
在您的 http://bbs.pediy.com/showthread.php?t=111428&highlight=py2exe
文章中,“用 WinHex 修改 dump 下来的文件,把添加的东西都去掉,这样就只剩下了 code_bytes。” ,具体是怎么修改啊?我修改了半天,LOAD结果总是错误的,开头我因该是处理好了,但结尾不知道怎么处理....,他报告“EOFError: EOF read where object expected”。
2010-5-12 05:26
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
学习了!支持看雪~~顶楼主~~谢谢分享
2010-5-12 10:26
0
雪    币: 340
活跃值: (927)
能力值: ( LV9,RANK:220 )
在线值:
发帖
回帖
粉丝
9
LZ,你这是不给python程序员活路啊。
2010-5-12 16:10
0
雪    币: 178
活跃值: (159)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
10
膜拜lz,楼主太强大了
2010-5-14 16:26
0
雪    币: 175
活跃值: (491)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
11
谢谢楼主,收了~~~~~~~~~~~
2017-12-22 17:19
0
雪    币: 50
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
下意识看了下时间
2017-12-22 17:30
0
游客
登录 | 注册 方可回帖
返回
//