bl4d3rvnner7/php-deobfuscator
GitHub: bl4d3rvnner7/php-deobfuscator
Stars: 0 | Forks: 0









# 🧬 php-deobfuscator - Universal PHP Deobfuscator
**An AST-based PHP deobfuscator that defeats the `${"\x47\x4c\x4f\x42\x41\x4c\x53"}[...]` / `$GLOBALS["key"]` variable-variable obfuscation used by phishing kits.**
It decodes hex/octal escaped strings, folds constant expressions, resolves indirect variable-variable lookups back to real names, strips the dead decoy assignments, and pretty-prints clean, runnable PHP.
Built on **nikic/php-parser v5.x**, so it works on a real syntax tree instead of fragile regex find-and-replace.
Perfect for reverse engineering, malware analysis, phishing-kit triage, and understanding obfuscated PHP.

## 📑 Table of Contents
- [The Obfuscation This Targets](#the-obfuscation-this-targets)
- [Features](#features)
- [Requirements](#requirements)
- [Installation](#installation)
- [Usage](#usage)
- [Basic Commands](#basic-commands)
- [Examples](#examples)
- [Sample Output](#sample-output)
- [Console Report](#console-report)
- [Generated Docblocks](#generated-docblocks)
- [How It Works](#how-it-works)
- [TODO](#todo)
- [Troubleshooting](#troubleshooting)
- [Contributing](#contributing)
- [Disclaimer](#disclaimer)
- [License](#license)
- [Support](#support)
- [Links](#links)
## The Obfuscation This Targets
If you are reverse engineering a phishing kit and the source looks like this — a wall of hex-escaped
`$GLOBALS` keys feeding variable-variable lookups — this is the tool for you:
_deobfuscated.` in the same directory, preserving the original extension
- [x] **CI-friendly** - color auto-disables when piped or when `NO_COLOR` is set
- [x] **Iterative** - re-runs passes until the AST stops changing (handles nested layers)
## Requirements
- PHP **7.1+** (PHP 8.x recommended)
- [nikic/php-parser](https://github.com/nikic/PHP-Parser) **v5.x**
composer require nikic/php-parser
## Installation
# Clone the repository
git clone https://github.com/bl4d3rvnner7/php-deobfuscator.git
cd php-deobfuscator
# Install the parser dependency (creates vendor/autoload.php)
composer require nikic/php-parser
# Optional: install globally
sudo ln -s "$(pwd)/universal-deobfuscator.php" /usr/local/bin/php-deobfuscator
## Usage
### Basic Commands
| Command | Description |
|---------|-------------|
| `php universal-deobfuscator.php input.php` | Deobfuscate → `input_deobfuscated.php` |
| `php universal-deobfuscator.php input.php out.php` | Deobfuscate to a custom output file |
| `php universal-deobfuscator.php --doc input.php` | Also generate heuristic docblocks |
| `php universal-deobfuscator.php --merge input.php` | Inline local `include`/`require` files first |
| `php universal-deobfuscator.php --color input.php out.php` | Force colored output even when redirected |
| `php universal-deobfuscator.php --help` | Show usage |
Flags combine freely, e.g. `--merge --doc`.
### Examples
# Basic deobfuscation (auto output name, same folder, same extension)
php universal-deobfuscator.php index.php
# Custom output path
php universal-deobfuscator.php 'phishing_kit/ys/index.php' clean.php
# Add auto-generated docblocks above each function/method
php universal-deobfuscator.php --doc index.php
# Multi-file: keys defined in helpers/config files, used in index.php
php universal-deobfuscator.php --merge 'phishing_kit/ys/index.php' clean.php
# Everything at once: merge includes, unwrap packers, add docblocks
php universal-deobfuscator.php --merge --doc index.php
# Works on any extension; output keeps it
php universal-deobfuscator.php login.phtml # -> login_deobfuscated.phtml
# Disable color for logs / CI
NO_COLOR=1 php universal-deobfuscator.php index.php
## Sample Output
### Console Report
PHP Deobfuscator
------------------------------------------------
[*] Input: index.php (31.92 KB)
[*] Merged: 4 files (main.php, block4.php, country.php, index.php)
------------------------------------------------
Transformations
[+] Constants folded/decoded 128
[+] Eval-packers unwrapped 2
[+] Indirect refs resolved 96
[+] $GLOBALS decoys removed 74
[+] Dead local assigns removed 41
[+] Docblocks generated 7
passes run: 4
removed vars: $xjzriqum, $kwpthywr, $duwqkzbk, $ctwsktz (+37 more)
------------------------------------------------
[+] Saved: ./index_deobfuscated.php
31.92 KB -> 14.12 KB (56% smaller)
### Generated Docblocks
With `--doc`, each function gets a heuristic summary inferred from its body — handy for
quickly spotting where a kit captures and exfiltrates credentials:
/**
* Handles POST input in capture_login().
*
* Side effects: sends email (mail); sends HTTP headers / redirects; reads or writes session data.
*
* Captured fields: xpass, xuser.
*/
function capture_login()
{
$ID = $_POST["xuser"];
$PW = $_POST["xpass"];
$_SESSION["xusername"] = $ID;
$_SESSION["xpassword"] = $PW;
mail($to, "creds", $ID . ":" . $PW);
header("Location: thankyou.php");
}
The **Captured fields** line lists the exact `$_POST` / `$_GET` / `$_COOKIE` / `$_FILES` keys the
function reads — the fastest way to map what a kit harvests.
## How It Works
The script parses the file once, then runs a small pipeline of AST passes per round, repeating until
nothing changes:
0. **Merge (optional, `--merge`)** - before anything else, local `include`/`require` targets with literal paths are recursively inlined into one combined source, so keys defined in another file resolve. Cycles and missing/dynamic/URL paths are skipped.
1. **Parse** - `nikic/php-parser` builds an AST. Hex/octal escapes inside string literals are decoded here, safely and for free.
2. **Constant fold** - constant concatenations and pure decode-function calls (`chr`, `base64_decode`, `str_rot13`, `strrev`, `gzinflate`, `gzuncompress`, `hex2bin`) collapse into literals.
3. **Unwrap packers** - once folding has turned an `eval(gzinflate(base64_decode(...)))` argument into a literal string of PHP, that string is compiled and its statements are spliced in place of the `eval`. Non-PHP or unparseable payloads are left untouched.
4. **Collect** - every constant string assignment to a plain variable or to `$GLOBALS["key"]` is recorded across the whole file, including single-assignment propagation (`$GLOBALS["a"] = $GLOBALS["b"]`).
5. **Resolve** - `${$GLOBALS["key"]}` and `${$var}` references are rewritten to their real `$names`, and the now-redundant `$GLOBALS["key"] = "name";` lines are dropped.
6. **Dead-code cleanup** - it recounts variable reads on the rewritten tree; any helper variable that was consumed only as an indirection name (and is read nowhere else) has its assignment removed. If a variable is genuinely used elsewhere, it is kept — logic is never altered.
7. **Docblocks (optional, `--doc`)** - a final pass inspects each function/method and prepends a generated summary, including the captured input field names.
8. **Print** - the standard pretty-printer emits clean, PSR-style PHP.
Because it operates on the AST, it does not suffer the classic regex-deobfuscator failure of corrupting
strings that happen to contain `{`, `}`, or backslash-digit sequences.
## TODO
### Done
* [x] AST-based hex/octal decoding
* [x] Constant folding (concat + pure decode functions)
* [x] `$GLOBALS` / `${$var}` variable-variable resolution
* [x] Dead decoy-assignment removal
* [x] Heuristic docblock generation (`--doc`)
* [x] Captured field names per function (e.g. "Captured fields: xuser, xpass")
* [x] Eval-packer unwrapping (`eval(gzinflate(base64_decode(...)))`)
* [x] Multi-file merge (`--merge` follows `include`/`require` to resolve cross-file keys)
* [x] Constant propagation for `$GLOBALS["a"] = $GLOBALS["b"]` / `$x = $GLOBALS["a"]` chains
* [x] Colored, CI-aware console report
### Planned
### Known Limitations
* Keys assigned dynamically (`$GLOBALS[$x] = $y`) or built at runtime cannot be resolved statically
* Constant propagation is intentionally conservative: a variable assigned more than once is never propagated (last-write-wins is ambiguous to a static tool)
* `--merge` only inlines `include`/`require` with literal local paths; URL wrappers and dynamic paths are left untouched by design
* Docblocks are heuristic hints to verify, not authoritative documentation
## Troubleshooting
| Issue | Solution |
|-------|----------|
| `Call to undefined method ParserFactory::create()` | You are on php-parser **v5**; this tool already uses the v5 API. Make sure you pulled the latest `universal-deobfuscator.php`. |
| `require_once vendor/autoload.php` fails | Run `composer require nikic/php-parser` in the tool's directory. |
| Output still shows `${$GLOBALS["..."]}` | The key may live in an included file — re-run with `--merge`. If it persists, the key is assigned dynamically; see [Known Limitations](#known-limitations). |
| `Parse error` printed, output unchanged | The input is not valid PHP (truncated paste, missing `
标签:ffuf