On Mar 17, 2004, Patrick Ahler wrote: > Am looking for a way to mangle packets forwarding from port X with > string "foo" in the packet and replace with "bar". Possible? I posted a patch to the netfilter-devel list a couple of days ago that makes this possible. The patch adds two options; --replace-string and --replace-hex-string that can be used like this: iptables -A FORWARD -p tcp --dport 80 -m string --string "/usr/bin/id" \ --replace-string "/usr/ben/id" -j LOG --log-prefix "nullify SID 1332" iptables -A FORWARD -p udp --dport 53 -m string --hex-string "|04|bind" \ --replace-hex-string "|05|lind" -j LOG --log-prefix "nullify SID 1435" My intention is to include this patch in fwsnort (http://www.cipherdyne.org/fwsnort/) if it doesn't get accepted by the iptables maintainers. The patch appears below. Feedback is much appreciated. --Mike Michael Rash http://www.cipherdyne.org/ Key fingerprint = 53EA 13EA 472E 3771 894F AC69 95D8 5D6B A742 839F --- linux-2.4.24.orig/include/linux/netfilter_ipv4/ipt_string.h 2004-03-23 00:24:17.000000000 -0500 +++ linux-2.4.24/include/linux/netfilter_ipv4/ipt_string.h 2004-03-22 22:04:50.000000000 -0500 @@ -14,8 +14,10 @@ struct ipt_string_info { char string[BM_MAX_NLEN]; + char replace_str[BM_MAX_NLEN]; u_int16_t invert; u_int16_t len; + u_int16_t replace_len; }; #endif /* _IPT_STRING_H */ --- linux-2.4.24.orig/net/ipv4/netfilter/ipt_string.c 2004-03-23 00:24:17.000000000 -0500 +++ linux-2.4.24/net/ipv4/netfilter/ipt_string.c 2004-03-23 01:25:07.000000000 -0500 @@ -3,6 +3,10 @@ * Copyright (C) 2000 Emmanuel Roger <winfield@xxxxxxxxxxxx> * * ChangeLog + * 22.03.2004: Michael Rash <mbr@xxxxxxxxxxxxxx> + * Added ability to replace a matching string in packet data + * with a new string (checksum automatically recalculated for + * tcp). * 19.02.2002: Gianni Tedesco <gianni@xxxxxxxxxx> * Fixed SMP re-entrancy problem using per-cpu data areas * for the skip/shift tables. @@ -22,6 +26,8 @@ #include <linux/skbuff.h> #include <linux/file.h> #include <net/sock.h> +#include <net/tcp.h> +#include <net/udp.h> #include <linux/netfilter_ipv4/ip_tables.h> #include <linux/netfilter_ipv4/ipt_string.h> @@ -52,7 +58,7 @@ /* Setup skip/shift tables */ M1 = right_end = needle_len-1; for (i = 0; i < BM_MAX_HLEN; i++) skip[i] = needle_len; - for (i = 0; needle[i]; i++) skip[needle[i]] = M1 - i; + for (i = 0; (int) needle[i]; i++) skip[(int) needle[i]] = M1 - i; for (i = 1; i < needle_len; i++) { for (j = 0; j < needle_len && needle[M1 - j] == needle[M1 - i - j]; j++); @@ -77,7 +83,7 @@ return haystack+(right_end - M1); } - sk = skip[haystack[right_end - i]]; + sk = skip[(int) haystack[right_end - i]]; sh = shift[i]; right_end = max(right_end - i + sk, right_end + sh); } @@ -113,18 +119,27 @@ { const struct ipt_string_info *info = matchinfo; struct iphdr *ip = skb->nh.iph; - int hlen, nlen; - char *needle, *haystack; + struct tcphdr *tcph; + struct udphdr *udph; + int hlen, nlen, rlen, rctr; + char *needle, *haystack, *repl_str, *repl_ptr; proc_ipt_search search=search_linear; if ( !ip ) return 0; - /* get lenghts, and validate them */ + /* get lengths, and validate them */ nlen=info->len; + rlen=info->replace_len; hlen=ntohs(ip->tot_len)-(ip->ihl*4); if ( nlen > hlen ) return 0; + /* if we are altering packet data, make absolutely sure + * replace length is less than or equal to needle length. + * We cannot start breaking protocols! */ + if ( rlen > 0 && rlen > nlen ) return 0; + needle=(char *)&info->string; + repl_str=(char *)&info->replace_str; haystack=(char *)ip+(ip->ihl*4); /* The sublinear search comes in to its own @@ -141,7 +156,44 @@ } } - return ((search(needle, haystack, nlen, hlen)!=NULL) ^ info->invert); + repl_ptr = search(needle, haystack, nlen, hlen); + + if (repl_ptr != NULL && rlen > 0) { + /* if we change the data portion of the packet we recalculate + * the transport layer checksum (mandatory for TCP). */ + if (skb->nh.iph->protocol == IPPROTO_TCP) { + /* repl_ptr points to the start of the needle + * in the packet, and we know the entire needle + * is there so we can just replace. */ + for (rctr=0; rctr < rlen; rctr++) + repl_ptr[rctr] = repl_str[rctr]; + + tcph = (struct tcphdr *)((u_int32_t*)skb->nh.iph + skb->nh.iph->ihl); + unsigned int tcplen = skb->len - (skb->nh.iph->ihl<<2); + tcph->check = 0; + tcph->check = tcp_v4_check(tcph, tcplen, skb->nh.iph->saddr, + skb->nh.iph->daddr, + csum_partial((char *)tcph, tcplen, 0)); + } else if (skb->nh.iph->protocol == IPPROTO_UDP) { + /* repl_ptr points to the start of the needle + * in the packet, and we know the entire needle + * is there so we can just replace. */ + for (rctr=0; rctr < rlen; rctr++) + repl_ptr[rctr] = repl_str[rctr]; + /* recalculate UDP checksum only if it was previously + * calculated */ + udph = (struct udphdr *)((char *)skb->nh.iph + (skb->nh.iph->ihl<<2)); + unsigned int udplen = skb->len - (skb->nh.iph->ihl<<2); + if (udph->check) { + udph->check = 0; + udph->check = csum_tcpudp_magic(skb->nh.iph->saddr, + skb->nh.iph->daddr, + udplen, IPPROTO_UDP, + csum_partial((char *)udph, udplen, 0)); + } + } + } + return ((repl_ptr!=NULL) ^ info->invert); } static int Index: extensions/libipt_string.c =================================================================== RCS file: /cvspublic/iptables/extensions/libipt_string.c,v retrieving revision 1.11 diff -u -r1.11 libipt_string.c --- extensions/libipt_string.c 5 Jan 2004 09:50:12 -0000 1.11 +++ extensions/libipt_string.c 23 Mar 2004 05:05:38 -0000 @@ -32,8 +32,10 @@ { printf( "STRING match v%s options:\n" -"--string [!] string Match a string in a packet\n" -"--hex-string [!] string Match a hex string in a packet\n", +"--string [!] string Match a string in a packet.\n" +"--hex-string [!] string Match a hex string in a packet.\n" +"--replace-string Replace matching string with a new string.\n" +"--replace-hex-string Replace matching string with a new hex string.\n", IPTABLES_VERSION); } @@ -41,6 +43,8 @@ static struct option opts[] = { { .name = "string", .has_arg = 1, .flag = 0, .val = '1' }, { .name = "hex-string", .has_arg = 1, .flag = 0, .val = '2' }, + { .name = "replace-string", .has_arg = 1, .flag = 0, .val = '3' }, + { .name = "replace-hex-string", .has_arg = 1, .flag = 0, .val = '4' }, { .name = 0 } }; @@ -54,15 +58,15 @@ static void -parse_string(const unsigned char *s, struct ipt_string_info *info) +parse_string(const unsigned char *s, char *string) { - if (strlen(s) <= BM_MAX_NLEN) strcpy(info->string, s); + if (strlen(s) <= BM_MAX_NLEN) strcpy(string, s); else exit_error(PARAMETER_PROBLEM, "STRING too long `%s'", s); } static void -parse_hex_string(const unsigned char *s, struct ipt_string_info *info) +parse_hex_string(const unsigned char *s, char *string, u_int16_t *len) { int i=0, slen, sindex=0, schar; short hex_f = 0, literal_f = 0; @@ -101,7 +105,7 @@ exit_error(PARAMETER_PROBLEM, "Bad literal placement at end of string"); } - info->string[sindex] = s[i+1]; + string[sindex] = s[i+1]; i += 2; /* skip over literal char */ literal_f = 0; } else if (hex_f) { @@ -123,20 +127,20 @@ if (! sscanf(hextmp, "%x", &schar)) exit_error(PARAMETER_PROBLEM, "Invalid hex char `%c'", s[i]); - info->string[sindex] = (char) schar; + string[sindex] = (char) schar; if (s[i+2] == ' ') i += 3; /* spaces included in the hex block */ else i += 2; } else { /* the char is not part of hex data, so just copy */ - info->string[sindex] = s[i]; + string[sindex] = s[i]; i++; } if (sindex > BM_MAX_NLEN) exit_error(PARAMETER_PROBLEM, "STRING too long `%s'", s); sindex++; } - info->len = sindex; + *len = sindex; } @@ -157,7 +161,7 @@ "Can't specify multiple strings"); check_inverse(optarg, &invert, &optind, 0); - parse_string(argv[optind-1], stringinfo); + parse_string(argv[optind-1], stringinfo->string); if (invert) stringinfo->invert = 1; stringinfo->len=strlen((char *)&stringinfo->string); @@ -167,15 +171,51 @@ case '2': if (*flags) exit_error(PARAMETER_PROBLEM, - "Can't specify multiple strings"); + "Can't specify multiple hex strings"); check_inverse(optarg, &invert, &optind, 0); - parse_hex_string(argv[optind-1], stringinfo); /* sets length */ + parse_hex_string(argv[optind-1], stringinfo->string, &stringinfo->len); if (invert) stringinfo->invert = 1; *flags = 1; break; + case '3': + if (! *flags) + exit_error(PARAMETER_PROBLEM, + "Must specify a string to replace with --string or --hex-string"); + + check_inverse(optarg, &invert, &optind, 0); + if (invert) + exit_error(PARAMETER_PROBLEM, + "Can't negate --replace-string"); + parse_string(argv[optind-1], stringinfo->replace_str); + stringinfo->replace_len=strlen((char *)&stringinfo->replace_str); + /* make absolutely sure the replace string length is less than + * or equal to the length of the string to be replaced */ + if (stringinfo->replace_len > stringinfo->len) + exit_error(PARAMETER_PROBLEM, + "Length of replace string must be <= length of string to be replaced"); + break; + + case '4': + if (! *flags) + exit_error(PARAMETER_PROBLEM, + "Must specify a string to replace with --string or --hex-string"); + + check_inverse(optarg, &invert, &optind, 0); + if (invert) + exit_error(PARAMETER_PROBLEM, + "Can't negate --replace-hex-string"); + parse_hex_string(argv[optind-1], stringinfo->replace_str, + &stringinfo->replace_len); + /* make absolutely sure the replace string length is less than + * or equal to the length of the string to be replaced */ + if (stringinfo->replace_len > stringinfo->len) + exit_error(PARAMETER_PROBLEM, + "Length of replace string must be <= length of string to be replaced"); + break; + default: return 0; } @@ -253,6 +293,16 @@ printf("STRING match %s", (info->invert) ? "!" : ""); print_string(info->string, info->len); } + /* print replace string (if any) */ + if (info->replace_len > 0) { + if (is_hex_string(info->replace_str, info->replace_len)) { + printf("REPLACE "); + print_hex_string(info->replace_str, info->replace_len); + } else { + printf("REPLACE "); + print_string(info->replace_str, info->replace_len); + } + } } @@ -269,6 +319,16 @@ } else { printf("--string %s", (info->invert) ? "! ": ""); print_string(info->string, info->len); + } + /* print out --replace-string args (if necessary) */ + if (info->replace_len > 0) { + if (is_hex_string(info->replace_str, info->replace_len)) { + printf("--replace-hex-string "); + print_hex_string(info->replace_str, info->replace_len); + } else { + printf("--replace-string "); + print_string(info->replace_str, info->replace_len); + } } }