python编辑环境_构建一个自定义Python编译器环境(修改操作码)的Docker容器

背景

老大: python 源码要加密,不给看...

大灰狼先生:好嘞!

实现

实现加密网传可行的有2种方式,代码变量混淆,修改编译器操作码(此文采用的方式)

两步:

修改 python 编译器,使得 只有 该特定的编译器才能执行 对应的 pyc 文件

将基础环境打包成 docker 镜像,作为程序发布的基础镜像

1. 修改操作码方法介绍

修改 python 源码文件

相关的目标文件有三个

Lib/opcode.py

Include/opcode.h

Python/opcode_targets.h

修改策略:打乱操作码

策略摘要:

opcode.py 中 HAVE_ARGUMENT = 90 是分隔符,大于90 的操作码有参数,小于90的操作码没有参数

将所有操作码分别放入不同的list:not_have_argument_code_list 和 have_argument_code_list,, 4 个回调操作码除外

将 not_have_argument_code_list 和 have_argument_code_list 顺序随机打乱

遍历 opcode.py 中所有的操作码,依次取 两个 list的最后一个 操作码 作为当前 操作的新的操作码,并将其保存的新的操作码字典 replace_dict

将 replace_dict 根据操作码 由小到大排序,覆盖写入到 Python/opcode_targets.h 文件

修改操作码执行方法 modify_opcodes.py 完整内容:

# -*- coding: utf-8 -*-

# @File : modify_opcodes.py

# @Date : 2019/11/28

# @Author : HiCooper

# @Desc : 修改操作码

# target Python-3.5.2

import argparse

import os

import random

import re

# 修改相关文件:

OPCODE_PY_PATH = "Lib/opcode.py"

OPCODE_H_PATH = "Include/opcode.h"

OPCODE_TARGETS_H_PATH = "Python/opcode_targets.h"

# 回调操作名集合:

CALL_OP = ['CALL_FUNCTION', 'CALL_FUNCTION_KW',

'CALL_FUNCTION_VAR', 'CALL_FUNCTION_VAR_KW']

# opcode_py 文件提取正则

regex_for_opcode_py = r'^(?P[a-z_]+)+\(\'+(?P[A-Z_]+)+\'+\,\s+(?P\d+)(?P.*)'

# opcode_h 文件提取正则

regex_for_opcode_h = r'^#define\s+(?P[A-Z_]+)\s+(?P\d+)(?P.*)'

try:

from importlib.machinery import SourceFileLoader

except ImportError:

import imp

class ReplaceOpCode(object):

"""

1. opcode.py 中 `HAVE_ARGUMENT = 90` 是分隔符,大于90 的操作码有参数,小于90的操作码没有参数

2. 将所有操作码分别放入不同的list:`not_have_argument_code_list` 和 `have_argument_code_list`,, 4 个回调操作码除外

3. 将 `not_have_argument_code_list` 和 `have_argument_code_list` 顺序随机打乱

4. 遍历 opcode.py 中所有的操作码,一次区 两个 list的最后一个 操作码 作为当前 操作的新的操作码,并将其保存的新的操作码字典 `replace_dict`

5. 将 `replace_dict` 根据操作码 由小到大排序,写入到 `Python/opcode_targets.h` 文件

"""

def __init__(self, source_directory):

self.replace_dict = {}

self.not_have_argument_code_list = []

self.have_argument_code_list = []

self.set_list(source_directory)

def set_list(self, source_directory):

"""

1. 读取 opcode_py 的内容,保存操作码到 `not_have_argument_code_list` 和 `have_argument_code_list`(跳过 4 个回调操作码)

2. 随机打乱顺序

"""

f1 = open(os.path.join(source_directory, OPCODE_PY_PATH), 'r+')

infos = f1.readlines()

f1.seek(0, 0)

for line in infos:

rex = re.compile(regex_for_opcode_py).match(line)

if rex:

op_code = rex.group('code')

if rex.group('name') in CALL_OP:

continue

elif int(op_code) < 90:

self.not_have_argument_code_list.append(int(op_code))

else:

self.have_argument_code_list.append(int(op_code))

