Search code examples
pythoncctypeskernel32createthread

Python - Ctype.windll.kernel32.CreateThread Parameter value


this might be a really simple question, but somehow I just cannot wrap my head around the answer, and I cannot find any good and related documentations on such topic either.

So I am attempting to do a PoC using Python's ctypes module and the CreateThread method within ctypes.windll.kernel32 class (to do some shellcode injection within a program's memory space)

According to msdn documentationCreateThreadThe 7 parameters are:

  • Pointer to Security Attributes
  • Initial Stack Size
  • Pointer to Start Address
  • Pointer to any parameters
  • Creation Flag
  • Pointer to a value that receives the thread identifier

And all the examples of using python to call c style functions and libs are as such:

thread_handle = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0),
                ctypes.c_int(0),
                ctypes.c_int(ptr),
                ctypes.c_int(0),
                ctypes.c_int(0),
                ctypes.pointer(ctypes.c_int(0)))

Can someone please explain why the last parameter was used as ctypes.pointer(c_int0), while the other null pointer's constant value of integer 0 is used for the other parameters. (eg. ctypes.c_int(0))

Update: Here is a sample code, and this implementation can be seen all over the net:

Line 786 of createThread function call in python

Note at the line of the script linked above, the comments mentioned:

  #   _Out_opt_ LPDWORD  lpThreadId  // NULL, so the thread identifier is not returned.

It looks like the author might be wrong when commenting the reference for the CreateThread function call.

Assumption: As per the comment in Mark's answer mentioned, the ThreadID and the ThreadHandle are different, and by passing in a ctypes.pointer(ctypes.c_int(0)) instead of just plain ctypes.c_int(0) (NULL) means that at the int 0 location, will store the thread ID. Can someone confirm this assumption?


Solution

  • The last parameter instantiates a C integer (c_int(0)) and passes it as a pointer. This matches the last parameter definition loosely. It should be a DWORD which is typically defined as unsigned long (c_ulong in ctypes). Using ctypes.byref is more efficient than creating a pointer. The parameter is used to return the thread ID as an output parameter, so need the address of an instance of the correct C type to store the ID.

    Here's a working example that explicitly defines then inputs/outputs of each function with ctypes. Note that ctypes has pre-defined Windows types in wintypes:

    import ctypes as c
    from ctypes import wintypes as w
    
    LPTHREAD_START_ROUTINE = c.WINFUNCTYPE(w.DWORD,w.LPVOID)
    SIZE_T = c.c_size_t
    
    k32 = c.WinDLL('kernel32')
    test = c.WinDLL('test')
    
    CreateThread = k32.CreateThread
    CreateThread.argtypes = w.LPVOID,SIZE_T,LPTHREAD_START_ROUTINE,w.LPVOID,w.DWORD,w.LPDWORD
    CreateThread.restype = w.HANDLE
    
    WaitForSingleObject = k32.WaitForSingleObject
    WaitForSingleObject.argtypes = w.HANDLE,w.DWORD
    WaitForSingleObject.restype = w.DWORD
    
    sa = None  # No security specified.  None == NULL pointer.
    stack = 0  # Use default stack
    start = LPTHREAD_START_ROUTINE(test.func)
    param = 0x12345
    flags = 0 # start thread immediately
    tid = w.DWORD()
    h = CreateThread(sa,stack,start,param,flags,c.byref(tid))
    WaitForSingleObject(h,1000) # wait for the thread to exit.
    

    Here's the code for a simple C function to run as a thread:

    #include <stdio.h>
    
    __declspec(dllexport) unsigned long __stdcall func(void* p)
    {
        printf("%p\n",p);
        return 0;
    }
    

    Here's the output:

    0000000000012345