How does this obfuscated Perl work?
perl
Solution
Step № 1 to understand obfuscated Perl: run it through `-MO=Deparse`. Then, we get this output: (with a small syntax error removed)
use warnings;
use strict;
my($japh, $q, $qq, %b) = "Just another Perl hacker\n";
$_ = join('', (1045737470, 21650696, 478656270, 205562730, 602861636, 965627100));
s/9/f/;
s/9/b/;
$q = $_;
*9 = sub {
$japh = '';
print $japh;
}
;
map {m{^((?i)(?#q#;
print \$japh;#()[^
for (my $(b) = 1; $(b) < $(q); \$b++;) {
/g])+[\](^.^)\[
}
]*$}x ? do {
$qq = ord($_) - 96 . (~~%b - $?);
$/ = $+;
$q =~ s[$qq][$/]g
} : 9->({});} 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z';
$qq = [$q, %b];
$\ = pack('h*', '' . $$qq[0]);
9->();
Still not pretty, but better. Especially, this regex looks interesting:
m{^((?i)(?#q#;
print \$japh;#()[^
for (my $(b) = 1; $(b) < $(q); \$b++;) {
/g])+[\](^.^)\[
}
]*$}x
The `(?# ... )` is an embedded comment, which we can remove. Next on are the character classes
[^
for (my $(b) = 1; $(b) < $(q); \$b++;) {
/g]
and
[\](^.^)\[
}
]
Here, they are equivalent to `[^bfgmoqry]` and `[\[\]()^.}\s]`, but as the regex will only ever be matched against single chars, the `[...]*` will match zero characters.
Thus, the regex is equivalent to
m/^([^bfgmoqry])$/
in this special case (being matched against `"a" .. "z"`).
The `*9 = sub {...}` assigns a coderef to a glob. Because `9` isn't a valid subroutine name, but can still be used with the `->` operator, `9->()` invokes that coderef. This is just like with `sub foo {...}`, `foo()` and `"foo"->()` are the same thing.
The char `a` is encoded as decimal `97` as ASCII, so `ord("a")-96` is `1`, for `z`, that would be 26.
The `$?` is the last child error, and should be zero. `$+` is the value of the last capture group (ergo, `$_`). The `~~%b` double-negates an empty hash. The scalarification of an empty hash is `0`, so double bitwise negation produces zero again.
The `$/ = $+; $q =~ s[$qq][$/]g` is just a bit obfuscation; as `$+` is `$_`, we could directly write `$q =~ s/$qq/$_/g`.
The `$qq = [$q, %b]; ... $$qq[0]` is lame, a `$\ = pack('h*', $q)` suffices.
In the `9` sub, the empty string will be printed, followed by `$\`, which is set to the empty string up to the penultimate line. Removing this level of indirection, and any now unused variables, as well as the simple substitutions, we get:
use warnings;
use strict;
my $q = '1045737470216506f6478656270205562730602861636b65627100';
for ("a" .. "z") {
m/^([^bfgmoqry])$/ or next;
my $qq = ord($_) - 96;
$q =~ s/${qq}0/$_/g;
}
print pack('h*', $q);
The `s/${qq}0/$_/g` substitution can only match at zeros, and we know that the output will be some hex-number for the `pack`. Possible positions for matching are:
1045737470216506f6478656270205562730602861636b65627100
*^ *^ *^ *^*^ *^*^ *^^
a g e g b c f a
of which `b, f, g` is forbidden due to the regex. The resulting string is
a45737470216e6f64786562702055627c602861636b65627a0
Which decodes to the JAPH.
Problem
``` #!/bin/perl use strict; use warnings; my($japh,$q,$qq,%b)= "Just another Perl hacker\n"; $_=join'',( 07625127776, 0122456410, 03441733416, 01420121552, 04373570104, 07143446334);s#9#f#;#s;#f#9#s;; s}9}b};$q=$_;*9= sub { $japh = ""; print $japh; }; map { /^((?i)(?#q#; print $japh;#()[^ for (my $(b) = 1; $(b) < $(q); \$b++;) { \/g])+[\](^.^)\[ } ]*$/x?do{$qq=((ord)-96).(~~%b-$?) ;$/#; =$+;$q=~s/$qq/${\/}/g;}:9->({}); }qw( a b c d e f g h i j k l m n o p q r s t u v w x y z );$qq=[$q,%b];$\=pack q*h\** ,qq$$.$$qq[0];9->(); ``` I think a lot of it is extra/purely obfuscation. I get that we assign "Just another Perl hacker" to $japh, and we print it but we assign it to "" before we do so I don't get how that still works. Can someone explain what's going on here? Thanks!