缓存时间:
2026/10/17 07:45
# WiFi密码、UART封锁与一颗损坏的闪存芯片的故事 |
来源:https://adepts.of0x.cc/eufy-homestation2-firmware/
亲爱的Fell**owl**ship成员们,正如我们在上一篇文章中所预告的那样,我们敬爱的上帝调试器(God Debugger)似乎投射了他神圣的光芒——载满了操作码——降临在他卑微的仆人身上,于是我们在寺庙中又迎来了一篇新的布道文。请坐下来,听听我们的一位猫头鹰兄弟和他的妹夫是如何继续解剖Eufy生态系统的下一个组件:Eufy HomeBase 2的。
*我们选择分析Eufy HomeBase 2而不是HomeBase 3,是因为正如我们在《从你的门铃到你的家庭网络》(https://adepts.of0x.cc/eufy-doorbell-hacking/)中解释的那样,这是我在我的城市中发现已经部署的版本,而且也是最容易买到的。我们在此分析的固件版本为 **3.4.2.2h(2026-06-05 18:45:48)**。
*这是我第一次接触与"硬件"相关的东西。我之前一直是对可以从网上下载的固件进行漏洞挖掘,所以请原谅我所犯下的罪过。
**重要!!!**在继续阅读本文之前,请务必先阅读前一篇文章。
## https://adepts.of0x.cc/eufy-homestation2-firmware/#table-of-contents目录
与上一篇文章一样,我们将在这里涵盖多种主题;因此,本组织的某些成员可能对某些章节不感兴趣,或许希望直接跳转到最吸引他们的部分。这就是我们呈现这份神圣献礼的原因:目录。
- 0x00 固件提取 (https://adepts.of0x.cc/eufy-homestation2-firmware/#0x00-firmware-extraction)。描述固件的提取过程(以及我们如何损坏了部分闪存)。
- 0x01 识别WiFi凭据生成算法 (https://adepts.of0x.cc/eufy-homestation2-firmware/#0x01-identifying-the-wifi-credential-generation-algorithm)。逆向工程用于生成OCEAN\_XXXXXX管理WiFi凭据的算法。
- 0x02 对UART封锁进行"橡皮管式"审讯 (https://adepts.of0x.cc/eufy-homestation2-firmware/#0x02-rubber-hose-treatment-to-the-uart-lockdown)。刷新一个植入后门的U-Boot镜像,以通过UART获取shell。
- 0x03 为调试目的进行文件系统修整 (https://adepts.of0x.cc/eufy-homestation2-firmware/#0x03-filesystem-gardening-for-debugging-purposes)。恢复镜像功能有限,需要一些文件系统调整才能运行主程序。
- 0x04 结语 (https://adepts.of0x.cc/eufy-homestation2-firmware/#0x04-final-words)。我分享了在AI时代写文章缺乏热情的感受。
在这个系列的上一章 (https://adepts.of0x.cc/eufy-doorbell-hacking/)中,我提到我们之所以能提取门铃的固件,是因为HomeBase的闪存直接焊接在电路板上,没有暴露任何引脚可以连接测试钩子来转储固件。起初我们并不想拆焊它(尽管最终不得不这样做,正如我稍后将解释的那样),所以我不得不等待获取额外的硬件。Craig S. Blackie (https://x.com/craigsblackie)为我指出了适合Winbond 25Q256JWEQ(WSON8 8.6mm)尺寸的弹簧夹。夹子到货后,我创建了一个你能想象到的最简陋的装置,用橡皮筋固定夹子,然后使用Raspberry Pi的SPI0接口读取数据,就像我们对门铃所做的那样。
使用橡皮筋的设置来读取固件。使用橡皮筋的设置来读取固件。我进行了三次读取,三次的校验和都不相同,这通常意味着出了什么问题。检查转储后,我很快意识到它们全是垃圾数据,完全没用。与门铃不同的是,在这种情况下,读取操作提供的电源同时激活了与内存交互的电路板上的其他组件;因此,为了提取固件,我们必须拆焊芯片并在板外读取。
我的妹夫Óscar Lestón (https://www.linkedin.com/in/%C3%B3scar-lest%C3%B3n-casais-5b970b243/?isSelfProfile=false)是那个勇敢的人,拆焊了闪存芯片(我甚至害怕得不敢尝试):
拆焊。拆焊。虽然内存模块拆下来后看起来很糟糕,但实际上几乎没怎么损坏...
取出的闪存芯片。取出的闪存芯片。最后我们使用Raspberry Pi SPI 0复现了相同的设置来转储固件。这一次三个转储的校验和都相同,并且包含"有趣的数据"。唯一的问题是......有一个64KB的块被擦除/无法读取,很可能是因为我们在取下芯片时损坏了它。
使用Raspberry Pi在板外读取芯片。使用Raspberry Pi在板外读取芯片。大部分固件可以使用Binwalk提取。
``
/home/psyconauta/research/Eufy/homebase2-firmware/chipoff/STUFF/extractions/homestation2_firmware00.bin
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
DECIMAL HEXADECIMAL DESCRIPTION
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
83328 0x14580 U-Boot version string: 1.1.3 (Jun 14 2023 - 16:18:56)
3022226 0x2E1D92 XZ compressed data, total size: 11514432 bytes
14536660 0xDDCFD4 XZ compressed data, total size: 2200 bytes
14538862 0xDDD86E XZ compressed data, total size: 2084 bytes
14540948 0xDDE094 XZ compressed data, total size: 1868 bytes
14542818 0xDDE7E2 XZ compressed data, total size: 168 bytes
14542988 0xDDE88C XZ compressed data, total size: 4192 bytes
14547182 0xDDF8EE XZ compressed data, total size: 2844 bytes
14550028 0xDE040C XZ compressed data, total size: 244 bytes
14550282 0xDE050A XZ compressed data, total size: 1452 bytes
15138816 0xE70000 uImage firmware image, header size: 64 bytes, data size: 3429849 bytes, compression: lzma, CPU: MIPS32, OS: Linux, image type: OS Kernel Image, load address: 0x80000000, entry point: 0x802E04F0, creation time: 2023-06-14 12:56:37, image name: "Linux Kernel Image"
21233664 0x1440000 JFFS2 filesystem, little endian, nodes: 3426, total size: 12255244 bytes
``
部分内容被恢复了,有些没有。由于有大量XZ流,我们选择了一个愚蠢的(也许没那么愚蠢的)想法:直接使用`7z`将所有内容提取到一个大块中(希望能在里面通过关键词搜索来雕刻文件)。
``
➜ extractions mkdir -p 7z_mandanga
➜ extractions 7z e -y -o7z_mandanga homestation2_firmware00.bin
7-Zip 23.01 (x64) : Copyright (c) 1999-2023 Igor Pavlov : 2023-06-20
64-bit locale=es_ES.UTF-8 Threads:16 OPEN_MAX:1024
Scanning the drive for archives:
1 file, 33554432 bytes (32 MiB)
Extracting archive: homestation2_firmware00.bin
ERRORS:
There are data after the end of archive
--
Path = homestation2_firmware00.bin
Type = xz
ERRORS:
There are data after the end of archive
Offset = 3022226
Physical Size = 11514432
Tail Size = 19017774
Method = LZMA2:17
Streams = 358
Blocks = 358
Characteristics = BlockPackSize BlockUnpackSize
ERROR: There are some data after the end of the payload data : homestation2_firmware00
Sub items Errors: 1
Archives with Errors: 1
Open Errors: 1
Sub items Errors: 1
➜ extractions ls -hal 7z_mandanga
total 42M
drwxrwxr-x 2 psyconauta psyconauta 4,0K sep 19 19:41 .
drwxrwxr-x 4 psyconauta psyconauta 4,0K sep 19 19:34 ..
-rw-rw-r-- 1 psyconauta psyconauta 42M sep 19 18:35 homestation2_firmware00
``
## https://adepts.of0x.cc/eufy-homestation2-firmware/#0x01-identifying-the-wifi-credential-generation-algorithm0x01 识别WiFi凭据生成算法
我们想通过固件调查的关键问题之一是用于生成**OCEAN\_XXXXXX**管理WiFi凭据的新算法,因为我们发现它与基于2024年论文所预期的完全不同。特别有趣的是,我们想知道这个算法是否像之前那样脆弱。
写完上一篇文章后,我意识到我作为导师辜负了我的妹夫。我向他展示了如何使用Ghidra和伪C代码,而不是引导他走上光明与理性之路。我为此赎罪,拿出了radare2。
首先我们检查了WiFi网络的ESSID和密码在固件中的位置。一个简单的`grep`就足以识别出包含WiFi名称和凭据的NVRAM变量(用于注册门铃的临时WiFi和管理WiFi):
``
➜ extractions strings homestation2_firmware00.bin_83358_unknown.raw | grep F1VSXfNQL_ZCd3k -A5 -B5
AuthMode=WPAPSKWPA2PSK;WPAPSKWPA2PSK
EncrypType=AES;AES
RekeyInterval=3600
RekeyMethod=TIME
PMKCachePeriod=10
WPAPSK1=F1VSXfNQL_ZCd3k
WPAPSK2=913ceac3
DefaultKeyID=2;2
Key1Type=0
Key1Str1=
Key2Type=0
➜ extractions strings homestation2_firmware00.bin_83358_unknown.raw | grep OCEAN_E9A5FC -A5 -B5
DDNSPassword=
CountryRegion=0
CountryRegionABand=7
CountryCode=ES
BssidNum=2
SSID1=OCEAN_E9A5FC
SSID2=4350c372
WirelessMode=9
TxRate=0
Channel=0
BasicRate=15
``
然后我们进行了一次更广泛的搜索,试图找到与WiFi设置相关的二进制文件和/或脚本:
``
➜ 7z_mandanga strings homestation2_firmware00 | rg 'OCEAN_'
%s,<%d>file not exist.return OCEAN_TEST_TF_CARD_NOT_FOUND.
OCEAN_MAGIC
out of memory of OCEAN_TEST_DATA
OCEAN_TF_CARD_FORMAT_SUCCESSED
OCEAN_TF_CARD_FORMAT_FAIL_MOUNT
OCEAN_TF_CARD_FORMAT_FAIL
OCEAN_TF_CARD_FORMAT_IN_USE
ssid_prefix="OCEAN_"
ssid_prefix="OCEAN_"
CONFIG_LEDS_OCEAN_PWM=y
OCEAN_RUN=/mnt/ocean.sh
if [ -f $OCEAN_RUN ];then
chmod +x $OCEAN_RUN
sh $OCEAN_RUN &
``
沿着这条线索进一步展开,我们发现了一个有趣的行,与我们已知的信息吻合:ESSID是"OCEAN\_"和MAC地址的拼接:
``
➜ 7z_mandanga strings homestation2_firmware00 | rg 'ssid_prefix'
ssid_prefix="ocean_factory_"
ssid_prefix="OCEAN_"
WIFI_SSID=$ssid_prefix$SSID_MAC
ssid_prefix="`nvram_get ssid_pre`"
if [ "$ssid_prefix" = "" ];then
ssid_prefix="OCEAN_"
WIFI_SSID=$ssid_prefix$SSID_MAC
``
让我们缩小视图以获取完整的脚本:
``
➜ 7z_mandanga strings homestation2_firmware00 | rg 'ssid_prefix' -B30 -A40
.sdata
.sbss
.bss
.pdr
.gnu.attributes
.mdebug.abi32
#!/bin/sh
work_mode="`cat /var/run/testmode`"
def_mode="`nvram_get 2860 work_mode`"
SSID_MAC=""
nvram_ssid="`nvram_get 2860 SSID1`"
bssid_num=`nvram_get 2860 BssidNum`
eth_state=`apcliDaemon get_eth_state`
repeater_enable=`nvram_get 2860 RepeaterEnable`
wifi_11bgn_enabled=`nvram_get 2860 wifi_11bgn_enabled`
Wireless_Mode=9
if [ "$eth_state" = "0" -a "$repeater_enable" = "1" -a "$wifi_11bgn_enabled" = "1" ]; then
Wireless_Mode=9
get_wifi_mac()
SSID_MAC="`ifconfig ra0 | grep HWaddr | awk '{print $5}' | awk -F ':' '{print $4$5$6}'`"
#SSID_MAC="`ifconfig ra0 | grep HWaddr | awk '{print $5}' | awk -F ':' '{print $5$6}'`"
while [ "$SSID_MAC" = "" ]
sleep 1
SSID_MAC="`ifconfig ra0 | grep HWaddr | awk '{print $5}' | awk -F ':' '{print $4$5$6}'`"
#SSID_MAC="`ifconfig ra0 | grep HWaddr | awk '{print $5}' | awk -F ':' '{print $5$6}'`"
done
echo "get mac in ssid is:$SSID_MAC"
if [ "$bssid_num" = "2" ];then
ifconfig ra1 down>/dev/null 2>&1
if [ "$work_mode" = "1" ];then
ssid_prefix="ocean_factory_"
if [ "$def_mode" = "factory_mode" ];then
ssid_prefix="OCEAN_"
get_wifi_mac
WIFI_SSID=$ssid_prefix$SSID_MAC
echo "set wifi SSID:$WIFI_SSID"
iwpriv ra0 set SSID="$WIFI_SSID"
if [ "$def_mode" = "factory_mode" ];then
# only WIFI_SSID is not equ nvram_ssid, update nvram.
if [ "$nvram_ssid" != $WIFI_SSID ];then
nvram_set SSID1 $WIFI_SSID
fi
else
ssid_prefix="`nvram_get ssid_pre`"
if [ "$ssid_prefix" = "" ];then
ssid_prefix="OCEAN_"
get_wifi_mac
WIFI_SSID=$ssid_prefix$SSID_MAC
echo "set wifi SSID:$WIFI_SSID"
# only WIFI_SSID is not equ nvram_ssid, update nvram.
if [ "$nvram_ssid" != $WIFI_SSID ];then
nvram_set SSID1 $WIFI_SSID
iwpriv ra0 set SSID="$WIFI_SSID"
if [ "$nvram_ssid" = "MT7628_AP" ];then
sleep 3
#reboot
/sbin/umount_and_reboot.sh
fi
iwpriv ra0 set HtBw=0
iwpriv ra0 set WirelessMode=$Wireless_Mode
iwpriv ra0 set HtGi=0
iwpriv ra0 set SCSEnable=1
if [ "$bssid_num" = "2" ];then
# ssid and pwd set in RT2860_default_vlan
#iwpriv ra1 set SSID="AKDevbind"
#iwpriv ra1 set WPAPSK="Anker39#*"
#iwpriv ra1 set SSID="XMCONFIG"
#iwpriv ra1 set WPAPSK="12345678"
iwpriv ra1 set HtBw=0
iwpriv ra1 set WirelessMode=$Wireless_Mode
iwpriv ra1 set HtGi=0
iwpriv ra1 set SCSEnable=1
iwpriv ra1 set WmmCapable=1
iwpriv ra1 set ed_chk=0
wpapsk="`nvram_get WPAPSK1`"
if [ "$wpapsk" != "" ];then
#echo "iwpriv ra0 set WPAPSK=$wpapsk"
iwpriv ra0 set WPAPSK=$wpapsk
# set wmm
iwpriv ra0 set WmmCapable=1
# change wan mode,
wifi
apcli
iwpriv
home_security
if [ "$eth_state" = "0" -a "$repeater_enable" = "1" ]; then
switch_wan_interface.sh eth2.2 apcli0 0
#!/bin/sh
``
因此,一方面我们找到了ESSID的生成方式,另一方面我们发现密码是从NVRAM变量中读取的(`wpapsk="nvram_get WPAPSK1"`)。让我们用这个名字做一个快速搜索:
``
➜ 7z_mandanga strings homestation2_firmware00 | rg -i 'WPAPSK1' -A5 -B5
wifi channel:%d.
fail to get freq.
/dev/gpio
Error while opening /dev/gpio
SSID1
WPAPSK1
SSID2
ra1 SSID=%s
WPAPSK2
ra1 WIFI_PWD=%s
/sbin/chpasswd.sh eufycamera %s
--
(...)
``
在第一个结果中就有一个看起来像是编译过的二进制文件的东西(那个`%d`很可疑)。我编写了这个简陋的脚本来提取搜索该字符串的ELF文件,然后向后搜索找到魔数头,并假设块中的下一个文件也是ELF:
``
#!/usr/bin/env python3
with open("homestation2_firmware00", "rb") as file:
data = file.read()
needle = data.find(b"WPAPSK1")
magic = data.rfind(b"\x7fELF", 0, needle)
end = data.find(b"\x7fELF", needle)
binary = open("elf_wifi_01", "wb")
binary.write(data[magic:end])
binary.close()
``
它完美地工作了:
``
➜ 7z_mandanga python3 carve_wifi.py
➜ 7z_mandanga file elf_wifi_01
elf_wifi_01: ELF 32-bit LSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, stripped
``
快速检查符号后,我们发现了大量与WiFi相关的内容,尤其是那个诱人的`set_wifi_pwd`。
``
➜ 7z_mandanga r2 elf_wifi_01
WARN: Relocs has not been applied. Please use `-e bin.relocs.apply=true` or `-e bin.cache=true` next time
-- Use 'zoom.byte=printable' in zoom mode ('z' in Visual mode) to find strings
[0x0045d130]> aaa
INFO: Analyze all flags starting with sym. and