pyc逆向之opcode简单置换

 最近做了一道pyc的逆向题,主要难点在于Python环境的opcode被置换,就简单记录一下相关知识。

opcode是什么

opcode其实是指Python源码的操作码,Python源代码*.py编译后可以得到二进制文件*.pyc,*.pyc文件中就含有opcode序列。对于不同版本的Python,其opcode是不完全相同的,这也就是为什么某一版本的Python虚拟机不能执行另一个版本的源码。

查看当前版本opcode的方式有很多种,最简单的是直接导入opcode库查看:

import opcode
for key in opcode.opmap.keys():
    print (key, opcode.opmap[key])

或者在/Python/Include/opcode.h文件中查看。

一个简单源码的opcode查看如下:

import dis
def foo():
    x = 20
    y = 10
    z = x - y
    return z
dis.dis(foo)

输出如下:

  3           0 LOAD_CONST               1 (20)
              2 STORE_FAST               0 (x)

  4           4 LOAD_CONST               2 (10)
              6 STORE_FAST               1 (y)

  5           8 LOAD_FAST                0 (x)
             10 LOAD_FAST                1 (y)
             12 BINARY_SUBTRACT
             14 STORE_FAST               2 (z)

  6          16 LOAD_FAST                2 (z)
             18 RETURN_VALUE

或者可以在命令行运行 python -m py_compile filename.pyc,然后利用pycdas等软件去反编译至opcode。

如何置换opcode

一些逆向题会构建一个修改的Python环境,从而使对应的*.pyc文件只能被本地识别并执行,而不能在别的计算机执行。

参考这篇文章:置换CPython 2.7.13的opcode

那如何构建这样的环境呢,一般是通过源码安装Python的方式实现,在安装前更改opcode的相关文件。对于2.7.13版本以下,似乎只需要更改/Include/opcode.h,/Lib/opcode.py两个文件,而2.7.14及以上版本则还需要修改/Python/opcode_targets.h文件。本文的例子是针对2.7.16的。

构建三个补丁文件(这里以加减法交换为例)如下,分别命名为re1.patch,re2.patch,re3.patch。

--- Include/opcode.h
+++ Include/opcode.h
@@ -30,2 +30,2 @@
-#define BINARY_ADD	23
-#define BINARY_SUBTRACT	24
+#define BINARY_ADD	24
+#define BINARY_SUBTRACT	23
--- Lib/opcode.py
+++ Lib/opcode.py
@@ -65,2 +65,2 @@
-def_op('BINARY_ADD', 23)
-def_op('BINARY_SUBTRACT', 24)
+def_op('BINARY_ADD', 24)
+def_op('BINARY_SUBTRACT', 23)
--- Python/opcode_targets.h
+++ Python/opcode_targets.h
@@ -25,2 +25,2 @@
-    &&TARGET_BINARY_ADD,
-    &&TARGET_BINARY_SUBTRACT,
+    &&TARGET_BINARY_SUBTRACT,
+    &&TARGET_BINARY_ADD,

下载Python2.7.16的源码:https://www.python.org/ftp/python/2.7.16/Python-2.7.16.tar.xz

运行以下命令:

~$ xz -d Python-2.7.16.tar.xz
~$ tar -xvf Python-2.7.16.tar
~$ cd Python-2.7.16
~/Python-2.7.16$ cp ~/re1.patch re1.patch
~/Python-2.7.16$ cp ~/re2.patch re2.patch
~/Python-2.7.16$ cp ~/re3.patch re3.patch
~/Python-2.7.16$ patch -p0 < re1.patch
~/Python-2.7.16$ patch -p0 < re2.patch
~/Python-2.7.16$ patch -p0 < re3.patch
~/Python-2.7.16$ ./configure --prefix=/usr/local/python
~/Python-2.7.16$ sudo make & make install

此时Python环境编译成功,可以用下面的小例子测试一下:

x = 10
y = input("number:")
print x-y

然后将源码编译为pyc文件:python -m py_compile main.py,分别在不同平台上运行main.pyc如下:

pyc逆向之opcode简单置换_第1张图片pyc逆向之opcode简单置换_第2张图片

在windows下使用uncompyle6尝试对main.pyc反编译:

D:\>uncompyle6 main.pyc
# uncompyle6 version 3.2.6
# Python bytecode 2.7 (62211)
# Decompiled from: Python 2.7.16 (v2.7.16:413a49145e, Mar  4 2019, 01:37:19) [MSC v.1500 64 bit (AMD64)]
# Embedded file name: main.py
# Compiled at: 2019-06-20 14:51:58
x = 10
y = input('number:')
print x + y
# okay decompiling main.pyc

我们发现,加减法已经被置换了。

简单置换也可以置换多个opcode,需要注意的是上面三个文件中opcode的顺序必须是一致的,否则make将会失败。

 

已知正确的opcode如何反编译至*.py源码

对于普通*.pyc文件的反编译,uncompyle6已经是非常好的工具了,但其显然是针对官方版本的opcode进行识别并反编译,对于修改过后的文件,如果修改处较多,我们逐字节更改就比较困难。推荐一款工具:Decompyle++,下载后可以更改默认的字节码map文件,本例中可以修改/pycdc/bytes/python_27.map,将加减法互换,然后make,官方推荐了cmake。

  • Generate a project or makefile with CMake (See CMake's documentation for details)
  • Build the generated project or makefile
    • For projects (e.g. MSVC), open the generated project file and build it
    • For makefiles, just run make
    • To run tests (on *nix or MSYS), run make test

pyc逆向之opcode简单置换_第3张图片

使用编译好的pycdc就可以反编译到正确的源码了。

最后

其实我们一般做题很难事先知道正确的opcode顺序是什么,主要还是得通过一次反编译之后的源码,对照其中异常的语句猜测正确的opcode值。

 

你可能感兴趣的:(pyc逆向之opcode简单置换)