---
title: Backdooring the PHP interpreter
date: 2017-06-12
tags:
  - security
  - php
  - information-security
  - rootkit
  - programming
url: https://blog.paradoxis.nl/your-interpreter-isnt-safe-anymore-the-php-module-rootkit-c7ca6a1a9af5
author:
  name: Luke Paris
  bio: OSCP & OSWE certified Dutch security researcher specializing in active directory, malware development and all things devops. Currently work for Merlon Security in The Hague, and previously worked in Fox-IT's Red Team for 6 years.
  links:
    - type: linkedin
      url: https://www.linkedin.com/in/paradoxis
    - type: github
      url: https://github.com/paradoxis
    - type: unsplash
      url: https://unsplash.com/@paradoxis
context: 'This is a blog post that goes into the development of a PHP-module based backdoor, and shows how functions can be hooked to log passwords before being passed to hashing functions. The author notes that the post was written by a 20 year old, very inexperienced version of himself, and it no longer reflects the quality of work he strives to deliver today. The post was originally named "PHP Rootkit" and was later renamed to "PHP-Backdoor". The proof of concept code can be found on GitHub: https://github.com/Paradoxis/PHP-Backdoor'
---

# Backdooring the PHP interpreter

> **Editor’s note:** The following post was written in 2017 at the very start of my career in Cyber Security. I was inspired to make a simple proof-of-concept backdoor for the PHP interpreter after reading “Hacking Exposed: Malware and Rootkits” by Michael A. Davis. As such, I named the project the “PHP-Rootkit”.
>
> Ever since I published the post, I’ve received criticism that the project is misleading for being called a “rootkit”. Which in my opinion, is justified. It isn’t a rootkit, it’s a backdoor. So, in an effort to try to retroactively fix this, I’ve renamed all instances of the term “rootkit” to “backdoor”; granted that it makes sense in the context of the sentence.
>
> When reading this post, please keep in mind that it was written by a 20 year old, very inexperienced version of myself, and it no longer reflects the quality of work I strive to deliver today.

---

## Introduction

Before I begin, I want to make it clear that I condemn the act of using backdoors 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 server backdoors, explain the reasoning behind writing one for the PHP interpreter, and end by showing a proof of concept backdoor, including its source code. If that’s all you came here for, scroll down or click [here](https://github.com/Paradoxis/PHP-Backdoor).

![The Google results say enough about awareness](https://blog.paradoxis.nl/assets/img/figure-01-68645957-1600.webp "The Google results say enough about awareness")

## A brief history of rootkits

Rootkits have existed since the early 90s *(*[*source*](https://en.wikipedia.org/wiki/Rootkit#History)*);* traditionally they’ve mainly resided in something software engineers like to refer to as *‘*[*kernel space*](https://en.wikipedia.org/wiki/Protection_ring#SUPERVISOR-MODE)*’*. Unlike regular *‘*[*user space*](https://en.wikipedia.org/wiki/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 lots of data loss.

---

Many rootkits work by finding 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*](https://en.wikipedia.org/wiki/Hooking)' and has many legitimate uses too (like monkey-patching code by other developers as a temporary workaround).

![Example of how backdoors can intercept function calls](https://blog.paradoxis.nl/assets/img/figure-02-d3432dbf-2400.webp "Example of how backdoors can intercept function calls")

## Why write a backdoor as a PHP module?

So why would anyone write a backdoor 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 backdoor in the form of 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 backdoor 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](https://en.wikipedia.org/wiki/Kernel_space), the chance of a poorly written rootkit crashing the entire system is very high. PHP interpreter backdoors don’t have this problem. While a poorly written backdoor can certainly cause some damage, it can’t crash the entire underlying system.

In the worst-case scenario, an interpreter backdoor will cause a [segmentation fault](https://en.wikipedia.org/wiki/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](https://en.m.wikipedia.org/wiki/System_call) 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](https://en.wikipedia.org/wiki/User_space), your backdoor also just became a **cross-platform backdoor**! This is because PHP is *(in most cases)* platform-independent. Code written for one platform can easily be adapted and compiled to be run on another platform (eg: modules written for Linux can often be compiled for Windows).

## Proof of concept

Now comes the 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 backdoor are registering the actual module 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).

![Registering the module in the Zend Engine](https://blog.paradoxis.nl/assets/img/figure-03-d2c057ac-1452.webp "Registering the module in the Zend Engine")

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:

1. It starts out by locating the method you want to hook in the global function table, and storing a reference to it.
2. 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 the `hook` variable.

If everything goes well, it will result in the hook getting called before the real method, which gives the backdoor 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.

![Injecting ‘invisible’ function hooks](https://blog.paradoxis.nl/assets/img/figure-04-2553b720-1816.webp "Injecting ‘invisible’ function hooks")

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.

1. The first command shows that the file ‘/tmp/php-module-rootkit.txt’ doesn’t exist, this will be important later.
2. The second command is where the backdoor is loaded into the interpreter with the `-dextension={module}` parameter; this would normally be done by loading it through `php.ini`.
3. 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 to `sha1()` and echo the results on the screen.
4. 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!

![POC — Hooking the sha1 function to intercept all data](https://blog.paradoxis.nl/assets/img/figure-05-95aa9dae-1504.webp "POC — Hooking the sha1 function to intercept all data")

### Disclaimer

I felt it was necessary to include this, since I still see people using weak cryptographic algorithms to store passwords: Please **never** use weak cryptographic algorithms such as [*md5*](http://php.net/manual/en/function.md5.php) or [*sha1*](http://php.net/manual/en/function.sha1.php) to store passwords, modern processors and GPU’s make it incredibly easy to crack thousands of passwords per second.

At the time of writing, using [*bcrypt*](http://php.net/manual/en/function.password-hash.php) 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 backdoor 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-Backdoor on GitHub](https://github.com/Paradoxis/PHP-Backdoor)

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:

```python title="Hashes all PHP modules to keep track of their integrity"
from argparse import ArgumentParser
from subprocess import Popen, PIPE
from hashlib import sha1
from os import path, walk
import re
import os
from sys import stderr


def extension_dir():
    return Popen(["php-config", "--extension-dir"], stdout=PIPE).stdout.read().decode().strip()


def hash_files():
    for root, dirs, files in walk(extension_dir()):
        for file in files:
            file = path.join(root, file)
            yield file, hash_file(file)


def hash_file(file):
    with open(file, "rb") as data:
        return sha1(data.read()).hexdigest()


def check_hashes(hashes):
    with open(hashes) as file:
        for expected_path, expected_hash in (line.strip().split(", ") for line in file):
            if hash_file(expected_path) != expected_hash:
                yield expected_path


def main():
    parser = ArgumentParser()
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument("-l", "--list", help="List hashes, save these to a file which you will use later", action="store_true")
    group.add_argument("-c", "--check", help="Check that all hashes are equal in the given file")
    args = parser.parse_args()

    if args.list:
        for full_path, file in hash_files():
            print("{}, {}".format(full_path, file))
        else:
            print("[!] Unable to find any extensions", file=stderr)

    if args.check:
        for expected_path in check_hashes(args.check):
            print("[!] Potentially malicious extension detected: {}".format(expected_path), file=stderr)
        else:
            print("[+] No changes detected")


if __name__ == "__main__":
    main()
```
