简单分析Python ctypes模块的WinDLL源码(我爱Python,吼吼~)
又是一个寂寞的周末啊同学们,这几天天气变冷自己却没有赖床,好吧,表扬一次^^
扯点八卦,今天是pycon2011在上海那边开了,我早上和刚才看了网上的直播,做的很不错,形式很像irongeek.com的视频,我觉得这块在国内还是有一点点商业前景的,哈.听了网易林伟和豆瓣洪强宁的演讲,开拓了点视野,豆瓣基本主要都是用python做,网易说一些服务端也是用python写,结合c去做,但是python的代码已经提高到70%了.所以,python都是很有前景的吧.(不过2.x和3.x的版本并行发展让哥蛋疼..)
今天有同学在群里发exploit-db上的MS11-080的连接,打开一看,尼玛的居然是用python写的,做系统提权.我感觉比较新鲜的是对windows系统api的调用居然这么方便(好吧,我孤陋寡闻- - ),顿时来了兴致,ctypes模块的WinDLL.既然python是一个开源的东东(口号:Human knowledge belongs to the world!),让我们可以深入了解底层的具体实现,我们要学习的总是这个渔而不是这个鱼对吧.下面算是我自己做做笔记吧,欢迎大家拍砖:
首先我们来看看WinDLL对windows api调用的方便程度:
上面图演示的是对Kernel32.dll里面的GetModuleHandleA的调用以及结果验证.同学们是不是觉得很方便哩.
看看WinDLL的实现,WinDLL实际上是一个类,下面是代码,我们一层一层往上回溯:
class WinDLL(CDLL):"""This class represents a dll exporting functions using theWindows stdcall calling convention."""_func_flags_ = _FUNCFLAG_STDCALL
WinDLL的代码很简单,主要是声明一个函数标志,通过名字可以知道是声明函数的调用约定,这里是STDCALL,一看到这里哥就冒出了一个api需要测试,后面再说,这里卖个关子.WinDLL这个类继承自CDLL,那么我们接下来看的就是CDLL的代码:
class CDLL(object):"""An instance of this class represents a loaded dll/sharedlibrary, exporting functions using the standard C callingconvention (named 'cdecl' on Windows).The exported functions can be accessed as attributes, or byindexing with the function name. Examples:<obj>.qsort -> callable object<obj>['qsort'] -> callable objectCalling the functions releases the Python GIL during the call andreacquires it afterwards."""_func_flags_ = _FUNCFLAG_CDECL_func_restype_ = c_intdef __init__(self, name, mode=DEFAULT_MODE, handle=None,use_errno=False,use_last_error=False):self._name = nameflags = self._func_flags_if use_errno:flags |= _FUNCFLAG_USE_ERRNOif use_last_error:flags |= _FUNCFLAG_USE_LASTERRORclass _FuncPtr(_CFuncPtr):_flags_ = flags_restype_ = self._func_restype_self._FuncPtr = _FuncPtrif handle is None:self._handle = _dlopen(self._name, mode)else:self._handle = handledef __repr__(self):return "<%s '%s', handle %x at %x>" % \(self.__class__.__name__, self._name,(self._handle & (_sys.maxint*2 + 1)),id(self) & (_sys.maxint*2 + 1))def __getattr__(self, name):if name.startswith('__') and name.endswith('__'):raise AttributeError(name)func = self.__getitem__(name)setattr(self, name, func)return funcdef __getitem__(self, name_or_ordinal):func = self._FuncPtr((name_or_ordinal, self))if not isinstance(name_or_ordinal, (int, long)):func.__name__ = name_or_ordinalreturn func
构造函数就是设置一些相关的成员,同时声明了一个内部的类对象_FuncPtr用于保存具体的api函数指针,这个类继承自_CFuncPtr,这个稍后说明.__repr__函数用于打印相关信息(以一种友好的方式 -- 书上这么说的~),__getattr__是'.'运算符,__getitem__是'[ ]'索引.
我们在上面的演示代码中, from ctypes import windll,当中的这个windll实际上是ctypes模块的一个成员对象,类型是LibraryLoader,具体代码如下:
class LibraryLoader(object):def __init__(self, dlltype):self._dlltype = dlltypedef __getattr__(self, name):if name[0] == '_':raise AttributeError(name)dll = self._dlltype(name)setattr(self, name, dll)return dlldef __getitem__(self, name):return getattr(self, name)def LoadLibrary(self, name):return self._dlltype(name)#......#if _os.name in ("nt", "ce"):windll = LibraryLoader(WinDLL)oledll = LibraryLoader(OleDLL)
实际上,LibraryLoader对象以一个类(类型)作为输入,通过记录这个类型,当对LibraryLoader对象进行'.'操作时(__getattr__方法),返回的是dll = self._dlltype(name),也就是说单我们执行windll.kernel32这样的语句时,背后运行的是self._dlltype(name) => dlltype(name) => WinDLL(name) => WinDLL.__init__(name), =>表示一个递进分析的过程,为什么需要加上这样的一个LIbraryLoader的对象,通过setattr(self, name, dll)这句我们可以很直观的理解到这是为了记录已经Load的DLL用以之后的使用.
,在WinDLL.__init__当中,需要保存要打开的dll的句柄,这部分的功能通过self._handle = _dlopen(self._name, mode)这句实现,关于_dlopen,它是从外部导入的,包括之前提到的_CFuncPtr对象,具体代码如下:
from _ctypes import Union, Structure, Array
from _ctypes import _Pointer
from _ctypes import CFuncPtr as _CFuncPtr
from _ctypes import __version__ as _ctypes_version
from _ctypes import RTLD_LOCAL, RTLD_GLOBAL
from _ctypes import ArgumentErrorif _os.name in ("nt", "ce"):from _ctypes import LoadLibrary as _dlopenfrom _ctypes import FUNCFLAG_STDCALL as _FUNCFLAG_STDCALL
一开始我一直不清楚_ctypes是什么东西,而在Python的文档当中也没有提到,这里吐槽一下搜索引擎,对于我的关键字_ctypes,他总是比较自做聪明的用ctypes替代,-___-|||,不过哩我们还是很容易能够搜索得到,_ctypes是一个c写的外部库,具体在python的Dlls文件夹下,_ctypes.pyd就是了,实际上pyd文件就是一个dll文件,好吧,一般人到这里就算停了,反正通过名字就能知道你具体完成的功能,但是哥作为一个喜欢追根刨底的男人,对于不能看到_ctypes的具体代码很是纠结(因为默认的安装包,win版是没有_ctypes的代码,C代码),既然python是开源,那么果断下他的源代码搜一搜,结果很明显,我们可以得到我们想要的^^
具体的实现代码和导出代码如下,对于函数的导出和对象的导出有不同的方式:
PyMethodDef _ctypes_module_methods[] = {/* ...... */{"LoadLibrary", load_library, METH_VARARGS, load_library_doc},/* ...... */{NULL, NULL} /* Sentinel */
};static PyObject *load_library(PyObject *self, PyObject *args)
{TCHAR *name;PyObject *nameobj;PyObject *ignored;HMODULE hMod;if (!PyArg_ParseTuple(args, "O|O:LoadLibrary", &nameobj, &ignored))return NULL;
#ifdef _UNICODEname = alloca((PyString_Size(nameobj) + 1) * sizeof(WCHAR));if (!name) {PyErr_NoMemory();return NULL;}{int r;char *aname = PyString_AsString(nameobj);if(!aname)return NULL;r = MultiByteToWideChar(CP_ACP, 0, aname, -1, name, PyString_Size(nameobj) + 1);name[r] = 0;}
#elsename = PyString_AsString(nameobj);if(!name)return NULL;
#endifhMod = LoadLibrary(name);if (!hMod)return PyErr_SetFromWindowsErr(GetLastError());
#ifdef _WIN64return PyLong_FromVoidPtr(hMod);
#elsereturn Py_BuildValue("i", hMod);
#endif
}Py_TYPE(&PyCFuncPtr_Type) = &PyCFuncPtrType_Type;
PyCFuncPtr_Type.tp_base = &PyCData_Type;
if (PyType_Ready(&PyCFuncPtr_Type) < 0)return;
Py_INCREF(&PyCFuncPtr_Type);
PyModule_AddObject(m, "CFuncPtr", (PyObject *)&PyCFuncPtr_Type);PyTypeObject PyCFuncPtr_Type = {PyVarObject_HEAD_INIT(NULL, 0)"_ctypes.PyCFuncPtr",sizeof(PyCFuncPtrObject), /* tp_basicsize */0, /* tp_itemsize */(destructor)PyCFuncPtr_dealloc, /* tp_dealloc */0, /* tp_print */0, /* tp_getattr */0, /* tp_setattr */0, /* tp_compare */(reprfunc)PyCFuncPtr_repr, /* tp_repr */&PyCFuncPtr_as_number, /* tp_as_number */0, /* tp_as_sequence */0, /* tp_as_mapping */0, /* tp_hash */(ternaryfunc)PyCFuncPtr_call, /* tp_call */0, /* tp_str */0, /* tp_getattro */0, /* tp_setattro */&PyCData_as_buffer, /* tp_as_buffer */Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_NEWBUFFER | Py_TPFLAGS_BASETYPE, /* tp_flags */"Function Pointer", /* tp_doc */(traverseproc)PyCFuncPtr_traverse, /* tp_traverse */(inquiry)PyCFuncPtr_clear, /* tp_clear */0, /* tp_richcompare */0, /* tp_weaklistoffset */0, /* tp_iter */0, /* tp_iternext */0, /* tp_methods */0, /* tp_members */PyCFuncPtr_getsets, /* tp_getset */0, /* tp_base */0, /* tp_dict */0, /* tp_descr_get */0, /* tp_descr_set */0, /* tp_dictoffset */0, /* tp_init */0, /* tp_alloc */PyCFuncPtr_new, /* tp_new */0, /* tp_free */
};
对于函数的导出,使用的是PyMethodDef的数组,而对于成员的导出,使用的是PyModule_AddObject()函数._ctypes的LoadLibrary的实际函数load_library,他的当然要么是LoadLibrary要么是LoadLibraryEx这两个API啦.而对于CFuncPtr成员,我们可以看到PyTypeObject的类型,里面记录了许多相关操作的函数指针.
回到CDLL的代码当中,当对一个CDLL对象使用'.'操作的时候,实际上返回的之后相应函数的地址,如windll.kernel32.GetModuleHandleA,执行的流程是CDLL.__getattr__() => CDLL.__getitem__(),在getitem当中通过CFuncPtr的操作得到相关的函数地址,函数指针都记录在PyCFuncPtr_Type成员中,具体我就没再细看了,既然回归到C里面,用GetProcAdress实现应该不是很麻烦.在getattr当中也将调用过的函数地址通过setattr保存下载,供之后调用.
基本上对windll这部分的寻找他源代码的故事(kuso,寻找他乡的故事- -好冷)到这里就到一段落了.我们之前说过有一个函数想测试调用的,同学们有没有猜到是哪个函数哩.
答案就是wsprintf这个api,这个函数特殊的地方在哪?他是windows api当中唯一(好吧,我不确定是否唯一)不是用stdcall调用约定的api.为什么不能用stacall?提示:1,参数个数可变;2.stdcall是由被调用者进行堆栈清理.那么他只能用cdcel的调用约定了
在WinDLL类的初始化当中我们看到,他把函数调用约定一并的设置为STDCALL,所以我猜测,对于wsprintf调用是会出问题的.验证了一下,果然是的,会提示参数个数不匹配,如图
所以,可怜的wsprintf就被这样排除在外了.
强大的python啊,通过ctypes,基本上想直接用sdk都ok了,当然python写不了驱动(笑).尼玛的当初脚本选python学还是对的~
今天看pycon2011的直播,接触了python很多高级语言特性在实际工程中的应用,也是很有体会,回头把Learning python再翻翻,估计能有新的收获.
大体上想记录的就是这些了,欢迎大家补充交流^_^
末尾的吐槽: 明天要去北校开会啊...伤不起啊,哥不想去...开完会还说要聚餐..果断那个时候开溜啦啦啦~
---------------------------------------------------------------
刚才写完,点击提交没有反应,我看了一下草稿一直保持在4点半..菊花一紧,果断保存在word里.果然..提交失败....重新排版....
简单分析Python ctypes模块的WinDLL源码(我爱Python,吼吼~)相关推荐
- 【Python 笔记】Linux 下源码编译安装 python
本文记录在 Linux 上源码编译安装 python 的过程. 文章目录 1. 源码编译安装说明 2. 安装 python2.7 3. 安装 python3.6 1. 源码编译安装说明 安装过程比我想 ...
- python 解包_【源码解析】python解包操作一文完全理解
解包是如何操作? >>> a, b = [1, 2] # 以下为此解包操作的字节码 0 LOAD_CONST 1 (1) 2 LOAD_CONST 2 (2) 4 BUILD_LIS ...
- python自动抢微信红包源码_用Python实现微信自动化抢红包,再也不用担心抢不到红包了...
1. 概述 刚刚收到了两个消息,一个好消息,一个坏消息. 先说好消息,好消息就是微信群里有人要发红包,开心~ 不过转念一想,前几次的红包一个都没抢到,这次???不由自主的叹了一口气 ... 过了一会, ...
- 聊聊Python ctypes 模块(转载)
聊聊Python ctypes 模块(转载) https://zhuanlan.zhihu.com/p/20152309?columnSlug=python-dev 作者:Jerry Jho 链接:h ...
- 简单直接让你也读懂springmvc源码分析(3.1)-- HandlerMethodReturnValueHandler
该源码分析系列文章分如下章节: springmvc源码分析(1)-- DispatcherServlet springmvc源码分析(2)-- HandlerMapping springmvc源码分析 ...
- python可视化数据分析开心麻花影视作品分析词云折线图等源码
wx供重浩:创享日记 对话框发送:python影视 免费获取完整源码源文件+说明文档+可执行文件等 在PyCharm中运行<开心麻花影视作品分析>即可进入如图1所示的系统主界面.在该界面中 ...
- python数据分析模块包括_数据开发必会 | Python数据分析模块
作为数据开发,Python强大的数据分析模块还是必须要会的,横向拓展数据分析与挖掘技术栈也是很有必要的.本文将对Pandas.NumPy.SciPy.Matplotlib等分析挖掘库的安装和使用进行简 ...
- python影视数据爬虫sqlite源码+论文(完整版和简洁版)
python影视数据爬虫sqlite源码+论文(完整版和简洁版)-99源码网,程序代做,代写程序代码,代写编程,代写Java编程,代写php编程,计算机专业代做,计算机毕业设计,网站建设,网站开发,程 ...
- 【 线性回归 Linear-Regression torch模块实现与源码详解 深度学习 Pytorch笔记 B站刘二大人(4/10)】
torch模块实现与源码详解 深度学习 Pytorch笔记 B站刘二大人 深度学习 Pytorch笔记 B站刘二大人(4/10) 介绍 至此开始,深度学习模型构建的预备知识已经完全准备完毕. 从本章开 ...
最新文章
- docker 删除容器_Docker (二) Windows10专业版安装教程
- python之父去面试-前端两年月入30K,高频面试题整理(含答案)
- 用PHP忙了一晚上写的图片缩略和图片等比缩放函数
- 为什么需要超过48k的采样音频?
- [设计模式-结构型]外观模式(Facade)
- 2021-2025年中国催产药行业市场供需与战略研究报告
- 显示墙 显示服务器地址,云墙怎么看服务器地址
- MFC 盾webBrowser打开弹出的页面
- 计算机打不开网络邻居,打不开网上邻居的电脑是怎么回事
- 阿里ACP云计算认证快速通关分享
- 换发型特效怎么制作?这些方法值得收藏
- MDWechat(微信美化)
- Sunshine数据库篇之查询
- DirectShow SDK笔记【关于DirectShow(2)】
- c语言建立小根堆的算法,小根堆(Heap)的详细实现
- 区块链笔记:技术栈、对等网络、密码技术、账户模型、网络共识、脚本系统、扩展技术
- golang基础教程(十)、结构体
- dubbo多协议配置
- 趣图一张:《几何原本》拓扑结构图
- 2019/1/6 初探JAVA京东 httpclient 模拟登陆(初篇)
热门文章
- Java JSON格式简介说明
- [P4]源码管理 - perforce(p4)的分支与集成
- js中的3种弹出式消息提醒(警告窗口,确认窗口,信息输入窗口)的命令是什么?
- 高质量的视频播放往往只需要一个m3u8文件 视频流搞起来
- Statspack ORA-00001 unique constraint violated错误的解决
- python条形图和线形图的绘制,并显示数据
- linux类似的vlookup函数,VLOOKUP进阶——你可能所不知道的VLOOKUP用法
- 机器学习 06:SMO 算法
- 2023西北大学计算机考研信息汇总
- 【组网工程】cisco packet tracer 路由器组网