Windows x86 MessageBox shellcode

January
19th,
2019

In this post we will continue exploring the world of Win32 shellcode development. You can check here the first part of a series of posts in which we have developed a reverse shell shellcode from scratch.

Today, however, we will try to keep it simpler and swifter. Everything will be done directly withing the debugger, so we can easily get the opcodes and test our shellcode step by step on the fly. Just get any binary, open it in a debugger and overwrite the few first instructions with our shellcode. Also, no more sockets and network related stuff. Let’s see if we really understood the concepts of the previous posts and pop a simple message box.

For that we will need to do the following:

Get the address of LoadLibraryA in kernel32.dll

Load user32.dll using LoadLibraryA

Get the address of MessageBoxA in user32.dll

Put parameters on the stack

Call MessageBoxA

Well, actually there is a lot more than that. For instance, take a look at the complete process described at this awesome post:

Obtain the kernel32.dll base address

Find the address of GetProcAddress function

Use GetProcAddress to find the address of LoadLibrary function

Use LoadLibrary to load a DLL (such as user32.dll)

Use GetProcAddress to find the address of a function (such as MessageBox)

Specify the function parameters

Call the function

This is a way more complete and somewhat more complex workflow. While SecurityCafe’s shellcode is certainly more reliable, our humble code will do the job for a specific system build and version. For the record, I am using a Windows XP SP3. This means the base addresses of some DLLs are not randomized (no ALSR for them), so our hardcoded adresses should work fine for every identical environment.

In our case, we will use an external tool to get these fixed address. It is Steve Hanna’s arwin, and you should definetly take a look at it here. Arwin will help us skipping steps 1 to 3 and also step 5. In fact, according to this source:

Why can’t addresses to API functions be hardcoded? Because system API addresses are no longer predictable on modern (post-XP) versions of Windows.

Prior to Microsoft’s release of Vista, Windows loaded system DLLs at hardcoded addresses in all processes. Because each version of the operating system and service pack level happened to load KERNEL32.DLL at the same base address, shellcode could hardcode the addresses for the two “holy-grail” functions (LoadLibrary() and GetProcAddress()) for common versions of Windows and have easy access to the remainder of APIs on the machine. For example, Windows XP Service Pack 3 always loaded KERNEL32 at 0x7C800000, LoadLibrary() at 0x7C801D7B and GetProcAddress() at 0x7C80AE30 system-wide. Besides shellcode needing a table of different addresses per function per version of Windows, it was still pretty convenient to access the system once shellcode gained control.

With the public release of Windows Vista in January 2007, Microsoft stepped up their game with a security feature known as ASLR (Address Space Layout Randomization). ASLR randomizes the base load address for all system DLLs including KERNEL32.DLL each time the operating system boots.

Since we are trying to do the quicker and dirtier shellcode possible in a Win XP, we should be fine considering kernel32.dll and LoadLibraryA at the same fixed addresses. Although it might feel like cheating, there is indeeed nothing magical in arwin. It actually is a very simple C code that uses Win32 API to… well, do pretty much the same as what is described in SecurityCafe’s post, but in a higher level language. Take a look by yourself:

Get the address of LoadLibraryA in kernel32.dll

Let’s take a break here and think a little further. Why exactly do we need LoadLibraryA? If you read the steps I wrote down back to front, you will realize what we want is the address of MessageBoxA. As we have seen, Win XP does not implement ASLR. So why did’t we just use arwin and hardcode it’s address?

You can load any binary into your favorite debugger and press SPACE to edit the current instruction.

The first line directly corresponds to step number 1. Lines 2 to 4 correspond to step number 2. Now, this is a little trickier than it might seem at first. Whenever we push stuff onto the stack, it must be in blocks of 4 bytes (32 bits). There are two caveats here, however:

The string must end up with a NULL byte

The order of the blocks

To solve the first point, we push a NULL double word onto the stack. Since \x00 is a common badchar, we avoid it by zeroing ECX with XOR ECX, ECX and then pushing ECX.

Now, for the second point, our string user32.dll gets divided into user, 32.d and ll. Since this last block is composed only by 2 bytes, we do not push it directly, due to the fact that unwanted NULLs would be inserted in our shellcode. Instead, we first move 6C6C to CX, which is 2-bytes long, and then push CX.

Get the address of MessageBoxA in user32.dll

Now that user32.dll is loaded into the memory space, we can use arwin to retrieve the address of MessageBoxA, which, as we have already seen, 0x7e4507ea.

Put parameters on the stack

This step will be similar to step 2, just with a few more parameters. Fire up MSDN and check the syntax of MessageBoxA:

Just remember those 2 points from step 2: the NULL byte and the 4-bytes block order. If you read the documentation (please do), you will know that both uType and hWnd might be zero. For lpCaption, we will push “Bug” and for lpText, we will push “Tree”.

However, “Bug” has only 3 bytes. As we are dealing with a 32 bits stack, we cannot push 1 byte. Because of this we cannot split “Bug” into “B” and “ug” and use the same trick as we did in step 2. Another solution would be to use a shift.

The instruction SHR allows us to rotate an operand a certain number of bits to the right. We can use it to complete our task:

MOV ECX,67754241 ;guBA
SHR ECX,8 ;ECX=00guB
PUSH ECX

We added a random character to our string (“A” in this case) and then shifted the whole thing to the right. This solution is also interesting because we could also insert the NULL byte we need at the end of the string!