Curiosa sull'implementazione di Microsoft "Buffer Security Check"

6

Uno sfondo molto veloce per aiutarti a rispondere alle mie domande:

  • Learning IDA Interactive Disassembler, la vecchia edizione gratuita (troppo cara per un hobbista)
  • Sysadmin Linux di 15 anni e amp; Esperienza DBA
  • Codificatore di hobbisti (C / ASM / Fortran /...)
  • Non molto informato su Windows & Microsoft in generale
  • non è un esperto di sicurezza, ma abbastanza da essere un amministratore di sistema

Dopo un sacco di disassemblaggi casuali di cose qua e là ho subito notato che hanno un sacco di codice comune. E proprio all'inizio di un sacco di eseguibili c'è qualcosa che assomiglia a questo:

lea     eax, [ebp+SystemTimeAsFileTime]
push    eax             ; lpSystemTimeAsFileTime
call    ds:GetSystemTimeAsFileTime
mov     eax, [ebp+SystemTimeAsFileTime.dwHighDateTime]
xor     eax, [ebp+SystemTimeAsFileTime.dwLowDateTime]

È letteralmente praticamente in ogni binario e in poco tempo si scopre che è una cosa chiamata da microsoft: "Buffer Security Check". Ed è aggiunto automaticamente dal compilatore usando il flag / GS.

È un stack canary utilizzato per rilevare l'overflow del buffer dello stack:

This method works by placing a small integer, the value of which is randomly chosen at program start, in memory just before the stack return pointer. Most buffer overflows overwrite memory from lower to higher memory addresses, so in order to overwrite the return pointer (and thus take control of the process) the canary value must also be overwritten. This value is checked to make sure it has not changed before a routine uses the return pointer on the stack

E dalla documentazione Microsoft :

On functions that the compiler recognizes as subject to buffer overrun problems, the compiler allocates space on the stack before the return address. On function entry, the allocated space is loaded with a security cookie that is computed once at module load. On function exit, and during frame unwinding on 64-bit operating systems, a helper function is called to make sure that the value of the cookie is still the same. A different value indicates that an overwrite of the stack may have occurred. If a different value is detected, the process is terminated.

Va tutto bene e tutti vivranno felici e contenti.

Tuttavia , sono confuso riguardo all'implementazione stessa. È un sacco di XOR di: System time, processId, ThreadId, uptime, più tempo (perf contatore) alla creazione del processo.

Non sto dicendo che è un'implementazione terribilmente brutta (ma a prima vista ci sto pensando) dato che è un sacco di XOR e un singolo bit sbagliato rovinerà completamente il cookie (a meno che non ci sia un attacco sono non a conoscenza di) e la forzatura bruta è prevenuta al 100% (o non lo è?) poiché il processo verrà terminato quando l'ipotesi non è corretta e un nuovo cookie generato al successivo avvio del processo.

Ora ho un sacco di domande:

  • Perché una così strana implementazione di generazione pseudo-casuale? Il sistema operativo non ha una fonte di entropia affidabile? È legato alla portabilità? Alcune varianti di Windows potrebbero non avere una fonte di entropia?
  • non è processId e threadId noti a tutti sul sistema comunque? quindi perché usarlo come fonte di entropia?
  • È tutto relativo al tempo e avviato all'avvio del processo, non vengono avviati i servizi all'avvio (roba che richiede più protezione) garantiti per avere uptime & perfcounter e un tempo di sistema noto e quindi l'entropia più debole?
  • Perché questa generazione di cookie non è una chiamata di sistema? È ora non modificabile senza ricompilazione.

Ecco il mio smontaggio commentato:

push    ebp             ; prolog
mov     ebp, esp        ; prolog
sub     esp, 14h        ; prolog
and     [ebp+SystemTimeAsFileTime.dwLowDateTime], 0
and     [ebp+SystemTimeAsFileTime.dwHighDateTime], 0
mov     eax, __security_cookie
push    esi
push    edi
mov     edi, 0BB40E64Eh ; a global constant thingy for security cookie
mov     esi, 0FFFF0000h
cmp     eax, edi        ; compare local security cookie
                    ; with global security cookie.
                    ; if security_cookie == 0BB40E64Eh then it's not initialized
                    ; and it need to be initialized
jz      short generate_security_cookie

