aidenpearce369
Published on

Windows x64 - Message Box Shellcode (Static)

Prologue

In the previous blog post, we delved into the fundamental concepts of Windows shellcoding, exploring the intricacies of crafting code snippets that manipulate a program's behavior at its core. From understanding the role of shellcode in cybersecurity to unraveling the essential mechanisms of system libraries, we embarked on a journey to unlock the potential of low-level programming. Building upon this foundation, in our current blog, we're about to embark on a deeper exploration. We'll unravel the art of creating sophisticated shellcode by leveraging the power of dynamic link libraries (DLLs).

The user32.dll

The user32.dll is a core dynamic link library (DLL) in the Windows operating system that plays a pivotal role in providing essential functions for creating and managing the user interface of graphical applications. It serves as a bridge between applications and the Windows graphical environment, enabling developers to create interactive and visually appealing user interfaces.

This DLL is used extensively by virtually all graphical applications in the Windows environment. Graphical user interface (GUI) applications, including desktop applications, system utilities, and games, rely on this DLL to provide the foundation for their user interfaces. Developers call functions from user32.dll to create windows, manage controls, respond to user input, and create interactive user experiences. Its vital role is to provide support for the below.

  1. Window Management: user32.dll provides functions for creating, managing, and manipulating windows, including creating new windows, resizing, moving, minimizing, maximizing, and closing them. It also handles window messages and events, enabling applications to respond to user input.
  2. GUI Controls: The DLL offers functions to create and manage various graphical controls and components such as buttons, text boxes, list views, combo boxes, and menus. These controls are crucial for building interactive user interfaces.
  3. Message Handling: user32.dll processes and dispatches messages related to user input events (e.g., mouse clicks, keyboard input) to the appropriate windows and controls. This allows applications to respond to user actions.
  4. User Input: Functions in user32.dll capture user input from devices like keyboards, mice, and touch screens. Developers can use these functions to gather input data from users and respond accordingly.
  5. Clipboard and Data Sharing: user32.dll offers functions for managing the clipboard, allowing applications to copy, cut, and paste data between different windows and applications.
  6. Dialog Boxes Functions in the DLL enable the creation and management of dialog boxes for displaying information, prompting users for input, and providing various options.
  7. Accessibility and UI Customization: user32.dll supports accessibility features by providing functions that allow applications to make their user interfaces more accessible to individuals with disabilities. It also supports UI customization through themes and visual styles.

Most of the WIN APIs malware use to interact with user interaction comes from user32.dll.

The Desired Code

Now lets use the power of user32.dll to display a message popup.

#include "Windows.h"

int main()
{
	MessageBoxA(0, "aidenpearce369 pwned you", "0xhacked", 0);
	return 0;
}

CodeExecution

But here is a catch. If we generate a shellcode with addresses of WinExec and MessageBoxA. It will only spawn the calculator, not the message box. The reason is, the process hosting the shellcode will have kernel32.dll already loaded within it. But it may or maynot have user32.dll in it. Assuming it may not have that DLL, the shellcode should take the burden of loading the user32.dll into the process so that it can map the DLL within the process and call the required function from it.

A workaround is there to make it happen. A WIN API named LoadLibraryA from kernel32.dll can be invoked to load any available DLL from the PATH into the process.

LoadLibraryA

If you use LoadLibraryA to dynamically load a DLL at runtime, the loaded DLL will not be listed in the PE (Portable Executable) file's import table. The import table typically lists the DLLs that are statically linked during the compilation process or explicitly referenced using import statements in the source code.

When you use LoadLibraryA to dynamically load a DLL at runtime, the importing process is dynamic and occurs after the PE file has already been compiled and generated. Therefore, the import table won't reflect the dynamically loaded DLL.

DynamicMessageBoxA

By creating a type definition of the WIN API and invoking it as an object from the pointer address of MessageBoxA from the memory would give the object to execute MessageBoxA without static import of user32.dll. But the key point is, in order to call or invoke MessageBoxA, the user32.dll should be loaded into the process memory. Otherwise the PE could not parse the memory address.

When it comes to our shellcode, we just have to load the user32.dll into the PE and directly call the MessageBoxA with its static address.

Spawning MessageBoxA

