Red Team TTPs Part 2: PUSH 0xPE, CALL 0xLOADER

Posted on 08 Oct 2020 by Paranoid Ninja


.blog

PE or Portable executable is one of the most important topic that revolves around information security. Anything ranging from executing a process on windows, loading a DLL from disk, memory based reflective PE injections or even reflecting Dot Net Assemblies, all revolve around the PE structure of windows. I decided to write this blog since I wanted to document my observations when I started writing a reflective PE loader in assembly (x64). The next set of blogs will focus more on PE obfuscation, packers, stubs, code caving and shellcoding reflective PE injections. However, this part will focus more on understanding the PE structure in detail and writing our own PE parser and loader without using any WinAPI Structs or Magic.

.headers

Microsoft has detailed documentation on the PE structure of windows, which we will be using throughout the blog. However, most of the blogs that I read focuses on using Microsoft PE Header structures like IMAGE_NT_HEADERS64 and similar other structures. Most code I’ve seen in C or other language focus on typecasting the structures to a loaded PE buffer in memory and then extract the offsets and RVAs from this structure. However, we will be doing raw arithmetic calculations to calculate these addresses. The most important part of understanding a PE are file offsets, relative virtual addresses and virtual addresses. Microsoft couldn’t be moreclear about this part, so I am not going to focus on that. Put it simply, File offsets are the addresses of specific parts of the PE when stored on disk. Virtual Addresses are actual physical memory addresses. And Relative Virtual Addresses (RVAs) are addresses which are ‘relative’ on the data stored inside the PE binary. All, of this might be a bit confusing, so let’s get our hands dirty and focus on the actual code. I will be using a sample executable named boxreflect.dll to perform a walk-through of the PE and parse the code in the PE. This DLL just prints a messagebox on screen when it’s exported function named boxit is loaded via a reflective loader which can be seen on the left hand size image here:

And Here is the code for the DllMain for this PE:

    #include <windows.h>
    #include <stdio.h>
    
    BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved)
    {
        switch (dwReason)
        {
        case DLL_PROCESS_ATTACH: {
            MessageBoxA(NULL, "Box Reflected", "Test", MB_OK);
            printf("[+] Returning this output\n");
            fflush(stdout);
            break;
        }
        case DLL_PROCESS_DETACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
            break;
        }
        return TRUE;
    }

Since this is a reflective DLL, we don’t need to store it as a DLL on disk. We can store it as raw buffer inside a header file, and during compilation of our C file, it will directly embed it inside our peparser. We can convert the DLL to buffer using bash with the following command:

    $ xxd -i boxreflect.dll

It should show something like this:

Figure 2: xxd c-styled output for boxreflect.dll

Copy this full output to a text file named boxreflect.h. You can find this source code in my git repository. We will follow the instructions of PE structure from the Microsoft documentation here and convert them to C code. Writing the code in a C program helps us to use pure arithmetic calculations which can later be used for creating a loader with similar calculations in assembly 32 or 64 bit. Since assembly compiler doesn’t understand structs, we will refrain ourselves from using Microsoft PE structs here.

Note: PE sections differ when compiled with GCC and when compiled with Visual Studio Clang compiler. Mingw GCC shows .edata for Export Sections whereas Clang shows .rdata. We will be using Mingw-x64 cross compiler to compile this program since it produces a smaller executable in size.

.init

According to Microsoft Documentation, the PE header starts with a MZ header which we can actually see in the Figure 2 as ‘0x4d 0x5A’. The initial header of a PE file is a DOS header which states “This program cannot be run in DOS mode”. The DOS header ends at offset 0x3C as stated by Microsoft and it also mentions that this offset specifies the start of the PE header. Let’s put this in our Code:

    #include <windows.h>
    #include <stdio.h>
    #include "boxreflect.h"
    
    int main() {
        DWORD initialOffset = 0x3c;
        DWORD peHeaderOffset = boxreflect_dll[initialOffset];
        printf(" [0x%04X] %-38s : 0x%02X\n", initialOffset, "[peHeader offset]", peHeaderOffset);
        printf(" [0x%04X] %-38s : %c%c\n", peHeaderOffset, "[peHeader]", boxreflect_dll[peHeaderOffset], boxreflect_dll[peHeaderOffset+1]);
        return 1;
    }

