Altering Msfvenom Exec Payload to Work Without an ExitFunc

4 minute read

On a few occasions as of late, I’ve wanted to use the windows[/x64]/exec payload from msfvenom, but with the goal of:

  1. Allowing execution to continue afterwards
  2. Executing in a single threaded environment
  3. Executing without an exception handler

Unfortunately, when setting the EXITFUNC option to none, no code is generated to allow execution to continue as normal after the payload has been executed, and ultimately results in an access violation.

The example I’ll be going over in this post is specifically the x64 version of the exec payload, which differs slightly to the x86 version. Most notably, the calling convention is different, the x86 version will see all arguments pushed on to the stack, but the x64 version will put the first few arguments into registers.

To generate the base shellcode I’ll be working with, I ran: msfvenom -p windows/x64/exec CMD='cmd.exe /k "net user /add Disecurity1! && net localgroup administrators /add"' EXITFUNC=none

What Causes the Problem

The exec payload uses the WinExec function to run the command specified in the CMD option.

To push the command text specified in the CMD option on to the stack, it is defined as raw bytes at the end of the payload, preceded by a call instruction:

When call is used, the address of the next instruction is then pushed on to the stack, so the execution flow can return to the correct place later. In the context of shellcoding, the pointer to the next instruction is effectively a pointer to the string that has been defined in place, and avoids the need for N amount of push instructions.

If we were to take the first few bytes that appear after call rbp in the above screenshot and convert them to ASCII, we can see that it is the equivalent of cmd.exe /k:

$ echo -e "\x63\x6d\x64\x2e\x65\x78\x65\x20\x2f\x6b"
cmd.exe /k

Eventually, the execution of the payload will end up being passed to these bytes, which will lead to an error at some point, as the bytes are nonsensical in terms of actual operations.

The Solution

The solution to this issue is quite simple. The ultimate goal will be to add some extra bytes before the command text bytes which will instruct the program to jump past the command text bytes so that normal operations can continue.

As you may be anticipating, doing this will cause an issue in that extra bytes will precede the command text in the call to WinExec. To avoid this becoming an issue, an additional change will need to be made to ensure the pointer used for the lpCmdLine argument is increased by an appropriate number of bytes, so that it points ahead of the new bytes being added.

The change to the command text area can be seen in the screenshot below:

Four new bytes have been added after call rbp, the first two are are for a jmp operation to jump forward past the in place bytes, and the subsequent two bytes are just two NOPs to visually show the separation.

With the new code in place to ensure the command text bytes are never executed, the change to offset the lpCmdLine pointer can be made.

The point at which WinExec is invoked is at the jmp rax operation. At this point, the command being executed will be stored in the rcx register. The code block to look for is:

pop  r8              ; 4158
pop  r8              ; 4158
pop  rsi             ; 5E
pop  rcx             ; 59
pop  rdx             ; 5A
pop  r8              ; 4158
pop  r9              ; 4159
pop  r10             ; 415A
sub  rsp,byte +0x20  ; 4883EC20
push r10             ; 4152
jmp  rax             ; FFE0

As 4 bytes will be added before jmp rax to make the adjustment, and 4 bytes were added to jump over the command text, rcx needs to be adjusted by 8 bytes. To do this, add rcx, 0x8 is inserted before jmp rax:

push r10             ; 4152
add  rcx, byte +0x8  ; 4883C108
jmp  rax             ; FFE0

Fixing Relative Jumps

The simple solution now becomes a bit more painful. Adding the adjustment to the rcx register causes a shift in a number of offsets by 4 bytes.

Thankfully, the visual aid on the left side of the screen in x64dbg makes it a bit easier to identify affected jumps by showing where they would land if taken.

Any jump or call instruction that has an offset that went forward past jmp rax will need to have its offset increased by 4, where as any jump or call that went backwards will need to have its offset decreased by 4.

A total of 4 operations were found that needed to be changed:

  • E8 C0 (call 0xca) changes to E8 C4
  • 74 67 (jz 0xbf) changes to 74 6B
  • E3 56 (jrcxz 0xbe) changes to E3 5A
  • The one jump backwards found towards the end of the payload changes from E9 57 FF FF FF to E9 53 FF FF FF

Wrapping Up & Testing

Now that all the changes have been made, checking the value of rcx when sitting on the jmp rax instruction should show it pointing to the start of the command text bytes:

After the call to WinExec, the execution should eventually return to the short jump that was added before the command text bytes, which should jump straight over the entirety of the command text:

What you place after the command text bytes is completely up to you. In this case, I placed a NOP sled, a stack adjustment and a call into some existing code.

After making these changes, the final payload (minus the additions after the final NOP sled) looks like this: