A Beginner’s Guide to Compiling Perl Scripts

Marc Bilodeau/ ItsADevLife

I love Perl. It’s my first real language as a developer, and I’ve done very well with it over the past 19 years writing code much of those days. However, not all code can be open source and freely available. That’s why compiling perl scripts into binary files is a great way to share software without sharing the source code.

Why Compile Perl Scripts?

Perl is an interpreted and dynamic programming language. It has proven versatile throughout its long history. Through CPAN, there is still an active and thriving community of roughly 200,000 modules from over 13,000 authors. As a result, developers can create meaningful one liner scripts to full fledge applications and platforms with Perl.

However, running perl scripts requires the interpreter and appropriate modules on the system. Thankfully, most Linux distributions come with Perl. This certainly saves time setting up the environment. Then, users can run programs by sharing perl scripts and modules.

Yet, the source code for those scripts is visible to anyone. Developers sometimes don’t want to share how they write an application. The developer can decide to release the code as open source, or compile the source code to protect their intellectual property. Open source software is wonderful, but developers do like to eat. Unfortunately, free code doesn’t always exchange well for food and essential services. Although, there are certainly real world exceptions through a service model.

Another reason to compile source code is portability. It simplifies the requirements for the end user by providing a binary executable file instead of copying files to a system with the interpreter.

What Does Compiling Perl Scripts Mean?

Compiling allows developers to turn their source code into binary files. Unfortunately, it is possible to decompile executables and look at the original source. Thankfully, compiling does add a layer of defense. It is possible to further protect the source code depending on the features of the compiler.

In Perl, the file size for compiled binaries is much larger than the script’s file size. That’s because the scripts, any dependencies, and a copy of the perl interpreter are in the binary.

There are several options available for compiling perl scripts. However, many of them are abandoned projects or discontinued products. Regardless, it demonstrates historical interest to share perl scripts in binary form.

PackageTypeDescription
ActiveState_PDKCommercialAvailable for Windows and Linux. This compiler requires ActiveState Perl and the Perl Dev Kit. However, in 2017 ActiveState will no longer develop its Perl Development Kit. Licenses range from $145 to $395 USD.
PARFreePerl Archive Toolkit. Well maintained and available through CPAN.
Perl2ExeCommercialAvailable for Windows, Linux, Solaris, AIX, and HP-UX. Licenses range from $49 to $5000 USD. Actively maintained.
PerlBin FreeLike App::Packer and PAR with similar features and options. However, PerlBin has not been updated in many years.
perlcc FreeIncluded in Perl 5.10 and older. In newer versions, the underlying modules are no longer packaged with the main distribution of Perl. Perlcc is no longer maintained.

Which Compiler Should I Use?

Currently, I use ActiveState’s PDK on the Microsoft Windows and Linux platforms. Unfortunately, in 2017 ActiveState announced that it will no longer develop its Perl Development Kit. It works very well, and I would continue to use it if it wasn’t discontinued. Alternatively, Perl2Exe is also an excellent compiler for those whom require a commercial product or technical support.

On the other hand, PAR is free and actively maintained. The documentation can be overwhelming when digging in for the first time. Also, support is restricted to forums and user groups. Regardless, PAR has all the necessary features, the right price tag, a community, and it’s well maintained. Now, let’s take a deeper dive into PAR.

The Best Perl Program in the World

After months of development and weeks of QA testing, The Best Perl Program in the World is ready for release. However, this code is so brilliant and the environment so complex to deploy, that the program should be compiled before releasing it to the masses.

The best perl script in the world is ready for the masses!
The Best Perl Program in the World is ready for release! Shh… Don’t tell anyone how it works.

Before the world can benefit from this life changing experience, the perl script must be compiled within a Build Environment.

Compiling Perl in a Build Environment

A Build Environment contains the essential perl scripts, libraries, and perl modules necessary when compiling the binaries. Secondly, there will be a different Build Environment for each operating system and processor architecture. For example, a Microsoft Windows Build Environment is necessary for compiling Windows executables. However, those binaries will not work on a Linux system. Hence, a Linux Build Environment is necessary to compile Linux binaries. Yet, those x64 binaries will not work on ARM devices like a Raspberry Pi with the same Linux OS. Therefore, a Build Environment is necessary for compiling those perl scripts.

