Is it possible to compile python?

Hello! Many people ask: Can you compile python? The answer is no, and you will say, why do you make an article if you can’t, I will explain why I say that you can’t:

¿Se puede compilar python?

Actually python is an interpreted language so it cannot be compiled, but there are several uses that can generate an executable from our python code, either standalone or dependent on libraries:

1- First way to compile python: cython

The first way to create an executable from our python script is with the Cython utility, which converts the code to C language to later compile it with a C compiler such as GCC:

Script “test.py”

def testPython():
   toprint = "Python mola"
   print(toprint)

if __name__=="__main__":
   testPython()

To do the following you need to install cython, you can install it with easy_install or pip:

root@host:~# pip install cython
root@host:~# 

Then we transform the python code into C and compile it with GCC:

root@host:~# cython --embed -o test.c test.py
root@host:~# gcc -I/usr/include/python2.7 -lpython2.7 -o test test.c
root@host:~# 

As you can see the executable is created and behaves exactly like the script:

root@host:~# ./test 
Python mola
root@host:~# python test.py 
Python mola

As you can see, a linux elf executable file (exe in windows) has been generated, it depends on the python libraries (and as in any binary you have to be careful of what you leave in the strings since with the strings command you can get plaintext):

root@host:~# file test
test: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=e54070acae5bb3d0c751b495919680933e43955f, not stripped
root@host:~# ldd test
	linux-vdso.so.1 (0x00007ffea39cc000)
	libpython2.7.so.1.0 => /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0 (0x00007fdf70a44000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fdf70699000)
	libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fdf7047e000)
	libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fdf70261000)
	libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fdf7005d000)
	libutil.so.1 => /lib/x86_64-linux-gnu/libutil.so.1 (0x00007fdf6fe5a000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fdf6fb59000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fdf70fc9000)
root@host:~# strings test|grep "Python"
testPython
Python mola
testPython
test.testPython
 while calling a Python object
__pyx_k_testPython
__pyx_k_Python_mola
__pyx_kp_s_Python_mola
__pyx_n_s_testPython
__pyx_mdef_4test_1testPython
__pyx_pw_4test_1testPython
__pyx_pf_4test_testPython

2- Second way to compile python: pyinstaller

We can also create an executable with pyinstaller, although in this way we really pack the python script and its libraries inside a binary, this way neither python nor its libraries are required to be installed in the system to run the generated binary:

The first thing is to install pyinstaller, it can be done with pip or easy_install.

root@host:~# pip install pyinstaller
root@host:~# 

Then we generate the binary with:

root@host:~# pyinstaller -F test.py 
23 INFO: PyInstaller: 3.2.1
23 INFO: Python: 2.7.9
24 INFO: Platform: Linux-3.16.0-4-amd64-x86_64-with-debian-8.10
24 INFO: wrote /tmp/test.spec
27 INFO: UPX is not available.
28 INFO: Extending PYTHONPATH with paths
['/tmp', '/tmp']
28 INFO: checking Analysis
28 INFO: Building Analysis because out00-Analysis.toc is non existent
28 INFO: Initializing module dependency graph...
29 INFO: Initializing module graph hooks...
61 INFO: running Analysis out00-Analysis.toc
74 INFO: Caching module hooks...
76 INFO: Analyzing /tmp/test.py
77 INFO: Loading module hooks...
77 INFO: Loading module hook "hook-encodings.py"...
1121 INFO: Looking for ctypes DLLs
1122 INFO: Analyzing run-time hooks ...
1125 INFO: Looking for dynamic libraries
1231 INFO: Looking for eggs
1231 INFO: Python library not in binary depedencies. Doing additional searching...
1260 INFO: Using Python library /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0
1262 INFO: Warnings written to /tmp/build/test/warntest.txt
1290 INFO: checking PYZ
1290 INFO: Building PYZ because out00-PYZ.toc is non existent
1290 INFO: Building PYZ (ZlibArchive) /tmp/build/test/out00-PYZ.pyz
1440 INFO: Building PYZ (ZlibArchive) /tmp/build/test/out00-PYZ.pyz completed successfully.
1465 INFO: checking PKG
1465 INFO: Building PKG because out00-PKG.toc is non existent
1465 INFO: Building PKG (CArchive) out00-PKG.pkg
3070 INFO: Building PKG (CArchive) out00-PKG.pkg completed successfully.
3074 INFO: Bootloader /usr/local/lib/python2.7/dist-packages/PyInstaller/bootloader/Linux-64bit/run
3074 INFO: checking EXE
3074 INFO: Building EXE because out00-EXE.toc is non existent
3075 INFO: Building EXE from out00-EXE.toc
3075 INFO: Appending archive to ELF section in EXE /tmp/dist/test
3090 INFO: Building EXE from out00-EXE.toc completed successfully.

The binary file has been generated in the “dist/” directory and as you can see below it behaves just like the script as the other binary we have generated before:

root@host:~# dist/test 
Python mola

As you can see, an ELF binary file has also been generated (in windows an EXE) ,it has less library dependencies and nothing of what is written in the strings appears when we execute the command “strings”:

root@host:~# file dist/test 
dist/test: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=373ec5dee826653796e927ac3d65c9a8ec7db9da, stripped
root@host:~# ldd dist/test 
	linux-vdso.so.1 (0x00007ffc22787000)
	libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fc7ada4b000)
	libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fc7ad830000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc7ad485000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fc7adc4f000)
root@host:~# strings dist/test |grep Python
Py_SetPythonHome
Cannot dlsym for Py_SetPythonHome
Error loading Python lib '%s': %s
Error detected starting Python VM.

Another thing to take into account is that the size of the executable file will be bigger with this utility since a standalone file has been generated and for that reason the necessary libraries have been included in the code:

root@host:~# ls -alh test
-rwxr-xr-x 1 root root 48K feb 15 01:24 test
root@host:~# ls -alh dist/test
-rwxr-xr-x 1 root root 3,6M feb 15 01:35 dist/test

Leave a Reply