APT案例分析:一個基於Meterpreter和Windows代理的攻擊事件

前言

幾個月前,在只可以通過代理進行訪問的公司windows網路中,我對其進行了我開發的模擬定製的APT攻擊。在測試過程中,我意外的發現我可以上傳https返回類型的meterpreter後門。一開始,我並不確定這個地方存在漏洞,或者這個地方對APT攻擊是否起作用。為了驗證這個地方是否存在漏洞,我現在需要處理好代理環境。

在對環境做了深入分析之後,我們使用的meterpreter模塊(windows/meterpreter/reverse_https)並沒有攻擊成功。

在介紹詳細攻擊信息前,我先介紹一下測試環境:

1. 靶機系統以及IP:Windows 8.1 x64商業版本 10.x.x.189n2. 進入內網途徑:通過認證代理n3. 代理ip以及埠:10.x.x.20:8080n4. 通過代理的外部Ip:190.x.x.xn5. 攻擊者機器:190.y.y.yn6. meterpreter選擇模塊:windows/meterpreter/reverse_httpsn

注意:作為提醒,這裡的meterpreter後門執行過程是分階段的,執行第一階段會後會通過反向注入將真正的攻擊載荷(metsrv.x86.dll或者metsrv.x64.dll)注入到內存當中。下面的截圖展示了靶機的外部ip:

下面截圖展示了靶機中的代理設置:

下面截圖展示了靶機中使用」autoprox.exe」.很顯然看到通過dhcp獲得代理設置。

在上面這張圖中我們可以觀察到,訪問」www.google.com「時,我們經過了10.x.x.20:8080代理伺服器。注意:根據我的分析,autoprox.exe(pierrelc@microsoft.com提供)會使用windows的API首先搜索通過DHCP的代理設置,如果沒有找到,他會繼續搜索通過DNS的代理設置。

攻擊過程分析

在分析這一問題過程中,我會更改一些meterpreter攻擊載荷的代碼,然後在被攻擊靶機中測試。因此這就需要我們產生https類型或者使用其他類型的meterpreter後門源碼。

注意:可以通過shellter或者任何受信任的軟體像putty.exe產生一個簡單的後門,另外使用powershell產生web協議模塊的meterpreter後門。我們會對產生的攻擊載荷進行修改,所以對所有的攻擊環境,只需要產生一個後門代碼。

我們在靶機中執行產生的後門軟體,攻擊值執行偵聽後門程序,觀察會發生什麼。接下來的屏幕截圖中展示了攻擊機中在443埠正在執行的偵聽,連接之後並沒有執行成功命令。

上圖中,我們可以發現靶機已經反向連接回我們的偵聽機器,然後獲得了一個meterpreter的shell。但是,這個shell並不能執行任何命令,緊接著這一會話就結束了。

從高層次的觀點來看,當靶機執行後門之後,會反彈回攻擊者機器上,然後下載一個相對較大的攻擊代碼進而注入到內存當中,然後持續控制靶機。這時被注入的meterpreter會再次連接攻擊者機器,然後運行攻擊者與靶機進行交互。

那麼現在從我們看到的現象中,我們可以推斷出第一階段已經成功,並且可以通過代理反彈到攻擊者機器。但是,當第二階段注入過程中,可能發生一些錯誤,導致命令執行不成功以及會話結束。

注意:在這種情況下,你可能會想知道是不是殺毒軟體進行了攔截,或者說網路管理員對https內容進行了監視。我手動創建了一個PEM證書,然後配置監聽器以便讓這一證書發揮作用,當metasploit的偵聽被訪問時,比較瀏覽器上觀察的指紋與剛剛創建的證書的指紋,以便確定證書在傳輸過程中並沒有被替換。這一步驟促使我繼續在別的方面尋找問題。

接下來,按照一般的思路很明顯就是嗅探在這一過程中的網路流量,去了解更多這一步驟發生的情況(從黑盒情況下來講)。

下面的截圖展示了通過wrieshark嗅探靶機的流量通信。

在上圖中,可以觀察到靶機(10.x.x.189)和代理伺服器(10.x.x.20:8080)的TCP流量通信。其中,靶機在第一個數據包中發送與攻擊機器(190.x.x.x:443)請求安全(SSL/TLS協議)連接的數據包。另外,還可以看到請求過程中使用了NTLM身份驗證(NTLMSSP_AUTH),並且代理伺服器返回的響應為「建立連接」(HTTP/1.1

200),經過這些通信之後,SSL/TLS協議握手成功。

值得一提的是在上圖中展示了在執行後門之後的第一階段的網路通信發送以及接受是正常的。當連接建立之後,在兩端(客戶端以及服務端)經典的SSL/TLS握手已經形成,接下來在加密的頻道,攻擊第二階段的後門從攻擊機到靶機進行傳輸。

現在我們已經確定在部署meterpreter的第一個部分是沒有問題的,接下來就是為了了解第二部分發生了什麼,也就是在第一部分的攻擊載荷以及msf偵聽之間的通信中發生了什麼。為了找到答案,我們只需要繼續通過wireshark分析網路流量。

下方截圖展示了第一階段攻擊載荷以及msf偵聽之間的通信,並且試圖在靶機不使用代理直接與攻擊機器進行通信。

上述圖像前五個數據包中,可以看到靶機(10.x.x.189)與代理伺服器(10.x.x.20)通信中TCP連接終止欄位(FIN,ACK;FIN.ACK;ACK;),接下來可以看到第六個數據包中包含從靶機不經過代理伺服器發送到攻擊機的TCP

SYN標誌位(發起TCP握手)。最後,在靶機從網管接收到的第七個數據包中可以看到攻擊機機器不可達,不能從該網路直接訪問(還記得我說過這種情況需要代理到達互聯網)。

通過觀察這一過程的流量通信,可以理解為meterpreter會話中斷的原因是第二截斷的後門載荷不能返回到msf的偵聽中。

