The section in blue is a single-quoted string, using ; as the delimiter. Let's un-obfuscate that (adding some whitespace, and moving the semicolon from the next line up). Notice that the first character in the string is a newline.$_=q; $_ *= $_ ++; ;s;\S+;<rekcaH lreP rehtonA tsuJ>;eg; print q ... reverse;
This blue text is a substitution (again with ; as the delimiter). We also notice "Just Another Perl Hacker" backwards, and between less-than and greater-than signs, and that the regex has the e modifier. Hmm...$_ = ' $_ *= $_ ++'; s;\S+;<rekcaH lreP rehtonA tsuJ>;eg; print q ... reverse;
The print line is meant to make you think I'm using the ... operator (which is like the .. flip-flop operator). But it's actually using the q// operator, using . as the delimiter. So we're doing print '' . reverse;, which is concatenating an empty string and the return value of reverse($_), which calls reverse in scalar context. We have to call it in scalar context, since print is a list operator, and won't impose the scalar context on any of its arguments. The intermediate code is now:$_ = ' $_ *= $_ ++'; s/\S+/<rekcaH lreP rehtonA tsuJ>/eg; print q ... reverse;
The only mystery left is how this works. The left-hand side of the s/// matches non-whitespace. So on the first run-through, it matches the string '$_'. Then it substitutes in <rekcaH lreP rehtonA tsuJ> for it. What the hell does that mean? Well, that madness is actually a file-glob, just like <*.txt>.$_ = "\n" . '$_ *= $_ ++'; s/\S+/<rekcaH lreP rehtonA tsuJ>/eg; print scalar reverse($_);
Because they're operators, you can't use 'm', 's', 'tr', or 'y' as the name of a function, or as a filehandle, as I "tried" to do here. The blue code is actually a long m//, with a comma as the delimiter. This obviously returns an empty string, since the match fails, and then the code in red adds 0 to it.open m, "mail japhy\@pobox.com < /etc/passwd |"; print m "Just Another Perl Hacker", +0; seek$|=>($/=\24,$\="\012",$=--,$=--,$=)[$++4]=>$[; print <4>.<3>.<2>.<1>.<0>.<blastoff>; close m; exit;
As you may or may not know, one-argument open() uses a package variable of the same name as the constant used, so, in package main, saying open FOO; is like open FOO, $main::FOO;. Here, we use 0 as the filehandle, so Perl opens $0.open 0; seek$|=>($/=\24,$\="\012",$=--,$=--,$=)[$++4]=>$[; print <4>.<3>.<2>.<1>.<0>.<blastoff>; close m; exit;
This is why the JAPH must be in a file when it is run -- the Perl program must open itself. If I'd used the <DATA> filehandle, it wouldn't need to be in its own file, but the mechanism would've been far more obvious.
Lots of obfuscation. Using Perl's special variables, like $|, $/, $\, $=, $+, and $[. Let me substitute in the default values for a few of them:open 0, $0; seek$|=>($/=\24,$\="\012",$=--,$=--,$=)[$++4]=>$[; print <4>.<3>.<2>.<1>.<0>.<blastoff>; close m; exit;
It's still a bit of a mess inside those parentheses.open 0, $0; seek 0, ($/ = \24, $\ = "\012", $=--, $=--, $=)[0 + 4], 0; print <4>.<3>.<2>.<1>.<0>.<blastoff>; close m; exit;
So we've seeked to the location of the JAPH string, and now we get to print the 24-byte record. The other filehandles used here are just for show, and to hide the important one, <0>. But the important thing to realize is that I couldn't have just said print <0>;, since that would be list context, and return more than I want. So I concatenate again, with the empty string, to enforce scalar context.open 0, $0; seek 0, ($/ = \24, $\ = "\012", 60, 59, 58)[4], 0; print <4>.<3>.<2>.<1>.<0>.<blastoff>; close m; exit;
That's just another m//, closing a non-existent filehandle. Remember, m// returns the empty string in scalar context on failure, not 0.open 0, $0; $/ = \24; $\ = "\n"; seek 0, 58, 0; print scalar <0>; close m; exit;
You'll notice this program won't work, because the length is now different, and I've removed the string that it was going to print. But this is WHAT the program does, in plainer english.open 0, $0; $/ = \24; $\ = "\n"; seek 0, 58, 0; print scalar <0>;
use overload;BEGIN{overload::constant
"q",sub{$_[1]=~s/\^(.)/qq!"\\c$1"!/eeg,return$_[1]}}
print "Just Another Python^H^H^H^H^Herl Hacker\n";
The blue code uses the overload module, and then (at compile-time)
calls overload::constant() to intercept quoted strings. It's not so
much obfuscation, as a fun feature of overloading. The regex changes the
two-character representation of the escape sequences into the actual sequence.
use overload;
BEGIN {
overload::constant "q", sub {
$_[1] =~ s/\^(.)/qq!"\\c$1"!/eeg;
return $_[1];
}
}
print "Just Another Python^H^H^H^H^Herl Hacker\n";
And that's really all there is to it.
In reply to japhy's Obfuscation Review by japhy
| For: | Use: | ||
| & | & | ||
| < | < | ||
| > | > | ||
| [ | [ | ||
| ] | ] |