# 🔓 php_mt_seed

> **PHP `mt_rand()` seed cracker** — blazing-fast, SIMD-accelerated, OpenMP-parallelized.

![Language](https://img.shields.io/badge/Language-C-blue?style=flat-square&logo=c)
![SIMD](https://img.shields.io/badge/SIMD-SSE2%20%7C%20AVX2%20%7C%20AVX--512-orange?style=flat-square)
![OpenMP](https://img.shields.io/badge/OpenMP-Enabled-green?style=flat-square)
![PHP](https://img.shields.io/badge/PHP-3.0.7%20→%207.x+-purple?style=flat-square&logo=php)

---

## 📖 What is php_mt_seed?

`php_mt_seed` finds possible seeds given `mt_rand()` outputs after seeding with `mt_srand()`.

In its most trivial invocation it matches the **very first** `mt_rand()` output after seeding.  
With advanced invocation modes it can match **multiple, non-first, and/or inexact** outputs.

### 🕹️ PHP algorithm support

| PHP Version Range | Status |
|---|---|
| 3.0.7 → 5.2.0 | ✅ Supported |
| 5.2.1 → 7.0.x | ✅ Supported |
| 7.1.0+ | ✅ Supported |

---

## ❓ Why crack `mt_rand()` seeds?

`mt_rand()` is a **non-cryptographic PRNG** with a 32-bit seed space — far too small for any security-sensitive use. Yet many PHP applications misuse it where a CSPRNG is required.

### ⚠️ Common misuses that become attack surfaces

- 🛡️ Anti-CSRF tokens
- 🔑 Custom session tokens
- 🔄 Password reset tokens
- 🔐 Generated passwords
- 💾 Database backup filenames

> **Attack pattern:** if an exposed value (e.g. anti-CSRF token) and a secret value (e.g. password reset token) are generated from the same PRNG state without reseeding, cracking the seed from the exposed value lets an attacker infer the secret one.

### 🎯 Legitimate use cases

- 🔍 Source code security audits
- 🕵️ Network/system penetration tests
- 🏁 CTF (Capture The Flag) competitions
- 🔬 Demonstrating mt_rand() vulnerability to developers

> 💡 As a curiosity: certain **website-encrypting ransomware** misused `mt_rand()` as well — seed cracking enabled quick recovery of the encryption key.

---

## 🚀 How to use

`php_mt_seed` runs from the command line. Usage can be trivial or advanced.

### ✅ Trivial example

**Step 1** — Generate a "random" number with PHP:

```bash
$ php5 -r 'mt_srand(1234567890); echo mt_rand(), "\n";'
1328851649
```

**Step 2** — Feed the output to `php_mt_seed`:

```bash
$ time ./php_mt_seed 1328851649
Pattern: EXACT
Version: 3.0.7 to 5.2.0
Found 0, trying 0xfc000000 - 0xffffffff, speed 16261.0 Mseeds/s 
Version: 5.2.1+
Found 0, trying 0x1e000000 - 0x1fffffff, speed 91.8 Mseeds/s 
seed = 0x1fd65f9a = 534142874 (PHP 7.1.0+)
Found 1, trying 0x26000000 - 0x27ffffff, speed 91.9 Mseeds/s 
seed = 0x273a3517 = 658126103 (PHP 5.2.1 to 7.0.x; HHVM)
Found 2, trying 0x48000000 - 0x49ffffff, speed 91.9 Mseeds/s 
seed = 0x499602d2 = 1234567890 (PHP 5.2.1 to 7.0.x; HHVM)
seed = 0x499602d2 = 1234567890 (PHP 7.1.0+)
Found 4, trying 0xfe000000 - 0xffffffff, speed 91.9 Mseeds/s 
Found 4

real    0m47.028s
user    6m15.211s
sys     0m0.015s
```

In 47 seconds, 4 seeds were found — including the original `1234567890` — after searching the entire 32-bit seed space.

---

## 📐 Command-line syntax

`php_mt_seed` accepts **1, 2, 4, or more numbers** on the command line, specifying constraints on `mt_rand()` outputs:

| Arguments | Meaning |
|---|---|
| `1` number | Exact first `mt_rand()` output |
| `2` numbers | Min/max bounds for the first output |
| `4` numbers | Bounds + range passed into `mt_rand()` |
| `5+` numbers | Groups of 4 (then a final group of 1/2/4) for successive outputs |

### 💡 Matching exact values with range syntax

To match an exact value when using the multi-output syntax, repeat the value as both min and max, and use `0 2147483647` to indicate no range was passed:

```bash
$ time ./php_mt_seed 1328851649 1328851649 0 2147483647  1423851145
Pattern: EXACT EXACT
Version: 3.0.7 to 5.2.0
Found 0, trying 0xfc000000 - 0xffffffff, speed 15658.7 Mseeds/s 
Version: 5.2.1+
Found 0, trying 0x48000000 - 0x49ffffff, speed 91.9 Mseeds/s 
seed = 0x499602d2 = 1234567890 (PHP 5.2.1 to 7.0.x; HHVM)
Found 1, trying 0xfe000000 - 0xffffffff, speed 91.9 Mseeds/s 
Found 1

real    0m47.035s
user    6m15.273s
sys     0m0.004s
```

The additional constraint eliminated extra seeds with no performance penalty.

### ⏭️ Skipping outputs

To skip (ignore) one or more `mt_rand()` outputs, use `0 0 0 0` as a placeholder group:

```
0 0 0 0
```

This literally means "output must be from 0 to 0 as returned by `mt_rand(0, 0)`" — always true.

---

## 🧩 Complex usage example — Drupal password cracking

The following illustrates a real-world'ish attack based on the **vulnerable password generation function** in old versions of Drupal.

### The vulnerable PHP function

```php
<?php
function user_password($length = 10) {
  // This variable contains the list of allowable characters for the
  // password. Note that the number 0 and the letter 'O' have been
  // removed to avoid confusion between the two. The same is true
  // of 'I', 1, and l.
  $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';

  // Zero-based count of characters in the allowable list:
  $len = strlen($allowable_characters) - 1;

  // Declare the password as a blank string.
  $pass = '';

  // Loop the number of times specified by $length.
  for ($i = 0; $i < $length; $i++) {

    // Each iteration, pick a random character from the
    // allowable string and append it to the password:
    $pass .= $allowable_characters[mt_rand(0, $len)];
  }

  return $pass;
}

if ($argc === 2) {
  mt_srand($argv[1]);
}

echo user_password(), "\n";
echo user_password(), "\n";
echo user_password(), "\n";
?>
```

### Step 1 — Generate passwords for an unknown seed

```bash
$ php drupal.php
pAiwtk6Yed
HW9UPrqKWC
57CN74bkzL
```

### Step 2 — Convert the known password to `php_mt_seed` arguments

```php
<?php
$allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
$len = strlen($allowable_characters) - 1;
$pass = $argv[1];
for ($i = 0; $i < strlen($pass); $i++) {
  $number = strpos($allowable_characters, $pass[$i]);
  echo "$number $number 0 $len  ";
}
echo "\n";
?>
```

### 🔍 Understanding the `pw2args.php` output

```bash
$ php pw2args.php pAiwtk6Yed
14 14 0 56  25 25 0 56  8 8 0 56  21 21 0 56  18 18 0 56  10 10 0 56  53 53 0 56  47 47 0 56  4 4 0 56  3 3 0 56
```

Each group of 4 numbers corresponds to **one character of the password** and encodes a constraint for `php_mt_seed`. Here's how it works.

The allowable character string is:
```
abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789
```
57 characters total, indexed from `0` to `56` (i.e. `$len = 56`).

Drupal generates each password character by calling `mt_rand(0, 56)` and using the result as an **index** into that string. Reversing this is straightforward: the position of each character in the string is exactly the value `mt_rand()` must have returned.

The format of each group is: **`position position 0 56`**

- The two identical numbers (`X X`) mean: "`mt_rand()` must have returned **exactly X**" (min = max = X, i.e. an exact match constraint)
- `0 56` is the range Drupal passed to `mt_rand(0, $len)` — required so that `php_mt_seed` knows how to scale the raw PRNG output correctly

| Call # | Character | Position in string | Generated group |
|:---:|:---:|:---:|---|
| 1 | `p` | 14 | `14 14 0 56` |
| 2 | `A` | 25 | `25 25 0 56` |
| 3 | `i` | 8 | `8 8 0 56` |
| 4 | `w` | 21 | `21 21 0 56` |
| 5 | `t` | 18 | `18 18 0 56` |
| 6 | `k` | 10 | `10 10 0 56` |
| 7 | `6` | 53 | `53 53 0 56` |
| 8 | `Y` | 47 | `47 47 0 56` |
| 9 | `e` | 4 | `4 4 0 56` |
| 10 | `d` | 3 | `3 3 0 56` |

The 40 numbers concatenated become **10 simultaneous constraints** for `php_mt_seed`: a seed candidate is only valid if its first 10 `mt_rand(0, 56)` outputs match this sequence exactly. More constraints = fewer false positives = a drastically more targeted search compared to the trivial single-value case.

### Step 3 — Crack the seed

```bash
$ time ./php_mt_seed `php pw2args.php pAiwtk6Yed`
Pattern: EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57 EXACT-FROM-57
Version: 3.0.7 to 5.2.0
Found 0, trying 0xfc000000 - 0xffffffff, speed 4404.0 Mseeds/s 
Version: 5.2.1+
Found 0, trying 0xa0000000 - 0xa1ffffff, speed 210.0 Mseeds/s 
seed = 0xa1872e34 = 2709990964 (PHP 5.2.1 to 7.0.x; HHVM)
Found 1, trying 0xfe000000 - 0xffffffff, speed 210.1 Mseeds/s 
Found 1

real    0m21.441s
user    11m21.957s
sys     0m0.034s
```

### Step 4 — Predict all passwords from the cracked seed

```bash
$ php drupal.php 2709990964
pAiwtk6Yed
HW9UPrqKWC
57CN74bkzL
```

✅ All three passwords reconstructed — using only the **first one**.


---

<sub>php_mt_seed — originally by Solar Designer. README reformatted for readability.</sub>
