ETTERFILTER(8)

✨ ETTERFILTER(8) ✨

okay so bestie, welcome to the glow-up of etterfilter(8) from the System Manager's Manual πŸ’– this is the filter compiler for the ettercap content filtering engine and she is iconic.


🌸 NAME 🌸

etterfilter β€” filter compiler for the ettercap content filtering engine πŸ’…


πŸ¦‹ SYNOPSIS πŸ¦‹

etterfilter [OPTIONS] FILE

πŸ‘‘ DESCRIPTION πŸ‘‘

okay so the etterfilter utility is what you use to compile your source filter files into binary filter files that the JIT interpreter inside the ettercap(8) filter engine can actually read and run. fr fr, you have to compile your filter scripts if you ever want to use them in ettercap, no shortcuts.

the bestie part? all your syntax/parse errors get checked at compile time, so you'll be sure you're producing a correct binary filter for ettercap. no sending cursed scripts into the wild 🫢


πŸ’œ GENERAL OPTIONS πŸ’œ

  • -o, --output <FILE> β€” you can specify the output file for a source filter file. by default the output is filter.ef.
  • -t, --test <FILE> β€” you can analyze a compiled filter file with this option. etterfilter will print, in a human readable form, all the instructions contained in it. it's like a "disassembler" for binary filter files πŸ”βœ¨
  • -d, --debug β€” prints some debug messages during the compilation. use it more than once to increase the debug level ( etterfilter -ddd ... ).
  • -w, --suppress-warnings β€” don't exit on warnings. with this option the compiler will compile the script even if it contains warnings. brave of her ngl πŸŽ€

πŸŽ€ STANDARD OPTIONS πŸŽ€

  • -v, --version β€” print the version and exit.
  • -h, --help β€” prints the help screen with a short summary of the available options.

🌈 SCRIPTS SYNTAX 🌈

so a script is a compound of instructions. it gets executed sequentially, and you can make branches with the 'if' statements. 'if' and 'if/else' statements are the only supported ones. no loops are implemented, sorry bestie, no for-loop era today. 😭

the syntax is almost like C code, except that you have to put 'if' blocks into graph parentheses '{' '}', even if they contain only one instruction.

NOTE: you have to put a space between the 'if' and the '('. you must not put the space between the function name and the '('. πŸ’…

example:

if (conditions) { }
func(args...);

the conditions for an 'if' statement can be either functions or comparisons. two or more conditions can be linked together with logical operators like OR '||' and AND '&&'.

example:

if (tcp.src == 21 && search(DATA.data, "ettercap")) {
}

pay attention to the operator precedence. you cannot use parentheses to group conditions, so be careful with the order, bestie. an AND at the beginning of a conditions block will exclude all the other tests if it is evaluated as false. the parsing is left-to-right, when an operator is found: if it is an AND and the previous condition is false, all the statement is evaluated as false; if it is an OR the parsing goes on even if the condition is false.

example:

if (ip.proto == UDP || ip.proto == TCP && tcp.src == 80) {
}

if (ip.proto == TCP && tcp.src == 80 || ip.proto == UDP) {
}

the former condition will match all udp or http traffic. the latter is wrong, because if the packet is not tcp, the whole condition block will be evaluated as false. 😭

if you want to make complex conditions, the best way is to split them into nested 'if' blocks. slay, structure, we love to see it ✨

since etterfilter supports both IP address families, you should care whether you use 'ip.proto' which is specific for the IPv4 address family, or its IPv6 counterpart 'ipv6.nh'. especially for the L4 protocol matching using 'ip.proto' and/or 'ipv6.nh', you should be careful if you're really acting on the right protocol. this should be enforced using the L3 protocol identifier 'eth.proto'.

example:

if (eth.proto == IP && ip.proto == TCP && tcp.dst == 80 || tcp.src == 80) {
}

if (eth.proto == IP6 && ipv6.nh == TCP && tcp.dst == 80 || tcp.src == 80) {
}

if (tcp.dst == 80 || tcp.src == 80) {
}

the first example correctly matches http traffic only on IPv4 while the second would match http traffic only on IPv6. the third example matches http regardless of its IP address family. main character behavior tbh 🌈

every instruction in a block must end with a semicolon ';'. periodt.

comparisons are implemented with the '==' operator and can be used to compare numbers, strings or ip addresses. an ip address MUST be enclosed within two single quotes (eg. '192.168.0.7' or '2001:db8::2'). you can also use the 'less than' ('<'), 'greater than' ('>'), 'less or equal' ('<=') and 'greater or equal' ('>=') operators. the lvalue of a comparison must be an offset (see later) πŸ‘€

example:

if (DATA.data + 20 == "ettercap" && ip.ttl > 16) {
}

