A useful feature would be to list installed profiles on OS X - essentially, information that shows up in the 'Profiles' preference pane under 'System Preferences'.
The main problem is: it appears that the actual information is stored in the file /private/var/db/ConfigurationProfiles/Store/ConfigProfiles.binary, but I don't recognize the format of the file. Some quick googling also returns very little information. It would be possible to get this information by using the profiles command (e.g. sudo profiles -P -o stdout-xml), but this does involve running a non-osquery command, something that we don't do right now.
Thoughts?
More information: https://gist.github.com/pudquick/3de58b9c4c682a4935a3
Interesting. And yeah, I don't think that shelling out to profiles is an option.
I don't have a Profiles pane under System Preferences (no profiles installed), which also means that both ConfigProfiles.binary and Provisioning.binary are empty files for me -- so I can't really poke at them.
However looking at the object files/libraries of /usr/bin/profiles, it is using ConfigurationProfiles (which is in turn using ConfigProfileHelper) framework, but unfortunately they are both private frameworks.
Looking at the symbols of those frameworks, my hunch is that the file format for ConfigProfiles.binary is a CoreData serialized binary plist of bzip2 plists (which describe the profiles):
U _BZ2_bzBuffToBuffCompress
U _BZ2_bzBuffToBuffDecompress
00000000000289b4 T _BZip2Compress
0000000000028b2b T _BZip2Decompress
Those bzipped2 plists (which describe the profiles) might also be optionally encrypted as that framework also plugs in Security framework. If my memory serves right, those encrypted profiles are decrypted by certificate present on the device, at least on iOS devices.
I currently don't have a way to virtualize OS X (at least not without much effort) to verify these, so if you have example profiles or those binary files that you are up for passing along, that might help.
But yeah, while it would be cool to have this functionality in osquery, it seems non trivial.
Also, if you have those .binary files handy, might help to run those through the magic table.
Something like:
select * from magic where path = '/private/var/db/ConfigurationProfiles/Store/ConfigProfiles.binary'
to see if it identifies anything.
Also IIRC, I think that Apple's bzip2 implementation is slightly different, so libmagic might not pick it up, even if the correct bits were carved out (at least didn't use to).
Also, does managed_policies or preferences tables expose any related information?
Edit to Add: From #879 it seems that managed_policies should enumerate at least some of the profiles if they are cached. Is that not the case?
So, I dug around a bit - it looks like the managed_policies table exposes the results of policies, but not the actual policies themselves. Here's what I've found.
First off, the profile I'm using for investigation:

You can see the description shown. If you run sudo profiles -P -v, the output shows the profile:
_computerlevel[1] attribute: name: Login restrictions for Laptops
_computerlevel[1] attribute: configurationDescription: Don't allow Guest accounts, Don't allow "Other users", enable Fast User Switching
_computerlevel[1] attribute: organization: Stripe, Inc.
_computerlevel[1] attribute: profileIdentifier: CA173F78-1158-4622-9DC2-7C2DF0060BE7
_computerlevel[1] attribute: profileUUID: CA173F78-1158-4622-9DC2-7C2DF0060BE7
_computerlevel[1] attribute: profileType: Configuration
_computerlevel[1] attribute: removalDisallowed: TRUE
_computerlevel[1] attribute: version: 1
_computerlevel[1] attribute: containsComputerItems: TRUE
_computerlevel[1] attribute: internaldata: TRUE
_computerlevel[1] payload count = 6
_computerlevel[1] payload[1] name = Passcode
_computerlevel[1] payload[1] description =
_computerlevel[1] payload[1] type = com.apple.mobiledevice.passwordpolicy
_computerlevel[1] payload[1] organization = Stripe, Inc.
_computerlevel[1] payload[1] identifier = 8AEBE656-548F-4929-8573-97BF25AC4B8F
_computerlevel[1] payload[1] uuid = 8AEBE656-548F-4929-8573-97BF25AC4B8F
_computerlevel[1] payload[2] name = Login Window: Scripts
_computerlevel[1] payload[2] description =
_computerlevel[1] payload[2] type = com.apple.mcxloginscripts
_computerlevel[1] payload[2] organization = Stripe, Inc.
_computerlevel[1] payload[2] identifier = 7D92F73C-E5B5-4FF2-9384-CE1EB6EEBCCF
_computerlevel[1] payload[2] uuid = 7D92F73C-E5B5-4FF2-9384-CE1EB6EEBCCF
_computerlevel[1] payload[3] name = Login Window: Global Preferences
_computerlevel[1] payload[3] description =
_computerlevel[1] payload[3] type = .GlobalPreferences
_computerlevel[1] payload[3] organization = Stripe, Inc.
_computerlevel[1] payload[3] identifier = 0BECC6E7-060F-45C3-884C-6E86D3521E89
_computerlevel[1] payload[3] uuid = 0BECC6E7-060F-45C3-884C-6E86D3521E89
_computerlevel[1] payload[4] name = Login Window: Screen Saver Preferences
_computerlevel[1] payload[4] description =
_computerlevel[1] payload[4] type = com.apple.screensaver
_computerlevel[1] payload[4] organization = Stripe, Inc.
_computerlevel[1] payload[4] identifier = E6FDB8FD-ECEE-4259-BA8C-3FC0B31FB855
_computerlevel[1] payload[4] uuid = E6FDB8FD-ECEE-4259-BA8C-3FC0B31FB855
_computerlevel[1] payload[5] name = Login Window
_computerlevel[1] payload[5] description =
_computerlevel[1] payload[5] type = com.apple.loginwindow
_computerlevel[1] payload[5] organization = Stripe, Inc.
_computerlevel[1] payload[5] identifier = D32B83B7-C971-4793-9241-EC60366DFE98
_computerlevel[1] payload[5] uuid = D32B83B7-C971-4793-9241-EC60366DFE98
_computerlevel[1] payload[6] name = MCX
_computerlevel[1] payload[6] description =
_computerlevel[1] payload[6] type = com.apple.MCX
_computerlevel[1] payload[6] organization = Stripe, Inc.
_computerlevel[1] payload[6] identifier = 7C194A77-E217-4D55-974A-03521738A293
_computerlevel[1] payload[6] uuid = 7C194A77-E217-4D55-974A-03521738A293
Then, running the query SELECT domain, uuid, name, value FROM managed_policies will show a bunch of information, including the policies that get applied by the profile (a bunch of information truncated):
| domain | uuid | name | value |
+----------------------------------------+--------------------------------------+--------------------------------------------+------------------------------------------------------------+
| com.apple.MCX | 7C194A77-E217-4D55-974A-03521738A293 | DisableGuestAccount | 1 |
| com.apple.MCX | 7C194A77-E217-4D55-974A-03521738A293 | EnableGuestAccount | 0 |
+----------------------------------------+--------------------------------------+--------------------------------------------+------------------------------------------------------------+
But, the query doesn't show the description or name of any of the profiles (i.e. the UUIDs from the profiles tool don't appear in the query output), and not all of the information directly correlates. For example, dumping the profiles as XML you can extract the portion responsible for minimum password length:
<dict>
<key>PayloadContent</key>
<dict>
<key>allowSimple</key>
<true/>
<key>forcePIN</key>
<true/>
<key>maxGracePeriod</key>
<integer>0</integer>
<key>minLength</key>
<integer>10</integer>
</dict>
<key>PayloadDescription</key>
<string></string>
<key>PayloadDisplayName</key>
<string>Passcode</string>
<key>PayloadIdentifier</key>
<string>8AEBE656-548F-4929-8573-97BF25AC4B8F</string>
<key>PayloadOrganization</key>
<string>Stripe, Inc.</string>
<key>PayloadType</key>
<string>com.apple.mobiledevice.passwordpolicy</string>
<key>PayloadUUID</key>
<string>8AEBE656-548F-4929-8573-97BF25AC4B8F</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
Note the PayloadType of com.apple.mobiledevice.passwordpolicy. However, in the managed_policies table, I'm pretty sure it shows up as:
| com.apple.screensaver | E6FDB8FD-ECEE-4259-BA8C-3FC0B31FB855 | minLength | 10
All of which is a long-winded way of saying: it would still be useful to show policies, I think.
P.S. file / the magic table doesn't recognize the ConfigProfiles.binary file. I'll dig a bit and see if I can figure out what it actually is.
Nice! Dumb question, how does one create a profile? It seems one needs Profile Manager from OS X Server? Do you know if manually creating the xml with valid keys work?
So, according to this document, you can indeed just manually create XML, and install with profiles -I -F /path/to/profile.file
Cool, and found this config keys reference doc. Will poke around some more over the break (I don't want to mess with my only working computer at the moment, so need to virtualize).
Some more details for you regarding the ConfigProfiles.binary file:
(It's amusing - I go looking for information and find people referencing my own studies :D)
It's a CoreData NSPersistentStore Binary format file which to properly "rehydrate", you'd need to work with NSPersistentStore initWithPersistentStoreCoordinator:, which requires you have an object that knows how to work with the data. And yes, the data looks encrypted (which is why I couldn't read anything from the binary before)
But as mentioned here earlier, yes, the profiles command links to the ConfigurationProfiles.framework PrivateFramework, which when I run profiles through Hopper, I get a nice reference to CPProfileManager and allProfiles:.
Looking at the framework with class-dump, I find:
@interface CPProfileManager : NSObject
{
}
+ (id)sharedProfileManager;
[...]
- (id)allProfiles:(id *)arg1;
and the profiles code seems to pass null for the argument, so let's try it in python:
from Foundation import NSBundle
_bundle_ = NSBundle.bundleWithPath_('/System/Library/PrivateFrameworks/ConfigurationProfiles.framework')
CPProfileManager = _bundle_.classNamed_('CPProfileManager')
shared = CPProfileManager.sharedProfileManager()
profiles = shared.allProfiles_(None)
Whoops, I get an error:
2015-11-30 16:22:36.657 Python[55846:634926] The caller "/System/Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app" is not authorized to use the ConfigurationProfiles framework.
Looks like it's secured.
Opening up ConfigurationProfiles.framework in Hopper, we see:
int _GetCallerType(int arg0, int arg1) {
r14 = arg0;
rbx = _CreatePathForPID(getpid(), arg1);
if (*_GetCallerType.gOnce != 0xffffffffffffffff) {
rax = dispatch_once(_GetCallerType.gOnce, void ^(void * _block) {
rdi = _block;
var_28 = *___stack_chk_guard;
var_58 = @"/System/Library/CoreServices/ManagedClient.app";
var_50 = @"/System/Library/PreferencePanes/Profiles.prefPane/Contents/XPCServices/com.apple.preferences.configurationprofiles.remoteservice.xpc";
var_48 = @"/usr/bin/profiles";
var_40 = @"/usr/local/bin/configprofileutil";
var_38 = @"/System/Library/Filesystems/acfs.fs/Contents/bin/xsanctl";
var_30 = @"/usr/libexec/opendirectoryd";
r12 = _objc_msgSend;
rbx = [NSArray arrayWithObjects:@"/usr/libexec/mdmclient" count:0x7];
var_68 = @"/System/Library/PreferencePanes/Security.prefPane/Contents/XPCServices/com.apple.preference.security.remoteservice.xpc";
rdx = @"/System/Library/PreferencePanes/Profiles.prefPane/Contents/Resources/CPPrefPaneEnabledTool";
[...]
Looking at each of those tools, none of them are signed with any common entitlements (profiles has no entitlements at all), so it looks like it's purely based on the path of the executable.
... /usr/local/bin/configprofileutil is pretty interesting considering it doesn't exist :)
But yeah, usage of the PrivateFramework is unfortunately a no-go as it protects against unauthorized callers.
So if you wanted to continue down this route, you'd probably need to emulate the decryption process the framework is doing.
Yeah, I saw that list too - I actually went ahead and figured out how it was getting the current binary's path, to see if it was possible to spoof (e.g. overwriting argv[0]). I put the actual code in a gist here, but the short version is that I'm pretty sure it's not possible to do easily.
I did consider trying to write a routine that would do some crazy in-process binary patching, but that's probably not something you want to deploy on real laptops...
So, it's a giant hack, but here's a thing that patches ConfigurationProfiles.framework in order to allow calling it from an arbitrary binary: https://github.com/andrew-d/profiles
We probably don't want to use this in "real" code.
To use: make all && ./profiles
I ended up writing a plugin last week that would simply call and parse the output of the profiles command-line utility, and exposes this. I'm doing a bit more refactoring now, but you can see it here: https://github.com/andrew-d/osquery-profiles
Most helpful comment
Some more details for you regarding the
ConfigProfiles.binaryfile:(It's amusing - I go looking for information and find people referencing my own studies :D)
It's a CoreData NSPersistentStore Binary format file which to properly "rehydrate", you'd need to work with NSPersistentStore initWithPersistentStoreCoordinator:, which requires you have an object that knows how to work with the data. And yes, the data looks encrypted (which is why I couldn't read anything from the binary before)
But as mentioned here earlier, yes, the
profilescommand links to theConfigurationProfiles.frameworkPrivateFramework, which when I runprofilesthrough Hopper, I get a nice reference toCPProfileManagerandallProfiles:.Looking at the framework with
class-dump, I find:and the
profilescode seems to pass null for the argument, so let's try it in python:Whoops, I get an error:
2015-11-30 16:22:36.657 Python[55846:634926] The caller "/System/Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app" is not authorized to use the ConfigurationProfiles framework.Looks like it's secured.
Opening up
ConfigurationProfiles.frameworkin Hopper, we see:Looking at each of those tools, none of them are signed with any common entitlements (
profileshas no entitlements at all), so it looks like it's purely based on the path of the executable....
/usr/local/bin/configprofileutilis pretty interesting considering it doesn't exist :)But yeah, usage of the PrivateFramework is unfortunately a no-go as it protects against unauthorized callers.
So if you wanted to continue down this route, you'd probably need to emulate the decryption process the framework is doing.