homepage
Menu
Open menu
  • Training
    Go one level top Back

    Training

    • Courses

      Build cyber prowess with training from renowned experts

    • Hands-On Simulations

      Hands-on learning exercises keep you at the top of your cyber game

    • Certifications

      Demonstrate cybersecurity expertise with GIAC certifications

    • Ways to Train

      Multiple training options to best fit your schedule and preferred learning style

    • Training Events & Summits

      Expert-led training at locations around the world

    • Free Training Events

      Upcoming workshops, webinars and local events

    • Security Awareness

      Harden enterprise security with end-user and role-based training

    Featured

    Get a Free Hour of SANS Training

    Free Course Demos

    Can't find what you are looking for?

    Let us help.
    Contact us
  • Learning Paths
    Go one level top Back

    Learning Paths

    • By Focus Area

      Chart your path to job-specific training courses

    • By NICE Framework

      Navigate cybersecurity training through NICE framework roles

    • DoDD 8140 Work Roles

      US DoD 8140 Directive Frameworks

    • By European Skills Framework

      Align your enterprise cyber skills with ECSF profiles

    • By Skills Roadmap

      Find the right training path based on critical skills

    • New to Cyber

      Give your cybersecurity career the right foundation for success

    • Leadership

      Training designed to help security leaders reduce organizational risk

    • Degree and Certificate Programs

      Gain the skills, certifications, and confidence to launch or advance your cybersecurity career.

    Featured: Solutions for Emerging Risks

    New to Cyber resources

    Start your career
  • Community Resources
    Go one level top Back

    Community Resources

    Watch & Listen

    • Webinars
    • Live Streams
    • Podcasts

    Read

    • Blog
    • Newsletters
    • White Papers
    • Internet Storm Center

    Download

    • Open Source Tools
    • Posters & Cheat Sheets
    • Policy Templates
    • Summit Presentations
    • SANS Community Benefits

      Connect, learn, and share with other cybersecurity professionals

    • CISO Network

      Engage, challenge, and network with fellow CISOs in this exclusive community of security leaders

  • For Organizations
    Go one level top Back

    For Organizations

    Team Development

    • Why Partner with SANS
    • Group Purchasing
    • Skills & Talent Assessments
    • Private & Custom Training

    Leadership Development

    • Leadership Courses & Accreditation
    • Executive Cybersecurity Exercises
    • CISO Network

    Security Awareness

    • End-User Training
    • Phishing Simulation
    • Specialized Role-Based Training
    • Risk Assessments
    • Public Sector Partnerships

      Explore industry-specific programming and customized training solutions

    • Sponsorship Opportunities

      Sponsor a SANS event or research paper

    Interested in developing a training plan to fit your organization’s needs?

    We're here to help.
    Contact us
  • Talk with an expert
  • Log In
  • Join - it's free
  • Account
    • Account Dashboard
    • Log Out
  1. Home >
  2. Blog >
  3. The Analysis of User Data In VADs: Extraction of Precise Data in Notepad Memory And Hunting For Malware Behavior
Abdelrhman Mohamed
Abdelrhman Mohamed

The Analysis of User Data In VADs: Extraction of Precise Data in Notepad Memory And Hunting For Malware Behavior

Adversaries have exploited Notepad as part of their malicious strategies, even disguising activities by masquerading malicious processes as Notepad.

December 5, 2024

This blog was coauthored by Nithin Chenthur Prabhu and Abdelrhman Mohamed.

Notepad is a common tool for windows users to jot down important notes or messages quickly. Beyond text files, Notepad can also open files in various known and unknown formats. However, over the years, adversaries have exploited Notepad as part of their malicious strategies, sometimes even disguising their activities by masquerading malicious processes as Notepad.

Given this trend, it’s important to understand what resides in Notepad’s process memory region. Advanced Persistent Threats (APTs) like Cozy Bear, Double Dragon, and Lazarus have utilized Notepad for process injection, DLL sideloading, shellcode or payload injection, and process hollowing.

We will look into methods for extracting user data from Notepad’s process memory and analyzing it for signs of malicious behavior.

To being our tests, we’ll use a Windows 10 virtual machine as a test environment. We launched Notepad and typed: “Abdelrhman and Azr43LKn1ght”.

If necessary, we can close Notepad, as its process will remain in memory for a short time. The first step is to capture the workstation’s memory using tools like FTK Imager’s Memory Capture, DumpIt.exe, or Magnet RAM Capture.

If the memory dump is not in .dmp format, it must be converted for compatibility with WinDbg. Tools like Volatility and MemProcFS can assist with this conversion. Once the memory dump (e.g., <file>.dmp ) is ready, we can process to debug it using WinDbg to analyze the captured Notepad process for signs of malicious behavior.

Before diving into the analysis, let’s examine the structure of memory in Windows. The operating system uses the _EPROCESS structure to represent a process. Each process has its own linear address space, called the Virtual Memory Space or Virtual Address Space, which is completely isolated from other processes.

This address space includes several key components:

  • The core process executable: The main binary file of the process.
  • Loaded modules: A list of all associated Dynamic Link Libraries (DLLs) an shared libraries.
  • Process stack: Memory used for function calls and local variables.
  • Process heaps : Memory areas for dynamic memory allocation.
  • Virtual Address Descriptors (VADs): Regions of memory allocated by the process.

The structure of _EPROCESS is:

nt!_EPROCESS

   +0x000 Pcb              : _KPROCESS

   +0x438 ProcessLock      : _EX_PUSH_LOCK

   +0x440 UniqueProcessId  : Ptr64 Void

   +0x448 ActiveProcessLinks : _LIST_ENTRY

   +0x458 RundownProtect   : _EX_RUNDOWN_REF

   +0x460 Flags2           : Uint4B

   +0x460 JobNotReallyActive : Pos 0, 1 Bit

    [SNIP]

   +0x510 Job              : Ptr64 _EJOB

   +0x518 SectionObject    : Ptr64 Void

   +0x520 SectionBaseAddress : Ptr64 Void

   +0x528 Cookie           : Uint4B

   +0x530 WorkingSetWatch  : Ptr64 _PAGEFAULT_HISTORY

   +0x538 Win32WindowStation : Ptr64 Void

   +0x540 InheritedFromUniqueProcessId : Ptr64 Void

   +0x548 OwnerProcessId   : Uint8B

   +0x550 Peb              : Ptr64 _PEB

   +0x558 Session          : Ptr64 _MM_SESSION_SPACE

    [SNIP]

   +0x7d8 VadRoot          : _RTL_AVL_TREE

   +0x7e0 VadHint          : Ptr64 Void

   +0x7e8 VadCount         : Uint8B

   +0x7f0 VadPhysicalPages : Uint8B

   +0x7f8 VadPhysicalPagesLimit : Uint8B

   [SNIP]

   +0xa00 DynamicEHContinuationTargetsTree : _RTL_AVL_TREE

   +0xa08 DynamicEHContinuationTargetsLock : _EX_PUSH_LOCK

The Process Control Block (PCB), represented by the _KPROCESS structure, is located at the base of the _EPROCESS structure. The PCB contains essential fields like:

  • DirectoryTableBase: Used for address translation.
  • Total time spent in kernel mode and user mode: Tracks the process’s CPU’s usage.
  • Process state: Indicated whether the process is running, waiting, or suspended.

Another critical field in _EPROCESS is ActiveProcessLinks, a doubly linked list that links all active processes on the machine. This list is fundamental to system process management, enabling tools like Task Manager and system APIs to navigate, access, and manage running processes effectively.

This structure is essential for operating system process management, as it facilitates efficient operations like process information retrieval and scheduling.

In 2010, a notorious malware called Prolaco exploited this system. It performed Direct Kernel Object Manipulation from user mode without any kernel space driver or rootkit. Prolaco achieved this by:

  • Enabling SeDebugPrivilege using the ZwSystemDebugControl function.
  • Accessing the first _EPROCESS object from PsInitialSystemProcess in the NT kernel module.
  • Traversing the double linked list of _EPROCESS objects to locate its own malware process.
  • Unlinking its process from the ActiveProcessLinks list by overwriting the flink (forward link) of next process and the blink (backward link) of previous process.

The PsActiveProcessHead is a global kernel variable that holds the address of the first process in the list of active processes.

The Process Environment Block (PEB) is a pointer in kernel mode that references an address in user mode. It holds critical information about a process, including:

  • Pointers to the process's DLL lists.
  • The current working directory.
  • Command-line arguments.
  • Environment variables.
  • Details of heaps.
  • Handles for standard input/output streams.

VadRoot is the root node of the VAD tree. The VAD tree holds detailed information about all memory segments allocated to a process. This includes the original access permissions which are read, write, or execute and whether a file is mapped to the memory region.

To begin analyzing the Notepad process, we first nee to locate the _EPROCESS block associated with notepad.exe. This can be achieved using the !process command in WinDbg:

!process 0 0 notepad.exe

The !process extension provides information about the specified process or all processes, including the _EPROCESS block. Here’s a breakdown of the command argument:

  • First 0 argument specifies the process option, which in this case is set to 0, meaning "all process contexts."
  • Second 0 argument specifies the "flags" parameter. A flag value of 0 means "minimal output."

We now know the _EPROCESS block’s address of notepad.exe is ffff8701845ce340

.process /r /p ffff8701845ce340

This command sets the debugger’s context to the specific process, notepad.exe. The /r switch tells WinDbg to reload the user-mode symbols and modules for the specified process. The /p switch forces the debugger to synchronize its process state with the target, halting the process execution to provide an accurate view of its memory and current state.

Before looking at the VAD tree, let’s first look at the PEB.

dx -id 0,0,ffff8701845ce340 -r1 ((ntkrnlmp!_PEB *)0xd22fcf8000)

((ntkrnlmp!_PEB *)0xd22fcf8000)                 : 0xd22fcf8000 [Type: _PEB *]

    [SNIP]

    [+0x030] ProcessHeap      : 0x21f68490000 [Type: void *]

    [+0x038] FastPebLock      : 0x7fff556590e0 [Type: _RTL_CRITICAL_SECTION *]

    [SNIP]

    [+0x0c0] CriticalSectionTimeout : {-25920000000000} [Type: _LARGE_INTEGER]

    [+0x0c8] HeapSegmentReserve : 0x100000 [Type: unsigned __int64]

    [+0x0d0] HeapSegmentCommit : 0x2000 [Type: unsigned __int64]

    [+0x0d8] HeapDeCommitTotalFreeThreshold : 0x10000 [Type: unsigned __int64]

    [+0x0e0] HeapDeCommitFreeBlockThreshold : 0x1000 [Type: unsigned __int64]

    [+0x0e8] NumberOfHeaps    : 0x4 [Type: unsigned long]

    [+0x0ec] MaximumNumberOfHeaps : 0x10 [Type: unsigned long]

    [+0x0f0] ProcessHeaps     : 0x7fff55657d40 [Type: void * *]

    [+0x0f8] GdiSharedHandleTable : 0x21f68790000 [Type: void *]

    [SNIP]

From this, we understand that the process heap base is 0x21f68490000 and heap parameters are:

  • HeapSegmentReserve = 0x100000 (1MB)
  • HeapSegmentCommit = 0x2000 (8KB)
  • HeapDeCommitTotalFreeThreshold = 0x10000 (64KB)
  • HeapDeCommitFreeBlockThreshold = 0x1000 (4KB)
  • NumberOfHeaps = 0x4 (4 heaps)
  • ProcessHeaps = 0x7fff55657d40 (pointer to array of heap handles)

From this, we get the heap allocation offsets from ProcessHeaps address that points to the heap handles.

kd> db 0x7fff55657d40

00007fff`55657d40  00 00 49 68 1f 02 00 00-00 00 2a 68 1f 02 00 00  ..Ih......*h....

00007fff`55657d50  00 00 5b f8 1e 02 00 00-00 00 06 6a 1f 02 00 00  ...i.......j....

From this, we get the heap allocation offsets:

0000021f68490000, 0000021f682a0000

0000021ef85b0000, 0000021f6a060000

Now, let’s look at the VAD tree for all memory allocations for our process. This can be accessed through the VadRoot field, located at offset 0x7d8 from the _EPROCESS structure.

kd> dx -id 0,0,ffff8701845ce340 -r1 (*((ntdll!_RTL_AVL_TREE *)0xffff8701845ceb18))

(*((ntdll!_RTL_AVL_TREE *)0xffff8701845ceb18))                 [Type: _RTL_AVL_TREE]

    [+0x000] Root             : 0xffff8701844d00a0 [Type: _RTL_BALANCED_NODE *]

We can see that the root of the VAD tree points to the first node which is at address 0xffff8701844d00a0.

Let’s inspect the root node of the VAD tree.

kd>  dt nt!_RTL_BALANCED_NODE 0xffff8701844d00a0

   +0x000 Children         : [2] 0xffff8701`841c36c0 _RTL_BALANCED_NODE

   +0x000 Left                : 0xffff8701`841c36c0 _RTL_BALANCED_NODE

   +0x008 Right              : 0xffff8701`844d0280 _RTL_BALANCED_NODE

   +0x010 Red                : 0y1

   +0x010 Balance          : 0y01

   +0x010 ParentValue   : 1

This node has two child nodes, each accessible via pointers. One child node is located to the left of the current node, and the other is to the right, depending on their respective positions in memory. By traversing the tree through these child nodes, we can reach a node that represents a heap allocation. Here, we will check the VADFLAGS and the allocation offsets for more memory allocation details.

kd> dt nt!_MMVAD ffff8701841c38a0

   +0x000 Core             : _MMVAD_SHORT

   +0x040 u2               : <anonymous-tag>

   +0x048 Subsection       : (null)

   +0x050 FirstPrototypePte : 0x00000000`00000001 _MMPTE

   +0x058 LastContiguousPte : (null)

   +0x060 ViewLinks        : _LIST_ENTRY [ 0x00000400`00000000 - 0x00000000`00060001 ]

   +0x070 VadsProcess      : 0xffff8701`841c3910 _EPROCESS

   +0x078 u4               : <anonymous-tag>

   +0x080 FileObject       : (null)

Now, we can look at the node’s core to determine the offset of allocation. This will give us the starting and ending virtual address space offset.

kd> dt nt!_MMVAD_SHORT ffff8701841c38a0

   +0x000 NextVad          : 0xffff8701`840e74b0 _MMVAD_SHORT

   +0x008 ExtraCreateInfo  : 0xffff8701`841c59c0 Void

   +0x000 VadNode          : _RTL_BALANCED_NODE

   +0x018 StartingVpn      : 0x21f6a060

   +0x01c EndingVpn        : 0x21f6a06f

   +0x020 StartingVpnHigh  : 0 ''

   +0x021 EndingVpnHigh    : 0 ''

   +0x022 CommitChargeHigh : 0 ''

   +0x023 SpareNT64VadUChar : 0 ''

   +0x024 ReferenceCount   : 0n0

   +0x028 PushLock         : _EX_PUSH_LOCK

   +0x030 u                : <anonymous-tag>

   +0x034 u1               : <anonymous-tag>

   +0x038 EventList        : (null)

Now, let’s look into the VADFLAGS.

dx -id 0,0,ffff8701845ce340 -r1 (*((ntkrnlmp!_MMVAD_FLAGS *)0xffff8701841c38d0))

(*((ntkrnlmp!_MMVAD_FLAGS *)0xffff8701841c38d0))                 [Type: _MMVAD_FLAGS]

    [+0x000 ( 0: 0)] Lock             : 0x0 [Type: unsigned long]

    [+0x000 ( 1: 1)] LockContended    : 0x0 [Type: unsigned long]

    [+0x000 ( 2: 2)] DeleteInProgress : 0x0 [Type: unsigned long]

    [+0x000 ( 3: 3)] NoChange         : 0x0 [Type: unsigned long]

    [+0x000 ( 6: 4)] VadType          : 0x0 [Type: unsigned long]

    [+0x000 (11: 7)] Protection       : 0x4 [Type: unsigned long]

    [+0x000 (17:12)] PreferredNode    : 0x0 [Type: unsigned long]

    [+0x000 (19:18)] PageSize         : 0x0 [Type: unsigned long]

    [+0x000 (20:20)] PrivateMemory    : 0x1 [Type: unsigned long]

 Lock = 0: The VAD is currently not locked for any operations.

 LockContended = 0: No other threads are waiting to acquire a lock on this VAD.

 DeleteInProgress = 0: The VAD is not being deleted.

 NoChange = 0: The VAD can be modified.

 VadType = 0: This is VadNone type, typical for normal memory allocations like heaps.

 Protection = 4: This means PAGE_READWRITE protection - the memory can be read from and written to this heap.

 PreferredNode = 0: No specific non-uniform memory access (NUMA) node preference for this memory.

 PageSize = 0: Using standard system page size (typically 4KB).

 PrivateMemory = 1: This is private memory, not shared with other processes.

Let’s look into the starting and ending offset of the heap allocation. StartingVpn is 0x21f6a060 and the EndingVpn is 0x21f6a06f. Since the page size is 4096 bytes (or 0x1000), the resulting address range will be 0x21f6a060000 - 0x21f6a06f000.

Now, let’s look into HEAP structure of our heap address.

0: kd> dt _HEAP 0x21f6a060000

ntdll!_HEAP

   +0x000 Segment          : _HEAP_SEGMENT

   +0x000 Entry            : _HEAP_ENTRY

   +0x010 SegmentSignature : 0xffeeffee

   +0x014 SegmentFlags     : 2

   +0x018 SegmentListEntry : _LIST_ENTRY [ 0x0000021f`6a060120 - 0x0000021f`6a060120 ]

   +0x028 Heap             : 0x0000021f`6a060000 _HEAP

   +0x030 BaseAddress      : 0x0000021f`6a060000 Void

   +0x038 NumberOfPages    : 0xf

   +0x040 FirstEntry       : 0x0000021f`6a060740 _HEAP_ENTRY

   +0x048 LastValidEntry   : 0x0000021f`6a06f000 _HEAP_ENTRY

   +0x050 NumberOfUnCommittedPages : 5

   +0x054 NumberOfUnCommittedRanges : 1

   +0x058 SegmentAllocatorBackTraceIndex : 0

    [SNIP]  

   +0x070 Flags            : 0x1002

   +0x074 ForceFlags       : 0

   +0x078 CompatibilityFlags : 0

   +0x07c EncodeFlagMask   : 0x100000

   +0x080 Encoding         : _HEAP_ENTRY

   [SNIP]

    [+0x198] FrontEndHeap     : 0x0 [Type: void *]

    [+0x1a0] FrontHeapLockCount : 0x0 [Type: unsigned short]

    [+0x1a2] FrontEndHeapType : 0x0 [Type: unsigned char]

    [+0x1a3] RequestedFrontEndHeapType : 0x0 [Type: unsigned char]

   [SNIP]

The first entry in the heap is located at 0x21f6a060740, while the last valid entry is at 0x21f6a06f000, which is also the EndingVpn. Before that we will have to look into _HEAP_ENTRY of encoding of the first heap allocation.

dx -r1 (*((ntdll!_HEAP_ENTRY *)0x21f6a060080))

(*((ntdll!_HEAP_ENTRY *)0x21f6a060080))                 [Type: _HEAP_ENTRY]

    [+0x000] UnpackedEntry    [Type: _HEAP_UNPACKED_ENTRY]

    [+0x000] PreviousBlockPrivateData : 0x0 [Type: void *]

    [+0x008] Size             : 0xfc0b [Type: unsigned short]

    [+0x00a] Flags            : 0xdc [Type: unsigned char]

    [+0x00b] SmallTagIndex    : 0xbd [Type: unsigned char]

    [+0x008] SubSegmentCode   : 0xbddcfc0b [Type: unsigned long]

    [+0x00c] PreviousSize     : 0xb3d [Type: unsigned short]

    [+0x00e] SegmentOffset    : 0x0 [Type: unsigned char]

    [+0x00e] LFHFlags         : 0x0 [Type: unsigned char]

    [+0x00f] UnusedBytes      : 0x0 [Type: unsigned char]

    [+0x008] CompactHeader    : 0xb3dbddcfc0b [Type: unsigned __int64]

    [+0x000] ExtendedEntry    [Type: _HEAP_EXTENDED_ENTRY]

    [SNIP]

Let’s look into the first _HEAP_ENTRY.

((ntdll!_HEAP_ENTRY *)0x21f6a060740)  : 0x21f6a060740 [Type: _HEAP_ENTRY *]

    [+0x000] UnpackedEntry    [Type: _HEAP_UNPACKED_ENTRY]

    [+0x000] PreviousBlockPrivateData : 0x0 [Type: void *]

    [+0x008] Size             : 0xfc08 [Type: unsigned short]

    [+0x00a] Flags            : 0xdd [Type: unsigned char]

    [+0x00b] SmallTagIndex    : 0xbf [Type: unsigned char]

    [+0x008] SubSegmentCode   : 0xbfddfc08 [Type: unsigned long]

    [+0x00c] PreviousSize     : 0xb49 [Type: unsigned short]

    [+0x00e] SegmentOffset    : 0x0 [Type: unsigned char]

    [+0x00e] LFHFlags         : 0x0 [Type: unsigned char]

    [+0x00f] UnusedBytes      : 0x10 [Type: unsigned char]

    [+0x008] CompactHeader    : 0x10000b49bfddfc08 [Type: unsigned __int64]

    [+0x000] ExtendedEntry    [Type: _HEAP_EXTENDED_ENTRY]

    [+0x000] Reserved         : 0x0 [Type: void *]

    [+0x008] FunctionIndex    : 0xfc08 [Type: unsigned short]

    [+0x00a] ContextValue     : 0xbfdd [Type: unsigned short]

    [+0x008] InterceptorValue : 0xbfddfc08 [Type: unsigned long]

    [SNIP]

Here, we use the CompactHeader from the encoding to calculate the heap allocation size. To do this, we perform an “Exclusive OR” with the second QWORD of the heap entry, which is 10000b49`bfddfc08. The result is 0x0003 as the last word.

When multiplied by 0x10, this value gives us 0x30, which is the size of the entire heap allocation. Adding this size to the starting address gives us: 0x21f6a060770.

0: kd> dq 0x21f6a060770 L2

