# Recognizing Obfuscated PHP Code

> eval, base64_decode and more: how to recognize obfuscated PHP code, search for it safely and tell legitimate encoders apart from malicious code.

Source: https://phpsos.de/en/blog/verschleierter-php-code/  
As of: 25/10/2026  
Provider: PHP SOS, PHP emergency help and server security. Immediate help via WhatsApp: https://wa.me/491736494914?text=Hello%20PHP%20SOS%2C%20we%20need%20help%20with%20our%20PHP%20server.%20Company%3A%20%E2%80%A6%20Website%3A%20%E2%80%A6

**In short:** Obfuscated PHP code is code that has been deliberately made unreadable, usually through encoding and compression that is resolved only at runtime. Typical detection patterns are `eval` together with `base64_decode`, `gzinflate` or `str_rot13`, very long lines, hex strings and function names assembled from fragments. Search with harmless grep commands, never execute the code and compare findings with the original package.

## Why attackers hide their code

Whoever places a webshell or backdoor on a server wants it to stay undiscovered for a long time. Readable code would be noticed by any admin who opens the file, and any scanner would find it. That is why attackers wrap their code: it is encoded, compressed and put back together and executed only when called.

For PHP2Shell there are no confirmed indicators of compromise. Obfuscated code is not a feature of this vulnerability but a general sign that someone has injected code, no matter by which route. How to find and remove webshells in general is described in [Recognizing and removing webshells](https://phpsos.de/en/ratgeber/webshell-erkennen/). This article focuses on the question of how to recognize obfuscated code.

## eval, base64 and more: the typical building blocks

Obfuscation almost always follows the same principle: an unreadable block of data is turned back into code by one or more functions, and that code is then executed. The following building blocks appear again and again.

| Building block | Examples | Why it stands out |
|---|---|---|
| Execution | `eval`, `assert` with a string (up to PHP 7.x), `create_function` (up to PHP 7.x) | executes text as code |
| Decoding | `base64_decode`, `str_rot13`, `hex2bin`, `strrev`, `convert_uudecode` | makes data readable again |
| Decompression | `gzinflate`, `gzuncompress`, `gzdecode` | unpacks embedded code |
| Assembly | `chr()` chains, concatenation of short fragments, `\x` escapes | hides function names |
| Dynamic call | variables as function name, `call_user_func`, `array_map` with a string | the call appears nowhere in plain text |
| System access | `system`, `exec`, `shell_exec`, `passthru`, `proc_open`, `popen` | commands on the server |

According to the PHP manual, since PHP 8.0 `assert()` no longer evaluates strings as code, and `create_function()` was removed in PHP 8.0. On servers with older PHP versions, both remain relevant search terms.

A single building block is rarely suspicious; many applications legitimately use `base64_decode`, for example. The combination becomes suspicious: decoding whose result flows directly into an execution.

## Searching safely: harmless grep commands

The following commands search files only as text. They execute no PHP code. Adjust the path.

```bash
# Execution combined with decoding or decompression
grep -rlE "(eval|assert)\s*\(\s*(base64_decode|gzinflate|gzuncompress|str_rot13|hex2bin)" /var/www --include="*.php*"

# Very long lines (often packed code)
grep -rlE ".{2000,}" /var/www --include="*.php*"

# Many hex escapes or chr() chains in one line
grep -rlE "(\\\\x[0-9a-fA-F]{2}){20,}" /var/www --include="*.php*"
grep -rlE "(chr\s*\(\s*[0-9]+\s*\)\s*\.\s*){8,}" /var/www --include="*.php*"

# Function call via a variable right after an assignment from strings
grep -rnE "\\\$[a-zA-Z_]+\s*=\s*['\"][a-z_]{4,}['\"]\s*;\s*\\\$[a-zA-Z_]+\s*\(" /var/www --include="*.php*" | head -50
```

First check hits with `ls -l` and the timestamp and then open them only as text, for example with `less`. Never call suspicious files through the browser, because that would make the server execute them.

## Where obfuscated code typically sits

Obfuscated code rarely sits in a separate, conspicuous file. More often it is inserted into existing files, right at the beginning or right at the end, often after many blank lines so that it does not catch the eye immediately when opened. Popular targets are files that are loaded on every request: the central `index.php`, configuration files and a theme's function files.

A particularly inconspicuous route goes through the configuration. With the `auto_prepend_file` setting, PHP loads an additional file before every script. If it is in a `.user.ini` or `.htaccess` that an attacker created, his code runs on every page request without a single application file having been changed. The included file likes to carry a harmless extension such as `.ico` or `.png`.

```bash
# Is additional code loaded before or after every script?
grep -rnE "auto_(prepend|append)_file" /var/www --include=".user.ini" --include=".htaccess" --include="*.conf" 2>/dev/null

# PHP code in files with an image or icon extension
grep -rlE "<\?php" /var/www --include="*.ico" --include="*.png" --include="*.jpg" --include="*.gif" 2>/dev/null
```

## Telling legitimate obfuscation from malicious code

Not all unreadable code is malicious. Three cases are common:

1. **Commercial encoders:** Some plugin and software vendors protect their code with tools such as ionCube or SourceGuardian. The files are unreadable but typically point to the required loader. Compare them with the original package.
2. **Minified or generated code:** Caches and template engines produce long lines without line breaks, though without executing decoded text.
3. **Embedded data:** Images or fonts as a Base64 string in a PHP file are unusual but harmless as long as the result is not executed.

The most reliable test is comparison with the source: if the file comes unchanged from the vendor's package, it is legitimate. If it is in no original package or deviates from it, it should be investigated.

> **Important:**
> Do not try to decrypt obfuscated code on the production server, for example by replacing `eval` with an output and calling the file. A single mistake in doing so executes the malicious code. The analysis belongs in an isolated environment.

## Limits of pattern searching

Attackers know the common search commands. They split function names, assemble them at runtime or move the actual code out into the database, into image files or into configuration entries. A pattern search therefore finds a lot, but not everything. Why scanners, too, reach their limits here is explained in [Malware scanners for websites: what they find and what they do not](https://phpsos.de/en/blog/grenzen-von-malware-scannern/).

A find is also rarely the only one. Attackers often use the same building block in several files, such as a conspicuous variable name or a distinctive string at the start of the encoded block. After a confirmed find, therefore, search specifically for exactly this string in the entire web directory, including other websites on the same server. That way you find siblings that none of the general pattern searches captured.

Supplement the search with comparison against original files and a look into the database, see [Checking the database for injected code](https://phpsos.de/en/blog/datenbank-auf-schadcode-pruefen/). As a preventive measure, system functions that are not needed can be blocked, as described in the guide [Using disable_functions correctly](https://phpsos.de/en/ratgeber/disable-functions/).

## How does PHP SOS help?

- **Search:** We search files and database with extended patterns and compare against original packages.
- **Analyze:** We examine suspicious code in an isolated environment without endangering your system.
- **Clean up:** With [malware removal](https://phpsos.de/en/leistungen/malware-entfernung/), we remove all components and close the entry route.
- **Secure:** We limit PHP functions and write permissions and monitor new or changed files.


## Frequently asked questions

**Is eval with base64_decode always malicious code?**  
Not always, but very often. Legitimate code hardly needs this combination. In files that do not belong to your application or a known plugin, it is a strong warning sign.

**May I decode obfuscated code in order to read it?**  
Only without executing it and not on the production server. Even accidental execution can do damage. When in doubt, leave the analysis to specialists who work with isolated environments.

**Why does my malware scanner not find the code?**  
Scanners look for known patterns. Attackers constantly change their code, split function names or assemble them at runtime. An empty scan is therefore no proof of a clean server.

**What about commercially encrypted plugins?**  
Some vendors protect their code with encoders such as ionCube or SourceGuardian. Such files are unreadable but legitimate if they come from the vendor. Compare them with the original package.

**Does disable_functions help against obfuscated code?**  
Partly. Functions such as exec or system can be blocked, eval cannot, because it is a language construct and not a function. The block makes the attacker's next step harder but does not prevent every piece of malicious code.

## Sources

- [PHP Manual: assert()](https://www.php.net/manual/de/function.assert.php)
- [PHP Manual: create_function()](https://www.php.net/manual/de/function.create-function.php)
- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
