Linux ha un dominio di esecuzione chiamato READ_IMPLIES_EXEC
, che provoca l'allocazione di tutte le pagine con PROT_READ
da dare anche PROT_EXEC
. I vecchi kernel Linux lo usavano per gli eseguibili che usavano l'equivalente di gcc -z execstack
. Questo programma ti mostrerà se è abilitato per se stesso:
#include <stdio.h>
#include <sys/personality.h>
int main(void) {
printf("Read-implies-exec is %s\n", personality(0xffffffff) & READ_IMPLIES_EXEC ? "true" : "false");
return 0;
}
Se lo compili insieme a un .s
vuoto file, vedrai che è abilitato, ma senza uno sarà disabilitato. Il valore iniziale di questo viene dalle meta-informazioni ELF nel tuo binario. Esegui readelf -Wl example
. Vedrai questa riga quando hai compilato senza il .s
vuoto file:
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10
Ma questo quando lo hai compilato:
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RWE 0x10
Nota RWE
invece di solo RW
. La ragione di ciò è che il linker presuppone che i tuoi file assembly richiedano read-implies-exec a meno che non sia esplicitamente detto che non lo fanno, e se qualsiasi parte del tuo programma richiede read-implies-exec, allora è abilitato per l'intero programma . I file assembly che GCC compila gli dicono che non ne ha bisogno, con questa riga (lo vedrai se compili con -S
):
.section .note.GNU-stack,"",@progbits
I permessi di sezione predefiniti non includono ex
ec. Vedi la parte ELF del .section
documentazione per il significato dei "flag" e degli @attributi.
(E non dimenticare di passare a un'altra sezione come .text
o .data
dopo quel .section
direttiva, se il tuo .s
faceva affidamento su .text
perché la sezione predefinita all'inizio del file.)
Inserisci quella riga in example.s
(e ogni altro .s
file nel tuo progetto). La presenza di quel .note.GNU-stack
servirà per dire al linker che questo file oggetto non dipende da uno stack eseguibile, quindi il linker userà RW
invece di RWE
sul GNU_STACK
metadati e il tuo programma funzionerà come previsto.
Analogamente per NASM, un section
direttiva con i flag giusti specifica stack non eseguibili.
I moderni kernel Linux tra 5.4 e 5.8 hanno cambiato il comportamento del caricatore di programmi ELF. Per x86-64, non si attiva nulla READ_IMPLIES_EXEC
più. Al massimo (con un RWE GNU_STACK
aggiunto da ld
), otterrai che lo stack stesso sia eseguibile, non tutte le pagine leggibili. (Questa risposta copre l'ultima modifica, in 5.8, ma devono esserci state altre modifiche prima, poiché quella domanda mostra l'esecuzione corretta del codice in .data
su x86-64 Linux 5.4)
exec-all
(READ_IMPLIES_EXEC
) si verifica solo per gli eseguibili legacy a 32 bit in cui il linker non ha aggiunto un GNU_STACK
voce di intestazione a tutti. Ma come mostrato qui, ld
moderno lo aggiunge sempre con un'impostazione o con l'altra, anche quando un input .o
manca una nota.
Dovresti comunque usare questo .note
sezione per segnalare stack non eseguibili nei normali programmi. Ma se speravi di testare il codice automodificante in .data
o seguire qualche vecchio tutorial per testare lo shellcode, non è un'opzione sui kernel moderni.
In alternativa alla modifica dei file assembly con varianti di direttive di sezione specifiche di GNU, puoi aggiungere -Wa,--noexecstack
alla riga di comando per la creazione di file di assieme. Ad esempio, guarda come lo faccio in configure
di musl :
https://git.musl-libc.org/cgit/musl/commit/configure?id=adefe830dd376be386df5650a09c313c483adf1a
Credo che almeno alcune versioni di clang con l'assembler integrato potrebbero richiedere che venga passato come --noexecstack
(senza -Wa
), quindi il tuo script di configurazione dovrebbe probabilmente controllare entrambi e vedere quale è accettato.
Puoi anche usare -Wl,-z,noexecstack
al momento del collegamento (in LDFLAGS
) per ottenere lo stesso risultato. Lo svantaggio di questo è che non aiuta se il tuo progetto produce file statici (.a
) file di libreria per l'utilizzo da parte di altri software, poiché non controlli le opzioni di tempo di collegamento quando viene utilizzato da altri programmi.