- Published on
Windows x64 - PEB, TEB and EAT
- Prologue
- Process Environment Block (PEB)
- Thread Environment Block (TEB)
- Debugging TEB and PEB
- Relative Virtual Address
- Parsing the PE Structure
- Export Directory
- RVA Of Target Function
- References
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,
Now lets enumerate our TEB
in WinDbg using !teb
to get its address,
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}
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.
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.
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
.
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
.
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,
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
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 ................
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
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
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]
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..
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
.
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,
Lets analyze the Export Directory
of kernel32.dll
using PEview
.
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
.
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)
.
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.
- Load the
TEB
from thegs
register. - Access the
PEB
from theTEB
with an offset of0x60
. - From the
PEB
access theLdr
with an offset of0x18
. - Querying the
InMemoryOrderModuleList
fromLdr
with an offset of0x20
. - Get the address of the parent process list entry from the linked list with
0x00
as offset. - Iterating to the next link and fetching the list entry of
ntdll.dll
with0x00
as offset. - Iterating to the next link and fetching the list entry of
kernel32.dll
with0x00
as offset. Upto here these modules position will be universally similar in the same order. - From the list entry of
kernel32.dll
get the base address from the offset of0x30
. - From the base address of
kernel32.dll
find the RVA of the PE header/PE signature with an offset of0x3c
. - Calculating the address of
PE header
(Base address of kernel32.dll + RVA of PE header). - Finding the RVA of
Export Directory
from the address of PE header with the offset of0x18+0x70
. The first data directory itself will be the Export Directory. - Calculating the address of
Export Directory
(Base address of kernel32.dll + RVA of Export Directory). - Fetching the number of exported functions from the address of Export Directory with an offset of
0x14
. - Finding the RVA of
Export Address Table
which is present with an offset of0x1c
from the Export Directory. - Calculating the address of Export Address Table (Base address of kernel32.dll + RVA of Export Address Table).
- Finding the RVA of
Name Pointer Table
which is present with an offset of0x20
from the Export Directory. - Calculating the address of Name Pointer Table (Base address of kernel32.dll + RVA of Name Pointer Table).
- Finding the RVA of
Ordinal Table
which is present with an offset of0x24
from the Export Directory. - Calculating the address of Ordinal Table (Base address of kernel32.dll + RVA of Ordinal Table).
- 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. - Store the counter value into a variable when the target function name gets matched.
- Find the
Ordinal Number
of the target function from theOrdinal Table
with the calculated address with respect to the counter value (Address of Ordinal Table + (2 * Counter Value)). Each ordinal entry is 2 bytes. - 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. - 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.