【问题标题】:Python - Ctype.windll.kernel32.CreateThread Parameter valuePython - Ctype.windll.kernel32.CreateThread 参数值
【发布时间】:2018-05-29 05:18:24
【问题描述】:

这可能是一个非常简单的问题,但不知何故,我无法完全理解答案,而且我也找不到关于该主题的任何好的相关文档。

所以我尝试使用 Python 的 ctypes 模块和 ctypes.windll.kernel32 类中的 CreateThread 方法进行 PoC(在程序的内存空间中进行一些 shellcode 注入)

根据msdn文档CreateThread7个参数分别是:

  • 指向安全属性的指针
  • 初始堆栈大小
  • 指向起始地址的指针
  • 指向任何参数的指针
  • 创建标志
  • 指向接收线程标识符的值的指针

所有使用python调用c风格函数和库的例子都是这样的:

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)))

谁能解释一下为什么最后一个参数被用作 ctypes.pointer(c_int0),而另一个空指针的整数 0 常量值用于其他参数。 (例如 ctypes.c_int(0))

更新:这是一个示例代码,这个实现可以在网上看到:

createThread function call in python 的第 786 行

请注意上面链接的脚本行,提到的 cmets:

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

看来作者在评论 CreateThread 函数调用的参考时可能是错误的。

假设: 根据 Mark's answer 中提到的评论,ThreadID 和 ThreadHandle 是不同的,并且通过传入 ctypes.pointer(ctypes.c_int(0)) 而不仅仅是普通的 ctypes.c_int(0) (NULL) 意味着在int 0 位置,将存储线程 ID。有人可以证实这个假设吗?

【问题讨论】:

  • 示例从何而来?过去人们经常将int 零与NULL 混为一谈(它们在C++ 中相同,但在C 中不同),这可能是您只是看到了一些不良做法的例子。我可以看到的主要区别是最右边的参数是输出,而其他两个是输入,但这不应该影响它。
  • 这是一个只有写代码的人才能回答的问题。我们只能推测。在 C 中,NULL 是一个 #define0。我个人不知道为什么代码是这样写的。它传递了一个存储tid的指针,之后就不能使用了,所以我不知道为什么NULL没有通过。但是,在 64 位上做这样的事情是 UB,你会大吃一惊。有很多如何“正确”使用 ctypes 的示例:例如stackoverflow.com/questions/49927672/…,
  • 返回值:thread_handle 是线程ID吗?在那种情况下,在最后一个参数上传递 null 会导致不返回返回值吗?今天下班回家我会贴一些源代码的链接。

标签: python c ctypes kernel32 createthread


【解决方案1】:

最后一个参数实例化一个 C 整数 (c_int(0)) 并将其作为指针传递。这与最后一个参数定义松散地匹配。它应该是一个 DWORD,通常定义为 unsigned long(在 ctypes 中为 c_ulong)。使用ctypes.byref 比创建指针更有效。该参数用于将线程ID作为输出参数返回,因此需要正确C类型的实例地址来存储ID。

这是一个使用 ctypes 显式定义每个函数的输入/输出的工作示例。注意ctypeswintypes 中有预定义的Windows 类型:

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.

这是一个简单的 C 函数作为线程运行的代码:

#include <stdio.h>

__declspec(dllexport) unsigned long __stdcall func(void* p)
{
    printf("%p\n",p);
    return 0;
}

这是输出:

0000000000012345

【讨论】:

  • 感谢@Mark 的详细解答,解开了我的一些疑惑。但只是为了跟进和澄清: 1. 将最后一个参数作为 ctypes,c_int(0) 传递一个 null 将导致函数根据文档返回 void。 2. 因此,看起来作者只是使用了一个指向类型为 0 的 int 的指针,而他这样做是为了存储返回的线程 id(句柄)。 3.这都是假设(out参数)thread id ==(returned value)thread_handle。你会说你同意以上 3 点吗?
  • 在做了一些研究之后,thread_id vs thread_handle 表示它们肯定是不同的。所以仅仅通过推测上面问题中的代码,对于 ThreadCreate 调用的最后一个参数,作者只是输入了一个指向 int 0 的指针,而不是仅仅 int 0,它在技术上是一个 NULL POINTER。这样在该 ​​int 0 (ctypes.pointer(ctypes.c_int(0)) 的位置它将存储线程 id 的值?
  • @Rennitbaby 是的,它们是不同的。如果您不想检索线程 ID,可以为最后一个参数传递 None。您绝对不应该使用您拥有的代码示例。这不是一个很好的例子。最后一个参数创建一个临时的c_int(),并将指向它的指针传递给函数,但在调用后它不再存在,因此您无论如何都无法读取 ID。在我上面的示例中,tid = w.DWORD() 创建了对正确类型的引用,并通过对函数的引用将其传递。之后您可以使用print(tid.value) 检索该值。
  • @Rennitbaby 另请注意,在 SO 中的谢谢是不必要的。支持任何有用的答案并接受最好的答案。
  • 你能解释一下为什么创建的 c_int(0) 是临时的吗?或有关此类主题的任何有用链接?是因为它是在堆栈上分配给函数调用的吗?您的示例从堆中分配了引用?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-03-13
  • 1970-01-01
  • 2010-10-09
  • 1970-01-01
  • 2017-11-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多