0000021f`6a060770  00000000`00000000 00000b3e`bfdcfc09

Now, using the same process, we can get 0x20, which will give us the next heap allocation, 0x21f6a060790.

Now we can list all heap memory allocations of our process with !heap -s -v -a.

The -s flag provides a summary of all the heaps, including details like total size, number of segments, number of blocks, and other statistics. This flag is useful for getting an overall picture of the heap usage in the process.

The -v flag is used for more detailed output to the command, such as specific heap flags, debugging information, and metadata about each heap. This level of detail can help in understanding the state and properties of each heap in-depth.

The -a flag is used to display all memory blocks allocated in the heap, including their sizes, addresses, and other properties. This is useful for finding patterns, such as unusual memory blocks or potential injected code.

Now we can look into the heap we analyzed.

0000021f6a060740  0000021f6a060750  0000021f6a060000  0000021f6a060000        30       740        10  busy

0000021f6a060770  0000021f6a060780  0000021f6a060000  0000021f6a060000        20        30         0  free

0000021f6a060790  0000021f6a0607a0  0000021f6a060000  0000021f6a060000        20        20        10  busy 

This will be same as the size and allocations we calculated.

We also have to parse all the subsegments. The offset of LFH Heap will be in the heap’s FrontEndHeap, which is 0x21f69dc0000.

dt _LFH_HEAP 0x21f69dc0000

ntdll!_LFH_HEAP

   +0x000 Lock             : _RTL_SRWLOCK

   +0x008 SubSegmentZones  : _LIST_ENTRY [ 0x0000021f`6a064070 - 0x0000021f`6a064070 ]

   +0x018 Heap             : 0x0000021f`6a060000 Void

   +0x020 NextSegmentInfoArrayAddress : 0x0000021f`69dc0f30 Void

   +0x028 FirstUncommittedAddress : 0x0000021f`69dc1000 Void

   +0x030 ReservedAddressLimit : 0x0000021f`69dc7000 Void

   +0x038 SegmentCreate    : 9

   +0x03c SegmentDelete    : 0

   +0x040 MinimumCacheDepth : 0

   +0x044 CacheShiftThreshold : 0

   +0x048 SizeInCache      : 0

   +0x050 RunInfo          : _HEAP_BUCKET_RUN_INFO

   +0x060 UserBlockCache   : [12] _USER_MEMORY_CACHE_ENTRY

   +0x2a0 MemoryPolicies   : _HEAP_LFH_MEM_POLICIES

   +0x2a4 Buckets          : [129] _HEAP_BUCKET

   +0x4a8 SegmentInfoArrays : [129] (null)

   +0x8b0 AffinitizedInfoArrays : [129] (null)

   +0xcb8 SegmentAllocator : (null)

   +0xcc0 LocalData        : [1] _HEAP_LOCAL_DATA

From here, we can go to the subsegment zone and look at the subsegment zone header.

dq 0x0000021f6a064070

0000021f`6a064070  0000021f`69dc0008 0000021f`69dc0008

0000021f`6a064080  00000000`00000009 00000000`00000000

Here, the fourth QWORD is 0x9, which tells us that there are a total of nine subsegments in this particular zone. Now, let’s look into the first subsegment that starts after the zone header, which is 0x20 bytes.

dt _HEAP_SUBSEGMENT 0x0000021f6a064090

ntdll!_HEAP_SUBSEGMENT

   +0x000 LocalInfo        : 0x0000021f`69dc0cf0 _HEAP_LOCAL_SEGMENT_INFO

   +0x008 UserBlocks       : 0x0000021f`6a063c60 _HEAP_USERDATA_HEADER

   +0x010 DelayFreeList    : _SLIST_HEADER

   +0x020 AggregateExchg   : _INTERLOCK_SEQ

   +0x024 BlockSize        : 3

   +0x026 Flags            : 0

   +0x028 BlockCount       : 0x13

   +0x02a SizeIndex        : 0x2 ''

   +0x02b AffinityIndex    : 0 ''

   +0x024 Alignment        : [2] 3

   +0x02c Lock             : 1

   +0x030 SFreeListEntry   : _SINGLE_LIST_ENTRY

Here, the UserBlocks start at 0x0000021f6a063c60, so let’s look at the header of the UserBlock.

dt _HEAP_USERDATA_HEADER 0x0000021f6a063c60

ntdll!_HEAP_USERDATA_HEADER

   +0x000 SFreeListEntry   : _SINGLE_LIST_ENTRY

   +0x000 SubSegment       : 0x0000021f`6a064090 _HEAP_SUBSEGMENT

   +0x008 Reserved         : 0x0000021f`6a060780 Void

   +0x010 SizeIndexAndPadding : 0xa

   +0x010 SizeIndex        : 0xa ''

   +0x011 GuardPagePresent : 0 ''

   +0x012 PaddingBytes     : 0

   +0x014 Signature        : 0xf0e0d0c0

   +0x018 EncodedOffsets   : _HEAP_USERDATA_OFFSETS

   +0x020 BusyBitmap       : _RTL_BITMAP_EX

   +0x030 BitmapData       : [1] 0xffffffff`ffffffff

The header is 0x40 bytes, so the first allocation will start at 0x0000021f6a063ca0. The total allocations will be 0x13 in count and 0x30 bytes in size, as mentioned in subsegment header.

We have all the allocations, but the heaps that need to be analyzed are the segment0x0 heaps, not the LFH and subsegments which will have user_flag.

Now, let’s look into a heap entry, its flags, and its significance.

HEAP_ENTRY_BUSY: Indicates if a memory block is currently in use (1) or free (0) - this is the fundamental allocation status flag.

HEAP_ENTRY_EXTRA_PRESENT: Signals that the heap block contains additional metadata beyond the standard header structure.

