Integritatea fluxului de control

Versiunea actuală a paginii nu a fost încă examinată de colaboratori experimentați și poate diferi semnificativ de versiunea revizuită pe 6 mai 2022; verificările necesită 3 modificări .

Control-flow integrity ( CFI ) este un nume general pentru tehnicile de securitate computerizată care vizează restricționarea căilor posibile de execuție a programului în cadrul unui grafic de flux de control pre-previzat pentru a crește securitatea acestuia [1] . CFI îngreunează un atacator să preia controlul asupra execuției unui program, făcând imposibilă reutilizarea unor părți deja existente ale codului mașină. Tehnici similare includ separarea cod-pointer (CPS) și integritatea cod-pointer (CPI) [2] [3] .

Suportul CFI este prezent în compilatoarele Clang [4] și GCC [5] , precum și în Control Flow Guard [6] și Return Flow Guard [7] de la Microsoft și Reuse Attack Protector [8] de la echipa PaX.

Istorie

Invenția modalităților de protecție împotriva execuției codului arbitrar, cum ar fi Data Execution Prevention și NX-bit , a condus la apariția unor noi metode care vă permit să obțineți controlul asupra programului (de exemplu, programarea orientată spre returnare ) [ 8] . În 2003, Echipa PaX a publicat un document care descrie posibilele situații care duc la piratarea programului și idei de protecție împotriva acestora [8] [9] . În 2005, un grup de cercetători Microsoft a oficializat aceste idei și a inventat termenul Control-flow Integrity pentru a se referi la metodele de protecție împotriva modificărilor fluxului de control original al unui program. Pe lângă aceasta, autorii au propus o metodă de instrumentare a codului mașină deja compilat [1] .

Ulterior, cercetătorii, pe baza ideii CFI, au propus multe modalități diferite de a crește rezistența programului la atacuri. Abordările descrise nu au fost adoptate pe scară largă din motive care includ încetinirile mari ale programelor sau nevoia de informații suplimentare (de exemplu, obținute prin profilare ) [10] .

În 2014, o echipă de cercetători de la Google a publicat o lucrare care a analizat implementarea CFI pentru compilatoarele industriale GCC și LLVM pentru instrumentarea programelor C++. Suportul oficial CFI a fost adăugat în 2014 în GCC 4.9.0 [5] [11] și în 2015 în Clang 3.7 [12] [13] . Microsoft a lansat Control Flow Guard în 2014 pentru Windows 8.1 , adăugând suport de la sistemul de operare la Visual Studio 2015 [6] .

Descriere

Dacă există salturi indirecte în codul programului , este posibil să se transfere controlul la orice adresă unde poate fi localizată comanda (de exemplu, pe x86 va fi orice adresă posibilă, deoarece lungimea minimă a comenzii este de un octet [14] ). Dacă un atacator poate modifica cumva valoarea prin care este transferat controlul atunci când execută o instrucțiune de salt, atunci el poate reutiliza codul programului existent pentru propriile nevoi.

În programele reale, salturile non-locale duc de obicei la începutul funcțiilor (de exemplu, dacă se folosește o instrucțiune de apel de procedură) sau la instrucțiunea care urmează instrucțiunii de apelare (întoarcerea procedurii). Primul tip de tranziții este o tranziție directă (în engleză forward-edge ), deoarece va fi notată printr-un arc direct pe graficul fluxului de control. Al doilea tip se numește tranziție înapoi (ing. back-edge ), prin analogie cu primul - arcul corespunzător tranziției va fi invers [15] .

Tranziții directe

Pentru salturile directe, numărul de adrese posibile la care poate fi transferat controlul va corespunde numărului de funcții din program. De asemenea, luând în considerare sistemul de tip și semantica limbajului de programare în care este scris codul sursă, sunt posibile restricții suplimentare [16] . De exemplu, în C++ , într-un program corect , un pointer de funcție folosit într-un apel indirect trebuie să conțină adresa unei funcții de același tip ca și pointerul însuși [17] .