現在我們將要做的就是下載meterpreter的源代碼,然後嘗試去了解發生這一情況的根本原因。為了做這一任務,我們應該參考在Rapid7的github裡面的」Buiding-Windows」指南。現在按照指南的建議,我們可以使用Visual Studio 2013打開項目解決方案文件(metasploit-payloadscmeterpreterworkspacemeterpreter.sln),之後就開始審計源代碼吧。

審計完源代碼之後,我們可以看到」server_transport_winhttp.c」文件中有一個實現代理的邏輯實現。(請通過參考引用快速定位源文件)

下面的屏幕截圖展示了meterpreter設置代理的部分代碼:

我從github中meterprter的reverse_https的相關線程中了解到,後門首先會嘗試使用WinHTTP Windows API來訪問互聯網。我們發現在源代碼中存在大量用於調試的dprintf語句,並且這將在我們運行過程中提供大量有價值的信息。

為了使調試信息能夠展示給我們,我們將common.h源代碼中的頭文件中的DEBUGTRACE預處理常量進行修改。這會使載入meterpreter動態鏈接庫的伺服器產生可以使用VS進行調試的窗口以及可以使用DebugView或者_Windbg_進行讀取信息。

下面的屏幕截圖是展示了在common.h源代碼中注釋的原始DEBUGTRACE常量。

下面截圖是展示了我們需要獲得調試信息進行的修改:

進行修改之後,我們將編譯好的放在「metasploit-payloadscmeterpreteroutputx86」位置中的metsrv.x86.dll複製到攻擊機的相應目錄。比如在我的機器中,目錄為:「/usr/share/metasploit-framework/vendor/bundle/ruby/2.3.0/gems/metasploit-payloads-1.1.26/data/meterpreter/」。

在測試靶機中,運行」DebugView」工具然後運行剛剛產生的後門,再反彈回攻擊機器一個meterpreter會話。

接下來的截圖是展示了在靶機中進行調試的調試信息。

從meterpreter產生的調試信息中,可以通過dprintf發現調試信息中的70到74行對應的是server_transport_winhttp.c源代碼中的48到52行。在71行中[PROXY]AutoDetect:yes表示在靶機中已經找到了AutoDetect代理設置。但是經過代理的url是空的。最後,可以在75行看出後門嘗試發出一個get請求,但是發送失敗。

根據meterpreter後門的調試信息,我們現在已經快接近問題的根源所在。問題好像是由於代碼塊在處理windows代理過程中發生了問題。為了解決這一問題,我們需要分析源代碼進行修改以及調試。

如果多次產生meterpreter的C源代碼然後將產生的netsrv dll到攻擊者機器中進行測試這樣太浪費時間。於是使用python比較簡單的對代理模塊的代碼進行複製,並且在靶機中進行多次調試。

以下代碼是Meterpreter代碼片段,可以在server_transport_winhttp.c源代碼中找到,但是是以Python形式進行編寫:

import ctypesnimport ctypes.wintypesnimport sysnclass WINHTTP_CURRENT_USER_IE_PROXY_CONFIG(ctypes.Structure):n _fields_ = [("fAutoDetect", ctypes.wintypes.BOOL),n ("lpszAutoConfigUrl", ctypes.wintypes.LPWSTR),n ("lpszProxy", ctypes.wintypes.LPWSTR),n ("lpszProxyBypass", ctypes.wintypes.LPWSTR)]nclass WINHTTP_AUTOPROXY_OPTIONS(ctypes.Structure):n _fields_ = [("dwFlags", ctypes.wintypes.DWORD),n ("dwAutoDetectFlags", ctypes.wintypes.DWORD),n ("lpszAutoConfigUrl", ctypes.wintypes.LPCWSTR),n ("lpvReserved", ctypes.c_void_p),n ("dwReserved", ctypes.wintypes.DWORD),n ("fAutoLogonIfChallenged", ctypes.wintypes.BOOL)]nclass WINHTTP_PROXY_INFO(ctypes.Structure):n _fields_ = [("dwAccessType", ctypes.wintypes.DWORD),n ("lpszProxy", ctypes.wintypes.LPCWSTR),n ("lpszProxyBypass", ctypes.wintypes.LPCWSTR)]n# dwFlags valuesnWINHTTP_AUTOPROXY_AUTO_DETECT = 0x00000001nWINHTTP_AUTOPROXY_CONFIG_URL = 0x00000002n# dwAutoDetectFlags valuesnWINHTTP_AUTO_DETECT_TYPE_DHCP = 0x00000001nWINHTTP_AUTO_DETECT_TYPE_DNS_A = 0x00000002n# Parameters for WinHttpOpennWINHTTP_USER_AGENT = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"nWINHTTP_ACCESS_TYPE_DEFAULT_PROXY = 0nWINHTTP_NO_PROXY_NAME = 0nWINHTTP_NO_PROXY_BYPASS = 0nWINHTTP_FLAG_ASYNC = 0x10000000ntest_url = "http://www.google.com"n# Gets the current user IE proxy configurationnieConfig = WINHTTP_CURRENT_USER_IE_PROXY_CONFIG()nresult = ctypes.windll.winhttp.WinHttpGetIEProxyConfigForCurrentUser(ctypes.byref(ieConfig))nif not result:n print "[-] Error on WinHttpGetIEProxyConfigForCurrentUser: %s" % ctypes.GetLastError()n sys.exit()nprint "[+] Got IE configuration"nprint "tAutoDetect: %s" % ieConfig.fAutoDetectnprint "tAuto URL: %s" % ieConfig.lpszAutoConfigUrlnprint "tProxy: %s" % ieConfig.lpszProxynprint "tProxy Bypass: %s" % ieConfig.lpszProxyBypassn# We have three alternatives:n# 1. The configuration is set to "auto detect" the proxy, that is, via DHCP or DNS (in that order)n# 2. There is a URL for downloading the script with the configuration (proxy autoconfiguration, PAC)n# 3. A manually configured proxy is being usednif ieConfig.lpszAutoConfigUrl:n autoProxyOpts = WINHTTP_AUTOPROXY_OPTIONS()n proxyInfo = WINHTTP_PROXY_INFO()n print "[+] IE config set to autodetect with URL %s" % ieConfig.lpszAutoConfigUrln autoProxyOpts.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT | WINHTTP_AUTOPROXY_CONFIG_URLn autoProxyOpts.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_An autoProxyOpts.fAutoLogonIfChallenged = Truen autoProxyOpts.lpszAutoConfigUrl = ieConfig.lpszAutoConfigUrln hInternet = ctypes.windll.winhttp.WinHttpOpen(WINHTTP_USER_AGENT, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, n WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC) n if not hInternet:n print "[-] Error on WinHttpOpen: %s" % ctypes.GetLastError()n sys.exit()n result = ctypes.windll.winhttp.WinHttpGetProxyForUrl(hInternet, unicode(test_url), ctypes.byref(autoProxyOpts), n ctypes.byref(proxyInfo))n if not result:n print "[-] Error on WinHttpGetProxyForUrl: %s" % ctypes.GetLastError()n sys.exit()n print "[+] Proxy Host: %s" % proxyInfo.lpszProxynelif ieConfig.lpszProxy:n print "[+] IE config set to proxy %s with bypass %s" % (ieConfig.lpszProxy, ieConfig.lpszProxyBypass)n

