#!/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; } #------------------------------------------------------