
Chaba Fitzell. Chaba. Thank you. Uh good morning everyone. Um my name is Chaba Fitz and if you came to an Apple related uh offensive talk then you are at the right place. So my talk is about Apple disco party. So this is all related to Apple's disk and file system related uh vulnerabilities. Uh a few words about myself. I'm currently working at a company called Kanji. Uh it's a company who does Mac OS MDM uh software and EDR as well and I'm working there as a principal Mac OS security researcher. I used to work at Offsec uh where I wrote a Mac OS exploitation training. Uh even before that I used to be red blue
teamer. Uh I do lot of Mac OS buck hunting uh both at work and my free time married with two kids. Uh and also I like to go hiking and trail running. Uh so the agenda the agenda sorry uh it's very packed. Uh I will briefly overview the discration service on Mac OS. uh some typical call flows related to disk mounting and then a whole lot of vulnerabilities uh related to these um uh demons and frameworks. So let's go in. So the discardation service what it is uh it's a systemwide service uh defined in in a as a launch demon. So it means that it will run as root uh every time uh the system starts up. It offers
a mock service. Uh mock on Mac OS is an interprocess communication method. Um and this mock service is reachable for every single process uh on the system even for those who are running inside the sandbox. And the demon is really responsible for managing disk mounting and unmounting. And it basically provides you an API uh with that and it it will call mount and unmount under the hood but it tries to do the heavy lifting uh of the mount of the whole mount oper operation. Uh so you don't need to call the mount core and uh so on. It also allows you to like register a call back and you can disallow or allow mount operation uh on
the system using this framework. Uh so why we like it? Why as as uh hackers or vulnerab researcher why this demon is so cool? So first of all it runs as root. Uh it's unsandbox. It has full disk access rights which so on Mac OS lot of stuff are not reachable even for the root user because of privacy reason. But with having full disk access right it uh opens up the gate for a whole lot of more access and again uh the service is reachable from the sandbox and it's also open source so it's very easy to audit uh its source board or its operation. Uh beyond that with the full disk access right it comes with the comapper private
security storage exempt heritable entitlement. The heritable means that if this uh demon spawns a process, that process will inherit this entitlement. And on Mac OS, entitlements will give you certain rights and specifically this right is like the close to the full disk access. Uh this is the the mock service it offers. So these are the functionality which are reachable uh from other processes. Uh it uses MIG the the mock interface generator. So it's like an um it's an easier way to call a mock service. Uh so how the call flow happens in this arbitration d uh it it's quite complex. So all the all the requests that are coming in to this uh demon arrive at the
DA server session Q request uh function and that will cue the request and do all sorts of stuff. um and eventually arrive to the DA command execute function which will call POSIX spawn and it will run the mount or unmount command depending on uh what you want to do and all of the permission checks uh like for sandbox or privilege or whatever happen on the DA server session Q request. So right at the initial function. So that's um this is why that's one of the most interesting function in this entire uh demon. So with that let's jump into the vulnerabilities. So the first one uh it's an old one uh from 2023 and it offers a sandbox escape.
Uh so I have a screenshot here and um I'm not sure who how many of you are familiar with Mac OS but basically what we have on the screen uh the the last line the disk 6S1 uh is is a downloaded disk image from the internet having the quarantine flag mounted in a directory that location is not um interesting now with all sorts of mount attribute like uh APFS, local, nodev, etc. Uh but one is missing um and that's the the quarantine flag. So on Mac OS um the quarantine flag is responsible for uh blocking non-signed and non- notorized processes from running. And when you download something from the internet, it will get the quarantine
flag. And if it's like a disk image and you mount the disk image, any file on the disk image will be quarantined. And when you try to start an application, uh the user will get a pop-up, hey, this application was downloaded from the internet. Do you allow it to run? And the user has to acknowledge it. And if that application is not signed or anything it the user don't even have the option to run it. So it will be blocked from execution. But if this flag is missing this prompt is kind of gone. So you can just execute anything. And similarly if you drop a file from the sandbox it will get the quarantine flag similarly as you
download it from the internet. Uh but if that flag is missing uh you can basically drop like a disk image and execute an unsandbox application uh from that and the user will not get any pop-ups. So again why is this problem? If the quarantine attribute is missing then the files are not quarantained. If they are not quarantained there is no gatekeeper check. Well, technically there is, but it's much more limited. And if there is no gatekeeper, we can launch anything we want. Uh like an unsandbox application. So if we are from running inside a sandbox, we can launch an application which is unsandboxed and uh that way we can escape the sandbox basically. Uh so what was the issue and why it
happened? uh because the disk arbitration D uh didn't add this flag um uh to the mounted image although the disk arbitration framework did have a function to check if the device which is mounted like the DMG file the the disk image has the quarantine flag or not but this routine didn't it was not called uh at So what happened? Uh if we if we have a disk image and call the open command on the disk image uh a device will be created using the DAS create from IO media and then an IO registry search will happen for the quarantine flag and if found then the quarantine option uh will be added and then the mount will be called with that
option. But if we just mount a dev disk um uh device directly uh then the DA create from BSD name will be called and that will call the mount operation directly and it will basically pass this uh quarantine search but the issue was that okay I'm in the sandbox I have a disk image how do I create a device uh like an attached device which I can mount um uh on a Mac OS. So the solution was I drop a DMG file from the sandbox. I run open DMG then the system will basically mount the disk image for me. It will create a disk device and it will mount it on a default location SL volume slash
whatever the volume name is. But now we have a de uh a device then I can call unamount I can unmount that device using the discardation framework and then I can remount it um in our location in the location of my choice without having the quarantine flag and once it's remounted I can just um open my custom application and and that's it I escaped the sandbox. So how Apple did fix this? Now this quarantine flag addition actually will happen in the kernel level um and not um not just on on the discardation D. So before I move on to the other vulnerabilities, I want to go through a couple of call flows. So what happens if
I just do a mount call? Uh I'm the caller running as X as as a user might be sandbox not sandbox. Uh it's not important. I call the mount uh command in that case. The mount command will run as the caller. So with the same privileges and then the mount command will call into the kernel and the kernel will check uh classic user permission. Does the user have permission to mount over this directory or does the user even have access to that um volume and it will also do a MAC call out. So it will also perform additional checks. So um let's just briefly check what happens uh if a user wants to mount over
a location which is owned by root. So we will call mount call it will run as the user because we are the user. The target is owned by root and the disk we we want to mount is also owned by root. Once it hits the kernel, it will fail because the user doesn't have privileges over a directory which is owned by root. So, but how things change uh when we have the discardation D framework. So, I'm the caller. I call into discardationd which runs on sandboxes and root and in order to prevent um attacks it has to perform some checks. So it will do a sandbox check to ensure that hey if this process is
sandbox can it mount over this location and it will also check um if the calling user ID is the same as the disk owner ID. This is a really weird check but probably one of the most clever checks um Apple ever did and we will see shortly uh why. Uh so if these checks are passed then the mount co um the mount command will be called by this arbitration and that mount command will be run as the disk owner and the mount command will call into the kernel. So let's see what happens uh if I want to mount over a a root own directory as the user. So I'm the user. I have a disk uh
owned by root and the target is owned by root. I call into this cardation D and it will fail right away because the disk is owned by root. So okay let's change it. Let's bring our own disk and I as a user will try to mount over a target which is owned by root. But now the disk is owned by me. So it will get a pass from this arbitration d and this arbitration d although it runs as root it will call the mount operation um as the user. So the mount uh command runs as the user and then once it hits the kernel it will fail because the user doesn't have privileges over a root on
um directory. So what happens if I try to trick all of this with with a sim link uh which was which was possible a couple of years ago. Uh so let's say everything is owned uh by the user. Uh initially the target is a symbolic link pointing to temp mntd which is owned by the user. I call into this arbitration d get a pass. Now this arbitration d um will call the mount call with the - k option which means do not follow symbolic links and when it hits the kernel uh it basically will fail because the target it's a symbolic link. It doesn't matter where it points to the fact that it's a symbolic link, the kernel will
drop the core. So then this leads us into the next vulnerability which is a sandbox escape and the local privilege escalation at the same time. Uh so this carration supports two types of file system I would say. One is backed uh by canel extensions. So like canal drivers and the other one is backed by user based file system which don't run as canal drivers but these two run in a different code path and the symbolic link check which I just show you does not happen in the case of user FS file system. Additionally um the user FS file system uh is mounted by the FSKit demon which runs as root and it will always call the mount
command as root. So there is no propagation of ownership of the disk. So basically what we have here uh the mount call will follow symbolic links because the dash k is not present and it will be run as root. Um um this is just a call flow uh showing how the call is different in case of a user base file system. So because it runs as root I can mount over any directory which is owned by root. Um and because symbolic links are followed, I can mount it anywhere I want even from the sandbox. Um so how can I weaponize this uh like actually exploit it? Uh I start uh as the user pointing uh the symbolic link
to okay place which will get me a pass on every check. uh once it passes this arbitration d I will swap the the symbolic link and I will point it to another location of my choice and eventually the mount call will be uh called as root and the mount will happen to to my location of choice. So how can I thank Gay for actually for this idea. Um so what we can do we can mount over any directory as root but how we get code execution as root uh from this now there would be many easy ways to do it but Mac OS lockdown um many many locations that even as root you cannot mount over it because it's
protected by the privacy framework. work. Um, so I I needed a way to actually turn this mount to a code execution as root. So what we did is mounting over the etc cup cops uh directory. Cops is the Unix printing service. Uh this directory has a cups files configuration file uh which has an error log um specification for a file where I can specify okay where the errors will be logged and I can also specify the permissions for those files and I will add some junks to the end of the configuration file to actually produce an error. So then I can call cubect ctl. Uh cubct ctl will trigger cub's demon which runs root. It will read the configuration file and
basically it will drop the error lock file. In this case it will drop a file into etc sudo sdlp. Now at this stage I have a file which is word writable. So I will put in the the no password trick that every user can elevate to uh to root with pseudo uh without a password. But the problem is that the the five permissions are 777. So sudo will ignore that. So I will edit uh the cups configuration file. Trigger cupl and change the lo file permission to 70. and then trigger cupl again. So it will reset the permissions and although there will be some junks in this uh in this file u p sudo will just ignore it and at the
end I can run sudo sue and I get a root prompt. Uh so this is how I get root from a user. But how do I escape the sandbox? by mounting over a specific directory. Uh that also not a trivial question to answer. So one solution I came up with is mount over the library preferences directory in the user's home folder and drop a a terminal uh property list file. This property list file will contain a command string uh which will be executed by terina when it's opened and because terminal runs unsandbox this command will run unsandbox. This can be any shell command I want. So what I did I put my um my LP script which utilizes this
technique um also in the preferences folder and I says okay once terminal is open run this LP script. So basically chaining the two together and once you open terminal it will run this command and you can escape the sandbox. Now Apple is listening and this technique was closed in Mac OSA. Uh so on latest version of Mac OS you cannot perform this trick because the privacy framework will protect the preferences uh directory and it will prevent you from mounting over that location sadly. Um so we need a new way then uh if you want to do sandbox cascape with mounting and this is just a video uh showing it. Uh so I'm a user I'm not even an admin
user and I have this DA user FXSBXRP application which is sandbox. I'm showing that pseudo requires a password and there is nothing mounted. Uh I'm starting the app because this uses symbolic links. It's a race condition. So it takes a while to uh to win the race condition, but we see it's happening. So I already escaped the sandbox. And now the LP the the privilege escalation is running uh inside terminal uh at this stage and it tries to mount our etc uh cups and it succeeded uh with triggering cupl we need to wait a little bit because by calling cubectl and that cause cubsd it takes some time to actually go through this entire uh call flow. So we
we give it some time uh to complete and yeah at the end we got root um that's it how Apple fixed this uh now they add no follow also for the user FS mounts and now fskit D gets the original uh caller being propagated did. So it will call the mount operation as the original caller. So not as root. Uh next CV is a sandbox escape and TCC bypass. TCC again is Mac OS privacy uh protection. Uh so what we are looking here is drop a custom privacy database which allows our application to access certain privacy protected resources like the desktop the documents folder or or whatever and this use directory traversal.
So let's take a look again at this core flow. when I provide a pass a destination for the mount operation for disk arbitration D uh we do it with using the DA dismount with arguments common uh API call which will do a pass resolution so if if I put any dot dot slash uh in the pass it will remove that and it will resolve the symbolic link and it will send it to this card iteration And this change is persistent. So whatever pass is resolved when it leaves my process, it will be retained until it hits the kernel which is very very important. So this arbitration D will do a symbolic link resolution and dot dot slash
removal but only for the time of uh privilege and sandbox verification otherwise it will pass it on. So this is why if I put a symbolic link in the pass it will remain all the way to the kernel. And actually this is really important because if if it would keep resolving the path I could keep dropping symbolic links and eventually the the kernel wouldn't receive the symbolic link because the path would be resolved earlier. Uh so the so all the no follow option would make no sense at all. So this this state is really important to to be intact but we will utilize it for our advantage. So because the dot dot slash and symbolic link
resolution happens in the API and the API is in my program in my program space. What I can do is well either patch it or just use re reimplement the the API in the way I want and call an other lower level function and call into this arbitration that way and with that this is under the user's control. So you don't need you can basically bypass this pass resolution and dot dot / removal. So what happens? So basically I can pass anything to this conversation D I want uh at this stage. So what we do I create a target private temp do slash pointing at the very end to the comapp tcc directory where the TCC
database is stored. And I have this directory pointing to private them 1 2 3. This is the the target pass will be passed to this arbitration D. So this arbitration D will okay I will do a sandbox verification and again that will follow symbolic links. There is no problem with that and it will resolve this the the directory and the resolve pass is private temp123/ etc. Eventually it translates to private temp users etc. and it will give it a pass because the como TCC under private temp is not a TCC. It's not a real TCC directory. The real one is in the user's home. But this is not the user's home. This is private temp fake user home.
Basically, it will command again. The target is retained. And at this stage, I drop the symbolic link. I delete it. Just create an empty directory called deer. And when it gets the kernel, it doesn't have a symbolic link. So the camera will say it's okay and the camera will resolve the path with all the dot dot slashes and since there is no symbolic link it will resolve to the real TCC directory at the end and it's also gets a pass and it will mount over the TCC directory and we can perform the same trick mounting over preferences uh as well. So this is what I'm showing here. I have this DA traverse application showing nothing is mounted
and
app is running. We can see that it's already mounted over the TCC directory. And now it tries to mount over preferences so it can escape the sandbox as well. We restarted TCC uh D and now we can see terminal has all sorts of extra permissions. Hower fix this? uh they said okay uh we will do one pass resolution but not when you make a call into this arbitration D but when the call hits this arbitration D at the very beginning uh they actually have an extra pass which um offers um no resolution so we can still pass in a dot dot slash if we want and hit the code part where it doesn't resolve that.
But now the sandbox check will drop uh will deny the request if there is a dot dot slash uh in the past and we will see why this was needed why this why there are two ways uh to call thisation D uh and this is because of this and the next vulnerability. So this corgisation D I think with all this callflow is already getting complex but we can add even more players uh to this story. We can add this storage kit D to the story and we cannot add this QTL to the story. So if I call this QL which is a command line utility to mount a directory this QTL we will call storage
kit D. Storage kit D runs as root on sandbox. Storage kit D will do some basic checks and storage kit D will call into this card withration D. This card withration D calls mount. Mount mount call. Um what could go wrong? So let's start. I'm running as the user. I have a disk owned by root and I try to mount over location as root. uh which own by root call this ut everything is the same get to storage kit d storage kd haven't done any checks uh on permissions it we get we get a pass now storage kit D calls this arbitration D not the user and storage kit D runs as root so from this
arbitration D perspective the caller is running as root everything is root, you get a pass, still everything is root and the kernel will give you a past. You don't even need to do anything. Uh you could just call this QTL to mount over any directory as the user. It just flow through uh straight away. Um I will run out of time so I will skip this video. Uh so Apple fixed this and thankfully it was wrong and it not only allowed us to do privilege escalation but to do also TCC bypass. So, storage kit D now performs um a privilege check, a sandbox check, everything is the same. But uh when storage kit D gets the request, it
will do the checks, some time passes and it will call this card withd. Now when it calls this code iteration D using the old API it did a pass resolution. So it means that whatever there was uh it resolved a pass. So if I send in a target temp link which let's say it's a symbolic link and I start to alternate that symbolic link between temp mnt which is owned by the user and let's say etc cup cuffs which is owned by root. What happens? Storage kit D will perform a check on temp mnt and when it calls this arbitration D, it will resolve the symbolic link and put etc cups and when it gets to this
arbitration D, there is no more symbolic link in in the game. Uh so the mount will succeed at the canel at the later stage. And the same trick just pointed to to the TCC directory. Um again we have we go through just by uh swapping the symbolic link. Um basically so eventually it meant that Apple in order to fix all these issues they had to oversee this entire process uh as one and make sure that storage kit D and this arbitration D kind of works together because if I do any symbolic link attack and if storage keyd resolves that uh then the mount-k option at this arbitration D is basically pointless um again we'll skip
the video so this is why Apple introduced two different call flows in this arbitration D. So when you normal if you are normal client using the normal APIs and call into this arbitration D it will do a pass resolution depending if the DA disk mount option no follow is set or not. Now you could think okay I'm a smart attacker I will set this option and bypass pass resolution but then you hit sendbox check and if it contains a symbolic link or if it contains a pass traversal option it will deny the request. So there is like a safe uh check there and then if the request is coming from storage kit D um then it will bypass
pass resolution and will go directly. So basically whatever pass you provide to storage kit D that will remain and go all the way to the kernel without being resolved. So if it has a symbolic link it will remain which is crucial um for not being exploited. So that was all the discretation related stuff. Uh now there are some more lightweight vulnerabilities. Uh one of them is bypassing time machine data pro uh protection with APFS. So time machine is Apple's backup uh solution. You connect your disk and it will basically back up all the new files uh to that disk. And APFS is Apple's file system. So I talked about privacy. Time machine backups of course they are they contain
a whole lot of private data. So they are protected by the dcc framework. So even if you are root try to list or browse uh that backup from either terminal or or like an un unprivileged app from privacy point you will get access denied again even if you are root uh because if it was allowed then you can access all the private data on on the user's backup like all the documents contacts everything. Uh you can browse Time Machine if you have full disk access permissions but in that case you have access to every other file anyway. So APFS defines quite a few disc crawls and it turns out that time machine is defined as the backup disc crawl
marked with T. Um and if if we list uh the disk with disk util we will see that it's marked as backup. Now if we check the Apple's sandbox profile we will find that um uh there is a storage class map and it will say that if if the storage is defined as backup then it will get this time machine assignment and that how the system knows um that it should TCC protect uh that location. So basically you have a time machine backup disk with the backup pole and that backup pearl will translate into a time machine uh protection. Turn out that you can just clear this row. So you can just say okay change
volume roll clear unmount the disk remount it and since the disk is no longer a backup disk because you cleared its role from that stage you can access all the data on the disk fix. you can no longer change this uh role. And the last one, this utility um local privileges escalation. So there is a utility uh called ASR on Apple. It stands for Apple software restore and basically it allows you to restore bit copy one disk to another. There are two ways you can call this um utility either from the command line as root. Uh if you call it as the user it will just exit. So you either call it as root or you can call ASR as a demon like
ASR can run as a demon but and you can call it over XPC like an interprocess communication but in order to do that you need to have an entitlement called com Apple private ASR. Now you can find out from the private this entitlement is for Apple only. So you cannot give this entitlement to your application. But this utility does have this entitlement. So this utility can call into ASR. Now the problem is that if you do a restore using this utility, it doesn't ask for a password. So it allows the GUI uh to restore a disk without a user password. And basically what you can do is take a disk image which has a sweet binary on
it and restore it somewhere on locally on your device. Um so we create an empty volume again you can do that with this utility. Um, I do restore and I have the disk with the SUID binary and it will bitcopy everything there and that disk uh now is restored like now that binary is basically copied over um I have this new volume mounted because it's a local volume on the disk uh It runs it suede and you can just run your sweet binary and get root from this. It was patched. Now I think if you do a restore with this utility um it does require a password. Now this does require user interaction. So you
cannot script or automate this um stuff. But let's say if there is a company with like an unattended workstation or something, you can do this trick. Uh conclusion, this conversation D is a demon that keeps giving. Um this is a sec I I think at Hackivity I presented a whole lot of other vulnerabilities from this caration D and for this talk there is a part two coming next year because there were still a whole lot of other stuff um in there. Thank you [Applause]