浅谈几种Python源代码加密技术

文章首发于 安恒网络空间安全讲武堂 :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打包后再加壳,这样就很难进行源代码还原操作了