<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    CobaltStrike ShellCode詳解

    VSole2022-08-04 16:51:50


    一、前言

    CobaltStrike大家應該知道,最近剛好遇上了一個CS的分段的Beacon樣本,詳細分析了下ShellCode,看它ShellCode是如何實現,給大家提供些混淆思路或者檢測思路,如有錯誤歡迎指出。

    二、目標樣本

    樣本基本信息如下:

    MD5: ad26a8c74596c32909923330eee2f6f7

    SHA1: 775275f0debf7dce1fd86cc067f986d86884e1e0

    SHA256: 627b6a48dbd4cd0c15a7cdd6ed125c2999ac0e6c6bbf8273481e5964a348cd28

    未加殼

    三、樣本情況說明

    這個樣本主要是一個loader負責加載起shellcode,其主要功能就在函數sub_1400111D0其中。

    該函數內的主要部分如下,申請內存寫入shellcode并在EnumObjects的回調函數中運行shellcode,加載方式也是比較經典了,這里可以多說兩句,此函數原來的功能是什么不重要,重要的是它可以指定一個回調函數就可以把shellcode的地址作為回調函數加載起來,還可以引申出一種免殺方式,那就是可以將shellcode轉化為包括但不限于UUID/MAC地址后硬編碼進樣本,之后調用對應的轉換函數轉為二進制后調用有回調函數的API將其運行起來,Lazarus的一次攻擊就用的這種方式具體的可以參考這篇文章:

    https://research.nccgroup.com/2021/01/23/rift-analysing-a-lazarus-shellcode-execution-method/

     

    接下來就是重點了,加載起來的這段shellcode開頭先將DF標志位置0,這里為什么這樣做后面會提到。

    第一個call就是先將返回地址pop到rbp中將wininet的十六進制賦值給R14,之后壓棧在取出字符,將后面要獲取的LoadLibraryA的特征值賦給r10d,最后調用call rbp返回。

    之后就是從PEB中獲取文件名和文件名最大長度,具體解析見下:

    GS+60h中存儲的PEB

    PEB+18h處存儲的是_PEB_LDR_DATA

    _PEB_LDR_DATA中+20h的地方就是LIST_ENTRY,這里有一個要注意的點,那就是這里的InMemoryOrderModuleList指向的其實是_LDR_DATA_TABLE_ENTRY中的InMemoryOrderLinks,所以在后面用這個地址的時候直接從InMemoryOrderLinks的地方開始取才是正確的,這里的知識點在下面就會有所用到。

    LIST_ENTRY是一個雙向鏈表,其內每個節點都存儲著一個_LDR_DATA_TABLE_ENTRY結構。

    InMemoryOrderLinks+48h處儲存的模塊名稱信息的結構體。

    注意:此處[rdx+50]并不是取_LDR_DATA_TABLE_ENTRY+50h而是InMemoryOrderLinks+50h,也就是_LDR_DATA_TABLE_ENTRY+60h的地方。

    _UNICODE_STRING內+2h的地方存儲著最大長度+8h的地方存儲著模塊名稱,也就是[rdx+50]和[rdx+4A]。

    之后就是我前面賣的關子了,shellcode在這塊會調用lodsb指令讀取模塊名,這個指令就是根據DF標志位控制向前還是后讀,這段shellcode功能就是循環讀取模塊名,判斷是否大于61也就是小寫的a,如果大于的話就減20,這里其實是在把小寫的字母轉化為大寫然后用ror和add指令求一個特征值。

    接下來就是保存指向InMemoryOrderLinks的rdx和先前求的特征值,在用InMemoryOrderLinks+20h獲取到DllBase,也就是加載的模塊基址,然后用這個基址+3Ch取到NT頭的偏移,在判斷NT頭+18h的地方是否為20B,以此判斷是否為64位文件,之后判斷導出表是否為空,為空則直接跳轉。

    這里放上一張PE結構圖供大家參考,也可直接010中對照。

    在跳轉處設置堆棧并獲取下一個_LDR_DATA_TABLE_ENTRY,然后跳轉回前面獲取BaseDllName的地址開始下一個循環,直到找到有導出表的模塊。

    如果判斷存在導出表的話就先用rax+rdx也就是導出表的RVA+基址獲取到內存中導出表的地址,之后用該地址分別+18h和20h依次獲取到名稱導出函數個數和函數名稱表,然后用函數名地址+函數名導出個數*4獲取到具體函數名,這里*4是因為函數名是dword類型。

    接下來就是對獲取到的函數名取特征值,用存著LoadLibraryA特征值的r10d與r9d對比是否為LoadLibraryA,如果遍歷完一個模塊都沒找到就跳轉獲取下一個模塊地址然后跳轉到獲取BaseDllName的地址在下一個模塊中開始下一個循環,直到找到LoadLibraryA。 

    在找到LoadLibraryA后先彈出導出表地址然后+24h獲取到導出序號表地址,接著獲取序號表內存中位置,用名稱表內的排序去序號表內找對應的序號,因為在導出表中名稱表是與序號表一一對應的,獲取到函數序號后我們可以直接用這個序號去函數地址表中找到對應的地址即可。

    一切準備完成后設置堆棧push+jmp直接調用LoadladLibraryA,因為是x64的所以用x64調用約定也就是依次用rcx,rdx,r8,r9,多出部分使用棧進行傳遞,這里的rcx就是之前push的wininet。

    這里貼上一段我用PEB找到Kernel32的基址后遍歷導出表找到LoadladLibraryA的地址后調用的代碼,這段代碼最后加載的dll是我寫的一個測試dll,功能是彈個窗。

    #include #include #include  typedef void* (WINAPI* FnLoadLibraryA)(char*);FnLoadLibraryA MyLoadLibraryA; int main(){    UINT_PTR uiBaseAddress = 0;    UINT_PTR uiExportDir = 0;    UINT_PTR uiNameArray = 0;    UINT_PTR uiAddressArray = 0;    UINT_PTR uiNameOrdinals = 0;    DWORD dwCounter = 0;    void* hKernel32 = NULL;     //直接通過PEB獲取到Kernel32的基址    __asm {        mov rdx, gs: [60h]        mov rdx, [rdx + 18h]        mov rdx, [rdx + 20h]        mov rdx, [rdx]        mov rdx, [rdx]        mov rdx, [rdx + 20h]        mov hKernel32, rdx    }    uiBaseAddress = (UINT_PTR)hKernel32;    //獲取NT頭    uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;    //獲取數據目錄表中的導出表RVA    uiNameArray = (UINT_PTR) & ((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];    //獲取導出表    uiExportDir = uiBaseAddress +((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress;    //獲取名稱表    uiNameArray = uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNames;    //獲取導出地址表    uiAddressArray = uiBaseAddress +((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions;    //獲取導出序號表    uiNameOrdinals = uiBaseAddress +((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNameOrdinals;    //獲取名稱導出的個數    dwCounter = ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->NumberOfNames;    while (dwCounter--)    {         char* cpExportedFunctionName = (char*)(uiBaseAddress + *(DWORD*)(uiNameArray));        //找LoadLibrary        if (strstr(cpExportedFunctionName, "LoadLibraryA") != NULL)        {            // 用導出序號*dword是在獲取函數在地址表內的位置+地址表獲取到內存中的位置            uiAddressArray += (*(WORD*)(uiNameOrdinals) * sizeof(DWORD));            printf(" LoadLibraryA RVA: % d", *(DWORD*)(uiAddressArray));            // 返回函數地址RVA+基址                       MyLoadLibraryA = (FnLoadLibraryA)( uiBaseAddress+* (DWORD*)uiAddressArray);            //調用后加載            HMODULE hUser32 = (HMODULE)MyLoadLibraryA((char*)"testdll.dll");            //返回LoadLibraryA的rva             return *(DWORD*)(uiAddressArray);        }        // 名稱表++        uiNameArray += sizeof(DWORD);        // 序號表++        uiNameOrdinals += sizeof(WORD);    }    return 0;}
    

    OK,我們接著說,shellcode在調用LoadLibrary加載wininet后返回shellcode起始處,開始遍歷加載模塊找加載的wininet遍歷其導出表依次找到并調用InternetOpenUrlA,InternetOpenA, InternetConnectA,HttpOpenRequestA, HttpSendRequestA,在調用HttpSendRequestA之后會判斷返回值是否為NULL,如果返回失敗的話就不停的嘗試10次。

    在嘗試10次都失敗后會跳轉賦值r14而不是賦值r10,因為R10在這段shellcode中是存儲函數特征值的,所以這樣就會導致r10一直為0從而沒有匹配到的函數,最后遍歷完所有模塊后lodsb指令讀取模塊名時導致異常。

    如果返回成功的話就會調用VirtualAllocEx申請空間。

    之后會調用InternetReadFile從c2分段讀取文件到申請的空間內,InternetReadFile在文件讀取完成后會將接收讀取字節數變量的指針置為0,shellcode借此判斷文件是否讀取完成,讀取完成后跳轉執行。

    跳轉會有一個小循環解密后續的payload。

    其連接c2在VT評論如下:

    解密出一個dll文件并加載,可以將其dump下來。

    其dll文件為CS的后門運行后會調用自身的反射加載函數,在內存中實現反射注入,這里反射如何實現與分析我們留待下次細說。

    之后會回連c2執行c2指令。

    根據獲取指令執行后續操作。

    通過上面的分析可以發現這是一個cs的分段Beacon,其特征就是會從C2下載并加載后續的payload,相對的也就是還有不分段的Beacon可以直接加載shellcode執行,這里我用cs4.4生成了一個默認的不分段的Beacon快速分析一下。

    不分段的Beacon

    樣本會在解密出shellcode后在線程回調中調用。

    其解密出的shellcdoe就是類似于前面分析的分段Beacon連接cc獲取的CS的dll文件。

    將其dump下來后會發現其用于接受指令執行的流程圖基本一致。

    可以看出這兩種cs的樣本最終的目的都是用外層的loader加載起來內層的shellcode,后面的后滲透操作都是由加載起來的dll所執行。分析下來后看這段ShellCode其實也是比較經典的,PEB找系統模塊,遍歷導出表調用,最后獲取payload解密調用。

    函數調用dword
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    近期有使用手機投屏的需求,用過幾個小工具感覺效果不是很理想,所以想著著手分析下。
    概述在windows系統上,涉及到內核對象的功能函數,都需要從應用層權限轉換到內核層權限,然后再執行想要的內核函數,最終將函數結果返回給應用層。本文就是用OpenProcess函數來觀察函數從應用層到內核層的整體調用流程。OpenProcess函數,根據指定的進程ID,返回進程句柄。NTSTATUS Status; //保存函數執行狀態。OBJECT_ATTRIBUTES Obja; //待打開對象的對象屬性。HANDLE Handle; //存儲打開的句柄。CLIENT_ID ClientId; //進程、線程ID. dwDesiredAccess, //預打開進程并獲取對應的權限。ObjectNamePresent = ARGUMENT_PRESENT ; //判斷對象名稱是否為空
    關于堆棧ShellCode操作:基礎理論002-利用fs寄存器尋找當前程序dll的入口:從動態運行的程序中定位所需dll003-尋找大兵LoadLibraryA:從定位到的dll中尋找所需函數地址004-被截斷的shellCode:加解密,解決shellCode的零字截斷問題
    EXP編寫學習之繞過GS
    2023-02-20 09:58:16
    棧中的守護天使 :GSGS原理向棧內壓入一個隨機的DWORD值,這個隨機數被稱為canary ,IDA稱為 Security Cookie。Security Cookie 放入 ebp前,并且data節中存放一個 Security Cookie的副本。棧中發生溢出時,Security Cookie首先被淹沒,之后才是ebp和返回地址。函數返回之前,會添加一個Security Cookie驗證操作,稱為Security Check。檢測到溢出時,系統將進入異常處理流程,函數不會正常返回,ret也不會被執行。函數使用無保護的關鍵字標記。緩沖區不是8字節類型 且 大小不大于4個字節。可以為函數強制啟用GS。
    該漏洞發生的位置是在驅動文件Win32k.sys中的xxxHandleMenuMessage函數,產生的原因是沒有對該函數中調用的xxxMNFindWindowFromPoint函數的返回值進行合法性驗證,直接將其作為參數傳遞給后面的xxxSendMessage函數調用,從而造成了提權漏洞。
    反射式DLL注入實現
    2022-05-13 15:59:21
    反射式dll注入與常規dll注入類似,而不同的地方在于反射式dll注入技術自己實現了一個reflective loader()函數來代替LoadLibaryA()函數去加載dll,示意圖如下圖所示。藍色的線表示與用常規dll注入相同的步驟,紅框中的是reflective loader()函數行為,也是下面重點描述的地方。
    在所有函數調用發生時,向棧幀內壓入一個額外的隨機 DWORD,隨機數標注為“SecurityCookie”。在函數返回之前,系統將執行一個額外的安全驗證操作,被稱做 Security check。
    MITM Fuzz下圖是用戶層與內核層實現通信的過程,可以看到,最后是通過NtDeviceIoControlFile來分發給相應驅動對象的派遣函數的,因此,可以通過對該函數進行HOOK操作。如果將修改以后的數據發送給NtDeviceIoControlFile函數以后,發生了內核崩潰或藍屏,往往預示著該驅動程序可能存在內核漏洞。
    當線程從等待狀態蘇醒后,會自動檢測自己得APC隊列中是否存在APC過程。所以只需要將目標進程的線程的APC隊列里面添加APC過程,當然為了提高命中率可以向進程的所有線程中添加APC過程。然后促使線程從休眠中恢復就可以實現APC注入。往線程APC隊列添加APC,系統會產生一個軟中斷。第二個參數表示插入APC的線程句柄,要求線程句柄必須包含THREAD_SET_CONTEXT訪問權限。第三個參數表示傳遞給執行函數的參數。如果直接傳入shellcode不設置第三個函數,可以直接執行shellcode。
    shellcode編寫探究
    2022-06-09 15:34:57
    前言shellcode是不依賴環境,放到任何地方都可以執行的機器碼。shellcode的應用場景很多,本文不研究shellcode的具體應用,而只是研究編寫一個shellcode需要掌握哪些知識。要使用字符串,需要使用字符數組。所以我們需要用到 LoadLibrary 和 GetProcAddress 這兩個函數,來動態獲取系統API的函數指針。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类