注册一个hook。
首先获取其句柄,然后枚举上面的子控件,什么都可以得到。

解决方案 »

  1.   

    我要回复 | 我感兴趣 | 打印贴子 | 推荐给朋友 | 关闭窗口  
    主  题:屏幕取词?
    作  者:iamxxg
    所属论坛:Delphi
    问题点数:34
    回复次数:7
    发表时间:2001-11-30 10:21:05
     
      
      烦死了,为什么c能做到delphi就做不到呢?哪位给个例程?还有,最高给分怎么算的?专家:335,可用:3295,参与:286为什么只能给34分? 
    回复贴子: 
    回复人: yopeng(鹏鹏) (2001-11-30 10:44:02)  得0分 
    给分=参与分/20+20
    计算一下你正好给34分!!!!!!!!!!!!!!!  
    回复人: byrybye(阿水) (2001-11-30 10:54:09)  得0分 
    delphi 能做到的,
    可是NT和2000上我也不清楚,
    主要是打破进程  
    回复人: szchengyu(cy) (2001-11-30 10:54:44)  得0分 
    HOOK  
    回复人: ai_daoluan(捣乱) (2001-11-30 11:06:35)  得0分 
    来一段:鼠标屏幕取词技术的原理和实现  
                            白瑜  “鼠标屏幕取词”技术是在电子字典中得到广泛地应用的,如四通利方和金山词霸等软件,这个技术看似简单,其实在windows系统中实现却是非常复杂的,总的来说有两种实现方式:
        第一种:采用截获对部分gdi的api调用来实现,如textout,textouta等。
        第二种:对每个设备上下文(dc)做一分copy,并跟踪所有修改上下文(dc)的操作。      
        第二种方法更强大,但兼容性不好,而第一种方法使用的截获windowsapi的调用,这项技术的强大可能远远超出了您的想象,毫不夸张的说,利用windowsapi拦截技术,你可以改造整个操作系统,事实上很多外挂式windows中文平台就是这么实现的!而这项技术也正是这篇文章的主题。
        截windowsapi的调用,具体的说来也可以分为两种方法:
        第一种方法通过直接改写winapi 在内存中的映像,嵌入汇编代码,使之被调用时跳转到指定的地址运行来截获;第二种方法则改写iat(import address table 输入地址表),重定向winapi函数的调用来实现对winapi的截获。
        第一种方法的实现较为繁琐,而且在win95、98下面更有难度,这是因为虽然微软说win16的api只是为了兼容性才保留下来,程序员应该尽可能地调用32位的api,实际上根本就不是这样!win 9x内部的大部分32位api经过变换调用了同名的16位api,也就是说我们需要在拦截的函数中嵌入16位汇编代码!
        我们将要介绍的是第二种拦截方法,这种方法在win95、98和nt下面运行都比较稳定,兼容性较好。由于需要用到关于windows虚拟内存的管理、打破进程边界墙、向应用程序的进程空间中注入代码、pe(portable executable)文件格式和iat(输入地址表)等较底层的知识,所以我们先对涉及到的这些知识大概地做一个介绍,最后会给出拦截部分的关键代码。
          先说windows虚拟内存的管理。windows9x给每一个进程分配了4gb的地址空间,对于nt来说,这个数字是2gb,系统保留了2gb到 4gb之间的地址空间禁止进程访问,而在win9x中,2gb到4gb这部分虚拟地址空间实际上是由所有的win32进程所共享的,这部分地址空间加载了共享win32 dll、内存映射文件和vxd、内存管理器和文件系统码,win9x中这部分对于每一个进程都是可见的,这也是win9x操作系统不够健壮的原因。win9x中为16位操作系统保留了0到4mb的地址空间,而在4mb到2gb之间也就是win32进程私有的地址空间,由于 每个进程的地址空间都是相对独立的,也就是说,如果程序想截获其它进程中的api调用,就必须打破进程边界墙,向其它的进程中注入截获api调用的代码,这项工作我们交给钩子函数(setwindowshookex)来完成,关于如何创建一个包含系统钩子的动态链接库,《电脑高手杂志》在第?期已经有过专题介绍了,这里就不赘述了。所有系统钩子的函数必须要在动态库里,这样的话,当进程隐式或显式调用一个动态库里的函数时,系统会把这个动态库映射到这个进程的虚拟地址空间里,这使得dll成为进程的一部分,以这个进程的身份执行,使用这个进程的堆栈,也就是说动态链接库中的代码被钩子函数注入了其它gui进程的地址空间(非gui进程,钩子函数就无能为力了),
    当包含钩子的dll注入其它进程后,就可以取得映射到这个进程虚拟内存里的各个模块(exe和dll)的基地址,如:
    hmodule hmodule=getmodulehandle(“mypro.exe”);
    在mfc程序中,我们可以用afxgetinstancehandle()函数来得到模块的基地址。exe和dll被映射到虚拟内存空间的什么地方是由它们的基地址决定的。它们的基地址是在链接时由链接器决定的。当你新建一个win32工程时,vc++链接器使用缺省的基地址0x00400000。可以通过链接器的base选项改变模块的基地址。exe通常被映射到虚拟内存的0x00400000处,dll也随之有不同的基地址,通常被映射到不同进程
    的相同的虚拟地址空间处。
    系统将exe和dll原封不动映射到虚拟内存空间中,它们在内存中的结构与磁盘上的静态文件结构是一样的。即pe (portable executable) 文件格式。我们得到了进程模块的基地址以后,就可以根据pe文件的格式穷举这个模块的image_import_descriptor数组,看看进程空间中是否引入了我们需要截获的函数所在的动态链接库,比如需要截获“textouta”,就必须检查“gdi32.dll”是否被引入了。说到这里,我们有必要介绍一下pe文件的格式,如右图,这是pe文件格式的大致框图,最前面是文件头,我们不必理会,从pe file optional header后面开始,就是文件中各个段的说明,说明后面才是真正的段数据,而实际上我们关心的只有一个段,那就是“.idata”段,这个段中包含了所有的引入函数信息,还有iat(import address table)的rva(relative virtual address)地址。
    说到这里,截获windowsapi的整个原理就要真相大白了。实际上所有进程对给定的api函数的调用总是通过pe文件的一个地方来转移的,这就是一个该模块(可以是exe或dll)的“.idata”段中的iat输入地址表(import address table)。在那里有所有本模块调用的其它dll的函数名及地址。对其它dll的函数调用实际上只是跳转到输入地址表,由输入地址表再跳转到dll真正的函数入口。具体来说,我们将通过image_import_descriptor数组来访问“.idata”段中引入的dll的信息,然后通过image_thunk_data数组来针对一个被引入的dll访问该dll中被引入的每个函数的信息,找到我们需要截获的函数的跳转地址,然后改成我们自己的函数的地址……具体的做法在后面的关键代码中会有详细的讲解。
      讲了这么多原理,现在让我们回到“鼠标屏幕取词”的专题上来。除了api函数的截获,要实现“鼠标屏幕取词”,还需要做一些其它的工作,简单的说来,可以把一个完整的取词过程归纳成以下几个步骤:
    1. 安装鼠标钩子,通过钩子函数获得鼠标消息。
    使用到的api函数:setwindowshookex
    2. 得到鼠标的当前位置,向鼠标下的窗口发重画消息,让它调用系统函数重画窗口。
        使用到的api函数:windowfrompoint,screentoclient,invalidaterect
    3. 截获对系统函数的调用,取得参数,也就是我们要取的词。
    对于大多数的windows应用程序来说,如果要取词,我们需要截获的是“gdi32.dll”中的“textouta”函数。
    我们先仿照textouta函数写一个自己的mytextouta函数,如:
    bool winapi mytextouta(hdc hdc, int nxstart, int nystart, lpcstr lpszstring,int cbstring)
    {
          // 这里进行输出lpszstring的处理
              // 然后调用正版的textouta函数
    }
    把这个函数放在安装了钩子的动态连接库中,然后调用我们最后给出的hookimportfunction函数来截获进程
    对textouta函数的调用,跳转到我们的mytextouta函数,完成对输出字符串的捕捉。hookimportfunction的
    用法:
    hookfuncdesc hd;
    proc        porigfuns;
    hd.szfunc="textouta";
    hd.pproc=(proc)mytextouta;
    hookimportfunction (afxgetinstancehandle(),"gdi32.dll",&hd,porigfuns);
    下面给出了hookimportfunction的源代码,相信详尽的注释一定不会让您觉得理解截获到底是怎么实现的
    很难,ok,let’s go:///////////////////////////////////////////// begin ///////////////////////////////////////////////////////////////
    #include <crtdbg.h>// 这里定义了一个产生指针的宏
    #define makeptr(cast, ptr, addvalue) (cast)((dword)(ptr)+(dword)(addvalue))// 定义了hookfuncdesc结构,我们用这个结构作为参数传给hookimportfunction函数
    typedef struct tag_hookfuncdesc
    {
      lpcstr szfunc; // the name of the function to hook.
      proc pproc;    // the procedure to blast in.
    } hookfuncdesc , * lphookfuncdesc;// 这个函数监测当前系统是否是windownt
    bool isnt();// 这个函数得到hmodule -- 即我们需要截获的函数所在的dll模块的引入描述符(import descriptor)
    pimage_import_descriptor getnamedimportdescriptor(hmodule hmodule, lpcstr szimportmodule);// 我们的主函数
    bool hookimportfunction(hmodule hmodule, lpcstr szimportmodule, 
                            lphookfuncdesc pahookfunc, proc* paorigfuncs)
    {
    /////////////////////// 下面的代码检测参数的有效性 ////////////////////////////
    _assert(szimportmodule);
    _assert(!isbadreadptr(pahookfunc, sizeof(hookfuncdesc)));
    #ifdef _debug
    if (paorigfuncs) _assert(!isbadwriteptr(paorigfuncs, sizeof(proc)));
    _assert(pahookfunc.szfunc);
    _assert(*pahookfunc.szfunc != '\0');
            _assert(!isbadcodeptr(pahookfunc.pproc));
    #endif
    if ((szimportmodule == null) || (isbadreadptr(pahookfunc, sizeof(hookfuncdesc))))
    {
      _assert(false);
      setlasterrorex(error_invalid_parameter, sle_error);
      return false;
    }
    //////////////////////////////////////////////////////////////////////////////// 监测当前模块是否是在2gb虚拟内存空间之上
    // 这部分的地址内存是属于win32进程共享的
    if (!isnt() && ((dword)hmodule >= 0x80000000))
    {
      _assert(false);
      setlasterrorex(error_invalid_handle, sle_error);
      return false;
    }
        // 清零
    if (paorigfuncs) memset(paorigfuncs, null, sizeof(proc)); // 调用getnamedimportdescriptor()函数,来得到hmodule -- 即我们需要
    // 截获的函数所在的dll模块的引入描述符(import descriptor)
    pimage_import_descriptor pimportdesc = getnamedimportdescriptor(hmodule, szimportmodule);
    if (pimportdesc == null)
    return false; // 若为空,则模块未被当前进程所引入//  从dll模块中得到原始的thunk信息,因为pimportdesc->firstthunk数组中的原始信息已经
    //  在应用程序引入该dll时覆盖上了所有的引入信息,所以我们需要通过取得pimportdesc->originalfirstthunk
    //  指针来访问引入函数名等信息
    pimage_thunk_data porigthunk = makeptr(pimage_thunk_data, hmodule, 
                                                  pimportdesc->originalfirstthunk);//  从pimportdesc->firstthunk得到image_thunk_data数组的指针,由于这里在dll被引入时已经填充了
    //  所有的引入信息,所以真正的截获实际上正是在这里进行的
    pimage_thunk_data prealthunk = makeptr(pimage_thunk_data, hmodule, pimportdesc->firstthunk);//  穷举image_thunk_data数组,寻找我们需要截获的函数,这是最关键的部分!
    while (porigthunk->u1.function)
    {
      // 只寻找那些按函数名而不是序号引入的函数
      if (image_ordinal_flag != (porigthunk->u1.ordinal & image_ordinal_flag))
      {
      // 得到引入函数的函数名
      pimage_import_by_name pbyname = makeptr(pimage_import_by_name, hmodule,
                  porigthunk->u1.addressofdata);  // 如果函数名以null开始,跳过,继续下一个函数  
      if ('\0' == pbyname->name[0])
        continue;  // bdohook用来检查是否截获成功
      bool bdohook = false;  // 检查是否当前函数是我们需要截获的函数
      if ((pahookfunc.szfunc[0] == pbyname->name[0]) &&
        (strcmpi(pahookfunc.szfunc, (char*)pbyname->name) == 0))
      {
        // 找到了!
        if (pahookfunc.pproc)
        bdohook = true;
      }
      if (bdohook)
      {
        // 我们已经找到了所要截获的函数,那么就开始动手吧
        // 首先要做的是改变这一块虚拟内存的内存保护状态,让我们可以自由存取
        memory_basic_information mbi_thunk;
        virtualquery(prealthunk, &mbi_thunk, sizeof(memory_basic_information));
        _assert(virtualprotect(mbi_thunk.baseaddress, mbi_thunk.regionsize, 
                            page_readwrite, &mbi_thunk.protect));    // 保存我们所要截获的函数的正确跳转地址
        if (paorigfuncs)
          paorigfuncs = (proc)prealthunk->u1.function;    // 将image_thunk_data数组中的函数跳转地址改写为我们自己的函数地址!
        // 以后所有进程对这个系统函数的所有调用都将成为对我们自己编写的函数的调用
        prealthunk->u1.function = (pdword)pahookfunc.pproc;    // 操作完毕!将这一块虚拟内存改回原来的保护状态
        dword dwoldprotect;
        _assert(virtualprotect(mbi_thunk.baseaddress, mbi_thunk.regionsize, 
                            mbi_thunk.protect, &dwoldprotect));
        setlasterror(error_success);
        return true;
      }
      }
      // 访问image_thunk_data数组中的下一个元素
      porigthunk++;
      prealthunk++;
    }
    return true;
    }// getnamedimportdescriptor函数的实现
    pimage_import_descriptor getnamedimportdescriptor(hmodule hmodule, lpcstr szimportmodule)
    {
    // 检测参数
    _assert(szimportmodule);
    _assert(hmodule);
    if ((szimportmodule == null) || (hmodule == null))
    {
      _assert(false);
      setlasterrorex(error_invalid_parameter, sle_error);
      return null;
    }// 得到dos文件头
    pimage_dos_header pdosheader = (pimage_dos_header) hmodule;// 检测是否mz文件头
    if (isbadreadptr(pdosheader, sizeof(image_dos_header)) || 
      (pdosheader->e_magic != image_dos_signature))
    {
      _assert(false);
      setlasterrorex(error_invalid_parameter, sle_error);
      return null;
    }// 取得pe文件头
    pimage_nt_headers pntheader = makeptr(pimage_nt_headers, pdosheader, pdosheader->e_lfanew);// 检测是否pe映像文件
    if (isbadreadptr(pntheader, sizeof(image_nt_headers)) || 
      (pntheader->signature != image_nt_signature))
    {
      _assert(false);
      setlasterrorex(error_invalid_parameter, sle_error);
      return null;
    }// 检查pe文件的引入段(即 .idata section)
    if (pntheader->optionalheader.datadirectory[image_directory_entry_import].virtualaddress == 0)
      return null;// 得到引入段(即 .idata section)的指针
    pimage_import_descriptor pimportdesc = makeptr(pimage_import_descriptor, pdosheader,
      pntheader->optionalheader.datadirectory[image_directory_entry_import].virtualaddress);// 穷举pimage_import_descriptor数组寻找我们需要截获的函数所在的模块
    while (pimportdesc->name)
    {
      pstr szcurrmod = makeptr(pstr, pdosheader, pimportdesc->name);
      if (stricmp(szcurrmod, szimportmodule) == 0)
          break; // 找到!中断循环
      // 下一个元素
      pimportdesc++;
    }// 如果没有找到,说明我们寻找的模块没有被当前的进程所引入!
    if (pimportdesc->name == null)
      return null;// 返回函数所找到的模块描述符(import descriptor)
    return pimportdesc;
    }// isnt()函数的实现
    bool isnt()
    {
    osversioninfo stosvi;
    memset(&stosvi, null, sizeof(osversioninfo));
    stosvi.dwosversioninfosize = sizeof(osversioninfo);
    bool bret = getversionex(&stosvi);
    _assert(true == bret);
    if (false == bret) return false;
    return (ver_platform_win32_nt == stosvi.dwplatformid);
    }
    /////////////////////////////////////////////// end //////////////////////////////////////////////////////////////////////  不知道在这篇文章问世之前,有多少朋友尝试过去实现“鼠标屏幕取词”这项充满了挑战的技术,也只有尝试过的朋友才能体会到其间的不易,尤其在探索api函数的截获时,手头的几篇资料没有一篇是涉及到关键代码的,重要的地方都是一笔代过,msdn更是显得苍白而无力,也不知道除了image_import_descriptor和image_thunk_data,微软还隐藏了多少秘密,好在硬着头皮还是把它给攻克了,希望这篇文章对大家能有所帮助。  
    回复人: ai_daoluan(捣乱) (2001-11-30 11:07:04)  得0分 
    再来一段:以前老看见Trap的资料,到用的时候却一个也找不到。
    中国人真TMD藏私,什么都喜欢偷偷留一手,还想进步!
    转贴一个讨论吧。金山词霸”屏幕取词技术揭密(讨论稿)
    主题  屏幕取词技术系列讲座(一) 
    作者   亦东 很多人对这个问题感兴趣。 
    原因是这项技术让人感觉很神奇,也很有商业价值。 
    现在词典市场金山词霸占了绝对优势,所以再做字典也没什么前途了。我就是这么认为的,所以我虽然掌握了这项技术,却没去做字典软件。只做了一个和词霸相似的软件自己用,本来想拿出来做共享软件,但我的词库是“偷”来的,而且词汇不多,所以也就算了,词库太小,只能取词有什么用呢?而且词霸有共享版的。 
    但既然很多人想了解这项技术,我也不会保留。我准备分多次讲述这项技术的所有细节。 
    大约每周一两次。想知道的人就常常来看看吧! 一.基础知识 
    首先想编这种程序需要一些基础知识。 
    会用Vc++,包括16/32位。 
    精通Windows API特别是GDI,KERNEL部分。 
    懂汇编语言,会用softice调试程序,因为这种程序最好用softice调试。 二.基本原理 
    在Window 3.x时代,windows系统提供的字符输出函数只有很少的几个。 
    TextOut 
    ExtTextOut 
    DrawText 
    ...... 
    其中DrawText最终是用ExtTextOut实现的。 所以Windows的所有字符输出都是由调用TextOut和ExtTextOut实现的。因此,如果你可以修改这两个函数的入口,让程序先调用你自己的一个函数再调用系统的字符输出,你就可以得到Windows所有输出的字符了。 到了Windows95时代,原理基本没变,但是95比3.x要复杂。开始的时候,一些在windows3.x下编写的取词软件仍然可以是使用。但是后来出了个IE4,结果很多词典软件就因为不支持IE4而被淘汰了,但同时也给一些软件创造了机会,如金山词霸。其实IE4的问题并不复杂,只不过它的输出的是unicode字符,是用TextOutW和ExtTextOutW输出的。知道了这一点,只要也截取就可以了。不过实现方法复杂一点,以后会有详细讲解。现在又出了个IE5,结果词霸也不好用了,微软真是#^@#$%$*&^&#@#@.......... 
    我研究后找到了一种解决办法,但还有些问题,有时会取错,正在继续研究,希望大家共同探讨。 另外还有WindowsNT,原理也是一样,只是实现方法和95下完全不同。 三.技术要点 
    要实现取词,主要要解决以下技术问题。 
    1.截取API入口,获得API的参数。 
    2.安全地潜入Windows内部,良好地兼容Windows的各个版本 
    3.计算鼠标所在的单词和字母。 
    4.如果你在Window95下,做32位程序,还涉及Windows32/16混合编程的技术。 今天先到这里吧!最好准备一份softice for 95/98和金山词霸,让我们先来分析一下别人是怎么做的。 欢迎与我联系 
    E-Mail:[email protected] 
    Guest  1999-04-30 16:00:48  
    请问用VC自己的DEBUGGER不行吗?为什么要用SOFTICE? 我没用过SOFTICE,它有什么特别之处吗? 
    葫芦  1999-04-30 19:15:03  
    本人对这个问题也有兴趣,以前研究过16位版本截获TextOut和ExtTextOut的过程; 但对金山词霸,用Softice跟踪,发现SetWindowsHookEx在程序装载时就安装了鼠标钩子,暂停取字/恢复取字只是设置的内部变量,但本人发现金山词霸并没有象16位版本那样修改TextOut和ExtTextOut。 
    苍蝇 (555021552)  1999-05-02 08:56:57  
    有哪位大虾愿意先介绍一下SOFTICE? 
    蟑螂  1999-05-04 13:58:22  
    把金山词霸的cjktl95.dll用tdump分析可以看到它根本用的不是hook,而是用了一些kernel32.dll中的Win32 SDK里没有包含的函数,使用这些函数来替换改写原先的几个GDI函数。看来是Win95未公开的一些东西。有些人知道,为什么不肯说出来呢?未必见得多么高深! 葫芦  1999-05-05 23:28:07  
    你可能没有研究过它的目标代码,怎么能断言没有使用hook呢? 其实金山词霸一运行就安装了鼠标钩子。 
    亦东  1999-05-07 09:52:42  
    未必见得多么高深? 
    你知道有些人是怎么知道这些Win95未公开的东西的吗?你不会是以为微软偷偷告诉他们的吧?其实他们大多和我一样是用softice自己跟踪Win95跟出来的。 还有你从cjktl95.dll里tdump出的未公开函数和hook无关,那是做别的用的。 
    setwindowshook在user里 
    亦东  1999-05-10 16:16:14  
    从cjktl95.dll里tdump出的未公开函数和32位和16位之间的互调有关。 主题  屏幕取词技术系列讲座(二) 
    作者   亦东 很抱歉让大家久等了! 
    我看了一些人的回帖,发现很多人对取词的原理还是不太清楚。 
    首先我来解释一下hook问题。词霸中的确用到了hook,而且他用了两种hook其中一种是Windows标准hook,通过SetWindowHook安装一个回调函数,它安装了一个鼠标hook,是为了可以及时响应鼠标的消息用的和取词没太大关系。 
    另一种钩子是API钩子,这才是取词的核心技术所在。他在TextOut等函数的开头写了一个jmp语句,跳转到自己的代码里。 
    你用softice看不到这个跳转语句是因为它只在取词的一瞬间才存在,平时是没有的。 
    你可以在TextOut开头设一个读写断点 
    bpm textout 
    再取词,就会找到词霸用来写钩子的代码了。 /********************************** 
    所以我在次强调,想学这种技术一定要懂汇编语言和熟练使用softice. 
    **********************************/ 至于从cjktl95中dump出来的未公开函数是和Windows32/16混合编程有关的,以后我会提到他们。 我先来讲述取词的过程, 0 判断鼠标是否在一个地方停留了一段时间 
    1 取得鼠标当前位置 
    2 以鼠标位置为中心生成一个矩形 
    3 挂上API钩子 
    4 让这个矩形产生重画消息 
    5 在钩子里等输出字符 
    6 计算鼠标在哪个单词上面,把这个单词保存下来 
    7 如果得到单词则摘掉API钩子,在一段时间后,无论是否得到单词都摘掉API钩子 
    8 用单词查词库,显示解释框。 很多步骤实现起来都有一些难度,所以在中国可以做一个完善的取词词典的人屈指可数。 其中0,1,2,7,8比较简单就不提了。 先说如何挂钩子: 
    所谓钩子其实就是在WindowsAPI入口写一个JMP XXXX:XXXX语句,跳转到自己的代码里。 步骤如下: 
    1.取得Windows API入口,用GetProcAddress实现 
    2.保存API入口的前五个字节,因为JMP是0xEA,地址是4个字节 
    3.写入跳转语句 
    这步最复杂 
    Windows的代码段本来是不可以写的,但是Microsoft给自己留了个后门。 
    有一个未公开函数是AllocCsToDsAlias, 
    UINT WINAPI ALLOCCSTODSALIAS(UINT); 
    你可以取到这个函数的入口,把API的代码段的选择符(要是不知道什么是选择符,就先去学学保护模式编程吧)传给他,他会返回一个可写的数据段选择符。这个选择符用完要释放的。用新选择符和API入口的偏移量合成一个指针就可以写windows的代码段了。 这就是取词技术的最核心的东东,不止取词,连外挂中文平台全屏汉化都是使用的这种技术。现在知道为什么这么简单的几句话却很少知道了吧?因为太多的产品使用他,太多的公司靠他赚钱了。 
    这些公司和产品有:中文之星,四通利方,南极星,金山词霸,实达铭泰的东方快车,roboword,译典通,即时汉化专家等等等等。还有至少20多家小公司。他们的具体实现虽然不同,但大致原理是相同的。 我这些都是随手写的,也没有提纲之类的东西,以后如果有机会我会整理一下,大家先凑合着看吧!xixi... 
    葫芦  1999-05-06 14:52:30  
    你说的这个技术是16位的,至少金山词霸III没有采用16位的AllocCsToDsAlias函数,也根本没有修改TextOut开始的语句为JMP XXXXXXXX。softice本人非常精通,是绝对不会错的! 
    葫芦  1999-05-06 15:38:40  
    谁假冒吾名,坏吾名声?!本人在此郑重宣布,以上贴子非本人所发! 
    经过跟踪分析,金山词霸III确实修改了TextOut的入口为转跳到自身代码的JMP XXXXXXXX语句,只是修改没有调用AllocCsToDsAlias,也不是在取字时才去修改,而是常常修改(估计是在Timer中进行的)。 
    亦东  1999-05-06 17:25:23  
    我说过词霸里用AllocCsToDsAlias了吗? 
    我是说AllocCsToDsAlias,的确可以用,但词霸里是用DPMI的Int 31做的,其实本质是一样的,你用softice跟一下就知道了 
    bpint 31 设一个中断断点。 实际上我至少有五种方法写Windows代码段 
    我介绍使用AllocCsToDsAlias是因为这种方法最简单,其他方法要麻烦得多。 
    词霸用int 31来做是出于兼容性的考虑, 
    AllocCsToDsAlias毕竟不是公开函数,但DPMI却是标准。 词霸在你鼠标在某一点停留超过200ms时就会取词,所以他有一个定时器,会经常修改API入口。 看来“假”葫芦只是自以为非常精通softice. mao  1999-05-06 19:29:28  
    在微软的MSDN中有一个程序,包含了全部的Source code, 名字好象叫"Inject" 或 stealth什么的,忘了。提供了一个很完善的hook任何一个windows函数的功能。对win16完全适用,在win95下也有用,但NT下不行。 建议大家去找找这个例子看看会很有帮助。 此外有本清华出的微软的Advanced Windows也介绍了具体的方法。 建议亦东干脆把source code给open出来,让有兴趣的朋友用起来更方便,把取词技术可发展的应用发扬光大! 
    亦东  1999-05-07 09:40:56  
    我不主张“把取词技术可发展的应用发扬光大”,这也是我掌握了这项技术一年多才公开它的原因。我公开它并不是想让大家都来做字典软件,相反我希望不要再有人做字典了,现在做字典的人已经太多了。看到某种软件有利可图,大家就一哄而上,这种恶性竞争对中国软件业的发展是极其有害的。我公开他的目的是希望提高大家的编程水平。你会发现在研究这项技术的过程中你的编程水平和对Windows的理解程度会有质的飞跃,我本人就从中获益匪浅。 MSDN中是有这样的代码。 
    甚至有一个叫ProcHook.Dll,提供SetProcAddress之类的函数,但是没有源码。源码在1994年的 MSJ 上很难弄到的。 
    源码我会分几次公开,每次会有详细的说明。 想要源码的人,准备一份VC++1.52或Borland C++,最好还有softice,下次我会给出一段代码,教你如何修改Windows的代码。 
    老冒 (555036)  1999-05-07 11:56:46  
    如果认为屏幕取词的应用就是做字典,就大错特错了。其实关于拦劫windows api的东东早就在93年的Undocument Windows上公开过了。 其实Adobe的Adobe Type Manager在Windows 3.0的时代就通过这种办法实现了漂亮的字体. (现在有TTF不需要ATM了) MSDN上的那个东东是有全部source和sample, 我抓下来编译过。是1996年夏天的一张MSDN Level 2光碟上的,现在也不知搁哪里了,有兴趣的朋友自己找去吧。 还是open 完整的source好,很多朋友其实只要用这项技术,并不太想知道细节,不是吗. 
    亦东  1999-05-07 13:28:19  
    这种技术不是做字典全屏汉化就是外挂语言平台,自从王志东使用它以来,就没用来编过其他软件,也许有但我不知道。 原来有那么多书和其他资料上都有这种技术的资料还有例子,到是我孤陋寡闻了,以为大家都不知道,在这里给大家讲一些众所周知的东西。回去我要好好研究一下,看看Rasir Dex的词霸是从那里抄的。 我不知道某项技术中细节是不是重要,如果很多人只想用而不想自己编,那么楼下那50多个回帖是怎么回是? 
    老冒  1999-05-07 13:54:18  
    呵呵,你可千万要坚持把讲座做下去,否则那50多个回应的哥们企不要把我给痛扁了...:) 俺已两年多不碰底层技术的,这方面很落后啦...俺可应付不了这么多热切的求知朋友,亦东要顶住呵! 欢迎和俺多多交流探导! P.S. 亦东大侠目前何方高就? 正在忙什么项目?交流交流 
    亦东  1999-05-07 17:46:06  
    没什么正经事做,到处瞎混呢! 
    葫芦  1999-05-08 21:50:29  
    我对此持否定观点,不要自作聪明,以为AllocCsToDsAlias就是能用的,其实AllocCsToDsAlias只是16位的Windows用的函数,32位的Win95程序不能使用此函数,不信你在VC 5.0或6.0中可以试试。 
    另外,int 31h也不是说能用就能拿来用的,在Windows 3.x下使用是没有问题的(本人还有这方面的文章发表),但在Win95下随意使用会产生GP错,主要原因是32位并不支持DPMI直接调用,不知亦东先生对此有没有研究,就在此发表诸多理论!本人就先请问:32位程序如何调用16位函数或动态库你懂不懂? 
    葫芦  1999-05-09 02:39:10  
    上面的这个帖子并没有攻击谁的意思,只是希望大家探讨问题都要本着认真的态度,不要不懂装懂,至少有一点大家要清楚:32位的程序根本不能使用 int 31h,调用16位的动态库Kernel中的AllocCsToDsAlias也并不是件简单的事。 
    nn_zdm (555031742)  1999-05-09 16:35:35  
    使用hook函数,可用的功能并非只是做字典全屏汉化和外挂语言平台。使用hook可以调试程序,就象你们说的softice其本身也是使用了hook函数。 
    nn_zdm (555031742)  1999-05-09 16:42:05  
    另外hook函数还可以使用在游戏修改工具中,本人就开发过此类工具。《整人专家》估计也是使用这种方法。当然还有另外两种方法。 
    亦东  1999-05-10 16:10:59  
    你们说的都有道理。 
    但hook有两种,一种是Windows标准钩子,通过SetWindowshook挂。 
    另一种是非标准的,通过在API入口写JMP XXXXXXXX来实现的。 
    softice的钩子更高级,他都挂到VXD上了。 
    从32为代码调用16位DLL碰巧我会。 打倒米D国主义!!! 
    瓜果  1999-05-10 17:07:00  
    谁知道在哪能搞到SOFTICE,我以前从未用过它! 
    葫芦  1999-05-10 21:39:46  
    愿继续拜读你以后的讲座。 SOFTICE吗?光盘上很多,有for DOS, for Windows95, for Windows NT 各个版本。 
    孙玮 (555031339)  1999-05-11 11:08:35  
    能否将 si for NT 上传到 10.82.46.33 
    (使用 ftp) user: haotao 
    pass: haotao123 
    tommy  1999-05-11 11:34:11  
    http://www.swww.com.cn/htm/down/others/main.html 可以下载 
    亦东  1999-05-11 14:01:19  
    最近忙于反美,暂时没时间再写了,过些时间才行,下次我会给出源码。 
    最新消息,美国海军被黑了。 
    http://www.nctsw.navy.mil/ 打倒米D国主义!!! 
    黄金狮子  1999-05-12 13:19:32  
    我对各位大虾的讨论深感兴趣。 有几个问题想请教: 
    1.AllocCsToDsAlias 在32-bit下调用是否采用Thunk? 2.32-bit 下是否有类似function? 3.Jeffrey Richter的"Advanced Windows"里Remote Thread 的Thread Stack来远程注入DLL函数,因此不需上述Function. 4.我有MSJ 1994-1的ProcHook.dll的source code,不知用于WinNT需如何改动. 5.总而言之,有无WinNT下hook API的source code,请告知. (我还有Softice 3.24 for Win95, 3.25 for WinNT.) --这个主题很好 
    鼹鼠  1999-05-14 09:41:20  
    请大虾给我发一份MSJ 1994-1的ProcHool.dll的source code, 我现在急需这方面的资料。谢谢!! Email Address: [email protected] 
    下面是一个Australia人的API hook软件, 它是基于VxD技术。 
    Molten Home Page: 
    http://ourworld.compuserve.com/homepages/molten 
    黄金狮子  1999-05-14 14:34:54  
    在Win95和NT上,可通过WriteProcessMemory()直接写Code Segment.(原来以为"advanced Windows" 调CreateRemoteThread(),是因为WriteProcessMemory()只能写代码段和堆栈段) 
    阿涛  1999-05-14 19:45:05  
    如何亦东老兄要分步公布是PROCHOOK的代码就不必了,这个程序的代码很容易搞到,只须到MSJ的站点上查一下就可找到。 
    亦东  1999-05-14 21:44:39  
    大家到msj的大海里去捞针好了。 在95你WriteProcessMemory 写kernel user gdi试试,一定失败。 调CreateRemoteThread并不是为了写代码段,有别的用途,是为了在其他进程里分配内存。回去再好好看看“advanced Windows" 最好用一个程序试试,你就明白了。 其实在NT4.0调CreateRemoteThread是没必要的,这是为了兼容NT3.51. 
    nn_zdm (555031742)  1999-05-18 13:57:48  
    利用CreateRemoteThread()函数,在WinNT4.0中使用很有用,它是在winNT中闯过进程边界的三种办法之一,在WinNT中,使用它可以进行远程调试,及修改他人代码. 
    nn_zdm (555031742)  1999-05-18 14:07:17  
    在winNT中用WriteProcessMemory()写code 
    代码是可以的,win95没试过,但MSDN上说是可以的.不过,可能没什么用,因为CreateRemoteThread()函数只在winNT中有用. 
    如: 
    WriteProcessMemory(...,"LoadLibrary(...,"mydll.dll",..). 
    CreateRemoteThread(...) 
    nn_zdm (555031742)  1999-05-18 14:14:19  
    上面写漏了,应是 
    WriteProcessMemory(...,"LoadLibrary(...,"mydll.dll",..). "); 
    CreateRemoteThread(...) ; 主题  关于屏幕取词的讨论(三) 
    作者   亦东 
    让大家久等,很抱歉,前些时候工作忙硬盘又坏了,太不幸了。 这回来点真格的。 咱们以截取TextOut为例。 下面是代码: //截取TextOut typedef UINT (WINAPI* ALLOCCSTODSALIAS)(UINT); ALLOCCSTODSALIAS AllocCsToDsAlias; BYTE NewValue[5];//保存新的入口代码 
    BYTE OldValue[5];//API原来的入口代码 
    unsigned char * Address=NULL;//可写的API入口地址 
    UINT DsSelector=NULL;//指向API入口的可写的选择符 
    WORD OffSetEntry=NULL;//API的偏移量 BOOL bHookAlready = FALSE; //是否挂钩子的标志 BOOL InitHook() 

    HMODULE hKernel,hGdi; 
    hKernel = GetModuleHandle("Kernel"); 
    if(hKernel==NULL) 
    return FALSE; AllocCsToDsAlias = (ALLOCCSTODSALIAS)GetProcAddress(hKernel,"AllocCsToDsAlias");//这是未公开的API所以要这样取地址 
    if(AllocCsToDsAlias==NULL) 
    return FALSE; hGdi = GetModuleHandle("Gdi"); 
    if(hmGdi==NULL) 
    return FALSE; FARPROC Entry = GetProcAddress(hGdi,"TextOut"); 
    if(Entry==NULL) 
    return FALSE; OffSetEntry = (WORD)(FP_OFF(Entry));//取得API代码段的选择符 
    DsSelector = AllocCsToDsAlias(FP_SEG(Entry));//分配一个等同的可写的选择符 
    Address = (unsigned char*)MK_FP(DsSelector,OffSetEntry);//合成地址 NewValue[0]=0xEA; 
    *((DWORD*)(NewValue+1)) = (DWORD)MyTextOut; OldValue[0]=Address[0]; 
    *((DWORD*)(OldValue+1)) = *((DWORD*)(Address+1)); 
    } BOOL ClearHook() 

    if(bHookAlready) 
    HookOff(); FreeSelector(DsSelector); 
    } BOOL HookOn() 

    if(!bHookAlready){ 
    for(int i=0;i<5;i++){ 
    Address[i]=NewValue[i]; 

    bHookAlready=TRUE; 

    } BOOL HookOff() 

    if(bHookAlready){ 
    for(int i=0;i<5;i++){ 
    Address[i]=OldValue[i]; 

    bHookAlready=FALSE; 

    } //钩子函数,一定要和API有相同的参数和声明 
    BOOL WINAPI MyTextOut(HDC hdc,int nXStart,int nYStart,LPCSTR lpszString,UINT cbString) 

    BOOL ret; 
    HookOff(); 
    ret = TextOut(hdc,nXStart,nYStart,lpszString,cbString);//调原来的TextOut 
    HookOn(); 
    return ret; 
    } 上面的代码是一个最简单的挂API钩子的例子,我要提醒大家的是,这段代码是我凭记忆写的,我以前的代码丢了,我没有编译测试过 
    因为我没有VC++1.52.所以代码可能会有错。 建议使用Borland c++,按16位编译。 
    如果用VC++1.52,则要改个选项 在VC++1.52的Option里,有个内存模式的设置,选大模式,和"DS!=SS DS Load on Function entry.",切记,否则会系统崩溃。 有什么不明白的可以给我写信 
    [email protected] 
    Guest  1999-05-21 22:20:47  
    你这是16为的地址存取模式吧 
    你看这个MK_FP,win32不用了, 
    而且GetProcAddress(hKernel,"AllocCsToDsAlias") 
    这个API有用吗? 
    sorry 
    Guest  1999-05-21 22:31:47  
    亦东,想请教一个问题, 
    win32下,每个process有 
    自己的地址空间,process 
    A得到process B 的一个窗口C的handle,这个 
    handle的值 等于process B自己得到的window C 的handle值 吗? 
    我想应该不相等,但系统是如何转换的呢?(比如process A 向 
    window C 发消息,系统如何 
    知道process A 里的handle 和process B 里的 
    handle 都是指的window C) 
    . 是不是用duplicatehandle()? 
    (声明,我是真的不知道) 
    亦东  1999-05-21 22:54:48  
    这段代码就是十六位的。 
    你用Win32根本就不能编译。 32位没有AllocCsToDsAlias,因为在32位里不能写系统代码段(其实有办法,不是这样,不过比较麻烦)。 系统代码都在0x80000000以上,都是只读的。 所以要截WinAPI只能用16位的代码。 每个Process有自己的地址空间没错,但Window的句柄是共享的,同一个窗口在任何进程里的句柄都是一样的。 你可以在自己的进程里向任何窗口发消息。 Window的句柄很多,有的是共享的有的不是, 
    我也不知道那里有说明,一般是凭经验或试试看。 
    GUEST  1999-05-22 20:51:51  
    如果 window handle 换成 
    moudle handle呢?我 
    用moudlefirst,moudlenext遍历 
    得到的某个moudle 的句柄, 
    在任何一个process 中得到的 
    这个moudle handle 都是一样的值吗。(这个handle 和进程地址空间无关吗?)。 
    thx 亦东  1999-05-23 22:31:59  
    绝对有关 
    在Win32里module handle就是模块的起始地址。 说起来比较复杂 
    在95和NT里有些不同 
    在Win32里,每个模块(DLL,EXE)有一个ImageBase,这个数存放在DLL和EXE的文件头里。每个模块通常是不一样的。当Windows加载这个模块时优先考虑把模块放到Imagebase指定的地址,但有时会出现两个模块的地址重叠会有冲突,Windows会把模块移到与Imagebase最近的地址。所以Imagebase相同的模块在不同进程可能会在不同的地址上。这个地址就是module handle.Imagebase是可以在编译时指定的。 你用moudlefirst,moudlenext遍历得到的module handle是和进程有关的。 比如:你编了一个Imagebase为0x10000000的DLL A,进程A调用这个DLL A,在进程A里这个DLL被加载到地址0x10000000处,他的module handle为0x10000000,进程B也调用这个DLL A,但是进程B还调用另外一个DLL B,这另外的DLL B也是Imagebase为0x10000000的而且先加载,这是进程B的这个DLL A可能就被加载到0x13000000了,DLL A在进程B里的module handle 就是0x13000000了。 在95下,模块是共享的,也就是Windows只加载一份模块到内存,所有用到这个模块的进程都映射同一个模块,也就是说在95里每个模块在物理内存里只有一份。NT则不同,他为每个进程都加载一份模块。所以NT比95需要的内存多。所以在NT里不但在不同进程里的Module handle可能不同,连物理地址都是不同的。 [email protected]  
    回复人: adailee(不谈恋爱的铅笔) (2001-11-30 11:18:15)  得0分 
    能发布一下Delphi下的源代码吗?  
    回复人: shinesi(阿shine) (2001-11-30 11:25:18)  得0分 
      

  2.   

    我是这样做的,我在98可以实现,但是我想转到win2000下,请问高手怎么实现啊