#!/usr/bin/perl
# shroud
# Copyright 2000 Robert Jones, Craic Computing, All rights reserved.
# This program is free software; you can redistribute it and/or modify it
# under the same terms as Perl itself.
# The software is supplied as is, with absolutely no warranty.
#-----------------------------------------------------------
# POD documentation section
#-----------------------------------------------------------
=head1 NAME
shroud - Make the source code of a perl program unreadable
=head1 SYNOPSIS
shroud [B<--input> perl_script] [B<--noheader>] [B<--nopod>] [B<--nocomments>] [B<--exclude> perl_regexp]
=head1 DESCRIPTION
The distribution of a commercial Perl script poses a problem to developers
in that the source code is, by default, available to anyone using the
code. Even with a strong licensing agreement, developers risk their
intellectual property being taken and used in the development of other
codes. Solutions exist for encrypting Perl code or compiling it to
byte-code prior to distribution but these have their own problems.
They may not permit POD documentation to be included in the files and
they may remove or obfuscate comments in the code related to
copyright and licensing issues.
This program provides an alternative approach in that it transforms your
nicely written and formatted perl code into something that is virtually
unreadable while still retaining all of the functionality of your code.
It does this by replacing variable names, declared with 'my' statements,
with arbitrary names and then by stripping extraneous whitespace and
comments.
After being shrouded your program will operate in exactly the same way
as the original. The logical sense of your code will not have been
changed in any way. It will, however, be extremely difficult to make
sense of the source code. This is an example of 'Security through
Obscurity' but it is a good compromise solution - pretty good protection
of your code at minimal 'cost' in terms of effort or complexity.
If you have had the dubious pleasure of trying to modify or port someone
else's code then you will realize how difficult it can be to figure
out what a piece of code is doing. After you have applied this
filter to the code then the task becomes much more difficult.
NOTE that only variable names that are declared with a 'my' statement in
the file are replaced, so as to avoid problems with libraries or perl special
variables like $0, $@, etc. This is an important restriction ! If you use
the 'strict' pragma then you'll be fine. If you don't use a single 'my'
statement then no variables will be replaced.
The default operation is to leave in all comments and POD documentation
but to replace all local declared variables, delete all blank lines
and strip out all leading whitespace from other lines.
Program options allow the user to strip out POD documentation, header
comments and all other comments. Header comments are defined as those at
the beginning of a file before any real code or POD documentation.
Typically these comments include the name of the program, the author and
any copyright and/or licensing information. It is often critical to
include this set of comments even though comments within the real code
can be eliminated. The 'nocomments' option does not strip the header comments.
In certain circumstances the user may wish to exclude certain variables
from the renaming step. This can be accomplished by using the 'exclude' option
and supplying a perl regular expression that matches that subset of variables.
=head1 OPTIONS
=item B<--input> input file
Specify the input perl script file
=item B<--noheader>
Strip out any header block of comments
=item B<--nopod>
Strip out any embedded POD documentation
=item B<--nocomments>
Strip out all comments
=item B<--exclude> Perl regexp
Exclude variables that match the pattern from the renaming process
=head1 EXAMPLES
shroud --nocomments --input mycode.pl
Strip all code comments and whitespace from 'mycode.pl' and replace all the
local variable names. Leave in the POD documentation and header comments.
shroud --nocomments --input mycode.pl --exclude '^foo_'
As above, but exclude variable names beginning with 'foo_' from being renamed.
shroud --nocomments --nopod --noheader --input mycode.pl
Remove all comments and documentation, strip whitespace and replace all
local variable names.
=head1 BUGS
Variable names where the '$' etc is escaped by a backslash in a
print statement will be replaced, which may not be the right thing
to do. For example, in the statement :
print "variable \$foo";
$foo will be replaced even though the user wanted '$foo' to appear in
the program output.
=head1 AUTHOR
Copyright 2000 Robert Jones, Craic Computing (jones@craic.com). All Rights Reserved.
This program is free software. You can redistribute it and/or modify it
under the same terms as Perl itself. The software is supplied as is, with
absolutely no warranty.
=head1 SCRIPT CATEGORIES
Unix/System_administration
=head1 OSNAMES
any
=head1 README
'shroud' is a script that will transform perl code into
virtually unreadable text, while retaining the full
functionality of that code. It is used to shroud the
source code of commercial perl programs. More information
is available from the POD documentation within the script
and from this URL: http://www.craic.com/resources/tech_notes/tech_note_2.html
=cut
#-----------------------------------------------------------
# End of POD documentation
#-----------------------------------------------------------
use strict;
use FindBin;
use lib "$FindBin::Bin";
use Getopt::Long;
my $filename = "";
my %varHash = ();
my $var = "";
my $i = 0;
my $line = "";
# Default is not to exclude the header comments or the POD documentation
my $excludePod = 0;
my $excludeHeader = 0;
my $excludeComments = 0;
my $headerFlag = 1;
my $podFlag = 0;
my $firstLine = 1;
my $excludeVarPattern = "";
#-----------------------------------------------------------
# Option handling
#-----------------------------------------------------------
my %options = ();
GetOptions(\%options, "input=s",
"exclude=s",
"nopod",
"noheader",
"nocomments",
);
if(defined $options{"input"}) {
$filename = $options{"input"};
} else {
die "You must specify an input file using the --input option\n";
}
if(defined $options{"nopod"}) {
$excludePod = 1;
}
if(defined $options{"noheader"}) {
$excludeHeader = 1;
}
if(defined $options{"nocomments"}) {
$excludeComments = 1;
}
if(defined $options{"exclude"}) {
$excludeVarPattern = $options{"exclude"};;
}
open INPUT, "< $filename" or die "Unable to open file $filename\n";
# Go through the code once extracting the names of all the
# variables ($xxx @xxx %xxx etc)
while() {
if(/(^|\s)my\s+\((.*?)\)/) {
# get lines like my ($a, $b, $c);
$line = $2;
} elsif(/(^|\s)my\s+(.*?)[\=\;]/) {
# get lines like my $a = 1;
$line = $2;
}
while($line =~ /[\$\@\%]\{?\s*(\w+)\s*\}?/g) {
$var = $1;
if($var =~ /$excludeVarPattern/) {
next;
}
$varHash{$var} = 1;
}
}
# Give each variable an alternate name
$i = 0;
foreach $var (sort keys %varHash) {
$varHash{$var} = newVariableName($i);
$i++;
}
# Second Pass - replace the variables
seek INPUT, 0, 0;
while() {
if($firstLine) {
print $_;
$firstLine = 0;
next;
}
if($headerFlag) {
if(/^\s*[^\s#]/ or $podFlag) {
$headerFlag = 0;
} else {
if(not $excludeHeader) {
print $_;
}
next;
}
}
if($podFlag == 0) {
if(/^\s*\=\w+/) {
$podFlag = 1;
next if $excludePod;
}
} else {
if(/^\s*\=cut/) {
$podFlag = 0;
}
next if $excludePod;
}
$line = $_;
if(not $podFlag) {
$line = replaceVariables($line);
$line = stripLeadingWhitespace($line);
if($excludeComments) {
$line = stripComments($line);
}
}
print $line;
}
close INPUT;
#------------------------------------------------------
sub stripLeadingWhitespace {
# Strip leading whitespace from lines
# and strips blank lines at the same time...
my $line = $_[0];
$line =~ s/^\s+//;
$line;
}
#------------------------------------------------------
sub stripComments {
# Strips comments from lines
my $line = $_[0];
if($line =~ /^\s*\#/) {
$line = "";
} elsif($line =~ /\s\#[^\'\"\$\@\%]+$/) {
$line =~ s/\s\#.*$//;
}
$line;
}
#------------------------------------------------------
sub replaceVariables {
my $line = $_[0];
my $var = "";
my $var1 = "";
my $newvar1 = "";
# Replace the variables
while(/(([\$\@\%]|\$\#)\{?\s*\w+\s*\}?)/g) {
$var = $1;
if($var =~ /([\$\@\%]|\$\#)\{?\s*(\w+)\s*\}?/) {
$var1 = $2;
if(exists $varHash{$var1}) {
$newvar1 = $varHash{$var1};
eval($line =~ s/([\$\@\%]|\$\#)(\{?\s*)$var1/$1$2$newvar1/);
}
}
}
$line;
}
#------------------------------------------------------
sub newVariableName {
# Replace a supplied INTEGER with an octal-based
# character string - eight characters
my $oldvar = $_[0];
my $newvar = "";
my @charlist = ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h');
my $str = sprintf "%08lo", $oldvar;
my $i = 0;
foreach($i=0; $i<8; $i++) {
$newvar .= $charlist[substr($str, $i, 1)];
}
$newvar;
}
#------------------------------------------------------