 Please be welcome to this fantastic kid here. I learned a lot from him, even though he's only since two years playing around with iOS. I mentioned as well the first untaturing case that I had with iPhone was something like with iOS 5.1 and with every update you had to do the whole shebang again, of course, that's what I remember. So, please give a warm welcome here to Little Lilo. I spelled this correctly. Yes. Little Lilo is really a very fascinating geek. He actually hacks any kind of OS device to my opinion, any kind of it. So throw it to him and it comes back in pieces. He's as sharp as a knife. Please give him a warm welcome and applause. We're going to untatter iOS. Yay! Okay, so yeah, I'm Little Lilo as already introduced. I'm interested in IT security in general and I started to look into iOS about two years ago. And then I met three awesome guys, Sigourza, Steg and Ivy Sparks. And we basically started chat a lot and in the end also released a jailbreak for iOS 10, on toilet, not spyware at all. So for everybody who isn't familiar with the jailbreak scene, it's like rooting on Android, so it basically is a tool that gives the user the capability to tweak their device and that's mostly done by basically installing a jailbreak. In this case, you just go to a website and then slide there and it will install a package manager on your phone. And then the end user can install tweaks, so just little dynamic libraries and they get injected into all the other system processes and then they can obviously modify their memory and that for example allows customization of the home screen or something like that. And basically with the release, we also formed a team that jacked there for 20 team and it was created and with that the Twitter account and there are also a few demos there if you want to check it out after the talk. And yeah, I basically had this pipe dream that in my life I really want to achieve an untatter jailbreak using only some sort of P-list or other safe file corruptions. So basically there are different kinds of jailbreaks. In modern jailbreak, the most common kind are semi-untattards which means that the user has to connect their device to a PC when they first install the jailbreak and then they side load an app onto the device, so it just installs itself there and then on each reboot, the user has to go into the app and press a button to jailbreak the phone. And basically after they reboot, they lose the jailbreak and have to go through this process again. And with an untatter jailbreak, the jailbreak gains persistent installation and then they will automatically jailbreak the device on each boot, so the device already jailbreaks jailbreak, boots jailbreak. So yeah, the untatter can be divided into four stages. In the first stage, that's the config file or database. Stage, I will go over how we gain execution of the boot. Then the config parser bug that gives us the right-to-wear primitive. And then I will talk about the main bug, the ASLR bypass, which allows us to get into ROP. Then stage two is basically a kernel exploit and completely written in ROP. And there I will go over the two main kernel bugs, so the KSLR leak and then the Ray C-33 we use to get the kernel read-write. And I will also talk about the KSLR weakness and the root domain user client memory leak, which aided us in exploitation. And after we get kernel read-write, we can remove a few restrictions from Apple. Mainly, we can bypass code signing and that gives us the ability to load an unsite dynamic library into our process. So now in stage three, we are basically in C via a dynamic library and there we are stashing the kernel task port to host special port four. So iOS has this concept of ports, task ports. And basically, if you have a send-write to a task port of a process, you can read and write its memory. They are most used for inter-process communication, basically. And where modern jail breaks use these, stash the kernel task port into a special port four so that other user mode applications can then retrieve it and with that retrieve the send-write and then they can just read and write process memory. And after that, I just fix up everything and then spawn stage four. And that's basically running in a separate process that has a few advantages I will go into later. And it's basically performing the whole post-exploitation process, including launching substrate, the framework that's used for tweak injection and then performing LUE start, to restart all launch demons on the system and inject the tweaks into them. So yeah, in stage one, we basically need to get executioner of the boot. And when iOS boots, launchD is the first process that's running on the system and it basically then loads a dynamic library with a list of executables that are launch demons and there are a few flags associated with them. And if the run and load flag is set, launchD will then spawn all of those launch demons afterwards. And we basically just attack one of those launch demons namely Raccoon. So what is Raccoon? Basically Raccoon is part of the IPsec package and it's a VPN client used to interact with an IPsec VPN server. And the problem is though that the IPsec tools project has been abandoned since 2014. So they obviously here state on their website that the development of the project has been abandoned and that IPsec tools has security issues and you should not use it. Apple still decided it was a good idea to ship it after 2014 and they now maintain their own fork on opensource.obl.com. And yeah, basically the only thing that's important for the talk is that Raccoon has a configuration parser. So on startup it will just look for a file on disk and start to parse that. And that's written in YAK, so completely written in C. So memory corruption might become a problem and it actually is a problem as we can see with the config parser bug. And for that one we have to go back a bit. Basically in 2011, Poch2G released Corona for iOS 5 which was also an untethered. And there he used a bug in Raccoon in the configuration parser. And Poch2G found this one an old bug tracker and basically the IPsec website had this bug tracker there where users could just report problems with their programs and one user in 2009 I think reported a bug that if he would use this specific config Raccoon would just sack fault but nobody looked at this for two years. So in 2011 Poch2G realized that this was actually exploitable and then used in a jailbreak. And after that obviously Apple got a bear of it and then they also said that they fixed it and it got a signed CV 2012, 3727. And yeah, they say that the issue was improved bounce checking and just improved bounce checking. So let's look at the patch. That's the function before the patch and that's the function after the patch. And if this was too fast for you, here's the diff. So basically there is no difference. The bug is still there and Apple didn't patch it. What really happened here was that basically this is obviously a configuration for parser. So there are different statements that nearly do the same thing. There are these DNS4 statements to parse an IP address and there are Wins4 statement followed by an IP address and for both of them they have to parse the IP address. So the function for parsing that was just copy pasted from another one and yeah, basically Apple fixed the bug in the other function, which I don't get because basically the engineer there had to look at it at the PUC Poch2G provided. Then they had to realize, okay, the bug is in there, fix it and then they had to recompile it and never test it against the original PUC because otherwise Raccoon would have crashed again. Yeah, it's basically an off by one which allows you to overwrite the index of an array and Poch2G gave a talk about this and we can basically reuse his strategy from back in 2011 to get the right repair primitive. So yeah, again, the one will function. Here at the top you can see the Yuck syntax. So an address wins list has to consist of either an address wins statement or an address wins statement followed by a comma followed by an address wins list. So that's a recursion there. It can be multiple address wins statements in a row and then below that an address wins statement is to find this containing an address string and that's just a regs to mention IP version four address and if the parser finds that it will run the code between those two brackets, so they get the pointer to a global struct and then they check if this nminus four index is bigger than the maximum wins and if so they return an error the problem here is that this is the off by one. It shouldn't be checking if it's greater than so but it should be a check for greater or equal than maximums and after that they use this in a pattern function to basically parse the address which is in this variable into the nminus four array at the nminus four index and then post increment the index and that will also become in handy when exploiting the Spark. So yeah, how is exploitation done? Basically there are here the globals. There's this LC config pointer in the globals pointing to a heap structure that the parser also uses and then there's this dns four array followed by the dns array index and then the nminus four array followed by the nminus four array index. So we can just use a modcfg statement followed by this wins four statement with the IP address to parse the IP address in the first array element and we can repeat this process four times to then move the index out of bounds so now it's pointing to itself and the cool thing is about that is that they thought it would be a good idea to use integers for those array indexes instead of unsigned integers so we can just point them, you just use negative number to bypass the bounds check. So yeah, we will override with minus one to point it to the dns array index and then we use another win statement to override the dns array index with a negative number and the cool thing about this is that because of the post incrementation it will move it back to zero so we can repeat this process as often times as we want and we will use a negative number to basically point it to the LC config pointer and then we can use two dns four statements to override the LC config pointer and point it anywhere in memory and after that we can use a timer statement with a counter and the parser will just try to write it to the LC config structure so it will dereference our pointer and write it there and the interval statement will basically write to UN32 which is followed by the UN, where the counter statement is written so we can turn this into a 64 bit write what we are primitive and write anywhere in process memory and that gives us the ability to basically exploit the ASLR bypass so this is the main bug of the whole untatted and allows us to basically have this primitive and it's also the reason why I decided to give this talk this year and not last year because there the bug was pretty fresh and only patched in iOS 12 and it gives an attack of the ability to exploit zero click more easily because they just need to write what we are primitive and that's why I held back on it. Seagruses are founded after looking into Apple's patches of the Corona ASLR bypass and basically from POTCH2G's presentation we as a team learned that ASLR is always bypassed when a writable region is larger than the maximum slide because then there's always a writable address in process memory and you can use this to brute force the slide and Seagruses found out that there was also the same problem with the cache in iOS 11 and a few versions of iOS 10 actually so what's the cache basically? The cache is a pre-compiled blob containing the dynamic link libraries from Apple so in each release they just move all of those into one big file to optimize load times of apps and keep memory pressure low so it's a pretty big file and for that on boot the kernel needs to calculate a slide and slide it in memory but they only slide it once on boot because it's used in every process and so Apple defines an area of memory for the cache with a base address and a size and that's hard-coded on iOS 11 the cache got so big that the maximum slide is now smaller than its data segment so the condition here is satisfied and ASLR can be bypassed basically and the weird thing about this is that actually the same thing also happened in iOS 7 back then the size was defined as 500 megabytes and the cache got bigger than 500 megabytes but before that it was also in a few versions possible to have the same condition that the data segment again was bigger than the maximum slide and Apple was actually also aware of this because ASLR talked about it in one of his talks but they just increased the maximum slide to one gigabyte and didn't add any asserts and now in iOS 11 it again got so big that the same thing happened and we believe that they didn't even really notice that up until iOS 12 where the cache was now bigger than one gigabyte so the kernel just couldn't fit it in the memory region and panicked and because of that they just thought well then we will increase it to four gigabyte while developing so we might see this reoccurring in, I don't know, iOS 15 or something like that. Exploitation is also pretty simple. We can just put forth the slide now and there are a lot of function pointers in the data segment so we decided to go with platform memmove because we can read it via Lcopy from the config parser and the basic strategy behind this is we write a slit ROP chain to an address we can always write to for a specific slide ABC and then we overwrite the platform memmove pointer at its unslit address plus the slide we just chose with a gadget that would pivot to our slit ROP chain and then we call it Lcopy and now there are basically two possibilities either we guess the right slide and then we jump to our gadget and pivot the stack or we guess the wrong slide but obviously nothing happened because we can just write there so we can go back to step one and try again and then with that we can just put forth the slide till we got the right one. The catch with this however is that the rightward way is pretty slow and it also needs a lot of storage so for a 64-bit write, I need about 120 bytes in the config file and because there are so many possible slide values the chain I have currently is around it's only two ROP gadgets but the config file is already 12 megabytes I think and because of that it takes around 10 seconds to run if it's a really bad slide so the ROP chain has to be as short as possible and the other problem is that if we have a bad slide we will basically smash the whole data segment and we can't really recover from that so we had crashes in malloc and stuff like that while developing basically. There are some solutions to that we can have a really short ROP chain in stage one and we can achieve this by basically just opening the big cache file to get a file scripted to it and then we can map it at a static address and then get many gadgets there because the file is obviously code signed by Apple so we can just jump there after mapping it but the problem with that is that nothing is set up so malloc and other functions aren't working but as I said earlier the current cache has to smash data segment so we don't really lose anything and after having the cache at a static address we can use the open and MAP functions from that cache to basically map stage two into the process memory and stage two is just a really big ROP stack so yeah. And then we are basically in ROP but we can only use raw syscalls and not much more because everything else will either use a function that uses malloc or will use malloc on its own and the other problem is that basically Erno is also broken so every syscall which fails will crash the binary so the first thing I do is basically map a few pages so that Erno works again because we have a free use discourse that might fail and then the other big problem is that we now need to implement a whole kernel exploit in ROP so I start to write a framework around this awesome gadget which is present in all the cache versions I looked at basically at the top you can see that all the high registers are moved into X0 through X7 so all the registers used for the calling convention ARM and then we branch link registers based on X27 so also a high register and after that we load all the register values back from the stack so we can just chain those gadgets together to call any functions we basically rob into the lower half and then chain another one of those afterwards and then they can load all the values call the function and then load all the values from the stack again and that's how the whole ROP chain works so yeah, I also used another gadget to basically add two registers together and another one which stores X0 so that I can just store the return value on the stack and later reuse it and for loops I use a gadget which just returns if X0 is zero so it's basically just a red instruction then and otherwise it will tail call a function using function pointer from the data segment and because I can control the whole data segment I can just put a function pointer there that will then jump to an epilogue and misses line a stack with that and then I can call long jump with two different register values and because of that pivot the stack to another location and when we basically didn't jump outside of the loop I just emmept the part of stage two which has the loop in it back over the old stack again and then I can just reuse the stack every time and then pivot up using long jump the problem with this however is obviously that it's pretty slow because I used MAP as a score but this can be solved by just unrolling your loops like for 10 iterations and then emmapping the file so that I get more loop iterations basically per second which is important for the race so now we will go over the kernel bugs so the KSLR leak is a CVE 2018, 4,413 by Panigall it was fixed in iOS 12.1 and it's luckily reachable from the Raccoon Sandbox because Apple Sandbox is most useful in processes and the Raccoon Sandbox is really tight we didn't have that many bugs that would work in Raccoon but luckily this one does because Raccoon has access to the CCTL functions and this one is in the CCTL ProgArcs X function the ProgArcs X function basically allocates a page using Kmalloc but it doesn't zero it so it might contain old heap data and then they copy the process arguments to the front of the page and now if you supply a wrong size from user space and there are a few other conditions that have to be met for some weird reason it copies from the end of the page into user space which I don't get by this even good through code review but there's a graphical illustration there's basically this page and this full of uninitialized data and then they put the process arguments in the front and copy out uninitialized heap data from the back into userland and we can obviously just spray specific heap objects with kernel pointers in them and then free them again and use this primitive to maybe leak them and if we find a kernel pointer in there we can just calculate this kernel slide so yeah and then we come to the WAC-23 as I said the main problem with the antenna is the Raccoon Sandbox so many of the kernel bugs that would work in iOS 11 from an app didn't work from Raccoon but luckily on the Halloween Spark it told me about the lightspeed bug from Scenetic which is reachable from the Sandbox it's a double free in Kellogg-16 so the kernel allocator is based on zones and with different sizes so there's for example Kellogg-16 and then all objects in that zone have a size of 16 or less bytes and there are obviously also other zones for bigger objects and basically there's the structure in there that then gets doubled and so there's the syscall that's handling async file events so a user mode application can just call in and tell the kernel, hey please write a buffer to a file and then move on with execution and it uses a kernel thread to perform those and for that it basically allocates a structure to contain some metadata like where the buffer is and to which file it should write and the kernel thread obviously has to free the structure after it's done because it just gets encroved into the query for the kernel thread then that wakes up and maybe sees it has a new job then performs the operation and then it has to free it, otherwise it will leak but the problem here is that if an error occurred while setting the structure up the second field in the structure will be zero and then the structure also isn't encroved into the jobs list so the kernel thread will never wake up and look at it so the syscall has to free it because otherwise it will leak and the problem here is that we can basically reallocate the structure before the syscall checks so what happens here is that the syscall allocates the structure and fills it up and then it gets added to the list and then the kernel thread is so fast that it runs while the syscall code isn't finished yet and basically gets the job done and freeze it and then we can spray all of we can spray the objects pretty fast to overlay with that structure and then the syscall finishes and checks the field and sees that it's zero because we just replaced it with an object that has zero at that location and so it frees it again, leading to a double free and yeah, we can obviously exploit that so for exploitation I just spam the syscall in one thread which is pretty hard to do in Rob but I just call thread create with a pointer to long jump and then pivot the stack to that location and then in the other thread I spray mach messages with mach port null in it and the thing with mach messages basically they can be used to do inter-process communication and you can also transfer port writes from one process to another so in this case we just send an empty port but you could also place something else there and that will create a structure in Kellogg 16 containing zero at that location and then if the mach message gets freed we can replace it with something and basically point it to somewhere in Kernel where a fake port structure lives and when we receive the mach message again it will basically think that this is a real port and treat it as such and with that we can create a fake kernel task port so yeah, but for that we obviously need to replace it and we need a heap spray and most commonly our surface is used for that as a kernel extension but because of our sandbox we are so limited that we don't have our surface access so the question is how we actually spray and their root domain user client comes to rescue with a memory leak so actually this function secure sleep system options is reachable from this Raccoon sandbox and Apple has a way of basically passing data to the kernel via XML so a user land application can just pass the XML to the kernel and then they will use this always answer as XML function to turn the XML back into C++ objects which the kernel can then use and if this sounds dangerous to you it actually is there where a few bucks in that but in this case we basically this just makes true with the OS dynamic cast that the data, the user mode application supplied is an OS dictionary so that it can use it afterwards and the problem here is that we can basically just supply an OS data object or an OS array so this OS dynamic cast will fail and un-serialized options will become null but the original point of return from OS un-serialized XML will get lost and so we will leak that memory and yeah we can just use this for spraying so then about those two primitives we use to basically exploitation the case law weakness there are these two CTL buffers and they are located in the kernel data region and because of that they are split with the same slide as the kernel text region and this means that as long as we know the kernel slide and we already do that from the KSLR leak and we can control the contents of a CCTL buffer we can get data to a known address and we can easily do that with Raccoon because it runs its route and so we can just switch our CCTL buffer and for example place the fake port structure there or we later also place a fake trust structure there but I will get into that so yeah now we can use the primitive to basically spray 10 OS data objects pointing to the CCTL buffer and then we just receive the mach message again and check if the port is null and if that's the case we basically place the structure and then for the non-SMAP version we can even get the case like by traversing a few pointers but that's not needed for the SMAP version because we already got it with the KSLR leak but non-SMAP devices we also don't need the CCTL buffers because we can just place the fake port structure in user land and then we get the kernel slide this way and with the kernel slide and this fake port we can create a fake user client and from there we can create a call primitive and then we can use this to overwrite the DSS trust cache pointer and point it to a buffer with two hashes for stage three and stage four so basically Apple has two ways of doing code signing either it has a user land demon that verifies third party applications or it has this so-called trust cache which is a list of hashes from all of their system applications and as soon as the process is spawned or a dynamically linked library is loaded and they will basically first verify if the hash of that file is inside of the trust cache and if so they will just trust it binary blindly because it comes from Apple basically and now when we overwrite this trust cache point and point it to our buffer we can basically place the hashes of stage three and four there and then the system will think those are Apple binaries and we can just load them so yeah and for that we need to use the ghetto DL open we can't use the real DL open because that uses malloc so we just open stage three to get a file descriptor then we attach the signature which now succeeds as the hashes and trust cache and then we can map it as read execute and jump there and then we are after two months of writing dropchains we are finally in C and we can write code more easily and the problem there is that we still don't have a working cache so we are still limited to the basic functionality and because of the ghetto DL open linking is obviously not working so we just rely on raw assembly for assist calls and I also pass some function pointers which I already used for stage two so for example open and then map to stage three from there we remit the kernel task and stash it into our special port four so that other user mode applications can use it and then we can basically escape the sandbox by zeroing the sandbox label in the process structure so that we can launch a new binary because otherwise the raccoon sandbox doesn't allow it but in the kernel those process structures basically have this label which tells the kernel which sandbox to use and by zeroing it you can just tell it to not use any sandbox and then we can launch stage four and with that get a working cache back and that's the big advantage from like having a separate file we now have the full cache functions working and can do work more easily and then it's just a call to poison spawn and then a raw exit syscall to exit the daemon without crashing because if launch D would see that one of the launch daemons crashed and the specific flag it said it would try to restart it and then our rope chain would run again we obviously want to prevent that so we use the exit syscall to exit it cleanly and then we are in stage four and from my side that was just basically to block all signals so we don't get killed by launch D because when launch D sees that the launch daemon exits it will send a sick kill to all its child process and I need to catch that otherwise stage four would get killed and then I just called the post exploitation framework which was written by Sparky and basically that does the following it first elevates the process to root with kernel credentials then it performs a remount of the root file system because on stock iOS the root file system is mounted as read only and we obviously need to mount it as read write to modify some files on there and then it sets the nonce so that the user might be able to downgrade to an older version if they have blobs verifies that the bootstrap is in place from the installation and then injects substrates so the framework that's used for to perform tweak injection and it's plug in into the trustcast and starts them so that they can start to inject into newly spawned processes then it spawns all the launch demons associated with the jailbreak unloads our own demons so that we don't respond it by accident run the kernel exploit again and then performs an LD restart to basically restart all of the launch teams of the system so that subset can inject tweak stem and with that the system is fully jailbroken and we can perform a few cleanup steps but yeah basically the end user has a jailbreak system then as a little side note while we were testing all the demons we got killed by jetsam a lot so basically jetsam is this kernel extension from Apple that is there for memory management and they basically want to make sure that the user mode application doesn't use too much memory because they don't have that much on an iPhone or all the iPhones actually so there is this list and jetsam if jetsam sees that a user and process uses more memory than it should use it will just kill it so we change the values in the pillars and the launch demons to actually allow the launch demons to use more memory but the weird thing about this is that this actually got accepted by jetsam and we had no more crashes while Apple actually tried to mitigate that beforehand so because jailbreakers always would modify those configuration files on the launch demons they start to move all of them into a dynamic library to guard them under code signing so that jailbreakers couldn't change them anymore but when we tried to figure out the launch at the launch order of demons we dumped that develop and there was also a playlist embedded for jetsam but Apple was still using those files on disk so I really want to look further into this because it seems like Apple isn't always ignoring those configuration files on disk and yeah then thanks to the whole team Sigus as Sparky and Stack for bouncing ideas back and forth and writing the remaining part of the jailbreak then for Poch2G and Synetic for the kernel box and basically also a big thanks to Zorik for Substrate and the whole jailbreaking framework and for Swagger, Parakeet, Geek and Smegas, and Ninsha for testing a few things and keeping me motivated and for Jonathan Levin for his books basically because he bought a few awesome books about iOS and that got me into it two years ago and yeah and in the future I think exploiting kernel vulnerability with other cache functions and only in Robb really is a pain and I probably won't do it again because I spent most of that but yeah the other big problem now is that with A12 so the newer iPhones pack so point of authentication kills most of these types of exploits because the problem there is that you would now need an ASLR bypass and the pack bypass to get into return oriented programming and it's pretty unlikely to basically have both because pack bypasses are even really rare and yeah I only know about this one ASLR bypass so yeah you would have to get pretty lucky also untill ring gets progressively harder Apple just fixed another good idea ahead in iOS 13.1 basically the idea was to use printf with the string format modifier percentage N to get a Turing complete machine because printf with this modifier is basically Turing complete and then use that to develop a pack bypass basically and get into Robb but now in iOS 13.1 I think Apple actually removed the percentage N modifier so you can no longer do this and yeah so this idea is also gone and yeah in the end I was able to complete my pipe dream so I guess I will need a new one so watch out Apple and that's Spice are there any questions? Thank you little Lilo for his fantastic work you know I suppose we're gonna hear more from you in the future maybe let's see I'm pretty sure are there questions here in this audience no one who wants to hire this guy now right away no one no one can you describe me what changed actually two times in these you know all the OSX OES OES versions what they changed to make this possible you know I told you like I started the the Turing challenge actually at 5.1 well they added a lot new mitigations and also obviously patched a few bugs like for example those ASLR bypasses that POTG2G used in Corona got patched and this one also now got patched by accident but yeah I mean like some bugs are still there for example the bug in Raccoon the config bar, the bug is still in ODE but yeah I don't really care about it and the kernel bugs got patched by Apple but for example the synthetic one they also patched wrong by accident and now it always leaked the struct but I think they also fixed that now your team you're mentioning your team you're working another in your room of course no and how are you structured how are the roles divided how is the well we are just like four people so and we have this group chat and then we are just hanging out there and bouncing ideas back and forth and maybe working on some stuff like close contact with the Apple developers no, not at all nothing no more I mean I reported one bug to their bounty ones or like actually just to them, not their bounty and that one got fixed and it was all fine but yeah for now I don't report bugs at the moment if you have time you have time left now actually you're looking for a new project isn't it yeah yeah and I might report some of those bugs in the meantime but I mean with the presentation they also know about them now so they might fix them they will be listening now and then at least probably I hope yeah is there anyone who has really we're sitting on a question here none of you it's already noon you know noon past so could be that none of you you can ask them something maybe someone else something maybe they can help you out with certain challenges that are there well I don't really have a question either I have my own research project now where like I do stuff at the moment and look at other things for example the the bootrom exploit came out now and so I yeah started developing on the check-rain team with them and that's what I currently do basically you're great man little Lilo thank you give him a warm-up applause