HEAP_ENTRY_FILL_PATTERN: Shows that the block is filled with a specific pattern, typically used for detecting memory corruption or buffer overflows.

HEAP_ENTRY_INTERNAL: Marks blocks that are reserved for the heap manager's internal use rather than application allocation.

HEAP_ENTRY_VIRTUAL_ALLOC: Identifies memory blocks that were allocated directly through VirtualAlloc instead of normal heap allocation.

HEAP_ENTRY_USER_FLAGS: This flag is used to indicate whether a memory block is marked for user-defined purposes, which can be helpful for debugging or tracking allocations in a more customized way. Reserved space for application-specific flags, allowing custom metadata to be associated with heap blocks.

HEAP_ENTRY_SETTABLE_FLAG1: First user-definable flag that can be used for custom memory management implementations.

HEAP_ENTRY_SETTABLE_FLAG2: Second user-definable flag available for custom memory tracking or debugging purposes.

Positions of bit

0 (0x1)   - HEAP_ENTRY_BUSY (1=allocated, 0=free)

1 (0x2)   - HEAP_ENTRY_EXTRA_PRESENT

2 (0x4)   - HEAP_ENTRY_FILL_PATTERN

3 (0x8)   - HEAP_ENTRY_INTERNAL

4 (0x10)  - HEAP_ENTRY_VIRTUAL_ALLOC

5 (0x20)  - HEAP_ENTRY_USER_FLAGS

6 (0x40)  - HEAP_ENTRY_SETTABLE_FLAG1

7 (0x80)  - HEAP_ENTRY_SETTABLE_FLAG2

We have many heaps for this process, but we need only those that have extra user_flag.

Now, let’s look at the heap contents. For this we can use db 0000021ef85b2ad0 L80.

Now, we have successfully extracted the desired information from memory! This method can be applied to any file opened in notepad.exe, making it straightforward to recover any file opened in notepad.exe from a memory image.

Now, let’s make a custom notepad-like application. This will provide better understanding of heap allocations.

#include <windows.h>

const wchar_t g_szClassName[] = L"myWindowClass";

HWND hEdit;

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)

{

    switch (msg)

    {

    case WM_CREATE:

        hEdit = CreateWindowEx(

            0, L"EDIT", L"",

            WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL |

            ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL,

            0, 0, 500, 500, hwnd, (HMENU)1, GetModuleHandle(NULL), NULL);

        break;

    case WM_SIZE:

        MoveWindow(hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);

        break;

    case WM_CLOSE:

        DestroyWindow(hwnd);

        break;

    case WM_DESTROY:

        PostQuitMessage(0);

        break;

    default:

        return DefWindowProc(hwnd, msg, wParam, lParam);

    }

    return 0;

}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

    LPSTR lpCmdLine, int nCmdShow)

{

    WNDCLASSEX wc;

    HWND hwnd;

    MSG Msg;

    wc.cbSize = sizeof(WNDCLASSEX);

    wc.style = 0;

    wc.lpfnWndProc = WndProc;

    wc.cbClsExtra = 0;

    wc.cbWndExtra = 0;

    wc.hInstance = hInstance;

    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

    wc.hCursor = LoadCursor(NULL, IDC_ARROW);

    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

    wc.lpszMenuName = NULL;

    wc.lpszClassName = g_szClassName;

    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

    if (!RegisterClassEx(&wc))

    {

        MessageBox(NULL, L"Window Registration Failed!", L"Error!",

            MB_ICONEXCLAMATION | MB_OK);

        return 0;

    }

    hwnd = CreateWindowEx(

        WS_EX_CLIENTEDGE,

        g_szClassName,

        L"Notepad Clone",

        WS_OVERLAPPEDWINDOW,

        CW_USEDEFAULT, CW_USEDEFAULT, 500, 500,

        NULL, NULL, hInstance, NULL);

    if (hwnd == NULL)

    {

        MessageBox(NULL, L"Window Creation Failed!", L"Error!",

            MB_ICONEXCLAMATION | MB_OK);

        return 0;

    }

    ShowWindow(hwnd, nCmdShow);

    UpdateWindow(hwnd);

    while (GetMessage(&Msg, NULL, 0, 0) > 0)

    {

        TranslateMessage(&Msg);

        DispatchMessage(&Msg);

    }

    return Msg.wParam;

}

Debugging the process and analyzing, we are able to get the heap allocation, which is:

0000026cb860edc0  0000026cb860edd0  0000026cb85f0000  0000026cb85f0000        60        80        20  busy extra user_flag

Let’s do that on real malware with the malware sample of brbbot.exe.

1 Find Process _EPROCESS (core).

2 Set Debugger Process context.

3. Now, let’s look into all the heap allocations.

As this is an actual malware process without any user defined heap allocations, we need to check all heaps associated with the process, not just the user_flag or extra heaps. The first heap address is 0000000001300000, and last heap address is 00000000016f5fd0. It’s better to view all the contents of the particular heap allocation. To do this, we can use the WinDbg command: db 0000000001300000  L 80000.

Further analysis on the malware’s heap allocations shows that it maintains a network connection with the threat actor’s server. During its execution in the system’s background, the malware sends encrypted data to the attacker’s server.

Takeaways and Applications

In this blog post, we explored heap memory management within Windows, focusing on the structure and functionality of _HEAP_ENTRY and the significance of its various flags in determining the state of memory allocations. We learned:

  • What is heap memory allocation in Windows and how it is managed.
  • The structure of process memory in Windows.
  • How to manually analyze heap allocation and subsegment allocations.
  • The process of decoding _HEAP_ENTRY and understanding its associated flags and their uses.
  • The role of heap protections and how to analyze user-defined and program-defined heap allocations.

