Your interpreter isn’t safe anymore — The PHP module rootkit
Introduction
Before I begin, I want to make it clear that I condemn the act of using rootkits or any other form of malware without clear consent from the individual or organization being targeted. This post aims to educate the reader about the dangers of malicious PHP modules and outline the very real threat that they pose.
In this post I’ll start with a brief introduction to rootkits, explain the reasoning behind writing one for the PHP interpreter, and end by showing a proof of concept rootkit, including its source code. If that’s all you came here for, scroll down or click here.
A brief history of the Rootkit
Rootkits have existed since the early 90s (source); traditionally they’ve mainly resided in something software engineers like to refer to as ‘kernel space’. Unlike regular ‘user space’ applications, like the word processor and web-browser you use on a daily basis, code that runs in the kernel runs with complete authority.
In kernel space you are permitted to do absolutely anything. However, doing so comes with a risk, as any mistake you make will be punished heftily and result in your operating system crashing, which in turn could lead to massive data loss.
Many rootkits work in the roughly same way, they find out where the operating system handles the communication between the kernel and the user, and inject code that will act as a proxy between the two spaces, giving it the ability to read and modify all the data that passes through.
In software development, this process is also referred to as 'hooking' and has many legitimate uses too (like monkey-patching code by other developers as a temporary workaround).
Why write a rootkit as a PHP module?
So why would anyone write a rootkit for PHP, of all languages? Well, there are quite a few reasons, the most important being:
PHP is one of the most popular programming languages used on the web.
But this still doesn’t explain why you wouldn’t just go and write a kernel rootkit and intercept function calls from there. The reasons can be summed up as follows:
Accessibility
The first and very obvious reason why you would write a rootkit as a PHP module is accessibility. In my experience, learning how to use the Zend Engine (the framework the entire PHP language is built with) is a lot easier than learning how to write kernel modules, simply because the code base itself is smaller, better documented and a lot less complex.
Even without good documentation or tutorials, I managed to learn the basics of writing a PHP module within a day. If I (a novice C developer) can do it, the bad guys definitely can.
Stability
Since traditional rootkits are designed to run in kernel space, the chance of a poorly written rootkit crashing the entire system is very high. PHP rootkits don’t have this problem. While a poorly written PHP rootkit can certainly cause some damage, it can’t crash the entire underlying system.
In the worst-case scenario, a rootkit will cause a segmentation fault and just interrupt the current request (note: most web servers report this in their error log, so this could raise suspicion).
Detectability
Let’s be realistic, when is the last time you actually checked the file integrity of your PHP modules? If I were to name my module something deceptive like 'curl.so’, would you be paranoid enough to check if it’s actually the curl extension for PHP?
This, as well as the fact it’s custom code (meaning no antivirus signatures exist), no network-based IDS systems get triggered (since networking is managed by the web server and is expected) and the fact there are many inexperienced developers that only just know how to install PHP, leaves you with the perfect conditions for stealth.
Furthermore, kernel rootkits require you to hook system calls for every process rather than just one, this slows down your machine drastically, which might lead to more suspicion.
Portability
Not only do you get to enjoy the benefits of writing code in the user space, your rootkit also just became a cross-platform rootkit! This is because PHP is (in most cases) platform-independent. Code written for one platform can easily be compiled to be run on another platform (eg: modules written for Linux can often be compiled for Windows).
Proof of concept
Now comes the most exciting part, actually getting to show off how dangerous a malicious extension can be. In my examples, it will be totally obvious to the user that they’re being spied on, but with a few tiny tweaks this becomes essentially invisible to a system administrator.
Hooking cryptography methods
The two most important parts of writing a PHP rootkit are registering the actual rootkit itself, and hooking the target functions. The following screenshots are the actual code I used to achieve all of the required functionality, which came in at only 80 lines of code (including comment blocks).
Here you can see how a basic PHP extension is registered with the Zend Engine (the underlying framework of PHP). Notice the two strange lines of code in the PHP_MINIT_FUNCTION
method?
You’re looking at malicious hooks being injected into the global function table (used to look up what method is located where in memory), in this case I decided to hook the generic hashing method hash
and the more popular sha1
method.
The actual hook code works as follows:
- It starts out by locating the method you want to hook in the global function table, and storing a reference to it.
- If it successfully finds the function, a detour is added by storing the original location of the method in a variable called
original
and setting the value in the global function table to the address of thehook
variable.
If everything goes well, it will result in the hook getting called before the real method, which gives the rootkit author full control of it, meaning they could read out parameters, change return values, skip the function call altogether and do pretty much anything their heart desires.
After writing the base hook code, I decided to make the extension log all parameter data that was passed to the sha1
and hash
methods. The screenshot below shows what this looks like.
- The first command shows that the file ‘/tmp/php-module-rootkit.txt’ doesn’t exist, this will be important later.
- The second command is where the rootkit is loaded into the interpreter with the
-dextension={module}
parameter; this would normally be done by loading it throughphp.ini
. - After that we run our code using the
-r '{code}'
parameter, any code put in between the quotes will be executed by the PHP interpreter. In this example I make a call tosha1()
and echo the results on the screen. - In the last command we see that we read out the newly created ‘/tmp/php-module-rootkit.txt’ file, which contains the password we just hashed!
Disclaimer
I felt it was necessary to include this, since I still see people using weak cryptographic algorithms to store passwords: Please never EVER use weak cryptographic algorithms such as md5 or sha1 to store passwords, modern processors make it incredibly easy to crack thousands of passwords per second.
At the time of writing, using bcrypt is an acceptable standard to use. However, outsourcing your authentication via methods like OAuth (Google / Facebook login) is deemed even more secure as you don’t need to store the passwords at all.
Public Source Code
For those who wish to view the code, I’ve published the full source code of the rootkit on GitHub for public reference. To prevent malicious script kiddies from getting their hands on a weaponized PHP module, I’ve removed the compilation instructions alongside the implementation of the method hooks. Furthermore, I will not be releasing pre-compiled binaries.
Paradoxis/PHP-Rootkit on GitHub
Any semi-experienced C developer should be able to find out how to compile PHP modules and implement the the rootkit_hook_function
method.
Prevention
It wouldn’t be a security post if I didn’t at least talk about how you could prevent falling victim to malicious PHP modules. The most simple way of detecting whether or not any of your modules are malicious would be to keep a list of the module hashes after installing PHP.
Once you have a list of hashes, add a cron job that tries to hash all files in the extension directory and compares them to the current hash. The following Python script should do the trick: