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/496 Thanks!
Enthusiastically, a cashier and
a security researcher,
assistant professor at Purdue University
in Indiana.
So let's hear what he has
on new
corruption attacks.
Thank you very much for the introduction.
Thanks, guys.
Wait was applause for until after the
talk, this is
not just my work, but the work of a whole
bunch of students that I want to give
credit to add a whole bunch
of different places from Sorich
to UC Berkeley to Purdue
University and a bunch of other places.
And I want to especially
call out Nicholas Carlini, who did a lot
of the coding work.
And this gives me the opportunity
to give you a little bit
of a peek towards a demo
that I will do at the end of the talk,
and we will drop something interesting at
the end of the talk. So stay tuned.
But this is not just a talk on
memory corruption, but
a whole journey that happens.
And it can best be described
as Dr. Strangelove or how I learned
to stop worrying and laughter fault
in the in the last couple of years, I've
worked a lot of a lot of
a lot of time, a long time with
software and different forms of
vulnerability's, memory, corruptions and
a whole bunch of.
Bad things that can happen and the world
that we are currently living in is that
all the software that we are running on
our systems is unsafe and insecure
and low level languages like in C++
trade, all the type safety and memory
safety for a so-called potential
of performance.
They don't necessarily give you the
performance, but they allow you to
maybe get the performance, but you
lose all the type safety and memory
safety that you would get otherwise from
safer languages.
And the programmer himself or
herself is responsible for all the
checks that have to be coded
explicitly into the source code.
And if any of these checks are missing,
then a program just explodes
in your face.
But it's not just a large set
of legacy applications that we are
running on our current systems, but also
a whole bunch of new applications that
are being written in C and C++
that are still prone to different forms
of memory corruption books.
And it just want to call out that even
at companies like Google, where
they look very closely
to coding standards and code reviews,
they still have a whole bunch of security
vulnerabilities. If you just look at the
at the Google Chrome browser that is
being pwned every single year,
also while finding and fixing
bugs is very important, there are just
too many books out there to find and fix
manually. So we have to protect the
integrity of our systems
through some form of additional safety
mechanisms that are out there.
And if you just look back at the
over the last two years or
the last couple of months, there have
been a whole bunch of security
vulnerabilities out there that are
calling for stronger and better defenses.
And just to call out the few that made it
to the press, there was heart Heartbleed
that basically corrupted all the
all the crypto keys out there on all the
different servers that we were running.
There was shell shocked that allowed us
to execute arbitrary commands
on different different servers.
And there was there was ghost, which
basically allowed us arbitrary memory
corruptions in any program
that just didn't get hosted by name call.
So what we are
facing is a huge
amount of memory unsafety that
our current programs have
and that we have to deal this through
different forms of additional
security mechanisms and security
guarantees.
But let me take a step back and talk
to you a little bit about the different
memory safety problems that we are
facing.
So to give you some some additional
background
at the core of any security
vulnerability, there is an
invalid reference
ETAs through a dangling pointer, what
we call a temporal reference, which is at
one point in time our point
during the low level language pointed to
a valid allocated object
which was later freed by the programmer
and is still accessed
after after the fact, after it has
been allocated.
And the other alternative is an out of
bounds pointer, which we call spatial
memory corruption.
That is the pointer at one point in
time pointed into the into
a valid object and then
through the execution of the program,
started to move outside of the object
and is now pointing into an illegal
memory region, which is
it? Was it it moved out
of the of the correct bounds
of the object.
But it's only a violation
or an invalid
reference if the pointer is read,
written or freed.
According to the C language standard,
you are completely free to have
out of bounds pointers.
You're free to have dangling pointers
as long as you do not, you reference
them. And this makes finding these
vulnerabilities in books so
hard because it only
becomes a bug if you are actually using
this invalid pointer, but not if you just
have them in the program.
And at any point in time, there are tons
and tons of of illegal pointers
in the program, which is completely safe
until you dereference them.
And there are two types of attacks that
build on these memory
and safety issues and are
either contraflow hatcheck attacks where
an attacker tries to execute some form of
code or inject new
code or just execute some some different
behavior that is not inherent to the
original program.
Or alternatively, are data only attacks
where where the attacker changes some
data that is then used along the way
of the program to change some internal
state of the after program
to achieve a specific specific
behavior.
Today in this talk, we focus on executing
code only because this is usually the
prime motivation of an attacker.
An attacker wants to execute code
on a on a compute platform to
get additional capabilities on top of
it.
So how does a control flow hijack
attack look like basically
in in high
level terms? We can abstract a program
into some form of control flow graph,
and then the execution of the program
follows this control flow graph and
passes from one notes to
the other one, thereby executing the
program.
And in this sample control flow graph,
we have a small loop and a and a
condition that then breaks out of the
loop and the control
flow of the application as the
application is executing passes
through this control flow graph, which is
an abstract concept that compiler writers
usually use.
And it moves from
one nodes to the other node as we are
executing through the program.
And attacker at one point in time might
be able to modify a code pointer.
Code pointers can be either function
returns, indirect Trump's or indirect
calls. These are all instructions
that are used on our hardware to
control how the
control passes from one basic block
to another, basic block, basically from
one node in the graph to another node in
the graph. And if an attacker can control
these code Pinder's that are then used in
the program, the attacker can redirect
the execution to a different location.
So after modifying such a code pointer
through one of the earlier mentioned
memory corruption vulnerabilities, the
control flow leaves to validate control
flow graph and starts executing
new new
instructions at a different location
and basically
leaving the valid control flow graph.
And the attacker can then reuse different
parts of existing codes
that are in the execution image of the
process and either through
return instructions.
Then it is called return or into
programing or through indirect
trump or indirect instructions and
is called trump oriented programing.
But let's switch to view a little bit and
look at the particular code example.
So we do have a small, vulnerable
function of a couple of lines of code.
And let's just assume that the attacker
controls the two parameters given
to this function, user and user.
Two, we also have a function
pointer that is that is being
defined somewhere in this in this
program.
But the attacker can use
these parameters that are passed
to the function to actually override
part of the function pointers.
We will see later.
And the underlying problem is that
the attacker can force an
out of bounds memory right through
this behavior by forging
an out of bounds pointer and then be
referencing that pointer to write the
specific value to to this memory
location.
If you look at the stack or the memory
layout of the program, we see the
individual variables.
Q We see the buffer, we see the function
pointer and we also have a piece of
code at the bottom.
So initially in a valid execution,
the pointer queue would point somewhere
into the buffer and the value user
two would be written into the buffer.
It can then be used by the program in an
odd way, but the attacker
can use this
addition to redirect the pointer.
Q To point to
the function pointer to the memory
location of the function pointer instead
of into the buffer.
And as we continue, when it is
being is being referenced,
the attacker can override this function
pointer and then make it point into
the code below.
And as soon as the
program executes or give references,
this function pointer, which has been
overwritten by the attacker in steps
one and two before the attacker can
start executing codes
that is being controlled by the attacker
and reuse different forms of gadgets.
And these are basically the two building
blocks that we have in current
attacks and that attackers use
to actually get control over the program.
First, we have memory unsafety
or memory safety violation that the
attacker uses to build up the
later steps of the exploit.
And then the control
flow of the application leads to valid
control flow graph of the application and
then the attacker executes arbitrary
code.
But we do have safety measures,
right? There has been a plethora
of proposals of different forms of
safety mechanisms that have been added
over the last couple of years.
And we've got a whole bunch of different
defenses that are
active on our current systems.
And let's just quickly go through them
so that we can see where their
limitations are and what other things
are possible.
So we started off this data
execution prevention, where we
removed the writable
bit from several locations
in memory and the executable bit from
other locations before data
execution prevention.
We could just inject our new code
somewhere on the stack or on the heap,
and the attacker could just redirect
the execution to these locations on the
stack. And he.
And then execute this newly injected
code, but with the advent of data
execution prevention, attackers have to
resort to already pre existing
code as no new code can be injected
into the executing image of the
application.
So this ensures that we can
no longer just inject new code
like Shell code or and
or other pieces of code that can be
used by the attacker to escalate his or
her privileges.
A second step is space
layout randomization.
Instead of allocating all
the different pieces of
often of an application
and well-known locations which are
scrambled these locations around every
single time the application is started.
And this makes it harder for the
attacker to guess where the
very exact locations are.
Obviously, this is prone to information
leaks. If the attacker can somehow learn
where these individual locations are, the
attacker can build and exploit that
actually
can mitigate these defenses.
We also have stack can to place
strategic values on the stack that
should not be overwritten and
safe except in handlers on top of that,
that guarantee that
the exceptions follow predefined
pattern.
So we've got a whole bunch of defenses
out there that
protect us from some
of the vulnerabilities that are out
there. But as we see this, all the
all the successful exploits on current
software, these defenses
are not complete and are at
best partial and make attacks
a little bit harder.
But they do in no means stop the attacks.
Also,
are Aerospace Ltd.
randomization and data execution
prevention are only effective in
combination if both of them are used
together on the honor system at both
times, because if you break
data execution prevention, you
can inject new code if you break
a seal or, you know, the addresses of
existing code and just stitch
newly new locations and code locations
together and then re execute
already existing code
as soon as you break a seal or you
can reuse these existing code
code locations.
And I just mentioned before that
information leaks actually enable
you to infer the locations of
specific pieces of code and you can then
stitch together the well-known code
locations to gain full
code executions on desktop
systems. Such information leaks are quite
common and it is
possible to find information leaks and
different pieces of software on servers.
In the last couple of years, code reuse
attacks have decreased
and attacks have gotten harder
due to the
because there just aren't that many
information leaks on servers.
So this summer we actually
worked on an attack that
leaks the accelerators basically at
randomization based addresses of
all the concurrently running virtual
machines for
different Windows versions and different
Linux versions by
using and memory.
Did the application side channel
that many cloud infrastructures
used? And we can basically learn the.
SLR based addresses of concurrently
running virtual machines by just
generating random or
well-known memory pages that are
just iterate through the available
entropy bits of the
Esler our base addresses and then
learn these other addresses.
But for more information, I would like to
refer you to the to the paper and
to the to the rootstock.
Also, the folks that worked on it, the
two students or the two other
folks that are working on it are here as
well. And they can be reached to if
you if you have questions.
So the status of the diploid
defenses is fairly incomplete.
And we constantly see a lot of attacks
that are going on and exploits
are possible on current systems.
But there must be some secret plan
somewhere out there that helps us protect
against many of these new upcoming
attacks. We somehow have to defend
against all of these new exploits.
And academia has come out of this
mess, hundreds of different proposals
on how we can protect against these
these different forms of memory safety
vulnerabilities.
Many of them are very unpractical
due to a high
EDAR, high performance overhead or
other forms of overhead or
incompatibilities with existing software.
But two proposals that are
gaining more and more momentum, our stack
integrity and control for integrity.
And we are seeing more and more
deployment of these these two
defense mechanisms.
But let's start with Steck integrity.
So we want to enforce
an additional set of restrictions
on return instructions,
on the source code
level. The return instructions
or the return from a function
to the caller is a very well-defined
function and it is usually a
one way function. You can only return to
one specific color
at any single time in the
during the execution of the program.
But at the hardware level, this is not
a restricted operation.
As the
return instructions implemented on
all the existing hardware architectures,
it basically allows you to read a code
pointer from the stack and then redirect
the execution flow to an arbitrary
code locations.
And what SEC integrity does is
it takes the semantics from the
programing language, from this high level
programing language, and enforces the
semantics on the overly
permissive instruction at
the at the hardware level.
So one example to enforce Steck
integrity could be to protect
the return instructions through
a shadow stack.
So, for example, if you do have these
code snippets here.
That are where we can
call food from
either function or function B.
We can enforce additional integrity on
top of these these functions
or on top of these returns on
the hardware level.
Then as we are executing function food,
we can return to any bytes
that is executable or map this executable
in the in the image of the
executing process.
But with Steck integrity, we can enforce
that. If Function A is calling for,
we will return only to function
A. as soon as we return from function.
On the other hand, if you're calling food
from function B, we can ensure
that food will only
be able to return to function B.
at this point in time, and thereby
we can ensure that the guarantees
on the programing language level
and the semantics on the programing
language level will be upheld
by the underlying executing program.
And we can guarantee that the contraflow
never leaves this to static,
well-defined grofe.
The second security principle that I
would like to talk about is control for
integrity and control.
Integrity.
At a concept level tries to ensure
that the execution of the program
never leaves the statically determined
control flow graph to ensure
the security property, we first have
to statically construct a control flow
graph.
And for each individual
in their control flow transfer
instruction, we have to find the set
of allowed targets at
at compile time and
at runtime.
We execute an online set check.
So for each in their own control flow
transfer, we do have a check
if the target is any
of the possible targets that were
determined at compile time,
if it is in this set of targets,
we allow the check to continue.
If it is not, then we terminate the
application.
So to show this to
you symbolically, if we execute.
A function pointer, if you execute a
function through a function pointer
without contraflow integrity, we
can basically reach
any possible executable byte
in memory. So there are no restrictions
on the machine level.
Same for the return instruction.
They can basically return to any
arbitrary executable instruction
with contraflow integrity.
We add additional restrictions, so we
first check the function pointer.
If it is in this set of statically
determined targets.
If it is not, then we terminate
the application.
Same for return instruction.
We check the number.
The current return instruction is used
at runtime against the statically
determined set of aloud instructions
or allowed targets at compile time.
If it is in this set, then we allow it
to happen.
So this is slightly different from
stack integrity that I talked about
before, right?
And we'll see a larger example
in in a bit, but what basically happens
is that under contraflow
integrity, the attacker
may write to memory
at any possible time
and the underlying memory safety
violation is allowed to happen
only at a later point in time.
This code pointer is verified when
it is used and there is some time between
the underlying memory corruption
that the attacker can can use
until the code pointer is checked later
on and are something that the attacker
can influence in that time window.
But let's go back to the to
the example on the
two callers that both call
for under a contraflow integrity
policy on the stack,
if a court function FUX,
then FUX can later return
to either A or B, both
are valid targets.
And the analysis that looked
or searched through the
code, through the source code found
that both A and B are calling function
food. So both A and
B are are allowed return targets
when returning from food.
And maybe the attacker can use
this to his
or her advantage at one point in time
to redirect the
control flow to either A or
B, as both targets are allowed
at runtime compared
to the statically determined
set of targets.
And this basically brings me to
new forms or novel code reuse
attacks.
And I would like to start with contraflow
bending, which is trying to work with
Nicolas Carlini and Antonio Barazi.
That did most of the work,
both at one of them is at UC
Berkeley, the other one at work.
And the underlying idea is
that instead of hijacking the
contraflow to a totally new location
in memory, which is bend's to control,
flow a little bit along a
valid control flow graph.
So we are not reaching new
locations that are not visible
in the program, but we are bending the
contraflow along the way.
So if we look at an execution
of a program, each individual contraflow
transfer is valid by itself.
But the trace of contraflow transfers
is not valid and might not
match the non exploit case.
So if you look at a trace, the individual
look for transfer
might not be possible due to the
different constraints that are upheld
along the the trains.
But each individual contraflow transfer
by itself is valid,
and this will allow us to circumvent
static, fully precise contraflow
integrity.
As we all see you in a bit.
The underlying limitation of CFI
is that it is stateless
and each individual state is verified
without any context and
control for integrity as a security
policy is unaware of all the constraints
between the individual states.
So any bending of the contraflow
along possible valuable
states is undetectable, as according
to what the contraflow integrity policy
can observe.
So as an attacker, we have to search APOs
in this abstract contraflow graph
that matches the desired behavior of
the attacker.
And I'm not talking about
some weak form of control for integrity,
weak control, for integrity has
been broken and is known to be broken.
And there's a whole bunch of papers
that were published at different security
conferences last year that show
that weak forms of control for integrity
is broken.
And by the way, Microsoft, Microsoft
contraflow guard is an instance of
a weak control for integrity mechanism
and can be can be broken by any
of these mechanisms that were just cited
here.
It might make the attack a little bit
harder or harder in some cases,
but it does not stop any attack from
happening.
So let's move to strong CFI
and see what we can do under a strong
control for integrity policy.
The assumption that we use this,
that we define a very
precise or precise as possible
control flow graph.
So we assume that there is no over
approximation.
And any possible compiler
or compile time analysis will always
have an approximation and
thereby have imprecision along
the way, but if we assume that there is
no over approximation and we have the
most precise possible contraflow graph
and then show that this contraflow graph
is broken, then by design
we can show that any possible
SIFY implementation can be broken
this way because even this is
unrealistically overly precise
contraflow graph.
We can still break it
because we assume that there's some
form of STAC integrity for some cases
and thereby define fully precise
static CFI so that a
transfer in the contraflow graph
or in the program is only allowed if
there's some benign execution that
uses it. So if you have a witness
of an execution, Chris, that actually
uses this target, only in this case
will we allow the targeted runtime.
Let's look at two possible
two possible forms of CFI.
First one, contraflow integrity form
with and then without
STAC integrity and
for simplicity.
Let's start the CFI
without STAC integrity.
So let's assume we have the strongest
possible analysis under control flow
graph.
And we have very small sets of possible
targets for each individual
in their control for transfer.
What is the best attack that we can do
under this condition?
Ideally, we will resort
to some form of return oriented
programing, and
if you don't have Steck integrity, the
goal of an attacker is to find some
path to, for example, system
in this control flow graph that we have.
And we have to find a set of constraints
and memory locations that we have
to write to to divert control
flow of the program along this
path, which might be constrained
through the memory vulnerability that we
have.
And as a second step, we have to control
the arguments, the system.
And we might ask
ourselves, what does such a control flow
graph actually look like?
And for a long time we thought
that this photograph is this super,
super complex graph that is very
complicated. And it will be almost
impossible for an attacker
to find a pass from the actual
vulnerability through this complex
graph and thereby controlling
all the different arguments and
constraints to then actually reach
system on the other end.
So the assumption is that it is very
unlikely that an attacker will
be able to find such a
such a pass, but.
What does a contraflow photograph really
look like?
In fact, we found that there's
a large amount of functions
that are basically connecting
and then connecting to control flow graph
between all of these individual
locations, there are
all these functions like MWM, Kopi
Mallock and a whole bunch of of
other functions or printf that
basically connect an arbitrary point
in the control flow graph with another
arbitrary point.
These functions are called from
everywhere, and many of these functions
can override their own return address or
in a later point call a function that
will then be able to override its return
addresses and under the control
flow integrity policy to present it
before. As soon as we find
such a function, we can return to any
of the possible callers of this function
and thereby connect to arbitrary
points in this control flow graph,
because both of these points will call
this function.
And then we can easily find a pass
through the control flow graph from the
vulnerability to the system and then
control the arguments and the attacker
wins.
The dispatcher functions are
often frequently called, and yet the
arguments can be under the attackers
control through the different forms of
memory corruptions that we discussed
before, the dispatcher
functions may override their own return
address.
So if you look at the simple men copy
call where the attacker can
supply to the three arguments we just
use, this meme can't be called to
override to
its own return target and then redirect
and connect these two parts of
the control flow graph.
Basically, we generate a shortcut
through this control flow graph and then
redirect the execution of flow to
the other part and can then continue on
at our location.
And we can
basically supply the attacker data,
which is the other location that called
meme copy, overwrite your return
address, make a shortcut
and then start executing closer
to a system until we can execute
our our code.
So basically, contraflow
integrity without any form of
integrity is broken, these
stateless defenses are completely
insufficient for this stack based attacks
that we present here and
such. This dispatcher functions
are fairly frequent and we can find
different set of dispatcher functions
that are basically being called from
everywhere, both in the in the
standard libraries that are supplied and
used everywhere, but also
in the actual code of the
of the programs.
GTECH now becomes
program dependent.
So while the defense
does not stop the ongoing attack,
it makes the attack harder and
the attacker at least has to find the
dispatcher functions and then find the
shortcut through the control graph.
But the attack is still possible.
So it looks like we are
under some trouble here.
Right. But we still
make the attack a little bit harder.
So another attack
that was presented fairly
recently is
counterfeit object oriented programing.
And this is
not my work and not the
work of my collaborators, but I found
it a very neat, neat
attack that that.
Should be mentioned here as well, and the
underlying idea is that a function
can be a Gachet by itself
and you can reconnect and
tie together different parts, often
of countrified objects, to execute
interesting behavior.
So if you look at this,
this C++ code here where
we define a small class
and we have this
little this structure over there that
is, of course, a virtual
the structure and the virtual
keyboard in C++ tells the compiler,
hey, I want this to be an indirect
call.
And this allows us to.
Redirect execution, because
we just heard that redirect
indirect calls are
the underlying principle for all these
contraflow, hatcheck attacks.
But then again, there is in
this example in this district, which is
iterate through a list of
of students that will all be allocated
and we call different forms of
virtual functions on all of these
all of these objects that we have
accumulated.
Now, assume that the attacker controls
the to list of students
that we have or the area of students,
then the attacker can certainly generate
a set of virtual targets
and can reuse existing
virtual virtual tables
to execute arbitrary
behavior.
And as a second step of
use these different forms of
virtual tables and connect them in
a new way.
And there are different forms of
arithmetic gadgets, there are
different forms of
other gadgets as well, right.
So, for example, in this simple
update score, we've got an arithmetic
gadget that we can combine
and a copy gadgets
as well. Then attacker can just stitch
together.
Also, they presented an interesting
technique that allows them to overlay
objects, remember, the
memory is under the control of the
attacker and instead of just
having each object by itself,
the objects themselves can overlay
each other and
the memory layout for a simple
exam. Objects would, for example, look
like as shown on the on the right hand
side, on the screen.
But the attacker can then use existing
fields and overlay these fields
with other objects and thereby
use different forms of arithmetic
gadgets to, for example, update the
virtual pointer table of that object
and redirected to somewhere else.
And through this object overlays,
the attacker can just stitch together
individual individual gadgets
and then get different forms of arbitrary
executions, arbitrary behavior,
and this looks pretty bad if we combine
these two these two attacks both
control flow bending and countrified,
object oriented programing.
But, well, we do have a large amount
of control for integrity proposals out
there, so we might want to
look at how well they hold up
against all of these different forms of
attacks. And I picked out a bunch
of different different control
for integrity mechanisms that have been
proposed either by academia
or by
Google or Microsoft and
others. So there's there's a lockdown
that enforces the
control integrity policy on top of
binaries by using a form of binary
analysis. So you don't need source code
for that.
There is MacFadyen Pasiphae,
which are two forms of source based
control for integrity that need
a full compile time analysis
of all the available code.
There's Google, LLB, TFI
that has been recently released and is
in the process of being upstream into the
New Year releases of LVM.
And there's also Google FCC,
which they presented last year but have
since redacted and removed from
resource base. And we'll see a little bit
in a little bit why they removed
the FCC.
And there's also Microsoft
contraflow guard.
There are many, many others that
implement different sub policies
of these CFI mechanisms
here.
So just to to give
you some context, when we are talking
about TFI, the
strengths of the defense depends on
the size of the equivalence classes that
we have.
And if you have
a certain piece of code, we have
a set of indirect control for transfers
and these are the instructions shown on
the left. All these instructions can be
used by an attacker to redirect
contraflow from one location to an
arbitrary other location.
Any contraflow integrity analysis
will return a set of
equivalence classes.
An equivalence class contains
a set of targets that are allowed
at specific locations.
And here in this example,
I'm showing three different equivalence
classes.
And the last two
call instructions are using the same
equivalence class. So multiple indirect
control for transfers can map to the same
equivalence class and the same
set of targets are allowed at these
different indirect control flow
transfers.
And then there's the size of
of an equivalence class, which
shows how much
opportunity the attacker has to
redirect the control flow
to different locations, and
ideally, you want to have many
equivalence classes with a
very small size.
The smaller, the size, ideally,
the size of an equivalence
class would be one in that case, the
attacker would not have an opportunity
to redirect contraflow to an alternate
locations,
but at best, if we want these equivalent
classes to be as small as possible.
So we looked at
the precision on the forward
edge. So for indirect call and indirect
Trump instructions and
we looked at the sizes of equivalence
classes and represent whisker
plots where we show both
the median with the red arrow
of the
of the sizes of the equivalence classes,
the twenty five percentile and the
seventy five percentile.
But we also show with these little pluses
on the side, different forms of outliers.
The different
CFI policies are on
the x axis and dynamic
shows the amount of possible
targets are actually required at runtime.
So we executed all these these
software or all these programs and
measured how many indirect transfers
are actually used and how
many locations.
And if we if you look at all these
different locations, we see that there's
a surprisingly high amount
of indirect control for transfers with
a surprisingly high amount of transfers
that are targets that is being allowed by
all these these different policies.
And we see that in the under the
dynamic policy in many locations,
only very few targets are allowed.
And I would like to remind you that
this is a logarithmic scale.
So the higher up you go, the
more transfers are allowed.
Let me just point out a couple of of
interesting things for AFSC
in many locations, the
FCC collapsed all the equivalence
classes into a single set.
So all the possible locations
all in there for transfers are
allowed to use the same set of targets.
Which basically implements
week form off of CFI that
allows you to reuse all the possible
targets, and this is likely
the reason why he
was removed later on.
Also, we see that for
lockdown, the spread is usually fairly
big. So there's an
outlier out there that has a very high
amount of transfer that are
allowed for a specific location.
And this is due to the
problem is the binary analysis where
we require additional symbols
and information about the libraries.
And there's usually one library out there
that does not provide symbols.
And then a large amount of transfers
are possible due to limitations of the
binary analysis.
So you cannot recover all the information
about all the possible targets
in in the code.
There's also McAfee and PC
fine, and we see that for most programs
they actually provide roughly the same
amount of precision.
But at five, which is per inputs, you
find sometimes restricts the
amount of transfers that are allowed
due to an additional runtime analysis
that they do.
There's also the new
LVM CFI, which is
basically the second version of
FCC that Google recently released
and will be part of your
upcoming
album releases, and
it looks like as if it's much
closer to the limit
or the minimum amount of transfers that
are being allowed than the old version.
And it gives you a much tighter security
bound.
So let's look at a summary of
all these different CFI mechanisms
and in the graph before we've only
looked at the forward edge.
So how good is the position for indirect
calls and indirect jumps?
But there's also the backward edge.
And we've talked at the beginning of
of this talk a little bit about the
backward edge and the importance of
protecting the backward edge for
basic control, flow bending.
And as we see the first three CFI
mechanisms, AFSC, Microsoft
Contraflow Guard and LLB, CFI
basically have no protection on the
backward edge except the already
existing protections out
there. And this basically makes
contraflow bending trivially possible
and allows you to reconnect and stitch
the contraflow in any possible way you
would want to do for and
see if there is some protection
on the on the backward edge.
And it makes contraflow bending attacks
harder. And we have to find these
despatcher functions and shortcuts
in this contraflow graph for lockdown.
The due to the
strong protection on the backward edge
that it adds, we have to resort to
contraflow bending on the forward edge,
which makes the attack much harder.
But due to the limits of the static
binary analysis and the other binary
analysis, we can still find possible
targets.
So it looks like as if the set of
protections that are being proposed and
are being integrated in current systems
are fairly limited.
They do make the attacks harder.
And as soon as we add
Steck integrity to the mix, they will
make the attack attacks much harder.
So if you do have steck integrity,
we can greatly increase the protection
of current systems.
But as we've shown before, many of
the systems do not have STAC integrity at
all.
So let's just for for a second assume
that we do have STAC integrity,
then return oriented programing
is no longer an option and attack
becomes much harder.
The attacker now needs to find a path
through the set of virtual calls that are
out there and resort to some form
of restricted coop.
And the attack becomes really hard
and interpretor would
actually make attacks much simpler.
Assume that you have some piece of code
that you can inject into the
executing image of the application
that even under the constraints
of full, full control
for integrity still allows you to
execute a different code.
And you would be
surprised by how many Turing complete
interpreters there are, and
I would like to introduce you to
printf oriented programing
where we'll show that printf
is fully Turing complete
and we can translate any arbitrary
program to a format string.
We can do memory reetz.
We can do memory, right, and
we can actually implement conditionals as
well.
The program counter becomes
the counter in the format string, and
we can just move the program along.
If you want to do loops, we can actually
overwrite this program counter
or this format string counter as
different parts of the format,
the string are being printed to the
screen and we can reset and jump around
in this format string and then readjust
our program counter to implement
different chumps in this
in this abstract language that we that
we implement.
And we are presenting a Turing complete
domain specific language.
And to make things interesting for you.
I wanted to know, have you ever heard of
Brain Fuck?
It's like this fun Turing complete
language that only has eight
different symbols.
You can you can move a data
point or forward.
You can move a data point or backward.
You can increase the value
on the current cell.
You can decrease the value on the current
cell. You can write a character to the
screen, you can get a character and
you can jump conditionally back and
force.
There's a former string statement for
the incrementing the data pointer
diclemente the data pointer, adding,
subtracting, adding
character.
And even going back and forth,
depending on the
implementation of PRINTF, you can
overwrites this format, string
the format string counter and reset and
jump around in the in the format string,
which is what makes it
dependent on the Lipsy version that you
currently use to make things a little bit
easier. We assume that
we can we can call printf
in a loop. So we basically either
have to look at the current
implementation of printf
in the lipsey and overwrite this printf
internal data Vali's by using arbitrary
memory. Right. Or we just have to find
a loop that loops printf
in the current program,
which we can usually do
by in
many of the programs that we looked at.
And we can, we can break the
brain fuck interpretor into a simple
fach, execute and retire
loop that keeps executing
by just calling Printemps one
after the other.
So let me present
to you a short demo
on how we can translate arbitrary
brainwork programs into print statements
and then execute brain fuck by
just doing a call to a single print
printf.
So I have a set of.
Small brain
programs.
And let's just start with
the Fibonacci thing, this
this is a small brainwork program that
just calculates Fibonacci programs.
Let's compiled the program
and we do have a.
Small.
The screen is a little bit small, but
I've shown the GetUp page that
you can download the programs
yourself and try them out.
So we do run the printer
Fibonacci program in a single
print statement, and we are generating
different forms of Fibonacci Fibonacci
numbers and this is just a print
statement that we keep executing
and we keep executing this this brainwork
program to just generate numbers.
But there's another fun program that's.
There's another fun program ever heard
of.
Ninety nine bottles of beer.
It's a little bit longer program.
So let's just execute
this this program and see what it does.
And interestingly, you see that it gets
faster as you're drinking more and more
beer, as you're counting down the numbers
and printing out the values, it gets
much, much, much faster.
There's also a game of life in there
that you can play around with, there's a
Mandelbrot fractal and
a.
Sarah Pinski trying all that, you can
print all part in a
in a single print statement.
So there's there's a lot of fun that you
can do with this this interpreter
and it's open
source, you can use it, you can download
it, you can play around with it
and.
It is available from
today on, so go around,
play with it, play with detouring
completeness and.
Let's see what happens.
So let me conclude,
low level languages are here to stay
and these low level languages are full of
potential.
And as I've just shown, even with a
single call to printf that
can either be executed
in a loop or by
looking at the printf internal
implementation, you can wrap around
and show the full potential of
these low level programing languages.
And Bizerte machines are hiding
everywhere out there and there's a single
memory corruption.
So if you do have either a formal string
vulnerability or a memory corruption that
allows you to control the first
argument to print, if you can get
a Turing complete interpreter that allows
you to inject your own little program
without Steck integrity, the defenses
that we will add are broken and we have
to add STAC integrity, but
even mistake integrity.
The attacker can still
still do a lot of fun stuff.
And I encourage you to play
around with Vista code that we've added.
There's a simple Python script that
allows you to translate
the any brainwork program
into a longer
format string and a simple program.
The format strings are out there.
Go around and have fun.
Thank you.
Thank you. Yes, we have some minutes
for questions, if any, so we got
some microphones here.
Any questions?
Are you OK?
So go home, play with Prenter,
OK, I'll be around.
I have.
Pleco.
Yes, hello, hello is working,
OK, so think of it.
Very good presentation.
I have a small question.
You use person turn in your printer for
programing, but in some implementations
it's disabled.
Do you have any workarounds?
There's only person and that gives you
memory writes.
So the in any of
the lip that we've seen, it's
enabled so that there are obscure, in
fact, that Microsoft is disabled by
default, you have to call a special
function to enable it.
Yeah.
Thank you.
Please leave or stay silent.
Thank you.
Yes, so I was wondering, I recently
saw some discussion and you go closer to
the microphone, please.
Lolo, can you hear me?
Yeah. So I recently saw some discussions
about, um, on the mill
about something called Safe Steck
where they.
Yeah. I'm one of the officers.
Oh. OK. I was going to ask your opinion
on it but it's good to make it, it's
great.
There's, we released an earlier version
at OASDI. I said the current
version that made it into LVM is a
little bit restricted and it's
mostly geared as a as a debugging tool.
But the underlying idea is to
give you a tool that allows you to
predict Steck frames and give you STAC
integrity without any overhead.
So I think it's great.
It's a it's it's a it's a great tool
and it should make it into LVM
without all the additional overhead.
Yeah, I think it's awesome.
So thumbs up.
Thanks.
OK, thanks again.
Yes.
See you soon. So we're back here in 14
minutes for console hacking.
Thank you.