- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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;
}
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.
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.
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.
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.