Heap Based Anti-Debug Technique — RtlCreateHeap

Bilal Bakartepe
6 min readFeb 13, 2024

--

There are a lot of anti debug techniques. Some of them are basics and some of them are advanced. Now, I explain a little bit hard one: Heap Based anti-debug.

During my analysis of Lockbit ransomware I noticed that something was going strange. In debugging, I was getting a exception. When I don’t run it under the debugger, it works. However, when I run it under debugger, I get an exception.

Then I realize something. Whenever it allocates space with the RtlAllocateHeap function, I was having problems after a few steps.

After examining whether there is a problem with the parameters given to the RtlAllocateHeap function, I thought that the problem might be in the Heap Handle structure.

RtlCreateHeap

When the above programme fragment is examined carefully, the heap handle structure obtained with RtlCreateHeap is broken after a comparison. This is not a normal situation. Because the handle is required to use the RtlAllocateHeap function afterwards.

Let’s analyse the comparison.

The address 031f0000 is the starting address of the heap handle structure. 031f0000 + 64 (0x40) has the address 031f0040. Since the value here is pulled into the register using little indian, there is 0x40041062 value in the register. Then it is subjected to shift right 28 (0x1c) operation and the current value is 0x04. The next operation compares this data in the register with 0x04. The heap structure returned after this comparison is put into role operation with 1.

Although it was difficult to understand at first, I came to the conclusion that it was pointless to corrupt the value of a handle structure. But what was this 0x04? How could the programme know this value?

First, let’s try to understand the event by reading the documentation on Microsoft’s site.

In the documentation, it was stated that the return value was a handle, so it was getting confusing. It really didn’t make sense that it wasn’t a struct. No matter how much I searched, I couldn’t come across any examples. I couldn’t be wrong, it was working just fine. Until I came across an article by a Japanese friend. He also experienced this situation and made some notes. I did more research using these notes and decided to write this article.

After a long research, I learnt that the value returned by the RtlCreateHeap function is not a meaningless handle. It was _HEAP struct structure. You can find the related documentation here.

This struct is as follows:

typedef struct _HEAP
{
HEAP_ENTRY Entry;
ULONG SegmentSignature;
ULONG SegmentFlags;
LIST_ENTRY SegmentListEntry;
PHEAP Heap;
PVOID BaseAddress;
ULONG NumberOfPages;
PHEAP_ENTRY FirstEntry;
PHEAP_ENTRY LastValidEntry;
ULONG NumberOfUnCommittedPages;
ULONG NumberOfUnCommittedRanges;
WORD SegmentAllocatorBackTraceIndex;
WORD Reserved;
LIST_ENTRY UCRSegmentList;
ULONG Flags;
ULONG ForceFlags;
ULONG CompatibilityFlags;
ULONG EncodeFlagMask;
HEAP_ENTRY Encoding;
ULONG PointerKey;
ULONG Interceptor;
ULONG VirtualMemoryThreshold;
ULONG Signature;
ULONG SegmentReserve;
ULONG SegmentCommit;
ULONG DeCommitFreeBlockThreshold;
ULONG DeCommitTotalFreeThreshold;
ULONG TotalFreeSize;
ULONG MaximumAllocationSize;
WORD ProcessHeapsListIndex;
WORD HeaderValidateLength;
PVOID HeaderValidateCopy;
WORD NextAvailableTagIndex;
WORD MaximumTagIndex;
PHEAP_TAG_ENTRY TagEntries;
LIST_ENTRY UCRList;
ULONG AlignRound;
ULONG AlignMask;
LIST_ENTRY VirtualAllocdBlocks;
LIST_ENTRY SegmentList;
WORD AllocatorBackTraceIndex;
ULONG NonDedicatedListLength;
PVOID BlocksIndex;
PVOID UCRIndex;
PHEAP_PSEUDO_TAG_ENTRY PseudoTagEntries;
LIST_ENTRY FreeLists;
PHEAP_LOCK LockVariable;
LONG * CommitRoutine;
PVOID FrontEndHeap;
WORD FrontHeapLockCount;
UCHAR FrontEndHeapType;
HEAP_COUNTERS Counters;
HEAP_TUNING_PARAMETERS TuningParameters;
} HEAP, *PHEAP;

Let’s do it together. Let’s find which struct variable corresponds to the 32 bits 64 bytes after the address we obtained with RtlCreateHeap. Then everything will start to come to light.

