Saturday, April 25, 2026

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. Source repo: github.com/lowlevel01/cpu_notes.

Thursday, April 2, 2026

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: 
  1. The name of the DLL where it resides.
  2. The name of the API function
  3. A pointer to the original function
  4. The "hook"


The "hook" is a function that calls the original one and logs telemetry. 
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.


This one is ReservedForOle field which it repurposed here as a counter.


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.


The problem lies in the string comparison function. It implements a sliding window comparison. Which allows XXXXkern3l32.dll to be also whitelisted.


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


And where is the database? It is in the root folder of the web server :D


We can download it and read it DB Browser for example. We now have access to many interesting things.


We can see the users and their passwords. It seems like someone is just trying to mess around.


These don't seems like public addresses. So definitely, it is not a serious campaign.



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.

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.



The prime number and the initial hash are decrypted from the stack at runtime.

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.



How is the config stored

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


An extra giveaway is the "expand 32-byte k" signature in the beginning of the key when used to decrypt the first C2 (set a breakpoint at the chacha20 function)




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.

Reproducing the decryption on cyberchef to make sure we're good



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


Usually, something like this is very interesting and worth exploring.

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.


So, a memory stream is decrypted, decompressed and then loaded.

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



Before interacting with the binary, we need to load it and this can be done via the command 


Afterwards, we retrieve the decryption method by its token as follows


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 call to the method was successful



Now, we need to call the decompression method on this result.

The decompression method is identified with the token 0x0600000D


We follow the same steps again.

Retrieving the method by its token.


This time we need to pass the decrypted stream as argument.


Let's see if the result is actually an executable



Seems so!

Anti-Analysis Techniques

Let's save the unpacked executable into a file and inspect it.

We save it to unpacked.exe with this command


Let's open it in Detect It Easy


It's a .NET DLL obfuscated with .NET Reactor.

Luckily there's a tool called NetReactorSlayer ( https://github.com/SychicBoy/NETReactorSlayer ) that deobfuscates such assemblies.


Let's open it in DnSpy. It's more readable now.

We can see anti-sandbox techniques


A BIOS check if the system is running in a virtualized environment



A check to see if the sample is run from cmd.exe to prevent running from a script



A check to see if the sample is running ina VMWare Virtual Machine


This is a very interesting check since it verifies the common virtual machine resolutions



Extracting the configuration

Skimming through the methods, we find this interesting one.



It's a base64 encoded string that is passed to a method.


The method deserializes it. Finally it is casted as (Class20) Object.

It's a Protobuf!




We can use an online website to deserialize the object.



We can clearly see the configuration!

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


The field that's passed to it is the 4th


So the mutex is 5bec48dd15fbca32

As for C2 communication is established with 



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.


Bytes are compressed with GZIP


And then encrypted with 3DES with key 5bec48dd15fbca32 (3rd element in the serialized object)


The sample also allow sending and execution of raw assemblies


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.


IOCs:

    - IP134.255.234.103:5888
     mutex5bec48dd15fbca32 

Conclusion

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/ . 

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...