Marco Federighi, Dev@Nephila
Voglio proteggere il codice sorgente del mio programma
Perchè?
Le soluzioni dipendono dall'implementazione del linguaggio di programmazione utilizzato
Nota: ogni linguaggio di programmazione può essere implementato in entrambi i modi.
Esistono anche soluzioni miste
Ovvero?
il codice sorgente viene compilato in una forma intermedia, detta bytecode, che poi sarà interpretata o compilata a tempo di esecuzione
python è compilato o interpretato? entrambe le cose
Il codice sorgente può essere eseguito direttamente, tuttavia si può anche generare del codice intermedio
(bytecode)
proteggere il codice di un programma python è difficile per la natura stessa del linguaggio.
Perchè?
ID_SOMETHING = 'some_id_code'
def print_secret(x, y):
"""
So secret function
:param x: important input
:param y: important input
:return: important output
"""
return (ID_SOMETHING * x) + y
main.py
$ python -m compileall main.py
$ ls
main.py main.pyc
Come si fa?
$ xxd main.pyc
0000000: 03f3 0d0a 0e5b 2e5b 6300 0000 0000 0000 .....[.[c.......
0000010: 0001 0000 0040 0000 0073 1300 0000 6400 .....@...s....d.
0000020: 005a 0000 6401 0084 0000 5a01 0064 0200 .Z..d.....Z..d..
0000030: 5328 0300 0000 740c 0000 0073 6f6d 655f S(....t....some_
0000040: 6964 5f63 6f64 6563 0200 0000 0200 0000 id_codec........
0000050: 0200 0000 4300 0000 730c 0000 0074 0000 ....C...s....t..
0000060: 7c00 0014 7c01 0017 5328 0100 0000 7376 |...|...S(....sv
0000070: 0000 000a 2020 2020 536f 2073 6563 7265 .... So secre
0000080: 7420 6675 6e63 7469 6f6e 0a20 2020 203a t function. :
0000090: 7061 7261 6d20 783a 2069 6d70 6f72 7461 param x: importa
00000a0: 6e74 2069 6e70 7574 0a20 2020 203a 7061 nt input. :pa
00000b0: 7261 6d20 793a 2069 6d70 6f72 7461 6e74 ram y: important
00000c0: 2069 6e70 7574 0a20 2020 203a 7265 7475 input. :retu
00000d0: 726e 3a20 696d 706f 7274 616e 7420 6f75 rn: important ou
00000e0: 7470 7574 0a20 2020 2028 0100 0000 740c tput. (....t.
00000f0: 0000 0049 445f 534f 4d45 5448 494e 4728 ...ID_SOMETHING(
0000100: 0200 0000 7401 0000 0078 7401 0000 0079 ....t....xt....y
0000110: 2800 0000 0028 0000 0000 7307 0000 006d (....(....s....m
0000120: 6169 6e2e 7079 740c 0000 0070 7269 6e74 ain.pyt....print
0000130: 5f73 6563 7265 7403 0000 0073 0200 0000 _secret....s....
0000140: 0007 4e28 0200 0000 5201 0000 0052 0400 ..N(....R....R..
0000150: 0000 2800 0000 0028 0000 0000 2800 0000 ..(....(....(...
0000160: 0073 0700 0000 6d61 696e 2e70 7974 0800 .s....main.pyt..
0000170: 0000 3c6d 6f64 756c 653e 0100 0000 7302 ..<module>....s.
0000180: 0000 0006 02 .....
import dis, marshal, struct
with open('main.pyc', 'rb') as f:
magic = f.read(4)
timestamp = f.read(4)
code = f.read()
code = marshal.loads(code)
magic = struct.unpack('<H', magic[:2])
timestamp = struct.unpack('<I', timestamp)
print(magic, timestamp)
print(code)
print(dis.disassemble(code))
((62211,), (1529764622,))
<code object <module> at 0x7f4c0c798eb0, file "main.py", line 1>
1 0 LOAD_CONST 0 ('some_id_code')
3 STORE_NAME 0 (ID_SOMETHING)
3 6 LOAD_CONST 1 (<code object print_secret at 0x7f4c0c798cb0, file "main.py", line 3>)
9 MAKE_FUNCTION 0
12 STORE_NAME 1 (print_secret)
15 LOAD_CONST 2 (None)
18 RETURN_VALUE
None
$ uncompyle6 main.pyc
# uncompyle6 version 3.2.3
# Python bytecode 2.7 (62211)
# Decompiled from: Python 2.7.9 (default, Jun 29 2016, 13:08:31)
# [GCC 4.9.2]
# Embedded file name: main.py
# Compiled at: 2018-06-23 17:07:15
ID_SOMETHING = 'some_id_code'
def print_secret(x, y):
"""
So secret function
:param x: important input
:param y: important input
:return: important output
"""
return ID_SOMETHING * x + y
# okay decompiling main.pyc
Sono generati tramite flag di ottimizzazione
$ python -OO -m compileall main.py
Si ottiene un risultato più compatto (no assert, docstring, etc.)
$ pyminifier --nonlatin --replacement-length=100 main.py
𦃧𞣁䲜ﰂ𩳹𐫅𡔣ﰂ𐱃꺞퀷𞠬𧢍𐡪嚐𖠯ﰐ𐦖될𝞴𒅑𞸖𩅺𐠝ꓷ𞸁𨺢𖫔𞢲𐳍𥵻𬧄𐳚眔ﯕ𩀽𐠍𐳅𢠨𪮙𦤁𦒘𞢇𞡗ﴉ𐬈𤰶𓃰𩀃𐣩㛒𧫎偶𐲁ﮭ𐱃𞣄𐬗𞺎𐤡豪퀬𫳼𞢃𠽱𣗀𐬤𐲙ࡄ脴𐳃𫘩𐪌כֿ𐤥掊סּ𞸧𣏔𐬳نﵓ𞸛餧𐳪𞢼𐦍𨮪𐢕㜻𢚔膲𐦬諹𣜾𣰓𨘳𒃇𐳨ﲥ='some_id_code'
def 𦃧𞣁䲜ﰂ𩳹𐫅𡔣ﰂ𐱃꺞퀷𞠬𧢍𐡪嚐𖠯ﰐ𐦖될𝞴𒅑𞸖𩅺𐠝ꓷ𞸁𨺢𖫔𞢲𐳍𥵻𬧄𐳚眔ﯕ𩀽𐠍𐳅𢠨𪮙𦤁𦒘𞢇𞡗ﴉ𐬈𤰶𓃰𩀃𐣩㛒𧫎偶𐲁ﮭ𐱃𞣄𐬗𞺎𐤡豪퀬𫳼𞢃𠽱𣗀𐬤𐲙ࡄ脴𐳃𫘩𐪌כֿ𐤥掊סּ𞸧𣏔𐬳نﵓ𞸛餧𐳪𞢼𐦍𨮪𐢕㜻𢚔膲𐦬諹𣜾𣰓𨘳𒃇𐳨𐠳(x,y):
return(𦃧𞣁䲜ﰂ𩳹𐫅𡔣ﰂ𐱃꺞퀷𞠬𧢍𐡪嚐𖠯ﰐ𐦖될𝞴𒅑𞸖𩅺𐠝ꓷ𞸁𨺢𖫔𞢲𐳍𥵻𬧄𐳚眔ﯕ𩀽𐠍𐳅𢠨𪮙𦤁𦒘𞢇𞡗ﴉ𐬈𤰶𓃰𩀃𐣩㛒𧫎偶𐲁ﮭ𐱃𞣄𐬗𞺎𐤡豪퀬𫳼𞢃𠽱𣗀𐬤𐲙ࡄ脴𐳃𫘩𐪌כֿ𐤥掊סּ𞸧𣏔𐬳نﵓ𞸛餧𐳪𞢼𐦍𨮪𐢕㜻𢚔膲𐦬諹𣜾𣰓𨘳𒃇𐳨ﲥ*x)+y
# Created by pyminifier (https://github.com/liftoff/pyminifier)
$pyminifier -O obfuscated.py
V='some_id_code'
def p(x,y):
return(V*x)+y
# Created by pyminifier (https://github.com/liftoff/pyminifier)
py2exe, cx_Freeze, ...
Reverse engineering abbastanza semplice (miriadi di tool e tecniche per ricavare i sorgenti :/ )
Trasformiamo il codice Python in codice C
$ cython main.py -o main.c
$ cat main.c
/* Generated by Cython 0.28.3 */
#define PY_SSIZE_T_CLEAN
#include "Python.h"
#ifndef Py_PYTHON_H
#error Python headers needed to compile C extensions, please install development version of Python.
#elif PY_VERSION_HEX < 0x02060000 || (0x03000000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x03030000)
#error Cython requires Python 2.6+ or Python 3.3+.
#else
#define CYTHON_ABI "0_28_3"
#define CYTHON_FUTURE_DIVISION 0
#include <stddef.h>
#ifndef offsetof
#define offsetof(type, member) ( (size_t) & ((type*)0) -> member )
#endif
#if !defined(WIN32) && !defined(MS_WINDOWS)
#ifndef __stdcall
...
...
...
gcc -shared -pthread -fPIC -fwrapv -O2 -Wall -fno-strict-aliasing \
-I/usr/include/python2.7 -o main.so main.c
Generiamo una libreria (.so)!
Verifichiamo
In [1]: from main import print_secret
In [2]: import dis
In [3]: dis.disassemble(print_secret.func_code)
Niente co_code !
...
/* Instruction opcodes for compiled code */
#define POP_TOP 1
#define ROT_TWO 2
#define ROT_THREE 3
#define DUP_TOP 4
#define DUP_TOP_TWO 5
#define NOP 9
#define UNARY_POSITIVE 10
#define UNARY_NEGATIVE 11
#define UNARY_NOT 12
...
opcode.h
idea stupida: opcode shuffling
vantaggio: diventa difficile analizzare un file .pyc
svantaggio: bisogna distribuire l'interprete (e proteggerlo in qualche modo)
Ne vale la pena, ma dipende dal progetto !
Considerare altre strade:
domande? idee?