transacted_hollowing
transacted_hollowing copied to clipboard
Just a question
Hey there,
the payload or malicious PE file should be on the fileytem here. Mostly it will be already fished away by AV, especially if it is malicious or suspicious enough :-) In my case for testing purpose it is mimikatz being wiped away. I was just wondering how malware can benefit from those techniques like process ghosting or transacted hollowing.........
Hi! Of course it is just a PoC, and on purpose it wasn't implemented as a ready-made loader for malware :) In the real-life scenario you will not read the payload from the file, but have it hardcoded in the binary. I implemented the injection part, not the storage of the payload - to demonstrate the technique, but at the same time to prevent it from being used as a malware packer by script kiddies. ;)
Thanks for your quick answer hasherezade. Sure it is a POC. I am just interested how malware is working with it. When in the real-life scenario the payload will not be read from the file - since when a PE file has a malware file backpacked so to say, why not just injecting the malicious part - why do I need techniques like transacted hollowing or process ghosting? Sorry for this maybe stupid question, I just want to understand.....
why not just injecting the malicious part
So, the techniques like transacted hollowing or process ghosting are exactly for the injection. you can have the payload stored in the loader - but in order to execute it, you have to load it first, and make it runnable. And there are various techniques dedicated to this part. Depending on the technique used, the loader may be less or more stealthy. Different loaders have also different steps, and may have different limitations. It is too long topic to elaborate here, but I will try to explain you briefly, Some examples:
- manual loading (each step of the PE loading is implemented manually - including filling the Imports. works best for self-injection, or reflective injection). the new module needs to be added into an existing process. payload is mapped as MEM_PRIVATE
- process hollowing (aka RunPE) - the most common technique of process impersonation. replaces a PE module in the newly created, suspended process. then takes advantage of Windows loader to fill the Imports of the payload. typically the payload is mapped as MEM_PRIVATE
- transacted hollowing - like process hollowing, but the payload is first loaded as image from a transacted file. so, it is mapped as MEM_IMAGE into the target (that makes it look similar to legitimately loaded modules)
- process ghosting - maps payload as MEM_IMAGE from the invisible (delete-pending) file. then creates the full process out of this image, manually mapping all the needed structures.
- and there are even more...
So they all are made to achieve the goal of running the payload under the cover of a different process - they just do it in different ways, and the end result may give different artifacts in memory.
Hey thank you for this awesome explanation. Very good insight you gave. My point is, for example for transacted hollowing, the malicious file must be on the HD in order to use the transacted feature for the file. And when the malware is touching the disk, AV can detect it.
In case of transacted hollowing, similarly to process doppelganging, the payload is dropped (temporarily) into a transacted file. but due to the transacted feature, this file is accessible only to the creator, and AV cannot scan it. more about it I described in the article about Process Doppelganging: https://hshrzd.wordpress.com/2017/12/18/process-doppelganging-a-new-way-to-impersonate-a-process/ - check also an original presentation of the authors of this technique (https://www.youtube.com/watch?v=Cch8dvp836w).
Ah thank you again! Now I see. FIRST a not malicious file is transacted and THEN the malicious code is written to the transacted file. That was my understanding problem. Thanks a lot again!
ok, I hope your doubts are resolved now :)
What about this one: https://github.com/hasherezade/transacted_hollowing/blob/a9eaed85627827d28d34d03d059bbdd4e88834c9/transacted_file.cpp#L40 Is it possible to not write the file to disk at all? This is also used in all other injection techniques you made i.e. process_doppelganging, process_overwriting and process_ghosting. In other projects you do NtSetInformationFile in order to force the file to be deleted on close but it's still not as ideal as it could've been if the binary was never written to disk. I'm not looking for a ready to use solution but rather just a hint or at least a yes or no if it's possible at all so that I can do some more research on my own. Thanks
@Ou7law007 - this particular line: https://github.com/hasherezade/transacted_hollowing/blob/a9eaed85627827d28d34d03d059bbdd4e88834c9/transacted_file.cpp#L40 is a part of the Transacted File Creation, which is a compulsory part of both Process Doppelgänging and transacted_hollowing. Check the writeup: https://hshrzd.wordpress.com/2017/12/18/process-doppelganging-a-new-way-to-impersonate-a-process/ The file is created inside the transaction, which means, until the transaction is commited, only the process that created it can see those changes. So the dropped file is never observed by other entities, since the transaction is rolled back. It is not a simple file dropping. As this technique is an essential part of the aforementioned techniques, it cannot be skipped.
Process Ghosting is similar - there we also drop the file to which other processes don't have access - but instead of using the transactions, we use delete-pending file. This is also a part of this technique, and a step that cannot be skipped.
Process Overwriting is very different in this - here you don't have to drop the payload at all, you can load it directly from a buffer in memory of the injector.
The thing that was added just for the purpose of the demo, so that my loaders will not be misused as ready-made components by script-kiddies, is, for example, this:
https://github.com/hasherezade/process_overwriting/blob/49c3365ceb2de21efbd24c15fc86faf01dfb8a81/process_overwrite/main.cpp#L97
// load the payload:
BYTE* payloadBuf = peconv::load_pe_module(payloadPath, payloadSize, false, false);
if (payloadBuf == NULL) {
std::cerr << "Cannot read payload!" << std::endl;
return -1;
}
/*
// if the file is NOT dropped on the disk, you can load it directly from a memory buffer:
BYTE* payloadBuf = peconv::load_pe_module(buffer, bufsize, payloadSize, false, false);
*/
In a real-life loader, the buffer used will not be from the dropped file, but rather a resource within a PE, or any other hardcoded buffer.