aidenpearce369
Published on

Windows x64 - PEB, TEB and EAT

Prologue

In the last blog we saw about the PE structure and its components. In order to master the art of dynamic shellcoding, we still have to understand few more concepts like PEB, TEB, Export Directory and Export Address Table. In simpler terms, PEB and TEB provide process-specific base addresses, while EAT enables dynamic function resolution, facilitating dynamic memory addressing in Windows programs. In this blog we will discuss about these in detail.

Process Environment Block (PEB)

Process Environment Block (PEB) is a data structure that is being used in Windows processes to maintain information across the whole process under user-mode memory. This block stores crucial information like process name and its ID, whether the process is being debugged or not, base address of the image, modules loaded into the memory, the command line used to invoke the process etc. The PEB is created by kernel but handles only user land data of the process in a fixed memory address througout the process.

The structure of PEB from winternl.h is given below,

typedef struct _PEB {
  BYTE                          Reserved1[2];
  BYTE                          BeingDebugged;
  BYTE                          Reserved2[1];
  PVOID                         Reserved3[2];
  PPEB_LDR_DATA                 Ldr;
  PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;
  PVOID                         Reserved4[3];
  PVOID                         AtlThunkSListPtr;
  PVOID                         Reserved5;
  ULONG                         Reserved6;
  PVOID                         Reserved7;
  ULONG                         Reserved8;
  ULONG                         AtlThunkSListPtr32;
  PVOID                         Reserved9[45];
  BYTE                          Reserved10[96];
  PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
  BYTE                          Reserved11[128];
  PVOID                         Reserved12[1];
  ULONG                         SessionId;
} PEB, *PPEB;

For more on PEB, visit the official documentation

There are three important fields to note: BeingDebugged, Ldr and ProcessParameters.

1. BeingDebugged: Indicates whether the specified process is currently being debugged.

2. ProcessParameters: A pointer to an RTL_USER_PROCESS_PARAMETERS structure that contains process parameter information such as the command line.

typedef struct _RTL_USER_PROCESS_PARAMETERS {
  BYTE           Reserved1[16];
  PVOID          Reserved2[10];
  UNICODE_STRING ImagePathName;
  UNICODE_STRING CommandLine;
} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;

ImagePathName consists the path of the image file of the process and CommandLine has the command line passed to execute the process.

3. Ldr: A pointer to a PPEB_LDR_DATA structure that contains information about the loaded modules for the process. This is a important structure from PEB, which can be used for many offensive purposes and evasion.

The structure of Ldr is,

