近期閱讀了一款開源遠控Havoc的源碼,留下了一些筆記,干脆發出來一起學習一下,這個遠控據說使用了很多高端免殺技術,比如Ekko,Ziliean,FOLIAGE睡眠混淆,返回地址欺騙,Indirect SysCall,Etw Patch,堆加密等等。
FullSessionGraph.jpeg
先簡單說說它TeamServer端是用golang寫的,Agent端是C寫的,UI是C++基于QT的,具體使用還是很多BUG,但抱著學習的心態來看看。
首先想著看看這個C2協議這塊咋樣,于是讀讀TeamServer端的代碼,看看目錄結構,命名還是很清晰明了的。
Pasted image 20230906161038.png
話不多說,直接進入主題,遠控提供了HTTP(S)、SMB的Agent,SMB是內網中繼直連用的,直接來看HTTP(S)方面的代碼。。
首先只有POST請求會被處理,其他請求都是直接跳fake404頁面。
h.GinEngine.POST("/*endpoint", h.request)h.GinEngine.GET("/*endpoint", h.fake404)
request里首先是對請求的header頭進行判斷,不符合直接跳fake404。
Pasted image 20230906160945.png
具體的Header頭定義在havoc.yaotl中。
Pasted image 20230906160453.png
然后是檢查url、ua,同樣是不符合跳fake404,默認配置的url是這樣子的。
Uris = [ "/funny_cat.gif", "/index.php", "/test.txt", "/helloworld.js"]
Pasted image 20230906161244.png
經過一連串的判斷后,來到parseAgentRequest函數,開始對Body內容進行判斷。
Pasted image 20230906161529.png
在ParseHeader中,最終是返回Header結構。
type Header struct { Size int MagicValue int AgentID int Data *parser.Parser}
Pasted image 20230906162130.png
NewParser時,試圖將body內容賦值給Parser結構的buffer中,至于bigEndian默認是true。
type Parser struct { buffer []byte bigEndian bool}
這里似乎將數據包分為了三種情況:
p.Length()小于4的情況下,直接丟棄該包,返回空的Response;
p.Length()等于4的情況下,直接將所有data復制到Header.Data中;
p.Length()大于4的情況下,將從末尾分別切出Size、MagicValue、AgentID,各為4個字節;
所以一個正常數據包的結構大致應該如下所示。
Pasted image 20230906172119.png
然后如果切出的MagicValue等于DEMON_MAGIC_VALUE,也就是0xDEADBEEF。
Pasted image 20230906170312.png
意味著是普通Deomon,否則是第三方Agent...等會,它是不是忘了什么?加解密呢?這不是白給么,建議做blueteam的小伙伴加一下流量規則。
繼續跟進到DemonAgent,進來直接查AgentID;
if Teamserver.AgentExist(Header.AgentID){ ...}else{ ...}
函數內容是迭代Teamserver中所有的Agent,true的話就是已經存在,先看false情況,也就是注冊的功能。
Pasted image 20230906171621.png
再次切掉一個CommandID,如果CommandID等于agent.DEMON_INIT也就是99,就意味著是注冊包,然后切掉RequestID丟掉,進入注冊流程。
Agent = agent.ParseDemonRegisterRequest(Header.AgentID, Header.Data, ExternalIP)if Agent == nil { return Response, false}go Agent.BackgroundUpdateLastCallbackUI(Teamserver)
接著從末尾切出AESKey和AESIv,并調用Parser.DecryptBuffer對Parser.buffer進行解密,解密完的結果放回buffer里。
Pasted image 20230906204004.png
所以數據包具體應該是這樣的。
Pasted image 20230906204313.png
至于解密出來的buffer,據官方說法如下。
[ Agent ID ] 4 bytes <-- this is needed to check if we successfully decrypted the data [ Host Name ] size + bytes [ User Name ] size + bytes [ Domain ] size + bytes [ IP Address ] 16 bytes? [ Process Name ] size + bytes [ Process ID ] 4 bytes [ Parent PID ] 4 bytes [ Process Arch ] 4 bytes [ Elevated ] 4 bytes [ Base Address ] 8 bytes [ OS Info ] ( 5 * 4 ) bytes [ OS Arch ] 4 bytes ..... more
如果注冊成功,將返回這個Agent的AgentID。
Pasted image 20230906205319.png
回到AgentExist,如果已經注冊,進入心跳包流程。
/* get our agent instance based on the agent id */Agent = Teamserver.AgentInstance(Header.AgentID)Agent.UpdateLastCallback(Teamserver)
先根據ID查出對象,更新心跳時間,同樣切出Command和RequestID。
Command = uint32(Header.Data.ParseInt32())RequestID = uint32(Header.Data.ParseInt32())
這里有點小混亂,劃分了第一次post的包和重連的包,如果是第一次提交,先進行解密(小聲叨叨:那重連的包不用解密了?)
Pasted image 20230906210103.png
然后判斷命令,是任務回顯還是GET_JOB。
Pasted image 20230906210153.png
如果是GET_JOB,就從Agent.GetQueuedJobs()拿任務,又分別對COMMAND_PIVOT、COMMAND_SOCKET、COMMAND_FS、COMMAND_MEM_FILE額外追加了一些參數,尤其是COMMADN_PIVOT內,如果是DEMON_PIVOT_SMB_COMMAND另外特殊處理。另外三個還沒有開發完畢,是空著的。
Pasted image 20230906211059.png
沒有細看,大意是對內網SMB Agent進行Socks代理時的特殊處理。
算了,通訊協議這部分大概就看到這里了,總結一下,其通訊協議整體來說是不那么可靠的。
CobaltStrike、Sliver常用的基本RSA+AES模式都沒有實現到,甚至AES密鑰同加密包一同發送。這個水準屬于是有點讓人失望了,希望在Agent端能夠讓人改觀。
本文作者:t43, 轉載請注明來自FreeBuf.COM
本文鏈接:http://www.tebozhan.com/showinfo-26-11886-0.htmlHavoc遠控源碼剖析(協議篇)
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: 50 種 ES6 模塊,面試被問麻了