Take the 2-minute tour ×

Had some spam issues on my server and, after finding out and removing some Perl and PHP scripts I'm down to checking what they really do, although I'm a senior PHP programmer I have little experience with Perl, can anyone give me a hand with the script here:


(It was one long line of code, script was called list.pl)

The start of the script is:

$??s:;s:s;;$?::s;(.*); ]="&\%[=.*.,-))'-,-#-*.).<.'.+-<-~-#,~-.-,.+,~-{-,.<'`.{'`'<-<--):)++,+#,-.{).+,,~+{+,,<)..})<.{.)-,.+.,.)-#):)++,+#,-.{).+,,~+{+,,<)..})<*{.}'`'<-<--):)++,+#,-.{).+:,+,+,',~+*+~+~+{+<+,)..})<'`'<.{'`'<'<-}.<)'+'.:*}.*.'-|-<.+):)~*{)~)|)++,+#,-.{).+:,+,+,',~+*+~+~+{+<+,)..})

It continues with precious few non-punctuation characters until the very end:

share|improve this question
are you sure it's obfuscated? .) –  yedpodtrzitko Mar 6 '12 at 20:50
Looks like normal Perl to me... are you sure this script isn't a commercially licensed purposely-obfuscated one? We're not here to help you violate licensing agreements. –  Marc B Mar 6 '12 at 20:51
I would be interested to know if that does something, but I don't really want to run it at work. It reminds me of one of these –  Mattrition Mar 6 '12 at 20:53
Even a fairly experienced Perl programmer would cringe faced with trash like that. It doesn't much matter what it does; it is horrible code. –  Jonathan Leffler Mar 6 '12 at 20:55
@MarcB that would be really sneaky wouldn't it? Make it plain if you can and don't tell me the result if it looks "commercial" –  The Coder Mar 6 '12 at 21:05
show 2 more comments

3 Answers

up vote 25 down vote accepted

Replace the s;(.*);$_;see; with print to get this. Replace s;(.*);$_;see; again with print in the first half of the payload to get this, which is the decryption code. The second half of the payload is the code to decrypt, but I can't go any further with it, because as you see, the decryption code is looking for a key in an envvar or a cookie (so that only the script's creator can control it or decode it, presumably), and I don't have that key. This is actually reasonably cleverly done.

share|improve this answer
and strict compliant! –  mob Mar 6 '12 at 21:40
A co-worker points out that if the code was still running, one could do cat /proc/<pid>/env and probably find out what the encryption key was (via E_KEY or HTTP_COOKIE). –  CanSpice Mar 6 '12 at 22:17
@The Coder - was this script visible under your server root? If you want to pursue this further, maybe you want to put a new script in its place to capture the key. –  mob Mar 6 '12 at 22:19
++++++++++WOW!!!!!!!!!!! –  tchrist Mar 6 '12 at 22:57
I'd say it's quite clever if not very helpful. ;-) –  drnewman Mar 13 '12 at 1:23
add comment

For those interested in the nitty gritty... The first part, when de-tangled looks like this:

       s/(.*)/...lots of punctuation.../;

The $? at the beginning of the line is the pre-defined variable containing the child error, which no doubt serves only as obfuscation. It will be undefined, as there can be no child error at this point.

The questionmark following it is the start of a ternary operator


Which is also added simply to obfuscate. The expression returned for true is a substitution regex, where the / slash delimiter has been replaced with colon s:pattern:replacement:. Above, I have put back slashes. The other expression, which is the one that will be executed is also a substitution regex, albeit an incredibly long one. The delimiter is semi-colon.

This substitution replaces .* in $_ - the default input and pattern-searching space - with a rather large amount of punctuation characters, which represents the bulk of the code. Since .* matches any string, even the empty string, it will simply get inserted into $_, and is for all intents and purposes identical to simply assigning the string to $_, which is what I did:

$_ = q;]="&\%[=.*.,-))'-,-# .......;;

The following lines are a transliteration and another substitution. (I inserted comments to point out the delimiters)

y; -"[%-.:<-@]-`{-}#~\$\\;{\$()*.0-9\;\\_rs}&a-h;;
#^                       ^           ^          ^
#1                       2                      3

(1,2,3 are delimiters, the semi-colon between 2 and 3 is escaped)

The basic gist of it is that various characters and ranges -" (space to double quote), and something that looks like character classes (with ranges) [%-.:<-@], but isn't, get transliterated into more legible characters e.g. curly braces, dollar sign, parentheses,0-9, etc.


The next substitution is where the magic happens. It is also a substitution with obfuscated delimiters, but with three modifers: see. s does nothing in this case, as it only allows the wildcard character . to match newline. ee means to evaluate the expression twice, however.

In order to see what I was evaluating, I performed the transliteration and printed the result. I suspect that I somewhere along the line got some characters corrupted, because there were subtle errors, but here's the short (cleaned up) version:

s;(.*);73756220656e6372797074696f6e5f6 .....;;  # very long line of alphanumerics
s;(.*);704b652318371910023c761a3618265 .....;;  # another long line

The long regexes are once again the data containers, and insert data into $_ to be evaluated as code.

The s/(..)/chr(hex($1))/eg; is starting to look rather legible. It is basically reading two characters at the time from $_ and converting it from hex to corresponding character.

The next to last line &e_echr(\$_); stumped me for a while, but it is a subroutine that is defined somewhere in this evaluated code, as hobbs so aptly was able to decode. The dollar sign is prefixed by backslash, meaning it is a reference to $_: I.e. that the subroutine can change the global variable.

After quite a few evaluations, $_ is run through this subroutine, after which whatever is contained in $_ is evaluated a last time. Presumably this time executing the code. As hobbs said, a key is required, which is taken from the environment %ENV of the machine where the script runs. Which we do not have.

share|improve this answer
Thanks for taking the time to do all this. –  hobbs Mar 13 '12 at 5:14
You're welcome. It was a fun project. –  TLP Mar 13 '12 at 6:40
add comment

Ask the B::Deparse module to make it (a little more) readable.

share|improve this answer
I tried that on the code (which Perl said was OK when I checked with perl -c list.pl); DeParse didn't want to have anything to do with me - or maybe it was the code it complained about. (perl -MO=DeParse list.pl and some minor variants. The error was Undefined subroutine &B::DeParse::compile called at (eval 1) line 24. CHECK failed--call queue aborted..) –  Jonathan Leffler Mar 6 '12 at 21:19
@JonathanLeffler that's because Deparse is spelled Deparse, not DeParse. –  hobbs Mar 6 '12 at 21:23
Thanks - I'm an idiot for not spotting that. The Deparse'd output isn't a lot easier to understand, though some of the regexes stand out more clearly. –  Jonathan Leffler Mar 6 '12 at 21:50
add comment

Your Answer