O modalitate de a implementa integritatea fluxului de control pentru salturile directe este că puteți analiza programul și puteți determina setul de adrese legale pentru diferite instrucțiuni de ramură [1] . Pentru a construi un astfel de set, analiza codului static este de obicei utilizată la un anumit nivel de abstractizare (la nivel de cod sursă , reprezentare internă a analizorului sau cod mașină [1] [10] ). Apoi, folosind informațiile primite, codul este inserat lângă instrucțiunile ramurii indirecte pentru a verifica dacă adresa primită la runtime se potrivește cu cea calculată static. În cazul divergenței, programul se blochează de obicei, deși implementările vă permit să personalizați comportamentul în cazul unei încălcări a fluxului de control prezis [18] [19] . Astfel, graficul fluxului de control este limitat doar la acele margini (apeluri de funcții) și vârfuri (puncte de intrare în funcție) [1] [16] [20] care sunt evaluate în timpul analizei statice, deci atunci când se încearcă modificarea indicatorului utilizat pentru sărituri indirecte , atacatorul va eșua.

Această metodă vă permite să preveniți programarea orientată spre sărituri [21] și programarea orientată către apeluri [22] , deoarece acestea din urmă folosesc în mod activ sărituri indirecte directe.

Tranziții inverse

Pentru tranzițiile înapoi, sunt posibile mai multe abordări ale implementării CFI [8] .

Prima abordare se bazează pe aceleași ipoteze ca CFI pentru salturile directe, adică capacitatea de a calcula adrese de retur dintr-o funcție [23] .

A doua abordare este de a trata adresa de retur în mod specific. Pe lângă simpla salvare pe stivă , se mai salvează, eventual cu unele modificări, într-un loc special alocat pentru el (de exemplu, la unul dintre registrele procesorului). De asemenea, înainte de instrucțiunea de returnare, se adaugă cod care restabilește adresa de retur și o verifică cu cea de pe stivă [8] .

A treia abordare necesită suport suplimentar din partea hardware-ului. Împreună cu CFI, se folosește o stivă umbră - o zonă de memorie specială inaccesibilă unui atacator, în care adresele de returnare sunt stocate atunci când se apelează funcții [24] .

La implementarea schemelor CFI pentru back jump-uri, este posibil să se prevină un atac de întoarcere la bibliotecă și o programare orientată spre returnare bazată pe schimbarea adresei de retur pe stivă [ 23] .

Exemple

În această secțiune, vor fi luate în considerare exemple de implementări ale integrității fluxului de control.

Clang Funcție indirectă Verificare apel

Verificarea apelurilor de funcții indirecte (IFCC) include verificări pentru salturi indirecte într-un program, cu excepția unor sărituri „speciale”, cum ar fi apelurile de funcții virtuale. La construirea unui set de adrese la care poate avea loc o tranziție, se ia în considerare tipul funcției. Datorită acestui fapt, este posibil să se prevină nu numai utilizarea valorilor incorecte care nu indică începutul funcției, ci și turnarea incorectă a tipului în codul sursă. Pentru a activa verificările în compilator, există o opțiune -fsanitize=cfi-icall[4] .

// clang-ifcc.c #include <stdio.h> int suma ( int x , int y ) { returnează x + y _ } int dbl ( int x ) { returnează x + x ; } void call_fn ( int ( * fn ) ( int )) { printf ( "Valoarea rezultatului: %d \n " , ( * fn )( 42 )); } void erase_type ( void * fn ) { // Comportamentul este nedefinit dacă tipul dinamic al lui fn nu este același cu int (*)(int). call_fn ( fn ); } int main () { // Când apelați erase_type, informațiile de tip static se pierd. tip_tergere ( suma ); returnează 0 ; }

Un program fără verificări se compilează fără mesaje de eroare și se execută cu un rezultat nedefinit care variază de la o rulare la alta:

$ clang -Perete -Wextra clang-ifcc.c $ ./a.out Valoarea rezultatului: 1388327490

Compilat cu următoarele opțiuni, obțineți un program care se anulează atunci când este apelat call_fn.

$ clang -flto -fvisibility=hidden -fsanitize=cfi -fno-sanitize-trap=all clang-ifcc.c $ ./a.out clang-ifcc.c:12:32: eroare de rulare: verificarea integrității fluxului de control pentru tipul „int (int)” a eșuat în timpul apelului indirect al funcției (./a.out+0x427a20): notă: (necunoscut) definit aici

