ber_data and ber_skip
Snort 3 introduces two new rule options that are useful when writing detection for ASN.1 Basic Encoding Rules (BER), ber_data
and ber_skip
. Both options move the detection cursor with respect to BER data, but the difference lies in where the cursor is moved.
BER is a type-length-value (TLV) encoding format that encodes data elements in a stream containing (in this order):
- Type: typically a single byte that identifies the kind of data being represented
- Length: one or more bytes identifying the size of the value field
- Value: length-number of bytes of the specified type's data
For example, consider the following BER-encoded element (assume the below bytes are hex):
02 03 01 00 01
The type is 0x02
, which corresponds an INTEGER
type. The length is 0x03
, which tells us that the value is 3-bytes long. Then we just parse those three bytes after the length field to get the integer value:
01 00 01
Encoded length fields
As mentioned above, the length field can either be a single byte, or it can span multiple bytes. More specifically, the length field will span multiple bytes if the value field contains more than 128 bytes. If that's the case, then bit 7 of the length field will be set, and bits 0-6 will indicate how many length bytes follow. For example, consider the start of this element:
30 82 04 9A 41 41 41 41 …
The first byte tells us the type, which in this case is 0x30
. However, notice that our length field is > 0x7f
. This means that bit 7 of the length field is set to 1, which means that the length field requires more than one byte. So, we can look at the binary of 0x82
to figure out what bits 0-6 give us:
10000010
As we can see, we get 2 from reading bytes 0-6, and so we know that two length bytes follow. We then grab those two bytes ‒ 0x049A
‒ to get the length of the subsequent value. Then we can just parse length-number of bytes as we normally would to get the value.
Now let's look at how Snort 3's ber_data
and ber_skip
rule options are used to work with this kind of data.
ber_data
The ber_data
option moves the detection cursor to the value portion of a specified BER element.
Using this option is done with ber_data:
followed by the element's type code. The code can be written in decimal (e.g., 1, 2, 3, 4) or in a hexadecimal (e.g., 0x1, 0x2, 0x3, 0x4), and the valid range of codes is 0:255
.
For example, let's assume we are looking at a packet starting with 02 03 01 00 01
. We can move to the value portion of the element with ber_data:0x02;
.
- Detection cursor before the
ber_data:0x02;
:00000000 02 03 01 00 01
- Detection cursor after the
ber_data:0x02;
:00000000 01 00 01
Note that ber_data
works relative to the cursor's current location in a given buffer. This means that if you don't move the cursor (for example with a content
match) prior to using this option, then ber_data
will try to pick up bytes at the start of the buffer.
Format:
ber_data:type;
Examples:
ber_data:0x02;
content:"|01 00 01|", within 3;
BER elements are commonly nested such that one element's value is the start of another element. Multiple ber_data
options can be used to verify that a specific element is present in another:
ber_data:0x30;
# checks that 0x02 is the first element present in 0x30's value
ber_data:0x02;
ber_skip
The ber_skip
option skips an entire BER element. It reads in the length value of the specified type, and jumps that many bytes, moving the cursor to the data immediately following the specified element.
Using this option is done with ber_skip:
followed by the type code of the element to skip. The code can be written in decimal (e.g., 1, 2, 3, 4) or in hexadecimal (e.g., 0x1, 0x2, 0x3, 0x4), and the valid range of elements is 0:255
.
For example, let's assume we are looking an INTEGER
element (identified by 0x02) followed by a BOOLEAN
element (identified by 0x01): 02 01 FF 01 01 05
. We can skip the INTEGER
element with ber_skip:0x02;
to move the cursor to the start of the very next element, the BOOLEAN
.
- Detection cursor before the
ber_skip:0x02;
:00000000 02 01 FF 01 01 05
- Detection cursor after the
ber_skip:0x02;
:00000000 01 01 05
By default, this rule option will return false if the specified BER element is not found. However, you can specify ,optional
after the element number to tell Snort that the BER element you want to skip is optional.
Note that ber_skip
works relative to the cursor's current location in a given buffer. This means that if you don't move the cursor (for example with a content
match) prior to using this option, then ber_skip
will try to pick up bytes at the start of the buffer.
Format:
ber_skip:type[,optional];
Examples:
ber_skip:0x02;
content:"|01 01 05|", within 3;
# will still continue evaluating even if the 0x02 is not present
ber_skip:0x02,optional;
content:"|01 01 05|", within 3;
Using ber_data and ber_skip together
ber_data
and ber_skip
will often be used together to iterate through BER-encoded structures. LDAP traffic is one common use case. For the below example, consider the following LDAP packet:
00000000 30 33 02 01 02 63 2E 04 00 0A 01 02 0A 01 00 02 03...c..........
00000010 01 00 02 01 00 01 01 00 A0 19 A4 0C 04 02 63 6E ..............cn
00000020 30 06 81 04 66 72 65 64 A3 09 04 02 64 6E 04 03 0...fred....dn..
00000030 6A 6F 65 30 00 joe0.
The below example will iterate through the various elements to get to the data of the first 0x0A
tag:
# LDAP is a common example
ber_data:0x30;
ber_skip:0x02,optional;
ber_data:0x63;
ber_skip:0x04;
ber_data:0x0a;
content:"|02|", within 1;