HEAP_ENTRY Entry: 8 bytes (2 32-bit pointers).
ULONG SegmentSignature: 4 bytes (32-bit integer).
ULONG SegmentFlags: 4 bytes (32-bit integer).
LIST_ENTRY SegmentListEntry: 8 bytes (2 32-bit pointers).
PHEAP Heap: 4 bytes (32-bit pointer).
PVOID BaseAddress: 4 bytes (32-bit pointer).
ULONG NumberOfPages: 4 bytes (32-bit integer).
PHEAP_ENTRY FirstEntry: 4 bytes (32-bit pointer).
PHEAP_ENTRY LastValidEntry: 4 bytes (32-bit pointer).
ULONG NumberOfUnCommittedPages: 4 bytes (32-bit integer).
ULONG NumberOfUnCommittedRanges: 4 bytes (32-bit integer).
WORD SegmentAllocatorBackTraceIndex: 2 bytes (16-bit integer).
WORD Reserved: 2 bytes (16-bit integer).
LIST_ENTRY UCRSegmentList: 8 bytes (2 32-bit pointers).
ULONG Flags: 4 bytes (32-bit integer).
ULONG ForceFlags: 4 bytes (32-bit integer).
ULONG CompatibilityFlags: 4 bytes (32-bit integer).
ULONG EncodeFlagMask: 4 bytes (32-bit integer).
HEAP_ENTRY Encoding: 8 bytes (2 32-bit pointers).
ULONG PointerKey: 4 bytes (32-bit integer).
ULONG Interceptor: 4 bytes (32-bit integer).
ULONG VirtualMemoryThreshold: 4 bytes (32-bit integer).
ULONG Signature: 4 bytes (32-bit integer).
ULONG SegmentReserve: 4 bytes (32-bit integer).
ULONG SegmentCommit: 4 bytes (32-bit integer).
ULONG DeCommitFreeBlockThreshold: 4 bytes (32-bit integer).
ULONG DeCommitTotalFreeThreshold: 4 bytes (32-bit integer).
ULONG TotalFreeSize: 4 bytes (32-bit integer).
ULONG MaximumAllocationSize: 4 bytes (32-bit integer).
WORD ProcessHeapsListIndex: 2 bytes (16-bit integer).
WORD HeaderValidateLength: 2 bytes (16-bit integer).
PVOID HeaderValidateCopy: 4 bytes (32-bit pointer).
WORD NextAvailableTagIndex: 2 bytes (16-bit integer).
WORD MaximumTagIndex: 2 bytes (16-bit integer).
PHEAP_TAG_ENTRY TagEntries: 4 bytes (32-bit pointer).
LIST_ENTRY UCRList: 8 bytes (2 32-bit pointers).
ULONG AlignRound: 4 bytes (32-bit integer).
ULONG AlignMask: 4 bytes (32-bit integer).
LIST_ENTRY VirtualAllocdBlocks: 8 bytes (2 32-bit pointers).
LIST_ENTRY SegmentList: 8 bytes (2 32-bit pointers).
WORD AllocatorBackTraceIndex: 2 bytes (16-bit integer).
ULONG NonDedicatedListLength: 4 bytes (32-bit integer).
PVOID BlocksIndex: 4 bytes (32-bit pointer).
PVOID UCRIndex: 4 bytes (32-bit pointer).
PHEAP_PSEUDO_TAG_ENTRY PseudoTagEntries: 4 bytes (32-bit pointer).
LIST_ENTRY FreeLists: 8 bytes (2 32-bit pointers).
PHEAP_LOCK LockVariable: 4 bytes (32-bit pointer).
LONG * CommitRoutine: 4 bytes (32-bit pointer).
PVOID FrontEndHeap: 4 bytes (32-bit pointer).
WORD FrontHeapLockCount: 2 bytes (16-bit integer).
UCHAR FrontEndHeapType: 1 byte (8-bit integer).

When we evaluate the situation, we see that anti-debug is tried to be done with a heap flag control. So what is in this flag? What do they represent?

  1. HEAP_GROWABLE: This flag indicates that a heap region is dynamically expandable. This means that the size of the heap region can be automatically increased, so that the heap region is automatically expanded when more memory is requested.
  2. HEAP_TAIL_CHECKING_ENABLED:This flag enables an overflow check at the end of the heap region. This check checks for overflow at the end of a block and helps to detect such cases.
  3. HEAP_FREE_CHECKING_ENABLED:This flag enables an overwrite check on freed memory blocks. This check detects accidental data overwriting of freed memory blocks.
  4. HEAP_VALIDATE_PARAMETERS_ENABLED:This flag enables checking the correctness of parameters passed to heap functions. This helps to prevent calls to heap functions with invalid parameters.

Well, if you are asking how these flags can be analysed to see if you are in debug mode or not, let me explain.

Normally when a process is running, i.e. not under debugger, all flags except HEAP_GROWABLE are disabled by default. However, all of these flags are set by default when the process is running under the debugger. The malware authors aim to make it difficult to analyse the program by examining these flags.

Now let’s test this. After compiling the code in the link, let’s run it both in debug mode and normally.

Normal mode

When we run the same programme under debugger, the output is as follows:

Debug mode

In summary, as best as I can, I tried to explain to you how to use an anti debug technique using the RtlCreateHeap Windows function. Although it is not a very difficult technique, it can be complicated for beginner analysts. I hope I was able to explain it clearly.

Thanks for reading :)

Github

Instagram

Twitter

--

--

Bilal Bakartepe

I am a Malware Analyst and Reverse Engineering Researcher. I insterested in Operating System and Low Level Things.