The setup is similar on various operating systems and processor architectures. Unfortunately, this can be expensive when using physical machines. Thankfully, using virtual machines on VMware, KVM, Hyper-V, openstack, or VirtualBox can save time and money. Virtual Machines are great for multiple Build Environments on a single system. Secondly, virtual machines have snapshot capabilities. Snapshots are helpful when experimenting with newer modules and configurations. If something goes wrong, it’s much faster to revert to a snapshot than to completely recreate a Build Environment.

A Microsoft Windows v10 Build Environment

Perl must be installed manually since it doesn’t come pre-installed on Microsoft Windows. Strawberry Perl is almost identical to a Perl installation on Linux systems. ActiveState ActivePerl is another alternative. Once one of those packages is installed, the next step is to install the modules PAR and PAR::Packer using cpan or cpanm. Any dependencies will install automatically.

C:\Users\dev>cpanm PAR
PAR is up to date. (1.015)

C:\User\dev>cpanm PAR::Packer
--> Working on PAR::Packer
Fetching http://www.cpan.org/authors/id/R/RS/RSCHUPP/PAR-Packer-1.043.tar.gz ... OK
Configuring PAR-Packer-1.043 ... OK
==> Found dependencies: Module::ScanDeps, Getopt::ArgvFile
--> Working on Module::ScanDeps
Fetching http://www.cpan.org/authors/id/R/RS/RSCHUPP/Module-ScanDeps-1.24.tar.gz ... OK
Configuring Module-ScanDeps-1.24 ... OK
Building and testing Module-ScanDeps-1.24 ... OK
Successfully installed Module-ScanDeps-1.24
--> Working on Getopt::ArgvFile
Fetching http://www.cpan.org/authors/id/J/JS/JSTENZEL/Getopt-ArgvFile-1.11.tar.gz ... OK
Configuring Getopt-ArgvFile-1.11 ... OK
Building and testing Getopt-ArgvFile-1.11 ... OK
Successfully installed Getopt-ArgvFile-1.11
Building and testing PAR-Packer-1.043 ... OK
Successfully installed PAR-Packer-1.043
3 distributions installed

C:\User\dev>cpanm PAR::Packer
PAR::Packer is up to date. (1.043)

C:\User\dev>

Ubuntu 16.04 and 18.04 Build Environments

At the time of this writing, Ubuntu 18.04 just released as the latest (LTS) Long Term Supported version. Thankfully, both 16.04 and 18.04 use the same steps to install the necessary software packages. Between the two version, Ubuntu 16.04 has an older version of perl (5.22.1) and Ubuntu 18.04 has 5.26.1. Regardless, executing sudo apt install libpar-packer-perl installs the necessary libraries and modules.

dev@virtualbox:~$ sudo apt install libpar-packer-perl
[sudo] password for dev:
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following additional packages will be installed:
 libgetopt-argvfile-perl libmodule-scandeps-perl libmodule-signature-perl
 libpar-dist-perl libpar-perl
The following NEW packages will be installed
 libgetopt-argvfile-perl libmodule-scandeps-perl libmodule-signature-perl
 libpar-dist-perl libpar-packer-perl libpar-perl
0 to upgrade, 6 to newly install, 0 to remove and 0 not to upgrade.
Need to get 2,006 kB of archives.
After this operation, 5,773 kB of additional disk space will be used.
Do you want to continue? [Y/n] Y
... output truncated on purpose ...
Setting up libgetopt-argvfile-perl (1.11-1) ...
Setting up libmodule-scandeps-perl (1.20-1) ...
Setting up libmodule-signature-perl (0.79-1) ...
Setting up libpar-dist-perl (0.49-2) ...
Setting up libpar-perl (1.010-1) ...
Setting up libpar-packer-perl (1.029-1) ...
dev@virtualbox:~$

A macOS 10.13 Build Environment

Like Ubuntu, different versions of macOS have different versions of Perl pre-installed. Unfortunately, Perl 5.18.2 is pre-installed with macOS 10.13. This version of perl is currently end-of-life. Although, fixes for urgent issues like severe security problems may still be released. Regardless, all packages for compiling perl scripts are already installed with the operating system. Nevertheless, a developer can upgrade to 5.26.1 if needed.

