# Hping3 工具使用


## hping3

`hping` 最早來自 Salvatore Sanfilippo (網名 antirez)。沒錯，他就是後來開發了 Redis 的那位傳奇工程師。

根據 AI，他的演進好像是：

* `hping` (1998 年)：最初版本。它打破了傳統 ping 的限制，允許使用者手動設定 TCP 序號。在當時，這對於研究 TCP 序列預測（Sequence Prediction）攻擊非常有用
* `hping2` (2001-2002 年)：這個版本將功能擴展到了 UDP、ICMP 以及更複雜的路徑 MTU 發現。它成為了當時滲透測試工程師的標準配備
* `hping3` (2004 年至今)：這是目前最穩定、最廣泛使用的版本

`hping3` 最核心的整合是，整合了 Tcl (Tool Command Language) 腳本語言。這代表你不再只能下單行指令，你可以寫腳本來定義複雜的封包發送邏輯。但在這邊我們只是看他的指令。

看了一下 [GitHub](https://github.com/antirez/hping)，上次提交已經是 12 年前了，看來之後沒什麼更新。

{{< admonition type=info >}}
Nmap 官方為了補足 Nmap 在「自定義封包發送」上的不足，他們開發了 `nping`。它幾乎可以做所有 `hping3` 能做的事，而且語法更現代化。
{{< /admonition >}}

## 跟 DDoS 模擬有關的選項

1. Base Option
   * `-c`：設定發送多少個封包
   * `-I --interface [名稱]`：強制指定從哪張網卡發包
   * `-i --interval [uX]`：設定發包間隔。預設是 1 秒
   * `--flood`：以硬體極限速度發包，完全不理會對方的回應
2. Protocol Seleciton: 設定用哪個協定，比較特殊的是
   * `-8, --scan`: 這讓 hping3 瞬間變成類似 Nmap 的工具
   * `-9, --listen`: 這是一個非常有創意的功能。它會讓 hping3 進入「等待」狀態，直到網路上出現包含特定「簽名（Signature）」的封包。可以用來建立隱蔽通道（Covert Channel）或簡易的後門觸發機制。
3. IP 相關選項
   * `-a`：指定一個假的 Src IP
   * `--rand-source`：每個封包都隨機 Src IP

## DDoS 實驗

這邊嘗試用兩台電腦直接相接，一台發起攻擊，另外一台模擬 IP 192.168.169.90 當作受害者，看看發起攻擊會有什麼特徵。

### SYN Flood

`hping3 -S --rand-source -p 80 <目標 IP>`，可以再加上 `--flood` 讓他不計代價瘋狂發封包，但模擬這樣就夠了。

電腦 OS 會有一個 SYN Queue (半開連線隊列)，所以打一般電腦就是要讓他塞滿。在 Linux 中，SYN Queue 的最大長度主要由核心參數 `tcp_max_syn_backlog` 決定。

```bash
kola:~$ sysctl net.ipv4.tcp_max_syn_backlog
net.ipv4.tcp_max_syn_backlog = 1024
kola:~$ sysctl net.core.somaxconn
net.core.somaxconn = 4096
```

可以用以下指令看現在目前半開的狀態：

```bash
# 計算目前處於 SYN_RECV 狀態的連線數量
ss -ant state syn-recv | wc -l
# 如果你想看具體是哪些 IP 佔著位子
ss -ant state syn-recv
```

實際上目前 Linux 預設都會有 SYN Cookie，如果發現太多 SYN 連線半開，就會在 Dmesg 看到相關的 LOG，所以你用 `ss -ant` 看不會有半開，因為都用 SYN Cookie。

```bash
adl@adl-D630MT:~$ sudo dmesg | grep SYN
[11132544.177997] TCP: request_sock_TCP: Possible SYN flooding on port 0.0.0.0:80. Sending cookies.
adl@adl-D630MT:~$ nstat -az TcpExtSyncookiesSent
#kernel
TcpExtSyncookiesSent            1272244            0.0
```

所以我們先把 SYN Cookie 關掉，`sudo sysctl -w net.ipv4.tcp_syncookies=0`。

開始打封包，可以看到大概我的電腦跑:

```bash
adl@Twinkle:~$ sudo hping3 -S --rand-source -i u10 -c 100000 -p 80 192.168.169.90
HPING 192.168.169.90 (enp6s0f1 192.168.169.90): S set, 40 headers + 0 data bytes
23155 packets transmitted, 0 packets received, 100% packet loss
round-trip min/avg/max = 0.0/0.0/0.0 ms
```

用 `-i u10` 跑大概 27 MBit/s，不用大概可以跑 400 MBit/s。

然後我們看接收端有什麼變化，用 `watch -n 1 "ss -ant state syn-recv | wc -l`。

```bash
adl@adl-D630MT:~$ watch -n 1 "ss -ant state syn-recv | wc -l"
adl@adl-D630MT:~$ netstat -s | grep "SYNs to LISTEN sockets dropped"
    18 SYNs to LISTEN sockets dropped
adl@adl-D630MT:~$ 
```

這時候發起攻擊可以看到

攻擊者：

```bash
adl@Twinkle:~$ sudo hping3 -S -p 80 --flood 192.168.169.90
HPING 192.168.169.90 (enp6s0f1 192.168.169.90): S set, 40 headers + 0 data bytes
hping in flood mode, no replies will be shown
^C
--- 192.168.169.90 hping statistic ---
7383383 packets transmitted, 0 packets received, 100% packet loss
round-trip min/avg/max = 0.0/0.0/0.0 ms
```

受害者：

![alt text](image.png)

```bash
adl@adl-D630MT:~$ cat /proc/sys/net/ipv4/tcp_max_syn_backlog
1024
```

大概停在 508 也就是接近 queue 的最大數字的一半，當 tcp_syncookies 設為 0 時，Linux 核心為了避免記憶體被完全耗盡，通常不會讓你真的用到 100% 的 tcp_max_syn_backlog。在某些核心版本或配置下，當隊列填滿到一定比例（通常是 1/2 或 3/4）時，系統就會開始主動丟棄（Drop）新的 SYN 包。

![alt text](image-1.png)

上面是驚人的 4,154,964 了嗎？這代表你的伺服器已經丟棄了超過 415 萬個 SYN 連線請求。

### PSUH + ACK Flood

因為你發送的是一個「偽造連線」的 ACK 封包，受害者的核心在收到後會執行以下檢查：

1. 查表：核心查看現有的 TCP 連線表（Established Table）
2. 查無此人：核心發現這封包對應的「來源 IP + Port」根本沒有建立過連線
3. 防衛機制：根據 TCP 協議（RFC 793），如果收到一個不屬於任何已知連線的 ACK 封包，接收端必須回傳一個 RST 封包，告訴對方：「我不認識這個連線，請關閉它」

雖然只是簡單的回傳一個 RST，但當攻擊量達到每秒數萬次時，會產生以下負擔：

1. 雙向流量壓力：你發出多少 PUSH+ACK，受害者就得回送多少 RST。這會瞬間佔滿受害者的上傳頻寬（Outgoing Bandwidth）
2. CPU 查表開銷：核心在決定回傳 RST 之前，必須先消耗 CPU 去搜尋雜湊表（Hash Table）
3. Softirq 負載：處理每個進來的封包都會觸發軟中斷

使用 `sudo hping3 -P -A -p 80 --flood 192.168.169.90` 模擬發起攻擊。

受害者我們用 `watch -n 1 "sar -n DEV 1"` 看收到的流量 (PPS 很大) 跟用 `htop` 看 CPU，會有一個 Core 變很大（都在處理封包，現代網卡雖然支援多隊列（Multi-queue），但對於來自同一個來源、打向同一個目標的流量，網卡通常會透過雜湊（Hash）將其分配給同一個 CPU 核心處理，以維持封包順序）。

![alt text](image-2.png)

![alt text](image-3.png)

然後會看到一堆 RST 封包送回去：

```bash
adl@adl-D630MT:~$ sudo tcpdump -i enp0s31f6 'tcp[tcpflags] & tcp-rst != 0' -n -c 100
[sudo] password for adl: 
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on enp0s31f6, link-type EN10MB (Ethernet), snapshot length 262144 bytes
17:22:19.772345 IP 192.168.169.90.80 > 192.168.169.81.1031: Flags [R], seq 685675530, win 0, length 0
17:22:19.772358 IP 192.168.169.90.80 > 192.168.169.81.1032: Flags [R], seq 1034589902, win 0, length 0
17:22:19.772363 IP 192.168.169.90.80 > 192.168.169.81.1033: Flags [R], seq 1457572995, win 0, length 0
17:22:19.772368 IP 192.168.169.90.80 > 192.168.169.81.1034: Flags [R], seq 1775432224, win 0, length 
```

### UDP Flood

通常 UDP Flood 會打有開 Port 的 UDP Service，會需要花時間處理 UDP 封包。

所以我們先寫一個簡單的 Python UDP Service：

```py
adl@adl-D630MT:~$ cat udp_listen.py 
import socket

# 設定監聽 IP 與 Port
UDP_IP = "0.0.0.0"
UDP_PORT = 8000

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((UDP_IP, UDP_PORT))

print(f"UDP Server 啟動，監聽端口: {UDP_PORT}")
count = 0

while True:
    data, addr = sock.recvfrom(1024) # 接收封包
    count += 1
    if count % 10000 == 0:
        print(f"已處理 {count} 個封包...")
```

使用 `sudo hping3 --udp -p 8000 --flood 192.168.169.90` 模擬攻擊

![alt text](image-4.png)

如上圖，這次有兩個 CPU Core 滿了，一個是 Python service，一個是專門處理網卡的。

![alt text](image-5.png)

然後可以用 `netstat -su` 看到 receive buffer errors (3,388,515)，這代表有 338 萬個封包 雖然成功進到了網卡、通過了核心（Kernel），但因為你跑的 UDP Service（可能是 Python 或 nc）讀取速度太慢，導致該 Socket 的接收緩衝區（Receive Buffer）完全爆滿。

### ICMP Flood

一般伺服器的下載頻寬（Inbound）通常很大，但上傳頻寬（Outbound）比較窄。攻擊者利用 ICMP 強迫受害者把自己的上傳頻寬塞滿，導致受害者無法傳送正常的網頁資料或回覆給其他客人。

使用 `sudo hping3 --icmp --flood 192.168.169.90` 模擬。

受害者使用 `watch -n 1 "netstat -s | grep -i icmp"` 查看。

![alt text](image-6.png)

如上，received and sent 幾乎一比一。

## Reference

* [hping3(8) - Linux man page](https://linux.die.net/man/8/hping3)

