Skip to content

Detect ETW Patching #1787

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

Merged
merged 15 commits into from
Apr 29, 2025
Merged

Detect ETW Patching #1787

merged 15 commits into from
Apr 29, 2025

Conversation

JakePeralta7
Copy link
Contributor

This plugin identifies ETW patching by inspecting the first opcode of the following functions:

  • EtwEventWrite
  • EtwEventWriteFull
  • NtTraceEvent

If the initial opcode is 0xC3 (RET) or 0xE9 (JMP), the function is flagged as patched.

Below is a screenshot demonstrating when the detection is triggered:
WhatsApp Image 2025-04-24 at 20 33 46_3972c8e1

Also, I've added support for scanning specific processes.

This method only checks the first opcode. If an attacker patches elsewhere in the function in a way that corrupts it - the modification will not be detected.

Note: This is my first plugin - any feedback is welcome!

@atcuno atcuno self-requested a review April 24, 2025 18:47
@atcuno
Copy link
Contributor

atcuno commented Apr 24, 2025

@ikelos - I will help with this one as I am very familiar with the intent and it should go through the pe_symbols API to be complete. I will ping you again when it is ready.

@JakePeralta7
Copy link
Contributor Author

@ikelos - I will help with this one as I am very familiar with the intent and it should go through the pe_symbols API to be complete. I will ping you again when it is ready.

Thanks for the feedback, I've extended the usage of the pe_symbols plugin

@atcuno
Copy link
Contributor

atcuno commented Apr 28, 2025

Thank you so much @JakePeralta7 - sorry for not getting back with more specifics. This weekend got lost in other work. I will leave just a few comments through the code itself then this should be good to go.

@JakePeralta7
Copy link
Contributor Author

JakePeralta7 commented Apr 28, 2025

Thank you so much @JakePeralta7 - sorry for not getting back with more specifics. This weekend got lost in other work. I will leave just a few comments through the code itself then this should be good to go.

Great comments, I think I've solved all of the problems you have raised (I have to say that the code look much more readable 🤣)

@atcuno
Copy link
Contributor

atcuno commented Apr 28, 2025

Looking very nice. Just two more comments on the latest push. After addressing those, I can run the tests plus test on my local samples with these functions hooked.

@JakePeralta7
Copy link
Contributor Author

Looking very nice. Just two more comments on the latest push. After addressing those, I can run the tests plus test on my local samples with these functions hooked.

I hope I got it right this time

@atcuno
Copy link
Contributor

atcuno commented Apr 28, 2025

The code looks good now, but it needs to be fixed for black and ruff. You can install these through pip then do (In order):

  1. black volatility3/framework/plugins/windows/etwpatch.py

^ This will lint the file

  1. ruff check volatility3/framework/plugins/windows/etwpatch.py

^ This will report any formatting or other coding issues that need to be fixed.

Once these pass locally, then re-commit your code.

@atcuno
Copy link
Contributor

atcuno commented Apr 28, 2025

The code analysis checks failed about the requirements as it looks like you copy/pasted the pslist requirement and changed the name= but not the component=. Just changed it to be the pe_symbols class instead:

name="pe_symbols", component=pslist.PsList, version=(3, 0, 0)

@JakePeralta7
Copy link
Contributor Author

@atcuno
Fixed it (thanks for the patience walking me through the process)

@atcuno
Copy link
Contributor

atcuno commented Apr 28, 2025

Ok I just re-triggered the tests. To fix the requirements error just make the line look like this:

https://github.com/volatilityfoundation/volatility3/blob/develop/volatility3/framework/plugins/windows/unhooked_system_calls.py#L101

@atcuno
Copy link
Contributor

atcuno commented Apr 28, 2025

This is ready now @ikelos. Awesome work @JakePeralta7 !!

@atcuno atcuno requested a review from ikelos April 28, 2025 16:29
Copy link
Member

@ikelos ikelos left a comment

Choose a reason for hiding this comment

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

One little clear up just to avoid confusion around unnecessary hex/string conversions... Otherwise looks good, just a couple of general questions to think about for the future.

  1. Have you considered whether the functionality used could be used by more plugins and could therefore be pulled out into an appropriately parameterized @classmethod?
  2. This may find false positives where databytes are read as if they're an opcode, it's likely overkill, but you could pull in capstone to do disassembly and check the actual opcodes (and only the opcodes)? For an example of this see direct_system_calls or skeleton_key_check...
  3. Possibly we should gather all the malware specific plugins and put them under the malware class (so windows.malware.etwpatch rather than just windows.etwpatch...

@JakePeralta7
Copy link
Contributor Author

One little clear up just to avoid confusion around unnecessary hex/string conversions... Otherwise looks good, just a couple of general questions to think about for the future.

  1. Have you considered whether the functionality used could be used by more plugins and could therefore be pulled out into an appropriately parameterized @classmethod?
  2. This may find false positives where databytes are read as if they're an opcode, it's likely overkill, but you could pull in capstone to do disassembly and check the actual opcodes (and only the opcodes)? For an example of this see direct_system_calls or skeleton_key_check...
  3. Possibly we should gather all the malware specific plugins and put them under the malware class (so windows.malware.etwpatch rather than just windows.etwpatch...
  1. My take on this kind of approach is when something else will need it I will abstract, but guessing what will be needed is sometimes unnecessary
  2. I'm reading from an offset of a dll exported function (the exact start of it), so is that really a risk?
  3. Sounds great

Copy link
Member

@ikelos ikelos left a comment

Choose a reason for hiding this comment

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

Nearly there, just black being a bit specific in its tastes...

Comment on lines 96 to 99
opcode = (
self.context.layers[proc_layer_name]
.read(func_addr, 1)[0]
)
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
opcode = (
self.context.layers[proc_layer_name]
.read(func_addr, 1)[0]
)
opcode = self.context.layers[proc_layer_name].read(
func_addr, 1
)[0]

Sorry, black just being a bit specific about the formatting... 5:S

Comment on lines 89 to 90
0xc3: "RET",
0xe9: "JMP",
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
0xc3: "RET",
0xe9: "JMP",
0xC3: "RET",
0xE9: "JMP",

@ikelos
Copy link
Member

ikelos commented Apr 29, 2025

Oh, beat me to it! 5;P

@ikelos ikelos merged commit e22e82d into volatilityfoundation:develop Apr 29, 2025
14 checks passed
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.

3 participants