random.shuffle(self.not_have_argument_code_list)

random.shuffle(self.have_argument_code_list)

def replace_file(self, reg, file, is_update_opcode_h=False):

"""

读取 opcode.py 或 opcode.h 内容并进行行替换

"""

f1 = open(file, 'r+')

infos = f1.readlines()

f1.seek(0, 0)

for line in infos:

rex = re.compile(reg).match(line)

if rex:

code = self.get_new_op_code(rex, is_update_opcode_h)

line = line.replace(rex.group('code'), str(code))

f1.write(line)

f1.close()

def get_new_op_code(self, rex, is_update_opcode_h):

"""

获取新的操作码

"""

op_name = rex.group('name')

op_code = rex.group('code')

if is_update_opcode_h:

# 修改 opcode.h 文件时,从已完成字典读取

try:

new_op_code = self.replace_dict[op_name]

except:

new_op_code = op_code

return new_op_code

# 修改 opcode.py 文件时,设置 name 和 code 到 字典 replace_dict

if op_name in CALL_OP:

# 属于回调操作,默认原操作码

new_op_code = int(op_code)

else:

if int(op_code) < 90:

new_op_code = self.not_have_argument_code_list.pop()

else:

new_op_code = self.have_argument_code_list.pop()

self.replace_dict[op_name] = new_op_code

return new_op_code

def write_opcode_targets_contents(self, source_directory):

"""Write C code contents to the target file object.

"""

targets = ['_unknown_opcode'] * 256

for opname, op in sorted(self.replace_dict.items(), key=lambda nc: nc[1]):

targets[op] = "TARGET_%s" % opname

with open(os.path.join(source_directory, OPCODE_TARGETS_H_PATH), 'w') as f:

f.write("static void *opcode_targets[256] = {\n")

sep = ',%s' % os.linesep

f.write(sep.join([" &&%s" % s for s in targets]))

f.write("\n};\n")

def run(self, source_directory):

print('\n====== 开始修改操作码... ======\n')

self.replace_file(reg=regex_for_opcode_py, file=os.path.join(

source_directory, OPCODE_PY_PATH))

self.replace_file(reg=regex_for_opcode_h, file=os.path.join(

source_directory, OPCODE_H_PATH), is_update_opcode_h=True)

self.write_opcode_targets_contents(source_directory)

print('\n====== 修改完成! ======\n')

if __name__ == '__main__':

parser = argparse.ArgumentParser(description='modify python opcodes')

parser.add_argument('--src', dest='src', type=str,

help='Python source code path(support relative path)', required=True)

args = parser.parse_args()

src = os.path.abspath(args.src)

replaceOpCode = ReplaceOpCode(src)

replaceOpCode.run(src)

修改执行 python modify_opcodes.py --src=./Python-3.5.2

注意:不是所有的 python 版本都支持 修改操作码,这里测试ton过的 python版本是 Python-3.5.2

2. 构建python执行环境镜像

以centos7 为基础镜像,修改 python3.5.2 操作码并编译安装~~~~

准备文件:

上一步的 modify_opcode.py

Python-3.5.2.tgz 源码压缩包,下载传送门

编译工程文件的 工具类 compile.py (后续构建项目镜像时会使用)

构建容器步骤摘要:

步骤1. 安装 编译 python 源码需要的基础包

步骤2. 修改操作码,编译安装,设置环境变量

步骤3. 添加软链

构建 镜像的 Dockfile 完整内容:

# 自定义 python3.5.2 环境

FROM centos:centos7

ENV LANG=en_US.UTF-8

# 工作目录

WORKDIR /python

# 添加脚本,py源码包

ADD Python-3.5.2.tgz compile.py modify_opcode.py /python/

# 安装基础包

RUN python -V && yum -y update && \

yum install -y yum-utils wget make device-mapper-persistent-data \

lvm2 net-tools vim-enhanced gcc zlib* openssl-devel readline sqlite-devel \

readline-devel libffi-devel libSM-devel libXrender libXext-devel && \

yum clean all

# 修改操作码,编译安装,设置环境变量,设置pip源,升级pip, 验证环境