下面截圖是展示了在靶機中運行此腳本的輸出內容:

此腳本的輸出和我們獲得的調試信息輸出的內容是一樣的。攻擊過程中已經設置了AutoDetect參數,但是並沒有獲得代理地址。

如果再次檢查源代碼,你會意識到DHCP以及DNS很可能在判斷是否使用自動配置url的if代碼塊中。但是如果只有AutoDetect選項打開這個代碼塊並不會被執行。所以,可以判斷出這就是問題所在。

在靶機這種特殊的環境下,代理設置是通過DHCPde 252選項獲得的。下面截圖是展示在靶機中嗅探到的DHCP流量包。

通過在靶機中嗅探得到的DHCP通信中可以發現伺服器返回的DHCP應答包含了用於代理URL獲取有關信息的252選項(自動查找代理)。請記住,這一發現是在我們使用autoprox.exe之前獲得的。

在繼續發現問題之前,先了解一下windows為代理設置提供的三種方案:

1. 自動選擇配置:通過252選項(DHCP)獲得URL。或者在允許的情況下,通過DNS,LLMNR或者NBNS協議請求WPAD主機名。n2. 使用自動配置腳本:從指定的url下載自動配置腳本,使用這個腳本進行代理選擇。n3. 代理伺服器: 為不同的協議手動配置代理伺服器。n

我們先在python環境中進行實驗,如果能夠成功,那麼我們再通過C進行編寫Meterpreter後門。

下面就是修改後的python代碼:

import ctypesnimport ctypes.wintypesnimport sysnnclass WINHTTP_CURRENT_USER_IE_PROXY_CONFIG(ctypes.Structure):n _fields_ = [("fAutoDetect", ctypes.wintypes.BOOL),n ("lpszAutoConfigUrl", ctypes.wintypes.LPWSTR),n ("lpszProxy", ctypes.wintypes.LPWSTR),n ("lpszProxyBypass", ctypes.wintypes.LPWSTR)]nnclass WINHTTP_AUTOPROXY_OPTIONS(ctypes.Structure):n _fields_ = [("dwFlags", ctypes.wintypes.DWORD),n ("dwAutoDetectFlags", ctypes.wintypes.DWORD),n ("lpszAutoConfigUrl", ctypes.wintypes.LPCWSTR),n ("lpvReserved", ctypes.c_void_p),n ("dwReserved", ctypes.wintypes.DWORD),n ("fAutoLogonIfChallenged", ctypes.wintypes.BOOL)]nnclass WINHTTP_PROXY_INFO(ctypes.Structure):n _fields_ = [("dwAccessType", ctypes.wintypes.DWORD),n ("lpszProxy", ctypes.wintypes.LPCWSTR),n ("lpszProxyBypass", ctypes.wintypes.LPCWSTR)]nn# dwFlags valuesnWINHTTP_AUTOPROXY_AUTO_DETECT = 0x00000001nWINHTTP_AUTOPROXY_CONFIG_URL = 0x00000002nn# dwAutoDetectFlags valuesnWINHTTP_AUTO_DETECT_TYPE_DHCP = 0x00000001nWINHTTP_AUTO_DETECT_TYPE_DNS_A = 0x00000002nn# Parameters for WinHttpOpennWINHTTP_USER_AGENT = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"nWINHTTP_ACCESS_TYPE_DEFAULT_PROXY = 0nWINHTTP_NO_PROXY_NAME = 0nWINHTTP_NO_PROXY_BYPASS = 0nWINHTTP_FLAG_ASYNC = 0x10000000nntest_url = "http://www.google.com"nn# Gets the current user IE proxy configurationnieConfig = WINHTTP_CURRENT_USER_IE_PROXY_CONFIG()nresult = ctypes.windll.winhttp.WinHttpGetIEProxyConfigForCurrentUser(ctypes.byref(ieConfig))nnif not result:n print "[-] Error on WinHttpGetIEProxyConfigForCurrentUser: %s" % ctypes.GetLastError()n sys.exit()nnprint "[+] Got IE configuration"nprint "tAutoDetect: %s" % ieConfig.fAutoDetectnprint "tAuto URL: %s" % ieConfig.lpszAutoConfigUrlnprint "tProxy: %s" % ieConfig.lpszProxynprint "tProxy Bypass: %s" % ieConfig.lpszProxyBypassnn# We have three alternatives:n# 1. The configuration is to "auto detect" the proxy, that is, via DHCP or DNSn# 2. There is a URL for the script with the configuratoin (proxy autoconfiguration, PAC)n# 3. A manually configured proxy is being usednnif ieConfig.lpszAutoConfigUrl or ieConfig.fAutoDetect:n autoProxyOpts = WINHTTP_AUTOPROXY_OPTIONS()n proxyInfo = WINHTTP_PROXY_INFO()nn if ieConfig.lpszAutoConfigUrl:n print "[+] IE config set to autodetect with URL %s" % ieConfig.lpszAutoConfigUrln autoProxyOpts.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URLn autoProxyOpts.dwAutoDetectFlags = 0n autoProxyOpts.lpszAutoConfigUrl = ieConfig.lpszAutoConfigUrlnn if ieConfig.fAutoDetect:n print "[+] IE config set to autodetect via DHCP or DNS"n autoProxyOpts.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECTn autoProxyOpts.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_An autoProxyOpts.lpszAutoConfigUrl = 0nn autoProxyOpts.fAutoLogonIfChallenged = Truenn hInternet = ctypes.windll.winhttp.WinHttpOpen(WINHTTP_USER_AGENT, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, n WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC) n if not hInternet:n print "[-] Error on WinHttpOpen: %s" % ctypes.GetLastError()n sys.exit()nn result = ctypes.windll.winhttp.WinHttpGetProxyForUrl(hInternet, unicode(test_url), ctypes.byref(autoProxyOpts), n ctypes.byref(proxyInfo))nn if not result:n print "[-] Error on WinHttpGetProxyForUrl: %s" % ctypes.GetLastError()n sys.exit()nn print "[+] Proxy Host: %s" % proxyInfo.lpszProxynnelif ieConfig.lpszProxy:n print "[+] IE config set to proxy %s with bypass %s" % (ieConfig.lpszProxy, ieConfig.lpszProxyBypass)n

