如何破解一个Python虚拟机壳并拿走12300元

之前在群里看到有人发了一个挑战 , 号称将 5 ETH 的私钥放在了加密的代码中 , 只要有人能解密就可以取走 , 所以我又管不住自己这双手了 。
前言某天在群里看到一个大佬看到另一个大佬的帖子而发的帖子的截图 , 如下所示:
如何破解一个Python虚拟机壳并拿走12300元文章插图
pediy
不过当我看到的时候已经过去了大概720小时 在查看该以太币交易记录的时候 , 发现在充值之后十几小时就被提走了 , 可能是其他大佬也可能是作者自己 。 虽然没钱可偷 , 但幸运的是 pyc 的下载地址依然有效 , 所以我就下载下来研究了一下 。
初步分析首先在专用的实验虚拟机里运行一下 , 程序执行没有问题:
$ python2 ether_v2.pycInput UR answer: whateverYou are too vegetable please try again!然后看看文件里是否有对应的字符串信息:
$ grep vegetable ether_v2.pyc很好 , 屁都没有 , 看来字符串也混淆了 。
目前市面上有一些开源的 pyc 还原工具 , 比如:

  • uncompyle6
  • pycdc
但是看作者的自信 , 应该是有信心可以抗住的 , 事实证明也确实可以 。
Python 反汇编既然没有现成工具能用 , 那么我们就需要通过自己的方法来对代码逻辑进行还原 。 要分析代码逻辑第一步至少要把字节码还原出来 , 使用 dis 模块可以实现:
import disimport marshalwith open('ether_v2.pyc', 'rb') as f: magic = f.read(4) timestamp = f.read(4) code = marshal.load(f) dis.disassemble(code).pyc 文件本身是字节码的 marshal 序列化格式 , 在 Python2.7 中加上 8 字节的 pyc 头信息 。 一般通过上面的代码即可打印出文件中的字节码信息 。 当然 , 这个事情并不一般:
$ python2 try1.pyTraceback (most recent call last):File "try1.py", line 9, in dis.disassemble(code)File "/usr/lib/python2.7/dis.py", line 64, in disassemblelabels = findlabels(code)File "/usr/lib/python2.7/dis.py", line 166, in findlabelsoparg = ord(code[i]) + ord(code[i+1])*256IndexError: string index out of range在 dis 模块中直接异常退出了 , 有点意思 。 查看 dis 的源码 , 查看出错的部分 , 发现在 co.co_code 、 co.co_names 、 co.co_consts 等多个地方都出现了下标溢出的 IndexError。 不管是什么原因 , 我们先把这些地方 patch 掉:
如何破解一个Python虚拟机壳并拿走12300元文章插图
patch
这回就能看到输出的 Python 字节码了 , 如下:
$ ./dec.py --pyc ether_v2.pyc30 JUMP_ABSOLUTE27643 LOAD_CONST65535 (consts[65535])6 <218>506739 SET_ADD1801612 IMPORT_NAME8316 (names[8316])15 STOP_CODE16 LOAD_CONST33 (8)19 COMPARE_OP2 ('==')22 POP_JUMP_IF_FALSE9925 LOAD_FAST28 ('/ *--tt-darkmode-bgcolor: #131313;">不过这些字节码的逻辑看起来很奇怪 , 看不出哪里奇怪不要紧 , 我们先来看看正常的 Python 字节码 。
Python ByteCode 101Python 是一种解释型语言 , 而 Python 字节码是一种平台无关的中间代码 , 由 Python 虚拟机动态(PVM)解释执行 , 这也是 Python 程序可以跨平台的原因 。
示例看一个简单的例子 test.py :
#!/usr/bin/env python2def add(a, b):return a - b + 42def main():b = add(3, 4)c = add(b, 5)result = 'evilpan: ' + str(c)print resultif __name__ == '__main__':main()使用上面的反汇编程序打印出字节码如下:
$ ./dec.py --pyc test.pyc30 LOAD_CONST0 (