La generazione viene eseguita solo se si confronta con una costante nota a livello mondiale "0BB40E64Eh". Non c'è un potenziale vettore di attacco? non potrebbe essere valore locale per il sistema?

  • Perché preoccuparsi di rendere condizionale la generazione?
  • È anche una funzione di sicurezza? o solo un debug significa rilevare se qualcosa è andato storto nello stack?

Sono un sacco di domande, è il mio primo passo in questo genere di cose, ma questo è così confuso. Ma soprattutto: perché non chiamare semplicemente una fonte di entropia del SO? Sono così confuso ...

Domanda bonus: se uno ha un mezzo per modificare il file binario (un singolo bit nella costante conosciuta in tutto il mondo?) non è possibile aggirare l'intera / GS roba? e se si tratta di un servizio di rete aperto ora hai un servizio con un potenziale di overflow del buffer aperto al mondo, giusto?

    
posta ker2x 03.10.2017 - 13:34
fonte

1 risposta

2

L'obiettivo del canary stack è proteggere il programma da un overflow del buffer. Il canarino è dinamico, ne viene usato uno nuovo per ogni esecuzione e, un attaccante avrà solo un tentativo di indovinarlo, poiché fallendo interromperà il programma.

La fonte del canarino è abbastanza simile a quella di CryptGenRandom , che è il modo ufficiale per ottenere numeri crittograficamente sicuri in Windows (precedente all'introduzione dell'API CNG), quindi non è male.

isn't processId and threadId known to everyone on the system anyway ? so why use it as entropy source ?

Non tutti gli attacchi arriveranno dal sistema locale. Un attaccante di rete non ne avrà idea. Poiché è economico includerli nel calcolo, vengono aggiunti per un'ulteriore entropia.

aren't services launch on boot (stuff that need the most protection) guaranteed to have low uptime & perfcounter (…)?

Non basta sapere che "viene utilizzato un valore basso". Hai bisogno del numero esatto per aggirare il canarino e hai solo un tentativo. Non penso che sarai in grado di indovinare il canarino di nessuno di loro.

Why isn't this cookie generation a system call anyway ?

Una chiamata di sistema significherebbe che il canarino può essere utilizzato solo su una nuova versione di Windows. In questo modo, non c'è alcuna dipendenza dalla versione del sistema operativo. Inoltre, ciò renderebbe la generazione più lenta.

I numeri casuali di Windows in generale beneficerebbero di avere una chiamata di sistema dedicata (o un'altra interfaccia SO per entropia, simile a unix / dev / random) per accedere a entropy mantenuto a livello di sistema, piuttosto che per il pool di processi creato da CryptGenRandom , ma sarebbe di piccolo beneficio qui.

Why bother make the generation conditional ?

Questo è un classico approccio check-if-it-is-inizializzato. La variabile globale per il cookie inizialmente è 0BB40E64Eh. Quindi, al primo utilizzo, vede che non è stato generato un canarino appropriato e esegue il calcolo. In questo modo, nessuna risorsa viene persa calcolando il canarino se non necessario. (Un valore più usuale sarebbe quello di aver usato 00000000h, non so perché è stato scelto 0BB40E64Eh)

Is it even a security feature ? or just a debug mean to detect something went wrong with the stack ?

Serve come aiuto per il debug, ma è una funzione di sicurezza.

Bonus question : If one have a mean to modify the binary (a single bit in the worldwide known constant ?) couldn't the whole /GS stuff could be bypassed ?

Sì. Usare un canarino costante permetterebbe di bypassare i canary check (che è ancora più difficile che non avere un controllo canarino, però!)

and if it's a network open service you now have a service with a buffer overflow potential open to the world, right ?

No. Hai un servizio con la protezione dello stack del compilatore disabilitata. Ma questo servizio non dovrebbe essere vulnerabile a un buffer overflow per cominciare!

E probabilmente non ti piacerebbe che il denial of service prodotto dal fallimentare controllo dei canarini prendesse il tuo servizio (è meglio dell'esecuzione di code , ma ancora male. Questo check è un'ultima riga di difesa).

Se puoi modificare il binario, anche se solo un po ', puoi probabilmente trovare obiettivi più letali che disabilitare il canarino dello stack, come cambiare il JE del confronto password in JNE :)

    
risposta data 15.07.2018 - 02:12
fonte

Leggi altre domande sui tag