文章首发于 安恒网络空间安全讲武堂 :https://mp.weixin.qq.com/s/N8liasMhiiTHC_1KC3JQJQ
随着Python近年的流行,很多开发者选择用Python作为自己首选的开发语言,其简洁的语法和轻量的运行方式受到越来越多开发者的喜爱,但是对于一些个人开发者而言,某些不想开源的程序使用Python开发后很难保证其源代码不被他人剽窃,今天作者就和大家说说几种流行的Python代码加密方式,分析各种利弊并给大家推荐一下最佳的Python代码加密方案,今天主要的对象是基于Python3.X开发的应用程序(实测这些方法在2.x版本中也是可以使用的)
生成pyc文件发布
简介
pyc文件是一种经py文件编译后的二进制文件,他可以跨平台在Python虚拟机中运行,pyc文件打开是无法看到我们正常的源码的,例如这样:
源代码:
编译成的pyc文件:
可以看到辨识度基本为0
方法
一般一个项目的代码会有多个Python文件,我们需要批量生成pyc文件,这里可以使用compileall模块,我这里的示例项目是在E盘下code文件夹中,生成代码为:
import compileall
compileall.compile_dir(r'E:/code')
执行完后会发现项目下多了一个 pycache 文件夹,打开后就是编译后的pyc文件
代码函数变量混淆
这种加密方式类似于PHP的变量混淆加密,通过奇特的变量命名方式使代码难以被读取和理解,这种混淆非常恶心,有时源代码就在你眼前,你却无法知道该段代码的含义,令人很头疼
经过一番探索,我找到了一个在线进行混淆的站点,下个月计划自己写一个混淆代码的程序,届时也会第一时间开源分享给大家
神奇的站点:http://pyob.oxyry.com/
拿我写的一个函数作为例子(特意找了一个长的,方便大家更直观看到差别)
源代码:
def get_flag(get_flag_cmd):
list = []
global flag
print("DEMO:http://10.10.10.10/index.php?flag= yml-flag &name=666")
submit_flag_url = input("请输入提交flag的链接(flag用yml-flag替换,两边加空格):")
with open("data/flagshell.txt", 'r') as f:
line = f.readline().strip()
while line:
list.append(line)
line = f.readline().strip()
i = 0
url = {}
passwd = {}
method = {}
for data in list:
if data:
ls = data.split(",")
method_tmp = str(ls[2])
method_tmp = method_tmp.lower()
#print(method_tmp)
if method_tmp == 'post' or method_tmp == 'get':
url[i] = str(ls[0])
method[i] = method_tmp
passwd[i] = str(ls[1])
#print(url[i])
#print(method[i])
#print(passwd[i])
i += 1
else:
print("[-] %s request method error!" % (str(ls[0])))
else:
pass
time_temp = 1
while True:
for j in range(len(url)):
return_flag = socket_flag(url=url[j], method=method[j], passwd=passwd[j], get_flag_cmd=get_flag_cmd)
flag.append(return_flag)
save_flag()
print("3秒后尝试提交flag:")
for i in range(1, 4):
s = '>' * i + '[' + str(i) + 's' + ']' # 这个方法同第二种类似
print('%s' % s, end='\r') # 每行以'\r'结尾,就可以输出在同一行
time.sleep(1)
for submit_flag_str in flag:
submit_flag(url=submit_flag_url, flag=submit_flag_str)
print("60秒进行下一轮操作:")
for i in range(1, 61):
s = '>' * i + '[' + str(i) + 's' + ']' # 这个方法同第二种类似
print('%s' % s, end='\r')
time.sleep(1)
flag = []
time_temp = time_temp + 1
print(time_temp)
混淆后的代码:
def get_flag (OOOO0000O0OOOOOO0 ):#line:1
O00000OOO0OO00OOO =[]#line:2
global flag #line:3
print ("DEMO:http://10.10.10.10/index.php?flag= yml-flag &name=666")#line:4
OO0OOOO0OOOO00OO0 =input ("请输入提交flag的链接(flag用yml-flag替换,两边加空格):")#line:5
with open ("data/flagshell.txt",'r')as OOO0000OO0O0O00O0 :#line:6
OOOO0OO0O000O0OO0 =OOO0000OO0O0O00O0 .readline ().strip ()#line:7
while OOOO0OO0O000O0OO0 :#line:8
O00000OOO0OO00OOO .append (OOOO0OO0O000O0OO0 )#line:9
OOOO0OO0O000O0OO0 =OOO0000OO0O0O00O0 .readline ().strip ()#line:10
O00OOOO0000OO00O0 =0 #line:11
OO00OO00000OOOOOO ={}#line:12
OO0OO000O0O0OO0O0 ={}#line:13
OOOOO0OO0O00OOOOO ={}#line:14
for OO00OOOO0000OOO0O in O00000OOO0OO00OOO :#line:15
if OO00OOOO0000OOO0O :#line:16
O00O0O00O000000O0 =OO00OOOO0000OOO0O .split (",")#line:17
OO000OO00OO00OO0O =str (O00O0O00O000000O0 [2 ])#line:18
OO000OO00OO00OO0O =OO000OO00OO00OO0O .lower ()#line:19
if OO000OO00OO00OO0O =='post'or OO000OO00OO00OO0O =='get':#line:21
OO00OO00000OOOOOO [O00OOOO0000OO00O0 ]=str (O00O0O00O000000O0 [0 ])#line:22
OOOOO0OO0O00OOOOO [O00OOOO0000OO00O0 ]=OO000OO00OO00OO0O #line:23
OO0OO000O0O0OO0O0 [O00OOOO0000OO00O0 ]=str (O00O0O00O000000O0 [1 ])#line:24
O00OOOO0000OO00O0 +=1 #line:28
else :#line:29
print ("[-] %s request method error!"%(str (O00O0O00O000000O0 [0 ])))#line:30
else :#line:31
pass #line:32
O00O00O000OOO000O =1 #line:33
while True :#line:34
for O00000O00O000O0O0 in range (len (OO00OO00000OOOOOO )):#line:35
OOO0OO0OO0000OO0O =socket_flag (url =OO00OO00000OOOOOO [O00000O00O000O0O0 ],method =OOOOO0OO0O00OOOOO [O00000O00O000O0O0 ],passwd =OO0OO000O0O0OO0O0 [O00000O00O000O0O0 ],get_flag_cmd =OOOO0000O0OOOOOO0 )#line:36
flag .append (OOO0OO0OO0000OO0O )#line:37
save_flag ()#line:38
print ("3秒后尝试提交flag:")#line:39
for O00OOOO0000OO00O0 in range (1 ,4 ):#line:40
OOO00O0O0O0OO0O00 ='>'*O00OOOO0000OO00O0 +'['+str (O00OOOO0000OO00O0 )+'s'+']'#line:41
print ('%s'%OOO00O0O0O0OO0O00 ,end ='\r')#line:42
time .sleep (1 )#line:43
for O0OO0O00OO0000OO0 in flag :#line:44
submit_flag (url =OO0OOOO0OOOO00OO0 ,flag =O0OO0O00OO0000OO0 )#line:45
print ("60秒进行下一轮操作:")#line:46
for O00OOOO0000OO00O0 in range (1 ,61 ):#line:47
OOO00O0O0O0OO0O00 ='>'*O00OOOO0000OO00O0 +'['+str (O00OOOO0000OO00O0 )+'s'+']'#line:48
print ('%s'%OOO00O0O0O0OO0O00 ,end ='\r')#line:49
time .sleep (1 )#line:50
flag =[]#line:51
O00O00O000OOO000O =O00O00O000OOO000O +1 #line:52
print (O00O00O000OOO000O )
假如只给你混淆后的代码,相信很难会理解这个函数究竟在程序中干了什么
打包成exe文件发布
简介
这里就要向大家介绍一款神器—pyinstaller,这是一个专门用来将Python程序打包成exe格式的应用程序,除此之外它还有一个特别大的优点,经pyinstaller打包后的程序可以无需安装Python环境直接在其他机器上运行,该程序的安装方式可以参考官网:http://www.pyinstaller.org/
使用操作
我的操作系统是windows10,这里给大家演示一下打包的操作
就拿我前几天发布的AWD框架作为例子
源码结构:
定位到pyinstaller主程序的位置,在cmd窗口中输入命令
pyinstaller -F E:\YML-AWD-FRAMEWORK\main.py
-F后输入的是项目主文件路径
成功生成后会在dist文件夹下找到刚刚打包好的文件
但是当我们打开生成的程序时,却发生了闪退,为了看清错误,我们在powershell下运行程序,错误提示我们缺少自写的第三方库
相信如果用过这个打包程序的师傅都会遇到这个问题,在打包简单程序时可以正常打开运行,但是当我们写的程序包含了自写的第三方库时,就会出现异常,最开始这个问题出现在我写机器人插件的时候,无奈之下我去百度上寻找pyinstaller的详细工作方式,它在打包的时候会自动导入Python的内置库,抱着试一试的心态我把自己写的第三方库放到了Python系统库目录下,而后同上面方式进行打包,最终成功解决了这个问题,后来的几个项目打包我也是采用这种方式,都很奏效。
通过打包成exe的方式,我们可以发布exe文件,这样就可以很好的保护源代码
弊端分析
对于第一种编译为pyc的方法,近年出现了pyc的反编译技术,一些ctf比赛也以此作为一个考点 有很多站点例如https://tool.lu/pyc/就可以将pyc文件还原成Python代码
对于第三种方法,可能有人会觉得很安全,有一个名为pyinstxtractor.py的脚本可以在打包完毕的程序中提取出pyc文件,我们可以通过反编译pyc文件获得源代码 ,脚本下载地址:https://sourceforge.net/projects/pyinstallerextractor/
方法推荐
函数变量混淆+pyc编译
虽然pyc文件可以反编译出源代码,但是经过我多次试验,将源代码经过变量混淆后再反编译为pyc文件是很困难的,以我上面混淆完的函数为例子,它编译成pyc文件后在进行反编译结果如下:
可以看到它无法还原,甚至部分还原出来的代码都是错的
打包为exe文件加壳
这个思路就很简单了,将项目用pyinstaller打包后再加壳,这样就很难进行源代码还原操作了