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

395 add the information about match context to the database #439

Draft
wants to merge 22 commits into
base: master
Choose a base branch
from

Conversation

michalkrzem
Copy link
Collaborator

Your checklist for this pull request

  • I've read the contributing guideline.
  • I've tested my changes by building and running mquery, and testing changed functionality (if applicable)
  • I've added automated tests for my change (if applicable, optional)
  • I've updated documentation to reflect my change (if applicable)

What is the current behaviour?

What is the new behaviour?

Test plan

Closing issues

fixes #issuenumber

@michalkrzem michalkrzem linked an issue Nov 22, 2024 that may be closed by this pull request
@michalkrzem michalkrzem requested a review from msm-cert December 1, 2024 23:23
src/tasks.py Outdated
Comment on lines 121 to 131
"""Reads a specific range of bytes from the already loaded file content around a given offset.

Args:
data (bytes): Data to read.
matched_length (int): Number of bytes to read.
offset (int): The offset in bytes from which to start reading.
byte_range (int): The range in bytes to read around the offset (default is 32).

Returns:
bytes: A chunk of bytes from the file, starting from the given offset minus bit_range
and ending at offset plus matched_length and byte_range.
Copy link
Member

Choose a reason for hiding this comment

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

Generated by chatgpt? 😀

I don't think the docstrings here are very good (some parts are actually plain wrong), and we don't generally use that docstring format for arguments, so I think argument docs can/should be removed. Maybe:

Suggested change
"""Reads a specific range of bytes from the already loaded file content around a given offset.
Args:
data (bytes): Data to read.
matched_length (int): Number of bytes to read.
offset (int): The offset in bytes from which to start reading.
byte_range (int): The range in bytes to read around the offset (default is 32).
Returns:
bytes: A chunk of bytes from the file, starting from the given offset minus bit_range
and ending at offset plus matched_length and byte_range.
"""Return `matched_length` bytes from `offset`, along with `byte_range` bytes before and after the match.

src/tasks.py Outdated
Comment on lines 107 to 115
@staticmethod
def read_file(file_path: str) -> bytes:
"""Reads the entire file content.

Returns:
bytes: The content of the file.
"""
with open(file_path, "rb") as file:
return file.read()
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this method is necessary - you can just inline this.

Even if you prefer to keep it, it doesn't belong in this class (Agent). But IMO it's best to inline it.

src/tasks.py Outdated
return file.read()

@staticmethod
def read_bytes_from_offset(
Copy link
Member

Choose a reason for hiding this comment

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

Not a very good method name. Maybe read_bytes_with_context?

Copy link
Member

Choose a reason for hiding this comment

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

Also there's no reason to use @staticmethod here - it's generic, and has no relation to the Agent class. Please make it a global method

src/tasks.py Outdated
"after": base64.b64encode(after).decode("utf-8"),
}
)
context.update({str(yara_match): match_context})
Copy link
Member

Choose a reason for hiding this comment

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

str(yara_match)? Probably better to avoid relying on str() representation and to use something like yara_match.rule (or .name? I don't remember)

Copy link
Member

Choose a reason for hiding this comment

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

One thing that's missing is that you don't put string name anywhere - this makes it impossible to tell which string matched. In most cases there is a single yara rule per query, but multiple strings.

It definitely must be stored somewhere. Probably the easiest way is to use a dict for contetx instead of a list:

{
    "rule_name_1": {
        "string_1": {
            "before": "CgkJCQkJcHJpbnQgU1RERVJSICRtc2c7CgkJCQkJdW4=",
            "match": "AbcDe9f=",
            "after": "CgkJCQkJcHJpbnQgU1RERVJSICRtc2c7CgkJCQkJdW4=",
        },
        "string_2": {
            "before": "CgkJCQkJcHJpbnQgU1RERVJSICRtc2c7CgkJCQkJdW4=",
            "match": "AbcDe9f=",
            "after": "CgkJCQkJcHJpbnQgU1RERVJSICRtc2c7CgkJCQkJdW4=",
        }
    },
    "rule_name_2": {
        "string_1": {
            "before": "CgkJCQkJcHJpbnQgU1RERVJSICRtc2c7CgkJCQkJdW4=",
            "match": "AbcDe9f=",
            "after": "CgkJCQkJcHJpbnQgU1RERVJSICRtc2c7CgkJCQkJdW4=",
        },
        "string_2": {
            "before": "CgkJCQkJcHJpbnQgU1RERVJSICRtc2c7CgkJCQkJdW4=",
            "match": "AbcDe9f=",
            "after": "CgkJCQkJcHJpbnQgU1RERVJSICRtc2c7CgkJCQkJdW4=",
        }
    }
}

Copy link
Member

Choose a reason for hiding this comment

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

Finally, this expression is overly complicated - I believe

context.update({A: B})

is equivalent to

context[A] = B

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Here, in the match key, we have information about the string we are searching for. But okay, I’ll replace the list with a dictionry.

src/tasks.py Outdated
for string_match in yara_match.strings:
expression_keys = []
for expression_key in string_match.instances:
if expression_key in expression_keys:
Copy link
Member

Choose a reason for hiding this comment

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

This check is a no-op - for (regular, with no eq override) python object object in SOMETHING will always be false.

Copy link
Member

Choose a reason for hiding this comment

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

I don't fully get the idea behind this. If the intention was to only add a single match to the context (as originally planned, I think) then you should check if string_match was already added instead.

Or even better, just take the first instance - no loop necessary

src/tasks.py Outdated
for yara_match in matches:
match_context = []
for string_match in yara_match.strings:
expression_keys = []
Copy link
Member

Choose a reason for hiding this comment

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

Since this is only used for presence checking, this should be a set instead of a list. But I think it's not necessary at all (see next comment)

src/tasks.py Outdated
Comment on lines 209 to 213
(before, matching, after,) = self.read_bytes_from_offset(
data=data,
offset=expression_key.offset,
matched_length=expression_key.matched_length,
)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
(before, matching, after,) = self.read_bytes_from_offset(
data=data,
offset=expression_key.offset,
matched_length=expression_key.matched_length,
)
(before, matching, after) = self.read_bytes_from_offset(
data,
expression_key.offset,
expression_key.matched_length,
)

src/tasks.py Outdated
@@ -140,6 +194,36 @@ def execute_yara(self, job: Job, files: List[str]) -> None:
f"in {scanned_datasets}/{job.total_datasets} ({dataset_percent:.0%}) of datasets.",
)

def get_match_context(
self, data: bytes, matches: List[yara.Match]
) -> dict:
Copy link
Member

Choose a reason for hiding this comment

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

for now we use typing classes everywhere (and also misses key/value types)

Suggested change
) -> dict:
) -> Dict[str, ???]:

(value type to be fixed depending on how other comments are resolved)

src/tasks.py Outdated
self.update_metadata(
job.id, orig_name, path, [r.rule for r in matches]
job=job.id,
orig_name=orig_name,
Copy link
Member

Choose a reason for hiding this comment

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

Why explicit parameter names everywhere? It's a bit verbose, but OK - just wondering.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I read somewhere that when we use such a notation, we don’t have to worry about the order of the arguments, and we already know during the function call what variables are being assigned to (if the naming differs). That was someone’s opinion; I’ll adapt to the project then. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add the information about match context to the database
2 participants