Skip to content

2. PE directories reader

DKorablin edited this page Jul 22, 2023 · 1 revision

After you choose suitable loader you can open PE file and after check header for validation you can read all sections and directories. By default any PE/PE+ file can contain limited number of directories IMAGE_DIRECTORY_ENTRY:

  1. EXPORT - Here lies table of names and address to all public native functions created in this executable. They can be invoked by GetProcAddress function. For example shell32.dll contains functions like DragQueryFile, ShellExecute etc.
  2. IMPORT - This directory contains table of links to other executables and public functions that are require to correct use of current file. Here lies main reason of DllHell problem, because this directory contains only file and function name. For example for .NET Framework executables here will be stored link to function _CorDllMain from mscoreee.dll library.
  3. RESOURCE - This directory contains native PE resources in folder structures. Mostly all new PE files contains RT_MANIFEST and RT_VERSION folders. If executable is a Desktop App then there can be more sections like RT_BITMAP, RT_ICON, RT_ACCELERATOR, RT_DIALOG etc.
  4. CERTIFICATE - If current executable is strongly signed then this section will contain public certificate that was used to validate signed executable.
  5. DEBUG - This directory contains precompiled debug information that is used for debugging purposes and to simplify memory dump reading
  6. DELAY_IMPORT - This is additional directory for import directory but unlike IMPORT directory referenced libraries will be resolved on a first call
  7. CLR_HEADER (COMHEADER) - This directory contains all .NET CLI metadata

Before we will start to read contents of each any directory we need to check that file is valid PE/PE+ file. But before loading file to reader you need to check that supplied files is not empty length and you have access to this file. For this purpose, the property IsValid is used:

String dll = @"%WINDIR%\System32\kernel32.dll";
using(PEFile file = new PEFile(dll, StreamLoader.FromFile(dll)))
    if(file.Header.IsValid)
    {
        if(file.Resource.IsEmpty == false)
        {//Native resource directory
        }

        if(file.ComDescriptor != null)
        {//Reading .NET Framework & .NET Core directory
        }

        if(file.Import == false)
        {//Get all Imported functions from this PE file
        }

        if(file.Export.IsEmpty == false)
        {//Get all exported functions from PE file
        }
    }

Export Directory

This directory contains current library name and table with all native functions visible for calling from outside through GetProcAddress function. To read all exported functions first of all we need to check that directory is not empty:

if(file.Export.IsEmpty == false)
{
    String moduleName = file.Export.DllName;
    foreach(var func in file.Export.GetExportFunctions())
        Console.WriteLine($"Function name: {func.Name} Function Index: {func.Ordinal} Address: {func.Address}");
}

Import Directory

Here lies information about required referenced libraries that will be resolved on current library load. List of referenced that will be resolved on call are located in Delay import directory.

if(file.Import.IsEmpty == false)
    foreach(var module in file.Import)
    {
        String moduleName = module.ModuleName;
        foreach(var func in module)
            Console.WriteLine($"Function name: {func.Name} Ordinal index: {func.Hint} Is by ordinal: {func.IsByOrdinal}");
    }

Resource Directory

Here lies information about resources for native Win32 applications

if(file.Resource.IsEmpty == true) return;

foreach(var rootDir in file.Resource)
{
    String directoryName = rootDir.Name;
    foreach(var subDir in rootDir)
    {
        directoryName = sibDir.Name;
        foreach(var subDir1 in subDir)
        {
            directoryName = subDir1.Name;
            if(subDir1.DirectoryEntry.IsDataEntry)
                switch(rootDir.DirectoryEntry.NameType)
                {
		case WinNT.Resource.RESOURCE_DIRECTORY_TYPE.RT_ACCELERATOR:
		    var acc = new AlphaOmega.Debug.NTDirectory.Resources.ResourceAccelerator(dir2).ToArray();
		    foreach(var a in acc)
		    {
		        UInt16 id = a.wId;
		        UInt16 wAnsi = a.wAnsi;
		        String key = a.StringKey;//string representation of wAnsi parameter
		        AlphaOmega.Debug.WinUser.AccelFlags flags = a.fFlags;
		    }
		    break;
		case WinNT.Resource.RESOURCE_DIRECTORY_TYPE.RT_STRING:
		    var strings = new AlphaOmega.Debug.NTDirectory.Resources.ResourceString(dir2);
		    foreach(var entry in strings)
		    {
		        UInt32 resourceId = entry.ID;
		        String resourceValue = entry.Value;
		    }
		    break;
		case WinNT.Resource.RESOURCE_DIRECTORY_TYPE.RT_VERSION:
		case WinNT.Resource.RESOURCE_DIRECTORY_TYPE.RT_MANIFEST:
		case WinNT.Resource.RESOURCE_DIRECTORY_TYPE.RT_MESSAGETABLE:
		case WinNT.Resource.RESOURCE_DIRECTORY_TYPE.RT_MENU:
		case WinNT.Resource.RESOURCE_DIRECTORY_TYPE.RT_TOOLBAR:
		case WinNT.Resource.RESOURCE_DIRECTORY_TYPE.RT_FONTDIR:
		case WinNT.Resource.RESOURCE_DIRECTORY_TYPE.RT_FONT:
		case WinNT.Resource.RESOURCE_DIRECTORY_TYPE.RT_BITMAP:
		case WinNT.Resource.RESOURCE_DIRECTORY_TYPE.RT_ICON:
		case WinNT.Resource.RESOURCE_DIRECTORY_TYPE.RT_DLGINIT:
		case WinNT.Resource.RESOURCE_DIRECTORY_TYPE.RT_DIALOG:
		    break;
                }
        }
    }
}

Exception Directory

Certificate Directory

Debug Directory

Delay Import Directory