怎么拦截系统函数

分享一下我老师大神的人工智能教程。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

               

 
主  题:  怎么拦截系统函数?(超高度难题!)

 

  在Dos下,我们可以拦截中断,用自己的处理替换系统中断,然后调用原来的中断,在Windows中,没有了中断,只有函数,那么我们如何来拦截系统的函数,然后我们的函数又调用原来的系统函数呢?比如说系统有一个GetSystemDirectory()函数,我想用我自己的函数替换它,经过我的函数MyGetSystemDirectory()进行处理,然后由我的函数调用原来的GetSystemDirectory()进行处理,该如何做到!如果能够解决,我可以再开贴子送分!目前只能给150分。
 
 
 
 回复人: wzs(兔子) ( ) 信誉:99  2000-11-11 12:09:00Z  得分:0
 
 
 
vxd编程!
 
 
Top
 
 回复人: tchaikov() ( ) 信誉:100  2000-11-11 12:19:00Z  得分:0
 
 
 
化点时间去看看
http://www.sysinternals.com/ntinternals.htm
http://www.sysinternals.com/filemon.htm
或许对你有好处。
 
 
Top
 
 回复人: Kingron(单身走我路……) ( ) 信誉:117  2000-11-11 13:40:00Z  得分:0
 
 
 
  呵呵,用VXD?太烦了,我知道可以用Delphi的内嵌汇编可以做到,这才是我要的效果!我看见有的人已经可以做到这一点,编译后的DCU文件才3KB,谁能够给出例程?快来拿分啊!
 
 
Top
 
 回复人: Kingron(单身走我路……) ( ) 信誉:117  2000-11-11 21:02:00Z  得分:0
 
 
 
  呵呵,请各位高手该出手时就出手!呵呵,千万别叫我用VXD。给出例程,马上给分!金山词霸取词就是拦截了TextOut,TextOutA吧?原理应该是一样的。Help Me!
 
 
Top
 
 回复人: Kingron(单身走我路……) ( ) 信誉:117  2000-11-12 21:26:00Z  得分:0
 
 
 
  快回答啊,各位高手!
 
 
Top
 
 回复人: mutant(异类) ( ) 信誉:100  2000-11-13 10:03:00Z  得分:0
 
 
 
楼上的,说错了.你是拦截消息,用钩子当然可以了.人家是替换函数,钩子能行吗?
拜托你看清楚问题再回答.
 
 
Top
 
 回复人: gameboy999(-'_'-) ( ) 信誉:100  2000-11-13 13:26:00Z  得分:10
 
 
 
钩子不行,也不是vxd,对了kingron,你可否买了csdn程序员大本营2000的微软版??
在vc版块里有你所需要的文章!!!!我昨天刚买的.我在网吧,没法贴那篇帖子,但我可以肯定那必然是你所需要的,(既动态重定向系统内部函数).
 
 
Top
 
 回复人: Kingron(单身走我路……) ( ) 信誉:117  2000-11-13 13:58:00Z  得分:0
 
 
 
 2:tchaikov
 我的机器不能够上国外的网站,用代理也不太行。多谢你的指点,而且我的E文太好,呵呵。
 2: GameBoy999
 对了,就是这个!呵呵,给VC的也行!我会一点儿VC,可以把它翻译过来!我没有买CSDN(在这个偏远的小城也很难买到,不知道有没有卖),麻烦你把它贴出来!
  呵呵,快点儿,就可以拿分了!那位先贴出来,先给分!
 
 
Top
 
 回复人: gameboy999(-'_'-) ( ) 信誉:100  2000-11-13 14:25:00Z  得分:0
 
 
 
to kingron:我也很想,不过我们这里的网吧不让上载,我很头疼这事情,我的原码也不能发上来,哎,您还是拜托一下人家吧:)
 
 
Top
 
 回复人: vcx(vcx) ( ) 信誉:100  2000-11-13 16:48:00Z  得分:20
 
 
 
用Detours或APIHOOKS 2.0。我也在研究这个问题,不知怎么老没有成功, Detours的原理是把DLL注入另一进程空间,APIHOOKS2.0我只成功了本进程的HOOK,不知哪为高手能给个成功的例子
谢谢!
 
 
Top
 
 回复人: 6bytes(吴下阿蒙) ( ) 信誉:100  2000-11-14 01:28:00Z  得分:0
 
 
 
If you mean redirect an API function so that your function gets called
instead, then try this:

Get the Entry point of the exported function in the dll.
Replace it in memory with the address of your own function.
 
 
Top
 
 回复人: Kingron(单身走我路……) ( ) 信誉:117  2000-11-14 07:53:00Z  得分:0
 
 
 
2 6bytes:
  how to do it?请给出例程。Delphi,VC的都行!
 
 
Top
 
 回复人: uestclx(乐乐) ( ) 信誉:100  2000-11-14 12:21:00Z  得分:0
 
 
 
trap 技术
我好像有原码
 
 
Top
 
 回复人: gameboy999(-'_'-) ( ) 信誉:100  2000-11-14 12:58:00Z  得分:0
 
 
 
to kringron:叫人家帮帮忙,把程序员大本营微软篇的vc下的activex下的那篇叫什么"陷阱技术探密"的文章贴出来.
 
 
Top
 
 回复人: Kingron(单身走我路……) ( ) 信誉:117  2000-11-16 12:10:00Z  得分:0
 
 
 
那位兄弟帮个忙,贴一下啊!陷阱技术探密的文章!
 
 
Top
 
 回复人: wr960204(武稀松) ( ) 信誉:94  2000-11-16 15:36:00Z  得分:0
 
 
 
与金山词霸原理相同,射击到DLL重入,好象要先写一个DLL,将系统DLL函数在内存中的的地址指向你的函数.有很多Windows内核的书都讲到不过几乎都是C++的代码
 
 
Top
 
 回复人: Kingron(单身走我路……) ( ) 信誉:117  2000-11-16 15:45:00Z  得分:0
 
 
 
2 wr960204:
  也行!只要达到目的即可,VC的没关系,我自己可以翻译,麻烦你贴出有关的文章好吗?
  呵呵,我想这个问题基本上每个人都看了吧?赶快出手啊!150哦!
 
 
Top
 
 回复人: ab(ab) ( ) 信誉:100  2000-11-16 15:51:00Z  得分:30
 
 
 
大家别争了,微软已经提供了这种开发包,就是上面 VCX 说的 Detours,到
http://www.research.microsoft.com/sn/detours
去下载。不过随带的文档太少了。
 
 
Top
 
 回复人: Kingron(单身走我路……) ( ) 信誉:117  2000-11-25 14:56:00Z  得分:0
 
 
 
哪儿有Detours的库文件下载?(.dll or .lib or .c or .h or .ocx......)
 
 
Top
 
 回复人: CoolSlob(我拿青春赌明天) ( ) 信誉:102  2000-11-26 19:26:00Z  得分:0
 
 
 
关注,Kingron!!!有了答案,能不能给我一份??????先谢了
 
 
Top
 
 回复人: temps(青石) ( ) 信誉:99  2000-11-28 12:45:00Z  得分:0
 
 
 
关注!!! 有答案 temps@263.net也希望可以得到一分
 
 
Top
 
 回复人: tibetty(laoduan) ( ) 信誉:100  2000-11-28 13:01:00Z  得分:0
 
 
 
在水木清华的Programming版探讨了不下十次, Matt Pietrek的<<Windows 95 System Programming Secret>>以及Jeffery Richter的<<Advanced Windows Programming>>
里面都有专门的探讨.
 
 
Top
 
 回复人: zheng(一笑) ( ) 信誉:100  2000-11-28 13:06:00Z  得分:0
 
 
 
guanzhu
 
 
Top
 
 回复人: Jujus(某人) ( ) 信誉:99  2000-11-28 13:59:00Z  得分:50
 
 
 
装载一篇文章,也许有用,但我在MSDN上没有查到IMAGE_IMPORT_DESCRIPTOR的结构,Kingron:你把它转成Delphi代码后记得给我发一份哦


