Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
CVE-2024-43630-POC — NtCopyFileChunk overflow del buffer dello stack POC | Kitploit
Strumenti/GitHubGitHub/quasarbinary/cve-2024-43630-poc
Memory ForensicsAnalisi delle VulnerabilitàExploitReverse EngineeringBinary Exploitation
GitHubquasarbinary/cve-2024-43630-poc

CVE-2024-43630-POC

NtCopyFileChunk overflow del buffer dello stack POC

Vedi Repository
1111 mesi faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

CVE-2024-43630-POC

Questa repository contiene un POC che, quando eseguito, innesca una scrittura OOB nello stack causando il crash del sistema. Questa vulnerabilità è un reperto estremamente interessante e raro che dimostra la complessità e le peculiarità della programmazione del kernel, in particolare della gestione degli oggetti. Inoltre, la vulnerabilità interessa il kernel di Windows 11 24h2, Windows 10 22h2/21h2, ma non Windows 11 22h2/23h2, il che la rende ancora più interessante.

Corretto il 12 novembre 2024

Versioni di Windows interessate

  • Windows 11 Versione 24H2
  • Windows 10 Versione 22H2
  • Windows 10 Versione 21H2
  • Windows Server 2025
  • Windows Server 2022, Edizione 23H2
  • Windows Server 2022
  • Testato su: Windows 11 24h2 (x64) ntoskrnl.exe versione 10.0.26100.1742

Panoramica della vulnerabilità

Una vulnerabilità di buffer overflow basato sullo stack (più tecnicamente, scrittura OOB) esiste nella funzione syscall del kernel Windows NtCopyFileChunk.

NtCopyFileChunk consente di eseguire due operazioni in un'unica syscall: la lettura del file sorgente e la scrittura sul file di destinazione.

NT_COPYFILE_DATA_BUFFER è una struttura che contiene tutto il necessario per la copia. Si noti che questa struttura è stata ottenuta tramite reverse engineering e il suo nome è stato inventato. Pertanto, tenetelo presente.

root@kitploit:~
struct NT_COPYFILE_DATA_BUFFER // sizeof=0x48
{
    DWORD64 UnknownQword1;
    DWORD64 UnknownQword2;
    DWORD64 UnknownQword3;
    DWORD64 UnknownQword4;
    PIRP WriteIrp;
    PDEVICE_OBJECT HighestDeviceObject;
    PFILE_OBJECT DestFileObject;
    PFILE_OBJECT SourceFileObject;
    DWORD64 SourceOffsetQuadPart;
};

La funzione appare più o meno così in pseudocodice:

root@kitploit:~
// Pseudocodice per la funzione nt!NtCopyFileChunk in win11 24h2
__int64 __fastcall NtCopyFileChunk(
	void* SourceHandle,
	void* DestHandle,
	void* UserInputHandleEvent,
	struct _IO_STATUS_BLOCK* IoStatusBlock,
	ULONG Length,
	__int64 SourceOffset,
	struct _KTHREAD** DestOffset,
	ULONG* SourceKey,
	_DWORD* DestKey,
	int Flags)
{
	[...]
	NTSTATUS Status;
	char is_alertable_io;
	DWORD64 SourceOffsetStack;
	struct _KTHREAD* DestOffsetValue;
	_OBJECT_HANDLE_INFORMATION* HandleInformation;
	NT_COPYFILE_DATA_BUFFER* DataBuffer_3;
	PVOID UserInputEventObject;
	_FILE_OBJECT* pSourceFileObject;
	PIRP WriteIrp;
	struct _KEVENT StackEvent; // [1]
	[...]

	memset(&StackEvent, 0, sizeof(StackEvent));
	DataBuffer = (NT_COPYFILE_DATA_BUFFER*)ExAllocatePool2(0x43u, Length + sizeof(NT_COPYFILE_DATA_BUFFER), 'pCoI');
	ArbDataBuffer = DataBuffer + sizeof(NT_COPYFILE_DATA_BUFFER); //point dopo NT_COPYFILE_DATA_BUFFER

	// Riferimento al file sorgente tramite handle
	ret = IopReferenceFileObject(SourceHandle, 1u, PreviousMode, (PVOID*)&DataBuffer_2->SourceFileObject, 0);
	if (ret < 0)
		goto RET;

	// Riferimento al file di destinazione tramite handle
	ret = ObReferenceFileObjectForWrite(
		(ULONG_PTR)DestHandle,
		PreviousMode,
		(_FILE_OBJECT*)&DataBuffer->DestFileObject,
		(_OBJECT_HANDLE_INFORMATION*)&HandleInformation);
	[...]

	//Riempie ArbDataBuffer con i dati che scriveremo sul file di destinazione
	ret = IopPopulateCopyWriteWorkerData(
		(__int64)DestFileObj,
		(__int64)IoStatusBlock,
		(__int64)ArbDataBuffer,
		Length,
		v28,
		(__int64)pSourceFileObject,
		UserInputHandleEvent_1,
		DestOffset,
		DestKey,
		SHIDWORD(HandleInformation),
		(__int64)&DataBuffer_2->WriteIrp);

	[...]


	if (DestFileObj->Flags & FO_SYNCHRONOUS_IO)
	{
    // [2]
		KeInitializeEvent(&StackEvent, SynchronizationEvent, 0);

    // [3]
		DataBuffer_3->WriteIrp->UserEvent = &StackEvent; //WriteIrp contiene un puntatore all'evento di stack!
		DataBuffer->WriteIrp->Flags |= IRP_MJ_WRITE;
	}
	else
	{
		//per la modalità asincrona, non ne abbiamo bisogno
		[...]
	}
	UserInputEventObject = 0;
  // [4]
	ret = ObReferenceObjectByHandle(
		UserInputHandleEvent,
		2u,
		(POBJECT_TYPE)ExEventObjectType,
		PreviousMode,
		&UserInputEventObject,
		0);
	if (ret >= 0)
	{
		//Se l'utente ha fornito l'evento corretto, procediamo con la logica principale per copiare un file in un altro. 
		//Ho omesso quella sezione di codice per semplicità.
		KeResetEvent((PRKEVENT)UserInputEventObject);
		goto NEXT_PATH_TO_READ_FILE_QUERY;
	}
RET:
	//Ecco qua! Libera la struttura DataBuffer (ricorda WriteIrp, che contiene un puntatore all'evento di stack).
  // [5]
	if (ArbDataBuffer)
		IopFreeCopyObjectsFromDataBuffer((__int64)ArbDataBuffer, 1);
	if (UserInputEventObject_1)
		ObfDereferenceObject(UserInputEventObject_1);
	return (unsigned int)ret;

}

In [1], possiamo vedere StackEvent, che è il nostro oggetto problematico. In [2], se il file di destinazione è stato aperto in modalità sincrona, il kernel usa un evento di stack per attendere in modo sincrono il completamento dell'operazione di scrittura sul file di destinazione. Per fare ciò, usa IopWaitForSynchronousIoEvent (non mostrato nello pseudocodice) sull'evento di stack anziché sull'evento passato dall'utente. Innanzitutto, il kernel attende l'evento di stack, e solo successivamente aggiorna l'evento passato dall'utente. Allo stesso modo, in [3], si può vedere che UserEvent di WriteIrp punta all'evento di stack. Tuttavia, cosa succede se formiamo la richiesta corretta ma passiamo un evento di input errato? In [4], possiamo vedere come viene referenziato l'evento di input, dove possiamo passare un handle non valido (ad esempio, il valore 1). E poi la memoria viene liberata in [5]. È qui che accade la cosa più interessante.

Analizziamo la funzione IopFreeCopyObjectsFromDataBuffer e vediamo cosa succede quando l'IRP viene liberato. Diamo un'occhiata più da vicino a WriteIrp->UserEvent.

root@kitploit:~
void __fastcall IopFreeCopyObjectsFromDataBuffer(__int64 ArbDataBuffer, char to_clear_irp)
{
  NT_COPYFILE_DATA_BUFFER *DataBuffer;
  PFILE_OBJECT SourceFileObject;
  PIRP WriteIrp;
  PFILE_OBJECT DestFileObject;

  DataBuffer = (NT_COPYFILE_DATA_BUFFER *)(ArbDataBuffer - 0x48);
  if ( to_clear_irp )
  {
    WriteIrp = DataBuffer->WriteIrp;
    DestFileObject = DataBuffer->DestFileObject;
    if ( WriteIrp )
    {
      IopFreeIrpExtension((__int64)DataBuffer->WriteIrp, 9, 1);

      //Ci spostiamo più in profondità, monitorando attentamente UserEvent
      IopExceptionCleanupEx((ULONG_PTR)DestFileObject, WriteIrp, WriteIrp->UserEvent, 0, 0);
      return;
    }
    if ( DestFileObject )
      ObfDereferenceObjectWithTag(DataBuffer->DestFileObject, 0x746C6644u);
  }
  SourceFileObject = DataBuffer->SourceFileObject;
  if ( SourceFileObject )
    ObfDereferenceObjectWithTag(SourceFileObject, 0x746C6644u);
  ExFreePoolWithTag(DataBuffer, 0);
}
root@kitploit:~
LONG_PTR __fastcall IopExceptionCleanupEx(ULONG_PTR DestFileObject, PIRP Irp, PVOID UserEvent, PVOID P, char a5)
{
  [...]
  if ( Irp )
  {
    MasterIrp = Irp->AssociatedIrp.MasterIrp;
    if ( MasterIrp )
      ExFreePoolWithTag(MasterIrp, 0);
    [...]
    IoFreeIrp(Irp);
  }
  //Oh, ecco! Ma come si può decrementare il contatore di riferimento per un oggetto di stack che non ha un OBJECT_HEADER?
  //Vuln!
  if ( UserEvent )
    ObfDereferenceObject(UserEvent);
  if ( P )
    ExFreePoolWithTag(P, 0);
  [...]
}

Ok, il kernel esegue ObfDereferenceObject su UserEvent. Ma cosa c'è che non va? Il punto è, come ho sottolineato per tutto il tempo, che questo è un KEVENT situato sullo stack. Non ha un OBJECT_HEADER e, di conseguenza, un contatore di riferimento, perché la sua durata è limitata dallo stack frame. Ma la funzione ZwCreateEvent permetterebbe di creare un tale header per l'evento, perché in quel caso la memoria viene allocata nel pool di sistema e deve essere liberata quando il contatore scende a zero. Tuttavia, questo non viene usato nel nostro caso. OBJECT_HEADER è sempre posizionato prima di ogni oggetto. È per questo che si verifica la scrittura OOB: ObfDereferenceObject fa riferimento a un offset negativo 0x30 e decrementa un fittizio contatore di riferimento. Questo consente di decrementare arbitrariamente qualcosa situato nelle variabili locali dello stack frame di NtCopyFileChunk. È del tutto casuale quali variabili locali ci saranno. Inoltre, se il contatore scende a zero, il kernel tenterà di liberare lo stack come se fosse un pool di sistema...

Perché Windows 11 23h2/22h2 non è vulnerabile?

Questa è la domanda cruciale. Ci permetterà di capire come una vulnerabilità così evidente possa essere sorta in queste circostanze.

Diamo un'occhiata allo stesso identico pezzo di codice all'interno di NtCopyFileChunk in ntoskrnl in Windows 11 23h2. Nel suddetto pseudocodice di NtCopyFileChunk per 24h2, l'evento viene inizializzato in [2].

root@kitploit:~
*(_QWORD *)&ObjectAttributes.Length = 48;
memset(&ObjectAttributes.Attributes + 1, 0, 20);
ObjectAttributes.RootDirectory = 0;
ObjectAttributes.Attributes = PreviousMode == 0 ? 0x200 : 0;
ObjectAttributes.ObjectName = 0;
//Nemmeno un evento di stack! Ha un OBJECT_HEADER e memorizza un contatore di riferimento.
status = ZwCreateEvent(&EventHandle, 0x1F0003u, &ObjectAttributes, SynchronizationEvent, 0);

Come possiamo vedere, in Windows 11 23h2/22h2, invece di un evento di stack, viene usato un evento allocato nel pool tramite ZwCreateEvent e con OBJECT_HEADER. Pertanto, qui non c'è alcuna vulnerabilità. ObfDereferenceObject è un'operazione consentita su un oggetto del genere. Tra l'altro, è esattamente così che è stata corretta questa vulnerabilità in 24h2. La radice del problema risiedeva nella desincronizzazione del codice tra versioni diverse. Forse a un certo punto gli ingegneri hanno deciso di risparmiare memoria e di definire un evento sullo stack (questa è una pratica standard). Ma si sono dimenticati che la funzione di pulizia IopFreeCopyObjectsFromDataBuffer esegue un decremento del contatore di riferimento, perché se ciò non accadesse, ci sarebbe una leak in 23h2. È sorprendente che il codice di NtCopyFileChunk non sia comune a tutte le versioni di Windows 11. Questo evidenzia la complessità dello sviluppo del kernel e mostra quanto sia facile commettere errori critici.