在修改後的代碼中我們可以發現現在後門會考慮通過DHCP或者DNS協議進行的代理。現在,讓我們執行這個後門,看看他是如何工作的。

下面截圖展現了修改後的python代碼在靶機中的運行結果。

發現成功的選擇了通過DCHP或者DNS的代理配置。並且他顯示了和文章開頭一樣的代理伺服器(10.x.x.20).現在我們知道了這一段代碼是能夠工作的。我們進而將meterpreter的C代碼進行更新,即對server_transport_winhttp.c進行修改,然後進行測試。

下面代碼就是meterpreter代碼進行更新的部分:

...ndprintf("[PROXY] Got IE configuration");ndprintf("[PROXY] AutoDetect: %s", ieConfig.fAutoDetect ? "yes" : "no");ndprintf("[PROXY] Auto URL: %S", ieConfig.lpszAutoConfigUrl);ndprintf("[PROXY] Proxy: %S", ieConfig.lpszProxy);ndprintf("[PROXY] Proxy Bypass: %S", ieConfig.lpszProxyBypass);nnif (ieConfig.lpszAutoConfigUrl || ieConfig.fAutoDetect)n{n WINHTTP_AUTOPROXY_OPTIONS autoProxyOpts = { 0 };n WINHTTP_PROXY_INFO proxyInfo = { 0 }; n if (ieConfig.fAutoDetect)n {n dprintf("[PROXY] IE config set to autodetect via DHCP or DNS");nn autoProxyOpts.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT;n autoProxyOpts.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A;n autoProxyOpts.lpszAutoConfigUrl = 0;n }n else if (ieConfig.lpszAutoConfigUrl)n {n dprintf("[PROXY] IE config set to autodetect with URL %S", ieConfig.lpszAutoConfigUrl);nn autoProxyOpts.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;n autoProxyOpts.dwAutoDetectFlags = 0;n autoProxyOpts.lpszAutoConfigUrl = ieConfig.lpszAutoConfigUrl;n }n autoProxyOpts.fAutoLogonIfChallenged = TRUE;n if (WinHttpGetProxyForUrl(ctx->internet, ctx->url, &autoProxyOpts, &proxyInfo))n ...n

修改之後,編譯後門,將生成的metsrv meterpreter dll複製到攻擊機中,然後再一次開啟偵聽等待靶機上線。

下面截圖展示了攻擊機器中反彈shell執行成功命令。

發現當靶機使用」Auto detect」設置時,meterpreter能夠執行命令。

問題根源所在

現在我們是時候討論一下當你閱讀這篇文章時可能遇到的感到困惑的地方:

* 為什麼要首先到達攻擊機。n* 在攻擊的第一階段以及第二階段有什麼不同。n

為了找到這些問題的答案,我們首先需要理解下meterpreter在這篇文章中是如何進行工作的。

先從一開始說起:windows的API通過https提供了兩種方法進行通信:WinInet和WinHTTP。在meterpreter處理https通信層時,有兩個比較有趣的功能:

1. 攻擊機器(https伺服器)具有提供的證書籤名的能力,防止像L7網路防火牆這樣的代理進行內容檢查。n2. 獲取當前用戶代理設置的能力,以便能夠通過網路到達攻擊機器。n

事實證明這兩個功能並不能在同一個API函數中找到,兩個API功能分別為:

WinInet:

1. 是否使用代理。意味著如果當前用戶的系統中使用了瀏覽器代理,那麼接下來就會對WinInet支持的程序進行代理。n2. 不會對自定義的SSL/TLS證書進行代理。n

WinHTTP:

1. 允許自定義的SSL證書進行代理。n2. 不使用當前系統中的代理。n

在meterpreter方面,我們可以使用兩種不同的攻擊載荷:

1. reverse_https攻擊載荷使用的是WinInet的Windows API,意味著不會通過證書驗證,但是可以使用系統當前的代理配置。於是,如果用戶可以通過IE進行網路訪問,那麼這個攻擊載荷就可以進行工作。n2. reverse_winhttps攻擊載荷使用的是Winhttp的Windows API,意味著他可以通過證書驗證,不過系統當前的代理設置不會被使用。n

這種情況下的meterpreter攻擊載荷使用的是默認的WinHTTP windows API,如果出現錯誤就會再次返回WinInet。在用戶使用」強制模式」下,優先檢測證書。因為WinInet不能驗證證書,所以他被認為是一個錯誤。

注意:在meterpreter中,」強制模式」意味著需要驗證SSL/TLS證書的簽名。如果簽名已經被替換,那麼這一階段就不能進行訪問,因此會話不能建立。如果當前用戶使用的是」強制模式」,那麼攻擊載荷應該使用WinHTTP。

現在我們有足夠的基礎知識去理解我們面對的困難。我剛才使用的是」reverse_https」meterpreter的攻擊載荷(沒有考慮到測試環境中的強制模式),這就意味著第一階段後門通過Windows

API使用當前用戶的代理設置反彈到偵聽機器中。但是因為默認攻擊載荷使用的是WinHTTP

API,根據我的推測他在第二階段有個錯誤,所以我認為他不能返回到偵聽者的機器中。所以我認為這是上面兩個問題的答案。

代理識別方法

另外一個我們沒有回答的問題是:當我們使用WinHTTP windows API函數時,應該怎麼樣獲取當前用戶的代理設置?

為了找到答案,我們需要在測試主機上設置多個代理選項,設置訪問優先順序,測試當一個代理選項不能用時,系統下一步會進行什麼操作?換句話說它會不會自動執行另外一個選項。

根據我所得到的結論,在設置網路代理的對話框中的代理選項是按照他們的優先順序進行設置的。首先是」自動選擇配置」,接下來是」使用自動配置腳本」,然後是」在區域網中使用代理」。

另外,可以在微軟MSDN的」開發者代碼實例」找到關於使用WinHTTP API的示例代碼,其中代碼說明如下:

請使用如下的步驟使用進行代理設置:n1. 配置自動選擇配置n2. 配置自動選擇urln3. 配置靜態代理設置n

我們之前的設置已經和說明相符。

容錯實現

最後一個問題,如果我配置了多個代理選項,不過一個優先順序高的代理不起作用,那麼會發生什麼?主機會一直向下尋找,直到找到一個有用的配置為止。

為了尋找到這一個答案,我們需要進行一些實驗,或者花費幾個小時逆向分析設置這一部分的組件,主要是wininet.dll。所以我們選擇進行一些實驗,當然這樣會比較省時。

實驗設置

為了更深層次的分析Windows代理設置以及工作,我搭建了具有以下功能的實驗環境:

一個擁有一個域控制器的域環境:

域名:lab.bransh.com

域控IP: 192.168.0.1

DHCP 伺服器:192.168.0.100-150

三個線程管理網關:

tmg1.lab.bransh.com: 192.168.0.10

tmg2.lab.bransh.com: 192.168.0.11

tmg3.lab.bransh.com: 192.168.0.12

每一個線程管理網關擁有兩個網卡:」internal」網卡在192.168.0.x網段,連接到了域環境中,允許客戶端通過網路到達。另外一個網卡連接到了另外一個網路,並使用代理進行網路訪問。

一個windows客戶端(windows 8.1 x64):

通過DHCP進行IP分配

其中代理配置:

通過DHCP(選項252): tmg1.lab.bransh.com

通過腳本代理:tmg2.lab.bransh.com/wpa

手工設置: tmg3.lab.bransh.com:8080

客戶端不能直接訪問網路

火狐瀏覽器已經設置使用系統代理

下方截圖顯示了windows客戶端代理設置:

下方截圖顯示了通過DHCP進行代理的流量:

注意:」自動選擇配置」選項不能選擇通過DHCP或者DNS進行的代理。當我們使用windowsAPI時,我們需要手動選擇需要使用哪一個,或者兩個都使用。

通過使用Windows提供的API的簡單代碼,可以測試幾個代理方案。我這時同樣使用python編寫代碼,因為修改和運行代碼非常容易,而無需在每次需要修改時在測試機器中編譯c或者c++代碼。不過,也可以使用你喜歡的語言進行編寫:

import ctypesnimport ctypes.wintypesnimport sysnnclass WINHTTP_CURRENT_USER_IE_PROXY_CONFIG(ctypes.Structure):n _fields_ = [("fAutoDetect", ctypes.wintypes.BOOL),n ("lpszAutoConfigUrl", ctypes.wintypes.LPWSTR),n ("lpszProxy", ctypes.wintypes.LPWSTR),n ("lpszProxyBypass", ctypes.wintypes.LPWSTR)]nnclass WINHTTP_AUTOPROXY_OPTIONS(ctypes.Structure):n _fields_ = [("dwFlags", ctypes.wintypes.DWORD),n ("dwAutoDetectFlags", ctypes.wintypes.DWORD),n ("lpszAutoConfigUrl", ctypes.wintypes.LPCWSTR),n ("lpvReserved", ctypes.c_void_p),n ("dwReserved", ctypes.wintypes.DWORD),n ("fAutoLogonIfChallenged", ctypes.wintypes.BOOL)]nnclass WINHTTP_PROXY_INFO(ctypes.Structure):n _fields_ = [("dwAccessType", ctypes.wintypes.DWORD),n ("lpszProxy", ctypes.wintypes.LPCWSTR),n ("lpszProxyBypass", ctypes.wintypes.LPCWSTR)]nnWINHTTP_USER_AGENT = ctypes.c_wchar_p(Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko)nWINHTTP_ACCESS_TYPE_DEFAULT_PROXY = 0nWINHTTP_ACCESS_TYPE_NO_PROXY = 1nWINHTTP_ACCESS_TYPE_NAMED_PROXY = 3nWINHTTP_NO_PROXY_NAME = 0nWINHTTP_NO_PROXY_BYPASS = 0nndef ShowLastError(message, alignment = 0):n error_id = ctypes.GetLastError()n print * alignment + [-] Error on %s: %s % (message, error_id)nn if error_id == 12167:n title = ERROR_WINHTTP_UNABLE_TO_DOWNLOAD_SCRIPTn message = The PAC file cannot be downloaded. For example, the server referenced by the PAC URL may not have been reachable, or the server returned a 404 NOT FOUND response.n elif error_id == 12007:n title = ERROR_WINHTTP_NAME_NOT_RESOLVEDn message = The server name cannot be resolved.n elif error_id == 12029:n title = ERROR_WINHTTP_CANNOT_CONNECTn message = Returned if connection to the server failed.n elif error_id == 12002:n title = ERROR_WINHTTP_TIMEOUTn message = The request has timed out.n elif error_id == 12180:n title = ERROR_WINHTTP_AUTODETECTION_FAILEDn message = Returned by WinHttpDetectAutoProxyConfigUrl if WinHTTP was unable to discover the URL of the Proxy Auto-Configuration (PAC) file.n else:n title = UNKNOWNn message = unknownnn msg_max_len = 70n msg_list = [message[i:i+msg_max_len] for i in range(0, len(message), msg_max_len)]nn print * alignment + => %s % titlen for msg in msg_list:n print * alignment + %s % msgnndef GetCurrentProxies():n pProxyConfig = WINHTTP_CURRENT_USER_IE_PROXY_CONFIG()n result = ctypes.windll.winhttp.WinHttpGetIEProxyConfigForCurrentUser(ctypes.byref(pProxyConfig))nn if result == False:n ShowLastError(WinHttpGetIEProxyConfigForCurrentUser)n return False, Nonenn return True, pProxyConfignndef GetProxyInfoList(pProxyConfig, target_url):n print n[*] Checking proxy configuration alternatives...nn proxy_list = []n hSession = ctypes.windll.winhttp.WinHttpOpen(WINHTTP_USER_AGENT, WINHTTP_ACCESS_TYPE_NO_PROXY,n WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0)nn if hSession is None:n ShowLastError(WinHttpOpen)n sys.exit()nn WINHTTP_AUTOPROXY_AUTO_DETECT = 0x00000001n WINHTTP_AUTO_DETECT_TYPE_DHCP = 0x00000001n WINHTTP_AUTO_DETECT_TYPE_DNS_A = 0x00000002n WINHTTP_AUTOPROXY_CONFIG_URL = 0x00000002nn if pProxyConfig.fAutoDetect:n print n (1) Automatically detect settings (enabled)n print [*] Trying to get the proxy using the conventional method...nn pAutoProxyOptions = WINHTTP_AUTOPROXY_OPTIONS()n pProxyInfo = WINHTTP_PROXY_INFO()nn pAutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECTn pAutoProxyOptions.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_An pAutoProxyOptions.lpszAutoConfigUrl = 0n lpcwszUrl = ctypes.wintypes.LPCWSTR(target_url)n result = ctypes.windll.winhttp.WinHttpGetProxyForUrl(hSession, lpcwszUrl, ctypes.byref(pAutoProxyOptions),n ctypes.byref(pProxyInfo))n if result == False:n ShowLastError(WinHttpGetProxyForUrl, 6)n print n [*] Trying to get the proxy using the AutoConfigURL...nn dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_An ppwszAutoConfigUrl = ctypes.wintypes.LPWSTR()n result = ctypes.windll.winhttp.WinHttpDetectAutoProxyConfigUrl(dwAutoDetectFlags, n ctypes.byref(ppwszAutoConfigUrl))nn if result == False:n ShowLastError(WinHttpDetectAutoProxyConfigUrl, 10)n else:n print [+] Trying to get the proxy from the obtained URL (%s) % ppwszAutoConfigUrl.valuen pAutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URLn pAutoProxyOptions.dwAutoDetectFlags = 0n pAutoProxyOptions.fAutoLogonIfChallenged = Truen pAutoProxyOptions.lpszAutoConfigUrl = ppwszAutoConfigUrln result = ctypes.windll.winhttp.WinHttpGetProxyForUrl(hSession, lpcwszUrl, ctypes.byref(pAutoProxyOptions),n ctypes.byref(pProxyInfo))n if result:n print [+] Proxy: %s % (pProxyInfo.lpszProxy)n proxy_list.append(pProxyInfo)n else:n ShowLastError(WinHttpGetProxyForUrl, 10)n else:n print [+] Proxy: %s % (pProxyInfo.lpszProxy)n proxy_list.append(pProxyInfo)nn if pProxyConfig.lpszAutoConfigUrl:n print n (2) Use automatic configuration script (%s) % pProxyConfig.lpszAutoConfigUrlnn pAutoProxyOptions = WINHTTP_AUTOPROXY_OPTIONS()n pProxyInfo = WINHTTP_PROXY_INFO()nn pAutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URLn pAutoProxyOptions.dwAutoDetectFlags = 0n pAutoProxyOptions.fAutoLogonIfChallenged = Truen pAutoProxyOptions.lpszAutoConfigUrl = pProxyConfig.lpszAutoConfigUrln lpcwszUrl = ctypes.wintypes.LPCWSTR(target_url)n result = ctypes.windll.winhttp.WinHttpGetProxyForUrl(hSession, lpcwszUrl, ctypes.byref(pAutoProxyOptions),n ctypes.byref(pProxyInfo))n if result == False:n ShowLastError(WinHttpGetProxyForUrl, 6)n else:n print [+] Proxy: %s % (pProxyInfo.lpszProxy)n proxy_list.append(pProxyInfo)nn if pProxyConfig.lpszProxy:n print n (3) Use a proxy server for your LANnn pProxyInfo = WINHTTP_PROXY_INFO()n WINHTTP_ACCESS_TYPE_NAMED_PROXY = 3nn pProxyInfo.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXYn pProxyInfo.lpszProxy = pProxyConfig.lpszProxyn pProxyInfo.lpszProxyBypass = pProxyConfig.lpszProxyBypassnn print [+] Proxy: %s % pProxyConfig.lpszProxyn print [+] Proxy Bypass: %s % pProxyConfig.lpszProxyBypassnn proxy_list.append(pProxyInfo)nn ctypes.windll.winhttp.WinHttpCloseHandle(hSession)nn return proxy_listnndef CheckProxyStatus(proxyInfo, target_server, target_port):n hSession = ctypes.windll.winhttp.WinHttpOpen(WINHTTP_USER_AGENT, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,n WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0)nn if hSession is None:n ShowLastError(WinHttpOpen)n returnnn server_name = ctypes.c_wchar_p(target_server)n INTERNET_DEFAULT_HTTP_PORT = target_portn hInternet = ctypes.windll.winhttp.WinHttpConnect(hSession, server_name, INTERNET_DEFAULT_HTTP_PORT, 0)nn if hInternet is None:n ShowLastError(WinHttpConnect, 8)n return Falsenn WINHTTP_FLAG_BYPASS_PROXY_CACHE = 0x00000100n WINHTTP_FLAG_SECURE = 0x00800000n dwFlags = WINHTTP_FLAG_BYPASS_PROXY_CACHEn pwszVerb = ctypes.c_wchar_p(GET)n pwszObjectName = ctypes.c_wchar_p()n hRequest = ctypes.windll.winhttp.WinHttpOpenRequest(hInternet, pwszVerb, pwszObjectName,n 0, 0, 0, dwFlags)nn if hRequest is None:n ShowLastError(WinHttpOpenRequest, 8)n return Falsenn WINHTTP_OPTION_PROXY = 38n result = ctypes.windll.winhttp.WinHttpSetOption(hRequest, WINHTTP_OPTION_PROXY, ctypes.byref(proxyInfo),n ctypes.sizeof(proxyInfo))nn if result == False:n ShowLastError(WinHttpSetOption, 8)n return Falsenn WINHTTP_NO_ADDITIONAL_HEADERS = 0n WINHTTP_NO_REQUEST_DATA = 0n result = ctypes.windll.winhttp.WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS,n 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0)nn if result == False:n ShowLastError(WinHttpSendRequest, 8)n return Falsen else:n WINHTTP_QUERY_STATUS_CODE = 19n WINHTTP_QUERY_STATUS_TEXT = 20n WINHTTP_QUERY_RAW_HEADERS_CRLF = 22n WINHTTP_HEADER_NAME_BY_INDEX = 0n WINHTTP_NO_HEADER_INDEX = 0n dwInfoLevel = WINHTTP_QUERY_RAW_HEADERS_CRLFn lpdwBufferLength = ctypes.wintypes.DWORD()n lpdwIndex = ctypes.wintypes.DWORD()nn result = ctypes.windll.winhttp.WinHttpReceiveResponse(hRequest, 0)n if result:n result = ctypes.windll.winhttp.WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_RAW_HEADERS_CRLF,n WINHTTP_HEADER_NAME_BY_INDEX, 0,n ctypes.byref(lpdwBufferLength),n WINHTTP_NO_HEADER_INDEX)n ERROR_INSUFFICIENT_BUFFER = 122n if ctypes.GetLastError() == ERROR_INSUFFICIENT_BUFFER:n lpBuffer = ctypes.create_string_buffer(lpdwBufferLength.value)n result = ctypes.windll.winhttp.WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_RAW_HEADERS_CRLF,n WINHTTP_HEADER_NAME_BY_INDEX, ctypes.byref(lpBuffer),n ctypes.byref(lpdwBufferLength),n WINHTTP_NO_HEADER_INDEX)n if result:n line = lpBuffer.raw.replace(x00, ).split(n)[0]n space_1 = line.find( )n space_2 = line.find( , space_1+1)n code = line[space_1:space_2].strip()n text = line[space_2:].strip()n print "t[*] HTTP Query Status Code / Text: nt %s / %s" % (code, text)n if code != "200":n return Falsen else:n return Truenn ctypes.windll.winhttp.WinHttpCloseHandle(hRequest)n ctypes.windll.winhttp.WinHttpCloseHandle(hInternet)n ctypes.windll.winhttp.WinHttpCloseHandle(hSession)nn return Truenndef main():n result, pProxyConfig = GetCurrentProxies()n if result == False:n sys.exit()nn print n[*] We got the proxy configuration.n if pProxyConfig is None:n print [*] No proxy setting found for the current user.n sys.exit()nn target_server = www.google.comn target_url = http:// + target_servern target_port = 80n proxy_list = GetProxyInfoList(pProxyConfig, target_url)n print n[*] Number of proxies: %s % str(len(proxy_list))nn print n[*] Testing if proxy servers actually work...n for proxy in proxy_list:n print n [*] Proxy "%s" ... % proxy.lpszProxyn result = CheckProxyStatus(proxy, target_server, target_port)n if result:n print [+] Works! :)n else:n print [-] Does not work :(nnif __name__ == __main__:n main()n