assignments are implemented with the '=' operator and the lvalue can be an offset (see later). the rvalue can be a string, an integer or a hexadecimal value.

example:

ip.ttl = 0xff;
DATA.data + 7 = "ettercap NG";

you can also use the 'inc' and 'dec' operations on the packet fields. the operators used are '+=' and '-='. the rvalue can be an integer or a hexadecimal value.

example:

ip.ttl += 5;

more examples can be found in the etter.filter.examples file. go look, it's giving tutorial vibes πŸ“š


🧚 OFFSET DEFINITION 🧚

an offset is identified by a virtual pointer. in short words, an offset is a pointer to the packet buffer. the virtual pointer is a tuple <L, O, S>, where L is the iso/osi level, O is the offset in that level and S is the size of the virtual pointer.

you can make algebraic operations on a virtual pointer and the result is still an offset. specifying 'vp + n' will result in a new virtual pointer <L, O+n, S>. and this is perfectly legal, we have changed the internal offset of that level. πŸ’…

virtual pointers are in the form 'name.field.subfield'. for example 'ip.ttl' is the virtual pointer for the Time To Live field in the IP header of a packet. it will be translated as <L=3, O=9, S=1>. indeed it is the 9th byte of level 3 and its size is 1 byte. 'ip.ttl + 1' is the same as 'ip.proto' since the 10th byte of the IP header is the protocol encapsulated in the IP packet. note that since etterfilter also supports processing of IPv6, the above mentioned only applies for IPv4 packets while the counterpart in IPv6 would be 'ipv6.nh'. ✨

the list of all supported virtual pointers is in the file etterfilter.tbl. you can add your own virtual pointers by adding a new table or modifying the existing ones. refer to the comments at the beginning of the file for the syntax of the etterfilter.tbl file. iykyk πŸŽ€


πŸ’– SCRIPTS FUNCTIONS πŸ’–

πŸ“ search(where, what)

this function searches the string 'what' in the buffer 'where'. the buffer can be either DATA.data or DECODED.data. the former is the payload at layer DATA (ontop TCP or UDP) as it is transmitted on the wire, the latter is the payload decoded/decrypted by dissectors.

so, if you want to search in an SSH connection, it is better to use 'DECODED.data' since 'data' will be encrypted. πŸ”

the string 'what' can be binary. you have to escape it.

example:

search(DATA.data, "\x41\x42\x43")

🌸 regex(where, regex)

this function will return true if the 'regex' has matched the buffer 'where'. the considerations about 'DECODED.data' and 'DATA.data' mentioned for the function 'search' are the same for the regex function.

NOTE: regex can be used only against a string buffer.

example:

regex(DECODED.data, ".*login.*")

✨ pcre_regex(where, pcre_regex ... )

this function will evaluate a perl compatible regular expression. you can match against both DATA and DECODED, but if your expression modifies the buffer, it makes sense to operate only on DATA.

the function accepts 2 or 3 parameters depending on the operation you want. the two parameter form is used only to match a pattern. the three parameter form means that you want to make a substitution. in both cases, the second parameter is the search string.

you can use $n in the replacement string. these placeholders are referred to the groups created in the search string. (e.g. pcre_regex(DATA.data, "^var1=([:digit:]*)&var2=([:digit:]*)", "var1=$2&var2=$1") will swap the value of var1 and var2).

NOTE: the pcre support is optional in ettercap and will be enabled only if you have the libpcre installed. the compiler will warn you if you try to compile a filter that contains pcre expressions but you don't have libpcre. use the -w option to suppress the warning. 🫢

example:

pcre_regex(DATA.data, ".*foo$")
pcre_regex(DATA.data, "([^ ]*) bar ([^ ]*)", "foo $1 $2")

πŸ¦‹ replace(what, with)

this function replaces the string 'what' with the string 'with'. they can be binary string and must be escaped. the replacement is always performed in DATA.data since is the only payload which gets forwarded. the 'DECODED.data' buffer is used only internally and never reaches the wire. πŸ’€

example:

replace("ethercap", "ettercap")
replace("\x00\x00\xa2", "\x00\x01\xa2")

πŸ”₯ inject(what)

this function injects the content of the file 'what' after the packet being processed. it always injects in DATA.data. you can use it to replace the entire packet with a fake one using the drop() function right before the inject() command. in that case the filtering engine will drop the current packet and inject the fake one. also you can also use log() function to capture payload of a packet and use it as 'what' file to send it with inject() function. sneaky, we love her 😈

example:

inject("./fake_packet")

πŸ’œ log(what, where)

this function dumps in the file 'where' the buffer 'what'. no information is stored about the packet, only the payload is dumped. so you will see the stream in the file. if you want to log packets in a more enhanced mode, you need to use the ettercap -L option and analyze it with etterlog(8).

