My notes on x86-64 CPU internals: execution modes, segmentation, interrupts, system calls, paging, debugging registers, and port I/O. Source repo: github.com/lowlevel01/cpu_notes.
rootkall
personal blog
Saturday, April 25, 2026
Thursday, April 2, 2026
Inside MacSync: The Stealer Silently Backdooring Ledger Wallets
This was a collaboration article with Abdelghafour Bouhdyd ( @1nt3l_hunt ) about a MacSync Stealer compaign. It can be found on his blog:
*image for the preview
Tuesday, March 31, 2026
Quirks of an EDR: Usermode
Introduction
EDR vendors solve the same problems, they just do it differently. This causes logical pitfalls to occur. This is a work in progress article about an EDR. A formal representation of my notes. A static ( not dynamic) analysis of different parts of an EDR. I'll highlight some remarks on the usermode components. Kernel mode part might come later. I'll try to be concise and on point. Of course, everything I mention here needs dynamic analysis to verify.
Hook Installation
The EDR installs hooks on a set of API functions in ntdll.dll to log telemetry into ETW by injecting a dll into all processes. Each function meant to be hooked is represented by a 4-part structure whose elements are in order:
- The name of the DLL where it resides.
- The name of the API function
- A pointer to the original function
- The "hook"
The next figure is an example:
After calling the original API function by the pointer in that qword_xxxxxxxx, there is a condition that involves the variable v14.
To avoid logging unnecessary telemetry through recursive hooks, this EDR makes use of repurposing a field in the Thread Environment Block because it's thread safe. It increments it, reads it into v14 then decrements it. Later in the checks it verifies if it was incremented more than once.
The used field is at offset 0x1758 from the TEB.
The next diagram illustrates when telemetry is logged (inc/decrementing that ReservedForOld is excluded from the diagram)
The thing is, every thread has access to its own Thread Environment Block Structure. So, it can write a big value in the ReservedForOle field and trick the EDR into thinking it's always inside a hook and thus suppress Telemetry.
Decoy DLLs
While looking at another DLL of the EDR. I came accross this table.
At first, I thought it was a table of suspicious DLL's to be flagged. But it also included a dll belonging to the EDR. Searching online, I found out it does this thing of loading some decoy DLL's.
It also employs LdrEnumerateLoadedModule API function with a callback that compares loaded DLL names to names in this table and skips monitoring them.
References
https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/teb/index.htm
Sunday, June 8, 2025
Another example of an Opsec failure in malware C2 Panels
Introduction
This is not going to be a long article. I wrote some article before about blunders in malware infrastractures. Some threat actors make some bad mistakes and that could cost them their infrastructures or logs in case of stealers.
- This article shows an example of a simple bug that could cost the threat actors their entire infrastructure https://www.rootkall.com/2025/03/hacking-website-used-to-deliver-malware.html
- This one is about a Stealer called Gremlin, the logs were accessible via an "Index Of" page :p https://www.rootkall.com/2025/04/gremlin-stealer-strings-decryption.html
I wasn't really sure if I should write about this case but sharing knowledge is always good.
As you can see in the screenshot, someone shared a C2 panel, and I love these in the sense that I try to compromise them. I pointed out in an answer to the tweet the blunder error that is made.
The C2 Panel is Open source:
A quick search of "C2WebServer" in a search engine would yield you the github repository of the malware as well as the Web Panel.
Taking a look at the "login.php" code shows something very weird. Passwords are not hashed in the database :p
We can download it and read it DB Browser for example. We now have access to many interesting things.
Conclusion
Even though, this doesn't seem like a serious campaign. It does highlight the almost certain fact that every C2 Panel has a vulnerability and sometimes these are not really that complicated. I was not sure about writing this article but it seems like a good thing to share with people.
Wednesday, April 23, 2025
Gremlin Stealer: Strings decryption + Where data is uploaded
Introduction
This is a quick static analysis blog post.
Tuesday, April 15, 2025
Quick LummaC2 discussion (fnv1a instead of murmur2 !?)
Introduction
This is by no means a thorough analysis. It's merely a quick discussion and highlight of my findings while working on a new LummaC2 version. I managed to write a config extractor which can be found here : https://github.com/lowlevel01/config-extractors/blob/main/lumma.py
Sample hash : sha256:820a1d5a52a6afbf36fe4c00e4d65716d3f796b53eab4c2bcd93a193e95a376d
TLDR;
- A new Lumma version that uses fnv1a hashing algorithm instead of MurMur2 for Dynamic API Resolving
- Hashing algorithm not identified by the Hash-db plugin
- ChaCha20 encrypted config. In this case, the 4 nullbytes mentioned by eSentire's blogpost are not appended to the nonce which is kept 8 bytes long
The config extractor:
new API Hashing algorithm
This new version uses FNV1A hashing algorithm instead of MurMur2 for Dynamic API Resolving.
Here is a re-implementation in python:
A friend of mine with good crypto knowledge, pointed out that we can extract the initial hash if we have a data and its corresponding hash. This can be easily seen since the xor is inversible and also the multiplication since the bianry AND is the same as modulo. The prime number and the modulo factor are co-prime therefore there exists an inverse.
I should note that this algorithm is not identified by OALabs's IDA Pro hash-db plugin. So, one needs to rebuild the API hash list from scratch. Write some emulation code to decrypt the prime number and initial hash and some IDAPython code replace every hash with its corresponding function.
This version uses ChaCha20 to decrypt the C2 servers which is not news.
The ChaCha20 algorithm can be identified by the left rotations by 16, 12, 8, 7 in this order
I used a debugger to locate where the signature was first used (or precisely decrypted).
The signature was decrypted from the stack by an algorithm that takes each character, XORs it with a byte (0x3C in this case) and adds a byte to it(0x88 in this case) then use the result as the XOR key for the next byte and so on and so forth. The signature is then moved from its location to right before where the key is located.
But we only care about the key and the nonce.
Went back a little bit in the disassembly and noticed some global references being moved in the stack. Suspected it was the key and nonce. Lengths confirmed it as I was expecting a 32 byte key and an 8 byte nonce.
The encrypted C2's, key and nonce are in the PE file with no extra treatment meaning you find a way to locate the key and nonce and dercrypt the C2's. I used a code signature to identify the beginning of the key (32 bytes), the nonce (8 bytes) is right after it and the C2's are right after the nonce.
Each C2 is in a block of 128 bytes.
I should note that, in this version, no null bytes are appended to the nonce as opposed to what was mentioned in eSentire's blogpost, so this is a new version.
Conclusion,
Again, this is by no means a full analysis. I just wanted to share these findings as they weren't mentioned online. This version same as the others uses control flow obfuscation heavily, Google published some research on this but it's theoretical. Who knows, maybe if I have time I'll tackle the control flow obfuscation part.
Monday, March 31, 2025
Unpacking and Analyzing Purelog Stealer (ft. a quick trick using Powershell)
Introduction
In this blog post, I'll be showcasing via example a useful trick to invoke .NET methods from Powershell and skipping all the anti-analysis routines. The same technique could be used for string decryption and other things. We'll be unpacking a sample which I suspect, based on the loading steps, to be Purelog Stealer (This is an advanced stage. I won't bother you with how I got to this stage tldr; boring stuff, AutoIt.) using Powershell and then presenting an analysis of how the sample works and what I think it does.
Hash of this stage sha256:46ddbdbe28dbdfb95cefa95b3597b989a50cd415fb978fe7fb14d2b8e3b5dee8
How is the payload stored?
Skimming through the method, we find this memory stream
Tracing this method. We find the decrypted stream used in this method.
Something is loaded, so that's a giveaway that we're dealing with an unpacking routine. Another method is called before the loading.
You could set a breakpoint and retrieve the values of the memory stream and do the decryption manually.
That's not only boring, but there could also be anti-analysis techniques that'll prevent you from reaching this part of the program.
You could write the decompiled code to a file and retrieve the stream from there then implement the decryption and decompressions routines. But, there's a better and faster solution.
Invoking the methods from Powershell
Powershell is build on top of the .NET framework which allows smooth interaction with .NET binaries.
Instead of a pointer, methods (pretty much everything) in .NET is referenced by tokens. Method tokens start with "0x06...". This is how we're going to find our method.
For example, the method that decrypts the memory stream is identified with the token 0x06000008
We then proceed to Invoke the method and save the return value which is the decrypted stream in a variable.
N.B: You could also attach DnSpy's debugger to the powershell process and set a breakpoint at whichever instruction you want inside the method.
The reason we pass $null as first argument is because the method is static so we don't need to pass a class instance to it.
The decompression method is identified with the token 0x0600000D
Retrieving the method by its token.
Anti-Analysis Techniques
Let's save the unpacked executable into a file and inspect it.
We save it to unpacked.exe with this command
Luckily there's a tool called NetReactorSlayer ( https://github.com/SychicBoy/NETReactorSlayer ) that deobfuscates such assemblies.
We can see anti-sandbox techniques
A check to see if the sample is running ina VMWare Virtual Machine
Extracting the configuration
Skimming through the methods, we find this interesting one.
It's a Protobuf!
We can use an online website to deserialize the object.
I used this one https://protobuf-decoder.netlify.app/
One small issue is which one is the mutex? (lmao)
Class20 looks like this
Instea of keeping the member names like this. I'll rename them depending on the order so we can see which one is which in the deserialized object since we don't have names.
We found the method that initialized the mutex
It is either establishing a connection to the first element in the object which is the IP address 134.255.234.103 with port 5888
OR, via a SOCKS5 Proxy via port 9050 Which is associated with TOR.
Another location where the a field in the serialized object is used which is to send bytes to the socket.
These methods (from right to left) decrypt from 3DES with the same key, decompress from Gzip, deserializes, load and execute the first method from the result.
So far, we didn't encounter any information stealing routines. Since, it receives and executes .NET assemblies then it is most likely a loader. The reaons I believe this is a PureLog stealer sample, is that the stages follow the same patterns here https://any.run/cybersecurity-blog/pure-malware-family-analysis/ .
Subscribe to:
Posts (Atom)
Intel x86-64 CPU Internals
My notes on x86-64 CPU internals: execution modes, segmentation, interrupts, system calls, paging, debugging registers, and port I/O. Sour...
-
Introduction In this blogpost, I'll be going through PE Reverse Engineering challenges on root-me.org
-
Introduction This is not going to be a long article. I wrote some article before about blunders in malware infrastractures. Some threat act...
-
Introduction In this blog post, I'll be showcasing via example a useful trick to invoke .NET methods from Powershell and skipping all t...