上面代碼中包括兩個重要的函數:

* GetProxyInfoList(pProxyConfig, target_url):這個函數將對當前用戶進行的代理設置進行獲取,返回指定url代理設置的列表。一定要注意代理列表中包含的可能用於URL進行訪問的代理地址。但是這並不意味著代理伺服器正在起作用。比如現在正在通過"自動選擇配置"選項讀取WPAD.DAT文件獲取代理地址,但是當訪問目標URL時代理服務並不工作。n* CheckProxyStatus(proxy, target_server, target_port):這個函數會對後門的服務以及ip進行檢查(通過訪問網站根目錄),進而驗證代理服務是否工作。這個函數會幫助確定當代理伺服器給出時,判斷是否可用。n

測試場景一

在這個測試場景中,客戶端在啟動後網卡(192.168.0.x)禁用了tmg1以及tmg2代理伺服器,這就意味著只有通過TMG3代理伺服器進入網路。下面截圖就展示了腳本的輸出,另外也展示了IE以及火狐瀏覽器在這個環境中處理方式:

腳本說明了以下內容:

1."自動選擇配置"選項已經開啟,並且在網卡禁用前通過在後台下載WPAD.PAC文件獲得到代理配置為:"192.168.0.10:8080"。但是由於tmg1被禁用,所以代理服務並沒有起作用,到達不了目的網路,得到連接超時頁面。n2. "使用自動配置腳本"選項開啟,並且與1相同,從WPAD.PAC中獲得了代理伺服器地址"192.168.0.11:8080",但是tmg2代理伺服器被禁用,所以同樣獲得連接超時頁面。n3. 手動設置的代理伺服器地址為:"tmg3.lab.bransh.com:8080",所以這種方式代理可以成功,並且可以到達目的網路n