MacBookAir:~ marc$ pp --version
PAR Packager, version 1.017 (PAR version 1.007)
Copyright 2002-2009 by Audrey Tang <cpan@audreyt.org>

Neither this program nor the associated "parl" program impose any
licensing restrictions on files generated by their execution, in
accordance with the 8th article of the Artistic License:

"Aggregation of this Package with a commercial distribution is
 always permitted provided that the use of this Package is embedded;
 that is, when no overt attempt is made to make this Package's
 interfaces visible to the end user of the commercial distribution.
 Such use shall not be construed as a distribution of this Package."

Therefore, you are absolutely free to place any license on the resulting
executable, as long as the packed 3rd-party libraries are also available
under the Artistic License.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself. There is NO warranty; not even for
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

MacBookAir:~ marc$

Other Operating Systems

As shown above, the steps to configure Build Environments are very similar. This means they could work with a little tinkering on other operating systems. Personally, I’m experimenting with Android since my Google Pixel XL is my PC. Also, Docker is showing promise as a portable Build Environment across different operating systems including binaries that can run on a Raspberry Pi.

Compiling Perl in a Build Environment

With the Build Environments ready, a developer can now build binaries. The pp command from the PAR Packager creates standalone executables from perl scripts. Remember The Best Perl Program in the World? The following demonstrates compiling perl on Microsoft Windows 10, Ubuntu 16.04, and macOS 10.13 using the pp command.

Compiling Perl Scripts on Microsoft Windows 10
Microsoft Windows 10 – compiling perl scripts into a windows executable
Compiling Perl Scripts on Ubuntu 16.04
Ubuntu 16.04 – compiling perl scripts into a portable linux binary
An example of compiling perl scripts on Mac OS 10.3
macOS 10.13 – compiling perl scripts into a portable macOS binary

The command line arguments are the same across the three different platforms. Most likely the arguments will be similar on any system that can install Perl and PAR Packer. However, there may be differences depending on the operating system, the program’s command line options, and processor architecture.

The pp command line

pp -M PAR -M Data::Dumper -x -o bestProgram.exe bestProgram.pl

The -M option adds modules and their dependencies. Furthermore, the more complex an application, the more modules are likely in use. For this reason, there are a few useful shortcuts to include multiple modules without having to list them separately.

  • -M Foo::** adds every module in the namespace except Foo. It will include Foo::Bar and Foo:Bar::More up to any depth except for Foo itself.
  • -M Foo::* adds every module in the first level. This include Foo::Bar and Foo::It but not Foo::Bar::More nor Foo.
  • -M Foo:: adds every module from Foo to any depth. This includes Foo, Foo::Bar, Foo::It, and Foo::Bar::More.

The -x option may not be necessary in some cases. However, it may find additional run-time dependencies which can’t be detected by static analysis. If the perl script uses arguments, use the xargs option to execute different code paths since detecting modules depends on which part of the code path runs. For more information, see the pp documentation.

The -o option is the name of the output binary file. It can be any name, and it doesn’t have to match the input file.

The last parameter, bestProgram.pl, is the input file. It is where pp starts the compiling process.

Securing the Source

Although the source is nicely compiled into a binary, it isn’t secure. Someone can still easily extract the source code from the executable files. That’s because the binary is a well-organized zip file right now.

dev@virtualbox:~/Desktop/decompile$ ll
total 3436
-rwxr-xr-x 1 dev dev 3506937 Apr 14 17:52 bestProgram*

dev@virtualbox:~/Desktop/decompile$ unzip bestProgram
Archive: bestProgram
 creating: lib/
 creating: script/
 inflating: MANIFEST
... output truncated on purpose ...
inflating: script/bestProgram.pl 
 inflating: script/main.pl 

dev@virtualbox:~/Desktop/decompile$ ll
total 3452
-rwxr-xr-x 1 dev dev 3506937 Apr 14 17:52 bestProgram*
drwxr-xr-x 14 dev dev 4096 Apr 8 01:37 lib/
-rw-r--r-- 1 dev dev 1282 Apr 8 01:37 MANIFEST
-rw-r--r-- 1 dev dev 219 Apr 8 01:37 META.yml
drwxr-xr-x 2 dev dev 4096 Apr 8 01:37 script/

