Skip to main content
  1. posts/

Windows x64 Shellcode - Part 1

2417 words·12 mins· ·
Challenges Maldev
Anthony Alonso
Author
Anthony Alonso
A lazy (but curious) computer engineer
Table of Contents
Writing Custom Shellcode - This article is part of a series.
Part 1: This Article

Introduction
#

I joined the Maldev Academy almost 2 years ago because I thought it would be super cool to understand malware development and the techniques used to bypass detection by security solutions. After joining, I started completing the basic modules quickly, but my progress plateaued for a bit due to settling in at my first full-time job and finishing my Master’s classes.

A few months ago, I started my Master’s thesis, which aims to investigate antivirus evasion techniques, and the Academy has been an enormous help in that regard. While performing some evasion tests, I noticed that my payload (i.e. msfvenom generated shellcode that launches a calc instance) kept getting caught right before execution. After also trying the sgn encoded version with no success, I decided to write my own PoC shellcode.

Learning how to write shellcode has been on my list of things to learn, and I always thought it would be super cool to know. And hey, what a coincidence, writing custom shellcode is one of the Maldev Academy challenges ! So, I figured this would be the perfect opportunity to dive in.

The Goal
#

Since I have no idea how to write shellcode, I’ll start with something simple: spawn a message box using the MessageBoxA WinAPI. Right now, I don’t care about dynamically resolving the function or anything too fancy. I’ll write some assembly code to spawn a message box in this first part of the series.

Development
#

C Prototype
#

My first step was writing what I wanted to accomplish in C. From the docs , we can see that the MessageBoxA function is defined like so:

int MessageBoxA(
  [in, optional] HWND   hWnd,
  [in, optional] LPCSTR lpText,
  [in, optional] LPCSTR lpCaption,
  [in]           UINT   uType
);

This function is exported from the user32.dll. We need to load the target DLL and find the target function’s address. This can be accomplished using the small C program below.

int main() {
	HMODULE user32 = LoadLibraryA("user32.dll");
	printf("user32.dll address: %p\n", user32);

	PVOID messageboxa = GetProcAddress(user32, "MessageBoxA");
	printf("MessageBoxA address: %p\n", messageboxa);

	info("Calling 'SpawnMessageBox'...");
	MessageBoxA(NULL, "A secret message!", "My message box", MB_OK);
	info("Returned from 'SpawnMessageBox'...");

	return 0;
}

Executing this program produces the following:

MessageBoxA output
The output of calling MessageBoxA

I printed out the address of the target function so I could hardcode the address in the shellcode. Keep in mind that this is doable because I’m running on a VM and saving the state instead of shutting it down to avoid having ASLR change the address each time. I’ll dynamically resolve the target function’s address in the next post of this series. But for now, hardcoding the address will suffice.

Assembly Time
#

After going over the Introduction to MASM Assembly module from Maldev Academy and using some previous knowledge about assembly, I know that a basic, valid skeleton of an assembly program looks like the following:

.data

;
;   We can define variables here
;

.code

;
;   The actual code to be executed
;

end

Since we’re calling a function, we need to know how to pass arguments to it. The MessageBoxA function is simple enough to call: two strings and two number values. Now, how are we supposed to pass arguments to the function? Here is where the calling convention comes into play.

Calling Convention
#

In the case of x64 MASM code, the docs clearly state that the first four parameters of the function are passed in a left-to-right order in the following registers:

  1. RCX
  2. RDX
  3. R8
  4. R9

In the case of any remaining arguments, they have to be passed on the stack. Lucky for us, MessageBoxA accepts 4 parameters. Following the calling convention we found before, the arguments need to be present in the registers as shown below.

Register Argument
RCX NULL
RDX A secret message!
R8 My message box
R9 MB_OK

Stacking Up
#

Since shellcode only comes with executable code (from the .code section), other sections cannot be used. This means we won’t be able to store the strings in a variable (in the .data section). Instead, we’ll have to push them onto the stack.

A few things must be considered before pushing the strings onto the stack.

  1. Since we’re dealing with 64-bit shellcode, we have to deal with 8-byte values (i.e. addresses).
  2. 64-bit assembly does not have an instruction for pushing 64-bit immediates onto the stack. Instead, the value to be pushed needs to be copied to a register, and then the register’s contents need to be pushed onto the stack.
  3. The stack is a LIFO structure.
  4. Data (i.e. the strings in this case) needs to be in little-endian format. Also, don’t forget that strings are null-terminated!

Keeping this in mind, we can start writing assembly in the .code section. The first argument is NULL, which doesn’t require more than one line of assembly.

xor rcx, rcx	; NULL