To spawn the message box popup we just need to call the MessageBoxA from user32.dll. Before that, we need to parse our strings in 8 byte little endian format to push it into the stack.

I have written a python script which does that work for us.

#!/bin/python3
import sys

chunk_size = 8

def slice_text_into_pieces(text, chunk_size):
    chunks = [text[i:i+chunk_size] for i in range(0, len(text), chunk_size)]
    return chunks

if len(sys.argv) != 2:
    print("Usage: python3 stringParser.py <string>")
else:
    try:
        input_text = str(sys.argv[1])
        sliced_chunks = slice_text_into_pieces(input_text, chunk_size)
        print("[+] Sliced into 8 bytes")
        print(sliced_chunks)
        for x in sliced_chunks:
            print(x +" <==> 0x"+ bytearray.fromhex(x.encode("utf-8").hex())[::-1].hex())
        
    except:
        print("Error occured.")

Lets convert our string calc.exe to the required format.

ubuntu-wsl@ra:/mnt/e/Shellcoding$ python3 stringParse.py "0xhacked"
[+] Sliced into 8 bytes
['0xhacked']
0xhacked <==> 0x64656b6361687830
ubuntu-wsl@ra:/mnt/e/Shellcoding$ python3 stringParse.py "aidenpearce369 pwned you"
[+] Sliced into 8 bytes
['aidenpea', 'rce369 p', 'wned you']
aidenpea <==> 0x6165706e65646961
rce369 p <==> 0x7020393633656372
wned you <==> 0x756f792064656e77

All strings need a null byte \x00 character to terminate it, so that the CPU can understand where the string is being ended. It can be done by simple push or byte shift operation.

We need to load user32.dll into the PE memory via LoadLibraryA before we execute MessageBoxA. The documentation of LoadLibraryA WIN API describes that there is only one argument required to call this function.

HMODULE LoadLibraryA(
  [in] LPCSTR lpLibFileName
);

Assembly code for loading user32.dll.

.load_user32dll:
    xor rax, rax                ; Store RAX as 0 for reusing purpose
    push rax                    ; Push null byte into stack for string termination
    mov rbx, 0x6c6c414141414141 ; 'll' padded with 'AAAAAA' for avoiding null byte
    shr rbx, 48                 ; Right byte shift operation to remove 'AAAAAA'
    push rbx                    ; Pushing it into stack
    mov rbx, 0x642e323372657375 ; 'user32.d' 
    push rbx                    ; Pushing it into stack
    mov rcx, rsp                ; Storing the DllName - LPCSTR lpLibFileName
    mov rbx, 0x7FFA4E090C704141 ; LoadLibraryA address padded with 'AA'
    shr rbx, 16                 ; Right byte shift operation to remove 'AA'
    call rbx                    ; Call LoadLibraryA("user32.dll")

Now lets work on the MessageBoxA. The documentation of MessageBoxA WIN API describes that there are two arguments required to call this function.

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

Lets create the assembly code for spawning the message box popup.

.message_box:
    push rax                    ; Null byte for string termination
    mov rbx, 0x756f792064656e77 ; 'wned you'
    push rbx                    ; Pushing it into stack
    mov rbx, 0x7020393633656372 ; 'rce369 p'
    push rbx                    ; Pushing it into stack
    mov rbx, 0x6165706e65646961 ; 'aidenpea'
    push rbx                    ; Pushing it into stack
    mov rdx, rsp                ; Storing the LPCSTR lpText
    push rax                    ; Null byte for string termination
    mov rbx, 0x64656b6361687830 ; '0xhacked'
    push rbx                    ; Pushing it into stack
    mov r8, rsp                 ; Storing the LPCSTR lpCaption
    xor rcx, rcx                ; HWND hWnd = 0
    xor r9, r9                  ; UINT uType = 0
    mov rbx, 0x7FFA4EE990D04141 ; MessageBoxA address padded with 'AA'
    shr rbx, 16                 ; Right byte shift operation to remove 'AA'
    call rbx                    ; Call MessageBoxA(...)

Adding the ExitProcess(0) at the end to return generic exit code. Now our whole assembly code looks like this.

section .text
    global main

main:

.load_user32dll:
    xor rax, rax                ; Store RAX as 0 for reusing purpose
    push rax                    ; Push null byte into stack for string termination
    mov rbx, 0x6c6c414141414141 ; 'll' padded with 'AAAAAA' for avoiding null byte
    shr rbx, 48                 ; Right byte shift operation to remove 'AAAAAA'
    push rbx                    ; Pushing it into stack
    mov rbx, 0x642e323372657375 ; 'user32.d' 
    push rbx                    ; Pushing it into stack
    mov rcx, rsp                ; Storing the DllName - LPCSTR lpLibFileName
    mov rbx, 0x7FFA4E090C704141 ; LoadLibraryA address padded with 'AA'
    shr rbx, 16                 ; Right byte shift operation to remove 'AA'
    call rbx                    ; Call LoadLibraryA("user32.dll")

.message_box:
    push rax                    ; Null byte for string termination
    mov rbx, 0x756f792064656e77 ; 'wned you'
    push rbx                    ; Pushing it into stack
    mov rbx, 0x7020393633656372 ; 'rce369 p'
    push rbx                    ; Pushing it into stack
    mov rbx, 0x6165706e65646961 ; 'aidenpea'
    push rbx                    ; Pushing it into stack
    mov rdx, rsp                ; Storing the LPCSTR lpText
    push rax                    ; Null byte for string termination
    mov rbx, 0x64656b6361687830 ; '0xhacked'
    push rbx                    ; Pushing it into stack
    mov r8, rsp                 ; Storing the LPCSTR lpCaption
    xor rcx, rcx                ; HWND hWnd = 0
    xor r9, r9                  ; UINT uType = 0
    mov rbx, 0x7FFA4EE990D04141 ; MessageBoxA address padded with 'AA'
    shr rbx, 16                 ; Right byte shift operation to remove 'AA'
    call rbx                    ; Call MessageBoxA(...)

.exit_process:
    xor rcx, rcx                ; UINT uExitCode = 0
    mov rbx, 0x7FFA4E08E8204141 ; ExitProcess address padded with 'AA'
    shr rbx, 16                 ; Right byte shift operation to remove 'AA'
    call rbx                    ; Call ExitProcess(0)

Lets compile and get the shellcode for the asm like we did in our last post. From the objdump it can be observed that it does not contain any null bytes.

ubuntu-wsl@ra:/mnt/e/shellcoding$ objdump -d Pwned.exe >dump
ubuntu-wsl@ra:/mnt/e/shellcoding$ cat dump

Pwned.exe:     file format pei-x86-64


Disassembly of section .text:

0000000140001000 <.text>:
   140001000:   48 31 c0                xor    %rax,%rax
   140001003:   50                      push   %rax
   140001004:   48 bb 41 41 41 41 41    movabs $0x6c6c414141414141,%rbx
   14000100b:   41 6c 6c
   14000100e:   48 c1 eb 30             shr    $0x30,%rbx
   140001012:   53                      push   %rbx
   140001013:   48 bb 75 73 65 72 33    movabs $0x642e323372657375,%rbx
   14000101a:   32 2e 64
   14000101d:   53                      push   %rbx
   14000101e:   48 89 e1                mov    %rsp,%rcx
   140001021:   48 bb 41 41 70 0c 09    movabs $0x7ffa4e090c704141,%rbx
   140001028:   4e fa 7f
   14000102b:   48 c1 eb 10             shr    $0x10,%rbx
   14000102f:   ff d3                   call   *%rbx
   140001031:   50                      push   %rax
   140001032:   48 bb 77 6e 65 64 20    movabs $0x756f792064656e77,%rbx
   140001039:   79 6f 75
   14000103c:   53                      push   %rbx
   14000103d:   48 bb 72 63 65 33 36    movabs $0x7020393633656372,%rbx
   140001044:   39 20 70
   140001047:   53                      push   %rbx
   140001048:   48 bb 61 69 64 65 6e    movabs $0x6165706e65646961,%rbx
   14000104f:   70 65 61
   140001052:   53                      push   %rbx
   140001053:   48 89 e2                mov    %rsp,%rdx
   140001056:   50                      push   %rax
   140001057:   48 bb 30 78 68 61 63    movabs $0x64656b6361687830,%rbx
   14000105e:   6b 65 64
   140001061:   53                      push   %rbx
   140001062:   49 89 e0                mov    %rsp,%r8
   140001065:   48 31 c9                xor    %rcx,%rcx
   140001068:   4d 31 c9                xor    %r9,%r9
   14000106b:   48 bb 41 41 d0 90 e9    movabs $0x7ffa4ee990d04141,%rbx
   140001072:   4e fa 7f
   140001075:   48 c1 eb 10             shr    $0x10,%rbx
   140001079:   ff d3                   call   *%rbx
   14000107b:   48 31 c9                xor    %rcx,%rcx
   14000107e:   48 bb 41 41 20 e8 08    movabs $0x7ffa4e08e8204141,%rbx
   140001085:   4e fa 7f
   140001088:   48 c1 eb 10             shr    $0x10,%rbx
   14000108c:   ff d3                   call   *%rbx

