Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Protodetect enip dns 4388 v7 #6185

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 37 additions & 10 deletions rust/src/dns/dns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -659,23 +659,50 @@ impl DNSState {
}
}

fn probe_header_validity(header : DNSHeader, rlen: usize) -> (bool, bool, bool) {
let opcode = ((header.flags >> 11) & 0xf) as u8;
if opcode >= 7 {
//unassigned opcode
return (false, false, false);
}
if header.additional_rr as usize
+ header.answer_rr as usize
+ header.authority_rr as usize
+ header.questions as usize
> rlen
{
//not enough data for additional records
return (false, false, false);
}
Comment on lines +668 to +676
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As these are counts not sizes, its not accurate, but also won't false positive. Should the size of the header be calculated in as well? If sizeof(header) + addtional_rr +answer_rr > rlen... Might be a bit more accurate. I think we could also assume a minumum of 2 bytes per expected record being the CLASS is a u16.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, these are counts, not sizes.
And I do not count the header size.
But as each of these has at least one byte, we can do this test to exclude some cases

So, should I include the header size in this sum ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so. Just to confirm, rlen is the size of the record. In TCP this would be the first 2 bytes, in UDP its the payload size.

So a test to make sure its greater than or equal to:

sizeof(header) + (additional_rr * 2) + (answer_rr * 2) + (authority_rr * 2) + (questions * 2)

should be safe and accurate, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so. Just to confirm, rlen is the size of the record. In TCP this would be the first 2 bytes, in UDP its the payload size.

Exactly, you can check the callers do it right

So a test to make sure its greater than or equal to:

sizeof(header) + (additional_rr * 2) + (answer_rr * 2) + (authority_rr * 2) + (questions * 2)

should be safe and accurate, right?

Ok, will do this

let is_request = header.flags & 0x8000 == 0;
return (true, is_request, false);
}