Clang Forward-Edge CFI pentru apeluri virtuale

Această metodă are ca scop verificarea integrității apelurilor virtuale în limbajul C++. Pentru fiecare ierarhie de clasă care conține funcții virtuale , sunt construite hărți de biți care arată ce funcții pot fi apelate pentru fiecare tip static. Dacă în timpul execuției în program, tabelul cu funcțiile virtuale ale oricărui obiect este corupt (de exemplu, tipul incorect care distruge ierarhia sau pur și simplu coruperea memoriei de către un atacator), atunci tipul dinamic al obiectului nu se va potrivi cu niciunul dintre cele prezise static. [10] [25] .

// virtual-calls.cpp #include <cstdio> struct B { virtual void foo () = 0 ; virtual ~ B () {} }; struct D : public B { void foo () override { printf ( "Funcția dreapta \n " ); } }; struct Bad : public B { void foo () override { printf ( "Funcție greșită \n " ); } }; int main () { rău rău ; // Standardul C++ permite casting astfel: B & b = static_cast < B &> ( bad ); // Derived1 -> Base -> Derived2. D & normal = static_cast < D &> ( b ); // Ca rezultat, tipul dinamic al obiectului este normal normal . foo (); // va fi rău și va fi apelată funcția greșită. returnează 0 ; }

După compilare fără verificări activate:

$ clang++ -std=c++11 virtual-calls.cpp $ ./a.out Funcție greșită

În program, în loc ca implementarea fooclasei Dsă fie apelată foodin Bad. Această problemă va fi surprinsă dacă compilați programul cu -fsanitize=cfi-vcall:

$ clang++ -std=c++11 -Wall -flto -fvisibility=hidden -fsanitize=cfi-vcall -fno-sanitize-trap=toate apelurile-virtuale.cpp $ ./a.out virtual-calls.cpp:24:3: eroare de rulare: verificarea integrității fluxului de control pentru tipul „D” a eșuat în timpul apelului virtual (adresa vtable 0x000000431ce0) 0x000000431ce0: notă: vtable este de tip „Bad” 00 00 00 00 30 a2 42 00 00 00 00 00 e0 a1 42 00 00 00 00 00 60 a2 42 00 00 00 00 00 00 00 00 00 ^