dev@virtualbox:~/Desktop/decompile/script$ ll script/
total 16
-rw-r--r-- 1 dev dev 138 Apr 8 01:37 bestProgram.pl
-rw-r--r-- 1 dev dev 698 Apr 8 01:37 main.pl

dev@virtualbox:~/Desktop/decompile/script$ more script/bestProgram.pl 
#!perl

use strict;
use Data::Dumper;

my %test =
 (
 first => 1,
 second => 2,
 third => 3
 );

print Dumper \%test;

dev@virtualbox:~/Desktop/decompile/script$

Oh no! The source code for The Best Perl Program in the World is exposed!  However, there are ways to protect the source code to a degree. Unfortunately, there is no bulletproof way to 100% protect source code. In fact, it’s not possible with any language. The best protection other than providing a comprehensive End User License Agreement, is to apply filters to the compile process.

Attempting to Secure the Source Code

PAR has the ability to apply filters to perl scripts and modules. However, the most common filter to hide source code from the casual eye is Bleach.

There are two additional command parameters relating to the Bleach filter. First, -f Bleach applies a filter to the input file, bestProgram.pl. Secondly, the -F Bleach option applies the filter to the perl modules. PAR provides flexibility to perform the filter on one or the other or both.

pp -f Bleach -F Bleach -M PAR -M Data::Dumper \
     -x -o bestProgram_Bleached.exe bestProgram.pl

One caveat when compiling perl binaries is that it will increase the overall executable size. In some cases, it’s quite a bit. It will vary depending on the operating system and the number of modules passing through the filter during compile time. Below is an example of the size differences on Microsoft Windows 10.

05/05/2018 12:01 PM 154 bestProgram.pl
05/05/2018 12:11 PM 7,646,565 bestProgram_Bleached.exe
05/05/2018 12:10 PM 5,945,544 bestProgram_unprotected.exe

Now when attempting to unzip the binary file after compiling perl to examine the source code, it looks hideous and unreadable. The same is true for the modules.

Directory of C:\Users\dev\decompile\bestProgram\script

04/23/2018 08:13 PM <DIR> .
04/23/2018 08:13 PM <DIR> ..
04/23/2018 08:06 PM 1,577 bestProgram.pl
04/23/2018 08:06 PM 698 main.pl
 2 File(s) 2,275 bytes
 2 Dir(s) 36,915,073,024 bytes free

C:\Users\dev\decompile\bestProgram\script>more bestProgram.pl
$_=<<'';y;\r\n;;d;y; \t;01;;$_=pack'b*',$_;$_=eval;$@&&die$@;$_

This is certainly a step in the right direction. Yet, this isn’t the only method when compiling perl scripts. There is another filter that encrypts the source code.

Making the Best Darn Attempt to Secure the Source Code

The compiler can encrypt files using the Crypto filter from the PAR::Filter::Crypto module. Like Bleach, the filter can take care of both the perl scripts and perl modules.

However, there are some important caveats concerning compiling perl scripts. Most noteworthy is both Bleach and Crypto filters will protect the source code from the causal curious user. Unfortunately, someone with more skills in hacking and debugging could obtain a copy of the source code.

Regardless, there are additional modules to install in the Build Environment. Although, the version of Perl does matter. Unfortunately, the Filter::Crypto module does not successfully install on all versions of Perl.

Microsoft Windows

The only requirement when compiling perl using the Crypto filter is to install the Filter::Crypto module.

cpan Filter::Crypto

However, the version of Strawberry Perl matters. At the time of this writing, the latest version (5.26.2.1) fails to install the Filter::Crypto module. Although, Strawberry Perl 5.24.4.1 successfully installs it. If 5.26.2.1 is absolutely necessary, then the Bleach filter is the best alternative.

Ubuntu 16.04

By default, essential libraries and header files aren’t available for the Filter::Crypto module. Therefore, the libssl-dev package is necessary to install first. The libssl-dev package is part of the OpenSSL project’s implementation of the SSL and TLS cryptographic protocols.

sudo apt install libssl-dev

Lastly, install the Filter::Crypto module using cpan or cpanm.

sudo cpan Filter::Crypto

Ubuntu 18.04