RUN python modify_opcode.py --src=/python/Python-3.5.2/ && \

cd Python-3.5.2 && ./configure --prefix=/usr/local/python3 && \

make && make install && \

echo "export PATH=/usr/local/python3/bin:$PATH" >> /etc/profile.d/python3.sh && \

echo "export LANG=en_US.UTF-8" >> /etc/profile.d/python3.sh && \

source /etc/bashrc && \

mkdir ~/.pip && \

echo "[global]" >> ~/.pip/pip.conf && \

echo "index-url = https://pypi.tuna.tsinghua.edu.cn/simple" >> ~/.pip/pip.conf && \

pip3 install --upgrade pip --no-cache-dir && \

python3 -V && pip3 -V

# 添加软连接

RUN ln -s -b /usr/local/python3/bin/python3 /usr/bin/python3 && \

ln -s -b /usr/local/python3/bin/pip3 /usr/bin/pip3

# Add Tini

ENV TINI_VERSION v0.18.0

ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini

RUN chmod +x /tini

ENTRYPOINT ["/tini", "--"]

CMD ["/bin/bash"]

根据Dockerfile build镜像

docker build -t hicooper/py3.5.2:v1.0 .

验证镜像环境

假设上一步生成的 ImageId 为 23fdd4e27c63

docker run -it --name py3.5.2v1.0 23fdd4e27c63 /bin/bash

成功进入容器后,验证 python 环境就完事了

文件 compile.py 完整内容

# -*- coding: utf-8 -*-

import argparse

import os,sys,shutil

import compileall

def search(curpath, s):

L = os.listdir(curpath) #列出当前目录下所有文件

for subpath in L: #遍历当前目录所有文件

if os.path.isdir(os.path.join(curpath, subpath)): #若文件仍为目录,递归查找子目录

newpath = os.path.join(curpath, subpath)

search(newpath, s)

elif os.path.isfile(os.path.join(curpath, subpath)): #若为文件,判断是否包含搜索字串

if s in subpath and "__pycache__" in curpath:

#移动pyc文件到上级目录

parent_path = os.path.dirname(curpath) ##获得parent_path所在的目录即parent_path的父级目录

shutil.copy(os.path.join(curpath, subpath),parent_path)

#重命名

name=subpath.split(".")

re_name=(name[0]+"."+name[2])

os.rename(os.path.join(parent_path, subpath),os.path.join(parent_path, re_name))

if ".py" in subpath and ".pyc" not in subpath:

#删除py文件

print(os.path.join(curpath, subpath))

os.remove(os.path.join(curpath, subpath))

if __name__ == '__main__':

parser = argparse.ArgumentParser(description='modify python opcodes')

parser.add_argument('--src', dest='src', type=str,help='project source code', required=True)

args = parser.parse_args()

workingpath = args.src

compileall.compile_dir(workingpath)

search(workingpath, ".pyc")

print('\n ====== 编译完成! ======\n')

3. 构建项目镜像

以上一步构建的镜像作为基础环境

测试项目仅为一个 app.py 文件,文件内容

# -*- coding: utf-8 -*-

import jieba

text = '道理千万条,安全第一条,行车不规范,亲人两行泪'

print("原句:", text)

seg_list = jieba.cut(text)

print("分词: \n" + " / ".join(seg_list))

对应的 项目构建 Dockerfile 内容

FROM hicooper/py3.5.2:v1.0

ENV LANG=en_US.UTF-8

WORKDIR /app

ADD app.py /app

# install pageckage

RUN pip install jieba && \

python /python/compile.py --src=./

CMD ["python", "app.pyc"]

构建项目镜像

docker build -t app .

运行项目

docker run app

结果类似

原句: 道理千万条,安全第一条,行车不规范,亲人两行泪

Building prefix dict from the default dictionary ...

Loading model from cache /tmp/jieba.cache

Loading model cost 1.309 seconds.

Prefix dict has been built succesfully.

分词:

道理 / 千万条 / , / 安全 / 第一条 / , / 行车 / 不 / 规范 / , / 亲人 / 两行 / 泪

结束

打包的项目镜像比较大,这里有:1.39GB, OMG!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值