Buffertspillskydd
Buffertspillskydd är någon av olika tekniker som används under programvaruutveckling för att förbättra säkerheten för körbara program genom att upptäcka buffertspill på stackallokerade variabler och förhindra dem från att orsaka programfel eller från att bli allvarliga säkerhetsbrister . Ett stackbuffertspill uppstår när ett program skriver till en minnesadress på programmets anropsstack utanför den avsedda datastrukturen, som vanligtvis är en buffert med fast längd. Stack buffer overflow buggar orsakas när ett program skriver mer data till en buffert som finns på stacken än vad som faktiskt är allokerat för den bufferten. Detta resulterar nästan alltid i korruption av intilliggande data i stacken, vilket kan leda till programkrascher, felaktig användning eller säkerhetsproblem.
Vanligtvis modifierar skyddet för buffertspill organisationen av stack-allokerade data så att det inkluderar ett kanariefågelvärde som, när det förstörs av ett stackbuffertspill, visar att en buffert som föregår den i minnet har flödat över. Genom att verifiera kanariefågelvärdet kan exekveringen av det berörda programmet avslutas, vilket förhindrar att det beter sig illa eller tillåter en angripare att ta kontroll över det. Andra skyddstekniker för buffertspill inkluderar gränskontroll , som kontrollerar åtkomst till varje tilldelat minnesblock så att de inte kan gå längre än det faktiskt tilldelade utrymmet, och taggning , som säkerställer att minne som tilldelats för lagring av data inte kan innehålla exekverbar kod.
Att överfylla en buffert tilldelad på stacken är mer sannolikt att påverka programexekveringen än att överfylla en buffert på högen eftersom stacken innehåller returadresserna för alla aktiva funktionsanrop. Liknande implementeringsspecifika skydd finns dock också mot heap-baserade spill.
Det finns flera implementeringar av skydd för buffertspill, inklusive de för GNU Compiler Collection , LLVM , Microsoft Visual Studio och andra kompilatorer.
Översikt
Ett stackbuffertspill uppstår när ett program skriver till en minnesadress på programmets anropsstack utanför den avsedda datastrukturen, som vanligtvis är en buffert med fast längd. Stack buffer overflow buggar orsakas när ett program skriver mer data till en buffert som finns på stacken än vad som faktiskt är allokerat för den bufferten. Detta resulterar nästan alltid i korruption av intilliggande data i stacken, och i de fall där överflödet utlöstes av misstag, kommer det ofta att orsaka att programmet kraschar eller fungerar felaktigt. Stackbuffertspill är en typ av det mer allmänna programmeringsfelet som kallas buffertspill (eller buffertöverskridande). Att överfylla en buffert på stacken är mer sannolikt att spåra ur programexekveringen än att överfylla en buffert på högen eftersom stacken innehåller returadresserna för alla aktiva funktionsanrop.
Stack buffertspill kan orsakas avsiktligt som en del av en attack som kallas stack smashing . Om det drabbade programmet körs med speciella privilegier, eller om det accepterar data från opålitliga nätverksvärdar (till exempel en offentlig webbserver ), är felet en potentiell säkerhetsrisk som gör att en angripare kan injicera körbar kod i det körande programmet och ta kontroll av processen. Detta är en av de äldsta och mer pålitliga metoderna för angripare att få obehörig åtkomst till en dator.
Vanligtvis modifierar skyddet för buffertspill organisationen av data i stackramen för ett funktionsanrop för att inkludera ett "kanariefågel"-värde som, när det förstörs, visar att en buffert som föregår den i minnet har flödat över. Detta ger fördelen att förhindra en hel klass av attacker. Enligt vissa forskare är effekten av dessa tekniker försumbar.
Stack-krossskydd kan inte skydda mot vissa former av attack. Den kan till exempel inte skydda mot buffertspill i högen. Det finns inget vettigt sätt att ändra layouten för data inom en struktur ; strukturer förväntas vara desamma mellan moduler, särskilt med delade bibliotek. All data i en struktur efter en buffert är omöjlig att skydda med kanariefåglar; Därför måste programmerare vara mycket försiktiga med hur de organiserar sina variabler och använder sina strukturer.
Kanarieöarna
Kanariefåglar eller kanariefåglar är kända värden som placeras mellan en buffert och styrdata på stacken för att övervaka buffertspill. När bufferten svämmar över är den första data som förstörs vanligtvis kanariefågeln, och en misslyckad verifiering av kanariefågeldatan kommer därför att varna om ett spill, som sedan kan hanteras, till exempel genom att ogiltigförklara den korrupta datan. Ett kanariefågelvärde ska inte förväxlas med ett sentinelvärde .
Terminologin är en hänvisning till den historiska praxis att använda kanariefåglar i kolgruvor , eftersom de skulle påverkas av giftiga gaser tidigare än gruvarbetarna, vilket ger ett biologiskt varningssystem. Kanarieöarna kallas omväxlande för cookies , vilket är tänkt att framkalla bilden av en "trasig kaka" när värdet är skadat.
Det finns tre typer av kanariefåglar som används: terminator , random och random XOR . Aktuella versioner av StackGuard stöder alla tre, medan ProPolice stöder terminator och slumpmässiga kanariefåglar.
Terminator kanariefåglar
Terminatorkanariefåglar använder observationen att de flesta buffertspillsattacker är baserade på vissa strängoperationer som slutar vid strängavslutare. Reaktionen på denna observation är att kanarieöarna är byggda av nollterminatorer , CR , LF och FF . Som ett resultat måste angriparen skriva ett nolltecken innan han skriver returadressen för att undvika att ändra kanariefågeln. Detta förhindrar attacker med strcpy()
och andra metoder som återkommer vid kopiering av ett nolltecken, medan det oönskade resultatet är att kanariefågeln är känd. Även med skyddet kan en angripare eventuellt skriva över kanariefågeln med dess kända värde och styrinformation med felaktiga värden, och därmed skicka kanariefågelkontrollkoden, som exekveras strax före den specifika processorns retur-från-samtal-instruktion.
Slumpmässiga kanariefåglar
Slumpmässiga kanariefåglar genereras slumpmässigt, vanligtvis från en entropi - insamlingsdemon , för att förhindra en angripare från att veta deras värde. Vanligtvis är det inte logiskt möjligt eller rimligt att läsa kanariefågel för exploatering; kanariefågeln är ett säkert värde som bara är känt av de som behöver veta det – skyddskoden för buffertspill i det här fallet.
Normalt genereras en slumpmässig kanariefågel vid programinitiering och lagras i en global variabel. Denna variabel är vanligtvis utfylld av omappade sidor så att försök att läsa den med hjälp av alla typer av knep som utnyttjar buggar för att läsa av RAM orsakar ett segmenteringsfel, vilket avslutar programmet. Det kan fortfarande vara möjligt att läsa kanariefågel om angriparen vet var den är eller kan få programmet att läsa från stacken.
Slumpmässiga XOR kanariefåglar
Slumpmässiga XOR-kanariefåglar är slumpmässiga kanariefåglar som XOR-krypteras med hjälp av hela eller delar av kontrolldata. På det här sättet, när kanariefågeln eller kontrolldata väl har klippts, är kanariefågelvärdet fel.
Random XOR-kanariefåglar har samma sårbarheter som slumpmässiga kanariefåglar, förutom att metoden "läs från stack" för att få kanariefågel är lite mer komplicerad. Angriparen måste få kanariefågel, algoritmen och kontrolldata för att återskapa den ursprungliga kanariefågel som behövs för att förfalska skyddet.
Dessutom kan slumpmässiga XOR-kanariefåglar skydda mot en viss typ av attack som innebär att en buffert i en struktur svämmar över till en pekare för att ändra pekaren till att peka på en del av kontrolldata. På grund av XOR-kodningen kommer kanariefågeln att ha fel om kontrolldata eller returvärde ändras. På grund av pekaren kan styrdata eller returvärde ändras utan att svämma över kanariefågeln.
Även om dessa kanariefåglar skyddar kontrolldatan från att ändras av klumpade pekare, skyddar de inte någon annan data eller själva pekarna. Speciellt funktionspekare är ett problem här, eftersom de kan flöda över i och kan exekvera skalkod när de anropas.
Gränskontroll
Gränskontroll är en kompilatorbaserad teknik som lägger till runtime bounds-information för varje tilldelat minnesblock och kontrollerar alla pekare mot dem vid körning. För C och C++ kan gränskontroll utföras vid pekarberäkningstidpunkt eller vid dereferenstid.
Implementeringar av detta tillvägagångssätt använder antingen ett centralt arkiv, som beskriver varje tilldelat minnesblock, eller fettpekare , som innehåller både pekaren och ytterligare data, som beskriver regionen som de pekar på.
Taggning
Taggning är en kompilatorbaserad eller hårdvarubaserad (kräver en taggad arkitektur ) teknik för att tagga typen av en databit i minnet, som huvudsakligen används för typkontroll. Genom att markera vissa områden i minnet som icke-körbara förhindrar det effektivt att minne som allokerats för att lagra data innehåller körbar kod. Vissa minnesområden kan också markeras som icke-allokerade, vilket förhindrar buffertspill.
Historiskt sett har taggning använts för att implementera programmeringsspråk på hög nivå; med lämpligt stöd från operativsystemet kan taggning också användas för att upptäcka buffertspill. Ett exempel är NX-bit- hårdvarufunktionen, som stöds av Intel , AMD och ARM -processorer.
Genomföranden
GNU Compiler Collection (GCC)
Stack-smashing-skydd implementerades först av StackGuard 1997 och publicerades vid USENIX Security Symposium 1998 . StackGuard introducerades som en uppsättning patchar till Intel x86-backend av GCC 2.7. StackGuard upprätthölls för Immunix Linux-distributionen från 1998 till 2003, och utökades med implementeringar för terminator, slumpmässiga och slumpmässiga XOR-kanariefåglar. StackGuard föreslogs för inkludering i GCC 3.x vid GCC 2003 Summit Proceedings, men detta uppnåddes aldrig.
Från 2001 till 2005 utvecklade IBM GCC-patchar för stack-smashing-skydd, känd som ProPolice . Det förbättrade idén med StackGuard genom att placera buffertar efter lokala pekare och funktionsargument i stackramen. Detta hjälpte till att undvika korruption av pekare, vilket förhindrade åtkomst till godtyckliga minnesplatser.
Red Hat- ingenjörer identifierade dock problem med ProPolice, och 2005 återimplementerades stack-smashing-skyddet för inkludering i GCC 4.1. Detta arbete introducerade -fstack-protector , som bara skyddar vissa sårbara funktioner, och -fstack-protector-all -flaggan, som skyddar alla funktioner oavsett om de behöver det eller inte.
Under 2012 implementerade Googles ingenjörer flaggan -fstack-protector-strong för att uppnå en bättre balans mellan säkerhet och prestanda. Den här flaggan skyddar fler typer av sårbara funktioner än vad -fstack-protector gör, men inte alla funktioner, vilket ger bättre prestanda än -fstack-protector-all . Den är tillgänglig i GCC sedan dess version 4.9.
Alla Fedora- paket är kompilerade med -fstack-protector sedan Fedora Core 5, och -fstack-protector-strong sedan Fedora 20. De flesta paket i Ubuntu är kompilerade med -fstack-protector sedan 6.10. Varje Arch Linux- paket är kompilerat med -fstack-protector sedan 2011. Alla Arch Linux-paket byggda sedan 4 maj 2014 använder -fstack-protector-strong . Stackskydd används endast för vissa paket i Debian och endast för FreeBSD- bassystemet sedan 8.0. Stackskydd är standard i vissa operativsystem, inklusive OpenBSD , Hardened Gentoo och DragonFly BSD [ citat behövs ] .
StackGuard och ProPolice kan inte skydda mot spill i automatiskt allokerade strukturer som svämmar över till funktionspekare. ProPolice kommer åtminstone att omarrangera tilldelningsordningen för att få sådana strukturer allokerade före funktionspekare. En separat mekanism för pekarskydd föreslogs i PointGuard och är tillgänglig på Microsoft Windows.
Microsoft Visual Studio
Kompilatorsviten från Microsoft implementerar buffertspillskydd sedan version 2003 genom kommandoradsväxeln /GS , som är aktiverad som standard sedan version 2005. Användning av /GS- inaktiverar skyddet.
IBM kompilator
Stack-smashing-skydd kan aktiveras av kompilatorflaggan -qstackprotect
.
Clang/ LLVM
Clang stöder samma -fstack-protector- alternativ som GCC, plus tre buffertspilldetektorer, nämligen AddressSanitizer (-fsanitize=adress), -fsanitize=bounds och SafeCode. Dessa system har olika kompromisser när det gäller prestandastraff, minneskostnader och klasser av upptäckta buggar. Stackskydd är standard i vissa operativsystem, inklusive OpenBSD .
Intel kompilator
Intels C- och C++-kompilator stöder stack-smashing-skydd med alternativ som liknar de som tillhandahålls av GCC och Microsoft Visual Studio.
Felsäkert C
Fail-Safe C är en minnessäker ANSI C-kompilator med öppen källkod som utför gränskontroll baserat på fettpekare och objektorienterad minnesåtkomst.
StackGhost (hårdvarubaserad)
StackGhost har uppfunnits av Mike Frantzen och är en enkel justering av rutinerna för registerfönsterspill/fyllning som gör buffertspill mycket svårare att utnyttja. Den använder en unik hårdvarufunktion i Sun Microsystems SPARC- arkitektur (det vill säga: uppskjuten på-stack i ramregister fönsterspill/fyllning) för att upptäcka modifieringar av returpekare ( ett vanligt sätt för ett utnyttjande för att kapa exekveringsvägar) transparent, automatiskt skyddar alla applikationer utan att kräva binära ändringar eller källändringar. Prestationspåverkan är försumbar, mindre än en procent. De resulterande gdb -problemen löstes av Mark Kettenis två år senare, vilket möjliggjorde aktivering av funktionen. Efter denna händelse integrerades (och optimerades) StackGhost-koden i OpenBSD /SPARC.
Ett kanariefågelexempel
Normal bufferttilldelning för x86- arkitekturer och andra liknande arkitekturer visas i buffertspillposten . Här kommer vi att visa den modifierade processen som den hänför sig till StackGuard.
När en funktion anropas skapas en stackram. En stackram byggs från slutet av minnet till början; och varje stapelram placeras på toppen av stapeln, närmast början av minnet. Sålunda, att springa av slutet av en bit data i en stackram förändrar data som tidigare matats in i stackramen; och att springa från slutet av en stackram placerar data i den föregående stackramen. En typisk stackram kan se ut som nedan, med en returadress (RETA) placerad först, följt av annan kontrollinformation (CTLI).
(CTLI)(RETA)
I C kan en funktion innehålla många olika datastrukturer per anrop. Varje bit data som skapas vid anrop placeras i stackramen i ordning och ordnas således från slutet till början av minnet. Nedan är en hypotetisk funktion och dess stackram.
a
int foo () { int a ; /* heltal */ int * b ; /* pekare till heltal */ char c [ 10 ]; /* teckenmatriser */ char d [ 3 ]; b = &a ; /* initiera b för att peka på platsen för en */ strcpy ( c , get_c ()); /* hämta c någonstans ifrån, skriv det till c */ * b = 5 ; /* data vid den punkt i minnet b indikerar är satt till 5 */ strcpy ( d , get_d ()); returnera * b ; /* läs från b och skicka det till den som ringer */ }
(d..)(c..........)(b...)(a...)(CTLI)(RETA)
I den här hypotetiska situationen, om mer än tio byte skrivs till arrayen c
, eller mer än 13 till teckenarrayen d
, kommer överskottet att svämma över till heltalspekaren b
, sedan till heltal a
, sedan till kontrollinformationen och slutligen returadress. Genom att skriva över b
får pekaren att referera till vilken position som helst i minnet, vilket orsakar en läsning från en godtycklig adress. Genom att skriva över RETA kan funktionen fås att exekvera annan kod (när den försöker återvända), antingen befintliga funktioner ( ret2libc ) eller kod inskriven i stacken under överflödet.
I ett nötskal kan dålig hantering av c
och d
, såsom de obegränsade strcpy ()-anropen ovan, tillåta en angripare att kontrollera ett program genom att direkt påverka värdena som tilldelats c
och d .
Målet med buffertspillskydd är att upptäcka detta problem på ett så minsta möjliga sätt som möjligt. Detta görs genom att ta bort det som kan vara farligt och placera en sorts snubbeltråd, eller kanariefågel , efter bufferten.
Buffertspillskydd implementeras som en ändring av kompilatorn. Som sådan är det möjligt för skyddet att ändra strukturen för data på stackramen. Detta är precis fallet i system som ProPolice . Ovanstående funktions automatiska variabler omarrangeras säkrare: arrayerna c
och d
allokeras först i stackramen, som placerar heltal a
och heltalspekare b
före dem i minnet. Så stapelramen blir
(b...)(a...)(d..)(c.........)(CTLI)(RETA)
Eftersom det är omöjligt att flytta CTLI eller RETA utan att bryta den producerade koden, används en annan taktik. En extra bit av information, som kallas en "kanariefågel" (CNRY), placeras efter buffertarna i stackramen. När buffertarna svämmar över ändras kanariefågelvärdet. För att effektivt attackera programmet måste en angripare lämna en tydlig indikation på sin attack. Stapelramen är
(b...)(a...)(d..)(c.........)(CNRY)(CTLI)(RETA)
I slutet av varje funktion finns en instruktion som fortsätter att utföras från minnesadressen som anges av RETA . Innan denna instruktion exekveras, säkerställer en kontroll av CNRY att den inte har ändrats. Om värdet på CNRY inte klarar testet, avslutas programkörningen omedelbart. I huvudsak resulterar både avsiktliga attacker och oavsiktliga programmeringsbuggar i att ett program avbryts.
Kanarietekniken lägger till några instruktioner om overhead för varje funktionsanrop med en automatisk array, omedelbart före all dynamisk bufferttilldelning och efter dynamisk buffertavallokering. Omkostnaderna som genereras i denna teknik är inte signifikanta. Det fungerar dock om inte kanariefågeln förblir oförändrad. Om angriparen vet att den finns där och kan bestämma värdet på kanariefågeln, kan de helt enkelt kopiera över den med sig själv. Detta är vanligtvis svårt att ordna medvetet och mycket osannolikt i oavsiktliga situationer.
Positionen för kanariefågeln är implementeringsspecifik, men den är alltid mellan buffertarna och den skyddade datan. Olika positioner och längder har olika fördelar.
Se även
- Sentinelvärde (som inte ska förväxlas med ett kanariefågelvärde)
- Kontrollflödesintegritet
- Randomisering av adressutrymmeslayout
- Körbart utrymmesskydd
- Minnesdebugger
- Statisk kodanalys
externa länkar
- GCC-toppmötet 2003 (PDF)
- Smashing the Stack för skojs skull och vinst av Aleph One
- Propolisens officiella hem
- Immunix StackGuards hemsida
- Original StackGuard-papper i USENIX Security 1998
- StackGhost: Hardware Facilitated Stack Protection
- FreeBSD 5.4 och 6.2 propolisimplementering
- Fyra olika knep för att kringgå StackShield- och StackGuard-skydd
- Stack Smashing Protector