Skip to content

Back to the catalog

dfir.chwriteup

Dissection of a PHP Backdoor leveraging php-win.exe

DFIR.ch details an incident response case where a PHP backdoor was executed via the GUI-mode php-win.exe (avoiding a visible console window) from a scheduled task named ClockLauncher. The malware stages its second-stage payload by parsing a utm_source parameter from a cutt.ly-shortened URL's redirect headers, double base64-decoding it, and eval()-ing the fetched code; the authors reproduce the technique in a lab and show it, plus a follow-on Bluetrait RMM agent install, went completely undetected by a tested EDR product.

TL;DR IR case dissects a stealthy PHP backdoor that hides its C2 payload in a URL shortener's utm_source redirect header and runs via php-win.exe to avoid a console window, evading EDR entirely.
Document

What it is

An IR case study of a PHP backdoor (5.php) found running as php-win.exe under a scheduled task ClockLauncher. The value of this page over the summary is the concrete artifacts: file paths, the task XML, the exact backdoor source, and the detection reasoning — enough to hunt for it.

How it works

The backdoor runs an infinite loop, sleeping a random 10–30s each iteration. It sends a curl HEAD-only request (CURLOPT_NOBODY, follow redirects, headers only) to a cutt.ly short link and regex-scans the returned headers for utm_source=. The extracted value is URL-decoded, then Base64-decoded twice to reconstruct a second-stage URL. It fetches that URL's body and passes it straight to eval('?>' . $response). The C2 pointer thus lives in the utm_source UTM parameter of a shortener's Location: redirect header — the visible short link can point anywhere innocuous. The author confirms the mechanism by creating his own cutt.ly link and showing the header:

$ curl -I -L https://cutt.ly/Yr7EQmQa
location: https://dfir.ch/posts/sysrv/?utm_source=dfir.ch

The $mem comparison means the payload only re-executes when the utm_source value changes.

Preconditions / Affected

Windows host with a PHP interpreter dropped by the attacker. Persistence was established as SYSTEM: the scheduled task runs S-1-5-18 at HighestAvailable on a BootTrigger. A second mechanism, a service ClockSystemService running as LocalSystem, was also present (stopped at investigation time).

What to do

Hunt for these host IOCs:

  • Directory C:\Windows\Temp\{0B1281F3-C9BC-4B85-AD92-0803ED04208F}\php_2\ containing run-clock.bat, php-win.exe, 5.php.
  • Scheduled task ClockLauncher (C:\Windows\System32\Tasks\ClockLauncher).
  • Service ClockSystemService under LocalSystem.
  • Outbound requests to cutt.ly (observed: https://cutt.ly/praXEwzs).

Detection note the post raises: the attacker chose php-win.exe rather than php.exe specifically because php.exe spawns a conhost.exe terminal window — php-win.exe runs windowless and quieter. Any php-win.exe process, especially from a Temp GUID directory or launched by a .bat, is worth alerting on.

flowchart LR
  T[ClockLauncher task / SYSTEM] --> B[run-clock.bat]
  B --> P[php-win.exe 5.php]
  P -->|HEAD, headers only| S[cutt.ly short link]
  S -->|Location: utm_source=| D[urldecode + double base64]
  D --> F[fetch payload URL]
  F --> E[eval]

Caveats

The page is truncated mid-sentence in the "Lab Time & Detection" section, so the full detection/YARA content beyond the php-win.exe vs php.exe observation isn't captured here. The second-stage payload itself was not recovered in the text — only the delivery mechanism.

Organisations named here

  • ClockLauncher
  • ClockSystemService

Technologies named here

  • curl
  • PHP
  • Windows
  • php-win.exe
  • AutoRuns
  • cutt.ly
  • Atera
  • Bluetrait
  • Pastebin
  • msiexec
  • EDR

Techniques named here

  • Scheduled task persistence
  • Windows Service persistence
  • Dead-drop resolver / C2 via URL shortener redirect
  • URL-encoding and double Base64 encoding of payload location
  • Dynamic code execution via eval
  • Use of GUI-subsystem binary to run invisibly
  • Living-off-the-land binary usage for payload delivery
  • Polling-based C2 check-in loop