Finding VMAs

Upon obtaining the function ordinal, our next objective is to convert it into its corresponding Virtual Memory Address, as this is essential for invoking Win32 APIs.

In the last section we were able to find functions in a module by name, this is only the first step in resolving the Virtual Memory Address (VMA) of a function. In order to call a function we need to know its VMA.

A VMA is a concept used in computer systems, particularly in the context of virtual memory management. It refers to a unique address or location in the virtual memory space of a process.

Virtual memory allows programs to operate on a larger address space than the physical memory (RAM) available in the system.

The Export Address Table (EAT) contains three lists:

When we looped over the AddressOfNames table we found a match for our function name that we wanted to resolve. We can now find that index in the AddressOfNameOrdinals which will point to the AddressOfFunctions table which contains pointers to the VMA of that function.

WinDbg

We can continue from where we left off in the previous section (the addresses may have changed but the offsets are the same). We can see in the output below that third entry in the EAT is ActivateActCtx at offset 0x9e3e5 in the AddressOfNames table:

0:006> dd kernel32+9a370+20 L1
00007ffc`ebf4a390  0009bd20

0:006> dd kernel32+0009bd20
00007ffc`ebf4bd20  0009e379 0009e3b2 0009e3e5 0009e3f4
00007ffc`ebf4bd30  0009e409 0009e412 0009e41b 0009e42c
00007ffc`ebf4bd40  0009e43d 0009e482 0009e4a8 0009e4c7
00007ffc`ebf4bd50  0009e4e6 0009e4f3 0009e506 0009e51e
00007ffc`ebf4bd60  0009e539 0009e54e 0009e56b 0009e5aa
00007ffc`ebf4bd70  0009e5eb 0009e5fe 0009e60b 0009e625
00007ffc`ebf4bd80  0009e643 0009e67a 0009e6bf 0009e70a
00007ffc`ebf4bd90  0009e765 0009e7ba 0009e80d 0009e862

0:006> da kernel32+9e3e5
00007ffc`ebf4e3e5  "ActivateActCtx"

Next we can take a look at the AddressOfNameOrdinals table. We can see that the third entry is the same index that the ActivateActCtx is located at, so this is easy to resolve:

0:006> dd kernel32+9a370+24 L1
00007ffc`ebf4a394  0009d6a8

0:006> dw kernel32+9d6a8
00007ffc`ebf4d6a8  0000 0001 0002 0003 0004 0005 0006 0007
00007ffc`ebf4d6b8  0008 0009 000a 000b 000c 000d 000e 000f
00007ffc`ebf4d6c8  0010 0011 0012 0013 0014 0015 0016 0017
00007ffc`ebf4d6d8  0018 0019 001a 001b 001c 001d 001e 001f
00007ffc`ebf4d6e8  0020 0021 0022 0023 0024 0025 0026 0027
00007ffc`ebf4d6f8  0028 0029 002a 002b 002c 002d 002e 002f
00007ffc`ebf4d708  0030 0031 0032 0033 0034 0035 0036 0037
00007ffc`ebf4d718  0038 0039 003a 003b 003c 003d 003e 003f

Finally we can look at the AddressOfFunctions table at offset 0x1c. We can then look at the third entry to get the offset address of 0x1be90. If we uncompile this we find that we now have the VMA of KERNEL32!ActivateActCtxWorker:

0:006> dd kernel32+9a370+1c L1
00007ffc`ebf4a38c  0009a398

0:006> dd kernel32+9a398+0c L1
00007ffc`ebf4a3a4  0001be90

0:006> u kernel32+0001be90
KERNEL32!ActivateActCtxWorker:
00007ffc`ebecbe90 4883ec28        sub     rsp,28h
00007ffc`ebecbe94 4883f9ff        cmp     rcx,0FFFFFFFFFFFFFFFFh
00007ffc`ebecbe98 0f846a940100    je      KERNEL32!ActivateActCtxWorker+0x19478 (00007ffc`ebee5308)
00007ffc`ebecbe9e 4c8bc2          mov     r8,rdx
00007ffc`ebecbea1 488bd1          mov     rdx,rcx
00007ffc`ebecbea4 33c9            xor     ecx,ecx
00007ffc`ebecbea6 48ff157b790600  call    qword ptr [KERNEL32!_imp_RtlActivateActivationContext (00007ffc`ebf33828)]
00007ffc`ebecbead 0f1f440000      nop     dword ptr [rax+rax]

Now that we have resolved a VMA manually we can write some shellcode to do this automatically and on demand.

Shellcode

The shellcode to resolve the VMA of a function is shown below:

Found function
; RCX contains the ordinal of the function we want to resolve
; RBX = Base address of KERNEL32
; R8 = VMA of the Export Address Table
found_function:
    xor rsi, rsi                    ; RSI = 0
    mov esi, [r8 + 0x24]            ; ESI = Offset to AddressOfNameOrdinals
    add rsi, rbx                    ; RSI = VMA of AddressOfNameOrdinals
    
    mov cx, [rsi + rcx * 2]         ; The number of the function
    
    xor rsi, rsi                    ; RSI = 0
    mov esi, [r8 + 0x1c]            ; Offset to AddressOfFunctions
    add rsi, rbx                    ; RSI = VMA of AddressOfFunctions
    
    xor rdx, rdx                    ; RDX = 0
    mov edx, [rsi + rcx * 4]        ; EDX = Index of the function VMA needed
    add rdx, rbx                    ; RDX = Function Address
    mov rdi, rdx                    ; Save Function Address in RDI
    ret                             ;

The shellcode corresponds to what we carried out manually using Windbg, it:

  • Locates the AddressOfNamesOrdinals on lines 5 through 7.

  • Uses the table to find the number of the function on line 9. Notice that the table entries are 2 bytes in size.

  • Lines 11 through 13 are used to locate the AddressOfFunctions table.

  • Uses the table to locate the offset of the function on line 16.

  • Adds this offset to the base address of kernel32.dll on line 17.

  • Finally the address is stored in rdi and the function returns.

In the next section we will put everything we have learned together to create shellcode that displays a message box.

Last updated