the file 'where' must be writable to the user EC_UID (see etter.conf(5)). πŸ‘‘

example:

log(DECODED.data, "/tmp/interesting.log")
  • msg(message) β€” this one pops a little message into the User Messages window so your human actually sees what's going on. Perfect for telling them whether a filter did its thing or not. 🫢

example:

msg("Packet filtered successfully")
  • drop() β€” marks the packet as "to be dropped" πŸ’€ it will not be forwarded to the real destination. Bye bestie, you're not going anywhere.

example:

drop()
  • kill() β€” absolutely unhinged, love that for us: it kills the whole connection that owns the matched packet. If it's a TCP connection, a RST goes out to both sides. If it's a UDP connection, an ICMP PORT UNREACHABLE gets sent back to whoever sent the packet. πŸ’…

example:

kill()
  • exec(command) β€” runs a shell command, slay. But listen: you have to give the full path to the command, because it runs with no environment at all. Also there is no way to find out whether the command succeeded or not. And it's executed asynchronously, since the main process forks it. πŸ™ˆ

example:

exec("/bin/cat /tmp/foo >> /tmp/bar")
  • execinject(command) β€” works basically like the inject function, except instead of pulling data from a file it takes the output of a shell command. It always injects into DATA.data. Pro move: you can swap the entire packet out for a fake one by calling drop() right before execinject() β€” in that case the filtering engine drops the current packet and injects the fake one instead. iconic behaviour honestly πŸ‘‘

example:

execinject("/bin/cat /tmp/foo")
  • execreplace(command) β€” like the replace function, but the replacement data comes from the output of a shell command instead of a file's contents. Another lil difference: the original packet content gets passed to the shell command on stdin. It always injects into DATA.data. Use it to swap the whole packet for a fake one depending on what the original looked like. πŸ¦‹

example:

execreplace("tr A-Z a-z")
  • random(where, offset, length) β€” randomizes length bytes at the buffer where. If length is bigger than the available buffer at where, it gets automatically truncated. With offset you can scoot the start of the random bytes further along inside the buffer where. And where can be DATA.data or DECODED.data or literally any other virtual pointer like ip.src or tcp.dst. ✨

example:

random(DATA.data, 0, 16)
random(ipv6.src, 8, 8)

In the first example, the first 16 bytes of the payload (data) get replaced with random bytes. 🌈

In the second example, the host portion (the last 64 bit) of the IPv6 source address gets replaced with random bytes, while the IPv6 network part stays perfectly intact. no cap.

  • exit() β€” makes the filter engine stop executing the code, like, right now. Super useful for bailing out of your script under some circumstance you checked with an 'if' statement. πŸŽ€

example:

exit()

🌸 EXAMPLES 🌸

okay so here are some examples of using etterfilter, bestie:

etterfilter filter.ecf -o filter.ef

That compiles the source filter.ecf into a binary filter.ef. ✨


πŸ’– ORIGINAL AUTHORS πŸ’–

The OGs, the founders, the main characters who started it all:

  • Alberto Ornaghi (ALoR) <alor@users.sf.net>
  • Marco Valleri (NaGA) <naga@antifork.org>

πŸ‘‘ PROJECT STEWARDS πŸ‘‘

The ones holding it down now:

  • Emilio Escobar (exfil) <eescobar@gmail.com>
  • Eric Milam (Brav0Hax) <jbrav.hax@gmail.com>

⭐ OFFICIAL DEVELOPERS ⭐

Certified genius squad, no notes:

  • Mike Ryan (justfalter) <falter@gmail.com>
  • Gianfranco Costamagna (LocutusOfBorg) <costamagnagianfranco@yahoo.it>
  • Antonio Collarino (sniper) <anto.collarino@gmail.com>
  • Ryan Linn <sussuro@happypacket.net>
  • Jacob Baines <baines.jacob@gmail.com>

🫢 CONTRIBUTORS 🫢

The absolute legends who chipped in, we're so grateful fr fr:

  • Dhiru Kholia (kholia) <dhiru@openwall.com>
  • Alexander Koeppe (koeppea) <format_c@online.de>
  • Martin Bos (PureHate) <purehate@backtrack.com>
  • Enrique Sanchez
  • Gisle Vanem <giva@bgnett.no>
  • Johannes Bauer <JohannesBauer@gmx.de>
  • Daten (Bryan Schneiders) <daten@dnetc.org>

πŸ¦‹ SEE ALSO πŸ¦‹

Go read these next, they're giving lore:


and that's the whole page bestie πŸ’œ this was ettercap 0.8.4.1 β€” the ETTERFILTER(8) era, iykyk β€” go forth and filter responsibly πŸŽ€βœ¨