The second argument is the message. Our message is a string of 18 bytes (17 characters + null-terminator). Remembering what was mentioned in the list above, the assembly I wrote for pushing the message onto the stack is presented below.

xor rcx, rcx
push rcx					; '\0'
push 21h					; '!'
mov rax, 6567617373656d20h	; 'egassem '
push rax
mov rax, 7465726365732041h	; 'terces A'
push rax

mov rdx, rsp	; 2nd parameter (message)

We will push the title string (14 bytes) onto the stack in a similar fashion as we did with the message.

xor rbx, rbx
push rbx					; '\0'
mov rax, 786f62206567h		; 'xob eg'
push rax
mov rax, 617373656d20794dh	; 'assem yM'
push rax

mov r8, rsp					; 3rd parameter (title)

The final argument is the button that will appear in the message box. The value of MB_OK expands to 0.

mov r9, 0	; 4th parameter (MB_OK = 0)

Calling the Target
#

Now that the function’s arguments have been set on the stack, we can call the function. To do this, we’ll call the hardcoded address found before: 0x00007FFEF39A8B70. However, we cannot just use the call instruction using an immediate value. There are a few ways to call the MessageBoxA function at that address, and the way I chose to do it for simplicity is to copy the address to a register and call the register (which is valid x64 MASM, as opposed to using an immediate value with call).

mov r10, 00007FFEF39A8B70h	; hardcoded address of MessageBoxA
call r10

First Run
#

To be able to execute this assembly code, we need to put it into a something that can be called. In assembly, this is called a procedure. We can define a procedure using the following syntax:

TheProcedureName PROC

	; assembly code corresponding to the procedure `TheProcedureName`

TheProcedureName ENDP

Using the code written so far, we end up with something like the following:

.data

.code

SpawnMessageBox PROC

; ======================================
;	Pushing the message onto the stack
; ======================================

	xor rcx, rcx
	push rcx					; '\0'
	push 21h					; '!'
	mov rax, 6567617373656d20h	; 'egassem '
	push rax
	mov rax, 7465726365732041h	; 'terces A'
	push rax

	mov rdx, rsp	; 2nd parameter (message)

; ====================================
;	Pushing the title onto the stack
; ====================================

	xor rbx, rbx
	push rbx					; '\0'
	mov rax, 786f62206567h		; 'xob eg'
	push rax
	mov rax, 617373656d20794dh	; 'assem yM'
	push rax

	mov r8, rsp		; 3rd parameter (title)

; =======================
;	Calling MessageBoxA
; =======================

	xor rcx, rcx	; 1st parameter (HWND = NULL)
	mov r9, 0		; 4th parameter (MB_OK = 0)

	mov r10, 00007FFEF39A8B70h	; hardcoded address of MessageBoxA
	call r10

	ret

SpawnMessageBox ENDP

Then, the C program must be changed to call the procedure SpawnMessageBox instead of the MessageBoxA function.

int main() {
	HMODULE user32 = LoadLibraryA("user32.dll");
	printf("user32.dll address: %p\n", user32);

	PVOID messageboxa = GetProcAddress(user32, "MessageBoxA");
	printf("MessageBoxA address: %p\n", messageboxa);

	info("Calling 'SpawnMessageBox'...");
	SpawnMessageBox();
	info("Returned from 'SpawnMessageBox'...");

	return 0;
}

The program appears to execute just fine until an exception is thrown in the ret instruction.

Exception thrown on ret instruction
An exception is thrown on the ret instruction, after clicking the ‘OK’ button on the message box.

I ran a quick search as to why the ret instruction could be throwing an access violation exception and it turns out that it’s due to some issues with the stack.

Revisiting the Stack
#

To exercise my debugging skills, I’ll debug this version of the program with x64dbg . I set breakpoints on the first and last assembly instructions.

Let’s look at the program’s state before executing the ret instruction since the exception is thrown while attempting to execute it.

State before ret instruction
State of the program right before executing the ret instruction.

I’ve marked the RSP register and the values we’ve pushed onto the stack. Now, if we look at the documentation , it states that the ret instruction “transfers program control to a return address located on the top of the stack”. At this moment, the stack pointer is pointing to the last value that we pushed, which is the title of the message box! Let’s take a look at the “address” that will be popped off the top of the stack.

Invalid address
As you may have guessed, the message box title string is not a valid address…

That explains the thrown exception: we’re trying to transfer program control to an invalid address!

We can fix this by setting up the stack frame. This is usually performed using a couple of simple instructions at the start and end of a procedure.

SpawnMessageBox PROC

	push rbp		; saving the value of rbp on the stack
	mov rbp, rsp	; setting rbp to the base of the stack frame

	; rest of the assembly code until the `ret` instruction

	mov rsp, rbp	; restore stack to original location (after pushing rbp)
	pop rbp			; restore the value of rbp

