Don Giot de Windows interface BM-WII: Het blokkeren van malware

advertisement
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ëren 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 één 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 exploitationofsehoverwriteswithsehop. aspx.[Último 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 één 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ëren,
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 één 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 één 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; één 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ëxporteerde 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ëxporteerde 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ëxporteerde
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 één 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 één 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ële
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ële 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 één 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ëxporteerd 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 één 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 één 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 één enkel functieadres van een functie die wordt geëxporteerd 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ëvalueerd.
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 één 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ëncrypteerde 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 één 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óór 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 één 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ëncrypteerd
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ëncrypteerd 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ëncrypteerde 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ëncrypteerd 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ëncrypteerde 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ëncrypteerd 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óór 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ëncrypteerd 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, Úlfar 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/archive/2009/02/02/preventingthe exploitationofsehoverwriteswithsehop. aspx.[Último 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
Download