0

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?

0x5929
  • 400
  • 1
  • 4
  • 16
  • Where did the example come from? In the past people have often mixed `int` zero with `NULL` and got away with it (they are the same in C++ but not in C), it could be that you are just seeing examples of bad practice. The main difference I can see is that the rightmost parameter is output whereas the other two are input, but that should not affect it. – cdarke May 29 '18 at 06:43
  • This is a question that only the person who wrote the code can answer. We can only speculate. in *C*, `NULL` is a `#define` to **_0_**. Personally I don't know why the code is written like this. It's passing a pointer to store the *tid*, that can't be used afterwards, so I don't know why `NULL` wasn't passed. But, doing things like this on 64 bit it's **_UB_** and you're in for a big surprise. There are plenty of examples how to use *ctypes* "correctly": e.g. https://stackoverflow.com/questions/49927672/python-and-ctypes-magnification-dll, – CristiFati May 29 '18 at 09:18
  • Would the return value: thread_handle be the thread ID? In that case would passing null on the last parameter cause the return value not to be returned? I will post some links of the source code when I get home from work today. – 0x5929 May 29 '18 at 14:41

1 Answers1

1

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
Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
  • Thank you for the detailed answer @Mark, cleared some of my doubts. But just to follow up and to clarify: 1. passing a null instead for the last parameter as ctypes,c_int(0) would cause the function to return void as per the documentation. 2. Thus it looks like the author just used a pointer to a type int of 0 instead, and he is doing this to store the returned thread id(handle). And 3. this is all under the assumption that the (out parameter)thread id == (returned value)thread_handle. Would you say you'd agree with the above 3 points? – 0x5929 May 29 '18 at 18:31
  • After doing some research, [thread_id vs thread_handle](https://stackoverflow.com/questions/5101918/thread-id-vs-thread-handle) states that they are definitely different. So just by speculating the code in the question above, for the last parameter of the ThreadCreate call, the author is just inputting a pointer to an int 0, instead of just int 0 which is technically a NULL POINTER. This way at the location of that int 0 (ctypes.pointer(ctypes.c_int(0)) it will store the value of the thread id? – 0x5929 May 29 '18 at 19:06
  • @Rennitbaby Yes they are different. You can pass `None` for the last parameter if you don't want to retrieve the thread ID. You definitely shouldn't use that code example you have. It's not a good example. That last parameter creates a temporary `c_int()`, and passes a pointer to it to the function, but it ceases to exist after the call so you wouldn't be able to read the ID anyway. In my example above `tid = w.DWORD()` creates a reference to the correct type and passes it by reference to the function. Afterward you can retrieve the value with `print(tid.value)`. – Mark Tolonen May 29 '18 at 20:40
  • @Rennitbaby Also note thank yous in SO are not necessary. Upvote any answers that are useful and accept the best one. – Mark Tolonen May 29 '18 at 20:42
  • Can you explain a little on why the c_int(0) created is temporary? Or any useful links on such topic? Is it because it is allocated on the stack for the function call? And your example allocated the reference from heap? – 0x5929 May 29 '18 at 20:45
  • @Rennitbaby It's allocated the same way in either case, but it isn't assigned to a variable so there is no reference once the function returns. In my example it is assigned to a variable so there is a reference keeping it from deallocating. Only when `tid` goes out of scope (if it was a local variable to a function, for example) or the reference is deleted (`del tid` for example) is the memory (potentially) freed. Exactly when the memory is released is implementation-defined, but if you don't have a reference to it as in your original example, you won't be able to retrieve the content. – Mark Tolonen May 29 '18 at 21:04