Executing the above code, gives us the below result:

Figure 3: PE DOS Header and Offset

As can be seen in the above screenshot, we will be printing the exact offsets for each of these objects to the left side of the object. Microsoft gives the exact offsets for each of the header objects here. Let’s add them to our existing offsets and get the bytes located at each of the offsets.

    DWORD machineTypeOffset = peHeaderOffset + 4;
    printf(" [0x%04x] %-38s : x%x%x\n", machineTypeOffset, "[machineType]", boxreflect_dll[machineTypeOffset+1], boxreflect_dll[machineTypeOffset]);

    DWORD noOfSectionsOffset = machineTypeOffset + 0x2;
    DWORD noOfSections = ( boxreflect_dll[noOfSectionsOffset+1] << 8 ) | ( boxreflect_dll[noOfSectionsOffset] );
    printf(" [0x%04x] %-38s : (%d) 0x%02X\n", noOfSectionsOffset, "[noOfSections]",  noOfSections, noOfSections);

    DWORD timeDateStampOffset = noOfSectionsOffset + 0x2;
    printf(" [0x%04x] %-38s : 0x%02X%02X%02X%02X\n", timeDateStampOffset, "[timeDateStamp]", boxreflect_dll[timeDateStampOffset+3], boxreflect_dll[timeDateStampOffset+2], boxreflect_dll[timeDateStampOffset+1], boxreflect_dll[timeDateStampOffset]);

    //PointerToSymbolTable deprecated = 0x4 bytes
    //NumberOfSymbols  deprecated = 0x4 bytes
    DWORD sizeOfOptionalHeaderOffset = timeDateStampOffset + 0x4 + 0x4 + 0x4;
    DWORD sizeOfOptionalHeader = ( boxreflect_dll[sizeOfOptionalHeaderOffset+1]<<8) | (boxreflect_dll[sizeOfOptionalHeaderOffset]);
    printf(" [0x%04x] %-38s : 0x%02X\n", sizeOfOptionalHeaderOffset, "[sizeOfOptionalHeader]", sizeOfOptionalHeader);

    //Characteristics = 0x2 bytes
    //Data at sizeOfOptionalHeaderOffset = 0x2 bytes
    //First Section Header Offset = sizeOfOptionalHeaderOffset (since data at sizeOfOptionalHeaderOffset is 2 bytes, add 2 to the start of this offset) + 0x2 + characteristicsOffset (2 bytes) + sizeOfOptionalHeader
    DWORD firstSectionHeaderOffset = sizeOfOptionalHeaderOffset + 0x2 + 0x2 + sizeOfOptionalHeader;

The comments should be self-explanatory here. This is the output of the above code:

The above image describes the type of the PE i.e. x64 compiled PE, the timestamp when it was compiled and the size of the Optional Header which comes directly after this File Header. We will skip the Optional Headers for now since our main aim is to understand the Export Data Section (.edata).

.section

In the last line of the above code, we have also calculated the file offset for the first section from the .section part of the headers. Figure 4 indicates that we have a total of 11 sections in the PE .section header. Microsoft documentation in the Section Table states that each section is of 40 bytes. This section constitutes of:

Field Field Size (bytes)
Section’s utf-8 name 8
Section’s virtual size 4
Section’s virtual address 4
Actual size of the section (sizeOfRawData) 4
Pointer to the start of the raw data section  (Pointer To Raw Data) 4
File pointer for relocation entries (zero for executables) 4
Deprecated 4
Number of file pointer relocations (zero for executables) 2
Deprecated 2
Section Flags/Charateristics (multiple hex values OR’d to each other) 4