/// Probe input to see if it looks like DNS.
fn probe(input: &[u8]) -> (bool, bool) {
match parser::dns_parse_request(input) {
fn probe(input: &[u8], dlen: usize) -> (bool, bool, bool) {
let i2 = if input.len() <= dlen { input } else { &input[..dlen] };
match parser::dns_parse_request(i2) {
Ok((_, request)) => {
let is_request = request.header.flags & 0x8000 == 0;
return (true, is_request);
return probe_header_validity(request.header, dlen);
},
Err(_) => (false, false),
Err(nom::Err::Incomplete(_)) => {
match parser::dns_parse_header(input) {
Ok((_, header)) => {
return probe_header_validity(header, dlen);
}
Err(nom::Err::Incomplete(_)) => (false, false, true),
Err(_) => (false, false, false),
}
}
Err(_) => (false, false, false),
}
}

/// Probe TCP input to see if it looks like DNS.
pub fn probe_tcp(input: &[u8]) -> (bool, bool, bool) {
match be_u16(input) as IResult<&[u8],_> {
Ok((rem, _)) => {
let r = probe(rem);
return (r.0, r.1, false);
match be_u16(input) as IResult<&[u8],u16> {
Ok((rem, dlen)) => {
return probe(rem, dlen as usize);
},
Err(nom::Err::Incomplete(_)) => {
return (false, false, true);
Expand Down Expand Up @@ -961,7 +988,7 @@ pub extern "C" fn rs_dns_probe(
let slice: &[u8] = unsafe {
std::slice::from_raw_parts(input as *mut u8, len as usize)
};
let (is_dns, is_request) = probe(slice);
let (is_dns, is_request, _) = probe(slice, slice.len());
if is_dns {
let dir = if is_request {
core::STREAM_TOSERVER
Expand Down
2 changes: 1 addition & 1 deletion rust/src/nfs/nfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1739,7 +1739,7 @@ pub fn nfs_probe(i: &[u8], direction: u8) -> i8 {
rpc.program == 100003 &&
rpc.procedure <= NFSPROC3_COMMIT
{
return 1;
return rpc_auth_type_known(rpc.creds_flavor);
} else {
return -1;
}
Expand Down
8 changes: 8 additions & 0 deletions rust/src/nfs/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,14 @@ pub fn rpc_auth_type_string(auth_type: u32) -> String {
}.to_string()
}

pub fn rpc_auth_type_known(auth_type: u32) -> i8 {
// RPCAUTH_GSS is the maximum
if auth_type <= RPCAUTH_GSS {
return 1;
}
return -1;
}

/* http://www.iana.org/assignments/rpc-authentication-numbers/rpc-authentication-numbers.xhtml */
pub const RPCAUTH_OK: u32 = 0; // success/failed at remote end [RFC5531]
pub const RPCAUTH_BADCRED: u32 = 1; // bad credential (seal broken) [RFC5531]
Expand Down
87 changes: 67 additions & 20 deletions src/app-layer-enip.c
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ static AppLayerResult ENIPParse(Flow *f, void *state, AppLayerParserState *pstat
SCReturnStruct(APP_LAYER_OK);
}


#define ENIP_LEN_REGISTER_SESSION 4 // protocol u16, options u16

static uint16_t ENIPProbingParser(Flow *f, uint8_t direction,
const uint8_t *input, uint32_t input_len, uint8_t *rdir)
Expand All @@ -371,43 +371,90 @@ static uint16_t ENIPProbingParser(Flow *f, uint8_t direction,
return ALPROTO_UNKNOWN;
}
uint16_t cmd;
uint16_t enip_len;
uint32_t status;
int ret = ByteExtractUint16(&cmd, BYTE_LITTLE_ENDIAN, sizeof(uint16_t),
(const uint8_t *) (input));
uint32_t option;
uint16_t nbitems;

int ret = ByteExtractUint16(
&enip_len, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), (const uint8_t *)(input + 2));
if (ret < 0) {
return ALPROTO_FAILED;
}
if (enip_len < sizeof(ENIPEncapHdr)) {
return ALPROTO_FAILED;
}
ret = ByteExtractUint32(
&status, BYTE_LITTLE_ENDIAN, sizeof(uint32_t), (const uint8_t *)(input + 8));
if (ret < 0) {
return ALPROTO_FAILED;
}
switch (status) {
case SUCCESS:
case INVALID_CMD:
case NO_RESOURCES:
case INCORRECT_DATA:
case INVALID_SESSION:
case INVALID_LENGTH:
case UNSUPPORTED_PROT_REV:
case ENCAP_HEADER_ERROR:
break;
default:
return ALPROTO_FAILED;
}
ret = ByteExtractUint16(&cmd, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), (const uint8_t *)(input));
if(ret < 0) {
return ALPROTO_FAILED;
}
ret = ByteExtractUint32(
&option, BYTE_LITTLE_ENDIAN, sizeof(uint32_t), (const uint8_t *)(input + 20));
if (ret < 0) {
return ALPROTO_FAILED;
}

//ok for all the known commands
switch(cmd) {
case NOP:
case LIST_SERVICES:
case LIST_IDENTITY:
case LIST_INTERFACES:
if (option != 0) {
return ALPROTO_FAILED;
}
break;
case REGISTER_SESSION:
if (enip_len != ENIP_LEN_REGISTER_SESSION) {
return ALPROTO_FAILED;
}
break;
case UNREGISTER_SESSION:
if (enip_len != ENIP_LEN_REGISTER_SESSION && enip_len != 0) {
// 0 for request and 4 for response
return ALPROTO_FAILED;
}
break;
case LIST_SERVICES:
case LIST_IDENTITY:
case SEND_RR_DATA:
case SEND_UNIT_DATA:
case INDICATE_STATUS:
case CANCEL:
ret = ByteExtractUint32(&status, BYTE_LITTLE_ENDIAN,
sizeof(uint32_t),
(const uint8_t *) (input + 8));
break;
case LIST_INTERFACES:
if (input_len < sizeof(ENIPEncapHdr) + 2) {
SCLogDebug("length too small to be a ENIP LIST_INTERFACES");
return ALPROTO_UNKNOWN;
}
ret = ByteExtractUint16(
&nbitems, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), (const uint8_t *)(input));
if(ret < 0) {
return ALPROTO_FAILED;
}
switch(status) {
case SUCCESS:
case INVALID_CMD:
case NO_RESOURCES:
case INCORRECT_DATA:
case INVALID_SESSION:
case INVALID_LENGTH:
case UNSUPPORTED_PROT_REV:
case ENCAP_HEADER_ERROR:
return ALPROTO_ENIP;
if (enip_len < sizeof(ENIPEncapHdr) + 2 * (size_t)nbitems) {
return ALPROTO_FAILED;
}
break;
default:
return ALPROTO_FAILED;
}
return ALPROTO_FAILED;
return ALPROTO_ENIP;
}

/**
Expand Down