Exploitation?

Secondo la stessa valutazione di Microsoft, lo sfruttamento di questa vulnerabilità è 'More Likely'. Questa affermazione ha inizialmente stuzzicato il mio interesse. Quindi scopriamo la verità.

Prestiamo attenzione allo stack frame su cui avvengono gli accessi OOB.

root@kitploit:~
struct _KTHREAD* DestOffset; //-0x30
OBJECT_HANDLE_INFORMATION HandleInformation; // -0x28
NT_COPYFILE_DATA_BUFFER* DataBuffer; // -0x20
PVOID UserInputEventObject; // -0x18 
_FILE_OBJECT* pSourceFileObject; //-0x10
PIRP WriteIrp; //-0x8
struct _KEVENT StackEvent; // il nostro evento vulnerabile

E come ObfDereferenceObject accede al nostro stack frame.

root@kitploit:~
;rcx = ptr a StackEvent
ObfDereferenceObject proc near
...
;rdi = inizio di OBJECT_HEADER,
;ma nel nostro caso è ptr a DestOffset (controllato dall'utente)
lea     rdi, [rcx-30h]
...
mov     rbx, -1
lock xadd [rdi], rbx ; decrementa DestOffset
sub     rbx, 1
jg      short RET ;se refcnt era >= 2, non eliminare l'oggetto, esci e basta

;Il percorso inevitabile verso la liberazione dell'oggetto, farà crashare il sistema...

mov     rcx, [rdi+8] ;rcx = HandleInformation (struct _OBJECT_HANDLE_INFORMATION)
test    rcx, rcx 
jnz     short BSOD ; HandleInformation deve essere zero, altrimenti BSOD

test    rbx, rbx ;controllo per contatore di riferimento negativo
js     short BSOD

...altro codice...

Come potete vedere, ci sono fin troppi modi per causare un BSOD qui. Siamo anche molto fortunati che DestOffset si sovrapponga a _OBJECT_HEADER.PointerCount, che fa parte della chiamata a NtCopyFileChunk (7° parametro). Quindi, se passiamo qualsiasi DestOffset positivo maggiore di 2, decrementiamo semplicemente DestOffset e non succederà nulla lì; usciremo tranquillamente dalla funzione. Ma se passiamo DestOffset = 1, il kernel tenterà di liberare il nostro evento di stack come un pool di sistema.

Prima che il pool di sistema venga liberato, viene chiamato un callback specifico per il tipo di oggetto specificato in _OBJECT_HEADER.TypeIndex, che si sovrappone a UserInputEventObject, che controlliamo anche noi, il che significa che possiamo ingannare il sistema facendogli accettare qualsiasi tipo di oggetto vogliamo. Questo apre tutta una serie di possibilità. Il nostro obiettivo principale è di dirottare il RIP prima dell'operazione di free pool, ad esempio tramite object type confusion, per sovrascrivere il callback da qualche parte.

Ma c'è una cosa molto spiacevole che annulla i nostri piani. Come potete vedere sopra, c'è un controllo per la presenza di _OBJECT_HEADER.HandleCount e, se è diverso da zero, il kernel chiama BSOD. Nel nostro caso, questo si sovrappone a HandleInformation. E poiché questo non è un parametro controllato dall'utente, sono necessarie ulteriori ricerche su come portarlo a zero.

root@kitploit:~
//0x8 byte (sizeof)
struct _OBJECT_HANDLE_INFORMATION
{
    ULONG HandleAttributes;                                                 //0x0
    ULONG GrantedAccess;                                                    //0x4
}; 

Come si scopre, questa struttura appartiene a DestHandle. Ciò implica che dobbiamo aprire un handle sulla destinazione con GrantedAccess pari a zero. Ciò significa anche che HANDLE_TABLE_ENTRY.GrantedAccessBits del nostro DestHandle deve essere zero. Tuttavia, poiché dobbiamo anche aprirlo per la scrittura (ObReferenceFileObjectForWrite), in ogni caso non può essere zero.

Questo ci porta a un vicolo cieco.

Scarica lo strumento