diff options
| author | Lain Iwakura <lain@iwakurahome.ru> | 2025-09-21 00:50:03 +0300 |
|---|---|---|
| committer | Lain Iwakura <lain@iwakurahome.ru> | 2025-09-21 00:50:03 +0300 |
| commit | e0126a5df719ea3c490df322a92a62e379a0dee9 (patch) | |
| tree | ac65379f3df97bf48ac49eb6594451ead98e4ae5 | |
| download | fjorker-e0126a5df719ea3c490df322a92a62e379a0dee9.tar.gz fjorker-e0126a5df719ea3c490df322a92a62e379a0dee9.zip | |
meow
| -rw-r--r-- | CHANGES | 47 | ||||
| -rw-r--r-- | LICENSE | 23 | ||||
| -rw-r--r-- | Makefile | 16 | ||||
| -rw-r--r-- | README | 38 | ||||
| -rw-r--r-- | fjorker.8 | 54 | ||||
| -rw-r--r-- | main.c | 183 | ||||
| -rwxr-xr-x | release.sh | 28 |
7 files changed, 389 insertions, 0 deletions
diff --git a/CHANGES b/CHANGES new file mode 100644 index 0000000..e17b491 --- /dev/null +++ b/CHANGES @@ -0,0 +1,47 @@ +fjorker 0.1.3 (August 2, 2025) + +* Update Makefile + +Known issues: + * None at this time + +Security: + * No additional security features at this time + +fjorker 0.1.2 (July 31, 2025) + +* Add TTL attack detection + +Known issues: + * None at this time + +Security: + * No additional security features at this time + +fjorker 0.1.1 (July 31, 2025) + +* BSD-style man page + +Known issues: + * None at this time + +Security: + * No additional security features at this time + +fjorker 0.1.0 (July 30, 2025) + + * Initial release + * Basic ICMP echo request/reply functionality + * Random identifiers for enhanced security + * Sequence number validation + * Minimal information disclosure + * Simple release script with GPG signature support + +Security: + * Uses arc4random() for identifier generation + * Validates sequence numbers in responses + * Minimal code base for better auditability + * No unnecessary system information disclosure + +Known issues: + * None at this time \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fd7d69a --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2025 Lain Iwakura. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e252f55 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +CC=cc +CFLAGS=-Wall -Wextra -Werror -O2 -s +TARGET=fjorker + +all: $(TARGET) + +$(TARGET): main.c + $(CC) $(CFLAGS) -o $(TARGET) main.c + +install: $(TARGET) + install -m 755 $(TARGET) /usr/local/bin/ + +clean: + rm -f $(TARGET) + +.PHONY: all clean install diff --git a/README b/README new file mode 100644 index 0000000..0584a56 --- /dev/null +++ b/README @@ -0,0 +1,38 @@ +fjorker(8) fjorker(8) + +NAME + fjorker - minimalistic ICMP echo request/reply implementation + +SYNOPSIS + fjorker host + fjorker --version + +DESCRIPTION + fjorker is a secure-by-default ICMP echo request/reply implementation + that follows OpenBSD's philosophy of minimalism and security. + + Unlike traditional ping implementations, fjorker: + * Minimizes information disclosure about the source host + * Uses random identifiers instead of process IDs + * Validates sequence numbers in responses + * Has minimal code base for better security + +EXAMPLES + fjorker 1.1.1.1 + Ping 1.1.1.1 using ICMP echo requests + + fjorker --version + Display version information + +EXIT STATUS + The fjorker utility exits 0 on success, and >0 if an error occurs. + +SEE ALSO + ping(8) + +STANDARDS + The fjorker utility implements ICMP echo request/reply as described in + RFC 792. + +HISTORY + The fjorker utility first appeared in 2025. \ No newline at end of file diff --git a/fjorker.8 b/fjorker.8 new file mode 100644 index 0000000..43b5856 --- /dev/null +++ b/fjorker.8 @@ -0,0 +1,54 @@ +.Dd July 31, 2025 +.Dt FJORKER 8 +.Os +.Sh NAME +.Nm fjorker +.Nd minimalistic ICMP echo request/reply implementation +.Sh SYNOPSIS +.Nm +.Ar host +.Nm +.Fl -version +.Sh DESCRIPTION +The +.Nm +utility is a secure-by-default ICMP echo request/reply implementation +that follows OpenBSD's philosophy of minimalism and security. +.Pp +Unlike traditional ping implementations, +.Nm +minimizes information disclosure about the source host, uses random +identifiers instead of process IDs, validates sequence numbers in responses, +and has minimal code base for better security. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl -version +Display version information and exit. +.El +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +.Bl -tag -width Ds +.It Nm Ar 1.1.1.1 +Ping 1.1.1.1 using ICMP echo requests +.It Nm Fl -version +Display version information +.El +.Sh SECURITY CONSIDERATIONS +.Nm +requires root privileges to create raw sockets for ICMP communication. +However, the --version option can be used without elevated privileges. +.Pp +The program uses random identifiers instead of process IDs to avoid +leaking system information. It also validates sequence numbers in +responses to detect potential ICMP packet manipulation. +.Sh SEE ALSO +.Xr ping 8 +.Sh STANDARDS +The +.Nm +utility implements ICMP echo request/reply as described in RFC 792. +.Sh HISTORY +The +.Nm +utility first appeared in 2025. \ No newline at end of file diff --git a/main.c b/main.c new file mode 100644 index 0000000..444bf4f --- /dev/null +++ b/main.c @@ -0,0 +1,183 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/ip_icmp.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <sys/time.h> +#include <time.h> +#include <errno.h> + +#define PACKET_SIZE 64 +#define ICMP_HEADER_LEN 8 +#define VERSION "0.1.3" +static volatile sig_atomic_t keep_running = 1; +static int sockfd; +static uint16_t seq = 0; +static int packets_sent = 0; +static int packets_received = 0; +static int packets_mismatched = 0; +static int packets_ttl_attack = 0; +static double min_time = -1; +static double max_time = -1; +static double total_time = 0; +static uint16_t identifier; +void cleanup(void) { + if (sockfd >= 0) { + close(sockfd); + } +} +void signal_handler(int signo __attribute__((unused))) { + keep_running = 0; +} +unsigned short checksum(unsigned short *addr, int len) { + int nleft = len; + int sum = 0; + unsigned short *w = addr; + unsigned short answer = 0; + + while (nleft > 1) { + sum += *w++; + nleft -= 2; + } + + if (nleft == 1) { + *(unsigned char *)(&answer) = *(unsigned char *)w; + sum += answer; + } + + sum = (sum >> 16) + (sum & 0xFFFF); + sum += (sum >> 16); + answer = ~sum; + return answer; +} +int main(int argc, char *argv[]) { + if (argc == 2 && strcmp(argv[1], "--version") == 0) { + printf("fjorker version %s\n", VERSION); + exit(0); + } + if (argc != 2) { + fprintf(stderr, "Usage: %s host\n %s --version\n", argv[0], argv[0]); + exit(1); + } + identifier = arc4random() & 0xFFFF; + struct hostent *host; + struct sockaddr_in addr; + struct icmp *icmp; + char packet[PACKET_SIZE]; + char recv_packet[PACKET_SIZE]; + struct timeval tv_out; + tv_out.tv_sec = 1; + tv_out.tv_usec = 0; + + if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) { + if (errno == EPERM) { + fprintf(stderr, "Permission denied. Run as root.\n"); + } else { + perror("socket"); + } + exit(1); + } + + if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv_out, sizeof(tv_out)) < 0) { + perror("setsockopt"); + cleanup(); + exit(1); + } + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + + if (inet_aton(argv[1], &addr.sin_addr) == 0) { + if ((host = gethostbyname(argv[1])) == NULL) { + fprintf(stderr, "Unknown host %s\n", argv[1]); + cleanup(); + exit(1); + } + addr.sin_addr = *(struct in_addr *)host->h_addr; + } + printf("PING %s (%s): %d data bytes\n", argv[1], + inet_ntoa(addr.sin_addr), PACKET_SIZE - ICMP_HEADER_LEN); + signal(SIGINT, signal_handler); + while (keep_running) { + memset(packet, 0, sizeof(packet)); + icmp = (struct icmp *)packet; + icmp->icmp_type = ICMP_ECHO; + icmp->icmp_code = 0; + icmp->icmp_id = identifier; + icmp->icmp_seq = seq++; + packets_sent++; + icmp->icmp_cksum = 0; + icmp->icmp_cksum = checksum((unsigned short *)icmp, PACKET_SIZE); + struct timeval send_time; + gettimeofday(&send_time, NULL); + if (sendto(sockfd, packet, PACKET_SIZE, 0, + (struct sockaddr *)&addr, sizeof(addr)) <= 0) { + perror("sendto"); + continue; + } + struct sockaddr_in from; + socklen_t fromlen = sizeof(from); + int bytes; + + if ((bytes = recvfrom(sockfd, recv_packet, sizeof(recv_packet), 0, + (struct sockaddr *)&from, &fromlen)) > 0) { + struct timeval recv_time; + gettimeofday(&recv_time, NULL); + + struct ip *ip = (struct ip *)recv_packet; + int ip_header_len = ip->ip_hl << 2; + icmp = (struct icmp *)(recv_packet + ip_header_len); + + if (icmp->icmp_type == ICMP_ECHOREPLY && + icmp->icmp_id == identifier) { + if (icmp->icmp_seq != seq - 1) { + packets_mismatched++; + printf("[!] Server returned wrong sequence: got %d, expected %d\n", + icmp->icmp_seq, seq - 1); + } + packets_received++; + double elapsed = ((recv_time.tv_sec - send_time.tv_sec) * 1000.0) + + ((recv_time.tv_usec - send_time.tv_usec) / 1000.0); + + if (min_time < 0 || elapsed < min_time) min_time = elapsed; + if (max_time < 0 || elapsed > max_time) max_time = elapsed; + total_time += elapsed; + int ttl = ip->ip_ttl; + const char* ttl_warning = ""; + if (icmp->icmp_type == ICMP_ECHOREPLY && (ttl < 30 || ttl > 255)) { + ttl_warning = " [!] Suspicious TTL value"; + packets_ttl_attack++; + } + printf("%d bytes from %s: icmp_seq=%d ttl=%d time=%.3f ms%s\n", + bytes - ip_header_len, inet_ntoa(from.sin_addr), + icmp->icmp_seq, ttl, elapsed, ttl_warning); + } + } + + sleep(1); + } + printf("\n--- %s ping statistics ---\n", argv[1]); + printf("%d packets transmitted, %d packets received, %.1f%% packet loss\n", + packets_sent, packets_received, + ((packets_sent - packets_received) * 100.0) / packets_sent); + if (packets_mismatched > 0) { + printf("%d packets had incorrect sequence numbers\n", packets_mismatched); + } + if (packets_ttl_attack > 0) { + printf("%d packets had suspicious TTL values\n", packets_ttl_attack); + } + if (packets_received > 0) { + double avg = total_time / packets_received; + + printf("round-trip min/avg/max = %.3f/%.3f/%.3f ms\n", + min_time, avg, max_time); + } + cleanup(); + return 0; +} \ No newline at end of file diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..6888f6b --- /dev/null +++ b/release.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +set -e + +VERSION=$(grep '#define VERSION' main.c | cut -d'"' -f2) +if [ -z "$VERSION" ]; then + echo "Error: Could not extract version from main.c" + exit 1 +fi + +DISTNAME="fjorker-${VERSION}" +DISTFILE="${DISTNAME}.tar.gz" + +rm -rf "$DISTNAME" +mkdir "$DISTNAME" + +cp main.c Makefile README LICENSE CHANGES fjorker.8 "$DISTNAME/" + +tar czf "$DISTFILE" "$DISTNAME" +rm -rf "$DISTNAME" + +if command -v gpg >/dev/null 2>&1; then + gpg --detach-sign --armor "$DISTFILE" + echo "Created: $DISTFILE and $DISTFILE.asc" +else + echo "Warning: gpg not found, skipping signature" + echo "Created: $DISTFILE" +fi \ No newline at end of file |