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