如何截获API函数(转)
该程序是基于HOOK原理,主要是将自己的函数放到目标PROCESS的地址空间,这里是使用HOOK实现.首先建立一个MOUSE的HOOK程序,然后在全局鼠标HOOK的DLL中做截获动作,可以在PROCESS_ATTACH时做,也可以在鼠标的HOOK链函数中做.
建立全局HOOK就不说了,可以在网上很多地方看到.主要是截获动作.通过PE格式(使用IMAGE)改变API函数在调用时的地址.DLL部分参考如下代码:
static int WINAPI MyMessageBoxW(HWND hWnd , LPCWSTR lpText, LPCWSTR lpCaption, UINT uType)//自己的MessageBoxW函数
{return MessageBox(hWnd, "TNT"/*lpText*/, "TNT"/*lpCaption*/, uType);
}
我定义了一个结构
typedef struct tag_HOOKAPI
{
LPCSTR szFunc;//待HOOK的API函数名
PROC pNewProc;//新的函数指针
PROC pOldProc;//老的函数指针
}HOOKAPI, *LPHOOKAPI;
extern "C" __declspec(dllexport)PIMAGE_IMPORT_DESCRIPTOR GetNamedImportDescriptor(HMODULE hModule, LPCSTR szImportMod)
{//首先是DOS头
PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER) hModule;
if(pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE) return NULL;
PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDOSHeader + (DWORD)(pDOSHeader->e_lfanew));
if(pNTHeader->Signature != IMAGE_NT_SIGNATURE) return NULL;
//如果没有Import部分,返回失败
if(pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress == 0)
return NULL;
//取Import部分
PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)
((DWORD)pDOSHeader + (DWORD)(pNTHeader->OptionalHeader.
DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress));
//寻找与szImportMod相配部分
while (pImportDesc->Name)
{
PSTR szCurrMod = (PSTR)((DWORD)pDOSHeader + (DWORD)(pImportDesc->Name));
if (stricmp(szCurrMod, szImportMod) == 0)
break; //找到
pImportDesc++;
}
if(pImportDesc->Name == NULL) return NULL;
return pImportDesc;
}
extern "C" __declspec(dllexport) HookAPIByName(HMODULE hModule/*被HOOK的目标进程MODULE*/, LPCSTR szImportMod/*如GDI32.DLL*/,LPHOOKAPI pHookApi/*指定函数名,如"MessageBoxW"*/)
{
PIMAGE_IMPORT_DESCRIPTOR pImportDesc =
GetNamedImportDescriptor(hModule, szImportMod);
if (pImportDesc == NULL)
return FALSE; //需要改换的API不能取到正确描PIMAGE_THUNK_DATA pOrigThunk = (PIMAGE_THUNK_DATA)((DWORD)hModule + (DWORD)(pImportDesc->OriginalFirstThunk));
PIMAGE_THUNK_DATA pRealThunk =
(PIMAGE_THUNK_DATA)((DWORD)hModule + (DWORD)(pImportDesc->FirstThunk));
while(pOrigThunk->u1.Function)
{
if((pOrigThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG) != IMAGE_ORDINAL_FLAG)
{
PIMAGE_IMPORT_BY_NAME pByName = (PIMAGE_IMPORT_BY_NAME)((DWORD)hModule + (DWORD)(pOrigThunk->u1.AddressOfData));
if(pByName->Name[0] == '/0')
return FALSE; //失败
if(strcmpi(pHookApi->szFunc, (char*)pByName->Name) == 0)
{
//改变thunk保护属性
MEMORY_BASIC_INFORMATION mbi_thunk;
VirtualQuery(pRealThunk, &mbi_thunk, sizeof(MEMORY_BASIC_INFORMATION));
VirtualProtect(mbi_thunk.BaseAddress,mbi_thunk.RegionSize, PAGE_READWRITE, &mbi_thunk.Protect);
//保存原来的API函数指针
if(pHookApi->pOldProc == NULL)
pHookApi->pOldProc = (PROC)pRealThunk->u1.Function;
//改变API函数指针
pRealThunk->u1.Function = (PDWORD)pHookApi->pNewProc;
//将thunk保护属性改回来
DWORD dwOldProtect;
VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize,
mbi_thunk.Protect, &dwOldProtect);
}
}
pOrigThunk++;
pRealThunk++;
}
SetLastError(ERROR_SUCCESS);
return TRUE;
}

 
 
Top
 
 回复人: Jera(天~~~~塌下来我也不怕...) ( ) 信誉:100  2000-11-28 16:11:00Z  得分:0
 
 
 
太难了
 
 
Top
 
 回复人: tender_edge(温柔一刀) ( ) 信誉:100  2000-12-01 03:27:00Z  得分:0
 
 
 
>>我看见有的人已经可以做到这一点,编译后的DCU文件才3KB,谁能够给出例程?快来拿分啊!

在哪里?给我来一份这个DCU如何?
 
 
Top
 
 回复人: Kingron(单身走我路……) ( ) 信誉:117  2000-12-01 14:17:00Z  得分:0
 
 
 
2 TENDER_EDGE:
  已经G给你了。
2 all:
  我想我的问题已经快解决了,不过欢迎大家继续讨论,我有了答案之后会贴出来,然后给分。但是我的时间比较紧张,可能要过一段时间。谢谢各位!
 
 
Top
 
 回复人: bbsboyy() ( ) 信誉:100  2000-12-01 17:42:00Z  得分:0
 
 
 
密切关注!
KINGRON:研究出来DELPHI 版一定别忘了给哥们来一份.
liujw@163.net
 
 
Top
 
 回复人: alwssoan(巧克力) ( ) 信誉:100  2000-12-01 21:44:00Z  得分:0
 
 
 
谁说windows下没有中断,用内欠汇编语句。获取中断描述表即可,
实在不行发email给我吧,okinetboy@sina.com.cn
 
 
Top
 
 回复人: wr960204(武稀松) ( ) 信誉:94  2000-12-11 14:25:00Z  得分:0
 
 
 
程序员大本营CD上VC中有例子,好几个呢。我比较喜欢超级解霸II源码中的函数截获例子。还有几篇技术文章你可以参考一下。反正我对VC++不太熟悉,翻译不了。有些例子有现成的。DLL文件可用。
 
 
Top
 
 回复人: Kingron(单身走我路……) ( ) 信誉:117  2000-12-12 10:24:00Z  得分:0
 
 
 
2 wr960204:
超级解霸II源码中的函数截获例子?麻烦你贴出来好吗?我这儿没有程序员大本营的CD!
有现成的。DLL文件可用?哪儿有下载?哪儿有说明?
谢谢!
 
 
Top
 
 回复人: catthunder(男儿何不带吴钩) ( ) 信誉:100  2000-12-12 21:22:00Z  得分:0
 
 
 
纠正一下,WinApi也是系统调用,也用中断,怎么说windows下无中断?
 
 
Top
 
 回复人: GoodHope(好望角) ( ) 信誉:100  2000-12-12 21:50:00Z  得分:50
 
 
 
Windows中有中断,但是跟Dos下的一般说的中断完全不同了。Windows中已经不怎么用中断来实现通用功能了,提及中断实际上是说的硬件的中断行为,一般是由硬件触发。而Dos下往往代表的通过中断实现特定的功能,一般是软中断。catthunder说的WinApi是系统调用是对的,但WinApi绝对不是中断。保护模式下,中断是用过中断门进入的,不是普通的函数调用。
 
 
Top
 
 回复人: WorldCreater(造化天尊) ( ) 信誉:99  2000-12-12 23:09:00Z  得分:0
 
 
 
研究结果出来后给我一份好吗
primary@email.com.cn
 
 
Top
 
 回复人: GoodHope(好望角) ( ) 信誉:100  2000-12-13 10:58:00Z  得分:50
 
 
 
to Jujus:
  你给出代码中缺一个回车。
return FALSE; //需要改换的API不能取到正确描PIMAGE_THUNK_DATA pOrigThunk = (PIMAGE_THUNK_DATA)((DWORD)hModule + (DWORD)(pImportDesc->OriginalFirstThunk));
应该是
return FALSE; //需要改换的API不能取到正确描
PIMAGE_THUNK_DATA pOrigThunk = (PIMAGE_THUNK_DATA)((DWORD)hModule + (DWORD)(pImportDesc->OriginalFirstThunk));

另外IMAGE_IMPORT_DESCRIPTOR在WinNT.h中定义,声明如下:

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;            // 0 for terminating null import descriptor
        DWORD   OriginalFirstThunk;         // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
    };
    DWORD   TimeDateStamp;                  // 0 if not bound,
                                            // -1 if bound, and real date/time stamp
                                            //     in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
                                            // O.W. date/time stamp of DLL bound to (Old BIND)

    DWORD   ForwarderChain;                 // -1 if no forwarders
    DWORD   Name;
    DWORD   FirstThunk;                     // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

我正在研究Delphi该怎么写。
 
 
Top
 
 回复人: Kingron(单身走我路……) ( ) 信誉:117  2000-12-13 15:34:00Z  得分:0
 
 
 
2 goodhope:
  多谢上次的帮忙,这次恐怕又要麻烦你了。写出来的Delphi版给我一份好吗?谢谢了。
 
 
Top
 
 回复人: jimusi() ( ) 信誉:100  2000-12-13 15:56:00Z  得分:0
 
 
 
欢迎到“软件开发者”——“Visual C++”——“严肃讨论...”参加讨论
 
 
Top
 
 回复人: GoodHope(好望角) ( ) 信誉:100  2000-12-13 16:21:00Z  得分:0
 
 
 
to: Kingron
  上次?我很少看名字的:)
 
 
Top
 
 回复人: wxy_2000(风月无痕) ( ) 信誉:100  2000-12-13 16:56:00Z  得分:0
 
 
 
to: goodhope
我敬佩你,请继续努力,研究出来了别忘了给老兄一份。
wxy_2000@263.net
 
 
Top
 
 回复人: tchaikov() ( ) 信誉:100  2000-12-13 17:01:00Z  得分:0
 
 
 
lu0.126.com 上好象有这方面的专题,去看看吧。
 
 
Top
 
 回复人: GoodHope(好望角) ( ) 信誉:100  2000-12-13 17:20:00Z  得分:0
 
 
 
哦,Ring0啊。:)
 
 
Top
 
 回复人: gypb(网螺) ( ) 信誉:99  2000-12-13 17:45:00Z  得分:0
 
 
 
给我发一个
1stchina@elong.com
 
 
Top
 
 回复人: GoodHope(好望角) ( ) 信誉:100  2000-12-15 13:44:00Z  得分:50
 
 
 
// 我只是照着翻译了一下,可以编译通过。代码本身不全,所以没有进行任何的调试。
// 如果在格式对齐上有问题,请把Tab设为2。

unit x;

// 如何截获API函数(转)
// 该程序是基于HOOK原理,主要是将自己的函数放到目标PROCESS的地址空间,这里是使用HOOK实现.
// 首先建立一个MOUSE的HOOK程序,然后在全局鼠标HOOK的DLL中做截获动作,
// 可以在PROCESS_ATTACH时做,也可以在鼠标的HOOK链函数中做.
// 建立全局HOOK就不说了,可以在网上很多地方看到.主要是截获动作.
// 通过PE格式(使用IMAGE)改变API函数在调用时的地址.DLL部分参考如下代码:

interface