typedef struct _PEB_LDR_DATA {
  BYTE       Reserved1[8];
  PVOID      Reserved2[3];
  LIST_ENTRY InMemoryOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;

The InMemoryOrderModuleList is the head of a doubly-linked list that contains the loaded modules for the process. Each item in the list is a pointer to an LDR_DATA_TABLE_ENTRY structure. By default the first link has the data of the parent process, following it there will be ntdll.dll and next to it kernel32.dll and next to it will be the kernelbase.dll.

The structure of the InMemoryOrderModuleList linked lists is,

typedef struct _LIST_ENTRY {
   struct _LIST_ENTRY *Flink;
   struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

Since it is a linked list, each entry (ie. module information) is linked with each other in a loop. The address of each linked list has a pointer to the data structure of LDR_DATA_TABLE_ENTRY.

typedef struct _LDR_DATA_TABLE_ENTRY {
    PVOID Reserved1[2];
    LIST_ENTRY InMemoryOrderLinks;
    PVOID Reserved2[2];
    PVOID DllBase;
    PVOID EntryPoint;
    PVOID Reserved3;
    UNICODE_STRING FullDllName;
    BYTE Reserved4[8];
    PVOID Reserved5[3];
    union {
        ULONG CheckSum;
        PVOID Reserved6;
    };
    ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

The DllBase will be the memory address of the PE module loaded into the process. For the art of creating dynamic shellcode, we will be requiring the base address of kernel32.dll from the PEB to extract the required function address from its Export Directory.

Thread Environment Block (TEB)

In certain instances, a process may encompass multiple threads, initially commencing with a primary thread and spawning additional threads as needed, all sharing the same virtual addresses. Each thread possesses its unique resources, encompassing exception handlers, local storage, and more. Consequently, similar to the PEB, each thread maintains its TEB within the process address space.

The structure of TEB from winternl.h is given below,

typedef struct _TEB {
  PVOID Reserved1[12];
  PPEB  ProcessEnvironmentBlock;
  PVOID Reserved2[399];
  BYTE  Reserved3[1952];
  PVOID TlsSlots[64];
  BYTE  Reserved4[8];
  PVOID Reserved5[26];
  PVOID ReservedForOle;
  PVOID Reserved6[4];
  PVOID TlsExpansionSlots;
} TEB, *PTEB;

The TEB inherits the required information for the threads from the parent process using ProcessEnvironmentBlock. TlsSlots represents the initial slots for Thread Local Storage in the Thread Environment Block, allowing threads to store thread-specific data. TlsExpansionSlots is a mechanism for dynamically expanding the available TLS slots if the initial allocation is insufficient, ensuring flexibility for programs with a high number of threads or extensive TLS requirements.

Debugging TEB and PEB

Each process can have single or many number of threads depending the image logic. Now, when we tried to access the PEB, we cannot reach it directly, because it is not directly referenced within registers. We have to go through from TEB to access PEB due to its inheriting nature. And in Windows x64 environment TEB can be accessed via gs register of the thread. In x86 bit environment, the TEB will be available in fs register.

We will use the same program which we used for our last blog,

ProgramOnDebugger

Now lets enumerate our TEB in WinDbg using !teb to get its address,

TEB-1

0:000> !teb
TEB at 0000006acb524000
    ExceptionList:        0000000000000000
    StackBase:            0000006acb700000
    StackLimit:           0000006acb6fd000
    SubSystemTib:         0000000000000000
    FiberData:            0000000000001e00
    ArbitraryUserPointer: 0000000000000000
    Self:                 0000006acb524000
    EnvironmentPointer:   0000000000000000
    ClientId:             00000000000037e8 . 0000000000005490
    RpcHandle:            0000000000000000
    Tls Storage:          00000171af364720
    PEB Address:          0000006acb523000
    LastErrorValue:       0
    LastStatusValue:      0
    Count Owned Locks:    0
    HardErrorMode:        0

Now we have got the address of TEB as 0x0000006acb524000

Lets view the TEB in its data structure using dt 0x0000006acb524000 ntdll!_TEB,

0:000> dt 0x0000006acb524000 ntdll!_TEB
   +0x000 NtTib            : _NT_TIB
   +0x038 EnvironmentPointer : (null) 
   +0x040 ClientId         : _CLIENT_ID
   +0x050 ActiveRpcHandle  : (null) 
   +0x058 ThreadLocalStoragePointer : 0x00000171`af364720 Void
   +0x060 ProcessEnvironmentBlock : 0x0000006a`cb523000 _PEB
   +0x068 LastErrorValue   : 0
   +0x06c CountOfOwnedCriticalSections : 0
   +0x070 CsrClientThread  : (null) 
   +0x078 Win32ThreadInfo  : (null) 
   +0x080 User32Reserved   : [26] 0
   +0x0e8 UserReserved     : [5] 0
   +0x100 WOW32Reserved    : (null) 
   +0x108 CurrentLocale    : 0x4009
   +0x10c FpSoftwareStatusRegister : 0
   +0x110 ReservedForDebuggerInstrumentation : [16] (null) 
   +0x190 SystemReserved1  : [30] (null) 
   +0x280 PlaceholderCompatibilityMode : 0 ''
   +0x281 PlaceholderHydrationAlwaysExplicit : 0 ''
   +0x282 PlaceholderReserved : [10]  ""
   +0x28c ProxiedProcessId : 0
   +0x290 _ActivationStack : _ACTIVATION_CONTEXT_STACK
   +0x2b8 WorkingOnBehalfTicket : [8]  ""
   +0x2c0 ExceptionCode    : 0n0
   +0x2c4 Padding0         : [4]  ""
   +0x2c8 ActivationContextStackPointer : 0x0000006a`cb524290 _ACTIVATION_CONTEXT_STACK
   +0x2d0 InstrumentationCallbackSp : 0
   +0x2d8 InstrumentationCallbackPreviousPc : 0
   +0x2e0 InstrumentationCallbackPreviousSp : 0
   +0x2e8 TxFsContext      : 0xfffe
   +0x2ec InstrumentationCallbackDisabled : 0 ''
   +0x2ed UnalignedLoadStoreExceptions : 0 ''
   +0x2ee Padding1         : [2]  ""
   +0x2f0 GdiTebBatch      : _GDI_TEB_BATCH
   +0x7d8 RealClientId     : _CLIENT_ID
   +0x7e8 GdiCachedProcessHandle : (null) 
   +0x7f0 GdiClientPID     : 0
   +0x7f4 GdiClientTID     : 0
   +0x7f8 GdiThreadLocalInfo : (null) 
   +0x800 Win32ClientInfo  : [62] 0
   +0x9f0 glDispatchTable  : [233] (null) 
   +0x1138 glReserved1      : [29] 0
   +0x1220 glReserved2      : (null) 
   +0x1228 glSectionInfo    : (null) 
   +0x1230 glSection        : (null) 
   +0x1238 glTable          : (null) 
   +0x1240 glCurrentRC      : (null) 
   +0x1248 glContext        : (null) 
   +0x1250 LastStatusValue  : 0
   +0x1254 Padding2         : [4]  ""
   +0x1258 StaticUnicodeString : _UNICODE_STRING ""
   +0x1268 StaticUnicodeBuffer : [261]  ""
   +0x1472 Padding3         : [6]  ""
   +0x1478 DeallocationStack : 0x0000006a`cb600000 Void
   +0x1480 TlsSlots         : [64] (null) 
   +0x1680 TlsLinks         : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
   +0x1690 Vdm              : (null) 
   +0x1698 ReservedForNtRpc : (null) 
   +0x16a0 DbgSsReserved    : [2] (null) 
   +0x16b0 HardErrorMode    : 0
   +0x16b4 Padding4         : [4]  ""
   +0x16b8 Instrumentation  : [11] (null) 
   +0x1710 ActivityId       : _GUID {00000000-0000-0000-0000-000000000000}
   +0x1720 SubProcessTag    : (null) 
   +0x1728 PerflibData      : (null) 
   +0x1730 EtwTraceData     : (null) 
   +0x1738 WinSockData      : (null) 
   +0x1740 GdiBatchCount    : 0
   +0x1744 CurrentIdealProcessor : _PROCESSOR_NUMBER
   +0x1744 IdealProcessorValue : 0x2020000
   +0x1744 ReservedPad0     : 0 ''
   +0x1745 ReservedPad1     : 0 ''
   +0x1746 ReservedPad2     : 0x2 ''
   +0x1747 IdealProcessor   : 0x2 ''
   +0x1748 GuaranteedStackBytes : 0
   +0x174c Padding5         : [4]  ""
   +0x1750 ReservedForPerf  : (null) 
   +0x1758 ReservedForOle   : (null) 
   +0x1760 WaitingOnLoaderLock : 0
   +0x1764 Padding6         : [4]  ""
   +0x1768 SavedPriorityState : (null) 
   +0x1770 ReservedForCodeCoverage : 0
   +0x1778 ThreadPoolData   : (null) 
   +0x1780 TlsExpansionSlots : (null) 
   +0x1788 DeallocationBStore : (null) 
   +0x1790 BStoreLimit      : (null) 
   +0x1798 MuiGeneration    : 0
   +0x179c IsImpersonating  : 0
   +0x17a0 NlsCache         : (null) 
   +0x17a8 pShimData        : (null) 
   +0x17b0 HeapData         : 0
   +0x17b4 Padding7         : [4]  ""
   +0x17b8 CurrentTransactionHandle : (null) 
   +0x17c0 ActiveFrame      : (null) 
   +0x17c8 FlsData          : 0x00000171`af368ef0 Void
   +0x17d0 PreferredLanguages : (null) 
   +0x17d8 UserPrefLanguages : (null) 
   +0x17e0 MergedPrefLanguages : (null) 
   +0x17e8 MuiImpersonation : 0
   +0x17ec CrossTebFlags    : 0
   +0x17ec SpareCrossTebBits : 0y0000000000000000 (0)
   +0x17ee SameTebFlags     : 0x420
   +0x17ee SafeThunkCall    : 0y0
   +0x17ee InDebugPrint     : 0y0
   +0x17ee HasFiberData     : 0y0
   +0x17ee SkipThreadAttach : 0y0
   +0x17ee WerInShipAssertCode : 0y0
   +0x17ee RanProcessInit   : 0y1
   +0x17ee ClonedThread     : 0y0
   +0x17ee SuppressDebugMsg : 0y0
   +0x17ee DisableUserStackWalk : 0y0
   +0x17ee RtlExceptionAttached : 0y0
   +0x17ee InitialThread    : 0y1
   +0x17ee SessionAware     : 0y0
   +0x17ee LoadOwner        : 0y0
   +0x17ee LoaderWorker     : 0y0
   +0x17ee SkipLoaderInit   : 0y0
   +0x17ee SpareSameTebBits : 0y0
   +0x17f0 TxnScopeEnterCallback : (null) 
   +0x17f8 TxnScopeExitCallback : (null) 
   +0x1800 TxnScopeContext  : (null) 
   +0x1808 LockCount        : 0
   +0x180c WowTebOffset     : 0n0
   +0x1810 ResourceRetValue : (null) 
   +0x1818 ReservedForWdf   : (null) 
   +0x1820 ReservedForCrt   : 0
   +0x1828 EffectiveContainerId : _GUID {00000000-0000-0000-0000-000000000000}

PEB-From-TEB

Since TEB inherits PEB, we can query TEB to get the address of the PEB block with the offfset of 0x60. Now we know that the PEB of this process is at 0x0000006acb523000.

This is the correct approach to find the PEB from the TEB of a thread. But in WinDbg, we can simply get the details of PEB using !peb command.

PEB-Details

We can see that the PEB gives the details about the process being debugged, base address of the image and loaded modules into the process. That is why we need to query PEB and handle it for offensive purposes. This is where the magic for querying dynamic information about a process is stored.

Now let's list the PEB using its address obtained from TEB using dx -r1 ((ntdll!_PEB *)0x6acb523000),

0:000> dx -r1 ((ntdll!_PEB *)0x6acb523000)
((ntdll!_PEB *)0x6acb523000)                 : 0x6acb523000 [Type: _PEB *]
    [+0x000] InheritedAddressSpace : 0x0 [Type: unsigned char]
    [+0x001] ReadImageFileExecOptions : 0x0 [Type: unsigned char]
    [+0x002] BeingDebugged    : 0x1 [Type: unsigned char]
    [+0x003] BitField         : 0x4 [Type: unsigned char]
    [+0x003 ( 0: 0)] ImageUsesLargePages : 0x0 [Type: unsigned char]
    [+0x003 ( 1: 1)] IsProtectedProcess : 0x0 [Type: unsigned char]
    [+0x003 ( 2: 2)] IsImageDynamicallyRelocated : 0x1 [Type: unsigned char]
    [+0x003 ( 3: 3)] SkipPatchingUser32Forwarders : 0x0 [Type: unsigned char]
    [+0x003 ( 4: 4)] IsPackagedProcess : 0x0 [Type: unsigned char]
    [+0x003 ( 5: 5)] IsAppContainer   : 0x0 [Type: unsigned char]
    [+0x003 ( 6: 6)] IsProtectedProcessLight : 0x0 [Type: unsigned char]
    [+0x003 ( 7: 7)] IsLongPathAwareProcess : 0x0 [Type: unsigned char]
    [+0x004] Padding0         [Type: unsigned char [4]]
    [+0x008] Mutant           : 0xffffffffffffffff [Type: void *]
    [+0x010] ImageBaseAddress : 0x7ff65dff0000 [Type: void *]
    [+0x018] Ldr              : 0x7ffa1355b4c0 [Type: _PEB_LDR_DATA *]
    [+0x020] ProcessParameters : 0x171af362cb0 [Type: _RTL_USER_PROCESS_PARAMETERS *]
    [+0x028] SubSystemData    : 0x0 [Type: void *]
    [+0x030] ProcessHeap      : 0x171af360000 [Type: void *]
    [+0x038] FastPebLock      : 0x7ffa1355b0e0 [Type: _RTL_CRITICAL_SECTION *]
    [+0x040] AtlThunkSListPtr : 0x0 [Type: _SLIST_HEADER *]
    [+0x048] IFEOKey          : 0x0 [Type: void *]
    [+0x050] CrossProcessFlags : 0x1 [Type: unsigned long]
    [+0x050 ( 0: 0)] ProcessInJob     : 0x1 [Type: unsigned long]
    [+0x050 ( 1: 1)] ProcessInitializing : 0x0 [Type: unsigned long]
    [+0x050 ( 2: 2)] ProcessUsingVEH  : 0x0 [Type: unsigned long]
    [+0x050 ( 3: 3)] ProcessUsingVCH  : 0x0 [Type: unsigned long]
    [+0x050 ( 4: 4)] ProcessUsingFTH  : 0x0 [Type: unsigned long]
    [+0x050 ( 5: 5)] ProcessPreviouslyThrottled : 0x0 [Type: unsigned long]
    [+0x050 ( 6: 6)] ProcessCurrentlyThrottled : 0x0 [Type: unsigned long]
    [+0x050 ( 7: 7)] ProcessImagesHotPatched : 0x0 [Type: unsigned long]
    [+0x050 (31: 8)] ReservedBits0    : 0x0 [Type: unsigned long]
    [+0x054] Padding1         [Type: unsigned char [4]]
    [+0x058] KernelCallbackTable : 0x0 [Type: void *]
    [+0x058] UserSharedInfoPtr : 0x0 [Type: void *]
    [+0x060] SystemReserved   : 0x0 [Type: unsigned long]
    [+0x064] AtlThunkSListPtr32 : 0x0 [Type: unsigned long]
    [+0x068] ApiSetMap        : 0x171af1f0000 [Type: void *]
    [+0x070] TlsExpansionCounter : 0x0 [Type: unsigned long]
    [+0x074] Padding2         [Type: unsigned char [4]]
    [+0x078] TlsBitmap        : 0x7ffa1355b440 [Type: void *]
    [+0x080] TlsBitmapBits    [Type: unsigned long [2]]
    [+0x088] ReadOnlySharedMemoryBase : 0x7ff456700000 [Type: void *]
    [+0x090] SharedData       : 0x0 [Type: void *]
    [+0x098] ReadOnlyStaticServerData : 0x7ff456700750 [Type: void * *]
    [+0x0a0] AnsiCodePageData : 0x7ff558840000 [Type: void *]
    [+0x0a8] OemCodePageData  : 0x7ff558850228 [Type: void *]
    [+0x0b0] UnicodeCaseTableData : 0x7ff558860650 [Type: void *]
    [+0x0b8] NumberOfProcessors : 0x8 [Type: unsigned long]
    [+0x0bc] NtGlobalFlag     : 0x70 [Type: unsigned long]
    [+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    : 0x2 [Type: unsigned long]
    [+0x0ec] MaximumNumberOfHeaps : 0x10 [Type: unsigned long]
    [+0x0f0] ProcessHeaps     : 0x7ffa13559d40 [Type: void * *]
    [+0x0f8] GdiSharedHandleTable : 0x0 [Type: void *]
    [+0x100] ProcessStarterHelper : 0x0 [Type: void *]
    [+0x108] GdiDCAttributeList : 0x0 [Type: unsigned long]
    [+0x10c] Padding3         [Type: unsigned char [4]]
    [+0x110] LoaderLock       : 0x7ffa135555c8 [Type: _RTL_CRITICAL_SECTION *]
    [+0x118] OSMajorVersion   : 0xa [Type: unsigned long]
    [+0x11c] OSMinorVersion   : 0x0 [Type: unsigned long]
    [+0x120] OSBuildNumber    : 0x4a65 [Type: unsigned short]
    [+0x122] OSCSDVersion     : 0x0 [Type: unsigned short]
    [+0x124] OSPlatformId     : 0x2 [Type: unsigned long]
    [+0x128] ImageSubsystem   : 0x3 [Type: unsigned long]
    [+0x12c] ImageSubsystemMajorVersion : 0x6 [Type: unsigned long]
    [+0x130] ImageSubsystemMinorVersion : 0x0 [Type: unsigned long]
    [+0x134] Padding4         [Type: unsigned char [4]]
    [+0x138] ActiveProcessAffinityMask : 0xff [Type: unsigned __int64]
    [+0x140] GdiHandleBuffer  [Type: unsigned long [60]]
    [+0x230] PostProcessInitRoutine : 0x0 : 0x0 [Type: void (__cdecl*)()]
    [+0x238] TlsExpansionBitmap : 0x7ffa1355b420 [Type: void *]
    [+0x240] TlsExpansionBitmapBits [Type: unsigned long [32]]
    [+0x2c0] SessionId        : 0x4 [Type: unsigned long]
    [+0x2c4] Padding5         [Type: unsigned char [4]]
    [+0x2c8] AppCompatFlags   : {0x0} [Type: _ULARGE_INTEGER]
    [+0x2d0] AppCompatFlagsUser : {0x0} [Type: _ULARGE_INTEGER]
    [+0x2d8] pShimData        : 0x171af230000 [Type: void *]
    [+0x2e0] AppCompatInfo    : 0x0 [Type: void *]
    [+0x2e8] CSDVersion       [Type: _UNICODE_STRING]
    [+0x2f8] ActivationContextData : 0x171af220000 [Type: _ACTIVATION_CONTEXT_DATA *]
    [+0x300] ProcessAssemblyStorageMap : 0x0 [Type: _ASSEMBLY_STORAGE_MAP *]
    [+0x308] SystemDefaultActivationContextData : 0x171af210000 [Type: _ACTIVATION_CONTEXT_DATA *]
    [+0x310] SystemAssemblyStorageMap : 0x0 [Type: _ASSEMBLY_STORAGE_MAP *]
    [+0x318] MinimumStackCommit : 0x0 [Type: unsigned __int64]
    [+0x320] SparePointers    [Type: void * [4]]
    [+0x340] SpareUlongs      [Type: unsigned long [5]]
    [+0x358] WerRegistrationData : 0x0 [Type: void *]
    [+0x360] WerShipAssertPtr : 0x0 [Type: void *]
    [+0x368] pUnused          : 0x0 [Type: void *]
    [+0x370] pImageHeaderHash : 0x0 [Type: void *]
    [+0x378] TracingFlags     : 0x0 [Type: unsigned long]
    [+0x378 ( 0: 0)] HeapTracingEnabled : 0x0 [Type: unsigned long]
    [+0x378 ( 1: 1)] CritSecTracingEnabled : 0x0 [Type: unsigned long]
    [+0x378 ( 2: 2)] LibLoaderTracingEnabled : 0x0 [Type: unsigned long]
    [+0x378 (31: 3)] SpareTracingBits : 0x0 [Type: unsigned long]
    [+0x37c] Padding6         [Type: unsigned char [4]]
    [+0x380] CsrServerReadOnlySharedMemoryBase : 0x7df4865c0000 [Type: unsigned __int64]
    [+0x388] TppWorkerpListLock : 0x0 [Type: unsigned __int64]
    [+0x390] TppWorkerpList   [Type: _LIST_ENTRY]
    [+0x3a0] WaitOnAddressHashTable [Type: void * [128]]
    [+0x7a0] TelemetryCoverageHeader : 0x0 [Type: void *]
    [+0x7a8] CloudFileFlags   : 0x60 [Type: unsigned long]
    [+0x7ac] CloudFileDiagFlags : 0x0 [Type: unsigned long]
    [+0x7b0] PlaceholderCompatibilityMode : 1 [Type: char]
    [+0x7b1] PlaceholderCompatibilityModeReserved : "" [Type: char [7]]
    [+0x7b8] LeapSecondData   : 0x7ff558830000 [Type: _LEAP_SECOND_DATA *]
    [+0x7c0] LeapSecondFlags  : 0x0 [Type: unsigned long]
    [+0x7c0 ( 0: 0)] SixtySecondEnabled : 0x0 [Type: unsigned long]
    [+0x7c0 (31: 1)] Reserved         : 0x0 [Type: unsigned long]
    [+0x7c4] NtGlobalFlag2    : 0x0 [Type: unsigned long]

To query more on the loaded modules and its base address we need to get inside the Ldr of the PEB.

LDR-From-PEB

Now lets dig into the Ldr which is present at the offset of 0x018 from PEB and check the linked list of InMemoryOrderModuleList which holds the details of modules loaded into the memory of the process.

0:000> dx -r1 ((ntdll!_PEB_LDR_DATA *)0x7ffa1355b4c0)
((ntdll!_PEB_LDR_DATA *)0x7ffa1355b4c0)                 : 0x7ffa1355b4c0 [Type: _PEB_LDR_DATA *]
    [+0x000] Length           : 0x58 [Type: unsigned long]
    [+0x004] Initialized      : 0x1 [Type: unsigned char]
    [+0x008] SsHandle         : 0x0 [Type: void *]
    [+0x010] InLoadOrderModuleList [Type: _LIST_ENTRY]
    [+0x020] InMemoryOrderModuleList [Type: _LIST_ENTRY]
    [+0x030] InInitializationOrderModuleList [Type: _LIST_ENTRY]
    [+0x040] EntryInProgress  : 0x0 [Type: void *]
    [+0x048] ShutdownInProgress : 0x0 [Type: unsigned char]
    [+0x050] ShutdownThreadId : 0x0 [Type: void *]
0:000> dx -r1 (*((ntdll!_LIST_ENTRY *)0x7ffa1355b4e0))
(*((ntdll!_LIST_ENTRY *)0x7ffa1355b4e0))                 [Type: _LIST_ENTRY]
    [+0x000] Flink            : 0x171af363780 [Type: _LIST_ENTRY *]
    [+0x008] Blink            : 0x171af3660b0 [Type: _LIST_ENTRY *]
0:000> dx -r1 ((ntdll!_LIST_ENTRY *)0x171af363780)
((ntdll!_LIST_ENTRY *)0x171af363780)                 : 0x171af363780 [Type: _LIST_ENTRY *]
    [+0x000] Flink            : 0x171af3635b0 [Type: _LIST_ENTRY *]
    [+0x008] Blink            : 0x7ffa1355b4e0 [Type: _LIST_ENTRY *]
0:000> dx -r1 ((ntdll!_LIST_ENTRY *)0x171af3635b0)
((ntdll!_LIST_ENTRY *)0x171af3635b0)                 : 0x171af3635b0 [Type: _LIST_ENTRY *]
    [+0x000] Flink            : 0x171af363cd0 [Type: _LIST_ENTRY *]
    [+0x008] Blink            : 0x171af363780 [Type: _LIST_ENTRY *]
0:000> dx -r1 ((ntdll!_LIST_ENTRY *)0x171af363cd0)
((ntdll!_LIST_ENTRY *)0x171af363cd0)                 : 0x171af363cd0 [Type: _LIST_ENTRY *]
    [+0x000] Flink            : 0x171af3643b0 [Type: _LIST_ENTRY *]
    [+0x008] Blink            : 0x171af3635b0 [Type: _LIST_ENTRY *]
0:000> dx -r1 ((ntdll!_LIST_ENTRY *)0x171af3643b0)
((ntdll!_LIST_ENTRY *)0x171af3643b0)                 : 0x171af3643b0 [Type: _LIST_ENTRY *]
    [+0x000] Flink            : 0x171af365e50 [Type: _LIST_ENTRY *]
    [+0x008] Blink            : 0x171af363cd0 [Type: _LIST_ENTRY *]
0:000> dx -r1 ((ntdll!_LIST_ENTRY *)0x171af365e50)
((ntdll!_LIST_ENTRY *)0x171af365e50)                 : 0x171af365e50 [Type: _LIST_ENTRY *]
    [+0x000] Flink            : 0x171af3660b0 [Type: _LIST_ENTRY *]
    [+0x008] Blink            : 0x171af3643b0 [Type: _LIST_ENTRY *]
0:000> dx -r1 ((ntdll!_LIST_ENTRY *)0x171af3660b0)
((ntdll!_LIST_ENTRY *)0x171af3660b0)                 : 0x171af3660b0 [Type: _LIST_ENTRY *]
    [+0x000] Flink            : 0x7ffa1355b4e0 [Type: _LIST_ENTRY *]
    [+0x008] Blink            : 0x171af365e50 [Type: _LIST_ENTRY *]
0:000> dx -r1 ((ntdll!_LIST_ENTRY *)0x7ffa1355b4e0)
((ntdll!_LIST_ENTRY *)0x7ffa1355b4e0)                 : 0x7ffa1355b4e0 [Type: _LIST_ENTRY *]
    [+0x000] Flink            : 0x171af363780 [Type: _LIST_ENTRY *]
    [+0x008] Blink            : 0x171af3660b0 [Type: _LIST_ENTRY *]
0:000> dx -r1 (*((ntdll!_LIST_ENTRY *)0x7ffa1355b4e0))

In the InLoadOrderModuleList, modules (executable files or dynamic link libraries) are arranged by their loading sequence, the InMemoryOrderModuleList, modules are organized based on their memory storage order within the process and the InInitializationOrderModuleList, modules are sorted according to the order in which they are initialized within the Process Environment Block.

Checking the linked list from InMemoryOrderModuleList from the offset of 0x020 from Ldr.

LDR-Linked-List

Lets check the details of each linked list entry using the _LDR_DATA_TABLE_ENTRY structure. The actual address of the structure will be 16 bytes / 0x10 before the _LIST_ENTRY address.

0:000> dt _LDR_DATA_TABLE_ENTRY 0x171af363780-0x10
ntdll!_LDR_DATA_TABLE_ENTRY
   +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x00000171`af3635a0 - 0x00007ffa`1355b4d0 ]
   +0x010 InMemoryOrderLinks : _LIST_ENTRY [ 0x00000171`af3635b0 - 0x00007ffa`1355b4e0 ]
   +0x020 InInitializationOrderLinks : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
   +0x030 DllBase          : 0x00007ff6`5dff0000 Void
   +0x038 EntryPoint       : 0x00007ff6`5e001267 Void
   +0x040 SizeOfImage      : 0x25000
   +0x048 FullDllName      : _UNICODE_STRING "E:\WinAPI\PE-Struct\SimpleProgram.exe"
   +0x058 BaseDllName      : _UNICODE_STRING "SimpleProgram.exe"
   +0x068 FlagGroup        : [4]  "???"
   +0x068 Flags            : 0x22cc
   +0x068 PackagedBinary   : 0y0
   +0x068 MarkedForRemoval : 0y0
   +0x068 ImageDll         : 0y1
   +0x068 LoadNotificationsSent : 0y1
   +0x068 TelemetryEntryProcessed : 0y0
   +0x068 ProcessStaticImport : 0y0
   +0x068 InLegacyLists    : 0y1
   +0x068 InIndexes        : 0y1
   +0x068 ShimDll          : 0y0
   +0x068 InExceptionTable : 0y1
   +0x068 ReservedFlags1   : 0y00
   +0x068 LoadInProgress   : 0y0
   +0x068 LoadConfigProcessed : 0y1
   +0x068 EntryProcessed   : 0y0
   +0x068 ProtectDelayLoad : 0y0
   +0x068 ReservedFlags3   : 0y00
   +0x068 DontCallForThreads : 0y0
   +0x068 ProcessAttachCalled : 0y0
   +0x068 ProcessAttachFailed : 0y0
   +0x068 CorDeferredValidate : 0y0
   +0x068 CorImage         : 0y0
   +0x068 DontRelocate     : 0y0
   +0x068 CorILOnly        : 0y0
   +0x068 ChpeImage        : 0y0
   +0x068 ReservedFlags5   : 0y00
   +0x068 Redirected       : 0y0
   +0x068 ReservedFlags6   : 0y00
   +0x068 CompatDatabaseProcessed : 0y0
   +0x06c ObsoleteLoadCount : 0xffff
   +0x06e TlsIndex         : 0
   +0x070 HashLinks        : _LIST_ENTRY [ 0x00007ffa`1355b2a0 - 0x00007ffa`1355b2a0 ]
   +0x080 TimeDateStamp    : 0x652d027f
   +0x088 EntryPointActivationContext : (null) 
   +0x090 Lock             : (null) 
   +0x098 DdagNode         : 0x00000171`af3638c0 _LDR_DDAG_NODE
   +0x0a0 NodeModuleLink   : _LIST_ENTRY [ 0x00000171`af3638c0 - 0x00000171`af3638c0 ]
   +0x0b0 LoadContext      : (null) 
   +0x0b8 ParentDllBase    : (null) 
   +0x0c0 SwitchBackContext : 0x00007ffa`1350c2f4 Void
   +0x0c8 BaseAddressIndexNode : _RTL_BALANCED_NODE
   +0x0e0 MappingInfoIndexNode : _RTL_BALANCED_NODE
   +0x0f8 OriginalBase     : 0x00007ff6`5dff0000
   +0x100 LoadTime         : _LARGE_INTEGER 0x01da0059`9c9bc993
   +0x108 BaseNameHashValue : 0xcbe11476
   +0x10c LoadReason       : 4 ( LoadReasonDynamicLoad )
   +0x110 ImplicitPathOptions : 0
   +0x114 ReferenceCount   : 2
   +0x118 DependentLoadFlags : 0
   +0x11c SigningLevel     : 0 ''

0:000> dt _LDR_DATA_TABLE_ENTRY 0x171af3635b0-0x10
ntdll!_LDR_DATA_TABLE_ENTRY
   +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x00000171`af363cc0 - 0x00000171`af363770 ]
   +0x010 InMemoryOrderLinks : _LIST_ENTRY [ 0x00000171`af363cd0 - 0x00000171`af363780 ]
   +0x020 InInitializationOrderLinks : _LIST_ENTRY [ 0x00000171`af3643c0 - 0x00007ffa`1355b4f0 ]
   +0x030 DllBase          : 0x00007ffa`133f0000 Void
   +0x038 EntryPoint       : (null) 
   +0x040 SizeOfImage      : 0x1f7000
   +0x048 FullDllName      : _UNICODE_STRING "C:\Windows\SYSTEM32\ntdll.dll"
   +0x058 BaseDllName      : _UNICODE_STRING "ntdll.dll"
   +0x068 FlagGroup        : [4]  "???"
   +0x068 Flags            : 0xa2c4
   +0x068 PackagedBinary   : 0y0
   +0x068 MarkedForRemoval : 0y0
   +0x068 ImageDll         : 0y1
   +0x068 LoadNotificationsSent : 0y0
   +0x068 TelemetryEntryProcessed : 0y0
   +0x068 ProcessStaticImport : 0y0
   +0x068 InLegacyLists    : 0y1
   +0x068 InIndexes        : 0y1
   +0x068 ShimDll          : 0y0
   +0x068 InExceptionTable : 0y1
   +0x068 ReservedFlags1   : 0y00
   +0x068 LoadInProgress   : 0y0
   +0x068 LoadConfigProcessed : 0y1
   +0x068 EntryProcessed   : 0y0
   +0x068 ProtectDelayLoad : 0y1
   +0x068 ReservedFlags3   : 0y00
   +0x068 DontCallForThreads : 0y0
   +0x068 ProcessAttachCalled : 0y0
   +0x068 ProcessAttachFailed : 0y0
   +0x068 CorDeferredValidate : 0y0
   +0x068 CorImage         : 0y0
   +0x068 DontRelocate     : 0y0
   +0x068 CorILOnly        : 0y0
   +0x068 ChpeImage        : 0y0
   +0x068 ReservedFlags5   : 0y00
   +0x068 Redirected       : 0y0
   +0x068 ReservedFlags6   : 0y00
   +0x068 CompatDatabaseProcessed : 0y0
   +0x06c ObsoleteLoadCount : 0xffff
   +0x06e TlsIndex         : 0
   +0x070 HashLinks        : _LIST_ENTRY [ 0x00007ffa`1355b280 - 0x00007ffa`1355b280 ]
   +0x080 TimeDateStamp    : 0x3be1c500
   +0x088 EntryPointActivationContext : (null) 
   +0x090 Lock             : (null) 
   +0x098 DdagNode         : 0x00000171`af3636f0 _LDR_DDAG_NODE
   +0x0a0 NodeModuleLink   : _LIST_ENTRY [ 0x00000171`af3636f0 - 0x00000171`af3636f0 ]
   +0x0b0 LoadContext      : (null) 
   +0x0b8 ParentDllBase    : (null) 
   +0x0c0 SwitchBackContext : (null) 
   +0x0c8 BaseAddressIndexNode : _RTL_BALANCED_NODE
   +0x0e0 MappingInfoIndexNode : _RTL_BALANCED_NODE
   +0x0f8 OriginalBase     : 0x00007ffa`133f0000
   +0x100 LoadTime         : _LARGE_INTEGER 0x01da0059`9c9bc993
   +0x108 BaseNameHashValue : 0xf46857d4
   +0x10c LoadReason       : 0 ( LoadReasonStaticDependency )
   +0x110 ImplicitPathOptions : 0
   +0x114 ReferenceCount   : 2
   +0x118 DependentLoadFlags : 0x800
   +0x11c SigningLevel     : 0 ''

0:000> dt _LDR_DATA_TABLE_ENTRY 0x171af363cd0-0x10
ntdll!_LDR_DATA_TABLE_ENTRY
   +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x00000171`af3643a0 - 0x00000171`af3635a0 ]
   +0x010 InMemoryOrderLinks : _LIST_ENTRY [ 0x00000171`af3643b0 - 0x00000171`af3635b0 ]
   +0x020 InInitializationOrderLinks : _LIST_ENTRY [ 0x00000171`af3660c0 - 0x00000171`af3643c0 ]
   +0x030 DllBase          : 0x00007ffa`11640000 Void
   +0x038 EntryPoint       : 0x00007ffa`116573e0 Void
   +0x040 SizeOfImage      : 0xbd000
   +0x048 FullDllName      : _UNICODE_STRING "C:\Windows\System32\KERNEL32.DLL"
   +0x058 BaseDllName      : _UNICODE_STRING "KERNEL32.DLL"
   +0x068 FlagGroup        : [4]  "???"
   +0x068 Flags            : 0xca2cc
   +0x068 PackagedBinary   : 0y0
   +0x068 MarkedForRemoval : 0y0
   +0x068 ImageDll         : 0y1
   +0x068 LoadNotificationsSent : 0y1
   +0x068 TelemetryEntryProcessed : 0y0
   +0x068 ProcessStaticImport : 0y0
   +0x068 InLegacyLists    : 0y1
   +0x068 InIndexes        : 0y1
   +0x068 ShimDll          : 0y0
   +0x068 InExceptionTable : 0y1
   +0x068 ReservedFlags1   : 0y00
   +0x068 LoadInProgress   : 0y0
   +0x068 LoadConfigProcessed : 0y1
   +0x068 EntryProcessed   : 0y0
   +0x068 ProtectDelayLoad : 0y1
   +0x068 ReservedFlags3   : 0y00
   +0x068 DontCallForThreads : 0y1
   +0x068 ProcessAttachCalled : 0y1
   +0x068 ProcessAttachFailed : 0y0
   +0x068 CorDeferredValidate : 0y0
   +0x068 CorImage         : 0y0
   +0x068 DontRelocate     : 0y0
   +0x068 CorILOnly        : 0y0
   +0x068 ChpeImage        : 0y0
   +0x068 ReservedFlags5   : 0y00
   +0x068 Redirected       : 0y0
   +0x068 ReservedFlags6   : 0y00
   +0x068 CompatDatabaseProcessed : 0y0
   +0x06c ObsoleteLoadCount : 0xffff
   +0x06e TlsIndex         : 0
   +0x070 HashLinks        : _LIST_ENTRY [ 0x00007ffa`1355b260 - 0x00007ffa`1355b260 ]
   +0x080 TimeDateStamp    : 0x9ec9da27
   +0x088 EntryPointActivationContext : (null) 
   +0x090 Lock             : (null) 
   +0x098 DdagNode         : 0x00000171`af363e10 _LDR_DDAG_NODE
   +0x0a0 NodeModuleLink   : _LIST_ENTRY [ 0x00000171`af363e10 - 0x00000171`af363e10 ]
   +0x0b0 LoadContext      : (null) 
   +0x0b8 ParentDllBase    : (null) 
   +0x0c0 SwitchBackContext : 0x00007ffa`1350c374 Void
   +0x0c8 BaseAddressIndexNode : _RTL_BALANCED_NODE
   +0x0e0 MappingInfoIndexNode : _RTL_BALANCED_NODE
   +0x0f8 OriginalBase     : 0x00007ffa`11640000
   +0x100 LoadTime         : _LARGE_INTEGER 0x01da0059`9c9bf0a0
   +0x108 BaseNameHashValue : 0x536cd652
   +0x10c LoadReason       : 4 ( LoadReasonDynamicLoad )
   +0x110 ImplicitPathOptions : 0x4000
   +0x114 ReferenceCount   : 2
   +0x118 DependentLoadFlags : 0
   +0x11c SigningLevel     : 0 ''

Now we have reached to a linked list entry where it contains the information of kernel32.dll module loaded into the process. We can get its base address from this entry with the offset of 0x30 from the entry. We can also see that _LIST_ENTRY is a part of _LDR_DATA_TABLE_ENTRY.

Kernel32-DLL-Entry

We have resolved the base address of the kernel32.dll module as 0x0x00007ffa11640000.

Lets check the DOS signature of the base address to confirm it,

Kernel32-DLL-DOSByte

Indeed it a PE and we can start parsing its internals to reach the Export Address Table to get the functions stored inside this DLL.

Relative Virtual Address

A Relative Virtual Address, or RVA for short, is the numerical disparity between two Virtual Addresses (VA), denoting the greater of the two. A Virtual Address is the original memory location, while the Relative Virtual Address (RVA) signifies the address relative to the ImageBase. In this context, ImageBase refers to the initial memory location where the executable file is loaded.

  Virtual Address = ImageBase + RVA

Parsing the PE Structure

To confirm our kernel32.dll address, we can simply query the WinDbg to fetch it.

0:000> lm m kernel32
Browse full module list
start             end                 module name
00007ffa`11640000 00007ffa`116fd000   KERNEL32   (pdb symbols)          C:\ProgramData\Dbg\sym\kernel32.pdb\B07C97792B439ABC0DF83499536C7AE53\kernel32.pdb

In the last blog, we have discussed about the PE structure and its components. Using this base address we are going to reach the e_lfanew, which is at the offset of 0x3c from the base address.

0:000> dt _IMAGE_DOS_HEADER 0x00007ffa`11640000
MessageBoxShellcode!_IMAGE_DOS_HEADER
   +0x000 e_magic          : 0x5a4d
   +0x002 e_cblp           : 0x90
   +0x004 e_cp             : 3
   +0x006 e_crlc           : 0
   +0x008 e_cparhdr        : 4
   +0x00a e_minalloc       : 0
   +0x00c e_maxalloc       : 0xffff
   +0x00e e_ss             : 0
   +0x010 e_sp             : 0xb8
   +0x012 e_csum           : 0
   +0x014 e_ip             : 0
   +0x016 e_cs             : 0
   +0x018 e_lfarlc         : 0x40
   +0x01a e_ovno           : 0
   +0x01c e_res            : [4] 0
   +0x024 e_oemid          : 0
   +0x026 e_oeminfo        : 0
   +0x028 e_res2           : [10] 0
   +0x03c e_lfanew         : 0n240 //Int4B

e_lfanew

The e_lfanew is at the RVA of 0n240 in decimal and 0xf0 in hexadecimal from the base location of the kernel32.dll. Now we know the location of e_lfanew, lets parse it so that we can reach the PE signature.

0:000> dd 0x00007ffa`11640000+0xf0
00007ffa`116400f0  00004550 00078664 9ec9da27 00000000
00007ffa`11640100  00000000 202200f0 140e020b 0007d600
00007ffa`11640110  0003a600 00000000 000173e0 00001000
00007ffa`11640120  11640000 00007ffa 00001000 00000200
00007ffa`11640130  0000000a 0000000a 0000000a 00000000
00007ffa`11640140  000bd000 00000400 000c0e27 41600003
00007ffa`11640150  00040000 00000000 00001000 00000000
00007ffa`11640160  00100000 00000000 00001000 00000000
0:000> db 0x00007ffa`11640000+0xf0
00007ffa`116400f0  50 45 00 00 64 86 07 00-27 da c9 9e 00 00 00 00  PE..d...'.......
00007ffa`11640100  00 00 00 00 f0 00 22 20-0b 02 0e 14 00 d6 07 00  ......" ........
00007ffa`11640110  00 a6 03 00 00 00 00 00-e0 73 01 00 00 10 00 00  .........s......
00007ffa`11640120  00 00 64 11 fa 7f 00 00-00 10 00 00 00 02 00 00  ..d.............
00007ffa`11640130  0a 00 00 00 0a 00 00 00-0a 00 00 00 00 00 00 00  ................
00007ffa`11640140  00 d0 0b 00 00 04 00 00-27 0e 0c 00 03 00 60 41  ........'.....`A
00007ffa`11640150  00 00 04 00 00 00 00 00-00 10 00 00 00 00 00 00  ................
00007ffa`11640160  00 00 10 00 00 00 00 00-00 10 00 00 00 00 00 00  ................

Reaching-PE-Signature

We can confirm that we have reached into the PE header. Now let's access it in the _IMAGE_NT_HEADERS64 format.

0:000> dt nt!_IMAGE_NT_HEADERS64 00007ffa`11640000+0xf0
ntdll!_IMAGE_NT_HEADERS64
   +0x000 Signature        : 0x4550
   +0x004 FileHeader       : _IMAGE_FILE_HEADER
   +0x018 OptionalHeader   : _IMAGE_OPTIONAL_HEADER64

NT-Headers

Now lets get into the File Header and Optional Header,

0:000> dx -r1 (*((ntdll!_IMAGE_FILE_HEADER *)0x7ffa116400f4))
(*((ntdll!_IMAGE_FILE_HEADER *)0x7ffa116400f4))                 [Type: _IMAGE_FILE_HEADER]
    [+0x000] Machine          : 0x8664 [Type: unsigned short]
    [+0x002] NumberOfSections : 0x7 [Type: unsigned short]
    [+0x004] TimeDateStamp    : 0x9ec9da27 [Type: unsigned long]
    [+0x008] PointerToSymbolTable : 0x0 [Type: unsigned long]
    [+0x00c] NumberOfSymbols  : 0x0 [Type: unsigned long]
    [+0x010] SizeOfOptionalHeader : 0xf0 [Type: unsigned short]
    [+0x012] Characteristics  : 0x2022 [Type: unsigned short]
0:000> dx -r1 (*((ntdll!_IMAGE_OPTIONAL_HEADER64 *)0x7ffa11640108))
(*((ntdll!_IMAGE_OPTIONAL_HEADER64 *)0x7ffa11640108))                 [Type: _IMAGE_OPTIONAL_HEADER64]
    [+0x000] Magic            : 0x20b [Type: unsigned short]
    [+0x002] MajorLinkerVersion : 0xe [Type: unsigned char]
    [+0x003] MinorLinkerVersion : 0x14 [Type: unsigned char]
    [+0x004] SizeOfCode       : 0x7d600 [Type: unsigned long]
    [+0x008] SizeOfInitializedData : 0x3a600 [Type: unsigned long]
    [+0x00c] SizeOfUninitializedData : 0x0 [Type: unsigned long]
    [+0x010] AddressOfEntryPoint : 0x173e0 [Type: unsigned long]
    [+0x014] BaseOfCode       : 0x1000 [Type: unsigned long]
    [+0x018] ImageBase        : 0x7ffa11640000 [Type: unsigned __int64]
    [+0x020] SectionAlignment : 0x1000 [Type: unsigned long]
    [+0x024] FileAlignment    : 0x200 [Type: unsigned long]
    [+0x028] MajorOperatingSystemVersion : 0xa [Type: unsigned short]
    [+0x02a] MinorOperatingSystemVersion : 0x0 [Type: unsigned short]
    [+0x02c] MajorImageVersion : 0xa [Type: unsigned short]
    [+0x02e] MinorImageVersion : 0x0 [Type: unsigned short]
    [+0x030] MajorSubsystemVersion : 0xa [Type: unsigned short]
    [+0x032] MinorSubsystemVersion : 0x0 [Type: unsigned short]
    [+0x034] Win32VersionValue : 0x0 [Type: unsigned long]
    [+0x038] SizeOfImage      : 0xbd000 [Type: unsigned long]
    [+0x03c] SizeOfHeaders    : 0x400 [Type: unsigned long]
    [+0x040] CheckSum         : 0xc0e27 [Type: unsigned long]
    [+0x044] Subsystem        : 0x3 [Type: unsigned short]
    [+0x046] DllCharacteristics : 0x4160 [Type: unsigned short]
    [+0x048] SizeOfStackReserve : 0x40000 [Type: unsigned __int64]
    [+0x050] SizeOfStackCommit : 0x1000 [Type: unsigned __int64]
    [+0x058] SizeOfHeapReserve : 0x100000 [Type: unsigned __int64]
    [+0x060] SizeOfHeapCommit : 0x1000 [Type: unsigned __int64]
    [+0x068] LoaderFlags      : 0x0 [Type: unsigned long]
    [+0x06c] NumberOfRvaAndSizes : 0x10 [Type: unsigned long]
    [+0x070] DataDirectory    [Type: _IMAGE_DATA_DIRECTORY [16]]

Optional Header is present at the offset of 0x18 from the PE Signature

File-Optional-Headers

Let's query the DataDirectory from Optional Headers which is present at the offset of 0x70 to find the Export Directory RVA. In the last blog, we have seen that the Export Directory is the first directory in the list structure.

0:000> dx -r1 (*((ntdll!_IMAGE_DATA_DIRECTORY (*)[16])0x7ffa11640178))
(*((ntdll!_IMAGE_DATA_DIRECTORY (*)[16])0x7ffa11640178))                 [Type: _IMAGE_DATA_DIRECTORY [16]]
    [0]              [Type: _IMAGE_DATA_DIRECTORY]
    [1]              [Type: _IMAGE_DATA_DIRECTORY]
    [2]              [Type: _IMAGE_DATA_DIRECTORY]
    [3]              [Type: _IMAGE_DATA_DIRECTORY]
    [4]              [Type: _IMAGE_DATA_DIRECTORY]
    [5]              [Type: _IMAGE_DATA_DIRECTORY]
    [6]              [Type: _IMAGE_DATA_DIRECTORY]
    [7]              [Type: _IMAGE_DATA_DIRECTORY]
    [8]              [Type: _IMAGE_DATA_DIRECTORY]
    [9]              [Type: _IMAGE_DATA_DIRECTORY]
    [10]             [Type: _IMAGE_DATA_DIRECTORY]
    [11]             [Type: _IMAGE_DATA_DIRECTORY]
    [12]             [Type: _IMAGE_DATA_DIRECTORY]
    [13]             [Type: _IMAGE_DATA_DIRECTORY]
    [14]             [Type: _IMAGE_DATA_DIRECTORY]
    [15]             [Type: _IMAGE_DATA_DIRECTORY]
0:000> dx -r1 (*((ntdll!_IMAGE_DATA_DIRECTORY *)0x7ffa11640178))
(*((ntdll!_IMAGE_DATA_DIRECTORY *)0x7ffa11640178))                 [Type: _IMAGE_DATA_DIRECTORY]
    [+0x000] VirtualAddress   : 0x99080 [Type: unsigned long]
    [+0x004] Size             : 0xdf58 [Type: unsigned long]

DataDirectories

We can also use !dh 0x00007ffa11640000 to dump the information of the PE header,

0:000> !dh 0x00007ffa`11640000

File Type: DLL
FILE HEADER VALUES
    8664 machine (X64)
       7 number of sections
9EC9DA27 time date stamp Tue Jun  2 21:28:31 2054

       0 file pointer to symbol table
       0 number of symbols
      F0 size of optional header
    2022 characteristics
            Executable
            App can handle >2gb addresses
            DLL

OPTIONAL HEADER VALUES
     20B magic #
   14.20 linker version
   7D600 size of code
   3A600 size of initialized data
       0 size of uninitialized data
   173E0 address of entry point
    1000 base of code
         ----- new -----
00007ffa11640000 image base
    1000 section alignment
     200 file alignment
       3 subsystem (Windows CUI)
   10.00 operating system version
   10.00 image version
   10.00 subsystem version
   BD000 size of image
     400 size of headers
   C0E27 checksum
0000000000040000 size of stack reserve
0000000000001000 size of stack commit
0000000000100000 size of heap reserve
0000000000001000 size of heap commit
    4160  DLL characteristics
            High entropy VA supported
            Dynamic base
            NX compatible
            Guard
   99080 [    DF58] address [size] of Export Directory
   A6FD8 [     794] address [size] of Import Directory
   BB000 [     520] address [size] of Resource Directory
   B4000 [    5550] address [size] of Exception Directory
   B7200 [    40D0] address [size] of Security Directory
   BC000 [     300] address [size] of Base Relocation Directory
   86B20 [      70] address [size] of Debug Directory
       0 [       0] address [size] of Description Directory
       0 [       0] address [size] of Special Directory
       0 [       0] address [size] of Thread Storage Directory
   7F7F0 [     118] address [size] of Load Configuration Directory
       0 [       0] address [size] of Bound Import Directory
   807C0 [    2A70] address [size] of Import Address Table Directory
   98E40 [      60] address [size] of Delay Import Directory
       0 [       0] address [size] of COR20 Header Directory
       0 [       0] address [size] of Reserved Directory


SECTION HEADER #1
   .text name
   7D4CB virtual size
    1000 virtual address
   7D600 size of raw data
     400 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
60000020 flags
         Code
         (no align specified)
         Execute Read

SECTION HEADER #2
  .rdata name
   32F3E virtual size
   7F000 virtual address
   33000 size of raw data
   7DA00 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
40000040 flags
         Initialized Data
         (no align specified)
         Read Only


Debug Directories(4)
	Type       Size     Address  Pointer
	cv           25       922a0    90ca0	Format: RSDS, guid, 1, kernel32.pdb
	(   13)     548       922c8    90cc8
	(   16)      24       92810    91210
	dllchar       4       92834    91234

00000001 extended DLL characteristics
          CET compatible


SECTION HEADER #3
   .data name
    121C virtual size
   B2000 virtual address
     600 size of raw data
   B0A00 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
C0000040 flags
         Initialized Data
         (no align specified)
         Read Write

SECTION HEADER #4
  .pdata name
    5550 virtual size
   B4000 virtual address
    5600 size of raw data
   B1000 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
40000040 flags
         Initialized Data
         (no align specified)
         Read Only

SECTION HEADER #5
  .didat name
      68 virtual size
   BA000 virtual address
     200 size of raw data
   B6600 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
C0000040 flags
         Initialized Data
         (no align specified)
         Read Write

SECTION HEADER #6
   .rsrc name
     520 virtual size
   BB000 virtual address
     600 size of raw data
   B6800 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
40000040 flags
         Initialized Data
         (no align specified)
         Read Only

SECTION HEADER #7
  .reloc name
     300 virtual size
   BC000 virtual address
     400 size of raw data
   B6E00 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
42000040 flags
         Initialized Data
         Discardable
         (no align specified)
         Read Only

We can directly get the Export Directory RVA using the above command,

   99080 [    DF58] address [size] of Export Directory

Now we have found the RVA of Export Directory, lets query it now from its strucutre as show below.

public struct IMAGE_EXPORT_DIRECTORY{
        public UInt32 Characteristics;
        public UInt32 TimeDateStamp;
        public UInt16 MajorVersion;
        public UInt16 MinorVersion;
        public UInt32 Name;
        public UInt32 Base;
        public UInt32 NumberOfFunctions;
        public UInt32 NumberOfNames;
        public UInt32 AddressOfFunctions;
        public UInt32 AddressOfNames;
        public UInt32 AddressOfNameOrdinals;
}

The values stored inside the IMAGE_EXPORT_DIRECTORY structure of Export Directory RVA is,

0:000> dd 0x00007ffa11640000+0x99080
00007ffa`116d9080  00000000 9ec9da27 00000000 0009d07c
00007ffa`116d9090  00000001 00000662 00000662 000990a8
00007ffa`116d90a0  0009aa30 0009c3b8 0009d0a1 0009d0d7
00007ffa`116d90b0  00020390 0001ba10 00059230 000128f0
00007ffa`116d90c0  00025950 00025960 0009d15d 0003bdd0
00007ffa`116d90d0  00059370 000593d0 00022580 0001e5d0
00007ffa`116d90e0  00039710 00020bb0 00039730 00037b30
00007ffa`116d90f0  0009d296 0009d2d6 00007200 000255a0
0:000> db 0x00007ffa11640000+0x99080
00007ffa`116d9080  00 00 00 00 27 da c9 9e-00 00 00 00 7c d0 09 00  ....'.......|...
00007ffa`116d9090  01 00 00 00 62 06 00 00-62 06 00 00 a8 90 09 00  ....b...b.......
00007ffa`116d90a0  30 aa 09 00 b8 c3 09 00-a1 d0 09 00 d7 d0 09 00  0...............
00007ffa`116d90b0  90 03 02 00 10 ba 01 00-30 92 05 00 f0 28 01 00  ........0....(..
00007ffa`116d90c0  50 59 02 00 60 59 02 00-5d d1 09 00 d0 bd 03 00  PY..`Y..].......
00007ffa`116d90d0  70 93 05 00 d0 93 05 00-80 25 02 00 d0 e5 01 00  p........%......
00007ffa`116d90e0  10 97 03 00 b0 0b 02 00-30 97 03 00 30 7b 03 00  ........0...0{..
00007ffa`116d90f0  96 d2 09 00 d6 d2 09 00-00 72 00 00 a0 55 02 00  .........r...U..

ExportDirectory

By mapping it to its structure, we get the required addresses.

public struct IMAGE_EXPORT_DIRECTORY{
        public UInt32 Characteristics;       // 00000000
        public UInt32 TimeDateStamp;         // 9ec9da27
        public UInt16 MajorVersion;          // 0000
        public UInt16 MinorVersion;          // 0000
        public UInt32 Name;                  // 0009d07c
        public UInt32 Base;                  // 00000001
        public UInt32 NumberOfFunctions;     // 00000662
        public UInt32 NumberOfNames;         // 00000662
        public UInt32 AddressOfFunctions;    // 000990a8 
        public UInt32 AddressOfNames;        // 0009aa30
        public UInt32 AddressOfNameOrdinals; // 0009c3b8
}

Export Directory

Export Directory consists of three important pointers. AddressOfFunctions also known as Export Address Table (EAT) which contains the information of all functions/symbols from the target module. AddressOfNames or Name Pointer Table which contains the names of the functions in the module. AddressOfNameOrdinals or Ordinal Table contains the ordinal value, also known as the index of the function in the Export Address Table.

ExportDirectoryPEBear

This is how it will be when analyzed in PE Bear for kernel32.dll.

The Export Address Table will be placed at an offset of 0x1c from the Export Directory, Name Pointer Table will be at the offset of 0x20 and the Ordinal Table will be at the offset of 0x24.

And the NumberOfFunctions is present with the offset of 0x14. We will be requiring this value to initialize our counter to search for the target function within the Name Pointer Table. If it matches then we use the Ordinal Table with reference to the target function to find its RVA from the Export Address Table. An ordinal is just the identifier for a function within a DLL, but these values are not static among different versions of DLL. That is why we start searching from the Name Pointer Table with refernce of Ordinal Table.

From the Name Pointer Table RVA, we can see the function names are being stored in the pointers,

NamePointers

Lets analyze the Export Directory of kernel32.dll using PEview.

NamePointerTable

Assume that we are looking for WinExec function from the DLL. The value/index for the function in the Name Pointer table will be the same value as the counter, while iterating in the table with incremental value of 4 bytes.

OrdinalTable

To get the Ordinal Number from the Ordinal Table, we just have to search with the index of (Counter Value * 2) from virtual address of Ordinal Table. Ironically, for x64 bit environment the Ordinal Number will be (Counter Value - 1).

EAT

It becomes easier now to get the RVA of the target function with a simple logic. Each pointer storing the RVA of the functions differ by 4 bytes from the virtual address of EAT. To find the exact RVA value, we just have to look with the index of (Ordinal Number * 4) from the EAT.

RVA Of Target Function

In order to locate the virtual address of a target function, we need to locate the RVA of the target function within the Export Address Table. This logic requires a few iterations and conditional checks. I have made some graphical representation of it, for easier understanding.

Workflow

  1. Load the TEB from the gs register.
  2. Access the PEB from the TEB with an offset of 0x60.
  3. From the PEB access the Ldr with an offset of 0x18.
  4. Querying the InMemoryOrderModuleList from Ldr with an offset of 0x20.
  5. Get the address of the parent process list entry from the linked list with 0x00 as offset.
  6. Iterating to the next link and fetching the list entry of ntdll.dll with 0x00 as offset.
  7. Iterating to the next link and fetching the list entry of kernel32.dll with 0x00 as offset. Upto here these modules position will be universally similar in the same order.
  8. From the list entry of kernel32.dll get the base address from the offset of 0x30.
  9. From the base address of kernel32.dll find the RVA of the PE header/PE signature with an offset of 0x3c.
  10. Calculating the address of PE header (Base address of kernel32.dll + RVA of PE header).
  11. Finding the RVA of Export Directory from the address of PE header with the offset of 0x18+0x70. The first data directory itself will be the Export Directory.
  12. Calculating the address of Export Directory (Base address of kernel32.dll + RVA of Export Directory).
  13. Fetching the number of exported functions from the address of Export Directory with an offset of 0x14.
  14. Finding the RVA of Export Address Table which is present with an offset of 0x1c from the Export Directory.
  15. Calculating the address of Export Address Table (Base address of kernel32.dll + RVA of Export Address Table).
  16. Finding the RVA of Name Pointer Table which is present with an offset of 0x20 from the Export Directory.
  17. Calculating the address of Name Pointer Table (Base address of kernel32.dll + RVA of Name Pointer Table).
  18. Finding the RVA of Ordinal Table which is present with an offset of 0x24 from the Export Directory.
  19. Calculating the address of Ordinal Table (Base address of kernel32.dll + RVA of Ordinal Table).
  20. Looping through the Name Pointer Table to find the target function address name in it with incrementing counter value up to the number of functions present in the Export Directory.
  21. Store the counter value into a variable when the target function name gets matched.
  22. Find the Ordinal Number of the target function from the Ordinal Table with the calculated address with respect to the counter value (Address of Ordinal Table + (2 * Counter Value)). Each ordinal entry is 2 bytes.
  23. Find the RVA of the target function from the Address Table with the calculated address specific to the ordinal number (Address of Address Table + (4 * Ordinal Number)). Each entry in address table is 4 bytes.
  24. Virtual address of the target function should be calculated (Base address of kernel32.dll + RVA of target function).

We will get into this calculation in depth to calculate our shellcode in the next blog post.

References

Windows Shellcode

Location Export Directory Table