Resolving Symbols
Symbol resolution is the process of associating function names with corresponding memory addresses, without which we cannot make the necessary API calls.
Last updated
Symbol resolution is the process of associating function names with corresponding memory addresses, without which we cannot make the necessary API calls.
Last updated
As exploit developers, discovering the Virtual Memory Addresses (VMAs) of functions within a DLL module involves enumerating its DOS and PE headers. These headers provide essential information about the module's executable file structure. By analysing these headers, we can locate the precise VMAs of functions, enabling effective vulnerability identification and exploitation.
The first step in finding the VMAs of functions is to find the index of those functions, to do that we need to find the name of the function in the Export Address Table (EAT).
We will do this using Windbg first, so it is clear what the shellcode is doing.
The EAT is a data structure found in the Portable Executable (PE) file format used by Windows operating systems. It is part of the DLL file and contains a list of exported functions and their corresponding memory addresses.
The EAT acts as a lookup table, allowing other modules or executables to easily access and utilise the functions provided by the DLL. When a DLL is loaded, the EAT provides a mechanism for resolving function addresses dynamically at runtime. This enables the calling module to invoke the DLL's exported functions without needing to know their memory addresses in advance.
The EAT also includes the names of the exported functions, which facilitates the identification and usage of specific functions within the DLL. By referencing the EAT, programs can dynamically link to and utilize the functions provided by the DLL, enhancing code modularity, reusability, and extensibility.
In the previous section we found the base address of kernel32.dll
, we can use this address to find the EAT and the AddressOfNames
linked list:
Load up any executable in Windbg, such as Notepad. When the program breaks we can input the following commands.
We can locate the AddressOfNames
table manually in Windbg, by finding the DOS Header in kernel32.dll
:
The DOS Header begins at offset 0x00
in the module (the signature is "MZ"). The next field of interest is an offset to the PE Header which is stored in e_lfanew
field at an offset of 0x3c
from the module base address. We can find this offset and then check it against the PE Header signature:
At an offset of 0x88
from the PE header we will find another offset, this is the offset to the ETA. We refer to this as the ETA VMA:
The number of ordinals (functions) in the ETA is recorded at offset 0x14
from the address of the ETA, we will use this in the next section:
The AddressOfNames
table offset is stored at offset 0x20
. This table is a sequential list of 4 byte offsets. The value that is stored in this entry is another offset, this time to the actual table:
The following depivts how we list the first 32 pointers to function names, then view the first two by using the base address and the offsets in the table:
Now we understand how to resolve symbols in a module we can write the shellcode.
Let's break this down in to two small sections of shellcode. First we locate the AddressOfNames
table, then we loop through the strings to find what we are looking for.
The shellcode is shown below:
The shellcode corresponds to what we carried out manually using Windbg, it:
Locates the PE signature (NT Header), on lines 4
through 7
.
Locates the _IMAGE_DATA_DIRECTORY
pointer by adding 0x88
to the address of the NT Header; line 9
.
Locates the EAT, on lines 11
and 11
.
Locates the AddressOfNames
table, lines 14
through 16
.
Sets the index for our loop to 0
on line 17
.
Essentially the shellcode ends with the address of the AddressOfNames
table in rsi
and is ready to traverse the table to find the function we require.
Note: Line 9
produces NULL bytes, this will be dealt with later.
The next piece of shellcode loops over the AddressOfNames
table
The shellcode carries out the following:
Increments the ordinal/index on line 2
.
Moves the next string offset in the AddressOfNames
table in to eax
(lower 8 bytes) on line 5
and adds the base address of the module (kernel32.dll
in rbx
) to rax
which gives us the address of the name; line 6
.
Line 8
compares the name we pass in via r9
to the current name in the table.
If they match then exit the loop, otherwise jump back to next_function_name
and loop.
At the end of the loop we should have the ordinal of the function address we require in rcx
. We will use this in the next section.
We can call the function using the following shellcode. Notice that we only put the first eight bytes of the function name we are looking for in to r9
, this is sufficient for our purposes (but be aware of this). Line 3
makes the call and line 4
stores the address of the found function in a variable offset from rbp
:
Note: we have not resolved the functions Virtual Memory Addresses yet (we have only found the ordinal/index of the function), this follows in the next section.