const
 IMAGE_ORDINAL_FLAG64 = $8000000000000000;
 IMAGE_ORDINAL_FLAG32 = $80000000;
{$IFDEF _WIN64}
 IMAGE_ORDINAL_FLAG = IMAGE_ORDINAL_FLAG64;
{$ELSE}
 IMAGE_ORDINAL_FLAG = IMAGE_ORDINAL_FLAG32;
{$ENDIF}


type
 // 我定义了一个结构
 tag_HOOKAPI = record
  szFunc: LPCSTR;//待HOOK的API函数名
  pNewProc: FARPROC;//新的函数指针
  pOldProc: FARPROC;//老的函数指针
 end;
 HOOKAPI = tag_HOOKAPI;
 LPHOOKAPI = ^HOOKAPI;
 THookAPI = tag_HOOKAPI;

 LONG = Longint;
 ULONGLONG = Int64;
 PSTR = LPSTR;
 PVOID = Pointer;

 // WinNT.h Begin;
{$ALIGN OFF}

{$IFDEF _WIN64}
 ULONG_PTR = ULONGLONG;
 PULONG_PTR = ^ULONG_PTR;
{$ELSE}
 ULONG_PTR = LONG;
 PULONG_PTR = ^ULONG_PTR;
{$ENDIF}
 
 SIZE_T = ULONG_PTR;
 PSIZE_T = ^SIZE_T;

 tag_IMAGE_IMPORT_DESCRIPTOR = record
  case Boolean of
  False: (Characteristics: DWORD);    // 0 for terminating null import descriptor
  True:  (OriginalFirstThunk: DWORD; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
      TimeDateStamp: DWORD;       // 0 if not bound,
                    // -1 if bound, and real date/time stamp
                    //     in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
                    // O.W. date/time stamp of DLL bound to (Old BIND)

      ForwarderChain: DWORD;      // -1 if no forwarders
      Name: DWORD;
      FirstThunk: DWORD;          // RVA to IAT (if bound this IAT has actual addresses)
  );
 end;
 IMAGE_IMPORT_DESCRIPTOR = tag_IMAGE_IMPORT_DESCRIPTOR;
 PIMAGE_IMPORT_DESCRIPTOR = ^IMAGE_IMPORT_DESCRIPTOR;

 tag_IMAGE_DOS_HEADER = record  // DOS .EXE header
  e_magic:  WORD;             // Magic number
  e_cblp:   WORD;             // Bytes on last page of file
  e_cp:    WORD;             // Pages in file
  e_crlc:   WORD;             // Relocations
  e_cparhdr: WORD;             // Size of header in paragraphs
  e_minalloc: WORD;             // Minimum extra paragraphs needed
  e_maxalloc: WORD;             // Maximum extra paragraphs needed
  e_ss:    WORD;             // Initial (relative) SS value
  e_sp:    WORD;             // Initial SP value
  e_csum:   WORD;             // Checksum
  e_ip:    WORD;             // Initial IP value
  e_cs:    WORD;             // Initial (relative) CS value
  e_lfarlc:  WORD;             // File address of relocation table
  e_ovno:   WORD;             // Overlay number
  e_res:array[0..4-1]of
        WORD;             // Reserved words
  e_oemid:  WORD;             // OEM identifier (for e_oeminfo)
  e_oeminfo: WORD;             // OEM information; e_oemid specific
  e_res2:array[0..10-1]of
        WORD;             // Reserved words
  e_lfanew:  LONG;             // File address of new exe header
 end;
 IMAGE_DOS_HEADER = tag_IMAGE_DOS_HEADER;
 PIMAGE_DOS_HEADER = ^IMAGE_DOS_HEADER;

 tag_IMAGE_FILE_HEADER  = record
  Machine:       WORD;
  NumberOfSections:   WORD;
  TimeDateStamp:    DWORD;
  PointerToSymbolTable: DWORD;
  NumberOfSymbols:   DWORD;
  SizeOfOptionalHeader: WORD;
  Characteristics:   WORD;
 end;
 IMAGE_FILE_HEADER = tag_IMAGE_FILE_HEADER;
 PIMAGE_FILE_HEADER = ^IMAGE_FILE_HEADER;

 tag_IMAGE_DATA_DIRECTORY = record
  VirtualAddress: DWORD;
  Size:      DWORD;
 end;
 IMAGE_DATA_DIRECTORY = tag_IMAGE_DATA_DIRECTORY;
 PIMAGE_DATA_DIRECTORY = ^IMAGE_DATA_DIRECTORY;

 tag_IMAGE_OPTIONAL_HEADER64 = record
  Magic: WORD;
  MajorLinkerVersion: BYTE;
  MinorLinkerVersion: BYTE;
  SizeOfCode: DWORD;
  SizeOfInitializedData: DWORD;
  SizeOfUninitializedData: DWORD;
  AddressOfEntryPoint: DWORD;
  BaseOfCode: DWORD;
  ImageBase: ULONGLONG;
  SectionAlignment: DWORD;
  FileAlignment: DWORD;
  MajorOperatingSystemVersion: WORD;
  MinorOperatingSystemVersion: WORD;
  MajorImageVersion: WORD;
  MinorImageVersion: WORD;
  MajorSubsystemVersion: WORD;
  MinorSubsystemVersion: WORD;
  Win32VersionValue: DWORD;
  SizeOfImage: DWORD;
  SizeOfHeaders: DWORD;
  CheckSum: DWORD;
  Subsystem: WORD;
  DllCharacteristics: WORD;
  SizeOfStackReserve: ULONGLONG;
  SizeOfStackCommit: ULONGLONG;
  SizeOfHeapReserve:  ULONGLONG;
  SizeOfHeapCommit: ULONGLONG;
  LoaderFlags: DWORD;
  NumberOfRvaAndSizes: DWORD;
  DataDirectory:
   array[0..IMAGE_NUMBEROF_DIRECTORY_ENTRIES-1]of IMAGE_DATA_DIRECTORY;
 end;
 IMAGE_OPTIONAL_HEADER64 = tag_IMAGE_OPTIONAL_HEADER64;
 PIMAGE_OPTIONAL_HEADER64 = ^IMAGE_OPTIONAL_HEADER64;

 tag_IMAGE_OPTIONAL_HEADER = record
  //
  // Standard fields.
  //

  Magic:          WORD;
  MajorLinkerVersion:    BYTE;
  MinorLinkerVersion:    BYTE;
  SizeOfCode:        DWORD;
  SizeOfInitializedData:  DWORD;
  SizeOfUninitializedData: DWORD;
  AddressOfEntryPoint:   DWORD;
  BaseOfCode:        DWORD;
  BaseOfData:        DWORD;

  //
  // NT additional fields.
  //

  ImageBase:          DWORD;
  SectionAlignment:       DWORD;
  FileAlignment:        DWORD;
  MajorOperatingSystemVersion: WORD;
  MinorOperatingSystemVersion: WORD;
  MajorImageVersion:      WORD;
  MinorImageVersion:      WORD;
  MajorSubsystemVersion:    WORD;
  MinorSubsystemVersion:    WORD;
  Win32VersionValue:      DWORD;
  SizeOfImage:         DWORD;
  SizeOfHeaders:        DWORD;
  CheckSum:           DWORD;
  Subsystem:          WORD;
  DllCharacteristics:      WORD;
  SizeOfStackReserve:      DWORD;
  SizeOfStackCommit:      DWORD;
  SizeOfHeapReserve:      DWORD;
  SizeOfHeapCommit:       DWORD;
  LoaderFlags:         DWORD;
  NumberOfRvaAndSizes:     DWORD;
  DataDirectory:array[0..IMAGE_NUMBEROF_DIRECTORY_ENTRIES-1]of
  IMAGE_DATA_DIRECTORY;
 end;
 IMAGE_OPTIONAL_HEADER32 = tag_IMAGE_OPTIONAL_HEADER;
 PIMAGE_OPTIONAL_HEADER32 = ^IMAGE_OPTIONAL_HEADER32;

 tag_IMAGE_NT_HEADERS64 = record
  Signature: DWORD;
  FileHeader: IMAGE_FILE_HEADER;
  OptionalHeader: IMAGE_OPTIONAL_HEADER64;
 end;
 IMAGE_NT_HEADERS64 = tag_IMAGE_NT_HEADERS64;
 PIMAGE_NT_HEADERS64 = IMAGE_NT_HEADERS64;

 tag_IMAGE_NT_HEADERS = record
  Signature: DWORD;
  FileHeader: IMAGE_FILE_HEADER;
  OptionalHeader: IMAGE_OPTIONAL_HEADER32;
 end;
 IMAGE_NT_HEADERS32 = tag_IMAGE_NT_HEADERS;
 PIMAGE_NT_HEADERS32 = ^IMAGE_NT_HEADERS32;


{$IFDEF _WIN64}
 IMAGE_NT_HEADERS = IMAGE_NT_HEADERS64;
 PIMAGE_NT_HEADERS = PIMAGE_NT_HEADERS64;
// IMAGE_FIRST_SECTION64(ntheader) IMAGE_FIRST_SECTION(ntheader)
{$ELSE}
 IMAGE_NT_HEADERS = IMAGE_NT_HEADERS32;
 PIMAGE_NT_HEADERS = PIMAGE_NT_HEADERS32;
