 Hi everyone, my name is Max Bazali and today we'll talk about jailbreaking Apple Watch. Okay. Let me fix it. Oh yeah, should be better. So once again yeah, I'm Max Bazali and I'll talk about jailbreaking Apple Watch. I work as security researcher at Lookout when I focus it on malware detection. Last year I was a lead researcher on a Pegasus exploit chain and currently focused on advanced exploitation techniques for software, hardware. I co-founded Friday Apple team and I'm after various jailbreaks for iOS, TVS and now for WatchOS. So what is Apple Watch? It's a smartwatch device that was released in 2015 and it's used Apple S1 or S2 processor. It's basically 32-bit architecture processor called the RM7K which is derived from RM7A. It uses a Tapticad engine for user notifications. It has a 512 megabytes of RAM and it's running the WatchOS which is derived from iOS. So I hear a question a few times why we even need to jailbreak a watch? Well, I have a few watches at home and so it should be pretty interesting from a security researcher point of view. Why not give it a try? Why not try to jailbreak it? Because it's, if I can jailbreak it, I have access to file system so I can like check the WatchOS internals. And it should be just fun to run a debugger like Redar or Frida on this teeny Apple Watch screen or use it as iPhone attack vector to send some more format data to the iPhone. So before jumping to jailbreak internals, let's make a quick overview of the Apple Watch security which starts from a secure boot chain where the each element in the boot chain is checked to be properly signed by Apple and it basically stops boot if it's not. The next thing is mandatory code signing so all code that is running on the WatchOS should be signed by a trusted party. The sandbox which is limiting application access to other applications data and limiting application access to critical system APIs. Exploit mitigations like address space layout randomization on user mode and the kernel mode, heap and stack cookies and of course the data execution prevention. Starting from second generation of Apple Watch, there is a hardware baked secure enclave processor that's used for safe crypto operations. And the data protection. So all user data is encrypted when device is locked. So after thinking about the possible attack vectors on a watch and the first idea was why not try to send some more format payload or even like fast USB descriptors because there is a teeny port, the debug port on a watch if we detach some of the things we see like this small port. The problem here, the cables from this debug port to USB, they are not public. So I don't want to mess with all the non-public hardware so I decided to switch for other way. The other way will be send a malformant emails or messages or photos from a phone to a watch and try to basically attack the parser. Well, this will be still limited by a sandbox, the parsers. So I need additional out of sandbox bug to continue the jailbreak. Well, starting from WatchOS 2, Apple added the support for user defined application extensions which is basically the native code that's running on a WatchOS. So I decided to stay on this one. It looks pretty good and I have a freedom on a bug choice. So to choose one of the unfixed bugs and use it for a jailbreak. So this is how the jailbreak may look like. So first of all, we need to locate a kernel in memory. All the security measurements are in a kernel so we need to patch a kernel to disable them. For this one, we need to break a Kaisalar and find a kernel base, then read the whole kernel to user mode, start analyzing it, look for gadgets, set up primitives like read write, then finally patch a kernel to disable security restrictions and run the SSH client on a watch. Well, looks doable. So as I say, I have a few versions of WatchOS at home, like WatchOS 2 and WatchOS 3, and I start looking for a good bug. For WatchOS 2, there is a pretty good bug, the CV4656, which is user to free in a kernel. And this bug is famous because it's a part of the Pegasus exploit chain. So this may be a pretty good candidate. As alternative way, it's CV4669, which is a Mac port register. It can be exploited in 32 bits as a free in the run zone. But I decide to stay with OSNs relies because it's more stable. As for WatchOS 3, there are two bugs, CV7644, set DP control, which was discovered and exploited by IonBear in a Mac portal jailbreak, and CV2370, which is heap overflow in voucher extractor site. It was used later by Luca Todesco in Yalu 10.2. So I decide to stay with CV2370 or WatchOS 3. Okay. We now know a bugs, how to get the kernel level code execution. Let's start from leaking a kernel base. There are a few good CVs, CV4655 and CV4680. They both share the same problem. So because during the parsing in a kernel, one of the objects, which is like OS number object, it's basically object constructor missing the bound check-in, which leads it attacker can create OS number with a height number of bits. And later on, this height number of bits will be used as an object length. And the object length will be used to copy how many bytes we need to copy from a kernel stack to a kernel heap and return to user length. So that leads that kernel stack memory will be leaked and we can determine a kernel base. And this bug can be triggered from a sandbox. I will check more detailed. So the bug is in iOS and Serlase binary, which is a method to handle binary Serlase data. It converts a binary format to a basic kernel data objects like arrays, strings, booleans, numbers and so on. And the problem is when the parser goes to OS number. The OS number represents the number object in a kernel. And it's basically blindly trust the input arguments, here is the value in LAN, and just call the designated constructor, which is OS number in it. And here is a problem, because one of the arguments, which is new number of bits, is set to one of the class variable size. And this size will be used later on, on other method is number number of bytes. It's lead that return value of number of bytes is fully controlled by attacker. And why it's bad? Well, later on in ioregistry and target property bytes, this number of bytes method will be used to calculate the OS number length. And which is bad because OS number of value is stored on a kernel stack. Here it is offset bytes buffer. And basically the object length will be used to copy that many bytes from a kernel stack that needed from this buffer and return to user mode. As we control how many bytes will be copied, we can control, well, like, 255 bytes and basically leak some of the kernel memory. If this kernel memory will have any of the kernel pointers, it will be enough to terminate the kernel base. So we know the kernel base, now it's time to get some code execution in the kernel end. So CV4656 is a pretty good. It's used after free in the same IOS centralized binary, in the different branch. The problem here that during the deserialization, the OS string object will be delegated, but the pointer to object still stored in one of the arrays. And later on, the retain method will be called on this delegated data, which leads to use of the free. So if we can relocate this object with attacker control object, like fake OS string, which point to attacker control, we table, we can get the kernel mode code execution. This is how it's looked like. So the same IOS centralized binary, which parsed the binary and coded values and returned to IOS objects. It's the problem in when the object will be deserialized and saved to objects array. So now we see the set index macro that basically stored the pointer of an object, but not doing any memory measurement on it, like not retaining the object. This is bad, because later on, when OS string will be casted to OS symbol, the OS string object will be delegated, but the pointer to it is still existing in one of the arrays. And in the case of IOS centralized object, which is a reference to an object, it will be retained, which causes the use of the free. So we get the kernel level mode execution and the problem that we need to know what to execute in a kernel. So basically there are no watch IOS dump available in public in the time, as well there is no keys for watch IOS kernels. So basically to continue the drill break, I need to know some part of the kernel. And I have an idea why not create the fake OS string pointed to the beginning of the kernel and basically read the kernel as OS string chunk. Looks doable. The problem here that even the object is fake, we still need the real retable offset. And retable offset stored in data const, which means basically I need some parts of the kernel to dump a kernel. It's like a chicken and egg problem. And I start looking for ways, is there any possibility to dump this retable just in a runtime? Well, it is. As we know, the retable is stored in a data const in a kernel. And the data const is referenced in a kernel micro header, which means somehow we can leak the kernel header, we can leak the data const address in a runtime. And the offset to the data const reference in a header is always constant. They are like 0x2 to 4. I determine it from similar XNU build. So if in runtime we can branch to start of the kernel plus this offset, we can leak the data const. And how we can do it? Just use it as a fake retable pointer. So the kernel will crash, but we will get the data const address. Now if we know the data const, I was trying to calculate the offset for retable by checking, again, the similar XNU build from the start of the data const to retable. Unfortunately, it's not just work out of the box because the kernels are different. So I was trying to tune it with plus or minus 4 bytes delta, but in reality, it never works. So later on, I found that the difference are really significant between the watchOS and the iOS. It's like more than 4 kilobytes of difference in a data segment. So this method is not working. Okay, I'll start looking. It should be some other way to do it. And I look at OS string layout in a memory. So for 32 bits, the size of the object is like 20 bytes, for 64 bits like 32 bytes, and the very first pointer of an object is a pointer to object retable, and which is more interesting the layout of the retable. So our bug triggered the user free by calling a retain. And retain is like fifth element in array. So I start looking, well, it should be some way to like reuse this information. Here is what I mean. We have the OS string object layout in a memory, and the very first pointer of OS string object is point to object retable. And retable is pointing somewhere in a kernel code section, and our trigger, which is OS object retain, is like fifth element in array. Okay, so how we can use it? What if the object that will be triggered in a bug will be delegated but not reallocated? So this memory chunk will be marked as free, and which is more interesting, this free memory chunk will be pointing to the next free memory chunk in a free list. This is interesting. This is how it looks like. So we have a node in a free list. We have a free list head, and each node is pointing to the next node in a free list. And again, it's like the first pointer in this free memory chunk is a pointer to the next free memory. So what if we not reallocate OS string object? This memory will be marked as free. And basically, now it's pointing to the next free node in a free list. And this pointer will be interpreted as OS string retable pointer. So if we call retain on it, it will basically branch out of bounds on the next node. This is what I mean. So again, we have the nodes, each node is pointing to other node. And if we call the user to free without reallocating an object, one of the nodes will be interpreted as OS string. And the beginning of the node is a pointer to the next node, and this pointer will be interpreted as a pointer to OS string retable. Then we call retain and run out of bounds, basically branch out of bounds to the next node. But what if we can control the next node? So if you spray OS string objects, make a few holes on the heap, then basically trigger user to free. The pointer to the next node will be interpreted as a retable, which is now branching to the next node. But if this next node is OS string, it will branch just in the beginning of the OS string object, which is a retable. This is what I mean. This is the initial state of the heap with some memories allocated. And basically, heap sprayed a lot of OS string objects. So we fill memory and trigger some of those OS string allocations. So basically, now we have holes on the heap and trigger the user to free. One of these holes will be interpreted as OS string object and pointing to the next hole on the heap. And when we call the retain, it will be out of bounds for the next node, which in our case is OS string. So we will dump a wittable in a panic lock. Well, this method is working. I get it working on 64 bits. I can dump a wittable on 64 bit kernels. But for 32 bits, it's pretty painful to make it work because of the size mismatch on the zone size and the object size. So I say, oh, it should be some other way to do it. Well, I started looking for similar kernels and I found that the reference to OS string is OS string wittable is in iOS centralized binary. That's interesting because our bug is in iOS centralized binary. And iOS centralized binary is referenced in iOS centralized XML. Say, okay, if somehow we can leak one of the opcodes from iOS centralized binary, we should like calculate this wittable offset. So, yeah, it should be doable. So I crash in iOS centralized binary during the object deserialization. Kernel will crash, device will crash. I get a panic lock. I just copy it from a watch, read the LR register. Well, and now I know where the iOS centralized binary is like clear out over the start of the kernel. Which is interesting because now we can start to dump in the opcodes by a panic lock. So we can use the address we want to dump as address of our fake wittable minus 0x10. This wittable is, this pointer will be interpreted as a wittable and branch of this address. Obviously, the device will crash. But we get the kernel panic lock and the register state. And the register state will contain basically the PC register which is pointing to the content of the address. So I start dumping a kernel by a four bytes by crashing the device. Using the address as a fake wittable, the watch crash, wait until it's restored. And now I need to copy panic somehow. That's the problem because it's not just work out of the box. So I need to break my phone, SSH to a phone, trigger special service which basically copy panic from a watch to a phone. Then copy it to a Mac, parse the panic, get the address content, put it in disassembler, get the opcodes and update this address with like plus four bytes delta, upload it to a watch. And repeat. So it's like, you can say it's pretty painful way to dump a kernel. I can say it's pretty fun. Yeah, so I have a lot of panics and a lot of crashes. Once again, how it's looked like. So we, we need to start dumping opcodes in iOS centralized XML until we found a branch to iOS centralized binary. As soon as we leak this address, we can start dumping opcodes in iOS centralized binary until we found the reference to our string wittable. It could take like another 10 panics. Again, the full attack plan, crash in iOS centralized XML, dump the four bytes, put it in disassembler, read the opcodes until we find the opcode which is branching to iOS centralized binary. Leak the address and start leaking iOS centralized binary opcodes until we found the reference to our string wittable. Yeah, so it takes me like a long time to dump a kernel by this way, but it's, it's doable. This is how long it takes. Usually it takes like five minutes to recover a watch after a panic. And it takes another five minutes to fetch a panic from a watch. Because as I say, I need to jailbreak a phone, SSH to your phone and like keep trying and triggering the special service that is fetching all the panics. Then copy it to my Mac, parse it and I don't find any way to automate the process because I need to recompile the binary all the time and re-upload it over the Bluetooth to my Apple watch. So it takes me just two weeks to dump the wittable. Yeah, but now I have the wittable, now I can construct my exploit. I use the fake OS string with a real wittable, put it into the beginning of the kernel and start reading kernel as OS string chunks. We can even read them in a user mode using IA registry and to get proper. So I like the kernel header, calculate the kernel size from a header and dump the whole kernel into user mode. Okay, we have a kernel, but there is no symbols. So the first thing I've done, I've started looking for a kernel extension, basically a driver sending kernel, which I've pre-linked to this kernel. There are a few ways of doing it. We can look for the XML, which is usually at the end of the kernel. We describe all the kernel extensions that are pre-linked to this kernel. Or in a bad case, which can just look for a macro header magic. And if you find the magic, we know this is the beginning of the sum of the kernel drivers. So I find the kernel drivers and the next step is to find the system call table. The trick here that system call table not really changes over the watch OS version. So which means that Apple add the new system call at the end of the table, but they not really change the system calls in the middle of the table. So if we found the beginning of the table, we can automatically resolve like more than 400 functions and basically get the symbols. The same for Mac Traps. And finally, we can resolve IO kit objects with tables and get like even more symbols. So I have the same as implicated kernel and I started to analyze it and set up the primitives. We already have the exact primitive with use of the free. So all I need to find some interesting gadgets for read and write. So they're on a slide. We set up the primitives. We get a semi-simplicated kernel. And one thing I need is to get the internal kernel structures layout. In this case, there's a proc structure which represents the process structure in the kernel space. And the kernels are different between OS and watch OS and like I cannot even use the X and U source code again because of the differences. So I start to reconstructing this proc structure just in runtime by looking for proc underscore functions. They are referenced to some of the fields in the proc structure. So I can reconstruct the layout I need. In some really bad cases, I just dump the piece of memory and look for constant values. For example, like CPU type or CPU subtype, they're always content on the watch OS. And I know, okay, if it's a CPU type, it should be like a field zero extent or so. So I semi reconstruct a few of the structures that I need. And well, now it's time to find the patches what to disable in the kernel. I use a pretty classic approach called the patch finder. So when we are looking for a string or byte references, find the, yeah, find the pattern, find the reference to it. And with some additional instruction analysis, we can find the beginning of the function or the variable we need or so. Again, resolving the syscall table will be pretty useful here as we can automatically resolve more than 400 functions. In some bad cases, RMC2 instruction emulation was needed to determine it was the sum of the variables allocated. So it was a pretty big win for me. I get the semi-simplicated kernel, I get the internal structures layout, I know what to patch. Now it's time to patch. So I begin my jailbreak work by escalating to root privileges and get out of the sandbox. So the most easy way to do it is to patch the set tree ID function. So there is no kernel patch protection on 32-bit kernels, including the Apple Watch. So nobody really prevents us to basically patch a kernel and patch the kernel page tables. So I patch out the checks and set tree ID and just call set tree ID zero zero to get a root. Or there is other way if we don't want to patch the kernel code, iterate the heap, find the pointer to the whole prog pointer. And this whole prog find the pointer to the prog structure of our process. And this prog structure find the pointer to Ucred structure. And in this Ucred basically update UID, GID, area ID fields and basically become a root. The same thing is how to get out of the sandbox. The same Ucred structure, just null out the sandbox label. No label, no sandbox restrictions. So we are out of the sandbox. The next thing is how to obtain the kernel task. The kernel task is a pretty useful way to write anywhere in the kernel memory, read anywhere and even allocate the kernel memory. So most obvious way to do it is to patch task for PID. It's a special API that returns the process task to a user mode based on the process ID. Of course there is a hard coded check if the process ID is zero, which is kernel. Return null. So we can patch out this check and just read the kernel task. Other way to do it is again iterate a heap. Look for a whole prog pointer. In this whole prog pointer find the prog structure for a kernel. Find a pointer to a task structure in a kernel memory and save the send write, which is a self. Just write it to our task bootstrap port. Then in user mode we can read it by task get special port. It will convert this pointer to useful task structure. Well, yeah, we get the kernel task port. With the kernel task it's pretty easy now to write anywhere in the kernel memory, including the kernel text. So I start disabling the code sign checks. There is a global variable called debug, which is referenced by all PE icon has debugger functions, which like set the debugger capabilities for a kernel or one of the drivers. So we can set it globally to one or we can patch PACon has debugger only for a kernel extension that we need by updating nl symbol pointers. The next thing is update the unfree variables, like cs enforcement disable and allow invalid signatures. This will allow us to run any unsigned code on the Apple watch. And the last thing we need is to remount the root file system. The problem here that root file system is mounted as read only, which mean if we want to override it with some of our binaries or just add the new binaries, we need to remount it first to be writable. There are a few ways of doing it. There is a Mac mount function in the kernel and this function is checking if the file system is a root file system prevented from remounting. So we can patch out this check and call remount or again iterate a heap, find the pointer to root of sv node and this v node find the pointer to the Mac, the mount flags and basically remove a check and then remove the flag which represents this file system is a root file system, which mean this file system is not true file system, we can easily remount it with a Mac mount. Additionally, we need to patch lightweight volume manager. It's a special kernel extension, which is prevent us to write any data to protected partitions. In our case, the root file is a protected partition, so we need to find map4io function and patch out is a very protected check. In addition, set PE, I can debugger capabilities in this lightweight volume manager. So yeah, basically we patch a kernel, we disable most of the restrictions. So now I start to basically compile in my payload. I recompile the drop beer, which is lightweight SSH client for this RM7K architecture and recompile the basic tools package like PS, Chmod, LS and so on. Put everything in my watchOS extension and basically as soon as I get out of sandbox, copy it to a root file system. There is a problem here, when I spawn the drop beer, it got killed, it got killed by a sandbox, which means that watchOS has more restrictions than the iOS. So some of the things that just work on iOS get killed by a sandbox on watchOS. So I just found pointers to specific sandbox operations in a kernel and just nulled them out. Okay, now it should work, but I have a surprise. Here's the list of watchOS interfaces. The AWDL0 looks promising, which is Apple Wi-Fi direct, but as we see, now it supports only the Bluetooth, which may be a problem because I plan to basically connect from my Mac to a watch over the SSH and I need to somehow figure out how to run this SSH over the Bluetooth. And there is a way, sent to for Luca to point me to this possibility. We can use a mobile device framework on a Mac and send the special message to your phone. And the fun part that phones should not be jailbroken, it can be any release phone. So we send the message, it would say, hey, please start forwarding service port with a port 22. And we get a response, we should companion proxy service port. So basically what we done, we bind the port on a watch to some random port on a phone. And later on, we can use this port on a phone to bind it to some port on a Mac. Oops, what happened? I guess HDMI. Yeah, okay. So as I said, we bind some port on a watch to some random port on a phone and bind this port on a phone to some port on a Mac and basically over this USB and Bluetooth, we can run the SSH connection. And basically it works. Now I can show you how. Okay. Yes. So yeah, the very first thing I need to do is like bind this port. So I call it like a Bluetooth proxy, which bind port 22 on a watch to a phone and bind the same port from a phone to my Mac. Now I can use that as a local host with this port to connect over the Bluetooth to a watch. Yeah, we got the password prompt and yeah, we got the shell spawn it. So let's prove that we are running on the Apple watch by running the U name, which is Apple watch on watch one two. Okay. Let's try one of the common line twos like PS and dump the list of all the processes. So we see that the watch is pretty similar for the IS in the point of security in the point how the operating system work. For example, we can yeah, we can list our drop your client, which is yeah, doing SSH connection. So as I said, I recompile the basic tools like tar, like STP to easy archive or unarchive stuff to copy files from a watch. But as for me, pretty painful to copy files from a watch and to a phone, then copy it from a phone to a Mac. So I found more easier way and just STP directly from a Mac to a watch and just upload the files. So in this case, I just take Proz Explorer binary. It's a tool written by Jonathan Lewin. And yeah, copy it to a watch. We get a pretty good speed over the Bluetooth. Still copying. Yeah, so it's copied. Now let's prove that it's basically only watch by running our Proz Explorer. As I said, it's a tool it's like similar to PS or top but show the memory pressure and some other useful things. So the process called the watch pound is our jailbreak. So yeah, basically it's a copying to watch work. Now we need to find a way how to easy copy files from a watch to a Mac. For this purpose, I use like SSH to watch tar or basic archive some of the files that I need and use a pipe to easily just dump everything to my Mac laptop. Let's check how it works. So yeah, it started archiving the private frameworks on an Apple watch. And I will switch to my Mac basically to prove that we got some files copied. Yeah, as we see like three megabytes of Apple watch data was just copied to my Mac. With like one single comment. Okay, this is basically the SSH on a watch. So I start looking, okay, we get a jailbreak on a watch and the watch have a test to SMS calls. Even the photos and emails can be synced to a watch. It can fit GPS location from a phone. It's in some cases, if you use a watch to answer a call, it has a microphone usage and even it's involved in Apple pay. So it may be pretty interesting to look what is on a file system. So as I said, we have a jailbreak. So it's full attest to file system and we can look for escalate databases not limited to the messages, the call history, contacts, emails and so on. So what's going to be next? I basically recompiled the hooking in giant that can do an interposing on trampolining to hook some of the system function. Which means that I can catch data when the data will be synced between iPhone and a watch. So maybe in the future we will see the tweaks for a watch or as I said, we can run the Frida or Radar and on this pretty small watch screen. Okay, so as we see, the watch has is a pretty secure operating system. Its security is equal to iOS. But still there are some differences and some of the techniques that we use on iOS should be adapted for a watch. And as for me, the data forensic may be a little bit easier on a watch as on a phone because phones have all the kernel patch protectors and like a hardware restriction which watch doesn't have. Okay, basically we still have a time for questions. Yeah, it was an Apple watch jailbreak.