发布新日志

  • 【转】QTP关键技术—Test和Top-Level Action间参数传递

    2010-06-04 10:46:17

    以下讲述一个关于QTP的Test参数和Top-Level Action参数的使用例子,

            有些人不知道这个参数做什么用的,尤其是Test的output不知道怎么取。

            其实它是外部对象传给它的(这个外部对象可以是Quality Center,也可以是vbs这样的驱动程序)。

            以下给大家讲解一个关于QuickTest的Flight的例子。

            首先,在QTP里录制一段脚本,代码如下:

            SystemUtil.CloseProcessByName "Flight4a.exe"

            SystemUtil.Run Environment.Value("ProductDir") & "\samples\flightapp\flight4a.exe"

            Dialog("Login").WinEdit("Agent Name:").Set Parameter("InAction1")

            Dialog("Login").WinEdit("Password:").SetSecure "46f1f4259cf01348f5a4c630bcee96084f3d1619"

            Dialog("Login").WinButton("OK").Click

            Window("Flight Reservation").Close

            Parameter("OutAction1") = true

            然后在QTP中进行参数设置,

            1)设置Action的参数

            鼠标选中Keyword View中的Action1,

            点右键---Action Property,

            在Parameters的Tab标签下,分别加入:

            输入参数 InAction1 ,类型String;

            输出参数 OutAction1,类型 Boolean。

            2)设置Test的参数

            在QTP的菜单File--->>Settings的Parameters的Tab标签下,分别加入:

            输入参数 InTest1 ,类型String;

            输出参数 OutTest1,类型 Boolean。

            3)将Test和Action间参数进行关联传递

            鼠标还是选中Keyword View中的Action1,点右键,

            这次点“Action Call Properties”,

            在Parameter Values里进行参数化传递设置,

            把InTest1的值传递给InAction1,

            把OutAction1的值传递给OutTest1。

            以上设置完毕后,点“保存”,保存到C:\下,存为Test1好了。

            最后,在你的硬盘上新建一个vbs文件,文件内容如下:

            Dim qtApp ,pDefColl,pDef ,rtParams,rtParam

            Set qtApp = CreateObject("QuickTest.Application")

            qtApp.Launch

            qtApp.Visible = True

            qtApp.Open "C:\Test1"

            Set pDefColl = qtApp.Test.ParameterDefinitions

            cnt = pDefColl.Count

            Indx = 1

            While Indx <= cnt

                Set pDef = pDefColl.Item(Indx)

                Indx = Indx + 1

            Wend

            Set rtParams = pDefColl.GetParameters()

            Set rtParam = rtParams.Item("InParam1")

            rtParam.Value = "songfun"

            qtApp.Test.Run , True, rtParams

            MsgBox rtParams.Item("OutParam1").Value

            做完这步之后,保存这个vbs文件,双击执行这个vbs文件,你会发现它自动启动了QTP,而且进行了自动测试,最后还取到了运行成功与否的布尔值。

            这就是关于Test、Top-Level Action参数使用的例子,它的参数的整个传递过程是:

            外部vbs文件 传参数给QuickTest的Test的输入参数InTest1,然后InTest1传参数到InAction1去驱动了Action1的测试,

            然后通过这个Action1得出了OutAction1的值,然后通过OutAction1传给OutTest1,最后再传回到vbs文件中。

            示例用MsgBox来打出重新传回到vbs文件中的字符串

  • 【转】远程电脑中运行QTP自动化程序

    2010-02-10 16:09:51

    默认情况下,当你在自动化程序中创建一个Application对象时,该对象创建于本地电脑中(使用QTP的本地副本)。你也可以选择在远程QTP电脑中运行自动化脚本。为了实现这个目标,你必须:

    1.保远程电脑的Distributed COM(DCOM)配置属性已设置为允许你运行QTP自动化脚本。

    2.自动化程序的创建Application对象的脚本行,指定远程电脑的名称,例如在VBscript的CreateObject函数中使用可选参数location。

    名词:

    COM-组件对象模型。COM提供了一套允许同一台计算机上的客户端和服务器之间进行通信的接口。

    DCOM-分布式组件对象模型。是一系列微软的概念和程序接口,利用这个接口,客户端程序对象能够请求来自网络中另一台计算机上的服务器程序对象。

    设置远程电脑中的DCOM配置属性

    要在远程电脑中运行自动化程序,必须确保在远程电脑的DCOM设置中,给了你装载及设置QTP COM 服务器的权限。

    下面描述了在远程电脑中设置DCOM的过程。注意因为远程电脑的操作系统的不同,DCOM配置对话框中显示可能不同。

    (下面的图片都是WinXP操作系统中的):

    1.在远程电脑中,选择Start>Run,打开Run对话框。

    2.在命令栏输入“dcomcnfg”后,点击OK,打开DCOM配置对话框或组件服务窗口(这与操作系统有关),并显示当前电脑中有效的COM应用程序。

    3.在列表中选择“QuickTest Professional Automation”,打开属性对话框。

    4.在QuickTest Professional Automation对话框中,选中Security(安全)页签。

    5.在“launch permissions”(启动和激活权限)部分,选择custom(“自定义”)选项,然后点击EDIT(“编辑”)按钮。

    6.使用Add及Remove操作选择网络用户或组,让用户或组就可以在本地电脑中通过自动化程序,远程让QTP脚本在本电脑上运行。当完成设置用户或组后,点击OK保存设置。

    7.重复5、6步,设置“configuration permissions”部分,设置用户或组,让用户或组可以通过自动化程序,远程修改本电脑的QTP的选项配置。

    8.在QuickTest Professional Automation对话框中,点击Identity(标识)页签,选择interactive user选项。

    9.点击OK保存QuickTest Professional Automation属性设置。

    10.点击OK关闭DCOM配置对话框,或关闭组件服务窗口。

    在远程电脑中创建Application

    当远程电脑的DCOM设置完成后,你就可以在自动化程序中指定该远程电脑。

    在VBscrīpt中,你可以在CreateObject函数的可选参数location中指定远程电脑名称。例如:在一个名为MyServer的远程电脑上运行自动程序,你可以编写以下脚本:

    Dim qtApp

    Set qtApp=CreateObject(“QuickTest.Application”,”MyServer”)

  • 【转帖】Windows Hook <2> 勾子基本理念

    2010-01-20 12:08:51

    上一期我们讲了勾子基本概念和一些简单的应用

    这一期我们就来学习用用钩子技术和内存文件映射共享技术来实现远程线程插入
      
       现在网上关于这项编程技术的介绍满天飞,因为要想写出一个好的后门,该后门至少要达到高隐蔽.
    防查杀,无端口,自启动等要求,而将木马以DLL的形式嵌入到系统进程中,基本上可以满足要求,而
    这种远程线程注入技术也成为现代后门和木马程序的一项标准技术指标.
    如果大家要想更为清晰地掌握该项编程技术,强烈推荐细读jeffery Richter的<<Windows核心技术>>.
    该书个人觉得是每个学习Windows黑客编程技术爱好者的圣经.

    大家知道,传统的远程线程插入是通过以下几个API来完成的;

    ·OpenProcess - 用于打开要寄生的目标进程。

    ·VirtualAllocEx/VirtualFreeEx - 用于在目标进程中分配/释放内存空间。

    ·WriteProcessMemory - 用于在目标进程中写入要加载的DLL名称。

    ·CreateRemoteThread - 远程加载DLL的核心内容,用于控制目标进程调用API函数。

    ·LoadLibrary - 目标进程通过调用此函数来加载病毒DLL。

    这种方法虽然好,但有个缺点:只能在NT核心的系统上有效,在98中无效

    并且由于易DLL的特殊性,上面的方法并不奏效,虽然可以用写入汇编码来解决问题

    但也较不方便,钩子的出现为我们解决了这个难题

    通过钩子实现远程线程插入的思路如下:

    通过安装windows 消息钩子WH_GETMESSAGE,把待插线程代码所在的DLL注入到其他进程里
    在钩子回调函数中,判断当前进程ID是否是要插入的进程ID,如果是则创建一个新线程
    这个新线程函数就是我们要执行的代码所在的函数,到这里也就达到了我们的目地.

    现在就产生了一个新问题,由于我们的要执行的代码是放在一个DLL里面的,创建新线程就需要加载
    这个DLL,就需要知道DLL路径,还有判断当前进程ID是否是要插入的进程ID,首先也要知道要插入的
    进程ID是多少等等这些信息,这就涉及到进程通讯,我们可以用文件映射技术来进行进程通讯.

    文件映射主要是通过以下几个API来完成的:

    *CreateFileMapping     //创建文件映射对象 ,成功返回文件映射对象句柄
         Dll命令名:CreateFileMapping
         所处动态链接库的文件名:kernel32
         在所处动态链接库中的命令名:CreateFileMappingA
         返回值类型:整数型
         参数<1>的名称为“文件映射句柄”,类型为“整数型”。注明:指定欲在其中创建映射的一个文件句柄。&HFFFFFFFF&(-1)表示在内存中创建一个文件映射。
         参数<2>的名称为“安全对象”,类型为“SECURITY_ATTRIBUTES”。注明:SECURITY_ATTRIBUTES指定一个安全对象,在创建文件映射时使用。如果为NULL(用ByVal As Long传递零),表示使用默认安全对象。
         参数<3>的名称为“打开映射方式”,类型为“整数型”。注明:下述常数之一:;PAGE_READONLY以只读方式打开映射;PAGE_READWRITE:以可读、可写方式打开映射;PAGE_WRITECOPY:为写操作留下备份可组合使用下述一个或多个常数;SEC_COMMIT:为文件映射一个小节中的所有页分配内存;SEC_IMAGE:文件是个可执行文件;SEC_RESERVE:为没有分配实际内存的一个小节保留虚拟内存空间
         参数<4>的名称为“文件映射最大长度”,类型为“整数型”。注明:文件映射的最大长度(高32位)。
         参数<5>的名称为“文件映射的最小长度”,类型为“整数型”。注明:文件映射的最小长度(低32位)。如这个参数和dwMaximumSizeHigh都是零,就用磁盘文件的实际长度。
         参数<6>的名称为“映射对象名”,类型为“文本型”。注明:指定文件映射对象的名字。如存在这个名字的一个映射,函数就会打开它。用vbNull创建一个无名的文件映射;。

    *OpenFileMappingA    //打开一个已存在的文件映射对象,成功返回打开的文件映射对象句柄
         Dll命令名:OpenFileMapping
         公开
         所处动态链接库的文件名:kernel32
         在所处动态链接库中的命令名:OpenFileMappingA
         返回值类型:整数型
         参数<1>的名称为“常数”,类型为“整数型”。注明:带有前缀FILE_MAP_???的一个常数。参考MapViewOfFile函数的dwDesiredAccess参数的说明。
         参数<2>的名称为“进程继承”,类型为“整数型”。注明:如这个函数返回的句柄能由当前进程启动的新进程继承,则这个参数为TRUE。
         参数<3>的名称为“文件映射对象名称”,类型为“文本型”。注明:指定要打开的文件映射对象名称;。

    *MapViewOfFile /将一个文件映射对象映射到当前应用程序的地址空间
    Dll命令名:MapViewOfFile
         将一个文件映射对象映射到当前应用程序的地址空间。MapViewOfFileEx允许我们指定一个基本地址来进行映射文件映射在内存中的起始地址。零表示出错。会设置GetLastError
         所处动态链接库的文件名:kernel32
         在所处动态链接库中的命令名:MapViewOfFile
         返回值类型:整数型
         参数<1>的名称为“hFileMappingObject”,类型为“整数型”。
         参数<2>的名称为“dwDesiredAccess”,类型为“整数型”。
         参数<3>的名称为“dwFileOffsetHigh”,类型为“整数型”。
         参数<4>的名称为“dwFileOffsetLow”,类型为“整数型”。
         参数<5>的名称为“dwNumberOfBytesToMap”,类型为“整数型”。

    通过以上API再结合易自带的写到内存(),指针到字节集(),指针到文本(),取字节集数据()等命令就可以在进程间读写数据了.
    内存文件映射技术就介绍到这里,在下面的实例中有具体应用,我就不多说了.
    以资源管理器进程(explorer.exe)为例,我们来开始解析程序:

    程序基本原理:
    start.exe 先在内存中创建一个映射文件,把自己的线程ID和查找到的 Explorer进程ID
    以及HookDL.dll的路径写到映射文件,再安装 HookDL.dll 中的 WH_GETMESSAGE 钩子,
    此时,start.exe进入消息循环,直到收到被插进程发来的线程退出消息WM_QUIT
        在钩子回调函数中,首先把start.exe在内存中创建的映射文件映射到当前进程,然后
    判断当前进程ID是否先前 Start.exe 查找到的 Explorer进程ID, 是的话,则
    再次LoadLibrary(HookDLL.dll),并定位到其中ThreadPro函数. 此时创建一个
    新线程,线程函数就是ThreadPro,该新线程首先往Start.exe消息队列放置一个线
    程退出消息 WM_QUIT,导致其消息循环结束.
         此时插入线程完成..可以看到屏幕左上角不断变化的数字..说明我们的代码正在执行.
    进程列表却没有Start.exe,用进程管理观察,可发现Explorer进程,的确多了个线程,且来自
    HookDLL.dll ..如果希望插入Explorer的线程结束,按 Alt+L 即可...   :)  

    现在我们来看看主程序代码,为照顾新手,我会逐行分析:

    .版本 2
    '系统核心支持库
    .应用接口支持库

    .程序集 窗口程序集1
    .程序集变量 Explorer_PID, 整数型
    .程序集变量 hhook, 整数型
    .程序集变量 FileMapH, 整数型
    .程序集变量 DLLPath, 文本型

    .子程序 __启动窗口_创建完毕
    .局部变量 nil, SECURITY_ATTRIBUTES
    .局部变量 TheNodeP, 整数型
    .局部变量 ThreadMessage, MSG
    .局部变量 MainPath, 字节集
    .局部变量 ExplorerID, 字节集
    .局部变量 MainThread, 字节集
    .局部变量 Mainhhook, 字节集

    ' 指定DLL路径
    DLLPath = 取运行目录 () + “\HookDLL.dll”

    ' 检查插入线程是否已经存在
    FileMapH = api_OpenFileMapping (#FILE_MAP_ALL_ACCESS, 0, “hacker0058Explorer8Mazi”)
    api_CloseHandle (FileMapH)

    .如果真 (FileMapH = 0)   '如果插入线程不存在,开始插入Explorer_PID = 取进程PID (“explorer.exe”)   ' 这里指定要插入的进程,用到了子程序:取进程PID()                                                   
        
    .如果真 (Explorer_PID = 0)     '查找explorer失败
            信息框 (“寻找指定进程出错!”, 0, )
            结束 ()
    .如果真结束
    '创建内存映射文件  
         FileMapH = api_CreateFileMapping (-1, nil, #PAGE_READWRITE, 0, 100,“HookExplorer8Mazi”)  
        
    '映射到本进程空间   
    TheNodeP = api_MapViewOfFile (FileMapH, #FILE_MAP_ALL_ACCESS, 0, 0, 0)
        
    '写入共享数据
         ExplorerID = 到字节集 (Explorer_PID)
         MainThread = 到字节集 (当前线程标志符_ ())
         MainPath = 到字节集 (DLLPath) + { 0, 0, 0 }   ' 字符串是以两字节的0为结束标志的,所以这里加上{ 0, 0, 0 }                                                    
         写到内存 (ExplorerID + MainThread + MainPath, TheNodeP, )
        
    '关闭内存映射
        API_UnmapViewOfFile (TheNodeP)

    '挂DLL跳板钩子   
        hMod = GetMsgHookOn ()
         .如果真 (hMod = 0)
             输出调试文本 (“挂DLL跳板钩子失败!”)
             api_CloseHandle (FileMapH)   ' 关闭映射文件
             结束 ()
         .如果真结束
    '等待插入Explorer的新线程发来消息    
        api_GetMessage (ThreadMessage, 0, 0, 0)

    '脱DLL跳板钩子   
        GetMsgHookOff ()
    ' 御载DLL      
         api_FreeLibrary (hMod)
    ' 关闭映射文件
         api_CloseHandle (FileMapH)  
       
    .如果真结束
    结束 ()

    .子程序 取进程PID, 整数型, , 成功返回进程PID,失败返回0
    .参数 标志文本, 文本型, , 进程名或窗口名
    .局部变量 进程, 进程信息, , "0"
    .局部变量 PID, 整数型
    .局部变量 i, 整数型

    PID = 0
    .如果真 (倒找文本 (标志文本, “.”, , 真) ≠ -1)
         进程 = 取系统进程列表 ()
         .计次循环首 (取数组成员数 (进程), i)
             .如果真 (倒找文本 (进程 [i].进程名称, 标志文本, , 真) ≠ -1)
                 PID = 进程 [i].进程标识符
                 跳出循环 ()
             .如果真结束

         .计次循环尾 ()
    .如果真结束
    .如果真 (PID = 0)
         API_取进程标识符 (api_FindWindow (0, 标志文本), PID)
    .如果真结束
    返回 (PID)

    ============================================================

    .现在主程序已经完成了它的任务,现在来看看我们执行的核心,HookDLL.dll的代码:


    .版本 2

    .全局变量 TheNodeP, 整数型
    .全局变量 hhook, 整数型, 公开
    .全局变量 DLL, Main

    .程序集 HOOK程序集
    .程序集变量 hMod, 整数型
    .程序集变量 lpProc, 子程序指针


    .程序集 DLL程序集
    .版本 2
    .子程序 _启动子程序, 整数型, , 请在本子程序中放置动态链接库初始化代码

    复制共享数据 ()   '初始化代码,只有在程序第一次加载DLL时才被执行
    _临时子程序 ()
    返回 (0)

    '先看看钩子回调函数:

    .子程序 GetMsgProc, 整数型, 公开, 钩子回调函数
    .参数 code, 整数型
    .参数 wParam, 整数型
    .参数 lParam, 整数型
    .局部变量 lpThreadA, SECURITY_ATTRIBUTES
    .局部变量 LibraryH, 整数型
    .局部变量 ThreadPt, 整数型
    .局部变量 ThreadID, 整数型

    ' 截获到消息,开始执行下面的自定以代码(回调函数)
    复制共享数据 ()  
    .如果真 (TheNodeP ≠ 0 且 pnode.ExplorerID ≠ 0 且 api_GetCurrentProcessId () = pnode.ExplorerID)   ' 是资源管理器
         .如果真 (倒找文本 (MainPath, “.dll”, , 真) ≠ -1)   ' DLL路径是否正确
             LibraryH = api_LoadLibraryA (MainPath)   ' 装载动态链接库
         .如果真结束
         .如果真 (LibraryH ≠ 0)
             ThreadPt = 到整数 (api_GetProcAddress (LibraryH, “ThreadPro”))   ' 定位线程函数
             .如果真 (ThreadPt ≠ 0)
                 api_CreateThread (lpThreadA, 0, ThreadPt, 0, 0, ThreadID)   ' 创建新线程
             .如果真结束

         .如果真结束

    .如果真结束
    返回 (api_CallNextHookEx (hhook, code, wParam, lParam))   ' 钩子循环

    再看看子程序:复制共享数据

    .子程序 复制共享数据, 逻辑型
    .局部变量 i, 整数型
    .局部变量 MainPath, 文本型
    .局部变量 FileMapH, 整数型

    FileMapH = api_OpenFileMapping (#FILE_MAP_ALL_ACCESS, 0, “HookExplorer8Mazi”)
    .如果真 (FileMapH = 0)
         输出调试文本 (“打开内存映射文件出错!”)
         返回 (假)
    .如果真结束
    TheNodeP = api_MapViewOfFile (FileMapH, #FILE_MAP_ALL_ACCESS, 0, 0, 0)
    .如果真 (TheNodeP = 0)
         api_CloseHandle (FileMapH)
         输出调试文本 (“映射到本进程空间出错!”)
         返回 (假)
    .如果真结束

    '取回共享数据
    DLL.ExplorerID = 取字节集数据 (指针到字节集 (TheNodeP, 4), 3, )'整数型数据尺寸大小是4
    DLL.MainThread = 取字节集数据 (指针到字节集 (TheNodeP + 4, 4), 3, )    '+4
    DLL.MainPath = 指针到文本 (TheNodeP + 8)    '+8
    API_UnmapViewOfFile (TheNodeP)   ' 关闭内存映射
    api_CloseHandle (FileMapH)   ' 关闭文件映射对象
    返回 (真)

    '钩子回调函数完成,现在开始写待插线程代码,这里实现写文字到屏幕的功能

    .版本 2

    .子程序 ThreadPro, , 公开, 待插线程代码
    .局部变量 Count, 整数型
    .局部变量 theMsg, MSG
    .局部变量 FileMap, 整数型
    .局部变量 nil, SECURITY_ATTRIBUTES
    .局部变量 HotKeyID, 整数型

    api_PostThreadMessage (DLL.MainThread, #WM_QUIT, 0, 0)
    FileMap = api_OpenFileMapping (#FILE_MAP_ALL_ACCESS, 0, “hacker0058Explorer8Mazi”)   ' 禁止重复运行
    api_CloseHandle (FileMap)
    .如果真 (FileMap = 0)
         FileMap = api_CreateFileMapping (-1, nil, 4, 0, 2, “hacker0058Explorer8Mazi”)
         Count = 0
         HotKeyID =GlobalAddAtom(“hacker0058andALT+L”) ' 申请全局原子
         RegisterHotKey (0, HotKeyID, 1, #L键)            '注册热键Alt+L
         .判断循环首 (api_PeekMessage (theMsg, 0, 0, 0, #PM_REMOVE) = 0 且 取反 (theMsg.message = #WM_HOTKEY) 或 theMsg.message = #WM_QUIT)
             WriteScreen (“   ” + Int2Hex (Count) + “   [The Thread is From ” + 取执行文件名 () + “ and Alt+L to Exit   ”)
             Count = Count + 1
             延时 (500)
         .判断循环尾 ()
         api_CloseHandle (FileMap)
         UnregisterHotKey(0, HotKeyID)    '撤销热键Alt+L
         DeleteAtom (HotKeyID)        '释放全局原子


    .子程序 WriteScreen, , , 写文字到屏幕
    .参数 s, 文本型
    .局部变量 hScreenDC, 整数型

    hScreenDC = 取设备场景_ (0)
    api_TextOut (hScreenDC, 0, 0, s, 取文本长度 (s))   '写文字到屏幕
    api_ReleaseDC (0, hScreenDC)   '撤消写文字到屏幕


    .子程序 Int2Hex, 文本型, , 数值转字符串
    .参数 v, 整数型
    .局部变量 i, 整数型
    .局部变量 a, 文本型
    .局部变量 b, 文本型

    b = 取十六进制文本 (v) '
    .计次循环首 (8 - 取文本长度 (b), i)
         a = a + “0”
    .计次循环尾 ()
    返回 (“$” + a + b)


    .子程序 GetMsgHookOff, 逻辑型, 公开, 关闭全局消息钩子

       返回 (api_UnhookWindowsHookEx (hhook))


    .子程序 GetMsgHookOn, 整数型, 公开, 安装全局消息钩子

    hMod = api_LoadLibraryA (MainPath) '加载DLL,返回模块地址

    lpProc = api_GetProcAddress (hMod, “GetMsgProc”) 'DLL中钩子回调函数地址


    hhook = api_SetWindowsHookExA (#WH_GETMESSAGE, lpProc, hMod, 0) '安装钩子

    返回 (hMod)


    好了,整个框架就这样了,由于篇幅原因,具体的不再细说了.附件里有完整的易程序源码,具体细节大家可以参阅,源码在Windows xp+sp2 易4.02中测试通过.

    下期预告:

    下一期我们将介绍API Hook技术,API Hook技术应用广泛,常用于屏幕取词,网络防火墙,病毒木马,加壳
    软件,串口红外通讯,游戏外挂,internet通信等领域.API HOOK的中文意思就是钩住API,对API进行预处理,先执行我们的函数,例如我们用API Hook技挂接ExitWindowsEx API函数,使关机失效等等......

    敬请期待

  • 【转帖】函数指针类型的定义

    2010-01-20 12:08:37

    typedef int (*a) (int *b,int *c)

    我做个比方:

    typedef int (*a) (int *b,int *c)

    上面是什么意思 我没看明白,     特别是(int *b,int *c)中的","是","运算符吗?
    (int *b,int *c)的意思就是int *a   ???

    这个就是传说中的"函数指针类型"了。实际上这样的写法是将函数指针定义为一个类型,这个类型叫做"a"! 然后你就可以用a去定义一个变量.比如:
    已经定义了两个函数叫 int test1(int *arg1,int *arg2){......} 和 int test2(int *arg1,int arg2){.......}
    在我已经定义了typedef int (*a) (int *b,int *c)以后,我可以这样写:
    a funpoint;
    这样我就已经成功地定义了一个函数指针funpoint,这个指针专门指向返回值为int类型,参数表为(int *,int *)类型的函数。
    然后,我可以这么干:
    funpoint = test1;
    funpoint(*a,*b);
    我也可以这么干:
    funpoint = test2;
    funpoint(*a,*b);
    如此用一个指针变量就能够调用两个函数体,岂不爽哉?
    而且我还可以把这个指针作为别的函数的参数,想象一下用函数作参数的境地!

  • 【转帖】Windows Hook (3) API Hook 初探

    2010-01-20 12:04:59

     

    .....

    上一期,我们讲了用HOOK技术实现远程线程插入,相信大家还记忆犹新.

    这一期我们来谈谈 API HOOK

        API Hook技术应用广泛,常用于屏幕取词,网络防火墙,病毒木马,加壳软件,串口红外通讯,游戏外

    挂,internet通信等领域API HOOK的中文意思就是钩住API,对API进行预处理,先执行我们的函数,例

    如我们用API Hook技术挂接ExitWindowsEx API函数,使关机失效,挂接ZwOpenProcess函

    数,隐藏进程等等......

      
       总的来说,常用的挂钩API方法有以下两种:

    <一>改写IAT导入表法
       
        我们知道,Windows下的可执行文档的文件格式是一种叫PE(“portable executable”,可移植

    的可执行文件)的文件格式,这种文件格式

    是由微软设计的,接下来这张图描述了PE文件的结构:

          +-------------------------------+      - offset 0
          | MS DOS标志("MZ") 和 DOS块      |
          +-------------------------------+     
          |       PE 标志 ("PE")            |
          +-------------------------------+
          |              .text              |      - 模块代码
          |            程序代码             |
          |                                |
          +-------------------------------+
          |              .data              |      - 已初始化的(全局静态)数据
          |           已初始化的数据        |
          |                                |
          +-------------------------------+
          |             .idata              |      - 导入函数的信息和数据
          |             导入表              |       
          |                                |
          +-------------------------------+
          |             .edata              |      - 导出函数的信息和数据
          |             导出表              |       
          |                                |
          +-------------------------------+
          |            调试符号             |
          +-------------------------------+

        

        这里对我们比较重要的是.idata部分的导入地址表(IAT)。这个部分包含了导入的相关信息和导

    入函数的地址。有一点很重要的是我们必须知道PE文件是如何创建的。当在编程语言里间接调用任

    意API(这意味着我们是用函数的名字来调用它,而不是用它的地址),编译器并不直接把调用连接到

    模块,而是用jmp指令连接调用到IAT,IAT在系统把进程调入内存时时会由进程载入器填满。这就是

    我们可以在两个不同版本的Windows里使用相同的二进制代码的原因,虽然模块可能会加载到不同的

    地址。进程载入器会在程序代码里调用所使用的IAT里填入直接跳转的jmp指令。所以我们能在IAT里

    找到我们想要挂钩的指定函数,我们就能很容易改变那里的jmp指令并重定向代码到我们的地址。完

    成之后每次调用都会执行我们的代码了。
       
        我们通过使用imagehlp.dll里的ImageDirectoryEntryToData来很容易地找到IAT。

       .DLL命令 ImageDirectoryEntryToData, 整数型, "imagehlp", , , 返回IMAGE_IMPORT_DEscrīptOR数组的首地址
         .参数 Base, 整数型, , 模块句柄
         .参数 MappedAsImage, 逻辑型, , 真
         .参数 DirectoryEntry, 整数型, , 恒量:IMAGE_DIRECTORY_ENTRY_IMPORT,1
         .参数 Size, 整数型, 传址, IMAGE_IMPORT_DEscrīptOR数组的大小


       .数据类型 IMAGE_IMPORT_DEscrīptOR, , 输入描述结构
         .成员 OriginalFirstThunk, 整数型, , , 它是一个RVA(32位),指向一个以0结尾的、由IMAGE_THUNK_DATA(换长数据)的RVA构成的数           组,其每个IMAGE_THUNK_DATA(换长数据)元素都描述一个函数。此数组永不改变。
         .成员 TimeDateStamp, 整数型, , , 它是一个具有好几个目的的32位的时间日期戳.
         .成员 ForwarderChain, 整数型, , , 它是输入函数列表中第一个中转的、32位的索引。
         .成员 Name, 整数型, , , 它是一个DLL文件的名称(0结尾的ASCII码字符串)的、32位的RVA。
         .成员 FirstThunk, 整数型, , , 它也是一个RVA(32位),指向一个0结尾的、由IMAGE_THUNK_DATA(换长数据)的RVA构成的数组,其每           个IMAGE_THUNK_DATA(换长数据)元素都描述一个函数。此数组是输入地址表的一部分,并且可以改变。

       
        如果我们找到了就必须用VirtualProtectEx函数来改变内存页面的保护属性,然后就可以通过
    WriteProcessMemory在内存中的这些部分写入代码了。在改写了地址之后我们要把保护属性改回来
    。在调用VirtualProtectEx之前我们还要先知道有关页面的信息,这通过VirtualQueryEx来实现。
        
        这种方法的好处是比较稳定,但有漏API的可能,因为并不是所有的API调用都是通过IAT的,可能易
    程序就是这种情况,我当初也是想用这种方法,但是在易里面调试时总不能在IAT里得到正确的程序调
    用的API,常常是些无关的API(可能是易的核心支持库在做怪),不得不放弃.

    <二>直接改写API函数入口点.(改写内存地址JMP法)


         改写函数入口点开始的一些字节这种方法相当简单,这也是本文采用的方法,就象改变IAT里的地址一样,我们也要先修改页面属性。

    .DLL命令 返回页面虚拟信息, 整数型, "kernel32", "VirtualQueryEx"
         .参数 hProcess, 整数型, , 对象的进程句柄,可以使用函数 OpenProcess() 返回。
         .参数 lpAddress, 整数型, , 对象指针地址
         .参数 lpBuffer, 虚拟信息, , 返回的虚拟信息
         .参数 dwLength, 整数型, , 信息长度,已知 28


    .DLL命令 修改页面虚拟保护, 逻辑型, "kernel32", "VirtualProtectEx"
         .参数 hProcess, 整数型, , 对象的进程句柄,可以使用函数 OpenProcess() 返回。
         .参数 lpAddress, 整数型, , 虚拟信息.BaseAddress
         .参数 dwSize, 整数型, , 修改虚拟保护的长度.
         .参数 flNewProtect, 整数型, , 修改类型,#PAGE_EXECUTE_READWRITE 64为可读写模式
         .参数 lpflOldProtect, 整数型, 传址, 虚拟信息.Protect

        通过调用VirtualQueryEx,VirtualProtectEx这两个API就可以修改我们要挂勾的API所在的页面属性为可读写模式,接着就可以调用API函数

    WriteProcessMemory写内存字节了.

        到这里,也就没有什么难点了,我们拿API函数ExitWindowsEx来简单说明下,如果要实现自定API函

    数,可以写入一个跳转指令,JMP OX00000(其中OX00000为自定API函数地址),请参照论坛上的教程,也

    当作我留给大家的问题吧,呵呵.               

    制作目标:挂勾ExitWindowsEx,使关机无效.

    分析:只要求关机无效,即ExitWindowsEx无效就可以了,我们不必那么麻烦去写跳转指令,可以直接在
          
          ExitWindowsEx首地址写入一个垃圾指令,使它后面的代码无法执行就可以了,我们可以写入     
          {104}→汇编 PUSH 或者 {195}→汇编 retn→返回命令(在易里面调用这个API会造成错误)这
         
          样就可以达到目地了.

    看下面的代码:

    ===================

    '通过API函数GetProcAddress和GetModuleHandleA很容易得到API函数ExitWindowsEx的首地址.


    API = GetProcAddress(GetModuleHandleA(“user32.dll”), “ExitWindowsEx”)


    API_BAK = 指针到字节集 (API, 1)   '为了以后可以还原,我们先备份一下

    '用OpenProcess打开其他进程前先提升进程权限,这样可以打开几乎所有的非内核程序

    提升进程权限 ()   '这个模块在附件里


    ==============================修改API首地址()=================

    .子程序 修改API首地址, 逻辑型
    .参数 Process, 整数型, , 目标进程句柄
    .参数 Papi, 整数型, , 要修改的API函数地址
    .参数 type, 字节集, , 要改写的内容,字节集
    .局部变量 mbi, 虚拟信息
    .局部变量 结果, 逻辑型
    .局部变量 MyAPI, 整数型
    .局部变量 Ptype, 字节集

    .如果真 (Papi = 0)
         返回 (假)
    .如果真结束
    .如果真 (返回虚拟信息 (Process, Papi, mbi, 28) = 0)
         返回 (假)
    .如果真结束
    .如果真 (修改虚拟保护 (Process, mbi.BaseAddress, 取字节集长度 (type) + 1, #PAGE_EXECUTE_READWRITE, mbi.Protect) = 假)
         返回 (假)
    .如果真结束
    结果 = 写内存字节 (Process, Papi, type, 取字节集长度 (type), 0)
    修改虚拟保护 (Process, mbi.BaseAddress, 取字节集长度 (type) + 1, #PAGE_EXECUTE_READ, mbi.Protect)   ' 改回只读模式
    返回 (结果)


    ========================

    我们通过以下几个API就可以举出系统所有进程,得到它们的进程ID

    *CreateToolhelp32Snapshot,创建进程快照

    *Process32First,开始进程快照  

    *Process32Next, 继续进程快照

    看代码:

    =========================刷新进程信息==============
    .子程序 刷新进程信息
    .参数 进程信息输出, 进程信息输出, 数组
    .局部变量 临时变量, 整数型
    .局部变量 临时变量B, 整数型
    .局部变量 线程基本优先级, 整数型, , "0"
    .局部变量 临时进程信息, 进程信息输出

    临时变量 = 创建进程快照 (2, 0)
    临时进程信息.结构大小 = 296
    临时变量B = 开始进程快照 (临时变量, 临时进程信息)
    .判断循环首 (临时变量B ≠ 0)
         加入成员 (进程信息输出, 临时进程信息)
         临时变量B = 继续进程快照 (临时变量, 临时进程信息)
    .判断循环尾 ()
    关闭内核对象 (临时变量)

    ============================HOOK_API()================================

    .子程序 HOOK_API
    .参数 是否HOOK, 逻辑型, , 真,表示HOOK,假,表示还原
    .局部变量 i, 整数型

    刷新进程信息 (进程信息)
    .计次循环首 (取数组成员数 (进程信息), i)
         进程句柄 = 打开进程 (2035711, 0, 进程信息 [i].进程标识)
         .如果 (是否HOOK)
             修改API首地址 (进程句柄, API, { 195 })   ' 写入返回命令{195}
         .否则
             修改API首地址 (进程句柄, API, API_BAK)
         
          关闭内核对象 (进程句柄)
    .计次循环尾 ()

    =================================================================

    最后我们通过调用HOOK_API(真)就可以挂勾ExitWindowsEx,

    调用HOOK_API (假)就可以还原ExitWindowsEx.

    继续看代码:

    ======================================

    .子程序 _选择框_挂勾API_被单击

    .如果 (选择框_挂勾API.标题 = “挂勾API”)
         HOOK_API (真)
         选择框_挂勾API.标题 = “还原”
         信息框 (“恭喜,挂勾ExitWindowsEx成功,你现在不能关闭系统了,不信你可以试试!”, 64, )
    .否则
         HOOK_API (假)
         选择框_挂勾API.标题 = “挂勾API”
         信息框 (“提示:还原ExitWindowsEx成功,你现在可以关闭系统了!”, 48, “提示”)
         结束 ()

    =======================================

    题外话:

       有了这个程序,再结合API函数ShowWindow,控制网管软件窗口的可见性,就可以破解几乎所有网管

    软件的限制,不过,我不同意你用它拿来破解网管软件,我之所以这么说,只是想告诉大家一个道理:

    思维不要只限制到一个很狭隘的空间中去......

    ==========================================
    总结:

       怎么样,是不是很简单啊,呵呵,通过本期的学习,我们的Windows Hook 易核心编程也就告一段落了

    希望对你有所帮助,如果你想了解上期的内容,请在论坛里搜索我的名字.

    自由 平等 随意 突破......

    此文献给所有易的支持者

    下次再见.

  • 【转帖】Windows Hook (3) API Hook 续 拦截API

    2010-01-20 12:04:24

     

        本来计划中是没有这一章的,但自从我的Windows Hook 易核心编程(3) API Hook发表以来,得到

    了广大易友的支持,这种情况很令我感动,于是再接再力,写出了这章,希望没有辜负广大易友对我的

    支持与信任,好,废话不多说了,我们言归正转.

       在开始之前,让我们先来回顾一下:

    什么叫Hook API?
       
        所谓Hook就是钩子的意思,而API是指Windows开放给程序员的编程接口,使得在用户级别下可

    以对操作系统进行控制,也就是一般的应用程序都需要调用API来完成某些功能,Hook API的意思

    就是在这些应用程序调用真正的系统API前可以先被截获,从而进行一些处理再调用真正的API来完

    成功能。在讲Hook API之前先来看一下如何Hook消息,例如Hook全局键盘消息,从而可以知道用户

    按了哪些键.这种Hook消息的功能可以由以下函数来完成,该函数将一新的Hook加入到原来的Hook

    链中,当某一消息到达后会依次经过它的Hook链再交给应用程序,这个在我的Windows Hook 易核心

    编程(1)和(2)里有具体的说明和应用,大家可以看一看.

       .DLL命令 api_SetWindowsHookExA, 整数型, , "SetWindowsHookExA"
         .参数 idHook, 整数型, , Hook类型,例如WH_KEYBOARD,WH_MOUSE
         .参数 lpfn, 子程序指针, , 钩子回调函数,Hook处理过程函数的地址,
         .参数 nMod, 整数型, , 包含Hook处理过程函数的dll句柄(若在本进程可以为NULL)
         .参数 dwThreadID, 整数型, , 要Hook的线程ID,若为0,表示全局Hook所有


    .DLL命令api_UnhookWindowsHookEx, 逻辑型, , "UnhookWindowsHookEx"
         .参数 hhook, 整数型,要关闭钩子的句柄.

        这里需要提一下的就是如果是Hook全局的而不是某个特定的进程则需要将Hook过程编写为一个

    DLL,以便让任何程序都可以加载它来获取Hook过程函数。而对于Hook API微软并没有提供直接的

    接口函数,也许它并不想让我们这样做,不过有2种方法可以完成该功能。第一种,修改可执行文

    件的IAT表(即输入表),因为在该表中记录了所有调用API的函数地址,则只需将这些地址改为自

    己函数的地址即可,但是这样有一个局限,因为有的程序会加壳,这样会隐藏真实的IAT表,从而

    使该方法失效。第二种方法是直接跳转,改变API函数的头几个字节,使程序跳转到自己的函数,

    然后恢复API开头的几个字节,在调用API完成功能后再改回来又能继续Hook了,但是这种方法也有

    一个问题就是同步的问题,当然这是可以克服的,并且该方法不受程序加壳的限制。


       
        上一章我们就是用的第二种方法,通过直接改写API函数ExitWindowsEx入口点为{195}(即汇编的

    返回命令retn),达到关机失效的目地,其实这不算是真正的API HOOK,没有实现自定API函数的功能.

    记得我说过,如果要实现自定API函数,可以写入一个跳转指令,JMP OX00000(其中OX00000为自定API

    函数地址),最后还给大家留了一道题,就是参照论坛上的教程写出自定API函数的功能,不知道大家

    完成的什么样了,呵呵.


        其实,要真正实现API HOOK,用JMP OX00000是不能达到我们的目地的,因为要转移参数,我们先要

    mov eax OX00000 ,然后再jmp eax,在易里面用用字节集连起来表示就是:

            { 184 } + 到字节集 (到整数 (&new_api)) + { 255, 224 }

       其中new_api就是新的自定API函数地址,结合上期的内容,我们现在就来解析核心代码:

    ===================================
    .子程序 修改API首地址, 逻辑型
    .参数 Process, 整数型, , 目标进程句柄
    .参数 Papi, 整数型, , 要修改的API函数地址
    .参数 type, 字节集, , 要改写的内容,字节集
    .局部变量 mbi, 虚拟信息
    .局部变量 结果, 逻辑型
    .局部变量 MyAPI, 整数型
    .局部变量 Ptype, 字节集

    .如果真 (Papi = 0)
         返回 (假)
    .如果真结束
    .如果真 (返回虚拟信息 (Process, Papi, mbi, 28) = 0)
         返回 (假)
    .如果真结束
    .如果真 (修改虚拟保护 (Process, mbi.BaseAddress, 取字节集长度 (type) + 1, #PAGE_EXECUTE_READWRITE, mbi.Protect) = 假)
         返回 (假)
    .如果真结束
    结果 = 写内存字节 (Process, Papi, type, 取字节集长度 (type), 0)
    修改虚拟保护 (Process, mbi.BaseAddress, 取字节集长度 (type) + 1, #PAGE_EXECUTE_READ, mbi.Protect)   ' 改回只读模式
    返回 (结果)
    ==================================

    这个子程序也就是上期用到的,上一期第3个参数是写的{195},这里换成 { 184 } + 到字节集 (到

    整数 (&new_api)) + { 255, 224 }就可以了,新的API函数要和原API函数的参数一样,还拿API函
    数ExitWindowsEx来说,看下面的新的API函数:

    ======================================
    .子程序 new_api, 整数型, , 新的API函数,要与原来的API函数的参数一样
    .参数 标志, 整数型
    .参数 保留值, 整数型

    ' 给自己留条后门先
    .如果 (保留值 = 3389)
         HOOKAPI (假)
         api_ExitWindowsEx (标志, 保留值)
    .否则
         信息框 (“您的操作已经被易拦截了!” + #换行符 + “ExitWindowsEx” + #换行符 + “参数<1>:” + 到文本 (标志) + #换行符 + “参数<2>:” + 到文本 (保留值), 0, )
    .如果结束
    返回 (1)

    =======================================

       看下面的代码:

    .子程序 HOOKAPI, 逻辑型
    .参数 是否HOOK, 逻辑型
    .局部变量 TYPE, 字节集

    .如果 (是否HOOK)
         TYPE = { 184 } + 到字节集 (到整数 (&new_ExitWindowsEx)) + { 255, 224 }
         返回 (修改API首地址(取当前进程伪句柄 (), API, TYPE))
    .否则
         返回 (修改API首地址(取当前进程伪句柄 (), API, API_BAK))


    当然,改写前,还是要备份和取API函数地址的:

    API = 取DLL函数地址 (取程序或DLL句柄 (“user32.dll”), “ExitWindowsEx”)

    API_BAK = 指针到字节集 (API, 8)

    ============================================
       这样,只要一句HOOKAPI(真)就可以轻松改写本进程的API函数,一句HOOKAPI(假)就可以还原API函

    数,当然前提是在本进程,因为地址不能跨进程使用,要想挂勾其他进程,我们可以使用我前面提到

    的Windows全局消息勾子,为了取得较好的效果,推荐使用WH_GETMSSAGE类的勾子.

       要使用全局消息勾子,先要在一个独立的DLL里声明钩子回调函数,看下面的代码:

    ============================
    .子程序 GetMsgProc, 整数型, 公开, 钩子回调函数
    .参数 code, 整数型
    .参数 wParam, 整数型
    .参数 lParam, 整数型

    返回 (api_CallNextHookEx (hhook, code, wParam, lParam))   ' 钩子循环

    ====================================
      
    主程序通过调用SetWindowsHookEx就可以安装钩子 ,继续看代码;

    .子程序 安装全局钩子, 整数型, , 安装全局消息钩子
    .参数 DLLPath, 文本型,DLL名
    .局部变量 hMod, 整数型
    .局部变量 lpProc, 子程序指针

    hMod = api_LoadLibraryA (DLLPath)
    lpProc = api_GetProcAddress (hMod, “GetMsgProc”)
    hook = api_SetWindowsHookExA (#WH_GETMESSAGE, lpProc, hMod, 0)
    返回 (hook)

    ======================================
    主程序通过api_UnhookWindowsHookEx(hook)就可以关闭全局钩子.

    结合下面的子程序(上期的HOOK_API)便可以还原API.
    =============================
    .子程序 还原API
    .局部变量 i, 整数型
    .局部变量 结果, 逻辑型
    .局部变量 进程信息, 进程信息输出, , "0"
    .局部变量 进程句柄, 整数型
    .局部变量 修改内容, 整数型

    刷新进程信息 (进程信息) '这个在上期中讲过,我就不重复了.
    .计次循环首 (取数组成员数 (进程信息), i)
         进程句柄 = 打开进程 (2035711, 0, 进程信息 [i].进程标识)
         .如果真 (API_BAK ≠ { 0 })
             结果 = 修改API首地址 (进程句柄, API, API_BAK)
         .如果真结束
         关闭内核对象 (进程句柄)
    .计次循环尾 ()
    ===========================
        可以看出,我们创建的Hook类型是WH_CALLWNDPROC类型,该类型的Hook在进程与系统一通信时

    就会被加载到进程空间,从而调用dll的初始化函数完成真正的Hook,而在SetWindowsHookEx函数

    中指定的HookProc函数将不作任何处理,只是调用CallNextHookEx将消息交给Hook链中下一个环节

    处理,因为这里SetWindowsHookEx的唯一作用就是让进程加载我们的dll。


         整个框架就是这样了,具体的就不在细说了.附件里有完整的易源码,大家可以下载下来研究.

    以上就是一个最简单的Hook API的例子,该种技术可以完成许多功能。例如网游外挂制作过程中

    截取发送的与收到的封包(API函数send)即可使用该方法,或者也可以在Hook到API后加入木马功能

    ,反向连接指定的主机或者监听某一端口,还有许多加壳也是用该原理来隐藏IAT表,填入自己的

    函数地址,等等.

        希望本文能够对一些朋友有所帮助,当然,一定有许多不足之处,希望看了文章的高手

    可以指出:以下程序在Windows XP +sp2和易4.02中测试通过,有什么Bug,请通知我.)

  • 【转帖】Windows Hook <1> 勾子基本理念

    2010-01-20 12:04:05

    <一>勾子基本概念
    本期导读:什么叫勾子,勾子又起什么作用,它有那些类别,怎么使用,等等这些问题将在本期找到答案
    =======================================================================================
    基本概念
    钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息或特定事件。
    钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控
    制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。
    运行机制
    1、钩子链表和钩子子程:
    每一个Hook都有一个与之相关联的指针列表,称之为钩子链表,由系统来维护。这个列表的指针指向指定的,应用程序定义的,被Hook子程调用的回调函数,也就是该钩子的各个处理子程。当与指定的Hook类型关联的消息发生时,系统就把这个消息传递到Hook子程。一些Hook子程可以只监视消息,或者修改消息,或者停止消息的前进,避免这些消息传递到下一个Hook子程或者目的窗口。最近安装的钩子放在链的开始,而最早安装的钩子放在最后,也就是后加入的先获得控制权。
    Windows 并不要求钩子子程的卸载顺序一定得和安装顺序相反。每当有一个钩子被卸载,Windows 便释放其占用的内存,并更新整个Hook链表。如果程序安装了钩子,但是在尚未卸载钩子之前就结束了,那么系统会自动为它做卸载钩子的操作。
    钩子子程是一个应用程序定义的回调函数(CALLBACK Function),不能定义成某个类的成员函数,只能定义为普通的C函数。用以监视系统或某一特定类型的事件,这些事件可以是与某一特定线程关联的,也可以是系统中所有线程的事件。
    钩子子程必须按照以下的语法:
    LRESULT CALLBACK HookProc
    (
    int nCode,
    WPARAM wParam,
    LPARAM lParam
    );
    HookProc是应用程序定义的名字。
    nCode参数是Hook代码,Hook子程使用这个参数来确定任务。这个参数的值依赖于Hook类型,每一种Hook都有自己的Hook代码特征字符集。
    wParam和lParam参数的值依赖于Hook代码,但是它们的典型值是包含了关于发送或者接收消息的信息。
    2、钩子的安装与释放:
    <1>钩子的安装
    使用API函数SetWindowsHookEx()把一个应用程序定义的钩子子程安装到钩子链表中。SetWindowsHookEx函数总是在Hook链的开头安装Hook子程。当指定类型的Hook监视的事件发生时,系统就调用与这个Hook关联的Hook链的开头的Hook子程。每一个Hook链中的Hook子程都决定是否把这个事件传递到下一个Hook子程。Hook子程传递事件到下一个Hook子程需要调用CallNextHookEx函数。
    HHOOK SetWindowsHookEx(
    int idHook,     //参数<1>
    HOOKPROC lpfn,   //参数<2>
    HINSTANCE hMod,    //参数<3>
    DWORD dwThreadId   //参数<4>
    );
    参数<1>idHook是钩子的类型,即它处理的消息类型
    参数<2>lpfn是钩子子程的地址指针。如果dwThreadId参数为0,或是一个由别的进程创建的线程的标识lpfn必须指向DLL中的钩子子程。除此以外,lpfn可以指向当前进程的一段钩子子程代码。                  参数<3>nMod是应用程序实例的句柄。标识包含lpfn所指的子程的DLL,如果dwThreadId 标识当前进程创建的一个线程,而且子程代码位于当前进程,hMod必须为NULL。可以很简单的设定其为本应用程序的实例句柄。
    参数<4>dwThreadID:与安装的钩子子程相关联的线程的标识符, 如果为0,钩子子程与所有的线程关联即为全局钩子。
    函数成功则返回钩子子程的句柄,失败返回(NULL)0。

    <2>钩子的循环
       以上所说的钩子子程与线程相关联是指在一钩子链表中发给该线程的消息同时发送给钩子子程,且被钩子子程先处理。在钩子子程中调用得到控制权的钩子函数在完成对消息的处理后,如果想要该消息继续传递,那么它必须调用另外一个SDK中的API函数CallNextHookEx来传递它,以执行钩子链表所指的下一个钩子子程。这个函数成功时返回钩子链中下一个钩子过程的返回值,返回值的类型依赖于钩子的类型。这个函数的原型如下:
    LRESULT CallNextHookEx
    (
    HHOOK hhook;
    int nCode;
    WPARAM wParam;
    LPARAM lParam;
    );
    hhook为当前钩子的句柄,由SetWindowsHookEx()函数返回。
    NCode为传给钩子过程的事件代码。
    wParam和lParam 分别是传给钩子子程的wParam值,其具体含义与钩子类型有关。
    钩子函数也可以通过直接返回(TRUE)真来丢弃该消息,并阻止该消息的传递。否则的话,其他安装了钩子的应用程序将不会接收到钩子的通知而且还有可能产生不正确的结果。
    <3>钩子的卸载
      
       钩子在使用完之后需要用UnHookWindowsHookEx()卸载,否则会造成麻烦。释放钩子比较简单,UnHookWindowsHookEx()只有一个参数。函数原型如下:
    UnHookWindowsHookEx
    (
    HHOOK hhook;
    );
    函数成功返回(TRUE)真,否则返回(FALSE)假。

    3、一些运行机制:
        在Win16环境中,DLL的全局数据对每个载入它的进程来说都是相同的;而在Win32环境中,情况却发生了变化,DLL函数中的代码所创建的任何对象(包括变量)都归调用它的线程或进程所有。当进程在载入DLL时,操作系统自动把DLL地址映射到该进程的私有空间,也就是进程的虚拟地址空间,而且也复制该DLL的全局数据的一份拷贝到该进程空间。也就是说每个进程所拥有的相同的DLL的全局数据,它们的名称相同,但其值却并不一定是相同的,而且是互不干涉的。
    因此,在Win32环境下要想在多个进程中共享数据,就必须进行必要的设置。在访问同一个Dll的各进程之间共享存储器是通过存储器映射文件技术实现的。也可以把这些需要共享的数据分离出来,放置在一个独立的数据段里,并把该段的属性设置为共享。必须给这些变量赋初值,否则编译器会把没有赋初始值的变量放在一个叫未被初始化的数据段中。
    #pragma data_seg预处理指令用于设置共享数据段。例如:
    #pragma data_seg("SharedDataName")
    HHOOK hHook=NULL;
    #pragma data_seg()
    在#pragma data_seg("SharedDataName")和#pragma data_seg()之间的所有变量将被访问该Dll的所有进程看到和共享。再加上一条指令#pragma comment(linker,"/section:.SharedDataName,rws"),那么这个数据节中的数据可以在所有DLL的实例之间共享。所有对这些数据的操作都针对同一个实例的,而不是在每个进程的地址空间中都有一份。
    当进程隐式或显式调用一个动态库里的函数时,系统都要把这个动态库映射到这个进程的虚拟地址空间里(以下简称"地址空间")。这使得DLL成为进程的一部分,以这个进程的身份执行,使用这个进程的堆栈。
    4、系统钩子与线程钩子:
    SetWindowsHookEx()函数的最后一个参数决定了此钩子是系统钩子还是线程钩子。
    线程勾子用于监视指定线程的事件消息。线程勾子一般在当前线程或者当前线程派生的线程内。
    系统勾子监视系统中的所有线程的事件消息。因为系统勾子会影响系统中所有的应用程序,所以勾子函数必须放在独立的动态链接库(DLL) 中。系统自动将包含"钩子回调函数"的DLL映射到受钩子函数影响的所有进程的地址空间中,即将这个DLL注入了那些进程。
    几点说明:
    (1)如果对于同一事件(如鼠标消息)既安装了线程勾子又安装了系统勾子,那么系统会自动先调用线程勾子,然后调用系统勾子。
    (2)对同一事件消息可安装多个勾子处理过程,这些勾子处理过程形成了勾子链。当前勾子处理结束后应把勾子信息传递给下一个勾子函数。
    (3)勾子特别是系统勾子会消耗消息处理时间,降低系统性能。只有在必要的时候才安装勾子,在使用完毕后要及时卸载。
    --------------------------------------------------------------------------------
    钩子类型
    每一种类型的Hook可以使应用程序能够监视不同类型的系统消息处理机制。下面描述所有可以利用的Hook类型。
    1、WH_CALLWNDPROC(4)和WH_CALLWNDPROCRET Hooks(12)
    WH_CALLWNDPROC和WH_CALLWNDPROCRET Hooks使你可以监视发送到窗口过程的消息。系统在消息发送到接收窗口过程之前调用WH_CALLWNDPROC Hook子程,并且在窗口过程处理完消息之后调用WH_CALLWNDPROCRET Hook子程。
    WH_CALLWNDPROCRET Hook传递指针到CWPRETSTRUCT结构,再传递到Hook子程。
    CWPRETSTRUCT结构包含了来自处理消息的窗口过程的返回值,同样也包括了与这个消息关联的消息参数。
    2、WH_CBT(5) Hook
    在以下事件之前,系统都会调用WH_CBT Hook子程,这些事件包括:
    1. 激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件;
    2. 完成系统指令;
    3. 来自系统消息队列中的移动鼠标,键盘事件;
    4. 设置输入焦点事件;
    5. 同步系统消息队列事件。
    Hook子程的返回值确定系统是否允许或者防止这些操作中的一个。
    3、WH_DEBUG(9) Hook
    在系统调用系统中与其他Hook关联的Hook子程之前,系统会调用WH_DEBUG Hook子程。你可以使用这个Hook来决定是否允许系统调用与其他Hook关联的Hook子程。
    4、WH_FOREGROUNDIDLE(11) Hook
    当应用程序的前台线程处于空闲状态时,可以使用WH_FOREGROUNDIDLE Hook执行低优先级的任务。当应用程序的前台线程大概要变成空闲状态时,系统就会调用WH_FOREGROUNDIDLE Hook子程。
    5、WH_GETMESSAGE(3) Hook
    应用程序使用WH_GETMESSAGE Hook来监视从GetMessage or PeekMessage函数返回的消息。你可以使用WH_GETMESSAGE Hook去监视鼠标和键盘输入,以及其他发送到消息队列中的消息。
    6、WH_JOURNALPLAYBACK(1) Hook
    WH_JOURNALPLAYBACK Hook使应用程序可以插入消息到系统消息队列。可以使用这个Hook回放通过使用WH_JOURNALRECORD Hook记录下来的连续的鼠标和键盘事件。只要WH_JOURNALPLAYBACK Hook已经安装,正常的鼠标和键盘事件就是无效的。
    WH_JOURNALPLAYBACK Hook是全局Hook,它不能象线程特定Hook一样使用。
    WH_JOURNALPLAYBACK Hook返回超时值,这个值告诉系统在处理来自回放Hook当前消息之前需要等待多长时间(毫秒)。这就使Hook可以控制实时事件的回放。
    WH_JOURNALPLAYBACK是system-wide local hooks,它們不會被注射到任何行程位址空間。
    7、WH_JOURNALRECORD(0) Hook
    WH_JOURNALRECORD Hook用来监视和记录输入事件。典型的,可以使用这个Hook记录连续的鼠标和键盘事件,然后通过使用WH_JOURNALPLAYBACK Hook来回放。
    WH_JOURNALRECORD Hook是全局Hook,它不能象线程特定Hook一样使用。
    WH_JOURNALRECORD是system-wide local hooks,它們不會被注射到任何行程位址空間。
    8、WH_KEYBOARD(2) Hook
    在应用程序中,WH_KEYBOARD Hook用来监视WM_KEYDOWN and WM_KEYUP消息,这些消息通过GetMessage or PeekMessage function返回。可以使用这个Hook来监视输入到消息队列中的键盘消息。
    9、WH_KEYBOARD_LL(13) Hook
    WH_KEYBOARD_LL Hook监视输入到线程消息队列中的键盘消息。
    10、WH_MOUSE(7) Hook
    WH_MOUSE Hook监视从GetMessage 或者 PeekMessage 函数返回的鼠标消息。使用这个Hook监视输入到消息队列中的鼠标消息。
    11、WH_MOUSE_LL(14) Hook    
    WH_MOUSE_LL Hook监视输入到线程消息队列中的鼠标消息。
    12、WH_MSGFILTER(-1) 和 WH_SYSMSGFILTER(6) Hooks
    WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以监视菜单,滚动条,消息框,对话框消息并且发现用户使用ALT+TAB or ALT+ESC 组合键切换窗口。WH_MSGFILTER Hook只能监视传递到菜单,滚动条,消息框的消息,以及传递到通过安装了Hook子程的应用程序建立的对话框的消息。WH_SYSMSGFILTER Hook监视所有应用程序消息。
    WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以在模式循环期间过滤消息,这等价于在主消息循环中过滤消息。
    通过调用CallMsgFilter function可以直接的调用WH_MSGFILTER Hook。通过使用这个函数,应用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息循环里一样。
    13、WH_SHELL Hook(10)
    外壳应用程序可以使用WH_SHELL Hook去接收重要的通知。当外壳应用程序是激活的并且当顶层窗口建立或者销毁时,系统调用WH_SHELL Hook子程。
    WH_SHELL 共有5钟情況:
    1. 只要有个top-level、unowned 窗口被产生、起作用、或是被摧毁;
    2. 当Taskbar需要重画某个按钮;
    3. 当系统需要显示关于Taskbar的一个程序的最小化形式;
    4. 当目前的键盘布局状态改变;
    5. 当使用者按Ctrl+Esc去执行Task Manager(或相同级别的程序)。
    按照惯例,外壳应用程序都不接收WH_SHELL消息。所以,在应用程序能够接收WH_SHELL消息之前,应用程序必须调用SystemParametersInfo function注册它自己。
    ========================================================================================
    呵呵,有点昏昏的感觉吗?不要紧的,多看几次就会好的!
    好了,这期的勾子基本概念就算讲完了,让我们来总结一下:
      
    1.钩子的基本概念及作用
    钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。

    2.使用API函数SetWindowsHookEx()安装钩子.

    3.用API函数CallNextHookEx来传递钩子

    4.用API函数UnHookWindowsHookEx()来卸载钩子

    我们拿最常用的上面的第5个WH_GETMESSAGE(3) Hook 来说明一下:
    看看下面这段易代码你就会明白的:

    hMod = LoadLibraryA (取运行目录 () + “\HookDLL.dll” )   '装载动态链接库
    lpProc =GetProcAddress (hMod, “GetMsgProc”)    '定位钩子回调函数函数
    hhook = SetWindowsHookExA (#WH_GETMESSAGE, lpProc, hMod, 0)    '安装钩子
    好了,这期的理论知识就到这了,请关注下期的精彩内容.

    下期预告:
    下期我们将用易源码实例来讲解,怎样用钩子技术和内存文件映射共享技术来
    实现远程线程插入资源管理器进程(explorer.exe),
    敬请期待.....
    end

  • 【转帖】几种注入方式

    2010-01-20 12:02:16

    通常有以下几种注入方式: 
    
    1.利用注册表如果我们准备拦截的进程连接了User32.dll,也就是使用了User32中的API(一般图形界面的应用程序都符合这个条件),那么就可以简单把你的钩子驱动器DLL的名字作为值添加在下面注册表的键下: HKEY_LOCAL_MACHINE\Software\Microsoft\WindowsNT\CurrentVersion\Windows\AppInit_DLLs 值的形式可以为单个DLL的文件名,或者是一组DLL的文件名,相邻的名称之间用逗号或空格间隔。所有由该值标识的DLL将在符合条件的应用程序启动的时候装载。这是一个操作系统内建的机制,相对其他方式来说危险性较小,但它有一些比较明显的缺点: 该方法仅适用于NT/2K操作系统。看看键的名称你就应该明白 为了激活或停止钩子的注入,必须重新启动Windows。这个就似乎太不方便了 不能用此方法向没有使用User32的应用程序注入DLL,例如控制台应用程序 不管需要与否,钩子DLL将注入每一个GUI应用程序,这将导致整个系统性能的下降
    
    2. 
    
    建立系统范围的Windows钩子要向某个进程注入DLL,一个十分普遍也是比较简单的方法就是建立在标准的Windows钩子的基础上。Windows钩子一般是在DLL中实现的,这是一个全局性的Windows钩子的基本要求,这也符合我们的需要。当我们成功地调用SetWindowsHookEx函数之后,便在系统中安装了某种类型的消息钩子,这个钩子可以是针对某个进程,也可以是针对系统中的所有进程。一旦某个进程中产生了该类型的消息,操作系统会自动把该钩子所在的DLL映像到该进程的地址空间中,从而使得消息回调函数(在SetWindowsHookEx的参数中指定)能够对此消息进行适当的处理,在这里,我们所感兴趣的当然不是对消息进行什么处理,因此在消息回调函数中只需把消息钩子向后传递就可以了,但是我们所需的DLL已经成功地注入了目标进程的地址空间,从而可以完成后续工作。
    我们知道,不同进程中使用的DLL之间是不能直接共享数据的,因为它们活动在不同的地址空间中。但在Windows钩子DLL中,有一些数据,例如Windows钩子句柄HHook,这是由SetWindowsHookEx函数返回值得到的,并且作为参数将在CallNextHookEx函数和UnhookWindoesHookEx函数中使用,显然使用SetWindowsHookEx函数的进程和使用CallNextHookEx函数的进程一般不会是同一个进程,因此我们必须能够使句柄在所有的地址空间中都是有效的有意义的,也就是说,它的值必须必须在这些钩子DLL所挂钩的进程之间是共享的。为了达到这个目的,我们就应该把它存储在一个共享的数据区域中。
    在VC++中我们可以采用预编译指令#pragma data_seg在DLL文件中创建一个新的段,并且在DEF文件中把该段的属性设置为“shared”,这样就建立了一个共享数据段。对于使用Delphi的人来说就没有这么幸运了:没有类似的比较简单的方法(或许是有的,但我没有找到)。不过我们还是可以利用内存映像技术来申请使用一块各进程可以共享的内存区域,主要是利用了CreateFileMapping和MapViewOfFile这两个函数。这倒是一个通用的方法,适合所有的开发语言,只要它能使用Windows的API。
    
    在Borland的BCB中有一个指令#pragma codeseg与VC++中的#pragma data_seg指令有点类似,应该也能起到一样的作用,但我试了一下,没有没有效果,而BCB的联机帮助中对此也提到的不多,不知怎样才能正确的使用。一旦钩子DLL加载进入目标进程的地址空间后,在我们调用UnHookWindowsHookEx函数之前是无法使它停止工作的,除非目标进程关闭。
    
     
    
    这种DLL注入方式有两个优点: 这种机制在Win 9x/Me和Win NT/2K中都是得到支持的,预计在以后的版本中也将得到支持 钩子DLL可以在不需要的时候,可由我们主动的调用UnHookWindowsHookEx来卸载,比起使用注册表的机制来说方便了许多尽管这是一种相当简洁明了的方法,但它也有一些显而易见的缺点: 首先值得我们注意的是,Windows钩子将会降低整个系统的性能,因为它额外增加了系统在消息处理方面的时间 其次,只有当目标进程准备接受某种消息时,钩子所在的DLL才会被系统映射到该进程的地址空间中,钩子才能真正开始发挥作用。因此如果我们要对某些进程的整个生命周期内的API调用情况进行监控,用这种方法显然会遗漏某些API的调用 
    
    3.
    
    使用 CreateRemoteThread函数在我看来这是一个相当棒的方法,然而不幸的是,CreateRemoteThread这个函数只能在Win NT/2K系统中才得到支持,虽然在Win 9x中这个API也能被安全的调用而不出错,但它除了返回一个空值之外什么也不做。整个DLL注入过程十分简单。我们知道,任何一个进程都可以使用LoadLibrary来动态地加载一个DLL。但问题是,我们如何让目标进程在我们的控制下来加载我们的钩子DLL(也就是钩子驱动器)呢?这里有一个API函数CreateRemoteThread,通过它可在一个进程中可建立并运行一个远程的线程。
    
     
    
    调用该API需要指定一个线程函数指针作为参数,该线程函数的原型如下: Function ThreadProc(lpParam: Pointer): DWORD;我们再来看一下LoadLibrary的函数原型: Function LoadLibrary(lpFileName: PChar): HModule;可以看出,这两个函数原型实质上是完全相同的(其实返回值是否相同关系不大,因为我们是无法得到远程线程函数的返回值的),只是叫法不同而已,这种相同使得我们可以把直接把LoadLibrary当做线程函数来使用,从而在目标进程中加载钩子DLL。
    
     
    
    类似的,当我们需要卸载钩子DLL时,也可以FreeLibrary作为线程函数来使用,在目标进程中移去钩子DLL。一切看来是十分的简洁方便。通过调用GetProcAddress函数,我们可以得到LoadLibrary函数的地址。由于LoadLibrary是Kernel32中的函数,而这个系统DLL的映射地址对每一个进程来说都是相同的,因此LoadLibrary函数的地址也是如此。这点将确保我们能把该函数的地址作为一个有效的参数传递给CreateRemoteThread使用。 
    AddrOfLoadLibrary := GetProcAddress(GetModuleHandle(‘Kernel32.dll’), ‘LoadLibrary’); 
    
    HremoteThread := CreateRemoteThread(HTargetProcess, nil, 0, AddrOfLoadLibrary, HookDllName, 0, nil); 
    
     
    
    要使用CreateRemoteThread,我们需要目标进程的句柄作为参数。当我们用OpenProcess函数来得到进程的句柄时,通常是希望对此进程有全权的存取操作,也就是以PROCESS_ALL_ACCESS为标志打开进程。但对于一些系统级的进程,直接这样显然是不行的,只能返回一个的空句柄(值为零)。为此,我们必须把自己设置为拥有调试级的特权,这样将具有最大的存取权限,从而使得我们能对这些系统级的进程也可以进行一些必要的操作。 
    
     
    
    4. 
    通过BHO来注入DLL 有时,我们想要注入DLL的对象仅仅是Internet Explorer。幸运的是,Windows操作系统为我们提供了一个简单的归档的方法(这保证了它的可靠性)―― 利用Browser Helper Objects(BHO)。一个BHO是一个在 DLL中实现的COM对象,它主要实现了一个IObjectWithSite接口,而每当IE运行时,它会自动加载所有实现了该接口的COM对象。 
    
  • 【转帖】T()和L的特点与区别是什么

    2010-01-20 12:00:42

     
    Visual C++里边定义字符串的时候,用_T来保证兼容性,是一种数据类型,但是它不会产生结果,被编译系统的预处理系统来解释,VC支持ascii和unicode两种字符类型,用_T可以保证从ascii编码类型转换到unicode编码类型的时候,程序不需要修改。
            如果将来你不打算升级到unicode,那么也不需要_T!

    _T是将字符串转换为TCHAR,TCHAR是一个宏定义,当定义了UNICODE时TCHAR等同于 WCHAR,否则等同于CHAR。为了和以后的平台兼容,建议使用TCHAR,而不要使用普通的CHAR。例子:TCHAR *s = _T("FSDF")。
          
    L将字符串转换为WCHAR,用于需要UNICODE的环境。例子:WCHAR *s = L"FSDF"。

    Example:

           SetWindowText(_T("我很好"));
        在中文Win2000上正常,在英文Win2000下就是乱码!

    // _T()自动将()内字符串转成unicode or Multibyte-character or SBCS (ASCII) 根据系统宏定义,
    // 为了将程序与vb等unicode 编码的程序交互,为了程序的国际化,为了...
    // 看msdn! 如果定义了 UNICODE 就变成 L把字符串转换成宽字符,否则没用。
    // 统一的字符编码标准, 采用双字节对字符进行编码
    // _T把参数转换成当前系统支持的字符,例如支持UNICODE就转换成宽字符,否则就是单字符
    #ifdef UNICODE
    #define _T(x) L##x
    #else
    #define _T(x) x
    #endif
    _T/_TEXT是在TCHAR.H头文件中定义的宏。
    在_UNICODE和_MBCS都没有定义时,对其后的字符串无影响
    在_MBCS定义时,对其后的字符串无影响
    在_UNICODE定义时,其后的字符串被定义为 L(即转换为Unicode字符)
    本质上是为了生成Unicode和非Unicode通用的程序而定义的宏。

    Unicode:宽字节字符集..
    1. 如何取得一个既包含单字节字符又包含双字节字符的字符串的字符个数?
    可以调用Microsoft Visual C++的运行期库包含函数_mbslen来操作多字节(既包括单字节也包括双字节)字符串。
    调用strlen函数,无法真正了解字符串中究竟有多少字符,它只能告诉你到达结尾的0之前有多少个字节。
    2. 如何对DBCS(双字节字符集)字符串进行操作?
    函数 描述
    PTSTR CharNext ( LPCTSTR ); 返回字符串中下一个字符的地址
    PTSTR CharPrev ( LPCTSTR, LPCTSTR ); 返回字符串中上一个字符的地址
    BOOL IsDBCSLeadByte( BYTE ); 如果该字节是DBCS字符的第一个字节,则返回非0值
    3. 为什么要使用Unicode?
    (1) 可以很容易地在不同语言之间进行数据交换。
    (2) 使你能够分配支持所有语言的单个二进制.exe文件或DLL文件。
    (3) 提高应用程序的运行效率。
    Windows 2000是使用Unicode从头进行开发的,如果调用任何一个Windows函数并给它传递一个ANSI字符串,那么系统首先要将字符串转换成 Unicode,然后将Unicode字符串传递给操作系统。如果希望函数返回ANSI字符串,系统就会首先将Unicode字符串转换成ANSI字符 串,然后将结果返回给你的应用程序。进行这些字符串的转换需要占用系统的时间和内存。通过从头开始用Unicode来开发应用程序,就能够使你的应用程序 更加有效地运行。
    Windows CE 本身就是使用Unicode的一种操作系统,完全不支持ANSI Windows函数
    Windows 98 只支持ANSI,只能为ANSI开发应用程序。
    Microsoft公司将COM从16位Windows转换成Win32时,公司决定需要字符串的所有COM接口方法都只能接受Unicode字符串。
    4. 如何编写Unicode源代码?
    Microsoft公司为Unicode设计了WindowsAPI,这样,可以尽量减少代码的影响。实际上,可以编写单个源代码文件,以便使用或者不使 用Unicode来对它进行编译。只需要定义两个宏(UNICODE和_UNICODE),就可以修改然后重新编译该源文件。
    _UNICODE宏用于C运行期头文件,而UNICODE宏则用于Windows头文件。当编译源代码模块时,通常必须同时定义这两个宏。
    5. Windows定义的Unicode数据类型有哪些?
    数据类型 说明
    WCHAR Unicode字符
    PWSTR 指向Unicode字符串的指针
    PCWSTR 指向一个恒定的Unicode字符串的指针
    对应的ANSI数据类型为CHAR,LPSTR和LPCSTR。
    ANSI/Unicode通用数据类型为TCHAR,PTSTR,LPCTSTR。
    6. 如何对Unicode进行操作?
    字符集 特性 实例
    ANSI 操作函数以str开头 strcpy
    Unicode 操作函数以wcs开头 wcscpy
    MBCS 操作函数以_mbs开头 _mbscpy
    ANSI/Unicode 操作函数以_tcs开头 _tcscpy(C运行期库)
    ANSI/Unicode 操作函数以lstr开头 lstrcpy(Windows函数)
    所有新的和未过时的函数在Windows2000中都同时拥有ANSI和Unicode两个版本。ANSI版本函数结尾以A表示;Unicode版本函数结尾以W表示。Windows会如下定义:
    #ifdef UNICODE
    #define CreateWindowEx CreateWindowExW
    #else
    #define CreateWindowEx CreateWindowExA
    #endif // !UNICODE
    7. 如何表示Unicode字符串常量?
    字符集 实例
    ANSI “string”
    Unicode L“string”
    ANSI/Unicode T(“string”)或_TEXT(“string”)if( szError[0] == _TEXT(‘J’) ){ }
    8. 为什么应当尽量使用操作系统函数?
    这将有助于稍稍提高应用程序的运行性能,因为操作系统字符串函数常常被大型应用程序比如操作系统的外壳进程Explorer.exe所使用。由于这些函数使用得很多,因此,在应用程序运行时,它们可能已经被装入RAM。
    如:StrCat,StrChr,StrCmp和StrCpy等。
    9. 如何编写符合ANSI和Unicode的应用程序?
    (1) 将文本串视为字符数组,而不是chars数组或字节数组。
    (2) 将通用数据类型(如TCHAR和PTSTR)用于文本字符和字符串。
    (3) 将显式数据类型(如BYTE和PBYTE)用于字节、字节指针和数据缓存。
    (4) 将TEXT宏用于原义字符和字符串。
    (5) 执行全局性替换(例如用PTSTR替换PSTR)。
    (6) 修改字符串运算问题。例如函数通常希望在字符中传递一个缓存的大小,而不是字节。这意味着不应该传递sizeof(szBuffer),而应该传递 (sizeof(szBuffer)/sizeof(TCHAR)。另外,如果需要为字符串分配一个内存块,并且拥有该字符串中的字符数目,那么请记住要 按字节来分配内存。这就是说,应该调用malloc(nCharacters *sizeof(TCHAR)),而不是调用malloc(nCharacters)。
    10. 如何对字符串进行有选择的
    比较?
    通过调用CompareString来实现。
    标志 含义
    NORM_IGNORECASE 忽略字母的大小写
    NORM_IGNOREKANATYPE 不区分平假名与片假名字符
    NORM_IGNORENONSPACE 忽略无间隔字符
    NORM_IGNORESYMBOLS 忽略符号
    NORM_IGNOREWIDTH 不区分单字节字符与作为双字节字符的同一个字符
    SORT_STRINGSORT 将标点符号作为普通符号来处理
    11. 如何判断一个文本文件是ANSI还是Unicode?
    判断如果文本文件的开头两个字节是0xFF和0xFE,那么就是Unicode,否则是ANSI。
    12. 如何判断一段字符串是ANSI还是Unicode?
    用IsTextUnicode进行判断。IsTextUnicode使用一系列统计方法和定性方法,以便猜测缓存的内容。由于这不是一种确切的科学方法,因此 IsTextUnicode有可能返回不正确的结果。
  • 【转帖】模拟键盘输入

    2008-09-16 17:08:52

    键盘是我们使用计算机的一个很重要的输入设备了,即使在鼠标大行其道的今天,很多程序依然离不开键盘来操作。但是有时候,一些重复性的,很繁琐的键盘操作总会让人疲惫,于是就有了用程序来代替人们按键的方法,这样可以把很多重复性的键盘操作交给程序来模拟,省了很多精力,按键精灵就是这样的一个软件。那么我们怎样才能用VB来写一个程序,达到与按键精灵类似的功能呢?那就让我们来先了解一下windows中响应键盘事件的机制。
          当用户按下键盘上的一个键时,键盘内的芯片会检测到这个动作,并把这个信号传送到计算机。如何区别是哪一个键被按下了呢?键盘上的所有按键都有一个编码,称作键盘扫描码。当你按下一个键时,这个键的扫描码就被传给系统。扫描码是跟具体的硬件相关的,同一个键,在不同键盘上的扫描码有可能不同。键盘控制器就是将这个扫描码传给计算机,然后交给键盘驱动程序。键盘驱动程序会完成相关的工作,并把这个扫描码转换为键盘虚拟码。什么是虚拟码呢?因为扫描码与硬件相关,不具有通用性,为了统一键盘上所有键的编码,于是就提出了虚拟码概念。无论什么键盘,同一个按键的虚拟码总是相同的,这样程序就可以识别了。简单点说,虚拟码就是我们经常可以看到的像VK_A,VK_B这样的常数,比如键A的虚拟码是65,写成16进制就是&H41,注意,人们经常用16进制来表示虚拟码。当键盘驱动程序把扫描码转换为虚拟码后,会把这个键盘操作的扫描码和虚拟码还有其它信息一起传递给操作系统。然后操作系统则会把这些信息封装在一个消息中,并把这个键盘消息插入到消息列队。最后,要是不出意外的话,这个键盘消息最终会被送到当前的活动窗口那里,活动窗口所在的应用程序接收到这个消息后,就知道键盘上哪个键被按下,也就可以决定该作出什么响应给用户了。这个过程可以简单的如下表示:
    用户按下按键-----键盘驱动程序将此事件传递给操作系统-----操作系统将键盘事件插入消息队列-----键盘消息被发送到当前活动窗口
    明白了这个过程,我们就可以编程实现在其中的某个环节来模拟键盘操作了。在VB中,有多种方法可以实现键盘模拟,我们就介绍几种比较典型的。

    1.局部级模拟

          从上面的流程可以看出,键盘事件是最终被送到活动窗口,然后才引起目标程序响应的。那么最直接的模拟方法就是:直接伪造一个键盘消息发给目标程序。哈哈,这实在是很简单,windows提供了几个这样的API函数可以实现直接向目标程序发送消息的功能,常用的有SendMessage和PostMessage,它们的区别是PostMessage函数直接把消息仍给目标程序就不管了,而SendMessage把消息发出去后,还要等待目标程序返回些什么东西才好。这里要注意的是,模拟键盘消息一定要用PostMessage函数才好,用SendMessage是不正确的(因为模拟键盘消息是不需要返回值的,不然目标程序会没反应),切记切记!PostMessage函数的VB声明如下:
    Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
    参数hwnd 是你要发送消息的目标程序上某个控件的句柄,参数wMsg 是消息的类型,表示你要发送什么样的消息,最后wParam 和lParam 这两个参数是随消息附加的数据,具体内容要由消息决定。
    再来看看wMsg 这个参数,要模拟按键就靠这个了。键盘消息常用的有如下几个:
    WM_KEYDOWN       表示一个普通键被按下
    WM_KEYUP         表示一个普通键被释放
    WM_SYSKEYDOWN    表示一个系统键被按下,比如Alt键
    WM_SYSKEYUP      表示一个系统键被释放,比如Alt键
    如果你确定要发送以上几个键盘消息,那么再来看看如何确定键盘消息中的wParam 和lParam 这两个参数。在一个键盘消息中,wParam 参数的含义较简单,它表示你要发送的键盘事件的按键虚拟码,比如你要对目标程序模拟按下A键,那么wParam 参数的值就设为VK_A ,至于lParam 这个参数就比较复杂了,因为它包含了多个信息,一般可以把它设为0,但是如果你想要你的模拟更真实一些,那么建议你还是设置一下这个参数。那么我们就详细了解一下lParam 吧。lParam 是一个long类型的参数,它在内存中占4个字节,写成二进制就是00000000 00000000 00000000 00000000    一共是32位,我们从右向左数,假设最右边那位为第0位(注意是从0而不是从1开始计数),最左边的就是第31位,那么该参数的的0-15位表示键的发送次数等扩展信息,16-23位为按键的扫描码,24-31位表示是按下键还是释放键。大家一般习惯写成16进制的,那么就应该是&H00 00 00 00 ,第0-15位一般为&H0001,如果是按下键,那么24-31位为&H00,释放键则为&HC0,那么16-23位的扫描码怎么会得呢?这需要用到一个API函数MapVirtualKey,这个函数可以将虚拟码转换为扫描码,或将扫描码转换为虚拟码,还可以把虚拟码转换为对应字符的ASCII码。它的VB声明如下:
    Declare Function MapVirtualKey Lib "user32" Alias "MapVirtualKeyA" (ByVal wCode As Long, ByVal wMapType As Long) As Long
    参数wCode 表示待转换的码,参数wMapType 表示从什么转换为什么,如果是虚拟码转扫描码,则wMapType 设置为0,如果是虚拟扫描码转虚拟码,则wMapType 设置为1,如果是虚拟码转ASCII码,则wMapType 设置为2.相信有了这些,我们就可以构造键盘事件的lParam参数了。下面给出一个构造lParam参数的函数:
    Declare Function MapVirtualKey Lib "user32" Alias "MapVirtualKeyA" (ByVal wCode As Long, ByVal wMapType As Long) As Long

    Function MakeKeyLparam(ByVal VirtualKey As Long, ByVal flag As Long) As Long
    '参数VirtualKey表示按键虚拟码,flag表示是按下键还是释放键,用WM_KEYDOWN和WM_KEYUP这两个常数表示
          Dim s As String
          Dim Firstbyte As String      'lparam参数的24-31位
          If flag = WM_KEYDOWN    Then '如果是按下键
              Firstbyte = "00"
          Else
              Firstbyte = "C0"         '如果是释放键
          End If
          Dim Scancode As Long
          '获得键的扫描码
          Scancode = MapVirtualKey(VirtualKey, 0)
          Dim Secondbyte As String     'lparam参数的16-23位,即虚拟键扫描码
          Secondbyte = Right("00" & Hex(Scancode), 2)
          s = Firstbyte & Secondbyte & "0001"    '0001为lparam参数的0-15位,即发送次数和其它扩展信息
          MakeKeyLparam = Val("&H" & s)
    End Function

    这个函数像这样调用,比如按下A键,那么lParam=MakeKeyLparam(VK_A,WM_KEYDOWN) ,很简单吧。值得注意的是,即使你发送消息时设置了lParam参数的值,但是系统在传递消息时仍然可能会根据当时的情况重新设置该参数,那么目标程序收到的消息中lParam的值可能会和你发送时的有所不同。所以,如果你很懒的话,还是直接把它设为0吧,对大多数程序不会有影响的,呵呵。
          好了,做完以上的事情,现在我们可以向目标程序发送键盘消息了。首先取得目标程序接受这个消息的控件的句柄,比如目标句柄是12345,那么我们来对目标模拟按下并释放A键,像这样:(为了简单起见,lParam这个参数就不构造了,直接传0)
    PostMessage 12345,WM_KEYDOWN,VK_A,0&     '按下A键
    PostMessage 12345,WM_UP,VK_A,0&          '释放A键
    好了,一次按键就完成了。现在你可以迫不及待的打开记事本做实验,先用FindWindowEx这类API函数找到记事本程序的句柄,再向它发送键盘消息,期望记事本里能诡异的自动出现字符。可是你马上就是失望了,咦,怎么一点反应也没有?你欺骗感情啊~~~~~~~~~~55555555555555    不是的哦,接着往下看啊。
    一般目标程序都会含有多个控件,并不是每个控件都会对键盘消息作出反应,只有把键盘消息发送给接受它的控件才会得到期望的反应。那记事本来说,它的编辑框其实是个edit类,只有这个控件才对键盘事件有反应,如果只是把消息发给记事本的窗体,那是没有用的。现在你找出记事本那个编辑框的句柄,比如是54321,那么写如下代码:
    PostMessage 54321,WM_KEYDOWN,VK_F1,0&     '按下F1键
    PostMessage 54321,WM_UP,VK_F1,0&          '释放F1键
    怎么样,是不是打开了记事本的“帮助”信息?这说明目标程序已经收到了你发的消息,还不错吧~~~~~~~~
    可以马上新问题就来了,你想模拟向记事本按下A这个键,好在记事本里自动输入字符,可是,没有任何反应!这是怎么一回事呢?
    原来,如果要向目标程序发送字符,光靠WM_KEYDOWN和WM_UP这两个事件还不行,还需要一个事件:WM_CHAR,这个消息表示一个字符,程序需靠它看来接受输入的字符。一般只有A,B,C等这样的按键才有WM_CHAR消息,别的键(比如方向键和功能键)是没有这个消息的,WM_CHAR消息一般发生在WM_KEYDOWN消息之后。WM_CHAR消息的lParam参数的含义与其它键盘消息一样,而它的wParam则表示相应字符的ASCII编码(可以输入中文的哦^_^),现在你可以写出一个完整的向记事本里自动写入字符的程序了,下面是一个例子,并附有这些消息常数的具体值:
    Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
    Declare Function MapVirtualKey Lib "user32" Alias "MapVirtualKeyA" (ByVal wCode As Long, ByVal wMapType As Long) As Long

    Public Const WM_KEYDOWN = &H100
    Public Const WM_KEYUP = &H101
    Public Const WM_CHAR = &H102
    Public Const VK_A = &H41

    Function MakeKeyLparam(ByVal VirtualKey As Long, ByVal flag As Long) As Long
          Dim s As String
          Dim Firstbyte As String      'lparam参数的24-31位
          If flag = WM_KEYDOWN    Then '如果是按下键
              Firstbyte = "00"
          Else
              Firstbyte = "C0"         '如果是释放键
          End If
          Dim Scancode As Long
          '获得键的扫描码
          Scancode = MapVirtualKey(VirtualKey, 0)
          Dim Secondbyte As String     'lparam参数的16-23位,即虚拟键扫描码
          Secondbyte = Right("00" & Hex(Scancode), 2)
          s = Firstbyte & Secondbyte & "0001"    '0001为lparam参数的0-15位,即发送次数和其它扩展信息
          MakeKeyLparam = Val("&H" & s)
    End Function

    Private Sub Form_Load()
          dim hwnd as long
          hwnd = XXXXXX    'XXXXX表示记事本编辑框的句柄
          PostMessage hwnd,WM_KEYDOWN,VK_A,MakeKeyLparam(VK_A,WM_KEYDOWN)    '按下A键
          PostMessage hwnd,WM_CHAR,ASC("A"),MakeKeyLparam(VK_A,WM_KEYDOWN)    '输入字符A
          PostMessage hwnd,WM_UP,VK_A,MakeKeyLparam(VK_A,WM_UP)         '释放A键
    End Sub

    这就是通过局部键盘消息来模拟按键。这个方法有一个极大的好处,就是:它可以实现后台按键,也就是说他对你的前台操作不会有什么影响。比如,你可以用这个方法做个程序在游戏中模拟按键来不断地执行某些重复的操作,而你则一边喝茶一边与QQ上的MM们聊得火热,它丝毫不会影响你的前台操作。无论目标程序是否获得焦点都没有影响,这就是后台模拟按键的原理啦~~~~


    2.全局级模拟

          你会发现,用上面的方法模拟按键并不是对所有程序都有效的,有的程序啊,你向它发了一大堆消息,可是它却一点反应也没有。这是怎么回事呢?这就要看具体的情况了,有些程序(特别是一些游戏)出于某些原因,会禁止用户对它使用模拟按键程序,这个怎么实现呢?比如可以在程序中检查一下,如果发现自己不是活动窗口,就不接受键盘消息。或者仔细检查一下收到的键盘消息,你会发现真实的按键和模拟的按键消息总是有一些小差别,从这些小差别上,目标程序就能判断出:这是假的!是伪造的!!因此,如果用PostMessage发送局部消息模拟按键不成功的话,你可以试一试全局级的键盘消息,看看能不能骗过目标程序。
    模拟全局键盘消息常见的可以有以下一些方法:
    (1) 用API函数keybd_event,这个函数可以用来模拟一个键盘事件,它的VB声明为:
    Declare Sub keybd_event Lib "user32" (ByVal bVk As Byte, ByVal bScan As Byte, ByVal dwFlags As Long, ByVal dwExtraInfo As Long)
    参数bVk表示要模拟的按键的虚拟码,bScan表示该按键的扫描码(一般可以传0),dwFlags表示是按下键还是释放键(按下键为0,释放键为2),dwExtraInfo是扩展标志,一般没有用。比如要模拟按下A键,可以这样:
    Const KEYEVENTF_KEYUP = &H2
    keybd_event VK_A, 0, 0, 0     '按下A键
    keybd_event VK_A, 0, KEYEVENTF_KEYUP, 0     '释放A键
    注意有时候按键的速度不要太快,否则会出问题,可以用API函数Sleep来进行延时,声明如下:
    Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
    参数dwMilliseconds表示延时的时间,以毫秒为单位。
    那么如果要模拟按下功能键怎么做呢?比如要按下Ctrl+C实现拷贝这个功能,可以这样:
    keybd_event VK_Ctrl, 0, 0, 0     '按下Ctrl键
    keybd_event VK_C, 0, 0, 0        '按下C键
    Sleep 500              '延时500毫秒
    keybd_event VK_C, 0, KEYEVENTF_KEYUP, 0     '释放C键
    keybd_event VK_Ctrl, 0, KEYEVENTF_KEYUP, 0     '释放Ctrl键
    好了,现在你可以试试是不是可以骗过目标程序了,这个函数对大部分的窗口程序都有效,可是仍然有一部分游戏对它产生的键盘事件熟视无睹,这时候,你就要用上bScan这个参数了。一般的,bScan都传0,但是如果目标程序是一些DirectX游戏,那么你就需要正确使用这个参数传入扫描码,用了它可以产生正确的硬件事件消息,以被游戏识别。这样的话,就可以写成这样:
    keybd_event VK_A, MapVirtualKey(VK_A, 0), 0, 0     '按下A键
    keybd_event VK_A, MapVirtualKey(VK_A, 0), KEYEVENTF_KEYUP, 0     '释放A键
    以上就是用keybd_event函数来模拟键盘事件。除了这个函数,SendInput函数也可以模拟全局键盘事件。SendInput可以直接把一条消息插入到消息队列中,算是比较底层的了。它的VB声明如下:
    Declare Function SendInput Lib "user32.dll" (ByVal nInputs As Long, pInputs As GENERALINPUT, ByVal cbSize As Long) As Long
    参数:
    nlnprts:定义plnputs指向的结构的数目。
    plnputs:指向INPUT结构数组的指针。每个结构代表插人到键盘或鼠标输入流中的一个事件。
    cbSize:定义INPUT结构的大小。若cbSize不是INPUT结构的大小,则函数调用失败。
    返回值:函数返回被成功地插人键盘或鼠标输入流中的事件的数目。若要获得更多的错误信息,可以调用GetlastError函数。
    备注:Sendlnput函数将INPUT结构中的事件顺序地插入键盘或鼠标的输入流中。这些事件与用户插入的(用鼠标或键盘)或调用keybd_event,mouse_event,或另外的Sendlnput插人的键盘或鼠标的输入流不兼容。
    嗯,这个函数用起来蛮复杂的,因为它的参数都是指针一类的东西。要用它来模拟键盘输入,先要构造一组数据结构,把你要模拟的键盘消息装进去,然后传给它。为了方便起见,把它做在一个过程里面,要用的时候直接调用好了,代码如下:
    Declare Function SendInput Lib "user32.dll" (ByVal nInputs As Long, pInputs As GENERALINPUT, ByVal cbSize As Long) As Long
    Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (pDst As Any, pSrc As Any, ByVal ByteLen As Long)
    Type GENERALINPUT
         dwType As Long
         xi(0 To 23) As Byte
    End Type

    Type KEYBDINPUT
        wVk As Integer
        wScan As Integer
        dwFlags As Long
        time As Long
        dwExtraInfo As Long
    End Type

    Const INPUT_KEYBOARD = 1

    Sub MySendKey(bkey As Long)
    '参数bkey传入要模拟按键的虚拟码即可模拟按下指定键
    Dim GInput(0 To 1) As GENERALINPUT
    Dim KInput As KEYBDINPUT
    KInput.wVk = bkey    '你要模拟的按键
    KInput.dwFlags = 0 '按下键标志
    GInput(0).dwType = INPUT_KEYBOARD
    CopyMemory GInput(0).xi(0), KInput, Len(KInput) '这个函数用来把内存中KInput的数据复制到GInput
    KInput.wVk = bkey  
    KInput.dwFlags = KEYEVENTF_KEYUP    ' 释放按键
    GInput(1).dwType = INPUT_KEYBOARD ' 表示该消息为键盘消息
    CopyMemory GInput(1).xi(0), KInput, Len(KInput)
    '以上工作把按下键和释放键共2条键盘消息加入到GInput数据结构中
    SendInput 2, GInput(0), Len(GInput(0))      '把GInput中存放的消息插入到消息列队
    End Sub

          除了以上这些,用全局钩子也可以模拟键盘消息。如果你对windows中消息钩子的用法已经有所了解,那么你可以通过设置一个全局HOOK来模拟键盘消息,比如,你可以用WH_JOURNALPLAYBACK这个钩子来模拟按键。WH_JOURNALPLAYBACK是一个系统级的全局钩子,它和WH_JOURNALRECORD的功能是相对的,常用它们来记录并回放键盘鼠标操作。WH_JOURNALRECORD钩子用来将键盘鼠标的操作忠实地记录下来,记录下来的信息可以保存到文件中,而WH_JOURNALPLAYBACK则可以重现这些操作。当然亦可以单独使用WH_JOURNALPLAYBACK来模拟键盘操作。你需要首先声明SetWindowsHookEx函数,它可以用来安装消息钩子:
    Declare Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As Long,ByVal lpfn As Long, ByVal hmod As Long, ByVal dwThreadId As Long) As Long
    先安装WH_JOURNALPLAYBACK这个钩子,然后你需要自己写一个钩子函数,在系统调用它时,把你要模拟的事件传递给钩子参数lParam所指向的EVENTMSG区域,就可以达到模拟按键的效果。不过用这个钩子模拟键盘事件有一个副作用,就是它会锁定真实的鼠标键盘,不过如果你就是想在模拟的时候不会受真实键盘操作的干扰,那么用用它倒是个不错的主意。
    3.驱动级模拟

          如果上面的方法你都试过了,可是你发现目标程序却仍然顽固的不接受你模拟的消息,寒~~~~~~~~~还好,我还剩下最后一招,这就是驱动级模拟:直接读写键盘的硬件端口!
          有一些使用DirectX接口的游戏程序,它们在读取键盘操作时绕过了windows的消息机制,而使用DirectInput.这是因为有些游戏对实时性控制的要求比较高,比如赛车游戏,要求以最快速度响应键盘输入。而windows消息由于是队列形式的,消息在传递时会有不少延迟,有时1秒钟也就传递十几条消息,这个速度达不到游戏的要求。而DirectInput则绕过了windows消息,直接与键盘驱动程序打交道,效率当然提高了不少。因此也就造成,对这样的程序无论用PostMessage或者是keybd_event都不会有反应,因为这些函数都在较高层。对于这样的程序,只好用直接读写键盘端口的方法来模拟硬件事件了。要用这个方法来模拟键盘,需要先了解一下键盘编程的相关知识。
          在DOS时代,当用户按下或者放开一个键时,就会产生一个键盘中断(如果键盘中断是允许的),这样程序会跳转到BIOS中的键盘中断处理程序去执行。打开windows的设备管理器,可以查看到键盘控制器由两个端口控制。其中&H60是数据端口,可以读出键盘数据,而&H64是控制端口,用来发出控制信号。也就是,从&H60号端口可以读此键盘的按键信息,当从这个端口读取一个字节,该字节的低7位就是按键的扫描码,而高1位则表示是按下键还是释放键。当按下键时,最高位为0,称为通码,当释放键时,最高位为1,称为断码。既然从这个端口读数据可以获得按键信息,那么向这个端口写入数据就可以模拟按键了!用过QbASIC4.5的朋友可能知道,QB中有个OUT命令可以向指定端口写入数据,而INP函数可以读取指定端口的数据。那我们先看看如果用QB该怎么写代码:
    假如你想模拟按下一个键,这个键的扫描码为&H50,那就这样
    OUT &H64,&HD2     '把数据&HD2发送到&H64端口。这是一个KBC指令,表示将要向键盘写入数据
    OUT &H60,&H50     '把扫描码&H50发送到&H60端口,表示模拟按下扫描码为&H50的这个键
    那么要释放这个键呢?像这样,发送该键的断码:
    OUT &H64,&HD2     '把数据&HD2发送到&H64端口。这是一个KBC指令,表示将要向键盘写入数据
    OUT &H60,(&H50 OR &H80)     '把扫描码&H50与数据&H80进行或运算,可以把它的高位置1,得到断码,表示释放这个键
          好了,现在的问题就是在VB中如何向端口写入数据了。因为在windows中,普通应用程序是无权操作端口的,于是我们就需要一个驱动程序来帮助我们实现。在这里我们可以使用一个组件WINIO来完成读写端口操作。什么是WINIO?WINIO是一个全免费的、无需注册的、含源程序的WINDOWS2000端口操作驱动程序组件(可以到http://www.internals.com/上去下载)。它不仅可以操作端口,还可以操作内存;不仅能在VB下用,还可以在DELPHI、VC等其它环境下使用,性能特别优异。下载该组件,解压缩后可以看到几个文件夹,其中Release文件夹下的3个文件就是我们需要的,这3个文件是WinIo.sys(用于win xp下的驱动程序),WINIO.VXD(用于win 98下的驱动程序),WinIo.dll(封装函数的动态链接库),我们只需要调用WinIo.dll中的函数,然后WinIo.dll就会安装并调用驱动程序来完成相应的功能。值得一提的是这个组件完全是绿色的,无需安装,你只需要把这3个文件复制到与你的程序相同的文件夹下就可以使用了。用法很简单,先用里面的InitializeWinIo函数安装驱动程序,然后就可以用GetPortVal来读取端口或者用SetPortVal来写入端口了。好,让我们来做一个驱动级的键盘模拟吧。先把winio的3个文件拷贝到你的程序的文件夹下,然后在VB中新建一个工程,添加一个模块,在模块中加入下面的winio函数声明:

    Declare Function MapPhysToLin Lib "WinIo.dll" (ByVal PhysAddr As Long, ByVal PhysSize As Long, ByRef PhysMemHandle) As Long
    Declare Function UnmapPhysicalMemory Lib "WinIo.dll" (ByVal PhysMemHandle, ByVal LinAddr) As Boolean
    Declare Function GetPhysLong Lib "WinIo.dll" (ByVal PhysAddr As Long, ByRef PhysVal As Long) As Boolean
    Declare Function SetPhysLong Lib "WinIo.dll" (ByVal PhysAddr As Long, ByVal PhysVal As Long) As Boolean
    Declare Function GetPortVal Lib "WinIo.dll" (ByVal PortAddr As Integer, ByRef PortVal As Long, ByVal bSize As Byte) As Boolean
    Declare Function SetPortVal Lib "WinIo.dll" (ByVal PortAddr As Integer, ByVal PortVal As Long, ByVal bSize As Byte) As Boolean
    Declare Function InitializeWinIo Lib "WinIo.dll" () As Boolean
    Declare Function ShutdownWinIo Lib "WinIo.dll" () As Boolean
    Declare Function InstallWinIoDriver Lib "WinIo.dll" (ByVal DriverPath As String, ByVal Mode As Integer) As Boolean
    Declare Function RemoveWinIoDriver Lib "WinIo.dll" () As Boolean

    ' ------------------------------------以上是WINIO函数声明-------------------------------------------

    Declare Function MapVirtualKey Lib "user32" Alias "MapVirtualKeyA" (ByVal wCode As Long, ByVal wMapType As Long) As Long

    '-----------------------------------以上是WIN32 API函数声明-----------------------------------------

    再添加下面这个过程:
    Sub KBCWait4IBE()     '等待键盘缓冲区为空
    Dim dwVal As Long
        Do
        GetPortVal &H64, dwVal, 1
    '这句表示从&H64端口读取一个字节并把读出的数据放到变量dwVal中
    'GetPortVal函数的用法是GetPortVal 端口号,存放读出数据的变量,读入的长度
        Loop While (dwVal And &H2)
    End Sub
    上面的是一个根据KBC规范写的过程,它的作用是在向键盘端口写入数据前等待一段时间,后面将会用到。
    然后再添加如下过程,这2个过程用来模拟按键:

    Public Const KBC_KEY_CMD = &H64      '键盘命令端口
    Public Const KBC_KEY_DATA = &H60     '键盘数据端口

    Sub MyKeyDown(ByVal vKeyCoad As Long)   
    '这个用来模拟按下键,参数vKeyCoad传入按键的虚拟码
    Dim btScancode As Long
    btScancode = MapVirtualKey(vKeyCoad, 0)
      
          KBCWait4IBE     '发送数据前应该先等待键盘缓冲区为空
          SetPortVal KBC_KEY_CMD, &HD2, 1       '发送键盘写入命令
    'SetPortVal函数用于向端口写入数据,它的用法是SetPortVal 端口号,欲写入的数据,写入数据的长度
          KBCWait4IBE
          SetPortVal KBC_KEY_DATA, btScancode, 1    '写入按键信息,按下键
        
    End Sub

    Sub MyKeyUp(ByVal vKeyCoad As Long)   
    '这个用来模拟释放键,参数vKeyCoad传入按键的虚拟码
    Dim btScancode As Long
    btScancode = MapVirtualKey(vKeyCoad, 0)
      
          KBCWait4IBE     '等待键盘缓冲区为空
          SetPortVal KBC_KEY_CMD, &HD2, 1    '发送键盘写入命令
          KBCWait4IBE
          SetPortVal KBC_KEY_DATA, (btScancode Or &H80), 1    '写入按键信息,释放键

    End Sub


    定义了上面的过程后,就可以用它来模拟键盘输入了。在窗体模块中添加一个定时器控件,然后加入以下代码:

    Private Sub Form_Load()

    If InitializeWinIo = False Then   
        '用InitializeWinIo函数加载驱动程序,如果成功会返回true,否则返回false
          MsgBox "驱动程序加载失败!"
          Unload Me
    End If
    Timer1.Interval=3000
    Timer1.Enabled=True
    End Sub

    Private Sub Form_Unload(Cancel As Integer)
    ShutdownWinIo '程序结束时记得用ShutdownWinIo函数卸载驱动程序
    End Sub

    Private Sub Timer1_Timer()
    Dim VK_A as Long = &H41
    MyKeyDown VK_A    
    MyKeyUp VK_A      '模拟按下并释放A键
    End Sub

    运行上面的程序,就会每隔3秒钟模拟按下一次A键,试试看,怎么样,是不是对所有程序都有效果了?
    需要注意的问题:
    要在VB的调试模式下使用WINIO,需要把那3个文件拷贝到VB的安装目录中。
    键盘上有些键属于扩展键(比如键盘上的方向键就是扩展键),对于扩展键不应该用上面的MyKeyDown和MyKeyUp过程来模拟,可以使用下面的2个过程来准确模拟扩展键:
    Sub MyKeyDownEx(ByVal vKeyCoad As Long)     '模拟扩展键按下,参数vKeyCoad是扩展键的虚拟码
    Dim btScancode As Long
    btScancode = MapVirtualKey(vKeyCoad, 0)

          KBCWait4IBE     '等待键盘缓冲区为空
          SetPortVal KBC_KEY_CMD, &HD2, 1       '发送键盘写入命令
          KBCWait4IBE
          SetPortVal KBC_KEY_DATA, &HE0, 1    '写入扩展键标志信息
        
        
          KBCWait4IBE     '等待键盘缓冲区为空
          SetPortVal KBC_KEY_CMD, &HD2, 1       '发送键盘写入命令
          KBCWait4IBE
          SetPortVal KBC_KEY_DATA, btScancode, 1    '写入按键信息,按下键
        
        
    End Sub


    Sub MyKeyUpEx(ByVal vKeyCoad As Long)     '模拟扩展键弹起
    Dim btScancode As Long
    btScancode = MapVirtualKey(vKeyCoad, 0)

          KBCWait4IBE     '等待键盘缓冲区为空
          SetPortVal KBC_KEY_CMD, &HD2, 1       '发送键盘写入命令
          KBCWait4IBE
          SetPortVal KBC_KEY_DATA, &HE0, 1    '写入扩展键标志信息
        
        
          KBCWait4IBE     '等待键盘缓冲区为空
          SetPortVal KBC_KEY_CMD, &HD2, 1       '发送键盘写入命令
          KBCWait4IBE
          SetPortVal KBC_KEY_DATA, (btScancode Or &H80), 1    '写入按键信息,释放键
        
    End Sub

    还应该注意的是,如果要从扩展键转换到普通键,那么普通键的KeyDown事件应该发送两次。也就是说,如果我想模拟先按下一个扩展键,再按下一个普通键,那么就应该向端口发送两次该普通键被按下的信息。比如,我想模拟先按下左方向键,再按下空格键这个事件,由于左方向键是扩展键,空格键是普通键,那么流程就应该是这样的:
    MyKeyDownEx VK_LEFT     '按下左方向键
    Sleep 200               '延时200毫秒
    MyKeyUpEx VK_LEFT       '释放左方向键

    Sleep 500
    MyKeyDown VK_SPACE     '按下空格键,注意要发送两次
    MyKeyDown VK_SPACE
    Sleep 200
    MyKeyUp VK_SPACE       '释放空格键

    好了,相信到这里,你的模拟按键程序也就差不多了,测试一下,是不是很有效呢,嘿嘿~~~~
    WINIO组件的下载地址:http://www.114vip.com.cn/download/winio.zip
    4.骨灰级模拟
          方法3算是很底层的模拟了,我现在还没有发现有它模拟无效的程序。但是如果你用尽上面所有的方法,仍然无效的话,那么还有最后一个方法,绝对对任何程序都会有效,那就是:把键盘拿出来,老老实实地按下去吧。~~~~

Open Toolbar