Since we have 11 sections and we know the exact size of each of the fields, we can just put this inside a new function inside a ‘for loop’. We can pass the variables firstSectionHeaderOffset and noOfSections to the new function to calculate the field values at each set of bytes that we saw in Table 1.

void findSectionHeaders(DWORD firstSectionHeaderOffset, DWORD noOfSections) {
    printf("\n [Sections headers start at: 0x%04x]\n", firstSectionHeaderOffset);

    DWORD nextSectionHeaderOffset = firstSectionHeaderOffset;
    for (int i = 0; i < noOfSections; i++) {
        CHAR headerName[] = { boxreflect_dll[nextSectionHeaderOffset], boxreflect_dll[nextSectionHeaderOffset+1],
            boxreflect_dll[nextSectionHeaderOffset+2], boxreflect_dll[nextSectionHeaderOffset+3],
            boxreflect_dll[nextSectionHeaderOffset+4], boxreflect_dll[nextSectionHeaderOffset+5],
            boxreflect_dll[nextSectionHeaderOffset+6], boxreflect_dll[firstSectionHeaderOffset+7], 0x00 };

        printf(" [+] [Section Header %d]\n", i);
        printf("     [+] [0x%04x] %-30s : %-30s\n", nextSectionHeaderOffset, "[Name]", headerName);

        DWORD virtualSizeOffset = nextSectionHeaderOffset + 0x8;
        printf("     [+] [0x%04x] %-30s : 0x%02X%02X%02X%02X\n", virtualSizeOffset, "[VirtualSize]", boxreflect_dll[virtualSizeOffset+3], boxreflect_dll[virtualSizeOffset+2], boxreflect_dll[virtualSizeOffset+1], boxreflect_dll[virtualSizeOffset]);

        DWORD virtualAddressOffset = virtualSizeOffset + 0x4;
        printf("     [+] [0x%04x] %-30s : 0x%02X%02X%02X%02X\n", virtualAddressOffset, "[VirtualAddress]", boxreflect_dll[virtualAddressOffset+3], boxreflect_dll[virtualAddressOffset+2], boxreflect_dll[virtualAddressOffset+1], boxreflect_dll[virtualAddressOffset]);

        DWORD sizeOfRawDataOffset = virtualAddressOffset + 0x4;
        printf("     [+] [0x%04x] %-30s : 0x%02X%02X%02X%02X\n", sizeOfRawDataOffset, "[SizeOfRawData]", boxreflect_dll[sizeOfRawDataOffset+3], boxreflect_dll[sizeOfRawDataOffset+2], boxreflect_dll[sizeOfRawDataOffset+1], boxreflect_dll[sizeOfRawDataOffset]);

        DWORD pointerToRawDataOffset = sizeOfRawDataOffset + 0x4;
        printf("     [+] [0x%04x] %-30s : 0x%02X%02X%02X%02X\n", pointerToRawDataOffset, "[PointerToRawData]", boxreflect_dll[pointerToRawDataOffset+3], boxreflect_dll[pointerToRawDataOffset+2], boxreflect_dll[pointerToRawDataOffset+1], boxreflect_dll[pointerToRawDataOffset]);

        DWORD pointerToRelocationsOffset = pointerToRawDataOffset + 0x4;
        printf("     [+] [0x%04x] %-30s : 0x%02X%02X%02X%02X\n", pointerToRelocationsOffset, "[PointerToRelocations]", boxreflect_dll[pointerToRelocationsOffset+3], boxreflect_dll[pointerToRelocationsOffset+2], boxreflect_dll[pointerToRelocationsOffset+1], boxreflect_dll[pointerToRelocationsOffset]);

        DWORD pointerToLinenumbersOffset = pointerToRelocationsOffset + 0x4;
        printf("     [+] [0x%04x] %-30s : 0x%02X%02X%02X%02X\n", pointerToLinenumbersOffset, "[PointerToLinenumbers]", boxreflect_dll[pointerToLinenumbersOffset+3], boxreflect_dll[pointerToLinenumbersOffset+2], boxreflect_dll[pointerToLinenumbersOffset+1], boxreflect_dll[pointerToLinenumbersOffset]);

        DWORD numberOfLinenumbersOffset = pointerToLinenumbersOffset + 0x4;
        printf("     [+] [0x%04x] %-30s : 0x%02X%02X%02X%02X\n", numberOfLinenumbersOffset, "[NumberOfLinenumbers]", boxreflect_dll[numberOfLinenumbersOffset+3], boxreflect_dll[numberOfLinenumbersOffset+2], boxreflect_dll[numberOfLinenumbersOffset+1], boxreflect_dll[numberOfLinenumbersOffset]);

        DWORD characteristicsOffset = numberOfLinenumbersOffset + 0x4;
        printf("     [+] [0x%04x] %-30s : 0x%02X%02X%02X%02X\n", characteristicsOffset, "[Characteristics]", boxreflect_dll[characteristicsOffset+3], boxreflect_dll[characteristicsOffset+2], boxreflect_dll[characteristicsOffset+1], boxreflect_dll[characteristicsOffset]);

        nextSectionHeaderOffset += 0x28;
        printf("\n");
    }
}