At the time of this writing, the version of Perl that is included with Ubuntu 18.04 (5.26.1) fails to install the Filter::Crypto module. Fortunately, a compiled encrypted binary from Ubuntu 16.04 will work on an Ubuntu 18.04 system. If Ubuntu 18.04 is absolutely necessary, either downgrade to a version of Perl that can successfully install the Filter::Crypto module or use the Bleach filter.

macOS 10.13

Although Perl 5.18.2 is available with this version of macOS, it can install the Filter::Crypto module. Furthermore, it is more manual and does involve answering some questions. During the process of installing the module, the location of OpenSSL is different than the default setting. However, the remaining questions use the defaults.

MacBookAir:~ marc$ sudo cpan Filter::Crypto
CPAN: Storable loaded ok (v2.41)
Reading '/Users/marc/.cpan/Metadata'
 Database was generated on Sat, 05 May 2018 11:54:10 GMT
Running install for module 'Filter::Crypto'
Running make for S/SH/SHAY/Filter-Crypto-2.07.tar.gz
CPAN: Digest::SHA loaded ok (v5.84_01)
CPAN: Compress::Zlib loaded ok (v2.06)
Checksum for /Users/marc/.cpan/sources/authors/id/S/SH/SHAY/Filter-Crypto-2.07.tar.gz ok
CPAN: File::Temp loaded ok (v0.23)
CPAN: Parse::CPAN::Meta loaded ok (v1.4404)
CPAN: CPAN::Meta loaded ok (v2.133380)
CPAN: Module::CoreList loaded ok (v3.03)

CPAN.pm: Building S/SH/SHAY/Filter-Crypto-2.07.tar.gz

Where is your OpenSSL? [/usr] /usr/local/opt/openssl

Found include directory .. /usr/local/Cellar/openssl/1.0.2n/include
Found OpenSSL version .... 1.0.2n
Found crypto library ..... /usr/local/Cellar/openssl/1.0.2n/lib/libcrypto.a
Found binary executable .. /usr/local/Cellar/openssl/1.0.2n/bin/openssl

Cipher algorithms available:
 [ 1] DES block cipher
 [ 2] Two key triple DES block cipher
 [ 3] Three key triple DES block cipher
 [ 4] DESX block cipher
 [ 5] RC4 stream cipher
 [ 6] IDEA block cipher
 [ 7] RC2 block cipher
 [ 8] Blowfish block cipher
 [ 9] Null cipher
 [10] RC5 block cipher
 [11] CAST5 block cipher
 [12] AES block cipher
Which cipher algorithm do you want to use? [12] 12

Modes of operation available:
 [1] ECB (Electronic Codebook Mode)
 [2] CBC (Cipher Block Chaining Mode)
 [3] CFB (64-Bit Cipher Feedback Mode)
 [4] OFB (64-Bit Output Feedback Mode)
Which mode of operation do you want to use? [2] 2

This is a variable key length algorithm.
Valid key lengths are: 16, 24 or 32 bytes.
What key length (in bytes) do you want to use? [32] 32

You can either specify a password from which the key to be used for
encryption/decryption will be derived using a PKCS#5 key derivation
algorithm, or you can directly specify the key to use.
You can also have a password or key randomly generated for you.

Options for specifying or deriving the key:
 [1] Enter a password when prompted
 [2] Have a password randomly generated
 [3] Enter a key when prompted
 [4] Have a key randomly generated
How do you want to specify or derive the key? [2] 2

Random number generators:
 [1] Perl's built-in rand() function
 [2] OpenSSL's rand command
Which RNG do you want to use? [2] 2

Your cipher configuration has been written to the file 'CipherConfig.h'.
You may want to keep this file in a safe place if you ever need to rebuild
these modules using the same configuration, especially if your key was
randomly generated.

Build options:
 [1] Build both components
 [2] Build CryptFile component only
 [3] Build Decrypt component only
Which component(s) do you want to build? [1] 1

Do you want to install 'crypt_file'? [y] y

Checking if your kit is complete...
Looks good
... output truncated on purpose ...
Installing /usr/local/bin/crypt_file
Appending installation info to /Library/Perl/Updates/5.18.2/darwin-thread-multi-2level/perllocal.pod
 SHAY/Filter-Crypto-2.07.tar.gz
 sudo /usr/bin/make install -- OK
MacBookAir:~ marc$

And Now the Magic

The pp command uses the Crypto filter like the Bleach filter. One other difference is the Filter::Crypto::Decrypt module must be packaged with the binary. Otherwise, the binary won’t know how to decrypt the files.

pp -f Crypto -F Crypto -M Filter::Crypto::Decrypt \
  -M PAR -M Data::Dumper -x -o bestProgram_Encrypted.exe bestProgram.pl

The file size of the binary is comparable to Bleach. However, both are still larger than the unprotected binary. Below is an example of the file sizes from an Ubuntu 16.04 Build Environment.

-rwxr-xr-x 1 dev dev 3980618 May 5 15:55 bestProgram_Bleached.exe*
-rwxr-xr-x 1 dev dev 3929391 May 5 15:59 bestProgram_Encrypted.exe*
-rwxr-xr-x 1 dev dev 3471673 May 5 15:55 bestProgram.exe*
-rw-rw-r-- 1 dev dev 140 May 5 15:53 bestProgram.pl

What would the casual curious user see when attempting to peel back the layers?

dev@virtualbox:~/Desktop/decompile$ unzip bestProgram_Encrypted.exe
Archive: bestProgram_Encrypted
creating: lib/
creating: script/
inflating: MANIFEST
... output truncated on purpose ...
inflating: script/bestProgram.pl
inflating: script/main.pl

dev@virtualbox:~/Desktop/decompile$ ll

total 3936
drwxrwxr-x 4 dev dev 4096 May 5 14:52 ./
drwxr-xr-x 3 dev dev 4096 May 5 14:52 ../
drwxr-xr-x 15 dev dev 4096 May 5 14:42 lib/
-rw-r--r-- 1 dev dev 1353 May 5 14:42 MANIFEST
-rw-r--r-- 1 dev dev 233 May 5 14:42 META.yml
drwxr-xr-x 2 dev dev 4096 May 5 14:42 script/

dev@virtualbox:~/Desktop/decompile$ more script/bestProgram.pl
 
use Filter::Crypto::Decrypt;
e6ad69e3dd1e9901ccf6ba701ff66dfb09ba6b2f6c3b872e33e1929e56298f9861777c6a21255012
c658fa873214cd41071f6915ef594ee5447ae02afc1e2d726333fb855148920518e827fbb0990053
73eddabe4e608e15fcc54008c659f218ac32d56ac8d05a78cfd446ae05cf6c19f7e3c1b30fab747f
c0b0017243f764b3d3db0ef081bbf245478bd5a6af4c361b22e488edf203d91d4018d930fe4d2c1b
8d4d103810eb0603

dev@virtualbox:~/Desktop/decompile$ ../bestProgram_Encrypted.exe 
$VAR1 = {
 'second' => 2,
 'first' => 1,
 'third' => 3
 };

dev@virtualbox:~/Desktop$

Distributing Binaries

Finally, The Best Perl Program in the World is ready to release. Not so fast!

There is one very important step before distributing binaries to the masses. In fact, it’s so important it should happen before any code is written using a perl module, library file, or SDK. If not, it could lead to legal issues. Yikes!

Usually, the license details are part of the package either in a LICENSE file, on a website, or in the documentation. However, there may be no license terms. Unfortunately, that doesn’t mean it’s ok to use it. In this case, it’s best to contact the author to either ask for permission or to clarify any licensing requirements.

Generally, it comes down to the license and how the application uses it. There are a lot of open source licenses and each has different rules. Also, many libraries and SDK have their own terms and conditions. Furthermore, some may require purchasing a license to distribute. Unfortunately, licensing is a complex issue. Therefore, it may be necessary to speak with a lawyer.

For anyone that develops applications today, it’s likely that 3rd party libraries and modules are involved. Also, each of them requires careful examination of their use cases within an application. This can be a daunting task. Thankfully, there are companies out there that specialize in source code scanning to identify libraries, SDKs, and open source software licenses. However, it can be pricey depending on the size and scale of the application.

Conclusion

Interpreted Languages like Perl require the interpreter and other modules on the system to run the program. Thankfully, there are options available for compiling perl scripts into binaries.

PAR is an excellent cross-platform package that can accomplish this goal. Additionally, PAR Filters make it easy to obscure source code and modules. However, it is important to comply with the licenses and terms of use for any third party component before releasing any software.