This detection identifies adversaries deploying obfuscated PHP webshells that utilize dynamic evaluation functions like $a($code) to execute base64-encoded payloads such as b374k, a common technique for hiding malicious logic within legitimate web applications. Proactive hunting in Azure Sentinel is essential because these encoded webshells often evade signature-based defenses, allowing attackers to establish persistent command-and-control channels that can be leveraged for lateral movement or data exfiltration before detection occurs.
rule WEBSHELL_PHP_Dynamic_Big
{
meta:
description = "PHP webshell using $a($code) for kind of eval with encoded blob to decode, e.g. b374k"
license = "Detection Rule License 1.1 https://github.com/Neo23x0/signature-base/blob/master/LICENSE"
author = "Arnim Rupp (https://github.com/ruppde)"
reference = "Internal Research"
date = "2021/02/07"
modified = "2025-08-18"
score = 50
hash = "6559bfc4be43a55c6bb2bd867b4c9b929713d3f7f6de8111a3c330f87a9b302c"
hash = "9e82c9c2fa64e26fd55aa18f74759454d89f968068d46b255bd4f41eb556112e"
hash = "6def5296f95e191a9c7f64f7d8ac5c529d4a4347ae484775965442162345dc93"
hash = "dadfdc4041caa37166db80838e572d091bb153815a306c8be0d66c9851b98c10"
hash = "0a4a292f6e08479c04e5c4fdc3857eee72efa5cd39db52e4a6e405bf039928bd"
hash = "4326d10059e97809fb1903eb96fd9152cc72c376913771f59fa674a3f110679e"
hash = "b49d0f942a38a33d2b655b1c32ac44f19ed844c2479bad6e540f69b807dd3022"
hash = "575edeb905b434a3b35732654eedd3afae81e7d99ca35848c509177aa9bf9eef"
hash = "ee34d62e136a04e2eaf84b8daa12c9f2233a366af83081a38c3c973ab5e2c40f"
id = "a5caab93-7b94-59d7-bbca-f9863e81b9e5"
strings:
//strings from private rule capa_bin_files
$dex1 = "dex\n0"
$dex2 = "dey\n0"
$pack = { 50 41 43 4b 00 00 00 02 00 }
//strings from private rule capa_php_new_long
// no <?=
$new_php2 = "<?php" nocase wide ascii
$new_php3 = "<script language=\"php" nocase wide ascii
$php_short = "<?"
//strings from private rule capa_php_dynamic
// php variable regex from https://www.php.net/manual/en/language.variables.basics.php
$dynamic1 = /\$[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff\[\]'"]{0,20}\s{0,20}\(\$/ wide ascii
$dynamic2 = /\$[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff\[\]'"]{0,20}\s{0,20}\("/ wide ascii
$dynamic3 = /\$[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff\[\]'"]{0,20}\s{0,20}\('/ wide ascii
$dynamic4 = /\$[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff\[\]'"]{0,20}\s{0,20}\(str/ wide ascii
$dynamic5 = /\$[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff\[\]'"]{0,20}\s{0,20}\(\)/ wide ascii
$dynamic6 = /\$[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff\[\]'"]{0,20}\s{0,20}\(@/ wide ascii
$dynamic7 = /\$[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff\[\]'"]{0,20}\s{0,20}\(base64_decode/ wide ascii
$dynamic8 = "eval(" wide ascii
//strings from private rule capa_gen_sus
// these strings are just a bit suspicious, so several of them are needed, depending on filesize
$gen_bit_sus1 = /:\s{0,20}eval}/ nocase wide ascii
$gen_bit_sus2 = /\.replace\(\/\w\/g/ nocase wide ascii
$gen_bit_sus6 = "self.delete"
$gen_bit_sus9 = "\"cmd /c" nocase
$gen_bit_sus10 = "\"cmd\"" nocase
$gen_bit_sus11 = "\"cmd.exe" nocase
$gen_bit_sus12 = "%comspec%" wide ascii
$gen_bit_sus13 = "%COMSPEC%" wide ascii
//TODO:$gen_bit_sus12 = ".UserName" nocase
$gen_bit_sus18 = "Hklm.GetValueNames();" nocase
// bonus string for proxylogon exploiting webshells
$gen_bit_sus19 = "http://schemas.microsoft.com/exchange/" wide ascii
$gen_bit_sus21 = "\"upload\"" wide ascii
$gen_bit_sus22 = "\"Upload\"" wide ascii
$gen_bit_sus23 = "UPLOAD" fullword wide ascii
$gen_bit_sus24 = "fileupload" wide ascii
$gen_bit_sus25 = "file_upload" wide ascii
$gen_bit_sus27 = "zuncomp" wide ascii
$gen_bit_sus28 = "ase6" wide ascii
// own base64 or base32 func
$gen_bit_sus29 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" fullword wide ascii
$gen_bit_sus29b = "abcdefghijklmnopqrstuvwxyz234567" fullword wide ascii
$gen_bit_sus30 = "serv-u" wide ascii
$gen_bit_sus31 = "Serv-u" wide ascii
$gen_bit_sus32 = "Army" fullword wide ascii
// single letter paramweter
$gen_bit_sus33 = /\$_(GET|POST|REQUEST)\["\w"\]/ fullword wide ascii
$gen_bit_sus34 = "Content-Transfer-Encoding: Binary" wide ascii
$gen_bit_sus35 = "crack" fullword wide ascii
$gen_bit_sus44 = "<pre>" wide ascii
$gen_bit_sus45 = "<PRE>" wide ascii
$gen_bit_sus46 = "shell_" wide ascii
//fp: $gen_bit_sus47 = "Shell" fullword wide ascii
$gen_bit_sus50 = "bypass" wide ascii
$gen_bit_sus52 = " ^ $" wide ascii
$gen_bit_sus53 = ".ssh/authorized_keys" wide ascii
$gen_bit_sus55 = /\w'\.'\w/ wide ascii
$gen_bit_sus56 = /\w\"\.\"\w/ wide ascii
$gen_bit_sus57 = "dumper" wide ascii
$gen_bit_sus59 = "'cmd'" wide ascii
$gen_bit_sus60 = "\"execute\"" wide ascii
$gen_bit_sus61 = "/bin/sh" wide ascii
$gen_bit_sus62 = "Cyber" wide ascii
$gen_bit_sus63 = "portscan" fullword wide ascii
$gen_bit_sus65 = "whoami" fullword wide ascii
$gen_bit_sus67 = "$password='" fullword wide ascii
$gen_bit_sus68 = "$password=\"" fullword wide ascii
$gen_bit_sus69 = "$cmd" fullword wide ascii
$gen_bit_sus70 = "\"?>\"." fullword wide ascii
$gen_bit_sus71 = "Hacking" fullword wide ascii
$gen_bit_sus72 = "hacking" fullword wide ascii
$gen_bit_sus73 = ".htpasswd" wide ascii
$gen_bit_sus74 = /\btouch\(\$[^,]{1,30},/ wide ascii
$gen_bit_sus99 = "$password = " wide ascii
$gen_bit_sus100 = "();$" wide ascii
// very suspicious strings, one is enough
$gen_much_sus7 = "Web Shell" nocase
$gen_much_sus8 = "WebShell" nocase
$gen_much_sus3 = "hidded shell"
$gen_much_sus4 = "WScript.Shell.1" nocase
$gen_much_sus5 = "AspExec"
$gen_much_sus14 = "\\pcAnywhere\\" nocase
$gen_much_sus15 = "antivirus" nocase
$gen_much_sus16 = "McAfee" nocase
$gen_much_sus17 = "nishang"
$gen_much_sus18 = "\"unsafe" fullword wide ascii
$gen_much_sus19 = "'unsafe" fullword wide ascii
$gen_much_sus24 = "exploit" fullword wide ascii
$gen_much_sus25 = "Exploit" fullword wide ascii
$gen_much_sus26 = "TVqQAAMAAA" wide ascii
$gen_much_sus30 = "Hacker" wide ascii
$gen_much_sus31 = "HACKED" fullword wide ascii
$gen_much_sus32 = "hacked" fullword wide ascii
$gen_much_sus33 = "hacker" wide ascii
$gen_much_sus34 = "grayhat" nocase wide ascii
$gen_much_sus35 = "Microsoft FrontPage" wide ascii
$gen_much_sus36 = "Rootkit" wide ascii
$gen_much_sus37 = "rootkit" wide ascii
$gen_much_sus38 = "/*-/*-*/" wide ascii
$gen_much_sus39 = "u\"+\"n\"+\"s" wide ascii
$gen_much_sus40 = "\"e\"+\"v" wide ascii
$gen_much_sus41 = "a\"+\"l\"" wide ascii
$gen_much_sus42 = "\"+\"(\"+\"" wide ascii
$gen_much_sus43 = "q\"+\"u\"" wide ascii
$gen_much_sus44 = "\"u\"+\"e" wide ascii
$gen_much_sus45 = "/*//*/" wide ascii
$gen_much_sus46 = "(\"/*/\"" wide ascii
$gen_much_sus47 = "eval(eval(" wide ascii
// self remove
$gen_much_sus48 = "unlink(__FILE__)" wide ascii
$gen_much_sus49 = "Shell.Users" wide ascii
$gen_much_sus50 = "PasswordType=Regular" wide ascii
$gen_much_sus51 = "-Expire=0" wide ascii
$gen_much_sus60 = "_=$$_" wide ascii
$gen_much_sus61 = "_=$$_" wide ascii
$gen_much_sus62 = "++;$" wide ascii
$gen_much_sus63 = "++; $" wide ascii
$gen_much_sus64 = "_.=$_" wide ascii
$gen_much_sus70 = "-perm -04000" wide ascii
$gen_much_sus71 = "-perm -02000" wide ascii
$gen_much_sus72 = "grep -li password" wide ascii
$gen_much_sus73 = "-name config.inc.php" wide ascii
// touch without parameters sets the time to now, not malicious and gives fp
$gen_much_sus75 = "password crack" wide ascii
$gen_much_sus76 = "mysqlDll.dll" wide ascii
$gen_much_sus77 = "net user" wide ascii
$gen_much_sus80 = "fopen(\".htaccess\",\"w" wide ascii
$gen_much_sus81 = /strrev\(['"]/ wide ascii
$gen_much_sus82 = "PHPShell" fullword wide ascii
$gen_much_sus821= "PHP Shell" fullword wide ascii
$gen_much_sus83 = "phpshell" fullword wide ascii
$gen_much_sus84 = "PHPshell" fullword wide ascii
$gen_much_sus87 = "deface" wide ascii
$gen_much_sus88 = "Deface" wide ascii
$gen_much_sus89 = "backdoor" wide ascii
$gen_much_sus90 = "r00t" fullword wide ascii
$gen_much_sus91 = "xp_cmdshell" fullword wide ascii
$gen_much_sus92 = "DEFACE" fullword wide ascii
$gen_much_sus93 = "Bypass" fullword wide ascii
$gen_much_sus94 = /eval\s{2,20}\(/ nocase wide ascii
$gen_much_sus100 = "rot13" wide ascii
$gen_much_sus101 = "ini_set('error_log'" wide ascii
$gen_much_sus102 = "base64_decode(base64_decode(" wide ascii
$gen_much_sus103 = "=$_COOKIE;" wide ascii
// {1}.$ .. |{9}.$
$gen_much_sus104 = { C0 A6 7B 3? 7D 2E 24 }
$gen_much_sus105 = "$GLOBALS[\"__" wide ascii
// those calculations don't make really sense :)
$gen_much_sus106 = ")-0)" wide ascii
$gen_much_sus107 = "-0)+" wide ascii
$gen_much_sus108 = "+0)+" wide ascii
$gen_much_sus109 = "+(0/" wide ascii
$gen_much_sus110 = "+(0+" wide ascii
$gen_much_sus111 = "extract($_REQUEST)" wide ascii
$gen_much_sus112 = "<?php\t\t\t\t\t\t\t\t\t\t\t" wide ascii
$gen_much_sus113 = "\t\t\t\t\t\t\t\t\t\t\textract" wide ascii
$gen_much_sus114 = "\" .\"" wide ascii
$gen_much_sus115 = "end($_POST" wide ascii
$weevely1 = /';\n\$\w\s?=\s?'/ wide ascii
$weevely2 = /';\x0d\n\$\w\s?=\s?'/ wide ascii // same with \r\n
$weevely3 = /';\$\w{1,2}='/ wide ascii
$weevely4 = "str_replace" fullword wide ascii
$gif = { 47 49 46 38 }
$fp1 = "# Some examples from obfuscated malware:" ascii
$fp2 = "* @package PHP_CodeSniffer" ascii
$fp3 = ".jQuery===" ascii
$fp4 = "* @param string $lstat encoded LStat string" ascii
$fp5 = "' => array('horde:"
$fp6 = "$messages['fileuploaderror'] = '"
condition:
//any of them or
not (
uint16(0) == 0x5a4d or
// <?xml
uint32be(0) == 0x3c3f786d or
// <?XML
uint32be(0) == 0x3c3f584d or
$dex1 at 0 or
$dex2 at 0 or
$pack at 0 or
// fp on jar with zero compression
uint16(0) == 0x4b50 or
1 of ($fp*)
)
and (
any of ( $new_php* ) or
$php_short at 0
)
and (
any of ( $dynamic* )
)
and
(
$gif at 0 or
(
(
filesize < 1KB and
(
1 of ( $gen_much_sus* )
)
) or (
filesize < 2KB and
(
( #weevely1 + #weevely2 + #weevely3 ) > 2 and
#weevely4 > 1
)
) or (
filesize < 4000 and
(
1 of ( $gen_much_sus* ) or
2 of ( $gen_bit_sus* )
)
) or (
filesize < 20KB and
(
2 of ( $gen_much_sus* ) or
4 of ( $gen_bit_sus* )
)
) or (
filesize < 50KB and
(
3 of ( $gen_much_sus* ) or
5 of ( $gen_bit_sus* )
)
) or (
filesize < 100KB and
(
3 of ( $gen_much_sus* ) or
6 of ( $gen_bit_sus* )
)
) or (
filesize < 160KB and
(
3 of ( $gen_much_sus* ) or
7 of ( $gen_bit_sus* ) or
(
// php files which use strings in the full ascii8 spectrum have a much hioher deviation than normal php-code
// e.g. 4057005718bb18b51b02d8b807265f8df821157ac47f78ace77f21b21fc77232
math.deviation(500, filesize-500, 89.0) > 70
// uncomment and include an "and" above for debugging, also import on top of file. needs yara 4.2.0
//console.log("high deviation") and
//console.log(math.deviation(500, filesize-500, 89.0))
)
// TODO: requires yara 4.2.0 so wait a bit until that's more common
//or
//(
// big file and just one line = minified
//filesize > 10KB and
//math.count(0x0A) < 2
//)
)
) or (
filesize < 500KB and
(
4 of ( $gen_much_sus* ) or
8 of ( $gen_bit_sus* ) or
#gen_much_sus104 > 4
)
)
) or (
// file shouldn't be too small to have big enough data for math.entropy
filesize > 2KB and filesize < 1MB and
(
(
// base64 :
// ignore first and last 500bytes because they usually contain code for decoding and executing
math.entropy(500, filesize-500) >= 5.7 and
// encoded text has a higher mean than text or code because it's missing the spaces and special chars with the low numbers
math.mean(500, filesize-500) > 80 and
// deviation of base64 is ~20 according to CyberChef_v9.21.0.html#recipe=Generate_Lorem_Ipsum(3,'Paragraphs')To_Base64('A-Za-z0-9%2B/%3D')To_Charcode('Space',10)Standard_Deviation('Space')
// lets take a bit more because it might not be pure base64 also include some xor, shift, replacement, ...
// 89 is the mean of the base64 chars
math.deviation(500, filesize-500, 89.0) < 23
) or (
// gzinflated binary sometimes used in php webshells
// ignore first and last 500bytes because they usually contain code for decoding and executing
math.entropy(500, filesize-500) >= 7.7 and
// encoded text has a higher mean than text or code because it's missing the spaces and special chars with the low numbers
math.mean(500, filesize-500) > 120 and
math.mean(500, filesize-500) < 136 and
// deviation of base64 is ~20 according to CyberChef_v9.21.0.html#recipe=Generate_Lorem_Ipsum(3,'Paragraphs')To_Base64('A-Za-z0-9%2B/%3D')To_Charcode('Space',10)Standard_Deviation('Space')
// lets take a bit more because it might not be pure base64 also include some xor, shift, replacement, ...
// 89 is the mean of the base64 chars
math.deviation(500, filesize-500, 89.0) > 65
)
)
)
)
}
This YARA rule can be deployed in the following contexts:
This rule contains 158 string patterns in its detection logic.
Here are 5 specific false positive scenarios for the PHP webshell using $a($code) for kind of eval with encoded blob detection rule, including suggested filters and exclusions:
Scenario: Automated Backup Scripts Executing Encoded Archives
base64_decode followed by an evaluation function to unpack and validate configuration snapshots stored as encoded strings.vssadmin, acronis_service) or filter URLs containing /backup/verify.php where the decoded blob size exceeds 50KB, indicating a data payload rather than an executable command.Scenario: CMS Plugin Updates and Theme Activation
eval(base64_decode(...)) to parse theme assets or migrate database schemas without user interaction./wp-content/plugins/, /sites/all/modules/) and restrict the rule trigger to files that are not recently modified (created > 24 hours ago) to avoid catching initial deployment noise.Scenario: Scheduled Cron Jobs for Data Transformation
$a($code) pattern to dynamically generate SQL queries or report templates based on the decoded input.