Dear viewer, these subtitles were generated by a machine via the service YouTube and therefore are (very) buggy. If you are capable, please help us to create good quality subtitles: https://c3subtitles.de/talk/2854 Thanks!
[Music]
let's talk about love again love is what
makes you go the extra mile so when Luca
de bomeo she's probably from Italy from
Rome the speaker of the following talk
had the idea to Port an existing x86
static binary Rider to arm 64 things
turned out different than
expected it must have been a real love
story Luca was all in today after more
than two years arore we learn what this
relationship is
about I'm excited to hear what was going
on in the binary
bedroom and what the outcome of this
love story is let's talk Amore please
give a lovely Round of Applause to luuka
hi everyone I'm luuka nice to meet you
so today we talk about binary writing uh
binary writing on arm and uh um what I
would like to present you is this tool
we developed called Dore which is the
first one being heuristic free which is
the important part here why why is it
the first you're going to see in a while
why no one this before unfortunately so
uh a disclaimer before we start um this
presentation is going to contain
assembly a bunch of assembly and uh I
know everyone hates looking at assembly
but I did it for the past two years so
you're going to get it for the next half
an hour sorry about that now uh what is
binary writing very quick recap uh it's
a process that allows to do late stage
code modifications to a binary which
means I have a binary I don't have the
source uh for some reason maybe I was
noted supposed to have this B for
whatever reason I want to change its
Behavior right but I want to not break
it right so as an example maybe I want
to change its architecture and the whole
process looks like this I get a bunch of
assembly and I need to transform it into
other assembly that works more or less
in the same way as before right and the
key challenge is in not breaking
stuff now um some quick necessary
background you need to be a to
understand this talk you need to know
that X6 is an architecture you need to
know that arm 64 is another
architecture you need to know about the
presence of uh uh pointers that are
unfortunately everywhere and uh they
really fight against you right you miss
one and it's game over and then there's
another important Concept in binary
writing which is fundamental to know
which is the concept of pre- tired the
feeling of succumbing to exhaustion
after thinking of everything you have to
do but before doing any of the work
right do not confuse this with laziness
okay very
important now um why I went into this
rabol uh because uh when I was starting
uh my PhD my advisor sent me a letter
was kind of like this right so we have
this existing uh existing
can you still hear me maybe okay I I
will shout so existing xa6 static B
writer that was cool and uh we wanted an
arm 64 one
right uh it was supposed to be a very
easy project you know like in binary
ninja when you add a new instruction set
you spend a couple of afternoons
implementing instructions lifting to INR
unfortunately this was 3 years ago and
uh it didn't really bur out that way so
um
why do people do binary writing who
cares about it the use cases are
multiple some people do it for hardening
because maybe you compiled a binary but
you forgot the mitigation and you want
to add it after you lost the source uh a
more popular use case is profiling in
fact uh V and NGB are kind of doing B
writing when you insert a breakpoint
this is more or less what happens uh
there is translation this is very
popular cimu is basically this is what
it does
and uh but the most popular use Case by
now the hot stuff is fuding right
because we want to fuds close source
binaries and we want to add coverage
instrumentation to that this is what
aflq does okay an important distinction
we need to say is that b writing could
be static or dynamic the difference is
do I change the code ahead of time or
during
execution I will not go into depth of
this but Dynamic is usually better
because you have runtime information
when I'm changing the code I can look at
the content of the registers I can look
at the state of the memory I have so
much more knowledge to fix pointers and
other stuff that will break the big
disadvantage as you can see in the last
line is that it's slow because I need to
stop execution think about what I have
and then change the code while study
happens ahead of time unfortunately
there are many issues and obviously uh
we're going to look into the
bad one
right so uh what was the starting point
uh there is was this tool called retro
um it was presented at the last CCC
actually there is a talk about it is
very interesting the key innovation of
it is that it used the relocations to
detect pointers that are in data we'll
see why this is important and our was
supposed to be an extension of this
right using the same concept and just
add a new architecture unfortunately
no uh unfortunately we figur out that
the challenges arm presents are
different so different that we had to
start from scratch redesign the whole
thing and now it's a completely
different project that has different
characteristic and so on why is this the
case for one reason mostly arm like
every other risk architecture is the
fixed size right and you may think but
this makes stuff easier right this
assembly is super easy on a fixed size
instruction set right you don't have the
the problem of uh uh variable size
instructions and so on so why well
because unfortunately very very often a
binary wants to put a pointer into a
register and while on X6 it is very easy
it looks like this it's a single
instruction on arm you cannot because
you don't have enough space you only
have four bytes especially in on arm 64
uh pointers will not fit right so um if
you go look into the
very famous arm arm the arm reference
manual among the 7,000,000 pages you
have the arm developers tell you you you
have two options to solve this issue the
first one is you use literal
pools which is very easy you just store
the pointer in R data and then you load
it back the second one is more creative
is more fancy you know why do you need
to use a single instruction to build a
pointer why not multiple right not
both and uh as you might guess this is
where problems start right so let's look
into uh into this a bit more in depth so
literal
pools looks like this you have ldr x0
then you use this weird syntax equals
pointer this is a pseudo instruction the
assembler will store a pointer in R data
for you and then generate the relative
load for you and this very easy to deal
with right because uh 99% of the cases
you have relocations for that not a
problem this instead is a problem now uh
I need to stop for 20 seconds and make
you really understand the process here
right this is the key of the talk now we
have two instructions right uh the first
one is called adrp stands for address
page now what does it do it stores The
Base address of a page into a register
okay it is PC relative but has a huge
range I don't remember how much but you
almost always are covered if you are too
far away then you use a literal pool but
then you're left with the offset inside
the page the last 12 bits and for that
you use you're free to use whatever you
want usually arithmetic
instructions but uh there is no strict
Rule now now that you know all the
basics let's look a bit more in depth in
the challenges I showed you you before
in the table so let's start from the
first one a very classic one if you ever
did any binary writing the problem is
that the average binary is a huge mess
even something that comes out of the
latest compiler that should be very easy
your main. C print word is not as easy
right and the first challenge is
distinguishing between code and
data they really look like right when
you look at the B you don't have symbols
obviously right you can use the B
symbols well
imagine you have this assembly then the
bytes will look like this there is
nothing that tells you that there is a
string in the middle of the instructions
right now
uh the problem is as soon as you
interpret a string as
instructions when you do binary writing
you're going to insert instrumentation
you're going to change instructions so
you might end up changing that string
right maybe it is fine maybe you don't
break
stuff but in most
cases any mistake you do will lead to a
crush right very very often because the
most likely case is not that in the
middle of a code there is a string in
the middle of a code there will be a
pointer right and as I told you before
you don't [ __ ] with pointers right you
cannot touch
them so this is the first
challenge this however is not relative
to arm this is uh also relative to X6 is
a very long old the challenge but this
new one the second one is unfortunately
spawned by arm and it's about recovering
pointers the problem is that as soon as
I want to add a single instruction
inside the bar the address of everything
else afterwards will change okay so I
break every single reference to anything
that is after
that which means in static B in writing
your objective is to recover every
single pointer inside the binary all of
them it's super unlikely that there is a
pointer which is just dead it's not used
more or less each pointer will be used
at some point and if it's wrong you will
crash now there are two types of
pointers in arm right literal pools and
pointer constructions so literal pools
are easy because almost always you are
looking at a position independent
executable with which will have one
relocation for each pointer that is
stored in memory um and so you can just
look at the relocation table and figure
them out if it's not uh position
dependent you have another solution I
will explain later now the problem comes
with um how do you detect pointer
constructions and um yeah that's uh
that's the two years here so why because
compilers when you tell them uh look
just grab reference to something the
code they will generate especially if
you turn optimizations on it's uh yeah
very creative you see stuff
like starting a pointer construction
then saving it on the stock and then res
resuming ages later right and then
finishing the pointer construction this
to solve statically is a huge pain you
need to keep track of what stuff goes
into the stock and so
on um but the most favorite hobby of
compilers is the following you have a
function and uh you say okay now let's
build 15 pointers all at the same time
and we're going to reuse partial
calculation mix and match between them
and uh here also recovering them
statically is another huge pain you need
to do kind of symbolic execution stuff
like that keep track of where goes what
lastly there is also switching registers
in the middle of the construction as you
can guess there is all kind of stuff you
can do right compilers are really uh
free to do whatever so I bet that many
of you in the audience after seeing this
we'll say okay look
but what's the problem here why don't
you just do data flow analysis obviously
right why don't you just track where
data is going where and my answer to
that is no no we tried we tried it
doesn't work it doesn't work why because
any mistake is fatal and data flow
analysis will never be 100% precise you
don't have the computation power to do
it and there are so many cases you will
never cover them all we tried the
pattern matching we said okay maybe
compilers have a finite set of uh
pointer construction patterns they will
generate maybe there are 30 there are 40
I look at a th binaries more or less I
have all of them no no there is no way
there are too many compiler versions
options and so on
no we tried the data flow approach yeah
that also didn't work any mistake is FAL
this is Challenge number two how to
recover the Val of a pointer and write
it to preserve its
Target now we move on to the final One
Challenge number three also very
painful which is Jump tables jump tables
why because uh you guys C developers
really really love using switches every
function has a switch why not two three
nested switches why
not and this when when you look at this
assembly it's bad because a jump table
doesn't store absolute pointers to the
cases it stores relative offset from the
base case to the various cases right and
it's sto it's stored in R data and it's
impossible to distinguish it from other
data and as the same as pointers you
need to detect jump tables why because
if you insert instrumentation between
the cases right then those offsets will
change this is how a jump table looks in
R data xa6 an arm you see those are
relative offset they're not absolute
pointers on the right you see that each
case is two bytes big this is not force
it could be one BTE it could be four
bytes depends on how the compiler felds
that day
it is really impossible to detect oh
this is a jump table and the worst case
that this is not a new problem people
have been doing this for years have
trying to detect jump tables right even
if you fire up Ida Pro it will still
[ __ ] up on some jump tables right even
state-of-the-art tools are not good
enough even the best techniques we found
they are never 100%
precise for example this is what a jump
table looks in arm you can see the last
instruction is the indir jump and what
you need to do is need to go back and
figure out oh where is it stored in
memory and in fact if you see the first
two instruction it's a pointer
construction obviously because why
not and you need to figure this out we
tried for so long to build a fancy
backward slicing data flow thing I
wasted so much time on
this H it doesn't work right I mean it
works 99.5% of the times but the jump
table you miss right it's going to give
you hours and hours and hours on the
bugging it's a edge case you didn't
predict it's not going to
work so we now move on
to what is the solution to this
right and it's using pre- tiredness okay
it's very important because what we saw
is that there are many solution that are
either
infusible take way too long to implement
they are not realistic
or they're not fully precise okay so our
intuition was just can we avoid the
problem can we run away from the
problem and um it works like this with a
few with a few hacks I'll show you so
the first thing we do is when we rewrite
a binary we use layout replication okay
layout replication is an old technique
it's nothing new was used in xa6 before
how does it work is that the binary you
produce you use a Linker script thingy
to make sure that each section will be
exactly at the same address as before
okay now how does this help is that if
there is a literal PP pointer in R dat
and you don't detect it it's fine
because stuff will be at the same
address right this very easy very
intuitive idea obviously I know the
people who are paying attention will
tell me this doesn't work for do text
obviously because I put instrumentation
there right and stuff will change okay
but we'll fix this later we just start
from this base okay everything is at the
same
address unfortunately this only works
for leral pools not pointer
constructions because they are PC
relative so you you cannot just preserve
their are coded address you need to fix
them as well
now how do we make each pointer
construction Target its um replicated
layout and this what saved us was the
arm arm again in the 7,000 Pages there
was a box saying uh look the program
counter register actually it's a special
register it's not general purpose
obviously but in particular can only be
read by two instructions which are the
following the branch and Link which is
the same as the call on X6 which reads
the reads the PC to store it in the
return
address and the address page I showed
before okay what what does this mean
this mean that every single pointer
construction needs to start with n adrp
there is no other way to load a pointer
Which is far away PC relative without
another page so this means that if I
manage to preserve offsets in set a page
I can just fix the first adrp and not
care about anything that happens in the
rest of the pointer construction that
huge mess of arithmetic instructions I
don't care about it right this means
that I don't need to do any fancy static
analysis any data flow anything else but
still code pointers are still broken
right because the text section move
around and for this we introduced the
what we call the rebound table the
rebound table is nothing else than a new
section a new code section which as you
can see stands exact in the same place
and of the same size of the old text
okay and the code instead is moved the
somewh else in the instrumented text you
can see below very far away in another
place in the virtual other space how is
the the bound table is incredibly simple
it's just a sequence of
branches why why does it work like why
does it work for code pointers so
imagine that now this is the layout of
my B right I didn't change any pointer
whatsoever right now there is a pointer
construction and it will make the
program counter go in the same old
others so somewhere in the rebound table
but the rebound table I can build it in
such a way that it's the branch will
bring me exactly to the new location in
the new
text if I had
instrumentation I just need to change
the target of the branches in fact you
can see in the arrow there I added a a
couple of
instructions pointers will still work
right because the the difference in
instructions I added is going to be
reflected only in the target of the
branches so now I have two places in my
viral add space one where everything is
exactly as
before and one in said where I add
instrumentation and stuff will
move however this means that anything
you that reference will go through the
old layout and if it's changed it will
go into the new through the rebound
table and this the the the good thing is
that doesn't only solve pointer
constructions it also solves jump tables
right because their base offset
calculation will be done on the old
address and will be done on the rebound
table where all offsets are preserved no
matter what they do with the
code now there is a caveat this costs
one branch okay every time I do an
direct jump I need to go through one
additional Branch uh in our benchmarks
this has like a 1% overhead though
almost uh non
noticeable however you're probably
thinking there is no way you do this
huge mess and stuff still works out of
the box and you're right you're right
stuff is broken um in particular what
breaks is banies that read themselves
they read their
code because what if a b tries to read
its own
text this is uh very rare but happens
some buies want to check some themselves
some buies want to read some data they
stored in text not everything comes from
a compiler sometimes you have uh
handwritten assembly with weird
stuff forget about doing any kind of
analysis to understand oh this load will
Target the text this doesn't work never
don't even think about it so what we do
we do another party trick this is the
this is weird but actually works uh
surprisingly we use uhom which is a
feature added in arm
8.2 and it's executable only memory
right so this originally is a mitigation
to prevent binaries from scanning for R
Gadgets in the text section what you can
do is that you can set a code section
permission to only execute you cannot
read from that on X6 this is not
possible
yet but on our M point2 yes now what do
we do with this we're going to install a
seful tler that will detect if you're
doing this kind of
[ __ ] and
then we're going to give you back the
Corr data how though and this is the bad
part we keep
another third copy of that section
yeah
so the seful Thunder will transparently
magically fix stuff so you read what you
think you were trying to read and uh you
can still scan for Rob gadgets um as
before now um we're done with the
solution of the challenges a quick
Showcase of what you can do with armore
what we did is that we implemented some
um instrumentation passes that are
automatic they're just already there
obviously this is the fing one so what
is the advantage of using this compared
to AFL qimo it's the speed because the
AFL qimo obviously needs to instrument
each branch for coverage right and needs
to do it during execution if you do it
ahead of time you have a 3X more or less
speed up and this is in the best case
because quo can cash a lot uh thanks to
the the TCG cache if you do another use
case which is not fuding probably the
difference is even larger but you can
expect three times as fast fuding
another cool thing we added is uh binary
add sanitizer which basically SC for
every load and store and adds code to
check if you're respecting asan red
zones or not which is cool for fuding or
debugging our close Source
binary and uh we use this to find uh a
couple of vulnerabilities in the Cuda
toolkit um it was the only really
interesting and easy to F software we
found on Linux clour arm um in hindsight
the Cuda tokit is so bad I could have
probably found many many more
vulnerabilities in those years by myself
but uh but yeah
um so um I don't have much time I'll
show you you very fast how does this
work uh if I manage
to
so
yeah hope you can see yeah perfect so
um what I'm going to try to do is
rewrite
BS forgive
me perfect so uh let's fire up uh
or more it's called redro because we
never change
name
so yeah what will this will do is
generate a text file with all the
disassembly already transformed to a new
bin right the cool stuff is that you can
open this with an editor
right and just change whatever you want
this actually works it's very surprising
but uh this is not a new thing it's
called symbolization but I could just
change this to one and it's going to
reassemble I'm not going to do it
because I'm going to break it but
potentially you can just change the
battery and then to reassemble it back
there is you just pass Das a and it will
transform it into a new battery this is
just a wrapper around some uh uh Linker
flags for GTC basically to do the layout
replication right and then you can run
this and it's the same as a l uh let's
see if I can actually break it maybe I
don't
know I don't know what kind of change
let's change this to X1 I'm not sure
this is going to do anything but maybe
it's going to break a
less perfect nice okay
so so what you usually the use Cas is
that you can there are a bunch of uh
built-in instrumentation passes like for
example the coverage
right this will uh um append AFL style
coverage information after every
branch and maybe I can show
you yeah for example here there is a
branch and here there is the relevant
code that will put a bite in the AFL map
and there's also the aan thing but yeah
so let's go back let me
finish okay
cool uh if so this was obv very
simplified the technical details are in
a paper that was proudly sponsored by
those three nonprofits you can see on
the right thank you very much for them
uh as
knowledgement um I would like to thank
my advisor that sent me the letter my
cter my flatmate
my as I said this was a talk about love
right because
[Music]
yeah and also the hexy lab at uh my
university is full of amazing people
that really supported me during all of
this uh key takeaways is that uh um yeah
B writing is hard pointers are terrible
and uh sometimes you don't need to face
your problems directly you can just run
away from them and it works better I
[Applause]
[Music]
guess um thank you I'm
done thank you
Luca that was a lot of love and I have
something special to announce you want
to announce it yourself or H yes as a
special feature for Wass good questions
easy questions there will be
stickers so and we have enough time I
think for questions uh do you send the
stickers also to the internet if
somebody's so no just joking just
kidding so we'll start with microphone
number one please hi uh thanks for the
talk is um so I was wondering is there a
size limit for the rebound table
mechanism because um how long can this
Branch instruction be or how wide can it
jump there is there is and uh the branch
instruction can go up to 128 megabytes
if I remember correctly so uh we looked
up the
the basically you can rewrite everything
up to chromium chromium is bigger than
100 it's the size of the text that
matters and chromium is huge everything
else was fine so yeah there is this
limitation
okay question from the internet please
yeah uh the internet wants to know if
you in theory can start an address
construction with uh brl to the next
instruction similar to the IP relative
addressing in 32bit uh x86 you can yes
well yeah yeah so um there will be
binaries that uh will look at use BL and
then look at their return address to
decide what to do um there is a way to
fix this we had an option it's called
call emulation and basically what it
does it fixes these two by changing
every branch and link to a normal branch
and then we ourselves push on the stock
uh the wrong return address so you think
everything is fine but this happens yes
good
question okay number two please
microphone is over there yeah yeah
thanks for the great talk so I've got
the question because you are having the
old BSS and new BSS but does it mean
that like you may have code that will uh
deal with the old PSS and you may have
Cod code that will deal with the new one
and does it then mean that you may have
a desing between the values in there or
something like that so the new BCS is
for the instrumentation you might want
to have additional data and it needs to
go into the new BSS the old BSS is only
for the old code and uh so so you're not
Chang where the original data lies it
always lies in the old BSS yeah okay
that makes sense ma come to the front
and collect your
sticker microphone number three please
uh hello thank you for the talk um how
did you fix the ADR instruction cuz I
saw the adap and blr but not the ADR so
uh you transform it into a longer adrp
sequence H okay thank you yeah
microphone number four uh thanks for
your talk on the on the work um so I
wonder how much this would be applicable
to to Native binaries or native code
provided I have the space for all the
two copies of the binary in my ROM and
know how it's laid out and so forth wait
sorry I missed it can you repeat U how
this would apply to to native code
running on some embedded device or so
that I can instrument with this so it
depends if you're doing arm
32 then it's a huge mess because of
thumb and um I will show you some people
that are working on this and they they
are going crazy yeah but uh it's not
applicable the same technique doesn't
work because as soon as you have a BX
that switches modes then there is this
new huge challenge of is this code thumb
or not right now and uh
yeah so no unfortunately no good thanks
okay microphone number one
please um yeah thanks for the talk um my
question was uh so chrome doesn't work
because it's too big did you try Firefox
did what that work the so so Firefox yes
because uh if I remember correctly you
can rewrite single libraries and then
which that compined into the main B I'm
not 100% confident I did this long time
ago but yes on Firefox it was kind of
working there is the problem that it's
C++ so you need to make um exceptions
work which is a huge pain uh this is a
limitation um I had this additional
slide of the limitations if you use use
the uh secondary cursed control flow
mechanism like exceptions you need to
take special care like for example if
you during run time you look at the
stock you look at the pointers there and
decide what to do based on them which is
what liban wind
does uh then it might break and you need
to handle it specially go does the same
and
uh you need special handling right but
uh C and go work uh rust still no I
don't think we implemented rust panics
yet but yeah Firefox should okay cool
okay microphone number three I'm curious
uh which of these um challenges are
really specific to AR 64 or wouldn't
like most of these challenges also occur
in x86 I mean you might have euristic
but I think they're also not always
going to work in x86 or maybe in other
words can your Solutions also be applied
to x86
rewriting no the problem is with xc6 is
that uh since it's not fixed size
instruction you cannot have the rebound
table because the the cool thing about
the rebound table is that every single
pointer will Target something which is
four bytes aligned so I can have
branches everywhere and I'm sure you're
going to land on a branch on S6 you can
land anywhere that's the problem so you
can really have the same thing okay
thanks maybe there is a super cursed way
of doing it but we didn't
explore this yet the next four years are
waiting for you um
so microphone number two okay so more of
a uh
question uh yeah more of a comment and a
question uh I I think that execute only
memory uh can partially work on other
architectures uh when the page is paged
out because when the colel decid to load
the page
it it inspects it inspects uh the flags
the permission Flags anyway I guess so
yeah that's is true yeah that is true
thank
you we try we try microphone number one
without microphone okay test test okay
thanks for the talk uh so one one of the
advantages of doing statical writing
over writing a dynamic is that you can
spend more time in optimizations right
and and it would be interesting if you
could like optimize multiple
instructions into one well as far as I
understand here you need to keep an
clear distinction between where one
instruction trans one translated
instruction start and the other one do
you see any opportunities for doing
let's say intern instructions
optimization so uh we didn't explore
this yet all the optimizations we tried
to do was um were about uh
how do we make the instrumentation we
add as fast as possible so for example
um one thing we did I don't know if I
have it here was uh
um yeah basically if you want to add for
example coverage instrumentation you
need to use some registers uh try to use
the ones that
are not clobbered for example so you
don't need to save stuff on the stock we
didn't look up into optimizing already
exist code it will be interesting though
thank you okay thank you very much
please
[Music]
again