Note

  1. ↑ 1 2 3 4 5 Martín Abadi, Mihai Budiu, Úlfar Erlingsson, Jay Ligatti. Control-flow Integrity  // Proceedings of the 12th ACM Conference on Computer and Communications Security. - New York, NY, SUA: ACM, 2005. - S. 340-353 . — ISBN 1595932267 . - doi : 10.1145/1102120.1102165 .
  2. Volodymyr Kuznetsov, László Szekeres, Mathias Payer, George Candea, R. Sekar. Code-pointer Integrity  // Proceedings of the 11th USENIX Conference on Operating Systems Design and Implementation. - Berkeley, CA, SUA: Asociația USENIX, 2014. - P. 147-163 . — ISBN 9781931971164 .
  3. ↑ Despre diferențele dintre proprietățile CFI, CPS și CPI  . nebelwelt.net. Preluat la 22 decembrie 2017. Arhivat din original la 22 decembrie 2017.
  4. ↑ 1 2 Control Flow Integrity - Documentația Clang 5 . releases.llvm.org. Preluat la 22 decembrie 2017. Arhivat din original la 23 decembrie 2017.
  5. ↑ 1 2 vtv - GCC Wiki . gcc.gnu.org. Preluat la 22 decembrie 2017. Arhivat din original la 11 iulie 2017.
  6. 1 2 Control Flow Guard (Windows  ) . msdn.microsoft.com. Preluat la 22 decembrie 2017. Arhivat din original la 22 decembrie 2017.
  7. ↑ Return Flow Guard - Laboratorul Xuanwu  al lui Tencent . xlab.tencent.com. Preluat la 22 decembrie 2017. Arhivat din original la 23 decembrie 2017.
  8. ↑ 1 2 3 4 5 grsecuritate  . _ www.grsecurity.net. Consultat la 22 decembrie 2017. Arhivat din original la 17 februarie 2018.
  9. [1] Arhivat pe 5 august 2017 la Wayback Machine PaX future
  10. ↑ 1 2 3 Caroline Tice, Tom Roeder, Peter Collingbourne, Stephen Checkoway, Úlfar Erlingsson. Implementarea integrității fluxului de control de vârf în GCC și LLVM  // Proceedings of the 23rd Conference USENIX on Security Symposium. - Berkeley, CA, SUA: Asociația USENIX, 2014. - S. 941-955 . — ISBN 9781931971157 .
  11. GCC 4.9 Release Series - GNU Project - Free Software Foundation (FSF  ) . gcc.gnu.org. Preluat la 22 decembrie 2017. Arhivat din original la 15 ianuarie 2018.
  12. Note de lansare Clang 3.7 — Documentația Clang 3.7 . releases.llvm.org. Preluat la 22 decembrie 2017. Arhivat din original la 26 noiembrie 2017.
  13. Lansări LLVM . releases.llvm.org. Consultat la 22 decembrie 2017. Arhivat din original la 15 decembrie 2017.
  14. Intel® 64 and IA-32 Architectures Software Developer Manuals |  Software Intel® . software.intel.com. Preluat la 22 decembrie 2017. Arhivat din original la 25 decembrie 2017.
  15. Securitate - WebAssembly . webassembly.org. Preluat la 22 decembrie 2017. Arhivat din original la 23 decembrie 2017.
  16. ↑ 1 2 Aho, Alfred W.; Seti, Ravi; Ullman, Jeffrey D. Compilatorii - Principii, Tehnologii, Instrumente, Ed. a II-a . — Williams. - 2008. - S.  1062 -1066. - ISBN 978-5-8459-1349-4 .
  17. ISO/IEC 14882:2014 - Tehnologia informației - Limbaje de programare - C++ . — ISO . - 2014. - P. 105. Copie de arhivă din 29 aprilie 2016 la Wayback Machine
  18. Vtable Verification - Ghidul utilizatorului . docs.google.com. Preluat la 22 decembrie 2017. Arhivat din original la 12 iunie 2019.
  19. Control Flow Integrity - Documentația Clang 5 . releases.llvm.org. Preluat la 22 decembrie 2017. Arhivat din original la 23 decembrie 2017.
  20. Muchnick, Steven S. Advanced Compiler Design and Implementation . - Morgan Kaufmann Publishers , 1997. - S.  609 -618. - ISBN 1-55860-320-4 .
  21. Tyler Bletsch, Xuxian Jiang, Vince W. Freeh, Zhenkai Liang. Programare orientată spre salt: O nouă clasă de atacuri prin reutilizarea codului  // Proceedings of the 6th ACM Symposium on Information, Computer and Communications Security. - New York, NY, SUA: ACM, 2011. - P. 30-40 . — ISBN 9781450305648 . - doi : 10.1145/1966913.1966919 .
  22. AliAkbar Sadeghi, Salman Niksefat, Maryam Rostamipour. Pure-Call Oriented Programming (PCOP): înlănțuirea gadgeturilor folosind instrucțiuni de apel  //  Journal of Computer Virology and Hacking Techniques. — 15.05.2017. - P. 1-18 . — ISSN 2263-8733 . - doi : 10.1007/s11416-017-0299-1 . Arhivat din original pe 22 decembrie 2017.
  23. ↑ 1 2 RAP: RIP ROP - Reuse Attack Protector (downlink) . Echipa PaX . Preluat la 22 decembrie 2017. Arhivat din original la 20 mai 2020. 
  24. Previzualizarea tehnologiei de aplicare a fluxului de control . Zona pentru dezvoltatori Intel . Preluat la 22 decembrie 2017. Arhivat din original la 14 august 2017.
  25. Control Flow Integrity Design Documentation - Clang 5 documentation . releases.llvm.org. Preluat la 22 decembrie 2017. Arhivat din original la 23 decembrie 2017.

Literatură

Cărți Articole

Link -uri