The output of the above code should look as follows:

In our PE, we have a total of 11 sections which are as follows:

Now, one important thing to note here is the ‘.edata’ section. If you compile the same file with clang (visual studio compiler), the file would have an ‘.rdata’ instead of an ‘.edata’ since Microsoft changed this overtime. But since GCC still uses a lot of old windows headers format, GCC uses the same old convention of ‘.edata’.

.edata

This section is the Export Data Section of the PE. This section will mostly be found in a DLL file which contains exported functions. Exported functions are more often referred as ‘Symbols’. The offsets, virtual addresses, relative virtual addresses, address of the executable code section for the symbols, count of the symbols and all other metadata related to Symbols will be stored in this section. We can get the file offset for the start of this section using the above code that we compiled earlier in the Section 6 output.

The PointerToRawData is the field which states the starting point of the .edata section field. The Name field is a 8 byte utf-8 encoded null padded string. Since we know that .edata is the export directory, we can add a simple string comparison in our above code to check if the current section is .edata. If it is, then we will jump to another function and pass PointerToRawData to that function to start calculating the symbols and their actual address which can be called when we load the reflective DLL in memory. We will return a hex DWORD from this function which will represent the File Offset of the exported function that we need. Below is the code which can be added after printing the characteristicsOffset of the .edata section:

if (strstr(headerName, ".edata")) {
        DWORD firstByte = boxreflect_dll[pointerToRawDataOffset+3];
        DWORD secondByte = boxreflect_dll[pointerToRawDataOffset+2];
        DWORD thirdByte = boxreflect_dll[pointerToRawDataOffset+1];
        DWORD fourthByte = boxreflect_dll[pointerToRawDataOffset+0];

        DWORD pointerToRawData = ( firstByte << 24 ) | ( secondByte << 16 ) | ( thirdByte << 8 ) | fourthByte;
        DWORD symbolRVA = findExportDirectoryInfo(pointerToRawData, virtualAddressOffset);
    }
…

Since the pointerToRawData is a 4 byte offset, we will concatenate the hex values to create a DWORD value using Shift Left and OR operators, and pass this pointerToRawData (0x2E00) and virtualAddressOffset to a new function which will calculate the Symbols and Addresses from the Export Directory Table of the .edata section. Microsoft Documentation for the Export Directory provides us with the following offset information:

Field Field Size
Export flags 4
Timestamp 4
Major version 2
Minor version 2
Name RVA (RVA to actual PE name on disk) 4
Ordinal base 4
Address table entries (Count of functions in Export Address Table) 4
Number of name pointers (Count of entries in the name pointer table/ordinal table) 4
Export address table RVA (RVA of the Export Address Table) 4
Name pointer RVA (RVA of the Export Name Pointer Table) 4
Ordinal table RVA (RVA of the Ordinal Table) 4

Using the above information, we can start adding the field sizes to pointerToRawData and get the actual File Offsets for all the above fields. Now we can combine all of the above information into a C code:

    DWORD findExportDirectoryInfo(DWORD pointerToRawData, DWORD virtualAddressOffset) {
    printf("         [-] [0x%04x] %-26s : 0x%02X%02X%02X%02X\n", pointerToRawData, "[exportFlags]", boxreflect_dll[pointerToRawData+3], boxreflect_dll[pointerToRawData+2], boxreflect_dll[pointerToRawData+1], boxreflect_dll[pointerToRawData]);

    DWORD timeDateStampOffset = pointerToRawData + 0x4;
    printf("         [-] [0x%04x] %-26s : 0x%02X%02X%02X%02X\n", timeDateStampOffset, "[Time/DateStamp]", boxreflect_dll[timeDateStampOffset+3], boxreflect_dll[timeDateStampOffset+2], boxreflect_dll[timeDateStampOffset+1], boxreflect_dll[timeDateStampOffset]);

    DWORD majorVersionOffset = timeDateStampOffset + 0x4;
    printf("         [-] [0x%04x] %-26s : 0x%02X%02X\n", majorVersionOffset, "[majorVersion]", boxreflect_dll[majorVersionOffset+1], boxreflect_dll[majorVersionOffset]);

    DWORD minorVersionOffset = majorVersionOffset + 0x2;
    printf("         [-] [0x%04x] %-26s : 0x%02X%02X\n", minorVersionOffset, "[minorVersion]", boxreflect_dll[minorVersionOffset+1], boxreflect_dll[minorVersionOffset]);

    DWORD nameRVAOffset = minorVersionOffset + 0x2;
    printf("         [-] [0x%04x] %-26s : 0x%02X%02X%02X%02X %s\n", nameRVAOffset, "[nameRVA]", boxreflect_dll[nameRVAOffset+3], boxreflect_dll[nameRVAOffset+2], boxreflect_dll[nameRVAOffset+1], boxreflect_dll[nameRVAOffset], " (RVA to PE name)");

    DWORD ordinalBaseOffset = nameRVAOffset + 0x4;
    printf("         [-] [0x%04x] %-26s : 0x%02X%02X%02X%02X\n", ordinalBaseOffset, "[ordinalBase]", boxreflect_dll[ordinalBaseOffset+3], boxreflect_dll[ordinalBaseOffset+2], boxreflect_dll[ordinalBaseOffset+1], boxreflect_dll[ordinalBaseOffset]);

    DWORD addressTableEntriesOffset = ordinalBaseOffset + 0x4;
    printf("         [-] [0x%04x] %-26s : 0x%02X%02X%02X%02X %s\n", addressTableEntriesOffset, "[addressTableEntries]", boxreflect_dll[addressTableEntriesOffset+3], boxreflect_dll[addressTableEntriesOffset+2], boxreflect_dll[addressTableEntriesOffset+1], boxreflect_dll[addressTableEntriesOffset], " (Count of functions in Export Address Table)");

    DWORD numberOfNamePointersOffset = addressTableEntriesOffset + 0x4;
    printf("         [-] [0x%04x] %-26s : 0x%02X%02X%02X%02X %s\n", numberOfNamePointersOffset, "[numberOfNamePointers]", boxreflect_dll[numberOfNamePointersOffset+3], boxreflect_dll[numberOfNamePointersOffset+2], boxreflect_dll[numberOfNamePointersOffset+1], boxreflect_dll[numberOfNamePointersOffset], " (Count of entries in the name pointer table/ordinal table)");

    DWORD exportAddressTableRVAOffset = numberOfNamePointersOffset + 0x4;
    printf("         [-] [0x%04x] %-26s : 0x%02X%02X%02X%02X %s\n", exportAddressTableRVAOffset, "[exportAddressTableRVA]", boxreflect_dll[exportAddressTableRVAOffset+3], boxreflect_dll[exportAddressTableRVAOffset+2], boxreflect_dll[exportAddressTableRVAOffset+1], boxreflect_dll[exportAddressTableRVAOffset], " (RVA of the Export Address Table)");

    DWORD namePointerRVAOffset = exportAddressTableRVAOffset + 0x4;
    printf("         [-] [0x%04x] %-26s : 0x%02X%02X%02X%02X %s\n", namePointerRVAOffset, "[namePointerRVA]", boxreflect_dll[namePointerRVAOffset+3], boxreflect_dll[namePointerRVAOffset+2], boxreflect_dll[namePointerRVAOffset+1], boxreflect_dll[namePointerRVAOffset], " (RVA of the Export Name Pointer Table)");

    DWORD ordinalTableRVAOffset = namePointerRVAOffset + 0x4;
    printf("         [-] [0x%04x] %-26s : 0x%02X%02X%02X%02X %s\n", ordinalTableRVAOffset, "[ordinalTableRVA]", boxreflect_dll[ordinalTableRVAOffset+3], boxreflect_dll[ordinalTableRVAOffset+2], boxreflect_dll[ordinalTableRVAOffset+1], boxreflect_dll[ordinalTableRVAOffset], " (RVA of the Ordinal Table)");
}