觀察到IE以及火狐並不能通過配置到達目標網路,但是使用tmg3作為代理伺服器的應用是可以連接到網路中的。

測試場景二

這個場景和場景一很相似,只是客戶端在啟動前網卡禁用了tmg1以及tmg2代理伺服器。這就意味著同樣只有通過tmg3才能進入網路。

以下是運行截圖:

通過運行代碼,可以獲得的內容如下:

1."自動選擇配置"開啟,但是並沒有獲得代理伺服器地址。因為tmg1代理伺服器一開始就不可達,所以不會下載WPAD.PAC文件,不會獲得代理設置文件。n2. "使用自動配置腳本"選項開啟,並且腳本中提供了"tmg2.lab.bransh.com/wpad.dat"代理腳本地址,不過tmg2伺服器不可達,同樣獲取不到代理伺服器地址。n3. 手動設置的代理伺服器地址為:"tmg3.lab.bransh.com:8080",所以這種方式代理可以成功,並且可以到達目的網路n

測試環境三

在這個測試場景中,客戶端在啟動前網卡禁用了tmg2代理伺服器。所以客戶端機器可以通過tmg1以及tmg3進行網路訪問。

截圖如下:

當運行程序後獲得信息如下:

1. "自動選擇配置"開啟,可以通過獲得的代理配置(192.168.0.10:8080)訪問網路.n2. "使用自動配置腳本"開啟,不過tmg2伺服器不可達,所以不能下載wpad.dat腳本。n3. 手動設置的代理伺服器地址為:"tmg3.lab.bransh.com:8080",所以這種方式代理可以成功,並且可以到達目的網路n

