36c3 preroll music
Herald: So, Samuel is working at Google
Project Zero on especially vulnerabilities
in Web browsers and mobile devices. He was
part of the team that discovered some of
the vulnerabilities that he will be
presenting in this talk today in detail
about the no user interaction
vulnerability that will be able to
remotely exploit and compromise iPhones
through iMessage. Please give Samuel a
warm round of applause.
Applause
Samuel: OK. Thanks, everyone. Welcome to
my talk. One note before I start,
unfortunately, I only have one hour. So I
had to omit quite a lot of details. But
there will be a blog post coming out
hopefully very soon that has a lot more
details. But for this talk, I wanted to
get everything in there and leave out some
details. OK. So this is about iMessage in
theory some of it applies, or quite a lot
actually applies to other messengers, but
we'll focus on iMessage. So what is
iMessage? Yeah, it's a messaging service
by Apple. We've heard about it in the
previous talk a bit. As far as I know, it
is enabled by default. As soon as someone
signs into an iPhone with their account,
which I guess most people do, because
otherwise you can't download apps.
Interestingly, anyone can send messages to
anyone else. So it's like SMS or phone
calling. And then if you do this, then it
pops up some notifications, which you can
see that here on the right screenshot,
which means that there must be some kind
of processing happening. And so, yeah,
this is like default enabled, zero click
attack surface without the user doing
anything, there's stuff happening. And
then on the very right screenshot, you can
see that you can receive messages from
unknown senders. It just like says there.
This sender is not in your contact list,
but all the processing still happens. In
terms of architecture, this is roughly how
iMessage is structured, not very, yeah,
anything too interesting, I guess. You
have Apple cloud servers and then sender
and receiver are connected to these
servers. That's pretty much it. Content is
end to end encrypted, which is very good.
We heard this before, also. Interestingly,
this also means that Apple can hardly
detect or block these exploits though,
because, well, they are encrypted, right?
So that's an interesting thing to note. So
what does an iMessage exploit look like?
So in terms of prerequisites, really the
attacker only needs to know the phone
number or the email address, which is the
Apple account. The iPhone has to be in
default configuration so you can disable
iMessage. But that's not done by default.
And the iPhone has to be connected to the
Internet. And in terms of prerequisites,
that's pretty much all you need for this
exploit to work. So that's quite a lot of
iPhones. The outcome is the attacker has
full control over the iPhone. After a few
minutes, I think it takes like five to
six, seven minutes maybe. And it is also
possible without any visual indicator. So
there's no... you can make it so there are
no notifications during this entire
exploit. OK. But before we get to
exploiting, of course, we need a
vulnerability and for that we need to do
some reverse engineering. So I want to
highlight a bit how we started this or how
we approached this. And I guess the first
question, you might be interested in, is
what daemon or what service is handling
iMessages. And one easy way to figure
this out is you can just make a guess. You
look at your process list on your Mac, the
Mac can also receive iMessages. You, like,
stop one of these processes and then you
see if iMessages are still delivered. And
if not, then probably you found a
process that's somewhat related to
iMessages. If you do this, you'll find
"imagent", already sounds kind of related.
If you look at it, it also has an iMessage
library that it's loading. Ok, so this
seems very relevant. And then you can load
this library in IDA. You see a screenshot
top right. And you find a lot of handlers.
So for example, this
"MessageServiceSession handler:
incomingMessage:", and then you can set a
breakpoint there. And then at that point
you can see these messages as they come
in. You can dump them, display them, look
at them, change them. And so this is a
good way to get started. Of course, from
there, you want to figure out how these
messages look like. So, yeah, you can dump
them in there when they come in in the
handler, on the right side you see how
these iMessages look like more or less on
the wire. They are encoded as a PList,
which is an Apple proprietary format.
Yeah, think of it like JSON or XML. And I
guess some fields are self-explanatory.
So, "p", that's the participants in this
case this is me sending a message to
another account I own. You have "T" which
is the text content of the message. So
"Hello 36C3!". You have a version, for
some reason you also have an XML or HTML-
ish field, which is probably some legacy
stuff. It's being parsed, this XML. But
yeah, the whole thing looks kind of
complex already. I mean maybe you would
expect a simple string message to just be
a string. In reality, it's sending this
dictionary over the wire. So let's do some
more attack service enumeration. If you
then do more reverse engineering, read the
code of the handler, you find two
interesting keys that can be present,
which is ATI and BP, and they can contain
NSKeyedUnarchiver data, which is another
Apple proprietary serialization format.
It's quite complex, it has had quite a few
bugs in the past. On the left side you see
an example for such an archive. It's yeah,
it's being encoded in a plist and then
it's pretty much one big array that has,
like, every object has an index in this
array. And here you can see, for example,
number 7 is some object, is the class
NSSharedKeyDictionary. And I think key one
is an instance of that class and so on. So
it's quite powerful. But really what this
means is that this serializer is now zero
click attack surface because it's being
passed on this path without any user
interaction. So I said it's quite complex.
It even supports things like cyclic
references. So you can send an object
graph where A points to B and B points
back to A for whatever reason you might
want that. Natalie wrote a great blog post
where she describes this in more detail.
What I have here is just an example for
the API, how you use it. This is Objective
C at the bottom. If you're not familiar
with Objective C, you can think of these
brackets as just being method calls. So
this is doing, in the last line, it's
calling the unarchivedObjectOfClasses
method for this NSKeyedUnarchiver. You can
see you can pass a whitelist of classes.
So in this case, it will only decode
dictionary, strings, data, etc. So looks
quite okay. Interestingly, if you dig
deeper into this, this is not quite true
because it also allows all the subclasses
to be decoded. So if you have an NS-
something-something dictionary that
inherits from NSDictionary, then that can
also be decoded here, which is quite
unintuitive I think. And this really blows
up the attack surface because now you have
not only these 7 or so classes, but you
have like 50. Okay. So this is what we
focused on when me and Natalie were
looking for vulnerabilities. It seemed
like the most complex thing we found. We
reported quite a few vulnerabilities here,
you can see it maybe a bit on the left.
The one I decided to write an exploit for
is this 1917, reported on July 29th and
then exploits sent on August 9th. Yeah,
mostly I decided to use this one because
it seemed the most convenient. I do think
many of the other ones could be exploited
in a similar way, but not quite as nice,
so would maybe take some more heap
manipulation, etc. So then Apple first
pushed the mitigation quite quickly, which
basically blocks this code from being
reached over iMessage. In particular, what
they did is, they exactly no longer allow
subclasses to be decoded in iMessage. So
that's quite a good mitigation, it blocks
off maybe 90 percent of the attack surface
here. Yeah. So then they fully fixed it in
iOS 13.2. But again, after August 26th
this was only just local attack surface.
OK, so what is the bug? It's some
initialization problem during decoding,
the vulnerable class is
SharedKeyDictionary, which again, it's a
subclass of NSDictionary, so it's allowed
to be decoded. So let's take a look at
that. So, yeah. SharedKeyDictionary.
Here's some pseudocode in Python. It's a
dictionary. So its purpose is to, well,
look up keys to values or map keys to
values. The lookup method is really
simple. It just looks up an index in a key
set. So every key dictionary has a shared
key set and then that index is used to
index into some area. OK, so that's quite
simple so most of the magic happens in the
SharedKeySet. And so what that does is
something like compute a hash of the key.
Use that hash to index into something
called a rankTable, which is an array of
indices. And then if that index is valid,
so it's being bounced, checked against the
number of keys. Then it has found the the
correct index and if not, it can recurse
to another SharedKeySet. So every
SharedKeySet, can have a sub-SharedKeySet,
and then it repeats the same procedure. So
it already looks kind of complex. Why does
it have... why does it need this
recursion? I'm not quite sure, but it's
there. And so now we look at how this goes
wrong. So this is the initWithCoder, which
is the SharedKeySet constructor used
during decoding with the keyedUnarchiver.
And it looks pretty solid at first, it's
really just taking the values out of the
archive and then storing them as the
fields of this SharedKeySet. I have a, I'm
gonna go through the code here in, like,
single step to highlight where it goes
wrong or what goes wrong here, what's
wrong with this code. So we start with
SharedKeySet1 which implies there's gonna
be another one. And at the start it's all
zero initialized. It's basically being
allocated through ?calloc?. So everything
is zero. Then we execute the first line.
Okay. So numKey, you see some interesting
values coming. So far this is all fine.
Note, that you can set numKey, at this
point numkey can be anything because it's
only being validated three lines further
down, right? Where it's making sure that
numKey matches the the real length of this
array. So this is fine, but here it's now
recursing and it's decoding another
SharedKeySet. So we start again. We have
another SharedKeySet, all filled with
zeros and we start from the top. Again,
numKey is one, so this is this is a
legitimate SharedKeySet, decoding a
rankTable. And here we are making a
circle. So for SharedKeySet2 we pretend
that its sub-KeySet is SharedKeySet1. And
this actually works. So the
NSKeyedUnarchiver has special handling to
handle this correctly. So it does not
create a third object and it makes the
cycle. And we're good to go. Okay. Next to
decode the keys area. So this is fine.
SharedKeySet2 seems legitimate so far. And
now it's doing some sanity checking. Where
it's trying, where it's making sure that
this SharedKeySet can look up every key.
And so it does this for the only key it
has, key one. Now, at this point, it's
again, remember, it's hashing the key
going into rank table, takes out 42, which
is bigger than numKey. So in this case,
this look up here has failed. And now it's
recursing to SharedKeySet1. Right? This
was the logic. And at this point it's
taking out this hex41414141 as index,
compares it against hexffffffff and that's
fine, and now it's accessing, null
pointer, which is.. the keys area is still
null pointer plus, well, 41 41 41 41 times
8. So at this point it's crashing. It's
accessing invalid memory, precisely
because in this situation the
SharedKeySet1 hasn't been validated yet.
OK, so that's the bug we're going to
exploit. I have these checkpoints just to
think where we are, so we now have a
vulnerability in this NSUnarchiver API. We
can trigger it through iMessage. So what
Exploit Primitive do we have? Let's take a
look again at the lookup function, which
we saw before. So here where it's bold,
this is where we crash. keys is null
pointer, index is fully controlled. So we
can access null pointer plus offset. And
then what happens is the result of this
memory access is going to be used as some
Object-C Object. So this is all
Objective-C in reality, it's doing some
comparison, which means it does something
like it called some method called
isNSString, for example. And then also
eventually it calls dealloc, which is the
destructor. So yeah, we have... the thing
it reads from whatever, it will treat it
as an objectif C-Object calls a
message on it. And that's our Exploitation
Primitive. Okay, so here we are. How do we
exploit this? So the rough idea for
exploiting such vulnerabilities looks like
this. You want to have some fake
Objective-C object somewhere in memory
that you're referencing. So again, we have
an we can access an arbitrary absolute
address. We want some fake Objective-C
object there. Every Objective-C object has
a pointer to its class. And then the class
has something called a method table, which
basically has function pointers to these
methods. Right. And so if we fake this
entire data structure thing, the fake
object and the fake class, then as soon as
the process calls some method on our fake
thing, we get code execution. So we get
control over the instruction pointer and
then it's game over. So that's going to be
our goal for this exploit. So here we have
two different types of addresses: On the
left side we have heap addresses or data,
really. And on the right side, in this
NSString-thing we need library addresses
or code addresses, simply because on iOS
you can't have writeable code regions. So
we have to necessarily reuse existing
code, do so to something like ROP also.
So we need to know where libraries are
mapped for this. And this is exactly the
problem we are gonna face now because
there is something called ASLR, Address
Space Layout Randomization. And what it
does is it will randomize this entire
address space. So on the left side, you
can see how a process looks like, how the
virtual memory of a process looks like
before ASLR. And there everything is
always mapped at the same address. So if
you start the same address twice on
different phones, maybe even without ASLR
the same library is at the same address,
the heap is always at the same address
stack. Everything is the same. And so this
would be really simple to exploit now
because, well, everything is the same.
With ASLR everything is shifted and now
all the addresses are randomized and we
don't really know where anything is. And
so that makes it harder to exploit this.
So we need an ASLR bypass is what this
means. We're gonna divide it into two
parts. So the heap addresses we get them
from in a different way than the library
addresses. So let's see how we get heap
addresses. It's really simple honestly,
what you can do is heap spraying, which is
an old technique. I think 15 years old
maybe. And it does still work today. The
idea is that you simply allocate lots of
memory. So if you look at this code there
put on the right, which you can use to
test that, what it does is that it allocates
256 megabytes of memory on the heap with
malloc. And then afterwards there's one
address or there's many addresses. But in
this case, I'm using this hex110000000
where you will find your data at. Okay. So
just spraying 256 megabytes lets you put
controlled data at a controlled address,
which is enough for this first part of the
exploit. The remaining question is how can
you heap spray over a iMessage. That's a
bit more complicated. But it is possible
because NSKeyedUnarchiver is great and it
lets you do all sorts of weird stuff which
you can abuse for heap spraying. So, yeah.
Blog posts will have more details. Okay.
So we have these, the heap addresses. We
have them. We need the library addresses.
Let's go back to the virtual memory space.
On iOS and also on macOS the libraries -
so maybe in this case all three libraries,
but in reality, it's like hundreds of
system libraries - they are all prelinked
into one gigantic binary blob, which is
called a dyld_shared_cache. The idea is
that this speeds up like loading times
because all the interdependencies between
libraries are resolved pretty much at
compile time. But yeah, so we have this
gigantic binary blob and it has everything
we need. So it has all the code, it has
all the ROP gadgets and it has all the
Objective-C classes. So we have to know
where this dyld_shared_cache is mapped. If
you dig into that a bit or if you look at
the documentation or the the binaries, you
can find out that it is going to be mapped
always between these two addresses. So
between 0x180000000 and 0x280000000, which
leaves only a 4 gigabyte region, so it's
only being mapped in these 4 gigabytes.
And then the randomization granularity is
also 0x4000 because iOS uses large pages
so it can only randomize with page
granularity, and that page granularity is
0x4000. But really what's most interesting
is that on the same device, the
dyld_shared_cache is only randomized once
per boot. So if if you have two different
processes on the same device, the shared
cache is at the same virtual address. And
if you have one process, then it crashes
and you have another one. And so on, like
the shared cache is always going to be at
the same address. And that makes it really
interesting. And also, it's one gigabyte
in size. It's gigantic. So it's not too
hard to find in this four gigabyte region.
Right. So this is what our our task has
boiled down to at this point. We have this
address range, we have the shared cache.
And all we need to know now is what is
this offset? So let's make a thought
experiment. Let's say we had an oracle
which would tell us... which we could give
an address. And it would tell us if this
address is mapped in the remote process.
OK, if we have this, it suddenly becomes
really easy to solve this problem, because
then all you have to do is you go in 1
gigabyte steps the the size of the shared
cache between these two addresses and then
at some point you find a valid address. So
maybe here after 3 steps, you find a valid
address, and then from there you just do a
binary search. Right. Because you know
that somewhere between the green and the
second red arrow, the shared cache starts.
So you can do a binary search and you find
the the start address in logarithmic time
in a few seconds, minutes, whatever. So
obviously the question is what? How? Where
would we get this oracle from? This seems
kind of weird. So let's look at receipts,
message receipts. So iMessage like many
other messengers - I think pretty much all
of them that I know - send receipts for
different things. iMessage in particular
has delivery receipts and read receipts.
Delivery received means the device
received the message, read receipt means
the user actually looked - opened the app,
looked at the message. You can turn off
read receipts, but as far as I know, you
cannot turn off delivery receipts. And so
here on the left you see a screenshot.
Three different messages were sent and
they have three different states. The
first message was marked as read, which
means it got a delivery receipt and a read
receipt. The second message is marked as
delivered. So it only got a delivery
receipt and the third message doesn't have
anything. So it hasn't received any
receipt. OK. So why is it useful? Here on
the left is some pseudocode of imagent's
handling of how it handles messages and
when it sends these receipts. And so you
can see that it first parses the plist
that's coming in and it's then doing this
nsUnarchive at some later time. And this
is this is exactly why all but would
trigger during nsUnarchive. And only then
does it send a delivery receipt. Right. So
what that means is if during our during
our nsUnarchive, if we can trigger the bug
and cause a crash, then we have somewhat
of a one bit sidechannel. Right. Because
if we cause a crash, then we won't see a
delivery receipt. And if we don't cause a
crash, then we see a delivery receipt. So
it's a one bit of information. And this is
going to be our oracle. All right. So
ideally, you have a vulnerability that
gives you this perfect oracle of is an
address mapped or not? So crash, if it is
not mapped, don't crash if it mapped. In
reality, you probably will not get this
perfect oracle from your bug. On the left
side, you see the real Oracle function for
this vulnerability, which is, well it has
to be mapped. OK. But then it's also using
the value that it's reading. And so it
will only not crash if the value is either
0 or if it has the most significant bit
set, that is some like pointer taking
stuff or if it's a real legitimate pointer
to an Objective-C object. So this Oracle
function is a bit more complex, but the
similar idea still works. So you can still
do something like a binary search, and
then infer the shared cache start address
in logarithmic time. Right. And so it only
takes maybe five minutes or so to do this.
But for this for this part, again, I have
to refer to the blog post which will cover
how this works. OK. So this is the summary
of the remote ASLR bypass. Two phases,
there's linear scan where it's just
scanning, sending these payloads and
checking if it gets the receipt back, and
the first time it gets a receipt back, it
knows. OK. This address is valid. I now
found an address that is within the shared
cache. And at that point it starts this
searching phase, which in logarithmic time
figures out the exact, precise starting
address. So there's a few common questions
about this that I want to briefly go into.
The first maybe obvious question is, can
you really just crash this agent like 20
plus times? And the answer is yes. There's
no indicator or anything that the user
would would see that this demon crashes.
The only thing you can do is you can go
into like settings, privacy, something
something, crash log something, and then
you can see these crash logs. Second
question is you can I think by default,
the iPhone is configured to send crash
logs to the vendor, to Apple. So isn't
that a problem? So I think I looked at
this briefly. What I stumbled across was
that it seems that iOS collects at most 25
crash logs per service. This is not
designed to be like a security feature.
Right. So this makes sense. But what that
means is that an attacker can use some
kind of, well, resource exhaustion bug to
crash this daemon maybe 25 times first,
and then only start to exploit and then no
trace of the exploit will be sent over.
Third question is whether this can be
fixed by simply sending the delivery
receipt very early on. I think this is...
this was my first suggestion to Apple to
just send this delivery receipt right at
the start. Eventually I figured out it
doesn't really work because you can still
make some kind of timing side channel,
because when when a demon crashes multiple
times, it's subject to some penalty and it
will only restart like a few seconds or
even minutes later. So from the timing of
getting a delivery receipt, you can then
still basically get this oracle. Right. So
it doesn't really work by just sending it
earlier. I'll go into some other ideas
that might work later. Okay. So at this
point I'm starting the demo. The demo is
two parts. Let's see where it is. Right.
So I have this iPhone here and you can
with QuickTime... the screen is mirrored
to the projector. So this iPhone is it's a
10S, so it's from last year. It's on 12.4,
which is the last vulnerable version. So
that's like half a year old at this point.
And what else? So there is no existing
chats open. Okay. And let's see. So I hope
the Wi-Fi works. What you can see here is
the way the exploit works that it's
hooking with Frida into... Do we get
delivery receipt? Uh, do we? Yeah. Okay,
cool. It works. So, yeah, it's popping up
these messages. The way the exploit works
that it's hooking the messages app on
macOS with Frida and then it's sending
these specific marker messages like
INJECT_ATI, and then the Frida hook
replaces this message with like the
current payload. Right. And now it's
testing these addresses. It's not too slow
I guess. Yeah. And it's popping up some
nice messages. Okay. It already found.
Okay. So this is already the end of the
first stage. So that was quite fast. It
found a valid address in this like first
probing step and now it has 21,000
candidates for the shared cache base. I
know it's doing this kind of binary search
thing to half that in every step. Okay.
Now it only has 10,000 left and so it's
quite fast and quite efficient. Okay.
While this runs, um, let's continue. So
this is where we are. We can now create
fake objects. We have all the addresses we
need. It's like this 1170 is where we can
place our stuff and then we will gain
control over the program counter. And from
there it's standard stuff, right? It's
what you would do in all of these exploits
you pivot maybe to the stack, you do
return oriented programing and then you
can run your code and you've succeeded.
Now, at this point, there is another thing
coming in. Pointer authentication is a new
security feature that Apple designed and
implemented first in the 10S, so this
device from 2018. And the idea is that you
can now - for this you need CPU support -
the idea that you can now store a
cryptographic signature in the top bits of
a pointer. OK, so here on the very left
side, you have a raw pointer. So the top
bits are zero because the way the address
space works. Now there's a set of
instructions that sign a pointer and they
will maybe take a context on it, but they
use some key that's not in memory - that's
in a register, compute a signature of this
pointer and store the signature in the top
bits. And that's what you see on the right
side. The green things. That's the
signature. And now before using this
pointer, the code will now authenticate by
running another instruction. And this
instruction, if the verification fails, it
will basically clobber this pointer,
make it invalid. And then the following
instructions will just crash. Right. So
here this is the function called the BL,
branch and link instruction. This is doing
a function call to a function pointer. But
first it's authenticating this pointer.
And if this authentication step fails,
then the process will crash right there.
What this means for an attacker is that
more or less, ROP is dead, because ROP
involves faking a bunch of function
point... or like, well, code pointers
really, that point in the middle of
existing code. So this is no longer
possible because an attacker cannot
generate these signatures. So this is
where our exploit breaks, right, the red
thing. Well, we have a fake objective C
class with our own function pointer. This
does no longer work because we cannot
compute these signatures. So what do we
do? One thing that's still possible and
it's even documented in the documentation
is that this class pointer in the object -
what's also called the ISA pointer -
it's not protected by PAC in any way.
Which means we can fake instances of
legitimate existing classes. Right. So in
this case here we can have a fake object
that points to a real class that has real,
legitimately signed method pointers. So
this tool works. And with this, we can now
get existing methods called, out of place
and kind of manipulate the control flow.
And these existing methods are basically
now gadgets. So if you want to think about
it that way. So what can we do with this?
One very interesting method we can get
called is dealloc, the destructor. So I
think in quite a few, maybe most of the
Objective-C exploitation scenarios, you
can probably get a dealloc method called.
Now what you do is you just enumerate all
the destructors in the shared cache.
There's tons of them, I think 50,000, and
you can get any of those called. And then
one of them or a few of them are really
interesting because they call this invoke
method, which is part of the NSInvocation
object, or class. And an NCInvocation is
basically a bound function. So it has a
target object, the method to be called and
all the arguments. And as soon as you call
invoke on this NCInvocation, it does this
method call with fully control arguments.
Right. So what that means is with this
destructor, we can now make a fake object
with a fake NSInvocation that has any
method call we would like to perform, and
then it's going to do that because it's
running this invoke here. Again, you see
this shield here, which I put in place for
things that Apple has hardened since we
sent them the exploit. So what they did so
far is they hardened NSInvocation and it's
now no longer easily possible to abuse it
in this way. But yeah. So for us, we can
now run arbitrary Objective-C methods with
controlled arguments. What about
sandboxing? If you do some more reverse
engineering and figure out what services
play into iMessage, this is what you end
up with. On the right side. So you have a
number of services. Most of them are
sandboxed. If it has the red border, it
means there's a sandbox. Interestingly,
Springboard also does some NSUnarchiver
stuff. So it's decoding the BP key. So it
could also trigger our vulnerability and
Springboard is not sandboxed. So it's the
main UI process. It's basically what's
handling showing the the welcome screen.
And so on. And so what that means is,
well, we can just target Springboard and
then we get code execution outside of the
sandbox so we don't actually need to worry
too much about the sandbox. As of iOS 13,
this is fixed and this key is now decoded
in the sandbox. Cool, so we can execute
Objective-C methods outside of the
sandbox. We can with that access user
data, activate camera, microphone, etc.
This is all possible through Objective-C
quite easily. But of course we don't care
about that. What we want is a calculator
and this is also quite easy, with one
Objective-C call - UIApplication
launchApplication blah blah blah. And so
let's see if this works. Go back to the
demo. So where are we at? So the, uh, the
ASLR bypass ran through. You can nicely
see that it roughly halved the candidates
in every round, or with every message
it had to send. It ended up with just
one message. Yeah, well with just one
candidate at the end. And that is the
shared cache base in this case
0x18a608000. Now it's preparing the heap
spray. This is all kind of hacked
together. I think if you wanted to do this
properly, for one, you can send the whole
heap spray in one message. I'm just lazy.
It's also probably way too big. Another
thing is, I think you would probably not
target springboard in reality just because
spring board is very sensitive. So if you
crash, did you get this re-spring and the
UI restarts. So I think in reality you
would probably target IM agent and then
chain the sandbox escape. Because while
this bug would also get you out of the
sandbox. So looks should be doable. Okay.
So I think the last message arrived. It's
freezing here for a couple of seconds. I
don't actually know why I never bothered,
but it does work.
Applause
Thank you. Yeah. So that was a demo. It's
it's kind of naturally reliable, this
exploit, because there is not much of heap
manipulation involved except this one
heaps spray, which is controllable. Okay.
Um, so what's left? I think one more thing
you can do is you can attack the kernel if
you want that. You have to deal with two
problems here. One is code signing. You
cannot execute unsigned code on iOS. And
then the standard workaround for that is
you abuse JIT pages in safari. But we are
not in safari or we are not in web
content, so we don't have JIT pages. What
I did here is I basically pivoted into
JavaScript core, which is the the JS
library. You can use it from from any app
also. And then I'm just bridging syscalls
into JavaScript and then implementing the
kernel exploit in JavaScript. This does
not require any more vulnerabilities. So
you do not need a JavaScript core bug to
do this. And the idea is very similar to
pwn.js. Maybe some of you know about that.
It's a library. I think initially
developed for Edge because they did
something similar was like JIT page
hardnings. So what I decided to do is take
SockPuppet from Ned or CVE-2019-8605,
which works on this version, it works on
12.4. This is the trigger for it. And I
only ported the trigger. I didn't bother
re -implementing the entire exploit. So
yeah, this is the trigger. It will cause a
kernel panic. It's quite short. Which is
nice. So if you want to run this from
JavaScript, really, there's only three
things you care about, right? So the first
one is you need the syscalls. So
highlighted here, there is like four or so
different syscalls here. Not a lot. And
you just have to be able to call them from
JavaScript. The other thing is you need
constants, right? So I have AF_INET6,
SOCK_STREAM. These are all integer
constants. So this is really easy, right?
You just need to look up what these values
end up being. And then the last thing is
you need some data structures. So in this
case, I need this so_np_extension thing.
It needs some integer value to pass
pointers to and so on. Yeah. And then this
is kind of the the magic that happens. You
take sock_puppet.c extract the syscalls
etc. There is one Objective C message you
can call which is very convenient, which
gives you a dlsym. What this lets you do
is, it lets you get native C function
pointers that are signed, right. Because
so far we can only call Objective C
methods, but we need to be able to call
syscalls or at least the C wrapper
functions. So with this dlsym method thing
we can get signed pointers to C functions.
Then we need to be able to pivot into
JavaScript code, which is also really easy
with one method call, the JSContext
evaluateScript. We need to mess around
with memory a bit like corrupt some
objects from outside, corrupt some area
buffers in javascript, get read, write.
Kind of standard browser exploitation
tricks I guess. But yeah. So if you do
this what you end up with is
sock_puppet.js. It looks very similar. You
can see a bit of my javascript API that
lets you allocate memory buffers. I read
and write memory, have some integer
constants and yeah, apart from that, it
doesn't really look much different from
the initial trigger. And so this can now
be served over, well, staged onto the
iMessage exploit building on top of this
object a C method called primitive. And I
guess at least in theory I didn't fully
implement it. This should be able to just
run a kernel exploit and fully compromise
the device without any interaction in
probably less than 10 minutes. Okay, so
this was the first part. How does how does
this exploit work. What I have now is a
number of suggestions how to make this
harder and how to improve things. So one
of the first things that is really
critical for this exploit is the ASLR
bypass, which relies on a couple of
things. And I think a lot of this ALSR
bypass also works on other platforms. So
Android has a very similar problem with
like mappings being at the same address
across processes. And other messengers
have these like receipts and so on. So I
think a lot of this applies not just to
Apple but to Android and to other
messengers. But okay. What is the first
point? So weak ALSR, this is basically the
heap spraying, which is just too easy.
This shouldn't be so easy. In terms of
theoretical ASLR, you can see it maybe
sketched here on the right. In theory,
ASLR could be much stronger, much more
randomized. In reality, it's just like the
small red bar. So it really it should just
have much more entropy to make heap
springing not viable anymore. The next
problem with ASLR is per-boot stuff. At
the bottom you can see it, right? So you
have three different processes, the shared
cache is always at the same address,
similar problems on other platforms, I
mentioned that. This is probably hard to
fix because by this point a lot of, quite
a lot relies on this. And it would be a
big performance hit to change this. But
maybe some clever engineers can figure out
how to do it better. The third part here
is the delivery receipts, which,
interestingly, they can give this side
channel, this one bit information side
channel and this can be enough to break
ASLR. And as I've mentioned before, I
think a lot of other messengers have this
same problem. What might work is to
either, well, remove these receipts. Sure.
Or maybe send them from a different
process so you can't do this timing thing
or even from the server. I think if you
send them, if the server already sends the
delivery receipt, it's a bit of cheating.
But at least this attack doesn't work.
Sandboxing, another thing, it's probably
obvious, right? So the everything that's
on zero click attack surface should be
sandboxed as much as possible. Of course,
to, you know, to require the attacker to
do another full exploit after getting code
execution. But Sandboxing can also
complicate information leaks. So not only
had this other iMessage bug
CVE-2019-8646, there's a blog post about
this one. It basically lets you. She was
able to send to cause a Springboard to
send HTTP requests to some server and
those would contain pictures, data,
whatever. If Springboard would've been
sandbox to not allow network activities,
this would have been much harder. So
sandboxing is not necessarily just about
this second breakout. What I do want to
say about sandboxing, ithat it shouldn't
be relied on. So I think that this remote
attack surface is pretty hard. And it's
not unlikely that it's actually harder
than the sandboxing attack surface. And
also on top of that, this bug, the
NSKeyedUnarchiver bug, it would also get
you out of the sandbox because the same
API is used locally for IPC. So there's
that. Yeah. This would be nice if the zero
click attack surface code would be open
source. Would have been nice for us. It
would have been easier to audit. Maybe
someday. Another feature that I would like
to see or another theme is reduced zero
attack surface. Make it one click at least
one click attack surface. Right. So before
and here you could see that an unknown
sender can send any messages. It would be
nice if there would be some pop up that's
like, well, do you actually want to accept
messages? Threema lets you block unknown
senders. I think that's a cool feature. So
yeah, there's more work to be done here.
Also, this restarting service problem, I
think it could get bigger even. So, here
we have pretty much unlimited tries for
the ASLR bypass. It's probably going to
become even more relevant with memory
tagging, which we can also be defeated if
you have many tries. So yeah, I guess if
there's some process or some critical
demon crashes ten times, maybe not restart
it. I don't know. It's gonna need some
more thinking, right? You don't want to
denial-of-service the user by just not
restarting this demon that crashed for
some unrelated reason. But yeah, this
would be a very good idea to have some
kind of limit here. Okay. Conclusion. So
yeah, zero click exploit, they are thing.
They do exist. It is possible to exploit
single memory corruption bugs on this
surface with, you know, without separate
info leaks. Despite all the mitigations we
have. However, I do think by turning the
right knobs, this could be made much
harder. So I gave some suggestions here.
And yeah, we need more atack surface
reduction, especially on the zero click
surface. But I think there is progress
being made. And with that thanks for your
attention. And I think we have time for
questions. Thank you.
applause
Herald: We do have time for questions. And
if you're in the room, you should line up
at the microphones and then we might also
have questions from the Internet. One
quick reminder is that all fun things that
what they work with explicit consent that
includes photos. So the photo policy of
the CCC is that if you take a photo, you
need to have explicit consent by the
people in the frame. So remember, don't do
any long shots into the crowd because you
want to have the consent of everybody
there. Good. We have the first question
from the Internet.
Question: The Internet wants to know. Did
Apple give you some kind of a reward? And
was it in your iPhone?
Answer: No, we did not get any kind of
reward. But we also didn't ask for it. No,
I didn't get a new iPhone, but I'm still
using mine. Which is it? Yeah. I mean,
this is a Xs, right? Current hardware
models can be defeated with this, if that
is the question.
Herald: Good. We have a question for
microphone number 3.
Q: Hello. Uh, just a question. I did not
truly understand how the fix with the
server or having another process, uh,
sending that there every message will fix
the problem because if it does work, if
you are in the right addresses, the thing
just will work. Make the server or the
process, send the delivery message and if
it crashes, it doesn't do anything so...
A: So the idea would be in this case, I'm
like sending this one method that would
crash and then either I get a delivery
received or I don't. If the server already
sends the delivery receipt before it
actually gives the message to the client
or to the receiver, then I would always
see a delivery receipt and I wouldn't be
able to figure out if my message caused
the crash or not. So that's the idea
behind maybe sending it on the server
side, if that makes sense.
Follow-up question: Yeah. But in this
case, if legit people send a message and
it doesn't reach the people because...
A: Yeah. Yeah. It's a hack. Right. So it's
not perfect. I mean the server could only
send to find this delivery receipt once it
like send it out over TCP and maybe got a
TCP ACK or whatever happens in the kernel.
But it's a hack in any case. Yeah. Like
it's a tradeoff.
Herald: We have a question for microphone
number two.
Q: Hello. Okay. Thanks for the talk. Two
questions. First: Is OS X also a
potential candidate for this bug. And
second: Can you distinguish multiple
devices with your address based
randomization detection?
A: Mm hmm. So yes: OS X or MacOS is
affected just the same. I think this
specific exploit wouldn't directly work
because address space looks a bit
different, but I think you could make it
work and it's affected. In terms of
multiple devices, so I haven't played
around with that. I could imagine that it
is possible to somehow figure out that
there are multiple devices or that you
know which device just crashed. But I
haven't investigated. That's the answer.
Follow-up: Thanks.
Herald: We still have time for more
questions. There was a question from
microphone number 1.
Q: Hi. Thanks for the talk. Quick
question. You said that exploitation could
be made without having any notification.
How would that be made?
A: Yeah, I briefly looked into how it
could work. Well. So for one, you can take
out parts of the message so that it fails
parsing later on in the processing and
then it will just be like thrown away
because it says, well, this is garbage.
The other thing is, of course, once you
get with the like very last message where
you get code execution, you cannot prevent
it from showing a message like a
notification, because that happens
afterwards.
Follow-up Q: But until you get the code
execution, you can't remove it. So you
see the first message?
A: So but you can do the other. The other
thing, like make it make the message look
bad - bad enough that like later parsing
stages will throw them away.
Follow-up: Thanks.
Herald: Good. We have a couple of more
questions. Remember, if you don't feel
comfortable lining up behind the
microphones, you can ask through the
signal angel through the Internet.
Microphone number 4, please.
Q: Yes. Hi. Hi Samuel. Um, I was curious
you have some suggestions about reducing
the attack surface. Are there any
suggestions that you'd make to save, like
Apple or Google? You know, in terms of
what they can see. You mentioned logging a
little bit earlier.
A: Yeah. So I sent pretty much this list
with the exploit I sent to Apple. And I
think the blog post will have a bit more.
But yeah, I told them the same thing.
Yeah, if that's your question, did I get
it right?
Follow-up Q: Yes. I mean, maybe I
misunderstood a little bit, but I suppose
that some of these reductions in the attack
surface seem to be in terms of like what's
happening on the device. Yeah. Whereas I'm
wondering in terms of monitoring. So being
able to catch something like this in
progress.
A: Right. Right. So this is gonna be
really hard because of end-to-end
encryption. So the server just sees like
encrypted garbage and has no way of
knowing is this an image? Is that the
text? This is an exploit? So on the
server, I don't think you can do much
there. I think it's gonna have to be on
the device.
Herald: We have a question from the
Internet.
Q: How do you approach a attack surface
mapping?
A: Um, well, reverse engineering, playing
around, looking at this message format. In
this case, what was somewhat obvious what
an attack surface was. Right. So figure
out which key is off this message are
being processed in some way. Make a note.
Decide which one looks most complex. Go
for that first. That's what we did.
Herald: We have a question from microphone
number 2, please.
Q: Hi. How long did you and your colleague
research to get the export running?
A: So the the vulnerability finding thing
was not only I think we spend maybe three
months finding the exploit. So I had a
rough idea how I wanted to how how I would
approach this exploit. So I think at the
end it took me maybe a week to finish it.
But I had thought about doing that for
like, while a looking while looking for
vulnerabilities and those two to three
months.
Herad: We have another question from
microphone number three.
Q: Um, is there the, uh, threat that the
attacked iPhone would itself turn into a
tack up by the exploit?
A: Sure. Yeah, you can do that. I mean,
you have full control, right? So you have
access to the contacts list and you can
send out iMessages. The question is if
it's necessary. Right. I mean, you can
also send messages from you don't really
need them, the iPhone to send the
messages. But I think in theory: Yes,
that's possible.
Herald: Do we have more questions from the
Internet?
Q: Does the phone stay compromised after
restart?
A: So there is no persistence exploit
here. No. You will need another exploit a
littlelailo did a talk. I think just an
hour ago about persistence. So you would
need to change this with what, for
example, to exploit that he showed.
Herald: And if you have questions in the
room, please line up behind the
microphones. Do we have more questions
from the Internet.
Q: Yes. So you've achieved the most novel
buck ever found to be fine in iOS. What's
the next big thing you'll be looking at?
A: Good question. I don't really know
myself, but I'm going to stay probably
around for zero click attack surface reduction
for a bit more.
Herald: Looks like we don't have any brave
people asking questions in the room. Does
the Internet have more courage?
Q: How long does discovery and
exploitation and development take and how
much does the team work to improve the
process and development time?
A: Okay, so how much how long does this
exploitation process work? That's the
first question. Yes. Yeah. I mean, this is
generally a hard thing to answer. Right.
There's like years of hacking around and
learning how to do this stuff, etc. that
you have to take into account. But as I
said, I had a rough idea how this exploit
would look like. So then really
implementing it was like one or two weeks.
The initial part of reverse engineering
iMessage reverse engineering this
NSUnarchiver thing. I kind of I think this
took forever. This took many months and it
was also very necessary for exploit
writing. Right. So a lot of the expert
primitives I use, they also abuse the
NSKeyUnarchiver thing.
Herald: We have time for perhaps two quick
questions. Mike number 4, please.
Q: Super. Uh, I'm not super familiar with
iOS virtual memory address space but you
should two heap regions when you showed the
picture of it. And I'm wondering why are
there two heap regions?
A: OK, because there is only a minor
detail, but I think there is one region
initially like below the shared cache and
one state is full. It just makes another
one above it. So it's really just like if
the one gets used or gets gets used up, it
makes another one. And that's going to be
like above the shared cache. I think
that's the picture you're referring to.
Follow-up: Yeah, thank you.
Heralds: And unfortunately, we are out of
time. So the person that might have some
number one, please come up to the stage
afterwards and perhaps you can grab a talk.
So please give a warm. I can't say this
exactly. applause Thanks.
applause
Postroll music
Subtitles created by c3subtitles.de
in the year 2020. Join, and help us!