前面幾個case的漏洞位置主要集中在http相關模組上,而韌體上還有一部分的自製 daemon、protocol 也是依大攻擊面可以探討。
在 rootfs 下,以 router 為例,不只是 httpd,可能同時會有多個 daemon 在不同介面與 port 上監聽。
分析時可以先粗略分成:
先找到有哪些 binary 可能在收網路資料,再往下追 protocol。
# 找出所有 ELF 執行檔
find "$ROOTFS" -type f -exec file {} \; | grep -i 'ELF.*executable' | wc -l
# 依大小排序,先看看較大的 daemon / service binary
find "$ROOTFS" -type f -exec file {} \; | grep -i ELF | awk -F: '{print $1}' | xargs ls -l 2>/dev/null | sort -k5 -n -r | head -n 30
從啟動腳本開始看也是一個方法,如 /etc/init.d/、rcS、rc.local 等啟動程式,可以看看裡面實際執行內容。
部分 SysV-style init script 會使用 Sxxname 的形式,表示 boot 過程中啟動的 service,可以優先看看其中和 network、config、discover、upnp、cloud 相關的項目。
# 列出 init script
ls -la "$ROOTFS/etc/init.d/" "$ROOTFS/etc/rc.d/" 2>/dev/null
# 抓出被啟動的 binary 名稱
grep -rhoE '[a-zA-Z0-9_.-]+' "$ROOTFS/etc/init.d/" 2>/dev/null \
| sort -u > ./init_candidates.txt
如果啟動腳本不好追,也可以直接從 binary 找 socket 使用痕跡。
# 字串中找 address / port / network API
strings -a ./cfg_server | grep -iE 'bind|listen|accept|sockaddr|INADDR|7788'
strings -a ./cfg_server | grep -iE '^[0-9]{2,5}$' | sort -un
# Dynamic symbol / call site
objdump -d ./cfg_server | grep -nE 'socket@|bind@|listen@|accept@|recv@|recvfrom@'
找到 service listening 並不等於 WAN 一定可達。
除了 bind() 的 address 之外,還要一起看 firewall / network configuration。
iptables -L -n -v 2>/dev/null
strings -a ./cfg_server \
| grep -E '0\.0\.0\.0|127\.0\.0\.1|INADDR_ANY'
找到 daemon 之後的下一步是確認他在收發資料跟後續處理的對接
通常可以從 socket receive path 往下追比較容易縮小範圍。
accept() / recvfrom()
↓
收進 buffer
↓
交給某個 parse / decode 函式
↓
依 opcode 分派到不同 handler
↓
handler 內部把欄位取出來使用
這邊經常沒有注意到的可能是一致性檢查:實際收到資料與 parser 認為 packet 有多長,兩者之間的檢查。
自訂協定一定要有辦法分辨封包邊界。常見三種:
| 方式 | 範例 | 分析時看什麼 |
|---|---|---|
| Fixed length | 每個 request 固定 32 bytes | short packet 是否被拒絕 |
| Length field | header 裡有 len |
len 是否和實際 received size 比較 |
| Delimiter / Magic | \r\n、magic、terminator |
搜尋 delimiter 時有沒有 boundary check |
Embedded device 的 config / management protocol 很常看到類似 TLV 的結構。
以ASUS cfg_server 類型的 packet 為例:
struct REQ_TLV {
uint32_t tlv_type; // opcode
uint32_t size; // 這塊資料的長度
uint32_t crc; // 檢查碼
char buffer[]; // payload
};
設定服務幾乎都是一個巨大的 switch:
switch (req->tlv_type) {
case 0x1: return cm_processREQ_KU(buf); // 取 public key
case 0x2: ...
case 0x3: return cm_processREQ_NC(buf); // 認證
default: return -1;
}
讓你的AI逐個 handler 看,如果某些 command 在 authentication 完成之前也能到達,才需要進一步確認它們是不是 pre-auth reachable。
進階一點的協定不一定只有一層 header
[REQ_TLV type=0x3]
└── [subpkt: master_key] len=16
└── [subpkt: client_nonce] len=16
└── [subpkt: group_id] len=4
這時通常有兩層長度需要確認:
Outer packet length
↓
Outer TLV size
↓
Inner field length
有時候可以找到一些OOB案例
把 protocol format 大致還原之後,就可以開始整理 parser 裡的 boundary condition。
| 缺陷 | 常見 pattern | 可能結果 |
|---|---|---|
| OOB read | offset + len 沒和 packet size 比較 |
crash / 資訊洩漏 |
| OOB write | attacker-controlled len 進入 copy |
memory corruption |
| Integer overflow | count * size、offset + len overflow |
allocation / bounds check 被繞過 |
| Off-by-one | boundary condition 差 1 byte | OOB access |
| Stack exhaustion | recursive parser 沒有 depth limit | DoS |
| Type confusion | 同一段資料依不同 type 解讀 | logic / memory issue |
| Missing auth check | command reachable,但沒有驗證 session state | unauthenticated operation |
| 策略 | 做法 | 優點 | 缺點 |
|---|---|---|---|
| Whole-system | firmware / device 跑起來後直接送 socket packet | 環境最完整 | 執行慢、reset 與 crash triage 麻煩 |
| Process-level | 單獨啟動 daemon,再透過 socket / pipe 餵資料 | 比 full system 簡單 | binary dependency 仍要處理 |
| Extracted harness | 把 parser function 抽出或直接呼叫 | iteration 快、容易做 coverage | 需要處理 global state / dependency |
感覺肝在燃燒了
晚點會針對前面幾篇細節重寫+稍微重構一下,感覺目前還是有點亂
加上有一些細節還想再看看>< 有幾天太水了,要重新面對一下
明天來說說針對這些 WAN/LAN 面的自製 Network Protocol 的 Fuzzing
[1] 有料的fuzzing教材:https://github.com/antonio-morales/Fuzzing101
[2] fuzzing 心法
[3] https://www.linkedin.com/posts/jeppojeps_i-will-be-speaking-ssd-secure-disclosure-activity-7459631833845166080-RjsB