This shellcode was originally written by Giuseppe D'Amore and I have edited it to print out "HelloWorld!
".
It searches for the API Fatal Application Exit
in the Export Table of kernel32
and then executes it.
In order to carve more information about the following structures (often opaque and not well documented), I suggest you to use Windbg from the Windows SDK. Its dt
feature will be used throughout the following explanation.
It is recommended to know the basics of the x86 assembly language to fully enjoy this write-up. This document may come in handy.
0: 31 d2 xor edx,edx
2: b2 30 mov dl,0x30
4: 64 8b 12 mov edx,DWORD PTR fs:[edx]
7: 8b 52 0c mov edx,DWORD PTR [edx+0xc]
a: 8b 52 1c mov edx,DWORD PTR [edx+0x1c]
d: 8b 42 08 mov eax,DWORD PTR [edx+0x8]
10: 8b 72 20 mov esi,DWORD PTR [edx+0x20]
13: 8b 12 mov edx,DWORD PTR [edx]
15: 80 7e 0c 33 cmp BYTE PTR [esi+0xc],0x33
19: 75 f2 jne 0xd
1b: 89 c7 mov edi,eax
1d: 03 78 3c add edi,DWORD PTR [eax+0x3c]
20: 8b 57 78 mov edx,DWORD PTR [edi+0x78]
23: 01 c2 add edx,eax
25: 8b 7a 20 mov edi,DWORD PTR [edx+0x20]
28: 01 c7 add edi,eax
2a: 31 ed xor ebp,ebp
2c: 8b 34 af mov esi,DWORD PTR [edi+ebp*4]
2f: 01 c6 add esi,eax
31: 45 inc ebp
32: 81 3e 46 61 74 61 cmp DWORD PTR [esi],0x61746146
38: 75 f2 jne 0x2c
3a: 81 7e 08 45 78 69 74 cmp DWORD PTR [esi+0x8],0x74697845
41: 75 e9 jne 0x2c
43: 8b 7a 24 mov edi,DWORD PTR [edx+0x24]
46: 01 c7 add edi,eax
48: 66 8b 2c 6f mov bp,WORD PTR [edi+ebp*2]
4c: 8b 7a 1c mov edi,DWORD PTR [edx+0x1c]
4f: 01 c7 add edi,eax
51: 8b 7c af fc mov edi,DWORD PTR [edi+ebp*4-0x4]
55: 01 c7 add edi,eax
57: 68 6c 64 21 01 push 0x121646c
5c: 68 6f 57 6f 72 push 0x726f576f
61: 68 48 65 6c 6c push 0x6c6c6548
66: 89 e1 mov ecx,esp
68: fe 49 0b dec BYTE PTR [ecx+0xb]
6b: 31 c0 xor eax,eax
6d: 51 push ecx
6e: 50 push eax
6f: ff d7 call edi
Shellcodes need to be as small as possible and they subsequently resort to neat techniques to obtain system information.
Spawning the message box through the Fatal Application Exit
WINAPI means that some steps have to be accomplished first:
- Get the
kernel32.dll
address sinceFatal Application Exit
is exported by this DLL - Get the position of
Fatal Application Exit
in the Export Table - Get the address of
Fatal Application Exit
- Call
Fatal Application Exit
The first thing the shellcode does is to save fs:[0x30]
into edx
.
0: 31 d2 xor edx,edx
2: b2 30 mov dl,0x30
4: 64 8b 12 mov edx,DWORD PTR fs:[edx] ; edx points to PEB
This is because in the FS
segment, under the Windows OS, reside several thread specific information: specifically, the structure which holds everything together is called TEB (Thread Environment Block)
.
0:000> dt ntdll!_TEB
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : Ptr32 Void
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : Ptr32 Void
+0x02c ThreadLocalStoragePointer : Ptr32 Void
+0x030 ProcessEnvironmentBlock : Ptr32 _PEB
+0x034 LastErrorValue : Uint4B
+0x038 CountOfOwnedCriticalSections : Uint4B
+0x03c CsrClientThread : Ptr32 Void
[snip...]
It is easily observable the TEB
contains lots of information about the running thread and it may also be useful to accomplish something else: the TEB
comes in handy when it is needed to retrieve details of the current process without having to resort to any WINAPI.
Moving on, what is there at offset 0x30
of the TEB
?
0:000> dt ntdll!_PEB
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x003 BitField : UChar
+0x003 ImageUsesLargePages : Pos 0, 1 Bit
+0x003 IsProtectedProcess : Pos 1, 1 Bit
+0x003 IsImageDynamicallyRelocated : Pos 2, 1 Bit
+0x003 SkipPatchingUser32Forwarders : Pos 3, 1 Bit
+0x003 IsPackagedProcess : Pos 4, 1 Bit
+0x003 IsAppContainer : Pos 5, 1 Bit
+0x003 IsProtectedProcessLight : Pos 6, 1 Bit
+0x003 SpareBits : Pos 7, 1 Bit
+0x004 Mutant : Ptr32 Void
+0x008 ImageBaseAddress : Ptr32 Void
+0x00c Ldr : Ptr32 _PEB_LDR_DATA
+0x010 ProcessParameters : Ptr32 _RTL_USER_PROCESS_PARAMETERS
+0x014 SubSystemData : Ptr32 Void
+0x018 ProcessHeap : Ptr32 Void
+0x01c FastPebLock : Ptr32 _RTL_CRITICAL_SECTION
+0x020 AtlThunkSListPtr : Ptr32 Void
[snip...]
It is a pointer to the PEB
structure, another fundamental data structure. From scape's paper:
The PEB structure holds information about the process’ heaps, binary image information, and, most importantly, three linked lists regarding loaded modules that have been mapped into process space.
Again, just like the TEB
, the PEB
holds several nice pointers: for example, the ImageBaseAddress
at offset 0x8
and the Ldr
at offset 0xc
. The latter is used by the shellcode to access the _PEB_LDR_DATA
structure:
7: 8b 52 0c mov edx,DWORD PTR [edx+0xc] ; edx points to Ldr
This is how it is composed:
0:000> dt ntdll!_PEB_LDR_DATA
+0x000 Length : Uint4B
+0x004 Initialized : UChar
+0x008 SsHandle : Ptr32 Void
+0x00c InLoadOrderModuleList : _LIST_ENTRY
+0x014 InMemoryOrderModuleList : _LIST_ENTRY
+0x01c InInitializationOrderModuleList : _LIST_ENTRY
+0x024 EntryInProgress : Ptr32 Void
+0x028 ShutdownInProgress : UChar
+0x02c ShutdownThreadId : Ptr32 Void
There are three doubly-linked lists (LIST_ENTRY) which represent every module, such as a library, loaded by the process in that very moment.
InLoadOrderModuleList
: built following the order in which the modules are loadedInMemoryOrderModuleList
: built following the order in which the modules are present in memoryInInitializationOrderModuleList
: built following the order in which the modules are initialized
Here the shellcode accesses to the head of the InInitializationOrderModuleList
which points to a LDR_DATA_TABLE_ENTRY
according to the MSDN.
It has to be noted that there is a one to one correspondence between the _LIST_ENTRY
fields of the two structures: the InInitializationOrderModuleList
points to the InInitializationOrderLinks
entry of the _LDR_DATA_TABLE_ENTRY
structure at offset 0.
0:000> dt ntdll!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY
+0x008 InMemoryOrderLinks : _LIST_ENTRY
+0x010 InInitializationOrderLinks : _LIST_ENTRY
+0x010 InProgressLinks : _LIST_ENTRY
+0x018 DllBase : Ptr32 Void
+0x01c EntryPoint : Ptr32 Void
+0x020 SizeOfImage : Uint4B
+0x024 FullDllName : _UNICODE_STRING
+0x02c BaseDllName : _UNICODE_STRING
+0x034 FlagGroup : [4] UChar
+0x034 Flags : Uint4B
+0x034 PackagedBinary : Pos 0, 1 Bit
+0x034 MarkedForRemoval : Pos 1, 1 Bit
+0x034 ImageDll : Pos 2, 1 Bit
+0x034 LoadNotificationsSent : Pos 3, 1 Bit
+0x034 TelemetryEntryProcessed : Pos 4, 1 Bit
+0x034 ProcessStaticImport : Pos 5, 1 Bit
+0x034 InLegacyLists : Pos 6, 1 Bit
+0x034 InIndexes : Pos 7, 1 Bit
+0x034 ShimDll : Pos 8, 1 Bit
+0x034 InExceptionTable : Pos 9, 1 Bit
+0x034 ReservedFlags1 : Pos 10, 2 Bits
[snip...]
The next thing the shellcode does is to iterate over the list in order to check every module's BaseDllName
buffer:
a: 8b 52 1c mov edx,DWORD PTR [edx+0x1c] ; Load InInitializationOrderModuleList next object
->d: 8b 42 08 mov eax,DWORD PTR [edx+0x8] ; eax = DllBase
| 10: 8b 72 20 mov esi,DWORD PTR [edx+0x20] ; esi = BaseDllName.Buffer
| 13: 8b 12 mov edx,DWORD PTR [edx] ; edx points to the next object
| 15: 80 7e 0c 33 cmp BYTE PTR [esi+0xc],0x33 ; FullDllName.Buffer[0xC] == "3"
|-19: 75 f2 jne 0xd
edx
is used as buffer to hold the current list position, eax
holds the .dll
address base which will be loaded later and esi
points to the start of the name of the .dll
. Note that the _UNICODE_STRING
type is not directly a buffer:
0:000> dt ntdll!_UNICODE_STRING
+0x000 Length : Uint2B
+0x002 MaximumLength : Uint2B
+0x004 Buffer : Ptr32 Uint2B
When a "3"
is found at position 0xC
or 12
it means the base address of kernel32.dll
has been successfully recovered. A little thing has to be noted here, though: the character "3"
is not located at position 6
because the string is stored in memory with spaces between each letter.
0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f|.|.|.|.|.|.|.|
k| |e| |r| |n| |e| |l| |3| |2| |.| |d| |l| |l|
In the next paragraph, this image will significantly help to keep the orientation in kernel32.dll
. It is also suggested to read the official Microsoft PE and COFF objects specification document.
The interesting offsets to keep an eye on are the following:
0x3c
: PE Header0x78
: Relative Virtual Address (RVA) of the Export Directory Table
The Export Directory Table plays a central role in determining the Fatal Application Exit
address. From Microsoft's specification document it is described that:
The export symbol information begins with the export directory table, which describes the remainder of the export symbol information. The export directory table contains address information that is used to resolve imports to the entry points within this image.
The Export Directory Table composition is as follows:
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; // 0x0
DWORD TimeDateStamp; // 0x4
WORD MajorVersion; // 0x8
WORD MinorVersion; // 0xA
DWORD Name; // 0xC
DWORD Base; // 0x10
DWORD NumberOfFunctions; // 0x14
DWORD NumberOfNames; // 0x18
DWORD AddressOfFunctions; // 0x1C - Export Address Table
DWORD AddressOfNames; // 0x20
DWORD AddressOfNameOrdinals; // 0x24
}
In the Export Directory Table, located at offset 0x78
from the start of the .dll
, there is the Export Address Table which, from the specification document:
contains the address of exported entry points and exported data and absolutes. An ordinal number is used as an index into the export address table.
The ordinal of Fatal Application Exit
is calculated by finding the offset of the WINAPI
name in the AddressOfNames
array of the Export Directory Table. Once the ordinal is found, it will be used to retrieve the entrypoint of Fatal Application Exit
by reading the corresponding entry in the AddressOfFunctions
array which represents the Export Address Table.
Let's recap the steps:
- Access the Export Directory Table
- Calculate the ordinal of
Fatal Application Exit
- Access the Export Address Table with the newfound ordinal
- Call
Fatal Application Exit
Steps 1 and 2 are executed below:
; eax = Kernel32 base address
1b: 89 c7 mov edi,eax ; edi = Kernel32 base address
1d: 03 78 3c add edi,DWORD PTR [eax+0x3c] ; Moving edi forward to the start of the PE Header
20: 8b 57 78 mov edx,DWORD PTR [edi+0x78] ; edx = rva of the Export Directory
23: 01 c2 add edx,eax ; edx = absolute address of the Export Directory
25: 8b 7a 20 mov edi,DWORD PTR [edx+0x20] ; edi = rva of names array
28: 01 c7 add edi,eax ; edi = absolute address of names' array
2a: 31 ed xor ebp,ebp ; ebp = index of Fatal Application Exit
->2c: 8b 34 af mov esi,DWORD PTR [edi+ebp*4] ;
| 2f: 01 c6 add esi,eax ;
| 31: 45 inc ebp ; i++
| 32: 81 3e 46 61 74 61 cmp DWORD PTR [esi],0x61746146 ; Fata
|-38: 75 f2 jne 0x2c
| 3a: 81 7e 08 45 78 69 74 cmp DWORD PTR [esi+0x8],0x74697845 ; Exit
|-41: 75 e9 jne 0x2c
Now that the ordinal for FatalAppExit
is known, it is used to retrieve the absolute address of the WINAPI
which can be found at position #ordinal
in the AddressOfFunctions
in the Export Table.
As final step, the HelloWorld!
message is pushed onto the stack and FatalAppExit
called!
; edx = absolute address of the Export Directory
; ebp = index of
43: 8b 7a 24 mov edi,DWORD PTR [edx+0x24] ; edi = rva of ordinals' array
46: 01 c7 add edi,eax ; edi = absolute address of ordinals' array
48: 66 8b 2c 6f mov bp,WORD PTR [edi+ebp*2] ; bp = ordinal of Fatal Application Exit
4c: 8b 7a 1c mov edi,DWORD PTR [edx+0x1c] ; edi = rva of functions' addresses array
4f: 01 c7 add edi,eax ; edi = absolute address of functions' addresses array
51: 8b 7c af fc mov edi,DWORD PTR [edi+ebp*4-0x4] ; edi = rva of Fatal Application Exit
55: 01 c7 add edi,eax ; edi = absolute value of Fatal Application Exit
57: 68 6c 64 21 01 push 0x121646c ;
5c: 68 6f 57 6f 72 push 0x726f576f ; HelloWorld!
61: 68 48 65 6c 6c push 0x6c6c6548 ;
66: 89 e1 mov ecx,esp ; ecx points to our message
68: fe 49 0b dec BYTE PTR [ecx+0xb] ; making last char == 0x00
6b: 31 c0 xor eax,eax ;
6d: 51 push ecx ; pushing lpMessageText
6e: 50 push eax ; pushing uAction
6f: ff d7 call edi ; WIN!