CVE-2020-10882&&tdpServer分析
為什么會對一個幾年前的漏洞進行分析呢,因為在HITP2023AMS會議上,看到一個關(guān)于TP-Link的tdpServer的json堆棧溢出(https://research.nccgroup.com/2023/04/24/hitbams-your-not-so-home-office-soho-hacking-at-pwn2own/)所以我看了關(guān)于這個服務(wù)前幾年的漏洞存在一個命令注入,這個命令注入是在pwn2own-tokyo由Flashback團隊發(fā)現(xiàn)的(https://www.zerodayinitiative.com/blog/2020/4/6/exploiting-the-tp-link-archer-c7-at-pwn2own-tokyo)關(guān)于漏洞影響的版本可以訪問鏈接。
下面就是正文,對原文中一些沒有詳細展開的函數(shù)點進行了分析,獲取固件后解開后尋找/usr/bin/tdpServe直接給Ghidra進行分析

recvfrom()
第一行,調(diào)用了recvfrom()
接收UDP數(shù)據(jù),獲取UDP_data后調(diào)用tdpd_pkt_parser()
tdpd_pkt_parser()
這個函數(shù)有一下幾個點
第十一行判斷接收的數(shù)據(jù)長度不小于0x10,也就是16.
并且在15行tdp_get_pkt_len()函數(shù),判斷請求頭中設(shè)置的長度不大于0x410
在這個函數(shù)里tdpd_pkt_sanity_checks()有多個判斷,判斷數(shù)據(jù)包中的第一個字節(jié)是否為0x01,還進行了CRC32校驗,和對第二個字節(jié)是否為0xf0.
30行判斷了第二個字節(jié)是否是-0x10
進入漏洞分支vuln_func_branch()
tdp_get_pkt_len()
這個函數(shù)比較簡單全部都在第七行,判斷param_1不為空.*(ushort *)(param_1 + 4)
因為是ushort獲取兩個字節(jié),加上0x10的長度小于0x410,而這個param_1+4
的位置是我們在數(shù)據(jù)包頭設(shè)置的payload的長度,判斷成功后會返回我們設(shè)置的長度,而不是0xffffffff.
tdpd_pkt_sanity_checks()
第12行判斷了數(shù)據(jù)包中第一個字節(jié)是否為0x01
第20行進行了CRC32循環(huán)冗余校驗,判斷數(shù)據(jù)包是否完整會進行詳細的分析,但是只對代碼分析不會展開講CRC32
26行判斷了第二個字節(jié)是否是0xf0,即使這里能夠通過也會在tdpd_pkt_parser函數(shù)的第30行進行判斷是否等于-0x10進行判斷,也就是0xf0,才能進入漏洞函數(shù)分支
CRC_32()
該函數(shù)進行了一些運算,本質(zhì)上就是CRC32的校驗,在寫POC時可以直接使用python中的zlib.crc32去生成curCheckSum,填充到數(shù)據(jù)包中.
在第8行判斷了長度和內(nèi)容不為空,重要的的操作都在第11和12行,第11行簡單來說就是整個data有多少個字節(jié)就循環(huán)多少次,第12行中的DAT_00416e90就是一個校驗用的數(shù)據(jù)表.
核心操作:?uVar1 = *(uint *)(&DAT_00416e90 + ((*Data ^ uVar1) & 0xff) * 4) ^ uVar1 >> 8;
先運行了
*Data ^ uVar1
獲取了Data中的第一個字節(jié)和uVar1進行異或,在第一次進入函數(shù)uVar1的值為0xffffffff,在循環(huán)完成一次后uVar1會被重新賦值為上一次的結(jié)果完成第一步的操作后將和0xff進行邏輯與操作并乘4,假設(shè)現(xiàn)在的通過運行算后生成了一個叫NewData的指針
*(uint *)(&DAT_00416e90 + NewData) ^ uVar1 >> 8
現(xiàn)在的公式是這個樣子的,因為uint
類型會從指針的位置獲取4個字節(jié),根據(jù)運算符的優(yōu)先級會先運行uVar1 >> 8
在將從DAT_00416e90獲取的數(shù)據(jù)和uVar1 >> 8
的結(jié)果進行異或生成我們的新的uVar1,并進入下一次循環(huán).
一下是python對這個操作的還原,值得注意的是在最后的生成的值由于 C 語言中整數(shù)類型的表示方式是使用補碼,所以我們在python中還原校驗是一個負數(shù)我們需要將他轉(zhuǎn)換為無符號整數(shù),當(dāng)然如果你直接使用zlib.crc32就不需要考慮這個了,在最后都需要將結(jié)果打包成一個32位大端的字節(jié)串才能夠拼接Payload
vuln_func_branch()
在第12中FUN_0040e074函數(shù)中獲取了config中的配置可以參考o(jì)penwrt在沒有實體機的情況下需要去添加配置文件
20行獲取請求頭中第三四個字節(jié)并減一,去switch我們需要到53行所以需要將請求頭設(shè)為7
48行對請求頭中的第7個字節(jié)&1不等于0,所以需要將第7個字節(jié)設(shè)為1
vuln_func()
這個函數(shù)太多了就給出關(guān)鍵位置
第一個data_decrypt,對data進行解密,AES加密CBC模式
通過對每一個json鍵值解析,并賦值并且所有的數(shù)據(jù)包都不能為空
就是漏洞點了snprintf拼接slave_mac的值,只需要閉合命令就可以在system中執(zhí)行了,閉合的方法:
主要看一下data_decrypt里面關(guān)鍵函數(shù)FUN_0040ade0
end

POC
要注意的是在這個簡單的POC中我們,并沒有獲取shell只是根目錄生成了一個叫xxx的文件
也沒有寫自動更改第42行,如果更改我們payload長度也需要對第42行進行更改,payload的長度在第33行輸出出來了
如果我們想要使用我們自己寫的CRC32校驗的話需要更改第57行
在非實體機運行tdpserver,還需要啟動UBUS守護進程ubusd,否則就不會攻擊成功