BM-WII: Het blokkeren van malware door isolatie van de Windows interface Don Giot
Promotor: prof. dr. ir. Bjorn De Sutter Begeleiders: ir. Stijn Volckaert, dr. Bart Coppens, ir. Bert Abrath Masterproef ingediend tot het behalen van de academische graad van Master of Science in de ingenieurswetenschappen: computerwetenschappen
Vakgroep Elektronica en Informatiesystemen Voorzitter: prof. dr. ir. Rik Van de Walle Faculteit Ingenieurswetenschappen en Architectuur Academiejaar 2014-2015
BM-WII: Het blokkeren van malware door isolatie van de Windows interface Don Giot
Promotor: prof. dr. ir. Bjorn De Sutter Begeleiders: ir. Stijn Volckaert, dr. Bart Coppens, ir. Bert Abrath Masterproef ingediend tot het behalen van de academische graad van Master of Science in de ingenieurswetenschappen: computerwetenschappen
Vakgroep Elektronica en Informatiesystemen Voorzitter: prof. dr. ir. Rik Van de Walle Faculteit Ingenieurswetenschappen en Architectuur Academiejaar 2014-2015
Toelating tot bruikleen De auteur geeft de toelating deze masterproef voor consultatie beschikbaar te stellen en delen van de masterproef te kopi¨eren voor persoonlijk gebruik. Elk ander gebruik valt onder de bepalingen van het auteursrecht, in het bijzonder met betrekking tot de verplichting de bron uitdrukkelijk te vermelden bij het aanhalen van resultaten uit deze masterproef. The author gives permission to make this master dissertation available for consultation and to copy parts of this master dissertation for personal use. In the case of any other use, the copyright terms have to be respected, in particular with regard to the obligation to state expressly the source when quoting results from this master dissertation.
17 juni 2015 Don Giot
iv
Dankwoord Allereerst wil ik graag mijn promotor bedanken, prof. dr. ir. Bjorn De Sutter voor zijn begeleiding doorheen het jaar. Ook wil ik hem bedanken voor het opstarten van de Werkgroep Ethical Hacking, waar ik mijn interesse voor het onderwerp van software beveiliging kon botvieren. Daarnaast bedank ik graag mijn begeleiders ir. Stijn Volckaert en ir. Bert Abrath voor hun hulp bij het tot stand brengen van deze thesis en het delen van hun kennis over de werking van het Windows besturingssysteem. Ik leerde dit jaar enorm veel bij over het hacken, aanpassen en doen crashen van Windows applicaties, waar ik zeer dankbaar voor ben. Vervolgens wil ik graag iedereen uit de thesisruimte bedanken voor de aangename werksfeer die er altijd te vinden was. Ik bedank hiervoor graag Jens, Ronald, Jonas en Bart, en natuurlijk ook Bert en Stijn, beide reeds vernoemd. Ook mijn twee collega’s op wie ik altijd kon rekenen om mijn onzekerheden mee te delen, alsook mijn middageten, verdienen een plaats in dit dankwoord: Joris en Thomas. Ik bedank hen graag voor de steun die ze me boden. Ik bedank ook graag mijn ouders en broer, die niet meteen een directe invloed hadden op deze thesis, maar dankzij wie mijn mentale gezondheid geen blijvende schade opliep bij het werken aan deze scriptie. Als laatste wil ik nog ´e´en iemand vermelden die me enorm veel steun bood tijdens dit avontuur. Met liefde, Hannah.
v
Het blokkeren van malware door isolatie van de Windows interface door Don Giot Scriptie ingediend tot het behalen van de academische graad van Master of Science in de ingenieurswetenschappen: Computerwetenschappen Promotor: Prof. Dr. Ir. B. De Sutter Scriptiebegeleiders: Ir. B. Abrath, Ir. S. Volckaert Vakgroep Elektronica en Informatiesystemen Voorzitter: Prof. Dr. Ir. R. Van de Walle Faculteit Ingenieurswetenschappen Universiteit Gent Academiejaar 2014–2015
Samenvatting Huidige verdedigingstechnieken maken aanvallen via code-injectie zo goed als onmogelijk. Om deze technieken te omzeilen, wordt de code aanwezig in het proces, en de functionaliteit aangeboden door het besturingssysteem zoveel mogelijk hergebruikt. Het Windows systeem stelt deze functionaliteit ter beschikking via dynamisch gelinkte bibliotheken. Een aanvaller gaat informatielekken binnen het systeem gebruiken om de locatie van deze bibliotheken op te sporen en ze te gebruiken in zijn aanval. In deze scriptie worden informatielekken op het Windows besturingssysteem onderzocht op hoe een aanvaller ze kan uitbuiten. De focus ligt op systeembibliotheken die altijd aanwezig zijn binnen een procesruimte. We stellen in deze scriptie een tool voor die deze informatielekken moet dichten. De tool gaat informatie verwijderen of encrypteren, naargelang of de applicatie de informatie nodig heeft om een correcte werking te garanderen. De beschermingen die worden ge¨ımplementeerd zijn werkzaam tijdens de initialisatie van proces en thread, en worden gebundeld in een DLL. Trefwoorden: Softwarebeveiliging, Informatielekken, Windows
vi
vii
Defeating Malware Through Windows Interface Isolation Don Giot Supervisor(s): Prof. Bjorn De Sutter, Ir. Bert Abrath, Ir. Stijn Volckaert Abstract— The current trend in defending against software exploitation is preventing code-reuse by diversifying the program or guarding its control flow. However, a category of software vulnerabilities that can be used to circumvent a wide range of defences is information leakage. Through an information leak, an attacker can expose the memory layout of a program, which in turn can lead to the discovery of exploitable weaknesses. For the Windows operating system, locating the DLL’s exported by the system can provide an attacker with a lot of functionality to build an attack. In this paper we examined the address space of a Windows process and searched for information leaks that make it trivial for an attacker to find these DLL’s. Next we propose a tool that patches the address space during the initialisation of the process or its threads to remove or encrypt the found leaks. Only in a few cases will the tool influence the performance during the execution of an application. There is also a noticeable performance overhead for the initialisation of a thread. Keywords—Software Security, Windows, Information Leakage
I. I NTRODUCTION Since our society is based on a software infrastructure where the value of collecting and processing data grows constantly, it becomes more interesting for attackers to exploit this infrastructure. Not surprisingly, investing in software security also becomes more important to protect against and prevent software vulnerabilities. A large portion of these vulnerabilities originate from the choice of which programming language was used to build the application. Developing in low-level languages like C and C++ is prone to produce memory corruption bugs [1][2], due to the lack of type-checking, bounds-checking or too little attention for correct memory management. An attacker will often inject a piece of code into the application memory. This piece of code, which we call shellcode [3], is the first step towards altering the behaviour of the program in the attackers favour. The shellcode has two tasks to fulfil in order to be successful. Firstly it has to divert the execution flow of the application, which can be achieved by for example overwriting a return address on the stack [4]. Secondly, it has to guide the execution towards the injected code, by for example overwriting the return address with the address of the start of that piece of code. A lot of defences have been proposed in the last 30 years to defend against these kinds of attacks but only a few have been adopted on a wide scale. For example, Windows implements
Software DEP [5], which is comparable to W⊕X, and makes it impossible for an attacker to execute code that was injected into memory. Stack Cookies [6] are used to protect addresses on the stack from overflow attacks, which makes it harder to divert the control flow. Address Space Layout Randomisation [7] [8] as the name says, randomizes the address space and made it a lot harder for attackers to find important memory structures. However, even with all these defences in place, several categories of attacks are still effective. Return Oriented Programming is a technique that relies on the reuse of code that is already present in the process address space [9][10]. Information leaks provide the attackers with the means to investigate the memory layout and control flow of an application, and to find code that can be reused for their attack. We narrow this down to reach the subject of this paper. Windows provides its system functionality by offering a set of DLL’s (Dynamic-link libraries)[8]. An attacker can be sure that these libraries are present in the address space of any application running on the Windows operation system. Thanks to ASLR however, an attacker will not know the location of these libraries beforehand. By using information leaks present within the Windows operating system, he can derive this location, and will have all the systems functionality available for his attack. In this paper we identify these information leaks and propose a tool to remove or encrypt them in such a way that it only minimally influences the application. II. I NFORMATION L EAKS We identify three leaks that can be used by an attacker to discover the location of the system libraries in memory. A first valuable source of information for an attacker is the process environment block (PEB). This is a data structure within the address space of a process that is mainly used internally by the Windows operating system [8][11]. It holds a range of data structures that are relevant across the whole process. The data structure that we focus on in this paper is the loaded module database, which is a structure within the PEB that holds the location of all the libraries that are present in the address space of the process. Each library that the system loads in the address space
of the process gets registered in this database. DLL’s get loaded into the address space when the process is being initialised and the dependencies of the application with these libraries are being resolved (an running application can also load a DLL with the loadlibrary function within the system library kernel32.dll). The location of the PEB can always be retrieved quite easily, which means an attacker can use this data structure to derive the location of the libraries he needs. A second leak is present within the exception handling structures of the operating system. Windows implements a mechanism called structured exception handling (SEH) [12]. This mechanism uses a linked list (the SEH-chain) of exception handling records (EH-records). Each record holds a reference to the next record in the chain and a reference to an exception handler. When the application raises an exception, the exception dispatcher walks the SEH-chain in search of a handler that is capable of handling the exception. Each thread in the process has its own chain and is built during the initialisation for that thread. The last EH-records in the SEH-chain, or in other words, the first EH-records that are added to the chain are always the same ones for each thread, containing default exception handlers provided by the system. These default handlers are implemented by the system which means the references in the last records point towards system libraries. For example, the sentinel element in the chain is the UnhandledExceptionFilter from ntdll.dll [8]. The SEH-chain is easily accessible at any given time during the execution of the thread. This means an attacker could walk through the SEH-chain to find the sentinel in the chain, and use its reference to UnhandledExceptionFilter to find the location of the ntdll.dll library. The third leak originates from the initialisation procedure of a thread within a process. Each thread is given its own stack. The problem here is that the initialisation routines will have used this stack quite heavily before any application code is actually executed. Firstly, this results in a stack that is filled with remnants of stack frames from the initialisation. To put it differently, the memory above the stack pointer doesn’t contain random garbage, but contains possible addresses (e.g. return addresses) to system libraries that were involved in the initialisation of the thread. An attacker could scan the stack in search of these addresses to locate the libraries. Secondly, this means that the first call to the application code is not the first call of the call stack. The bottom of the call stack still contains active stack frames which are frames to which the system still can return to, belonging to the initialisation routines. In this case, an attacker could walk through all the stack frames by using the frame pointer and find the frames created by initialisation functions implemented within the system libraries.
III. T HE D EFENCE DLL We implemented a DLL that patches two of the discussed leaks, namely the PEB and the vulnerable stack. For the SEHchain leak, we stumbled across a compatibility problem with already implemented guards for the SEH-chain. Since the EHrecords within the chain are also stored on the stack, an attacker could use them to subvert the control flow of the application. He could overwrite the reference to an exception handler and then try and trigger an exception. When the exception dispatcher passes control to the corrupt exception handler, the attack would be successful. Windows implemented different types of integrity guards to make sure the chain wasn’t overwritten [13][14], which made developing a patch for it quite difficult. The DLL we developed patches the initialisation procedure for both the process and its threads. The PEB-patch simply removes the registration from the loaded module database. However, since we didn’t know the impact this would have on the stability of a running process because the registrations might be used internally by the operating system, we tested this patch on several practical applications to find a configuration of registrations that could be removed without disrupting them. We found that the registration for kernel32.dll can be removed entirely, but the registration for ntdll.dll could only be removed partially or else the process would execute in an endless loop. This is not illogical since the loaded module database is implemented as a doubly-linked list, which would indicate that the process loops through this list forever in search of the registration of ntdll.dll. Our DLL applies the second patch (for the stack leak) by modifying the function BaseThreadInitThunk [8] which is the function in the initialisation routine that is responsible for calling the thread entry point (which is the start of the application code). Instead of calling the entry point, the function will now call a function within our DLL that applies two defences to protect the stack after which our function calls the entry point for the application. The first defence will encrypt all the active frames on the call stack at that point, or in other words it encrypts all the frames belonging to the initialisation code. The reason that we can’t just destroy these frames is that the thread needs them to exit cleanly, and not returning properly to one of those stack frames might lead to unstable behaviour. The DLL also makes sure the decryption is done in time by using the vectored exception handling mechanism implemented by Windows [15]. This is an exception handling mechanism that supersedes the SEH mechanism and registers process wide exception handlers instead of thread specific exception handlers. We engineer the encryption in such a way that returning to an encrypted stack frame results in an exception. The handler registered by our DLL will identify the exception as being one resulting from our encryp-
tion and will decrypt the stack frame and return control to the application. The second defence deals with the remnant stack frames that are present on the stack when the application code starts. Just before calling the entry point, this defence will simply clean the memory above the stack. By finding out the stack limit, the defence simply starts pushing zeroes until the stack limit is reached, after which it restores the stack pointer. It’s important to note that we consciously adapted the last function of the initialisation since cleaning the stack any earlier would miss some of the remnant stack frames of the initialisation at the thread entry point. IV. E VALUATION 300
250
Execution time(ms)
200
150
100
50
0
No Patch
Stack Patch
PEB Patch
Stack + PEB
Fig. 1. Measuring the initialisation overhead for the different configurations.
We evaluated different configurations of our patches by applying them to a browser (mozilla firefox) and running javascript benchmarks (Kraken, Octane and SunSpider) with these browsers. The three tested configurations were applying only the stack patch, only the PEB patch or applying both at the same time. We compared the results with the benchmarks run on an unpatched browser and found that there is almost no performance overhead when applying any of the tested configurations. However when we only measured the initialisation overhead (as seen in figure 1), we could observe a performance overhead of more then 33% which means our defence DLL performs better for applications with minimal threading. R EFERENCES [1] Richard Fateman. Software fault prevention by language choice: Why c is not my favorite language. Advances in Computers, 56:167–188, 2002. [2] Yves Younan. C and c++: vulnerabilities, exploits and countermeasures. Security Research Group. Retrieved from http://secappdev. org/handouts/2012/Yves% 20Younan/C% 20and% 20C++% 20vulnerabilit ies. pdf, 2013. [3] Jack Koziol, David Litchfield, Dave Aitel, Chris Anley, Sinan Eren, Neel Mehta, and Riley Hassell. The shellcoder’s handbook. Wiley Indianapolis, 2004. [4] Aleph One. Smashing the stack for fun and profit. Phrack, 49, 1996. [5] J. N. Rob Enderle. The new approach to windows security, 2004. [6] Crispin Cowan, Steve Beattie, Ryan Finnin Day, Calton Pu, Perry Wagle, and Erik Walthinsen. Protecting systems from stack smashing attacks with stackguard. In Linux Expo. Citeseer, 1999.
[7] PaX Team. Pax address space layout randomization (aslr), 2003. [8] Mark Russinovich, David Solomon, and Alex Ionescu. Windows internals. Pearson Education, 2012. [9] Ryan Roemer, Erik Buchanan, Hovav Shacham, and Stefan Savage. Returnoriented programming: Systems, languages, and applications. ACM Transactions on Information and System Security (TISSEC), 15(1):2, 2012. [10] Nergal. The advanced return-into-lib(c) exploits, a pax case study, 2001. [11] Matt Pietrek. Under the hood: Reading another processes environment. August, 2004. MSDN Magazine. [12] Matt Pietrek. Under the hood: A crash course on the depths of win32 structured exception handling. http://www.microsoft.com/msj/0197/exception/exception.aspx. January, 1997. [13] Matt Miller. Preventing the exploitation of seh overwrites. Uninformed Journal, 5, 2006. [14] M Miller. Preventing the exploitation of structured exception handler (seh) overwrites with sehop. Online]. Dispon´ıvel em: http://blogs. technet. com/srd/archive/2009/02/02/preventingthe exploitationofsehover´ writeswithsehop. aspx.[Ultimo acesso em: 29 Nov., 2009], 2009. [15] Matt Pietrek. Under the hood: New vectored exception handling in windows xp. MSDN Magazine, 2001.
Inhoudsopgave Overzicht
vi
1 Inleiding 1.1 Probleemstelling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2 Doelstellingen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.3 Overzicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Technische Achtergrond 2.1 Evolutie van aanvallen en verdedigingen . . . . . . . 2.1.1 Stack Cookie . . . . . . . . . . . . . . . . . . 2.1.2 DEP: Data Execution Prevention . . . . . . . 2.1.3 ROP: Return Oriented Programming . . . . . 2.1.4 ASLR: Address Space Layout Randomisation 2.1.5 Besluit: Informatielekken . . . . . . . . . . . 2.2 Geavanceerde Verdedigingen . . . . . . . . . . . . . . 2.2.1 CFI: Control-Flow Integrity . . . . . . . . . . 2.2.2 Diversiteit . . . . . . . . . . . . . . . . . . . . 2.3 Basisconcepten Windows . . . . . . . . . . . . . . . . 2.3.1 DLL: Dynamic-Link Library . . . . . . . . . 2.3.2 PE-formaat . . . . . . . . . . . . . . . . . . . 2.3.3 Een Proces in Windows . . . . . . . . . . . . 2.3.4 Excepties . . . . . . . . . . . . . . . . . . . . 2.4 Besluit . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Ontwerp van de BM-WII Tool 3.1 Informatielekken in Windows . . . . 3.2 Vereisten . . . . . . . . . . . . . . . 3.3 Het dichten van de informatielekken 3.3.1 Het PEB . . . . . . . . . . . 3.3.2 De SEH-ketting . . . . . . . .
. . . . .
xi
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . .
. . . . .
. . . . . . . . . . . . . . .
. . . . .
1 1 2 2
. . . . . . . . . . . . . . .
4 4 5 5 5 6 6 7 7 7 8 8 10 13 20 24
. . . . .
25 25 27 28 28 29
3.4
3.3.3 De Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ontwerp van de BM-WII Tool . . . . . . . . . . . . . . . . . . . . . . . . . . .
4 Implementatie BM-WII Tool 4.1 PEB-Patch . . . . . . . . . 4.2 Stack Patch . . . . . . . . . 4.3 SEH-Patch . . . . . . . . . 4.4 BM-WII DLL initialisatie .
30 31
. . . .
33 33 34 37 38
5 Evaluatie 5.1 Configuratie van de PEB patch . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2 Invloed op de Uitvoeringstijd . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3 Invloed op de intialisatietijd . . . . . . . . . . . . . . . . . . . . . . . . . . . .
40 40 41 42
6 Conclusie
44
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
xii
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
Lijst van figuren 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 3.1 3.2
Verschillende secties binnen het PE-formaat. . . . . . . . . . . . . . . . . . . De layout van de adresruimte van een proces. . . . . . . . . . . . . . . . . . . Een visualisatie van de stappen die het systeem doorloopt bij het aanmaken van een nieuw proces of thread. . . . . . . . . . . . . . . . . . . . . . . . . . . Het controleverloop van de RtlUserThreadStart functie. . . . . . . . . . . . . Het PEB en de loader database. . . . . . . . . . . . . . . . . . . . . . . . . . Een element uit de loader database, een loader data entry. . . . . . . . . . . . Een visualisatie van het thread environment block. . . . . . . . . . . . . . . . Het begin van de SEH-ketting wordt steeds bijgehouden door het TEB. . . .
11 14 15 16 17 18 19 21
Een visualisatie van informatielekken op de thread stack. . . . . . . . . . . . Dit is een visualisatie van waar de beschermingen worden toegepast in de initialisatieprocedure. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
27
4.1
De patches toegepast binnen RtlUserThreadStart. . . . . . . . . . . . . . . . .
35
5.1 5.2 5.3 5.4 5.5
Eerste deel van de resultaten voor de Octane benchmark. Tweede deel van de resultaten voor de Octane benchmark. Resultaten voor de Kraken benchmark. . . . . . . . . . . . Resultaten voor de Sunspider benchmark. . . . . . . . . . Meetresultaten van de thread-creatietest. . . . . . . . . .
41 41 42 43 43
xiii
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
32
Hoofdstuk 1
Inleiding Software is vandaag niet meer weg te denken uit het dagelijkse leven. Onze maatschappij is gebaseerd op een enorme software-infrastructuur, waarbij het verzamelen en verwerken van data alsmaar waardevoller wordt. Het nadeel hiervan is dat kwetsbaarheden in software ook alsmaar waardevoller worden om uit te buiten. Die kwetsbaarheden komen voornamelijk voort uit fouten in de programmatuur van de software, die niet altijd te wijten zijn aan de programmeurs zelf, maar aan de keuze van de programmeeromgeving. Zo komen veel kwetsbaarheden voor bij software die werd ontwikkeld in low-level programmeertalen zoals C/C++ [1] [2] [3]. De reden hiervoor is het gebrek aan controle op het correct gebruik van het geheugen, bijvoorbeeld door type-checking of bounds-checking. Door corruptie van het geheugen zal een aanvaller het gedrag van een applicatie kunnen wijzigen. We zouden deze uitbuitbare kwetsbaarheden kunnen ontwijken door deze low-level talen niet meer te gebruiken, maar dat is onrealistisch. Ten eerste vergt dit in veel gevallen een dure investering. De reeds bestaande legacy code, kennis en expertise zit vaak diepgeworteld in het gebruik van deze talen. Ten tweede kan men vaak niet anders dan deze talen te gebruiken voor software waar prestatie cruciaal is, bijvoorbeeld bij het ontwikkelen van besturingssystemen. Deze talen laten immers veel optimalisatie toe.
1.1
Probleemstelling
Een typische aanval op een applicatie begint met het injecteren van een klein, positieonafhankelijk codefragment, dat we shellcode noemen, door het uitbuiten van bijvoorbeeld een buffer-overflow [4]. De naam shellcode is ontstaan omdat het oorspronkelijke doel bij deze aanvallen het verkrijgen van een shell was [5]. Nu zal deze shellcode als doel het opzetten en inladen van meer geavanceerde stappen van een aanval hebben. Omdat shellcode zo klein mogelijk moet blijven, zal het veel gebruik willen maken van de functionaliteit die het systeem aanbiedt. Windows heeft een veranderlijke kernelinterface, wat inhoudt dat over verschillende versies van Windows de systeemoproepen veranderen. Om een schaalbare en stabiele aanval 1
op te zetten moet shellcode gebruik maken van de systeembibliotheken die immers wel een onveranderlijke interface hebben over de verschillende versies van het systeem. Omdat de locatie van deze systeembibliotheken door Address Space Layout Randomisation (ASLR [6]) na elke reboot van het systeem verandert, moet shellcode eerst naar de bibliotheken opzoek gaan. Daarna moet het de benodigde functies in de bibliotheken zien terug te vinden alvorens het een aanval kan uitvoeren. Het opsporen van die bibliotheken in het geheugen blijkt nog steeds mogelijk omdat Windows veel meta-informatie ter beschikking stelt. Deze informatie is aanwezig in elk lopend proces en wordt gebruikt voor de algemene werking van het besturingssysteem. Zo zal bijvoorbeeld de Windows loader die de bibliotheken in het procesgeheugen inlaadt bij de opstart van een proces deze meta-informatie gebruiken. Ook shellcode kan van deze meta-informatie gebruik maken om de systeembibliotheken op een deterministische wijze terug te vinden.
1.2
Doelstellingen
In de eerste plaats willen we voor een aanval bruikbare meta-informatie binnen het Windows besturingssysteem identificeren. Dit zijn onbeschermde aanvalsvectoren waarvan aanvallers kunnen gebruik maken. Meer concreet gaan we op zoek naar manieren waarop een aanvaller de systeembibliotheken kan opsporen binnen de adresruimte van een proces. Ten tweede bouwen we een tool die een bescherming toepast op de gevonden aanvalsvectoren. We ontwikkelen een module die door een applicatie bij opstart kan worden ingeladen en die bij proces- en threadinitialisatie de verdedigingen toepast. Deze beveiligingen gaan van het verwijderen van meta-informatie, tot het encrypteren ervan. Hierbij onderzoeken we hoever we kunnen gaan bij het beschermen van deze meta-informatie.
1.3
Overzicht
We geven een overzicht van de hoofdstukken van deze scriptie: • In hoofdstuk 2 bespreken we eerst enkele basisconcepten van het Windows besturingssysteem die we in het vervolg van de scriptie gebruiken. Daarna geven we een kort historisch overzicht over de vooruitgang bij verdedigingen tegen software kwetsbaarheden om deze scriptie beter te schetsen in de context van softwarebeveiliging. • In hoofdstuk 3 identificeren we informatielekken binnen het Windows besturingssysteem en stellen we hier oplossingen voor. We bespreken aan welke vereisten deze oplossingen moeten voldoen en waar de uitdagingen liggen. • In hoofdstuk 4 bespreken we de implementatie van de voorgestelde oplossingen. 2
• In hoofdstuk 5 evalueren we de ge¨ımplementeerde tool. Dit doen we door de impact van de toegepaste technieken op de uitvoeringstijd te onderzoeken aan de hand van Javascript benchmarks, en door de prestatieoverhead te meten bij het initialiseren van een thread. • In hoofdstuk 6 geven we een kort besluit van deze scriptie.
3
Hoofdstuk 2
Technische Achtergrond Alvorens we de zoektocht naar een oplossing kunnen beginnen moeten we het landschap verkennen. In het eerste gedeelte van dit hoofdstuk geven we een korte schets van de evolutie van het kat-en-muisspel tussen de aanvallen en verdedigingen op het Windows besturingssysteem. Daarna bespreken we technische concepten en mechanismen van Windows, waarbij de focus ligt op hoe een aanvaller deze mechanismen kan gebruiken om informatie te vergaren over de werking van de applicatie en de interne structuur van het proces.
2.1
Evolutie van aanvallen en verdedigingen
In deze sectie geven we een historisch overzicht van hoe een aanvaller een applicatie kan aanvallen, en hoe daartegen verdedigd wordt. De criteria die we gebruiken om deze verdedigingen te beoordelen zijn enerzijds prestatie. De prestatieoverhead van een beveiligingstechniek mag niet zeer hoog worden. Hoe groter de overhead wordt bij het toepassen van een verdediging, hoe kleiner de kans dat men ze in de praktijk zal toepassen. Anderzijds moet de techniek zo weinig mogelijk valse positieven vertonen. We stellen dat deze technieken de uitvoering van een applicatie niet mogen stilleggen wanneer geen ongeoorloofd gedrag zich heeft voorgedaan. Vooral wanneer het gaat over self-modifying code of Just in Time (JIT) compilatie is dit een zeer restrictief criterium. Als een aanvaller een kwetsbaarheid ontdekt in een applicatie kan hij deze proberen uitbuiten. Met uitbuiten bedoelen we dat een aanvaller het gedrag van de applicatie wijzigt. Hiervoor moeten concreet twee dingen gebeuren: De aanvaller moet het controleverloop van de applicatie wijzigen en de uitvoering afleiden naar een door de aanvaller ge¨ınjecteerd codefragment. Het wijzigen van het controleverloop gebeurt vaak door het overschrijven van een kwetsbare functiepointer of returnadres. Een voorbeeld van typisch uitbuitbare kwetsbaarheden zijn de buffer-overflows [4]. Het kwetsbare adres moet een aanvaller in de tweede stap doen wijzen naar de ge¨ınjecteerde code, de shellcode. De volgende twee subsecties bespreken 4
verdedigingen die elk ´e´en van deze twee stappen bemoeilijken.
2.1.1
Stack Cookie
Een Stack Cookie is een verdediging tegen de buffer overflow-aanvallen. Bij deze verdedigingstechniek zet het systeem een sleutelwaarde op stack net na elk returnadres [7]. Deze waarde noemen we ook wel de stack canary. Wanneer de aanvaller nu een buffer laat overlopen, overschrijft hij niet alleen het terugkeeradres maar ook de stack cookie, en kunnen we een corruptie van de stack detecteren. Een vergelijkbare verdediging, die ook de integriteit van de terugkeeradressen probeert te vrijwaren is de StackShield techniek. Deze techniek zal terugkeeradressen naar een veilige plaats in het geheugen (de shadow stack [8]) kopi¨eren, en toetst bij het terug keren naar een adres, dat adres met de genomen kopie. Wanneer de adressen niet overeenkomen, detecteren we dit als een corruptie. De meeste huidige compilers ondersteunen deze technieken (voor de Microsoft VS compiler worden stack cookies toegevoegd met de /GS vlag [9]). De technieken hebben een gemiddelde overhead van minder dan 1%, maar ook zij kennen zwakheden. Zo verdedigen ze immers enkel tegen het overschrijven van het terugkeeradres, terwijl er andere adressen of datastructuren op de stack staan die een aanvaller kan overschrijven. De literatuur beschrijft manieren om deze technieken te omzeilen [10] [11].
2.1.2
DEP: Data Execution Prevention
Data Execution Prevention [12](ook wel Hardware DEP of write xor execute(W⊕X) genaamd) is de techniek waarbij de processor pagina’s in het geheugen als ofwel uitvoerbaar ofwel schrijfbaar markeert, zodat een pagina waarop geschreven werd, niet kan worden uitgevoerd. Dit gebeurt door het al dan niet zetten van een NX-bit (No-eXecute bit) per pagina. Code die aanwezig is op pagina’s die niet expliciet als uitvoerbaar zijn gemarkeerd, mag het systeem niet uitvoeren. Wanneer een programma dit toch probeert, schort de processor de uitvoering op. Aangezien ook de stack en heap gemarkeerd zijn als schrijfbaar is het voor een aanvaller bijna onmogelijk om expliciet code te injecteren, en kunnen er geen instructies meer op de stack of heap worden uitgevoerd. Aangezien de onderliggende hardware (Intel en AMD processoren bieden ondersteuning voor DEP [13]) deze techniek ondersteunt, is het mogelijk ze transparant toe te passen en is er nauwelijks een overhead. Hoewel code-injectie hierdoor zo goed als onmogelijk is, biedt deze techniek geen weerstand tegen het hergebruik van reeds bestaande code in het geheugen.
2.1.3
ROP: Return Oriented Programming
Een categorie van aanvallen die deze twee besproken beveiligingstechnieken kan omzeilen is Return Oriented Programming (ROP). Een aanvaller gaat voor deze soort aanval op zoek naar herbruikbare stukken code in het geheugen. Dit kan gaan over volledige functies, maar 5
kan ook gaan over een willekeurige set van instructies (gadgets) die eindigen in een return. Deze functies en gadgets worden dan via de return instructies op de stack aan elkaar geknoopt [14] (Dit is ook waar de term Return Oriented Programming vandaan komt). Wanneer de aanvaller enkel gebruik maakt van functies spreken we van return-to-libc [15] aanvallen, wat een zwakkere subset is van dit soort aanvallen. Om de gadgets terug te vinden moet een aanvaller beschikken over de adressen naar het uitvoerbaar bestand of de bibliotheken binnen de adresruimte van de aangevallen applicatie. De volgende besproken verdediging probeert het achterhalen van deze adressen te bemoeilijken.
2.1.4
ASLR: Address Space Layout Randomisation
Address Space Layout Randomisation (ASLR) is een techniek waarbij de virtuele adresruimte van een proces wordt gerandomiseerd [6]. Windows bepaalt bij het opstarten van het systeem steeds een nieuwe randomisatie voor de adressen waarop de loader de verschillende systeembibliotheken moet inladen, alsook voor de locatie van het uitvoerbare bestand zelf. Elk proces krijgt voor het uitvoerbare bestand en de systeembibliotheken dezelfde configuratie voor ASLR, zolang het systeem niet opnieuw is opgestart. Voor de andere bibliotheken en geheugenstructuren van een proces, zoals de heap en de stack genereert het systeem een randomisatie voor elke nieuwe instantie van dat proces [16] [17]. ASLR maakt het voor aanvallers onmogelijk om hard-gecodeerde adressen te gebruiken. Ook wordt het moeilijk om op voorhand de locatie van belangrijke datastructuren te kennen zoals de heap en de stack. Desondanks kent deze techniek zwakheden [18]. Zo is op 32-bit systemen de entropie van de randomisatie zeer klein, waardoor een brute-force aanval nog steeds haalbaar blijft voor het vinden van belangrijke structuren en code [19]. ASLR is ook vatbaar voor information leakage. Als de aanvaller een pointer bemachtigt kan hij deze gebruiken als aanknopingspunt om de geheugenstructuren en bibliotheken terug te vinden en kan hij zo heel de verdediging omzeilen.
2.1.5
Besluit: Informatielekken
Een informatielek is een kwetsbaarheid waaruit een aanvaller de interne geheugenstructuur van een proces kan afleiden. Met de informatie vergaart uit zo een lek kan een aanvaller de coderandomisatie omzeilen en opnieuw op een deterministische manier gadgets vinden om een aanval mee op te bouwen. Een informatielek manifesteert zich als een pointer van een functie of een terugkeeradres. De aanvaller kijkt naar waar deze pointer wijst en vindt zo de pagina’s in het geheugen die code bevatten. Een aanvaller moet dus op zoek gaan naar deze informatielekken om de huidig ge¨ımplementeerde verdedigingen te verslaan. Meer specifiek voor het verhaal in deze scriptie zal een aanvaller op zoek gaan naar meta-informatie binnen de adresruimte van een Windows proces om op deterministische wijze de locatie van 6
de systeembibliotheken terug te vinden. Eens hij deze gevonden heeft, beschikt hij opnieuw over de volledige functionaliteit van het systeem waarmee hij een aanval kan opbouwen.
2.2
Geavanceerde Verdedigingen
In deze sectie bespreken we een kleine subset van meer geavanceerde verdedigingen. Deze technieken leggen de focus op het vermijden van hergebruik van code, waar een ROP-aanval is op gebaseerd.
2.2.1
CFI: Control-Flow Integrity
Waar de vorige secties vooral de code en data integriteit bewaken, is er ook verkend in hoeverre we het controleverloop van een applicatie kunnen bewaken. Immers, wanneer een aanvaller code naar zijn hand probeert te zetten, probeert hij de uitvoering van het doelwit te doen afwijken van het originele controleverloop. Hiervoor werd Control-Flow Integrity [20] voorgesteld, waarbij er een statische en dynamische versie bestaat. In het algemeen begint CFI met het opstellen van een controleverloopgraaf van het te beschermen programma. Aan de hand van de opgestelde graaf worden de sprongen in het programma ge¨ınstrumenteerd met controles. Wanneer het programma een sprong neemt, wordt er gecontroleerd of deze overeenkomt met de opgestelde graaf. De statische variant kan enkel de broncode gebruiken voor het opstellen van de graaf, en zal in de meeste gevallen minder goed presteren dan de dynamische variant, maar aan een lagere overhead. CFI biedt een zeer goede bescherming tegen de ROP aanvallen, aangezien deze hard steunen op het aanpassen van terugkeeradressen en de callstack niet zullen respecteren wat meteen zal worden opgevangen. De bescherming komt wel met de kost van een significante overhead (een gemiddelde van 8% op de SPEC2000 benchmark [20]). Er kan voor gekozen worden een subset van de sprongen te controleren, om de overhead te verminderen maar er werd aangetoond dat deze grovere granulariteit opnieuw kwetsbaar is voor ROP aanvallen. En hoewel een fijnere granulariteit sterk presteert tegen low-level ROP aanvallen, werd ook aangetoond dat aanvallers binnen de grenzen van het controleverloop kunnen blijven door het gebruik het object model van C++(COOP: Counterfeit Object-oriented programming [21]), en deze techniek nog steeds kan worden omzeild. “This is no silver bullet”
2.2.2
Diversiteit
Een andere invalshoek bij het bevechten van het hergebruik van code is programmadiversiteit [22]. Omdat vandaag de uitvoerbare bestanden van applicaties op massale schaal kunnen worden verdeeld en deze bestanden zonder diversificatie identiek zijn, zal een aanvaller een succesvolle aanval op diezelfde massale schaal kunnen inzetten. Wanneer diversiteit op een 7
uitvoerbaar bestand wordt toegepast, zal een succesvolle aanval niet kunnen worden overgedragen naar andere instanties van dit bestand. Als het bijvoorbeeld gaat over een ROP-aanval, zal de aanvaller niet kunnen rekenen op de aanwezigheid van dezelfde gadgets op andere instanties. Wanneer enkel het binair bestand zelf wordt aangepast en deze volledig isomorf is met andere instanties van dat bestand spreken we over statische diversiteit. Tijdens de uitvoering ervan zullen er geen artefacten van de diversiteit optreden. Bij dynamische diversiteit zal er ook randomisatie van de controle transfers worden toegepast, en is er extra aandacht vereist om de indirecte sprongen binnen de uitvoering correct te laten verlopen. Eigenlijk is ASLR ook een vorm van dynamische diversificatie, maar dit is een zeer minieme vorm en het lekken van ´e´en enkele pointer kan genoeg zijn voor het omzeilen van de gehele verdediging. De kost om diversiteit toe te passen, alsook de compatibiliteit met huidige ontwikkelings- en verdelingsprocessen is zeer afhankelijk van het tijdstip wanneer binnen het ontwikkelingsproces de diversiteit wordt toegepast. Hoe vroeger er in de compilatiepipeline wordt gediversifieerd, hoe groter de kost zal zijn [22]. Wanneer er pas gediversifieerd wordt na het compilatieproces zal dit minder kosten, maar zal de diversificatie de kans missen om de rijke kennis van de compiler te gebruiken, zoals bijvoorbeeld profiel- of debuginformatie. Hoewel diversiteit zeer sterk zal zijn in het verbergen van implementatiedetails en in het beschermen van softwareupdates [23], en tegen piraterij zal een fout in de programmalogica aanwezig zijn in alle instanties van het gediversifieerde bestand. Dit kan zelfs meer algemeen gesteld worden, er werd aangetoond dat informatielekken en niet gediversifieerde eigenschappen van een applicatie aanknopingspunten zijn voor aanvallers die kunnen gebruikt worden om deze verdediging te omzeilen [24]. Ook diversiteit is geen panacea, maar zal de complexiteit die nodig is om een aanval succesvol uit te voeren, en schaalbaar in te zetten, verhogen.
2.3
Basisconcepten Windows
Bij de besproken verdedigingen valt eenzelfde thema op. Het probleem wordt verplaatst van het vermijden van hergebruik van code, naar het verwijderen van informatielekken. In deze sectie verkennen we de adresruimte van een proces van het Windows besturingssysteem. We beginnen met het uitdiepen van het dynamisch linken van bibliotheken, en het bestandsformaat van deze bibliotheken. Vervolgens bespreken we een proces in het geheugen, waarbij de focus ligt op de relevante geheugenstructuren binnen de adresruimte. De sectie zal ook dieper ingaan op de initialisatie van zowel een proces als een thread. We eindigen de sectie met exceptieverwerking op Windows. In het volgende hoofdstuk gebruiken we deze concepten om informatielekken binnen de adresruimte van het proces op te sporen.
2.3.1
DLL: Dynamic-Link Library
Een dynamische bibliotheek (DLL of Dynamic-Link Library) is een verzameling van functionaliteit die wordt gebundeld in een binair bestand [25]. Het systeem laadt dit bestand in 8
het geheugen van een applicatie die deze functionaliteit wil gebruiken. Een applicatie of een DLL kan via een importtabel in zijn bestandsdefinitie aangeven van welke andere DLL’s hij wil gebruik maken. Bij het initialiseren van het proces, importeert het systeem deze DLL’s alvorens de applicatie te starten. Een DLL definieert ook een exporttabel die samenvat welke functionaliteit hij exporteert, die dan door andere DLL’s of uitvoerbare bestanden kan worden ge¨ımporteerd. Het dynamisch linken slaat op het feit dat een DLL of uitvoerbaar bestand niet al de gewenste functionaliteit moet bevatten, maar enkel hoeft te beschrijven welke bibliotheken moeten worden ge¨ımporteerd om deze functionaliteit te verkrijgen. Bij de opstart van het proces, linkt het besturingssysteem de juiste bibliotheken aan het uitvoerbaar bestand. Het voordeel hiervan is dat er in het beste geval maar ´e´en fysieke kopie van de DLL in het geheugen, alsook op de harde schijf, aanwezig is. Het inladen van een bibliotheek is dan beperkt tot het mappen van deze fysieke kopie op pagina’s in de virtuele adresruimte van het proces, waardoor applicaties deze bibliotheek eigenlijk delen. De pagina’s waarop deze bibliotheek wordt gemapt, zijn gemarkeerd als copy-on-write [26], wat inhoudt dat zodra de applicatie een aanpassing wil doen aan de gedeelde bibliotheek, het systeem een nieuwe fysieke kopie maakt van de pagina, en de applicatie met deze kopie mag verder werken. Zo voorkomt het besturingssysteem dat een applicatie de grenzen van zijn private virtuele adresruimte overschrijdt en een andere applicatie be¨ınvloedt door de bibliotheekcode of data aan te passen. Naast dit directe geheugenvoordeel, zijn de uitvoerbare bestanden van de applicaties hierdoor ook een stuk kleiner. Veel van de benodigde functionaliteit wordt immers geoutsourced naar de gedeelde bibliotheken. Het updaten van DLL’s gebeurt afzonderlijk van de applicaties, wat het onderhoud van het systeem ook vergemakkelijkt. Een nadeel van deze techniek is dat het werken met verschillende versies van DLL’s kan leiden tot compatibiliteitsproblemen. Hoewel de systeembibliotheken van Windows een interface hebben die zelden verandert, zijn DLL’s in het algemeen niet verplicht bij updates dezelfde interface te ondersteunen. Daarenboven verandert ook het toevoegen van functionaliteit de interface van een DLL. Omdat applicaties zich niet meteen aan deze updates aanpassen, en er op het systeem hierdoor met verschillende versies van een DLL moet gewerkt worden, leidt dit tot een ware DLL hell [27] [28]. Een tweede nadeel is dat dynamisch gelinkte bibliotheken ook beveiligingsrisico’s met zich meebrengen. Zoals uit het tweede deel van dit hoofdstuk zal blijken, is het hergebruik van aanwezige code in de adresruimte van het proces vaak een sleutelelement in het opzetten van een aanval. Aangezien de gedeelde bibliotheken, zeker deze aangeboden door het besturingssysteem zelf, gekend zijn door een aanvaller, kan hij deze al op voorhand reverse engineeren om stukken code te zoeken waarmee hij zijn aanval kan opzetten. Anders gezegd, als de gedeelde bibliotheken gekend terrein zijn voor de aanvaller, dan kan hij over hun functionaliteit beschikken zodra hij ze gevonden heeft in de adresruimte van het systeem.
9
De Windows API [16] is een onderdeel van het Windows-besturingssysteem die systeemfunctionaliteit aanbiedt aan applicaties aan de hand van enkele dynamisch gelinkte bibliotheken met een niet-veranderlijke interface; ´e´en van deze DLL’s is kernel32.dll. Deze DLL exporteert de meeste basissysteemfunctionaliteiten die worden aangeboden door de kernel. Meer concreet is dit functionaliteit met betrekking tot geheugenmanagement, input/output, en proces- en threadcreatie. Een voorbeeld van een functie binnen deze bibliotheek, die we in deze scriptie nog vaak zullen gebruiken, is de LoadLibrary functie. Deze functie maakt het mogelijk om DLL’s tijdens de uitvoering van een applicatie in te laden. Een tweede voorbeeld zijn de CreateProcess en de CreateThread functies die instaan voor het aanmaken van respectievelijk een proces en een thread. Samen met gdi32.dll, die verantwoordelijk is voor communicatie met grafische hardwarecomponenten, en user32.dll die functionaliteit bevat om gebruikersinterfaces mee op te bouwen, vormt kernel32.dll de Windows API voor 32-bit systemen. De meeste applicaties voor het Windows-besturingssysteem zijn gebaseerd op deze API. We vermelden nog een belangrijke DLL, namelijk ntdll.dll, die de Windows Native API exporteert. Deze bibliotheek bevindt zich net boven de kernel, en importeert zelf geen functionaliteit uit andere DLL’s. Deze DLL communiceert met de kernel via systeemoproepen, en exporteert in essentie wrappers voor die systeemoproepen.
2.3.2
PE-formaat
Het PE-formaat, waarbij PE staat voor Portable Executable, is het bestandsformaat voor uitvoerbare Windows bestanden [29]. Zo goed als alle uitvoerbare bestanden en DLL’s op het 32-bit Windows platform gebruiken dit formaat. Uitzondering hierop zijn de 16-bit DLL’s en de VxD, wat staat voor Virtual x Driver, maar deze zijn niet relevant voor deze scriptie. Een PE-bestand (zie Figuur 2.1) begint met een aantal headers die de rest van het bestand, dat is ingedeeld in verschillende secties, beschrijven. De headers duiden aan welke secties aanwezig zijn in het bestand, hoe groot deze zijn en waar ze zich bevinden ten opzichte van de start van het bestand. In plaats van absolute adressen te gebruiken om naar functies of data te verwijzen binnen het PE-bestand, worden de relatieve adressen (ook wel Relative Virtual Address, RVA genoemd) ten opzichte van het basisadres van het bestand gebruikt. Met het basisadres bedoelen we het adres waarop het bestand in het geheugen wordt ingeladen. Nog een belangrijk gegeven waar we in volgende secties naar zullen verwijzen is dat de eerste 64 bytes in een PE-bestand steeds een MS-DOS header vormen waarvan de eerste twee bytes de magic numbers MZ vormen, of 4D 5A in hexadecimale waarden [30] [31]. Door het bestand op te delen in verschillende secties kan de processor voor elke sectie andere toegangsregels toepassen. Zo zal een sectie die data bevat aan de hand van W⊕X [12] (Deze verdediging wordt meer in detail besproken in sectie 2.1.2) gemarkeerd worden als nietuitvoerbaar [32]. Een Windows applicatie bevat typisch negen secties, maar deze zullen we
10
Process memory 0x0
64 KiB aligned
DLL in memory MS-DOS Headers
“MZ”
.text section .idata section .edata section section section
0x7FFFFFFF
Figuur 2.1: Verschillende secties binnen het PE-formaat.
hier niet allemaal bespreken. Voor verdere documentatie over dit bestandsformaat verwijzen we naar de Microsoft PE/COFF documentatie [33]. De exporttabel van een DLL bevindt zich in de .edata-sectie die alle ge¨exporteerde functionaliteit van een DLL beschrijft. De belangrijkste structuur binnen deze sectie is de image export directory. Deze houdt bij hoeveel functies de DLL exporteert, of ze enkel bij ordinaal of ook bij naam de functies exporteert en waar elke functie zich bevindt in de .text-sectie (de sectie die de eigenlijke code van het bestand bevat) . Het ordinaal van een functie is een volgnummer dat aan die functie wordt toegekend. Om een ge¨exporteerde functie terug te vinden in de export directory kunnen drie tabellen gebruikt worden. De eerste is de export name table (ENT), die de namen van de functies die het bestand bij naam exporteert bevat. De tweede tabel is de export ordinal table (EOT) die de volgnummers van de ge¨exporteerde functies bevat. De laatste tabel is de export address table (EAT) die de eigenlijke locaties van de functies bevat, aan de hand van relatieve adressering. De ENT en EOT zijn twee arrays die naast elkaar lopen, waarbij elk element van ENT overeenkomt met een element in de EOT. Het ordinaal dat overeenkomt met een naam is op zijn beurt een index voor de derde tabel, de EAT. Wanneer het ordinaal al gekend is, kan men dit rechtstreeks gebruiken om het EAT te indexeren. Desalniettemin is dit af te raden omdat het toevoegen van een functie alle
11
ordinalen kan wijzigen, en het vinden van een functie via de functienaam een meer robuuste aanpak is, zei het trager vanwege de extra indirectie. Een DLL of uitvoerbaar bestand importeert functionaliteit uit andere DLL’s via de .idatasectie. Er zijn verschillende structuren die deze informatie beschrijven, waarvan de belangrijkste de image import directory is. Deze directory is een array van records die elk ´e´en DLL beschrijven waaruit functies worden ge¨ımporteerd. Een dergelijk record (ook wel image import descriptor ) bevat op zijn beurt twee even lange arrays die paarsgewijs functies uit de DLL voorstellen. In de eerste array, de import name tabel (INT), zullen de namen of de ordinalen van de functies te vinden zijn, en in de tweede array, de import address table (IAT), de adressen van de functies. Deze adressen worden bij het inladen van de DLL ge-update zodat ze naar de juiste locatie binnen de adresruimte wijzen. Wanneer een DLL of het uitvoerbare bestand nu een ge¨ımporteerde functie wil gebruiken raadpleegt ze de IAT om de locatie van die functie te vinden. Door deze indirectie is het mogelijk de adressen van de ge¨ımporteerde functies te overschrijven, wat bekend staat als IAT-hooking [34]. In het algemeen is hooken een manier om het gedrag van applicaties aan te passen of te analyseren door het onderscheppen van functieoproepen tussen verschillende modules [35]. Als we een adres naar een functie binnen het IAT overschrijven met een adres naar een zelfgeschreven functie kunnen we de functionaliteit van een applicatie aanpassen. Het overschrijven van deze adressen kan bijvoorbeeld door het injecteren van een DLL, een techniek die we in een volgende sectie meer in detail bespreken. Elk PE-bestand definieert ook een ingangspunt dat voor een uitvoerbaar bestand het beginpunt is van de applicatie (ook vaak de main functie genoemd). Voor een DLL is dit een functie die door het systeem wordt opgeroepen telkens de DLL in een nieuw proces of thread in- of uitgeladen wordt [36]. Dit laat de ingeladen DLL toe om initialisatiecode uit te voeren, of om gereserveerde systeembronnen terug vrij te geven. Bij de oproep geeft het systeem steeds mee in welke situatie het proces zich bevindt, door ´e´en van de volgende mogelijke vlaggen mee te geven: • DLL PROCESS ATTACH : Deze vlag wordt meegegeven wanneer een DLL voor het eerst wordt ingeladen in het proces. Meer concreet gebeurt dit voor alle bibliotheken die worden ingeladen tijdens de initialisatie van het proces, of die tijdens de uitvoering via de LoadLibrary API worden ingeladen. • DLL THREAD ATTACH : Bij het aanmaken van een nieuwe thread roept het systeem alle ingeladen DLL’s op met deze vlag. Een uitzondering hierop is de eerste, initi¨ele thread. Voor de oproep naar de functie binnen de context van deze thread wordt de DLL PROCESS ATTACH vlag meegegeven. 12
• DLL THREAD DETACH : Wanneer een thread termineert, wordt het ingangspunt van elke DLL opgeroepen met deze vlag. Deze oproep is vooral bedoeld als clean-up, bijvoorbeeld om gereserveerd geheugen terug vrij te geven. • DLL PROCESS DETACH : Wanneer een proces termineert, zullen alle DLL’s nog een laatste keer worden opgeroepen, opnieuw om gebruikte resources terug vrij te geven. Binnen de DLL PROCESS ATTACH context heeft het gedrag van de ingangsfunctie een belangrijke nuance. Het systeem gaat ervan uit dat wanneer een DLL bij de opstart van een proces wordt ingeladen, deze DLL cruciaal is voor het proces. Daarom moet de ingangsfunctie true teruggeven, om aan te duiden dat ze succesvol is uitgevoerd. Bij het optreden van een fout, zal de functie false teruggeven en faalt de initialisatie van het proces, en sluit het systeem het proces af. Wanneer de DLL met LoadLibrary werd ingeladen resulteert een fout enkel in het mislukken van het inladen van de DLL, en blijft het proces intact. Voor een andere context heeft de terugkeerwaarde van de functie geen enkele invloed op het proces, en mogen we de waarde negeren.
2.3.3
Een Proces in Windows
Elk proces krijgt zijn eigen virtuele adresruimte, die op 32-bit systemen 4 GiB groot is. Figuur 2.2 toont een proces in het geheugen en zijn datastructuren die in het vervolg van deze scriptie worden besproken. Om de kritieke data die het besturingssysteem nodig heeft om correct te functioneren te beschermen is deze adresruimte opgedeeld in de gebruikersruimte en de kernelruimte. De gebruikersruimte beslaat de onderste helft van de adressen (0x0 tot 0x7FFFFFFF ) en de kernelruimte beslaat de bovenste helft (0x80000000 tot 0xFFFFFFFF ). Om de toegang naar de kernelruimte die is voorbehouden voor het besturingssysteem te beschermen, gebruikt Windows twee toegangsmodi die door de processor worden aangeboden, namelijk de gebruikersmodus en de kernelmodus. Wanneer de processor zich in de kernelmodus bevindt zal hij toegang hebben tot de gehele adresruimte. In gebruikersmodus, zal hij enkel toegang hebben tot de gebruikersruimte. Als er in deze modus toch geprobeerd wordt toegang te krijgen tot de kernelruimte wordt er een exceptie opgegooid. In sectie 2.3.4 leggen we uit wat dit precies inhoudt, maar voor deze sectie volstaat het om te stellen dat een exceptie een opschorting van de uitvoering is. De manier om verschillende onderdelen binnen dezelfde adresruimte van elkaar te beschermen is segmentering [37]. Een adres voor een gesegmenteerde adresruimte bestaat nu uit een segmentselector, wat we mogen interpreteren als de meest significante bits van het adres binnen een segmentregister, en een offset. De x86 processors ondersteunen zes segment registers (CS, DS, SS, ES, FS en GS). Desondanks maakt Windows in 32-bit modus vooral gebruik van het CS en DS segmentregister voor respectievelijk code en data [38]. De betekenis van de andere segmentregisters is doorheen de jaren veranderd. Op dit moment gebruikt Windows 13
0x00000000 Executable
Heap
Thread1 Stack Thread2 Stack DLL DLL DLL TEB Thread1 TEB Thread2 PEB Kernel Space
0x7FFFFFFF 0x80000000 0xFFFFFFFF
Figuur 2.2: De layout van de adresruimte van een proces.
het FS- en GS-register om te verwijzen naar datastructuren binnen de adresruimte met een speciale betekenis. 32-bit versies van Windows gebruiken het FS-register om het adres naar het Thread Environment Block (TEB) van de thread in uitvoering op te slaan. Op 64-bit versies vervult het GS-register deze rol. Het TEB is een geheugenstructuur die informatie over een bepaalde thread beschrijft. We bespreken deze geheugenstructuur in sectie 2.3.3 in meer detail. Via het FS-register kunnen we verschillende elementen in het TEB adresseren. Zo zal FS:[0x0] wijzen naar het eerste element in het TEB, en FS:[0x4] naar het tweede element in het TEB. We vermelden dit omdat deze manier van indexeren van het TEB doorheen deze scriptie uitvoerig wordt gebruikt. Figuur 2.3 visualiseert de stappen die het systeem onderneemt bij de initialisatie van een proces en een thread. Wanneer een proces wordt opgestart [16], bijvoorbeeld door een oproep naar CreateProcess of CreateProcessAsUser uit kernel32.dll, gaat het systeem eerst het
14
Call to CreateProcess Load exe
PspUserThreadStartup RtlUserThreadStart Create PEB
Create thread and stack
Start thread execution
Create TEB
Load & Init DLL
Load & Init DLL
Create SEH Chain
Call entry point
LdrInitializeThunk
Call to CreateThread
PspUserThreadStartup RtlUserThreadStart Create thread and stack
Start thread execution
Create TEB
Init DLL
Init DLL
Create SEH Chain
Call entry point
LdrInitializeThunk
Figuur 2.3: Een visualisatie van de stappen die het systeem doorloopt bij het aanmaken van een nieuw proces of thread.
uitvoerbare bestand dat als argument werd meegegeven, inladen in de gebruikersruimte. Het besturingssysteem maakt daarna aan de hand van de meegeleverde argumenten aan de oproep en de rechten die het nieuwe proces verkreeg het Process Environment Block (PEB) aan en slaat dit op in de gebruikersruimte van het nieuwe proces. Het PEB is een datastructuur die meta-informatie en configuratiewaarden van het proces bevat en bespreken we in een volgende sectie in meer detail. Als volgende stap, maakt het systeem de heap en de initi¨ele thread voor het proces aan, alsook de stack van deze thread. Op Figuur 2.2, die de layout van de adresruimte van een proces visualiseert, duiden de pijlen aan in welke richting de stack en heap groeien binnen de adresruimte. De kernel start de uitvoering van de eerste thread met een oproep naar PspUserThreadStartup. Deze functie krijgt als argument het adres van het entry point van de thread mee en voert verschillende taken uit, zoals het melden aan een mogelijk aanwezige debugger dat een nieuwe thread werd aangemaakt, alsook het klaarzetten van het Thread Environment Block (TEB). Dit laatste is een structuur die thread-specifieke informatie bevat. Een andere taak die deze routine heeft, is het oproepen van de LdrInitializeThunk wat de functie binnen ntdll.dll is die instaat voor het inladen van alle bibliotheken waarmee het uitvoerbaar bestand gelinkt is. We noemen deze laatste functie ook de loader wat een verzamelnaam is voor het gehele inlaadproces. In elk Windows proces zijn steeds drie systeembibliotheken aanwezig die als eerste worden ingeladen, namelijk ntdll.dll, kernelbase.dll en kernel32.dll. Daarna laadt de loader de DLL’s in waarvan de applicatie nog afhankelijk is. Nog een belangrijk detail is dat elke DLL op een adres wordt ingeladen dat een veelvoud is van 64 KiB, of in andere woorden elke DLL wordt op adressen gealigneerd met 64 KiB ingeladen. Voor elke ingeladen DLL, roept de loader de ingangsfunctie op met het DLL PROCESS ATTACH signaal. Elke DLL krijgt zo de kans om een eerste maal intialisatiecode uit te voeren. Eens alle afhankelijkheden vervuld zijn, roept de PspUserThreadStartup routine nog een 15
laatste functie op alvorens de thread kan starten met de applicatiecode, namelijk RtlUserThreadStart. Deze laatst opgeroepen functie is voor alle gebruikersthreads hetzelfde, ongeacht de manier waarop ze werden opgestart. De argumenten die deze functie gebruikt zijn als eerste het adres van de main-functie van de applicatiecode, en als tweede argument een lijst van argumenten die door de code kan worden gebruikt. Figuur 2.4 toont het controleverloop van de RtlUserThreadStart functie. Deze functie is verantwoordelijk voor het opbouwen van de exceptieverwerkingstructuren, die we in meer detail bespreken in een volgende sectie. De laatste oproep van de RtlUserThreadStart functie gaat naar de BaseThreadInitThunk functie binnen kernel32.dll die de applicatiecode voor deze thread oproept. ntdll.dll
RtlUserThreadStart
kernel32.dll
RtlInitializeExceptionChain
_SEH_prolog4
BaseThreadInitThunk
Main
Figuur 2.4: Het controleverloop van de RtlUserThreadStart functie.
PEB: Process Environment Block Het Process Environment Block (PEB) is een datastructuur die bij het opstarten van een proces wordt ingeladen in de gebruikersruimte van dat proces [16] [39] [40]. Windows zal tijdens de uitvoering van een proces, ten allen tijde een verwijzing naar het PEB bijhouden via het FS-register (FS:[0x30]). Interne componenten van Windows, zoals bijvoorbeeld de Windows loader die instaat voor het inladen van DLL’s gebruiken de informatie in het PEB. Deze informatie vat de rechten van het proces samen, beschrijft bijvoorbeeld hoe groot een stack mag worden, hoe de heap moet geconfigureerd worden, waar de heap zich bevindt (de heap is immers een proces-specifieke datastructuur, in tegenstelling tot de stack die behoort tot ´e´en thread). Een belangrijke datastructuur voor deze scriptie binnen het PEB is de loaded module database. De database, ook wel PEB LDR DATA genoemd, zie Figuur 2.5, bevat dubbel-gelinkte lijsten waarin de ingeladen bibliotheken van het proces zijn geregistreerd. Elk van deze gelinkte lijsten bevatten exact dezelfde inhoud, en zijn dus even lang, maar hebben 16
een andere ordening. De ordening van drie van deze lijsten zijn load order, waarbij de regi-
Process Environment Block Image Base Address
Loader Data
Loader Database
InLoadOrderModuleList
TLS Data
InMemoryOrderModuleList
Heap Size Info
InInitOrderModuleList
Process Heap Address
Length
User Process Parameters
Figuur 2.5: Het PEB en de loader database.
straties zijn geordend in de volgorde van inladen in de adresruimte van het proces, memory order, waarbij de registraties werden geordend volgens de locatie van de bibliotheken in het geheugen, en initialization order, waarbij de registraties werden geordend in de volgorde die de loader heeft gevolgd bij het oproepen van initialisatie code van de ingeladen DLL’s. De elementen in de lijsten van de loader database noemen we loader data entries. Ze bevatten alle metadata over een ingeladen bibliotheek, zoals de naam van de DLL, de locatie van de DLL in de adresruimte(het basisadres of DLLBase), de grootte van het bestand, en waar de ingangsfunctie (entry point) zich bevindt, zie Figuur 2.6. Omdat het over gelinkte lijsten gaat, bevatten ze ook verwijzingen naar het volgende en vorige element in de lijst, met behulp van link elementen. Voor elke lijst waarvan het loader data entry deel uit maakt, bevat dit entry een link element om zijn locatie binnen deze lijst aan te duiden. Windows implementeert nog een vierde lijst via deze links, namelijk de HashLink lijst. Het systeem gebruikt deze lijst tijdens het opstarten en de terminatie van een proces voor snellere opzoekingen in de loader database. Het PEB is niet bedoeld om rechtstreeks gelezen te worden. De info is beschikbaar via verschillende Win32 API’s. De API garandeert dat de interface stabiel blijft maar zegt niets over de implementatie. We verwijzen voor meer informatie naar het Windows Internals boek [16]. TEB: Thread Environment Block Het Thread Environment Block (Figuur 2.7) is vergelijkbaar met het PEB als datastructuur, maar daar waar het PEB per-proces gedefinieerd is, is het TEB een per-thread datastructuur. Het bevat thread-specifieke informatie, zoals de locatie van de stack en tot hoever deze mag
17
Loader Data Entry
Link Element Next Entry
InLoadOrderLink
Previous Entry InMemoryOrderLink Next Entry InInitOrderLink
Previous Entry
DLLBase
Next Entry
EntryPoint
Previous Entry
SizeOfImage DLLName Next Entry HashLink
Previous Entry
Figuur 2.6: Een element uit de loader database, een loader data entry.
groeien, de locatie van het eerste element in de SEH-ketting, alsook het thread-ID. Zoals in een vorige sectie aangehaald, houdt Windows tijdens de uitvoering van een thread het TEB bij in het FS-segment. De base van het FS-segment wijst naar het eerste element van het TEB. De eerste elementen, die gaan van FS:[0x0] tot FS:[0x18] vormen het Thread Information Block (TIB). Het element op het FS:[0x18] element bevat steeds het adres naar het TIB, wat op de hudige versies van Windows overeenkomt met het TEB. Voorts bevat het TEB ook een adres naar het PEB van het proces waarin de thread uitvoert, in element FS:[0x30]. Het laatste element waarbij we stilstaan is dat op FS:[0x34], het bevat de foutcode van de laatst opgetreden fout tijdens de uitvoering van de thread. Een DLL inladen Zoals reeds aangehaald zijn er verschillende manieren om de DLL in het proces te injecteren. Het injecteren van een DLL is een techniek om binnen de adresruimte van een proces code uit te voeren, en het gedrag van een applicatie te wijzigen op een manier die niet bedoeld was door de programmeurs van de applicatie [41] [35]. We bespreken drie manieren en beargumenteren hun voor- en nadelen in het perspectief van de verdediger die een DLL injecteert in applicaties die dienen beschermd te worden. Allereerst zagen we al dat bij het aanmaken van een proces, al de benodigde DLL’s iteratief worden ingeladen in de procesruimte. Na het inladen van het programmabestand loopt de loader de importlijsten van de reeds ingeladen bestanden af en laadt de nodige DLL’s in. Daarbij wordt telkens de ingangsfunctie van elke DLL opgeroepen met de DLL PROCESS ATTACH 18
FS:[0x0] FS:[0x4] FS:[0x8]
FS:[0x18]
Exception List StackBase StackBase StackLimit StackLimit ... ...
...
Address TEB Address Address TEB TEB ... ... FS:[0x24] ... Current ThreadID Current Current ThreadID ThreadID ... ... FS:[0x30] ... Address PEB Address Address PEB PEB FS:[0x34] Last error number Last Last error error number number ...
Figuur 2.7: Een visualisatie van het thread environment block.
vlag. Wanneer we een DLL willen toevoegen aan de applicatie, hoeft er enkel een aanpassing te gebeuren in de importsectie van het uitvoerbare bestand en zal loader deze vanzelf inladen in het proces bij de initialisatie. Het voordeel hiervan is dat de DLL zijn intialisatiecode kan uitvoeren nog voor er applicatiecode werd uitgevoerd. Een nadeel is dat we toegang moeten hebben tot het uitvoerbare bestand omdat we het moeten aanpassen. Een tweede manier om een DLL te injecteren in een proces is gebruik maken van de LoadLibrary functie uit kernel32.dll. Hiervoor koppelen we eerst een extern proces aan de applicatie waarin we de DLL willen injecteren, gebruikmakende van de OpenProcess functie, ook uit kernel32.dll. Deze functie geeft de handle terug van een proces op basis van het proces-ID. Dit extern proces schrijft vervolgens de bestandslocatie van de te injecteren DLL weg in de adresruimte van de applicatie. Meer concreet reserveert het externe proces geheugen om de string met het pad naar de DLL in op te slaan. Als laatste stap maakt dit proces een nieuwe thread aan binnen de context van de applicatie met als startroutine de LoadLibrary functie en als argument het adres van de weggeschreven string. Wanneer deze thread termineert is de DLL ingeladen in de applicatie. Een duidelijk voordeel ten opzichte van de vorige techniek is dat het proces en de uitvoering van de applicatie niet wordt opgeschort, en dat het inladen van de DLL transparant gebeurt. Een nadeel is dat een extern proces zich moet koppelen aan de applicatie. Omdat dit proces geheugen reserveert binnen de adresruimte van de applicatie moet deze ook over genoeg toegangsrechten beschikken om dit te mogen doen. 19
De derde manier gebruikt het registry van het Windows besturingssysteem. Kort samengevat is dit een databank voor globale configuratievariabelen van het besturingssysteem. Een van deze variabelen is de AppInit DLL vlag [42]. Via deze vlag kan men DLL’s registreren die altijd in een proces moeten worden ingeladen. Deze aanpak heeft als voordeel dat het programmabestand niet moet worden aangepast en er ook geen extern proces wordt gebruikt. Er bestaan ook weer enkele nadelen. Ten eerste moet het uitvoerbare bestand van de applicatie afhankelijk zijn van user32.dll. Deze DLL exporteert functionaliteit voor gebruikersinterfaces, en wordt door de meeste applicaties ingeladen maar dit is geen garantie. Ten tweede mogen deze DLL’s enkel gebruik maken van functionaliteit die wordt ge¨exporteerd door ntdll.dll en kernel32.dll. Microsoft raadt af om van deze vlag gebruik te maken, en garandeert niet dat de volgende versies van Windows dit registry-element blijven ondersteunen.
2.3.4
Excepties
Er zijn twee manieren waarop de uitvoering van een stroom instructies kan worden onderbroken door het besturingssysteem: interrupts en excepties [37] [43]. Een interrupt kan optreden op gelijk welk tijdstip en heeft een gebeurtenis van buitenaf als oorzaak. Dit is steeds asynchroon en kan bijvoorbeeld gaan over een muisklik, het ontvangen van een datapakket over het netwerk, of het falen van een hardware component. Excepties worden ook wel software interrupts genoemd. Het opgooien van een exceptie gebeurt steeds door de processor zelf, en is steeds het resultaat van de uitvoering van een instructie. Voorbeelden waarbij een exceptie wordt opgeroepen zijn wanneer de processor moet delen door nul, wanneer expliciet een interruptinstructie wordt uitgevoerd als breakpoint of wanneer de code zelf een exceptie opgooit met de RaiseException functie uit kernel32.dll. In de volgende secties bespreken we de implementatiedetails van exception handling op Windows. SEH: Structured Exception Handling Windows implementeert de verwerking van excepties aan de hand van Structured Exception Handling [44]. Het systeem implementeert een gelinkte lijst (de SEH-ketting) die bestaat uit exception handling records (EH-records), zie Figuur 2.8. Zo een record bevat een adres dat verwijst naar het volgende element in de ketting, alsook een adres dat verwijst naar een exception handler. Heel deze ketting is thread-specifiek, wat wil zeggen dat elke thread zijn eigen ketting bevat. Het TEB houdt voor zijn thread het begin van de ketting bij in FS:[0x0]. Alle records van deze ketting bevinden zich op de stack van die thread. De volgorde van de EH-records in de gelinkte lijst wordt bewaard op de stack. Bij een functieoproep, wordt er een stack frame op de stack gezet, waaraan een exception handler is gekoppeld. Het laatste element, dat eerst is toegevoegd aan de ketting, wordt steeds aangemaakt binnen de RtlUserThreadStart functie, nog voor de applicatie begint met zijn uitvoering. 20
Wanneer er een exceptie optreedt, zal de kernel twee datastructuren klaarzetten alvorens de uitvoering terug aan gebruikerscode te geven. Deze zijn het exceptierecord en het contextobject. Het zijn de argumenten voor de exception handlers. Het exception record (niet te verwarren met het exception handling record) bevat onder andere het adres van de locatie waar de exceptie plaatsvond, alsook de exceptiecode die de aard van de exceptie identificeert. Een access violation exceptie heeft bijvoorbeeld 0xC0000005 als exceptiecode. Het contextobject is een samenvatting van de context van de uitvoering waarin de exceptie zich voordeed. Meer concreet zullen alle registerwaarden op het moment van het optreden van de exceptie hierin te vinden zijn. Wanneer de kernel na de exceptie de uitvoering laat terugkeren naar de gebruikersruimte doet ze dit via de exception dispatcher. Dit is een component die verantwoordelijk is voor het doorlopen van de SEH-ketting. De dispatcher geeft na de oproep van de kernel het eerste element in de SEH-ketting de kans om de exceptie te verwerken. Aan de hand van het contextobject en exceptierecord kan de handler in dit element beslissen of ze de exceptie kan verwerken. Wanneer de handler dit niet kan, geeft ze exception continue search terug aan de dispatcher, en moet hij het volgende element in de SEH-ketting aanspreken voor de exceptieverwerking. Het verwerken van een exceptie kan gaan van het herstellen van de situatie waarin de exceptie zich voordeed, door bijvoorbeeld bij het delen door nul de noemer aan te passen, tot het termineren van de thread of zelfs het proces waarin de exceptie zich voordeed. Wanneer de handler de exceptie wel heeft verwerkt, geeft ze het signaal exception continue execution terug, wat voor de dispatcher een teken is om de uitvoering terug te geven aan de applicatie die de exceptie opriep. Thread Environment Block FS:[0x00] Exception Handling Record Next Handler
Next Handler
Next Handler
Exception Handler
Exception Handler
Exception Handler
0xFFFFFFFF UnhandledExceptionFilter
Figuur 2.8: Het begin van de SEH-ketting wordt steeds bijgehouden door het TEB.
21
Het sentinel in de lijst wordt aangeduid door het adres van het volgende element op -1 te zetten (hexadecimaal 0XFFFFFFFF ). Ze bevat geen exception handler maar een UnhandledExceptionFilter en wordt enkel opgeroepen als de dispatcher geen handler vond die de exceptie kon verwerken. De filter is een functie binnen ntdll.dll en heeft verschillende taken, zoals het doorgeven van de controle aan een debugger als deze aanwezig is, en het oproepen van de ingestelde unhandled exception handlers. Een programmeur kan deze unhandled exception handlers instellen met de SetUnhandledExceptionFilter functie, en deze kunnen ingrijpen wanneer een exceptie dreigt de applicatie te termineren. Na het uitvoeren van deze taken zal de filter immers de thread of het gehele proces afsluiten. We moeten dit verhaal nog wat verder nuanceren. Zo zal het gedrag van de dispatcher anders zijn wanneer er een debugger aanwezig is. De kernel geeft in dit geval de controle eerst aan de debugger. Het is dan aan de debugger om te beslissen of het SEH-mechanisme in gang kan treden of niet. Ook zal de dispatcher bij het niet vinden van een handler die de exceptie kan verwerken, de controle niet aan de UnhandledExceptionFilter geven, maar aan de debugger en deze moet zelf beslissen wat er met de exceptie gebeurt. VEH: Vectored Exception Handling Uit de vorige sectie blijkt dat wanneer een SEH-element een exceptie afhandelt, hij ervoor kan kiezen volgende SEH-elementen in de ketting niet meer aan de beurt te laten en de controle terug te geven aan het programma die de exceptie opgooide, of zelfs dat proces te laten termineren. Dit is een tekortkoming inherent aan de SEH-architectuur. Immers, wanneer er bijvoorbeeld een exception handler zou bestaan die een opgegooide exceptie kan opvangen op zo’n manier dat de applicatie niet hoeft te termineren en correct de uitvoering kan verzetten, en die exception handler zou in de SEH-ketting worden voorafgegaan door een andere verwerker die de applicatie termineert, dan is dit een gemiste kans en wordt de exceptie niet optimaal opgevangen. Vectored exception handling is een extensie voor het SEH-model om net te vermijden dat een door de programmeur gedefinieerde exception handler wordt voorafgegaan door een standaard verwerker [45]. Een applicatie kan een functie registreren als een VEH-handler om excepties op te vangen. Deze handlers worden niet opgenomen in de SEH-ketting en bij het optreden van een exceptie worden eerst alle geregistreerde VEH-handlers opgeroepen alvorens de SEH-ketting af te lopen. Bij het registreren van een functie kan de applicatie deze achteraan of vooraan de lijst van VEH-handlers plaatsen. Net als een SEH-handler kan de VEH-handler na het verwerken van de exceptie de controle doorgeven aan de volgende verwerker, of de verwerking van de exceptie stopzetten en de controle teruggeven aan de applicatie.
22
SEH-ketting Integriteit Bij het opbouwen van een ROP-aanval op het Windows besturingssysteem worden SEHrecords uit de SEH-ketting vaak het doelwit van aanvallers om de controle over de uitvoering van een applicatie over te nemen. Als de aanvaller ´e´en van de functieadressen naar een exception handler kan overschrijven en vervolgens een exceptie kan veroorzaken is de controle terug in handen van de aanvaller. Windows implementeert vele checks om de integriteit van de SEH-ketting te beveiligen [46], of tijdig te detecteren wanneer deze corrupt is. In deze sectie bespreken we SEHOP en SafeSEH, wat twee zo’n verdedigingen zijn die relevant zijn voor de volgende hoofdstukken van deze scriptie. SafeSEH: Safe Structured Exception Handling Een eerste integriteitsbescherming voor de SEH-ketting is SafeSEH. Deze techniek wordt ook wel Software DEP genoemd maar heeft verder geen banden met Hardware DEP (besproken in sectie 2.1.2). Deze techniek voegt metadata toe in het uitvoerbare bestand en de ingeladen bibliotheken, over de exception handlers die deze binaire bestanden exporteren [16]. Deze metadata geldt als een soort registratie van de handlers. Wanneer er nu een exceptie optreedt, zal Windows deze registratie controleren alvorens de controle door te geven. Meer concreet zal er gekeken worden of het adres naar de exception handler overeenkomt met een geregistreerd adres in de metadata van de ingeladen bibliotheken. Wanneer deze registratie niet overeenkomt wordt het proces waarin de exceptie zich voordeed afgesloten, omdat de SEH-ketting als corrupt wordt aanzien. Een zwakheid van deze techniek is dat ze er vanuit gaat dat elke ingeladen bibliotheek gecompileerd is met deze metadata. Dit zo is voor alle systeembibliotheken, maar niet per se voor meegeleverde bibliotheken. Wanneer het adres in een SEH-record verwijst naar een bibliotheek die niet met SafeSEH werd gecompileerd zal Windows de check niet kunnen uitvoeren en blijft dit record kwetsbaar. SEHOP: Structured Exception Handling Overwrite Protection Een tweede verdediging is SEHOP, wat staat voor structured exception handling overwrite protection [47]. Daar waar SafeSEH de oorsprong van de exception handlers aftoetst, bewaakt SEHOP de integriteit van de SEH-ketting zelf, door enkele invariante eigenschappen ervan te controleren. Enerzijds wordt er gekeken of we het sentinel in de ketting kunnen bereiken. Indien dit niet zo is, mogen we aannemen dat er een record in de ketting is overschreven en ze hierdoor gebroken is. Anderzijds wordt er getoetst of het adres in het laatste element van de ketting de UnhandledExceptionFilter is. Op die manier is het ook onmogelijk voor een aanvaller om het laatste element te vervalsen. Deze techniek is standaard geactiveerd vanaf Windows Server 2008, en is compatibel met Windows Vista SP1, maar is daar standaard niet geactiveerd.
23
2.4
Besluit
Dit hoofdstuk stond in teken van het schetsen van de context waarin het onderwerp van deze scriptie past. Uit dit hoofdstuk weten we dat informatielekken uitbuitbare kwetsbaarheden kunnen blootstellen en zo kunnen gebruikt worden om verdedigingen te omzeilen. Verder hebben we relevante concepten voor deze scriptie besproken. In het volgende hoofdstuk worden er informatielekken binnen het Windows besturingssysteem ge¨ıdentificeerd. Meer concreet gingen we op zoek naar manieren om de locatie van de systeembibliotheken binnen de adresruimte te bemachtigen. Vervolgens stellen we een oplossing voor die deze informatielekken moet dichten.
24
Hoofdstuk 3
Ontwerp van de BM-WII Tool Zoals uit de vorige hoofdstukken bleek, is het elimineren van informatielekken een zeer waardevolle investering bij het beveiligen van een applicatie. Het doel van dit hoofdstuk is het identificeren van informatielekken binnen de adresruimte van een Windowsproces. Meer concreet gaan we op zoek naar manieren waarop een aanvaller de locatie kan bemachtigen van die systeembibliotheken die altijd aanwezig zijn in een proces (ntdll.dll, kernelbase.dll en kernel32.dll ). Daarna bespreken we mogelijke oplossingen om te beschermen tegen de gevonden aanvalsvectoren. Om dit hoofdstuk af te sluiten, stel ik de BM-WII tool voor die een subset van deze oplossingen bundelt in ´e´en enkele DLL en waarbij ik telkens beargumenteer waarom ik wel of niet voor bepaalde oplossingen koos, en wat de impact van die keuzes is. BM-WII staat voor het Blokkeren van Malware door Windows Interface Isolatie, waarbij interface isolatie slaat op mijn poging om de systeembibliotheken, die de systeeminterface op Windowssystemen voorstelt, af te schermen van aanvallers.
3.1
Informatielekken in Windows
In deze sectie onderzoek ik hoe een aanvaller de basisadressen van DLL’s kan bemachtigen. Eens een aanvaller dit adres heeft kan hij immers door de DLL navigeren en al de functionaliteit die de DLL exporteert bereiken. Bij het zoeken naar informatielekken baseerde ik mij op gedocumenteerde technieken om shellcode aanvallen op te zetten op Windows systemen [5]. Een eerste datastructuur waaruit een aanvaller informatie over de bibliotheken kan vinden, is het PEB. Aangezien het basisadres van het FS-segment steeds wijst naar het TEB (FS:[0x00]), en zo ook indirect naar het PEB (FS:[0x30]), heeft een aanvaller ten alle tijde toegang tot deze structuur. De loader maakt het PEB aan bij de opstart van een proces, en registreert hierin al de DLL’s die worden ingeladen in de adresruimte van dat proces. Een aanvaller weet met zekerheid dat ook de systeembibliotheken geregistreerd zijn in deze database. Meer zelfs, deze bibliotheken worden altijd als eerste en in de zelfde volgorde ingeladen, waardoor een 25
aanvaller simpelweg naar de load ordered lijst kan kijken en van de eerste elementen in deze lijst mag aannemen dat ze de systeembibliotheken voorstellen die hij zoekt. Een aanvaller kan ook op een indirecte manier het basisadres van een bibliotheek bemachtigen. Het volstaat om ´e´en enkel functieadres van een functie die wordt ge¨exporteerd door een DLL te vinden om hier het basisadres uit af te leiden. De manier om dit te doen steunt op de eigenschappen van hoe Windows een DLL in het systeem inlaadt. Zoals we reeds in het vorige hoofdstuk zagen, wordt een DLL gealigneerd ingeladen op 64 KiB adressen. Startend vanaf het gevonden functieadres kan een aanvaller in stappen van 64 KiB de adresruimte bewandelen op zoek naar de eerste header van de DLL. Aangezien het bestandsformaat van een DLL het PE-formaat is, en de eerste header binnen dat PE-formaat steeds de MS-DOS header moet zijn, gaat een aanvaller bij het bewandelen van het geheugen, op zoek naar de karakters MZ die het begin van een MS-DOS header aanduiden. We gaan dan ook op zoek naar functieadressen die een aanvaller mogelijk kan misbruiken. Een eerste structuur waaruit een aanvaller zo een functieadres kan vinden is de SEH-ketting. We zagen al in de sectie over exceptieverwerking op Windows dat de laatste elementen in de ketting door het systeem worden aangemaakt en niet door de applicatiecode. Het laatste element bevat de UnhandledExceptionFilter functie uit kernel32.dll. Een aanvaller kan dus de SEH-ketting doorlopen en uit dit laatste element het functieadres halen dat hij nodig heeft om aan het basisadres van kernel32.dll te komen. De tweede structuur die een aanvaller kan gebruiken om functieadressen te vinden is de thread stack. Het systeem volgt bij het aanmaken van een thread altijd hetzelfde stappenplan (Het aanmaken van de stack en het TEB, het oproepen van de ingangspunten van de DLL’s. . . ) alvorens deze thread met zijn eigenlijke uitvoering kan beginnen. Het probleem dat zich hierbij voordoet is dat de thread tijdens zijn intialisatie de stack al uitvoerig gebruikt. Zo gebruiken bijvoorbeeld alle ingangsfuncties van de ingeladen DLL’s deze stack voor hun uitvoering. Zodra de uitvoering van de eigenlijke applicatie begint, bevat de stack al restanten van stack frames die een overblijfsel zijn van de uitgevoerde intialisatiecode. Een aanvaller kan op zoek gaan binnen deze restanten naar adressen die afkomstig zijn uit de ingeladen DLL’s, en het informatielek is compleet. Een tweede probleem is dat de callstack van gelijk welke thread op dezelfde manier begint. Zoals reeds vermeld in een vorig hoofdstuk, zal voor gelijk welke thread, de initialisatie eindigen met oproep naar de functie RtlUserThreadStart uit ntdll.dll. Deze functie is verantwoordelijk voor het oproepen van de applicatiecode voor de desbetreffende thread. Een belangrijk nadeel hiervan is dat een aanvaller nog voor de aanval weet hoe het begin van de call stack eruit ziet. Een aanvaller kan de call stack van een thread simpelweg aflopen tot hij zich in stack frames bevindt die afkomstig zijn van initialisatiecode, of anders gezegd afkomstig zijn van de functie RtlUserThreadStart voor die de applicatiecode opriep. 26
Uit deze stack frames haalt de aanvaller adressen (bijvoorbeeld de terugkeeradressen) waarvan hij zeker kan zijn dat ze afkomstig zijn uit die systeembibliotheken die verantwoordelijk zijn voor de initialisatie van threads (ntdll.dll en kernel32.dll ). Figuur 3.1 is een visualisatie van de staat van de stack net voor en net na het oproepen van de applicatiecode.
Thread stack
Thread stack
lagere adressen
overblijfselen stack frames van initialisatie
Call naar main stack frame
hogere adressen
stack frame
stack frame
stack frame
stack frame
stack frame
actieve stack frames van de initialisatie
Figuur 3.1: Een visualisatie van informatielekken op de thread stack.
3.2
Vereisten
Wanneer we beschermingstechnieken voorstellen om de vermelde informatielekken te dichten, nemen we volgende vereisten in acht: • De prestatieoverhead tijdens de uitvoering moet zo laag mogelijk blijven. Hoe groter de overhead, hoe kleiner de kans dat deze oplossing in de praktijk zal worden toegepast. • We willen vermijden dat de functionaliteit van een te beveiligen applicatie wordt aangepast, of gewenst gedrag als een aanval wordt bestempeld. Dit zal een belangrijk criterium zijn voor de genomen beslissingen in deze scriptie. • De manier waarop de beveiligingstechniek wordt toegepast zal ook een impact hebben op de bruikbaarheid ervan. Wanneer hercompilatie van een uitvoerbaar bestand nodig 27
is, of zelfs het aanpassen van de broncode, zal er een grotere drempel zijn om de techniek te gebruiken dan wanneer de techniek tijdens de uitvoering kan worden toegepast of via een module kan worden toegevoegd. • Een laatste vereiste is dat de verdediging effectief is. Met andere woorden, de informatielekken moeten wel degelijk afgeschermd zijn van de aanvaller, en de kost om een aanval op te zetten moet vergroot worden door het afwezig zijn van deze informatielekken. De tool die we in het vervolg van dit hoofdstuk voorstellen, zal in een volgend hoofdstuk op basis van deze vereisten worden ge¨evalueerd.
3.3 3.3.1
Het dichten van de informatielekken Het PEB
Een eerste intu¨ıtieve aanpak om de informatielekken in het PEB te dichten, is het randomiseren van de lijsten met registraties, of het aanpassen van de volgorde van de elementen in deze lijsten. Deze aanpak heeft echter enkele nadelen. Een eerste nadeel is dat na het toepassen van de verdediging de informatie nog steeds aanwezig is, en hoewel de aanvaller nu niet meer met zekerheid kan zeggen waar de gezochte bibliotheek zich in de lijst bevindt, kan hij er wel zeker van zijn dat ze aanwezig is. Een tweede nadeel is dat de betekenis van de lijsten wordt vernietigd. Elke lijst kende zijn specifieke volgorde, die met deze aanpak wordt teniet gedaan. Omdat deze aanpak in wezen geen informatielek dicht, maar gewoon verplaatst heb ik ervoor gekozen deze niet op te nemen in de tool. Een tweede aanpak is het encrypteren van de elementen in elk van de lijsten. Deze aanpak heeft geen van de voorgaande nadelen (alle elementen zijn nog steeds in volgorde aanwezig) maar hij brengt een implementatieprobleem met zich mee. De encryptie moet op het moment van effectief gebruik worden ongedaan gemaakt, om een correcte werking te garanderen. Na het gebruik moeten we de encryptie weer toepassen, anders is de bescherming na het eerste gebruik niet meer effectief. Het blijkt moeilijk, onder andere door het gebrek aan documentatie, te bepalen wanneer het PEB wordt gebruikt. Er bestaat immers geen eenduidige API om het PEB te benaderen. Als deze wel had bestaan konden we de oproepen naar deze API onderscheppen en de encryptie en decryptie toevoegen. Ik koos dan ook niet voor deze techniek, hoewel ik in het hoofdstuk met de evaluatie van de tool wel zal proberen te achterhalen in hoeverre de loaded module database door het systeem wordt gebruikt. Een derde aanpak is het verwijderen van de kritieke elementen uit de lijsten. Hoewel er hiermee informatie verloren gaat, zullen de overblijvende elementen nog steeds in juiste volgorde voorkomen in de lijsten zoals bedoeld is en zal ook een aanvaller de verwijderde informatie
28
niet meer kunnen raadplegen. We kunnen deze beveiliging ook in verschillende gradaties toepassen. Ofwel kiezen we ervoor om de maar enkele, goedgekozen elementen uit de lijsten te verwijderen, zoals bijvoorbeeld de systeembibliotheken, ofwel kunnen we de volledige lijsten verwijderen. We kozen er dan ook voor om deze aanpak op te nemen in de tool. In een volgend hoofdstuk onderzochten we de stabiliteit van deze oplossing waarbij we op zoek gingen naar de elementen welke we mogen verwijderen zonder dat de applicatie instabiel wordt. Van zodra een proces is ge¨ınitialiseerd, en de uitvoering van de applicatie die moet beschermd worden begint, kan een aanvaller de applicatie proberen aan te vallen. In ieder geval mogen we er vanuit gaan dat de applicatie nog niet is aangevallen tijdens zijn initialisatie en willen we de bescherming van het PEB dan ook in deze stap toepassen. Aangezien de loader alle DLL’s die worden ingeladen zal registreren in het PEB, mag deze bescherming hierna pas worden toegepast. Of juister, de bescherming kan pas toegepast worden zodra alle bibliotheken zijn ingeladen die we uit het PEB willen verwijderen. Het PEB verandert na het begin van de uitvoering van de applicatiecode enkel nog als er expliciet bibliotheken worden ingeladen met de LoadLibrary functionaliteit van kernel32.dll. Aangezien de focus ligt op het beschermen van systeemfunctionaliteit, die zo goed als altijd bij de start van een proces al aanwezig is, grijpen we hier niet op in, en zullen bibliotheken die worden ingeladen op deze manier niet uit het PEB verwijderd worden.
3.3.2
De SEH-ketting
De grootste uitdaging bij het ontwerpen van een bescherming van de adressen binnen de EHrecords is dat Windows zeer uitvoerig de integriteit van de ketting bewaakt. Zo kunnen we de laatste elementen uit de lijst niet zomaar verwijderen of aanpassen, niet enkel omdat ze een cruciale rol spelen in de exceptieverwerking van de lopende applicatie, maar ook omdat de exceptiedispatcher bij elke exceptie controleert of deze elementen niet corrupt zijn (zie 2.3.4). Deze beschermingen staan los van de applicatie en zijn ingebed in de systeembibliotheken. Dit wil zeggen dat zelfs wanneer de verdedigingen die Windows al implementeert bij de compilatie van de applicatie expliciet worden uitgeschakeld (door de applicatie bijvoorbeeld te compileren met de /SAFESEH:NO vlag), die verdedigingen nog steeds actief zijn voor de systeembibliotheken. De oplossing die voor deze scriptie werd ge¨ımplementeerd zal daarom incompatibel zijn met de verdedigingen van Windows. Het doel bij het beschermen van de EH-records is om te vermijden dat een aanvaller het adres van de exception handler gebruikt om de DLL te vinden die deze functie exporteert. Wanneer dit gaat over ´e´en van de laatste elementen, weet de aanvaller immers dat de gevonden DLL een systeembibliotheek zal zijn en beschikt hij opnieuw over veel functionaliteit voor zijn aanval. Wanneer we de adressen van deze exception handlers vervangen met een adres van
29
een functie uit een DLL die geen bruikbare functionaliteit exporteert, zal de aanvaller weinig kunnen aanvangen met de SEH-ketting als informatielek. Deze functie kan zeer eenvoudig de oorspronkelijk exception handler oproepen, of zelf de nodige functionaliteit implementeren. Zoals reeds vermeld, wordt de opbouw van de SEH-ketting gestart door de RtlUserThreadStart functie. De eerste EH-records worden voor het uitvoeren van de applicatiecode op de stack gezet en in de SEH-ketting toegevoegd. Het is op dit moment dat we de bescherming toepassen, aangezien bij de start van de applicatiecode enkel nog exception handlers worden toegevoegd door de applicatie zelf. Een tweede voordeel om de bescherming op dit moment toe te passen is dat de adressen van de exception handlers niet op voorhand gekend zijn, en we ze op deze manier kunnen uitlezen alvorens ze te vervangen. Tijdens de implementatie van de voorgestelde techniek slaagde ik er niet in het compatibiliteitsprobleem met SafeSEH te overwinnen. Voor details over de implementatie en de mogelijke obstakels verwijs ik naar het volgende hoofdstuk. Voor de evaluatie van de voorgestelde tool neem ik het patchen van de SEH-ketting dan ook niet in rekening.
3.3.3
De Stack
Allereerst bespreken we een oplossing voor het probleem van de restanten van data op de stack na de initialisatie van het proces. De gekozen oplossing steunt op de eigenschap van de stack. Een stack is een last in, first out datastructuur die aangroeit naarmate er meer geheugen wordt gereserveerd of meer data op de stack wordt geplaatst. Een stack pointer duidt ten allen tijde de top van de stack aan. Alle data die nog boven deze pointer zouden staan in het geheugen zijn de restanten van de uitvoering en mogen als ongeldig worden gezien. De intu¨ıtieve aanpak is dan ook het opschonen van de stack door al het geheugen van de stack boven de stackpointer te wissen, en zo de restanten van de stack frames te verwijderen. Ik implementeerde deze aanpak door de oproep naar de applicatiecode vanuit de RtlUserThreadStart te onderscheppen en op dit moment de schoonmaak toe te passen. In het TEB vinden we hoe groot de stack mag worden aan de hand van de stack limit. We gebruiken deze waarde om te weten tot waar we het geheugen mogen wissen. In het volgende hoofdstuk beschrijf ik de implementatie in meer detail. Het tweede probleem is complexer aangezien de data onder de stackpointer nog gebruikt kunnen worden door het systeem en applicatie. We verwijzen opnieuw naar Figuur 3.1 waar de actieve stack frames zijn gevisualiseerd net na het oproepen van de main functie. Het verwijderen van de stack frames zou leiden tot het crashen van het proces aangezien de uitvoering nog naar deze frames kan terugkeren. De oplossing die we kozen is het encrypteren van de stack frames om zo de kritieke data van een aanvaller te beschermen zonder ze te verwijderen. Het terugkeren naar een stack frame gebeurt door het uitvoeren van een ret instructie. Deze
30
instructie zal het terugkeeradres van de stack halen en inladen in het register eip dat als instructiepointer wordt gebruikt. Als de encryptie van dit terugkeeradres zo gekozen wordt dat het wijst naar een ongeldige locatie, of beter gezegd een niet-uitvoerbare locatie, zal dit resulteren in een exceptie. Het is dit mechanisme dat we gebruiken om op het juiste moment het ge¨encrypteerde stack frame te decrypteren. Mijn tool zal een exceptieverwerker registreren om bij dit soort excepties in te grijpen en de decryptie te doen. Daarna geeft de exceptieverwerker de uitvoering terug aan de applicatie en lijkt het alsof er geen encryptie heeft plaatsgevonden. Ik gebruikte hiervoor het vectored exception handling mechanisme dat we in een vorig hoofdstuk hebben besproken, omdat het niet mag voorvallen dat een andere exceptieverwerker de exceptie eerst afhandelt. We passen de encryptie toe, door net als bij het opschonen van stack, de oproep naar de applicatiecode te onderscheppen binnen RtlUserThreadStart. Met deze methode encrypteren we enkel de stack frames afkomstig van de initialisatiecode net voor het oproepen van de applicatiecode. Dit wil zeggen dat een aanvaller er niet meer kan vanuit gaan dat de eerste stack frames op de callstack bruikbare informatie bevat maar dit wil ook zeggen dat de stack frames die worden aangemaakt door de applicatie met deze techniek niet beschermd zijn.
3.4
Ontwerp van de BM-WII Tool
Nu de verschillende oplossingen zijn voorgesteld, zoomen we in op hoe deze samen gebundeld worden binnen ´e´en tool en hoe we de beschermingen toepassen. Al de oplossingen zijn werkzaam tijdens de initialisatie van ofwel een proces ofwel een thread binnen dat proces. Daarom implementeren we de BM-WII tool als een DLL. Wanneer een applicatie de beschermingen wil toepassen zal ze de DLL moeten inladen. Ik koos ervoor om de te beschermen applicatie afhankelijk te maken van de BM-WII DLL. We willen namelijk dat de oplossing voor het PEB wordt toegepast v´ o´ or de uitvoering van de applicatie, om er zeker van te zijn dat de aanvaller de kans niet kijgt om dit informatielek uit te buiten. De bescherming voor het PEB moet maar ´e´en maal worden uitgevoerd, maar de beschermingen voor de thread stack (het wissen van overbodige data en het encrypteren van de stack frames) moet voor elke thread gebeuren, en dit bij de opstart ervan. Ook de bescherming voor de SEH-ketting moet per thread gebeuren omdat elke thread zijn eigen SEH-ketting heeft. Deze bescherming kan op hetzelfde moment als de stack patch gebeuren. Figuur 3.2 visualiseert waar in de initialisatie van een thread of een proces de verdedigingen worden toegepast.
31
Call to CreateProcess Load exe
PspUserThreadStartup RtlUserThreadStart Create PEB
Create thread and stack
Start thread execution
Create TEB
Load & Init DLL
Load & Init BMWII
Create SEH Chain
Patch SEH en Stack
Call entry point
LdrInitializeThunk Patch PEB Call to CreateThread
PspUserThreadStartup RtlUserThreadStart Create thread and stack
Start thread execution
Create TEB
Init DLL
Init BMWII
Create SEH Chain
Patch SEH en Stack
Call entry point
LdrInitializeThunk
Figuur 3.2: Dit is een visualisatie van waar de beschermingen worden toegepast in de initialisatieprocedure.
32
Hoofdstuk 4
Implementatie BM-WII Tool In dit hoofdstuk lichten we de implementatie van de BM-WII Tool toe. Eerst bespreken we voor elke oplossing hoe we deze implementeerden, wat de mogelijke tekortkomingen nog zijn en welke uitbreidingen we nog kunnen toevoegen. Daarna bespreken we pas de initialisatie via het ingangspunt BMWII-Main van de DLL omdat we dan beter kunnen verduidelijken wat we nodig hebben voor de verschillende patches.
4.1
PEB-Patch
Zoals in een vorig hoofdstuk vermeld, moeten we het PEB patchen tijdens de initialisatie van het proces, maar kunnen we dat pas doen na het inladen van de te verwijderen bibliotheken. Hiervoor zijn concreet twee mogelijkheden. Een eerste mogelijkheid is dat we het PEB patchen tijdens de oproep naar BMWII-Main binnen de DLL PROCESS ATTACH context. We kijken er hierbij op toe dat de DLL pas wordt opgeroepen nadat al de te verwijderen bibliotheken reeds aanwezig zijn in de adresruimte van het proces. We kunnen dit bereiken door onze DLL afhankelijk te maken van al de bibliotheken die de applicatie zou inladen zonder het toepassen van onze bescherming wat we bijvoorbeeld kunnen doen door alle DLL’s waarvan de applicatie afhankelijk is toe te voegen in de importsectie van onze DLL. In het speciale geval dat we enkel de registraties van de systeembibliotheken willen verwijderen, hoeven we geen extra aandacht te schenken aan het be¨ınvloeden van de volgorde van inladen. De systeembibliotheken worden immers, zoals we al in vorige hoofdstukken zagen, altijd als eerst ingeladen. Een tweede mogelijkheid om de patch toe te passen, die in geen enkel geval door de inlaadvolgorde wordt be¨ınvloed is het hooken van de RtlUserThreadStart, een techniek die we in een vorig hoofdstuk kort bespraken. Omdat deze functie pas na de inlaadroutine wordt toegepast, mag onze DLL er steeds van uitgaan dat al de te verwijderen bibliotheken reeds geregistreerd zijn in het PEB. We kozen ervoor om in de huidige implementatie van de tool echter toch de eerste methode te gebruiken, en in BMWII-Main de patch al toe te passen. Omdat de focus van deze thesis op het beschermen van de systeembibliotheken ligt en deze 33
steeds voor onze BMWII-DLL worden ingeladen, is de extra indirectie die de methode met het hooken met zich meebrengt overbodig. Zoals we in het vorige hoofdstuk hebben vermeld, kan de patch in verschillende gradaties worden toegepast. Het PEB bevat enkele geordende, gedocumenteerde lijsten (namelijk de load order, init order, en de memory order ) maar ook een lijst die niet is gedocumenteerd (de hashlinks lijst). Bij het implementeren van de patch ontdekten we dat deze ongedocumenteerde lijst een impact heeft op de aangeboden systeemfunctionaliteit van de systeembibliotheken. Om een concreet voorbeeld te geven bespreken we de GetModuleHandle functie uit kernel32.dll, die aan de hand van een meegegeven DLLnaam kijkt of een DLL is ingeladen in het procesgeheugen en daarvan het basisadres teruggeeft als dit het geval is. Wanneer we de registratie van een DLL uit de hashlinks lijst verwijderen, kan deze functie de DLL niet meer terugvinden en lijkt het voor de functie alsof de DLL niet aanwezig is in het geheugen. In het volgende hoofdstuk evalueren we deze patch daarom ook op stabiliteit door ze toe te passen op verschillende applicaties om het gedrag van de bescherming te onderzoeken. We kijken ook of het weglaten van de bescherming van de hashlinks lijst een andere impact heeft, dan wanneer de bescherming wel wordt toegepast.
4.2
Stack Patch
De stack-patch moet voor elke thread geactiveerd worden tijdens de initialisatie. Een eerste poging om de stackpatching toe te passen was om dit te implementeren bij een aanroep naar BMWII-Main binnen de DLL THREAD ATTACH context voor elke thread. Dit is helaas geen goede aanpak omdat de patch te vroeg in de initialisatieroutine wordt toegepast, en er nog steeds restanten van stack frames afkomstig van initialisatiecode op de stack zullen verschijnen. De initialisatie is immers op dit punt nog niet gedaan. Een tweede poging was het hooken van de RtlUserThreadStart functie omdat dit de laatste functie is die het systeem oproept alvorens er applicatiecode wordt uitgevoerd. Dit is een goed moment om de stack te wissen, maar is een slecht moment om de encryptie toe te passen, omdat er nog enkele stack frames worden opgebouwd alvorens het eerste stack frame van de applicatie wordt aangemaakt. Deze stack frames kunnen in de hook niet ge¨encrypteerd worden, en zijn nog steeds kwetsbaar voor inspectie door de aanvaller. De RtlUserThreadStart functie doet een oproep naar BaseThreadInitThunk, een functie binnen kernel32.dll. Deze functie heeft als taak de applicatiecode op te roepen die in de thread moet worden uitgevoerd. Het is binnen deze functie dat we onze beschermingen zullen toepassen door de oproep naar de applicatiecode te onderscheppen. De BMWII-Main functie past bij de initialisatie van het proces de BaseThreadInitThunk functie aan, om zo de patch toe 34
RtlUserThreadStart
ntdll.dll
kernel32.dll
RtlInitializeExceptionChain
_SEH_prolog4
Eerste Stadium BaseThreadInitThunk
Tweede Stadium Derde Stadium SEH-Patch Stack Encryption
Main
Stack Cleaning
Figuur 4.1: De patches toegepast binnen RtlUserThreadStart.
te passen. Meer concreet zal de BMWII-Main binnen deze functie drie stadia toevoegen die we in Figuur 4.1 visualiseren. Allereerst voegen we enkele instructies toe aan het begin van de code van BaseThreadInitThunk. Deze instructies vormen het eerste stadium en hebben als doel naar het tweede stadium te springen. Het tweede stadium is een naakte functie binnen onze BMWII-DLL die de bedoeling heeft de context van de BaseThreadInitThunk functie op te slaan. Met een naakte functie bedoelen we dat er geen functieproloog wordt uitgevoerd, en de registers en stack niet worden aangepast bij het springen naar deze functie. De context van de functie, waarmee we de staat van de stack en de inhoud van de registers bedoelen, bevat een adres naar het begin van de op te roepen applicatiecode. Dit adres slaan we in het tweede stadium op en vervangen we door het adres van het derde stadium, wat ook een functie binnen de BMWII-DLL is. Als laatste stap geeft het tweede stadium de controle terug aan de BaseThreadInitThunk functie die nu ons derde stadium zal oproepen in plaats van de applicatiecode. Het is in dit stadium dat we de verdedigingen toepassen en de context weer herstellen naar een staat waarin de uitvoering zich zou verkeren als onze stadia niet aanwezig zouden zijn. Het derde stadium gebruikt het adres dat in het tweede stadium werd opgeslagen om de applicatiecode op te roepen en de applicatie te hervatten. Hoe we deze stadia precies toevoegen, bespreken we in een volgende sectie. Binnen het derde stadium moeten er concreet twee dingen gebeuren, de encryptie en het wissen van de resterende data op de stack. Als eerste encrypteren we alle actieve stack frames, waarbij we met actief bedoelen dat er nog naar het stack frame kan worden teruggekeerd.
35
Aan de hand van het framepointer, dat in het register ebp wordt bijgehouden, wandelen we door deze actieve stack frames en voor elk gevonden stack frame lezen we het terugkeeradres in dat we vervolgens encrypteren. We kunnen er zeker van zijn dat ebp steeds het framepointer bevat op het moment dat we de encryptie toepassen omdat we ons nog steeds in de initialisatiefase bevinden van de thread die gelijk is voor alle threads en dus zeer voorspelbaar gedrag vertoont. Wanneer er nu wordt teruggekeerd vanuit een ge¨encrypteerd stack frame, wordt het instructiepointer op het versleutelde terugkeeradres gezet door het adres in te laden in het eip register. Om te verzekeren dat deze laatste stap altijd resulteert in het opgooien van een acces violation exceptie, kiezen we de encryptie zo dat de meest significante bit van het ge¨encrypteerde adres steeds op 1 staat, of met andere woorden dat het adres steeds naar de kernelruimte wijst. Wanneer dit vervuld is, zal de processor altijd een acces violation opgooien aangezien de RtlUserThreadStart routine in gebruikersmodus uitgevoerd wordt, en deze geen toegangsrechten heeft tot de kernelruimte. Naast het vervullen van deze eigenschap, zijn we vrij in het kiezen van de gebruikte encryptie. In de huidige implementatie wordt in BMWII-Main een waarde willekeurig bepaald. Om de encryptie te doen passen we de exclusieve of operatie toe op het adres met deze waarde. Een tweede keer deze operatie toepassen leidt tot de decryptie van het adres. Deze encryptie is vrij zwak maar is zeer eenvoudig te vervangen met een robuuster encryptieschema. Desondanks volstaat de huidige encryptie in deze scriptie als een proof of concept. Om deze exceptie op te vangen, registreren we een exception handler binnen de BMWIIMain bij de initialisatie van het proces in de lijst van vectored exception handlers. Onze handler onderzoekt binnenkomende excepties op verschillende criteria om de excepties die afkomstig zijn van onze encryptie te kunnen verwerken en de overige excepties verder door te geven aan de andere handlers. Als eerste kijkt de handler of een binnengekomen exceptie van het juiste type is (een acces violation). Vervolgens wordt er naar de context waarin de exceptie optrad gekeken. De handler kijkt of het eip register een adres naar de kernelruimte bevat, en of de stackpointer wijst naar een adres binnen een ge¨encrypteerd stack frame. Als de eigenschappen van de opgegooide exceptie niet overeenkomen met deze criteria, geeft onze handler de uitvoering terug aan het systeem om het exceptieverwerkingsmechanisme zijn werk te laten doen. Wanneer de eigenschappen wel overeenkomen, zal de handler de decryptie uitvoeren. De handler herstelt de stack door het ge¨encrypteerde adres te decrypteren, en in het contextobject dat werd meegegeven het instructiepointer door dit gedecrypteerde adres te vervangen. Daarna geeft de handler de uitvoering terug aan de dispatcher met het exception continue execution signaal. De dispatcher herstelt de context van de thread net voor de exceptie, met het herstelde eip register en de decryptie is compleet. Nu rest ons nog enkel de stack op te schonen. Wanneer we tijdens de initialisatie van de 36
thread, alle actieve stack frames ge¨encrypteerd hebben, beginnen we met het wissen van het ongebruikte deel van de stack. We doen deze stap bewust als laatste omdat we zo de restanten van de stack frames die tijdens het encrypteren werden opgezet, ook mee wissen. Het gedeelte van de stack dat we opschonen is de volledige geheugenruimte tussen de stackpointer en de stack limit (deze stack limit kunnen we opvragen in het TEB).
4.3
SEH-Patch
Zoals reeds in een vorig hoofdstuk vermeld, lukt het ons niet de laatste elementen uit de SEH-ketting te verwijderen. Deze laatste elementen bevatten de functieadressen uit de systeembibliotheken die we proberen te beschermen. In de eerste plaats zijn deze elementen nodig om een correcte werking te garanderen, en ten tweede zal bij het verwijderen ervan het SafeSEH mechanisme het proces afsluiten omdat de gebroken ketting ongeldige functieadressen bevat. Bij een tweede poging, probeerden we de adressen van de laatste elementen naar een ongeldig adres in de adresruimte te doen wijzen, net zoals bij de stack-patch. Wanneer een exceptie in dit geval de aangepaste exceptieverwerkers zou bereiken, zou er een tweede exceptie moeten optreden die een vector handler op zijn beurt kan opvangen. Dit nesten van excepties geeft op Windows zeer onstabiel gedrag en kan zelfs leiden tot een systeemfout, wat meteen ook de reden is dat we niet voor deze methode kozen. Ook zal de SafeSEH bescherming ingrijpen alvorens deze geneste exceptie zou optreden, door het proces af te sluiten. We kozen ervoor de UnhandledExceptionFilter functie uit het laatste element te vervangen door een zelfgedefinieerde exceptieverwerker. Deze kan kiezen uit twee mogelijkheden, het termineren van de thread of het proces waarin de exceptie optrad of het doorgeven van de functieargumenten aan de UnhandledExceptionFilter. Aangezien een exceptie die deze functie bereikt bijna altijd zou leiden tot het eindigen van de uitvoering van het proces, willen we toch zoveel mogelijk van de functionaliteit van de UnhandlerdExceptionFilter bewaren, en kozen we voor de tweede mogelijkheid voor onze huidige implementatie. Deze methode zal desondanks nog steeds incompatibel zijn met SafeSEH. De opbouw van de SEH-ketting begint bij de RtlInitializeExceptionChain functie in de RtlUserThreadStart routine. Wanneer we een bescherming voor de deze ketting willen toepassen mag dit ten vroegste na de oproep van deze functie gebeuren. Aangezien we op dezelfde plaats reeds de stack zullen bewerken, kunnen we het derde stadium bij de stack-patch ook gebruiken om de SEH-patch in te verwerken, zie Figuur 4.1. We kozen ervoor de SEH-patch v´o´or de stackencryptie en stackschoonmaak toe te passen, omdat we op die manier ook de stack frames van deze patch zullen wissen. De encryptiestap en de SEH-patch stap mogen in principe omgewisseld worden en zouden geen invloed mogen hebben op elkaar.
37
Een oplossing voor het compatibiliteitsprobleem is het herschrijven van de KiUserExceptionDispatcher, de functie verantwoordelijk voor het toepassen van de SEH-checks alsook het dispatchen van de excepties naar de verschillende exceptieverwerkers. Het moet mogelijk zijn de dispatcher zo te herschrijven dat de laatste elementen kunnen ge¨encrypteerd worden op een manier die gekend is voor de dispatcher. Bij de SafeSEH check kan de decryptie van de deze elementen intern door de dispatcher worden toegepast waarna de normale check gebeurt. Wanneer de exceptie werd verwerkt, is het de dispatcher die de uitvoering terug moet geven aan de applicatie, of het proces op een correcte manier moet afsluiten. Het is op dit punt dat de dispatcher de encryptie weer zou kunnen toepassen. Dit is een mogelijke uitbreiding voor de stack-patch die in deze scriptie niet werd ge¨ımplementeerd.
4.4
BM-WII DLL initialisatie
In deze sectie bespreken we wat er in de ingangsfunctie BMWII-Main gebeurt om de drie patches succesvol te implementeren. De PEB-patch roepen we op bij de eerste oproep naar BMWII-Main met de DLL PROCESS ATTACH vlag, zoals we eerder al zagen. Op dit moment passen we de PEB-patch toe en worden al de systeembibliotheken uit de lijsten verwijderd, naargelang de gekozen configuratie. Een complexer mechanisme dat we bespreken, is het opzetten van de stack-patch en de SEH-patch. Voor de stack-patch wordt eerst een willekeurige negatieve waarde gegenereerd. Het negatief zijn van de waarde verzekert ons dat bij het encrypteren van een adres uit de gebruikersruimte, het resultaat van de encryptie steeds naar de kernelruimte zal wijzen. Daarna wordt op zoek gegaan naar het adres van de RtlBaseThreadInitThunk functie die wordt opgeroepen binnen RtlUserThreadStart. Meer concreet doen we dit door het basisadres van kernel32.dll op te sporen en de exporttabel ervan te doorzoeken naar de locatie van RtlUserThreadStart. Een aandachtige lezer merkt meteen dat de PEB-patch een invloed kan hebben op de correcte toepassing van de stackpatch, aangezien we gebruik maken van GetModuleHandle en deze functie niet meer werkt voor systeembibliotheken zoals kernel32.dll eens we de registratie van die bibliotheek uit de hashlinks lijst zouden verwijderd hebben. We vermijden dit probleem door de PEB-patch als laatste stap toe te passen binnen BMWII-Main. Eens we de functielocatie gevonden hebben van RtlBaseThreadInitThunk, kunnen we beginnen met de code van deze functie aan te passen. De systeembibliotheken hebben een accommoderende eigenschap voor het aanpassen van hun functionaliteit, namelijk in de code wordt elke functie gescheiden van andere functies aan de hand van enkele bytes die nop instructies (wat staat voor no-operation) of int3 instructies (wat de instructie is om een breakpoint aan te duiden) bevatten. Dit laat ons toe het begin van een functie ongestoord aan te passen. Het eerste stadium is een codefragment van zes bytes dat springt naar het tweede stadium, en injecteren we net boven rtlBaseThreadInitThunk. De eerste instructie van deze functie overschrijven we met een kleine sprong (twee byte instructie 38
lengte) naar dit codefragment. Het tweede stadium is een functie binnen onze DLL, en zal alle registers met een verwijzing naar de main functie vervangen met een verwijzing naar de functie binnen onze DLL die het derde stadium implementeert. Alvorens de uitvoering terug te geven aan RtlBaseThreadInitThunk zal het tweede stadium nog de verwijzing naar de main opslaan. We moeten ons realiseren dat we in een threaded-context bevinden. De verwijzing naar de main functie voor deze thread mag niet in een globale variabele worden opgeslagen. We gebruiken een lijst die aan de hand van het thread-ID wordt ge¨ındexeerd om de juiste main op te slaan en op te vragen. Het thread-ID is beschikbaar via het PEB. Het derde stadium is waar we onze patches toepassen, en is ook een functie binnen onze DLL. Het is belangrijk voor de encryptie van de stack frames, dat zelfs de oproep naar de main beschermd wordt. Want als we de encryptie vroeger zouden uitvoeren (RtlBaseThreadInitThunk roept de main functie op) zou deze oproep een terugkeeradres op de stack zetten die verwijst naar de RtlBaseThreadInitThunk en dus indirect naar kernel32.dll, en zou onze encryptie een adres missen.
39
Hoofdstuk 5
Evaluatie In dit hoofdstuk evalueren we de besproken beschermingen, namelijk de PEB-patch en de stack-patch. Allereerst zochten we naar de strengste configuratie voor een stabiele PEBpatch. Daarna bekeken we de prestatieoverhead van de toegepaste methoden voor zowel de uitvoering als de initialisatie van een proces. Hierbij vergeleken we een proces zonder de beschermingen, een proces met enkel de stack-patch, een proces met enkel de PEB-patch voor de strengste configuratie die we vonden en een proces waarbij de PEB-patch en de stack-patch samen werden toegepast.
5.1
Configuratie van de PEB patch
In een vorig hoofdstuk zagen we dat er verschillende configuraties mogelijk zijn om het PEB te patchen. Enerzijds kunnen we kiezen welke bibliotheken we verwijderen uit het PEB, anderzijds moeten we beslissen of we de registratie van een bibliotheek verwijderen uit de hashlink -lijst of niet. Om de strengste configuratie te vinden die schijnbaar geen invloed heeft op de stabiliteit van het te beschermen proces, probeerden we verschillende configuraties op meerdere applicaties, meer bepaald Mozilla Firefox [48], VLC [49], Microsoft Word [50] en het opstartproces van Google Chrome [51]. De aanpak die we volgden was de strengste configuraties eerst uit te proberen en de configuratie te verzwakken tot we een stabiel proces kregen. Als we alle registraties uit het PEB verwijderen, faalt elke uitgeteste applicatie. Windows gaat ervan uit dat de loaded module database nooit leeg is, en een toegang tot de lege database leidt tot een access violation die niet wordt opgevangen. In een tweede poging verwijderden we enkel de kernel32.dll en ntdll.dll bibliotheek uit het PEB, alsook hun hashlink -registraties. Bij deze configuratie blijft elke applicatie hangen, en stopt ze met reageren. We vermoeden dat Windows verwacht dat ntdll.dll of kernel32.dll steeds aanwezig is in een proces en dus ook in de database. Windows blijft de gelinkte lijst itereren zonder resultaat wat een eindeloze lus veroorzaakt. Daarbij komen we bij de configuratie die we gebruikten voor het vervolg van de evaluatie. In deze configuratie verwijderden we kernel32.dll 40
volledig, dus ook de hashlink -registratie, en ntdll.dll zonder zijn hashlink registratie. Bij deze configuratie werkt elk van de uitgeteste applicaties zonder falen, en zonder een merkbaar verschil. 45000
40000
35000
Octane Score
30000
25000
Zonder Patch StackPatch PEBPatch
20000
Stack + PEB
15000
10000
5000
0 Octane
Crypto
EarleyBoyer
Regexp
Splay
SplayLatency
NavierStokes
Figuur 5.1: Eerste deel van de resultaten voor de Octane benchmark.
70000
60000
Octane Score
50000
40000 Zonder Patch StackPatch PEBPatch 30000
Stack + PEB
20000
10000
0 pdf.js
Mandreel
MandreelLatency
GB emulator
CodeLoad
Box2DWeb
Zlib
Typescript
Figuur 5.2: Tweede deel van de resultaten voor de Octane benchmark.
5.2
Invloed op de Uitvoeringstijd
We hebben de overhead van de voorgestelde methoden getest op 3 verschillende Javascript benchmarks uitgevoerd met de Mozilla Firefox browser. Figuur 5.1 en Figuur 5.2 tonen 41
140
Uitvoeringstijd (ms)
120
100
80 Zonder Patch 60
StackPatch PEBPatch Stack + PEB
40
20
0
Figuur 5.3: Resultaten voor de Kraken benchmark.
de resultaten van de Google Octane [52] testsuite. Deze testsuite geeft een score aan de uitvoering van de benchmarks die omgekeerd evenredig is met de uitvoeringstijd. Naast de GBemulator benchmark, zien we geen significant verschil in score. We kunnen hieruit afleiden dat het aanpassen van het PEB in sommige gevallen een impact heeft op de uitvoering van de systeemfunctionaliteit, en dus op de uitvoeringstijd. In Figuur 5.3 tonen we de resultaten van de Kraken benchmarks [53]. Opnieuw zien we geen grote verschillen in uitvoeringstijd tussen een proces met een patch en zonder een patch. Daarentegen zien we wel voor de meeste benchmarks een grotere spreiding op de uitvoeringstijd. Een mogelijke verklaring hiervoor is dat hoe meer threads, hoe meer onze patch wordt uitgevoerd. De stackpatch is immers werkzaam bij de initialisatie van elke nieuwe thread. Als laatste test op de uitvoering toont Figuur 5.4 het resultaat van de Sunspider benchmarks [54]. Opnieuw blijken de patches over het algemeen weinig invloed hebben op de uitvoeringstijd van het proces.
5.3
Invloed op de intialisatietijd
Omdat de stackpatch werkzaam is tijdens de initialisatie van een thread, voerde ik ook een test uit om de invloed op de opstart van een thread te meten. Deze test meet hoelang het duurt om 200 threads na elkaar op te starten. In Figuur 5.5 zien we dat de stackpatch wel degelijk een invloed heeft op de opstarttijd van een thread, en dat de overhead zelfs meer dan 33% bedraagt. We merken ook op dat de standaardafwijking groter wordt bij het toepassen van de stack-patch, wat we ook merkten bij sommige Kraken benchmarks.
42
20
18
16
12
10
Zonder Patch StackPatch PEBPatch
8
Stack + PEB 6
4
2
0
Figuur 5.4: Resultaten voor de Sunspider benchmark.
300
250
200
Uitvoeringstijd (ms)
Uitvoeringstijd (ms)
14
150
100
50
0
Zonder
StackPatch
PEBPatch
Stack + PEB
Figuur 5.5: Meetresultaten van de thread-creatietest.
43
Hoofdstuk 6
Conclusie Complementair met de huidige trend in het verdedigen tegen het uitbuiten van softwarekwetsbaarheden, werden in deze scriptie op het Windows besturingssysteem drie informatielekken ge¨ıdentificeerd waaruit een aanvaller de locatie van bibliotheken aangeboden door het systeem kan afleiden. Deze informatielekken zijn in wezen adressen uit geheugenpagina’s die code bevatten. We onderzochten geheugenstructuren binnen de adresruimte van een proces, en vonden deze adressen binnen enerzijds het process environment block en anderzijds op de stack van elke thread. Voor elk informatielek werd een oplossing voorgesteld om het lek te dichten. Twee van de drie oplossingsmethoden werden succesvol ge¨ımplementeerd. De derde methode kon niet worden toegepast wegens compatibiliteitsproblemen met reeds actieve verdedigingen. De ge¨ımplementeerde methoden zijn werkzaam bij de initialisatie van het proces en zijn threads, en encrypteren of verwijderen de informatielekken zodat ze niet meer bereikbaar zijn voor de aanvaller. In het geval van encryptie werd een methode ge¨ımplementeerd om op tijd de informatie weer te decrypteren. Bij de evaluatie van de methodes werd er geen merkbaar verschil in uitvoeringstijd gemeten, en kon er geconcludeerd worden dat de methodes een minieme prestatieoverhead hebben. Wanneer enkel de initialisatietijd gemeten werd is er wel een duidelijke toename van minstens 33% in uitvoeringstijd. De methoden hebben dus een grotere impact op applicaties die van veel threads gebruik maken.
44
Bibliografie [1] Richard Fateman. Software fault prevention by language choice: Why c is not my favorite language. Advances in Computers, 56:167–188, 2002. [2] Yves Younan. C and c++: vulnerabilities, exploits and countermeasures. Security Research Group. Retrieved from http://secappdev. org/handouts/2012/Yves% 20Younan/C% 20and% 20C++% 20vulnerabilit ies. pdf, 2013. [3] Stephen Turner. Security vulnerabilities of the top ten programming languages: C, java, c++, objective-c, c#, php, visual basic, python, perl, and ruby. Journal of Technology Research, 5:1, 2014. [4] Aleph One. Smashing the stack for fun and profit. Phrack, 49, 1996. [5] Jack Koziol, David Litchfield, Dave Aitel, Chris Anley, Sinan Eren, Neel Mehta, and Riley Hassell. The shellcoder’s handbook. Wiley Indianapolis, 2004. [6] PaX Team. Pax address space layout randomization (aslr), 2003. [7] Crispin Cowan, Steve Beattie, Ryan Finnin Day, Calton Pu, Perry Wagle, and Erik Walthinsen. Protecting systems from stack smashing attacks with stackguard. In Linux Expo. Citeseer, 1999. http://www.immunix.org/documentation.html. [8] Saravanan Sinnadurai, Qin Zhao, and Weng fai Wong. Transparent runtime shadow stack: Protection against malicious return address modifications, 2008. [9] MSDN. Visual studio 2013, /gs (buffer security check). https://msdn.microsoft.com/ en-us/library/8dbf701c.aspx. [10] Gerardo Richarte. Four different tricks to bypass stackshield and stackguard protection. http://www.coresecurity.com/files/attachments/StackGuard.pdf. April, 2002. [11] David Litchfield. Defeating the stack based buffer overflow prevention mechanism of microsoft windows 2003 server, 2003. [12] J. N. Rob Enderle. The new approach to windows security, 2004. 45
[13] ARM Architecture Reference Manual. Apx and xn (execute never) bits have been added in vmsav6 [virtual memory system architecture]. www.arm.com, 2009. [14] Ryan Roemer, Erik Buchanan, Hovav Shacham, and Stefan Savage. Return-oriented programming: Systems, languages, and applications. ACM Transactions on Information and System Security (TISSEC), 15(1):2, 2012. [15] Nergal. The advanced return-into-lib(c) exploits, a pax case study, 2001. [16] Mark Russinovich, David Solomon, and Alex Ionescu. Windows internals. Pearson Education, 2012. [17] Ollie Whitehouse. An analysis of address space layout randomization on windows vista. Symantec advanced threat research, pages 1–14, 2007. [18] Hovav Shacham, Matthew Page, Ben Pfaff, Eu-Jin Goh, Nagendra Modadugu, and Dan Boneh. On the effectiveness of address-space randomization. In Proceedings of the 11th ACM conference on Computer and communications security, pages 298–307. ACM, 2004. [19] Ali Rahbar. An analysis of microsoft windows vista’s aslr, 2006. ´ [20] Mart´ın Abadi, Mihai Budiu, Ulfar Erlingsson, and Jay Ligatti. Control-flow integrity principles, implementations, and applications. ACM Transactions on Information and System Security (TISSEC), 13(1):4, 2009. [21] Felix Schuster, Thomas Tendyck, Christopher Liebchen, Lucas Davi, Ahmad-Reza Sadeghi, and Thorsten Holz. Counterfeit object-oriented programming, 2015. [22] Per Larsen, Stefan Brunthaler, and Michael Franz. Security through diversity: Are we there yet? Security & Privacy, IEEE, 12(2):28–35, 2014. [23] Bart Coppens, Bjorn De Sutter, and Koen De Bosschere. Protecting your software updates. Security & Privacy, IEEE, 11(2):47–54, 2013. [24] Kevin Z Snow, Fabian Monrose, Lucas Davi, Alexandra Dmitrienko, Christopher Liebchen, and Ahmad-Reza Sadeghi. Just-in-time code reuse: On the effectiveness of finegrained address space layout randomization. In Security and Privacy (SP), 2013 IEEE Symposium on, pages 574–588. IEEE, 2013. [25] Microsoft MSDN. Dynamic-link libraries. https://msdn.microsoft.com/en-us/ library/windows/desktop/ms682589%28v=vs.85%29.aspx. [26] Microsoft MSDN. Copy-on-write protection. https://msdn.microsoft.com/en-us/ library/windows/desktop/aa366785%28v=vs.85%29.aspx. [27] Wikipedia. Dll hell. http://en.wikipedia.org/wiki/DLL_Hell. 46
[28] Matt Pietrek. Avoiding dll hell: Introducing application metadata in the microsoft .net framework. MSDN Magazine, 2000. [29] Matt Pietrek. Inside windows: An in-depth look into the win32 portable executable file format. MSDN Magazine, 2002. [30] Wikipedia. Magic number (programming). http://en.wikipedia.org/wiki/Magic_ number_%28programming%29. [31] Wikipedia. Mark zbikowski. http://en.wikipedia.org/wiki/Mark_Zbikowski. [32] PaX Team. Pax non-executable pages design and implementation, 2003. [33] C+ Visual and Business Unit. Microsoft portable executable and common object file format specification, 1999. [34] John Leitch. Iat hooking revisited. iat-hooking-revisited.pdf, 2011. [35] Amit Malik. Dll injection and hooking. dll-injection-and-hooking.php.
http://www.autosectools.com/
http://securityxploded.com/
[36] MSDN. Dllmain entry point. https://msdn.microsoft.com/en-us/library/windows/ desktop/ms682583%28v=vs.85%29.aspx. [37] Abraham Silberschatz, Peter B Galvin, Greg Gagne, and A Silberschatz. Operating system concepts, volume 4. Addison-Wesley Reading, 1998. [38] Under the hood: Happy 10th anniversary, windows. https://msdn.microsoft.com/ nl-nl/magazine/bb985015(en-us).aspx. July, 2000. [39] Matt Pietrek. Under the hood: Reading another process’s environment. http://blogs. msdn.com/b/matt_pietrek/archive/2004/08/25/220330.aspx. August, 2004. [40] MSDN. Peb structure. https://msdn.microsoft.com/en-us/library/windows/ desktop/aa813706%28v=vs.85%29.aspx. [41] Wikipedia. Dll injection. http://en.wikipedia.org/wiki/DLL_injection. [42] MSDN. Working with the appinit dlls registry value. https://support.microsoft. com/en-us/kb/197571. [43] Yariv Kaplan. Interrupts and exceptions. protmode/interrupts.htm, 1997.
47
http://www.internals.com/articles/
[44] Matt Pietrek. Under the hood: A crash course on the depths of win32 structured exception handling. http://www.microsoft.com/msj/0197/exception/exception.aspx. January, 1997. [45] Matt Pietrek. Under the hood: New vectored exception handling in windows xp. MSDN Magazine, 2001. [46] Matt Miller. Preventing the exploitation of seh overwrites. Uninformed Journal, 5, 2006. [47] M Miller. Preventing the exploitation of structured exception handler (seh) overwrites with sehop. Online]. Dispon´ıvel em: http://blogs. technet. com/srd/archi´ ve/2009/02/02/preventingthe exploitationofsehoverwriteswithsehop. aspx.[Ultimo acesso em: 29 Nov., 2009], 2009. [48] Mozilla firefox. https://www.mozilla.org/nl/firefox/new/. [49] Vlc, videolan. http://www.videolan.org/vlc/. [50] Microsoft word. https://office.live.com/start/default.aspx. [51] Google chrome. https://www.google.com/chrome/browser/desktop/index.html. [52] Google octane, javascript benchmark. https://developers.google.com/octane/. [53] Kraken, javascript benchmark. http://krakenbenchmark.mozilla.org/. [54] Sunspider, javascript benchmark. sunspider.html.
https://www.webkit.org/perf/sunspider/
48