This is a tutorial for setting up your own open-source anti-theft software in OSX. Upon completion of this tutorial, you will have a system daemon that will securely perform, nearly all of the core functions as many paid Macbook / Macbook Pro anti-theft applications provide.
Be advised, this guide is aimed towards those with a bit of Unix/Linux knowledge, so if there are points that are not clear to novice users, do not hesitate to contact me for clarifications.
Why are you reading this?
Some time ago, I read about an individual who had their Macbook Pro stolen from them and recovered it with the help of the police and an app called “hiddenapp”. Since I had actually had a 2008 Macbook Pro stolen from me in the past, I decided to purchase (waste my money on) the app for my current machine.
Right after I performed the installation, I immediately noticed an outbound request from a curl to hiddenapp.com, thanks to Little Snitch. Before allowing this egress traffic, I decided to fire up Wireshark to inspect the traffic leaving my machine and network, whereby I found the following unacceptable application behaviors:
- Traffic leaves the Mac unencrypted to hiddenapp.com, to port 80
- The outbound request for status (Stolen, Test Mode, or OK) attempted to send my Macbook Pro’s serial number in plain text in the curl (GET) request URI!
Having witnessed this, I attempted to reconstruct the request via curl over HTTPS, only to find that the domain does not offer SSL encryption whatsoever. Not being able to live with the fact this app transmits my Macbook Pro’s serial number in the clear back to the mothership, I took it upon myself to identify the core features this self proclaimed “Most advanced theft tracking software for your Mac”, and replicate the functionality securely on my own setup.
Requirements
This tutorial is for the adventurous. You must have the following at your disposal to complete the setup:
- ImageSnap
- Web Server with shell access (I’m using Nginx for this tutorial)
- Intermediate Unix commandline fu (or the motivation to follow some the steps in this guide)
Step #1: Install and Test ImageSnap
As stated above, you have two options for getting the imagesnap binary installed. Grab the ImageSnap source code either way, as it contains a precompiled binary that you can copy into your $PATH or desired location, or compile your own using the Xcode project file contained within.
Once you have the imagesnap binary in $PATH, run the following test to ensure that it works as expected (taking a picture with the iSight camera):
1 |
imagesnap test.jpg |
The above should produce a Jpeg image in the current directory.
Step #2: Configure Your Nginx vhost with SSL and Auth Basic
We will now setup an Nginx vhost to handle our application’s requests. Our web server will have the following attributes:
- SSL encryption for our status requests
- User / Password authentication for status requests
- Custom user agent filtering with Nginx to provide an added layer of authentication
- A single web page containing your Mac’s status code (Stolen or Not)
Generate a self signed SSL certificate:
1 2 3 4 5 6 7 8 9 10 |
openssl genrsa -aes256 -out server.key 2048 openssl req -new -key server.key -out server.csr openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt cp server.key server.key.secure openssl rsa -in server.key.secure -out server.key chmod 400 server.csr chmod 400 server.crt chmod 400 server.key chmod 400 server.key.secure cp server.{crt,key} /etc/nginx/ |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
server { listen 12345; # Something random server_name _; # Send no reply for any user agents other than our app's if ($http_user_agent != MyCoolUserAgentName) { return 444; } location / { root /var/www/; # A random page name index myHiddenPage.html; # Authentication page message auth_basic "Restricted"; # File containing user/pass (read below for info on generating, # if you do not have nor know how to use Apache's htpasswd utility auth_basic_user_file /etc/nginx/download.access; } # SSL directives ssl on; ssl_certificate /etc/nginx/server.crt; ssl_certificate_key /etc/nginx/server.key; ssl_ciphers SSLv3:+HIGH; ssl_protocols SSLv3 TLSv1; } } |
* If you do not have htpasswd installed to create the passwd file, see this.
Test the Nginx configuration:
1 2 3 |
nginx -t # If ok, reload.. nginx -s reload |
Create the file on the remote server with:
1 |
echo 911 > /var/www/myHiddenPage.html |
In this example, we will use the number “0″ as the status message indicating our machine is not stolen. Anything other than “0″ will indicate the device is in fact stolen. We’ll come back to this file later.
Step #3: Test Authentication with cURL
Now let’s ensure that we can authenticate with the web server before proceeding. Use the following curl command to test:
1 2 |
curl -v --user username:password -A MyCoolUserAgentName -k \ --connect-timeout 15 https://X.X.X.X:12345/ |
Confirm the status code in the server response headers is “HTTP/1.1 200 OK” and not “HTTP/1.1 401 Unauthorized“. If you get a 401, fix your configuration in Step #2.
Step #4: Generate an SSH Key and Server User Account
For this setup, we will use an SSH key for a user account on the remote server with limited privileges. If our status check indicates the machine is stolen, the machine will transfer the picture taken with the iSight camera to the remote server via the SCP protocol, so we can retrieve the photo(s) taken.
We will configure the remote user’s SSH authorized_keys file to only allow SCP access to the remote user’s /home/ folder.
ssh-keygen -t rsa -b 2048 -C "some text you can identify"
It would be a good idea to bury this key somewhere in the filesystem, but make note of the path.
Next, setup a user account on the remote server (useradd -m -s /bin/bash someusername). Create the user’s .ssh folder in it’s home account and create an authorized_keys file (a shortcut for this is to run: su – someusername , then run ssh localhost). Do not give the new user account a password (although you shouldn’t be allowing users to login with passwords from the get go).
Add the contents of the SSH .pub file to the authorized_keys file, save it then chmod 700 the file. Run a test to ensure the Mac can ssh to the server (ssh someusername@someserverip -i /path/to/ssh/private/key).
We will need to restrict what this user can do once SSH’d to the remote server. A quick way to accomplish this, is to edit the authorized_keys, preceding it with a “command=”. In our case, we will be using a basic Perl script (scp-wrapper) to ensure that this user only be allowed to copy files. Download the file here and copy this to the remote server as /usr/bin/scp-wrapper (make sure to chmod +x the file).
Edit the user’s authorized_keys file, so the beginning of the file looks like this:
command=”/usr/bin/scp-wrapper” ssh-rsa <rest of key bits>
Test that the user’s functionality is limited to scp only:
ssh someusername@someserverip -p someport -i /ssh/private/key “uptime”
Should return the following:
/usr/bin/scp-wrapper: account restricted: only scp allowed (“uptime”)
Now test scp functionality:
scp -P someport -i /ssh/private/key test.txt someusername@someserverip:
test.txt 100% 0 0.0KB/s 00:00
Success.
This is not the only method available for restricting commands for remote user accounts, so once again feel free to be creative.
Step #5: Create Script to Perform Status Checks
Next, we will create a simple bash script to perform the status checks. The below script is rather simple in form, but performs the basic functions needed for operation (let your creativity improve on this if desired).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#!/bin/bash # # script.sh # SERVER=X.X.X.X # Your server IP SSLPORT=12345 SSHPORT=32694 SSHUSER=someuser SSHKEY='/path/to/ssh/private/key' AUTHUSER=somerandonusername AUTHPASS=somerandompassword USERAGENT=MyCoolUserAgentName for port in $SSLPORT $SSHPORT; do if [[ ! "`nc -z $SERVER $port`" ]] then logger "WARNING: port $port is down" fi done CHK=`curl --user $AUTHUSER:$AUTHPASS -A $USERAGENT -k --connect-timeout 15 \ https://$SERVER:$SSLPORT/` DATE=`date +%s` RANDNUM=`head -c10 <(echo $RANDOM$RANDOM$RANDOM)` PIC="theif-$RANDNUM-$DATE.jpg" if [[ $CHK -eq "0" ]]; then exit else # Do it imagesnap -q $PIC scp -P$SSHPORT -i $SSHKEY $PIC $SSHUSER@$SERVER:/home/$SSHUSER/ rm $PIC fi |
Make this script executable (chmod +x) and note the location.
Step #6: Test Run
It’s now time to test our script. Run the file from the terminal, and ensure that it has scp’d a copy of a fresh iSight snapshot to the remote server (since our status page is set to “123″ indicating a status other than not stolen).
Congratulations, you’re almost done.
Step #7: Create a Launch Daemon
We’ll now create a launch daemon to load and run our script. Become root in a terminal and create the following file: /Library/LaunchDaemons/com.myapp.SomeName.plist.
Grab the template file here
I’ve set the integer value to 300 (seconds), edit to your taste (don’t forget to set the script path in the file). Be sure to set remote status page content to “0″, then load the plist with: launchctl load -w /Library/LaunchDaemons/com.myapp.SomeName.plist and you’re in business
Step #7: Dummy Account & Conclusion
Setup a new “dummy” user account on your Mac, require no login. If you are unlucky enough to have your Mac swiped, the perpetrator will hopefully be dumb enough to fall for the non-password protected user account and the script will do it’s job thereafter (so long as they connect to the internet).
You can check your Nginx server logs for the IP they have connencted from, and provide the information to law enforcement along with any pictures that were successfully uploaded to the server. With any luck, karma will be on your side and you will recover your machine.
Once again, creativity is at your disposal here. One could setup a custom Twitter page and change the status to something indicating the machine is stolen (if you do not prefer to have to SSH in to change the status page of the static HTML on the server).
You could even setup a custom email account on the server and use a procmail recipe to modify the status code on the file if certain regex expressions are met in the Subject and/or body of the email sent to the server. Anything is possible.
Hiddenapp should change their name to Hiddencrap.