The ability to manually parse heap allocations allows for deeper insights into application behavior. This understanding enables more informed decisions regarding reverse engineering and digital forensics and incident response (DFIR) analysis of malwares and other Windows applications.

As we continue to navigate the complexities of memory management, the insights gained from these structures will remain invaluable for enhancing Windows core analysis skills.

Explore more DFIR resources and stay ahead in the ever-evolving field. Join the SANS community today to stay connected to the latest insights and innovations.

Share:
TwitterLinkedInFacebook
Copy url Url was copied to clipboard
Subscribe to SANS Newsletters
Receive curated news, vulnerabilities, & security awareness tips
United States
Canada
United Kingdom
Spain
Belgium
Denmark
Norway
Netherlands
Australia
India
Japan
Singapore
Afghanistan
Aland Islands
Albania
Algeria
American Samoa
Andorra
Angola
Anguilla
Antarctica
Antigua and Barbuda
Argentina
Armenia
Aruba
Austria
Azerbaijan
Bahamas
Bahrain
Bangladesh
Barbados
Belarus
Belize
Benin
Bermuda
Bhutan
Bolivia
Bonaire, Sint Eustatius, and Saba
Bosnia And Herzegovina
Botswana
Bouvet Island
Brazil
British Indian Ocean Territory
Brunei Darussalam
Bulgaria
Burkina Faso
Burundi
Cambodia
Cameroon
Cape Verde
Cayman Islands
Central African Republic
Chad
Chile
China
Christmas Island
Cocos (Keeling) Islands
Colombia
Comoros
Cook Islands
Costa Rica
Cote D'ivoire
Croatia (Local Name: Hrvatska)
Curacao
Cyprus
Czech Republic
Democratic Republic of the Congo
Djibouti
Dominica
Dominican Republic
East Timor
Ecuador
Egypt
El Salvador
Equatorial Guinea
Eritrea
Estonia
Eswatini
Ethiopia
Falkland Islands (Malvinas)
Faroe Islands
Fiji
Finland
France
French Guiana
French Polynesia
French Southern Territories
Gabon
Gambia
Georgia
Germany
Ghana
Gibraltar
Greece
Greenland
Grenada
Guadeloupe
Guam
Guatemala
Guernsey
Guinea
Guinea-Bissau
Guyana
Haiti
Heard And McDonald Islands
Honduras
Hong Kong
Hungary
Iceland
Indonesia
Iraq
Ireland
Isle of Man
Israel
Italy
Jamaica
Jersey
Jordan
Kazakhstan
Kenya
Kiribati
Korea, Republic Of
Kosovo
Kuwait
Kyrgyzstan
Lao People's Democratic Republic
Latvia
Lebanon
Lesotho
Liberia
Liechtenstein
Lithuania
Luxembourg
Macau
Madagascar
Malawi
Malaysia
Maldives
Mali
Malta
Marshall Islands
Martinique
Mauritania
Mauritius
Mayotte
Mexico
Micronesia, Federated States Of
Moldova, Republic Of
Monaco
Mongolia
Montenegro
Montserrat
Morocco
Mozambique
Myanmar
Namibia
Nauru
Nepal
Netherlands Antilles
New Caledonia
New Zealand
Nicaragua
Niger
Nigeria
Niue
Norfolk Island
North Macedonia
Northern Mariana Islands
Oman
Pakistan
Palau
Palestine
Panama
Papua New Guinea
Paraguay
Peru
Philippines
Pitcairn
Poland
Portugal
Puerto Rico
Qatar
Reunion
Romania
Russian Federation
Rwanda
Saint Bartholemy
Saint Kitts And Nevis
Saint Lucia
Saint Martin
Saint Vincent And The Grenadines
Samoa
San Marino
Sao Tome And Principe
Saudi Arabia
Senegal
Serbia
Seychelles
Sierra Leone
Sint Maarten
Slovakia
Slovenia
Solomon Islands
South Africa
South Georgia and the South Sandwich Islands
South Sudan
Sri Lanka
St. Helena
St. Pierre And Miquelon
Suriname
Svalbard And Jan Mayen Islands
Sweden
Switzerland
Taiwan
Tajikistan
Tanzania, United Republic Of
Thailand
Togo
Tokelau
Tonga
Trinidad And Tobago
Tunisia
Turkey
Turkmenistan
Turks And Caicos Islands
Tuvalu
Uganda
Ukraine
United Arab Emirates
United States Minor Outlying Islands
Uruguay
Uzbekistan
Vanuatu
Vatican City State
Venezuela
Vietnam
Virgin Islands (British)
Virgin Islands (U.S.)
Wallis And Futuna Islands
Western Sahara
Yemen
Zambia
Zimbabwe

By providing this information, you agree to the processing of your personal data by SANS as described in our Privacy Policy.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Tags:
  • Digital Forensics, Incident Response & Threat Hunting

Related Content

