Monday, July 21, 2008

Using Perltidy To Beautify Ugly Perl Scripts

Howdy again,

Today, we're going to look at almost the exact opposite of what we've done in the past in our posts on security through obfuscation (the series being linked back to on the most recent page) and look at a freely available, and highly configurable, way to unmangle Perl code. The program (or, to be more exact, the Perl Module) that takes care of that for us is called Perl::Tidy, which also, in the build process, creates a binary called perltidy that I find is more convenient to run tests with for the most part, since it requires no edits of the mangled Perl script and can easily be dumped to different output files or file descriptors via simple command line switches.

And, speaking of perltidy's command line switches, if you ever want to take a look at all of them (and there are more than a few ;), just type:

host # perltidy -h |more <-- The pipe to more is only a suggestion, but should make it easier to read the 3 to 4 pages of output you're going to get :) The output should be the same for everyone, but this was only specifically tested on Solaris Unix, RedHat Linux and Cygwin for Windows.

For a simple example of how this Perl Module/program works, let's take a look at a simple Perl program that asks for a username and password, after which it spits out the simple "crypt" result which you could cut-and-paste into your /etc/shadow file (if you're a cowboy ;)

host # cat crypt_mess.pl

#!/usr/bin/perl

#
# crypt_mess.pl
#
# This code is insane
#

print "Enter a user name: "; $name = <STDIN>; system("stty -echo"); print "Password: "; $pass = <STDIN>; system("stty echo"); print "\n"; chop($name); chop($pass); print &CryptPasswd($name,$pass); print "\n"; sub CryptPasswd { local($ph_alias,$clearpasswd) = @_; @saltset = ('a' .. 'z','A' .. 'Z', '0' .. '9','.','/'); $now = time(); ($pert1, $pert2) = unpack ("C2",$ph_alias); $week = $now / (60*60*24*7) + $pert1 + $pert2; $nsalt = $saltset[$week % 64] . $saltset[$now % 64]; $cryptpass = crypt($clearpasswd,$nsalt); return($cryptpass);}


Just for kicks, we'll make sure it works (which we could also reasonably assume from the output of a "perl -c" syntax check):

host # ./crypt_mess.pl
Enter a user name: user1
Password:
dTumj3pR1ZtjA


Cool (of course, if you really want to test it, you can use the crypt function to see if that string actually matches the password you typed in, like we did in a previous post on simple password guessing).

Now let's tidy up that code with perltidy (we'll also use the -syn flag to do the equivalent of a "perl -c" syntax check at the same time - this only shows output if there is an error in the syntax. You get no indication of the syntax being "OK" like you do with "perl -c"):

host # perltidy -syn crypt_mess.pl

Note that, since we didn't use any options, the default action for perltidy (insofar as file preservation is concerned) is to leave the original file alone and create a new "tidied up" file with a .tdy extension (using perltidy with the -b option will make a backup of the original file and inline-edit the existing file):

host # cat crypt_mess.pl.tdy
#!/usr/bin/perl

#
# crypt_mess.pl
#
# This code is insane
#

print "Enter a user name: ";
$name = <STDIN>;
system("stty -echo");
print "Password: ";
$pass = <STDIN>;
system("stty echo");
print "\n";
chop($name);
chop($pass);
print &CryptPasswd( $name, $pass );
print "\n";

sub CryptPasswd {
local ( $ph_alias, $clearpasswd ) = @_;
@saltset = ( 'a' .. 'z', 'A' .. 'Z', '0' .. '9', '.', '/' );
$now = time();
( $pert1, $pert2 ) = unpack( "C2", $ph_alias );
$week = $now / ( 60 * 60 * 24 * 7 ) + $pert1 + $pert2;
$nsalt = $saltset[ $week % 64 ] . $saltset[ $now % 64 ];
$cryptpass = crypt( $clearpasswd, $nsalt );
return ($cryptpass);
}


And it looks much nicer. But, and we should always do this test, does it still work?

host # ./crypt_mess.pl.tdy
Enter a user name: user1
Password:
dsT2fglQvgnyM


Assuming we've checked that the output string is good, this is good news! :) Of course, like I mentioned, perltidy has a ton of options (literally pages worth, unless you have an extremely high-rez monitor). For instance, if we wanted to conform to Gnu's formatting spec's (-gnu), and just print to STDOUT (-st), instead of creating a new file, we could do this:

host # perltidy -st -gnu crypt_mess.pl
#!/usr/bin/perl

#
# crypt_mess.pl
#
# This code is insane
#

print "Enter a user name: ";
$name = <STDIN>;
system("stty -echo");
print "Password: ";
$pass = <STDIN>;
system("stty echo");
print "\n";
chop($name);
chop($pass);
print &CryptPasswd($name, $pass);
print "\n";

sub CryptPasswd
{
local ($ph_alias, $clearpasswd) = @_;
@saltset = ('a' .. 'z', 'A' .. 'Z', '0' .. '9', '.', '/');
$now = time();
($pert1, $pert2) = unpack("C2", $ph_alias);
$week = $now / (60 * 60 * 24 * 7) + $pert1 + $pert2;
$nsalt = $saltset[$week % 64] . $saltset[$now % 64];
$cryptpass = crypt($clearpasswd, $nsalt);
return ($cryptpass);
}


And, lastly, it can also create output in HTML format, which may or may not be useful for you. It's very easy to implement, but does have its limitations. For instance:

host # perltidy -html crypt_mess.pl

will create HTML output, but (per the picture at the end of this post) it's not really very well formatted (This is due to the fact that perltidy, in this instance, doesn't really do too much tidying. You can get around this by running perltidy on your file and then running "perltidy -html" on your tidied file so that it looks better - see picture number 2). However, the code itself can be modified and basic css is included in the html file. You can also auto-create, or read in, a css style sheet by using that command line option. Note, here, that the default behaviour of the -css flag is to create the file if it doesn't exist but to leave it alone if it does:

host # perltidy -css=my.css -html crypt_mess.pl

And this is the default css stylesheet you can expect it to output if none exists:

host # cat my.css
/* default style sheet generated by perltidy */
body {background: #FFFFFF; color: #000000}
pre { color: #000000;
background: #FFFFFF;
font-family: courier;
}

.c { color: #228B22;} /* comment */
.cm { color: #000000;} /* comma */
.co { color: #000000;} /* colon */
.h { color: #CD5555; font-weight:bold;} /* here-doc-target */
.hh { color: #CD5555; font-style:italic;} /* here-doc-text */
.i { color: #00688B;} /* identifier */
.j { color: #CD5555; font-weight:bold;} /* label */
.k { color: #8B008B; font-weight:bold;} /* keyword */
.m { color: #FF0000; font-weight:bold;} /* subroutine */
.n { color: #B452CD;} /* numeric */
.p { color: #000000;} /* paren */
.pd { color: #228B22; font-style:italic;} /* pod-text */
.pu { color: #000000;} /* punctuation */
.q { color: #CD5555;} /* quote */
.s { color: #000000;} /* structure */
.sc { color: #000000;} /* semicolon */
.v { color: #B452CD;} /* v-string */
.w { color: #000000;} /* bareword */


And that should be enough to get you started. As I mentioned, perltidy has "a lot" of options. Using it straight-up, I find, is about all I ever really need, but you can certainly use this module to do more things than are dreamt of in my philosophy ;)

Click the picture below to see it in it's original size:

Sample perltidy HTML output

Click below here to see tidied up Perl that was then converted to HTML

Tidied up and them HTMLed Perl script

Cheers,

, Mike