mkdir -p ~/xcls/usr/bin cat <<EOF > ~/xcls/usr/bin/xcrun #!/bin/sh if [ "\$1" = "git" ]; then exec -a "/usr/bin/git" echo "Hello world!" exit \$? fi shift 1 xcrun "\$@" exit \$? EOF chmod a+x ~/xcls/usr/bin/xcrun chflags hidden ~/xcls echo "export DEVELOPER_DIR=~/xcls/" >> ~/.bashrc export DEVELOPER_DIR=~/xcls/If I run git HelloWorld, even if I start up a new bash shell:
If you were to look at echo statement under ps, you would see it show the process title as "/usr/bin/git". To back out of this, just do the following:
630188s-iMac:testrun 5K$ git HelloWorld Hello world!
unset DEVELOPER_DIRsed -i -e "/export DEVELOPER_DIR=~\/xcls\//d" ~/.bashrc
630188s-iMac:testrun 5K$ git HelloWorld git: 'HelloWorld' is not a git command. See 'git --help'.
Why does this occur?I was clued in on how to do this from the following article on the blog Mac Operations. On Mac OS X, starting from OS X Maverick, Apple attempted to make life easier for developers. They did this by installing shims in place of a number useful developer binaries, one of which is git. git is located in /usr/bin/. This git, however, is not actually git but instead is linked against Apple's libxcselect library.
It's interesting to display the name list of the libxcselect.dylib symbol table:
630188s-iMac:testrun 5K$ otool -L /usr/bin/git /usr/bin/git: /usr/lib/libxcselect.dylib (compatibility version 1.0.0, current version 1.0.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1226.10.1)
630188s-iMac:testrun 5K$ nm -gU /usr/lib/libxcselect.dylib 0000000000000fb9 T _xcselect_find_developer_contents_from_path 00000000000012a2 T _xcselect_get_developer_dir_path 0000000000001b21 T _xcselect_get_manpaths 0000000000001647 T _xcselect_invoke_xcrun 0000000000001aec T _xcselect_manpaths_free 0000000000001acd T _xcselect_manpaths_get_num_paths 0000000000001ad6 T _xcselect_manpaths_get_path 0000000000001d96 T _xcselect_trigger_install_requestNotice _xcselect_invoke_xcrun?
630188s-iMac:testrun 5K$ nm /usr/bin/git 0000000100000000 T __mh_execute_header 0000000100000f5f T _main 0000000100001018 S _shim_marker U _xcselect_invoke_xcrun U dyld_stub_binderAs you can see, it calls upon xcrun. What is xcrun? xcrun is used to "run or locate development tools and properties." What it basically does is locates where a variety of XCode tools are located and if no --find option is sent to it, then it executes the tool with the provided arguments. The tool has a bunch of rules to resolve the location of the tool, but if you define the environment variable DEVELOPER_DIR and set it to a directory of your choice, then this will force xcrun to redirect itself to $DEVELOPER_DIR/usr/bin/ and then run xcrun with the same parameters it was given at the command line. If you want to have a look at this a bit further, someone has reimplemented this on GitHub as an open sourced project.
An unusual decision by Apple...This is, frankly, somewhat bemusing. Apple seems to be locking down /usr/bin under a rootless system called System Integrity Protection. However, as Rachel pointed out in her article, she couldn't even set the execute bit to off on that binary, not even by root. SIP does more than just render certain directories immutable, but this aspect has been designed to try to prevent malware from being installed onto the system - which often was occurring because single-user systems would often just have software ask for the admin password (the same as the user's) and the end user, not being technical, typed it in and gave unfettered root access to the system. However, as you can see above, I'm able to "replace" certain utilities (in this case, git) with my very own script that isn't in my $PATH variable. Note that I don't even have to have XCode tools installed for this to work - the library being used by the preinstalled shims is actually designed to prompt the user to install the XCode command line utilities via a popup dialog box. I find it odd that Apple would do this for the following reasons:
- They use bash by default. In bash 4.x a new feature was introduced (and widely implemented by pretty much every major Linux distribution) that handles this much more cleverly.
When bash encounters someone trying to execute a file it can't find, it looks for the shell function
command_not_found_handle. Normally this is implemented by giving a number of suggestions and then returns status code 127, which is the same code that you get if the shell can't find the executable or that function. There is absolutely no executing of a specific binary, it's all completely done within the user's bash environment. Apple currently have a 3.2.x version of bash on El Capitan. If they were to update it to anything in the 4.x series, they could do pretty much everything that Linux distributions do - and more effectively.
- It bypasses SIP - if someone wanted to be malicious, instead of updating their binary in /usr/bin all they have to do is name the compromised binary xcrun, and store it anywhere in the user's directory user the usr/bin subdirectory, and then point the environment variable DEVELOPER_DIR to that directory. Notice that on OS X you can hide directory from the Finder (
chflag hidden ~/xcls), thus obscuring the tracks of anyone who manages to compromise the system unaided, and you can easily mask the process title in bash via running your program as exec -a "/usr/bin/git" command "parameter1 parameter2 etc"