Blog
DFIR - Blog - Running EZ Tools Natively on Linux_340 x 340.jpg
Digital Forensics, Incident Response & Threat Hunting
April 23, 2025
Running EZ Tools Natively on Linux: A Step-by-Step Guide
Developed by Eric Zimmerman, the EZ Tools suite is a collection of utilities written to assist with multiple aspects of forensic analysis.
Seth_Enoka_370x370.png
Seth Enoka
read more
Blog
DFIR - Blog - Are Ransomware Victims Paying Less_340 x 340.jpg
Digital Forensics, Incident Response & Threat Hunting
April 11, 2025
Are Ransomware Victims Paying Less? Insights from the Latest Stay Ahead of Ransomware Live Stream
In this month's reboot of the SANS Stay Ahead of Ransomware live stream, we dove into one of the most pressing questions in cyber extortion today.
Mari DeGrazia
Mari DeGrazia
read more
Blog
powershell_option_340x340.jpg
Cyber Defense, Digital Forensics, Incident Response & Threat Hunting, Cybersecurity and IT Essentials, Offensive Operations, Pen Testing, and Red Teaming
July 12, 2022
Month of PowerShell - Windows File Server Enumeration
In this Month of PowerShell article we look at several commands to interrogate Windows SMB servers as part of our incident response toolkit.
Josh Wright - Headshot - 370x370 2025.jpg
Joshua Wright
read more
  • Company
  • Mission
  • Instructors
  • About
  • FAQ
  • Press
  • Contact Us
  • Careers
  • Policies
  • Training Programs
  • Work Study
  • Academies & Scholarships
  • Public Sector Partnerships
  • Law Enforcement
  • SkillsFuture Singapore
  • Degree Programs
  • Get Involved
  • Join the Community
  • Become an Instructor
  • Become a Sponsor
  • Speak at a Summit
  • Join the CISO Network
  • Award Programs
  • Partner Portal
Subscribe to SANS Newsletters
Receive curated news, vulnerabilities, & security awareness tips
United States
Canada
United Kingdom
Spain
Belgium
Denmark
Norway
Netherlands
Australia
India
Japan
Singapore
Afghanistan
Aland Islands
Albania
Algeria
American Samoa
Andorra
Angola
Anguilla
Antarctica
Antigua and Barbuda
Argentina
Armenia
Aruba
Austria
Azerbaijan
Bahamas
Bahrain
Bangladesh
Barbados
Belarus
Belize
Benin
Bermuda
Bhutan
Bolivia
Bonaire, Sint Eustatius, and Saba
Bosnia And Herzegovina
Botswana
Bouvet Island
Brazil
British Indian Ocean Territory
Brunei Darussalam
Bulgaria
Burkina Faso
Burundi
Cambodia
Cameroon
Cape Verde
Cayman Islands
Central African Republic
Chad
Chile
China
Christmas Island
Cocos (Keeling) Islands
Colombia
Comoros
Cook Islands
Costa Rica
Cote D'ivoire
Croatia (Local Name: Hrvatska)
Curacao
Cyprus
Czech Republic
Democratic Republic of the Congo
Djibouti
Dominica
Dominican Republic
East Timor
Ecuador
Egypt
El Salvador
Equatorial Guinea
Eritrea
Estonia
Eswatini
Ethiopia
Falkland Islands (Malvinas)
Faroe Islands
Fiji
Finland
France
French Guiana
French Polynesia
French Southern Territories
Gabon
Gambia
Georgia
Germany
Ghana
Gibraltar
Greece
Greenland
Grenada
Guadeloupe
Guam
Guatemala
Guernsey
Guinea
Guinea-Bissau
Guyana
Haiti
Heard And McDonald Islands
Honduras
Hong Kong
Hungary
Iceland
Indonesia
Iraq
Ireland
Isle of Man
Israel
Italy
Jamaica
Jersey
Jordan
Kazakhstan
Kenya
Kiribati
Korea, Republic Of
Kosovo
Kuwait
Kyrgyzstan
Lao People's Democratic Republic
Latvia
Lebanon
Lesotho
Liberia
Liechtenstein
Lithuania
Luxembourg
Macau
Madagascar
Malawi
Malaysia
Maldives
Mali
Malta
Marshall Islands
Martinique
Mauritania
Mauritius
Mayotte
Mexico
Micronesia, Federated States Of
Moldova, Republic Of
Monaco
Mongolia
Montenegro
Montserrat
Morocco
Mozambique
Myanmar
Namibia
Nauru
Nepal
Netherlands Antilles
New Caledonia
New Zealand
Nicaragua
Niger
Nigeria
Niue
Norfolk Island
North Macedonia
Northern Mariana Islands
Oman
Pakistan
Palau
Palestine
Panama
Papua New Guinea
Paraguay
Peru
Philippines
Pitcairn
Poland
Portugal
Puerto Rico
Qatar
Reunion
Romania
Russian Federation
Rwanda
Saint Bartholemy
Saint Kitts And Nevis
Saint Lucia
Saint Martin
Saint Vincent And The Grenadines
Samoa
San Marino
Sao Tome And Principe
Saudi Arabia
Senegal
Serbia
Seychelles
Sierra Leone
Sint Maarten
Slovakia
Slovenia
Solomon Islands
South Africa
South Georgia and the South Sandwich Islands
South Sudan
Sri Lanka
St. Helena
St. Pierre And Miquelon
Suriname
Svalbard And Jan Mayen Islands
Sweden
Switzerland
Taiwan
Tajikistan
Tanzania, United Republic Of
Thailand
Togo
Tokelau
Tonga
Trinidad And Tobago
Tunisia
Turkey
Turkmenistan
Turks And Caicos Islands
Tuvalu
Uganda
Ukraine
United Arab Emirates
United States Minor Outlying Islands
Uruguay
Uzbekistan
Vanuatu
Vatican City State
Venezuela
Vietnam
Virgin Islands (British)
Virgin Islands (U.S.)
Wallis And Futuna Islands
Western Sahara
Yemen
Zambia
Zimbabwe

By providing this information, you agree to the processing of your personal data by SANS as described in our Privacy Policy.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
  • Privacy Policy
  • Terms and Conditions
  • Do Not Sell/Share My Personal Information
  • Contact
  • Careers
© 2025 The Escal Institute of Advanced Technologies, Inc. d/b/a SANS Institute. Our Terms and Conditions detail our trademark and copyright rights. Any unauthorized use is expressly prohibited.
  • Twitter
  • Facebook
  • Youtube
  • LinkedIn