您好,欢迎访问三七文档
当前位置:首页 > 临时分类 > 灰帽 Python之旅3
3自己动手写一个windos调试器现在我们已经讲解完了基础知识,是时候实现一个真正的的调试器的时候了。当微软开发windows的时候,他们增加了一大堆的令人惊喜的调试函数以帮助开发者们保证产品的质量。我们将大量的使用这些函数创建你自己的纯python调试器。有一点很重要,我们本质上是在深入的学习PyDbg(PedramAmini’s)的使用,这是目前能找到的最简洁的Windows平台下的Python调试器。拜Pedram所赐,我尽可能用PyDbg完成了我的代码(包括函数名,变量,等等),同时你也可以更容易的用PyDbg实现你的调试器。为了对一个进程进行调试,你首先必须用一些方法把调试器和进程连接起来。所以,我们的调试器要不然就是装载一个可执行程序然后运行它,要不然就是动态的附加到一个运行的进程。Windows的调试接口(WindowsdebuggingAPI)提供了一个非常简单的方法完成这两点。运行一个程序和附加到一个程序有细微的差别。打开一个程序的优点在于他能在程序运行任何代码之前完全的控制程序。这在分析病毒或者恶意代码的时候非常有用。附加到一个进程,仅仅是强行的进入一个已经运行了的进程内部,它允许你跳过启动部分的代码,分析你感兴趣的代码。你正在分析的地方也就是程序目前正在执行的地方。第一种方法,其实就是从调试器本身调用这个程序(调试器就是父进程,对被调试进程的控制权限更大)。在Windows上创建一个进程用CreateProcessA()函数。将特定的标志传进这个函数,使得目标进程能够被调试。一个CreateProcessA()调用看起来像这样:BOOLWINAPICreateProcessA(LPCSTRlpApplicationName,LPTSTRlpCommandLine,LPSECURITY_ATTRIBUTESlpProcessAttributes,LPSECURITY_ATTRIBUTESlpThreadAttributes,BOOLbInheritHandles,DWORDdwCreationFlags,LPVOIDlpEnvironment,LPCTSTRlpCurrentDirectory,LPSTARTUPINFOlpStartupInfo,LPPROCESS_INFORMATIONlpProcessInformation);初看这个调用相当恐怖,不过,在逆向工程中我们必须把大的部分分解成小的部分以便理解。这里我们只关心在调试器中创建一个进程需要注意的参数。这些参数是lpApplicationName,lpCommandLine,dwCreationFlags,lpStartupInfo,和lpProcessInformation。剩余的参数可以设置成空值(NULL)。关于这个函数的详细解释可以查看MSDN(微软之葵花宝典)。最前面的两个参数用于设置,需要执行的程序的路径和我们希望传递给程序的参数。dwCreationFlags(创建标记)参数接受一个特定值,表示我们希望程序以被调试的状态启动。最后两个参数分别分别指向2个结构(STARTUPINFOandPROCESS_INFORMATION),不仅包含了进程如何启动,以及启动后的许多重要信息。(lpStartupInfo:STARTUPINFO结构,用于在创建子进程时设置各种属性,lpProcessInformation:PROCESS_INFORMATION结构,用来在进程创建后接收相关信息,该结构由系统填写。)创建两个Python文件my_debugger.py和my_debugger_defines.py。我们将创建一个父类debugger()接着逐渐的增加各种调试函数。另外,把所有的结构,联合,常量放到my_debugger_defines.py方便以后维护。#my_debugger_defines.pyfromctypesimport*#Let'smaptheMicrosofttypestoctypesforclarityWORD=c_ushortDWORD=c_ulongLPBYTE=POINTER(c_ubyte)LPTSTR=POINTER(c_char)HANDLE=c_void_p#ConstantsDEBUG_PROCESS=0x00000001CREATE_NEW_CONSOLE=0x00000010#StructuresforCreateProcessA()functionclassSTARTUPINFO(Structure):_fields_=[(cb,DWORD),(lpReserved,LPTSTR),(lpDesktop,LPTSTR),(lpTitle,LPTSTR),(dwX,DWORD),(dwY,DWORD),(dwXSize,DWORD),(dwYSize,DWORD),(dwXCountChars,DWORD),(dwYCountChars,DWORD),(dwFillAttribute,DWORD),(dwFlags,DWORD),(wShowWindow,WORD),(cbReserved2,WORD),(lpReserved2,LPBYTE),(hStdInput,HANDLE),(hStdOutput,HANDLE),(hStdError,HANDLE),]classPROCESS_INFORMATION(Structure):_fields_=[(hProcess,HANDLE),(hThread,HANDLE),(dwProcessId,DWORD),(dwThreadId,DWORD),]#my_debugger.pyfromctypesimport*frommy_debugger_definesimport*kernel32=windll.kernel32classdebugger():def__init__(self):passdefload(self,path_to_exe):#dwCreationflagdetermineshowtocreatetheprocess#setcreation_flags=CREATE_NEW_CONSOLEifyouwant#toseethecalculatorGUIcreation_flags=DEBUG_PROCESS#instantiatethestructsstartupinfo=STARTUPINFO()process_information=PROCESS_INFORMATION()#Thefollowingtwooptionsallowthestartedprocess#tobeshownasaseparatewindow.Thisalsoillustrates#howdifferentsettingsintheSTARTUPINFOstructcanaffect#thedebuggee.startupinfo.dwFlags=0x1startupinfo.wShowWindow=0x0#WetheninitializethecbvariableintheSTARTUPINFOstruct#whichisjustthesizeofthestructitselfstartupinfo.cb=sizeof(startupinfo)ifkernel32.CreateProcessA(path_to_exe,None,None,None,None,creation_flags,None,None,byref(startupinfo),byref(process_information)):print[*]Wehavesuccessfullylaunchedtheprocess!print[*]PID:%d%process_information.dwProcessIdelse:print[*]Error:0x%08x.%kernel32.GetLastError()现在我们将构造一个简短的测试模块确定一下一切都能正常工作。调用my_test.py,保证前面的文件都在同一个目录下。#my_test.pyimportmy_debuggerdebugger=my_debugger.debugger()debugger.load(C:\\WINDOWS\\system32\\calc.exe)如果你是通过命令行或者IDE手动输入上面的代码,将会新产生一个进程也就是你键入程序名,然后返回进程ID(PID),最后结束。如果你用上面的例子calc.exe,你将看不到计算器的图形界面出现。因为进程没有把界面绘画到屏幕上,它在等待调试器继续执行的命令。很快我们就能让他继续执行下去了。不过在这之前,我们已经找到了如何产生一个进程用于调试,现在让我们实现另一个功能,附加到一个正在运行的进程。为了附加到指定的进程,就必须先得到它的句柄。许多后面将用到的函数都需要句柄做参数,同时我们也能在调试之前确认是否有权限调试它(如果附加都不行,就别提调试了)。这个任务由OpenProcess()完成,此函数由kernel32.dll库倒出,原型如下:HANDLEWINAPIOpenProcess(DWORDdwDesiredAccess,BOOLbInheritHandleDWORDdwProcessId);dwDesiredAccess参数决定了我们希望对将要打开的进程拥有什么样的权限(当然是越大越好rootishack)。因为要执行调试,我们设置成PROCESS_ALL_ACCESS。bInheritHandle参数设置成False,dwProcessId参数设置成我们希望获得句柄的进程ID,也就是前面获得的PID。如果函数成功执行,将返回一个目标进程的句柄。接下来用DebugActiveProcess()函数附加到目标进程:BOOLWINAPIDebugActiveProcess(DWORDdwProcessId);把需要a附加的PID传入。一旦系统认为我们有权限访问目标进程,目标进程就假定我们的调试器已经准备好处理调试事件,然后把进程的控制权转移给调试器。调试器接着循环调用WaitForDebugEvent()以便俘获调试事件。函数原型如下:BOOLWINAPIWaitForDebugEvent(LPDEBUG_EVENTlpDebugEvent,DWORDdwMilliseconds);第一个参数指向DEBUG_EVENT结构,这个结构描述了一个调试事件。第二个参数设置成INFINITE(无限等待),这样WaitForDebugEvent()就不用返回,一直等待直到一个事件产生。调试器捕捉的每一个事件都有相关联的事件处理函数,在程序继续执行前可以完成不同的操作。当处理函数完成了操作,我们希望进程继续执行用,这时候再调用ContinueDebugEvent()。原型如下:BOOLWINAPIContinueDebugEvent(DWORDdwProcessId,DWORDdwThreadId,DWORDdwContinueStatus);dwProcessId和dwThreadId参数由DEBUG_EVENT结构里的数据填充,当调试器捕捉到调试事件的时候,也就是WaitForDebugEvent()成功执行的时候,进程ID和线程ID就以及初始化好了。dwContinueStatus参数告诉进程是继续执行(DBG_CONTINUE),还是产生异常(DBG_EXCEPTION_NOT_HANDLED)。还剩下一件事没做,从进程分离出来:把进程ID传递给DebugActiveProcessStop()。现在我们把这些全合在一起,扩展我们的my_debugger类,让他拥有附加和分离一个进程的功能。同时加上打开一个进程和获得进程句柄的能力。最后在我们的主循环里完成事件处理函数。打开my_debugger.py键入以下代码。提示:所有需要的结构,联合和常量都定义在
本文标题:灰帽 Python之旅3
链接地址:https://www.777doc.com/doc-4870279 .html