Background
byte_jump
is considered one of the more difficult to use Snort/Suricata keywords. It seems that every time byte_jump
is called for it requires re-learning the specific syntax and nuances of this keyword.
byte_jump
is used when the length of a packet, or the length of a “segment” of data is encoded into the packet itself. In relation to malware, this behavior can be observed in many malware families, and we can make use of it to reduce false positives by ensuring the packet length is exactly that which is encoded in the packet.
Many protocols also include payload/data lengths, and byte_jump
can be used to test for buffer overflows.
Resources
Feel free to use the public Dalton instance located at https://doesmyrule.work to replicate the examples findings.
Official Documentation of byte_jump
Helpful blogs/mailing list posts
Cisco Talos - Using byte_jump as Detection Mechanism
Detection Pointer
In order to understand byte_jump, it’s helpful to think of a “detection pointer” which corresponds to the current location within the packet/payload/buffer/etc the IDS engine has progressed to while matching content. It can be moved forward and backward depending on which keywords and options are used.
This “detection pointer” is known formally as the “Detection Offset End Pointer” or the doe_ptr
.
This detection pointer exists before, after or between bytes. Some keywords, such as content
, offset
and distance
move the detection pointer.
-
content
- moves the detection pointer when it finds a positive match
-
offset
- statically moves the detection pointer relative to the start of the packet/payload/buffer
-
distance
- moves the detection pointer relative to a previous positive
content
match. - negative values move the pointer backward
- moves the detection pointer relative to a previous positive
byte_jump
is another keyword that moves the detection pointer. In order to determine how far to move the detection pointer, byte_jump
will read bytes from the packet/payload/buffer.
Important Behaviors
Location of the start of jump
By default byte_jump
will jump from just after the last byte used to determine the distance of the jump.
Consider the payload used in Example 1 below:
\x00\x00\x00\x2bWindows 10 Pro|x64|12 GB|John-PC|John|Admin
If byte_jump is configured to use the first four bytes of this payload (byte_jump:4,0;
), the jump wil start from just after \x2b
and before W
. As the detection pointer exists between bytes, the engine will start the jump between two bytes.
No alert if there is no data beyond the jump
If the jump causes the detection pointer to move to the end of, or beyond, the packet/payload/buffer, no alert will be produced. See Example 1 for more details.
Syntax
There are many options to byte_jump
and not all will be discussed here. There are only two required options, which are positional in nature.
- num of bytes
- offset
The most common optional arguments used are:
-
post_offset
- steps backwards or forward a number of bytes after making the jump
-
from_beginning
- makes the “jump” from before the first byte of the packet/payload/buffer
-
relative
- adjust the offset “relative” to the last positive content match
Examples
The following examples will provide a simple introduction to byte_jump using the mentioned common optional arguments. The examples will focus on using the byte_jump
keyword to detection a malware checkin. Example rules will focus on the use of the byte_jump
keyword and less on the best practices of rule writing, as a result some rules will be intentionally sub-optimal to not distract from the specifics of the byte_jump
.
Example 1
Example 1 will demonstrate the basic use of byte_jump
in combination with the post_offset option and the isdataat
keyword to ensure that a payload or buffer is an exact length included in the packet itself.
Payload
Consider pcap_1.pcap with the following raw content:
\x00\x00\x00\x2bWindows 10 Pro|x64|12 GB|John-PC|John|Admin
The first 4 bytes are a “length header” (\x00\x00\x00\x2b
) indicating the length of the remaining payload in hex*. Note that it does not include the “length header” bytes themselves. For the sake of this example, assume the length is variable but the start of the data (Windows\x20
) is always the same. As other elements in the payload are variable length, as such, the packet will not have a predetermined length. In effort to reduce FPs, the signature should ensure the packet length matches the length in the “length header”.
*byte_jump does allow for an option (dec) to treat the bytes used to determine the distance of the jump as decimal instead of hex.
In this example, the “length header” indicates there are 43 bytes of data.
The signature logic being attempted here is:
- Ensure
Windows\x20
is in the packet (for simplicities the exact location of those bytes will not be considered) - Ensure the data after the “length header” is exactly the length set forth in the “length header”.
Signature Attempt #1
alert tcp-pkt $HOME_NET any -> $EXTERNAL_NET any (flow:established,to_server; content:"Windows|20|"; byte_jump:4,0; sid:1;)
The content match provided will move the pointer right between the \x20
and 1
of 10 Pro
. The byte_jump options indicate to interpret four bytes, starting at “absolute” (no relative
option) offset of 0. byte_jump will then move the pointer to before the first byte (0x00
), read in four bytes (\x00\x00\x00\x2b) and then make the jump of 43 bytes (hex \x2b) starting at the end of the four bytes read. This moves the pointer to the end of the payload.
Step 1: Content Match
Step 2: back to offset 0
Step 3: “Consume” the bytes
Step 4: Make the jump
Visual Analysis
Outcome
sid:1 does not produce an alert. This lack of alert is due to the behavior of byte_jump
which only evaluates to true (causing an alert) when there is data beyond the location of the detection pointer, after the jump.
Two rules below will demonstrate this behavior by using the
isdataat
keyword in attempt to prove there either is or is not data after the jump. Based on the rules below (sid:2 and sid:3), it’s reasonable to assume at least one of them should produce an alert.
alert tcp-pkt $HOME_NET any -> $EXTERNAL_NET any (flow:established,to_server; content:"Windows|20|"; byte_jump:4,0; isdataat:1,relative; sid:2;)
alert tcp-pkt $HOME_NET any -> $EXTERNAL_NET any (flow:established,to_server; content:"Windows|20|"; byte_jump:4,0; isdataat:!1,relative; sid:3;)
However, neither of them do. This demonstrates an important behavior of byte_jump
. It will not alert if there is no data beyond the detection pointer after the jump as been completed.
Signature Attempt #2
alert tcp-pkt $HOME_NET any -> $EXTERNAL_NET any (flow:established,to_server; content:"Windows|20|"; byte_jump:4,0, post_offset -1; sid:4;)
In order to cause an alert, we can use the post_offset
option with a value of -1
to “step back” one byte, which will move the detection pointer back into the buffer between i
and n
as demonstrated by the gif below.
Visual Analysis
Outcome
While sid:4 does produce an alert, there is no logic which ensures that the next byte is end of the
packet/payload/buffer.
Signature Attempt #3
In order to ensure the byte_jump has actually jumped to the end of the packet/payload/buffer, the isdataat
keyword, along with a negated match can be used.
In practice, this will ensure that there is NOT data, two byes away from the current detection pointer location.
alert tcp-pkt $HOME_NET any -> $EXTERNAL_NET any (flow:established,to_server; content:"Windows|20|"; byte_jump:4,0, post_offset -1; isdataat:!2,relative; sid:5;)
Visual Analysis
Outcome
sid:5 contains logic which matches on:
-
Windows\x20
is in the payload - The payload, not including the “length header”, is exactly the length indicated in the “length header”.
Example 2
Example 2 will demonstrate the use of from_beginning
. This option is useful when the “length header” includes the length of the “length header”.
Payload
Consider pcap_2.pcap with the following raw content:
\x00\x00\x00\x2fWindows 10 Pro|x64|12 GB|John-PC|John|Admin
This content includes the length of the header in the “length header” field. Given there is only a single “length header” this is a good candidate to demonstrate the use of the from_beginning
option.
Using from_beginning
causes the engine jump from the start of the packet/payload/buffer. As demonstrated in Example 1 the post_offset
option and isdataat
keyword will be leveraged again.
Signature Attempt #1
alert tcp-pkt $HOME_NET any -> $EXTERNAL_NET any (flow:established,to_server; content:"Windows|20|"; byte_jump:4,0, from_beginning, post_offset -1; isdataat:!2,relative; sid:6;)
Example 3
Example 3 will demonstrate the use of the relative
option.
The relative
options effects the offset of the byte_jump command. That is to stay, it effects which bytes the engines uses to determine the length of the jump, it does not make the start of the jump relative to the last content match.
Payload
Consider pcap_3.pcap with the raw data of:
\x00\x01\x25\x00\x00\x00\x2bWindows 10 Pro|x64|12 GB|John-PC|John|Admin
This content a total of 4 different fields:
- Packet Type Length -
\x00\x01
- Packet Type -
\x25
- Data Length -
\x00\x00\x00\x2b
- Data -
Windows 10 Pro|x64|12 GB|John-PC|John|Admin
Signature Attempt #1
As the “data length header” is at a variable location from the start of the packet, the relative
option can be leveraged. In this case, the relative
keyword will be used to “back up” to the start of the “data length” field after finding the start of the Data. As in previous example, assume the length is variable but the start of the data (Windows\x20
) is always the same.
alert tcp-pkt $HOME_NET any -> $EXTERNAL_NET any (flow:established,to_server; content:"Windows|20|"; byte_jump:4,-12,relative, post_offset -1; isdataat:!2,relative; sid:6;)
Visual Analysis
Outcome
sid:6 produces an alert and contains all the required logic to ensure the “Data” is exactly the length indicated in the “Data Length” header and that there is nothing after it in the payload.f