SpawnMessageBox ENDP

After applying these changes, the program should execute without any issues.

Another exception thrown
An exception is thrown when trying to restore the stack like we’re supposed to…?

Hmm… Back to the debugger. After running through the execution flow, I realized that the access violation exception is actually being thrown when we attempt to execute the call r10 instruction. Let’s take a look at the program state right before executing this function (I’ve marked the value of RSP).

Unaligned stack
Appears to be fine, right? …right?

Well, there’s another concept that I haven’t gone over yet, which is a concept known as stack alignment. Many sources describe this, but I’ll reference section 5.7.4 of The Art of 64-Bit Assembly, Volume 1 x86-64 Machine Organization and Programming by Randall Hyde. It’s clearly stated that before performing a call to code that uses Microsoft calling conventions (which we can safely assume is the case for MessageBoxA), the stack must be aligned on a 16-byte boundary. A basic way to check if the stack is aligned correctly is to xor the value of RSP with 10 (hexadecimal representation of 16). If it’s aligned, the result must be 0.

Let’s perform this check using the value of RSP shown in the image above, right before executing the call instruction.

$$ \texttt{00000001000FFA88} \oplus \texttt{10} = \texttt{8} $$

So, the problem is that the stack is not aligned correctly! Out of curiosity, why didn’t this happen before? Well, let’s take the value of RSP from the first screenshot in this section .

$$ \texttt{000000592453F980} \oplus \texttt{10} = \texttt{0} $$
A trick you can use to easily tell if the result of the previously described operation will be 0 is to look at the last digit of the hexadecimal value. If it’s 0, the result of the xor operation will be zero.

This means that we unintentionally had “aligned” the stack before, and adding the stack frame instructions “unaligned” it. The question now is: how do we align the stack on a 16 byte boundary, and why do we have to do it?

In MASM, there is a concept known as shadow space . Turns out that it’s our responsibilty as the caller to allocate shadow space for the callee, regardless of whether the callee actually uses the space. The shadow space must be at least 32-bytes in size (four 8-byte registers which hold the first four arguments the callee receives). A good resource for visualizing this here .

For our program, we need to allocate at least 32 bytes of shadow space. Note that if we just allocate 32 bytes, the stack will still not be aligned. Instead of just allocating 32 bytes, we’ll allocate 40.

SpawnMessageBox PROC

	push rbp		; saving the value of rbp on the stack
	mov rbp, rsp	; setting rbp to the base of the stack frame
	sub rsp, 40		; reserving shadow space

	; rest of the assembly code until...

	call r10

	add rsp, 40		; done with shadow space
	mov rsp, rbp	; restore stack to original location (after pushing rbp)
	pop rbp			; restore the value of rbp
	ret				; return to the caller

Now let’s run the program again and hope there are no issues.

Final Run
#

Final run
The message box spawns and the program exits with no errors!

Great, we finally got a working version of assembly that executes the MessageBoxA! As I mentioned before, this is a basic version, and there are things that need to be improved/fixed. In the next post, I’ll focus on dynamically resolving the MessageBoxA function instead of using a hardcoded address.

The final assembly code
option casemap:none
.data

.code

public SpawnMessageBox

SpawnMessageBox PROC

	push rbp		; saving the value of rbp on the stack
	mov rbp, rsp	; setting rbp to the base of the stack frame
	sub rsp, 40		; reserving shadow space

; ======================================
;	Pushing the message onto the stack
; ======================================

	xor rcx, rcx
	push rcx					; '\0'
	push 21h					; '!'
	mov rax, 6567617373656d20h	; 'egassem '
	push rax
	mov rax, 7465726365732041h	; 'terces A'
	push rax

	mov rdx, rsp	; 2nd parameter (message)

; ====================================
;	Pushing the title onto the stack
; ====================================

	xor rbx, rbx
	push rbx					; '\0'
	mov rax, 786f62206567h		; 'xob eg'
	push rax
	mov rax, 617373656d20794dh	; 'assem yM'
	push rax

	mov r8, rsp		; 3rd parameter (title)

; =======================
;	Calling MessageBoxA
; =======================

	xor rcx, rcx	; 1st parameter (HWND = NULL)
	mov r9, 0		; 4th parameter (MB_OK = 0)

	mov r10, 00007FFEF39A8B70h	; hardcoded address of MessageBoxA
	call r10


	add rsp, 40		; restore stack
	mov rsp, rbp	; restore stack to original location (after pushing rbp)
	pop rbp			; restore the value of rbp
	ret				; return to the caller


SpawnMessageBox ENDP


end
Writing Custom Shellcode - This article is part of a series.
Part 1: This Article