Channels ▼
RSS

Web Development

Creating Self-Contained Perl Executables, Part II


Julius is a systems administrator for Ayala Systems Technology. He can be contacted at jcduque@lycos.com.


You can convert a Perl script into a self-contained executable in more than one way. One way is to use LibZip, which I've already discussed in Part I of this article last month. LibZip builds an external self-contained library that you distribute along with your compiled Perl script. Another approach is to use PAR—the Perl Archive Toolkit.

As in LibZip, you need a working C compiler to compile Perl scripts. A sample Perl script, sample1.pl (shown in Listing 1), is provided with this article. This script will be compiled to showcase the capabilities of PAR. You may also need to install Win32::Autoglob before you can use sample1.pl.

Also, although the entire discussion here focuses on building executables for the Windows system, the steps presented here apply to other nonWindows platforms, too.

The Perl Archive Toolkit

PAR is the tool to use if you dislike having to bundle an executable with a separate library and prefer, instead, to have just a single executable. PAR, which is always available from http://par.perl.org/ index.cgi, requires the following modules: File::Temp, Compress::Zlib, Archive::Zip, Module::ScanDeps, and PAR::Dist. Apart from these prerequisite modules, the PAR maintainers also recommend the following modules: Parse::Binary and Win32::Exe, if you're on a Windows system.

Assuming you have installed all the prerequisite modules, using PAR is easy.

Compiling Scripts Using the Command Line

If you prefer using the command line, the following command will compile the sample Perl script, sample1.pl, into a Windows executable:

pp -o sample1.exe sample1.pl

The resulting executable will be named sample1.exe, as specified by the -o option to the Perl Packer, pp. pp converts the source script, sample1.pl, into Windows native code.

If you want to compress the executable, you can invoke the -z option with a mandatory integer argument, from 0 to 9. 9 will use the maximum possible compression. If -z is not specified, the compression level defaults to 6.

pp -o sample1.exe -z 9 sample1.pl

You can also eliminate more excess baggage by using the filter option (-f), which requires an argument:

pp -o sample1.exe -z 9 -f PodStrip sample1.pl

This filter strips away all POD sections from the executable.

GUI-based Script Compilation

If you hate command lines, there is tkpp, a GUI-based version of pp. When you use tkpp, you must first set up some important paths: the locations of your Perl interpreter and pp. To set these, click on "File" and then choose "Preferences."

On the "Source file" text box, type the complete path to your script, or click on the icon beside the text box. In doing so, a file browser will pop up, where you can choose your script. On the "Output file" text box, type the name of the executable you want to create. Leave all other buttons in their default states. At the bottom center of the GUI, there is a "Build" button. Click this button to proceed with the compilation of your script. You should see the message "Building..." while your script is being processed. When compilation is finally completed, you should see the message "Ready."

You can also build an executable without a console window by clicking on the "GUI" checkbox. This is ignored, however, on nonWindows systems. The command-line version of this is the -gui option:

pp -o sample1.exe -z 9 -f PodStrip -gui sample1.pl

That's it!

Obfuscation

Like LibZip, PAR can also hide your source code from casual snooping by turning your script into comment-free Perl code with mangled variable names. Of course, this is not the same as encryption and surely will not discourage a determined cracker. Obfuscation cannot offer 100-percent protection of your Perl code.

To obfuscate your code, use the -f filter with Obfuscate as argument:

pp -o sample1.exe -z 9 -f PodStrip \
  -f Obfuscate sample1.pl

A harmless message will be displayed during compilation, which looks something like this:

C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\6mBQIYqCex 
  syntax OK

Just ignore it.

But before you can use obfuscation, you must first install B::Deobfuscate, and before you can do that, you need to install B::Keywords and YAML, as well. Be sure to use Version 0.35 of YAML, and not the latest development release, or else you won't be able to install B::Deobfuscate.

Caveats

Using the diagnostics Module. Just like my experience with LibZip, I found out that having use diagnostics in my scripts will compile fine, but the resulting executables will not run properly. I often get error messages similar to this:

No diagnostics? at diagnostics.pm line 408.
Compilation failed in require at 
  xxxx/sample1.pl line 8.
BEGIN failed—compilation aborted at 
  xxxx/sample1.pl line 8.

I recommend that you remove or uncomment out diagnostics from your script prior to building its native code equivalent.

Using Obfuscation. One ugly problem with using obfuscation is that the generated executable is not always guaranteed to run. If I compile my script like this:

pp -o sample1.exe -f Obfuscate -z 9 \
  -f PodStrip sample1.pl

and run the resulting executable on the current directory,

sample1.exe . —recursive —all

I sometimes get this error:

Undefined subroutine &main::performance called 
  at sample1.pl line 41.

Even weirder is when I rearrange the order of arguments I feed to pp:

pp -o sample1.exe -f PodStrip -z 9 \
  -f Obfuscate sample1.pl

In which case I get a very different error:

String found where operator expected at 
script/hash.pl line 10, near 

"GetOptions 'digest=s'"
        (Do you need to predeclare GetOptions?)
syntax error at script/hash.pl line 10, near 
"GetOptions 'digest=s'"

But I don't get these errors if I run the original Perl script!

It looks like pp itself gets confused when obfuscation is performed. I have informed the PAR maintainers about this. Let's hope this problem goes away in the next release of PAR.

What can we learn from this? Aside from offering minimal source-code protection, use of obfuscation is also problematic. So, I discourage you from employing it, at least in its present state.

Using UPX. In the previous article where I discussed LibZip, I used UPX to compress the library and executable even more. This time, I attempted to use UPX on the executable generated by PAR. It compressed the executable all right, but when I ran the executable, I got a partial error:

IO error: reading header signature :
 at -e line 830
IO error: reading header signature :
 at -e line 830

But despite this imperfection, the executable still managed to proceed as usual.

Compiling Scripts For Linux. PAR works equally well on Linux. As a matter of fact, PAR also runs on FreeBSD, AIX, Solaris, HP-UX, NetBSD, and Mac OS. From the PAR FAQ: "The resulting executable will run on any platform that supports the binary format of the generating platform.''

The steps enumerated here also apply to nonWindows systems without the slightest modification.

TPJ



Listing 1

#!/usr/local/bin/perl

# sample1.pl
# Julius C. Duque

#use diagnostics;
#use strict;
#use warnings;
use Cwd;
use Getopt::Long;
use File::Find;
use Win32::Autoglob;
use Digest::MD5;

my $VERSION = "1.0.0 (for TPJ)";

my ($showfiles, $showdigests, $recursive, $all, $quiet, $help) = ();

GetOptions(
    "showfiles"   => \$showfiles,
    "showdigests" => \$showdigests,
    "recursive"   => \$recursive,
    "all"         => \$all,
    "quiet"       => \$quiet,
    "help"        => \$help
);

$showfiles = $showdigests = 1 if ($all);

syntax() if ($help or !@ARGV);

foreach my $infile (@ARGV) {
    chomp $infile;
    if (! -e $infile) {
        print "*** ERROR: $infile does not exist, skipping it...\n" if (!$quiet);
        next;
    } elsif (-d $infile) {
        if ($recursive) {
            find({wanted => sub {
                if (-f) {
                    print make_digest($_, 'MD5');
                    print "  $_" if ($showfiles);
                    print "  [MD5]" if ($showdigests);
                    print "\n";
                }
            }, no_chdir => 1}, $infile);
        } else {
            if (!$quiet) {
                print "*** ERROR: $infile is actually a directory, skipping it.\n";
                print "*** ERROR: Use --recursive if you want to process $infile recursively\n";
            }

            next;
        }
    } else {
        print make_digest($infile, 'MD5');
        print "  $infile" if ($showfiles);
        print "  [MD5]" if ($showdigests);
        print "\n";
    }
}

sub make_digest
{
    my ($file, $tmd) = @_;
    my $digest_obj;
    open INFILE, $file or die "Cannot open $file: $!";
    binmode INFILE;
    $tmd = $tmd =~ /^Digest::/ ? $tmd : "Digest::$tmd";
    eval "require $tmd";
    $digest_obj = new $tmd;
    $digest_obj->addfile(*INFILE);
    close INFILE;
    return $digest_obj->hexdigest;
}

sub syntax
{
    if ($^O eq "MSWin32") {
        $0 =~ s/.*\\//g;    # Windows
    } else {
        $0 =~ s/.*\///g;
    }

    print "$0 $VERSION\n\n";
    print "Usage: $0 file1 [file2 ...]\n";
    print "\n";
    print "Other options:\n";
    print "  --showfiles    print filenames\n";
    print "  --showdigests  print digests used\n";
    print "  --recursive    recursively descend into directory\n";
    print "  --all          implies --showfiles and --showdigests output\n";
    print "  --quiet        suppress error messages\n";
    print "  --help         print this help message\n";
    exit 1;
}
Back to article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 
Dr. Dobb's TV