//IMAGE_FIRST_SECTION32(ntheader) = IMAGE_FIRST_SECTION(ntheader);
{$ENDIF}

 tag_IMAGE_IMPORT_BY_NAME = record
  Hint: WORD;
  Name: array[0..0]of BYTE;
 end;
 IMAGE_IMPORT_BY_NAME = tag_IMAGE_IMPORT_BY_NAME;
 PIMAGE_IMPORT_BY_NAME = ^IMAGE_IMPORT_BY_NAME;

 tag_U1_64 = record
  case BYTE of
  1: (ForwarderString: PBYTE;);
  2: (Function_: PDWORD;);
  3: (Ordinal: ULONGLONG;);
  4: (AddressOfData: PIMAGE_IMPORT_BY_NAME;);
 end;
 
 tag_IMAGE_THUNK_DATA64 = record
  u1: tag_U1_64;
 end;
 IMAGE_THUNK_DATA64 = tag_IMAGE_THUNK_DATA64;
 PIMAGE_THUNK_DATA64 = ^IMAGE_THUNK_DATA64;

 tag_U1_32 = record
  case BYTE of
  1: (ForwarderString: PBYTE; );
  2: (Function_: PDWORD);
  3: (Ordinal: DWORD);
  4: (AddressOfData: PIMAGE_IMPORT_BY_NAME);
 end;

 tag_IMAGE_THUNK_DATA32 = record
  u1: tag_U1_32;
 end;

 IMAGE_THUNK_DATA32 = tag_IMAGE_THUNK_DATA32;

 PIMAGE_THUNK_DATA32 = ^IMAGE_THUNK_DATA32;


{$IFDEF _WIN64}
 // IMAGE_ORDINAL64(Ordinal) = IMAGE_ORDINAL(Ordinal)
 IMAGE_THUNK_DATA = IMAGE_THUNK_DATA64;
 PIMAGE_THUNK_DATA = PIMAGE_THUNK_DATA64;
 // IMAGE_SNAP_BY_ORDINAL64(Ordinal)IMAGE_SNAP_BY_ORDINAL(Ordinal)
 //IMAGE_TLS_DIRECTORY = IMAGE_TLS_DIRECTORY64;
 //PIMAGE_TLS_DIRECTORY = PIMAGE_TLS_DIRECTORY64
{$ELSE}
 // IMAGE_ORDINAL32(Ordinal) =IMAGE_ORDINAL(Ordinal)
 IMAGE_THUNK_DATA = IMAGE_THUNK_DATA32;
 PIMAGE_THUNK_DATA = PIMAGE_THUNK_DATA32;
 // IMAGE_SNAP_BY_ORDINAL32(Ordinal) = IMAGE_SNAP_BY_ORDINAL(Ordinal)
 //IMAGE_TLS_DIRECTORY = IMAGE_TLS_DIRECTORY32
 //PIMAGE_TLS_DIRECTORY = PIMAGE_TLS_DIRECTORY32
{$ENDIF}

{$ALIGN ON}

function GetNamedImportDescriptor(
 hModule: HMODULE;
 szImportMod: LPCSTR): PIMAGE_IMPORT_DESCRIPTOR; stdcall;
function HookAPIByName(
 hModule: HMODULE;    // 被HOOK的目标进程MODULE
 szImportMod: LPCSTR;  // 如GDI32.DLL
 pHookApi: LPHOOKAPI   // 指定函数名,如'MessageBoxW'
): Boolean; stdcall

implementation

//自己的MessageBoxW函数

function MyMessageBoxW(hWnd: HWND;
 lpText: LPCWSTR; lpCaption: LPCWSTR; uType: UINT): Integer; stdcall;
begin
 Result := MessageBox(hWnd, 'TNT'{lpText}, 'TNT'{lpCaption}, uType);
end;

function GetNamedImportDescriptor(
 hModule: HMODULE;
 szImportMod: LPCSTR): PIMAGE_IMPORT_DESCRIPTOR;
var
 pDOSHeader: PIMAGE_DOS_HEADER;
 pNTHeader: PIMAGE_NT_HEADERS;
 pImageDataDirectory: PIMAGE_DATA_DIRECTORY;
 pImportDesc: PIMAGE_IMPORT_DESCRIPTOR;
 szCurrMod: PSTR;
begin
 Result := nil;
 //首先是DOS头
 pDOSHeader := PIMAGE_DOS_HEADER(hModule);

 if pDOSHeader.e_magic <> IMAGE_DOS_SIGNATURE then Exit;

 pNTHeader :=
    PIMAGE_NT_HEADERS(DWORD(pDOSHeader) + DWORD(pDOSHeader.e_lfanew));

 if pNTHeader.Signature <> IMAGE_NT_SIGNATURE then Exit;

 //如果没有Import部分,返回失败
 pImageDataDirectory :=
  @pNTHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];

 if (pImageDataDirectory.VirtualAddress = 0) then Exit;

 //取Import部分
 pImportDesc := PIMAGE_IMPORT_DESCRIPTOR
  (DWORD(pDOSHeader) + DWORD(pImageDataDirectory.VirtualAddress));

 //寻找与szImportMod相配部分
 while pImportDesc.Name <> 0 do
 begin
  szCurrMod := PSTR(DWORD(pDOSHeader) + DWORD(pImportDesc.Name));
  if (CompareText(szCurrMod, szImportMod) = 0) then
  Break; //找到
  Inc(pImportDesc);
 end;
 if pImportDesc.Name <> 0 then
  Result := pImportDesc;
end;

function HookAPIByName(
 hModule: HMODULE;
 szImportMod: LPCSTR;
 pHookApi: LPHOOKAPI
): Boolean;
var
 pImportDesc: PIMAGE_IMPORT_DESCRIPTOR;
 pOrigThunk: PIMAGE_THUNK_DATA;
 pRealThunk: PIMAGE_THUNK_DATA;
 pByName: PIMAGE_IMPORT_BY_NAME;
 mbi_thunk: TMemoryBasicInformation;
 dwOldProtect: DWORD;
begin
 Result := False;
 pImportDesc :=
  GetNamedImportDescriptor(hModule, szImportMod);

 //需要改换的API不能取到正确描
 pOrigThunk :=
  PIMAGE_THUNK_DATA(DWORD(hModule) + DWORD(pImportDesc.OriginalFirstThunk));
 if pImportDesc = nil then Exit;
 pRealThunk :=
  PIMAGE_THUNK_DATA(DWORD(hModule) + DWORD(pImportDesc.FirstThunk));

 while pOrigThunk.u1.Function_ <> nil do
 begin
  if(pOrigThunk.u1.Ordinal and IMAGE_ORDINAL_FLAG) <> IMAGE_ORDINAL_FLAG then
  begin
   pByName :=
    PIMAGE_IMPORT_BY_NAME(DWORD(hModule) + DWORD(pOrigThunk.u1.AddressOfData));
   if pByName.Name[0] = 0 then Exit; //失败
   if CompareText(pHookApi.szFunc, PChar(@pByName.Name[0])) = 0 then
   begin
    //改变thunk保护属性
    VirtualQuery (
     pRealThunk,
     mbi_thunk,
     SizeOf(MEMORY_BASIC_INFORMATION));
    VirtualProtect(
     mbi_thunk.BaseAddress,
     mbi_thunk.RegionSize,
     PAGE_READWRITE,
     @mbi_thunk.Protect);

    //保存原来的API函数指针
    if pHookApi.pOldProc = nil then
     pHookApi.pOldProc := FARPROC(pRealThunk.u1.Function_);

    //改变API函数指针
    pRealThunk.u1.Function_ := PDWORD(pHookApi.pNewProc);

    //将thunk保护属性改回来
    VirtualProtect(
     mbi_thunk.BaseAddress,
     mbi_thunk.RegionSize,
     mbi_thunk.Protect,
     @dwOldProtect);
   end;
  end;
  Inc(pOrigThunk);
  Inc(pRealThunk);
 end;
 SetLastError(ERROR_SUCCESS);
 Result := True;
end;


 
 
Top
 
 回复人: GoodHope(好望角) ( ) 信誉:100  2000-12-15 13:47:00Z  得分:0
 
 
 
怎么头部的TAB都被干掉了@_@。唉,那就慢慢看把。
 
 
Top
 
 回复人: Kingron(单身走我路……) ( ) 信誉:117  2000-12-15 14:23:00Z  得分:0
 
 
 
我看看,谢谢了............
一定给分的!
 
 
Top
 
 回复人: GoodHope(好望角) ( ) 信誉:100  2000-12-15 14:27:00Z  得分:0
 
 
 