另外,可以觀察到IE能讀取到代理設置訪問網路,但是火狐確不可以。

測試環境四

只有tmg2代理伺服器允許使用。

測試環境五

這個測試環境當中,內部網卡三個代理伺服器都可以訪問,但是外部網卡禁用了tmg1,tmg2網卡。

當運行程序後獲得信息如下:

1."自動選擇配置"開啟,可以通過獲得的代理配置(192.168.0.10:8080),但是響應返回502錯誤,所以不能訪問網路。

2. "使用自動配置腳本"同樣開啟,可以訪問到tmg2代理伺服器。但是訪問響應同樣是502,也不能訪問網路。

3. 手動設置的代理伺服器地址為:"tmg3.lab.bransh.com:8080",所以這種方式代理可以成功,並且可以到達目的網路

觀察到IE以及火狐都不能夠訪問網路。但是使用tmg3代理伺服器進行代理的應用軟體確可以進行訪問。

總結

在某些情況下,就像這篇文章的第一部分一樣,我們發現常用的工具並不能像往常一樣進行工作。這種情況下,我們有兩種選擇:一種是使用另外一種方法進行攻擊,第二種是找到修復這種問題的方法。對於我這種特定的企業環境中,修復了meterpreter生成的dll,讓我們可以進行攻擊。我不知道我修改的代碼是否被msf採納,但是如果你以後遇到這樣的問題,你會知道怎麼修復。