Extracting the shellcode for spawning the message box.

ubuntu-wsl@ra:/mnt/e/shellcoding$ ./extract dump

Odfhex - object dump shellcode extractor - by steve hanna - v.01
Trying to extract the hex of dump which is 2089 bytes long
"\x48\x31\xc0\x50\x48\xbb\x41\x41\x41\x41\x41\x41\x6c\x6c\x48\xc1\xeb"\
"\x30\x53\x48\xbb\x75\x73\x65\x72\x33\x32\x2e\x64\x53\x48\x89\xe1\x48"\
"\xbb\x41\x41\x70\x0c\x09\x4e\xfa\x7f\x48\xc1\xeb\x10\xff\xd3\x50\x48"\
"\xbb\x77\x6e\x65\x64\x20\x79\x6f\x75\x53\x48\xbb\x72\x63\x65\x33\x36"\
"\x39\x20\x70\x53\x48\xbb\x61\x69\x64\x65\x6e\x70\x65\x61\x53\x48\x89"\
"\xe2\x50\x48\xbb\x30\x78\x68\x61\x63\x6b\x65\x64\x53\x49\x89\xe0\x48"\
"\x31\xc9\x4d\x31\xc9\x48\xbb\x41\x41\xd0\x90\xe9\x4e\xfa\x7f\x48\xc1"\
"\xeb\x10\xff\xd3\x48\x31\xc9\x48\xbb\x41\x41\x20\xe8\x08\x4e\xfa\x7f"\
"\x48\xc1\xeb\x10\xff\xd3";

142 bytes extracted.

Now lets try it with our shellcode wrapper.

#include "Windows.h"

int main()
{
	unsigned char shellcode[] =
		"\x48\x31\xc0\x50\x48\xbb\x41\x41\x41\x41\x41\x41\x6c\x6c\x48\xc1\xeb"\
		"\x30\x53\x48\xbb\x75\x73\x65\x72\x33\x32\x2e\x64\x53\x48\x89\xe1\x48"\
		"\xbb\x41\x41\x70\x0c\x09\x4e\xfa\x7f\x48\xc1\xeb\x10\xff\xd3\x50\x48"\
		"\xbb\x77\x6e\x65\x64\x20\x79\x6f\x75\x53\x48\xbb\x72\x63\x65\x33\x36"\
		"\x39\x20\x70\x53\x48\xbb\x61\x69\x64\x65\x6e\x70\x65\x61\x53\x48\x89"\
		"\xe2\x50\x48\xbb\x30\x78\x68\x61\x63\x6b\x65\x64\x53\x49\x89\xe0\x48"\
		"\x31\xc9\x4d\x31\xc9\x48\xbb\x41\x41\xd0\x90\xe9\x4e\xfa\x7f\x48\xc1"\
		"\xeb\x10\xff\xd3\x48\x31\xc9\x48\xbb\x41\x41\x20\xe8\x08\x4e\xfa\x7f"\
		"\x48\xc1\xeb\x10\xff\xd3";

	void* exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	if (exec != 0) {
		memcpy(exec, shellcode, sizeof shellcode);
		((void(*)())exec)();
	}
	return 0;
}

When we run this program, our shellcode should be successfully executed and it should pop a message box.

ShellcodeExecution

Now we have covered how to create a shellcode with static address. In the next blog we will be covering how to make shellcode dynamic by resolving addresses of the required functions and DLLs in the dynamic runtime.