---
description: >-
  Symbol resolution is the process of associating function names with
  corresponding memory addresses, without which we cannot make the necessary API
  calls.
---

# Resolving Symbols

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.

## Using Windbg

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:

<div align="left"><figure><img src="https://1133556953-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F6fSnuRpvF32xyL2X75m5%2Fuploads%2FHoOIxFanbocnUaK2uq41%2Flocate-eat.png?alt=media&#x26;token=726dfd5f-1158-4495-b55b-be2ad395d529" alt=""><figcaption><p>Locating the AddressOfNames table</p></figcaption></figure></div>

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`:

{% code lineNumbers="true" %}
```
0:000> da kernel32
00007ffc`c5f90000  "MZ."
```
{% endcode %}

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:

{% code lineNumbers="true" %}
```
0:000> db kernel32+3c L1
00007ffc`c5f9003c  f8                                               
0:000> da kernel32+f8
00007ffc`c5f900f8  "PE"
```
{% endcode %}

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:

{% code lineNumbers="true" %}
```
0:000> dd kernel32+f8+88 L1
00007ffc`c5f90180  0009a370
```
{% endcode %}

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:

```
0:000> dd kernel32+9a370+14 L1
00007ffc`c602a384  00000661
```

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:

{% code lineNumbers="true" %}
```
0:000> dd kernel32+9a370+20 L1
00007ffc`c602a390  0009bd1c
```
{% endcode %}

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:

{% code lineNumbers="true" %}
```
0:000> dd kernel32+9bd1c
00007ffc`c602bd1c  0009e36f 0009e3a8 0009e3db 0009e3ea
00007ffc`c602bd2c  0009e3ff 0009e408 0009e411 0009e422
00007ffc`c602bd3c  0009e433 0009e478 0009e49e 0009e4bd
00007ffc`c602bd4c  0009e4dc 0009e4e9 0009e4fc 0009e514
00007ffc`c602bd5c  0009e52f 0009e544 0009e561 0009e5a0
00007ffc`c602bd6c  0009e5e1 0009e5f4 0009e601 0009e61b
00007ffc`c602bd7c  0009e639 0009e670 0009e6b5 0009e700
00007ffc`c602bd8c  0009e75b 0009e7b0 0009e803 0009e858
0:000> da kernel32+9e36f
00007ffc`c602e36f  "AcquireSRWLockExclusive"
0:000> da kernel32+9e3a8
00007ffc`c602e3a8  "AcquireSRWLockShared"
```
{% endcode %}

Now we understand how to resolve symbols in a module we can write the shellcode.

## 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.

### AddressOfNames

The shellcode is shown below:

{% code title="Resolve symbols" lineNumbers="true" %}
```nasm
; RBX = Base address of KERNEL32
; R9  = First 8 bytes of the function we want to find 
get_function:
    xor r8, r8                      ; R8 = 0
    mov r8d, [rbx + 0x3c]           ; R8D = DOS->e_lfanew offset
    mov rdx, r8                     ; RDX = DOS->e_lfanew
    add rdx, rbx                    ; RDX = PE Header

    add rdx, 0x88                   ; add 0x88 to RDX to avoid null bytes

    mov r8d, [rdx]                  ; R8D = Offset to EAT
    add r8, rbx                     ; R8 = EAT
    
    xor rsi, rsi                    ; Clear RSI
    mov esi, [r8 + 0x20]            ; RSI = Offset AddressOfNames table
    add rsi, rbx                    ; RSI = AddressOfNames table
    xor rcx, rcx                    ; RCX = 0, this is used as the ordinal/index
```
{% endcode %}

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.

### Next Function Name Loop

The next piece of shellcode loops over the `AddressOfNames` table

{% code title="Next function loop" lineNumbers="true" %}
```nasm
next_function_name:
    inc rcx                         ; Increment the ordinal
    
    xor rax, rax                    ; RAX = 0
    mov eax, [rsi + rcx * 4]        ; Get string offset (function name)
    add rax, rbx                    ; RAX = function name
    
    cmp qword [rax], r9             ; Does it match the function name in R9 ?
    jnz next_function_name          ; Loop if it doesn't
```
{% endcode %}

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.

### Calling GetFunction

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`:

{% code title="Calling GetFunction" lineNumbers="true" %}
```nasm
get_getprocaddress:
    mov r9, 0x41636f7250746547      ; GetProcA (in ASCII AcorPteG)
    call QWORD [rbp-0x20]           ; CALL get_function
    mov [rbp-0x18], rdi             ; [RBP-0x18] = *GetProcAddress
```
{% endcode %}

**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.
