hasherezade / pe-sieve

Scans a given process. Recognizes and dumps a variety of potentially malicious implants (replaced/injected PEs, shellcodes, hooks, in-memory patches).
https://hshrzd.wordpress.com/pe-sieve/
BSD 2-Clause "Simplified" License
2.97k stars 420 forks source link

Issues with querying virtual memory of vmmem #68

Closed Jack-McDowell closed 3 years ago

Jack-McDowell commented 3 years ago

When scanning the vmmem process, it appears a valid handle is being granted, even though the handle doesn't have the access requested.

In workingset_enum.cpp, this is extremely problematic, since the entire usermode address space is enumerated. If the VirtualQuery fails (and it does), the start_va is incremented by one page size and an error message is printed. While adequate for expected scenarios, this process of iterating the entire address space one page at a time, printing out an error with each page is extremely slow. For all intents and purposes, this is a scan that will never actually finish.

Unfortunately, I don't think the trivial check of "is the process named vmmem" is a viable solution; what's to stop a piece of malware from calling itself vmmem?

While this is not specifically related to the problem here, it appears that this is also writing to standard error even in quiet mode rather than just reporting an error in the scan report.

hasherezade commented 3 years ago

I think this is the main issue to solve here:

When scanning the vmmem process, it appears a valid handle is being granted, even though the handle doesn't have the access requested.

Unfortunately nowadays I am extremely busy (working almost non-stop to meet some close deadlines) and I cannot dedicate much time to this. Also I cannot reproduce it in my current environment. Is it something very urgent for you? Can you provide more details, including screenshots, that could help me in troubleshooting? As much details as possible, but mostly I am interested in:

I believe there will be some possibility to determine that we cannot read the workingset at all, and this is the direction of bugfixing here.

Jack-McDowell commented 3 years ago

I went with a check for whether the process name is vmmem. It's not an ideal solution, but at least it's not an urgent issue. I can try to see if I can diagnose the issue. I'll add more info as I find it

hasherezade commented 3 years ago

ok, as a temporary workaround - maybe not the name of the process, because it can be spoofed. but the path to the image that is mapped.

Jack-McDowell commented 3 years ago

The error being returned by VirtualQueryEx is ERROR_BAD_LENGTH.

My access mask on the handle is 0x1410 (corresponding to Query information and VM read).

Using process hacker's kernel driver, I was able to peek at the memory of vmmem. It appears that the only usermode memory section is 0xFFFF0000 bytes of reserved (but uncommitted) starting at address 0x10000.

As a side note, vmmem appears to be a process that mostly is used for representing the resources used by Hyper-V in running other VMs. I'm wondering if this is a result of the WinAPI not interacting well with virtualization yet...

Also - vmmem has no mapped image. I think it's a pico process. All it has is its name.

https://devblogs.microsoft.com/oldnewthing/20180717-00/?p=99265

hasherezade commented 3 years ago

hmm, it seems a tough case, and it will need time and many tests to find a proper solution, which will not break other things at the same time. Maybe some redesigning of the workingset scan will be required. I will take care of this after I am done with my deadlines, but for now it unfortunately has to wait.

hasherezade commented 3 years ago

Basing on what you wrote, and assuming things are the way I understood, I tried to make a workaround in a new branch: https://github.com/hasherezade/pe-sieve/tree/issue_68

Please let me know if it works.

You said:

the start_va is incremented by one page size and an error message is printed.

Which error message is printed then? Isn't it this one? https://github.com/hasherezade/pe-sieve/blob/a7bfff9562c3d58f37233cc76fdd2ee0c21c8470/utils/workingset_enum.cpp#L37-L41

The error being returned by VirtualQueryEx is ERROR_BAD_LENGTH.

So, if as you said error == ERROR_BAD_LENGTH, yet the above message is printed, it means: out != sizeof(page_info). I based my idea for the fix on this assumption.

Jack-McDowell commented 3 years ago

Oh, my bad, I saw that was the error message being issued, and my brain went ERROR_BAD_LENGTH. The correct error is ERROR_PARTIAL_COPY, "Only part of a ReadProcessMemory or WriteProcessMemory request was completed."

The handle lying about its access may also have been a premature assumption I made. I'd seen it before when something I'd written was trying to read lsaiso, so I jumped to conclusions a bit here.

After playing around a bit with the debugger, it looks like the MEMORY_BASIC_INFORMATION struct actually holds the correct data. For some reason, it's just giving that error, and pe-sieve is adding PAGE_SIZE instead of page_info.RegionSize. The fix should be pretty easy. Just change

if (out != sizeof(page_info) || error != ERROR_BAD_LENGTH) {

to

if (error != ERROR_PARTIAL_COPY && (out != sizeof(page_info) || error != ERROR_BAD_LENGTH)) {

Sorry about the confusion!

hasherezade commented 3 years ago

ok, maybe this will help then: https://github.com/hasherezade/pe-sieve/tree/issue_68a

Jack-McDowell commented 3 years ago

That works! Thank you for fixing it so quickly!

hasherezade commented 3 years ago

oh, no. I merged a wrong branch... just a moment...

hasherezade commented 3 years ago

ok, should be fine now. I reworked some things, so please let me know if everything works fine. and feel free to close the issue after the passed tests.

Jack-McDowell commented 3 years ago

All good now!