Simovits

Skyddsåtgärder mot exploatering i operativsystem: del 1 av ?

För fem år sedan skrev jag ett blogginlägg om minnessäkerhet där jag främst skrev om hur programkod i C och C++ kan skrivas säkrare eller exekveras i säkrare miljöer då brister i minnessäkerhet ofta leder till godtycklig-kodexekveringssårbarheter.

I dag tänkte jag fokusera på skyddsåtgärder operativsystem implementerar för att förhindra exploatering av kompilerad kod. Operativsystemet kan inte anta att kod skrivs minnessäkert utan t.ex. buffer overflows så därför måste systemet försvara sig själv och andra program från ett exploaterat program. Operativsystemet bör sträva efter att erbjuda en så säker exekveringsmiljö som möjligt utan att inskränka programmens uttrycksförmåga eller negativt påverka effektiviteten.

Grundläggande skyddsmekanismer i operativsystem är tidsdelad-multikörning och indirekt minnesåtkomst.

Tidsdelad-multikörning

Tidsdelad-multikörning förebygger att enskilda program låser eller kraschar hela systemet genom att operativsystemet implementerar en schemaläggare som tilldelar körtid till program och utan förbehåll växlar kontext. [1]

Ett nytt program laddas, exekveras i ett tidsintervall enligt följande i moderna tidsdelade system:

  1. Ett nytt program laddas i minnet
  2. Exekveringsmiljön förbereds för programmet
  3. Operativsystemet sätter en hårdvarutimer
  4. Exekvering överförs till programmet
  5. Hårdvarutimern utlöses och exekvering överförs till operativsystemets avbrottshanteringskod som sparar undan en minimal körtidsmiljö
  6. Exekvering överförs till en timeravbrottsstjänstrutin
  7. Operativsystemet bedömer om programmet har förbrukat sin tilldelade körtid och ifall en kontextväxling skall inledas
  8. Ifall en kontextväxling inleds sparas den fulla körtidsmiljön för den gamla processen
  9. Körtid tilldelas en annan process och antingen utförs 1 för ett nytt program eller så återställs en exekveringsmiljö för en avbruten process.

[2][3][4]

Indirekt Minnesåtkomst

Tidiga system gav program direkt minnesåtkomst så att arbetsminnet och hårdvara mappades upp till globala minnesadresser utan någon form av översättning. Alla program hade full åtkomst till allt minne och hårdvara. Varje program där exekveringen kapas genom någon sårbarhet hade full åtkomst till systemet. Detta åtgärdas genom indirekt minnesåtkomst där operativsystemet översätter program-interna virtuella adresser till fysiska adresser med åtkomstkontroller så att alla program inte delar allt minne. [5]

Bild över hur adressöversättning i indirekt minnesåtkomst ofta implementeras med paging.

Tidsdelad multikörning tillsammans med indirekt minnesåtkomst är relativt billiga åtgärder som lägger grunden för operativsystems skydd mot fullständig systemkompromettering från utnyttjande av sårbarheter i program. Moderna skyddsmekanismer förfinar dessa skydd mot tillgänglighet (begränsning av resursutnyttjande som tidsdelad multikörning) och konfidentialitet plus riktighet (begränsning av resursåtkomst för riktighet och tillgänglighet som indirekt minnesåtkomst).

Separation av skrivbart och körbart minne

Operativsystemets minneshantering som följer indirekt minnesåtkomst implementeras oftast med paging där systemet delar in och fördelar minnesblock av samma storlek vilket även lindrar minnesfragmenteringsproblemet i ett multikörningssystem. Dessa minnesblock möjliggör skyddsåtgärder genom att typa minnen som antingen körbara eller skrivbara. [5] Problemet med både körbart och skrivbart minne är en följd av Von Neuman plattformen som alla moderna datorer följer inte har separata minnen för kod och data [6] och således gör självmodifierande kod enkelt. Hårdvarufunktionalitet som Execute Disable (XD) (Intel term) och No Execute (NX) (AMD term) kan utnyttjas av operativsystemsskyddsåtgärder som DEP i Windows eller minnesåtkomstkontroll i SELinux. [7][8]

Även om alla pages typas som antingen körbara eller skrivbara så går det simulera självmodifierande kod och få godtycklig kodexekvering från buffer-overflow sårbarheter. Detta genom att till exempel dela upp kod i en interpreterare av ett programspråk och skrivbar programkod i samma språk [9] eller genom att koppla ihop ett önskat kodflöde från små snuttar av instruktioner med en returinstruktion i slutet som redan finns importerade i bibliotek (gadgets) i en ROP-kedja (return operated programming). [7][8]

Så hur förebygger man att kod ”hoppar runt” mellan olika gadgets i en ROP-kedja? Jo genom att implementera moderna skyddsåtgärder såsom randomiserad addressrymdslayout och kontrollflödesintegritet. Men det blir ämnen för nästa blogginlägg.

Referenser

  1. Geeksforgeeks.org: Difference between Preemptive and Cooperative Multitasking 2025-06-30
  2. Harward CS 161 2009 Lecture 3 2025-06-30
  3. Nicholas Henricksen – Medium.com: Interrupt Handling in the Linux Kernel: A Deep Dive into System Level Event Management 2025-06-30
  4. Nicholas Henricksen – Medium.com: Context Switching in Linux: How Your CPU Juggles Processes Efficiently 2025-06-30
  5. Prof. Margaret Martionosi – Princeton University COS 318 Virtual Memory and Address Translation 2025-06-30
  6. Geeksforgeeks.org: Computer Organization – Von Neumann architecture 2025-06-30
  7. 0x434b.dev – Exploit Mitigation Techniques – Part 1 – Data Execution Prevention (DEP) 2025-06-30
  8. memn0ps.github.io – Linux User Mode Exploit Development: Data Execution Prevention (DEP) and Address Space Layout Randomisation (ASLR) Part 1 2025-06-30
  9. Geeksforgeeks.org: Introduction to Interpreters 2025-06-30