另一方面,我們了解到windows在代理使用方面是有順序的。但是就像測試環境一那樣,如果獲得了一個代理配置,windows就會使用它(不管其是否能工作),不會在嘗試另外一種代理方式。另外,我們觀察到火狐以及IE同樣在使用系統代理設置選項下,他們呈現的結果是不同的。最後,我們在兩種情況下都可以發現當獲得代理設置後(即使代理不能工作),都不會使用其他代理配置。

考慮到這個結果,我們確實有必要使用windowsAPI進行代理配置的測試,設置測試他們是否允許我們進入網路。因此,我們可以編寫代碼增大我們APT攻擊成功的可能性,讓它在這種環境也起作用。但是我不得不承認,這種環境實在太特殊了,如果不是測試,我想管理員不會使用這種環境。

作為最後的結論,讓我們的APT攻擊方案連接成功可能性與IE一樣,這就足以在大多數環境中攻擊成功。如果IE能夠訪問網路,那麼APT攻擊就能成功。

參考資料:

自動選擇配置:Optimizing Performance with automatic Proxyconfiguration scripts (PAC)

windwos web端代理配置:Understanding Web Proxy Configuration

meterpreter編譯:rapid7/metasploit-payloads

meterpreter winhttp源碼:github.com/rapid7/metas

meterpreter common.h源碼:github.com/rapid7/metas

DebugView:DebugView

WinHTTP與WinInet不同:rapid7/metasploit-framework

Winhttp簡單實例源碼:WinHTTP proxy sample

本文翻譯自:medium.com/@br4nsh/a-me,如若轉載,請註明來源於嘶吼:tAPT案例分析:一個基於Meterpreter和Windows代理的攻擊事件 更多內容請關注「嘶吼專業版」——Pro4hou

推薦閱讀:

使用 Burp Infiltrator 進行漏洞挖掘
威脅警報:Cisco IOS高危漏洞觸發Rockwell工業系統危機
保護路由器免受DDoS攻擊的5個最新絕招

TAG:技术分析 | 信息安全 | 网络安全 |