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/94 Thanks!
OK, now now we're going to start,
so on that three next speakers
are Sergei Brutus
is a research assistant professor at
Dartmouth College.
Next is backs.
She's a Ph.D.
student, also at Dartmouth College,
and to the right is Julian
Baggett. He's a student at MIT.
And they all specialize in finding
machines where there shouldn't be any
machines. And sometimes sometimes these
machines turn out to be Turing complete,
and they're now going to show us
their latest work.
Please give it up for trusting
trust for binary tool.
Hello. It's great to be here.
We're going to build on
our previous series talk, which
some of you may have caught.
If not, we'll catch you up.
And I
am Sergei. We're from Dartmouth.
Julian was from Dartmouth as well, but
he defected.
Traitor.
OK, so
this is what we're going to talk about.
Imagine, if you will, that we
could make bugs disappear
magically for some definition
of disappear, for some definition of
bugs. Would we still be able
to or will we be able to build chains
of trust that wouldn't suck so much as
they do now?
And we're going to explain why not
and why the standard
practices of constructing chains of trust
these days have to change before we
can actually trust
them. And if you want
a tongue twister to go with this as a
take home and bagless, Babel breaks
badly.
Right. Because this is going to be about
the Tower of Babel and
the problems that you get when you have
things disagreeing on components of
your trust chain, disagreeing on
dialects.
And we're going to show you some case
studies, in
particular el signing and
a bit about.
And then we're going to show you how
the Linux kernel disagrees with the
Linux loader about
what is where in the loaded image.
Isn't that fun?
I'll start with theory and then handed
to books, and then she'll handle to
Julian.
So 1984
Ken Thompson in his famous reflections
on trusting trust.
Almost 30 years ago, you know,
29 that Watson of Buy One
came up with this maxim
that you can't trust code that
you didn't completely write yourself.
And he planted the bug in the compiler
and the compiler,
having compiled whatever was built
with that trust chain, propagated the
backdoor into everything and compiled.
And so you shouldn't really trust your
compiler.
He went on to predict the future.
And by now, everything
that he predicted came true,
including well-placed
microcode bugs.
And really, these days,
when you look at software, you wonder,
well, which software doesn't have planted
bugs right
of certain organizations has something
to do with it. But honestly,
50 years ago, I never imagined
that I would ever encounter
a deliberately planted bug.
And yet here we go.
But what if
we could wave a magic wand and make it
all go away?
What if we could completely trust the
author?
And what if the author could completely
trust him or herself
to have the code do
exactly what the author intended?
Would we solve trusting trust them?
And the answer is
Hell, no.
And the reason is that
our trust chains are
built up and up and up,
and all of the components of those trust
chains must talk to each
other and agree on what they're saying
and agree on what they're interpreting.
This is what software engineering
pretty much looks today,
right?
If you need the
narrative biblical narrative
reminder, people
decided to build a tower to reach
the sky.
They, the
powers that be did not like that.
So they made
the dialects of the builders diverge.
And suddenly, if
one builder asked for a rope,
he was instead handed the sole
or the,
you know, somebody said, Give me a sword.
And the other person heard.
Well, your father is a donkey.
And as you can imagine, the construction
didn't go well afterwards.
So if we're to explain it with cats,
right? The components of the
trust chain must
speak precisely the same dialect
of the inputs
or else you are going to have.
Well, we're
going to call this the Babel cat.
You know, the ceiling cat,
which is of your moral health.
The Babel cat
isn't your trust chance.
Well, let's make
this a bit more theoretical before we get
to the practice.
What is a chain of trust?
A chain of trust is really a chain
of execution environments
of increasing power.
You start at, but you execute
some code, you load your OS,
you execute some code and
eventually you want to get to the
executable that you want to run,
construct a process out of it and
run it. And hopefully throughout
that entire process, no
unexpected computation
otherwise known as personnage occurs.
It's the same code.
It's the same bytes that are being
interpreted by a chain of passers
and handled by a chain of loaders.
And so the meaning of those bytes
has to be completely agreed upon
between all of those truss
links.
Now, when I say trust,
what exactly do I mean, right?
How do we trust?
Why do we trust things?
Why do we trust bikes
to run them?
Well, you know, there are two approaches
to this.
You can't analyze
Turing complete binary code,
which is, of course, what you compile
down to.
And so instead, you
sign it.
You trust the process of
your developers, you trust their good
intentions, you trust their
scopes and so on.
And so you get this frozen bit of
code and you hope
that it will be immutable.
Or there is another way
you can actually check the code
do static on the list is of it.
Convince yourself that the
execution of it will be exactly as
intended.
It can do that as well.
And then
you can trust it because you've analyzed
it. It doesn't work with General
Code. No matter what, NATO
virus vendors will tell you how many
times a day they sold the halting
problem, you know,
so.
Let's see which kinds of trust
are are interested in, and the answer is
both. Right?
You freeze some bits
in your binary, but you also
want to do composition.
You also want to have libraries.
Laudable modules combine
sign code from different sources.
So in the end, you were
trusted. Binary ends up being
this kind of a sausage thing with
some bits of frozen and hard
and some other bits telling you
where in that sausage,
the trusted bits the frozen
bits are.
So enter
stage right or stage
left tables,
tables that describe whether
the sign pieces are and
where those signatures are.
And you know, a table is a table, right?
The table is very simple.
You can
put the stuff at full of offsets, right?
Or you can even put some data
in there about how you replicate
the code, how to patch the
code.
It's still a table, right?
So that makes it asla
possible.
But really, if you don't, if
you do composition, you can't avoid
having tables.
But in fact, any input
is a program and any
table is a program as
well.
So what happens there is here's
your table, you know, periodic
table.
This is probably the the top
of my graphics abilities, so
please bear with me.
Right? So it's a table
that describes the elements of
the sausage or the trusted sausage
of the composition
of the binary file.
Well, I could I could be saying API
application binder interface.
But what happens is
there are pieces of code.
You can think of them as pieces
of logic that are
baked into the processor.
Well, except they aren't.
They are in your loader.
They are in your kernel.
They are in your RTL.
And these
pieces of code consume the tables
just as
your processor consumes its
inputs.
Now, hopefully, that logic is simple
enough so that your computation
doesn't get in the way.
But tables drive computation
just like anything else.
Tables are, in fact, something of a
bytecode.
If you think of how you
compute those offsets and
locate the signed pieces and
their signatures, well, you're doing
arithmetic, you're doing branches,
you're doing loops.
Are all of these things are known
if you are unlucky.
To give you a Turing machine
and Becks showed such a touring machine
on relocation tables
last season, you see.
So in fact,
any input is a program that's just
a matter of principle.
This is what we stand on.
Any sufficiently complex input data
is indistinguishable from bytecode
driving some virtual machine.
Oh, if you see that, do this.
If you see this, do that.
That's how you write a VM.
A partial code for any sufficiently
complex input is indistinguishable
from a virtual machine running its
inputs.
So what happens there is that what
you think of as input validation
is this table valid
is actually something of
runtime verification
of the core of the bytecode that
are your tables, that are your metadata.
So what can go wrong?
And of course, your partner may be buggy.
Your partner may overflow memory
corrupted.
And then
some other piece of logic will take
the corruption and run with it and run
away with it and with your
trustworthy system as such.
But the input can be completely
well-formed.
But as a matter of software engineering,
so complex that there is no predicting
what it would do.
Whole thing problem, Turing completeness,
it turns out that even there were
locations and symbols.
Tables have that
or the input could be seen differently.
The Babel effect and
different pieces of the trust chain would
disagree, and the trust will be lost.
And we'll show you both examples.
So just.
And oh, by the way, these are the three
cats of the apocalypse.
Right.
You know, and we're going to
be looking at the two cats, you know, who
get the gold star and the
red star, the well-formed input
that's so complex and the parts of
differentials.
And so, you know, you think you're doing
software engineering, you think you're
doing code reviews and things like that.
What you're in fact doing is you're
getting the Turing cat out of the bag.
Right?
And the cat then turns
out to be quite an impressive
cat.
You know, medieval bestiary
writers knew their cats well.
So here is your runtime
after boot.
Boot process has the same issues,
but here is the original Ken
Thompson Ken Thompson's planted bug.
It was on the compiler.
Then, of course, comes the linker and
then the loader and then
the dynamic linker.
If you are for code reuse and
the array locator,
the dynamic here locator machine.
And then of course, there is memory
management and
pitch faults and double faults
if you're unlucky and exception handling.
So this is sort of your process from
birth to grave.
And
just to remind you, we found that
these pieces that have the
gold stars are actually Turing
complete on their inputs.
So if you feel them crafted
tables that are complex enough,
you can actually compile any computation,
any program into those crafted tables.
The favorite of
especially favorite of mine is
the weird machine
in the
peaceful tunneling logic,
because people handling logic actually
not only reads from memory and is
triggered by read from memory or a right,
it also writes to memory itself.
She can close the loop and
keep your processor doing
whatever it's doing to kindle a page
fold alternating between the page
fold and the double fold as a tick
of the machine.
And while no
execution instruction, no instruction is
getting fixed successfully,
you're continually getting the illegal
instruction and the
oh sorry, the page fold.
You can still compute anything
you like and write
interesting patterns into memory.
Julian, take a bath!
Julian wrote the compiler for that sort
of thing.
The
the dynamic
curve locater right byxe
take of all
the checks are implemented.
The language called brain fuck
and the compiler into that language,
which is known to be Turing complete.
You know, some academic has had
an unerring sense of taste.
Probably
so. But you know, term, can
you do?
The real accounting engine
that builds up your program and memory
is actually can be convinced is
actually Turing complete can be convinced
to compute anything?
Here's a new thing that we're going to
show you.
The RTL Rd
and the loader can be made
to disagree about what
is in the program.
They can see completely different
pictures of memory that they are
constructing or have constructed
for the runtime.
And that's, you know, a second
kind of cat of Do
you will find. We finally wrote this up.
I mean, the first we gave it at hacker
conferences because hacker conferences
are just about the only place where this
kind of thing is appreciated.
Because why would you really want to know
what's in your machine and how to
you know what sorts of extra
logic can you squeeze out of it
when you know you can so
much more interesting problems, such as
how to have graphs that look like.
Yeah.
Anyhow.
Anyhow, so what finally,
we published those papers
in court.
After having been rejected from
some high and mighty
other avenues.
And there will been more coming.
So with this mind.
Tables are programs
they run on the pieces of logic that
interpret them.
So when you are
validating your inputs,
I mean, God forbid you sanitize your
inputs. This is an anti pattern.
This is absolutely the worst thing you
can do.
You know, the idea that, oh, there are
bad things in the input, but
you can suppress them by checking for
them piecemeal.
Oh, you know, this is this is just poat.
This is this is wrong.
This is poll.
But
validation is actually
the same as verification
because that data
causes computation.
And you don't think of it
that way. You don't think of it as
program verification.
You know, there have been talks here
about for millennia Alsace, but in
fact, it is, and the validation
of tables is in fact
static analysis of the computation
that they induce.
And even in the absence of bugs,
you can have a rather
complex analysis or impossible
analysis to perform so that
you will be trusting your tables when
they shouldn't be trusted and
you will be solving the antivirus
vendors holding problem
when you are and you
know whether the the
result is somewhat predictable.
So with that, let's look at the specifics
of the machines that execute tables.
Yes.
Yes, there. There we go.
So with this little hiccup,
I try, I have a microphone.
Oh, you
have a microphone? Excellent.
Go ahead. Now we can talk at the same
time.
Prepared students.
Code signing is
definitely being worked into the trust
train more and more, and we're seeing it
and say in certain environments are
becoming what walled gardens?
But we're also seeing it popping up
more and more, and it's open source
Linux for kernel modules,
drivers signing and Windows, et
cetera. And it's
it's use being used to provide
trust evidence and that this particular
binaries not only
has some sort of integrity and it hasn't
been changed since, you know, signed
since the developer distributed handed
to you. But we also are able
to have some sort of attribution
so that this binary really came from
these people.
And it's, you know, those are two
good things to know about a given
binary.
It's easy to implement poorly
because code signing, it's not
just an algorithm, it's not just finding
the right way to hash the ET,
it's a lifestyle.
There are so many components
involved in code signing and to have it
done well and to really
be able to have a good amount of trust
and evidence from code
signing. You know, something verifying
we need to maintain
we have to have a proper key management.
And there's other things the program
that is is loaded in memory that
we really want to know is trustful
and trustworthy is not the
same as the one on disk because
we like to write things
that are portable to different
architectures or randomize
able to help with other types of attacks.
And really, the program that's in
memory is merely influenced by
that which is on disk.
And then so we have to ask the question,
does code signing really give us what we
want it to give us?
That this which was handed
to us is what we'll
be running and we are feeling
trust. We feel like we can trust it
because we know who
gave it to us and we know there's
some sort of integrity to the
binary.
But there's many machines involved
in verification of
a particular binary code signing.
We have to worry about passers,
interpreters, slash translators,
validators. There's all these things that
we bring together and
we end up with some sort of like Rube
Goldberg machine that we
it's not even though it's different
components of the machine will
of a good sign will actually
put together pieces of data.
The two hands the next one.
And so there's many different components
working together.
And we we have to think about how this
how this really fits and how we can trust
the different components and trust
composition because different
security doesn't compose particularly
well.
We have to think about it.
So when we are considering
code signing, there's a bunch of
questions that come to my mind when I
want to look at a system.
Are these machines that implement code
signing correctly implemented
bug free?
But even if they're not, do we understand
what these different machines are capable
of? We've shown that metadata
for elves in the last year's
talk that I did is capable
of of running Turing
complete or pushing Turing complete
processes, making the loader do something
that we really don't expect, necessarily
if we have two different machines
operating on the same data.
Do they agree on how it's passed
in and how to interpret it?
Do do the tables that we are
basing these computations off of.
Are they correct or are they complete?
They really portray what we
need to know in order to trust
the code the signature.
Can we trust the transformations that are
done to the to the process once it's
loaded into memory?
After we do this sort of static analysis
and look at these frozen bits and, you
know, pray that these like squishy
bits that we can change or
change it in in the right
way. And then another big question is
enforcement, which I won't cover
at all, because that's that's a whole
thing. What how do we want to enforce
enforce validation?
I looked at a bunch of different
elf code signing mechanisms, many that
seem kind of obsolete, but this is a case
study and I hope we can learn a little
from what has been done.
And in thinking about
the code signing and elf, we we
have a bunch of components machines that
are working together. We have partners
that pass out this signature metadata and
other data and the file.
We have interpreters and translators such
ones that maybe may look at
the binary and produce a bunch of hashes
based on that so that we can start
looking at signatures.
And we have validator machines part of
this whole code siting process that look
at certificates and signatures hashes.
There's a lot working together
and there's Rube Goldberg machine.
So I started looking at different
implementations of code signing and elf
for both executable user line binaries
and kernel modules.
And I must say those they have
very creative names
for their code site.
I actually had trouble telling a lot
of them apart and figuring out which
were different, and a lot of
these were actually written in 2003,
2004, 2005 are pretty obsolete.
I'm seeing I started seeing
a resurgence of work and especially more
in the kernel module area.
But, yeah, so a lot of what I looked at
was is kind of obsolete, but it is
interesting. So questions that
I asked before I'm going to revise it
right now are the machines
involved with this code signing correctly
implemented
in Marco.
There's actually XML embedded
into their code signature.
And we know how how
easy it is to parse XML and
how well implemented XML passers
are
certificates and
anything that's signed as often
encoded in s and one BR
format. And those are,
yeah, those those are easy to pass and
everyone will agree on the correct
parsing of a particular certificate.
There is no problem there.
And in Elf, I saw
that everything was written
in either c actually everything.
But one thing I looked at was written in
C was one thing written
in C++ and those are safe languages.
I'm sure there won't be any other bugs
that are exploitable because of that.
How powerful are machines that
take part in this, the signing
process?
I think there's a work that we need to
really do in this and just thinking about
it old at our old work,
things that just look like tables like
relocation metadata we found to be
to drive this unexpected computation.
So if we're able, we
want to make sure we understand
what the components of code signing
are able to do because
we, this is old, work in
putting backdoor into
ping that drops a root shell and on
the top itself and the green is
data that was added to the file.
And there is a couple of other changes
made to the file.
The bottom is the same exact
backdoor in Moscow and the red bytes.
Those were changed.
Now, no other changes.
Nothing else was added or removed from
the file.
So it's actually
I I would I would argue
that it hiding computation and
metadata.
It can be so you can see
it. But it's also it can be
hard to see, especially if two different
interpreters pass the metadata
differently.
And we so that's something we need to be
careful with and code signing.
So I also started looking
this is code from various different
code signing for Elf
implementations I looked at.
So the question is, can
can what we're looking at, can the
metadata that we're looking at that's
involved with designing the tables that
are driving our computation?
How much do we need to trust?
In this particular example, this is from
be signed.
It's doing a lot of checks, but I love
this comment right here.
It's like, I don't recall why we need
to check more than just the elf header,
and the author goes
on to check different headers as well.
But the interesting part is, the author
thought I could stop here and I think
we'll be OK. But there
there's more to be considered and how
much data the check.
And I am a see this as a red flag.
We need to think a little more while
we're developing code signing
if we want to actually be able to
trust what comes from it
in passer differential.
So having two different dialects,
two different passers for the same data.
So in these particular
and one in PR and coded
messages for it
or for signing
can be a little difficult to decode.
Elf has multiple ways to interpret,
and Julian will actually get into this.
So I will just say that there's multiple
ways to load an elf
and there's multiple ways to locate a
section. So if your signature is
in a single section,
you need to be sure that everything
that's looking up the signature is
looking up the same signature because
it's possible to put in two sections with
the same name.
And this particular piece of code
was a.
It was a proposed patch to the kernel
for module assigning, and
it's just looking for the signature
section by looping through all the
segments and stopping at the first one
that has the correct name and breaking
out of that loop. And I've seen other
implementations that will loop through
all the signature sections and remember
the last one, and we need
to be careful with these differentials.
We need to think about completeness
and correctness of record signing.
This is an example from Elf GPG
and his comments only look
at the interesting section, so it's
just if it has,
let's see if it's of type know,
then perhaps it won't get loaded.
And then let's see skip over the sign
sections. OK, well, I can name
other sections.
I can give other sections the same name.
So is there something else in the code
signing that's preventing me from
inserting new sections?
So there's a larger picture we need to
look at and with more with
correctness and completeness.
Signing the signatures who watches the
watchers there?
But these signatures are still loaded in
memory.
And this particular example from Elf
Sign, it just assumes that the last
segment in the file is
the signature, and it won't try to hash
that and for the signature at
all. So does that leave us
an interesting way to sneak in more data
that gets mapped to memory?
So a little bit about
Marco, could I am so lucky
to get our graphic designer
to to draw up a
nice
sketch sketch up what a code is Blob
looked like that that are embedded at the
end of Marco files that you could
signing.
It's slightly complicated, so I
simplified it a lot
to give you an idea of all the passers
and interpreters that are required
for this. So in Marco code signing
Blob, there's a bunch of metadata.
There are something called internal
requirements, which is actually encoded
in this bytecode.
And what I wrote here is a
text representation of it, of
what might need a sign, the interpreter
if it's a Perl program and what
needs to sign the certificate.
But these are encoded until by
code with strings and et cetera.
And it's a hand-rolled interpreter
entitlements.
Our next email and I noted that before
that, we all perfectly know how to pass
XML and there's no problems with that.
But yeah, and
so there's also a bunch of hashes, and so
those need to be passed out.
That's another table.
And finally, a nice encoding
of a signature. So there's all these
different components that are working
together that we must must trust.
And yet there is so much room for persons
that disagree.
So to take away from what I am thinking
is you can't trust
her that you did not totally create
yourself.
But you also have to think the
environment in which loaded it all
everything that signing all the
relocations and everything because that
which is on desk doesn't match that
well, that's loaded in memory.
So the corollary is you cannot
trust Canada. You did not totally load
yourself.
And with that, I will let Julianne
continue.
OK. Hello.
Now, for the third part of the talk, we
bring out the third cat of the
apocalypse, which takes the form of an
elephant that stomping all over our trust
chains and also our trusted China.
And that Elephant Pass differentials
a differentials break, chains of trust,
chains of trust and multiple elements
that have that implicitly trust each
other. Now, if various parts
in the binary tool chain see different
things, that implicit trust is violated
and can or can be made to interpret the
same data differently, and the trust
is implicitly violated.
Historically, there have been issues such
as with X five or nine SSL certificate
suspects already mentioned that I
might get the Certificate of Authority to
see a subdomain of Julian Bango dot com
being signed. But when your browser looks
at the same certificate, it actually
sees a certificate for PayPal dot com and
then you might run into an issue.
More recently, there have been issues
with Android's code signing our Android
Master key vulnerability, where you have
two separate policies for the
same zip file that contains an Android
application, one of them implemented in
C, one of them implemented in Java,
and the designers of Java did not believe
in unsigned integers, whereas the various
the C implementation uses unsigned
integers.
Then suddenly, well, the the
signature matches when it's installed.
But hey, you can run something completely
differently when you're actually
executing the thing.
The same also happens on Apple iOS
as the invaders demonstrated in the iOS
six jailbreak.
Different components within the tool
chain can see the same binary
differently and therefore you can get you
can sneak past the code signature scheme
in that case. And as specs demonstrate
it, the same happens for the Linux kernel
with their kernel mode's module signing
schemes.
And what I'm going to talk about is it
also happens in user space.
We found a positive differential between
the in kernel positives
part of the exact system call and
the user space dynamic loaded.
It handles things like actually setting
up your address space and loading in all
the various dynamic libraries you have.
And little quick question the audience
into the audience, how many health passes
are involved in your typical program
execution?
Does anyone know?
Indeed, well, that tends to be at least
two, depending on what your system, while
you might have a lot more, there's all
sorts of things from kernel modules
onwards, but the two that we are going to
focus on is the elf loading
the kernel in bin form, former elf
and the user land LDA so
loaded. That's part of the new C library.
And in order for there to be any sort of
trust and in any
code signing schemes, all of these elf
pulses have to see the same few of
the sections in the segments and have to
interpret all of the metadata that they
rely on to enforce their trust.
Exactly the same way.
And now, when these two policies
implemented by two totally
separate entities and don't share any
code or don't share any
formal grammar that can be used to prove
the equivalence.
It's pretty unlikely that they'll
actually see the same information in that
sequence in just the right sequence of
bytes.
The particular example we're going to
look at today is about the
Elf program header table.
In particular, the Peachey Load Entries
50 Load is there to map a
range of bytes from the file into
the address range of the program you just
want to execute.
It describes the various code and data
segments in your program.
Internally, the implementation of
the exact system called just converts
every load entry in that table into a
map system called.
That's what the invaders noted as part of
the iOS jailbreak that
a memory map at a fixed address replaces
any previous mappings at that particular
address, which means they could remap
an address that had already been
validated and certain other
parts of the tool chain would just look
for the first entry mapping.
That was really the kernel, which was the
last one, which is, if
you're looking at the past, is not
immediately obvious because
they actually are impulses for a table,
the automaton that are executing on the
data that's present in that table.
And unless you're thinking of them as
automata that have mutable state,
it's unlikely that you'll catch all of
these bugs because the spec
note mentions no way that the order of
these entries is in fact important.
In order to drive home the point, this is
the actual politics, the Linux kernel
source code as of three point four that
actually processes peachy
load entries in an L Files program hit a
table. And this is not really look like a
parser. There is no grammar.
There's no BNF.
There's just randomly casting
around pointers and having stuff.
And a particularly interesting bit that
I'll get to in a second is this
is this line here.
So the the typical layout
of an L file is that there's the elf
headed at Pax already described, followed
immediately by this program.
Head to table In the program head a table
describes how the alpha is supposed to be
mapped into memory.
And in order to execute the program, the
colonel first reads this program at a
table into an buy into a buffet,
allocates performs one memory map for
each payload with the awesome pulsing
source code that we just saw.
It then finds out if that program
is dynamically linked, which nowadays is
true for just about any binary and loads
the dynamic loader from
the file name the present in
a special section.
And then in order to tell the dynamic
loader where from where it's supposed
to start parsing the file, it has to
give it the virtual address where the.
Where the program hit a table resides
because the Linux kernel has access to
the acceptable file as a file, it can
read the bytes there is the user space
loader is actually already mapped into
the address space and therefore can only
access to virtual addresses.
So the kernel has to bootstrap the
translation between virtual
addresses and ranges of bytes.
And it does that by looking at
the address where the first party
load was mapped
and adding the file offset in bytes
to that address.
And it ends writes it into a special data
structure called an ox vector, where
the user space loader will then look and
begin passing the file.
If.
The post had been written in the same
war type, safe language that would
probably not have compiled because it
turns out you're adding a file offset to
a memory pointer in the
usual case of any binary that comes out
of CC or clang, or whatever your compiler
is, which always places the program
in a table in the first segment.
That works perfectly fine because
really the mapping is perfectly linear,
so you can add file offsets to virtual
memory addresses as you please.
However, if someone were to be
a little less nice and a little
less orderly and place, the program had a
table somewhere else in memory, the
kernel which is reading it as a sequence
of bytes. Oh, well, so the offset
in the file is a bit larger, so we have
to speak to a different sector and disk.
No big deal.
However, then when it adds that suddenly
the pointer in the X Factor is pointing
to some other garbage.
If you craft things just right and you
can, because if you're controlling the
program at a table, you can point.
You can map arbitrary memory at arbitrary
addresses.
Really, something else will be
interpreted as a program head a table
that no other tool will
see as such, which
of course, is quite a nice thing
if you want to hide a binary or hide some
other things from reverse engineers who
tend to poke into your code.
So if instead of having the normal
layout, you have
this particular there where you place the
program at a table at the very end of the
file and you craft
things just right and you make the memory
layout just right.
You can make sure that the user
space loader will actually see a program
at a table somewhere in the middle of the
data segment that could have previously
been some unused padding.
Or you could even make a new segment with
it. If you still want to, or
if you have one of Max's code signing
schemes, you could point it to the middle
of a BGP signature.
Oh, and it was should.
And in order to demonstrate this, we
cooked up a little demo.
A fun fact for assigned it.
We're going to use and this is that in
Linux, you can actually execute a
library.
I don't really understand why, but if
you have the next term, you can try
executing, see an intellectually print
out. It's compiled data and a few options
and give you an email address where you
can bug them about bugs.
Oh, and if you do
that well, it goes through the normal
kernel policy for Exec.
and then passive of control to the
separate Tulsa that's also living as part
of Lipsey for the
user space loader.
However, when you're loading that same
file as a shared library from another
process, the kernel never gets to touch
it. The kernel never runs for shared
library, and it
will only be loaded by LDA.
So which in this case will get a
consistent view, which might, which
will be completely different?
And now I hope that the demo works
properly.
Demigods.
Pray.
Yeah, OK.
I was someplace we first have our shared
library.
It's relatively straightforward.
It just calls another function.
I'll explain in a second why,
and that other function normally comes
from a file lib good dot
c, which does nothing but print Hello
world.
And in order to demonstrate this awesome
asset library that link licenses
for for something that I can't disclose
except under NDA.
And apparently that's a type anymore.
We just have a little sample program that
will invoke that function.
And if we do it, indeed everything as
well.
We have Helloworld, we can even trace the
execution we see, OK?
It's loading
lib good acid when it's loading lib
pocket, so everything is perfectly fine.
However, now when we're executing Lib
Pak'nSave directly,
we tend to get this nasty little bugger,
which is a shell which people might not
have expected, because maybe your device
isn't supposed to have a shell binary
running on it, but some nasty engineer
compiled stuff and we weird way.
And actually, if you're running on
Debian, you again might want to figure
out what is going on.
And you see, OK, what binaries, what
libraries and linked into lib POCSO?
Oh, it's just lib good.
And seeing that you
can then load everything up pro and
you'll see, OK, it's just going to move
good and that's looking to look good.
So it doesn't even
mention a bin shell.
So how the hell does it fork something?
Well, it turns out that because it uses
these crafted headers and the source code
for the alpha manipulation is available
in the good Pesta manual.
The Frog's International Journal of
P.O.S. would get the fuck out
um, which is available at your local
samizdat retailer.
You'll notice that, in fact, and you can
do this if you actually take the pain of
debugging the thing and then looking at
maps, it will actually link to evil
S0, which contains the shell drop.
Because the user space lotus is a
completely different program, head a
table from which on I can point it to a
completely different dynamic table, which
allows me to completely manipulate what
the binary is doing.
If I wouldn't want to load another shared
library, I could also use the tricks that
Bax has demonstrated and many other
people say escapes lo
create to completely rewrite
my binary at runtime
and just embed the code directly into it.
But for the purposes of having a clear
and understandable PC loading a different
shared library is
just as easily as a side note.
If you happen to run this on a more sane
system and not Debien
led will actually say on arch
led will actually tell you that it's
linking to enslave evil because
as some side effect of, I think
and say, Linux implementation
it actually invokes,
the shed actually invokes the user space
loader as an executable, which is an
alternative way of executing a
binary without having access to an exact
system call, which
means that the debugging tool actually
does not debug what would happen
normally.
So in this way, you can quietly hide code
in a shared library.
And feel free to play around with it as a
few others, I'm sure there's many other
passing ambiguities in Elf.
And with that, I'll hand it back to see.
And as I mentioned, the idea of
loading a shared library is really just a
trick for privacy.
If you want to, you can use the various
tables that you now have control over and
that you now have successfully hidden
from all those pesky IRA wielding is
to rewrite your binary at will.
Well, so
just to cover the last point.
Imagine, I mean, so OK, we've
used relocation entries to completely
rewrite an image
that is loading, and whatever
you think you're getting is absolutely
not what is in your runtime.
However, relocation entries are powerful.
They are there for editing
the loaded code for
patching the road load of code for
rewriting addresses.
So that's cheating, right?
How about just symbols?
Just dynamic symbols?
Just a dynamic symbol table.
Turns out with this trick, we can't
because I can pretend
that all of your program
is the global offset table.
A global also table is something
that the dynamic linker writes
into, because
this is how this is where the address
of the resolved function by name.
Say you want to print, OK, are
you? Your first jump will
be into the stub
through the global or sub table,
which will be pointing at that
stub. And then the stub
will bring you into the dynamic loader,
which will resolve print by name.
Load the library well,
you know Lipsy, and then
it will overwrite the global.
Also table entry pointing
there. And so your next
jump. Your next call to Prince
will go straight to the
library to the resolved symbol.
So now I can pretend that
my entire program is
got.
And yes, that will take a rather large
assemble tables.
But you
know, we can fingerprint pimpin memory
this way.
So to conclude?
Channels of trust are something
that we absolutely want to have.
All those little steps,
little links in them, such
as loading,
signing, checking
signatures.
Anything that involves finding
the pieces that we trust
and we are the pieces that we trust,
other pieces to go
by and
contain evidence,
say cryptographic evidence that
they have not changed.
They have not been tampered with.
All those steps are as important
computationally as
the original as the
original development process.
And even though you may have no bugs
there whatsoever.
You will nevertheless
likely have disagreements between
the links in the chain of trust, and
you will likely also have
a rather powerful
software engineering
hacks like
relocation that
in fact.
Can rewrite your program and
will deprive you of the ability
to try to apply the first kind of
trust. I analyzed it.
Therefore, I trust it.
And of course, you can start trusting
starts signing everything.
But you know.
Composition is the soul of
software engineering, so
you will at some point face
the necessity to combine
things programs out of pieces
of code.
So let's come back
to trusting trust, reflections on trust
and trust.
Even though there may be no
bugs, the bugs it was never
all about the bugs
are sure.
Memory corruption is fun.
Sure, you know
shellcode is lovely,
but partial differentials
are equally important.
Agreement in the dialects
of the parsers that are
the partners in the chain of trust
is equally important.
Complex data formats that
do not allow you to check things
out for the first order of trust.
I have analyzed it fully.
I know what kind of a program
that table is.
They kill trust.
Diversion of dialects
kills trust.
There can be no chain of trust in
Babel.
You are, you know, all your cats
building up that Babel Tower.
And yeah, not
not a good software engineering practice.
So what could we do?
We should be.
I mean, what do we want?
Chains of trust.
When do we want them
now, right?
What do I want? Simple formats.
Simple formats that.
Take away the computational
power from the tables
so that you can actually
statically analyze the computation
that they will cause.
Squeeze complexity out of data.
We want simple formats
now starting with
a boot.
You know, we've looked at the top of the
trust chain. What about the beginning,
the bottom unify?
How simple is that?
OK.
Software package formats
zipped crypto,
I mean, Android Master key bugs were
just brilliant, you know, oh,
you shouldn't roll your own crypto.
Therefore, we will use the JavaScript
library because that's
great code on one problem
the installer is a Linux
installer, and
what it is is not what the
JavaScript of their fire sees.
And with my best impression of Agent
Smith, you know what?
Good is a signature, Mr. Anderson.
If you can't even see the document,
it's.
But it goes deeper than that.
It's all about computation, it's all
about input being programs and
tables being programs in particular.
So you have to hobble the
power that these can induce
in your code.
So we have a few
of the modest engineering
proposals. One of them is called ealth
back. You will see the
TR.
We will be releasing code eventually
next year.
Julian is the
main developer.
And that thing actually breaks
because this weird machine,
because it deprives
the replicator automaton
of some of the implicit flows that she
has used.
And it breaks other techniques
as well.
And of course, everyone
wants new hardware that would contain
parsers and
not allow them to run the way with the
rest of your program, even if they
contain the bug.
But as we have seen, the bugs are
not a necessity for getting potent.
It's a little disagreement between what
the dialects of your
input the parsers,
the pastors defect or accept
they will not see the same thing.
And so we're going to hold an academic
workshop in this.
The inventor
of the inventors of the language
theoretic approach to security, which
is what I've been sneaking in
during the entire introduction,
is
finally, you know, finally, we're
going to have a workshop
where these topics
will be considered and
it's co-located with the
Police Security Enterprise Symposium 2014
May 18th, you will find the
CFP and the Program Committee lists
and instructions and
whatnot at the CRL.
It will be dedicated to the memory of
Len Sassaman, who, together
with his wife Meredith Patterson,
invented the topic.
So consider
submitting case
studies, theory papers,
exploits that
use these ideas of
language theoretic security parser
differentials and
weird machines that
run away on
their input by being way too powerful
and by solving by enforcing
the defender to solve the thing problem,
which of course, cannot be solved
with it.
I think you.
He ended up.
It's great to be here.
OK, so we have very little
time for Q&A.
So if you have a question, please sign
a line up at one of the eight microphones
that we have in this room.
If you leave, please take your trash with
you and
please put all the bottles
in one of the crates located at each
exit. So do we have a question?
So we have a question from the internet.
So the internet would like to know
if you if you could implement
relocation without a
Turing complete semantics.
I have
not understood. Please repeat the
question.
So the question is, can you
implement relocation without
Turing? Complete semantics?
Well, yes, and
you should.
Moreover, you should implement
linking and every
other step in the API
without Turing completeness, because
if you actually do have Turing
completeness accidentally or
by design,
you are completely
depriving yourself of the ability
to check what the actual
result would look like other
than by renting it otherwise, you
know, known as halting.
This will probably be a
part of the thesis.
But I mean, this is a very good
question.
We see Turing
completeness being shown
everywhere, you know, in
order to download a file
from, Oh, Dropbox,
I need to run JavaScript.
Right. What is so Turing complete
about downloading
a file? Why do I need to run JavaScript,
you know, daily to interact, to
show numbers to a website
and get numbers out of the website?
I need to run JavaScript
Y and HTML
five together
with CSS.
Look up.
Excellent works by Mario Heidrich.
That thing can be a tricked
into Turing completeness
via things like what the filling
of forms and it's been actually
proven. Turing complete
by I actually,
I'm failing on the reference, but it
is in our paper which you'll find
on the links site.
So we are surrounded
by completely useless and
unnecessary Turing
completeness.
And that's just it's
just there to break our trust.
It's just there to empower
the attacker, the up.
Competition, the minimum privileged
principle, the least privileged principle
should be that
computation also
is a privilege and it is
a privilege given to attacker.
So, Turing, completeness
where it doesn't belong.
You know, Turing completeness of
communication boundaries in particular,
where untrusted data arrives
is a really, really bad idea
if you haven't yet.
I encourage you to watch the talk
by Meredith Fetters on the science of
insecurity at 28 c
three. Again, you will find links
on length sector work site.
Have I responded?
Okay, yeah, so
the gentleman at microphone number one,
please?
Very quick.
I'm not sure if I understood this
correctly.
The tables that are used to
rewrite the sign codes are other part
of or not part of the code signing
process.
So many signing
schemes do not sign metadata.
Hurray.
Right.
So, like,
yeah, yeah, well, the answer is
again, and we've been asked
by, well,
I mean, Becks has done this for Elf,
but our P E
is a cousin of
elf with all
sorts of interesting properties.
MECO is a cousin
of wealth and neck. Oh, actually,
he does real location with bytecode,
a special dedicated bytecode,
not just
sue structures with offsets as
elf with offsets and types
and pointers to symbols.
That is actual bytecode.
The virtual machine is included for it.
And guess what?
Of course, it's
Turing complete as well.
OK, our time is up.
Thank you very much.