And this is the output of the code:

Now comes the main part of enumerating the Symbols in the Export Address Table. For this, we will first need the count of the exported Symbols in the Export Address Table. In the above image, the addressTableEntries specify the count of exported symbols from the PE. In our case, it’s just one. The names of the symbols are stored in the Export Name Pointer Table (ENPT), but we need the File Offset for ENPT which we don’t have at the current moment. We can enumerate the name of our symbol using the namePointerRVA. The namePointerRVA offset at 0x2e20 gives us the RVA (Relative Virtual Address) of the Export Name Pointer Table (ENPT) which is 0x802C. We can calculate the file offset of the ENPT using a simple arithmetic calculation:

    File Offset of a Field = ( RVA of the Field – Virtual Address of the Field’s Section ) + Pointer To Raw Data for the Field’s Section
    File Offset of ENPT = 802C – 8000 + 2E00 = 2E2C

Now let’s see what we have at 0x2E2C file offset. We will perform the above calculations in C to extract the data at 0x2E2C. So, here is how the code would look like:

DWORD findExportDirectoryInfo(DWORD pointerToRawData, DWORD virtualAddressOffset) {
    …
        DWORD exportNamePointerRVA = (boxreflect_dll[namePointerRVAOffset+3] << 24) | (boxreflect_dll[namePointerRVAOffset+2] << 16) | (boxreflect_dll[namePointerRVAOffset+1] << 8) | boxreflect_dll[namePointerRVAOffset];
        DWORD edataVirtualAddress = (boxreflect_dll[virtualAddressOffset+3] << 24) | (boxreflect_dll[virtualAddressOffset+2] << 16) | (boxreflect_dll[virtualAddressOffset+1] << 8) | boxreflect_dll[virtualAddressOffset];
        DWORD exportNamePointerFileOffset = ( exportNamePointerRVA - edataVirtualAddress ) + pointerToRawData;
        printf("         [-] [0x%04x] %-26s : 0x%02X%02X%02X%02X\n", exportNamePointerFileOffset, "[exportNamePointerRVA]", boxreflect_dll[exportNamePointerFileOffset+3], boxreflect_dll[exportNamePointerFileOffset+2], boxreflect_dll[exportNamePointerFileOffset+1], boxreflect_dll[exportNamePointerFileOffset]);
    }

And the output as follows:

0x8041 is also an RVA. We can calculate the file offset for 0x8041 similar to the previous calculation:

File Offset = ( 80418000 ) + 2E00 = 2E41.

And repeating the same C code we used above, we can get the data at this file offset. This should be a string following by a null byte, so we can simply use a for loop to run till we find the null byte.

…
    DWORD symbolNameRVA = (boxreflect_dll[exportNamePointerFileOffset+3] << 24) | (boxreflect_dll[exportNamePointerFileOffset+2] << 16) | (boxreflect_dll[exportNamePointerFileOffset+1] << 8) | boxreflect_dll[exportNamePointerFileOffset];
    DWORD symbolFileOffset = ( symbolNameRVA - edataVirtualAddress ) + pointerToRawData;
    unsigned char symbolName[MAX_PATH] = { 0 };
    for (int i = 0; i < MAX_PATH; i++) {
        if (boxreflect_dll[symbolFileOffset+i] == 0) {
            break;
        }
        symbolName[i] = boxreflect_dll[symbolFileOffset+i];
    }
    printf("         [-] [0x%04x] %-26s : %s\n", symbolFileOffset, "[symbolName]", symbolName); 
…

And here is the output of the first Symbol name in the Export Name Pointer Table:

Similar to Export Name Pointer Table, we will now find the Address of this Symbol in the Export Address Table. Figure 9 shows the exportAddressTableRVA is 0x8028. So we can calculate the file offset as follows:

File Offset for Symbol’s Address: 80288000 + 2E00 = 2E28

Let’s put this in the code and see what we have at offset 0x2E28

…
    DWORD exportAddressTableRVA = (boxreflect_dll[exportAddressTableRVAOffset+3] << 24) | (boxreflect_dll[exportAddressTableRVAOffset+2] << 16) | (boxreflect_dll[exportAddressTableRVAOffset+1] << 8) | boxreflect_dll[exportAddressTableRVAOffset];
    DWORD symbolRVAOffset = ( exportAddressTableRVA - edataVirtualAddress ) + pointerToRawData;
    DWORD symbolRVA = (boxreflect_dll[symbolRVAOffset+3] << 24) | (boxreflect_dll[symbolRVAOffset+2] << 16) | (boxreflect_dll[symbolRVAOffset+1] << 8) | boxreflect_dll[symbolRVAOffset];
    printf("         [-] [0x%04x] %-26s : 0x%08X\n", symbolRVAOffset, "[symbolRVA]", symbolRVA);
    return symbolRVA; 
}

And the output:

Since we have the RVA of the symbol, we have to calculate the File Offset for this Symbol which we can later use to call this function. But unlike the previous Virtual Address (0x8000), this symbol address (0x2990) belongs to a different section, which means the Virtual Address and the pointerToRawData will be different. We can check which section this address belongs to using a simple arithmetic calculation. We will loop through all the section Virtual Addresses and the Size of these sections that we’ve found while looping through the 11 sections, and we will check if our RVA fits into that Virtual Address. If it does, then we will use the same arithmetic calculation we used previously to calculate the File Offset.

In our case, the .text section starts at offset 0x1000 and has a size of 0x1E00, which means the .text section ends at offset 0x2E00. Since our symbol RVA 0x2990 is less than 0x2E00, it means we can use the .text section to calculate the file offset for our symbol. So, the calculations would be:

Symbol ‘boxit’ File offset = 2990 - 1000 + 400 = 1D90

Let’s do the same calculations using C now.

