Dear viewer, these subtitles were generated by a machine via the service Trint and therefore are (very) buggy. If you are capable, please help us to create good quality subtitles: https://c3subtitles.de/talk/179 Thanks!
Thanks for the introduction.
It's good to be back in this in
this room,
I guess all of you know that
memory
corruption attacks are real and they
have a long past.
In this talk, we're going to work
through the different kinds of memory
corruption.
What kind of
passes an attacker can follow to
exploit a memory vulnerability?
And we'll also look
at what kind of different defenses
have to have been proposed in the last 30
or 40 years of software development.
And we'll also discuss why most of these
defenses can be circumvented easily
in just
a couple of steps.
So all these memory corruptions actually
led to an arms race between attackers
and defenders.
And in the movie war games where you
see in the background here, we are told
that the only winning move is not
to play, but well, we
are hackers, right?
So maybe we can change the rules of the
game to actually win nevertheless,
and we'll see how we can do that later
on.
So to start off with some numbers
to convince you that memory corruption is
actually a real thing if you just look at
vulnerability classes, according to the
list of common vulnerabilities and
exposures on the x axis,
we see two years from ninety nine.
Then the data collection was started
to
last year
and we grouped different kinds
of vulnerabilities, according to
a set of factors.
And we see that there's just like a ton
of attacks out there, even though we've
been rolling out defense mechanism after
defense mechanism after defense mechanism
and academia has proposed like two
hundred different defense mechanisms
that are out there that haven't been
adopted yet. And we'll also look into
why these mechanisms have not been
adopted.
So these memory
war or this war in memory is an
ongoing war.
And the problem inherently
lies in the way how we write software
so low level
languages like C or C++,
that
trait type safety and memory safety
for potential performance and study
can get.
So all the
checks that
are executed during the runtime of the
application are completely in the control
of the programmer, and the programmer
can add or remove checks at
his or her convenience.
And well,
the problem is that often
these
programmers forgets to add these checks,
and this will lead to a whole set of bugs
in addition to all the other high level
bugs that exist in the software as well.
And also, there's a large set of legacy
code and new applications written in
C and C++ that are prone to memory
bugs. Let's just look at the
Google Chrome browser.
A software,
a big software project that has just been
started a couple of years ago.
It's written
in a kind
of stripped down version of C++ that's
very close to C,
and it's it gets
only four to six times
each
year.
And that's
supposed to be high quality code that has
been just started a couple of
years ago.
And that clearly shows that we have no
way how to to get around that problem and
how to fix
it right now.
In addition, there
are way too many bugs out there
to find and fix manually.
There's
there's way
too much code and so many bugs
out there that we need some additional
services and defense mechanisms that
actually contains these
bugs. So it's still a very
valid
approach to actually go, find and fix
the bugs that are in there.
But it's not feasible to protect the
security of our systems by just fixing
these bugs one after one because it would
take way too much, way too much time.
We need an additional approach
that protects
running software
from attacks, even in the presence off
of bugs in the software, and we'll look
into that as well.
So let's start with
memory corruption.
The root of of the problem that we are
facing.
And Andreas actually gave
a very good talk a couple of days ago.
Was it yesterday or two days ago,
two days
who seemed to talk
OK about a.
And he he gave a very good introduction
into into memory corruption.
And he told us that's the only
winning option would be to
recompile all our
software with an
extended C compiler that adds
all these type checks on top of it.
Well, all this stuff comes as an
additional price tag of like two hundred
and fifty percent performance drawback.
So that might not be as feasible
and if
you want to compile any any regular code.
So
in this section, we're
going to follow the same motivation like
he had and we're going to enter.
Use memory corruption, but using a more
formal approach.
So be systematic his these memory
corruption into a model
of
poor memory attacks and defined security
policies. On top of that model,
that helps us classify different
defense mechanism, according this
this structure.
So memory corruption
on a very high level is an unintended
modification of a memory location due to
either a missing or a faulty, faulty
safety check type check or anything
like that. So the programmer forgot
to add something that prohibits
an attacker controlled inputs to modify
some of our internal state of
the program that might lead to
some other follow up corruption.
And I've I've shown a couple of lines
of code in the bottom that shows a
vulnerable function.
And if the user is able
to supply a specific value,
the user can leave
the bounds of the object that
he or she is supposed to access and write
anywhere into memory.
And this is the core of
any, any
attraction that uses memory corruption.
This bug is only exploitable if the
address or the value is dependent on
inputs. So if the
attacker can craft a pass from the
beginning of the execution or input flows
into the program to the actual
instruction, the dusty memory access
and the attacker therefore
sees all memory and we assume
that the attacker can control all right
memory using such and such an approach.
So what is a let's
splits this memory corruption attack into
two different types?
The first one is a temporal
error
where the uh,
where
we have a small, vulnerable function and
at the beginning
of the
execution of the function,
the pointer still points to the correct
object and we run into a temporal
error.
If we freed up that object, which
is perfectly fine, but later on, if the
access that object.
Then we get a memory corruption.
And this could
lead to some, maybe
the
memory allocator allocated some other
memory in the background or some other
object. I mean, you can use that box to
overwrite that,
that part of it.
The other possible memory error is a
spatial error.
So something is
a pointer is moving itself.
The other one was a temporal error mode.
A pointer is no longer here due to
some change constraints during the
execution of the program.
And this this one is that the pointer
itself moves along.
So initially,
two pointer points to a valid
data point.
It's used to write something and it's
updated and later on.
The pointer no longer points to the exact
same object, but to a different
one that is no longer valid.
Maybe some found information or
anything like that that can then be
overwritten with
the debug, and if
that's attacker controlled,
the attacker can use that as a first
step to start the escalation process.
So this is at the core
of every attack,
but what
happens afterwards?
So to discuss this part, we're going
to look into the dominant attack
vector that's used in these days for any
software attacks that you see.
Contraflow, high tech attacks
are very common and basically
the only or the biggest attack vector
that's that's used today.
And we'll look into the security policies
and the defense mechanisms and follow
each step that an attacker has to do to
accomplish a contraflow hijack
attack, given an example.
So on a high level,
we have
a control flow
graph
with a set of basic blocks
and the control flow
flows
from one basic block to the other.
So let's assume it starts
with the first basic
block and then
goes to the
to the basic block.
But now let's assume at a certain
basic block, it ends with an indirect
control flow transfer and the attacker
is able to modify the code pointer
that is used for that in their control
for transfer somehow.
So maybe the attacker can override an
address on the on the stack,
the return instruction pointer or
overwrite the
target address for the end, for an
indirect jump or for an indirect
call.
Using that approach, the attacker can
redirect the control flow from a valid
control flow graph to a different,
different part of the application
that would never be reached otherwise.
So the control flow of the software
leaves the statically defined control
flow graph and enters a
new stage that would not be reached
otherwise, which can be used
to bootstrap something that's called or
is called a beer machine.
And then the attacker
can reuse and continue to reuse existing
codes, for example, using return oriented
programing, chomp or into programing,
or any of these other code reuse
techniques, or even like in the old days,
inject new code if there's if a
page was readable
and executed with permission.
So on a on a high level,
I want you to walk through
a control flow
hijack attack step by step and
discuss the capabilities that an
attacker actually needs to have
to carry out the attacks and will then
use these capabilities to discuss
the different defense strategies.
One step after the other so that we can
see how we can refine and define
different security policies
after each other. So let's start the
execution.
Let's assume we restart
the vulnerable function and we jump into
that and we open up a new stack frame on
the bottom.
We start executing this vulnerable
function, and we've allocated the
argument to the vulnerable function on
the stack. The return address, a
safe base pointer and the compiler
made space for the temporary error.
As soon as we hit the vulnerable
string copy function.
The code starts copying
data from an attacker controlled
buffer into the
stack array and built might
overwrite the
of the the other
addresses.
So if the attacker controls
input is small enough to fit into the
buffer, we don't care and everything is
fine.
If the attacker continues to write,
we
reach a memory safety violation, which is
the first step that the attacker needs to
be able to execute.
So at this memory right instruction,
the attacker needs to have the
capabilities to circumvent the memory
safety checks. So there must be a missing
check somewhere, which is the very first
SEC. As we continue
in the next
set,
the attacker needs to circumvent the
integrity of the code pointer itself.
So only if the attacker can override
the code pointer and is allowed to
actually overwrite the code pointer
detector can continuously attack.
And in addition, the attacker
actually needs to know
with what value he or
she needs to override
the code pointer.
So if the attacker doesn't know where the
location of the system function in
this case is, the attacker will not be
able to succeed with the attack.
And as the execution continues.
People reach the return instruction and
will reach the next step at their
return instruction itself.
The attacker needs to have the
capabilities to actually execute
the indirect contraflow transfer with
a new is
a new targets that would otherwise not be
reached, and only if the attacker can
circumvent all these checks
or if these checks are missing, then the
attacker will succeed with a contraflow,
high tech attack on a high level.
So all these steps are necessary
for an attacker to succeed with an
attack. So if you want to use retourner
in the programing, we need to have all
these capabilities and we need to have
all that information to actually run it.
And on the other hand, a defense
mechanism can stop the attack at any
of these steps as long as the attacker
doesn't reach the bottom of the graph.
The software is fine and the attack
is not successful.
Until recently, a very
popular attack
vector was
coach.
Corruption were actually code was
rewritten,
and
I've actually read papers about that.
Even nowadays, even though any
processor or any CPU that I'm aware
of nowadays has
read only permissions and
executable bits that can be used to
restrict the right ability of many
memory pages.
So code
an attacker can no longer inject code
directly into the memory space of
the application.
So this attack is actually no
longer a problem.
But what's the big attack vector until
the mid mid 2000s?
So to to to close
that part, the most
common attack vector is our
control flow. High tech attacks that
reuse existing code inside the image
of the application and code.
Corruption attacks are no longer
that much of a problem.
And all the attacks that we've seen and
in the beginning on the motivating slide
are basically code corruption attacks.
So
what kind
of defense strategy strategies have
been proposed against his control for
hijacking attacks?
What's what's out there?
What what can we do at each individual
step?
So on
a first step
on the memory safety step, we can
actually stop the memory corruption from
happening so we can look at each
individual memory.
Read a memory, right?
If they violate
some memory safety properties
and like Andreas mentioned as well,
they are safe dialect of C and C++
like secured or cyclone
that actually enforce additional
properties for each,
each memory access.
The drawback, of course, is that you need
to rewrite your software in a
in a in this new language, you need to
constrain it to follow that pattern
or you
can retrofit.
You can change to
your
compiler to add these additional checks
automatically, whereas a possible
performance degradation by using
the soft bound and CTS approach.
So in general, memory
and forcing memory safety is considered
to be too expensive.
If you want memory safety, you could
actually rewrite the software in a type
safe and memory safe language like C or
like
Java or C Sharp,
which would be
much more convenient to
instead of rewriting it in a language
that supported arbitrary memory accesses
in the first
place and
more natural that way.
So rewriting in a safe language is
the safest option.
But while we still have this
large set of legacy code, so we should
look into a defense strategy that's
actually feasible if
he goes to the next step.
We can enforce the integrity
of a large
set of read and write.
So there
are there's a large set of code pointers
that's out there, and we can enforce
the
integrity of just code pointers by
restricting it to
specific security checks
that are added either on the compiler
side or at one
one part of the runtime side.
So there is right integrity testing
that checks all the different memory
rights and checks it checks them,
according to a
Points two
graph that's that remain at compile
time.
And there's, of course, data execution
prevention and readable XOR executable
for code, which is kind of an integrity
check for for just the code pages itself.
The next step are probabilistic defenses.
You can
shuffle around all that stuff in memory
and hope that the attacker will not be
able to find out what
kind of bird
locations of your code are
if detect. Who doesn't know where to jump
to then he or she will have a hard time
to succeed this the actual attack.
And as the last step,
there's
flow integrity, for example, control flow
integrity or data flow integrity
that protects control flow transfers
from one code location to the next one.
So data flow integrity checks for
what kind of buffers or what, what
happens on a high level and control
flow integrity only looks at the indirect
control flow transfer, and we
can add defense mechanisms
that each of key steps
the
hottest currently discussed defense
mechanism in academia is control
flow integrity.
So I want to go a little bit more into
detail how this defense mechanism
would actually work.
The idea behind this defense mechanism
is that you restrict
all the control flow transfers to
a statically determined graph of
valid contraflow transfers.
So at compile time, you run
a points through analysis and you check.
You get a statically determined
control flow
graph and
each control flow location
you check if the target
is in the set
of valid
control flow transfers points.
So let's say you are at the end of basic
Block two, as shown on the slide.
And according
to the Static Control Flow Graph, we have
two possible options where we can jump to
at runtime. The add an additional check
and see if it's either basic Block two
or basic Block three if it's a different
basic block.
We can stop the and terminate the
application with a memory
or with
a contraflow
integrity violation and say that
the attacker modified it somehow.
But unfortunately, all the current
implementations that are proposed and
there are about 20 different
implementations of contraflow integrity
out there
right now.
All these implementations over
approximate first of all, due to the
static analysis at compile time,
which is inherently imprecise because
it doesn't consider runtime information
or dynamically loaded code.
And in addition to cut some corners and
to get enough speed,
they only have one set of indirect,
indirect calls.
One set of valid targets for each
indirect called indirect trumps and
return instructions.
So again, if you're at the end of basic
block number one, we see
that according to the to the
check, any of these
very basic blocks would be a value
transfer points, and the check would only
ensure that you target
actually a valid, valid basic
block. But it would not ensure
that it targets the only single one valid
basic block that would be
determined by
the runtime information that you have.
So what are the limitations and the
drawbacks of these defense mechanisms?
First of all, the precision is limited to
the static analysis.
All this imprecision leads ambiguities
and other problems that you need to
circumvent by adding a whole bunch
of other stuff or losing precision.
And most implementations choose
to lose precision and end up with just
one set of possible value targets.
They enforce for every single
possible targets.
And other problem is that this static
analysis must see all the code
at the compile time.
So support for dynamic libraries
and dynamic loading and all that stuff
becomes very challenging.
So most sci fi solutions will not
support shared libraries that are
allotted dynamically at runtime, which is
kind of a showstopper for current
software
on a
regular Linux system.
You cannot even link to Lipsy
statically anymore.
You have to link it as a
as a shared library.
Yeah, they drop that support a couple of
years ago, and
you're faced with the decision of either
having performance overhead or additional
imprecision in that area.
So the current implementations all choose
to have over approximation
and loss of precision and
end up with roughly five to 10 percent
performance overhead.
So this is currently
the hottest, hottest alternative
for defense mechanism because it already
constrains the control flow
just through the basic blocks that are
out there. But it still has some
drawbacks, and maybe we can get
a finer notion of
where we can set up new defense
mechanisms that are more precise than
this approach
and actually have
lower overhead because it's always kind
of a compromise.
But let's
summarize the different,
the different paths from the memory
corruption down to the to the successful
attack in a in a more general
model, which allows us
to classify the different attack
and. Security policies.
So again, we have our
control flow, high tech attack that
starts with a memory corruption first.
The attacker has to control the memory
corruption, then
circumvent the integrity of the code
pointer itself, circumvent
the randomization and know the locations
of code in the process
image and then circumvent the control
flow integrity checks
at
the indirect control flow transfer itself
and other kind of attack that we quickly
discussed as the code corruption attack
that fits into this model as well.
If the attacker just modifies the
code
and the randomization
of the
of the code layout, but
we also
have a large set of other attacks, namely
data on the attacks that do not modify
any
of the of
the order
control flow data structures or the code
itself. So we can actually copy
all these boxes on the left hand side
to the right hand side.
And we can we can look into an
attack that modifies data
and data, locate
and need to note the locations of data
and data, location pointers
and the data flow integrity
as well, which kind of combines
all the different steps and pass the
attacker can take to
to reach the bottom to actually
execute some, some bad
behavior.
And the data only attacks are actually
generalization, as code
can just be seen as a subset of the data,
and the code pointers are a subset of the
data pointers.
So the data side is
the harder problem of the of
the left hand side.
And if you look at data
on the attacks,
the attacker
is only able to modify
part
of the heap or off the stack and
inject data values there that are then
interpreted that by the application in
a different
way.
And these attacks are very simple but
powerful.
So imagine that the attacker
is able to control the admin variable,
an FTP server or something like that.
The attacker can use that approach to
change the control flow
of the
application, but by just changing
the value
of the of the data itself and not
changing any of the code pointers
and circumventing code pointer integrity
or anything higher level like that
and using just such a simple change in
memory, the attacker can already
circumvent many of the other
checks.
But we'll see
more about
this hard problem later
on.
So what kind of defenses
are actually deployed out there?
What is what is
the state of the art?
How how far have become in the last
20 years of research and defense
mechanisms?
Well, only a few
of those have seen widespread use,
and they'll look at each of those
individual defenses and see how it
handles different forms of attacks and
how it fits into this model of attacks
to be justified.
And we can then see at which steps
there's still something missing that we
might be able to add on top of it.
So the first
and strongest protection that's currently
in place is data execution prevention,
which
enforces the integrity of
code on patch
level granularity at all
times.
It's basically
a hardware extension that adds an
additional bit in the patch table
that tells us or tell us the CPU
if the
stuff that is on that page
is actually executable or just
data and
we can enforce
by the
loader and the kernel
readable
exe or executable patches so
that a single page is either readable or
executable, which mitigates against all
the code corrupted attacks like dropping
your shell code on the stack.
That's no longer possible if the if the
stack itself is not executable.
And the nice thing is it has very
low overheads
like zero percent its hardware
enforced and it's widely deployed.
So
but unfortunately,
it has limitations as well.
So, for example, it doesn't support
self modifying code or any dynamically
generated code as soon as you want a
cheat compiler like for your JavaScript
engine or for you or for your Java
engine. You actually need memory pages
that are both readable and executable,
which leads to two other problems.
But nevertheless, it's a very strong
protection that enforces to the code
integrity for the code that is protected
that way. And let's see how it
actually fits into the memory model.
So it completely closes this attack
vector on the very left hand side, where
an attacker can override
code
in memory or add additional code in
memory and completely protects us
from this code corruption attack.
And the only thing that remains is.
A controversial high tech attack or data
only
attack, but we've got
other defenses, right?
So let's look at those as well.
We've got air space layout randomization
that has been
called
the
new or has been introduced as a new
and very powerful safety mechanism.
And the idea is that it randomize us to
locations of code and data regions
all throughout the memory
of an
error you restart a program.
And this is a probabilistic
defense and it depends on the on
the load or in both the OSes
fall.
A problem is that
even though the attacker needs
to
find the location, this is
actually not too hard
because many
applications contain some information
leak
and they'll tell you to point to
offer of a
specific object.
For example, all JavaScript
implementations returned
the address of
the of the object.
If you just asked for the for the hash
value of the
object, because that's kind
of a unique, unique value,
right?
And there are a whole bunch of these
information leaks out there that can be
can be exploited to gain additional
information.
So address space, layout, randomization
and other randomization techniques.
All these probabilistic techniques are
very prone to information leaks.
So instead of
a one shot attack where we can attack
the program or the application in one
go, you get a two shot attack.
So you first have to gain information
about the application and then you can
exploit it in the second step.
And it's still not too
hard, right?
Maybe you have to find two bucks, but
that's that's still doable.
And in addition, at least on x86,
a large set of code locations
that remain static.
Otherwise, you would get a more than 10
percent performance impact.
So the current Linux
Aceler implementation still has large
static control
blocks
for the for the main executable itself,
for example.
And if we
would randomize
that part as well, we would see a huge
increase in performance overhead of
more than 10 percent on average.
And all the
libraries there are already compilers
position independent code
so that the
performance
overhead comes
from
using an additional register for
Re-look double code.
So if you have
codes
that can be placed at any location in
memory, you need to keep one register
as a site that always keeps the base
pointer off the of the current
current code segment so that you can jump
around in a code segment.
If everything is at a static address like
for the main executable, then you're fine
because you don't need to keep that extra
register.
So if you tell the
if you tell GTC to actually compile
the code as relocated table, we see
that for spec CPU benchmarks
that I've shown here, you get a
10 percent performance degradation,
which is almost too costly for
a for a security mechanism.
So if we summarize
this aerospace layout, randomization
protection that we've got here, then we
can see that,
yeah, we've got some
protection against
the different forms of randomization.
So both the
locations of
code gets
randomized and the locations of data gets
randomized, and both of those are
scrambled around and it's
harder for an attacker to
to circumvent it as soon as the attacker
learns the secret.
So let's say you're attacking an Apache
server and you crashed one threat
and you got the information
about the memory layout.
You can use that information on the
second threat because it's only
randomized for each, for each process,
but not for each threat.
The last security mechanism that's out
there are stacked canneries
which protect the return instruction
pointer on this deck, and it's a compiler
based modification, and the compiler
changes to stack layouts during
the during
the compilation.
It's also probabilistic
probabilistic protection as the compiler
places a secret cookie
next to a buffer that could be
could be
overrun by some, some
missing integrity checks.
And again, it's probabilistic.
As soon as the attacker knows that
cookie, the attacker can place that
cookie and in the exploit
and just circumvent the protection
easily or the attacker can use it
directed right?
If it's not a continuous buffer, buffer
overflow and circumvent the stack cookie
that way, so there's no protection
against targeted reader rights.
But again,
if you look into our model, we
see that these stacked cookies protect
a subset
of all
code pointers, namely the return
instruction pointers with a very weak
probabilistic protection.
And that's basically all we have.
So we don't have any protection against
memory safety, corruption or memory
safety
errors, and we only have
very limited and partial protection
against any integrity violations.
So we do have
code integrity, which is a very good
thing and can be used to actually
build up much stronger defenses on
top of it.
But we don't have code pointer integrity
that is
effective against any randomization
attacks or
other other possible attacks,
and we don't
have any data integrity,
either.
Randomization is partially implemented.
We do have air space layout
randomization, but there are also
a bunch of other randomization schemes
that have been proposed
in in academia.
A drawback of
those is that many have a high,
higher
performance overhead of 10 or
more percent.
And we don't have any
control flow or data flow integrity
solutions that are implemented in this
in this current
system.
So if
you look at the model and combine all the
different defenses that we have, we see
that well, we have code corruption
protection,
which is a good thing.
We have stacked can risk that somehow
protect the return instruction pointers
on the stack,
which is not too bad,
right? It's at least something, and it's
cheap
and we
have a asla which randomized
us the locations of code and data
throughout the
application.
So we do it.
It looks like we have a decent
protection, right?
But all of these security mechanisms,
even if used in combinations,
can still be circumvented by using
techniques like return or into
programing.
And well, it looks like
the
defense mechanisms that we are using on
current systems are just not enough,
and we need to come up with something
that's better and that's stronger than
what we already have.
So you might ask,
why
did these stronger defenses fail?
Did we have more than a hundred
different alternatives that we
could just implement right away?
But all
of them failed due to a couple of reasons
or have not been been used to do a couple
of reasons. Well, first of all, they
have too much overhead.
If a defense mechanism has more than 10
percent overhead,
or
even if they have 10 percent overhead,
they're not going
to be used in
in the wild, right?
You're already using a C compiler,
and you're so happy.
If your C compiler gets a two percent
performance improvement, then you're not
going to give up like 200 percent just to
run it vis a vis a Type C version.
Otherwise, you would have implemented
your software in a type safe language in
the first place.
Or the next option is that
many of these defense mechanisms
lose compatibility to legacy and source
code.
So many systems
needs to have a static compilation.
For example, most of these contraflow
integrity solutions, they need to see all
the code at compile time, which leads to
statically executable,
static statically compiled executable.
And is this just not
feasible together with
shared library support?
In addition, a whole bunch of
defense mechanisms that rely on source
code modifications.
And if you have 200 million lines of
code, you're not going
to rewrite them in a in a safe language
of other safe version of your
of your sort of your programing
language.
And the last step or the
last reason why they have not been
adopted is
the
strong defense mechanisms need to
have
effectiveness against a large set of
attacks and the need to protect against
complete classes of attacks and not just
against the tiny little version of it.
So to summarize, defense mechanism
must be cheap.
They must be compatible to legacy and
source code, and this should be
effective.
So where do we go from
now?
What kind of options do we have?
We've seen that attackers are still
more powerful than defenders, and
defense mechanism mechanisms have a hard
time of getting a top adopted
and and
working into in the first place.
So there are several approaches that we
can do. We can change the compiler.
We can change to
the language
itself. Or we can change the runtime
system.
And let's quickly discuss
these three options one after the other.
So
memory safety or having memory safety
would be an awesome treat to
protect our software.
Unfortunately, if you just run
cell phones together with CTS, which
gives us some strong
form of memory safety for C and C++
programs, we see that we have a
performance overhead of up to 250
percent, which is way too
much to run for commodity
software.
Imagine running your browser
with a two hundred and fifty percent
overhead.
But what we can do
is
we can try to enforce memory safety for
some pointers, just a subset
of the pointers, maybe not for all data,
but we can if we can somehow select
the amount of data that we actually want
to protect.
We could offer strong protection for this
subset of data without
taking control and protecting
all the data that is out there, which
will just lead to too much overhead.
And Mel,
maybe compiler analysis
will help.
There will definitely be some tricky
engineering to to make it work,
especially
these shared libraries
that should be supported and so on.
So we'll need to look into that
and
we can also use a safe
runtime system,
so some form
of secure execution platform
that must support legacy code and binary
code itself.
One possible solution will be
binary translation that translates
the application as we go along and
kind of detects what kind of
data pointers are important, what kind
of data regions are
used for
control flow transfers and then protect
these data locations using additional
steps on top of it.
And we can leverage to run time
information, dynamically collect
information which enables us more
precise or security checks on top of it.
So what we can do on a high level,
we can rapidly running application
into a secure
runtime platform that kind
of keeps collects
information as the application goes along
and then enforces that information.
But just for the
for the privileged data
locations that we want to protect and not
for all the data locations that are out
there. So we have two little sandbox
that wraps the application and
we can control the interaction.
We can rely on
the information that is available from
the loader so we can infer
some form
of control
flow integrity policy by just extracting
all the information at the loader
provides us.
So all the
different,
different symbol tables can be used
to restrict and refine that possible.
Allow targets for the
control flow transfer transfers that
are executed inside the sandbox,
and we can also check the interaction
with the system.
If you guys don't know
what
binary
translation is, I want to give you a
quick highlight instead of executing
the original
code.
We add a small virtualization
system on top of it and
virtualized it in one additional step.
So code is not executed on the barebones
hardware, but we actually compile code as
we go along in a just in time compilation
process that dynamically checks the
targets and origins,
gets the information from the
loader,
refines that information and adds
a whole bunch of additional security
checks into the Protect the code that
is then executed
on the on the real hardware.
And therefore we can enforce
some form of dynamic security policy
on top of it without too much overhead.
And we'll actually see how how far
we can we can go with that approach
or
if we have to tap into the compiler
information as far.
So
what did we learn?
How far did we did we come
what what will be the result
of all of this?
Well, first of all,
low level, low
level languages are here to stay.
We've seen that even nowadays, a whole
bunch of software projects are started
and newly started in these low level
languages.
And we
need protection against all different
forms of memory vulnerabilities,
and we need to adhere
to the performance characteristics.
We need to support the legacy code that's
out there.
We cannot allow source code changes
and we have to be compatible with a large
set of other options.
So.
We have we can use one
that we
can try to protect against
the
biggest attack
vector that is out there nowadays.
And we have to find a forum to mitigate
against this contraflow, high tech
attacks and secure execution
platform as one start.
But then we'll have to follow up to the
next lulls and adapt and change
our compilers this fall to get protection
that is stronger than what we already
have. And we have to rethink
the existing security
to suit
defense mechanisms that are rolled out
nowadays, especially
things like Stack can can raise that only
add little protection on top of it and be
much better spent is one or two percent
performance overhead
in
into other instructions that it can be
can implement a stronger security
policy on top of it
and future directions after that.
If if,
if if protected against controversial
high tech attack, then there are still
data attacks out there and data attacks
are a completely different problem that
we'll have
to tackle.
So I
strongly believe that if we can fix
contraflow, high tech attacks pretty
soon, but we'll still have to look into
the all the data attacks that are out
there.
But I want to end this
talk with that.
If the winning move is not to
play, then maybe we need to change the
rules of the game.
We are the
first software that ever runs so we
can actually constrain the system in
additional ways and changed the rules
of the game. Add additional security,
defends mechanism that are stronger and
protects the software from being
exploited.
And instead, I
would like to thank you for your
attention and I am happy to take your
questions.
Thanks so much for that really great
overview.
So the microphones are two on the
left and the right, and I will also take
questions from the internet.
Let's start with microphone number two.
Hello. It was great talk.
I kind of got two questions.
One of them is, you're
saying that we should look at moving
things like Java because that's a safer
language. But everyone is now seems
to be saying, get Java off your system,
don't use it because the number of
exploits.
So we're confronted with these
mixed messages now, you know, and the
other question I've got is
whenever I see memory
errors discussed, they always
discuss the stack.
People mutter about
he picks points, but I never see an
example of one, so I've no idea how
one would work. I can only see that he
picks police work by
modifying something on the heap.
I mean, the program would read that onto
the stack and then you'd have a stack
exploit.
Is there a way to directly exploit the
heap? And if there isn't, would simply
persuading people to read or use
the data into
the heap rather than the stack
harden a lot of programing in
low level languages or not?
Let me answer your first question first.
It's not Java, as well as in
the language that gets exploited.
It's Java, as in the crappy browser
plug in that gets exploited.
There's a whole bunch of fairly
secure
Java software that runs on the server
side, and that one is perfectly
secure.
But the Java plug in itself
that runs the NOWADAY browsers is
inherent is very insecure, and
this
plug in is actually written in a low
level language.
Most of the Java virtual machine is
written in C, and the attacks
happen against this,
this virtual machine and try to exploit
the virtual machine.
The attackers applies to Java code
and breaks the virtual machine.
But on the
other hand, on the server systems,
the Java runs perfectly safe
to
the towers and runs a large,
let's say, a data processing engine or
something like that.
And the attacker can only supply data,
but not code.
And then it's perfectly fine
and well,
you still have all these problems
with low level languages like C or C++
that you can attack the
data
parts of the
application.
How does that work on the heap?
Because no one ever demonstrates how
that works.
I'd like to know how the
heap exploits work because everyone
always says they're there, but I never
see an example of one to one part.
One possible
way for a heat to exploit are the classic
use after free
exploit, where
you change some specific data structures
and not
one easy heap exploit could be.
You have you're writing in C++,
you have your
object somewhere on the stack, that
object is freed
and
the other object is located on top of it.
But you still get control over one
field off the old object because a
pointer points in there.
And conveniently, this
pointer points to the table pointer off
the new
object, and
you can then override overwrite the table
pointer. And as soon as that object
is used in the context of the
application, the attacker controlled V
table pointer will be followed and
some attacker controlled code will be
executed.
And you
don't. You didn't have to push anything
onto the stack.
It's all on the heap.
There will be an example, right?
OK. That's exactly what we're looking
for. OK.
All right, next question from the
microphone behind
us.
Hi.
I got a question concerning the
250 percent
performance tradeoff.
From what I understood
those, this tradeoff
comes from additional checks.
Those compiler extensions
put into your code,
but those
additional checks, shouldn't they have
been in your original code in the first
place?
Well, LVM,
well, Edra sanitizer
and soft pounds plus that implement those
checks, they have to be very
conservative.
They usually
run the analysis on a
per
on a per function basis, and they
have to instrument every single axis
that
you do you as a programmer,
you can actually think about, oh,
well, the attacker will never be able to
supply these and these values, and
the values will only be between one and
10.
If you know that
for sure, because the function that
you use
that the caller
actually just uses static
values, right?
So so you can actually use inherent
knowledge in between the caller and the
colleague to remove
Stacks as a programmer and
the software based approach compiler
based approach has to be conservative and
has to add all that. All the checks, in
addition to the C language, is very
diverse, so you have a large set
of different options that you can you can
add that makes all the analysis much,
much harder.
OK. I kind of anticipated
that answer.
And but isn't this
exactly kind of a kind of
the problem?
I mean, the programmer
thinking that at this point,
you can't the code can
be exploited.
But uh, yeah,
maybe the code is reused somewhere else
and the exact assumptions or
assumptions change and then you're
screwed.
Yeah, that's what happens.
OK.
All right. Now we take action on the
internet.
Uh, so first question from the internets
to new version of C and C++
provide necessary protection mechanism
or language constructs
to to prevent common attacks, like
you mentioned.
I think you've covered a little bit of
that at the beginning of your thought.
If you could experiment.
Can you repeat the question?
OK, the question is the new version of C
and C++ provide necessary,
necessary protection mechanism or
language constructs to prevent common
attacks
to protect what detects common attacks.
Common.
OK.
No, don't
answer.
You can use tools like Edra
Sanitizer
or a whole bunch of
other analysis tools that run on top of
it. Or you can use soft
bones and CTS, which is also a
compiler extension.
And these tools basically use liberties
in the language specification to actually
inject additional
security checks on top of it.
But
most of these tools, like in
the academic
papers, these
tools are
advertised as are
usually advertised as debugging
tools
just because they have so much
or such such a huge overhead.
You can
definitely if you want safe software, you
can re compile a list with soft bones
and then you should be
if you should be good to go right.
But you, you will
hit the huge performance impact.
All right.
All right, another question for
microphone to place.
Yes, hello.
Uh, good to, uh, have
a question. This Gear VR
security kernel patches.
How does it fit into this picture?
Mm hmm.
So the security patches
are then a bunch of
hardening
patches against the kernel and
kernel modifications itself.
So that would be kind of a soft source
code based approach to actually
harden the software itself.
I guess they also have
the implement a whole bunch of
defense mechanism, right?
So they introduced,
uh, they introduced Asal.
All right.
So yeah,
even before it was in the
in the mainline kernel.
So did.
Did they have
multiple roles right today at new
security defense mechanism for user land?
They make exploitation of the kernel
itself harder.
Yeah, it's a it's a nice project,
but it's not the language based,
the the program in C,
and
they add all these nice security
features. So it's a good thing.
I got another question from the second
microphone down to number two.
Hi. Thanks for the great talk.
Is there anything? What's your opinion on
the secure function that Microsoft
has implemented from the C standard like
string copy and
you know that family of functions?
Well.
It's still see your rights, you can make
your mistakes somewhere else,
and not just there.
And do you feel that it
adds any protection like
that? A programmer would be less likely
to make a mistake using these functions.
Well, it's it's definitely better than
than the non-secure version.
But the question is how much
better is it?
Or it
might solve a
small set of
problems, but then there are others
that are still cylinder, so it's
very debatable question.
All right. And it
was an easy fix
that they could add
very quickly.
But it's not
a sort fix that will save the inherent
problem of these little love
languages, especially
as it only
fixes like the a couple of
functions.
All right, I think we had another
question from the internet.
There are two more questions.
The first one is, uh, are you aware
of the existence of muscle lipsy
for the which makes it so you can
eclipsing muscle Lipsy and
so which
makes static linking feasible?
Yeah, there are
a bunch of ellipses that are statically
drinkable.
Yeah, there are. But the standard,
the each Lipsy is
not.
OK.
Next question is, uh,
if you compare all your programs for
yourself with the custom set of C
Flux or presumably L diplomas, what
would you consider your binaries secure
because no one else knows the exact Lipsy
address?
That's the same problem, right, as soon
as
you have
an information
leak off, like
I assume
the application that you compile yourself
processes, input and output.
And if
you get the binary, you can reverse that
one
or yeah, it's it's
still C, right?
Do you have any more questions?
Yeah, I'm great.
Well, before we wrap up this talk,
I would just like to remind everyone to
provide feedback on the impact these
presentations.
We get feedback also helps us speakers
a lot, so please go online,
provide the feedback and was that I would
like to thank us for that really
comprehensive overview of this
problem. Class and security.
Thank you very much, my thanks.