调试一把,当场出错:(
if pByName.Name[0] = 0 then Exit; //失败
一句内存访问违背,应该是前面生成pByName有问题。

路漫漫兮其修远
 
 
Top
 
 回复人: Kingron(单身走我路……) ( ) 信誉:117  2000-12-17 10:42:00Z  得分:0
 
 
 
佩服!我还等一等!
 
 
Top
 
 回复人: sorry2000(方言) ( ) 信誉:96  2000-12-17 11:04:00Z  得分:40
 
 
 
我看到一篇文章:
此处引用一下
意思相当明了:
AllocCStoDSAlias//这个函数我以前没有见过;不只那位仁兄能指点一二:
"陷阱"技术探秘
----动态汉化Windows技术的分析
华光科技股份公司研究开发中心 张高峰
 
摘 要: 四通利方(RichWin),中文之星(CStar)是大家广为熟知的汉化Windows产品,
"陷阱”技术即动态修改Windows代码,一直是其对外宣称的过人技术,它究竟是如
何实现的,这自然是核心机密。本文试图解开这个秘密,并同时介绍Windows的模块
调用机制与重定位概念,并给出了采用"陷阱"技术动态修改Windows代码的示例源程
序。
关键词:汉化Windows重定位技术

一、发现了什么?
作者多年来一直从事Windows下的软件开发工作,经历
了Windows2.0、3.0、3.1,直至WindowsNT,95的成长过程,也遍历
了长青窗口、长城窗口、DBWin、CStar、RichWin等多个Windows汉化
产品。从现在看来,影响最大也最为成功的,当推四通利方
的RichWin,此外,中文之星CStar与RichWin师出一门,其核心技术
自然也差不许多。其对外宣传采用独特的“陷阱”技术动态
修改Windows代码,一直是作者感兴趣的地方。

EXEHDR是MicrosoftVisualC++开发工具中很有用的一个程
序,它可以检查NE(New_Executable)格式文件,用它来分析RichWin
的WSENGINE.DLL或CStar的CHINESE.DLL就会发现与众不同的两点:
( 以CStar 1.20 为 例)
C:/CSTAR>exehdr chinese.dll /v
..................................
6 type offset target
BASE060aseg 2 offset 0000
PTR 047eimp GDI.GETCHARABCWIDTHS
PTR 059bimp GDI.ENUMFONTFAMILIES
PTR 0451imp DISPLAY.14( EXTTEXTOUT )
PTR 0415imp KEYBOARD.4( TOASCII )
PTR 04baimp KEYBOARD.5( ANSITOOEM )
PTR 04c9imp KEYBOARD.6( OEMTOANSI )
PTR 04d8imp KEYBOARD.134( ANSITOOEMBUFF)
PTR 05f5imp USER.430( LSTRCMP )
PTR 04e7imp KEYBOARD.135( OEMTOANSIBUFF)
PTR 0514imp USER.431( ANSIUPPER)
PTR 0523imp USER.432( ANSILOWER )
PTR 05aaimp GDI.56( CREATEFONT)
PTR 056eimp USER.433( ISCHARALPHA )
PTR 05b9imp GDI.57( CREATEFONTINDIRECT )
PTR 057dimp USER.434( ISCHARALPHANUMERIC )
PTR 049cimp USER.179( GETSYSTEMMETRICS )
PTR 0550imp USER.435( ISCHARUPPER)
PTR 055fimp USER.436( ISCHARLOWER)
PTR 0532imp USER.437( ANSIUPPERBUFF)
PTR 0541imp USER.438( ANSILOWERBUFF)
PTR 05c8imp GDI.69( DELETEOBJECT )
PTR 058cimp GDI.70( ENUMFONTS )
PTR 04abimp KERNEL.ISDBCSLEADBYTE
PTR 05d7imp GDI.82( GETOBJECT)
PTR 048dimp KERNEL.74 ( OPENFILE )
PTR 0460imp GDI.91( GETTEXTEXTENT)
PTR 05e6imp GDI.92( GETTEXTFACE)
PTR 046fimp GDI.350 ( GETCHARWIDTH )
PTR 0442imp GDI.351 ( EXTTEXTOUT )
PTR 0604imp USER.471( LSTRCMPI )
PTR 04f6imp USER.472( ANSINEXT )
PTR 0505imp USER.473( ANSIPREV )
PTR 0424imp USER.108( GETMESSAGE )
PTR 0433imp USER.109( PEEKMESSAGE)
35 relocations

*******扩号内为作者加上的对应WindowsAPI函数
第一,在数据段中,发现了重定位信息。
第二,这些重定位信息提示的函数,全都与文字显示
输出和键盘,字符串有关。也就是说汉化Windows,必须修改这些函数。
在这非常特殊的地方,隐藏着什么呢?无庸致疑,这与众不同的两点,对打开“陷阱”技术之门而言,不是金钥匙,也是敲门砖。

二、Windows的模块调用机制与重定位概念
为了深入探究“陷阱”技术,我们先来介绍Windows的模块调用机制。Windows的运行分实模式(RealMode),标准模式(StandMode)和增强模式(386EnhancedMode)三种,虽然这几种模式各不相同,但其核心模块的调用关系却是完全一致的。
主要的三个模块,有如下的关系:

KERNEL是Windows系统内核,它不依赖其它模块。

GDI是Windows图形设备接口模块,它依赖于KERNEL模块。

USER是Windows用户接口服务模块,它依赖于KERNEL,GDI模块及设备驱动程序等所有模块。
这三个模块,实际上就是Windows的三个动态连接库,在系统的存在形式如下,KERNEL有三种不同形式,Kernel.exe(实模式),Krnl286.exe(标准模式),Krnl386.
exe(386增强模式);GDI模块是Gdi.exe;USER模块是User.exe,虽然文件名都以EXE为扩展名,但它们实际都是动态连接库。

同时,几乎所有的API函数都隐藏在这三个模块中。用EXEHDR对这三个模块分析,就可列出一大堆你所熟悉的WindowsAPI函数。

以GDI模块为例,

C:/WINDOWS/SYSTEM>exehdr gdi.exe
Exports:
ord seg offset name
............
351 1923eEXTTEXTOUT exported, shared data
56 319e1CREATEFONT exported, shared data
............

至此,你已能从Windows纷繁复杂的系统中,理出一些头续来。下面,再引入一个重要概念——重定位。
一个Windows执行程序对调用API函数,或对其它动态库的调用,在程序装入内存前,都是一些不能定位的动态连接,当程序调入内存时,这些远调用都需要重新定位,重新定位的依据就是重定位表。在Windows执行程序(包括动态库)的每个段后面,通常都跟有这样一个重定位表。重定位包含调用函数所在模块,函数序列号,以及定位在模块中的位置。

例如,用EXEHDR/v分析CHINESE.DLL得到

6 type offset target
..........
PTR 0442imp GDI.351
..........

就表明,在本段的0442H偏移处,调用了GDI的第351号函数。如果在0442H处是0000:FFFF,则表示,本段内仅此一处调用了GDI.351函数,否则,表明了本段内还有一处调用此函数,调用的位置就是0442H处所指向的内容,实际上重定位表只含有引用位置的链表的链头。那么,GDI.351是一个什么函数呢?还是用EXEHDR对GDI.EXE作一分析,就可得出,在GDI的出口(Export)函数中,第351号是ExtTextOut。
这样,我们在EXEHDR这一简单而非常有用的工具帮助下,已经在Windows的浩瀚海洋中畅游了一会,下面就来掀开“陷阱”技术的神秘面纱。

三、动态汉化Windows原理
我们知道,传统的汉化Windows的方法,是要直接修改Windows的显示、输入、打印等模块代码,或用DDK直接开发“中文设备”驱动模块,这样不仅工作量浩大,而且,系统的完备性很难保证,性能上也有很多限制(早期的长青窗口就是这样),这样,只有从内核上修改Windows核心代码才是最彻底的办法。
从Windows的模块调用机制,我们可以看到,Windows实际上是由包括在KERNEL,GDI,USER等几个模块中的众多函数支撑的。那么,修改其中涉及语言文字处理的函数,使之能适应中文需要,不就能达到汉化目的了吗?因而,我们可以得出这样的结论:在自己的模块中重新编写涉及文字显示,输入的多个函数,然后,将Windows中对这些函数的引用,改向到自己的这些模块中来。

修改哪些函数才能完成汉化,这需要深入分析Windows的内部结构,但CHINESE.DLL已明确无误地告诉了我们,在其数据段的重定位表中列出的引用函数,正是CStar修改了的Windows函数!

为了验证这一思路,我们利用RichWin作一核实。

用EXEHDR分析GDI.EXE,得出ExtTextOut函数在GDI的第一代码段6139H偏移处(不同版本的Windows其所在代码段和偏移可能不一样)。然后,用HelpWalk(也是MicrosoftVisualC++开发工具中的一个)检查GDI的Code1段,6139H处前5个字节是B8FF054555,经过运行RichWin4.3forInternet后,再查看同样的地方,已改为EA08088F3D,其实反汇编就知道,这5个字节就是代表Jmp3D8F:0808,而句柄为0x3D8F的模块,用HelpWalk能观察到正是RichWin的WSENGINE.DLL的第一代码段(模块名为TEXTMAN)。而偏移0808H处B8B73D45558BEC1E,正是一个函数起始的地方,这实际上就是RichWin所重改写的ExtTextOut函数。退出RichWin后,再用HelpWalk观察GDI的Code1代码段,一切又恢复正常!这与前面的分析结论完全吻合!那么,下一个关键点就是如何动态修改Windows的函数代码,也就是汉化Windows的核心——“陷阱”技术。

四、“陷阱”技术
讨论“陷阱”技术,还要回到前面的两个发现。发现之二,已能解释为修改的Windows函数,而发现之一,却仍是一个迷。
数据段存放的是变量及常量等内容,如果这里面包含有重定位信息,那么,必定要在变量说明中将函数指针赋给一个FARPROC类型的变量,于是,在变量说明中写下:
FARPROCFarProcFunc=ExtTextOut;
果然,我自己程序的数据段中也有了重定位信息。这样,当程序调入内存中时,变量FarProcFunc已是函数ExtTextOut的地址了。

要直接修改代码段的内容,还遇到一个难题,就是代码段是不可改写的。这时,需要用到一个未公开的Windows函数AllocCStoDSAlias取得与代码段有相同基址的可写数据段别名,其函数声明为
WORDFARPASCALAllocCStoDSAlias(WORDcode_sel);
参数是代码段的句柄,返回值是可写数据段别名句柄。

Windows中函数地址是32位,高字是其模块的内存句柄,低字是函数在模块内的偏移。将得到的可写数据段别名句柄锁定,再将函数偏移处的5个字节保留下来,然后将其改为转向替代函数(用EAJmp)

*(lpStr+wOffset)=0xEA;
*(lpStr+wOffset+1)=lpFarProcReplace;

反汇编即是JmplpFarProcReplace,最后,内存解锁。
这就是我们为Windows设的“陷阱”,当所有对此函数的调用都无条件地转到我们规定的替代函数处。当程序结束之前,将保留的5字节内容再置回来,否则,系统会崩溃。

下面给出作者编写的使Windows的ExtTextOut函数落入自己函数“陷阱”的源程序。

//源程序 relocate.c
#include
#include

BOOL WINAPI MyExtTextOut(HDC hDC, int x,
int y, UINT nInt1, const RECT
FAR* lpRect,LPCSTR lpStr, UINT nInt2, int FAR* lpInt);
WORD FAR PASCAL AllocCStoDSAlias(WORD code_sel);

typedef struct tagFUNC
{
FARPROC lpFarProcReplace;//替代函数地址
FARPROC lpFarProcWindows;//Windows函数地址
BYTEbOld;//保存原函数第一字节
LONGlOld;//保存原函数接后的四字节长值
}FUNC;

FUNCFunc={MyExtTextOut,ExtTextOut};

//Windows主函数
int PASCAL WinMain(HINSTANCE
hInstance,HINSTANCE hPrevInstance,
LPSTR lpCmdLine,int nCmdShow)
{
HANDLE hMemCode;//代码段句柄
WORD hMemData;//相同基址的可写数据段别名
WORD wOffset; //函数偏移
LPSTRlpStr;
LPLONG lpLong;
char lpNotice[96];


hMemCode=HIWORD((LONG) Func.lpFarProcWindows );
wOffset=LOWORD((LONG) Func.lpFarProcWindows );

wsprintf(lpNotice,"函数所在模块句柄 0x%4xH,偏移 0x%4xH",
hMemCode,wOffset);
MessageBox(NULL,lpNotice,"提示",MB_OK);

//取与代码段有相同基址的可写数据段别名
hMemData=AllocCStoDSAlias(hMemCode);

lpStr=GlobalLock(hMemData);

lpLong=(lpStr+wOffset+1 );
//保存原函数要替换的头几个字节
Func.bOld=*(lpStr+wOffset);
Func.lOld=*lpLong;

*(lpStr+wOffset)=0xEA;
*lpLong=Func.lpFarProcReplace;
GlobalUnlock(hMemData);

MessageBox(NULL,"改为自己的函数","提示",MB_OK);

//将保留的内容改回来
hMemData=AllocCStoDSAlias(hMemCode);
lpStr=GlobalLock(hMemData);
lpLong=(lpStr+wOffset+1 );
*(lpStr+wOffset)=Func.bOld;
*lpLong=Func.lOld;
GlobalUnlock(hMemData);

MessageBox(NULL,"改回原Windows函数","提示",MB_OK);
return 1;
}

//自己的替代函数
BOOL WINAPI MyExtTextOut(HDC hDC, int x, int y, UINT nInt1,
const RECT FAR* lpRect, LPCSTR lpStr, UINT nInt2, int FAR* lpInt)
{
BYTE NameDot[96]={
0x09, 0x00, 0xfd, 0x08, 0x09, 0x08, 0x09, 0x10, 0x09, 0x20,
0x79, 0x40, 0x41, 0x04, 0x47, 0xfe, 0x41, 0x40, 0x79, 0x40,
0x09, 0x20, 0x09, 0x20, 0x09, 0x10, 0x09, 0x4e, 0x51, 0x84,
0x21, 0x00, 0x02, 0x00, 0x01, 0x04, 0xff, 0xfe, 0x00, 0x00,
0x1f, 0xf0, 0x10, 0x10, 0x10, 0x10, 0x1f, 0xf0, 0x00, 0x00,
0x7f, 0xfc, 0x40, 0x04, 0x4f, 0xe4, 0x48, 0x24, 0x48, 0x24,
0x4f, 0xe4, 0x40, 0x0c, 0x10, 0x80, 0x10, 0xfc, 0x10, 0x88,
0x11, 0x50, 0x56, 0x20, 0x54, 0xd8, 0x57, 0x06, 0x54, 0x20,
0x55, 0xfc, 0x54, 0x20, 0x55, 0xfc, 0x5c, 0x20, 0x67, 0xfe,
0x00, 0x20, 0x00, 0x20, 0x00, 0x20
};

HBITMAP hBitmap,hOldBitmap;
HDC hMemDC;
BYTE far *lpDot;
int i;

for ( i=0;i<3;i++ )
{
lpDot=(LPSTR)NameDot+i*32;
hMemDC=CreateCompatibleDC(hDC);
hBitmap=CreateBitmap(16,16,1,1,lpDot);
SetBitmapBits(hBitmap,32L,lpDot);
hOldBitmap=SelectObject(hMemDC,hBitmap);
BitBlt(hDC,x+i*16,y,16,16,hMemDC,0,0,SRCCOPY);
DeleteDC(hMemDC);
DeleteObject(hBitmap);
}

return TRUE;
}

//模块定义文件relocate.def
NAMERELOCATE
EXETYPE WINDOWS
CODEPRELOAD MOVEABLE DISCARDABLE
DATAPRELOAD MOVEABLE MULTIPLE
HEAPSIZE1024
EXPORTS

五、结束语
本文从原理上分析了称为“陷阱”技术的汉化Windows方法。要彻底汉化Windows还要涉及显示,键盘输入等诸多内容,决非一日之功。但作为对“陷阱”技术的分析,本文介绍了将任一Windows函数调用改向到自己指定函数处的通用方法,这种方法可以拓展到其它应用中,如多语种显示,不同内码制式的切换显示等。
参考文献:
AndrewSchulmanDavidMaxeyMattPietrek,《未公开的Windows核心技术》,清华大学出版社,1993年。

王志东,“Windows中文环境”,《Windows软件的应用与开发》,1993.5。
(作者地址:山东潍坊华光科技股份公司研究开发中心 张高峰 邮编261041 ) 

 
 
Top
 
 回复人: sorry2000(方言) ( ) 信誉:96  2000-12-17 11:09:00Z  得分:0
 
 
 
那位仁兄研究出来什么结果能不能给我mail,bless_2000@263.net,我这里没弄到好的
代理根本上不来
 
 
Top
 
 回复人: GoodHope(好望角) ( ) 信誉:100  2000-12-17 15:10:00Z  得分:0
 
 
 
唉,研究了半天,我说怎么看怎么像16位的带码。才发现是1993年的东西。这些东西跟32位差距还是很大的。
 
 
Top
 
 回复人: stonejhao(navystone) ( ) 信誉:100  2000-12-17 15:43:00Z  得分:0
 
 
 
看看Microsoft 的Detour软件包是否符合你的要求!
 
 
Top
 
 回复人: GoodHope(好望角) ( ) 信誉:100  2000-12-17 15:48:00Z  得分: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:yeedong@163.net


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: yanshg@263.net


下面是一个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.",切记,否则会系统崩溃。

有什么不明白的可以给我写信
yeedong@163.net


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可能不同,连物理地址都是不同的。

yeedong@163.net


 
 
Top
 
 回复人: GoodHope(好望角) ( ) 信誉:100  2000-12-17 16:11:00Z  得分:0
 
 
 
想来想去。还是学病毒最简单。将Windows的DLL改名,自己的做一个同名的,调用Windows本身的DLL实现所有API,不就截获了?嘿嘿,就是工作量大了点。但是一劳永逸啊,什么API都栏截了,多好。
 
 
Top
 
 回复人: Kingron(单身走我路……) ( ) 信誉:117  2000-12-17 16:40:00Z  得分:0
 
 
 
感谢大家热情参与讨论,要是那位高手封装一个单元出来或者封装一两个函数就好了,继续加分!

 
 
Top
 
 回复人: Kingron(单身走我路……) ( ) 信誉:117  2000-12-17 16:45:00Z  得分:0
 
 
 
最多只能给300'了。有没有谁愿意封装两个函数或者封装成一个类?我再开一个贴子送分!
 
 
Top
 
 回复人: GoodHope(好望角) ( ) 信誉:100  2000-12-17 17:21:00Z  得分:0
 
 
 
找到一个93年的API的Hook例子,运行了一下,似乎可以。不知道最终是否有效果。贴出来先,共同探讨。

// GPA.C源文件

/*
  GPA.C - Copyright (c) 1993 James M. Finnegan, All Rights Reserved
*/
#include <windows.h>
#include "gpa.h"
#include "prochook.h"
#include "goodies.h"

// Global stuff
HANDLE hInst;
HWND   ghWnd;
// Magic cookie for GetProcAddress hook
NPHOOKCHILD npHookChild;


int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpszCmdLine,
           int nCmdShow)
{
    static char szAppName[]="gpa";
    HWND        hWnd;
    MSG         msg;
    WNDCLASS    wndclass;

   
    hInst=hInstance;
   
    if(!hPrevInstance)
    {
        wndclass.style         = CS_HREDRAW | CS_VREDRAW;
        wndclass.lpfnWndProc   = WndProc;
        wndclass.cbClsExtra    = 0;
        wndclass.cbWndExtra    = DLGWINDOWEXTRA;
        wndclass.hInstance     = hInstance;
        wndclass.hIcon         = LoadIcon(hInstance,szAppName);
        wndclass.hCursor       = LoadCursor(NULL, IDC_ARROW);
        wndclass.hbrBackground = COLOR_WINDOW + 1;
        wndclass.lpszMenuName  = NULL;
        wndclass.lpszClassName = szAppName;

        if(!RegisterClass(&wndclass))
            return -1;
    }

    if((hWnd=CreateDialog(hInstance,szAppName,0,NULL)) == NULL)
        return -1;

    // Make HWND global
    ghWnd=hWnd;
   
    ShowWindow(hWnd,nCmdShow);

    while(GetMessage(&msg,NULL,0,0))
    {
        if((!IsWindow(hWnd)) ||
           (!IsDialogMessage(hWnd,&msg)))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return msg.wParam;
}


long WINAPI WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{
    FARPROC lpfnNewGetPA;  // Pointer for MakeProcInstance()
    int iTabStop[4];       // Tabs for the list box
                           
                           
    switch(message)
    {
        case WM_CREATE:
            //Center the window on the screen
            CenterWindow(hWnd);
           
            // Set up the hook to our new function
            lpfnNewGetPA=MakeProcInstance((FARPROC)NewGetProcAddress,hInst);
            npHookChild=SetProcAddress((FARPROC)GetProcAddress,lpfnNewGetPA,FALSE);
           
            // Post a message to set the tabs in the list box
            PostMessage(hWnd,WM_SETLBTABS,0,0L);
            break;
       
        case WM_SETLBTABS:
            iTabStop[0]=28;
            iTabStop[1]=83;
            iTabStop[2]=127;
            iTabStop[3]=146;
            SendDlgItemMessage(hWnd,IDL_MAINBOX,LB_SETTABSTOPS,4,(LONG)(LPINT)iTabStop);

            SetFocus(GetDlgItem(hWnd,IDB_CLEAR));
            break;
           
        // Processing for the listbox buttons...
        case WM_COMMAND:
            switch(wParam)
            {
                case IDB_CLEAR:
                    SendDlgItemMessage(hWnd,IDL_MAINBOX,LB_RESETCONTENT,0,0L);
                    break;

                case IDB_EXIT:
                    PostMessage(hWnd,WM_CLOSE,0,0L);
                    break;
            }
            break;
           
        case WM_DESTROY:
            // Delete the reference to the hook
            SetProcRelease(npHookChild);
            PostQuitMessage(0);
            break;

        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
            break;
    }
    return 0L;
}
           
/*
 All the stuff below is called only when GetProcAddress() is called from
 another application!
*/

FARPROC __export WINAPI NewGetProcAddress(HINSTANCE hInst,LPCSTR lpszExp)
{
    FARPROC     i;
    static char szItem[100];
    static char szResult[5];
    HTASK       wCurTask;
    HMODULE     wModHandle=NULL;
   
   
    // Unhook our reference
    ProcUnhook(npHookChild);
    // Reissue the call to GetProcAddress
    i=GetProcAddress(hInst,lpszExp);
    // Rehook our reference
    ProcHook(npHookChild);

    // Get the caller's task handle
    wCurTask=GetCurrentTask();
   
    // Get the module handle of the referenced library.
    // Quasi-documented... pass hInst instead of a string as a parameter!
    if(hInst != NULL)
        wModHandle=GetModuleHandle((LPCSTR)hInst);

    // Assign some readable text to the result of the call to GetProcAddress
    if(i == NULL)
        lstrcpy(szResult,"Fail");
    else
        lstrcpy(szResult,"OK");
  
    // If the caller passed in an ordinal instead of a string, print the
    // ordinal and search for the string in the EXE header
    if(HIWORD(lpszExp) == 0)
    {   
        wsprintf(szItem,"%s/t%-8.8s/t%-8.8s/t(%03d)/t%s",(LPSTR)szResult,
                                                         GetTaskName(wCurTask),
                                                         GetModuleName(wModHandle),
                                                         LOWORD(lpszExp),
                                                         GetNameFromOrd(wModHandle,(WORD)LOWORD(lpszExp)));
    }
    // The caller used the "straight and narrow".  Just print the
    // parameters and the result.
    else
    {   
        wsprintf(szItem,"%s/t%-8.8s/t%-8.8s/t/t%s",(LPSTR)szResult,
                                                   GetTaskName(wCurTask),
                                                   GetModuleName(wModHandle),
                                                   lpszExp);
    }
  
    // Put the string in the listbox.
    SendDlgItemMessage(ghWnd,IDL_MAINBOX,LB_ADDSTRING,0,(LONG)(LPSTR)szItem);
     
    // Return the value returned from the call to GetProcAddress to
    // make us look transparent!
    return i;
}    

// ProcHook 头文件
/*
  PROCHOOK.H - Copyright (c) 1993 James M. Finnegan, All Rights Reserved
*/

// ProcHook Header.  Used by the ProcHook DLL source files, as well
// as any app using the DLL.

#ifndef _INC_PROCHOOK
#define _INC_PROCHOOK

#ifdef __cplusplus
extern "C" {            // No mangling!
#endif

#include <windows.h>

// Byte pack structures
#pragma pack(1)

typedef struct tagHOOKCHILD
{
    WORD                 wSig;         // Signature
    struct  tagHOOKCHILD near *prev;   // Ptr to previous hook
    struct  tagHOOKCHILD near *next;   // Ptr to next hook
    VOID            near *npHookMaster;// Ptr to master structure
    BYTE                 cBytes[5];    // Bytes of hooked function
    FARPROC              lpfnNewFunc;  // Ptr to New function
}HOOKCHILD;               

typedef HOOKCHILD            *PHOOKCHILD;
typedef HOOKCHILD NEAR       *NPHOOKCHILD;
typedef HOOKCHILD FAR        *LPHOOKCHILD;

typedef struct tagHOOKMASTER
{
    WORD                 wSig;         // Signature
    struct tagHOOKMASTER near *prev;   // Ptr to previous master
    struct tagHOOKMASTER near *next;   // Ptr to next master
    NPHOOKCHILD          head;         // Ptr to head of child struct
    NPHOOKCHILD          tail;         // Ptr to tail of child struct
    BOOL                 bExclusive;   // Exclusive Hook field
    WORD                 wCount;       // Count of # of hooks to this function
    WORD                 wUnhookCount; // Count of # of unhooks to this function
    WORD                 wDataSel;     // Selector to aliased code selector
    FARPROC              lpfnHookedFunction;// Ptr to original function
}HOOKMASTER;

typedef HOOKMASTER            *PHOOKMASTER;
typedef HOOKMASTER NEAR       *NPHOOKMASTER;
typedef HOOKMASTER FAR        *LPHOOKMASTER;


NPHOOKCHILD  WINAPI SetProcAddress (FARPROC, FARPROC, BOOL);
BOOL         WINAPI SetProcRelease (NPHOOKCHILD);
BOOL         WINAPI ProcHook       (NPHOOKCHILD);
BOOL         WINAPI ProcUnhook     (NPHOOKCHILD);

NPHOOKMASTER WINAPI GetFirstHMaster(LPHOOKMASTER);
NPHOOKMASTER WINAPI GetNextHMaster (LPHOOKMASTER);
NPHOOKCHILD  WINAPI GetFirstHChild (LPHOOKMASTER, LPHOOKCHILD);
NPHOOKCHILD  WINAPI GetNextHChild  (LPHOOKCHILD);

WORD         WINAPI GetOrdFromAddr (HMODULE, FARPROC);
LPSTR        WINAPI GetNameFromOrd (HMODULE, WORD);


// Set packing back to its default
#pragma pack()

#ifdef __cplusplus
}                       // End of extern "C" {
#endif

#endif  // _INC_PROCHOOK

// Goodies头文件

/*
  GOODIES.H - Copyright (c) 1993 James M. Finnegan, All Rights Reserved
*/

// Prototypes!
VOID    WINAPI CenterWindow(HWND);

LPSTR   WINAPI GetModuleName(HMODULE);
LPSTR   WINAPI GetTaskName(HTASK);
HMODULE WINAPI GetTaskHandle(LPSTR);
 
 
Top
 
 回复人: GoodHope(好望角) ( ) 信誉:100  2000-12-17 17:24:00Z  得分:0
 
 
 
贴漏了几个文件

// GPA头文件。
/*
  GPA.H - Copyright (c) 1993 James M. Finnegan, All Rights Reserved
*/

// Definitions for dialog box stuff
#define   IDL_MAINBOX 100
#define   IDB_CLEAR   200
#define   IDB_EXIT    201

// Our message to set the tabs in the listbox
#define WM_SETLBTABS  WM_USER+100

// Declarations
long    WINAPI WndProc(HWND, UINT, WPARAM, LPARAM);
FARPROC WINAPI NewGetProcAddress(HINSTANCE, LPCSTR);

// GPA Make文件
# Microsoft Visual C++ generated build script - Do not modify

PROJ = GPA
DEBUG = 0
PROGTYPE = 0
CALLER =
ARGS =
DLLS =
D_RCDEFINES = /d_DEBUG
R_RCDEFINES = /dNDEBUG
ORIGIN = MSVC
ORIGIN_VER = 1.00
PROJPATH = C:/SOURCES/SYSTEM/PROCHOOK/GPAUSEMFC = 0
CC = cl
CPP = cl
CXX = cl
CCREATEPCHFLAG =
CPPCREATEPCHFLAG =
CUSEPCHFLAG =
CPPUSEPCHFLAG =
FIRSTC = GPA.C      
FIRSTCPP =            
RC = rc
CFLAGS_D_WEXE = /nologo /G2 /W3 /Zi /Od /D "_DEBUG" /Gw /Fd"GPA.PDB"
CFLAGS_R_WEXE = /nologo /Gs /G2 /Zp1 /W3 /Gf /O1 /D "WINVER"="0x300" /D "NDEBUG" /Gw
LFLAGS_D_WEXE = /NOLOGO /NOD /PACKC:61440 /ALIGN:16 /ONERROR:NOEXE /CO
LFLAGS_R_WEXE = /NOLOGO /NOD /PACKC:61440 /ALIGN:16 /ONERROR:NOEXE
LIBS_D_WEXE = oldnames libw slibcew
LIBS_R_WEXE = oldnames libw slibcew
RCFLAGS = /nologo
RESFLAGS = /nologo /30
RUNFLAGS =
DEFFILE = GPA.DEF
OBJS_EXT =
LIBS_EXT = ../PROCHOOK.LIB ../GOODIES/GOODIES.LIB
!if "$(DEBUG)" == "1"
CFLAGS = $(CFLAGS_D_WEXE)
LFLAGS = $(LFLAGS_D_WEXE)
LIBS = $(LIBS_D_WEXE)
MAPFILE = nul
RCDEFINES = $(D_RCDEFINES)
!else
CFLAGS = $(CFLAGS_R_WEXE)
LFLAGS = $(LFLAGS_R_WEXE)
LIBS = $(LIBS_R_WEXE)
MAPFILE = nul
RCDEFINES = $(R_RCDEFINES)
!endif
!if [if exist MSVC.BND del MSVC.BND]
!endif
SBRS = GPA.SBR


PROCHOOK_DEP =

GOODIES_DEP =

GPA_DEP = c:/sources/system/prochook/gpa/gpa.h  c:/sources/system/prochook/prochook.h  c:/sources/system/prochook/goodies/goodies.h


GPA_RCDEP = c:/sources/system/prochook/gpa/gpa.h  c:/sources/system/prochook/gpa/gpa.ico


all: $(PROJ).EXE

GPA.OBJ: GPA.C $(GPA_DEP)
 $(CC) $(CFLAGS) $(CCREATEPCHFLAG) /c GPA.C

GPA.RES: GPA.RC $(GPA_RCDEP)
 $(RC) $(RCFLAGS) $(RCDEFINES) -r GPA.RC


$(PROJ).EXE:: GPA.RES

$(PROJ).EXE:: GPA.OBJ $(OBJS_EXT) $(DEFFILE)
 echo >NUL @<<$(PROJ).CRF
GPA.OBJ +
$(OBJS_EXT)
$(PROJ).EXE
$(MAPFILE)
c:/msvc/lib/+
c:/msvc/mfc/lib/+
../PROCHOOK.LIB+
../GOODIES/GOODIES.LIB+
$(LIBS)
$(DEFFILE);
<<
 link $(LFLAGS) @$(PROJ).CRF
 $(RC) $(RESFLAGS) GPA.RES $@
 @copy $(PROJ).CRF MSVC.BND

$(PROJ).EXE:: GPA.RES
 if not exist MSVC.BND  $(RC) $(RESFLAGS) GPA.RES $@

run: $(PROJ).EXE
 $(PROJ) $(RUNFLAGS)


$(PROJ).BSC: $(SBRS)
 bscmake @<<
/o$@ $(SBRS)
<<

// GPA定义文件
NAME            GPA
DESCRIPTION     'GetProcAddress Hook Application (c) 1993 James M. Finnegan'
EXETYPE         WINDOWS
STUB            'WINSTUB.EXE'
CODE            PRELOAD MOVEABLE DISCARDABLE
DATA            PRELOAD MOVEABLE MULTIPLE
HEAPSIZE        4096
STACKSIZE       8192

EXPORTS         WndProc           @1
                NewGetProcAddress @2

 
 
Top
 
 回复人: GoodHope(好望角) ( ) 信誉:100  2000-12-17 17:33:00Z  得分:0
 
 
 
还是漏了一个文件:
/*
  GOODIES.C - Copyright (c) 1993 James M. Finnegan, All Rights Reserved
*/
#include <windows.h>
#include <toolhelp.h>
#include "goodies.h"


// Snoar!
int WINAPI LibMain(HANDLE hInstance, WORD wDataSeg, WORD wHeapSize,
                       LPSTR lpszCmdLine)
{
    if(wHeapSize > 0)
        UnlockData(0);
                              
    return 1;
}


int __export WINAPI WEP(int t)
{
    return 1;
}


VOID __export WINAPI CenterWindow(HWND hWnd)
{
    RECT rect;
    WORD wWidth,
         wHeight;
        
        
    GetWindowRect(hWnd,&rect);

    wWidth =GetSystemMetrics(SM_CXSCREEN);
    wHeight=GetSystemMetrics(SM_CYSCREEN);

    MoveWindow(hWnd,(wWidth/2)   - ((rect.right -  rect.left)/2),
                    (wHeight/2)  - ((rect.bottom - rect.top) /2),
                     rect.right  -   rect.left,
            rect.bottom -   rect.top,
            FALSE);
}

/*
 The following functions are wrappers for ToolHelp functions.  The
 structures are declared as static to minimize the impact to the
 calling application's stack, as well as to permit the passing of a
 string pointer back to the caller.
*/

LPSTR __export WINAPI GetModuleName(HMODULE handle)
{
    static MODULEENTRY me;

   
    me.dwSize=sizeof(MODULEENTRY);
   
    if(ModuleFindHandle(&me,handle) == NULL)
    {
        return(GetTaskName(handle));
    }
       
    return me.szModule;
}
         

HMODULE __export WINAPI GetTaskHandle(LPSTR szName)
{
    static MODULEENTRY me;

  
    me.dwSize=sizeof(MODULEENTRY);
    if(ModuleFindName(&me,szName) == NULL)
        return NULL;
       
    return me.hModule;
}


LPSTR __export WINAPI GetTaskName(HTASK hTask)
{
    static TASKENTRY te;
   
   
    te.dwSize=sizeof(TASKENTRY);
   
    if(TaskFindHandle(&te,hTask) == NULL)
        return NULL;
       
    return te.szModule;
}

 
 
Top
 
 回复人: GoodHope(好望角) ( ) 信誉:100  2000-12-17 17:35:00Z  得分:0
 
 
 
好了,编译过去了。剩下的,大家慢慢折腾吧。
 
 
Top
 
 回复人: GoodHope(好望角) ( ) 信誉:100  2000-12-17 18:27:00Z  得分:0
 
 
 
受不了了。还差文件。
原始的压缩文件我放到goodhope.myetang.com和goodhope.go.163.com
感兴趣的自己去载。
 
 
Top
 
 回复人: KAMI(火焰上跳舞的面包) ( ) 信誉:100  2000-12-17 22:18:00Z  得分:0
 
 
 
“   private
      oldwinproc,newwinproc:pointer;
    所以程序必须调用MakeObjectInstance对象方法向该对象方法添加一个前缀,让系统将他用作函数。所有这些说明只需要两条复杂的语句:
    newwinproc:=makeobjectinstance(newwinprocedure);
    oldwinproc:=pointer(setwindowlong(clienthandle,gwl_wndproc,cardinal(newwinproc)));”
    我其实不太懂,这是摘自sybex《DELPHI 5从入门到精通》的,具体在Page272;希望对你有帮助^_^.
     

 
 
Top
 
 回复人: CoolSlob(我拿青春赌明天) ( ) 信誉:102  2000-12-17 22:44:00Z  得分:0
 
 
 
大家继续探讨!!其实你们可以这样想:自己是在为中国的软件事业做贡献!因为即使他(她)知道了这种技术,在做出个什么市场上已有的软件,其实这也很正常!你不觉得吗?!我不知道国外的高手是否会这样,但是我们国家的软件事业为什么总不如一些别的国家?你想过为什么吗?!你有没有想过我们的国家在IT方面为什么总超不过US?!有饭大家一起吃,难道你有了一碗饭,就忍心眼睁睁看着别人受饿,甚至死亡!?希望大家能理解我的意思,也希望大家不要功击我!
 
 
Top
 
 回复人: cybercake(数字蛋糕) ( ) 信誉:100  2000-12-18 05:31:00Z  得分:0
 
 
 
关注!
 
 
Top
 
 回复人: GoodHope(好望角) ( ) 信誉:100  2000-12-18 11:40:00Z  得分:0
 
 
 
KAMI的方法是勾住WinProc,而不是Windows的API。用处不大。
 
 
Top
 
 回复人: BCB(天下三分明月夜,二分无赖是扬州) ( ) 信誉:100  2000-12-18 12:34:00Z  得分:0
 
 
 
救命!
我晕倒了!

 
 
Top
 
 回复人: Kingron(单身走我路……) ( ) 信誉:117  2000-12-20 14:01:00Z  得分:0
 
 
 
答案请看:
http://x57.deja.com/threadmsg_ct.xp?AN=696933069&CONTEXT=976054538.742850612
有具体的源文件和调用的例子.
感谢大家参与讨论,本来还想要加分,可是该死的CSDN,限制了分数,而且现在哈哈,分数很紧张了,我的手头没有宽裕的分数。但是我要感谢大家的无私的奉献!为了让大家更多地从这个讨论中学到一些东西,我准备过一个星期才给分!也欢迎大家继续讨论。如果方便的话,我会把一篇翻译的关于Detours的文章贴到文档中心,对大家也许有帮助。:-)。
//Thanks All
 
 
Top
 
 回复人: daniel_zan(小丹尼) ( ) 信誉:100  2000-12-21 09:38:00Z  得分:0
 
 
 
密切关注!
KINGRON:研究出来,一定要给我来一份.
daniel_zan@163.net
 
 
Top
 
 回复人: GoodHope(好望角) ( ) 信誉:100  2000-12-21 11:04:00Z  得分:0
 
 
 
to:Kingron

2000运行不下去啊:(,是不是又是9x下的?
 
 
Top
 
 回复人: Kingron(单身走我路……) ( ) 信誉:117  2000-12-21 11:08:00Z  得分:0
 
 
 
啊,是的吧!我这儿没有2000,没试过。
哎,看样子,还有一段漫长的路要走啊............
 
 
Top
 
 回复人: largewang(成都:王珍义[老王][C#学习中断]) ( ) 信誉:99  2000-12-21 11:12:00Z  得分:0
 
 
 
.............
 
 
Top
 
 回复人: GoodHope(好望角) ( ) 信誉:100  2000-12-21 11:40:00Z  得分:0
 
 
 
to: Kingron
VC那边有一个例子,说是NT下能用。我最近很忙,没时间看,你先看看吧。有空我还是先折腾ring0吧。
 
 

 
 

           

分享一下我老师大神的人工智能教程。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值