void findSectionHeaders(DWORD firstSectionHeaderOffset, DWORD noOfSections) {
    …
                DWORD symbolRVA = findExportDirectoryInfo(pointerToRawData, virtualAddressOffset);
                DWORD tempSectionHeaderOffset = firstSectionHeaderOffset;
                for (int i = 0; i < 11; i++) {
                    // VirtualAddress offset is 12 bytes from firstSectionHeaderOffset ( Name = 8 bytes, VirtualSize = 4 bytes )
                    DWORD sectionVirtualAddressOffset = firstSectionHeaderOffset + 0xC;
                    DWORD sectionVirtualAddress = (boxreflect_dll[sectionVirtualAddressOffset+3] << 24) | (boxreflect_dll[sectionVirtualAddressOffset+2] << 16) | (boxreflect_dll[sectionVirtualAddressOffset+1] << 8) | boxreflect_dll[sectionVirtualAddressOffset];
                    // SizeOfRawData offset is 4 bytes from VirtualAddress ( VirtualAddress = 4 )
                    DWORD sectionSizeOfRawDataOffset = sectionVirtualAddressOffset + 0x4;
                    DWORD sectionSizeOfRawData = (boxreflect_dll[sectionSizeOfRawDataOffset+3] << 24) | (boxreflect_dll[sectionSizeOfRawDataOffset+2] << 16) | (boxreflect_dll[sectionSizeOfRawDataOffset+1] << 8) | boxreflect_dll[sectionSizeOfRawDataOffset];
                    // SizeOfRawData offset is 4 bytes from SizeOfRawData ( SizeOfRawData = 4 )
                    DWORD sectionPointerToRawDataOffset = sectionSizeOfRawDataOffset + 0x4;
                    DWORD sectionPointerToRawData = (boxreflect_dll[sectionPointerToRawDataOffset+3] << 24) | (boxreflect_dll[sectionPointerToRawDataOffset+2] << 16) | (boxreflect_dll[sectionPointerToRawDataOffset+1] << 8) | boxreflect_dll[sectionPointerToRawDataOffset];
    
                    if  (symbolRVA > sectionVirtualAddress && (symbolRVA < sectionVirtualAddress + sectionSizeOfRawData) ) {
                        DWORD symbolFileOffset = ( symbolRVA - sectionVirtualAddress ) + sectionPointerToRawData;
                        printf("     [*] [0x%04x] %-30s : 0x%08X\n", symbolRVA, "[symbolFileOffset]", symbolFileOffset);
                        break;
                    }
                    tempSectionHeaderOffset += 0x28;
                }
    …

And the output:

Once we have the required Symbol File Offset, we will use windows API CreateRemoteThread to call this symbolFileOffset. We will perform the following steps in order to do that:

  1. Allocate space for executable memory buffer using VirtualAllocEx
  2. Write our boxreflect_dll buffer to that allocate space using WriteProcessMemory
  3. Get the current address for our symbol’s file offset (Allocated space + Symbol’s file offset)
  4. Get a handle of current Process using GetCurrentProcess and execute the reflective loader’s current address

Putting all of that inside the previous code for finding the file offset, it looks like this:

...
    if  (symbolRVA > sectionVirtualAddress && (symbolRVA < sectionVirtualAddress + sectionSizeOfRawData) ) {
        DWORD symbolFileOffset = ( symbolRVA - sectionVirtualAddress ) + sectionPointerToRawData;
        printf("     [*] [0x%04x] %-30s : 0x%08X\n", symbolRVA, "[symbolFileOffset]", symbolFileOffset);

        LPVOID boxreflectDllExectuableBuffer = VirtualAllocEx(GetCurrentProcess(), NULL, boxreflect_dll_len, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        WriteProcessMemory(GetCurrentProcess(), boxreflectDllExectuableBuffer, boxreflect_dll, boxreflect_dll_len, NULL);
        LPTHREAD_START_ROUTINE symbolExecutableAddress = (LPTHREAD_START_ROUTINE)( (ULONG_PTR)boxreflectDllExectuableBuffer + symbolFileOffset );
        DWORD lpThreadId;
        HANDLE hThread = CreateRemoteThread( GetCurrentProcess(), NULL, 1024*1024, symbolExecutableAddress, NULL, NULL, &lpThreadId);
        WaitForSingleObject(hThread, INFINITE);
        break;
    }
    tempSectionHeaderOffset += 0x28;
...

And finally the output:

Similar to this, we can also parse the Import Address Tables as well, but I will not be delving into that since it’s not as important as extracting the exported symbol’s address for a PE loader. In the next blog, we will be taking a look into Packers, Stubs and changing our PE headers and fields to hide it from EDR detections.