This commit is contained in:
Lain Iwakura 2025-07-26 11:27:06 +03:00
parent 80fe58d1e5
commit 2c16541654
No known key found for this signature in database
GPG Key ID: C7C18257F2ADC6F8

View File

@ -2,36 +2,59 @@
layout: blog.njk layout: blog.njk
title: Making eBPF "Cursed Ping" Work on Linux, macOS, and BusyBox title: Making eBPF "Cursed Ping" Work on Linux, macOS, and BusyBox
date: 2025-07-25 date: 2025-07-25
description: How I made my eBPF ping hack universal description: How I made eBPF ping hack universal
--- ---
## Introduction ## Introduction
Messing with ping using eBPF is fun-just mutate the timestamp in the ICMP Echo Reply and watch the round-trip time go wild! Messing with ping using eBPF is funjust mutate the timestamp in the ICMP Echo Reply and watch the round-trip time go wild!
But in practice, different ping implementations use different payload formats. If you corrupt the wrong bytes, BSD/macOS ping will instantly complain about "wrong data byte". But in practice, different ping implementations use different payload formats. If you corrupt the wrong bytes, BSD/macOS ping will instantly complain about "wrong data byte".
In this post, Ill show how I made eBPF hack universal: it now works on Linux and macOS/BSD! In this post, I'll show how I made my eBPF hack universal: it now works on Linux, macOS/BSD, and even BusyBox.
--- ---
## The Problem ## The Problem: Different Ping Implementations
- **Linux (iputils):** timestamp is 16 bytes (2 x uint64) ### ICMP Echo Request/Reply Structure
- **macOS/BSD:** timestamp is only 8 bytes, followed by a signature sequence (0x08, 0x09, 0x0a, ...), which must not be touched!
- **BusyBox:** timestamp is just 4 bytes (uint32_t)
If you mutate the "extra" bytes, BSD ping immediately reports a corrupted payload. All ping implementations follow the same basic ICMP structure:
```
[Ethernet Header] -> [IP Header] -> [ICMP Header] -> [Payload]
```
The payload contains the timestamp and additional data, but the format varies:
- **Linux (iputils):** 16-byte timestamp (2 x uint64_t)
```
[8 bytes: timestamp_sec] [8 bytes: timestamp_nsec] [rest of payload...]
```
- **macOS/BSD:** 8-byte timestamp + integrity signature
```
[8 bytes: timestamp] [0x08] [0x09] [0x0a] [0x0b] [0x0c] [0x0d] [0x0e] [0x0f] [rest...]
```
- **BusyBox:** 4-byte timestamp (uint32_t)
```
[4 bytes: timestamp] [rest of payload...]
```
### Why BSD Ping Complains
BSD ping validates the integrity signature after the timestamp. If you mutate bytes 8-15 (which contain the signature), ping detects corruption and reports "wrong data byte".
--- ---
## The Solution ## The Solution: Automatic Detection
In the eBPF program, we: ### Detection Algorithm
- Automatically detect the ping type by inspecting the payload signature
- Mutate only the correct number of bytes: We inspect the payload to determine the ping type:
- Linux: 16 bytes
- BSD/macOS: 8 bytes 1. **Check for BSD signature** at offset 8: `0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f`
- BusyBox: 4 bytes 2. **Check payload size** to identify BusyBox (smaller payloads)
3. **Default to Linux** if neither condition is met
### Key eBPF Code Snippet ### Key eBPF Code Snippet
@ -39,19 +62,25 @@ In the eBPF program, we:
__u8* payload = (void*)(icmphdr + 1); __u8* payload = (void*)(icmphdr + 1);
int ping_type = 0; // 0=linux, 1=bsd, 2=busybox int ping_type = 0; // 0=linux, 1=bsd, 2=busybox
// Check for BSD signature: 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
if ((void*)(payload + 16) <= data_end) { if ((void*)(payload + 16) <= data_end) {
if (payload[8] == 0x08 && payload[9] == 0x09 && payload[10] == 0x0a && payload[11] == 0x0b) { if (payload[8] == 0x08 && payload[9] == 0x09 && payload[10] == 0x0a && payload[11] == 0x0b &&
payload[12] == 0x0c && payload[13] == 0x0d && payload[14] == 0x0e && payload[15] == 0x0f) {
ping_type = 1; // BSD/macOS ping_type = 1; // BSD/macOS
} }
} }
// Check for BusyBox (smaller payload, no BSD signature)
if (ping_type == 0 && (void*)(payload + 8) <= data_end) { if (ping_type == 0 && (void*)(payload + 8) <= data_end) {
if ((void*)(payload + 16) > data_end || if ((void*)(payload + 16) > data_end ||
(payload[8] != 0x08 && payload[9] != 0x09)) { (payload[8] != 0x08 && payload[9] != 0x09 && payload[10] != 0x0a && payload[11] != 0x0b)) {
ping_type = 2; // BusyBox ping_type = 2; // BusyBox
} }
} }
// Mutate based on detected type
if (ping_type == 2) { if (ping_type == 2) {
// BusyBox: mutate only first 4 bytes (uint32_t timestamp)
__u32* ts_busybox = (void*)(icmphdr + 1); __u32* ts_busybox = (void*)(icmphdr + 1);
if ((void*)(ts_busybox + 1) <= data_end) { if ((void*)(ts_busybox + 1) <= data_end) {
__u32 old_ts = *ts_busybox; __u32 old_ts = *ts_busybox;
@ -59,6 +88,7 @@ if (ping_type == 2) {
recalc_icmp_csum(icmphdr, old_ts, *ts_busybox); recalc_icmp_csum(icmphdr, old_ts, *ts_busybox);
} }
} else if (ping_type == 1) { } else if (ping_type == 1) {
// BSD: mutate only first 8 bytes (timestamp)
__u64* ts_secs = (void*)(icmphdr + 1); __u64* ts_secs = (void*)(icmphdr + 1);
if ((void*)(ts_secs + 1) <= data_end) { if ((void*)(ts_secs + 1) <= data_end) {
__u64 old_secs = *ts_secs; __u64 old_secs = *ts_secs;
@ -66,6 +96,7 @@ if (ping_type == 2) {
recalc_icmp_csum(icmphdr, old_secs, *ts_secs); recalc_icmp_csum(icmphdr, old_secs, *ts_secs);
} }
} else { } else {
// Linux: mutate both 8-byte timestamps
__u64* ts_secs = (void*)(icmphdr + 1); __u64* ts_secs = (void*)(icmphdr + 1);
if ((void*)(ts_secs + 1) <= data_end) { if ((void*)(ts_secs + 1) <= data_end) {
__u64 old_secs = *ts_secs; __u64 old_secs = *ts_secs;
@ -81,11 +112,24 @@ if (ping_type == 2) {
} }
``` ```
### Checksum Recalculation
After mutating the timestamp, we must recalculate the ICMP checksum:
```c
__attribute__((__always_inline__))
static inline void recalc_icmp_csum(struct icmphdr* hdr, __be32 old_value, __be32 new_value) {
__u64 csum = hdr->checksum;
update_csum(&csum, old_value, new_value);
hdr->checksum = csum;
}
```
--- ---
## Real-World Examples ## Real-World Examples
### Linux (iputils): ### Linux (iputils) - 16-byte mutation:
```bash ```bash
$ ping 77.110.117.147 $ ping 77.110.117.147
@ -94,7 +138,9 @@ PING 77.110.117.147 (77.110.117.147): 56 data bytes
64 bytes from 77.110.117.147: icmp_seq=2 ttl=63 time=9997898 ms 64 bytes from 77.110.117.147: icmp_seq=2 ttl=63 time=9997898 ms
``` ```
### macOS/BSD: **Analysis:** Both timestamp_sec and timestamp_nsec are mutated, resulting in massive RTT values.
### macOS/BSD - 8-byte mutation:
```bash ```bash
platon@Moofbook~> ping 77.110.117.147 platon@Moofbook~> ping 77.110.117.147
@ -108,7 +154,9 @@ PING 77.110.117.147 (77.110.117.147): 56 data bytes
round-trip min/avg/max/stddev = -872349695955.565/-178891434622.269/704643072044.353/657684464294.250 ms round-trip min/avg/max/stddev = -872349695955.565/-178891434622.269/704643072044.353/657684464294.250 ms
``` ```
### BusyBox: **Analysis:** Only the 8-byte timestamp is mutated, but the integrity signature remains intact. Negative values occur due to timestamp underflow.
### BusyBox - 4-byte mutation:
```bash ```bash
/ # ping 77.110.117.147 / # ping 77.110.117.147
@ -116,14 +164,36 @@ PING 77.110.117.147 (77.110.117.147): 56 data bytes
64 bytes from 77.110.117.147: seq=893 ttl=63 time=82.448 ms 64 bytes from 77.110.117.147: seq=893 ttl=63 time=82.448 ms
64 bytes from 77.110.117.147: seq=664 ttl=63 time=61.749 ms 64 bytes from 77.110.117.147: seq=664 ttl=63 time=61.749 ms
``` ```
> On busybox mutate ONLY seq (idk why)
**Analysis:** Only the 4-byte timestamp is mutated. BusyBox shows more reasonable RTT values because it uses a different calculation method.
---
## Technical Challenges
### 1. Memory Safety
All pointer arithmetic must be bounds-checked to prevent eBPF verification failures:
```c
if ((void*)(payload + 16) <= data_end) {
// Safe to access payload[8] through payload[15]
}
```
### 2. Checksum Updates
ICMP checksum must be recalculated after each mutation to maintain packet integrity.
### 3. Random Number Generation
Using `bpf_get_prandom_u32()` ensures different mutations for each packet, making the effect more noticeable.
--- ---
## Conclusion ## Conclusion
Now our eBPF hack works on all major ping implementations, without breaking the payload or triggering "wrong data byte" errors. Now our eBPF hack works on all major ping implementations, without breaking the payload or triggering "wrong data byte" errors.
The automatic detection algorithm ensures compatibility across different platforms while maintaining the fun factor of corrupted timestamps.
You can safely troll sysadmins and test network tools on any platform! You can safely troll sysadmins and test network tools on any platform!
--- ---
If youre interested, the original source code is on [GitHub](https://github.com/x3lfyn/cursed-ping) and mine on [Github](https://github.com/crypetxctl/cursed-ping) and [own Gitea](https://git.iwakurahome.ru/lain/cursed-ping) If you're interested, the original source code is on [GitHub](https://github.com/x3lfyn/cursed-ping) and mine on [GitHub](https://github.com/cryptexctl/cursed-ping) and [own Gitea](https://git.iwakurahome.ru/lain/cursed-ping)