﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-道。道。道-随笔分类-Win32</title><link>http://www.cppblog.com/eday/category/2954.html</link><description /><language>zh-cn</language><lastBuildDate>Tue, 20 May 2008 00:53:09 GMT</lastBuildDate><pubDate>Tue, 20 May 2008 00:53:09 GMT</pubDate><ttl>60</ttl><item><title>用VS2005也能制作体积很小的Win32程序(2KB - 3KB)</title><link>http://www.cppblog.com/eday/archive/2007/05/23/24707.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Wed, 23 May 2007 11:51:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/05/23/24707.html</guid><description><![CDATA[<p>话就不多说了，你一定能看懂。这里运行时库一定要指定为"多线程/MT"，否则最后Release版本的程序在其他机器上无法运行．<br>命令行 /MT&nbsp; 或 <br><img height=420 alt="" src="http://www.cppblog.com/images/cppblog_com/eday/2991/o_msvcrt.JPG" width=660 border=0><br><br>这里指定自定义入口函数名 命令行 /entry:Start&nbsp; 或<img height=420 alt="" src="http://www.cppblog.com/images/cppblog_com/eday/2991/o_vc8link.JPG" width=659 border=0><br><br><strong>//Win32控制台程序<br></strong>//----------------------------------------------<br>//stdafx.h file<br>//----------------------------------------------<br>#pragma once</p>
<p>#ifndef _WIN32_WINNT&nbsp;<br>#define _WIN32_WINNT 0x0501<br>#endif&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p>
<p>#include &lt;Windows.h&gt;<br></p>
<p>//----------------------------------------------<br>//console.cpp<br>//----------------------------------------------<br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #000000">#include&nbsp;</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">stdafx.h</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000"><br><br>HANDLE&nbsp;hStdIn;<br>HANDLE&nbsp;hStdOut;<br><br>BOOL&nbsp;__stdcall&nbsp;CtrlHandler(DWORD&nbsp;CtrlType)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(CtrlType</span><span style="COLOR: #000000">==</span><span style="COLOR: #000000">CTRL_C_EVENT&nbsp;</span><span style="COLOR: #000000">||</span><span style="COLOR: #000000">&nbsp;CtrlType</span><span style="COLOR: #000000">==</span><span style="COLOR: #000000">CTRL_BREAK_EVENT)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CloseHandle(hStdIn);<br>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;TRUE;<br>}<br><br></span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;Start()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;hStdIn&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;GetStdHandle(STD_INPUT_HANDLE);<br>&nbsp;&nbsp;&nbsp;&nbsp;hStdOut&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;GetStdHandle(STD_OUTPUT_HANDLE);<br>&nbsp;&nbsp;&nbsp;&nbsp;SetConsoleMode(hStdIn,ENABLE_LINE_INPUT</span><span style="COLOR: #000000">|</span><span style="COLOR: #000000">ENABLE_ECHO_INPUT</span><span style="COLOR: #000000">|</span><span style="COLOR: #000000">ENABLE_PROCESSED_INPUT);<br>&nbsp;&nbsp;&nbsp;&nbsp;SetConsoleCtrlHandler(CtrlHandler,TRUE);<br><br>&nbsp;&nbsp;&nbsp;&nbsp;HANDLE&nbsp;hHeap&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;GetProcessHeap();<br>&nbsp;&nbsp;&nbsp;&nbsp;PVOID&nbsp;szBuffer&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;HeapAlloc(hHeap,</span><span style="COLOR: #000000">HEAP_ZERO_MEMORY,</span><span style="COLOR: #000000">1024</span><span style="COLOR: #000000">);<br>&nbsp;&nbsp;&nbsp;&nbsp;DWORD&nbsp;dwBytesRead,dwBytesWrite;<br>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">while</span><span style="COLOR: #000000">(TRUE){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(</span><span style="COLOR: #000000">!</span><span style="COLOR: #000000">ReadConsole(hStdIn,szBuffer,</span><span style="COLOR: #000000">1024</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">dwBytesRead,NULL)&nbsp;</span><span style="COLOR: #000000">||</span><span style="COLOR: #000000">&nbsp;((</span><span style="COLOR: #0000ff">char</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">)szBuffer)[</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">]&nbsp;</span><span style="COLOR: #000000">==</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">'</span><span style="COLOR: #000000">q</span><span style="COLOR: #000000">'</span><span style="COLOR: #000000">&nbsp;)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">break</span><span style="COLOR: #000000">;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WriteConsole(hStdOut,szBuffer,dwBytesRead,</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">dwBytesWrite,NULL);<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;HeapFree(hHeap,HEAP_NO_SERIALIZE,szBuffer);<br>&nbsp;&nbsp;&nbsp;&nbsp;ExitProcess(</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">);<br>}</span></div>
<p><br><strong>//Windows程序</strong><br>//----------------------------------------------------<br>//stdafx.h<br>//----------------------------------------------------<br>#ifndef WINVER&nbsp;&nbsp;&nbsp;&nbsp;<br>#define WINVER 0x0501&nbsp;&nbsp;<br>#endif</p>
<p>#ifndef _WIN32_WINNT&nbsp;&nbsp;<br>#define _WIN32_WINNT 0x0501&nbsp;<br>#endif&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p>
<p>#ifndef _WIN32_WINDOWS&nbsp;&nbsp;<br>#define _WIN32_WINDOWS 0x0410 <br>#endif</p>
<p>#ifndef _WIN32_IE&nbsp;&nbsp;&nbsp;<br>#define _WIN32_IE 0x0600&nbsp;<br>#endif</p>
<p>#define WIN32_LEAN_AND_MEAN&nbsp;&nbsp;</p>
<p>#include &lt;Windows.h&gt;<br><br>//----------------------------------------------------<br>// winapp.cpp<br>//----------------------------------------------------<br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #000000">#include&nbsp;</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">stdafx.h</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000"><br><br></span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;__stdcall&nbsp;Start()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;MessageBoxA(NULL,</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">Hello&nbsp;World!</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">?</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">,MB_OK);<br>&nbsp;&nbsp;&nbsp;&nbsp;ExitProcess(</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">);<br>}</span></div>
<br>// Release版本<br><img height=119 alt="" src="http://www.cppblog.com/images/cppblog_com/eday/2991/o_depend1.JPG" width=441 border=0><br><br><span style="COLOR: #0000ff">//制作Win32 - DLL文件的方法与前面相同</span><br><br>以上仅使用<span style="COLOR: #ff0000">Windows标准库</span>，最后生成的程序只有 3KB大小，基本上和汇编写的程序大小差不多了．　<br>&nbsp;<br>如果要再小点，我们可以把 只读数据、导入表以及导出表节.rdata与代码节.text合并。(这里提到的节区是以VC编译器为准，不同的编译器对节的命名也许会有些不同)<br>连接器命令行添加 <font face="Courier New">/merge:.rdata=.text 或 </font><br><img height=423 alt="" src="http://www.cppblog.com/images/cppblog_com/eday/2991/o_dmerge.JPG" width=661 border=0><br><br>现在再看看大小 :(&nbsp; 2KB 了。<br>
<img src ="http://www.cppblog.com/eday/aggbug/24707.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2007-05-23 19:51 <a href="http://www.cppblog.com/eday/archive/2007/05/23/24707.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>从BMP得到ICON句柄最简单的方法</title><link>http://www.cppblog.com/eday/archive/2007/05/05/23459.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 05 May 2007 12:37:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/05/05/23459.html</guid><description><![CDATA[<br><br>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;HBITMAP&nbsp;newimg&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;(HBITMAP)LoadImage(AfxGetInstanceHandle(),_T(</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">f:\\1.bmp</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">),IMAGE_BITMAP,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,LR_LOADFROMFILE);<br>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(newimg)<br>&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CBitmap&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">pBitmap&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;CBitmap::FromHandle(newimg);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;BITMAP&nbsp;bmpData;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(pBitmap</span><span style="COLOR: #000000">-&gt;</span><span style="COLOR: #000000">GetBitmap(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">bmpData))<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ICONINFO&nbsp;iconInfo&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;{&nbsp;</span><span style="COLOR: #0000ff">true</span><span style="COLOR: #000000">,&nbsp;bmpData.bmWidth</span><span style="COLOR: #000000">/</span><span style="COLOR: #000000">2</span><span style="COLOR: #000000">,&nbsp;bmpData.bmHeight</span><span style="COLOR: #000000">/</span><span style="COLOR: #000000">2</span><span style="COLOR: #000000">,&nbsp;newimg,&nbsp;newimg&nbsp;&nbsp;};<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HICON&nbsp;bIcon&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;CreateIconIndirect(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">iconInfo);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(bIcon)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CClientDC&nbsp;dc(</span><span style="COLOR: #0000ff">this</span><span style="COLOR: #000000">);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dc.DrawIcon(</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,bIcon);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;::DestroyIcon(bIcon);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br></span></div>
<br>显示ICON仅仅为了演示， 这里主要的目的是得到 HICON 句柄。 
<img src ="http://www.cppblog.com/eday/aggbug/23459.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2007-05-05 20:37 <a href="http://www.cppblog.com/eday/archive/2007/05/05/23459.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Win32 调用系统命令取得结果</title><link>http://www.cppblog.com/eday/archive/2007/03/29/20811.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Wed, 28 Mar 2007 16:16:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/03/29/20811.html</guid><description><![CDATA[<br>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #008000">//</span> <span style="COLOR: #008000">----------------------------------------------------------------------------------<br></span><span style="COLOR: #008000">//</span> <span style="COLOR: #008000">&nbsp;Use:&gt;&nbsp;<br></span><span style="COLOR: #008000">//</span> <span style="COLOR: #008000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CString&nbsp;resultContext;<br></span><span style="COLOR: #008000">//</span> <span style="COLOR: #008000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ExecuteCmdEx(&nbsp;(LPTSTR)(LPCTSTR)CString("net&nbsp;help"),&nbsp;resultContext);<br></span><span style="COLOR: #008000">//</span> <span style="COLOR: #008000">----------------------------------------------------------------------------------</span> <span style="COLOR: #008000"><br></span><span style="COLOR: #000000">BOOL&nbsp;ExecuteCmdEx(LPTSTR&nbsp;cmdline,&nbsp;CString</span> <span style="COLOR: #000000">&amp;</span> <span style="COLOR: #000000">&nbsp;outputResult)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;SECURITY_ATTRIBUTES&nbsp;sa;<br>&nbsp;&nbsp;&nbsp;&nbsp;sa.nLength&nbsp;</span> <span style="COLOR: #000000">=</span> <span style="COLOR: #000000">&nbsp;</span> <span style="COLOR: #0000ff">sizeof</span> <span style="COLOR: #000000">(SECURITY_ATTRIBUTES);<br>&nbsp;&nbsp;&nbsp;&nbsp;sa.lpSecurityDescriptor&nbsp;</span> <span style="COLOR: #000000">=</span> <span style="COLOR: #000000">&nbsp;NULL;<br>&nbsp;&nbsp;&nbsp;&nbsp;sa.bInheritHandle&nbsp;</span> <span style="COLOR: #000000">=</span> <span style="COLOR: #000000">&nbsp;TRUE;<br>&nbsp;&nbsp;&nbsp;&nbsp;HANDLE&nbsp;hInput,hOutput;<br>&nbsp;&nbsp;&nbsp;&nbsp;</span> <span style="COLOR: #0000ff">if</span> <span style="COLOR: #000000">&nbsp;(</span> <span style="COLOR: #000000">!</span> <span style="COLOR: #000000">CreatePipe(</span> <span style="COLOR: #000000">&amp;</span> <span style="COLOR: #000000">hInput,</span> <span style="COLOR: #000000">&amp;</span> <span style="COLOR: #000000">hOutput,</span> <span style="COLOR: #000000">&amp;</span> <span style="COLOR: #000000">sa,</span> <span style="COLOR: #000000">0</span> <span style="COLOR: #000000">))&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span> <span style="COLOR: #0000ff">return</span> <span style="COLOR: #000000">&nbsp;FALSE;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;STARTUPINFO&nbsp;si;<br>&nbsp;&nbsp;&nbsp;&nbsp;PROCESS_INFORMATION&nbsp;pi;<br>&nbsp;&nbsp;&nbsp;&nbsp;ZeroMemory(&nbsp;</span> <span style="COLOR: #000000">&amp;</span> <span style="COLOR: #000000">si,&nbsp;</span> <span style="COLOR: #0000ff">sizeof</span> <span style="COLOR: #000000">(si)&nbsp;);<br>&nbsp;&nbsp;&nbsp;&nbsp;ZeroMemory(&nbsp;</span> <span style="COLOR: #000000">&amp;</span> <span style="COLOR: #000000">pi,&nbsp;</span> <span style="COLOR: #0000ff">sizeof</span> <span style="COLOR: #000000">(pi)&nbsp;);<br>&nbsp;&nbsp;&nbsp;&nbsp;si.cb&nbsp;</span> <span style="COLOR: #000000">=</span> <span style="COLOR: #000000">&nbsp;</span> <span style="COLOR: #0000ff">sizeof</span> <span style="COLOR: #000000">(si);<br>&nbsp;&nbsp;&nbsp;&nbsp;si.hStdError&nbsp;</span> <span style="COLOR: #000000">=</span> <span style="COLOR: #000000">&nbsp;hOutput;<br>&nbsp;&nbsp;&nbsp;&nbsp;si.hStdOutput&nbsp;</span> <span style="COLOR: #000000">=</span> <span style="COLOR: #000000">&nbsp;hOutput;<br>&nbsp;&nbsp;&nbsp;&nbsp;si.wShowWindow&nbsp;</span> <span style="COLOR: #000000">=</span> <span style="COLOR: #000000">&nbsp;SW_HIDE;<br>&nbsp;&nbsp;&nbsp;&nbsp;si.dwFlags&nbsp;</span> <span style="COLOR: #000000">=</span> <span style="COLOR: #000000">&nbsp;STARTF_USESHOWWINDOW&nbsp;</span> <span style="COLOR: #000000">|</span> <span style="COLOR: #000000">&nbsp;STARTF_USESTDHANDLES;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;</span> <span style="COLOR: #0000ff">if</span> <span style="COLOR: #000000">&nbsp;(</span> <span style="COLOR: #000000">!</span> <span style="COLOR: #000000">CreateProcess(NULL,cmdline,NULL,NULL,TRUE,NULL,NULL,NULL,</span> <span style="COLOR: #000000">&amp;</span> <span style="COLOR: #000000">si,</span> <span style="COLOR: #000000">&amp;</span> <span style="COLOR: #000000">pi)){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CloseHandle(hInput);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CloseHandle(hOutput);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span> <span style="COLOR: #0000ff">return</span> <span style="COLOR: #000000">&nbsp;FALSE;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;WaitForSingleObject(&nbsp;pi.hProcess,&nbsp;INFINITE&nbsp;);<br>&nbsp;&nbsp;&nbsp;&nbsp;CloseHandle(&nbsp;pi.hProcess&nbsp;);<br>&nbsp;&nbsp;&nbsp;&nbsp;CloseHandle(&nbsp;pi.hThread&nbsp;);<br><br>&nbsp;&nbsp;&nbsp;&nbsp;DWORD&nbsp;rByte&nbsp;</span> <span style="COLOR: #000000">=</span> <span style="COLOR: #000000">&nbsp;</span> <span style="COLOR: #000000">4095</span> <span style="COLOR: #000000">;<br>&nbsp;&nbsp;&nbsp;&nbsp;</span> <span style="COLOR: #0000ff">char</span> <span style="COLOR: #000000">&nbsp;outputBuffer[</span> <span style="COLOR: #000000">4096</span> <span style="COLOR: #000000">];<br>&nbsp;&nbsp;&nbsp;&nbsp;</span> <span style="COLOR: #0000ff">while</span> <span style="COLOR: #000000">(rByte</span> <span style="COLOR: #000000">==</span> <span style="COLOR: #000000">4095</span> <span style="COLOR: #000000">){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ZeroMemory(outputBuffer,</span> <span style="COLOR: #0000ff">sizeof</span> <span style="COLOR: #000000">(</span> <span style="COLOR: #0000ff">char</span> <span style="COLOR: #000000">)</span> <span style="COLOR: #000000">*</span> <span style="COLOR: #000000">4096</span> <span style="COLOR: #000000">);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ReadFile(hInput,outputBuffer,</span> <span style="COLOR: #0000ff">sizeof</span> <span style="COLOR: #000000">(</span> <span style="COLOR: #0000ff">char</span> <span style="COLOR: #000000">)</span> <span style="COLOR: #000000">*</span> <span style="COLOR: #000000">4095</span> <span style="COLOR: #000000">,</span> <span style="COLOR: #000000">&amp;</span> <span style="COLOR: #000000">rByte,&nbsp;NULL);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;outputResult&nbsp;</span> <span style="COLOR: #000000">+=</span> <span style="COLOR: #000000">&nbsp;outputBuffer;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;CloseHandle(hInput);<br>&nbsp;&nbsp;&nbsp;&nbsp;CloseHandle(hOutput);<br><br>&nbsp;&nbsp;&nbsp;&nbsp;</span> <span style="COLOR: #0000ff">return</span> <span style="COLOR: #000000">&nbsp;TRUE;<br>}</span> </div>
<img src ="http://www.cppblog.com/eday/aggbug/20811.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2007-03-29 00:16 <a href="http://www.cppblog.com/eday/archive/2007/03/29/20811.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>解决窗口刷新闪烁</title><link>http://www.cppblog.com/eday/archive/2007/03/19/20106.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sun, 18 Mar 2007 16:14:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/03/19/20106.html</guid><description><![CDATA[
		<p>一般的windows 复杂的界面需要使用多层窗口而且要用贴图来美化，所以不可避免在窗口移动或者改变大小的时候出现闪烁。</p>
		<p>先来谈谈闪烁产生的原因</p>
		<p>原因一：<br />如果熟悉显卡原理的话，调用GDI函数向屏幕输出的时候并不是立刻就显示在屏幕<br />上只是写到了显存里，而显卡每隔一段时间把显存的内容输出到屏幕上，这就是刷新周期。</p>
		<p>一般显卡的刷新周期是 1/80秒左右，具体数字可以自己设置的。</p>
		<p>这样问题就来了，一般画图都是先画背景色，然后再把内容画上去，如果这两次操作不在同一个<br />刷新周期内完成，那么给人的视觉感受就是，先看到只有背景色的图像，然后看到画上内容的图像，<br />这样就会感觉闪烁了。</p>
		<p>解决方法：尽量快的输出图像，使输出在一个刷新周期内完成，如果输出内容很多比较慢，那么采用<br />内存缓冲的方法，先把要输出的内容在内存准备好，然后一次输出到显存。要知道一次API调用一般可以<br />在一个刷新周期内完成。</p>
		<p>对于GDI，用创建内存DC的方法就可以了</p>
		<p>原因二：</p>
		<p>复杂的界面有多层窗口组成，当windows在窗口改变大小的时候是先重画父窗口，然后重画子窗口，子父<br />窗口重画的过程一般无法在一个刷新周期内完成，所以会呈现闪烁。</p>
		<p>我们知道父窗口上被子窗口挡住的部分其实没必要重画的</p>
		<p>解决方法：给窗口加个风格 WS_CLIPCHILDREN ,这样父窗口上被子窗口挡住的部分就不会重画了。</p>
		<p>如果同级窗口之间有重叠，那么需要再加上 WS_CLIPSIBLINGS 风格</p>
		<p>原因三：</p>
		<p>有时候需要在窗口上使用一些控件，比如IE，当你的窗口改变大小的时候IE会闪烁，即使你有了WS_CLIPCHILDREN<br />也没用。原因在于窗口的类风格有CS_HREDRAW 或者 CS_VREDRAW，这两个风格表示窗口在宽度或者高度变化的时候<br />重画，但是这样就会引起IE闪烁</p>
		<p>解决方法：注册窗口类的时候不要使用这两个风格，如果窗口需要在改变大小的时候重画，那么可以在WM_SIZE的时候<br />调用RedrawWindow。</p>
		<p>原因四：</p>
		<p>界面上窗口很多，而且改变大小时很多窗口都要移动和改变大小，如果使用MoveWindow或者SetWindowPos两个API来<br />改变窗口的大小和位置，由于他们是等待窗口重画完成后才返回，所以过程很慢，这样视觉效果就可能会闪烁。</p>
		<p>解决方法：</p>
		<p>使用以下API来处理窗口移动，BeginDeferWindowPos, DeferWindowPos，EndDeferWindowPos<br />先调用 BeginDeferWindowPos 设定需要移动的窗口的个数<br />使用DeferWindowPos，来移动窗口，这个API并不真的造成窗口移动<br />EndDeferWindowPos 一次性完成所有窗口的大小和位置的改变。</p>
		<p>有个地方要特别注意，要仔细计算清楚要移动多少个窗口，BeginDeferWindowPos设定<br />的个数一定要和实际的个数一致，否则在Win9x下，如果实际移动的窗口数多于调用BeginDeferWindowPos<br />时设定的个数，可能会造成系统崩溃。在Windows NT系列下不会有这样的问题。</p>
		<p>如果你在属性里设置了 拖动窗口显示窗口内容的话，屏幕看起来会闪许多。你可以通过api SystemParameters（） ，把它去掉在你的应用程序里。这样在用户看来会好一点。这只是我个人建议。</p>
		<p>----------------------------<br /><br />        1、将Invalidate()替换为InvalidateRect()<br /><br />　　Invalidate()会导致整个窗口的图象重画，需要的时间比较长，而InvalidateRect()仅仅重画Rect区域内的内容，所以所需时间会少一些。虫虫以前很懒，经常为一小块区域的重画就调用Invalidate()，不愿意自己去计算需要重画的Rect，但是事实是，如果你确实需要改善闪烁的情况，计算一个Rect所用的时间比起重画那些不需要重画的内容所需要的时间要少得多。 <br /><br />　　2、禁止系统搽除你的窗口<br /><br />　　系统在需要重画窗口的时候会帮你用指定的背景色来搽除窗口。可是，也许需要重画的区域也许非常小。或者，在你重画这些东西之间还要经过大量的计算才能开始。这个时候你可以禁止系统搽掉原来的图象。直到你已经计算好了所有的数据，自己把那些需要搽掉的部分用背景色覆盖掉（如：dc.FillRect(rect,&amp;brush);rect是需要搽除的区域，brush是带背景色的刷子），再画上新的图形。要禁止系统搽除你的窗口，可以重载OnEraseBkgnd()函数，让其直接返回pUE就可以了。如 <br /><br /></p>
		<table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e7e9e9" border="1">
				<tbody>
						<tr>
								<td>BOOL CMyWin::OnEraseBkgnd(CDC* pDC) <br />{ <br />　return pUE; <br />　//return CWnd::OnEraseBkgnd(pDC);//把系统原来的这条语句注释掉。 <br />} </td>
						</tr>
				</tbody>
		</table>
		<br />　　3、有效的进行搽除<br /><br />　　搽除背景的时候，不要该搽不该搽的地方都搽。比如，你在一个窗口上放了一个很大的Edit框，几乎占了整个窗口，那么你频繁的搽除整个窗口背景将导致Edit不停重画形成剧烈的闪烁。事实上你可以CRgn创建一个需要搽除的区域，只搽除这一部分。如 <br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e7e9e9" border="1"><tbody><tr><td>GetClientRect(rectClient); <br />rgn1.CreateRectRgnIndirect(rectClient); <br />rgn2.CreateRectRgnIndirect(m_rectEdit); <br />if(rgn1.CombineRgn(&amp;rgn1,&amp;rgn2,RGN_XOR) == ERROR)//处理后的rgn1只包括了Edit框之外的客户区域，这样，Edit将不会被我的背景覆盖而导致重画。 <br />{ <br />　ASSERT(FALSE); <br />　return ; <br />} <br />brush.CreateSolidBrush(m_clrBackgnd); <br />pDC-&gt;FillRgn(&amp;rgn1,&amp;brush); <br />brush.DeleteObject(); </td></tr></tbody></table><br />　　注意：在使用这个方法的时候要同时使用方法二。别忘了，到时候又说虫虫的办法不灵。 <br /><br />　　4、使用MemoryDC先在内存里把图画好，再复制到屏幕上<br /><br />　　这对于一次画图过程很长的情况比较管用。毕竟内存操作比较快，而且复制到屏幕又是一次性的，至少不会出现可以明显看出一个东东从左画到右的情况。 <br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e7e9e9" border="1"><tbody><tr><td>void CMyWin::OnPaint() <br />{ <br />　CPaintDC dc1(this); // device context for painting <br />　dcMemory.CreateCompatibleDC(&amp;dc1); <br />　CBitmap bmp;//这里的Bitmap是必须的，否则当心弄出一个大黑块哦。 <br />　bmp.CreateCompatibleBitmap(&amp;dc1,rectClient.Width(),rectClient.Height()); <br />　dcMemory.SelectObject(&amp;bmp); <br /><br />　//接下来你想怎么画就怎么画吧。 <br />　//dcMemory.FillRect(rectClient,&amp;brush); <br /><br />　dc1.BitBlt(0,0,rectClient.Width(),rectClient.Height(),&amp;dcMemory,0,0,SRCCOPY); <br />　dcMemory.DeleteDC(); <br />　// Do not call CWnd::OnPaint() for painting messages <br />} </td></tr></tbody></table><br />　<b>争议</b><br /><br />　　上述方法确实有效，但在有很多控件的情况下，计算一个窗口中需要擦除并重绘的“空白区域”是一件很麻烦的事情。为了方便这种方法的实际应用，我写了一组宏来完成”计算空白区域“的功能：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e7e9e9" border="1"><tbody><tr><td><p>/************************************************************************/<br />/* MFC版<br />/* 宏功能: 界面刷新时仅刷新指定控件以外的空白区域;可有效避免窗口闪烁<br />/* 使用于: WM_ERASEBKGND 消息处理函数/************************************************************************/<br />#define ERASE_BKGND_BEGIN \<br />CRect bgRect;\<br />GetClientRect(&amp;bgRect);\<br />CRgn bgRgn;\<br />bgRgn.CreateRectRgnIndirect(bgRect);<br />//#define ERASE_BKGND_BEGIN <br />// Marco parameter 'IDC' specifies the identifier of the control <br />#define ADD_NOERASE_CONTROL(IDC)\<br />{\<br />　CRect controlRect;\<br />　GetDlgItem(IDC)-&gt;GetWindowRect(&amp;controlRect);\<br />　CRgn controlRgn;\<br />　controlRgn.CreateRectRgnIndirect(controlRect);\<br />　if(bgRgn.CombineRgn(&amp;bgRgn, &amp;controlRgn, RGN_XOR)==ERROR)\<br />　　return false;\<br />}<br /><br />// Marco parameter 'noEraseRect' specifies a screen coordinates based RECT, <br />// which needn't erase.<br />#define ADD_NOERASE_RECT(noEraseRect)\<br />{\<br />　CRgn noEraseRgn;\<br />　noEraseRgn.CreateRectRgnIndirect(noEraseRect);\<br />　if(bgRgn.CombineRgn(&amp;bgRgn, &amp;noEraseRgn, RGN_XOR)==ERROR)\<br />　　return false;\<br />}<br /><br />// Marco parameter 'pDC' is a kind of (CDC *) type.<br />// Marco parameter 'clBrushColor' specifies the color to brush the area.<br />#define ERASE_BKGND_END(pDC, clBrushColor)\<br />CBrush brush;\<br />brush.CreateSolidBrush(clBrushColor);\<br />CPoint saveOrg = (pDC)-&gt;GetWindowOrg();\<br />(pDC)-&gt;SetWindowOrg(bgRect.TopLeft());\<br />(pDC)-&gt;FillRgn(&amp;bgRgn, &amp;brush);\<br />(pDC)-&gt;SetWindowOrg(saveOrg);\<br />brush.DeleteObject();\<br />//#define ERASE_BKGND_END<br />/*************************************************/<br /><br /><br />/************************************************************************/<br />/* WTL版<br />/* 宏功能: 界面刷新时仅刷新指定控件以外的空白区域;可有效避免窗口闪烁<br />/* 使用于: WM_ERASEBKGND 消息处理函数<br />/************************************************************************/<br />#define ERASE_BKGND_BEGIN \<br /> CRect bgRect;\<br /> GetClientRect(&amp;bgRect);\<br /> CRgn bgRgn;\<br /> bgRgn.CreateRectRgnIndirect(bgRect);<br />//#define ERASE_BKGND_BEGIN <br />// Marco parameter 'IDC' specifies the identifier of the control <br />#define ADD_NOERASE_CONTROL(IDC)\<br />{\<br /> CRect controlRect;\<br /> GetDlgItem(IDC)-&gt;GetWindowRect(&amp;controlRect);\<br /> CRgn controlRgn;\<br /> controlRgn.CreateRectRgnIndirect(controlRect);\<br /> if(bgRgn.CombineRgn(&amp;bgRgn, &amp;controlRgn, RGN_XOR)==ERROR)\<br />  return false;\<br />}</p><p>// Marco parameter 'noEraseRect' specifies a screen coordinates based RECT, <br />// which needn't erase.<br />#define ADD_NOERASE_RECT(noEraseRect)\<br />{\<br /> CRgn noEraseRgn;\<br /> noEraseRgn.CreateRectRgnIndirect(noEraseRect);\<br /> if(bgRgn.CombineRgn(bgRgn.m_hRgn, noEraseRgn.m_hRgn, RGN_XOR)==ERROR)\<br />  return false;\<br />}</p><p>// Marco parameter 'pDC' is a kind of (CDC *) type.<br />// Marco parameter 'clBrushColor' specifies the color to brush the area.<br />#define ERASE_BKGND_END(pDC, clBrushColor)\<br /> CBrush brush;\<br /> brush.CreateSolidBrush(clBrushColor);\<br /> CPoint saveOrg;\<br /> (pDC)-&gt;GetWindowOrg(&amp;saveOrg);\<br /> (pDC)-&gt;SetWindowOrg(bgRect.TopLeft());\<br /> (pDC)-&gt;FillRgn(bgRgn.m_hRgn, brush.m_hBrush);\<br /> (pDC)-&gt;SetWindowOrg(saveOrg);\<br /> brush.DeleteObject();\<br />//#define ERASE_BKGND_END<br />/*************************************************/<br /></p></td></tr></tbody></table><br />　　说明：<br /><br />　　1)宏 ERASE_BKGND_BEGIN 和 ERASE_BKGND_END(pDC, clBrushColor) 搭配使用。<br /><br />　　2)宏 ADD_NOERASE_CONTROL(IDC) 和 ADD_NOERASE_RECT(noEraseRect) 根据需要放在上面两个宏的中间，用来添加不需要重绘背景的区域(正是这些区域导致了闪烁)，使用次数不限。其中参数noEraseRect是一个屏幕坐标系的RECT类型或CRect类型。<br /><br />　　使用举例1：<br /><br />　　在当前窗体的类中重写WM_ERASEBKGND消息处理函数如下：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e7e9e9" border="1"><tbody><tr><td>BOOL CMyWnd::OnEraseBkgnd(CDC* pDC) <br />{<br />　ERASE_BKGND_BEGIN;<br />　ADD_NOERASE_RGN(IDC_BUTTON2);<br />　ADD_NOERASE_RGN(IDC_BUTTON1);<br />　ADD_NOERASE_RGN(IDC_LIST_STAT);<br />　ERASE_BKGND_END(pDC, GetSysColor(COLOR_3DFACE));<br />　return false;<br />}</td></tr></tbody></table><br />　　上面的IDC_BUTTON2，IDC_BUTTON1，IDC_LIST_STAT即窗体上的控件。<br /><br />　　你可以指定其他已存在的控件。<br /><br />　　这样，窗口在擦除背景时，将只对上述控件以后的”空白区域“使用系统色重绘，有效避免了闪烁。<br /><br />　　备注：<br /><br />　　重载WM_ERASEBKGND消息处理函数OnEraseBkgnd的方法，选择View-&gt;ClassWizard-&gt;classinfo选项卡:message filter下拉框:<br /><br />　　选择window,然后再选择message maps选项卡,在messages下拉框应该可以找到wm_erasebkgnd.双击添加.<br /><br />　　使用举例2：防止CListCtrl在拉动窗口时闪烁。<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e7e9e9" border="1"><tbody><tr><td>/* * No further full-erasing is required, <br />* to prevent screen flashing caused by background erase and view repaint. <br />* Only erase the blank area. <br />*/<br /><br />BOOL CExListCtrl::OnEraseBkgnd(CDC* pDC) {<br />　//compute the holding-data-items area of this list control CRect rect; <br />　CPoint dataRgnTopLeftPoint; <br />　CPoint dataRgnBottomRightPoint; <br />　GetItemPosition(0 , &amp;dataRgnTopLeftPoint); <br />　GetItemPosition(GetItemCount() , &amp;dataRgnBottomRightPoint); <br />　if(!GetHeaderCtrl()-&gt;GetItemRect(GetHeaderCtrl()-&gt;GetItemCount()-1, rect)) return <br />　CListCtrl::OnEraseBkgnd(pDC);<br />　dataRgnBottomRightPoint.x = rect.right;<br />　rect.SetRect(dataRgnTopLeftPoint, (CPoint)(dataRgnBottomRightPoint - CPoint(2,2)));<br />　ClientToScreen(dataRgnRect); <br />　//compute and erase the blank area. Using the Marco. ERASE_BKGND_BEGIN; <br />　ADD_NOERASE_RECT(dataRgnRect);<br />　ERASE_BKGND_END(pDC, GetBkColor());<br />　return false;<br />}</td></tr></tbody></table><br />　　说明：CListCtrl在拉动的时候，会前以背景色重刷背景，再在上面绘制有数据的Items， 而没有数据的区域则保持背景色。因此，如果在BOOL CExListCtrl::OnEraseBkgnd(CDC* pDC) 函数中简单的return false，那么没有数据的区域将显示不正常。 故举例2中先计算出有数据的items的区域，这是不需要以背景重刷的区域。 再使用本文的宏，就可以有效避免CListCtrl在拉动时候的闪烁。<img src ="http://www.cppblog.com/eday/aggbug/20106.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2007-03-19 00:14 <a href="http://www.cppblog.com/eday/archive/2007/03/19/20106.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>鼠标屏幕取词技术的原理和实现</title><link>http://www.cppblog.com/eday/archive/2007/02/28/19065.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Wed, 28 Feb 2007 12:44:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/02/28/19065.html</guid><description><![CDATA[“鼠标屏幕取词”技术是在电子字典中得到广泛地应用的，如四通利方和金山词霸等软件，这个技术看似简单，其实在WINDOWS系统中实现却是非常复杂的，总的来说有两种实现方式：<br />    第一种：采用截获对部分GDI的API调用来实现,如TextOut,TextOutA等。<br />    第二种：对每个设备上下文(DC)做一分Copy,并跟踪所有修改上下文(DC)的操作。      <br /> <br />   第二种方法更强大,但兼容性不好，而第一种方法使用的截获WindowsAPI的调用，这项技术的强大可能远远超出了您的想象，毫不夸张的说，利用WindowsAPI拦截技术，你可以改造整个操作系统，事实上很多外挂式Windows中文平台就是这么实现的！而这项技术也正是这篇文章的主题。<br /><br />    截WindowsAPI的调用，具体的说来也可以分为两种方法：<br />    第一种方法通过直接改写WinAPI 在内存中的映像，嵌入汇编代码，使之被调用时跳转到指定的地址运行来截获；第二种方法则改写IAT（Import Address Table 输入地址表），重定向WinAPI函数的调用来实现对WinAPI的截获。<br /><br />    第一种方法的实现较为繁琐，而且在Win95、98下面更有难度，这是因为虽然微软说WIN16的API只是为了兼容性才保留下来，程序员应该尽可能地调用32位的API,实际上根本就不是这样！WIN 9X内部的大部分32位API经过变换调用了同名的16位API，也就是说我们需要在拦截的函数中嵌入16位汇编代码！<br /><br />    我们将要介绍的是第二种拦截方法，这种方法在Win95、98和NT下面运行都比较稳定，兼容性较好。由于需要用到关于Windows虚拟内存的管理、打破进程边界墙、向应用程序的进程空间中注入代码、PE（Portable Executable）文件格式和IAT（输入地址表）等较底层的知识，所以我们先对涉及到的这些知识大概地做一个介绍，最后会给出拦截部分的关键代码。<br /><br />      先说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进程，钩子函数就无能为力了），<br />当包含钩子的DLL注入其它进程后，就可以取得映射到这个进程虚拟内存里的各个模块（EXE和DLL）的基地址，<br />如：HMODULE hmodule=GetModuleHandle(“Mypro.exe”);<br /><br />在MFC程序中,我们可以用AfxGetInstanceHandle()函数来得到模块的基地址。EXE和DLL被映射到虚拟内存空间的什么地方是由它们的基地址决定的。它们的基地址是在链接时由链接器决定的。当你新建一个Win32工程时，VC＋＋链接器使用缺省的基地址0x00400000。可以通过链接器的BASE选项改变模块的基地址。EXE通常被映射到虚拟内存的0x00400000处，DLL也随之有不同的基地址，通常被映射到不同进程<br />的相同的虚拟地址空间处。<br />系统将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）地址。<br />说到这里，截获WindowsAPI的整个原理就要真相大白了。实际上所有进程对给定的API函数的调用总是通过PE文件的一个地方来转移的，这就是一个该模块(可以是EXE或DLL)的“.idata”段中的IAT输入地址表（Import Address Table）。在那里有所有本模块调用的其它DLL的函数名及地址。对其它DLL的函数调用实际上只是跳转到输入地址表，由输入地址表再跳转到DLL真正的函数入口。 
<p></p><p>具体来说，我们将通过IMAGE_IMPORT_DESCRIPTOR数组来访问“.idata”段中引入的DLL的信息，然后通过IMAGE_THUNK_DATA数组来针对一个被引入的DLL访问该DLL中被引入的每个函数的信息，找到我们需要截获的函数的跳转地址，然后改成我们自己的函数的地址……具体的做法在后面的关键代码中会有详细的讲解。<br />   讲了这么多原理，现在让我们回到“鼠标屏幕取词”的专题上来。除了API函数的截获，要实现“鼠标屏幕取词”，还需要做一些其它的工作，简单的说来，可以把一个完整的取词过程归纳成以下几个步骤：<br />1． 安装鼠标钩子，通过钩子函数获得鼠标消息。<br />使用到的API函数：SetWindowsHookEx<br />2． 得到鼠标的当前位置，向鼠标下的窗口发重画消息，让它调用系统函数重画窗口。<br />     使用到的API函数：WindowFromPoint，ScreenToClient，InvalidateRect<br />3． 截获对系统函数的调用，取得参数，也就是我们要取的词。<br />对于大多数的Windows应用程序来说，如果要取词，我们需要截获的是“Gdi32.dll”中的“TextOutA”函数。<br />我们先仿照TextOutA函数写一个自己的MyTextOutA函数，如：<br />BOOL WINAPI MyTextOutA(HDC hdc, int nXStart, int nYStart, LPCSTR lpszString,int cbString)<br />{<br />       // 这里进行输出lpszString的处理<br />           // 然后调用正版的TextOutA函数<br />}<br />把这个函数放在安装了钩子的动态连接库中，然后调用我们最后给出的HookImportFunction函数来截获进程<br />对TextOutA函数的调用，跳转到我们的MyTextOutA函数，完成对输出字符串的捕捉。HookImportFunction的<br />用法：<br /> HOOKFUNCDESC hd;<br /> PROC         pOrigFuns;<br /> hd.szFunc="TextOutA";<br /> hd.pProc=(PROC)MyTextOutA;<br /> HookImportFunction (AfxGetInstanceHandle(),"gdi32.dll",&amp;hd,pOrigFuns);<br /><br />下面给出了HookImportFunction的源代码，相信详尽的注释一定不会让您觉得理解截获到底是怎么实现的<br />很难，Ok,Let’s Go： </p><p></p><p>///////////////////////////////////////////// Begin ///////////////////////////////////////////////////////////////<br />#include &lt;crtdbg.h&gt;</p><p>// 这里定义了一个产生指针的宏<br />#define MakePtr(cast, ptr, AddValue) (cast)((DWORD)(ptr)+(DWORD)(AddValue))</p><p>// 定义了HOOKFUNCDESC结构,我们用这个结构作为参数传给HookImportFunction函数<br />typedef struct tag_HOOKFUNCDESC<br />{<br />  LPCSTR szFunc; // The name of the function to hook.<br />  PROC pProc;    // The procedure to blast in.<br />} HOOKFUNCDESC , * LPHOOKFUNCDESC;</p><p>// 这个函数监测当前系统是否是WindowNT<br />BOOL IsNT();</p><p>// 这个函数得到hModule -- 即我们需要截获的函数所在的DLL模块的引入描述符(import descriptor)<br />PIMAGE_IMPORT_DESCRIPTOR GetNamedImportDescriptor(HMODULE hModule, LPCSTR szImportModule);</p><p>// 我们的主函数<br />BOOL HookImportFunction(HMODULE hModule, LPCSTR szImportModule, <br />                         LPHOOKFUNCDESC paHookFunc, PROC* paOrigFuncs)<br />{<br />/////////////////////// 下面的代码检测参数的有效性 ////////////////////////////<br /> _ASSERT(szImportModule);<br /> _ASSERT(!IsBadReadPtr(paHookFunc, sizeof(HOOKFUNCDESC)));<br />#ifdef _DEBUG<br /> if (paOrigFuncs) _ASSERT(!IsBadWritePtr(paOrigFuncs, sizeof(PROC)));<br /> _ASSERT(paHookFunc.szFunc);<br /> _ASSERT(*paHookFunc.szFunc != '\0');<br />        _ASSERT(!IsBadCodePtr(paHookFunc.pProc));<br />#endif<br /> if ((szImportModule == NULL) || (IsBadReadPtr(paHookFunc, sizeof(HOOKFUNCDESC))))<br /> {<br />  _ASSERT(FALSE);<br />  SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);<br />  return FALSE;<br /> }<br />//////////////////////////////////////////////////////////////////////////////</p><p> // 监测当前模块是否是在2GB虚拟内存空间之上<br /> // 这部分的地址内存是属于Win32进程共享的<br /> if (!IsNT() &amp;&amp; ((DWORD)hModule &gt;= 0x80000000))<br /> {<br />  _ASSERT(FALSE);<br />  SetLastErrorEx(ERROR_INVALID_HANDLE, SLE_ERROR);<br />  return FALSE;<br /> }<br />     // 清零<br /> if (paOrigFuncs) memset(paOrigFuncs, NULL, sizeof(PROC)); </p><p> // 调用GetNamedImportDescriptor()函数,来得到hModule -- 即我们需要<br /> // 截获的函数所在的DLL模块的引入描述符(import descriptor)<br /> PIMAGE_IMPORT_DESCRIPTOR pImportDesc = GetNamedImportDescriptor(hModule, szImportModule);<br /> if (pImportDesc == NULL)<br /> return FALSE; // 若为空,则模块未被当前进程所引入</p><p> //  从DLL模块中得到原始的THUNK信息,因为pImportDesc-&gt;FirstThunk数组中的原始信息已经<br /> //  在应用程序引入该DLL时覆盖上了所有的引入信息,所以我们需要通过取得pImportDesc-&gt;OriginalFirstThunk<br /> //  指针来访问引入函数名等信息<br /> PIMAGE_THUNK_DATA pOrigThunk = MakePtr(PIMAGE_THUNK_DATA, hModule, <br />                                               pImportDesc-&gt;OriginalFirstThunk);<br /><br /> </p><p></p><p> //  从pImportDesc-&gt;FirstThunk得到IMAGE_THUNK_DATA数组的指针,由于这里在DLL被引入时已经填充了<br /> //  所有的引入信息,所以真正的截获实际上正是在这里进行的<br /> PIMAGE_THUNK_DATA pRealThunk = MakePtr(PIMAGE_THUNK_DATA, hModule, pImportDesc-&gt;FirstThunk);</p><p> //  穷举IMAGE_THUNK_DATA数组,寻找我们需要截获的函数,这是最关键的部分!<br /> while (pOrigThunk-&gt;u1.Function)<br /> {<br />  // 只寻找那些按函数名而不是序号引入的函数<br />  if (IMAGE_ORDINAL_FLAG != (pOrigThunk-&gt;u1.Ordinal &amp; IMAGE_ORDINAL_FLAG))<br />  {<br />   // 得到引入函数的函数名<br />   PIMAGE_IMPORT_BY_NAME pByName = MakePtr(PIMAGE_IMPORT_BY_NAME, hModule,<br />               pOrigThunk-&gt;u1.AddressOfData);</p><p>   // 如果函数名以NULL开始,跳过,继续下一个函数   <br />   if ('\0' == pByName-&gt;Name[0])<br />    continue;</p><p>   // bDoHook用来检查是否截获成功<br />   BOOL bDoHook = FALSE;</p><p>   // 检查是否当前函数是我们需要截获的函数<br />   if ((paHookFunc.szFunc[0] == pByName-&gt;Name[0]) &amp;&amp;<br />    (strcmpi(paHookFunc.szFunc, (char*)pByName-&gt;Name) == 0))<br />   {<br />    // 找到了!<br />    if (paHookFunc.pProc)<br />    bDoHook = TRUE;<br />   }<br />   if (bDoHook)<br />   {<br />    // 我们已经找到了所要截获的函数,那么就开始动手吧<br />    // 首先要做的是改变这一块虚拟内存的内存保护状态,让我们可以自由存取<br />    MEMORY_BASIC_INFORMATION mbi_thunk;<br />    VirtualQuery(pRealThunk, &amp;mbi_thunk, sizeof(MEMORY_BASIC_INFORMATION));<br />    _ASSERT(VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize, <br />                        PAGE_READWRITE, &amp;mbi_thunk.Protect));</p><p>    // 保存我们所要截获的函数的正确跳转地址<br />    if (paOrigFuncs)<br />      paOrigFuncs = (PROC)pRealThunk-&gt;u1.Function;</p><p>    // 将IMAGE_THUNK_DATA数组中的函数跳转地址改写为我们自己的函数地址!<br />    // 以后所有进程对这个系统函数的所有调用都将成为对我们自己编写的函数的调用<br />    pRealThunk-&gt;u1.Function = (PDWORD)paHookFunc.pProc;</p><p>    // 操作完毕!将这一块虚拟内存改回原来的保护状态<br />    DWORD dwOldProtect;<br />    _ASSERT(VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize, <br />                        mbi_thunk.Protect, &amp;dwOldProtect));<br />    SetLastError(ERROR_SUCCESS);<br />    return TRUE;<br />   }<br /><br />  }<br />  // 访问IMAGE_THUNK_DATA数组中的下一个元素<br />  pOrigThunk++;<br />  pRealThunk++;<br /> }<br /> return TRUE;<br />} </p><p></p><p>// GetNamedImportDescriptor函数的实现<br />PIMAGE_IMPORT_DESCRIPTOR GetNamedImportDescriptor(HMODULE hModule, LPCSTR szImportModule)<br />{<br /> // 检测参数<br /> _ASSERT(szImportModule);<br /> _ASSERT(hModule);<br /> if ((szImportModule == NULL) || (hModule == NULL))<br /> {<br />  _ASSERT(FALSE);<br />  SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);<br />  return NULL;<br /> }</p><p> // 得到Dos文件头<br /> PIMAGE_<a href="http://tech.acnow.net/html/OS/DOS/" target="_blank">DOS</a>_HEADER pDOSHeader = (PIMAGE_DOS_HEADER) hModule;</p><p> // 检测是否MZ文件头<br /> if (IsBadReadPtr(pDOSHeader, sizeof(IMAGE_DOS_HEADER)) || <br />  (pDOSHeader-&gt;e_magic != IMAGE_DOS_SIGNATURE))<br /> {<br />  _ASSERT(FALSE);<br />  SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);<br />  return NULL;<br /> }</p><p> // 取得PE文件头<br /> PIMAGE_NT_HEADERS pNTHeader = MakePtr(PIMAGE_NT_HEADERS, pDOSHeader, pDOSHeader-&gt;e_lfanew);</p><p> // 检测是否PE映像文件<br /> if (IsBadReadPtr(pNTHeader, sizeof(IMAGE_NT_HEADERS)) || <br />   (pNTHeader-&gt;Signature != IMAGE_NT_SIGNATURE))<br /> {<br />  _ASSERT(FALSE);<br />  SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);<br />  return NULL;<br /> }</p><p> // 检查PE文件的引入段(即 .idata section)<br /> if (pNTHeader-&gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress == 0)<br />  return NULL;</p><p> // 得到引入段(即 .idata section)的指针<br /> PIMAGE_IMPORT_DESCRIPTOR pImportDesc = MakePtr(PIMAGE_IMPORT_DESCRIPTOR, pDOSHeader,<br />  pNTHeader-&gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);</p><p> // 穷举PIMAGE_IMPORT_DESCRIPTOR数组寻找我们需要截获的函数所在的模块<br /> while (pImportDesc-&gt;Name)<br /> {<br />  PSTR szCurrMod = MakePtr(PSTR, pDOSHeader, pImportDesc-&gt;Name);<br />  if (stricmp(szCurrMod, szImportModule) == 0)<br />      break; // 找到!中断循环<br />  // 下一个元素<br />  pImportDesc++;<br /> }</p><p> // 如果没有找到,说明我们寻找的模块没有被当前的进程所引入!<br /> if (pImportDesc-&gt;Name == NULL)<br />  return NULL;</p><p> // 返回函数所找到的模块描述符(import descriptor)<br /> return pImportDesc;<br />}</p><p>// IsNT()函数的实现<br />BOOL IsNT()<br />{<br /> OSVERSIONINFO stOSVI;<br /> memset(&amp;stOSVI, NULL, sizeof(OSVERSIONINFO));<br /> stOSVI.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);<br /> BOOL bRet = GetVersionEx(&amp;stOSVI);<br /> _ASSERT(TRUE == bRet);<br /> if (FALSE == bRet) return FALSE;<br /> return (VER_PLATFORM_WIN32_NT == stOSVI.dwPlatformId);<br />}<br />/////////////////////////////////////////////// End //////////////////////////////////////////////////////////////////////</p><p>   不知道在这篇文章问世之前，有多少朋友尝试过去实现“鼠标屏幕取词”这项充满了挑战的技术，也只有尝试过的朋友才能体会到其间的不易，尤其在探索API函数的截获时，手头的几篇资料没有一篇是涉及到关键代码的，重要的地方都是一笔代过，MSDN更是显得苍白而无力，也不知道除了IMAGE_IMPORT_DESCRIPTOR和IMAGE_THUNK_DATA，微软还隐藏了多少秘密，好在硬着头皮还是把它给攻克了，希望这篇文章对大家能有所帮助。<br /><br /></p><img src ="http://www.cppblog.com/eday/aggbug/19065.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2007-02-28 20:44 <a href="http://www.cppblog.com/eday/archive/2007/02/28/19065.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>截取系统 API 调用</title><link>http://www.cppblog.com/eday/archive/2007/02/26/18987.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Mon, 26 Feb 2007 05:43:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/02/26/18987.html</guid><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 在很多情况下，为了测试代码或扩展操作系统的功能，软件开发人员或测试人员必须截取系统函数调用。有一些软件包能够提供该功能，如微软公司的 Detours* 库，或 OK Thinking Software 的 Syringe*。但是从另一个角度而言，开发人员可能希望不需借助第三方软件，自己就能实现该功能。本文描述了函数截取的几种不同方式，并详细介绍了无需使用商业软件包，也不需受 GNU*（通用公共许可...&nbsp;&nbsp;<a href='http://www.cppblog.com/eday/archive/2007/02/26/18987.html'>阅读全文</a><img src ="http://www.cppblog.com/eday/aggbug/18987.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2007-02-26 13:43 <a href="http://www.cppblog.com/eday/archive/2007/02/26/18987.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++ 类库</title><link>http://www.cppblog.com/eday/archive/2007/01/20/17805.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Fri, 19 Jan 2007 18:58:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/01/20/17805.html</guid><description><![CDATA[C++类库介绍 <br />再次体现了C++保持核心语言的效率同时大力发展应用库的发展趋势!!在C++中，库的地位是非常高的。C++之父 Bjarne Stroustrup先生多次表示了设计库来扩充功能要好过设计更多的语法的言论。现实中，C++的库门类繁多，解决的问题也是极其广泛，库从轻量级到重量级的都有。不少都是让人眼界大开，亦或是望而生叹的思维杰作。由于库的数量非常庞大，而且限于笔者水平，其中很多并不了解。所以文中所提的一些库都是比较著名的大型库。 <br /><br />标准库 <br /><br />标准库中提供了C++程序的基本设施。虽然C++标准库随着C++标准折腾了许多年，直到标准的出台才正式定型，但是在标准库的实现上却很令人欣慰得看到多种实现，并且已被实践证明为有工业级别强度的佳作。 <br /><br />1、 Dinkumware C++ Library <br /><br />参考站点：<a href="http://www.dinkumware.com/" target="_blank"><font color="#261cdc">http://www.dinkumware.com/</font></a>'&gt;<a href="http://www.dinkumware.com/" target="_blank"><font color="#261cdc">http://www.dinkumware.com/</font></a> <br /><br />P.J. Plauger编写的高品质的标准库。P.J. Plauger博士是Dr. Dobb's程序设计杰出奖的获得者。其编写的库长期被Microsoft采用，并且最近Borland也取得了其OEM的license，在其C/C+ +的产品中采用Dinkumware的库。 <br /><br />2、 RogueWave Standard C++ Library <br /><br />参考站点：<a href="http://www.roguewave.com/" target="_blank"><font color="#261cdc">http://www.roguewave.com/</font></a>'&gt;<a href="http://www.roguewave.com/" target="_blank"><font color="#261cdc">http://www.roguewave.com/</font></a>'&gt;<a href="http://www.roguewave.com/" target="_blank"><font color="#261cdc">http://www.roguewave.com/</font></a>'&gt;<a href="http://www.roguewave.com/" target="_blank"><font color="#261cdc">http://www.roguewave.com/</font></a> <br /><br />这个库在Borland C++ Builder的早期版本中曾经被采用，后来被其他的库给替换了。笔者不推荐使用。 <br /><br />3、SGI STL <br /><br />参考站点：<a href="http://www.roguewave.com/" target="_blank"><font color="#261cdc">http://www.roguewave.com/</font></a>'&gt;<a href="http://www.roguewave.com/" target="_blank"><font color="#261cdc">http://www.roguewave.com/</font></a>'&gt;<a href="http://www.roguewave.com/" target="_blank"><font color="#261cdc">http://www.roguewave.com/</font></a>'&gt;<a href="http://www.roguewave.com/" target="_blank"><font color="#261cdc">http://www.roguewave.com/</font></a> <br /><br />SGI公司的C++标准模版库。 <br /><br />4、STLport <br /><br />参考站点：<a href="http://www.stlport.org/" target="_blank"><font color="#261cdc">http://www.stlport.org/</font></a>'&gt;<a href="http://www.stlport.org/" target="_blank"><font color="#261cdc">http://www.stlport.org/</font></a> <br /><br />SGI STL库的跨平台可移植版本。 <br /><br /><br /><br />准标准库——Boost <br /><br />Boost 库是一个经过千锤百炼、可移植、提供源代码的C++库，作为标准库的后备，是C++标准化进程的发动机之一。 Boost库由C++标准委员会库工作组成员发起，在C++社区中影响甚大，其成员已近2000人。 Boost库为我们带来了最新、最酷、最实用的技术，是不折不扣的"准"标准库。 <br /><br />Boost中比较有名气的有这么几个库： <br /><br />Regex <br />正则表达式库 <br /><br />Spirit <br />LL parser framework，用C++代码直接表达EBNF <br /><br />Graph <br />图组件和算法 <br /><br />Lambda <br />在调用的地方定义短小匿名的函数对象，很实用的functional功能 <br /><br />concept check <br />检查泛型编程中的concept <br /><br />Mpl <br />用模板实现的元编程框架 <br /><br />Thread <br />可移植的C++多线程库 <br /><br />Python <br />把C++类和函数映射到Python之中 <br /><br />Pool <br />内存池管理 <br /><br />smart_ptr <br />5个智能指针，学习智能指针必读，一份不错的参考是来自CUJ的文章： <br /><br />Smart Pointers in Boost,哦，这篇文章可以查到，CUJ是提供在线浏览的。中文版见笔者在《Dr. Dobb's Journal软件研发杂志》第7辑上的译文。 <br /><br /><br />Boost 总体来说是实用价值很高，质量很高的库。并且由于其对跨平台的强调，对标准C++的强调，是编写平台无关，现代C++的开发者必备的工具。但是Boost 中也有很多是实验性质的东西，在实际的开发中实用需要谨慎。并且很多Boost中的库功能堪称对语言功能的扩展，其构造用尽精巧的手法，不要贸然的花费时间研读。Boost另外一面，比如Graph这样的库则是具有工业强度，结构良好，非常值得研读的精品代码，并且也可以放心的在产品代码中多多利用。 <br /><br />参考站点：<a href="http://www.boost.org/" target="_blank"><font color="#261cdc">http://www.boost.org</font></a>'&gt;<a href="http://www.boost.org/" target="_blank"><font color="#261cdc">http://www.boost.org</font></a>（国内镜像：<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt; <a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.c-view.org/tech/lib/boost/index.htm" target="_blank"><font color="#261cdc">http://www.c-view.org/tech/lib/boost/index.htm</font></a>） <br /><br />GUI <br /><br />在众多C++的库中，GUI部分的库算是比较繁荣，也比较引人注目的。在实际开发中，GUI库的选择也是非常重要的一件事情，下面我们综述一下可选择的GUI库，各自的特点以及相关工具的支持。 <br /><br />1、 MFC <br /><br />大名鼎鼎的微软基础类库（Microsoft Foundation Class）。大凡学过VC++的人都应该知道这个库。虽然从技术角度讲，MFC是不大漂亮的，但是它构建于Windows API 之上，能够使程序员的工作更容易,编程效率高，减少了大量在建立 Windows 程序时必须编写的代码，同时它还提供了所有一般 C++ 编程的优点，例如继承和封装。MFC 编写的程序在各个版本的Windows操作系统上是可移植的，例如，在 Windows 3.1下编写的代码可以很容易地移植到 Windows NT 或 Windows 95 上。但是在最近发展以及官方支持上日渐势微。 <br /><br /><br /><br />2、 QT <br /><br />参考网站：<a href="http://www.trolltech.com/" target="_blank"><font color="#261cdc">http://www.trolltech.com/</font></a>'&gt;<a href="http://www.trolltech.com/" target="_blank"><font color="#261cdc">http://www.trolltech.com/</font></a> <br /><br />Qt 是Trolltech公司的一个多平台的C++图形用户界面应用程序框架。它提供给应用程序开发者建立艺术级的图形用户界面所需的所用功能。Qt是完全面向对象的很容易扩展，并且允许真正地组件编程。自从1996年早些时候，Qt进入商业领域，它已经成为全世界范围内数千种成功的应用程序的基础。Qt也是流行的Linux桌面环境KDE 的基础，同时它还支持Windows、Macintosh、Unix/X11等多种平台。 <br /><br /><br /><br />3、WxWindows <br /><br />参考网站：<a href="http://www.wxwindows.org/" target="_blank"><font color="#261cdc">http://www.wxwindows.org/</font></a>'&gt;<a href="http://www.wxwindows.org/" target="_blank"><font color="#261cdc">http://www.wxwindows.org/</font></a> <br /><br />跨平台的GUI库。因为其类层次极像MFC，所以有文章介绍从MFC到WxWindows的代码移植以实现跨平台的功能。通过多年的开发也是一个日趋完善的 GUI库，支持同样不弱于前面两个库。并且是完全开放源代码的。新近的C++ Builder X的GUI设计器就是基于这个库的。 <br /><br />4、Fox <br /><br />开放源代码的GUI库。作者从自己亲身的开发经验中得出了一个理想的GUI库应该是什么样子的感受出发，从而开始了对这个库的开发。有兴趣的可以尝试一下。 <br /><br />参考网站：<a href="http://www.fox/" target="_blank"><font color="#261cdc">http://www.fox</font></a>'&gt;<a href="http://www.fox-toolkit.org/" target="_blank"><font color="#261cdc">http://www.fox-toolkit.org/</font></a> <br /><br />5、 WTL <br /><br />基于ATL的一个库。因为使用了大量ATL的轻量级手法，模板等技术，在代码尺寸，以及速度优化方面做得非常到位。主要面向的使用群体是开发COM轻量级供网络下载的可视化控件的开发者。 <br /><br />6、 GTK <br /><br />参考网站：<a href="http://gtkmm.sourceforge.net/" target="_blank"><font color="#261cdc">http://gtkmm.sourceforge.net/</font></a> <br /><br />GTK是一个大名鼎鼎的C的开源GUI库。在Linux世界中有Gnome这样的杀手应用。而GTK就是这个库的C++封装版本。 <br /><br />? <br /><br />库 <br /><br /><br />网络通信 <br /><br />ACE <br /><br />参考网站：<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.cs.wustl.edu/~schmidt/ACE.html" target="_blank"><font color="#261cdc">http://www.cs.wustl.edu/~schmidt/ACE.html</font></a> <br /><br />C+ +库的代表，超重量级的网络通信开发框架。ACE自适配通信环境（Adaptive Communication Environment）是可以自由使用、开放源代码的面向对象框架，在其中实现了许多用于并发通信软件的核心模式。ACE提供了一组丰富的可复用C++ 包装外观（Wrapper Facade）和框架组件，可跨越多种平台完成通用的通信软件任务，其中包括：事件多路分离和事件处理器分派、信号处理、服务初始化、进程间通信、共享内存管理、消息路由、分布式服务动态（重）配置、并发执行和同步，等等。 <br /><br />StreamModule <br /><br />参考网站：<a href="http://www.omnifarious.org/StrMod/" target="_blank"><font color="#261cdc">http://www.omnifarious.org/StrMod/</font></a>'&gt;<a href="http://www.omnifarious.org/StrMod/" target="_blank"><font color="#261cdc">http://www.omnifarious.org/StrMod/</font></a> <br /><br />设计用于简化编写分布式程序的库。尝试着使得编写处理异步行为的程序更容易，而不是用同步的外壳包起异步的本质。 <br /><br />SimpleSocket <br /><br />参考网站：<a href="http://home.hetnet.nl/~lcbokkers/si" target="_blank"><font color="#261cdc">http://home.hetnet.nl/~lcbokkers/simsock.htm</font></a> <br /><br />这个类库让编写基于socket的客户/服务器程序更加容易。 <br /><br />A Stream Socket API for C++ <br /><br />参考网站：<a href="http://www.pcs.cnu.edu/" target="_blank"><font color="#261cdc">http://www.pcs.cnu.edu/</font></a>'&gt;<a href="http://www.pcs.cnu.edu/~dgame/sockets/socketsC++/sockets.html" target="_blank"><font color="#261cdc">http://www.pcs.cnu.edu/~dgame/sockets/socketsC++/sockets.html</font></a> <br /><br />又一个对Socket的封装库。 <br /><br />XML <br /><br />Xerces <br /><br />参考网站：<a href="http://xml.apache.org/xerces-c/" target="_blank"><font color="#261cdc">http://xml.apache.org/xerces-c/</font></a> <br /><br />Xerces-C++ 是一个非常健壮的XML解析器，它提供了验证，以及SAX和DOM API。XML验证在文档类型定义(Document Type Definition，DTD)方面有很好的支持，并且在2001年12月增加了支持W3C XML Schema 的基本完整的开放标准。 <br /><br />XMLBooster <br /><br />参考网站：<a href="http://www.xmlbooster.com/" target="_blank"><font color="#261cdc">http://www.xmlbooster.com/</font></a>'&gt;<a href="http://www.xmlbooster.com/" target="_blank"><font color="#261cdc">http://www.xmlbooster.com/</font></a> <br /><br />这个库通过产生特制的parser的办法极大的提高了XML解析的速度，并且能够产生相应的GUI程序来修改这个parser。在DOM和SAX两大主流XML解析办法之外提供了另外一个可行的解决方案。 <br /><br />Pull Parser <br /><br />参考网站：<a href="http://www.extreme.indiana.edu/xgws/xsoap/xpp/" target="_blank"><font color="#261cdc">http://www.extreme.indiana.edu/xgws/xsoap/xpp/</font></a>'&gt;<a href="http://www.extreme.indiana.edu/xgws/xsoap/xpp/" target="_blank"><font color="#261cdc">http://www.extreme.indiana.edu/xgws/xsoap/xpp/</font></a> <br /><br />这个库采用pull方法的parser。在每个SAX的parser底层都有一个pull的parser，这个xpp把这层暴露出来直接给大家使用。在要充分考虑速度的时候值得尝试。 <br /><br />Xalan <br /><br />参考网站：<a href="http://xml.apache.org/xalan-c/" target="_blank"><font color="#261cdc">http://xml.apache.org/xalan-c/</font></a> <br /><br />Xalan是一个用于把XML文档转换为HTML，纯文本或者其他XML类型文档的XSLT处理器。 <br /><br />CMarkup <br /><br />参考网站：<a href="http://www.firstobject.com/xml.htm" target="_blank"><font color="#261cdc">http://www.firstobject.com/xml.htm</font></a>'&gt;<a href="http://www.firstobject.com/xml.htm" target="_blank"><font color="#261cdc">http://www.firstobject.com/xml.htm</font></a> <br /><br />这是一种使用EDOM的XML解析器。在很多思路上面非常灵活实用。值得大家在DOM和SAX之外寻求一点灵感。 <br /><br />libxml++ <br /><br /><a href="http://libxmlplusplus.sourceforge.net/" target="_blank"><font color="#261cdc">http://libxmlplusplus.sourceforge.net/</font></a> <br /><br />libxml++是对著名的libxml XML解析器的C++封装版本 <br /><br /><br /><br />科学计算 <br /><br />Blitz++ <br /><br />参考网站：<a href="http://www.oonumerics.org/blitz/" target="_blank"><font color="#261cdc">http://www.oonumerics.org/blitz/</font></a>'&gt;<a href="http://www.oonumerics.org/blitz/" target="_blank"><font color="#261cdc">http://www.oonumerics.org/blitz/</font></a> <br /><br />Blitz++ 是一个高效率的数值计算函数库，它的设计目的是希望建立一套既具像C++ 一样方便，同时又比Fortran速度更快的数值计算环境。通常，用C++所写出的数值程序，比 Fortran慢20%左右，因此Blitz++正是要改掉这个缺点。方法是利用C++的template技术，程序执行甚至可以比Fortran更快。 Blitz++目前仍在发展中，对于常见的SVD，FFTs，QMRES等常见的线性代数方法并不提供，不过使用者可以很容易地利用Blitz++所提供的函数来构建。 <br /><br />POOMA <br /><br />参考网站：<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.codesourcery.com/pooma/pooma" target="_blank"><font color="#261cdc">http://www.codesourcery.com/pooma/pooma</font></a> <br /><br />POOMA是一个免费的高性能的C++库，用于处理并行式科学计算。POOMA的面向对象设计方便了快速的程序开发，对并行机器进行了优化以达到最高的效率，方便在工业和研究环境中使用。 <br /><br />MTL <br /><br />参考网站：<a href="http://www.osl.iu.edu/research/mtl/" target="_blank"><font color="#261cdc">http://www.osl.iu.edu/research/mtl/</font></a>'&gt;<a href="http://www.osl.iu.edu/research/mtl/" target="_blank"><font color="#261cdc">http://www.osl.iu.edu/research/mtl/</font></a> <br /><br />Matrix Template Library(MTL)是一个高性能的泛型组件库，提供了各种格式矩阵的大量线性代数方面的功能。在某些应用使用高性能编译器的情况下，比如Intel的编译器，从产生的汇编代码可以看出其与手写几乎没有两样的效能。 <br /><br />CGAL <br /><br />参考网站：<a href="http://www.cgal.org/" target="_blank"><font color="#261cdc">www.cgal.org</font></a> <br /><br />Computational Geometry Algorithms Library的目的是把在计算几何方面的大部分重要的解决方案和方法以C++库的形式提供给工业和学术界的用户。 <br /><br /><br /><br />游戏开发 <br /><br />Audio/Video 3D C++ Programming Library <br /><br />参考网站：<a href="http://www.galacticasoftware.com/products/av/" target="_blank"><font color="#261cdc">http://www.galacticasoftware.com/products/av/</font></a>'&gt;<a href="http://www.galacticasoftware.com/products/av/" target="_blank"><font color="#261cdc">http://www.galacticasoftware.com/products/av/</font></a> <br /><br />AV3D是一个跨平台，高性能的C++库。主要的特性是提供3D图形，声效支持（SB,以及S3M），控制接口（键盘，鼠标和遥感），XMS。 <br /><br />KlayGE <br /><br />参考网站：<a href="http://home.g365.net/enginedev/" target="_blank"><font color="#261cdc">http://home.g365.net/enginedev/</font></a> <br /><br />国内游戏开发高手自己用C++开发的游戏引擎。KlayGE是一个开放源代码、跨平台的游戏引擎，并使用Python作脚本语言。KlayGE在LGPL协议下发行。感谢龚敏敏先生为中国游戏开发事业所做出的贡献。 <br /><br />OGRE <br /><br />参考网站：<a href="http://www.ogre3d.org/" target="_blank"><font color="#261cdc">http://www.ogre3d.org</font></a>'&gt;<a href="http://www.ogre3d.org/" target="_blank"><font color="#261cdc">http://www.ogre3d.org</font></a> <br /><br />OGRE （面向对象的图形渲染引擎）是用C++开发的，使用灵活的面向对象3D引擎。它的目的是让开发者能更方便和直接地开发基于3D硬件设备的应用程序或游戏。引擎中的类库对更底层的系统库（如：Direct3D和OpenGL）的全部使用细节进行了抽象，并提供了基于现实世界对象的接口和其它类。 <br /><br /><br /><br />线程 <br /><br />C++ Threads <br /><br />参考网站：<a href="http://threads.sourceforge.net/" target="_blank"><font color="#261cdc">http://threads.sourceforge.net/</font></a> <br /><br />这个库的目标是给程序员提供易于使用的类，这些类被继承以提供在Linux环境中很难看到的大量的线程方面的功能。 <br /><br />ZThreads <br /><br />参考网站：<a href="http://zthread.sourceforge.net/" target="_blank"><font color="#261cdc">http://zthread.sourceforge.net/</font></a> <br /><br />一个先进的面向对象，跨平台的C++线程和同步库。 <br /><br /><br /><br />序列化 <br /><br />s11n <br /><br />参考网站：<a href="http://s11n.net/" target="_blank"><font color="#261cdc">http://s11n.net/</font></a> <br /><br />一个基于STL的C++库，用于序列化POD，STL容器以及用户定义的类型。 <br /><br />Simple XML Persistence Library <br /><br />参考网站：<a href="http://sxp.sourceforge.net/" target="_blank"><font color="#261cdc">http://sxp.sourceforge.net/</font></a> <br /><br />这是一个把对象序列化为XML的轻量级的C++库。 <br /><br /><br /><br />字符串 <br /><br />C++ Str Library <br /><br />参考网站：<a href="http://www.utilitycode.com/str/" target="_blank"><font color="#261cdc">http://www.utilitycode.com/str/</font></a>'&gt;<a href="http://www.utilitycode.com/str/" target="_blank"><font color="#261cdc">http://www.utilitycode.com/str/</font></a> <br /><br />操作字符串和字符的库，支持Windows和支持gcc的多种平台。提供高度优化的代码，并且支持多线程环境和Unicode，同时还有正则表达式的支持。 <br /><br />Common Text Transformation Library <br /><br />参考网站：<a href="http://cttl.sourceforge.net/" target="_blank"><font color="#261cdc">http://cttl.sourceforge.net/</font></a> <br /><br />这是一个解析和修改STL字符串的库。CTTL substring类可以用来比较，插入，替换以及用EBNF的语法进行解析。 <br /><br />GRETA <br /><br />参考网站：<a href="http://research.microsoft.com/projects/greta/" target="_blank"><font color="#261cdc">http://research.microsoft.com/projects/greta/</font></a> <br /><br />这是由微软研究院的研究人员开发的处理正则表达式的库。在小型匹配的情况下有非常优秀的表现。 <br /><br />综合 <br /><br />P::Classes <br /><br />参考网站：<a href="http://pclasses.com/" target="_blank"><font color="#261cdc">http://pclasses.com/</font></a> <br /><br />一个高度可移植的C++应用程序框架。当前关注类型和线程安全的signal/slot机制，i/o系统包括基于插件的网络协议透明的i/o架构，基于插件的应用程序消息日志框架，访问sql数据库的类等等。 <br /><br />ACDK - Artefaktur Component Development Kit <br /><br />参考网站：<a href="http://acdk.sourceforge.net/" target="_blank"><font color="#261cdc">http://acdk.sourceforge.net/</font></a> <br /><br />这是一个平台无关的C++组件框架，类似于Java或者.NET中的框架（反射机制，线程，Unicode，废料收集，I/O，网络，实用工具，XML，等等），以及对Java, Perl, Python, TCL, Lisp, COM 和 CORBA的集成。 <br /><br />dlib C++ library <br /><br />参考网站：<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.cis.ohio-state.edu/~kingd/dlib/" target="_blank"><font color="#261cdc">http://www.cis.ohio-state.edu/~kingd/dlib/</font></a> <br /><br />各种各样的类的一个综合。大整数，Socket，线程，GUI，容器类,以及浏览目录的API等等。 <br /><br />Chilkat C++ Libraries <br /><br />参考网站：<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.chilkatsoft.com/cpp_libraries.asp" target="_blank"><font color="#261cdc">http://www.chilkatsoft.com/cpp_libraries.asp</font></a> <br /><br />这是提供zip，e-mail，编码，S/MIME，XML等方面的库。 <br /><br />C++ Portable Types Library (PTypes) <br /><br />参考网站：<a href="http://www.melikyan.com/ptypes/" target="_blank"><font color="#261cdc">http://www.melikyan.com/ptypes/</font></a>'&gt;<a href="http://www.melikyan.com/ptypes/" target="_blank"><font color="#261cdc">http://www.melikyan.com/ptypes/</font></a> <br /><br />这是STL的比较简单的替代品，以及可移植的多线程和网络库。 <br /><br />LFC <br /><br />参考网站：<a href="http://lfc.sourceforge.net/" target="_blank"><font color="#261cdc">http://lfc.sourceforge.net/</font></a> <br /><br />哦，这又是一个尝试提供一切的C++库 <br /><br /><br /><br />其他库 <br /><br />Loki <br /><br />参考网站：<a href="http://www.moderncppdesign.com/" target="_blank"><font color="#261cdc">http://www.moderncppdesign.com/</font></a>'&gt;http: //<a href="http://www.moderncppdesign.com/" target="_blank"><font color="#261cdc">www.moderncppdesign.com/</font></a>'&gt;<a href="http://www.moderncppdesign.com/" target="_blank"><font color="#261cdc">http://www.moderncppdesign.com/</font></a>'&gt;<a href="http://www.moderncppdesign.com/" target="_blank"><font color="#261cdc">http://www.moderncppdesign.com/</font></a> <br /><br />哦，你可能抱怨我早该和Boost一起介绍它，一个实验性质的库。作者在loki中把C++模板的功能发挥到了极致。并且尝试把类似设计模式这样思想层面的东西通过库来提供。同时还提供了智能指针这样比较实用的功能。 <br /><br />ATL <br /><br />ATL(Active Template Library)是一组小巧、高效、灵活的类，这些类为创建可互操作的COM组件提供了基本的设施。 <br /><br />FC++: The Functional C++ Library <br /><br />这个库提供了一些函数式语言中才有的要素。属于用库来扩充语言的一个代表作。如果想要在OOP之外寻找另一分的乐趣，可以去看看函数式程序设计的世界。大师 Peter Norvig在 "Teach Yourself Programming in Ten Years"一文中就将函数式语言列为至少应当学习的6类编程语言之一。 <br /><br />FACT! <br /><br />参考网站：<a href="http://www.kfa/" target="_blank"><font color="#261cdc">http://www.kfa</font></a>'&gt;<a href="http://www.kfa-juelich.de/zam/FACT/start/index.html" target="_blank"><font color="#261cdc">http://www.kfa-juelich.de/zam/FACT/start/index.html</font></a> <br /><br />另外一个实现函数式语言特性的库 <br /><br />Crypto++ <br /><br />提供处理密码，消息验证，单向hash，公匙加密系统等功能的免费库。 <br /><br />还有很多非常激动人心或者是极其实用的C++库，限于我们的水平以及文章的篇幅不能包括进来。在对于这些已经包含近来的库的介绍中，由于并不是每一个我们都使用过，所以难免有偏颇之处，请读者见谅。 <br /><br /><br /><br />资源网站 <br /><br />正如我们可以通过计算机历史上的重要人物了解计算机史的发展，C++相关人物的网站也可以使我们得到最有价值的参考与借鉴，下面的人物我们认为没有介绍的必要，只因下面的人物在C++领域的地位众所周知，我们只将相关的资源进行罗列以供读者学习，他们有的工作于贝尔实验室，有的工作于知名编译器厂商，有的在不断推进语言的标准化，有的为读者撰写了多部千古奇作...... <br /><br />Bjarne Stroustrup <a href="http://www.research.att.com/" target="_blank"><font color="#261cdc">http://www.research.att.com/</font></a>'&gt;<a href="http://www.research.att.com/~bs/" target="_blank"><font color="#261cdc">http://www.research.att.com/~bs/</font></a> <br /><br />Stanley B. Lippman <br /><br />http: //blogs.msdn.com/slippman/(中文版<a href="http://www.zengyihome.net/" target="_blank"><font color="#261cdc">http://www.zengyihome.net</font></a>'&gt;http: //<a href="http://www.zengyihome.net/slippman/index.htm" target="_blank"><font color="#261cdc">www.zengyihome.net/slippman/index.htm</font></a>'&gt;<a href="http://www.zengyihome.net/" target="_blank"><font color="#261cdc">http://www.zengyihome.net</font></a>'&gt;<a href="http://www.zengyihome.net/slippman/index.htm" target="_blank"><font color="#261cdc">http://www.zengyihome.net/slippman/index.htm</font></a>) <br /><br />Scott Meyers <a href="http://www.aristeia.com/" target="_blank"><font color="#261cdc">http://www.aristeia.com/</font></a>'&gt;<a href="http://www.aristeia.com/" target="_blank"><font color="#261cdc">http://www.aristeia.com/</font></a> <br /><br />David Musser <a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.cs.rpi.edu/~musser/" target="_blank"><font color="#261cdc">http://www.cs.rpi.edu/~musser/</font></a> <br /><br />Bruce Eckel <a href="http://www.bruceeckel.com/" target="_blank"><font color="#261cdc">http://www.bruceeckel.com</font></a>'&gt;<a href="http://www.bruceeckel.com/" target="_blank"><font color="#261cdc">http://www.bruceeckel.com</font></a> <br /><br />Nicolai M. Josuttis <a href="http://www.josuttis.com/" target="_blank"><font color="#261cdc">http://www.josuttis.com/</font></a>'&gt;<a href="http://www.josuttis.com/" target="_blank"><font color="#261cdc">http://www.josuttis.com/</font></a> <br /><br />Herb Sutter <a href="http://www.gotw.ca/" target="_blank"><font color="#261cdc">http://www.gotw.ca/</font></a>'&gt;<a href="http://www.gotw.ca/" target="_blank"><font color="#261cdc">http://www.gotw.ca/</font></a> <br /><br />Andrei Alexandrescu <a href="http://www.moderncppdesign.com/" target="_blank"><font color="#261cdc">http://www.moderncppdesign.com/</font></a>'&gt;<a href="http://www.moderncppdesign.com/" target="_blank"><font color="#261cdc">http://www.moderncppdesign.com/</font></a>'&gt;<a href="http://www.moderncppdesign.com/" target="_blank"><font color="#261cdc">http://www.moderncppdesign.com/</font></a>'&gt;<a href="http://www.moderncppdesign.com/" target="_blank"><font color="#261cdc">http://www.moderncppdesign.com/</font></a><img src ="http://www.cppblog.com/eday/aggbug/17805.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2007-01-20 02:58 <a href="http://www.cppblog.com/eday/archive/2007/01/20/17805.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>反病毒引擎设计三</title><link>http://www.cppblog.com/eday/archive/2007/01/19/17804.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Fri, 19 Jan 2007 14:44:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/01/19/17804.html</guid><description><![CDATA[
		<strong>目录 <br /><br /><font color="#800080">3.1实时监控概论<br />3.2病毒实时监控实现技术概论<br />3.3WIN9X下的病毒实时监控<br />  3.3.1实现技术详解<br />  3.3.2程序结构与流程<br />  3.3.3HOOKSYS.VXD逆向工程代码剖析<br />    3.3.3.1钩子函数入口代码<br />    3.3.3.2取得当前进程名称代码<br />    3.3.3.3通信部分代码 <br />3.4 WINNT/2000下的病毒实时监控<br />  3.4.1实现技术详解<br />  3.4.2程序结构与流程<br />  3.4.3HOOKSYS.SYS逆向工程代码剖析<br />    3.4.3.1取得当前进程名称代码<br />    3.4.3.2启动钩子函数工作代码 <br />    3.4.3.3映射系统内存至用户空间代码</font><br /><br /></strong>
		<font color="#330099">
				<strong>3．病毒实时监控</strong>
		</font>
		<br />
		<br />
		<b>
				<a name="1">3.1实时监控概论</a>
		</b>
		<br />
		<br />实时监控技术其实并非什么新技术，早在DOS编程时代就有之。只不过那时人们没有给这项技术冠以这样专业的名字而已。早期在各大专院校机房中普遍使用的硬盘写保护软件正是利用了实时监控技术。硬盘写保护软件一般会将自身写入硬盘零磁头开始的几个扇区（由0磁头0柱面1扇最开始的64个扇区是保留的，DOS访问不到）并修改原来的主引导记录以使启动时硬盘写保护程序可以取得控制权。引导时取得控制权的硬盘写保护程序会修改INT13H的中断向量指向自身已驻留于内存中的钩子代码以便随时拦截所有对磁盘的操作。钩子代码的作用当然是很明显的，它主要负责由判断中断入口参数，包括功能号，磁盘目标地址等来决定该类型操作是否被允许，这样就可以实现对某一特定区域的写操作保护。后来又诞生了在此基础之上进行改进了的磁盘恢复卡之类的产品，其利用将写操作重定向至目标区域外的临时分区并保存磁盘先前状态等技术来实现允许写入并可随时恢复之功能。不管怎么改进，这类产品的核心技术还是对磁盘操作的实时监控。对此有兴趣的朋友可参看高云庆著《硬盘保护技术手册》。DOS下还有许多通过驻留并截获一些有用的中断来实现某种特定目的的程序，我们通常称之为TSR（终止并等待驻留terminate-and-stay-resident，此种程序不容易编好，需要大量的关于硬件和Dos中断的知识，还要解决Dos重入，tsr程序重入等问题，搞不好就会当机）。在WINDOWS下要实现实时监控决非易事，普通用户态程序是不可能监控系统的活动的，这也是出于系统安全的考虑。HPS病毒能在用户态下直接监控系统中的文件操作其实是由于WIN9X在设计上存在漏洞。而我们下面要讨论的两个病毒实时监控（For WIN9X&amp;WINNT/2000）都使用了驱动编程技术，让工作于系统核心态的驱动程序去拦截所有的文件访问。当然由于工作系统的不同，这两个驱动程序无论从结构还是工作原理都不尽相同的，当然程序写法和编译环境更是千差万别了，所以我们决定将其各自分成独立的一节来详细地加以讨论。上面提到的病毒实时监控其实就是对文件的监控，说成是文件监控应该更为合理一些。除了文件监控外，还有各种各样的实时监控工具，它们也都具有各自不同的特点和功用。这里向大家推荐一个关于WINDOWS系统内核编程的站点:www.sysinternals.com。在其上可以找到很多实时监控小工具，比如能够监视注册表访问的Regmon（通过修改系统调用表中注册表相关服务入口），可以实时地观察TCP和UDP活动的Tdimon（通过hook系统协议驱动Tcpip.sys中的dispatch函数来截获tdi clinet向其发送的请求），这些工具对于了解系统内部运作细节是很有裨益的。介绍完有关的背景情况后，我们来看看关于病毒 实时监控的具体实现技术的情况。 <br /><br /><b><a name="2">3.2病毒实时监控实现技术概论</a></b><br /><br />正如上面提到的病毒实时监控其实就是一个文件监视器，它会在文件打开，关闭，清除，写入等操作时检查文件是否是病毒携带者，如果是则根据用户的决定选择不同的处理方案，如清除病毒，禁止访问该文件，删除该文件或简单地忽略。这样就可以有效地避免病毒在本地机器上的感染传播，因为可执行文件装入器在装入一个文件执行时首先会要求打开该文件，而这个请求又一定会被实时监控在第一时间截获到，它确保了每次执行的都是干净的不带毒的文件从而不给病毒以任何执行和发作的机会。以上说的仅是病毒实时监控一个粗略的工作过程，详细的说明将留到后面相应的章节中。病毒实时监控的设计主要存在以下几个难点： <br /><br />其一是驱动程序的编写不同于普通用户态程序的写作，其难度很大。写用户态程序时你需要的仅仅就是调用一些熟知的API函数来完成特定的目的，比如打开文件你只需调用CreateFile就可以了；但在驱动程序中你将无法使用熟悉的CreateFile。在NT/2000下你可以使用ZwCreateFile或NtCreateFile（native API），但这些函数通常会要求运行在某个IRQL（中断请求级）上，如果你对如中断请求级，延迟/异步过程调用，非分页/分页内存等概念不是特别清楚，那么你写的驱动将很容易导致蓝屏死机（BSOD），Ring0下的异常将往往导致系统崩溃，因为它对于系统总是被信任的，所以没有相应处理代码去捕获这个异常。在NT下对KeBugCheckEx的调用将导致蓝屏的出现，接着系统将进行转储并随后重启。另外驱动程序的调试不如用户态程序那样方便，用象VC++那样的调试器是不行的，你必须使用系统级调试器，如softice,kd,trw等。 <br /><br />其二是驱动程序与ring3下客户程序的通信问题。这个问题的提出是很自然的，试想当驱动程序截获到某个文件打开请求时，它必须通知位于ring3下的查毒模块检查被打开的文件，随后查毒模块还需将查毒的结果通过某种方式传给ring0下的监控程序，最后驱动程序根据返回的结果决定请求是否被允许。这里面显然存在一个双向的通信过程。写过驱动程序的人都知道一个可以用来向驱动程序发送设备I/O控制信息的API调用DeviceIoControl，它的接口在MSDN中可以找到，但它是单向的，即ring3下客户程序可以通过调用DeviceIoControl将某些信息传给ring0下的监控程序但反过来不行。既然无法找到一个现成的函数实现从ring0下的监控程序到ring3下客户程序的通信，则我们必须采用迂回的办法来间接做到这一点。为此我们必须引入异步过程调用（APC）和事件对象的概念，它们就是实现特权级间唤醒的关键所在。现在先简单介绍一下这两个概念，具体的用法请参看后面的每子章中的技术实现细节。异步过程调用是一种系统用来当条件合适时在某个特定线程的上下文中执行一个过程的机制。当向一个线程的APC队列排队一个APC时，系统将发出一个软件中断，当下一次线程被调度时，APC函数将得以运行。APC分成两种：系统创建的APC称为内核模式APC，由应用程序创建的APC称为用户模式APC。另外只有当线程处于可报警（alertable）状态时才能运行一个APC。比如调用一个异步模式的ReadFileEx时可以指定一个用户自定义的回调函数FileIOCompletionRoutine，当异步的I/O操作完成或被取消并且线程处于可报警状态时函数被调用，这就是APC的典型用法。Kernel32.dll中导出的QueueUserAPC函数可以向指定线程的队列中增加一个APC对象，因为我们写的是驱动程序，这并不是我们要的那个函数。很幸运的是在Vwin32.vxd中导出了一个同名函数QueueUserAPC，监控程序拦截到一个文件打开请求后，它马上调用这个服务排队一个ring3下客户程序中需要被唤醒的函数的APC，这个函数将在不久客户程序被调度时被调用。这种APC唤醒法适用于WIN9X，在WINNT/2000下我们将使用全局共享的事件和信号量对象来解决互相唤醒问题。有关WINNT/2000下的对象组织结构我将在3.4.2节中详细说明。NT/2000版监控程序中我们将利用KeReleaseSemaphore来唤醒一个在ring3下客户程序中等待的线程。目前不少反病毒软件已将驱动使用的查毒模块移到ring0，即如其所宣传的“主动与操作系统无缝连接”，这样做省却了通信的消耗，但把查毒模块写成驱动形式也同时会带来一些麻烦，如不能调用大量熟知的API，不能与用户实时交互，所以我们还是选择剖析传统的反病毒软件的监控程序。 <br /><br />其三是驱动程序所占用资源问题。如果由于监控程序频繁地拦截文件操作而使系统性能下降过多，则这样的程序是没有其存在的价值的。本论文将对一个成功的反病毒软件的监控程序做彻底的剖析，其中就包含有分析其用以提高自身性能的技巧的部分，如设置历史记录，内置文件类型过滤，设置等待超时等。 <br /><br /><b><a name="3">3.3WIN9X下的病毒实时监控</a></b><br /><br /><a name="31">3.3.1实现技术详解</a><br /><br />WIN9X下病毒实时监控的实现主要依赖于虚拟设备驱动（VXD）编程，可安装文件系统钩挂（IFSHook），VXD与ring3下客户程序的通信（APC/EVENT）三项技术。 <br /><br />我们曾经提到过只有工作于系统核心态的驱动程序才具有有效地完成拦截系统范围文件操作的能力，VXD就是适用于WIN9X下的虚拟设备驱动程序，所以正可当此重任。当然，VXD的功能远不止由IFSMGR.vxd提供的拦截文件操作这一项，系统的VXDs几乎提供了所有的底层操作的接口--可以把VXD看成ring0下的DLL。虚拟机管理器本身就是一个VXD，它导出的底层操作接口一般称为VMM服务，而其他VXD的调用接口则称为VXD服务。 <br /><br />二者ring0调用方法均相同，即在INT20（CD 20）后面紧跟着一个服务识别码，VMM会利用服务识别码的前半部分设备标识--Device Id找到对应的VXD，然后再利用服务识别码的后半部分在VXD的服务表（Service Table）中定位服务函数的指针并调用之： <br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>CD 20 INT 20H
01 00 0D 00 DD VKD_Define_HotKey</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />这条指令第一次执行后，VMM将以一个同样6字节间接调用指令替换之（并不都是修正为CALL指令，有时会利用JMP指令），从而省却了查询服务表的工作： <br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>FF 15 XX XX XX XX CALL [$VKD_Define_HotKey]</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />必须注意，上述调用方法只适用于ring0，即只是一个从VXD中调用VXD/VMM服务的ring0接口。VXD还提供了V86（虚拟8086模式），Win16保护模式，Win32保护模式调用接口。其中V86和Win16保护模式的调用接口比较奇怪： <br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>XOR DI DI
  MOV ES,DI
  MOV AX,1684 ；INT 2FH,AX = 1684H--&gt;取得设备入口
  MOV BX,002A ；002AH = VWIN32.VXD的设备标识
  INT 2F
  MOV AX,ES ；现在ES:DI中应该包含着入口
  OR AX,AX
  JE failure
MOV AH,00 ；VWIN32 服务 0 = VWIN32_Get_Version
  PUSH DS
  MOV DS,WORD PTR CS:[0002]
  
  MOV WORD PTR [lpfnVMIN32],DI 
  MOV WORD PTR [lpfnVMIN32+2],ES ；保存ES和DI
  CALL FAR [lpfnVMIN32] ；call gate（调用门）
  ES:DI指向了3B段的一个保护模式回调： 
003B：000003D0 INT 30 ；#0028:C025DB52 VWIN32（04）+0742</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />INT30强迫CPU从ring3提升到ring0，然后WIN95的INT30处理函数先检查调用是否发自3B段，如是则利用引发回调的CS:IP索引一个保护模式回调表以求得一个ring0地址。本例中是0028:C025DB52 ，即所需服务VWIN32_Get_Version的入口地址。 <br /><br />VXD的Win32保护模式调用接口我们在前面已经提到过。一个是DeviceIoControl，我们的ring3客户程序利用它来和监控驱动进行单向通信；另一个是VxdCall，它是Kernel32.dll的一个未公开的调用，被系统频繁使用，对我们则没有多大用处。 <br /><br />你可以参看WIN95DDK的帮助，其中对每个系统VXD提供的调用接口均有详细说明，可按照需要选择相应的服务。 <br /><br />可安装文件系统钩挂（IFSHook）就源自IFSMGR.VXD提供的一个服务IFSMgr_InstallFileSystemApiHook，利用这个服务驱动程序可以向系统注册一个钩子函数。以后系统中所有文件操作都会经过这个钩子的过滤，WIN9X下文件读写具体流程如下： <br /><br />在读写操作进行时，首先通过未公开函数EnterMustComplete来增加MUSTCOMPLETECOUNT变量的记数，告诉操作系统本操作必须完成。该函数设置了KERNEL32模块里的内部变量来显示现在有个关键操作正在进行。有句题外话，在VMM里同样有个函数，函数名也是EnterMustComplete。那个函数同样告诉VMM，有个关键操作正在进行。防止线程被杀掉或者被挂起。 <br /><br />接下来，WIN9X进行了一个_MapHandleWithContext(又是一个未公开函数)操作。该操作本身的具体意义尚不清楚，但是其操作却是得到HANDLE所指对象的指针，并且增加了引用计数。 <br /><br />随后，进行的乃是根本性的操作：KERNEL32发出了一个调用VWIN32_Int21Dispatch的VxdCall。陷入VWIN32后，其 检查调用是否是读写操作。若是，则根据文件句柄切换成一个FSD能识别的句柄，并调用IFSMgr_Ring0_FileIO。接下来任务就转到了IFS MANAGER。 <br /><br />IFS MANAGER生成一个IOREQ，并跳转到Ring0ReadWrite内部例程。Ring0ReadWrite检查句柄有效性，并且获取FSD在创建文件句柄时返回的CONTEXT，一起传入到CallIoFunc内部例程。CallIoFunc检查IFSHOOK的存在，如果不存在，IFS MANAGER生成一个缺省的IFS HOOK，并且调用相应的VFatReadFile/VFatWriteFile例程(因为目前 MS本身仅提供了VFAT驱动)；如果IFSHOOK存在，则IFSHOOK函数得到控制权，而IFS MANAGER本身就脱离了文件读写处理。然后，调用被层层返回。KERNEL32调用未公开函数LeaveMustComplete，减少MUSTCOMPLETECOUNT计数，最终回到调用者。 <br /><br />由此可见通过IFSHook拦截本地文件操作是万无一失的，而通过ApiHook或VxdCall拦截文件则多有遗漏。著名的CIH病毒正是利用了这一技术，实现其驻留感染的，其中的代码片段如下： <br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>lea eax, FileSystemApiHook-@6[edi] ；取得欲安装的钩子函数的地址
  push eax 
  int 20h ；调用IFSMgr_InstallFileSystemApiHook
  IFSMgr_InstallFileSystemApiHook = $
  dd 00400067h 
  mov dr0, eax ；保存前一个钩子的地址
  pop eax</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />正如我们看到的，系统中安装的所有钩子函数呈链状排列。最后安装的钩子，最先被系统调用。我们在安装钩子的同时必须将调用返回的前一个钩子的地址暂存以便在完成处理后向下传递该请求： <br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>mov eax, dr0 ；取得前一个钩子的地址
jmp [eax] ； 跳到那里继续执行</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />对于病毒实时监控来说，我们在安装钩子时同样需要保存前一个钩子的地址。如果文件操作的对象携带了病毒，则我们可以通过不调用前一个钩子来简单的取消该文件请求；反之，我们则需及时向下传递该请求，若在钩子中滞留的时间过长--用于等待ring3级查毒模块的处理反馈--则会使用户明显感觉系统变慢。 <br /><br />至于钩子函数入口参数结构和怎样从参数中取得操作类型（如IFSFN_OPEN）和文件名（以UNICODE形式存储）请参看相应的代码剖析部分。 <br /><br />我们所需的另一项技术--APC/EVENT也是源自一个VXD导出的服务，这便是著名的VWIN32.vxd。这个奇怪的VXD导出了许多与WIN32 API对应的服务：如_VWIN32_QueueUserApc，_VWIN32_WaitSingleObject，_VWIN32_ResetWin32Event，_VWIN32_Get_Thread_Context，_VWIN32_Set_Thread_Context 等。这个VXD叫虚拟WIN32，大概名称即是由此而来的。虽然服务的名称与WIN32 API一样，但调用规则却大相径庭，千万不可用错。_VWIN32_QueueUserApc用来注册一个用户态的APC，这里的APC函数当然是指我们在ring3下以可告警状态睡眠的待查毒线程。ring3客户程序首先通过IOCTL把待查毒线程的地址传给驱动程序，然后当钩子函数拦截到待查文件时调用此服务排队一个APC，当ring3客户程序下一次被调度时，APC例程得以执行。_VWIN32_WaitSingleObject则用来在某个对象上等待，从而使当前ring0线程暂时挂起。我们的ring3客户程序先调用WIN32 API--CreateEvent创建一组事件对象，然后通过一个未公开的API--OpenVxdHandle将事件句柄转化为VXD可辩识的句柄（其实应是指向对象的指针）并用IOCTL发给ring0端VXD，钩子函数在排队APC后调用_VWIN32_WaitSingleObject在事件的VXD句柄上等待查毒的完成，最后由ring3客户程序在查毒完毕后调用WIN32 API--SetEvent来解除钩子函数的等待。 <br /><br />当然，这里面存在着一个很可怕的问题：如果你按照的我说的那样去做，你会发现它会在一端时间内工作正常，但时间一长，系统就被挂起了。就连驱动编程大师Walter Oney在其著作《System Programming For Windows 95》的配套源码的说明中也称其APC例程在某些时候工作会不正常。而微软的工程师声称文件操作请求是不能被中断掉的，你不能在驱动中阻断文件操作并依赖于ring3的反馈来做出响应。网上关于这个问题也有一些讨论，意见不一：有人认为当系统DLL--KERNEL32在其调用ring0处理文件请求时拥有一个互斥量（MUTEX），而在某些情况下为了处理APC要拥有同样的互斥量，所以死锁发生了；还有人认为尽管在WIN9X下32位线程是抢先多任务的，但Win16子系统是以协作多任务来运行的。为了能平滑的运行老的16位程序，它引入了一个全局的互斥量--Win16Mutex。任何一个16位线程在其整个生命周期中都拥有Win16Mutex而32位线程当它转化成16位代码也要攫取此互斥量，因为WIN9X内核是16位的,如Knrl386.exe,gdi.exe。如果来自于拥有Win16Mutex的线程的文件请求被阻塞，系统将陷入死锁状态。这个问题的正确答案似乎在没有得到WIN9X源码的之前永远不可能被证实，但这是我们实时监控的关键，所以必须解决。 <br /><br />我通过跟踪WIN95文件操作的流程，并反复做实验验证，终于找到了一个比较好的解决办法：在拦截到文件请求还没有排队APC之前我们通过Get_Cur_Thread_Handle取得当前线程的ring0tcb，从中找到TDBX，再在TDBX中取得ring3tcb根据其结构，我们从偏移44H处得到Flags域值，我发现如果它等于10H和20H时容易导致死锁，这只是一个实验结果，理由我也说不清楚，大概是这样的文件请求多来自于拥有Win16Mutex的线程，所以不能阻塞；另外一个根本的解决方法是在调用_VWIN32_WaitSingleObject时指定超时，如果在指定时间里没有收到ring3的唤醒信号，则自动解除等待以防止死锁的发生。 <br /><br />以上对WIN9X下的实时监控的主要技术都做了详细的阐述。当然，还有一部分关于VXD的结构，编写和编译的方法由于篇幅的关系不可能在此一一说明。需要了解更详细内容的，请参看Walter Oney的著作《System Programming For Windows 95》，此书尚有台湾候俊杰翻译版《Windows 95系统程式设计》。 <br /><br /><a name="32">3.3.2程序结构与流程</a><br /><br />以下的程序结构与流程分析来自一著名反病毒软件的WIN9X实时监控虚拟设备驱动程序Hooksys.vxd： <br /><br />1.当VXD收到来自VMM的ON_SYS_DYNAMIC_DEVICE_INIT消息--需要注意这是个动态VXD，它不会收到系统虚拟机初始化时发送的Sys_Critical_Init, Device_Init和Init_Complete控制消息--时，它开始初始化一些全局变量和数据结构，包括在堆上分配内存（HeapAllocate），创建备用，历史记录，打开文件，等待操作，关闭文件5个双向循环链表及用于链表操作互斥的5个信号量（调用Create_Semaphore），同时将全局变量_gNumOfFilters即文件名过滤项个数设置为0。 <br /><br />2.当VXD收到来自VMM的ON_W32_DEVICEIOCONTROL消息时，它会从入口参数中取得用户程序利用DeviceIoControl传送进来的IO控制代码（IOCtlCode），以此判断用户程序的意图。和Hooksys.vxd协同工作的ring3级客户程序guidll.dll会依次向Hooksys.vxd发送IO控制请求来完成一系列工作，具体次序和代码含义如下： <br /><br />83003C2B：将guidll取得的操作系统版本传给驱动（保存在iOSversion变量中），根据此变量值的不同，从ring0tcb结构中提取某些域时将采用不同的偏移，因为操作系统版本不同会影响内核数据结构。 <br /><br />83003C1B：初始化后备链表，将guidll传入的用OpenVxdHandle转换过的一组事件指针保存在每个链表元素中。 <br /><br />83003C2F：将guidll取得的驱动器类型值传给驱动（保存在DriverType变量中），根据此变量值的不同，调用VWIN32_WaitSingleObject设置不同的等待超时值，因为非固定驱动器的读写时间可能会稍长些。 <br /><br />83003C0F：保存guidll传送的用户指定的拦截文件的类型，其实这个类型过滤器在查毒模块中已存在，这里再设置显然是为了提高处理效率：它确保不会将非指定类型文件送到ring3级查毒模块，节省了通信的开销。经过解析的各文件类型过滤块指针将保存在_gaFileNameFilterArra数组中，同时更新过滤项个数_gNumOfFilters 变量的值。 <br /><br />83003C23：保存guidll中等待查杀打开文件的APC函数地址和当前线程KTHREAD指针。 <br /><br />83003C13：安装系统文件钩子，启动拦截文件操作的钩子函数FilemonHookProc的工作。 <br /><br />83003C27：保存guidll中等待查杀关闭文件的APC函数地址和当前线程KTHREAD指针。 <br /><br />83003C17：卸载系统文件钩子，停止拦截文件操作的钩子函数FilemonHookProc的工作。 <br /><br />以上列出的IO控制代码的发出是固定，而当钩子函数启动后，还会发出一些随机的控制代码： <br /><br />83003C07：驱动将打开文件链表的头元素即最先的请求打开的文件删除并插入到等待链表尾部，同时将元素的用户空间地址传送至ring3级等待查杀打开文件的APC函数中处理。 <br /><br />83003C0B：驱动将关闭文件链表的头元素即最先的请求关闭的文件删除并插入到备用链表尾部，同时将元素中的文件名串传送至ring3级等待查杀关闭文件的APC函数中处理 <br /><br />83003C1F：当查得关闭文件是病毒时，更新历史记录链表。 <br /><br />下面介绍钩子函数和guidll中等待查杀打开文件的APC函数协同工作流程，写文件和关闭文件的处理与之类似： <br /><br />当文件请求进入钩子函数FilemonHookProc后，它先从入口参数中取得被执行的函数的代号并判断其是否为打开操作（IFSFN_OPEN 24H），若非则马上将这个IRQ向下传递，即构造入口参数并调用保存在PrevIFSHookProc中前一个钩子函数；若是则程序流程转向打开文件请求的处理分支。分支入口处首先要判断当前进程是否是我们自己，若是则必须放过去，因为查毒模块中要频繁的进行文件操作，所以拦截来自自身的文件请求将导致严重的系统死锁。接下来是从堆栈参数中取得完整的文件路径名并通过保存的文件类型过滤阵列检查其是否在拦截类型之列，如通过则进一步检查文件是否是以下几个须放过的文件之一：SYSTEM.DAT，USER.DAT，\PIPE\。然后查找历史记录链表以确定该文件是否最近曾被检查并记录过，若在历史记录链表中找到关于该文件的记录并且记录未失效即其时间戳和当前系统时间之差不得大于1F4h，则可直接从记录中读取查毒结果。至此才进入真正的检查打开文件函数_RAVCheckOpenFile，此函数入口处先从备用，等待或关闭链表头部摘得一空闲元素（_GetFreeEntry）并填充之（文件路径名域等）。接着通过一内核未公开的数据结构中的值（ring3tcb-&gt;Flags）判断可否对该文件请求排队APC。如可则将空闲元素加入打开文件链表尾部并排队一个ring3级检查打开文件函数的APC。然后调用_VWIN32_WaitSingleObject在空闲元素中保存的一个事件对象上等待ring3查毒的完成。当钩子函数挂起不久后，ring3的APC函数得到执行：它会向驱动发出一IO控制码为83003C07的请求以取得打开文件链表头元素即保存最先提交而未决的文件请求，驱动可以将内核空间中元素的虚拟地址直接传给它而不必考虑将之重新映射。实际上由于WIN9X内核空间没有页保护因而ring3级程序可以直接读写之。接着它调用RsEngine.dll中的fnScanOneFile函数进行查毒并在元素中设置查毒结果位，完毕后再对元素中保存的事件对象调用SetEvent唤醒在此事件上等待的钩子函数。被唤醒的钩子函数检查被ring3查毒代码设置的结果位以此决定该文件请求是被采纳即继续向下传递还是被取消即在EAX中放入-1后直接返回，同时增加历史记录。 <br /><br />以上只是钩子函数与APC函数流程的一个简单介绍，其中省略了诸如判断固定驱动器，超时等内容，具体细节请参看guidll.dll和hooksys.vxd的反汇编代码注释。 <br /><br />3.当VXD收到来自VMM的ON_SYS_DYNAMIC_DEVICE_EXIT消息时，它释放初始化时分配的堆内存（HeapFree），并清除5个用于互斥的信号量（Destroy_Semaphore）。 <br /><br /><a name="33">3.3.3HOOKSYS.VXD逆向工程代码剖析</a><br /><br />在剖析代码之前有必要介绍一下逆向工程的概念。逆向工程（Reverse Engineering）是指在没有源代码的情况下对可执行文件进行反汇编试图理解机器码本身的含义。逆向工程的用途很多，如摘掉软件保护，窥视其设计和编写技术，发掘操作系统内部奥秘等。本文中我们用到的不少未公开数据结构和服务就是利用逆向的方法得到的。逆向工程的难度可想而知：一个1K大小的exe文件反汇编后就有1000行左右，而我们要逆向的3个文件加起来有80多K，总代码量是8万多行。所以必须掌握一定的逆向技巧，否则工作起来将是非常困难的。 <br /><br />首先要完成逆向工作，必须选择优秀的反汇编及调试跟踪工具。IDA（The Interactive Disassembler）是一款功能强大的反汇编工具：它以交互能力强而著称，允许使用者增加标签，注释及定义变量，函数名称；另外不少反汇编工具对于特殊处理的反逆向文件，如导入节损坏等显得无能为力，但IDA仍可胜任之。当文件被加过壳或插入了干扰指令时 就需要使用调试工具进行动态跟踪。Numega公司的Softice是调试工具中的佼佼者：它支持所有类型的可执行文件，包括vxd和sys驱动程序，能够用热键实时呼出，可对代码执行，内存和端口访问设置断点，总之功能非常之强大以至于连微软总裁比尔盖茨对此都惊叹不已。 <br /><br />其次需要对编译器常用的编译结构有一定了解，这样有助于我们理解代码的含义。 <br /><br />如下代码是MS编译器常用的一种编译高级语言函数的形式： <br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>0001224A push ebp ；保存基址寄存器
  0001224B mov ebp, esp
  0001224D sub esp, 5Ch ；在堆栈留出局部变量空间
  00012250 push ebx
  00012251 push esi
  00012252 push edi
  ......
  0001225B lea edi, [ebp-34h] ；引用局部变量
  ......
  0001238D mov esi, [ebp+08h] ；引用参数
  ......
  00012424 pop edi
  00012425 pop esi
  00012426 pop ebx
  00012427 leave
  00012428 retn 8 ；函数返回
  如下代码是MS编译器常用的一种编译高级语言取串长度的形式：
  0001170D lea edi, [eax+1Ch] ；串首地址指针 
  00011710 or ecx, 0FFFFFFFFh ；将ecx置为-1
  00011713 xor eax, eax ；扫描串结束符号（NULL） 
  00011715 push offset 00012C04h ；编译器优化 
  0001171A repne scasb ；扫描串结束符号位置 
  0001171C not ecx ；取反后得到串长度 
  0001171E sub edi, ecx ；恢复串首地址指针</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />最后一点是必须要有坚忍的毅力和清晰的头脑。逆向工程本身是件痛苦的工作：高级语言源代码中使用的变量和函数名字在这里仅是一个地址，需要反复调试琢磨才能确定其含义；另外编译器优化更为我们理解代码增加了不少障碍，如上例中那句压栈指令是将后面函数调用时参数入栈提前放置。所以毅力和头脑二者缺一不可。 <br /><br />以下进入hooksys.vxd代码剖析，由于代码过于庞大，我只选择有代表性且精彩的部分进行介绍。代码中的变量和函数及标签名是我分析后自己添加的，可能会与原作者的意图有些出入。 <br /><br /><a name="331">3.3.3.1钩子函数入口代码</a><br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>C00012E0 push ebp
  C00012E1 mov ebp, esp
  C00012E3 sub esp, 11Ch
  C00012E9 push ebx
  C00012EA push esi
  C00012EB push edi
  C00012EC mov eax, [ebp+arg_4] ； 被执行的函数的代号
  C00012EF mov [ebp+var_11C], eax
  C00012F5 cmp [ebp+var_11C], 1 ； IFSFN_WRITE
  C00012FC jz writefile
  C0001302 cmp [ebp+var_11C], 0Bh ； IFSFN_CLOSE
  C0001309 jz closefile
  C000130F cmp [ebp+var_11C], 24h ； IFSFN_OPEN
  C0001316 jz short openfile
  C0001318 jmp irqpassdown
  钩子函数入口处，堆栈参数分布如下：
  ebp+00h -&gt; 保存的EBP值.
  ebp+04h -&gt; 返回地址.
  ebp+08h -&gt; 提供这个API要调用的FSD函数的的地址
  ebp+0Ch -&gt; 提供被执行的函数的代号
  ebp+10h -&gt; 提供了操作在其上执行的以1为基准的驱动器代号（如果UNC为-1)
  ebp+14h -&gt; 提供了操作在其上执行的资源的种类。
  ebp+18h -&gt; 提供了用户串传递其上的代码页
  ebp+1Ch -&gt; 提供IOREQ结构的指针。</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />钩子函数利用[ebp+0Ch]中保存的被执行的函数的代号来判断该请求的类型。同时它利用[ebp+0Ch]中保存的IOREQ结构的指针从该结构中偏移0ch处path_t ir_ppath域取得完整的文件路径名称。 <br /><br /><a name="332">3.3.3.2取得当前进程名称代码</a><br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>C0000870 push ebx
  C0000871 push esi
  C0000872 push edi
  C0000873 call VWIN32_GetCurrentProcessHandle ；在eax中返回ring0 PDB（进程数据库）
  C0000878 mov eax, [eax+38h] ；HTASK W16TDB 
  ；偏移38h处是Win16任务数据库选择子 
  C000087B push 0 ；DWORD Flags
  C000087D or al, 
  C000087F push eax ；DWORD Selector
  C0000880 call Get_Sys_VM_Handle@0 
  C0000885 push eax ；取得系统VM的句柄 VMHANDLE hVM
  C0000886 call _SelectorMapFlat ；将选择子基址映射为平坦模式的线形地址
  C000088B add esp, 0Ch
  C000088E cmp eax, 0FFFFFFFFh ；映射错误
  C0000891 jnz short loc_C0000899 
  ......
  C0000899 lea edi, [eax+0F2h] ；从偏移0F2h取得模块名称
  ；char TDB_ModName[8]</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br /><a name="333">3.3.3.3通信部分代码</a><br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>hooksys.vxd中代码：
C00011BC push ecx ；客户程序的ring0线程句柄
  C00011BD push ebx ；传入APC的参数
  C00011BE push edx ；ring3级APC函数的平坦模式地址
  C00011BF call _VWIN32_QueueUserApc ；排队APC
  C00011C4 mov eax, [ebp+0Ch] ；事件对象的ring0句柄
  C00011C7 push eax
  C00011C8 call _VWIN32_ResetWin32Event；设置事件对象为无信号态 
  ......
  C00011E7 mov eax, [ebp+0Ch]
  C00011EA push 3E8h ；超时设置
  C00011EF push eax ；事件对象的ring0句柄
  C00011F0 call _VWIN32_WaitSingleObject ；等待ring3查毒的完成
  guidll.dll中代码：

  APC函数入口：
  10001AD1 mov eax, hDevice ；取得设备句柄
  10001AD6 lea ecx, [esp+4]
  10001ADA push 0 
  10001ADC push ecx ；返回字节数
  10001ADD lea edx, [esp+8]
  10001AE1 push 4 ；输出缓冲区大小
  10001AE3 push edx ；输出缓冲区指针
  10001AE4 push 0 ；输入缓冲区大小
  10001AE6 push 0 ；输入缓冲区指针
  10001AE8 push 83003C07h ；IO控制代码
  10001AED push eax ；设备句柄
  10001AEE call ds:DeviceIoControl
  10001AF4 test eax, eax
  10001AF6 jz short loc_10001B05
  10001AF8 mov ecx, [esp+0] ；得到打开文件链表头元素
  10001AFC push ecx
  10001AFD call ScanOpenFile ；调用查毒函数
  ScanOpenFile函数中： 

  1000185D call ds:fnScanOneFile ；调用真正查毒库导出函数
  10001863 mov edx, hMutex
  10001869 add esp, 8
  1000186C mov esi, eax ；查毒结果
  1000186E push edx 
  1000186F call ds:ReleaseMutex
  10001875 test esi, esi ；检查结果
  10001877 jnz short OpenFileIsVirus ；如发现病毒则跳到OpenFileIsViru进一步处理
  10001879 mov eax, [ebp+10h] ；事件对象的ring3句柄
  1000187C mov byte ptr [ebp+16h], 0 ；设置元素中的结果位为无病毒
  10001880 push eax
  10001881 call ds:SetEvent ；设置事件对象为有信号态唤醒钩子函数</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br /><b><a name="4">3.4 WINNT/2000下的病毒实时监控</a></b><br /><br /><a name="41">3.4.1实现技术详解</a><br /><br />WINNT/2000下病毒实时监控的实现主要依赖于NT内核模式驱动编程，拦截IRP，驱动与ring3下客户程序的通信（命名的事件与信号量对象）三项技术。程序的设计思路和大体流程与前面介绍的WIN9X下病毒实时监控非常相似，只是在实现技术由于运行环境的不同将呈现很大的区别。 <br /><br />WINNT/2000下不再支持VXD，我将在后面剖析的hooksys.sys其实是一种称为NT内核模式设备驱动的驱动程序。这种驱动程序无论从其结构还是工作方式都与VXD有很大不同。比较而言，NT内核模式设备驱动的编写比VXD难度更大：因为它要求编程者熟悉WINNT/2000的整体架构和运行机制，NT/2000是纯32位微内核操作系统，与WIN9X有很大区别；能灵活使用内核数据结构，如驱动程序对象，设备对象，文件对象，IO请求包，执行体进程/线程块，系统服务调度表等。另外编程者在编程时还需注意许多重要事项，如当前系统运行的IO请求级，分页/非分页内存等。 <br /><br />这里首先介绍几个重要的内核数据结构，它们在NT内核模式设备驱动的编程中经常被用到，包括文件对象，驱动程序对象，设备对象，IO请求包（IRP），IO堆栈单元（IO_STACK_LOCATION）： <br /><br />文件明显符合NT中的对象标准：它们是两个或两个以上用户态进程的线程可以共享的系统资源；它们可以有名称；它们被基于对象的安全性所保护；并且它们支持同步。对于用户态受保护的子系统，文件对象通常代表一个文件，设备目录，或卷的打开实例；而对于设备和中间型驱动，文件对象通常代表一个设备。文件对象结构中的域大部分是透明的驱动可以访问的域包括： <br /><br />PDEVICE_OBJECT DeviceObject：指向文件于其上被打开的设备对象的指针。 <br /><br />UNICODE_STRING FileName：在设备上被打开的文件的名字，如果当由DeviceObject代表的设备被打开时此串长度（FileName.Length）为0。 <br /><br />驱动程序对象代表可装载的内核模式驱动的映象，当驱动被加载至系统中时，有I/O管理器负责创建。指向驱动程序对象的指针将作为一个输入参数传送到驱动的初始化例程（DriverEntry），再初始化例程（Reinitialize routines）和卸载例程（Unload routine）。驱动程序对象结构中的域大部分是透明的，驱动可以访问的域包括： <br /><br />PDEVICE_OBJECT DeviceObject：指向驱动创建的设备对象的指针。当在初始化例程中成功调用IoCreateDevice后这个域将被自动更新。当驱动卸载时，它的卸载例程将使用此域和设备对象中NextDevice域调用IoDeleteDevice来清除驱动创建的每个设备对象。 <br /><br />PDRIVER_INITIALIZE DriverInit：由I/O管理器设置的初始化例程（DriverEntry）入口地址。该例程负责创建驱动程序操作的每个设备的设备对象，需要的话还可以在设备名称和设备对用户态可见名称间创建符号链接。同时它还把驱动程序各例程入口点填入驱动程序对象相应的域中。 <br /><br />PDRIVER_UNLOAD DriverUnload：驱动程序的卸载例程入口地址。 <br /><br />PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION+1]：一个或多个驱动程序调度例程入口地址数组。每个驱动必须在此数组中为驱动处理的IRP_MJ_XXX请求集设置至少一个调度入口，这样所有的IRP_MJ_XXX请求都会 被I/O管理器导入同一个调度例程。当然，驱动程序也可以为每个IRP_MJ_XXX请求设置独立的调度入口。 <br /><br />当然，驱动程序中可能包含的例程将远不止以上列出的。比如启动I/O例程，中断服务例程（ISR），中断服务DPC例程，一个或多个完成例程，取消I/O例程，系统关闭通知例程，错误记录例程。只不过我们将要剖析的hooksys.sys中只用到例程中很少一部分，故其余的不予详细介绍。 <br /><br />设备对象代表已装载的驱动程序为之处理I/O请求的一个逻辑，虚拟或物理设备。每个NT内核模式驱动程序必须在它的初始化例程中一次或多次调用IoCreateDevice来创建它支持的设备对象。例如tcpip.sys在其DriverEntry中就创建了3个共用此驱动的设备对象：Tcp，Udp，Ip。目前有一种比较流行的称为WDM（Windows Driver Model）的驱动程序，在大多数情况下，其二进制映像可以兼容WIN98和WIN2000(32位版本)。WDM与NT内核模式驱动程序的主要区别在于如何创建设备：在WDM驱动程序中，即插即用（PnP）管理器告知何时向系统中添加一个设备，或者从系统中删除设备。WDM驱动程序有一个特殊的AddDevice例程，PnP管理器为共用该驱动的每个设备实例调用该函数；而NT内核模式驱动程序需要做大量额外的工作，它们必须探测自己的硬件，为硬件创建设备对象(通常在DriverEntry中)，配置并初始化硬件使其正常工作。设备程序对象结中的域大部分是透明的，驱动可以访问的域包括： <br /><br />PDRIVER_OBJECT DriverObject：指向代表驱动程序装载映象的驱动程序对象的指针。 <br /><br />所有I/O都是通过I/O请求包（IRP）驱动的。所谓IRP驱动，是指I/O管理器负责在系统的非分页内存中分配一定的空间，当接受用户发出的命令或由事件引发后，将工作指令按一定的数据结构置于其中并传递到驱动程序的服务例程。换言之，IRP中包含了驱动程序的服务例程所需的信息指令。IRP有两部分组成：固定部分（称为标题）和一个或多个堆栈单元。固定部分信息包括：请求的类型和大小，是同步请求还是异步请求，用于缓冲I/O的指向缓冲区的指针和由于请求的进展而变化的状态信息。 <br /><br />PMDL MdlAddress：指向一个内存描述符表(MDL)，该表描述了一个与该请求关联的用户模式缓冲区。如果顶级设备对象的Flags域为DO_DIRECT_IO，则I/O管理器为IRP_MJ_READ或IRP_MJ_WRITE请求创建这个MDL。如果一个IRP_MJ_DEVICE_CONTROL请求的控制代码指定METHOD_IN_DIRECT或METHOD_OUT_DIRECT操作方式，则I/O管理器为该请求使用的输出缓冲区创建一个MDL。MDL本身用于描述用户模式虚拟缓冲区，但它同时也含有该缓冲区锁定内存页的物理地址。 <br /><br />PVOID AssociatedIrp.SystemBuffer：SystemBuffer指针指向一个数据缓冲区，该缓冲区位于内核模式的非分页内存中于IRP_MJ_READ和IRP_MJ_WRITE操作，如果顶级设备指定DO_BUFFERED_IO标志I/O管理器就创建这个数据缓冲区。对于IRP_MJ_DEVICE_CONTROL操作，如果I/O控制功能代码指出需要缓冲区，则I/O管理器就创建这个数据缓冲区。I/O管理器把用户模式程序发送给驱动程序的数据复制到这个缓冲区，这也是创建IRP过程的一部分。这些数据可以是与WriteFile调用有关的数据，或者是DeviceIoControl调用中所谓的输入数据。对于读请求，设备驱动程序把读出的数据填到这个缓冲区，然后I/O管理器再把缓冲区的内容复制到用户模式缓冲区。对于指定了METHOD_BUFFERED的I/O控制操作，驱动程序把所谓的输出数据放到这个缓冲区， 然后I/O管理器再把数据复制到用户模式的输出缓冲区。 <br /><br />IO_STATUS_BLOCK IoStatus：IoStatus(IO_STATUS_BLOCK)是一个仅包含两个域的结构，驱动程序在最终完成请求时设置这个结构。IoStatus.Status域将收到一个NTSTATUS代码。 <br /><br />PVOID UserBuffer：对于METHOD_NEITHER方式的IRP_MJ_DEVICE_CONTROL请求，该域包含输出缓冲区的用户模式虚拟地址。该域还用于保存读写请求缓冲区的用户模式虚拟地址，但指定了DO_BUFFERED_IO或DO_DIRECT_IO标志的驱动程序，其读写例程通常不需要访问这个域。当处理一个METHOD_NEITHER控制操作时，驱动程序能用这个地址创建自己的MDL。 <br /><br />任何内核模式程序在创建一个IRP时，同时还创建了一个与之关联的IO_STACK_LOCATION结构数组：数组中的每个堆栈单元都对应一个将处理该IRP的驱动程序，另外还有一个堆栈单元供IRP的创建者使用。堆栈单元中包含该IRP的类型代码和参数信息以及完成函数的地址。 <br /><br />UCHAR MajorFunction：该IRP的主功能码。这个代码应该为类似IRP_MJ_READ一样的值，并与驱动程序对象中MajorFunction表的某个派遣函数指针相对应。 <br /><br />UCHAR MinorFunction：该IRP的副功能码。它进一步指出该IRP属于哪个主功能类。 <br /><br />PDEVICE_OBJECT DeviceObject：与该堆栈单元对应的设备对象的地址。该域由IoCallDriver函数负责填写。 <br /><br />PFILE_OBJECT FileObject：内核文件对象的地址，IRP的目标就是这个文件对象。 <br /><br />下面简要介绍一下WINNT/2000下I/O请求处理流程。先看对单层驱动程序的同步的I/O请求：I/O请求经过子系统DLL子系统DLL调用I/O管理器中相应的服务。I/O管理器以IRP的形式给设备驱动程序发送请求。驱动程序启动I/O操作。在设备完成了操作并且中断CPU时，设备驱动程序服务于中断。最后I/O管理器完成I/O请求。以上六步只是一个非常粗略的描述，其中的中断处理和I/O完成阶段比较复杂。 <br /><br />当设备完成了I/O操作后，它将发出中断请求服务。设备中断发生时，处理器将控制权交给内核陷阱处理程序，内核陷阱处理程序将在它的中断调度表（IDT）中定位用于设备的ISR。驱动程序的ISR例程获得控制权后，它通常只在设备IRQL上停留获得设备状态所必需的一段时间，然后停止设备中断，接着它排队一个DPC并清除中断退出操作。IRQL降低至Dispatch/DPC级之前，所有中间优先级中断因而可以得到服务。当DPC例程得到控制时，它将启动设备队列中下一个I/O请求，然后完成中断服务。 <br /><br />当驱动的DPC例程执行完后，在I/O请求可以考虑结束之前还有一些工作要做。如某些情况下，I/O系统必须将存储在系统内存中的数据复制到调用者的虚拟地址空间中，如将操作结果记录在调用者提供的I/O状态块中或执行缓冲I/O的服务将数据返回给调用线程。这样当DPC例程调用I/O管理器完成原始I/O请求后，I/O管理器会为调用线程调用线程排队一个核心态APC。当线程被调度执行时，挂起的APC被交付。它将把数据和返回状态复制到调用者的地址空间，释放代表I/O操作的IRP，并将调用者的文件句柄或调用者提供的事件或I/O完成端口设置为有信号状态。如果调用者用异步I/O函数ReadFileEx和WriteFileEx指定了用户态APC，则此时还需要将用户态APC排队。最后可以考虑完成I/O。在文件或其它对象句柄上等待的线程将被释放。 <br /><br />基于文件系统设备的I/O请求处理过程与此是基本相同的，主要区别在于增加一个或多个附加的处理层。例如读文件操作，用户应用程序调用子系统库Kernel32.dll中的API函数ReadFile，ReadFile接着调用系统库Ntdll.dll中的NtReadFile，NtReadFile通过一个陷入指令（INT2E）将处理器模式提升至ring0。然后Ntoskrnl.exe中的系统服务调度程序KiSystemService将在系统服务调度表中定位Ntoskrnl.exe中的NtWReadFile并调用之，同时解除中断。此服务例程是I/O管理器的一部分。它首先检查传递给它们的参数以保护系统安全或防止用户模式程序非法存取数据，然后创建一个主功能代码为IRP_MJ_READ的IRP，并将之送到文件系统驱动程序的入口点。以下的工作会由文件系统驱动程序与磁盘驱动程序分层来完成。文件系统驱动程序可以重用一个IRP或是针对单一的I/O请求创建一组并行工作的关联（associated）IRP。执行IRP的磁盘驱动程序最后可能会访问硬件。对于PIO方式的设备，一个IRP_MJ_READ操作将导致直接读取设备的端口或者是设备实现的内存寄存器。尽管运行在内核模式中的驱动程序可以直接与其硬件会话，但它们通常都使用硬件抽象层(HAL)访问硬件：读操作最终会调用Hal.dll中的READ_PORT_UCHAR例程来从某个I/O口读取单字节数据。 <br /><br />WINNT/2000下设备和驱动程序的有着明显堆栈式层次结构：处于堆栈最底层的设备对象称为物理设备对象，或简称为PDO，与其对应的驱动程序称为总线驱动程序。在设备对象堆栈的中间某处有一个对象称为功能设备对象，或简称FDO，其对应的驱动程序称为功能驱动程序。在FDO的上面和下面还会有一些过滤器设备对象。位于FDO上面的过滤器设备对象称为上层过滤器，其对应的驱动程序称为上层过滤器驱动程序；位于FDO下面(但仍在PDO之上)的过滤器设备对象称为下层过滤器，其对应的驱动程序称为下层过滤器驱动程序。这种栈式结构可以使I/O请求过程更加明了。每个影响到设备的操作都使用IRP。通常IRP先被送到设备堆栈的最上层驱动程序，然后逐渐过滤到下面的驱动程序。每一层驱动程序都可以决定如何处理IRP。有时，驱动程序不做任何事，仅仅是向下层传递该IRP。有时，驱动程序直接处理完该IRP，不再向下传递。还有时，驱动程序既处理了IRP，又把IRP传递下去。这取决于设备以及IRP所携带的内容。 <br /><br />通过上面的介绍可得知：如果我们想拦截系统的文件操作，就必须拦截I/O管理器发向文件系统驱动程序的IRP。而拦截IRP最简单的方法莫过于创建一个上层过滤器设备对象并将之加入文件系统设备所在的设备堆栈中。具体方法如下：首先通过IoCreateDevice创建自己的设备对象，然后调用IoGetDeviceObjectPointer来得到文件系统设备（Ntfs，Fastfat，Rdr或Mrxsmb，Cdfs）对象的指针，最后通过IoAttachDeviceToDeviceStack将自己的设备放到设备堆栈上成为一个过滤器。 <br /><br />这是拦截IRP最常用也是最保险的方法，Art Baker的《Windows NT设备驱动程序设计指南》中有详细介绍，但用它实现病毒实时监控却存在两个问题：其一这种方法是将过滤器放到堆栈的最上层，当存在其它上层过滤器时就不能保证过滤器正好在文件系统设备之上；其二由于过滤器设备需要表现的和文件系统设备一样，这样其所有特性都需从文件系统设备中复制。另外文件系统驱动对象中调度例程过滤器驱动必须都支持，这就意味着我们无法使过滤器驱动中的调度例程供自己的ring3级客户程序所专用，因为原本发往文件系统驱动调度例程的IRP现在都会先从过滤器驱动的调度例程中经过。 <br /><br />所以Hooksys.sys没有使用上述方法。它的方法更简单且更为直接：它先通过ObReferenceObjectByName得到文件系统驱动对象的指针。然后将驱动对象中MajorFunction数组中的打开，关闭，清除，设置文件信息，和写入调度例程入口地址改为Hooksys.sys中相应钩子函数的入口地址来达到拦截IRP的目的。具体操作细节请参看代码剖析一节。 <br /><br />下面介绍驱动与ring3下客户程序的通信技术。与WIN9X下驱动与ring3下客户程序通信技术相同，NT/2000仍然支持使用DeviceIoControl实现从ring3到ring0的单向通信，但从ring0通过排队APC来唤醒ring3线程的方法却无法使用了。原因是我没有找到一个公开的函数来实现（Walter Oney的书中说存在一个未公开的函数实现从ring0排队APC）。其实不通过APC我们也可以通过命名的事件/信号量对象来实现双向唤醒，而且这可能比APC更为可靠些。 <br /><br />对象管理器在Windows NT/2000内核中占了极其重要的位置，其一个最主要职能是组织管理系统内核对象。在Windows NT/2000中，内核对象管理器大量引入了C++面向对象的思想，即所有内核对象都封装在对象管理器内部，除对象管理器自己以外，对其他所有想引用内核对象结构成员的子系统都是不透明的，也即都需通过对象管理器访问这些结构。Microsoft极力推荐内核驱动代码遵循这一原则（用户态代码根本不能直接访问这些数据），它提供了一系列以Ob开头的例程供我们使用。 <br /><br />内核已命名对象存于系统全局命名内核区，与传统的DOS目录和文件组织方式相似，对象管理器也采用树状结构管理这些对象，这样可以快速检索内核对象。当然使用这种树状结构组织内核已命名对象，还有另一个优点，那就是使所有已命名对象组织的十分有条理，如设备对象处于\Device下，而对象类型名称处于\ObjectTypes下等等。再者这样也能达到使用户态进程仅能访问\??与\BaseNamedObjects下的对象，而内核态代码则没有任何限制的目的。至于系统内部如何组织管理这些已命名对象，其实Windows NT/2000内部由内核变量ObpRootDirectoryObject指向的Directory对象代表根目录，使用哈希表(HashTable)来组织管理这些命名内核对象。 <br /><br />Hooksys.sys中使用命名的信号量来唤醒ring3级线程。具体做法如下：首先在guidll.dll中调用CreateSemaphore创建一个命名信号量Hookopen并设为无信号状态，同时调用CreateThread创建一个线程。线程代码的入口处通过调用WaitForSingleObject在此信号量上等待被ring0钩子函数唤醒查毒。驱动程序这边则在初始化过程中通过未公开的例程ObReferenceObjectByName（\BaseNamedObjects\Hookopen）得到命名信号量对象Hookopen的指针，当它拦截到文件打开请求时调用KeReleaseSemaphore将Hookopen置为有信号状态唤醒ring3级等待检查打开文件的线程。其实guidll.dll共创建了两个命名信号量，还有一个Hookclose用于唤醒ring3级等待检查关闭文件的线程。 <br /><br />guidll.dll中使用命名的事件来唤醒暂时挂起等待查毒完毕的ring0钩子函数。具体做法如下：Hooksys.sys在其初始化过程中通过ZwCreateEvent函数创建一组命名事件对象（此处必须合理设置安全描述符，否则ring3线程将无法使用事件句柄）并得到其句柄，同时通过ObReferenceObjectByHandle得到句柄引用的事件对象的指针。然后Hooksys.sys将这一组事件句柄和指针对以及事件名保存在备用链表的每个元素中：ring3使用句柄，ring0使用指针。当钩子函数拦截到文件请求时它首先唤醒ring3查毒线程，然后马上调用KeWaitForSingleObject在一个事件\BaseNamedObjects\Hookxxxx上等待查毒的完成。而被唤醒的ring3查毒线程通过OpenEventA函数由事件名字得到其句柄，在结束查毒后发出一个SetEvent调用将事件置为有信号状态从而唤醒ring0挂起的钩子函数。当然，以上讨论仅限于打开文件操作，钩子函数在拦截到其它文件请求时并不调用KeWaitForSingleObject等待查毒的完成，而是唤醒ring3查毒线程后直接返回；相应的ring3查毒线程也就不必在查毒完成后调用SetEvent进行远程唤醒。 <br /><br />另外在编写NT内核模式驱动程序时还必须注意一些事项。首先是中断请求级（IRQL），这是在进行NT驱动编程时特别值得注意的问题。每个内核例程都要求在一定的IRQL上运行，如果在调用时不能确定当前IRQL在哪个级别，则可调用KeGetCurrentIrql获取当前的IRQL值并进行判断。例如欲获得指向当前进程Eprocess的指针可以考虑先判断当前的IRQL，如大于等于DISPATCH_LEVEL时可调用IoGetCurrentProcess；而当IRQL小于调度/延迟过程调用级别时（DISPATCH_LEVEL/DPC）则可使用PsGetCurrentProcessId和PsLookupProcessByProcessId。其次要注意的问题是分页/非分页内存。由于执行在提升的IRQL级上时系统将不能处理页故障，因为系统在APC级处理页故障，因而这里总的原则是：执行在高于或等于DISPATCH_LEVEL级上的代码绝对不能造成页故障。这也意味着执行在高于或等于DISPATCH_LEVEL级上的代码必须存在于非分页内存中。此外，所有这些代码要访问的数据也必须存在于非分页内存中。最后是同步互斥问题，这对于如病毒实时监控等系统范围共享的驱动程序尤显重要。虽然在Hooksys中没有创建多线程（PsCreateSystemThread），但由于它挂接了系统文件钩子，系统中所有线程的文件请求都会从Hooksys中经过。当一个线程的文件请求被处理过程中Hooksys会去访问一些全局共享的数据，如过滤器，历史记录等，有可能在访问进行到一半时该线程由于某种原因被抢占了，结果是其它线程的文件请求经过时Hooksys访问的共享数据将是错误的。为此驱动程序必须合理使用自旋锁，互斥量，资源等内核同步对象对共享全局数据的所有线程进行同步。 <br /><br /><a name="42">3.4.2程序结构与流程</a><br /><br />以下的程序结构与流程分析来自一著名反病毒软件的WINNT/2000实时监控NT内核模式设备驱动程序Hooksys.sys： <br /><br />1.初始化例程（DriverEntry）：调用_GetProcessNameOffset取得进程名在Eprocess中的偏移。初始化备用，打开文件等待操作，关闭文件，历史记录5个双向循环链表及用于链表操作互斥的4把自旋锁和1个快速互斥量。将全局变量_IrqCount（IRP记数）设置为0。创建卸载保护用事件对象。为文件名过滤数组初始化同步用资源变量。在系统全局命名内核区中检索Hookopen和Hookclose两个命名信号量（ _CreateSemaphore）。为备用（_AllocateBuffer）和历史记录（_AllocatHistoryBuf）链表在系统非分页池中分配空间，同时创建一组命名事件对象Hookxxxx并保存至备用链表的每个元素中（_CreateOneEvent）。创建设备，设置驱动例程入口，为设备建立符号连接。创建磁盘驱动器设备对象指针（_QuerySymbolicLink）和文件系统驱动程序对象指针（_HookSys）列表。 <br /><br />2.打开例程（IRP_MJ_CREATE）：将备用链表用系统非分页内存（首地址保存在_SysBufAddr中）映射到用户空间中（保存在_UserBufAddr）以便从用户态可以直接访问这段内存（_MapMemory）。 <br /><br />3.设备控制例程（IRP_MJ_DEVICE_CONTROL）：它会从入口IRP当前堆栈单元中取得用户程序利用DeviceIoControl传送进来的IO控制代码（IoControlCode），以此判断用户程序的意图。和Hooksys.sys协同工作的ring3级客户程序guidll.dll会依次向Hooksys.sys发送IO控制请求来完成一系列工作，具体次序和代码含义如下： <br /><br />83003C2F：将guidll取得的驱动器类型值传给驱动（保存在DriverType变量中），根据此变量值的不同，设置不同的等待（KeWaitForSingleObject）超时值，因为非固定驱动器的读写时间会稍长些。 <br /><br />83003C0F：保存guidll传送的用户指定的拦截文件的类型，其实这个类型过滤器在查毒模块中已存在，这里再设置显然是为了提高处理效率：它确保不会将非指定类型文件送到ring3级查毒模块，节省了通信的开销。经过解析的各文件类型过滤块指针将保存在_gaFileNameFilterArra数组中，同时更新过滤项个数_gNumOfFilters变量的值。 <br /><br />83003C13：修改文件系统驱动程序对象调度例程入口，启动拦截文件操作的钩子函数的工作。 <br /><br />83003C17：恢复文件系统驱动程序原调度例程入口，停止拦截文件操作的钩子函数工作。 <br /><br />以上列出的IO控制代码的发出是固定，而当钩子函数启动后，还会发出一些随机的控制代码： <br /><br />83003C07：驱动将打开文件链表的头元素即最先的请求打开的文件删除并插入到等待链表尾部，同时将元素的用户空间地址传送至ring3级等待查杀打开文件的线程中处理。 <br /><br />83003C0B：驱动将关闭文件链表的头元素即最先的请求关闭的文件删除并插入到备用链表尾部，同时将元素中的文件名串传送至ring3级等待查杀关闭文件的线程中处理 <br /><br />83003C1F：当查得关闭文件是病毒时，更新历史记录链表。 <br /><br />下面介绍钩子函数_HookCreateDispatch和guidll中等待查杀打开文件的线程协同工作流程，而关闭，清除，设置文件信息，和写入操作的处理与此大同小异： <br /><br />当文件请求进入钩子函数_HookCreateDispatch后，它首先从入口IRP中定位当前的堆栈单元并从中取得代表此次请求的文件对象。然后判断当前进程是否为我们自己，若是则必须放过去，因为查毒模块中要频繁的进行文件操作，所以拦截来自ravmon的文件请求将导致严重的系统死锁。接下来利用堆栈单元中的文件对象取得完整的文件路径名并确保文件不是：\PIPE\，\IPC。之后查找历史记录链表以确定该文件是否最近曾被检查并记录过，若在历史记录链表中找到关于该文件的记录并且记录未失效即其时间戳和当前系统时间之差不得大于1F4h，则可直接从记录中读取查毒结果。如历史链表中没有该文件的记录则利用保存的文件类型过滤阵列检查文件是否在被拦截的文件类型之列。至此才进入真正的检查打开文件函数_RAVCheckOpenFile，此函数入口处先从备用，等待或关闭链表头部摘得一空闲元素（_GetFreeEntry）并填充之，如文件路径名域等。接着将空闲元素加入打开文件链表尾部并释放Hookopen信号量唤醒ring3下等待检查打开文件的线程。然后调用KeWaitForSingleObject在空闲元素中保存的一个事件对象上等待ring3查毒的完成。当钩子函数挂起后，ring3查毒线程得到执行：它会向驱动发出一IO控制码为83003C07的请求以取得打开文件链表头元素即保存最先提交而未决的文件请求，驱动会将元素映射到用户空间中的偏移地址直接传给它。接着它调用RsEngine.dll中的fnScanOneFile函数进行查毒并在元素中设置查毒结果位，完毕后再对元素中保存的事件对象调用SetEvent唤醒在此事件上等待的钩子函数。被唤醒的钩子函数检查被ring3查毒代码设置的结果位以此决定该文件请求是被采纳即调用保存的原调度例程还是被取消即调用IofCompleteRequest直接返回，同时增加历史记录。 <br /><br />以上只是钩子函数与ring3线程流程的一个简单介绍，其中省略了诸如判断固定驱动器，超时等内容，具体细节请参看guidll.dll和hooksys.sys的反汇编代码注释。 <br /><br />4.关闭例程（IRP_MJ_CLOSE）：停止钩子函数工作，恢复文件系统驱动程序原调度入口（_StopFilter）。解除到用户空间的内存映射。 <br /><br />5.卸载例程（DriverUnload）：停止钩子函数工作，恢复文件系统驱动程序原调度入口。删除设备和符号连接。删除初始化时创建的一组命名事件对象Hookxxxx，包括解除指针引用，关闭打开的句柄。释放为MDL（_pMdl），备用链表（_SysBufAddr），历史记录链表（_HistoryBuf）和过滤器分配的内存空间。删除为文件名过滤数组访问同步设置的资源变量（_FilterResource）。解除对系统全局命名内核区中Hookopen和Hookclose两个命名信号量的指针引用。 <br /><br /><a name="43">3.4.3HOOKSYS.SYS逆向工程代码剖析</a><br /><br /><a name="431">3.4.3.1取得当前进程名称代码</a><br /><br />初始化例程中取得进程名在Eprocess中偏移 <br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>00011889 call ds:__imp__IoGetCurrentProcess@0 ；
得到当前进程System的Eprocess指针
  0001188F mov edi, eax ；Eprocess基地址
  00011891 xor esi, esi ；初始化偏移为0
  00011893 lea eax, [esi+edi] ；扫描指针
  00011896 push 6 ；进程名长度
  00011898 push eax ；扫描指针
  00011899 push offset $SG8452 ; "System" ；进程名串
  0001189E call ds:__imp__strncmp ；比较扫描指针处是否为进程名
  000118A4 add esp, 0Ch ；恢复堆栈
  000118A7 test eax, eax ；测试比较结果
  000118A9 jz short loc_118B9 ；找到则跳出循环
  000118AB inc esi ；增加偏移量
  000118AC cmp esi, 3000h ；在12K范围中扫描
  000118B2 jb short loc_11893 ；在范围之内则继续比较
  钩子函数开始处取得当前进程名

  00010D1E call ds:__imp__IoGetCurrentProcess@0 ；得到当前进程System的Eprocess指针
  00010D24 mov ecx, _ProcessNameOffset ；取得保存的进程名偏移量
  00010D2A add eax, ecx ；得到指向进程名的指针</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br /><a name="432">3.4.3.2启动钩子函数工作代码</a><br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>000114F4 push 4 ；预先将文件系统驱动对象个数压栈
  000114F6 mov esi, offset FsDriverObjectPtrList ；
取得文件系统驱动对象指针列表偏移地址
  000114FB pop edi ；用EDI做记数器，初始值为4
  000114FC mov eax, [esi] ；取得第一个驱动对象的指针 
  000114FE test eax, eax ；测试是否合法
  00011500 jz short loc_11548 ；不合法则继续下一个修改驱动对象
  00011502 mov edx, offset _HookCreateDispatch@8 ；
取得自己的钩子函数的偏移地址
  00011507 lea ecx, [eax+38h] ；取得对象中打开调度例程（IRP_MJ_CREATE）偏移
  0001150A call @InterlockedExchange@8 ；
原子操作，替换驱动对象中打开调度例程的入口为钩子函数的偏移地址
  0001150F mov [esi-10h], eax ；保存原打开调度例程的入口</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br /><a name="433">3.4.3.3映射系统内存至用户空间代码</a><br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>0001068E push esi ；系统内存大小
  0001068F push _SysBufAddr ；系统内存基地址
  00010695 call ds:__imp__MmSizeOfMdl@8 ；计算描述系统内存所需内存描述符表（MDL）大小 
  0001069B push 206B6444h ；调试用标签
  000106A0 push eax ；MDL大小 
  000106A1 push 0 ；在系统非分页内存池中分配
  000106A3 call ds:__imp__ExAllocatePoolWithTag@12 ；为MDL分配内存
  000106A9 push esi ；系统内存大小
  000106AA mov _pMdl, eax ；保存MDL指针
  000106AF push _SysBufAddr ；系统内存基地址 
  000106B5 push eax ；MDL指针
  000106B6 call ds:__imp__MmCreateMdl@12 ；初始化MDL
  000106BC push eax ；MDL指针
  000106BD mov _pMdl, eax ；保存MDL指针
  000106C2 call ds:__imp__MmBuildMdlForNonPagedPool@4 
  ；填写MDL后物理页面数组
  000106C8 push 1 ；访问模式
  000106CA push _pMdl ；MDL指针
  000106D0 call ds:__imp__MmMapLockedPages@8 ；映射MDL描述的物理内存页面
  ......
  000106DB mov _UserBufAddr, eax ；保存映射后的用户空间地址 
  _UserBufAddr 和_SysBufAddr映射到相同的物理地址。</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br /><b>主要参考文献</b><br /><br />David A. Solomon, Mark Russinovich 《Inside Microsoft Windows 2000》September 2000 <br /><br />David A. Solomon 《Inside Windows NT》 May 1998 <br /><br />Prasad Dabak，Sandeep Phadke，Milind Borate 《Undocumented Windows NT》October 1999 <br /><br />Matt Pietrek 《Windows 95 System Programming Secrets》 March 1996 <br /><br />Walter Oney 《System Programming for Windows 95》 March 1996 <br /><br />Walter Oney 《Programming the Windows Driver Model》 1999 <br /><br />陆麟 《WINDOWS9X文件读写Internal》2001 <br /><img src ="http://www.cppblog.com/eday/aggbug/17804.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2007-01-19 22:44 <a href="http://www.cppblog.com/eday/archive/2007/01/19/17804.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>反病毒引擎设计二</title><link>http://www.cppblog.com/eday/archive/2007/01/19/17803.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Fri, 19 Jan 2007 14:43:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/01/19/17803.html</guid><description><![CDATA[
		<strong>目录 <br /><br /></strong>
		<font color="#800080">2.1虚拟机概论<br />2.2加密变形病毒<br />2.3虚拟机实现技术详解<br />2.4虚拟机代码剖析<br />  2.4.1不依赖标志寄存器指令模拟函数的分析 <br />  2.4.2依赖标志寄存器指令模拟函数的分析<br />2.5反虚拟机技术</font>
		<br />
		<br />
		<b>
				<font color="#330099">2．虚拟机查毒</font>
		</b>
		<br />
		<br />
		<b>
				<a name="1">2.1虚拟机概论</a>
		</b>
		<br />
		<br />近些年,虚拟机，在反病毒界也被称为通用解密器，已经成为反病毒软件中最引人注目的部分，尽管反病毒者对于它的运用还远没有达到一个完美的程度，但虚拟机以其诸如"病毒指令码模拟器"和"Stryker"等多变的名称为反病毒产品的市场销售带来了光明的前景。以下的讨论将把我们带入一个精彩的虚拟技术的世界中。 <br /><br />首先要谈及的是虚拟机的概念和它与诸如Vmware（美国VMWARE公司生产的一款虚拟机，它支持在WINNT/2000环境下运行如Linux等其它操作系统）和WIN9X下的VDM（DOS虚拟机，它用来在32位保护模式环境中运行16实模式代码）的区别。其实这些虚拟机的设计思想是有渊源可寻的，早在上个世纪60年代IBM就开发了一套名为VM/370的操作系统。VM/370在不同的程序之间提供抢先式多任务，作法是在单一实际的硬件上模式出多部虚拟机器。典型的VM/370会话，使用者坐在电缆连接的远程终端前，经由控制程序的一个IPL命令，模拟真实机器的初始化程序装载操作，于是 一套完整的操作系统被载入虚拟机器中，并开始为使用者着手创建一个会话。这套模拟系统是如此的完备，系统程序员甚至可以运行它的一个虚拟副本，来对新版本进行除错。Vmware与此非常相似，它作为原操作系统下的一个应用程序可以为运行于其上的目标操作系统创建出一部虚拟的机器，目标操作系统就象运行在单独一台真正机器上，丝毫察觉不到自己处于Vmware的控制之下。当在Vmware中按下电源键（Power On）时，窗口里出现了机器自检画面，接着是操作系统的载入，一切都和真的一样。而WIN9X为了让多个程序共享CPU和其它硬件资源决定使用VMs（所有Win32应用程序运行在一部系统虚拟机上；而每个16位DOS程序拥有一部DOS虚拟机）。VM是一个完全由软件虚构出来的东西，以和真实电脑完全相同的方式来回应应用程序所提出的需求。从某种角度来看，你可以将一部标准的PC的结构视为一套API。这套API的元素包括硬件I/O系统，和以中断为基础的BIOS和MS-DOS。WIN9X常常以它自己的软件来代理这些传统的API元素，以便能够对珍贵的硬件多重发讯。在VM上运行的应用程序认为自己独占整个机器，它们相信自己是从真正的键盘和鼠标获得输入，并从真正的屏幕上输出。稍被加一点限制，它们甚至可以认为自己完全拥有CPU和全部内存。实现虚拟技术关键在于软件虚拟化和硬件虚拟化，下面简要介绍WIN9X下的DOS虚拟机的实现。 <br /><br />当Windows移往保护模式后，保护模式程序无法直接调用实模式的MS-DOS处理例程，也不能直接调用实模式的BIOS。软件虚拟化就是用来描述保护模式Windows部件是如何能够和实模式MS-DOS和BIOS彼此互动。软件虚拟化要求操作系统能够拦截企图跨越保护模式和实模式边界的调用，并且调整适当的参数寄存器后，改变CPU模式。WIN9X使用虚拟设备驱动（VXD）拦截来自保护模式的中断，通过实模式中断向量表（IVT），将之转换为实模式中断调用。做为转换的一部分，VXD必须使用置于保护模式扩展内存中的参数，生成出适当的参数，并将之放在实模式（V86）操作系统可以存取的地方。服务结束后，VXD在把结果交给扩展内存中保护模式调用端。16位DOS程序中大量的21H和13H中断调用就此解决，但其中还存在不少直接端口I/O操作，这就需要引入硬件虚拟化来解决。虚拟硬件的出现是为了在硬件中断请求线上产生中断请求，为了回应IN和OUT指令，改变特殊内存映射位置等原因。硬件虚拟化依赖于Intel 80386+的几个特性。其中一个是I/O许可掩码，使操作系统可能诱捕（Trap）对任何一个端口的所有IN/OUT指令。另一个特性是：由硬件辅助的分页机制，使操作系统能够提供虚拟内存，并拦截对内存地址的存取操作，将Video RAM虚拟化是此很好的例证。最后一个必要的特性是CPU的虚拟8086（V86）模式 ，让DOS程序象在实模式中那样地执行。 <br /><br />我们下面讨论用于查毒的虚拟机并不是象某些人想象的：如Vmware一样为待查可执行程序创建一个虚拟的执行环境，提供它可能用到的一切元素，包括硬盘，端口等，让它在其上自由发挥，最后根据其行为来判定是否为病毒。当然这是个不错的构想，但考虑到其设计难度过大（需模拟元素过多且行为分析要借助人工智能理论），因而只能作为以后发展的方向。我设计的虚拟机严格的说不能称之为虚拟机器，而叫做虚拟CPU，通用解密器等更为合适一些，但由于反病毒界习惯称之为虚拟机，所以在下面的讨论中我还将延续这个名称。查毒的虚拟机是一个软件模拟的CPU，它可以象真正CPU一样取指，译码，执行，它可以模拟一段代码在真正CPU上运行得到的结果。给定一组机器码序列，虚拟机会自动从中取出第一条指令操作码部分，判断操作码类型和寻址方式以确定该指令长度，然后在相应的函数中执行该指令，并根据执行后的结果确定下条指令的位置，如此循环反复直到某个特定情况发生以结束工作，这就是虚拟机的基本工作原理和简单流程。设计虚拟机查毒的目的是为了对付加密变形病毒，虚拟机首先从文件中确定并读取病毒入口处代码，然后以上述工作步骤解释执行病毒头部的解密段（decryptor），最后在执行完的结果（解密后的病毒体明文）中查找病毒的特征码。这里所谓的“虚拟”，并非是创建了什么虚拟环境，而是指染毒文件并没有实际执行，只不过是虚拟机模拟了其真实执行时的效果。这就是虚拟机查毒基本原理，具体介绍请参看后面的相关章节。 <br /><br />当然，虚拟执行技术使用范围远不止自动脱壳（虚拟机查毒实际上是自动跟踪病毒入口的解密子将加密的病毒体按其解密算法进行解密），它还可以应用在跨平台高级语言解释器，恶意代码分析，调试器。如刘涛涛设计的国产调试器Trdos就是完全利用虚拟技术解释执行被调试程序的每条指令，这种调试器比较起传统的断点式调试器（Debug,Softice等）具有诸多优势，如不易被被调试者察觉，断点个数没有限制等。 <br /><br /><b><a name="2">2.2加密变形病毒</a></b><br /><br />前面提到过设计虚拟机查毒的目的是为了对付加密变形病毒。这一章就重点介绍加密变形技术。 <br /><br />早期病毒没有使用任何复杂的反检测技术，如果拿反汇编工具打开病毒体代码看到的将是真正的机器码。因而可以由病毒体内某处一段机器代码和此处距离病毒入口（注意不是文件头）偏移值来唯一确定一种病毒。查毒时只需简单的确定病毒入口并在指定偏移处扫描特定代码串。这种静态扫描技术对付普通病毒是万无一失的。 <br /><br />随着病毒技术的发展，出现了一类加密病毒。这类病毒的特点是：其入口处具有解密子（decryptor），而病毒主体代码被加了密。运行时首先得到控制权的解密代码将对病毒主体进行循环解密，完成后将控制交给病毒主体运行，病毒主体感染文件时会将解密子，用随机密钥加密过的病毒主体，和保存在病毒体内或嵌入解密子中的密钥一同写入被感染文件。由于同一种病毒的不同传染实例的病毒主体是用不同的密钥进行加密，因而不可能在其中找到唯一的一段代码串和偏移来代表此病毒的特征，似乎静态扫描技术对此即将失效。但仔细想想，不同传染实例的解密子仍保持不变机器码明文（从理论上讲任何加密程序中都存在未加密的机器码，否则程序无法执行），所以将特征码选于此处虽然会冒一定的误报风险（解密子中代码缺少病毒特性，同样的特征码也会出现在正常程序中），但仍不失为一种有效的方法。 <br /><br />由于加密病毒还没有能够完全逃脱静态特征码扫描，所以病毒写作者在加密病毒的基础之上进行改进，使解密子的代码对不同传染实例呈现出多样性，这就出现了加密变形病毒。它和加密病毒非常类似，唯一的改进在于病毒主体在感染不同文件会构造出一个功能相同但代码不同的解密子，也就是不同传染实例的解密子具有相同的解密功能但代码却截然不同。比如原本一条指令完全可以拆成几条来完成，中间可能会被插入无用的垃圾代码。这样，由于无法找到不变的特征码，静态扫描技术就彻底失效了。下面先举两个例子说明加密变形病毒解密子构造，然后再讨论怎样用虚拟执行技术检测加密变形病毒。 <br /><br />著名多形病毒Marburg的变形解密子： <br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>00401020: movsx edi,si ；病毒入口
  00401023: movsx edx,bp
  00401026: jmp 00408a99
  ......
  00407400: ；病毒体入口
  加密的病毒主体
  00408a94: ；解密指针初始值
  ......
  00408a99: mov dl,f7
  00408a9b: movsx edx,bx
  00408a9e: mov ecx,cf4b9b4f
  00408aa3: call 00408ac4
  ......
  00408ac4: pop ebx
  00408ac5: jmp 00408ade
  ......
  00408ade: mov cx,di
  00408ae1: add ebx,9fdbd22d
  00408ae7: jmp 00408b08
  ......
  00408b08: add ecx,80c1fbc1
  00408b0e: mov ebp,7fcdeff3 ；循环解密记数器初值
  00408b13: sub cl,39
  00408b16: movsx esi,si
  00408b19: add dword ptr[ebx+60242dbf],9ef42073 ；解密语句，9ef42073是密钥
  00408b23: mov edx,6fd1d4cf
  00408b28: mov di,dx
  00408b2b: inc ebp
  00408b2c: xor dl,a3
  00408b2f: mov cx,si
  00408b32: sub ebx,00000004 ；移动解密偏移指针，逆向解密 
  00408b38: mov ecx,86425df9
  00408b3d: cmp ebp,7fcdf599 ；判断解密结束与否
  00408b43: jnz 00408b16
  00408b49: jmp 00408b62
  ......
  00408b62: mov di,bp
  00408b65: jmp 00407400 ；将控制权交给解密后的病毒体入口
  著名多形病毒Hps的变形解密子：

  005365b8: ；解密指针初始值和病毒体入口
  加密的病毒主体 
  ......
  005379cd: call 005379e2
  ......
  005379e2: pop ebx
  005379e3: sub ebx,0000141a ；设置解密指针初值
  005379e9: ret
  ......
  005379f0: dec edx ；减少循环记数值
  005379f1: ret
  ......
  00537a00: xor dword ptr[ebx],10e7ed59 ；解密语句，10e7ed59是密钥
  00537a06: ret
  ......
  00537a1a: sub ebx,ffffffff 
  00537a20: sub ebx,fffffffd ；移动解密指针，正向解密
  00537a26: ret
  ......
  00537a30: mov edx,74d9cb97 ；设置循环记数初值
  00537a35: ret
  ......
  00537a3f: call 005379cd ；病毒入口
  00537a44: call 00537a30
  00537a49: call 00537a00
  00537a4e: call 00537a1a
  00537a53: call 005379f0
  00537a58: mov esi,edx
  00537a5a: cmp esi,74d9c696 ；判断解密结束与否
  00537a60: jnz 00537a49
  00537a66: jmp 005365b8 ；将控制权交给解密后的病毒体入口</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />以上的代码看上去绝对不会是用编译器编译出来，或是编程者手工写出来的，因为其中充斥了大量的乱数和垃圾。代码中没有注释部分均可认为是垃圾代码，有用部分完成的功能仅是循环向加密过的病毒体的每个双字加上或异或一个固定值。这只是变形病毒传染实例的其中一个，别的实例的解密子和病毒体将不会如此，极度变形以至让人无法辩识。至于变形病毒的实现技术由于涉及复杂的算法和控制，因此不在我们讨论范围内。 <br /><br />这种加密变形病毒的检测用传统的静态特征码扫描技术显然已经不行了。为此我们采取的方法是动态特征码扫描技术，所谓“动态特征码扫描”指先在虚拟机的配合下对病毒进行解密，接着在解密后病毒体明文中寻找特征码。我们知道解密后病毒体明文是稳定不变的，只要能够得到解密后的病毒体就可以使用特征码扫描了。要得到病毒体明文首先必须利用虚拟机对病毒的解密子进行解释执行，当跟踪并确定其循环解密完成或达到规定次数后，整个病毒体明文或部分已被保存到一个内部缓冲区中了。虚拟机之所以又被称为通用解密器在于它不用事先知道病毒体的加密算法，而是通过跟踪病毒自身的解密过程来对其进行解密。至于虚拟机怎样解释指令执行，怎样确定可执行代码有无循环解密段等细节将在下一节中介绍。 <br /><br /><b><a name="3">2.3虚拟机实现技术详解</a></b><br /><br />有了前面关于加密变形病毒的介绍，现在我们知道动态特征码扫描技术的关键就在于必须得到病毒体解密后的明文，而得到明文产生的时机就是病毒自身解密代码解密的完毕。目前有两种方法可以跟踪控制病毒的每一步执行，并能够在病毒循环解密结束后从内存中读出病毒体明文。一种是单步和断点跟踪法，和目前一些程序调试器相类似；另一种方法当然就是虚拟执行法。下面分别分析单步和断点跟踪法和虚拟执行法的技术细节。 <br /><br />单步跟踪和断点是实现传统调试器的最根本技术。单步的工作原理很简单：当CPU在执行一条指令之前会先检查标志寄存器，如果发现其中的陷阱标志被设置则会在指令执行结束后引发一个单步陷阱INT1H。至于断点的设置有软硬之分，软件断点是指调试器用一个通常是单字节的断点指令（CC，即INT3H）替换掉欲触发指令的首字节，当程序执行至断点指令处，默认的调试异常处理代码将被调用，此时保存在栈中的段/偏移地址就是断点指令后一字节的地址；而硬件断点的设置则利用了处理器本身的调试支持，在调试寄存器（DR0--DR4）中设置触发指令的线形地址并设置调试控制寄存器（DR7）中相关的控制位，CPU会在预设指令执行时自动引发调试异常。而Windows本身又提供了一套调试API，使得调试跟踪一个程序变得非常简单：调试器本身不用接挂默认的调试异常处理代码，而只须调用WaitForDebugEvent等待系统发来的调试事件；调试器可利用GetThreadContext挂起被调试线程获取其上下文，并设置上下文中的标志寄存器中的陷阱标志位，最后通过SetThreadContext使设置生效来进行单步调试；调试器还可通过调用两个功能强大的调试API--ReadProcessMemory和WriteProcessMemory来向被调试线程的地址空间中注入断点指令。根据我逆向后的分析结果，VC++的调试器就是直接利用这套调试API写成的。使用以上的调试技术既然可以写出像VC++那样功能齐全的调试器，那么没有理由不能将之运用于病毒代码的自动解密上。最简单的最法：创建待查可执行文件为调试器的调试子进程，然后用上述方法对其进行单步跟踪，每当收到具有EXCEPTION_SINGLE_STEP异常代码的事件时就可以分析该条以单步模式执行的指令，最后当判断病毒的整个解密过程结束后即可调用ReadProcessMemory读出病毒体明文。 <br /><br />用单步和断点跟踪法的唯一一点好处就在于它不用处理每条指令的执行--这意味着它无需编写大量的特定指令处理函数，因为所有的解密代码都交由CPU去执行，调试器不过是在代码被单步中断的间隙得到控制权而已。但这种方法的缺点也是相当明显的：其一容易被病毒觉察到，病毒只须进行简单的堆栈检查，或直接调用IsDebugerPresent就可确定自己正处于被调试状态；其二由于没有相应的机器码分析模块，指令的译码，执行完全依赖于CPU，所以将导致无法准确地获取指令执行细节并对其进行有效的控制。；其三单步和断点跟踪法要求待查可执行文件真实执行，即其将做为系统中一个真实的进程在自己的地址空间中运行，这当然是病毒扫描所不能允许的。很显然，单步和断点跟踪法可以应用在调试器，自动脱壳等方面，但对于查毒却是不合适的。 <br /><br />而使用虚拟执行法的唯一一点缺点就在于它必须在内部处理所有指令的执行--这意味着它需要编写大量的特定指令处理函数来模拟每种指令的执行效果，这里根本不存在何时得到控制权的问题，因为控制权将永远掌握在虚拟机手中。用软件方法模拟CPU并非易事，需要对其机制有足够的了解，否则模拟效果将与真实执行相去甚远。举两个例子：一个是病毒常用的乘法后ASCII调整指令AAM，这条指令因为存在未公开的行为从而常常被病毒用来考验虚拟机设计的优劣。通常情况下AAM是双字节指令，操作码为D4 0A（其实0A隐含代表了操作数10）；但也可作为单字节指令明确地指定第二字节除数为任意8位立即数，此时操作码仅为D4。虚拟机必需考虑到后一种指定除数的情况来保证模拟结果的正确性；还有一个例子是关于处理器响应中断的方式，即CPU在刚打开中断后将不会马上响应中断，而必须隔一个指令周期。如果虚拟机没有考虑到该机制则很可能虚拟执行流程会与真实情况不符。但虚拟执行的优点也是很明显的，同时它正好填补了单步和断点跟踪法所力不能及的方面：首先是不可能被病毒觉察到，因为虚拟机将在其内部缓冲区中为被虚拟执行代码设立专用的堆栈，所以堆栈检查结果与实际执行无二（不会向堆栈中压入单步和断点中断时的返回地址）；其次由于虚拟机自身完成指令的解码和地址的计算，所以能够获取每条指令的执行细节并加以控制；最后，最为关键的一条在于虚拟执行确实做到了“虚拟”执行，系统中不会产生代表被执行者的进程，因为被执行者的寄存器组和堆栈等执行要素均在虚拟机内部实现，因而可以认为它在虚拟机地址空间中执行。鉴于虚拟执行法诸多的优点，所以将其运用于通用病毒体解密上是再好不过的了。 <br /><br />通常，虚拟机的设计方案可以采取以下三种之一：自含代码虚拟机（SCCE），缓冲代码虚拟机（BCE），有限代码虚拟机（LCE）。 <br /><br />自含代码虚拟机工作起来象一个真正的CPU。一条指令取自内存，由SCCE解码，并被传送到相应的模拟这条指令的例程，下一条指令则继续这个循环。虚拟机会包含一个例程来对内存/寄存器寻址操作数进行解码，然后还会包括一个用于模拟每个可能在CPU上执行的指令的例程集。正如你所想到的，SCCE的代码会变的无比的巨大而且速度也会很慢。然而SCCE对于一个先进的反病毒软件是很有用的。所有指令都在内部被处理，虚拟机可以对每条指令的动作做出非常详细的报告，这些报告和启发式数据以及通用清除模块将相互参照形成一个有效的反毒系统。同时，反病毒程序能够最精确地控制内存和端口的访问，因为它自己处理地址的解码和计算。 <br /><br />缓冲代码虚拟机是SCCE的一个缩略版,因为相对于SCCE它具有较小的尺寸和更快的执行速度。在BCE中，一条指令是从内存中取得的，并和一个特殊指令表相比较。如果不是特殊指令，则它被进行简单的解码以求得指令的长度，随后所有这样的指令会被导入到一个可以通用地模拟所有非特殊指令的小过程中。而特殊指令，只占整个指令集的一小部分，则在特定的小处理程序中进行模拟。BCE通过将所有非特殊指令用一个小的通用的处理程序模拟来减少它必须特殊处理的指令条数，这样一来它削减了自身的大小并提高了执行速度。但这意味着它将不能真正限制对某个内存区域，端口或其他类似东西的访问，同时它也不可能生成如SCCE提供的同样全面的报告。 <br /><br />有限代码虚拟机有点象用于通用解密的虚拟系统所处的级别。LCE实际上并非一个虚拟机，因为它并不真正的模拟指令，它只简单地跟踪一段代码的寄存器内容，也许会提供一个小的被改动的内存地址表，或是调用过的中断之类的东西。选择使用LCE而非更大更复杂的系统的原因，在于即使只对极少数指令的支持便可以在解密原始加密病毒的路上走很远，因为病毒仅仅使用了INTEL指令集的一小部分来加密其主体。使用LCE，原本处理整个INTEL指令集时的大量花费没有了，带来的是速度的巨大增长。当然，这是以不能处理复杂解密程序段为代价的。当需要进行快速文件扫描时LCE就变的有用起来，因为一个小型但象样的LCE可以用来快速检查执行文件的可疑行为，反之对每个文件都使用SCCE算法将会导致无法忍受的缓慢。当然，如果一个文件看起来可疑，LCE还可以启动某个SCCE代码对文件进行全面检查。 <br /><br />下面开始介绍32位自含代码虚拟机w32encode（w32encode.cpp，Tw32asm.h，Tw32asm.cpp做为查毒引擎的一部分和其它搜索清除模块联编为Rsengine.dll）的程序结构和流程。由于这是一个设计完备且复杂的大型商用虚拟机，其中不可避免地包含了对某些特定病毒的特定处理，为了使虚拟机模型的结构清晰脉络分明，分析时我将做适当的简化。 <br /><br />w32encode的工作原理很简单：它首先设置模拟寄存器组（用一个DWORD全局变量模拟真实CPU内部的一个寄存器，如ENEAX）的初始值，初始化执行堆栈指针（虚拟机用内部的一个数组static int STACK[0x20]来模拟堆栈）。然后进入一个循环，解释执行指令缓冲区ProgBuffer中的头256条指令，如果循环退出时仍未发现病毒的解密循环则可由此判定非加密变形病毒，若发现了解密循环则调用EncodeInst函数重复执行循环解密过程，将病毒体明文解密到DataSeg1或DataSeg2中。相关部分代码如下： <br /><br />W32Encode0中总体流程控制部分代码： <br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>for (i=0;i&lt;0x100;i++) //首先虚拟执行256条指令试图发现病毒循环解密子
  {
  if (InstLoc&gt;=0x280) 
  return(0);
  if (InstLoc+ProgSeekOff&gt;=ProgEndOff) 
  return(0); //以上两条判断语句检查指令位置的合法性
  saveinstloc(); //存储当前指令在指令缓冲区中的偏移
  HasAddNewInst=0;
  if (!(j=parse())) //虚拟执行指令缓冲区中的一条指令
  return(0); //遇到不认识的指令时退出循环
  if (j==2) //返回值为2说明发现了解密循环 
  break;
  }
  if (i==0x100) //执行过256条指令后仍未发现循环则退出
  return(0);
  PreParse=0; 
  ProcessInst();
  if (!EncodeInst()) //调用解密函数重复执行循环解密过程
  return(0);
  jmp中判定循环出现部分代码： 

  if ((loc&gt;=0)&amp;&amp;(loc&lt;InstLoc)) //若转移后指令指针小于当前指令指针则可能出现循环
  if (!isinstloc(loc)) //在保存的指令指针数组InstLocArray中查找转移后指
  ...... //令指针值，如发现则可判定循环出现
  else
  {
  ...... 
  return(2); //返回值2代表发现了解密循环
  }</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />parse中虚拟执行每条指令的过程较复杂一些：通常parse会从取得指令缓冲区ProgBuffer中取得当前指令的头两个字节（包括了全部操作码）并根据它们的值调用相应的指令处理函数。例如当第一个字节等于0F并且第二个字节位与BE后等于BE时，可判定此指令为movszx并同时调用movszx进行处理。当执行进入特定指令的处理函数中时，首先要通过判断寻址方式（调用modregrm或modregrm1）确定指令长度并将控制权交给saveinst函数。saveinst在保存该指令的相关信息后会调用真正指令执行函数W32ExecuteInst。这个函数和parse非常相似，它从SaveInstBuf1中取得当前指令的头两个字节并根据它们的值调用相应的指令模拟函数以完成一条指令的执行。相关部分代码如下： <br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>W32ExecuteInst中指令分遣部分代码：
  if ((c&amp;0xf0)==0x50)
  {if (ExecutePushPop1(c)) //模拟push和pop
  return(gotonext());
  return(0);
  }
  if (c==0x9c)
  {if (ExecutePushf()) //模拟pushf
  return(gotonext());
  return(0);
  }
  if (c==(char)0x9d)
  {if (ExecutePopf()) //模拟popf
  return(gotonext());
  return(0);
  }
  if ((c==0xf)&amp;&amp;((c2&amp;0xbe)==0xbe))
  {if (i=ExecuteMovszx(0)) //模拟movszx
  return(gotonext());
  return(0);
  }</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br /><b><a name="4">2.4虚拟机代码剖析</a></b><br /><br />总体流程控制和分遣部分的相关代码，在上一章中都已分析过了。下面分析具体的特定指令模拟函数，这才是虚拟机的精华之所在。我将指令分成不依赖标志寄存器和依赖标志寄存器两大类分别介绍： <br /><br /><a name="41">2.4.1不依赖标志寄存器指令模拟函数的分析</a><br /><br />push和pop指令的模拟： <br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>static int ExecutePushPop1(int c)
  {
  if (c&lt;=0x57)
  {if (StackP&lt;0) //入栈前检查堆栈缓冲指针的合法性
  return(0);
  }
  else
  if (StackP&gt;=0x40) //出栈前检查堆栈缓冲指针的合法性
  return(0);
  if (c&lt;=0x57) {
  StackP--;
  ENESP-=4; //如果是入栈指令则在入栈前减少堆栈指针
  }
  switch (c)
  {case 0x50:STACK[StackP]=ENEAX; //模拟push eax
  break;
  ......
  case 0x5f:ENEDI=STACK[StackP]; //模拟push edi
  break;
  }
  if (c&gt;=0x58) {
  StackP++;
  ENESP+=4; //如果是出栈指令则在出栈后增加堆栈指针 
  }
  return(1);
  }</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br /><a name="42">2.4.2依赖标志寄存器指令模拟函数的分析</a><br /><br />CW32Asm类中cmp指令的模拟： <br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>void CW32Asm:: cmpw(int c1,int c2)
  {
  char FlgReg;
  __asm {
  mov eax,c1 //取得第一个操作数
  mov ecx,c2 //取得第二个操作数
  cmp eax,ecx //比较
  lahf //将比较后的标志结果装入ah
  mov FlgReg,ah //保存结果在局部变量FlgReg中
  }
  FlagReg=FlgReg; //保存结果在全局变量FlagReg中
  }
  CW32Asm类中jnz指令的模拟：

  int CW32Asm::JNE()
  {int i;
  char FlgReg=FlagReg; //用保存的FlagReg初始化局部变量FlgReg
  __asm
  {
  mov ah,FlgReg //设置ah为保存的模拟标志寄存器值
  pushf //保存虚拟机自身当前标志寄存器
  sahf //将模拟标志寄存器值装入真实标志寄存器中
  mov eax,1
  jne l //执行jnz
  popf //恢复虚拟机自身标志寄存器
  xor eax,eax
  l:
  popf //恢复虚拟机自身标志寄存器 
  mov i,eax
  }
  return(i); //返回值为1代表需要跳转
  }</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br /><b><a name="5">2.5反虚拟机技术</a></b><br /><br />任何一个事物都不是尽善尽美，无懈可击的，虚拟机也不例外。由于反虚拟执行技术的出现，使得虚拟机查毒受到了一定的挑战。这里介绍几个比较典型的反虚拟执行技术： <br /><br />首先是插入特殊指令技术，即在病毒的解密代码部分人为插入诸如浮点，3DNOW，MMX等特殊指令以达到反虚拟执行的目的。尽管虚拟机使用软件技术模拟真正CPU的工作过程，它毕竟不是真正的CPU，由于精力有限，虚拟机的编码者可能实现对整个Intel指令集的支持，因而当虚拟机遇到其不认识的指令时将会立刻停止工作。但通过对这类病毒代码的分析和统计，我们发现通常这些特殊指令对于病毒的解密本身没有发生任何影响，它们的插入仅仅是为了干扰虚拟机的工作，换句话说就是病毒根本不会利用这条随机的垃圾指令的运算结果。这样一来，我们可以仅构造一张所有特殊指令对应于不同寻址方式的指令长度表，而不必为每个特殊指令编写一个专用的模拟函数。有了这张表后，当虚拟机遇到不认识的指令时可以用指令的操作码索引表格以求得指令的长度，然后将当前模拟的指令指针（EIP）加上指令长度来跳过这条垃圾指令。当然，还有一个更为保险的办法那就是：得到指令长度后，可以将这条我们不认识的指令放到一个充满空操作指令（NOP）的缓冲区中，接着我们将跳到缓冲区中去执行，这等于让真正的CPU帮我们来执行这条指令，最后一步当然是将执行后真实寄存器中的结果放回我们的模拟寄存器中。这虚拟执行和真实执行参半方法的好处在于：即便在特殊指令对于病毒是有意义的，即病毒依赖其返回结果的情况下，虚拟机仍可保证虚拟执行结果的正确。 <br /><br />其次是结构化异常处理技术，即病毒的解密代码首先设置自己的异常处理函数，然后故意引发一个异常而使程序流程转向预先设立的异常处理函数。这种流程转移是CPU和操作系统相互配合的结果，并且在很大程度上，操作系统在其中起了很大的作用。由于目前的虚拟机仅仅模拟了没有保护检查的CPU的工作过程，而对于系统机制没有进行处理。所以面对引发异常的指令会有两种结果：其一是某些设计有缺陷的虚拟机无法判断被模拟指令的合法性，所以模拟这样的指令将使虚拟机自身执行非法操作而退出；其二虚拟机判断出被模拟指令属于非法指令，如试图向只读页面写入的指令，则立刻停止虚拟执行。通常病毒使用该技术的目的在于将真正循环解密代码放到异常处理函数后，如此虚拟机将在进入异常处理函数前就停止了工作，从而使解密子有机会逃避虚拟执行。因而一个好的虚拟机应该具备发现和记录病毒安装异常过滤函数的操作并在其引发异常时自动将控制转向异常处理函数的能力。 <br /><br />再次是入口点模糊（EPO）技术，即病毒在不修改宿主原入口点的前提下，通过在宿主代码体内某处插入跳转指令来使病毒获得控制权。通过前面的分析，我们知道虚拟机扫描病毒时出于效率考虑不可能虚拟执行待查文件的所有代码，通常的做法是：扫描待查文件代码入口，假如在规定步数中没有发现解密循环，则由此判定该文件没有携带加密变形病毒。这种技术之所以能起到反虚拟执行的作用在于它正好利用了虚拟机的这个假设：由于病毒是从宿主执行到一半时获得控制权的，所以虚拟机首先解释执行的是宿主入口的正常程序，当然在规定步数中不可能发现解密循环，因而产生了漏报。如果虚拟机能增加规定步数的大小，则很有可能随着病毒插入的跳转指令跟踪进入病毒的解密子，但确定规定步数大小实在是件难事：太大则将无谓增加正常程序的检测时间；太小则容易产生漏报。但我们对此也不必过于担心，这类病毒由于其编写技术难度较大所以为数不多。在没有反汇编和虚拟执行引擎的帮助下，病毒很难在宿主体内定位一条完整指令的开始处来插入跳转，同时很难保证插入的跳转指令的深度大于虚拟机的规定步数，并且没有把握插入的跳转指令一定会被执行到。 <br /><br />另外还有多线程技术，即病毒在解密部分入口主线程中又启动了额外的工作线程，并且将真正的循环解密代码放置于工作线程中运行。由于多线程间切换调度由操作系统负责管理，所以我们的虚拟机只能在假定被执行线程独占处理器时间，即保证永远不被抢先，的前提下进行。如此一来，虚拟机对于模拟启用多线程工作的代码将很难做到与真实效果一致。多线程和结构化异常处理两种技术都利用了特定的操作系统机制来达到反虚拟执行的目的，所以在虚拟CPU中加入对特定操作系统机制的支持将是我们今后改进的目标。 <br /><br />最后是元多形技术（MetaPolymorphy）,即病毒中并非是多形的解密子加加密的病毒体结构，而整体均采用变形技术。这种病毒整体都在变，没有所谓“病毒体明文”。当然，其编写难度是很大的。如果说前几种反虚拟机技术是利用了虚拟机设计上的缺陷，可以通过代码改进来弥补的话，那么这种元多形技术却使虚拟机配合的动态特征码扫描法彻底失效了，我们必须寻求如行为分析等更先进的方法来解决。【未完待续】 <br /><br /><b>主要参考文献</b><br /><br />David A. Solomon, Mark Russinovich 《Inside Microsoft Windows 2000》September 2000 <br /><br />David A. Solomon 《Inside Windows NT》 May 1998 <br /><br />Prasad Dabak，Sandeep Phadke，Milind Borate 《Undocumented Windows NT》October 1999 <br /><br />Matt Pietrek 《Windows 95 System Programming Secrets》 March 1996 <br /><br />Walter Oney 《System Programming for Windows 95》 March 1996 <br /><br />Walter Oney 《Programming the Windows Driver Model》 1999 <br /><br />陆麟 《WINDOWS9X文件读写Internal》2001<img src ="http://www.cppblog.com/eday/aggbug/17803.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2007-01-19 22:43 <a href="http://www.cppblog.com/eday/archive/2007/01/19/17803.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>反病毒引擎设计一</title><link>http://www.cppblog.com/eday/archive/2007/01/19/17802.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Fri, 19 Jan 2007 14:41:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/01/19/17802.html</guid><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 1														．绪论																																																										本文研究的主要内容正如其题目所示是设计并编写一个先进的反病毒引擎。首先需要对这“先进”二字做一个解释，何为“先进”？众所周知，传统的反病毒软件使用的是基于特征码的静态扫描技术，即在文件...&nbsp;&nbsp;<a href='http://www.cppblog.com/eday/archive/2007/01/19/17802.html'>阅读全文</a><img src ="http://www.cppblog.com/eday/aggbug/17802.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2007-01-19 22:41 <a href="http://www.cppblog.com/eday/archive/2007/01/19/17802.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>在内存中运行可执行程序</title><link>http://www.cppblog.com/eday/archive/2007/01/19/17801.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Fri, 19 Jan 2007 14:28:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/01/19/17801.html</guid><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 在内存中运行可执行程序，好处是可以给程序加壳，加密源程序，静态反汇编无法获得PE输入节，但是因为运行后仍然是独立的进程，所以没办法防止远程线程注入，挂接API钩子。  1 typedef IMAGE_SECTION_HEADER ( * PIMAGE_SECTION_HEADERS)[ 1 ];     2     3  //  计算对齐后的大小      4  unsigned  long  G...&nbsp;&nbsp;<a href='http://www.cppblog.com/eday/archive/2007/01/19/17801.html'>阅读全文</a><img src ="http://www.cppblog.com/eday/aggbug/17801.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2007-01-19 22:28 <a href="http://www.cppblog.com/eday/archive/2007/01/19/17801.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Visual C++实现Flash动画播放</title><link>http://www.cppblog.com/eday/archive/2006/12/02/15896.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 02 Dec 2006 13:36:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/12/02/15896.html</guid><description><![CDATA[　　摘要： 本文通过在VC中将外部swf格式文件内嵌为VC的内部资源，使其在程序运行过程中从资源动态释放到临时文件，从而实现VC对Flash动画的播放。<br /><br /><br />　　引言<br /><br />　　Flash动画由于可以很方便地把用户的想象通过动画显现出来，使原本只属于专业制作人员的动画制作变的异乎寻常的快捷、方便。由于Flash制作的动画在层次、内容、表现形式等诸多方面均比较出色，因此在网络上得到迅猛的发展，更有不少厂商用Flash在互联网上做起了广告和产品演示，效果丝毫不比视频的差，而体积则要小的多。Flash不仅在网络上有广泛的应用，在普通的应用程序中也可以借助Flash实现一些VC、Delphi等编程语言所难以实现的特效，比如在一些演示版的程序中完全可以将程序运行前的闪屏用Flash来制作。本文下面将通过对内嵌资源的动态释放来实现VC对Flash动画的播放，并给出了部分实现代码。<br /><br />　　嵌资源的动态释放<br /><br />　　Flash动画在此是作为程序的一个模块，虽然也可以以文件的形式作为一个外部资源来使用，但为了避免因外部模块遗失而造成程序的非正常运行，可将由Flash 5.0预先制作好的swf格式的文件以资源的形式打包到应用程序中去，而在程序运行时再将其从资源恢复到文件，使用完毕再通过程序将其从磁盘删除。<br /><br />　　在导入资源时由于swf格式文件并非VC的标准资源，所以在导入时需要在"Resource type"栏指定资源类型"SWF"，特别需要注意的是在此必须要包含引号。加入到资源后可以通过资源视图看到导入的SWF资源是以二进制形式保存的，一但加入就不能再通过资源视图对其进行编辑了。<br /><br />　　在使用SWF资源前首先要将其动态从应用程序中释放到文件中才可对资源做进一步的使用。可先通过宏MAKEINTRESOURCE（）将资源标识号IDR_SWF转换成字符串Name，再分别通过FindResource()、LoadResource()函数查找、装载该资源到内存：<br /><br />CString Type="swf"；<br />HRSRC res=FindResource (NULL,Name,Type);<br />HGLOBAL gl=LoadResource (NULL,res); <br /><br />　　当资源加载到内存后，还要通过对资源内存的锁定来返回指向资源内存的地址的指针，并籍此实现资源从内存到磁盘的保存，至于存盘的操作则由文件函数CreateFile()、和WriteFile()来完成： <br /><br />LPVOID lp=LockResource(gl); //返回指向资源内存的地址的指针。<br />CString filename="Temp.swf"； //保存的临时文件名<br />// CREATE_ALWAYS为不管文件存不存在都产生新文件。<br />fp= CreateFile(filename ,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,0,NULL);<br />DWORD a;<br />//sizeofResource 得到资源文件的大小<br />if (!WriteFile (fp,lp,SizeofResource (NULL,res),&amp;a,NULL))<br />return false;<br />CloseHandle (fp); //关闭句柄<br />FreeResource (gl); //释放内存 <br /><br />　　通过上述代码，可将SWF资源从应用程序中提取并释放到临时文件Temp.swf中，在此后只对此临时文件操作，与程序内嵌资源无关。<br /><br />　　Flash动画的播放<br /><br />　　swf格式的Flash动画通常主要应用在网页上，也就是说IE浏览器本身可以支持Flash动画的播放。这样就不必再单独编写用于播放swf文件的代码，从而大大减少编程的工作量。在VC ++ 6.0中新增了一个从CView派生的、用于处理网页的视类CHtmlView，由于该类是以Internet Explorer为后台支持，因此在创建工程时只需在最后一步指定视类从CHtmlView派生就可以使程序不编一行代码而具备IE浏览器的网页显示能力。<br /><br />　　程序刚生成的时候缺省的连接主页是为微软公司的主页，需要对此修改，使程序在执行时立即显示刚才提取出来的Flash临时文件Temp.swf。显示缺省主页的代码是在视类的初始化函数中进行的：<br /><br />void CEmbedModuleView::OnInitialUpdate()<br />{<br />CHtmlView::OnInitialUpdate();<br />Navigate2(_T("http://www.microsoft.com"),NULL,NULL);<br />} <br /><br />　　显然要将Navigate2（）函数的第一个参数改成Temp.swf的存放路径。刚才在释放资源到文件时并没有指定绝对路径，因此释放出来的资源文件应当和应用程序处于同一目录。但是在此处如果不写明绝对路径是无法显示该临时文件的。获取该临时文件的绝对路径可用如下方法实现：先获取应用程序本身的绝对路径，然后去处应用程序全名（程序名和扩展名）此时得到的是应用程序和临时文件所处文件夹的路径，最后只需在此基础上加上临时文件的文件名Temp.swf即可得到临时文件的全路径。下面是实现的主要代码：<br /><br /><br />//获取应用程序的全路径<br />char exeFullPath[MAX_PATH]; <br />GetModuleFileName(NULL,exeFullPath,MAX_PATH);<br />//将其格式化为字符串<br />m_TempFile.Format("%s",exeFullPath);<br />//去掉应用程序的全名（15为应用程序文件全名的长度）<br />exeFullPath[m_TempFile.GetLength()-15]='\0';<br />//得到应用程序所在路径<br />m_TempFile.Format("%s",exeFullPath);<br />//得到临时文件的全路径<br />m_TempFile+="Temp.swf"; <br /><br />　　最后将得到的临时文件的全路径m_TempFile作为参数传递给Navigate2（）即可在程序运行时把Flash动画作为主页而显示<br /><table align="center" border="0"><tbody><tr><td></td></tr></tbody></table>　　由于临时文件Temp.swf是在程序运行过程中从应用程序的资源中提取出来的，因此在程序退出之前需要将其删除。一般是在消息WM_DESTORY的响应函数里通过DeleteFile()函数来加以实现的。<br /><br />　　小结<br /><br />　　本文通过对CHtmlView和内嵌资源的动态释放实现了Flash动画在VC程序中的播放，并对资源的动态释放作了较为清晰的描述。通过类似的方法，可以将动态链接库、HTML文件等程序模块作为资源嵌入其中，在使用时再动态释放到临时文件，这样可有效避免文件模块过多时的杂乱以及程序模块丢失导致程序非正常运行等情况的发生。本文所述程序在Windows 98下，由Microsoft Visual C++ 6.0编译通过。Flash动画由 Macromedia Flash 5.0制作，所需浏览器支持为Internet Explorer 6.0。<img src ="http://www.cppblog.com/eday/aggbug/15896.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-12-02 21:36 <a href="http://www.cppblog.com/eday/archive/2006/12/02/15896.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>实现带阴影弹出窗口的技术</title><link>http://www.cppblog.com/eday/archive/2006/11/28/15709.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Mon, 27 Nov 2006 17:05:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/28/15709.html</guid><description><![CDATA[一．问题的提出 <br /><br />   在WINDOWS的WINHELPER帮助系统中大量使用一类带阴影的弹出窗口, 这类窗口非常简洁,并具有立体感,它们用来显示一些只读信息.此类弹出窗口不同于一般的窗口,它们没有标题和滚动杆,但都具有带阴影的边框, 并且其窗口的大小随显示字符串多少而自动调节,当显示信息弹出之后，任何来自键盘或鼠标的消息都将导致弹出窗口的消失。 然而WINDOWS API接口中没有现成的函数来实现此项功能,即使是最新版的 VISUAL C++ MFC也没有提供现成的类和函数来实现带阴影的此类窗口。为此,笔者基于面向对象的程序设计思想，从CWnd派生一个新类来实现这个功能，并且将该类窗口的所有函数完全封装在一起，使用就像调用“ MessageBox()”函数显示信息一样简单。 <br /><br />二．实现方法的几个关键部分说明如下 ,要解决怎样画非用户区的问题:当WINDOWS需要创建一个窗口时,它发送两个消息：WM_NCPAINT和 WM_PAINT到应用程序消息队列。WM_NCPAINT用于重画窗口的非用户区,如标题,边框和滚动杆,本程序正是响应WM_NCPAINT消息来重画带阴影的弹出窗口的边框；画客户区很简单,只需响应WM_PAINT消息处理字符的显示即可.2.如何动态调整弹出窗口的尺寸:大家知道,在一个矩形内显示文本串时,常用函数DrawText(HDC hDC,LPTSTR lpszText,int cbCount,RECT FAR* lpRect,UINT fuFormat).但是,此时我们的带阴影的弹出窗口并为建立.当然不能利用它来显示.然而,我们注意到上述函数中的最后一个参数FuFormat, 它是文字格式的组合,其中有一个鲜为人知的参数 DT_CALCRECT, 使用这个参数,字符串不显示,但它根据当前字体测量待显示串的高度, 本程序正是根据这个参数来确定弹出窗口的大小，并以此建立一个随字符串大小而变化的窗口,下面给出其实现该功能的片断: void CShadowWnd::ShowText(CString sText) dc.CreateDC("DISPLAY",NULL,NULL,NULL); //创建一个显示设备描述表 dc.SelectObject(GetStockObject(SYSTEM_FONT)); //选择字体到设备描述表 CRect rect(0,0,MAXWIDTH,0);//　 <br /><br />//获得待显示的字符串 sText 的实际高度和宽度,并将其存入矩形rect中　　　 <br /><br />  dc.DrawText(sText,rect,DT_WORDBREAK|DT_CENTER|DT_CALCRECT|DT_NOPREFIX); <br /><br />3.怎样获取对系统的控制权: <br /><br />   在带阴影的弹出窗口显示之后,怎样获取对系统的控制权,使得当用户按下键盘任意键或鼠标时都将使带阴影的弹出窗口消失,这里采取的方法是,当弹出窗口创建和显示之后,立即进入一个消息循环,从应用程序队列中获取所有消息,并判断是否为鼠标消息或键盘消息,如是,则摧毁窗口结束,并将控制权归还给调用程序.实现片断如下: <br />//进入消息循环,获取全部消息,控制整个系统<br /><div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #008080"> 1</span> <span style="COLOR: #000000">    MSG Msg;<br /></span><span style="COLOR: #008080"> 2</span> <span style="COLOR: #000000">    BOOL bDone;<br /></span><span style="COLOR: #008080"> 3</span> <span style="COLOR: #000000">    SetCapture();<br /></span><span style="COLOR: #008080"> 4</span> <span style="COLOR: #000000">    bDone </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> FALSE;<br /></span><span style="COLOR: #008080"> 5</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">while</span><span style="COLOR: #000000">(</span><span style="COLOR: #000000">!</span><span style="COLOR: #000000">bDone)<br /></span><span style="COLOR: #008080"> 6</span> <span style="COLOR: #000000">    {<br /></span><span style="COLOR: #008080"> 7</span> <span style="COLOR: #000000">        </span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(PeekMessage(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">Msg,NULL,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,PM_REMOVE))<br /></span><span style="COLOR: #008080"> 8</span> <span style="COLOR: #000000">            </span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(Msg.message </span><span style="COLOR: #000000">==</span><span style="COLOR: #000000"> WM_KEYDOWN </span><span style="COLOR: #000000">||</span><span style="COLOR: #000000"> Msg.message </span><span style="COLOR: #000000">==</span><span style="COLOR: #000000"> WM_SYSKEYDOWN</span><span style="COLOR: #000000">||</span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 9</span> <span style="COLOR: #000000">                Msg.message </span><span style="COLOR: #000000">==</span><span style="COLOR: #000000"> WM_LBUTTONDOWN </span><span style="COLOR: #000000">||</span><span style="COLOR: #000000"> Msg.message </span><span style="COLOR: #000000">==</span><span style="COLOR: #000000"> WM_RBUTTONDOWN)<br /></span><span style="COLOR: #008080">10</span> <span style="COLOR: #000000">                bDone </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> TRUE;<br /></span><span style="COLOR: #008080">11</span> <span style="COLOR: #000000">            </span><span style="COLOR: #0000ff">else</span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">12</span> <span style="COLOR: #000000">            {<br /></span><span style="COLOR: #008080">13</span> <span style="COLOR: #000000">                TranslateMessage(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">Msg);<br /></span><span style="COLOR: #008080">14</span> <span style="COLOR: #000000">                DispatchMessage(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">Msg);<br /></span><span style="COLOR: #008080">15</span> <span style="COLOR: #000000">            }<br /></span><span style="COLOR: #008080">16</span> <span style="COLOR: #000000">    }<br /></span><span style="COLOR: #008080">17</span>  ReleaseCapture();<br />      DestroyWindow();<span style="COLOR: #000000"></span></div><br />. 带阴影的类 CShadowWnd 类的头文件及其实现文件的全部细节<br /><br /><div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #008080"> 1</span> <span style="COLOR: #000000">#pragma once<br /></span><span style="COLOR: #008080"> 2</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 3</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 4</span> <span style="COLOR: #000000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"> CShadowWnd</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 5</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 6</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">class</span><span style="COLOR: #000000"> CShadowWnd : </span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000"> CWnd<br /></span><span style="COLOR: #008080"> 7</span> <span style="COLOR: #000000">{<br /></span><span style="COLOR: #008080"> 8</span> <span style="COLOR: #000000">    DECLARE_DYNAMIC(CShadowWnd)<br /></span><span style="COLOR: #008080"> 9</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">10</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">:<br /></span><span style="COLOR: #008080">11</span> <span style="COLOR: #000000">    CShadowWnd();<br /></span><span style="COLOR: #008080">12</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">virtual</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">~</span><span style="COLOR: #000000">CShadowWnd();<br /></span><span style="COLOR: #008080">13</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">14</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">protected</span><span style="COLOR: #000000">:<br /></span><span style="COLOR: #008080">15</span> <span style="COLOR: #000000">    DECLARE_MESSAGE_MAP()<br /></span><span style="COLOR: #008080">16</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">17</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">:<br /></span><span style="COLOR: #008080">18</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">virtual</span><span style="COLOR: #000000"> BOOL Create(</span><span style="COLOR: #0000ff">const</span><span style="COLOR: #000000"> RECT</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000"> rect, CWnd</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000"> pParentWnd);<br /></span><span style="COLOR: #008080">19</span> <span style="COLOR: #000000">    CString m_sShowText;<br /></span><span style="COLOR: #008080">20</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000"> ShowReadOnlyText(CString sText);<br /></span><span style="COLOR: #008080">21</span> <span style="COLOR: #000000">    CBrush m_bmpBrush;<br /></span><span style="COLOR: #008080">22</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">protected</span><span style="COLOR: #000000">:<br /></span><span style="COLOR: #008080">23</span> <span style="COLOR: #000000">    afx_msg </span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000"> OnNcPaint();<br /></span><span style="COLOR: #008080">24</span> <span style="COLOR: #000000">    afx_msg </span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000"> OnPaint();<br /></span><span style="COLOR: #008080">25</span> <span style="COLOR: #000000">    afx_msg </span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000"> OnCreate(LPCREATESTRUCT lpCreateStruct);<br /></span><span style="COLOR: #008080">26</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">27</span> <span style="COLOR: #000000">};<br /></span><span style="COLOR: #008080">28</span> <span style="COLOR: #000000"></span></div><br />// cpp<br /><div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #008080">  1</span> <span style="COLOR: #008000">//</span><span style="COLOR: #008000"> ShadowWnd.cpp : 实现文件<br /></span><span style="COLOR: #008080">  2</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//<br /></span><span style="COLOR: #008080">  3</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">  4</span> <span style="COLOR: #000000">#include </span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">stdafx.h</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">  5</span> <span style="COLOR: #000000">#include </span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">MFCApp.h</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">  6</span> <span style="COLOR: #000000">#include </span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">ShadowWnd.h</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">  7</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">  8</span> <span style="COLOR: #000000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">阴影位图数组 </span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080">  9</span> <span style="COLOR: #008000"></span><span style="COLOR: #0000ff">static</span><span style="COLOR: #000000"> </span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000"> aPattern[]</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">{</span><span style="COLOR: #000000">0xAA</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0x55</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0xAA</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0x55</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0xAA</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0x55</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0xAA</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0x55</span><span style="COLOR: #000000">};<br /></span><span style="COLOR: #008080"> 10</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000"> SPOPUP_SHADOWWIDTH 10    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">阴影宽度 </span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 11</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000"> SPOPUP_SHADOWHEIGHT 13    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">阴影高度 </span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 12</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">    MAXWIDTH    400            </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">显示字符矩形的最大宽度 </span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 13</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 14</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 15</span> <span style="COLOR: #000000">IMPLEMENT_DYNAMIC(CShadowWnd, CWnd)<br /></span><span style="COLOR: #008080"> 16</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 17</span> <span style="COLOR: #000000">CShadowWnd::CShadowWnd()<br /></span><span style="COLOR: #008080"> 18</span> <span style="COLOR: #000000">{<br /></span><span style="COLOR: #008080"> 19</span> <span style="COLOR: #000000">    CBitmap bmp;<br /></span><span style="COLOR: #008080"> 20</span> <span style="COLOR: #000000">    bmp.CreateBitmap(</span><span style="COLOR: #000000">8</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">8</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">1</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">1</span><span style="COLOR: #000000">,(</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">)aPattern);    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">创建一个阴影位图 </span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 21</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">    m_bmpBrush.CreatePatternBrush(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">bmp);        </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">创建一把阴影刷 </span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 22</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">}<br /></span><span style="COLOR: #008080"> 23</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 24</span> <span style="COLOR: #000000">CShadowWnd::</span><span style="COLOR: #000000">~</span><span style="COLOR: #000000">CShadowWnd()<br /></span><span style="COLOR: #008080"> 25</span> <span style="COLOR: #000000">{<br /></span><span style="COLOR: #008080"> 26</span> <span style="COLOR: #000000">}<br /></span><span style="COLOR: #008080"> 27</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 28</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 29</span> <span style="COLOR: #000000">BEGIN_MESSAGE_MAP(CShadowWnd, CWnd)<br /></span><span style="COLOR: #008080"> 30</span> <span style="COLOR: #000000">    ON_WM_NCPAINT()<br /></span><span style="COLOR: #008080"> 31</span> <span style="COLOR: #000000">    ON_WM_PAINT()<br /></span><span style="COLOR: #008080"> 32</span> <span style="COLOR: #000000">    ON_WM_CREATE()<br /></span><span style="COLOR: #008080"> 33</span> <span style="COLOR: #000000">END_MESSAGE_MAP()<br /></span><span style="COLOR: #008080"> 34</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 35</span> <span style="COLOR: #000000">BOOL CShadowWnd::Create(</span><span style="COLOR: #0000ff">const</span><span style="COLOR: #000000"> RECT</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000"> rect, CWnd</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000"> pParentWnd)<br /></span><span style="COLOR: #008080"> 36</span> <span style="COLOR: #000000">{<br /></span><span style="COLOR: #008080"> 37</span> <span style="COLOR: #000000">    LPCTSTR pClassName </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> AfxRegisterWndClass(CS_HREDRAW</span><span style="COLOR: #000000">|</span><span style="COLOR: #000000">CS_VREDRAW);<br /></span><span style="COLOR: #008080"> 38</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000"> CWnd::CreateEx(WS_EX_STATICEDGE,pClassName,L</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">Shadow window</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">,WS_POPUP,<br /></span><span style="COLOR: #008080"> 39</span> <span style="COLOR: #000000">        rect.left,rect.top,rect.right,rect.bottom,<br /></span><span style="COLOR: #008080"> 40</span> <span style="COLOR: #000000">        pParentWnd</span><span style="COLOR: #000000">-&gt;</span><span style="COLOR: #000000">GetSafeHwnd(),</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,NULL);<br /></span><span style="COLOR: #008080"> 41</span> <span style="COLOR: #000000">}<br /></span><span style="COLOR: #008080"> 42</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000"> CShadowWnd::OnNcPaint()<br /></span><span style="COLOR: #008080"> 43</span> <span style="COLOR: #000000">{<br /></span><span style="COLOR: #008080"> 44</span> <span style="COLOR: #000000">    CWindowDC dc(</span><span style="COLOR: #0000ff">this</span><span style="COLOR: #000000">);<br /></span><span style="COLOR: #008080"> 45</span> <span style="COLOR: #000000">    CRect rc;<br /></span><span style="COLOR: #008080"> 46</span> <span style="COLOR: #000000">    GetWindowRect(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">rc);<br /></span><span style="COLOR: #008080"> 47</span> <span style="COLOR: #000000">    rc.right </span><span style="COLOR: #000000">-=</span><span style="COLOR: #000000"> rc.left;<br /></span><span style="COLOR: #008080"> 48</span> <span style="COLOR: #000000">    rc.bottom </span><span style="COLOR: #000000">-=</span><span style="COLOR: #000000"> rc.top;<br /></span><span style="COLOR: #008080"> 49</span> <span style="COLOR: #000000">    rc.top </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;<br /></span><span style="COLOR: #008080"> 50</span> <span style="COLOR: #000000">    rc.left </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;<br /></span><span style="COLOR: #008080"> 51</span> <span style="COLOR: #000000">    m_bmpBrush.UnrealizeObject();<br /></span><span style="COLOR: #008080"> 52</span> <span style="COLOR: #000000">    CBrush </span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">OldBrush </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> dc.SelectObject(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">m_bmpBrush);<br /></span><span style="COLOR: #008080"> 53</span> <span style="COLOR: #000000">    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">画底部阴影 </span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 54</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">    dc.PatBlt(rc.left</span><span style="COLOR: #000000">+</span><span style="COLOR: #000000">SPOPUP_SHADOWWIDTH,rc.bottom</span><span style="COLOR: #000000">-</span><span style="COLOR: #000000">SPOPUP_SHADOWHEIGHT,<br /></span><span style="COLOR: #008080"> 55</span> <span style="COLOR: #000000">        rc.right</span><span style="COLOR: #000000">-</span><span style="COLOR: #000000">SPOPUP_SHADOWWIDTH,SPOPUP_SHADOWHEIGHT,PATCOPY);<br /></span><span style="COLOR: #008080"> 56</span> <span style="COLOR: #000000">    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">画右边阴影 </span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 57</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">    dc.PatBlt(rc.right</span><span style="COLOR: #000000">-</span><span style="COLOR: #000000">SPOPUP_SHADOWWIDTH,rc.top</span><span style="COLOR: #000000">+</span><span style="COLOR: #000000">SPOPUP_SHADOWHEIGHT,<br /></span><span style="COLOR: #008080"> 58</span> <span style="COLOR: #000000">        SPOPUP_SHADOWWIDTH,rc.bottom,PATCOPY);<br /></span><span style="COLOR: #008080"> 59</span> <span style="COLOR: #000000">    dc.SelectObject(OldBrush);<br /></span><span style="COLOR: #008080"> 60</span> <span style="COLOR: #000000">    CBrush </span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">pBrush </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> CBrush::FromHandle(GetSysColorBrush(COLOR_WINDOWFRAME));<br /></span><span style="COLOR: #008080"> 61</span> <span style="COLOR: #000000">    rc.right </span><span style="COLOR: #000000">-=</span><span style="COLOR: #000000"> SPOPUP_SHADOWWIDTH;<br /></span><span style="COLOR: #008080"> 62</span> <span style="COLOR: #000000">    rc.bottom </span><span style="COLOR: #000000">-=</span><span style="COLOR: #000000"> SPOPUP_SHADOWHEIGHT;<br /></span><span style="COLOR: #008080"> 63</span> <span style="COLOR: #000000">    dc.FrameRect(rc,pBrush);    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">画边框 </span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 64</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">}<br /></span><span style="COLOR: #008080"> 65</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 66</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000"> CShadowWnd::OnPaint()<br /></span><span style="COLOR: #008080"> 67</span> <span style="COLOR: #000000">{<br /></span><span style="COLOR: #008080"> 68</span> <span style="COLOR: #000000">    CPaintDC dc(</span><span style="COLOR: #0000ff">this</span><span style="COLOR: #000000">); </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"> device context for painting</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 69</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">    CRect rect;<br /></span><span style="COLOR: #008080"> 70</span> <span style="COLOR: #000000">    GetClientRect(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">rect);<br /></span><span style="COLOR: #008080"> 71</span> <span style="COLOR: #000000">    rect.left </span><span style="COLOR: #000000">+=</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">5</span><span style="COLOR: #000000">;<br /></span><span style="COLOR: #008080"> 72</span> <span style="COLOR: #000000">    rect.top </span><span style="COLOR: #000000">+=</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">5</span><span style="COLOR: #000000">;<br /></span><span style="COLOR: #008080"> 73</span> <span style="COLOR: #000000">    rect.right </span><span style="COLOR: #000000">-=</span><span style="COLOR: #000000"> SPOPUP_SHADOWWIDTH;<br /></span><span style="COLOR: #008080"> 74</span> <span style="COLOR: #000000">    rect.bottom </span><span style="COLOR: #000000">-=</span><span style="COLOR: #000000"> SPOPUP_SHADOWHEIGHT;<br /></span><span style="COLOR: #008080"> 75</span> <span style="COLOR: #000000">    dc.SetTextColor(RGB(</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">255</span><span style="COLOR: #000000">));<br /></span><span style="COLOR: #008080"> 76</span> <span style="COLOR: #000000">    dc.DrawText(m_sShowText,rect,DT_WORDBREAK</span><span style="COLOR: #000000">|</span><span style="COLOR: #000000">DT_NOPREFIX);<br /></span><span style="COLOR: #008080"> 77</span> <span style="COLOR: #000000">}<br /></span><span style="COLOR: #008080"> 78</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 79</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000"> CShadowWnd::ShowReadOnlyText(CString sText)<br /></span><span style="COLOR: #008080"> 80</span> <span style="COLOR: #000000">{<br /></span><span style="COLOR: #008080"> 81</span> <span style="COLOR: #000000">    m_sShowText </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> sText;    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">存入显示字符串 </span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 82</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">    CDC dc;<br /></span><span style="COLOR: #008080"> 83</span> <span style="COLOR: #000000">    dc.CreateDC(L</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">DISPLAY</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">,NULL,NULL,NULL);    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">创建一个显示设备描述表 </span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 84</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">    dc.SelectObject(GetStockObject(SYSTEM_FONT));    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">选择字体到设备描述表 </span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 85</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">    CRect rect(</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,MAXWIDTH,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">);<br /></span><span style="COLOR: #008080"> 86</span> <span style="COLOR: #000000">    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">获得待显示的字符串 sText 的实际高度和宽度 </span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 87</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">    dc.DrawText(sText,rect,DT_WORDBREAK</span><span style="COLOR: #000000">|</span><span style="COLOR: #000000">DT_CENTER</span><span style="COLOR: #000000">|</span><span style="COLOR: #000000">DT_CALCRECT</span><span style="COLOR: #000000">|</span><span style="COLOR: #000000">DT_NOPREFIX);<br /></span><span style="COLOR: #008080"> 88</span> <span style="COLOR: #000000">    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">为矩形留些余量 </span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 89</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">    rect.right </span><span style="COLOR: #000000">+=</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">3</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">SPOPUP_SHADOWWIDTH;<br /></span><span style="COLOR: #008080"> 90</span> <span style="COLOR: #000000">    rect.bottom </span><span style="COLOR: #000000">+=</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">3</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">SPOPUP_SHADOWHEIGHT;<br /></span><span style="COLOR: #008080"> 91</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">this</span><span style="COLOR: #000000">-&gt;</span><span style="COLOR: #000000">Create(rect,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">);    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">创建窗口 </span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 92</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">this</span><span style="COLOR: #000000">-&gt;</span><span style="COLOR: #000000">ShowWindow(SW_SHOW);<br /></span><span style="COLOR: #008080"> 93</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">this</span><span style="COLOR: #000000">-&gt;</span><span style="COLOR: #000000">UpdateWindow();    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">立刻更新窗口 <br /></span><span style="COLOR: #008080"> 94</span> <span style="COLOR: #008000">    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">进入消息循环,获取全部消息,控制整个系统 </span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 95</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">    MSG Msg;<br /></span><span style="COLOR: #008080"> 96</span> <span style="COLOR: #000000">    BOOL bDone;<br /></span><span style="COLOR: #008080"> 97</span> <span style="COLOR: #000000">    SetCapture();<br /></span><span style="COLOR: #008080"> 98</span> <span style="COLOR: #000000">    bDone </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> FALSE;<br /></span><span style="COLOR: #008080"> 99</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">while</span><span style="COLOR: #000000">(</span><span style="COLOR: #000000">!</span><span style="COLOR: #000000">bDone)<br /></span><span style="COLOR: #008080">100</span> <span style="COLOR: #000000">    {<br /></span><span style="COLOR: #008080">101</span> <span style="COLOR: #000000">        </span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(PeekMessage(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">Msg,NULL,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,PM_REMOVE))<br /></span><span style="COLOR: #008080">102</span> <span style="COLOR: #000000">            </span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(Msg.message </span><span style="COLOR: #000000">==</span><span style="COLOR: #000000"> WM_KEYDOWN </span><span style="COLOR: #000000">||</span><span style="COLOR: #000000"> Msg.message </span><span style="COLOR: #000000">==</span><span style="COLOR: #000000"> WM_SYSKEYDOWN</span><span style="COLOR: #000000">||</span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">103</span> <span style="COLOR: #000000">                Msg.message </span><span style="COLOR: #000000">==</span><span style="COLOR: #000000"> WM_LBUTTONDOWN </span><span style="COLOR: #000000">||</span><span style="COLOR: #000000"> Msg.message </span><span style="COLOR: #000000">==</span><span style="COLOR: #000000"> WM_RBUTTONDOWN)<br /></span><span style="COLOR: #008080">104</span> <span style="COLOR: #000000">                bDone </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> TRUE;<br /></span><span style="COLOR: #008080">105</span> <span style="COLOR: #000000">            </span><span style="COLOR: #0000ff">else</span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">106</span> <span style="COLOR: #000000">            {<br /></span><span style="COLOR: #008080">107</span> <span style="COLOR: #000000">                TranslateMessage(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">Msg);<br /></span><span style="COLOR: #008080">108</span> <span style="COLOR: #000000">                DispatchMessage(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">Msg);<br /></span><span style="COLOR: #008080">109</span> <span style="COLOR: #000000">            }<br /></span><span style="COLOR: #008080">110</span> <span style="COLOR: #000000">    }<br /></span><span style="COLOR: #008080">111</span> <span style="COLOR: #000000">    ReleaseCapture();<br /></span><span style="COLOR: #008080">112</span> <span style="COLOR: #000000">    DestroyWindow();<br /></span><span style="COLOR: #008080">113</span> <span style="COLOR: #000000">}<br /></span><span style="COLOR: #008080">114</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">115</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000"> CShadowWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)<br /></span><span style="COLOR: #008080">116</span> <span style="COLOR: #000000">{<br /></span><span style="COLOR: #008080">117</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000"> (CWnd::OnCreate(lpCreateStruct) </span><span style="COLOR: #000000">==</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">-</span><span style="COLOR: #000000">1</span><span style="COLOR: #000000">)<br /></span><span style="COLOR: #008080">118</span> <span style="COLOR: #000000">        </span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">-</span><span style="COLOR: #000000">1</span><span style="COLOR: #000000">;<br /></span><span style="COLOR: #008080">119</span> <span style="COLOR: #000000">    CenterWindow();<br /></span><span style="COLOR: #008080">120</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;<br /></span><span style="COLOR: #008080">121</span> <span style="COLOR: #000000">}<br /></span><span style="COLOR: #008080">122</span> <span style="COLOR: #000000"></span></div><br />四.使用方法: <br /><br />1.   将该类增加到一个项目文件中 <br /><br />2. 在你欲使用函数的类(一般为视类或框架窗口类)中增加一个成员变量(如:CShadowWnd m_ShadowWnd),当需要使用带阴影的弹出窗口显示信息时,调用成员函数(如: m_ShadowWnd.ShowReadOnlyText(String sText)即可,无须考虑其实现细节<img src ="http://www.cppblog.com/eday/aggbug/15709.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-28 01:05 <a href="http://www.cppblog.com/eday/archive/2006/11/28/15709.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>在VC中改变控件的背景色</title><link>http://www.cppblog.com/eday/archive/2006/11/27/15706.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Mon, 27 Nov 2006 12:35:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/27/15706.html</guid><description><![CDATA[        在VC编程中要改变控件（诸如CView, CFrameWnd, or CWnd等）的背景色可通过处理特定的消息来实现。但如果想改变按钮的颜色,就只能使用自绘制的按钮(也可以用位图按钮，此处未做说明)而不能通过OnCtlColor()改变。 <br />　　 一、在一个MFC应用程序中，要改变控件的背景色可通过重载OnCtlColor()函数来实现。方法是在该函数中设置所需颜色后再返回一个画刷句柄便可重绘控件背景色。OnCtlColor()函数对于控件背景色的处理是通过捕捉相应的控件消息来实现的。常用的此类消息有： <br /><br />CTLCOLOR_DLG 对话框 <br /><br />CTLCOLOR_EDIT 编辑框 <br /><br />CTLCOLOR_LISTBOX 列表框 <br /><br />CTLCOLOR_MSGBOX 消息框 <br /><br />CTLCOLOR_SCROLLBAR 滑动条 <br /><br />CTLCOLOR_STATIC 静态文本框、矩形等。 <br /><br />以下示例代码说明如何更改以上控件的背景色： <br /><br />//CmyDialog.h定义 <br /><br />class CMyDialog : public Cdialog //派生自己的对话框类 <br /><br />{ <br /><br />…….. <br /><br />// Implementation <br /><br />protected: <br /><br />// Generated message map functions <br /><br />//{{AFX_MSG(CMyDialog) <br /><br />afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor); <br /><br />……. <br /><br />//}}AFX_MSG <br /><br />DECLARE_MESSAGE_MAP() <br /><br />}; <br /><br />//CmyDialog.cpp 定义 <br /><br />…… <br /><br />HBRUSH CMyDialog::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) <br /><br />{ <br /><br />switch (nCtlColor) { <br /><br />case CTLCOLOR_EDIT: <br /><br />case CTLCOLOR_MSGBOX: <br /><br />case CTLCOLOR_DLG : <br /><br />case CTLCOLOR_EDIT : //在此加入你想要改变背景色的控件消息 <br /><br />pDC-&gt;SetBkMode(TRANSPARENT); <br /><br />HBRUSH B = CreateSolidBrush(COLOR); //COLOR是你想设置的颜色 <br /><br />return (HBRUSH) B; <br /><br />default: //其他控件设置自己默认的颜色和背景刷. <br /><br />return CDialog::OnCtlColor(pDC, pWnd, nCtlColor); <br /><br />}} <br /><br />说明：1、可分别处理以上消息以实现不同控件不同背景色。 <br /><br />2、此方法不适用于按纽控件。 <br /><br />二、通过定制来实现不同颜色按纽。 <br /><br />以下通过定制方形彩色按纽来说明： <br /><br />第一步：派生出自己的按纽类。 <br /><br />//CcolorButton.h <br /><br />class CColorButton : public CButton <br /><br />{ <br /><br />DECLARE_DYNAMIC(CColorButton) <br /><br />public: <br /><br />CColorButton(); <br /><br />virtual ~CColorButton(); <br /><br />BOOL Attach(const UINT nID, CWnd* pParent, <br /><br />const COLORREF BGColor = RGB(192, 123, 192), // 按纽的背景色 <br /><br />const COLORREF FGColor = RGB(1, 1, 1), // 文本颜色 <br /><br />); <br /><br />protected: <br /><br />virtual void DrawItem(LPDRAWITEMSTRUCT lpDIS); //重定义虚拟函数DrawItem <br /><br />void DrawFrame(CDC *DC, CRect R); //绘制按纽框 <br /><br />void DrawFilledRect(CDC *DC, CRect R, COLORREF color); //填充按纽框 <br /><br />void DrawLine(CDC *DC, CRect EndPoints, COLORREF color); <br /><br />void DrawLine(CDC *DC, long left, long top, long right, long bottom, COLORREF color); <br /><br />void DrawButtonText(CDC *DC, CRect R, const char *Buf, COLORREF TextColor); <br /><br />//绘制按纽上的文本 <br /><br />COLORREF GetFGColor() { return m_fg; } <br /><br />COLORREF GetBGColor() { return m_bg; } <br /><br />private: <br /><br />COLORREF m_fg, m_bg; <br /><br />}; <br /><br />#endif <br /><br />第二步：定义各函数 <br /><br />//CcolorButton.cpp <br /><br />…… <br /><br />// CColorButton <br /><br />IMPLEMENT_DYNAMIC(CColorButton, CButton) <br /><br />CColorButton::CColorButton() <br /><br />{ } <br /><br />CColorButton::~CColorButton() <br /><br />{ <br /><br />} <br /><br />//定义Attach（）函数 <br /><br />BOOL CColorButton::Attach(const UINT nID, CWnd* pParent, const COLORREF BGColor, const COLORREF FGColor) <br /><br />{ <br /><br />if (!SubclassDlgItem(nID, pParent)) <br /><br />return FALSE; <br /><br />m_fg = FGColor; <br /><br />m_bg = BGColor; <br /><br />return TRUE; <br /><br />} <br /><br />//重载DrawItem（） <br /><br />void CColorButton::DrawItem(LPDRAWITEMSTRUCT lpDIS) <br /><br />{ <br /><br />CDC* pDC = CDC::FromHandle(lpDIS-&gt;hDC); <br /><br />UINT state = lpDIS-&gt;itemState; <br /><br />CRect focusRect, btnRect; <br /><br />focusRect.CopyRect(&amp;lpDIS-&gt;rcItem); //按纽的选中虚线框 <br /><br />btnRect.CopyRect(&amp;lpDIS-&gt;rcItem); <br /><br />// 设置表示按纽被选中的虚线框 <br /><br />focusRect.left += 4; <br /><br />focusRect.right -= 4; <br /><br />focusRect.top += 4; <br /><br />focusRect.bottom -= 4; <br /><br />// 按纽标题 <br /><br />const int bufSize = 512; <br /><br />TCHAR buffer[bufSize]; <br /><br />GetWindowText(buffer, bufSize); <br /><br />// 绘制并标志按纽 <br /><br />DrawFilledRect(pDC, btnRect, GetBGColor()); <br /><br />DrawFrame(pDC, btnRect); <br /><br />DrawButtonText(pDC, btnRect, buffer, GetFGColor()); <br /><br />// 如果按纽处于选中状态则在其上绘制选中虚线框 <br /><br />if (state &amp; ODS_FOCUS) { <br /><br />DrawFocusRect(lpDIS-&gt;hDC, (LPRECT)&amp;focusRect); <br /><br />} <br /><br />} <br /><br />void CColorButton::DrawFrame(CDC *DC, CRect R) <br /><br />{ //绘制按纽，用户通过定制该函数可实现不同形状的按纽。 <br /><br />DrawLine(DC, R.left, R.top, R.right, R.top, RGB(255, 255, 255)); <br /><br />DrawLine(DC, R.left, R.top, R.left, R.bottom, RGB(255, 255, 255)); <br /><br />//以下绘制按纽的外围框线以使按纽有立体感 <br /><br />DrawLine(DC, R.left + 1, R.bottom - 1, R.right, R.bottom - 1, RGB(1, 1, 1)); <br /><br />//绘制按纽左框线和上框线 <br /><br />DrawLine(DC, R.right - 1, R.top + 1, R.right - 1, R.bottom, RGB(1, 1, 1)); <br /><br />//绘制按纽右框线和下框线 <br /><br />} <br /><br />//用色彩填充按纽框 <br /><br />void CColorButton::DrawFilledRect(CDC *DC, CRect R, COLORREF color) <br /><br />{ <br /><br />CBrush B; <br /><br />B.CreateSolidBrush(color); <br /><br />DC-&gt;FillRect(R, &amp;B); <br /><br />} <br /><br />// DrawLine用于绘制按纽，其为多态函数 <br /><br />void CColorButton::DrawLine(CDC *DC, CRect EndPoints, COLORREF color) <br /><br />{ <br /><br />…… <br /><br />} <br /><br />void CColorButton::DrawLine(CDC *DC, long left, long top, long right, long bottom, COLORREF color) <br /><br />{ <br /><br />…… <br /><br />} <br /><br />//绘制按纽文本 <br /><br />void CColorButton::DrawButtonText(CDC *DC, CRect R, const char *Buf, COLORREF TextColor) <br /><br />{ <br /><br />COLORREF prevColor = DC-&gt;SetTextColor(TextColor); <br /><br />DC-&gt;SetBkMode(TRANSPARENT); <br /><br />DC-&gt;DrawText(Buf, strlen(Buf), R, DT_CENTER|DT_VCENTER|DT_SINGLELINE); <br /><br />DC-&gt;SetTextColor(prevColor); <br /><br />} <br /><br />第三步：引用定制类 <br /><br />定制任意对话框CColorDlg，在其上画一按键控件。ID为IDOK。 <br /><br />//CColorDlg.h <br /><br />class CColorDlg : public CDialog <br /><br />{ <br /><br />….. <br /><br />// Implementation <br /><br />protected: <br /><br />CColorButton m_btnOK; <br /><br />} <br /><br />//CColorDlg.cpp <br /><br />……. <br /><br />BOOL CColorBtnSampleDlg::OnInitDialog() <br /><br />{ <br /><br />CDialog::OnInitDialog(); <br /><br />……. <br /><br />VERIFY(m_btnOK.Attach(IDOK, this, RED, BLUE, YELLOW)); <br /><br />……. <br /><br />}<br /><img src ="http://www.cppblog.com/eday/aggbug/15706.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-27 20:35 <a href="http://www.cppblog.com/eday/archive/2006/11/27/15706.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Windows 中不规则窗体的编程实现</title><link>http://www.cppblog.com/eday/archive/2006/11/26/15668.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 25 Nov 2006 16:28:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/26/15668.html</guid><description><![CDATA[
		<img height="1" src="http://www.pcbookcn.com/images/ads_200.gif" width="1" align="left" />
		<span class="main">
				<font size="3">
						<strong>一、序言</strong>
						<br />
						<br />　　在绝大多数的Windows应用程序中，其窗体都是使用的正规正矩的矩形窗体,例如我们常用的，“记事本”，“扫雷”，等等。矩形窗体，具有编程实现简单，风格简洁的优点，所以在普通文档应用程序和简单小游戏中使用足矣。但在某些娱乐游戏程序中使用就略显呆板些了，这时若用不规则窗体替代原先的矩形窗体，将会使这类程序更添情趣。典型的例子有windows 自代的Media Player,新版本的Media Player有个控制面板的选项，选中这些面板，播放器就以选中的面板形状出现，这时的播放器比以前版本的Media Player的古老矩形界面要生动有趣的多了。 要实现不规则窗体不是太难，知道了基本原理后，你也可以创建各种有趣的不规则窗体。<br /><br />　　<b>二、实现原理</b><br /><br />　　所有的 Windows 窗体都位于一个称为“region”中，窗体的大小如果超出“region”的范围，windows 会自动裁剪超出"region"范围那部分的窗体，使其不可见。所以，要创建不规则窗体有两个步骤：第一步就是创建不规则"region".第二步就是将窗体放到创建的“region”中。<br /><br />　　其中第二步很简单就调用一条语句即可。在SDK中调用API函数SetWindowRgn，该函数原型如下：<br /><br /></font>
				<table bordercolor="#ffcc66" width="90%" align="center" bgcolor="#dadacf" border="1">
						<tbody>
								<tr>
										<td>int SetWindowRgn( HWND hWnd, HRGN hRgn, BOOL bRedraw );</td>
								</tr>
						</tbody>
				</table>
				<br />　　其中hWnd为待设置的窗体句柄，hRgn为已经创建的"region"句柄，bRedraw代表是否要重绘窗体。在MFC 中使用窗口类CWnd的成员函数int CWnd::SetWindowRgn（HRGN hRgn, BOOL bRedraw );该函数的参数意义与API中同名函数相同。<br /><br />　　相对与第二步，创建不规则窗体的第一步要复杂许多，并且不规则窗体越复杂，创建其"region"的过程也越复杂。接下去我们将由浅入深地介绍各种创建”region”的方法。<br /><br />　　在MFC中"region"对象，由CRgn类实现。CRgn的几乎每个成员函数都有同名的SDK API函数对应。<br /><br />　　<b>三、简单“region”的创建</b><br /><br />　　类CRgn创建一个新的"region"的简单方法有以下几个成员函数： BOOL CRgn::CreateRectRgn( int x1, int y1, int x2, int y2 ); 创建矩形的“region”。 <br /><br /><table bordercolor="#ffcc66" width="90%" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>BOOL CRgn::CreateEllipticRgn( int x1, int y1, int x2, int y2 ); 创建圆形或椭圆形“region”。 <br />BOOL CRgn::CreateRoundRectRgn( int x1, int y1, int x2, int y2, int x3, int y3 ); 创建圆角矩形“region”。 <br />BOOL CRgn::CreatePolygonRgn( LPPOINT lpPoints, int nCount, int nMode ); 创建多边形“region”。 </td></tr></tbody></table><br />　　这里以创建椭圆窗体为例，介绍椭圆窗体创建的方法。在创建椭圆“region”的CreateEllipticRgn函数中，x1,y1指椭圆所在矩形的左上角坐标，x2,y2指该矩形的右下角坐标。<br /><br />　　下面的代码加入到MFC对话框程序的OnInitDialog函数中，可将该对话框变成椭圆窗体：<br /><br /><table bordercolor="#ffcc66" width="90%" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>BOOL CTestDlg::OnInitDialog()<br />{<br />CDialog::OnInitDialog();<br />...<br />CRgn rgn;<br />rgn. CreateEllipticRgn(0,0,200,100);<br />SetWindowRgn(rgn,TRUE);<br />}</td></tr></tbody></table><br /><table width="90%" align="center" border="0"><tbody><tr><td><div align="center"><img src="http://www.cppblog.com/images/cppblog_com/eday/2991/o_rgn1.gif" /><br />图一 椭圆窗体效果图</div></td></tr></tbody></table><br />　　<b>四、作图路径法创建”region”</b><br /><br />　　使用该方法创建”region”的过程如下：<br /><br />　　第一步绘制所要创建的窗体形状。<br />　 <br />　　该步骤中使用到CDC类中的一些成员函数如下：BOOL CDC::BeginPath( );<br /><br />　　调用该函数后当前设备环境(DC)开始追踪绘图的过程。<br /><br /><table bordercolor="#ffcc66" width="90%" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>int CDC::SetBkMode( int nBkMode );</td></tr></tbody></table><br />　　设置绘图时的背景模式，此应用中nBkMode必须取值为TRANSPARENT 。即设置绘图时背景不发生变化。<br /><br /><table bordercolor="#ffcc66" width="90%" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>BOOL CDC::EndPath( );</td></tr></tbody></table><br />　　调用该函数后当前设备环境(DC)结束追踪绘图的过程。<br /><br />　　开始绘图前，先调用BeginPath，然后调用SetBkMode。接下去就可调用CDC的其他绘图函数作图，例如Arc,AngleArc,LineTo,MoveTo,RoundRect,,Textout等等。绘图完毕调用EndPath().<br /><br />　　第二步将绘制的结果转成”region”.<br /><br />　　此步骤中使用SDK API函数<br /><br /><table bordercolor="#ffcc66" width="90%" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>HRGN PathToRegion( HDC hdc );</td></tr></tbody></table><br />　　Hdc为作图DC的句柄， CDC类中的m_hDC成员变量可做此参数传入。示例，将下面代码加入某个按钮单击事件中，可以将当前窗体变为字符串”hello”的形状<br /><br /><table bordercolor="#ffcc66" width="90%" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>void CTestDlg::OnTest() <br />{<br />　HRGN wndRgn;<br />　CClientDC dc(this);<br />　CFont mFont;<br /><br />　if (dc.m_hDC!=NULL)<br />　{<br />　　VERIFY(mFont.CreateFont(200, 50, 0, 0, FW_HEAVY, TRUE, FALSE, 0, ANSI_CHARSET, OUT_DEFAULT_PRECIS, <br />CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, "宋体")); <br /><br />　　//开始记录窗体轮廓路径<br />　　dc.BeginPath(); <br /><br />　　//设置背景为透明模式,这句话是必须有的。<br />　　dc.SetBkMode(TRANSPARENT); <br /><br />　　CFont * pOldFont;<br />　　pOldFont = dc.SelectObject( &amp;mFont );<br />　　dc.TextOut(0, 0, "Hello");<br /><br />　　//结束记录窗体轮廓路径<br />　　dc.SelectObject( pOldFont );<br />　　dc.EndPath();<br /><br />　　//把所记录的路径转化为窗体轮廓句柄<br />　　wndRgn = ::PathToRegion(dc.m_hDC);<br /><br />　　//赋予窗体指定的轮廓形状 <br />　　this-&gt;SetWindowRgn(wndRgn, TRUE); <br />　}<br />}</td></tr></tbody></table><br />　　CClientDC是CDC的派生类，故此该类具有所有CDC类的成员变量和成员函数。 <br /><br /><table width="90%" align="center" border="0"><tbody><tr><td><div align="center"><img src="http://www.cppblog.com/images/cppblog_com/eday/2991/o_rgn2.gif" /><br />图二 hello形状的窗体效果图</div></td></tr></tbody></table><br />　　<b>五、根据图像创建”region”</b><br /><br />　　此法创建不规则窗体比较复杂。首先准备一张含有目标窗体形状的图片，设置透明色即将图片中部不属于窗体形状的部分，标记成同一种颜色，例如蓝色RGB(0,0,255).程序运行后先装入图片。然后逐个扫描图片的每个像素，如这个像素不属于透明色，则在相应位置创建一个只含一个像素的“region”然后将这些小”region ”合并起来组成一个任意形状的”region”.这里将使用到CRgn的一个成员函数 ：int CRgn::CombineRgn( CRgn* pRgn1, CRgn* pRgn2, int nCombineMode );<br /><br />　　其中pRgn1,pRgn2为要合并的两个“region”，nCombineMode为合并的方式，此应用中取RGN_OR，即两”region”全部合并去处重复部分。代码实现如下：<br /><br /><table bordercolor="#ffcc66" width="90%" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>void SetupRegion(<br />　CDC *pDC, //窗体的DC指针<br />　CBitmap &amp;cBitmap, //含有窗体形状的位图对象<br />　COLORREF TransColor //透明色<br />)<br />{ <br />　CDC memDC;<br />　//创建与传入DC兼容的临时DC<br />　memDC.CreateCompatibleDC(pDC);<br /><br />　CBitmap *pOldMemBmp=NULL;<br />　//将位图选入临时DC<br />　pOldMemBmp=memDC.SelectObject(&amp;cBitmap);<br /><br />　CRgn wndRgn;<br />　//创建总的窗体区域，初始region为0<br />　wndRgn.CreateRectRgn(0,0,0,0);<br /><br />　BITMAP bit; <br />　cBitmap.GetBitmap (&amp;bit);//取得位图参数，这里要用到位图的长和宽 <br /><br />　int y;<br />　for(y=0;y&lt;=bit.bmHeight ;y++)<br />　{<br />　　CRgn rgnTemp; //保存临时region<br /><br />　　int iX = 0;<br />　　do<br />　　{<br />　　　//跳过透明色找到下一个非透明色的点.<br />　　　while (iX &lt;= bit.bmWidth &amp;&amp; memDC.GetPixel(iX, y) == TransColor)<br />　　　　iX++;<br />　　　　//记住这个起始点<br />　　　　int iLeftX = iX;<br /><br />　　　　//寻找下个透明色的点<br />　　　　while (iX &lt;= bit.bmWidth &amp;&amp; memDC.GetPixel(iX, y) != TransColor)<br />　　　　　++iX;<br /><br />　　　　//创建一个包含起点与重点间高为1像素的临时“region”<br />　　　　rgnTemp.CreateRectRgn(iLeftX, y, iX, y+1);<br /><br />　　　　//合并到主"region".<br />　　　　wndRgn.CombineRgn(&amp;wndRgn, &amp;rgnTemp, RGN_OR);<br /><br />　　　//删除临时"region",否则下次创建时和出错<br />　　　rgnTemp.DeleteObject();<br />　　}while(iX GetWindow();<br />　　pWnd-&gt;SetWindowRgn(wndRgn,TRUE); <br />　　pWnd-&gt;SetForegroundWindow(); <br />　}</td></tr></tbody></table><br />　　上述代码创建的不规则窗体中，在OnEraseBkgnd事件中绘制该位图，就可得到与该位图形状一模一样的窗体。 <br /><br /><table width="90%" align="center" border="0"><tbody><tr><td><div align="center"><img src="http://www.cppblog.com/images/cppblog_com/eday/2991/o_rgn3.gif" /><br />图三 根据位图和位图中的透明色创建的窗体效果图</div></td></tr></tbody></table><br />　　<b>六、小结</b><br /><br />　　三种创建“region”的方法，第一种最简单，如果所需的窗体形状是简单的几何图形，这种方法最合适；第二种稍微复杂些，但是创建的窗体形状更多些；第三种方法可以创建任何在图片中画出的窗体形状，但是实现的复杂度也最高。 <br /></span>
<img src ="http://www.cppblog.com/eday/aggbug/15668.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-26 00:28 <a href="http://www.cppblog.com/eday/archive/2006/11/26/15668.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>是使用_beginthread 还是 CreateThread  !</title><link>http://www.cppblog.com/eday/archive/2006/11/25/15648.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Fri, 24 Nov 2006 16:51:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/25/15648.html</guid><description><![CDATA[
		<div class="postText">
				<p>CreateThread 是一个Win 32API 函数,_beginthread 是一个CRT(C Run-Time)函数，他们都是实现多线城的创建的函数，而且他们拥有相同的使用方法,相同的参数列表。</p>
				<p>但是他们有什么区别呢？</p>
				<p>一般来说，从使用角度是没有多大的区别的，CRT函数中除了signal()函数不能在CreateThread创建的线城中使用外，其他的CRT函数都可一正常使用，但是如果在CreateThread创建的线城中使用CRT函数的话，会产生一些Memory Leak.</p>
				<p>下面是摘自KB的原话：</p>
				<div class="summary">
						<h2>SUMMARY</h2>All C Run-time functions except the signal() function work correctly when used in threads created by the CreateThread() function. However, depending on what CRT functions are called, there may be a small memory leak when threads are terminated. Calling strlen(), for example, does not trigger the allocation of the CRT thread data-block, and calling malloc(), fopen(), _open(), strtok(), ctime(), or localtime() causes allocation of a CRT per-thread data-block, which may cause a memory leak. </div>
				<div class="moreinformation">
						<h2>MORE INFORMATION</h2>The "Programming Techniques" manual supplied with Visual C++ 32-bit Edition states that using CreateThread() in a program that uses Libcmt.lib causes many CRT functions to fail. Actually, the only function that should not be used in a thread created with CreateThread() is the signal() function. <br /><br />There are two ways to create threads. One method involves using the CRT _beginthread() or _beginthreadex() (with Visual C++ 2.0 and later); the other method involves using the CreateThread() API. All CRT functions other than the signal() function work correctly in threads created with either _beginthread() or CreateThread(). However, there are some problems involved with using CRT functions in threads created with CreateThread(). <br /><br />Threads that are created and terminated with the CreateThread() and ExitThread() Win32 API functions do not have memory that is allocated by the CRT for static data and static buffers cleaned up when the thread terminates. Some examples of this type of memory are static data for errno and _doserrno and the static buffers used by functions such as asctime(), ctime(), localtime(), gmtime(), and mktime(). Using CreateThread() in a program that uses the CRT (for example, links with LIBCMT.LIB) may cause a memory leak of about 70-80 bytes each time a thread is terminated. <br /><br />To guarantee that all static data and static buffers allocated by the CRT are cleaned up when the thread terminates, _beginthreadex() and _endthreadex() should be used when creating a thread. The _beginthreadex() function includes the same parameters and functionality as CreateThread(). <br /></div>
				<div class="moreinformation">另外有个小小的测验：</div>
				<div class="moreinformation"> 用CreateThread 创建的线城能否被CRT函数 _endthreadex() 关闭？</div>
				<div class="moreinformation"> </div>
				<div class="moreinformation"> </div>
				<div class="moreinformation">CreateThread()和_beginthreadex()在Jeffrey的《Windows核心编程》中讲的很清楚，应当尽量避免使用CreateThread()。 <br />事实上，_beginthreadex()在内部先为线程创建一个线程特有的tiddata结构，然后调用CreateThread()。在某些非线程安全的CRT函数中会请求这个结构。如果直接使用CreateThread()的话，那些函数发现请求的tiddata为NULL，就会在现场为该线程创建该结构，此后调用EndThread()时会引起内存泄漏。_endthreadex()可以释放由CreateThread()创建的线程，实际上，在它的内部会先释放由_beginthreadex()创建的tiddata结构，然后调用EndThread()。 <br />因此，应当使用_beginthreadex()和_endthreadex()，而避免使用CreateThread()和EndThread()。当然，_beginthread()和_endthread()也是应当避免使用的。 <br /></div>
				<div class="moreinformation"> </div>
				<div class="moreinformation"> </div>
				<div class="moreinformation">程序员对于Windows程序中应该用_beginthread还是CreateThread来创建线程，一直有所争论。本文将从对CRT源代码出发探讨这个问题。 
<p><font face="courier new, courier, mono">I. 起因</font></p><p><font face="courier new, courier, mono">今天一个朋友问我程序中究竟应该使用_beginthread还是CreateThread，并且告诉我如果使用不当可能会有内存泄漏。其实我过去对这个问题也是一知半解，为了对朋友负责，专门翻阅了一下VC的运行库（CRT）源代码，终于找到了答案。</font></p><p><font face="courier new, courier, mono">II. CRT</font></p><p><font face="courier new, courier, mono">CRT(C/C++ Runtime Library)是支持C/C++运行的一系列函数和代码的总称。虽然没有一个很精确的定义，但是可以知道，你的main就是它负责调用的，你平时调用的诸如strlen、strtok、time、atoi之类的函数也是它提供的。我们以Microsoft Visual.NET 2003中所附带的CRT为例。假设你的.NET 2003安装在C:Program FilesMicrosoft Visual Studio .NET 2003中，那么CRT的源代码就在C:Program FilesMicrosoft Visual Studio .NET 2003Vc7crtsrc中。既然有了这些实现的源代码，我们就可以找到一切解释了。</font></p><p><font face="courier new, courier, mono">III. _beginthread/_endthread</font></p><p><font face="courier new, courier, mono">这个函数究竟做了什么呢？它的代码在thread.c中。阅读代码，可以看到它最终也是通过CreateThread来创建线程的，主要区别在于，它先分配了一个_tiddata，并且调用了_initptd来初始化这个分配了的指针。而这个指针最后会被传递到CRT的线程包装函数_threadstart中，在那里会把这个指针作为一个TLS（Thread Local Storage）保存起来。然后_threadstart会调用我们传入的线程函数，并且在那个函数退出后调用_endthread。这里也可以看到，_threadstart用一个__try/__except块把我们的函数包了起来，并且在发生异常的时候，调用exit退出。（_threadstart和endthread的代码都在thread.c中）<br />这个_tiddata是一个什么样的结构呢？它在mtdll.h中定义，它的成员被很多CRT函数所用到，譬如int _terrno，这是这个线程中的错误标志；char* _token，strtok以来这个变量记录跨函数调用的信息，...。<br />那么_endthread又做了些什么呢？除了调用浮点的清除代码以外，它还调用了_freeptd来释放和这个线程相关的tiddata。也就是说，在_beginthread里面分配的这块内存，以及在线程运行过程中其它CRT函数中分配并且记录在这个内存结构中的内存，在这里被释放了。<br />通过上面的代码，我们可以看到，如果我使用_beginthread函数创建了线程，它会为我创建好CRT函数需要的一切，并且最后无需我操心，就可以把清除工作做得很好，可能唯一需要注意的就是，如果需要提前终止线程，最好是调用_endthread或者是返回，而不要调用ExitThread，因为这可能造成内存释放不完全。同时我们也可以看出，如果我们用CreateThread函数创建了线程，并且不对C运行库进行调用（包括任何间接调用），就不必担心什么问题了。</font></p><p><font face="courier new, courier, mono">IV. CreateThread和CRT</font></p><p><font face="courier new, courier, mono">或许有人会说，我用CreateThread创建线程以后，我也调用了C运行库函数，并且也使用ExitThread退出了，可是我的程序运行得好好的，既没有因为CRT没有初始化而崩溃，也没有因为忘记调用_endthread而发生内存泄漏，这是为什么呢，让我们继续我们的CRT之旅。<br />假设我用CreateThread创建了一个线程，我调用strtok函数来进行字符串处理，这个函数肯定是需要某些额外的运行时支持的。strtok的源代码在strtok.c中。从代码可见，在多线程情况下，strtok的第一句有效代码就是_ptiddata ptd = _getptd()，它通过这个来获得当前的ptd。可是我们并没有通过_beginthread来创建ptd，那么一定是_getptd捣鬼了。打开tidtable.c，可以看到_getptd的实现，果然，它先尝试获得当前的ptd，如果不能，就重新创建一个，因此，后续的CRT调用就安全了。可是这块ptd最终又是谁释放的呢？打开dllcrt0.c，可以看到一个DllMain函数。在VC中，CRT既可以作为一个动态链接库和主程序链接，也可以作为一个静态库和主程序链接，这个在Project Setting-&gt;Code Generations里面可以选。当CRT作为DLL链接到主程序时，DllMain就是CRT DLL的入口。Windows的DllMain可以由四种原因调用：Process Attach/Process Detach/Thread Attach/Thread Detach，最后一个，也就是当线程函数退出后但是线程还没有销毁前，会在这个线程的上下文中用Thread Detach调用DllMain，这里，CRT做了一个_freeptd(NULL)，也就是说，如果有ptd，就free掉。所以说，恰巧没有发生内存泄漏是因为你用的是动态链接的CRT。<br />于是我们得出了一个更精确的结论，如果我没有使用那些会使用_getptd的CRT函数，使用CreateThread就是安全的。</font></p><p><font face="courier new, courier, mono">V. 使用ptd的函数</font></p><p><font face="courier new, courier, mono">那么，究竟那些函数使用了_getptd呢？很多！在CRT目录下搜索_getptd，你会发觉很多意想不到的函数都用到了它，除了strtok、rand这类需要保持状态的，还有所有的字符串相关函数，因为它们要用到ptd中的locale信息；所有的mbcs函数，因为它们要用到ptd中的mbcs信息，...。</font></p><p><font face="courier new, courier, mono">VI. 测试代码</font></p><p><font face="courier new, courier, mono">下面是一段测试代码（leaker中用到了atoi，它需要ptd）：</font></p><p><font face="courier new, courier, mono">#include <windows.h><br />#include <process.h><br />#include <iostream><br />#include <crtdbg.h></crtdbg.h></iostream></process.h></windows.h></font></p><p><font face="courier new, courier, mono">volatile bool threadStarted = false;</font></p><p><font face="courier new, courier, mono">void leaker()<br />{<br />    std::cout &lt;&lt; atoi( "0" ) &lt;&lt; std::endl;<br />}</font></p><p><font face="courier new, courier, mono">DWORD __stdcall CreateThreadFunc( LPVOID )<br />{<br />    leaker();<br />    threadStarted = false;<br />    return 0;<br />}</font></p><p><font face="courier new, courier, mono">DWORD __stdcall CreateThreadFuncWithEndThread( LPVOID )<br />{<br />    leaker();<br />    threadStarted = false;<br />    _endthread();<br />    return 0;<br />}</font></p><p><font face="courier new, courier, mono">void __cdecl beginThreadFunc( LPVOID )<br />{<br />    leaker();<br />    threadStarted = false;<br />}</font></p><p><font face="courier new, courier, mono">int main()<br />{<br />    for(;;)<br />    {<br />        while( threadStarted )<br />            Sleep( 5 );<br />        threadStarted = true;<br />//      _beginthread( beginThreadFunc, 0, 0 );//1<br />        CreateThread( NULL, 0, CreateThreadFunc, 0, 0, 0 );//2<br />//      CreateThread( NULL, 0, CreateThreadFuncWithEndThread, 0, 0, 0 );//3<br />    }<br />    return 0;<br />}</font></p><p><font face="courier new, courier, mono">如果你用VC的多线程+静态链接CRT选项去编译这个程序，并且尝试打开1、2、3之中的一行，你会发觉只有2打开的情况下，程序才会发生内存泄漏（可以在Task Manager里面明显的观察到）。3之所以不会出现内存泄漏是因为主动调用了_endthread。</font></p><p><font face="courier new, courier, mono">VII. 总结</font></p><p><font face="courier new, courier, mono">如果你使用了DLL方式链接的CRT库，或者你只是一次性创建少量的线程，那么你或许可以采取鸵鸟策略，忽视这个问题。上面一节代码中第3种方法基于对CRT库的了解，但是并不保证这是一个好的方法，因为每一个版本的VC的CRT可能都会有些改变。看来，除非你的头脑清晰到可以记住这一切，或者你可以不厌其烦的每调用一个C函数都查一下CRT代码，否则总是使用_beginthread（或者它的兄弟_beginthreadex）是一个不错的选择。</font></p><p><font face="Courier New"><strong>[后记]<br />网友condor指出本文的一个错误：在dllcrt0.c中，DllMain的Thread Detach所释放的ptd，其实是dllcrt0.c的DllMain中的Thread Attach所创建的。也就是说，当你用CRT DLL的时候，DllMain对线程做了一切初始化/清除工作。我查看源代码，thread.c中的_threadstart函数，在设置TLS之前做了检查，这其实就是为了避免重复设置导致的内存泄漏。</strong></font></p></div>
		</div>
<img src ="http://www.cppblog.com/eday/aggbug/15648.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-25 00:51 <a href="http://www.cppblog.com/eday/archive/2006/11/25/15648.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>WinAPI编程基础之消息分流器</title><link>http://www.cppblog.com/eday/archive/2006/11/22/15545.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Wed, 22 Nov 2006 08:53:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/22/15545.html</guid><description><![CDATA[对于熟悉Win API编程的同志们来说，windowsx.h这个头文件应该不会太陌生吧，这次要讲的内容就来自这个windowsx.h头文件。<br /><br />经常能在msdn上查到这样一些函数，明明是个函数，而且模样长得和一般的api函数也一样一样的，可却叫做macro，为什么呢？留意一下函数使用的requirement，你会发现，它的声明正是在windowsx.h这个头文件里。<br /><br />Windowsx.h包含了这样一些内容：<br />宏API，窗口消息分流器，控件API；<br /><br />所有的这些宏定义，可以使你的程序更加安全，简洁，结构更清晰，大大提高程序的可读性；其中窗口消息分流器（message cracker）是我们今天要讨论的话题，它可以使我们的API程序变得更简洁。下面就进入我们的主题：（有关windowsx.h的更多内容，可以参考MS Knowledge Base Article #83456.）<br /><br />消息分流器是Windows提供的一组宏定义，它的两个最大的作用，用MS的话来说，就是：<br /><br />● 安全的数据类型，因为消息分流器完成了大量的类型转换的工作；<br />● 使程序向32位windows的转化更简单；<br /><br />当然，使用消息分流器会大大改变程序的面貌，你也可以选择不使用它。<br /><br />下面我们就以一个对话框窗口的消息处理过程为例，看看消息分流器到底是怎么运作的。<br /><br /><br />1．消息分流器的基本使用<br />先看一个普通的窗口消息处理函数，它可能需要处理一些窗口的初始化，无效客户区重绘等消息：<br /><br />LRESULT CALLBACK WndProc (HWND hwnd, UINT msg, <br />WPARAM wParam, LPARAM lParam)<br />{<br />switch(msg)<br />{<br />  case WM_CREATE:<br />  // ...<br />  return 0;<br />    <br />  case WM_PAINT:<br />  // ...<br />  return 0;<br />          <br />  case WM_DESTROY:<br />  //...<br />  return 0;<br />}<br />return DefWindowProc(hwnd, msg, wParam, lParam);<br />}<br /><br />而通过使用消息分流器，我们可以把每个case都写到相应的消息处理函数中，就像下面这样：<br /><br />LRESULT CALLBACK WndProc (HWND hwnd, UINT msg, <br />WPARAM wParam, LPARAM lParam)<br />{<br />switch(msg)<br />{<br />  case WM_CREATE:<br />      return HANDLE_WM_CREATE(hwnd, wParam, lParam, Cls_OnCreate);<br />    <br />  case WM_PAINT:<br />      return HANDLE_WM_PAINT(hwnd, wParam, lParam, Cls_OnPaint);<br />          <br />  case WM_DESTROY:<br />  return HANDLE_WM_DESTROY(hwnd, wParam, lParam, Cls_OnDestroy);<br />}<br />return DefWindowProc(hwnd, msg, wParam, lParam);<br />}<br /><br />这里用到了三个宏定义：HANDLE_WM_CREATE, HANDLE_WM_PAINT, HANDLE_WM_DESTROY；这三个宏定义就是我们的三个消息分流器（别看叫什么分流器，说穿了也不值几个钱，呵呵），它们在windowsx.h中的定义如下：<br /><br />#define HANDLE_WM_CREATE(hwnd, wParam, lParam, fn) \<br />((fn)((hwnd), (LPCREATESTRUCT)(lParam)) ? 0L : (LRESULT)-1L)<br />#define HANDLE_WM_PAINT(hwnd, wParam, lParam, fn) \<br />((fn)(hwnd), 0L)<br />#define HANDLE_WM_DESTROYCLIPBOARD(hwnd, wParam, lParam, fn) \<br />  ((fn)(hwnd), 0L)<br /><br />把这三个宏定义替换回去，就变成：<br /><br />LRESULT CALLBACK WndProc (HWND hwnd, UINT msg, <br />WPARAM wParam, LPARAM lParam)<br />{<br />switch(msg)<br />{<br />  case WM_CREATE:<br />      return Cls_OnCreate(hwnd, (LPCREATESTRUCT)(lParam) ? 0L : (LRESULT)-1L;<br />    // 如果处理了消息，则Cls_OnCreate应返回TRUE，导致WndProc返回0，否则Cls_OnCreate返回FALSE，导致WndProc返回-1；<br />  case WM_PAINT:<br />      return Cls_OnPaint(hwnd), 0L;<br />    // 逗号表达式；Cls_OnPaint是void类型，这里返回0；     <br />  case WM_DESTROY:<br />  return Cls_OnDestroy(hwnd), 0L;         // 同Cls_OnPaint<br />}     <br />return DefWindowProc(hwnd, msg, wParam, lParam);<br />}<br /><br />之后我们就可以按照消息分流器的定义编写相应的消息处理函数了：<br /><br />BOOL Cls_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct){…};<br />void Cls_OnPaint(HWND hwnd){…};<br />void Cls_OnDestroyClipboard(HWND hwnd){…};<br /><br />windowsx.h还提供了一个更加简化的方法：使用HANDLE_MSG宏，这个宏是这样定义的：<br /><br />#define HANDLE_MSG(hwnd, message, fn)   \<br />  case (message): return HANDLE_##message((hwnd), (wParam), (lParam), (fn))<br /><br />这个宏要做的就是根据不同的message（##用来连接前后的字符串），把自己“变成”相应的HANDLE_XXXXMESSAGE形式的宏，再通过相应的宏来执行消息处理代码；<br />比如实际代码中写入：<br /><br />HANDLE_MSG(hwnd, WM_CREATE, Cls_OnCreate)<br /><br />则经过转换就变成：<br /><br />case (WM_CREATE): return HANDLE_WM_CREATE((hwnd), (wParam), (lParam), (Cls_OnCreate))<br /><br />这样，我们就可以直接把程序写为：<br />LRESULT CALLBACK WndProc (HWND hwnd, UINT msg, <br />WPARAM wParam, LPARAM lParam)<br />{<br />switch(msg)<br />{<br />HANDLE_MSG(hwnd, WM_CREATE, Cls_OnCreate);<br />HANDLE_MSG(hwnd, WM_PAINT, Cls_OnPaint);<br />HANDLE_MSG(hwnd, WM_DESTROY, Cls_OnDestroy);<br />}<br />return DefWindowProc(hwnd, msg, wParam, lParam);<br />}<br /><br />之后直接编写相应的消息处理过程就可以了。是不是简洁多了？而且把消息处理封装到函数里面，就可以使用VS直接跳转到这个函数，再也不用费劲去找那个case了。要注意的一点是，虽然windowsx.h里包括了所有消息对应的分流器，但它们的参数是宏定义显式说明的，在编写消息处理函数时，必须遵循宏定义中的参数类型，否则会导致错误；这么多消息分流器，我们每次新写一个消息处理函数时就得看看是否把参数设置正确了，整个过程繁琐冗长。好在已经有一个工具叫Message Cracker Wizard，可以帮助我们生成消息分流器和相关的处理过程，具体见：<a href="http://www.codeproject.com/win32/msgcrackwizard.asp" target="_blank">http://www.codeproject.com/win32/msgcrackwizard.asp</a>。<br /><br /><br />2．在对话框中使用消息分流器<br />在对话框消息处理中，窗口子类化是我们经常使用的手段，这也可以通过消息分流器实现，但是有点小问题 :&gt;<br />下面是一个使用了windowsx.h消息分流器的对话框及其处理过程：<br />……<br />int WINAPI _tWinMain(HINSTANCE hinstExe, HINSTANCE, PTSTR pszCmdLine, int) <br />{<br />DialogBoxParam(<br />        hinstExe, MAKEINTRESOURCE(IDD_PASSTHRU), NULL, (DLGPROC)Dlg_Proc, 0);<br /><br />  return(0);<br />}<br />……<br /><br />LRESULT CALLBACK Dlg_Proc (HWND hwnd, UINT msg, <br />WPARAM wParam, LPARAM lParam)<br />{<br />switch(msg)<br />{<br />HANDLE_MSG(hwnd, WM_INITDIALOG, Cls_OnInitDialog); // 不能直接使用HANDLE_MSG宏<br />HANDLE_MSG(hwnd, WM_COMMAND, Cls_OnCommand);     // 不能直接使用HANDLE_MSG宏<br />}<br /><br />return false;<br />}<br /><br />以上程序中直接使用HANDLE_MSG可能导致错误；为什么呢？问题出在子类化的消息处理过程的返回值上，msdn中对于对话框消息处理过程的返回值有如下说明：<br /><br />一般情况下，对话框过程函数应该在处理了消息的情况下返回TRUE，如果没有处理，则返回FALSE。如果对话框过程返回了FALSE，那么对话框管理器为这条消息准备默认的对话操作。<br /><br />如果对话框处理了一个需要特定返回值的消息，则对话框的返回值应该被设置为调用SetWindowLong后的返回值，并在返回TRUE之前立即返回这个值。注意你必须立即调用SetWindowLong（这个函数用于调用窗口子类化的过程），这会导致DWL_MSGRESULT值被一个嵌套的对话框消息改写。返回值为特定值的消息有：<br />•     WM_CHARTOITEM <br />•     WM_COMPAREITEM <br />•     WM_CTLCOLORBTN <br />•     WM_CTLCOLORDLG <br />•     WM_CTLCOLOREDIT <br />•     WM_CTLCOLORLISTBOX <br />•     WM_CTLCOLORSCROLLBAR <br />•     WM_CTLCOLORSTATIC <br />•     WM_INITDIALOG <br />•     WM_QUERYDRAGICON <br />•     WM_VKEYTOITEM<br />看到没有？ 我们的消息WM_INITDIALOG也在其中，对这个消息进行处理的过程不能简单的返回TRUE表示对消息进行了处理，而是另有其意；它将转化为：<br /><br />case (WM_INITDIALOG): return HANDLE_WM_INITDIALOG(hwnd, wParam, lParam, Cls_OnInitDialog);<br /><br />宏HANDLE_WM_INITDIALOG定义如下：<br /><br />#define HANDLE_WM_INITDIALOG(hwnd, wParam, lParam, fn) \<br />(LRESULT)(DWORD)(UINT)(BOOL)(fn)((hwnd), (HWND)(wParam), lParam)<br /><br />对WM_INITDIALOG的处理，如果返回TRUE，则表示设置键盘焦点到对话框的默认控件，否则返回FALSE；这时好像还看不出什么问题，而对于我们的另外一个消息WM_COMMAND，HANDLE_MSG简单的把它变成：<br /><br />case (WM_COMMAND): return HANDLE_WM_COMMAND(hwnd, wParam, lParam, Cls_OnCommand);<br /><br />宏HANDLE_WM_COMMAND定义如下：<br /><br />#define HANDLE_WM_COMMAND(hwnd, wParam, lParam, fn) \<br />  ((fn)((hwnd), (int)(LOWORD(wParam)), (HWND)(lParam), (UINT)HIWORD(wParam)), 0L)<br /><br />问题出来了，我们的Cls_OnCommand由于是个void型的函数，是没有返回值的，因此windows默认这种消息处理过程必须返回一个0值，而返回0值不就表示我们的消息过程不处理这个消息么？这个矛盾是HANDLE_MSG无法解决的。怎么办才能使消息过程在处理完WM_COMMAND消息之后正确的返回一个TRUE呢？ 答案是使用另一个windowsx.h中的宏：SetDlgMsgResult(hwnd, msg, result) <br /><br />这个宏定义如下：<br /><br />#define   SetDlgMsgResult(hwnd, msg, result) (( <br />    (msg) == WM_CTLCOLORMSGBOX     || \<br />    (msg) == WM_CTLCOLOREDIT     || \<br />    (msg) == WM_CTLCOLORLISTBOX   || \<br />    (msg) == WM_CTLCOLORBTN       || \<br />    (msg) == WM_CTLCOLORDLG       || \<br />    (msg) == WM_CTLCOLORSCROLLBAR   || \<br />    (msg) == WM_CTLCOLORSTATIC     || \<br />    (msg) == WM_COMPAREITEM       || \<br />    (msg) == WM_VKEYTOITEM       || \<br />    (msg) == WM_CHARTOITEM       || \<br />    (msg) == WM_QUERYDRAGICON     || \<br />    (msg) == WM_INITDIALOG         \<br />  ) ? (BOOL)(result) : (SetWindowLongPtr((hwnd), DWLP_MSGRESULT, (LPARAM)(LRESULT)(result)), TRUE))<br /><br />（有没有注意到，里面多了一个WM_CTLCOLORMSGBOX ？ 这个消息是16位WinAPI中的消息，一度被转换为Win32 API的一个消息；现在在最新的32位API中已经被删除了；保留它可能考虑到兼容性的问题，这里不做进一步讨论）<br />现在看到了，如果对话框过程处理的消息恰巧为返回特定值中的一个，则如实返回result；不要被前面的BOOL蒙蔽，BOOL在头文件中的定义实际上是一个int型，一旦需要返回非TRUE或FALSE的其他值，照样可以；这样，我们的Cls_OnInitDialog就能够正确的返回它的BOOL值了，而Cls_OnCommand在处理之后，也可以由后面的逗号表达式正确的返回一个TRUE表示消息已处理。<br /><br />在《Windows核心编程》一书中，大牛Jeffrey自己定义了一个宏，使SetDlgMsgResult宏的使用更加方便：<br /><br />#define chHANDLE_DLGMSG(hwnd, message, fn)           \<br />  case (message): return (SetDlgMsgResult(hwnd, uMsg,   \<br />    HANDLE_##message((hwnd), (wParam), (lParam), (fn)))) <br /><br />可见这个宏只是简单的对SetDlgMsgRseult宏进行了封装。<br /><br />这样，我们最终的代码可以写成：<br /><br />LRESULT CALLBACK Dlg_Proc (HWND hwnd, UINT msg, <br />WPARAM wParam, LPARAM lParam)<br />{<br />switch(msg)<br />{<br />chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Cls_OnInitDialog); // 使用大牛的chHANDLE_DLGMSG宏<br />chHANDLE_DLGMSG(hwnd, WM_COMMAND, Cls_OnCommand);     <br />}<br /><br />return false;<br />}<br /><br /><br /><br />下面把原来程序整个框架列出来：<br /><br />LRESULT CALLBACK Dlg_Proc(HWND hwnd, UNIT umsg, WPARAM wparam, LPARAM lparam)<br />{<br />    switch(msg)     <br />    {<br />    case WM_COMMAND:           // 每个case都被一个message cracker代替，这里使用大牛同志的<br />        // do something;     // chHANDLE_DLGMSG宏；这个宏负责对消息筛选，处理并返回相应的值<br />        return true;<br /><br />    case WM_INITDIALOG: <br />        // do something;<br />        return xxxx;<br />}<br /><br />return false;     // 如果消息不在我们的DlgProc过程中被处理，则告诉调用这个DlgProc的消息，<br />}                       //告诉系统的对话框管理器，这个消息我们不处理，交给你了<br /><br />对比一下，消息分流器的作用不言自明。<br /><br />以上只是介绍了消息分流器的部分应用，更多创造性的用法还等你自己在实践中发掘。<br /><br />下面列出一些有用的参考资料：<br /><br /><a href="http://support.microsoft.com/default.aspx?scid=kb;en-us;83456" target="_blank">http://support.microsoft.com/default.aspx?scid=kb;en-us;83456</a> 介绍了STRICT宏定义以及windowsx.h<br /><a href="http://www.codeproject.com/win32/msgcrackwizard.asp" target="_blank">http://www.codeproject.com/win32/msgcrackwizard.asp</a>     提供message cracker wizard的下载，而且附有源代码<br />《windows核心编程》windows系统编程，就跟定大牛了 :&gt; 他在自己的sample中大量使用了message cracker<img src ="http://www.cppblog.com/eday/aggbug/15545.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-22 16:53 <a href="http://www.cppblog.com/eday/archive/2006/11/22/15545.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>PE文件格式详解(上)</title><link>http://www.cppblog.com/eday/archive/2006/11/18/15391.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 18 Nov 2006 09:01:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/18/15391.html</guid><description><![CDATA[
		<strong>摘要</strong>
		<br />
		<br />　　 Windows NT 3.1引入了一种名为PE文件格式的新可执行文件格式。PE文件格式的规范包含在了MSDN的CD中（Specs and Strategy, Specifications, Windows NT File Format Specifications），但是它非常之晦涩。 <br />　　 然而这一的文档并未提供足够的信息，所以开发者们无法很好地弄懂PE格式。本文旨在解决这一问题，它会对整个的PE文件格式作一个十分彻底的解释，另外，本文中还带有对所有必需结构的描述以及示范如何使用这些信息的源码示例。 <br />　　 为了获得PE文件中所包含的重要信息，我编写了一个名为PEFILE.DLL的动态链接库，本文中所有出现的源码示例亦均摘自于此。这个DLL和它的源代码都作为PEFile示例程序的一部分包含在了CD中（译注：示例程序请在MSDN中寻找，本站恕不提供），你可以在你自己的应用程序中使用这个DLL；同样，你亦可以依你所愿地使用并构建它的源码。在本文末尾，你会找到PEFILE.DLL的函数导出列表和一个如何使用它们的说明。我觉得你会发现这些函数会让你从容应付PE文件格式的。 <br /><br /><strong>介绍</strong><br /><br />　　 Windows操作系统家族最近增加的Windows NT为开发环境和应用程序本身带来了很大的改变，这之中一个最为重大的当属PE文件格式了。新的PE文件格式主要来自于UNIX操作系统所通用的COFF规范，同时为了保证与旧版本MS-DOS及Windows操作系统的兼容，PE文件格式也保留了MS-DOS中那熟悉的MZ头部。 <br />　　 在本文之中，PE文件格式是以自顶而下的顺序解释的。在你从头开始研究文件内容的过程之中，本文会详细讨论PE文件的每一个组成部分。 <br />　　 许多单独的文件成分定义都来自于Microsoft Win32 SDK开发包中的WINNT.H文件，在这个文件中你会发现用来描述文件头部和数据目录等各种成分的结构类型定义。但是，在WINNT.H中缺少对PE文件结构足够的定义，在这种情况下，我定义了自己的结构来存取文件数据。你会在PEFILE.DLL工程的PEFILE.H中找到这些结构的定义，整套的PEFILE.H开发文件包含在PEFile示例程序之中。 <br />　　 本文配套的示例程序除了PEFILE.DLL示例代码之外，还有一个单独的Win32示例应用程序，名为EXEVIEW.EXE。创建这一示例目的有二：首先，我需要测试PEFILE.DLL的函数，并且某些情况要求我同时查看多个文件；其次，很多解决PE文件格式的工作和直接观看数据有关。例如，要弄懂导入地址名称表是如何构成的，我就得同时查看.idata段头部、导入映像数据目录、可选头部以及当前的.idata段实体，而EXEVIEW.EXE就是查看这些信息的最佳示例。 <br />　　 闲话少叙，让我们开始吧。 <br /><br /><strong>PE文件结构 </strong><br /><br />　　 PE文件格式被组织为一个线性的数据流，它由一个MS-DOS头部开始，接着是一个是模式的程序残余以及一个PE文件标志，这之后紧接着PE文件头和可选头部。这些之后是所有的段头部，段头部之后跟随着所有的段实体。文件的结束处是一些其它的区域，其中是一些混杂的信息，包括重分配信息、符号表信息、行号信息以及字串表数据。我将所有这些成分列于图1。<br /><img height="413" src="http://www.vckbase.com/document/journal/vckbase38/images/pe1.gif" width="104" /><br /><strong>图1.PE文件映像结构</strong><br />　　 从MS-DOS文件头结构开始，我将按照PE文件格式各成分的出现顺序依次对其进行讨论，并且讨论的大部分是以示例代码为基础来示范如何获得文件的信息的。所有的源码均摘自PEFILE.DLL模块的PEFILE.C文件。这些示例都利用了Windows NT最酷的特色之一——内存映射文件，这一特色允许用户使用一个简单的指针来存取文件中所包含的数据，因此所有的示例都使用了内存映射文件来存取PE文件中的数据。 <br />　　 注意：请查阅本文末尾关于如何使用PEFILE.DLL的那一段。 <br /><br /><strong>MS-DOS头部/实模式头部</strong><br /><br />　　 如上所述，PE文件格式的第一个组成部分是MS-DOS头部。在PE文件格式中，它并非一个新概念，因为它与MS-DOS 2.0以来就已有的MS-DOS头部是完全一样的。保留这个相同结构的最主要原因是，当你尝试在Windows 3.1以下或MS-DOS 2.0以上的系统下装载一个文件的时候，操作系统能够读取这个文件并明白它是和当前系统不相兼容的。换句话说，当你在MS-DOS 6.0下运行一个Windows NT可执行文件时，你会得到这样一条消息：“This program cannot be run in DOS mode.”如果MS-DOS头部不是作为PE文件格式的第一部分的话，操作系统装载文件的时候就会失败，并提供一些完全没用的信息，例如：“The name specified is not recognized as an internal or external command, operable program or batch file.” <br />　　 MS-DOS头部占据了PE文件的头64个字节，描述它内容的结构如下： <pre>//WINNT.H

typedef struct _IMAGE_DOS_HEADER { // DOS的.EXE头部
  USHORT e_magic; // 魔术数字
  USHORT e_cblp; // 文件最后页的字节数
  USHORT e_cp; // 文件页数
  USHORT e_crlc; // 重定义元素个数
  USHORT e_cparhdr; // 头部尺寸，以段落为单位
  USHORT e_minalloc; // 所需的最小附加段
  USHORT e_maxalloc; // 所需的最大附加段
  USHORT e_ss; // 初始的SS值（相对偏移量）
  USHORT e_sp; // 初始的SP值
  USHORT e_csum; // 校验和
  USHORT e_ip; // 初始的IP值
  USHORT e_cs; // 初始的CS值（相对偏移量）
  USHORT e_lfarlc; // 重分配表文件地址
  USHORT e_ovno; // 覆盖号
  USHORT e_res[4]; // 保留字
  USHORT e_oemid; // OEM标识符（相对e_oeminfo）
  USHORT e_oeminfo; // OEM信息
  USHORT e_res2[10]; // 保留字
  LONG e_lfanew; // 新exe头部的文件地址
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;</pre>第一个域e_magic，被称为魔术数字，它被用于表示一个MS-DOS兼容的文件类型。所有MS-DOS兼容的可执行文件都将这个值设为0x5A4D，表示ASCII字符MZ。MS-DOS头部之所以有的时候被称为MZ头部，就是这个缘故。还有许多其它的域对于MS-DOS操作系统来说都有用，但是对于Windows NT来说，这个结构中只有一个有用的域——最后一个域e_lfnew，一个4字节的文件偏移量，PE文件头部就是由它定位的。对于Windows NT的PE文件来说，PE文件头部是紧跟在MS-DOS头部和实模式程序残余之后的。 <br /><br /><strong>实模式残余程序</strong><br /><br />　　 实模式残余程序是一个在装载时能够被MS-DOS运行的实际程序。对于一个MS-DOS的可执行映像文件，应用程序就是从这里执行的。对于Windows、OS/2、Windows NT这些操作系统来说，MS-DOS残余程序就代替了主程序的位置被放在这里。这种残余程序通常什么也不做，而只是输出一行文本，例如：“This program requires Microsoft Windows v3.1 or greater.”当然，用户可以在此放入任何的残余程序，这就意味着你可能经常看到像这样的东西：“You can''t run a Windows NT application on OS/2, it''s simply not possible.” <br />　　 当为Windows 3.1构建一个应用程序的时候，链接器将向你的可执行文件中链接一个名为WINSTUB.EXE的默认残余程序。你可以用一个基于MS-DOS的有效程序取代WINSTUB，并且用STUB模块定义语句指示链接器，这样就能够取代链接器的默认行为。为Windows NT开发的应用程序可以通过使用-STUB:链接器选项来实现。 <br /><br /><strong>PE文件头部与标志</strong><br /><br />　　 PE文件头部是由MS-DOS头部的e_lfanew域定位的，这个域只是给出了文件的偏移量，所以要确定PE头部的实际内存映射地址，就需要添加文件的内存映射基地址。例如，以下的宏是包含在PEFILE.H源文件之中的： <br /><pre>//PEFILE.H

#define NTSIGNATURE(a) ((LPVOID)((BYTE *)a + \
                       ((PIMAGE_DOS_HEADER)a)-&gt;e_lfanew))</pre>在处理PE文件信息的时候，我发现文件之中有些位置需要经常查阅。既然这些位置仅仅是对文件的偏移量，那么用宏来实现这些定位就比较容易，因为它们较之函数有更好的表现。 <br />　　 请注意这个宏所获得的是PE文件标志，而并非PE文件头部的偏移量。那是由于自Windows与OS/2的可执行文件开始，.EXE文件都被赋予了目标操作系统的标志。对于Windows NT的PE文件格式而言，这一标志在PE文件头部结构之前。在Windows和OS/2的某些版本中，这一标志是文件头的第一个字。同样，对于PE文件格式，Windows NT使用了一个DWORD值。 <br />　　 以上的宏返回了文件标志的偏移量，而不管它是哪种类型的可执行文件。所以，文件头部是在DWORD标志之后，还是在WORD标志处，是由这个标志是否Windows NT文件标志所决定的。要解决这个问题，我编写了ImageFileType函数（如下），它返回了映像文件的类型： <br /><pre>//PEFILE.C

DWORD WINAPI ImageFileType (LPVOID lpFile)
{
  /* 首先出现的是DOS文件标志 */
  if (*(USHORT *)lpFile == IMAGE_DOS_SIGNATURE)
  {
    /* 由DOS头部决定PE文件头部的位置 */
    if (LOWORD (*(DWORD *)NTSIGNATURE (lpFile)) ==
        IMAGE_OS2_SIGNATURE ||
        LOWORD (*(DWORD *)NTSIGNATURE (lpFile)) ==
        IMAGE_OS2_SIGNATURE_LE)
      return (DWORD)LOWORD(*(DWORD *)NTSIGNATURE (lpFile));
    else if (*(DWORD *)NTSIGNATURE (lpFile) ==
      IMAGE_NT_SIGNATURE)
    return IMAGE_NT_SIGNATURE;
    else
      return IMAGE_DOS_SIGNATURE;
  }
  else
    /* 不明文件种类 */
    return 0;
}
</pre>以上列出的代码立即告诉了你NTSIGNATURE宏有多么有用。对于比较不同文件类型并且返回一个适当的文件种类来说，这个宏就会使这两件事变得非常简单。WINNT.H之中定义的四种不同文件类型有： <pre>//WINNT.H

#define IMAGE_DOS_SIGNATURE 0x5A4D // MZ
#define IMAGE_OS2_SIGNATURE 0x454E // NE
#define IMAGE_OS2_SIGNATURE_LE 0x454C // LE
#define IMAGE_NT_SIGNATURE 0x00004550 // PE00
　　</pre>首先，Windows的可执行文件类型没有出现在这一列表中，这一点看起来很奇怪。但是，在稍微研究一下之后，就能得到原因了：除了操作系统版本规范的不同之外，Windows的可执行文件和OS/2的可执行文件实在没有什么区别。这两个操作系统拥有相同的可执行文件结构。 <br />　　 现在把我们的注意力转向Windows NT PE文件格式，我们会发现只要我们得到了文件标志的位置，PE文件之后就会有4个字节相跟随。下一个宏标识了PE文件的头部： <pre>//PEFILE.C

#define PEFHDROFFSET(a) ((LPVOID)((BYTE *)a + \
                        ((PIMAGE_DOS_HEADER)a)-&gt;e_lfanew + \
                        SIZE_OF_NT_SIGNATURE))
　　</pre>这个宏与上一个宏的唯一不同是这个宏加入了一个常量SIZE_OF_NT_SIGNATURE。不幸的是，这个常量并未定义在WINNT.H之中，于是我将它定义在了PEFILE.H中，它是一个DWORD的大小。 <br />　　 既然我们知道了PE文件头的位置，那么就可以检查头部的数据了。我们只需要把这个位置赋值给一个结构，如下： <pre>PIMAGE_FILE_HEADER pfh;
pfh = (PIMAGE_FILE_HEADER)PEFHDROFFSET(lpFile);</pre>在这个例子中，lpFile表示一个指向可执行文件内存映像基地址的指针，这就显出了内存映射文件的好处：不需要执行文件的I/O，只需使用指针pfh就能存取文件中的信息。PE文件头结构被定义为： <pre>//WINNT.H

typedef struct _IMAGE_FILE_HEADER {
  USHORT Machine;
  USHORT NumberOfSections;
  ULONG TimeDateStamp;
  ULONG PointerToSymbolTable;
  ULONG NumberOfSymbols;
  USHORT SizeOfOptionalHeader;
  USHORT Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

#define IMAGE_SIZEOF_FILE_HEADER 20
</pre>请注意这个文件头部的大小已经定义在这个包含文件之中了，这样一来，想要得到这个结构的大小就很方便了。但是我觉得对结构本身使用sizeof运算符（译注：原文为“function”）更简单一些，因为这样的话我就不必记住这个常量的名字IMAGE_SIZEOF_FILE_HEADER，而只需要记住结构IMAGE_FILE_HEADER的名字就可以了。另一方面，记住所有结构的名字已经够有挑战性的了，尤其在是这些结构只有WINNT.H中才有的情况下。 <br />　　 PE文件中的信息基本上是一些高级信息，这些信息是被操作系统或者应用程序用来决定如何处理这个文件的。第一个域是用来表示这个可执行文件被构建的目标机器种类，例如DEC(R) Alpha、MIPS R4000、Intel(R) x86或一些其它处理器。系统使用这一信息来在读取这个文件的其它数据之前决定如何处理它。 <br />　　 Characteristics域表示了文件的一些特征。比如对于一个可执行文件而言，分离调试文件是如何操作的。调试器通常使用的方法是将调试信息从PE文件中分离，并保存到一个调试文件（.DBG）中。要这么做的话，调试器需要了解是否要在一个单独的文件中寻找调试信息，以及这个文件是否已经将调试信息分离了。我们可以通过深入可执行文件并寻找调试信息的方法来完成这一工作。要使调试器不在文件中查找的话，就需要用到IMAGE_FILE_DEBUG_STRIPPED这个特征，它表示文件的调试信息是否已经被分离了。这样一来，调试器可以通过快速查看PE文件的头部的方法来决定文件中是否存在着调试信息。 <br />　　 WINNT.H定义了若干其它表示文件头信息的标记，就和以上的例子差不多。我把研究这些标记的事情留给读者作为练习，由你们来看看它们是不是很有趣，这些标记位于WINNT.H中的IMAGE_FILE_HEADER结构之后。 <br />　　 PE文件头结构中另一个有用的入口是NumberOfSections域，它表示如果你要方便地提取文件信息的话，就需要了解多少个段——更明确一点来说，有多少个段头部和多少个段实体。每一个段头部和段实体都在文件中连续地排列着，所以要决定段头部和段实体在哪里结束的话，段的数目是必需的。以下的函数从PE文件头中提取了段的数目： <pre>PEFILE.C
int WINAPI NumOfSections(LPVOID lpFile)
{
  /* 文件头部中所表示出的段数目 */
  return (int)((PIMAGE_FILE_HEADER)
    PEFHDROFFSET (lpFile))-&gt;NumberOfSections);
}
</pre>如你所见，PEFHDROFFSET以及其它宏用起来非常方便。<br /><br /><strong>PE可选头部</strong><br /><br />　　 PE可执行文件中接下来的224个字节组成了PE可选头部。虽然它的名字是“可选头部”，但是请确信：这个头部并非“可选”，而是“必需”的。OPTHDROFFSET宏可以获得指向可选头部的指针： <pre>//PEFILE.H

#define OPTHDROFFSET(a) ((LPVOID)((BYTE *)a + \
                        ((PIMAGE_DOS_HEADER)a)-&gt;e_lfanew + \
                        SIZE_OF_NT_SIGNATURE + \
                        sizeof(IMAGE_FILE_HEADER)))
　　</pre>可选头部包含了很多关于可执行映像的重要信息，例如初始的堆栈大小、程序入口点的位置、首选基地址、操作系统版本、段对齐的信息等等。IMAGE_OPTIONAL_HEADER结构如下： <pre>//WINNT.H

typedef struct _IMAGE_OPTIONAL_HEADER {
  //
  // 标准域
  //
  USHORT Magic;
  UCHAR MajorLinkerVersion;
  UCHAR MinorLinkerVersion;
  ULONG SizeOfCode;
  ULONG SizeOfInitializedData;
  ULONG SizeOfUninitializedData;
  ULONG AddressOfEntryPoint;
  ULONG BaseOfCode;
  ULONG BaseOfData;
  //
  // NT附加域
  //
  ULONG ImageBase;
  ULONG SectionAlignment;
  ULONG FileAlignment;
  USHORT MajorOperatingSystemVersion;
  USHORT MinorOperatingSystemVersion;
  USHORT MajorImageVersion;
  USHORT MinorImageVersion;
  USHORT MajorSubsystemVersion;
  USHORT MinorSubsystemVersion;
  ULONG Reserved1;
  ULONG SizeOfImage;
  ULONG SizeOfHeaders;
  ULONG CheckSum;
  USHORT Subsystem;
  USHORT DllCharacteristics;
  ULONG SizeOfStackReserve;
  ULONG SizeOfStackCommit;
  ULONG SizeOfHeapReserve;
  ULONG SizeOfHeapCommit;
  ULONG LoaderFlags;
  ULONG NumberOfRvaAndSizes;
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;
</pre>如你所见，这个结构中所列出的域实在是冗长得过分。为了不让你对所有这些域感到厌烦，我会仅仅讨论有用的——就是说，对于探究PE文件格式而言有用的。 <br /><br /><strong>标准域</strong><br /><br />　　 首先，请注意这个结构被划分为“标准域”和“NT附加域”。所谓标准域，就是和UNIX可执行文件的COFF格式所公共的部分。虽然标准域保留了COFF中定义的名字，但是Windows NT仍然将它们用作了不同的目的——尽管换个名字更好一些。 <br />　　 ·Magic。我不知道这个域是干什么的，对于示例程序EXEVIEW.EXE示例程序而言，这个值是0x010B或267（译注：0x010B为.EXE，0x0107为ROM映像，这个信息我是从eXeScope上得来的）。 <br />　　 ·MajorLinkerVersion、MinorLinkerVersion。表示链接此映像的链接器版本。随Window NT build 438配套的Windows NT SDK包含的链接器版本是2.39（十六进制为2.27）。 <br />　　 ·SizeOfCode。可执行代码尺寸。 <br />　　 ·SizeOfInitializedData。已初始化的数据尺寸。 <br />　　 ·SizeOfUninitializedData。未初始化的数据尺寸。 <br />　　 ·AddressOfEntryPoint。在标准域中，AddressOfEntryPoint域是对PE文件格式来说最为有趣的了。这个域表示应用程序入口点的位置。并且，对于系统黑客来说，这个位置就是导入地址表（IAT）的末尾。以下的函数示范了如何从可选头部获得Windows NT可执行映像的入口点。 <pre>//PEFILE.C

LPVOID WINAPI GetModuleEntryPoint(LPVOID lpFile)
{
  PIMAGE_OPTIONAL_HEADER poh;
  poh = (PIMAGE_OPTIONAL_HEADER)OPTHDROFFSET(lpFile);
  if (poh != NULL)
    return (LPVOID)poh-&gt;AddressOfEntryPoint;
  else
    return NULL;
}
</pre>·BaseOfCode。已载入映像的代码（“.text”段）的相对偏移量。 <br />　　 ·BaseOfData。已载入映像的未初始化数据（“.bss”段）的相对偏移量。 <br /><br /><strong>Windows NT附加域</strong><br /><br />　　 添加到Windows NT PE文件格式中的附加域为Windows NT特定的进程行为提供了装载器的支持，以下为这些域的概述。 <br />　　 ·ImageBase。进程映像地址空间中的首选基地址。Windows NT的Microsoft Win32 SDK链接器将这个值默认设为0x00400000，但是你可以使用-BASE:linker开关改变这个值。 <br />　　 ·SectionAlignment。从ImageBase开始，每个段都被相继的装入进程的地址空间中。SectionAlignment则规定了装载时段能够占据的最小空间数量——就是说，段是关于SectionAlignment对齐的。 <br />　　 Windows NT虚拟内存管理器规定，段对齐不能少于页尺寸（当前的x86平台是4096字节），并且必须是成倍的页尺寸。4096字节是x86链接器的默认值，但是它可以通过-ALIGN: linker开关来设置。 <br />　　 ·FileAlignment。映像文件首先装载的最小的信息块间隔。例如，链接器将一个段实体（段的原始数据）加零扩展为文件中最接近的FileAlignment边界。早先提及的2.39版链接器将映像文件以0x200字节的边界对齐，这个值可以被强制改为512到65535这么多。 <br />　　 ·MajorOperatingSystemVersion。表示Windows NT操作系统的主版本号；通常对Windows NT 1.0而言，这个值被设为1。 <br />　　 ·MinorOperatingSystemVersion。表示Windows NT操作系统的次版本号；通常对Windows NT 1.0而言，这个值被设为0。 <br />　　 ·MajorImageVersion。用来表示应用程序的主版本号；对于Microsoft Excel 4.0而言，这个值是4。 <br />　　 ·MinorImageVersion。用来表示应用程序的次版本号；对于Microsoft Excel 4.0而言，这个值是0。 <br />　　 ·MajorSubsystemVersion。表示Windows NT Win32子系统的主版本号；通常对于Windows NT 3.10而言，这个值被设为3。 <br />　　 ·MinorSubsystemVersion。表示Windows NT Win32子系统的次版本号；通常对于Windows NT 3.10而言，这个值被设为10。 <br />　　 ·Reserved1。未知目的，通常不被系统使用，并被链接器设为0。 <br />　　 ·SizeOfImage。表示载入的可执行映像的地址空间中要保留的地址空间大小，这个数字很大程度上受SectionAlignment的影响。例如，考虑一个拥有固定页尺寸4096字节的系统，如果你有一个11个段的可执行文件，它的每个段都少于4096字节，并且关于65536字节边界对齐，那么SizeOfImage域将会被设为11 * 65536 = 720896（176页）。而如果一个相同的文件关于4096字节对齐的话，那么SizeOfImage域的结果将是11 * 4096 = 45056（11页）。这只是个简单的例子，它说明每个段需要少于一个页面的内存。在现实中，链接器通过个别地计算每个段的方法来决定SizeOfImage确切的值。它首先决定每个段需要多少字节，并且最后将页面总数向上取整至最接近的SectionAlignment边界，然后总数就是每个段个别需求之和了。 <br />　　 ·SizeOfHeaders。这个域表示文件中有多少空间用来保存所有的文件头部，包括MS-DOS头部、PE文件头部、PE可选头部以及PE段头部。文件中所有的段实体就开始于这个位置。 <br />　　 ·CheckSum。校验和是用来在装载时验证可执行文件的，它是由链接器设置并检验的。由于创建这些校验和的算法是私有信息，所以在此不进行讨论。 <br />　　 ·Subsystem。用于标识该可执行文件目标子系统的域。每个可能的子系统取值列于WINNT.H的IMAGE_OPTIONAL_HEADER结构之后。 <br />　　 ·DllCharacteristics。用来表示一个DLL映像是否为进程和线程的初始化及终止包含入口点的标记。 <br />　　 ·SizeOfStackReserve、SizeOfStackCommit、SizeOfHeapReserve、SizeOfHeapCommit。这些域控制要保留的地址空间数量，并且负责栈和默认堆的申请。在默认情况下，栈和堆都拥有1个页面的申请值以及16个页面的保留值。这些值可以使用链接器开关-STACKSIZE:与-HEAPSIZE:来设置。 <br />　　 ·LoaderFlags。告知装载器是否在装载时中止和调试，或者默认地正常运行。 <br />　　 ·NumberOfRvaAndSizes。这个域标识了接下来的DataDirectory数组。请注意它被用来标识这个数组，而不是数组中的各个入口数字，这一点非常重要。 <br />　　 ·DataDirectory。数据目录表示文件中其它可执行信息重要组成部分的位置。它事实上就是一个IMAGE_DATA_DIRECTORY结构的数组，位于可选头部结构的末尾。当前的PE文件格式定义了16种可能的数据目录，这之中的11种现在在使用中。 <br /><br /><strong>数据目录</strong><br /><br />WINNT.H之中所定义的数据目录为：<pre>//WINNT.H
 
// 目录入口
// 导出目录
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0
// 导入目录
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1
// 资源目录
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2
// 异常目录
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3
// 安全目录
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4
// 重定位基本表
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5
// 调试目录
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6
// 描述字串
#define IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7
// 机器值（MIPS GP）
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8
// TLS目录
#define IMAGE_DIRECTORY_ENTRY_TLS 9
// 载入配置目录
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10
　　</pre>基本上，每个数据目录都是一个被定义为IMAGE_DATA_DIRECTORY的结构。虽然数据目录入口本身是相同的，但是每个特定的目录种类却是完全唯一的。每个数据目录的定义在本文的以后部分被描述为“预定义段”。 <pre>//WINNT.H

typedef struct _IMAGE_DATA_DIRECTORY {
  ULONG VirtualAddress;
  ULONG Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
</pre>每个数据目录入口指定了该目录的尺寸和相对虚拟地址。如果你要定义一个特定的目录的话，就需要从可选头部中的数据目录数组中决定相对的地址，然后使用虚拟地址来决定该目录位于哪个段中。一旦你决定了哪个段包含了该目录，该段的段头部就会被用于查找数据目录的精确文件偏移量位置。 <br />　　 所以要获得一个数据目录的话，那么首先你需要了解段的概念。我在下面会对其进行描述，这个讨论之后还有一个有关如何定位数据目录的示例。 <br /><br /><strong>PE文件段</strong><br /><br />　　 PE文件规范由目前为止定义的那些头部以及一个名为“段”的一般对象组成。段包含了文件的内容，包括代码、数据、资源以及其它可执行信息，每个段都有一个头部和一个实体（原始数据）。我将在下面描述段头部的有关信息，但是段实体则缺少一个严格的文件结构。因此，它们几乎可以被链接器按任何的方法组织，只要它的头部填充了足够能够解释数据的信息。 <br /><br /><strong>段头部</strong><br /><br />　　 PE文件格式中，所有的段头部位于可选头部之后。每个段头部为40个字节长，并且没有任何的填充信息。段头部被定义为以下的结构： <pre>//WINNT.H

#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER {
UCHAR Name[IMAGE_SIZEOF_SHORT_NAME];
  union {
    ULONG PhysicalAddress;
    ULONG VirtualSize;
  } Misc;
  ULONG VirtualAddress;
  ULONG SizeOfRawData;
  ULONG PointerToRawData;
  ULONG PointerToRelocations;
  ULONG PointerToLinenumbers;
  USHORT NumberOfRelocations;
  USHORT NumberOfLinenumbers;
  ULONG Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
　　</pre>你如何才能获得一个特定段的段头部信息？既然段头部是被连续的组织起来的，而且没有一个特定的顺序，那么段头部必须由名称来定位。以下的函数示范了如何从一个给定了段名称的PE映像文件中获得一个段头部： <pre>//PEFILE.C

BOOL WINAPI GetSectionHdrByName(LPVOID lpFile, IMAGE_SECTION_HEADER *sh, char *szSection)
{
  PIMAGE_SECTION_HEADER psh;
  int nSections = NumOfSections (lpFile);
  int i;
  if ((psh = (PIMAGE_SECTION_HEADER)SECHDROFFSET(lpFile))
      != NULL)
  {
    /* 由名称查找段 */
    for (i = 0; i &lt; nSections; i++)
    {
      if (!strcmp(psh-&gt;Name, szSection))
      {
        /* 向头部复制数据 */
        CopyMemory((LPVOID)sh, (LPVOID)psh,
            sizeof(IMAGE_SECTION_HEADER));
        return TRUE;
      }
      else
        psh++;
    }
  }
  return FALSE;
}</pre>这个函数通过SECHDROFFSET宏将第一个段头部定位，然后它开始在所有段中循环，并将要寻找的段名称和每个段的名称相比较，直到找到了正确的那一个为止。当找到了段的时候，函数将内存映像文件的数据复制到传入函数的结构中，然后IMAGE_SECTION_HEADER结构的各域就能够被直接存取了。 <br /><br /><strong>段头部的域</strong><br /><br />　　 ·Name。每个段都有一个8字符长的名称域，并且第一个字符必须是一个句点。 <br />　　 ·PhysicalAddress或VirtualSize。第二个域是一个union域，现在已不使用了。 <br />　　 ·VirtualAddress。这个域标识了进程地址空间中要装载这个段的虚拟地址。实际的地址由将这个域的值加上可选头部结构中的ImageBase虚拟地址得到。切记，如果这个映像文件是一个DLL，那么这个DLL就不一定会装载到ImageBase要求的位置。所以一旦这个文件被装载进入了一个进程，实际的ImageBase值应该通过使用GetModuleHandle来检验。 <br />　　 ·SizeOfRawData。这个域表示了相对FileAlignment的段实体尺寸。文件中实际的段实体尺寸将少于或等于FileAlignment的整倍数。一旦映像被装载进入了一个进程的地址空间，段实体的尺寸将会变得少于或等于FileAlignment的整倍数。 <br />　　 ·PointerToRawData。这是一个文件中段实体位置的偏移量。 <br />　　 ·PointerToRelocations、PointerToLinenumbers、NumberOfRelocations、NumberOfLinenumbers。这些域在PE格式中不使用。 <br />　　 ·Characteristics。定义了段的特征。这些值可以在WINNT.H及本光盘（译注：MSDN的光盘）的PE格式规范中找到。 <br /><br />值         定义 <br />0x00000020 代码段 <br />0x00000040 已初始化数据段 <br />0x00000080 未初始化数据段 <br />0x04000000 该段数据不能被缓存 <br />0x08000000 该段不能被分页 <br />0x10000000 共享段 <br />0x20000000 可执行段 <br />0x40000000 可读段 <br />0x80000000 可写段 <br /><br /><strong>定位数据目录</strong><br /><br />　　 数据目录存在于它们相应的数据段中。典型地来说，数据目录是段实体中的第一个结构，但不是必需的。由于这个缘故，如果你需要定位一个指定的数据目录的话，就需要从段头部和可选头部中获得信息。 <br />　　 为了让这个过程简单一点，我编写了以下的函数来定位任何一个在WINNT.H之中定义的数据目录。 <pre>// PEFILE.C

LPVOID WINAPI ImageDirectoryOffset(LPVOID lpFile,
    DWORD dwIMAGE_DIRECTORY)
{
  PIMAGE_OPTIONAL_HEADER poh;
  PIMAGE_SECTION_HEADER psh;
  int nSections = NumOfSections(lpFile);
  int i = 0;
  LPVOID VAImageDir;
  /* 必须为0到(NumberOfRvaAndSizes-1)之间 */
  if (dwIMAGE_DIRECTORY &gt;= poh-&gt;NumberOfRvaAndSizes)
    return NULL;
  /* 获得可选头部和段头部的偏移量 */
  poh = (PIMAGE_OPTIONAL_HEADER)OPTHDROFFSET(lpFile);
  psh = (PIMAGE_SECTION_HEADER)SECHDROFFSET(lpFile);
  /* 定位映像目录的相对虚拟地址 */
  VAImageDir = (LPVOID)poh-&gt;DataDirectory
      [dwIMAGE_DIRECTORY].VirtualAddress;
  /* 定位包含映像目录的段 */
  while (i++ &lt; nSections)
  {
    if (psh-&gt;VirtualAddress &lt;= (DWORD)VAImageDir &amp;&amp;
        psh-&gt;VirtualAddress + 
        psh-&gt;SizeOfRawData &gt; (DWORD)VAImageDir)
      break;
    psh++;
  }
  if (i &gt; nSections)
    return NULL;
  /* 返回映像导入目录的偏移量 */
  return (LPVOID)(((int)lpFile + 
    (int)VAImageDir. psh-&gt;VirtualAddress) +
    (int)psh-&gt;PointerToRawData);
}
　　</pre>该函数首先确认被请求的数据目录入口数字，然后它分别获取指向可选头部和第一个段头部的两个指针。它从可选头部决定数据目录的虚拟地址，然后它使用这个值来决定数据目录定位在哪个段实体之中。如果适当的段实体已经被标识了，那么数据目录特定的位置就可以通过将它的相对虚拟地址转换为文件中地址的方法来找到。 <img src ="http://www.cppblog.com/eday/aggbug/15391.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-18 17:01 <a href="http://www.cppblog.com/eday/archive/2006/11/18/15391.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>PE文件格式详解(下)</title><link>http://www.cppblog.com/eday/archive/2006/11/18/15392.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 18 Nov 2006 09:01:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/18/15392.html</guid><description><![CDATA[
		<strong>预定义段</strong>
		<br />
		<br />　　 一个Windows NT的应用程序典型地拥有9个预定义段，它们是.text、.bss、.rdata、.data、.rsrc、.edata、.idata、.pdata和.debug。一些应用程序不需要所有的这些段，同样还有一些应用程序为了自己特殊的需要而定义了更多的段。这种做法与MS-DOS和Windows 3.1中的代码段和数据段相似。事实上，应用程序定义一个独特的段的方法是使用标准编译器来指示对代码段和数据段的命名，或者使用名称段编译器选项-NT——就和Windows 3.1中应用程序定义独特的代码段和数据段一样。 <br />　　 以下是一个关于Windows NT PE文件之中一些有趣的公共段的讨论。 <br /><br /><strong>可执行代码段，.text</strong><br /><br />　　 Windows 3.1和Windows NT之间的一个区别就是Windows NT默认的做法是将所有的代码段（正如它们在Windows 3.1中所提到的那样）组成了一个单独的段，名为“.text”。既然Windows NT使用了基于页面的虚拟内存管理系统，那么将分开的代码放入不同的段之中的做法就不太明智了。因此，拥有一个大的代码段对于操作系统和应用程序开发者来说，都是十分方便的。 <br />　　 .text段也包含了早先提到过的入口点。IAT亦存在于.text段之中的模块入口点之前。（IAT在.text段之中的存在非常有意义，因为这个表事实上是一系列的跳转指令，并且它们的跳转目标位置是已固定的地址。）当Windows NT的可执行映像装载入进程的地址空间时，IAT就和每一个导入函数的物理地址一同确定了。要在.text段之中查找IAT，装载器只用将模块的入口点定位，而IAT恰恰出现于入口点之前。既然每个入口拥有相同的尺寸，那么向后退查找这个表的起始位置就很容易了。 <br /><br /><strong>数据段，.bss、.rdata、.data</strong><br /><br />　　 .bss段表示应用程序的未初始化数据，包括所有函数或源模块中声明为static的变量。 <br />　　 .rdata段表示只读的数据，比如字符串文字量、常量和调试目录信息。 <br />　　 所有其它变量（除了出现在栈上的自动变量）存储在.data段之中。基本上，这些是应用程序或模块的全局变量。 <br /><br /><strong>资源段，.rsrc</strong><br /><br />　　 .rsrc段包含了模块的资源信息。它起始于一个资源目录结构，这个结构就像其它大多数结构一样，但是它的数据被更进一步地组织在了一棵资源树之中。以下的IMAGE_RESOURCE_DIRECTORY结构形成了这棵树的根和各个结点。 <pre>//WINNT.H

typedef struct _IMAGE_RESOURCE_DIRECTORY {
  ULONG Characteristics;
  ULONG TimeDateStamp;
  USHORT MajorVersion;
  USHORT MinorVersion;
  USHORT NumberOfNamedEntries;
  USHORT NumberOfIdEntries;
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
　　</pre>请看这个目录结构，你将会发现其中竟然没有指向下一个结点的指针。但是，在这个结构中有两个域NumberOfNamedEntries和NumberOfIdEntries代替了指针，它们被用来表示这个目录附有多少入口。附带说一句，我的意思是目录入口就在段数据之中的目录后边。有名称的入口按字母升序出现，再往后是按数值升序排列的ID入口。 <br />　　 一个目录入口由两个域组成，正如下面IMAGE_RESOURCE_DIRECTORY_ENTRY结构所描述的那样： <pre>// WINNT.H

typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
  ULONG Name;
  ULONG OffsetToData;
} IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;
</pre>根据树的层级不同，这两个域也就有着不同的用途。Name域被用于标识一个资源种类，或者一种资源名称，或者一个资源的语言ID。OffsetToData与常常被用来在树之中指向兄弟结点——即一个目录结点或一个叶子结点。 <br />　　 叶子结点是资源树之中最底层的结点，它们定义了当前资源数据的尺寸和位置。IMAGE_RESOURCE_DATA_ENTRY结构被用于描述每个叶子结点： <pre>// WINNT.H

typedef struct _IMAGE_RESOURCE_DATA_ENTRY {
  ULONG OffsetToData;
  ULONG Size;
  ULONG CodePage;
  ULONG Reserved;
} IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;
</pre>OffsetToData和Size这两个域表示了当前资源数据的位置和尺寸。既然这一信息主要是在应用程序装载以后由函数使用的，那么将OffsetToData作为一个相对虚拟的地址会更有意义一些。——幸甚，恰好是这样没错。非常有趣的是，所有其它的偏移量，比如从目录入口到其它目录的指针，都是相对于根结点位置的偏移量。 <br />　　 要更清楚地了解这些内容，请参考图2。 <br /><img height="423" src="http://www.vckbase.com/document/journal/vckbase38/images/pe2.gif" width="491" /><br /><strong>图2.一个简单的资源树结构</strong><br />　　 图2描述了一个非常简单的资源树，它包含了仅仅两个资源对象：一个菜单和一个字串表。更深一层地来说，它们各自都有一个子项。然而，你仍然可以看到资源树有多么复杂——即使它像这个一样只有一点点资源。 <br />　　 在树的根部，第一个目录有一个文件中包含的所有资源种类的入口，而不管资源种类有多少。在图2中，有两个由树根标识的入口，一个是菜单的，另一个是字串表的。如果文件中拥有一个或多个对话框资源，那么根结点会再拥有一个入口，因此，就有了对话框资源的另一个分支。 <br />　　 WINUSER.H中标识了基本的资源种类，我将它们列到了下面：<pre>//WINUSER.H

/*
* 预定义的资源种类
*/
#define RT_CURSOR MAKEINTRESOURCE(1)
#define RT_BITMAP MAKEINTRESOURCE(2)
#define RT_ICON MAKEINTRESOURCE(3)
#define RT_MENU MAKEINTRESOURCE(4)
#define RT_DIALOG MAKEINTRESOURCE(5)
#define RT_STRING MAKEINTRESOURCE(6)
#define RT_FONTDIR MAKEINTRESOURCE(7)
#define RT_FONT MAKEINTRESOURCE(8)
#define RT_ACCELERATOR MAKEINTRESOURCE(9)
#define RT_RCDATA MAKEINTRESOURCE(10)
#define RT_MESSAGETABLE MAKEINTRESOURCE(11)
　　</pre>在树的第一层级，以上列出的MAKEINTRESOURCE值被放置在每个种类入口的Name处，它标识了不同的资源种类。 <br />　　 每个根目录的入口都指向了树中第二层级的一个兄弟结点，这些结点也是目录，并且每个都拥有它们自己的入口。在这一层级，目录被用来以给定的种类标识每一个资源种类。如果你的应用程序中有多个菜单，那么树中的第二层级会为每个菜单都准备一个入口。 <br />　　 你可能意识到了，资源可以由名称或整数标识。在这一层级，它们是通过目录结构的Name域来分辨的。如果如果Name域最重要的位被设置了，那么其它的31个位就会被用作一个到IMAGE_RESOURCE_DIR_STRING_U结构的偏移量。 <pre>// WINNT.H

typedef struct _IMAGE_RESOURCE_DIR_STRING_U {
  USHORT Length;
  WCHAR NameString[1];
} IMAGE_RESOURCE_DIR_STRING_U, *PIMAGE_RESOURCE_DIR_STRING_U;
　　</pre>这个结构仅仅是由一个2字节长的Length域和一个UNICODE字符Length组成的。 <br />　　 另一方面，如果Name域最重要的位被清空，那么它的低31位就被用于表示资源的整数ID。图2示范的就是菜单资源作为一个命名的资源，以及字串表作为一个ID资源。 <br />　　 如果有两个菜单资源，一个由名称标识，另一个由资源标识，那么它们二者就会在菜单资源目录之后拥有两个入口。有名称的资源入口在第一位，之后是由整数标识的资源。目录域NumberOfNamedEntries和NumberOfIdEntries将各自包含值1，表示当前的1个入口。 <br />　　 在第二层级的下面，资源树就不再更深一步地扩展分支了。第一层级分支至表示每个资源种类的目录中，第二层级分支至由标识符表示的每个资源的目录中，第三层级是被个别标识的资源与它们各自的语言ID之间一对一的映射。要表示一个资源的语言ID，目录入口结构的Name域就被用来表示资源的主语言ID和子语言ID了。Windows NT的Win32 SDK开发包中列出了默认的值资源，例如对于0x0409这个值来说，0x09表示主语言LANG_ENGLISH，0x04则被定义为子语言的SUBLANG_ENGLISH_CAN。所有的语言ID值都定义于Windows NT Win32 SDK开发包的文件WINNT.H中。 <br />　　 既然语言ID结点是树中最后的目录结点，那么入口结构的OffsetToData域就是到一个叶子结点（即前面提到过的IMAGE_RESOURCE_DATA_ENTRY结构）的偏移量。 <br />　　 再回过头来参考图2，你会发现每个语言目录入口都对应着一个数据入口。这个结点仅仅表示了资源数据的尺寸以及资源数据的相对虚拟地址。 <br />　　 在资源数据段（.rsrc）之中拥有这么多结构有一个好处，就是你可以不存取资源本身而直接可以从这个段收集很多信息。例如，你可以获得有多少种资源、哪些资源（如果有的话）使用了特别的语言ID、特定的资源是否存在以及单独种类资源的尺寸。为了示范如何利用这一信息，以下的函数说明了如何决定一个文件中包含的不同种类的资源： <pre>// PEFILE.C

int WINAPI GetListOfResourceTypes(LPVOID lpFile, HANDLE hHeap, char **pszResTypes)
{
  PIMAGE_RESOURCE_DIRECTORY prdRoot;
  PIMAGE_RESOURCE_DIRECTORY_ENTRY prde;
  char *pMem;
  int nCnt, i;
  /* 获得资源树的根目录 */
  if ((prdRoot = (PIMAGE_RESOURCE_DIRECTORY)ImageDirectoryOffset
      (lpFile, IMAGE_DIRECTORY_ENTRY_RESOURCE)) == NULL)
    return 0;
  /* 在堆上分配足够的空间来包括所有类型 */
  nCnt = prdRoot-&gt;NumberOfIdEntries * (MAXRESOURCENAME + 1);
  *pszResTypes = (char *)HeapAlloc(hHeap, HEAP_ZERO_MEMORY,
      nCnt);
  if ((pMem = *pszResTypes) == NULL)
    return 0;
  /* 将指针指向第一个资源种类的入口 */
  prde = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)((DWORD)prdRoot +
      sizeof (IMAGE_RESOURCE_DIRECTORY));
  /* 在所有的资源目录入口类型中循环 */
  for (i = 0; i &lt; prdRoot-&gt;NumberOfIdEntries; i++)
  {
    if (LoadString(hDll, prde-&gt;Name, pMem, MAXRESOURCENAME))
      pMem += strlen(pMem) + 1;
    prde++;
  }
  return nCnt;
}
　　</pre>这个函数将一个资源种类名称的列表写入了由pszResTypes标识的变量中。请注意，在这个函数的核心部分，LoadString是使用各自资源种类目录入口的Name域来作为字符串ID的。如果你查看PEFILE.RC，你会发现我定义了一系列的资源种类的字符串，并且它们的ID与它们在目录入口中的定义完全相同。PEFILE.DLL还有有一个函数，它返回了.rsrc段中的资源对象总数。这样一来，从这个段中提取其它的信息，借助这些函数或另外编写函数就方便多了。 <br /><br /><strong>导出数据段，.edata</strong><br /><br />　　 .edata段包含了应用程序或DLL的导出数据。在这个段出现的时候，它会包含一个到达导出信息的导出目录。 <pre>// WINNT.H

typedef struct _IMAGE_EXPORT_DIRECTORY {
  ULONG Characteristics;
  ULONG TimeDateStamp;
  USHORT MajorVersion;
  USHORT MinorVersion;
  ULONG Name;
  ULONG Base;
  ULONG NumberOfFunctions;
  ULONG NumberOfNames;
  PULONG *AddressOfFunctions;
  PULONG *AddressOfNames;
  PUSHORT *AddressOfNameOrdinals;
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
　　</pre>导出目录中的Name域标识了可执行模块的名称。NumberOfFunctions域和NumberOfNames域表示模块中有多少导出的函数以及这些函数的名称。 <br />　　 AddressOfFunctions域是一个到导出函数入口列表的偏移量。AddressOfNames域是到一个导出函数名称列表起始处偏移量的地址，这个列表是由null分隔的。AddressOfNameOrdinals是一个到相同导出函数顺序值（每个值2字节长）列表的偏移量。 <br />　　 三个AddressOf...域是当模块装载时进程地址空间中的相对虚拟地址。一旦模块被装载，那么要获得进程地质空间中的确切地址的话，就应该在相对虚拟地址上加上模块的基地址。可是，在文件被装载前，仍然可以决定这一地址：只要从给定的域地址中减去段头部的虚拟地址（VirtualAddress），再加上段实体的偏移量（PointerToRawData），这个结果就是映像文件中的偏移量了。以下的例子解说了这一技术： <br /><pre>// PEFILE.C

int WINAPI GetExportFunctionNames(LPVOID lpFile, HANDLE hHeap, char **pszFunctions)
{
  IMAGE_SECTION_HEADER sh;
  PIMAGE_EXPORT_DIRECTORY ped;
  char *pNames, *pCnt;
  int i, nCnt;
  /* 获得.edata域中的段头部和指向数据目录的指针 */
  if ((ped = (PIMAGE_EXPORT_DIRECTORY)ImageDirectoryOffset
      (lpFile, IMAGE_DIRECTORY_ENTRY_EXPORT)) == NULL)
    return 0;
  GetSectionHdrByName (lpFile, &amp;sh, ".edata");
  /* 决定导出函数名称的偏移量 */
  pNames = (char *)(*(int *)((int)ped-&gt;AddressOfNames -
    (int)sh.VirtualAddress + (int)sh.PointerToRawData +
    (int)lpFile) - (int)sh.VirtualAddress +
    (int)sh.PointerToRawData + (int)lpFile);
  /* 计算出要为所有的字符串分配多少内存 */
  pCnt = pNames;
  for (i = 0; i &lt; (int)ped-&gt;NumberOfNames; i++)
    while (*pCnt++);
  nCnt = (int)(pCnt.pNames);
  /* 在堆上为函数名称分配内存 */
  *pszFunctions = HeapAlloc (hHeap, HEAP_ZERO_MEMORY, nCnt);
  /* 将所有字符串复制到缓冲区 */
  CopyMemory((LPVOID)*pszFunctions, (LPVOID)pNames, nCnt);
  return nCnt;
}</pre>请注意，在这个函数之中，变量pNames是由决定偏移量地址和当前偏移量位置的方法来赋值的。偏移量的地址和偏移量本身都是相对虚拟地址，因此在使用之前必须进行转换——函数之中体现了这一点。虽然你可以编写一个类似的函数来决定顺序值或函数入口点，但是我为什么不为你做好呢？——GetNumberOfExportedFunctions、GetExportFunctionEntryPoints和GetExportFunctionOrdinals已经存在于PEFILE.DLL之中了。 <br /><br /><strong>导入数据段，.idata</strong><br /><br />　　 .idata段是导入数据，包括导入库和导入地址名称表。虽然定义了IMAGE_DIRECTORY_ENTRY_IMPORT，但是WINNT.H之中并无相应的导入目录结构。作为代替，其中有若干其它的结构，名为IMAGE_IMPORT_BY_NAME、IMAGE_THUNK_DATA与IMAGE_IMPORT_DESCRIPTOR。在我个人看来，我实在不知道这些结构是如何和.idata段发生关联的，所以我花了若干个小时来破译.idata段实体并且得到了一个更简单的结构，我名之为IMAGE_IMPORT_MODULE_DIRECTORY。 <pre>// PEFILE.H

typedef struct tagImportDirectory
{
  DWORD dwRVAFunctionNameList;
  DWORD dwUseless1;
  DWORD dwUseless2;
  DWORD dwRVAModuleName;
  DWORD dwRVAFunctionAddressList;
} IMAGE_IMPORT_MODULE_DIRECTORY, *PIMAGE_IMPORT_MODULE_DIRECTORY;
</pre>和其它段的数据目录不同的是，这个是作为文件中的每个导入模块重复出现的。你可以将它看作模块数据目录列表中的一个入口，而不是一个整个数据段的数据目录。每个入口都是一个指向特定模块导入信息的目录。 <br />　　 IMAGE_IMPORT_MODULE_DIRECTORY结构中的一个域dwRVAModuleName是一个相对虚拟地址，它指向模块的名称。结构中还有两个dwUseless参数，它们是为了保持段的对齐。PE文件格式规范提到了一些东西，关于导入标记、时间/日期标志以及主/次版本，但是在我的实验中，这两个域自始而终都是空的，所以我仍然认为它们没有什么用处。 <br />　　 基于这个结构的定义，你便可以获得可执行文件中导入的所有模块和函数名称了。以下的函数示范了如何获得特定的PE文件中的所有导入函数名称： <pre>//PEFILE.C

int WINAPI GetImportModuleNames(LPVOID lpFile, HANDLE hHeap, char **pszModules)
{
  PIMAGE_IMPORT_MODULE_DIRECTORY pid;
  IMAGE_SECTION_HEADER idsh;
  BYTE *pData;
  int nCnt = 0, nSize = 0, i;
  char *pModule[1024];
  char *psz;
  pid = (PIMAGE_IMPORT_MODULE_DIRECTORY)ImageDirectoryOffset 
      (lpFile, IMAGE_DIRECTORY_ENTRY_IMPORT);
  pData = (BYTE *)pid;
  /* 定位.idata段头部 */
  if (!GetSectionHdrByName(lpFile, &amp;idsh, ".idata"))
    return 0;
  /* 提取所有导入模块 */
  while (pid-&gt;dwRVAModuleName)
  {
    /* 为绝对字符串偏移量分配缓冲区 */
    pModule[nCnt] = (char *)(pData + 
        (pid-&gt;dwRVAModuleName-idsh.VirtualAddress));
    nSize += strlen(pModule[nCnt]) + 1;
    /* 增至下一个导入目录入口 */
    pid++;
    nCnt++;
  }
  /* 将所有字符串赋值到一大块的堆内存中 */
  *pszModules = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, nSize);
  psz = *pszModules;
  for (i = 0; i &lt; nCnt; i++)
  {
    strcpy(psz, pModule[i]);
    psz += strlen (psz) + 1;
  }
  return nCnt;
}
  </pre>这个函数非常好懂，然而有一点值得指出——注意while循环。这个循环当pid-&gt;dwRVAModuleName为0的时候终止，这就暗示了在IMAGE_IMPORT_MODULE_DIRECTORY结构列表的末尾有一个空的结构，这个结构拥有一个0值，至少dwRVAModuleName域为0。这便是我在对文件的实验中以及之后在PE文件格式中研究的行为。 <br />　　 这个结构中的第一个域dwRVAFunctionNameList是一个相对虚拟地址，这个地址指向一个相对虚拟地址的列表，这些地址是文件中的一些文件名。如下面的数据所示，所有导入模块的模块和函数名称都列于.idata段数据中了： <pre>E6A7 0000 F6A7 0000 08A8 0000 1AA8 0000 ................
28A8 0000 3CA8 0000 4CA8 0000 0000 0000 (...&lt;...L.......
0000 4765 744F 7065 6E46 696C 654E 616D ..GetOpenFileNam
6541 0000 636F 6D64 6C67 3332 2E64 6C6C eA..comdlg32.dll
0000 2500 4372 6561 7465 466F 6E74 496E ..%.CreateFontIn
6469 7265 6374 4100 4744 4933 322E 646C directA.GDI32.dl
6C00 A000 4765 7444 6576 6963 6543 6170 l...GetDeviceCap
7300 C600 4765 7453 746F 636B 4F62 6A65 s...GetStockObje
6374 0000 D500 4765 7454 6578 744D 6574 ct....GetTextMet
7269 6373 4100 1001 5365 6C65 6374 4F62 ricsA...SelectOb
6A65 6374 0000 1601 5365 7442 6B43 6F6C ject....SetBkCol
6F72 0000 3501 5365 7454 6578 7443 6F6C or..5.SetTextCol
6F72 0000 4501 5465 7874 4F75 7441 0000 or..E.TextOutA..
</pre>以上的数据是EXEVIEW.EXE示例程序.idata段的一部分。这个特别的段表示了导入模块列表和函数名称列表的起始处。如果你开始检查数据中的这个段，你应该认出一些熟悉的Win32 API函数以及模块名称。从上往下读的话，你可以找到GetOpenFileNameA，紧接着是COMDLG32.DLL。然后你能发现CreateFontIndirectA，紧接着是模块GDI32.DLL，以及之后的GetDeviceCaps、GetStockObject、GetTextMetrics等等。 <br />　　 这样的式样会在.idata段中重复出现。第一个模块是COMDLG32.DLL，第二个是GDI32.DLL。请注意第一个模块只导出了一个函数，而第二个模块导出了很多函数。在这两种情况下，函数和模块的排列的方法是首先出现一个函数名，之后是模块名，然后是其它的函数名（如果有的话）。 <br />　　 以下的函数示范了如何获得指定模块的所有函数名。 <pre>// PEFILE.C

int WINAPI GetImportFunctionNamesByModule(LPVOID lpFile, HANDLE hHeap,
    char *pszModule, char **pszFunctions)
{
  PIMAGE_IMPORT_MODULE_DIRECTORY pid;
  IMAGE_SECTION_HEADER idsh;
  DWORD dwBase;
  int nCnt = 0, nSize = 0;
  DWORD dwFunction;
  char *psz;
  /* 定位.idata段的头部 */
  if (!GetSectionHdrByName(lpFile, &amp;idsh, ".idata"))
    return 0;
  pid = (PIMAGE_IMPORT_MODULE_DIRECTORY)ImageDirectoryOffset 
      (lpFile, IMAGE_DIRECTORY_ENTRY_IMPORT);
  dwBase = ((DWORD)pid. idsh.VirtualAddress);
  /* 查找模块的pid */
  while (pid-&gt;dwRVAModuleName &amp;&amp; strcmp (pszModule, 
      (char *)(pid-&gt;dwRVAModuleName+dwBase)))
    pid++;
  /* 如果模块未找到，就退出 */
  if (!pid-&gt;dwRVAModuleName)
    return 0;
  /* 函数的总数和字符串长度 */
  dwFunction = pid-&gt;dwRVAFunctionNameList;
  while (dwFunction &amp;&amp; *(DWORD *)(dwFunction + dwBase) &amp;&amp;
      *(char *)((*(DWORD *)(dwFunction + dwBase)) + dwBase+2))
  {
    nSize += strlen ((char *)((*(DWORD *)(dwFunction +
      dwBase)) + dwBase+2)) + 1;
    dwFunction += 4;
    nCnt++;
  }
  /* 在堆上分配函数名称的空间 */
  *pszFunctions = HeapAlloc (hHeap, HEAP_ZERO_MEMORY, nSize);
  psz = *pszFunctions;
  /* 向内存指针复制函数名称 */
  dwFunction = pid-&gt;dwRVAFunctionNameList;
  while (dwFunction &amp;&amp; *(DWORD *)(dwFunction + dwBase) &amp;&amp;
    *((char *)((*(DWORD *)(dwFunction + dwBase)) + dwBase+2)))
  {
    strcpy (psz, (char *)((*(DWORD *)(dwFunction + dwBase)) +
        dwBase+2));
    psz += strlen((char *)((*(DWORD *)(dwFunction + dwBase))+
        dwBase+2)) + 1;
    dwFunction += 4;
  }
  return nCnt;
}
　　</pre>就像GetImportModuleNames函数一样，这一函数依靠每个信息列表的末端来获得一个置零的入口。这在种情况下，函数名称列表就是以零结尾的。 <br />　　 最后一个域dwRVAFunctionAddressList是一个相对虚拟地址，它指向一个虚拟地址表。在文件装载的时候，这个虚拟地址表会被装载器置于段数据之中。但是在文件装载前，这些虚拟地址会被一些严密符合函数名称列表的虚拟地址替换。所以在文件装载之前，有两个同样的虚拟地址列表，它们指向导入函数列表。 <br /><br /><strong>调试信息段，.debug </strong><br /><br />　　 调试信息位于.debug段之中，同时PE文件格式也支持单独的调试文件（通常由.DBG扩展名标识）作为一种将调试信息集中的方法。调试段包含了调试信息，但是调试目录却位于早先提到的.rdata段之中。这其中每个目录都涉及了.debug段之中的调试信息。调试目录的结构IMAGE_DEBUG_DIRECTORY被定义为： <pre>// WINNT.H

typedef struct _IMAGE_DEBUG_DIRECTORY {
  ULONG Characteristics;
  ULONG TimeDateStamp;
  USHORT MajorVersion;
  USHORT MinorVersion;
  ULONG Type;
  ULONG SizeOfData;
  ULONG AddressOfRawData;
  ULONG PointerToRawData;
} IMAGE_DEBUG_DIRECTORY, *PIMAGE_DEBUG_DIRECTORY;
</pre>这个段被分为单独的部分，每个部分为不同种类的调试信息数据。对于每个部分来说都是一个像上边一样的调试目录。不同的调试信息种类如下： <pre>// WINNT.H

#define IMAGE_DEBUG_TYPE_UNKNOWN 0
#define IMAGE_DEBUG_TYPE_COFF 1
#define IMAGE_DEBUG_TYPE_CODEVIEW 2
#define IMAGE_DEBUG_TYPE_FPO 3
#define IMAGE_DEBUG_TYPE_MISC 4
　　</pre>每个目录之中的Type域表示该目录的调试信息种类。如你所见，在上边的表中，PE文件格式支持很多不同的调试信息种类，以及一些其它的信息域。对于那些来说，IMAGE_DEBUG_TYPE_MISC信息是唯一的。这一信息被添加到描述可执行映像的混杂信息之中，这些混杂信息不能被添加到PE文件格式任何结构化的数据段之中。这就是映像文件中最合适的位置，映像名称则肯定会出现在这里。如果映像导出了信息，那么导出数据段也会包含这一映像名称。 <br />　　 每种调试信息都拥有自己的头部结构，该结构定义了它自己的数据。这些结构都列于WINNT.H之中。关于IMAGE_DEBUG_DIRECTORY一件有趣的事就是它包括了两个标识调试信息的域。第一个是AddressOfRawData，为相对文件装载的数据虚拟地址；另一个是PointerToRawData，为数据所在PE文件之中的实际偏移量。这就使得定位指定的调试信息相当容易了。 <br />　　 作为最后的例子，请你考虑以下的函数代码，它从IMAGE_DEBUG_MISC结构中提取了映像名称。 <pre>//PEFILE.C

int WINAPI RetrieveModuleName(LPVOID lpFile, HANDLE hHeap, char **pszModule)
{
  PIMAGE_DEBUG_DIRECTORY pdd;
  PIMAGE_DEBUG_MISC pdm = NULL;
  int nCnt;
  if (!(pdd = (PIMAGE_DEBUG_DIRECTORY)ImageDirectoryOffset(lpFile, 
      IMAGE_DIRECTORY_ENTRY_DEBUG)))
  return 0;
  while (pdd-&gt;SizeOfData)
  {
    if (pdd-&gt;Type == IMAGE_DEBUG_TYPE_MISC)
    {
      pdm = (PIMAGE_DEBUG_MISC)((DWORD)pdd-&gt;PointerToRawData + (DWORD)lpFile);
      nCnt = lstrlen(pdm-&gt;Data) * (pdm-&gt;Unicode ? 2 : 1);
      *pszModule = (char *)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, nCnt+1);
      CopyMemory(*pszModule, pdm-&gt;Data, nCnt);
      break;
    }
    pdd ++;
  }
  if (pdm != NULL)
    return nCnt;
  else
    return 0;
}
</pre>你看到了，调试目录结构使得定位一个特定种类的调试信息变得相对容易了些。只要定位了IMAGE_DEBUG_MISC结构，提取映像名称就如同调用CopyMemory函数一样简单。 <br />　　 如上所述，调试信息可以被剥离到单独的.DBG文件中。Windows NT SDK包含了一个名为REBASE.EXE的程序可以实现这一目的。例如，以下的语句可以将一个名为TEST.EXE的调试信息剥离： <br />　　 rebase -b 40000 -x c:\samples\testdir test.exe <br />　　 调试信息被置于一个新的文件中，这个文件名为TEST.DBG，位于c:\samples\testdir之中。这个文件起始于一个单独的IMAGE_SEPARATE_DEBUG_HEADER结构，接着是存在于原可执行映像之中的段头部的一份拷贝。在段头部之后，是.debug段的数据。也就是说，在段头部之后，就是一系列的IMAGE_DEBUG_DIRECTORY结构及其相关的数据了。调试信息本身保留了如上所描述的常规映像文件调试信息。 <br /><br /><strong>PE文件格式总结</strong><br /><br />　　 Windows NT的PE文件格式向熟悉Windows和MS-DOS环境的开发者引入了一种全新的结构。然而熟悉UNIX环境的开发者会发现PE文件格式与COFF规范很相像（如果它不是以COFF为基础的话）。 <br />　　 整个格式的组成：一个MS-DOS的MZ头部，之后是一个实模式的残余程序、PE文件标志、PE文件头部、PE可选头部、所有的段头部，最后是所有的段实体。 <br />　　 可选头部的末尾是一个数据目录入口的数组，这些相对虚拟地址指向段实体之中的数据目录。每个数据目录都表示了一个特定的段实体数据是如何组织的。 <br />　　 PE文件格式有11个预定义段，这是对Windows NT应用程序所通用的，但是每个应用程序可以为它自己的代码以及数据定义它自己独特的段。 <br />　　 .debug预定义段也可以分离为一个单独的调试文件。如果这样的话，就会有一个特定的调试头部来用于解析这个调试文件，PE文件中也会有一个标志来表示调试数据被分离了出去。 <br /><br /><strong>PEFILE.DLL函数描述</strong><br /><br />　　 PEFILE.DLL主要由一些函数组成，这些函数或者被用来获得一个给定的PE文件中的偏移量，或者被用来把文件中的一些数据复制到一个特定的结构中去。每个函数都有一个需求——第一个参数是一个指针，这个指针指向PE文件的起始处。也就是说，这个文件必须首先被映射到你进程的地址空间中，然后映射文件的位置就可以作为每个函数第一个参数的lpFile的值来传入了。 <br />　　 我意在使函数的名称使你能够一见而知其意，并且每个函数都随一个详细描述其目的的注释而列出。如果在读完函数列表之后，你仍然不明白某个函数的功能，那么请参考EXEVIEW.EXE示例来查明这个函数是如何使用的。以下的函数原型列表可以在PEFILE.H中找到： <pre>// PEFILE.H

/* 获得指向MS-DOS MZ头部的指针 */
BOOL WINAPI GetDosHeader(LPVOID, PIMAGE_DOS_HEADER);

/* 决定.EXE文件的类型 */
DWORD WINAPI ImageFileType(LPVOID);

/* 获得指向PE文件头部的指针 */
BOOL WINAPI GetPEFileHeader(LPVOID, PIMAGE_FILE_HEADER);

/* 获得指向PE可选头部的指针 */
BOOL WINAPI GetPEOptionalHeader(LPVOID, PIMAGE_OPTIONAL_HEADER);

/* 返回模块入口点的地址 */
LPVOID WINAPI GetModuleEntryPoint(LPVOID);

/* 返回文件中段的总数 */
int WINAPI NumOfSections(LPVOID);

/* 返回当可执行文件被装载入进程地址空间时的首选基地址 */
LPVOID WINAPI GetImageBase(LPVOID);

/* 决定文件中一个特定的映像数据目录的位置 */
LPVOID WINAPI ImageDirectoryOffset(LPVOID, DWORD);

/* 获得文件中所有段的名称 */
int WINAPI GetSectionNames(LPVOID, HANDLE, char **);

/* 复制一个特定段的头部信息 */
BOOL WINAPI GetSectionHdrByName(LPVOID, PIMAGE_SECTION_HEADER, char *);

/* 获得由空字符分隔的导入模块名称列表 */
int WINAPI GetImportModuleNames(LPVOID, HANDLE, char **);

/* 获得一个模块由空字符分隔的导入函数列表 */
int WINAPI GetImportFunctionNamesByModule(LPVOID, HANDLE, char *, char **);

/* 获得由空字符分隔的导出函数列表 */
int WINAPI GetExportFunctionNames(LPVOID, HANDLE, char **);

/* 获得导出函数总数 */
int WINAPI GetNumberOfExportedFunctions(LPVOID);

/* 获得导出函数的虚拟地址入口点列表 */
LPVOID WINAPI GetExportFunctionEntryPoints(LPVOID);

/* 获得导出函数顺序值列表 */
LPVOID WINAPI GetExportFunctionOrdinals(LPVOID);

/* 决定资源对象的种类 */
int WINAPI GetNumberOfResources (LPVOID);

/* 返回文件中所使用的所有资源对象的种类 */
int WINAPI GetListOfResourceTypes(LPVOID, HANDLE, char **);

/* 决定调试信息是否已从文件中分离 */
BOOL WINAPI IsDebugInfoStripped(LPVOID);

/* 获得映像文件名称 */
int WINAPI RetrieveModuleName(LPVOID, HANDLE, char **);

/* 决定文件是否是一个有效的调试文件 */
BOOL WINAPI IsDebugFile(LPVOID);

/* 从调试文件中返回调试头部 */
BOOL WINAPI GetSeparateDebugHeader(LPVOID, PIMAGE_SEPARATE_DEBUG_HEADER);
　　除了以上所列的函数之外，本文中早先提到的宏也定义在了PEFILE.H中，完整的列表如下：
/* PE文件标志的偏移量 */
#define NTSIGNATURE(a) ((LPVOID)((BYTE *)a + \
                       ((PIMAGE_DOS_HEADER)a)-&gt;e_lfanew))

/* MS操作系统头部标识了双字的NT PE文件标志；PE文件头部就紧跟在这个双字之后 */
#define PEFHDROFFSET(a) ((LPVOID)((BYTE *)a + \
                        ((PIMAGE_DOS_HEADER)a)-&gt;e_lfanew + \
                        SIZE_OF_NT_SIGNATURE))

/* PE可选头部紧跟在PE文件头部之后 */
#define OPTHDROFFSET(a) ((LPVOID)((BYTE *)a + \
                        ((PIMAGE_DOS_HEADER)a)-&gt;e_lfanew + \
                        SIZE_OF_NT_SIGNATURE + \
                        sizeof(IMAGE_FILE_HEADER)))

/* 段头部紧跟在PE可选头部之后 */
#define SECHDROFFSET(a) ((LPVOID)((BYTE *)a + \
                        ((PIMAGE_DOS_HEADER)a)-&gt;e_lfanew + \
                        SIZE_OF_NT_SIGNATURE + \
                        sizeof(IMAGE_FILE_HEADER) + \
                        sizeof(IMAGE_OPTIONAL_HEADER)))
　　</pre>要使用PEFILE.DLL，你只用包含PEFILE.H文件并在应用程序中链接到这个DLL即可。所有的这些函数都是互斥性的函数，但是有些函数的功能可以相互支持以获得文件信息。例如，GetSectionNames可以用于获得所有段的名称，这样一来，为了获得一个拥有独特段名称（在编译期由应用程序开发者定义的）的段头部，你就需要首先获得所有名称的列表，然后再对那个准确的段名称调用函数GetSectionHeaderByName了。现在，你可以享受我为你带来的这一切了！ <img src ="http://www.cppblog.com/eday/aggbug/15392.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-18 17:01 <a href="http://www.cppblog.com/eday/archive/2006/11/18/15392.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>在MFC中调用COM组件中的方法</title><link>http://www.cppblog.com/eday/archive/2006/11/18/15385.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 18 Nov 2006 08:56:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/18/15385.html</guid><description><![CDATA[通过ClassWizard利用类型库生成包装类（图一），不过有个前提就是com组件的接口必须是派生自IDispatch 
<p><img style="DISPLAY: block; MARGIN: 0px auto 10px; TEXT-ALIGN: center" alt="10b3f9a0487.jpg" src="http://img3.pp.sohu.com/images/2006/5/2/17/19/10b3f9a0487.jpg" border="0" /></p><p>    这时就会生成你熟悉的 .h 声明文件，可以象使用其他类一样来使用这个COM组件中的方法了。</p><p>有时 COM 组件中的方法使用的是 VARIANT 类型。你可以用 COleVariant来代替</p><p>//VARIANT Send(VARIANT * pszServer, VARIANT * pszMsg, VARIANT * pszOut)</p><p> COleVariant ipaddress = "ipaddress";<br /> COleVariant pszMsg = "pszMsg";<br /> COleVariant pszOut ;</p><p> CoInitialize(NULL);</p><p> CSocket0 soc;<br /> if (soc.CreateDispatch("L2ATL.Socket") != 0)<br /> {<br />  soc.Send( &amp;ipaddress , &amp;pszMsg , &amp;pszOut );<br />  soc.ReleaseDispatch();<br /> }</p><p> CoUninitialize(); </p><img src ="http://www.cppblog.com/eday/aggbug/15385.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-18 16:56 <a href="http://www.cppblog.com/eday/archive/2006/11/18/15385.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>游戏外挂设计技术探讨-四</title><link>http://www.cppblog.com/eday/archive/2006/11/18/15383.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 18 Nov 2006 08:55:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/18/15383.html</guid><description><![CDATA[
		<div class="item-content">1）我们要起动WSA，这时个要用到的WSAStartup函数，用法如下： 
<p><br />　　 2）使用socket函数得到socket句柄，m<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>hSocket:=Socket(AF<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>INET, SOCK<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>RAW, IPPROTO<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>IP); 用法如下：</p><p><br />　　在程序里m<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>hSocket为socket句柄，AF<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>INET，SOCK<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>RAW，IPPROTO<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>IP均为常量。</p><p>　　3)定义SOCK<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>ADDR类型，跟据我们的网卡IP给Sock<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>ADDR类型附值，然后我们使用bind函数来绑定我们的网卡，Bind函</p><p>数用法如下：</p><p>4)用WSAIoctl来注册WSA的输入输出组件，其用法如下：</p><p><br />　　5)下面做死循环，在死循环块里，来实现数据的接收。但是徇环中间要用Sleep()做延时，不然程序会出错。</p><p>　　6)在循环块里，用recv函数来接收数据，recv函数用法如下：</p><p><br />　　7)在buffer里就是我们接收回来的数据了，如果我们想要知道数据是什么地方发来的，那么，我们要定义一定IP包结</p><p>构，用CopyMemory()把IP信息从buffer里面读出来就可以了，不过读出来的是十六进制的数据需要转换一下。</p><p>　　看了封包捕获的全过程序，对你是不是有点起发，然而在这里要告诉大家的是封包的获得是很容易的，但是许多<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a></p><p>的封包都是加密的，如果你想搞清楚所得到的是什么内容还需要自己进行封包解密。 </p><p><br />四种网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂的设计方法</p><p>[文章导读] <br /> <br />在几年前我看到别人玩网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>用上了外挂，做为程序员的我心里实在是不爽 </p><p><br /> </p><p>在几年前我看到别人玩网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>用上了外挂，做为程序员的我心里实在是不爽，想搞清楚这到底是怎么回事。就拿了一些</p><p>来<a href="http://hackbase.com/hacker/leak" target="_blank"><font color="#000000">研究</font></a>，小有心得，拿出来与大家共享，外挂无非就是分几种罢了（依制作难度）：</p><p><br />　1、动作式，所谓动作式，就是指用API发命令给窗口或API控制鼠标、键盘等，使<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>里的<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>进行流动或者<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>，最</p><p>早以前的“石器”外挂就是这种方式。（这种外挂完全是垃圾，TMD，只要会一点点API的人都知道该怎么做，不过这种外</p><p>挂也是<a href="http://hackbase.com/hacker/hacker" target="_blank"><font color="#000000">入门</font></a>级的好东东，虽然不能提高你的战斗力，但是可以提高你的士气）</p><p>　　2、本地修改式，这种外挂跟传统上的一些<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>修改器没有两样，做这种外挂在<a href="http://hackbase.com/hacker/program" target="_blank"><font color="#000000">编程</font></a>只需要对内存地址有一点认识并</p><p>且掌握API就可以实现，“精灵”的外挂这是这种方式写成的，它的难点在于找到那些地址码，找地址一般地要借助于别</p><p>人的<a href="http://hackbase.com/hacker/tool" target="_blank"><font color="#000000">工具</font></a>，有的<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>还有双码校验，正正找起来会比较困难。（这种外挂，比上一种有一点点难度，但是这种外挂做起来</p><p>能够用，也是有一定难度的啦~~，这种外挂可以很快提升你对内存地址的理解及应用，是你<a href="http://hackbase.com/hacker/program" target="_blank"><font color="#000000">编程</font></a><a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>提高的好东东）</p><p>　　3、<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">木马</font></a>式，这种外挂的目的是帮外挂制作者偷到用户的<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">密码</font></a>（TMD，“烂”就一个字，不过要知已知彼所以还是要谈</p><p>一下啦~~），做这种外挂有一定的难度，需要HOOK或键盘监视<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>做底子，才可以完成，它的原理是先首截了用户的帐号</p><p>或<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">密码</font></a>，然后发到指定邮箱。（我以前写过这样的东东，但是从来没有用过，我知道这种东东很不道德，所以以后千万别</p><p>用呀！）</p><p>　　4、加速式，这种外挂可以加快<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的速度……（对不起大家，这种东东我没有实际做过，所以不能妄自评，惭愧）</p><p>　　这几种外挂之中，前三种可以用VB，Delphi等语言比较好实现，后两种则要用VC等底层支持比较好的<a href="http://hackbase.com/hacker/program" target="_blank"><font color="#000000">编程</font></a><a href="http://hackbase.com/hacker/tool" target="_blank"><font color="#000000">工具</font></a>才好实</p><p>现。 </p><p>　　动作式外挂</p><p>　　首先，先来谈一下动作式的外挂，这也是我第一次写外挂时做的最简单的一种。</p><p>　　记得还在“石器”时代的时候，我看到别人挂着一种<a href="http://down.hackbase.com/" target="_blank"><font color="#000000">软件</font></a>（外挂）<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>就可以四外游走（当时我还不知道外挂怎么回</p><p>事），于是找了这种<a href="http://down.hackbase.com/" target="_blank"><font color="#000000">软件</font></a>过来<a href="http://hackbase.com/hacker/leak" target="_blank"><font color="#000000">研究</font></a>（拿来后才听别人说这叫外挂），发现这种东东其实实现起来并不难，仔佃看其实<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a></p><p>的行走无非就是鼠标在不同的地方点来点去而已，看后就有实现这功能的冲动，随后跑到MSDN上看了一些资料，发现这种</p><p>实现这几个功能，只需要几个简单的API函数就可以搞定：</p><p>　　1、首先我们要知道现在鼠标的位置（为了好还原现在鼠标的位置）所以我们就要用到API函数GetCursorPos，它的使</p><p>用方法如下：</p><p><br />　　 2、我们把鼠标的位置移到要到<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>走到的地方，我们就要用到SetCursorPos函数来移动鼠标位置，它的使用方法如</p><p>下：</p><p><br />　　3、模拟鼠标发出按下和放开的动作，我们要用到mouse<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>event函数来实现，具休使用方法用下：</p><p><br />　　在它的dwFlags处，可用的事件很多如移动MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>MOVE，左键按下MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>LEFTDOWN，左键放开</p><p>MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>LEFTUP，具体的东东还是查一下MSDN吧~~~~~</p><p>　　 好了，有了前面的<a href="http://hackbase.com/network/zs" target="_blank"><font color="#000000">知识</font></a>，我们就可以来看看<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>移走是怎么实现的了：</p><p><br />　　看了以上的<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>，是不是觉得<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>的游走很简单啦~~，举一仿三，还有好多好东东可以用这个<a href="http://hackbase.com/network/network" target="_blank"><font color="#000000">技巧</font></a>实现（我早就说过</p><p>，TMD，这是垃圾外挂的做法，相信了吧~~~），接下来，再看看<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>里面自动<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>的做法吧（必需<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>中<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>支持快捷键</p><p>的），道理还是一样的，只是用的API不同罢了~~~，这回我们要用到的是keybd<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>event函数，其用法如下：</p><p> </p><p>　　我们还要知道扫描码不可以直接使用，要用函数MapVirtualKey把键值转成扫描码，MapVirtualKey的具体使用方法如</p><p>下：</p><p><br />　　好了，比说此快接键是CTRL+A，接下来让我们看看实际<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>是怎么写的：</p><p><br />　　首先模拟按下了CTRL键，再模拟按下A键，再模拟放开A键，最后放开CTRL键，这就是一个模拟按快捷键的周期。</p><p>　　（看到这里，差不多对简易外挂有了一定的了解了吧~~~~做一个试试？如果你举一仿三还能有更好的东东出来，这就</p><p>要看你的领悟能力了~~，不过不要高兴太早这只是才开始，以后还有更复杂的东东等着你呢~~）</p><p><br />本地修改式外挂</p><p>　　现在我们来看看，比动作式外挂更进一步的外挂——本地修改式外挂的整个制作过程进行一个详细的分解。</p><p>　　具我所知，本地修改式外挂最典型的应用就是在“精灵”<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>上面，因为我在近一年前（“精灵”还在测试阶段），</p><p>我所在的公司里有很多同事玩“精灵”，于是我看了一下<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的数据处理方式，发现它所发送到<a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器上的信息是存在于</p><p>内存当中（我看后第一个感受是：修改这种<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>和修改单机版的<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>没有多大分别，换句话说就是在他向<a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器提交信息</p><p>之前修改了内存地址就可以了），当时我找到了地址于是修改了内存地址，果然，按我的想法修改了地址，让系统自动提</p><p>交后，果然成功了~~~~~，后来“精灵”又改成了双地址校检，内存校检等等，在这里我就不废话了~~~~，OK，我们就来</p><p>看看这类外挂是如何制作的：</p><p>　　在做外挂之前我们要对Windows的内存有个具体的认识，而在这里我们所指的内存是指系统的内存偏移量，也就是相</p><p>对内存，而我们所要对其进行修改，那么我们要对几个Windows API进行了解，OK，跟着例子让我们看清楚这种外挂的制</p><p>作和API的应用（为了保证网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的正常运行，我就不把找内存地址的方法详细解说了）：</p><p>　　1、首先我们要用FindWindow,知道<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>窗口的句柄，因为我们要通过它来得知<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的运行后所在进程的ID，下面就是</p><p>FindWindow的用法：</p><p><br />　　2、我们GetWindowThreadProcessId来得到<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>窗口相对应进程的进程ID，函数用法如下：</p><p>DWORD GetWindowThreadProcessId(</p><p>HWND hWnd, // handle of window<br />LPDWORD lpdwProcessId // address of variable for process identifier<br />);</p><p>　　3、得到<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>进程ID后，接下来的事是要以最高权限打开进程，所用到的函数OpenProcess的具体使用方法如下：</p><p><br />　　在dwDesiredAccess之处就是设存取方式的地方，它可设的权限很多，我们在这里使用只要使用PROCESS<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>ALL<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>ACCESS </p><p>来打开进程就可以，其他的方式我们可以查一下MSDN。</p><p>　　4、打开进程后，我们就可以用函数对存内进行操作，在这里我们只要用到WriteProcessMemory来对内存地址写入数</p><p>据即可（其他的操作方式比如说：ReadProcessMemory等，我在这里就不一一介绍了），我们看一下WriteProcessMemory</p><p>的用法：</p><p><br />　　5、下面用CloseHandle关闭进程句柄就完成了。</p><p>　　这就是这类<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂的程序实现部份的方法，好了，有了此方法，我们就有了理性的认识，我们看看实际例子，提升</p><p>一下我们的感性认识吧，下面就是XX<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的外挂<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>，我们照上面的方法对应去<a href="http://hackbase.com/hacker/leak" target="_blank"><font color="#000000">研究</font></a>一下吧：</p><p><br />　　这个<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>是用了多地址对所要提交的数据进行了校验，所以说这类<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂制作并不是很难，最难的是要找到这些地</p><p>址。</p><p><br /><a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">木马</font></a>式外挂</p><p>　　<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">木马</font></a>式外挂，可能大多像<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">木马</font></a>吧，是帮助做外挂的人偷取别人<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的帐号及<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">密码</font></a>的东东。因为网络上有此类外挂的存</p><p>在，所以今天不得不说一下（我个人是非常讨厌这类外挂的，请看过本文的朋友不要到处乱用此<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>，谢谢合作）。要做</p><p>此类外挂的程序实现方法很多（比如HOOK，键盘监视等<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>），因为HOOK<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>对程序员的<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>要求比较高并且在实际应用</p><p>上需要多带一个<a href="http://hackbase.com/News/industry" target="_blank"><font color="#000000">动态</font></a>链接库，所以在文中我会以键盘监视<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>来实现此类<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">木马</font></a>的制作。键盘监视<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>只需要一个.exe文件</p><p>就能实现做到后台键盘监视，这个程序用这种<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>来实现比较适合。</p><p>　　在做程序之前我们必需要了解一下程序的思路：</p><p>　　1、我们首先知道你想记录<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的登录窗口名称。</p><p>　　2、判断登录窗口是否出现。</p><p>　　3、如果登录窗口出现，就记录键盘。</p><p>　　4、当窗口关闭时，把记录信息，通过邮件发送到程序设计者的邮箱。</p><p>　　第一点我就不具体分析了，因为你们比我还要了解你们玩的是什么<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>，登录窗口名称是什么。从第二点开始，我们</p><p>就开始这类外挂的程序实现之旅：</p><p>　　那么我们要怎么样判断登录窗口虽否出现呢？其实这个很简单，我们用FindWindow函数就可以很轻松的实现了：</p><p><br />　　实际程序实现中，我们要找到'xx'窗口，就用FindWindow(nil,'xx')如果当返回值大于0时表示窗口已经出现，那么</p><p>我们就可以对键盘信息进行记录了。</p><p>　　先首我们用SetWindowsHookEx设置监视日志，而该函数的用法如下：</p><p><br />　　在这里要说明的是在我们程序当中我们要对HOOKPROC这里我们要通过写一个函数，来实现而HINSTANCE这里我们直接</p><p>用本程序的HINSTANCE就可以了，具体实现方法为：</p><p><br />　　而HOOKPROC里的函数就要复杂一点点：</p><p><br />　　以上就是记录键盘的整个过程，简单吧，如果记录完可不要忘记释放呀，UnHookWindowsHookEx(hHook)，而hHOOK,就</p><p>是创建setwindowshookex后所返回的句柄。</p><p>　　我们已经得到了键盘的记录，那么现在最后只要把记录的这些信息发送回来，我们就大功造成了。其他发送这块并不</p><p>是很难，只要把记录从文本文件里边读出来，用DELPHI自带的电子邮件组件发一下就万事OK了。<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>如下：</p><p><br />　　这个程序全部功能已经实现，编编试试。</p><p><br />加速型外挂</p><p>　　原本我一直以为加速外挂是针对某个<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>而写的，后来发现我这种概念是不对的，所谓加速外挂其实是修改时钟频率</p><p>达到加速的目的。</p><p>　　以前DOS时代玩过<a href="http://hackbase.com/hacker/program" target="_blank"><font color="#000000">编程</font></a>的人就会马上想到，这很简单嘛不就是直接修改一下8253寄存器嘛，这在以前DOS时代可能可以</p><p>行得通，但是windows则不然。windows是一个32位的操作系统，并不是你想改哪就改哪的（微软的东东就是如此霸气，说</p><p>不给你改就不给你改），但要改也不是不可能，我们可以通过两种方法来实现：第一是写一个硬件驱动来完成，第二是用</p><p>Ring0来实现（这种方法是CIH的作者陈盈豪首用的，它的原理是修改一下IDE表-&gt;创建一个中断门-&gt;进入Ring0-&gt;调用中断</p><p>修改向量，但是没有办法只能用ASM汇编来实现这一切*<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>*，做为高级语言使用者惨啦！），用第一种方法用点麻烦，所以</p><p>我们在这里就用第二种方法实现吧~~~</p><p>　　在实现之前我们来理一下思路吧：</p><p>　　1、我们首先要写一个过程在这个过程里嵌入汇编语言来实现修改IDE表、创建中断门，修改向量等工作</p><p>　　2、调用这个过程来实现加速功能</p><p>　　好了，现在思路有了，我们就边看<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>边讲解吧：</p><p>　　首先我们建立一个过程，这个过程就是本程序的核心部份：</p><p><br />　　最核心的东西已经写完了，大部份读者是知其然不知其所以然吧，呵呵，不过不知其所以然也然。下面我们就试着用</p><p>一下这个过程来做一个类似于“变速齿轮”的一个东东吧！</p><p>　　先加一个窗口，在窗口上放上一个trackbar控件把其Max设为20，Min设为1，把Position设为10，在这个控件的</p><p>Change事件里写上：</p><p><br />　　因为windows默认的值为$1742，所以我们把1742做为基数，又因为值越小越快，反之越慢的原理，所以写了这样一个</p><p>公式，好了，这就是“变速齿轮”的一个Delphi＋ASM版了（只适用于win9X），呵呵，试一下吧，这对你帮助会很大的，</p><p>呵呵。</p><p>　　在win2000里，我们不可能实现在直接对端口进行操作，Ring0也失了效，有的人就会想到，我们可以写驱动程序来完</p><p>成呀，但在这里我告诉你，windows2000的驱动不是一个VxD就能实现的，像我这样的低手是写不出windows所用的驱动WDM</p><p>的，没办法，我只有借助外力实现了，ProtTalk就是一个很好的设备驱动，他很方便的来实现对低层端口的操作，从而实</p><p>现加速外挂。</p><p>　　1、我们首先要下一个PortTalk驱动，他的官方网站是<a href="http://www.beyondlogic.org/"><font color="#000000">http://www.beyondlogic.org/</font></a></p><p>　　2、我们要把里面的prottalk.sys拷贝出来。</p><p>　　3、建立一个Protalk.sys的接口（我想省略了，大家可以上<a href="http://www.freewebs.com/liuyue/porttalk.pas"><font color="#000000">http://www.freewebs.com/liuyue/porttalk.pas</font></a>下个pas </p><p>文件自己看吧）</p><p>　　4、实现加速外挂。</p><p>　　下面就讲一下这程序的实现方法吧，如果说用ProtTalk来操作端口就容易多了，比win98下用ring权限操作方便。</p><p>　　1、新建一个工程，把刚刚下的接口文件和Protalk.sys一起拷到工程文件保存的文件夹下。</p><p>　　2、我们在我们新建的工程加入我们的接口文件</p><p><br />　　3、我们建立一个过程</p><p><br />　　4、先加一个窗口，在窗口上放上一个trackbar控件把其Max设为20，Min设为1，把Position设为10，在这个控件的</p><p>Change事件里写上：</p><p><br />　　就这么容易。</p><p><br />在内存中修改数据的网游外挂</p><p>[文章导读] <br /> <br />现在很多<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>都是把一些信息存入内存单元的，那么我们只需要修改具体内存值就能修改<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>中的属性，很多网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>也</p><p>不外于此 </p><p><br /> </p><p>　　现在很多<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>都是把一些信息存入内存单元的，那么我们只需要修改具体内存值就能修改<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>中的属性，很多网络游</p><p>戏也不外于此。</p><p><br />　　曾几何时，一些网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>也是可以用内存外挂进行修改的，后来被发现后，这些<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>就把单一内存地址改成多内存地</p><p>址校验，加大了修改难度，不过仍然可以通过内存分析器可以破解的。诸如“FPE”这样的<a href="http://down.hackbase.com/" target="_blank"><font color="#000000">软件</font></a>便提供了一定的内存分析</p><p>功能。</p><p>　　“FPE”是基于内存外挂的佼佼者，是家喻户晓的<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>修改<a href="http://down.hackbase.com/" target="_blank"><font color="#000000">软件</font></a>。很多同类的<a href="http://down.hackbase.com/" target="_blank"><font color="#000000">软件</font></a>都是模仿“FPE”而得到玩家的认可</p><p>。而“FPE”实现的<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>到现在都没有公开，很多人只能够通过猜测“FPE”的实现方法，实现同类外挂。笔者也曾经模仿</p><p>过“FPE”实现相应的功能，如“内存修改”、“内存查询”等<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>。稍后会对此<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>进行剖析。</p><p>　　既然要做内存外挂，那么就必须对Windows的内存机制有所了解。<a href="http://hackbase.com/skill" target="_blank"><font color="#000000">计算机</font></a>的内存(RAM)总是不够用的，在操作系统中内</p><p>存就有物理内存和虚拟内存之分，因为程序创建放入物理内存的地址都是在变化的，所以在得到<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>属性时并不能够直接</p><p>访问物理内存地址。在v86模式下，段寄存器使用方法与实模式相同，那么可以通过段寄存器的值左移4位加上地址偏移量</p><p>就可以得到线性地址，而程序创建时在线性地址的中保留4MB-2GB的一段地址，<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>中属性便放于此。在windows中把虚拟</p><p>内存块称之为页，而每页为4KB，在访问内存时读取<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>属性时，为了不破坏数据完整性的快速浏览内存地址值，最好一</p><p>次访问一页。</p><p>　　在操作进程内存时，不需要再使用汇编语言，Windows中提供了一些访问进程内存空间的API，便可以直接对进程内存</p><p>进行操作。但初学者一般掌握不了这一项<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>，为了使初学者也能够对内存进行操作，做出基于内存控制的外挂，笔者把</p><p>一些内存操作及一些内存操作逻辑进行了封装，以控件形式提供给初学者。控件名为：MpMemCtl。</p><p>　　初学者在使用此控件时，要先安装外挂引擎控件包（在此后的每篇文章中外挂引擎控件包仅提供与该文章相应的控制</p><p>控件），具体控件安装方式，请参阅《Delphi指南》，由于篇幅所限，恕不能详细提供。</p><p>　　在引擎安装完成后，便可以在Delphi中的组件栏内，找到[MP GameControls]控件组，其中可以找到[MpMemCtl]控件</p><p>。初学者可以使用此控件可以对内存进行控制。</p><p>　　一、 得到进程句柄</p><p>　　需要操作<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>内存,那么首先必须确认要操作的<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>,而<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>程序在运行时所产生的每一个进程都有一个唯一的句柄。</p><p>　　使用控件得到句柄有三种方法：</p><p>　　1、 通过控件打开程序得到句柄。</p><p>　　在控件中，提供了startProgram方法，通过该方法，可以打开程序得到进程句柄，并且可以返回进程信息。</p><p><br />　　该方法提供了两个参数，第一个参数为要打开的程序路径，第二个参数为打开程序后所创建进程的进程信息。使用这</p><p>个方法在得到进程信息的同时，并给控件的ProcHandle（进程句柄）属性进行了附值，这时可以使用控件直接对内存进程</p><p>读写操作。其应用实例如下：</p><p><br />　　2、通过控件根据程序名称得到句柄。</p><p>　　在控件中，对系统运行进程也有了相应的描述，控件提供了两个方法，用于根据程序名称得到相应的进程句柄。</p><p>getProcIDs()可以得到系统现在所运行的所有程序的名称列表。getProcID()可以通过所运行程序名称，得到相应进程的</p><p>句柄。</p><p><br />　　其应用实例如下：</p><p>　　首先可以通过getProcIDs()并把参数列表返回ComboBox1.Items里：</p><p><br />　　接着可以通过getProcID()得到相应的进程句柄，并给控件的ProcHandle（进程句柄）属性进行了附值，这时可以使</p><p>用控件直接对内存进程读写操作。</p><p><br />　　3、通过控件根据窗口名称得到句柄。</p><p>　　在控件中，控件提供了两个方法，用于根据窗口名称得到相应的进程句柄。可以通过getALLWindow()得到所有在进程</p><p>中运行的窗口。getWinProcHandle()可以通过相应的窗口名称，得到相应的进程的句柄。</p><p><br />　　其应用实例如下：</p><p>　　首先可以通过getALLWindow ()并把参数列表返回ComboBox1.Items里：</p><p><br />　　接着可以通过getWinProcHandle ()得到相应的进程句柄，并给控件的ProcHandle（进程句柄）属性进行了附值，这</p><p>时可以使用控件直接对内存进程读写操作。</p><p><br />　　二、使<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>暂停</p><p>　　在程序中，为了便于更好的得到<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的当前属性。在控件中提供了<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>暂停方法。只需要调用该方法，<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>便可以自</p><p>由的暂停或启动。该方法为：pauseProc()</p><p><br />　　控制类型只能够传入参数0或1，0代表使<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>暂停，1代表取消暂停。其应用实例如下：</p><p><br />　　三、读写内存值</p><p>　　<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>属性其实寄存在内存地址值里，<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>中要了解或修改<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>属性，可以通过对内存地值的读出或写入完成。</p><p>　　通过控件，要读写内存地址值很容易。可以通过调用控件提供的getAddressValue（）及setAddressValue（）两个方</p><p>法即可，在使用方法之前，要确认的是要给ProcHandle属性进行附值，因为对内存的操作必须基于进程。给ProcHandle属</p><p>性附值的方法，在上文中已经介绍。无论是对内存值进行读还是进行写，都要明确所要操作的内存地址。</p><p><br />　　要注意的是，传入内存地址时，内存地址必须为Pointer型。其应用实例如下：</p><p>　　读取地址值（如果“主角”等级所存放的地址为4549632）：</p><p><br />　　这时aValue变量里的值为内存地址[4549632]的值。</p><p>　　写入地址值：</p><p><br />　　通过该方法可以把要修改的内存地址值改为87，即把“主角”等级改为87。</p><p>　　四、内存地址值分析</p><p>　　在<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>中要想要到<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>属性存放的内存地址，那么就对相应内存地址进行内存分析，经过分析以后才可得到<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>属性</p><p>存放的人存地址。</p><p>　　控件提供两种基于内存地址的分析方法。一种是按精确地址值进行搜索分析，另一种是按内存变化增减量进行搜索分</p><p>析。</p><p>　　1、 如果很明确的知道当前想要修改的地址值，那么就用精确地址值进行搜索分析</p><p>　　在<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>中，需要修改<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>的经验值，那么首先要从<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>画面上获得经验值信息，如<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a><a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>当前经验值为9800，需要</p><p>把经验值调高，那么这时候就需要对<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>经验值在内存中搜索得到相应的内存地址，当然很可能在内存中地址值为9800的</p><p>很多，第一次很可能搜索出若干个地址值为9800的地址。等待经验值再有所变化，如从9800变为了20000时，再次进行搜</p><p>索，那么从刚刚所搜索到的地址中，便可以进一步获得范围更少的内存地址，以此类推，那么最后可得到经验值具体存放</p><p>的地址。</p><p>　　如要用控件来实现内存值精确搜索，其实方法很简单，只需要调用该控件的Search（）方法即可。但是在搜索之前要</p><p>确认搜索的范围，正如前文中所说：“而程序创建时在线性地址的中保留4MB-2GB的一段地址”，所以要搜索的地址应该</p><p>是4MB-2GB之间，所以要把控件的MaxAddress属性设为2GB，把控件的MinAddress属性设为4MB。还有一个需要确认的是需</p><p>要搜索的值，那么应该把SearchValue属性设置为当前搜索的值。如果需要显示搜索进度那么可以把ShowGauge属性挂上一</p><p>个相应的TGauge控件（该控件为进度条控件）。</p><p><br />　　在搜索分析时为了提高搜索效率、实现业务逻辑，那么需要传入一个参数，从而确认是否是第一次进行内存。其应用</p><p>实例如下：</p><p><br />　　2、 如果不明确当前想要修改的地址值，只知道想要修改的值变大或变小，那么就按内存变化增减量进行搜索分析。</p><p>　　如有些<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>血值不显示出来，但要对<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>血值进行修改，那么只有借助于内存量增减变化而进行搜索分析出该</p><p><a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>血值存放的地址。如果<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>被怪物打了一下，那么<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>血值就会减少，那么这时候就用减量进行搜索分析，如果<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a></p><p>吃了“血”<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>血值就会增加，那么这时候就用增量进行搜索分析。经过不断搜索，最后会把范围放血值的内存地址给搜</p><p>索出来。</p><p>　　如要用控件来实现内存值精确搜索，其实方法很简单，只需要调用该控件的compare()方法即可。MaxAddress、</p><p>MinAddress属性设置上面章节中有详细介绍，在此不再重提。在此分析中不需要再指定SearchValue属性。如果需要显示</p><p>搜索进度那么可以把ShowGauge属性挂上一个相应的TGauge控件。</p><p><br />　　在搜索分析时为了提高搜索效率、实现业务逻辑，那么需要传入一个参数，从而确认是否是第一次进行内存。搜索分</p><p>析类型有两种：如果参数值为0，那么就代表增量搜索。如果参数值为1，那么就代表减量搜索。其应用实例如下：</p><p><br />　　五、得到内存地址值</p><p>　　在控件中，提供获得分析后内存地址列表的方法，只需要调用getAddressList()方法，便可以获得分析过程中或分析</p><p>结果地址列表。但如果使用的是按内存变化增减量进行搜索分析的方法，那么第一次可能会搜索出来很多的地址，致使返</p><p>回速度过长，那么建议使用getAddressCount（）方法确定返回列表为一定长度后才给予返回。</p><p><br />　　其应用实例如下：</p><p><br />　　通过以上五个步骤，便可以整合成一个功能比较完备的，基于内存控制方法的<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂。有了“FPE”的关键部份功</p><p>能。利用此<a href="http://hackbase.com/hacker/tool" target="_blank"><font color="#000000">工具</font></a>，通过一些方法，不仅仅可以分析出来<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>属性单内存地址，而且可以分析出一部份多内存<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>属性存放</p><p>地址。</p><div class="clear"></div></div>
<img src ="http://www.cppblog.com/eday/aggbug/15383.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-18 16:55 <a href="http://www.cppblog.com/eday/archive/2006/11/18/15383.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>游戏外挂设计技术探讨-二</title><link>http://www.cppblog.com/eday/archive/2006/11/18/15381.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 18 Nov 2006 08:54:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/18/15381.html</guid><description><![CDATA[
		<div class="item-content">(8).编译项目ActiveKey，生成ActiveKey.DLL和ActiveKey.lib。 
<p>　　接着，我们还需要创建一个外壳程序将全局钩子安装了Windows系统中，这个外壳程序编写步骤如下：</p><p>　　(1).创建一个对话框模式的应用程序，项目名为Simulate。</p><p>　　(2).在主对话框中加入一个按钮，使用ClassWizard为其创建CLICK事件。</p><p>　　(3).将ActiveKey项目Debug目录下的ActiveKey.DLL和ActiveKey.lib拷贝到Simulate项目目录下。</p><p>　　(4).从“工程”菜单中选择“设置”，弹出Project Setting对话框，选择Link标签，在“对象/库模块”中输入</p><p>ActiveKey.lib。</p><p>　　(5).将ActiveKey项目中的ActiveKey.h头文件加入到Simulate项目中，并在Stdafx.h中加入#include ActiveKey.h。</p><p>　　(6).在按钮单击事件函数输入如下<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>：</p><p>　　　void CSimulateDlg::OnButton1() <br />　　　{<br />// TODO: Add your control notification handler code here<br />if( !bSetup )<br />{<br />m<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>hook.Start();//激活全局钩子。<br />}<br />else<br />{<br />m<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>hook.Stop();//撤消全局钩子。<br />}<br />bSetup = !bSetup;</p><p>　　　}  </p><p>　　(7).编译项目，并运行程序，单击按钮激活外挂。</p><p>　　(8).启<a href="http://hackbase.com/flash" target="_blank"><font color="#000000">动画</font></a>笔程序，选择文本<a href="http://hackbase.com/hacker/tool" target="_blank"><font color="#000000">工具</font></a>并将笔的颜色设置为红色，将鼠标放在任意位置后，按F10键，画笔程序自动移动</p><p>鼠标并写下一个红色的大写R。图一展示了按F10键前的画笔程序的状态，图二展示了按F10键后的画笔程序的状态。</p><p><br />图一：按F10前状态(001.jpg)</p><p><br />图二：按F10后状态(002.jpg) </p><p><br /> </p><p>　五、封包<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a></p><p>　　通过对动作模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>的介绍，我们对<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂有了一定程度上的认识，也学会了使用动作模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>来实现简单的动作</p><p>模拟型<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂的制作。这种动作模拟型<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂有一定的局限性，它仅仅只能解决使用<a href="http://hackbase.com/skill" target="_blank"><font color="#000000">计算机</font></a>代替人力完成那么有规律</p><p>、繁琐而无聊的<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>动作。但是，随着网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的盛行和复杂度的增加，很多<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>要求将客户端动作信息及时反馈回<a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a></p><p>器，通过<a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器对这些动作信息进行有效认证后，再向客户端发送下一步<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>动作信息，这样动作模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>将失去原有的</p><p>效应。为了更好地“外挂”这些<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>，<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂程序也进行了升级换代，它们将以前针对<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>用户界面层的模拟推进到数</p><p>据通讯层，通过封包<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>在客户端挡截<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a><a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器发送来的<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>控制数据包，分析数据包并修改数据包；同时还需按照游</p><p>戏数据包结构创建数据包，再模拟客户端发送给<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a><a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器，这个过程其实就是一个封包的过程。</p><p>　　封包的<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>是实现第二类<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂的最核心的<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>。封包<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>涉及的<a href="http://hackbase.com/network/zs" target="_blank"><font color="#000000">知识</font></a>很广泛，实现方法也很多，如挡截WinSock</p><p>、挡截API函数、挡截消息、VxD驱动程序等。在此我们也不可能在此文中将所有的封包<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>都进行详细介绍，故选择两种</p><p>在<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂程序中最常用的两种方法：挡截WinSock和挡截API函数。</p><p>　　1． 挡截WinSock</p><p>　　众所周知，Winsock是Windows网络<a href="http://hackbase.com/hacker/program" target="_blank"><font color="#000000">编程</font></a>接口，它工作于Windows应用层，它提供与底层传输<a href="http://hackbase.com/network/protocol" target="_blank"><font color="#000000">协议</font></a>无关的高层数据传输</p><p><a href="http://hackbase.com/hacker/program" target="_blank"><font color="#000000">编程</font></a>接口。在Windows系统中，使用WinSock接口为应用程序提供基于<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">TCP/IP</font></a><a href="http://hackbase.com/network/protocol" target="_blank"><font color="#000000">协议</font></a>的网络访问<a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>，这些<a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>是由</p><p>Wsock32.DLL<a href="http://hackbase.com/News/industry" target="_blank"><font color="#000000">动态</font></a>链接库提供的函数库来完成的。</p><p>　　由上说明可知，任何Windows基于<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">TCP/IP</font></a>的应用程序都必须通过WinSock接口访问网络，当然网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>程序也不例外。</p><p>由此我们可以想象一下，如果我们可以控制WinSock接口的话，那么控制<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>客户端程序与<a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器之间的数据包也将易如</p><p>反掌。按着这个思路，下面的工作就是如何完成控制WinSock接口了。由上面的介绍可知，WinSock接口其实是由一个<a href="http://hackbase.com/News/industry" target="_blank"><font color="#000000">动态</font></a></p><p>链接库提供的一系列函数，由这些函数实现对网络的访问。有了这层的认识，问题就好办多了，我们可以制作一个类似的</p><p><a href="http://hackbase.com/News/industry" target="_blank"><font color="#000000">动态</font></a>链接库来代替原WinSock接口库，在其中实现WinSock32.dll中实现的所有函数，并保证所有函数的参数个数和顺序、</p><p>返回值类型都应与原库相同。在这个自制作的<a href="http://hackbase.com/News/industry" target="_blank"><font color="#000000">动态</font></a>库中，可以对我们感兴趣的函数（如发送、接收等函数）进行挡截，放</p><p>入外挂控制<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>，最后还继续调用原WinSock库中提供的相应功能函数，这样就可以实现对网络数据包的挡截、修改和发</p><p>送等封包功能。</p><p>　　下面重点介绍创建挡截WinSock外挂程序的基本步骤：</p><p>　　(1) 创建DLL项目，选择Win32 Dynamic-Link Library，再选择An empty DLL project。</p><p>　　(2) 新建文件wsock32.h，按如下步骤输入<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>：</p><p>　　① 加入相关变量声明：</p><p>　　　HMODULE hModule=NULL; //模块句柄<br />　　　char buffer[1000]; //缓冲区<br />　　　FARPROC proc; //函数入口指针  </p><p>　　② 定义指向原WinSock库中的所有函数地址的指针变量，因WinSock库共提供70多个函数，限于篇幅，在此就只选择</p><p>几个常用的函数列出，有关这些库函数的说明可参考MSDN相关内容。</p><p>　　　//定义指向原WinSock库函数地址的指针变量。<br />　　　SOCKET (<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a><a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>stdcall *socket1)(int ,int,int);//创建Sock函数。<br />　　　int　(<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a><a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>stdcall *WSAStartup1)(WORD,LPWSADATA);//初始化WinSock库函数。<br />　　　int　(<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a><a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>stdcall *WSACleanup1)();//清除WinSock库函数。<br />　　　int (<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a><a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>stdcall *recv1)(SOCKET ,char FAR * ,int ,int );//接收数据函数。<br />　　　int (<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a><a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>stdcall *send1)(SOCKET ,const char * ,int ,int);//发送数据函数。<br />　　　int (<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a><a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>stdcall *connect1)(SOCKET,const struct sockaddr *,int);//创建连接函数。<br />　　　int (<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a><a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>stdcall *bind1)(SOCKET ,const struct sockaddr *,int );//绑定函数。<br />　　　......其它函数地址指针的定义略。  </p><p>　　(3) 新建wsock32.cpp文件，按如下步骤输入<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>：</p><p>　　① 加入相关头文件声明：</p><p>　　　#include <br />　　　#include <br />　　　#include "wsock32.h"  </p><p>　　② 添加DllMain函数，在此函数中首先需要加载原WinSock库，并获取此库中所有函数的地址。<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>如下：</p><p>　　　BOOL WINAPI DllMain (HANDLE hInst,ULONG ul<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>reason<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>for<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>call,LPVOID lpReserved)<br />　　　{<br />　　　　if(hModule==NULL){<br />　　　　　//加载原WinSock库，原WinSock库已复制为wsock32.001。<br />　　　hModule=LoadLibrary("wsock32.001"); <br />　　}<br />　　　　else return 1;<br />//获取原WinSock库中的所有函数的地址并保存，下面仅列出部分<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>。<br />if(hModule!=NULL){<br />　　　　　//获取原WinSock库初始化函数的地址，并保存到WSAStartup1中。<br />proc=GetProcAddress(hModule,"WSAStartup");<br />　　　WSAStartup1=(int (<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>stdcall *)(WORD,LPWSADATA))proc;<br />　　　　　//获取原WinSock库消除函数的地址，并保存到WSACleanup1中。<br />　　　　proc=GetProcAddress(hModule i,"WSACleanup");<br />　　　　WSACleanup1=(int (<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>stdcall *)())proc;<br />　　　　　//获取<a href="http://hackbase.com/News/hk" target="_blank"><font color="#000000">原创</font></a>建Sock函数的地址，并保存到socket1中。<br />　　　　proc=GetProcAddress(hModule,"socket");<br />　　　　　socket1=(SOCKET (<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>stdcall *)(int ,int,int))proc;<br />　　　　　//获取<a href="http://hackbase.com/News/hk" target="_blank"><font color="#000000">原创</font></a>建连接函数的地址，并保存到connect1中。<br />　　　　　proc=GetProcAddress(hModule,"connect");<br />　　　　　connect1=(int (<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>stdcall *)(SOCKET ,const struct sockaddr *,int ))proc;<br />　　　　　//获取原发送函数的地址，并保存到send1中。<br />　　　　　proc=GetProcAddress(hModule,"send");<br />　　　　　send1=(int (<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>stdcall *)(SOCKET ,const char * ,int ,int ))proc;<br />　　　　　//获取原接收函数的地址，并保存到recv1中。<br />　　　　　proc=GetProcAddress(hModule,"recv");<br />　　　　　recv1=(int (<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>stdcall *)(SOCKET ,char FAR * ,int ,int ))proc;<br />　　　　　......其它获取函数地址<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>略。<br />　　　}<br />　　　else return 0;<br />　　　return 1;<br />} </p><p>　　③ 定义库输出函数，在此可以对我们感兴趣的函数中添加外挂控制<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>，在所有的输出函数的最后一步都调用原</p><p>WinSock库的同名函数。部分输出函数定义<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>如下：</p><p>//库输出函数定义。<br />//WinSock初始化函数。<br />　　　　int PASCAL FAR WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData)<br />　　　　{<br />　　　　　//调用原WinSock库初始化函数<br />　　　　　return WSAStartup1(wVersionRequired,lpWSAData);<br />　　　　}<br />　　　　//WinSock结束清除函数。<br />　　　　int PASCAL FAR WSACleanup(void)<br />　　　　{<br />　　　　　return WSACleanup1(); //调用原WinSock库结束清除函数。<br />　　　　}<br />　　　　//创建Socket函数。<br />　　　　SOCKET PASCAL FAR socket (int af, int type, int protocol)<br />　　　　{<br />　　　　　//调用原WinSock库创建Socket函数。<br />　　　　　return socket1(af,type,protocol);<br />　　　　}<br />　　　　//发送数据包函数<br />　　　　int PASCAL FAR send(SOCKET s,const char * buf,int len,int flags)<br />　　　　{<br />　　　//在此可以对发送的缓冲buf的内容进行修改，以实现欺骗<a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器。<br />　　　外挂<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>......<br />　　　//调用原WinSock库发送数据包函数。<br />　　　　　return send1(s,buf,len,flags);<br />　　　　}<br />//接收数据包函数。<br />　　　　int PASCAL FAR recv(SOCKET s, char FAR * buf, int len, int flags)<br />　　　　{<br />　　　//在此可以挡截到<a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器端发送到客户端的数据包，先将其保存到buffer中。<br />　　　strcpy(buffer,buf);<br />　　　//对buffer数据包数据进行分析后，对其按照玩家的指令进行相关修改。<br />　　　外挂<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>......<br />　　　//最后调用原WinSock中的接收数据包函数。<br />　　　　　return recv1(s, buffer, len, flags);<br />　　　　　}<br />　　　　.......其它函数定义<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>略。</p><div class="clear"></div></div>
<img src ="http://www.cppblog.com/eday/aggbug/15381.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-18 16:54 <a href="http://www.cppblog.com/eday/archive/2006/11/18/15381.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>游戏外挂设计技术探讨-三</title><link>http://www.cppblog.com/eday/archive/2006/11/18/15382.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 18 Nov 2006 08:54:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/18/15382.html</guid><description><![CDATA[
		<div class="item-content">(4)、新建wsock32.def配置文件，在其中加入所有库输出函数的声明，部分声明<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>如下： 
<p>　　　LIBRARY "wsock32"<br />　　　E<a href="http://hackbase.com/skill/XP" target="_blank"><font color="#000000">XP</font></a>ORTS <br />　　　　WSAStartup @1<br />　　　WSACleanup @2<br />　　　　recv @3<br />　　　　send @4<br />　　　　socket @5<br />　　　bind @6<br />　　　closesocket @7<br />　　　connect @8 </p><p>　　　......其它输出函数声明<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>略。</p><p>　　(5)、从“工程”菜单中选择“设置”，弹出Project Setting对话框，选择Link标签，在“对象/库模块”中输入</p><p>Ws2<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>32.lib。</p><p>　　(6)、编译项目，产生wsock32.dll库文件。</p><p>　　(7)、将系统目录下原wsock32.dll库文件拷贝到被外挂程序的目录下，并将其改名为wsock.001；再将上面产生的</p><p>wsock32.dll文件同样拷贝到被外挂程序的目录下。重新启动<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>程序，此时<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>程序将先加载我们自己制作的</p><p>wsock32.dll文件，再通过该库文件间接调用原WinSock接口函数来实现访问网络。上面我们仅仅介绍了挡载WinSock的实</p><p>现过程，至于如何加入外挂控制<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>，还需要外挂开发人员对<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>数据包结构、内容、加密算法等方面的仔细分析（这个</p><p>过程将是一个艰辛的过程），再生成外挂控制<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>。关于数据包分析方法和<a href="http://hackbase.com/network/network" target="_blank"><font color="#000000">技巧</font></a>，不是本文讲解的范围，如您感兴趣可以</p><p>到网上查查相关资料。</p><p><br />(3)、注入外挂<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>进入被挂<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>进程中</p><p>　　完成了定位和修改程序中调用API函数<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>后，我们就可以随意设计自定义的API函数的替代函数了。做完这一切后，</p><p>还需要将这些<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>注入到被外挂<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>程序进程内存空间中，不然<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>进程根本不会访问到替代函数<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>。注入方法有很多</p><p>，如利用全局钩子注入、利用<a href="http://hackbase.com/skill/regedit" target="_blank"><font color="#000000">注册表</font></a>注入挡截User32库中的API函数、利用CreateRemoteThread注入（仅限于NT/2000）、</p><p>利用BHO注入等。因为我们在动作模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>一节已经接触过全局钩子，我相信聪明的读者已经完全掌握了全局钩子的制作</p><p>过程，所以我们在后面的实例中，将继续利用这个全局钩子。至于其它几种注入方法，如果感兴趣可参阅MSDN有关内容。</p><p>　　有了以上理论基础，我们下面就开始制作一个挡截MessageBoxA和recv函数的实例，在开发<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂程序 时，可以此</p><p>实例为框架，加入相应的替代函数和处理<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>即可。此实例的开发过程如下：</p><p>　　(1) 打开前面创建的ActiveKey项目。</p><p>　　(2) 在ActiveKey.h文件中加入HOOKAPI结构，此结构用来存储被挡截API函数名称、原API函数地址和替代函数地址。</p><p>　　　typedef struct tag<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>HOOKAPI <br />　　　{ <br />　　　LPCSTR szFunc;//被HOOK的API函数名称。<br />　　　PROC pNewProc;//替代函数地址。<br />　　　PROC pOldProc;//原API函数地址。<br />　　　}HOOKAPI, *LPHOOKAPI;  </p><p>　　(3) 打开ActiveKey.cpp文件，首先加入一个函数，用于定位输入库在输入数据段中的IAT地址。<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>如下：</p><p>　　　extern "C" <a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a><a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>declspec(dllexport)PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>IMPORT<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DESCRIPTOR <br />　　　LocationIAT(HMODULE hModule, LPCSTR szImportMod) <br />　　　//其中，hModule为进程模块句柄；szImportMod为输入库名称。<br />　　　{ <br />　　　//检查是否为DOS程序，如是返回NULL，因DOS程序没有IAT。<br />　　　PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DOS<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>HEADER pDOSHeader = (PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DOS<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>HEADER) hModule; <br />　　　if(pDOSHeader-&gt;e<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>magic != IMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DOS<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>SIGNATURE) return NULL; <br />　　　　//检查是否为NT标志，否则返回NULL。<br />　　　　PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>NT<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>HEADERS pNTHeader = (PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>NT<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>HEADERS)((DWORD)pDOSHeader+ (DWORD)(pDOSHeader-</p><p>&gt;e<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>lfanew)); <br />　　　　if(pNTHeader-&gt;Signature != IMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>NT<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>SIGNATURE) return NULL; <br />　　　　//没有IAT表则返回NULL。<br />　　　　if(pNTHeader-&gt;OptionalHeader.DataDirectory[IMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DIRECTORY<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>ENTRY<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>IMPORT].VirtualAddress == 0) </p><p>return NULL; <br />　　　　//定位第一个IAT位置。 <br />　　　　PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>IMPORT<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DESCRIPTOR pImportDesc = (PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>IMPORT<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DESCRIPTOR)((DWORD)pDOSHeader + (DWORD)</p><p>(pNTHeader-&gt;OptionalHeader.DataDirectory[IMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DIRECTORY<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>ENTRY<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>IMPORT].VirtualAddress)); <br />　　　　//根据输入库名称循环检查所有的IAT，如匹配则返回该IAT地址，否则检测下一个IAT。<br />　　　　while (pImportDesc-&gt;Name) <br />　　　　{ <br />　　　　　//获取该IAT描述的输入库名称。<br />　　　PSTR szCurrMod = (PSTR)((DWORD)pDOSHeader + (DWORD)(pImportDesc-&gt;Name)); <br />　　　if (stricmp(szCurrMod, szImportMod) == 0) break; <br />　　　pImportDesc++; <br />　　　　} <br />　　　　if(pImportDesc-&gt;Name == NULL) return NULL; <br />　　　return pImportDesc; <br />　　　} </p><p>　　再加入一个函数，用来定位被挡截API函数的IAT项并修改其内容为替代函数地址。<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>如下：</p><p>　　　extern "C" <a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a><a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>declspec(dllexport) <br />　　　HookAPIByName( HMODULE hModule, LPCSTR szImportMod, LPHOOKAPI pHookApi) <br />　　　//其中，hModule为进程模块句柄；szImportMod为输入库名称；pHookAPI为HOOKAPI结构指针。<br />　　　{ <br />　　　　//定位szImportMod输入库在输入数据段中的IAT地址。<br />　　　　PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>IMPORT<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DESCRIPTOR pImportDesc = LocationIAT(hModule, szImportMod); <br />　　if (pImportDesc == NULL) return FALSE; <br />　　　　//第一个Thunk地址。<br />　　　　PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>THUNK<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DATA pOrigThunk = (PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>THUNK<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DATA)((DWORD)hModule + (DWORD)(pImportDesc-</p><p>&gt;OriginalFirstThunk)); <br />　　 //第一个IAT项的Thunk地址。<br />　　　　PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>THUNK<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DATA pRealThunk = (PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>THUNK<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DATA)((DWORD)hModule + (DWORD)(pImportDesc-</p><p>&gt;FirstThunk)); <br />　　　　//循环查找被截API函数的IAT项，并使用替代函数地址修改其值。<br />　　　while(pOrigThunk-&gt;u1.Function) <br />{ <br />　//检测此Thunk是否为IAT项。<br />if((pOrigThunk-&gt;u1.Ordinal &amp; IMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>ORDINAL<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>FLAG) != IMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>ORDINAL<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>FLAG) <br />{<br />　 //获取此IAT项所描述的函数名称。<br />　PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>IMPORT<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>BY<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>NAME pByName =(PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>IMPORT<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>BY<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>NAME)((DWORD)hModule+(DWORD)(pOrigThunk-</p><p>&gt;u1.AddressOfData)); <br />　if(pByName-&gt;Name[0] == '\0') return FALSE; <br />　　//检测是否为挡截函数。<br />if(strcmpi(pHookApi-&gt;szFunc, (char*)pByName-&gt;Name) == 0) <br />　 { <br />　　　　　　　MEMORY<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>BASIC<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>INFORMATION mbi<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>thunk;<br />　　　　　　　//查询修改页的信息。<br />　　　　　　　VirtualQuery(pRealThunk, &amp;mbi<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>thunk, sizeof(MEMORY<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>BASIC<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>INFORMATION)); <br />//改变修改页保护属性为PAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>READWRITE。<br />　　　　　　　VirtualProtect(mbi<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>thunk.BaseAddress,mbi<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>thunk.RegionSize, PAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>READWRITE, </p><p>&amp;mbi<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>thunk.Protect); <br />//保存原来的API函数地址。<br />　　　 　　if(pHookApi-&gt;pOldProc == NULL) <br />pHookApi-&gt;pOldProc = (PROC)pRealThunk-&gt;u1.Function; <br />　 //修改API函数IAT项内容为替代函数地址。<br />pRealThunk-&gt;u1.Function = (PDWORD)pHookApi-&gt;pNewProc; <br />//恢复修改页保护属性。<br />DWORD dwOldProtect; <br />　　　　　　　VirtualProtect(mbi<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>thunk.BaseAddress, mbi<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>thunk.RegionSize, mbi<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>thunk.Protect, </p><p>&amp;dwOldProtect); <br />　　　　　 } <br />} <br />　 pOrigThunk++; <br />　 pRealThunk++; <br />} <br />　　SetLastError(ERROR<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>SUCCESS); //设置错误为ERROR<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>SUCCESS，表示成功。<br />　　return TRUE; <br />　　　}  </p><p>　　(4) 定义替代函数，此实例中只给MessageBoxA和recv两个API进行挡截。<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>如下：</p><p>　　　static int WINAPI MessageBoxA1 (HWND hWnd , LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)<br />　　　{<br />　　　　//过滤掉原MessageBoxA的正文和标题内容，只显示如下内容, 。<br />return MessageBox(hWnd, "Hook API OK!", "Hook API", uType); <br />　　　} <br />　　　static int WINAPI recv1(SOCKET s, char FAR *buf, int len, int flags )<br />　　　{<br />　　　//此处可以挡截<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a><a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器发送来的网络数据包，可以加入分析和处理数据<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>。<br />　　　return recv(s,buf,len,flags);<br />　　　}  </p><p>　　(5) 在KeyboardProc函数中加入激活挡截API<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>，在if( wParam == 0X79 )语句中后面加入如下else if语句：</p><p>　　　......<br />　　　//当激活F11键时，启动挡截API函数功能。<br />　　　else if( wParam == 0x7A )<br />　　　{ <br />　　　　HOOKAPI api[2];<br />api[0].szFunc ="MessageBoxA";//设置被挡截函数的名称。<br />api[0].pNewProc = (PROC)MessageBoxA1;//设置替代函数的地址。<br />api[1].szFunc ="recv";//设置被挡截函数的名称。<br />api[1].pNewProc = (PROC)recv1; //设置替代函数的地址。<br />//设置挡截User32.dll库中的MessageBoxA函数。<br />HookAPIByName(GetModuleHandle(NULL),"User32.dll",&amp;api[0]);<br />//设置挡截Wsock32.dll库中的recv函数。<br />HookAPIByName(GetModuleHandle(NULL),"Wsock32.dll",&amp;api[1]);<br />　　　}<br />　　　......  </p><p>　　(6) 在ActiveKey.cpp中加入头文件声明 "#include "wsock32.h"。 从“工程”菜单中选择“设置”，弹出Project </p><p>Setting对话框，选择Link标签，在“对象/库模块”中输入Ws2<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>32..lib。</p><p>　　(7) 重新编译ActiveKey项目，产生ActiveKey.dll文件，将其拷贝到Simulate.exe目录下。运行Simulate.exe并启动</p><p>全局钩子。激活任意应用程序，按F11键后，运行此程序中可能调用MessageBoxA函数的操作，看看信息框是不是有所变化</p><p>。同样，如此程序正在接收网络数据包，就可以实现封包功能了。</p><p>　　六、结束语</p><p>　　除了以上介绍的几种<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂程序常用的<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>以外，在一些外挂程序中还使用了<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>数据修改<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>、<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>加速<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>等</p><p>。在这篇文章里，就不逐一介绍了。</p><p>网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂核心封包揭密</p><p>[文章导读] <br /> <br />网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的封包<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>是大多数<a href="http://hackbase.com/hacker/program" target="_blank"><font color="#000000">编程</font></a>爱好者都比较关注的关注的问题之一，在这里就让我们一起<a href="http://hackbase.com/hacker/leak" target="_blank"><font color="#000000">研究</font></a>一下这一个问题吧 <br />　　网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的封包<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>是大多数<a href="http://hackbase.com/hacker/program" target="_blank"><font color="#000000">编程</font></a>爱好者都比较关注的关注的问题之一，在这里就让我们一起<a href="http://hackbase.com/hacker/leak" target="_blank"><font color="#000000">研究</font></a>一下这一个问题吧</p><p>。</p><p>　　别看这是封包这一问题，但是涉及的<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>范围很广范，实现的方式也很多（比如说APIHOOK,VXD,Winsock2都可以实现</p><p>），在这里我们不可能每种<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>和方法都涉及，所以我在这里以Winsock2<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>作详细讲解，就算作抛砖引玉。</p><p>　　由于大多数读者对封包类<a href="http://hackbase.com/hacker/program" target="_blank"><font color="#000000">编程</font></a>不是很了解，我在这里就简单介绍一下相关<a href="http://hackbase.com/network/zs" target="_blank"><font color="#000000">知识</font></a>：</p><p>　　APIHooK：</p><p>　　由于Windows的把内核提供的功能都封装到API里面，所以大家要实现功能就必须通过API，换句话说就是我们要想捕</p><p>获数据封包，就必须先要得知道并且捕获这个API，从API里面得到封包信息。</p><p>　　VXD：</p><p>　　直接通过控制VXD驱动程序来实现封包信息的捕获，不过VXD只能用于win9X。</p><p>　　winsock2：</p><p>　　winsock是Windows网络<a href="http://hackbase.com/hacker/program" target="_blank"><font color="#000000">编程</font></a>接口，winsock工作在应用层，它提供与底层传输<a href="http://hackbase.com/network/protocol" target="_blank"><font color="#000000">协议</font></a>无关的高层数据传输<a href="http://hackbase.com/hacker/program" target="_blank"><font color="#000000">编程</font></a>接口，</p><p>winsock2是winsock2.0提供的<a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>提供者接口，但只能在win2000下用。</p><p>　　好了，我们开始进入winsock2封包式<a href="http://hackbase.com/hacker/program" target="_blank"><font color="#000000">编程</font></a>吧。</p><p>　　在封包<a href="http://hackbase.com/hacker/program" target="_blank"><font color="#000000">编程</font></a>里面我准备分两个步骤对大家进行讲解：1、封包的捕获，2、封包的发送。</p><p>　　首先我们要实现的是封包的捕获：</p><p>　　Delphi的封装的winsock是1.0版的，很自然winsock2就用不成。如果要使用winsock2我们要对winsock2在Delphi里面</p><p>做一个接口，才可以使用winsock2。</p><p>　　1、如何做winsock2的接口？</p><p>　　1）我们要先定义winsock2.0所用得到的类型，在这里我们以WSA<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DATA类型做示范，大家可以举一仿三的来实现</p><p>winsock2其他类型的封装。</p><p>　　我们要知道WSA<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DATA类型会被用于WSAStartup(wVersionRequired: word; var WSData: TWSAData): Integer;，大家</p><p>会发现WSData是引用参数，在传入参数时传的是变量的地址，所以我们对WSA<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DATA做以下封装：</p><p><br />　　2）我们要从WS2<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>32.DLL引入winsock2的函数，在此我们也是以WSAStartup为例做函数引入：</p><p><br />　　通过以上方法，我们便可以对winsock2做接口，下面我们就可以用winsock2做封包捕获了，不过首先要有一块网卡。</p><p>因为涉及到正在运作的网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>安全问题，所以我们在这里以IP数据包为例做封包捕获，如果下面的某些数据类型您不是</p><p>很清楚，请您查阅MSDN：</p><div class="clear"></div></div>
<img src ="http://www.cppblog.com/eday/aggbug/15382.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-18 16:54 <a href="http://www.cppblog.com/eday/archive/2006/11/18/15382.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>DLL的远程注入技术</title><link>http://www.cppblog.com/eday/archive/2006/11/18/15379.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 18 Nov 2006 08:53:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/18/15379.html</guid><description><![CDATA[DLL的远程注入技术是目前Win32病毒广泛使用的一种技术。使用这种技术的病毒体通常位于一个DLL中，在系统启动的时候，一个EXE程序会将这个DLL加载至某些系统进程（如Explorer.exe）中运行。这样一来，普通的进程管理器就很难发现这种病毒了，而且即使发现了也很难清除，因为只要病毒寄生的进程不终止运行，那么这个DLL就不会在内存中卸载，用户也就无法在资源管理器中删除这个DLL文件，真可谓一箭双雕哉。<br />记得2003年QQ尾巴病毒肆虐的时候，就已经有些尾巴病毒的变种在使用这种技术了。到了2004年初，我曾经尝试着仿真了一个QQ尾巴病毒，但独是跳过了DLL的远程加载技术。直到最近在学校论坛上看到了几位朋友在探讨这一技术，便忍不住将这一尘封已久的技术从我的记忆中拣了出来，以满足广大的技术爱好者们。<br /><br />必备知识<br /><br />在阅读本文之前，你需要了解以下几个API函数：<br /><br />OpenProcess - 用于打开要寄生的目标进程。<br />VirtualAllocEx/VirtualFreeEx - 用于在目标进程中分配/释放内存空间。<br />WriteProcessMemory - 用于在目标进程中写入要加载的DLL名称。<br />CreateRemoteThread - 远程加载DLL的核心内容，用于控制目标进程调用API函数。<br />LoadLibrary - 目标进程通过调用此函数来加载病毒DLL。<br /><br />在此我只给出了简要的函数说明，关于函数的详细功能和介绍请参阅MSDN。<br /><br />示例程序<br /><br />我将在以下的篇幅中用一个简单的示例Virus.exe来实现这一技术。这个示例的界面如下图：<br /><img src="http://home.nuc.edu.cn/~titilima/images/remote1.GIF" border="0" /><br /><br />首先运行Target.exe，这个文件是一个用Win32 Application向导生成的“Hello, World”程序，用来作为寄生的目标进程。<br />然后在界面的编辑控件中输入进程的名称“Target.exe”，单击“注入DLL”按钮，这时候Virus.exe就会将当前目录下的DLL.dll注入至Target.exe进程中。<br />在注入DLL.dll之后，你也可以单击“卸载DLL”来将已经注入的DLL卸载。<br />点这里下载示例程序<br /><br />模拟的病毒体DLL.dll<br /><br />这是一个简单的Win32 DLL程序，它仅由一个入口函数DllMain组成：<br /><br />BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved )<br />{<br />  switch ( fdwReason )<br />  {<br />  case DLL_PROCESS_ATTACH:<br />    {<br />        MessageBox( NULL, _T("DLL已进入目标进程。"), _T("信息"), MB_ICONINFORMATION );<br />    }<br />    break;<br />  case DLL_PROCESS_DETACH:<br />    {<br />        MessageBox( NULL, _T("DLL已从目标进程卸载。"), _T("信息"), MB_ICONINFORMATION );<br />    }<br />    break;<br />  }<br />  return TRUE;<br />}<br /><br />如你所见，这里我在DLL被加载和卸载的时候调用了MessageBox，这是用来显示我的远程注入/卸载工作是否成功完成。而对于一个真正的病毒体来说，它往往就是处理DLL_PROCESS_ATTACH事件，在其中加入了启动病毒代码的部分：<br /><br />  case DLL_PROCESS_ATTACH:<br />    {<br />        StartVirus();<br />    }<br />    break;<br /><br />注入！<br /><br />现在要开始我们的注入工作了。首先，我们需要找到目标进程：<br /><br />DWORD FindTarget( LPCTSTR lpszProcess )<br />{<br />  DWORD dwRet = 0;<br />  HANDLE hSnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );<br />  PROCESSENTRY32 pe32;<br />  pe32.dwSize = sizeof( PROCESSENTRY32 );<br />  Process32First( hSnapshot, &amp;pe32 );<br />  do<br />  {<br />    if ( lstrcmpi( pe32.szExeFile, lpszProcess ) == 0 )<br />    {<br />        dwRet = pe32.th32ProcessID;<br />        break;<br />    }<br />  } while ( Process32Next( hSnapshot, &amp;pe32 ) );<br />  CloseHandle( hSnapshot );<br />  return dwRet;<br />}<br /><br />这里我使用了Tool Help函数库，当然如果你是NT系统的话，也可以选择PSAPI函数库。这段代码的目的就是通过给定的进程名称来在当前系统中查找相应的进程，并返回该进程的ID。得到进程ID后，就可以调用OpenProcess来打开目标进程了：<br /><br />// 打开目标进程<br />HANDLE hProcess = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, dwProcessID );<br /><br />现在有必要说一下OpenProcess第一个参数所指定的三种权限。在Win32系统下，每个进程都拥有自己的4G虚拟地址空间，各个进程之间都相互独立。如果一个进程需要完成跨进程的工作的话，那么它必须拥有目标进程的相应操作权限。在这里，PROCESS_CREATE_THREAD表示我可以通过返回的进程句柄在该进程中创建新的线程，也就是调用CreateRemoteThread的权限；同理，PROCESS_VM_OPERATION则表示在该进程中分配/释放内存的权限，也就是调用VirtualAllocEx/VirtualFreeEx的权限；PROCESS_VM_WRITE表示可以向该进程的地址空间写入数据，也就是调用WriteProcessMemory的权限。<br />至此目标进程已经打开，那么我们该如何来将DLL注入其中呢？在这之前，我请你看一行代码，是如何在本进程内显式加载DLL的：<br /><br />HMODULE hDll = LoadLibrary( "DLL.dll" );<br /><br />那么，如果能控制目标进程调用LoadLibrary，不就可以完成DLL的远程注入了么？的确是这样，我们可以通过CreateRemoteThread将LoadLibrary作为目标进程的一个线程来启动，这样就可以完成“控制目标进程调用LoadLibrary”的工作了。到这里，也许你会想当然地写下类似这样的代码：<br /><br />DWORD dwID;<br />LPVOID pFunc = LoadLibraryA;<br />HANDLE hThread = CreateRemoteThread( hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, (LPVOID)"DLL.dll", 0, &amp;dwID );<br /><br />不过结果肯定会让你大失所望——注入DLL失败！<br />嗯嗯，那么现在让我们来分析一下失败的原因吧。我是前说过，在Win32系统下，每个进程都拥有自己的4G虚拟地址空间，各个进程之间都是相互独立的。在这里，我们当作参数传入的字符串"DLL.dll"其实是一个数值，它表示这个字符串位于Virus.exe地址空间之中的地址，而这个地址在传给Target.exe之后，它指向的东西就失去了有效性。举个例子来说，譬如A、B两栋大楼，我住在A楼的401；那么B楼的401住的是谁我当然不能确定——也就是401这个门牌号在B楼失去了有效性，而且如果我想要入住B楼的话，我就必须请B楼的楼长为我在B楼中安排新的住处（当然这个新的住处是否401也就不一定了）。<br />由此看来，我就需要做这么一系列略显繁杂的手续——首先在Target.exe目标进程中分配一段内存空间，然后向这段空间写入我要加载的DLL名称，最后再调用CreateRemoteThread。这段代码就成了这样：<br /><br />// 向目标进程地址空间写入DLL名称<br />DWORD dwSize, dwWritten;<br />dwSize = lstrlenA( lpszDll ) + 1;<br />LPVOID lpBuf = VirtualAllocEx( hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE );<br />if ( NULL == lpBuf )<br />{<br />  CloseHandle( hProcess );<br />  // 失败处理<br />}<br />if ( WriteProcessMemory( hProcess, lpBuf, (LPVOID)lpszDll, dwSize, &amp;dwWritten ) )<br />{<br />  // 要写入字节数与实际写入字节数不相等，仍属失败<br />  if ( dwWritten != dwSize )<br />  {<br />    VirtualFreeEx( hProcess, lpBuf, dwSize, MEM_DECOMMIT );<br />    CloseHandle( hProcess );<br />    // 失败处理<br />  }<br />}<br />else<br />{<br />  CloseHandle( hProcess );<br />  // 失败处理<br />}<br />// 使目标进程调用LoadLibrary，加载DLL<br />DWORD dwID;<br />LPVOID pFunc = LoadLibraryA;<br />HANDLE hThread = CreateRemoteThread( hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, lpBuf, 0, &amp;dwID );<br /><br />需要说的有两点，一是由于我要在目标进程中为ANSI字符串来分配内存空间，所以这里凡是和目标进程相关的部分，都明确使用了后缀为“A”的API函数——当然，如果要使用Unicode字符串的话，可以换作后缀是“W”的API；第二，在这里LoadLibrary的指针我是取的本进程的LoadLibraryA的地址，这是因为LoadLibraryA/LoadLibraryW位于kernel32.dll之中，而Win32下每个应用程序都会把kernel32.dll加载到进程地址空间中一个固定的地址，所以这里的函数地址在Target.exe中也是有效的。<br />在调用LoadLibrary完毕之后，我们就可以做收尾工作了：<br /><br />// 等待LoadLibrary加载完毕<br />WaitForSingleObject( hThread, INFINITE );<br />// 释放目标进程中申请的空间<br />VirtualFreeEx( hProcess, lpBuf, dwSize, MEM_DECOMMIT );<br />CloseHandle( hThread );<br />CloseHandle( hProcess );<br /><br />在此解释一下WaitForSingleObject一句。由于我们是通过CreateRemoteThread在目标进程中另外开辟了一个LoadLibrary的线程，所以我们必须等待这个线程运行完毕才能够释放那段先前申请的内存。<br />好了，现在你可以尝试着整理这些代码并编译运行。运行Target.exe，然后开启一个有模块查看功能的进程查看工具（在这里我使用我的July）来查看Target.exe的模块，你会发现在注入DLL之前，Target.exe中并没有DLL.dll的存在：<br /><br /><img src="http://home.nuc.edu.cn/~titilima/images/remote2.GIF" border="0" /><br /><img src="http://home.nuc.edu.cn/~titilima/images/remote3.GIF" border="0" /><br /><br />矛盾相生<br /><br />记得2004年初我将QQ尾巴病毒成功仿真后，有很多网友询问我如何才能杀毒，不过我都没有回答——因为当时我研究的重点并非病毒的寄生特性。这一寄生特性直到今天可以说我才仿真完毕，那么，我就将解毒的方法也一并公开吧。<br />和DLL的注入过程类似，只不过在这里使用了两个API：GetModuleHandle和FreeLibrary。出于篇幅考虑，我略去了与注入部分相似或相同的代码：<br /><br />// 使目标进程调用GetModuleHandle，获得DLL在目标进程中的句柄<br />DWORD dwHandle, dwID;<br />LPVOID pFunc = GetModuleHandleA;<br />HANDLE hThread = CreateRemoteThread( hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, lpBuf, 0, &amp;dwID );<br />// 等待GetModuleHandle运行完毕<br />WaitForSingleObject( hThread, INFINITE );<br />// 获得GetModuleHandle的返回值<br />GetExitCodeThread( hThread, &amp;dwHandle );<br />// 释放目标进程中申请的空间<br />VirtualFreeEx( hProcess, lpBuf, dwSize, MEM_DECOMMIT );<br />CloseHandle( hThread );<br />// 使目标进程调用FreeLibrary，卸载DLL<br />pFunc = FreeLibrary;<br />hThread = CreateRemoteThread( hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, (LPVOID)dwHandle, 0, &amp;dwID );<br />// 等待FreeLibrary卸载完毕<br />WaitForSingleObject( hThread, INFINITE );<br />CloseHandle( hThread );<br />CloseHandle( hProcess );<br /><br />用这个方法可以卸载一个进程中的DLL模块，当然包括那些非病毒体的DLL。所以，这段代码还是谨慎使用为好。<br />在完成卸载之后，如果没有别的程序加载这个DLL，你就可以将它删除了。<br /><img src ="http://www.cppblog.com/eday/aggbug/15379.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-18 16:53 <a href="http://www.cppblog.com/eday/archive/2006/11/18/15379.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>游戏外挂设计技术探讨 一</title><link>http://www.cppblog.com/eday/archive/2006/11/18/15380.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 18 Nov 2006 08:53:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/18/15380.html</guid><description><![CDATA[
		<p>网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>盛行催生了各种外挂，让商家头痛不已，那么外挂是怎么设计的呢？看看本文就知道了 </p>
		<p> </p>
		<p>一、 前言 </p>
		<p>　　所谓<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂，其实是一种<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外辅程序，它可以协助玩家自动产生<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>动作、修改<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>网络数据包以及修改<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>内</p>
		<p>存数据等，以实现玩家用最少的时间和金钱去完成功力升级和过关斩将。虽然，现在对<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂程序的“合法”身份众说</p>
		<p>纷纭，在这里我不想对此发表任何个人意见，让时间去说明一切吧。</p>
		<p>
				<br />　　随着网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的时代的来临，<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂在原有的功能之上进行了新的发展，它变得更加多种多样，功能更加强大，操</p>
		<p>作更加简单，以至有些<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的外挂已经成为一个体系，比如《石器时代》，外挂品种达到了几十种，自动战斗、自动行走</p>
		<p>、自动练级、自动补血、加速、不遇敌、原地遇敌、快速增加经验值、按键精灵……几乎无所不包。</p>
		<p>　　<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂的设计主要是针对于某个<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>开发的，我们可以根据它针对的<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的类型可大致可将外挂分为两种大类。</p>
		<p>　　一类是将<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>中大量繁琐和无聊的<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>动作使用外挂自动完成，以帮助玩家轻松搞定<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>对象并可以快速的增加玩家</p>
		<p>的经验值。比如在《龙族》中有一种工作的设定，玩家的工作等级越高，就可以驾驭越好的装备。但是增加工作等级却不</p>
		<p>是一件有趣的事情，毋宁说是重复枯燥的机械劳动。如果你想做法师用的杖，首先需要做基本工作--?砍树。砍树的方法</p>
		<p>很简单，在一棵大树前不停的点鼠标就可以了，每10000的经验升一级。这就意味着玩家要在大树前不停的点击鼠标，这</p>
		<p>种无聊的事情通过"按键精灵"就可以解决。外挂的"按键精灵"功能可以让玩家摆脱无趣的点击鼠标的工作。</p>
		<p>　　另一类是由外挂程序产生欺骗性的网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>封包，并将这些封包发送到网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a><a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器，利用这些虚假信息欺骗<a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a></p>
		<p>器进行<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>数值的修改，达到修改角色能力数值的目的。这类外挂程序针对性很强，一般在设计时都是针对某个<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>某个</p>
		<p>版本来做的，因为每个网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a><a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器与客户端交流的数据包各不相同，外挂程序必须要对欺骗的网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a><a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器的数据</p>
		<p>包进行分析，才能产生<a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器识别的数据包。这类外挂程序也是当前最流利的一类<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂程序。</p>
		<p>　　另外，现在很多外挂程序功能强大，不仅实现了自动动作代理和封包功能，而且还提供了对网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的客户端程序的</p>
		<p>数据进行修改，以达到欺骗网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a><a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器的目的。我相信，随着网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>商家的反外挂<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>的进展，<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂将会产生</p>
		<p>更多更优秀的<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>，让我们期待着看场<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>大战吧......</p>
		<p>　　三、外挂<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>综述</p>
		<p>　　可以将开发<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂程序的过程大体上划分为两个部分：</p>
		<p>　　前期部分工作是对外挂的主体<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>进行分析，不同类型的外挂分析主体<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的内容也不相同。如外挂为上述谈到的外</p>
		<p>挂类型中的第一类时，其分析过程常是针对<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的场景中的<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>对象的位置和分布情况进行分析，以实现外挂自动进行攻</p>
		<p>击以及位置移动。如外挂为外挂类型中的第二类时，其分析过程常是针对<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a><a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器与客户端之间通讯包数据的结构、内</p>
		<p>容以及加密算法的分析。因网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>公司一般都不会公布其<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>产品的通讯包数据的结构、内容和加密算法的信息，所以</p>
		<p>对于开发第二类外挂成功的关键在于是否能正确分析<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>包数据的结构、内容以及加密算法，虽然可以使用一些<a href="http://hackbase.com/hacker/tool" target="_blank"><font color="#000000">工具</font></a>辅助</p>
		<p>分析，但是这还是一种坚苦而复杂的工作。</p>
		<p>　　后期部分工作主要是根据前期对<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的分析结果，使用大量的程序开发<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>编写外挂程序以实现对<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的控制或修改</p>
		<p>。如外挂程序为第一类外挂时，通常会使用到鼠标模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>来实现<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>角色的自动位置移动，使用键盘模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>来实现游</p>
		<p>戏角色的自动<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>。如外挂程序为第二类外挂时，通常会使用到挡截Sock和挡截API函数<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>，以挡截<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a><a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器传来的</p>
		<p>网络数据包并将数据包修改后封包后传给<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a><a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器。另外，还有许多外挂使用对<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>客户端程序内存数据修改<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>以及</p>
		<p>
				<a href="http://hackbase.com/game" target="_blank">
						<font color="#000000">游戏</font>
				</a>加速<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>。</p>
		<p>　　本文主要是针对开发<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂程序后期使用的程序开发<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>进行探讨，重点介绍的如下几种在<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂中常使用的程</p>
		<p>序开发<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>：</p>
		<p>　　● 动作模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>：主要包括键盘模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>和鼠标模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>。</p>
		<p>　　● 封包<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>：主要包括挡截Sock<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>和挡截API<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>。</p>
		<p>四、动作模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a></p>
		<p>　　我们在前面介绍过，几乎所有的<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>都有大量繁琐和无聊的<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>动作以增加玩家的功力，还有那些数不完的迷宫，这</p>
		<p>些好像已经成为了角色<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的代名词。现在，外挂可以帮助玩家从这些繁琐而无聊的工作中摆脱出来，专注于<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>情节的</p>
		<p>进展。外挂程序为了实现自动角色位置移动和自动<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>等功能，需要使用到键盘模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>和鼠标模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>。下面我们将重</p>
		<p>点介绍这些<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>并编写一个简单的实例帮助读者理解动作模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>的实现过程。</p>
		<p>　　１． 鼠标模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a><br />　　<br />　　几乎所有的<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>中都使用了鼠标来改变角色的位置和方向，玩家仅用一个小小的鼠标，就可以使角色畅游天下。那么</p>
		<p>，我们如何实现在没有玩家的参与下角色也可以自动行走呢。其实实现这个并不难，仅仅几个Windows API函数就可以搞</p>
		<p>定，让我们先来认识认识这些API函数。</p>
		<p>　　(1) 模拟鼠标动作API函数mouse<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event，它可以实现模拟鼠标按下和放开等动作。</p>
		<p>　　　　VOID mouse<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(<br />　　　　　　DWORD dwFlags, // 鼠标动作标识。<br />　　　　　　DWORD dx, // 鼠标水平方向位置。<br />　　　　　　DWORD dy, // 鼠标垂直方向位置。<br />　　　　　　DWORD dwData, // 鼠标轮子转动的数量。<br />　　　　　　DWORD dwExtraInfo // 一个关联鼠标动作辅加信息。<br />　　　　); </p>
		<p>　　其中，dwFlags表示了各种各样的鼠标动作和点击活动，它的常用取值如下：</p>
		<p>　　　MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>MOVE　表示模拟鼠标移动事件。</p>
		<p>　　　MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>LEFTDOWN 表示模拟按下鼠标左键。</p>
		<p>　　　MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>LEFTUP 表示模拟放开鼠标左键。</p>
		<p>　　　MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>RIGHTDOWN 表示模拟按下鼠标右键。</p>
		<p>　　　MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>RIGHTUP 表示模拟放开鼠标右键。</p>
		<p>　　　MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>MIDDLEDOWN 表示模拟按下鼠标中键。</p>
		<p>　　　MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>MIDDLEUP 表示模拟放开鼠标中键。</p>
		<p>　　(2)、设置和获取当前鼠标位置的API函数。获取当前鼠标位置使用GetCursorPos()函数，设置当前鼠标位置使用</p>
		<p>SetCursorPos()函数。</p>
		<p>　　　　BOOL GetCursorPos(<br />　　　　　LPPOINT　lpPoint // 返回鼠标的当前位置。<br />　　　　);<br />　　　　BOOL SetCursorPos(<br />　　　　int X, // 鼠标的水平方向位置。<br />　　　　　　int Y //鼠标的垂直方向位置。<br />　　　　);  </p>
		<p>　　通常<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>角色的行走都是通过鼠标移动至目的地，然后按一下鼠标的按钮就搞定了。下面我们使用上面介绍的API函</p>
		<p>数来模拟角色行走过程。</p>
		<p>　　　CPoint oldPoint,newPoint;<br />　　　GetCursorPos(&amp;oldPoint); //保存当前鼠标位置。<br />　　　newPoint.x = oldPoint.x+40;<br />　　　newPoint.y = oldPoint.y+10;<br />　　　SetCursorPos(newPoint.x,newPoint.y); //设置目的地位置。<br />　　　mouse<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>RIGHTDOWN,0,0,0,0);//模拟按下鼠标右键。<br />　　　mouse<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>RIGHTUP,0,0,0,0);//模拟放开鼠标右键。  </p>
		<p>　　2． 键盘模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a></p>
		<p>　　在很多<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>中，不仅提供了鼠标的操作，而且还提供了键盘的操作，在对<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>对象进行<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>时还可以使用快捷键。为</p>
		<p>了使这些<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>过程能够自动进行，外挂程序需要使用键盘模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>。像鼠标模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>一样，Windows API也提供了一系列</p>
		<p>API函数来完成对键盘动作的模拟。</p>
		<p>　　模拟键盘动作API函数keydb<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event，它可以模拟对键盘上的某个或某些键进行按下或放开的动作。</p>
		<p>　　　VOID keybd<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(<br />　　　　　BYTE bVk, // 虚拟键值。<br />　　　　　BYTE bScan, // 硬件扫描码。<br />　　　　　DWORD dwFlags, // 动作标识。<br />　　　　　DWORD dwExtraInfo // 与键盘动作关联的辅加信息。<br />　　　); </p>
		<p>　　其中，bVk表示虚拟键值，其实它是一个BYTE类型值的宏，其取值范围为1-254。有关虚拟键值表请在MSDN上使用关键</p>
		<p>字“Virtual-Key Codes”查找相关资料。bScan表示当键盘上某键被按下和放开时，键盘系统硬件产生的扫描码，我们可</p>
		<p>以MapVirtualKey()函数在虚拟键值与扫描码之间进行转换。dwFlags表示各种各样的键盘动作，它有两种取值：</p>
		<p>KEYEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>EXTENDEDKEY和KEYEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>KEYUP。</p>
		<p>　　下面我们使用一段<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>实现在<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>中按下Shift+R快捷键对<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>对象进行<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>。</p>
		<p>　　　keybd<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(VK<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>CONTROL,MapVirtualKey(VK<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>CONTROL,0),0,0); //按下CTRL键。<br />　　　keybd<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(0x52,MapVirtualKey(0x52,0),0,0);//键下R键。<br />　　　keybd<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(0x52,MapVirtualKey(0x52,0), KEYEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>KEYUP,0);//放开R键。<br />　　　keybd<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(VK<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>CONTROL,MapVirtualKey(VK<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>CONTROL,0), <br />　　　KEYEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>KEYUP,0);//放开CTRL键。 </p>
		<p>　　3． 激活外挂</p>
		<p>　　上面介绍的鼠标和键盘模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>实现了对<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>角色的动作部分的模拟，但要想外挂能工作于<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>之上，还需要将其与</p>
		<p>
				<a href="http://hackbase.com/game" target="_blank">
						<font color="#000000">游戏</font>
				</a>的场景窗口联系起来或者使用一个激活键，就象按键精灵的那个激活键一样。我们可以用GetWindow函数来枚举窗口</p>
		<p>，也可以用Findwindow函数来查找特定的窗口。另外还有一个FindWindowEx函数可以找到窗口的子窗口，当<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>切换场景</p>
		<p>的时候我们可以用FindWindowEx来确定一些当前窗口的特征，从而判断是否还在这个场景，方法很多了，比如可以</p>
		<p>GetWindowInfo来确定一些东西，比如当查找不到某个按钮的时候就说明<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>场景已经切换了等等办法。当使用激活键进</p>
		<p>行关联，需要使用Hook<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>开发一个全局键盘钩子，在这里就不具体介绍全局钩子的开发过程了，在后面的实例中我们将</p>
		<p>会使用到全局钩子，到时将学习到全局钩子的相关<a href="http://hackbase.com/network/zs" target="_blank"><font color="#000000">知识</font></a>。</p>
		<p>
				<br />　　4． 实例实现</p>
		<p>　　通过上面的学习，我们已经基本具备了编写动作式<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂的能力了。下面我们将创建一个画笔程序外挂，它实现自</p>
		<p>动移<a href="http://hackbase.com/flash" target="_blank"><font color="#000000">动画</font></a>笔字光标的位置并写下一个红色的“R”字。以这个实例为基础，加入相应的<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>动作规则，就可以实现一个完</p>
		<p>整的<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂。这里作者不想使用某个<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>作为例子来开发外挂（因没有<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>商家的授权啊！），如读者感兴趣的话可以</p>
		<p>找一个<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>试试，最好仅做测试<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>用。</p>
		<p>　　首先，我们需要编写一个全局钩子，使用它来激活外挂，激活键为F10。创建全局钩子步骤如下：</p>
		<p>　　(1)．选择MFC AppWizard(DLL)创建项目ActiveKey，并选择MFC Extension DLL（共享MFC拷贝）类型。</p>
		<p>　　(2).插入新文件ActiveKey.h，在其中输入如下<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>：</p>
		<p>　　　#ifndef <a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>KEYDLL<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>H<br />　　　#define <a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>KEYDLL<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>H</p>
		<p>　　　class AFX<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>EXT<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>CLASS CKeyHook:public CObject<br />　　　{<br />　　　　public:<br />　CKeyHook();<br />　~CKeyHook();<br />　HHOOK Start();　//安装钩子<br />　BOOL Stop(); //卸载钩子<br />　　　};<br />　　　#endif  </p>
		<p>　　(3).在ActiveKey.cpp文件中加入声明＂#include ActiveKey.h＂。</p>
		<p>　　(4).在ActiveKey.cpp文件中加入共享数据段，<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>如下：</p>
		<p>　　　//Shared data section<br />　　　#pragma data<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>seg("sharedata")<br />　　　HHOOK glhHook=NULL; //钩子句柄。<br />　　　HINSTANCE glhInstance=NULL; //DLL实例句柄。<br />　　　#pragma data<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>seg()  </p>
		<p>　　(5).在ActiveKey.def文件中设置共享数据段属性，<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>如下：</p>
		<p>　　　SETCTIONS<br />　　　shareddata READ WRITE SHARED </p>
		<p>　　(6).在ActiveKey.cpp文件中加入CkeyHook类的实现<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>和钩子函数<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>：</p>
		<p>　　　//键盘钩子处理函数。<br />　　　extern "C" LRESULT WINAPI KeyboardProc(int nCode,WPARAM wParam,LPARAM lParam)<br />　　　{<br />　　　if( nCode &gt;= 0 )<br />　　　{<br />　　　if( wParam == 0X79 )//当按下F10键时，激活外挂。<br />　{<br />　　//外挂实现<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>。<br />CPoint newPoint,oldPoint;<br />　　 GetCursorPos(&amp;oldPoint);<br />　　 newPoint.x = oldPoint.x+40;<br />　　 newPoint.y = oldPoint.y+10;<br />　　 SetCursorPos(newPoint.x,newPoint.y);<br />　　 mouse<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>LEFTDOWN,0,0,0,0);//模拟按下鼠标左键。<br />　　mouse<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>LEFTUP,0,0,0,0);//模拟放开鼠标左键。<br />　　keybd<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(VK<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>SHIFT,MapVirtualKey(VK<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>SHIFT,0),0,0); //按下SHIFT键。<br />　　keybd<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(0x52,MapVirtualKey(0x52,0),0,0);//按下R键。<br />　　keybd<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(0x52,MapVirtualKey(0x52,0),KEYEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>KEYUP,0);//放开R键。<br />　　keybd<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(VK<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>SHIFT,MapVirtualKey(VK<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>SHIFT,0),KEYEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>KEYUP,0);//放开SHIFT键。<br />　　　　　　SetCursorPos(oldPoint.x,oldPoint.y);<br />　}<br />　　　}<br />　　　return CallNextHookEx(glhHook,nCode,wParam,lParam);<br />　　　}</p>
		<p>　　　CKeyHook::CKeyHook(){}<br />　　　CKeyHook::~CKeyHook()<br />　　　{　<br />　　　if( glhHook )<br />Stop();<br />　　　}<br />　　　//安装全局钩子。<br />　　　HHOOK CKeyHook::Start()<br />　　　{ <br />glhHook = SetWindowsHookEx(WH<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>KEYBOARD,KeyboardProc,glhInstance,0);//设置键盘钩子。<br />return glhHook;<br />}<br />　　　//卸载全局钩子。<br />　　　BOOL CKeyHook::Stop()<br />　　　{<br />　　　BOOL bResult = TRUE;<br />　if( glhHook )<br />　　　bResult = UnhookWindowsHookEx(glhHook);//卸载键盘钩子。<br />　　　return bResult;<br />　　　}  </p>
		<p>　　(7).修改DllMain函数，<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>如下：</p>
		<p>　　　extern "C" int APIENTRY<br />　　　DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)<br />　　　{<br />//如果使用lpReserved参数则删除下面这行 <br />UNREFERENCED<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>PARAMETER(lpReserved);</p>
		<p>if (dwReason == DLL<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>PROCESS<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>ATTACH)<br />{<br />　　TRACE0("NOtePadHOOK.DLL Initializing!\n");<br />　　 //扩展DLL仅初始化一次 <br />　　if (!AfxInitExtensionModule(ActiveKeyDLL, hInstance))<br />return 0;<br />　　new CDynLinkLibrary(ActiveKeyDLL);<br />　　　　　　//把DLL加入<a href="http://hackbase.com/News/industry" target="_blank"><font color="#000000">动态</font></a>MFC类库中 <br />　　glhInstance = hInstance;<br />　　//插入保存DLL实例句柄 <br />}<br />else if (dwReason == DLL<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>PROCESS<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>DETACH)<br />{<br />　　TRACE0("NotePadHOOK.DLL Terminating!\n");<br />　　//终止这个链接库前调用它 <br />　　AfxTermExtensionModule(ActiveKeyDLL);<br />}<br />return 1; <br />　　　} </p>
<img src ="http://www.cppblog.com/eday/aggbug/15380.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-18 16:53 <a href="http://www.cppblog.com/eday/archive/2006/11/18/15380.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用户层下拦截系统 API 的原理与实现 二</title><link>http://www.cppblog.com/eday/archive/2006/11/18/15378.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 18 Nov 2006 08:52:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/18/15378.html</guid><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 1 //////////////////////////////////////////////////////////////////////   2 附录：一个拦截CreateFile函数的简单实现   3 //////////////////////////////////////////////////////////////////////   4 #include &lt;stdio....&nbsp;&nbsp;<a href='http://www.cppblog.com/eday/archive/2006/11/18/15378.html'>阅读全文</a><img src ="http://www.cppblog.com/eday/aggbug/15378.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-18 16:52 <a href="http://www.cppblog.com/eday/archive/2006/11/18/15378.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用户层下拦截系统 API 的原理与实现 一</title><link>http://www.cppblog.com/eday/archive/2006/11/18/15377.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 18 Nov 2006 08:51:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/18/15377.html</guid><description><![CDATA[　　拦截api的技术有很多种，大体分为用户层和内核层的拦截．这里只说说用户层的拦截．而用户层也分为许多种：修改PE文件导入表，直接修改要拦截的api的内存（从开始到最后，使程序跳转到指定的地址执行）．不过大部分原理都是修改程序流程，使之跳转到你要执行的地方，然后再返回到原地址．原来api的功能必须还能实现．否则拦截就失去作用了．修改文件导入表的方法的缺点是如果用户程序动态加载（使用LoadLibrary和GetProcAddress函数），拦截将变得复杂一些．所以这里介绍一下第二种方法，直接修改api，当然不是全局的．（后面会说到） <br /><br />　　需要了解的一些知识： <br /><br />　　１．windows内存的结构属性和进程地址空间 <br /><br />　　２．函数堆栈的一些知识 <br /><br /><br /><br />一：win2000和xp的内存结构和进程地址空间 <br /><br />  windows采用4GB平坦虚拟地址空间的做法。即每个进程单独拥有4GB的地址空间。每个进程只能访问自己的这4GB的虚拟空间，而对于其他进程的地址空间则是不可见的。这样保证了进程的安全性和稳定性。但是，这4GB的空间是一个虚拟空间，在使用之前，我们必须先保留一段虚拟地址，然后再为这段虚拟地址提交物理存储器。可是我们的内存大部分都还没有1GB，那么这4GB的地址空间是如何实现的呢？事实上windows采用的内存映射这种方法，即把物理磁盘当作内存来使用，比如我们打开一个可执行文件的时候，<b><a href="http://www.baidu.com/s?tn=piglet&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=操作系统" target="_blank"><font color="#ff0000">操作系统</font></a></b>会为我们开辟这个4GB的地址空间：0x00000000--0xffffffff。其中0x00000000--0x7fffffff是属于用户层的空间.0x80000000--0xffffffff则属于共享内核方式分区，主要是<b><a href="http://www.baidu.com/s?tn=piglet&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=操作系统" target="_blank"><font color="#ff0000">操作系统</font></a></b>的线程调度，内存管理，文件系统支持，网络支持和所有设备驱动程序。对于用户层的进程，这些地址空间是不可访问的。任何访问都将导致一个错误。开辟这4GB的虚拟地址空间之后，系统会把磁盘上的执行文件映射到进程的地址空间中去(一般是在地址0x00400000，可以通过修改编译选项来修改这个地址)而一个进程运行所需要的动态库文件则一般从0x10000000开始加载。但是如果所有的动态库都加载到这个位置肯定会引起冲突。因此必须对一些可能引起冲突的dll编译时重新修改基地址。但是对于所有的<b><a href="http://www.baidu.com/s?tn=piglet&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=操作系统" target="_blank"><font color="#ff0000">操作系统</font></a></b>所提供的动态库windows已经定义好了映射在指定的位置。这个位置会随着版本的不同而会有所改变，不过对于同一台机器上的映射地址来说都是一样的。即在a进程里映射的kernel32.dll的地址和在进程b里的kernel32.dll的地址是一样的。对于文件映射是一种特殊的方式，使得程序不需要进行磁盘i/o就能对磁盘文件进行操作，而且支持多种保护属性。对于一个被映射的文件，主要是使用CreateFileMapping函数，利用他我们可以设定一些读写属性:PAGE_READONLY,PAGE_READWRITE,PAGE_WRITECOPY.第一参数指定只能对该映射文件进行读操作。任何写操作将导致内存访问错误。第二个参数则指明可以对映射文件进行读写。这时候，任何对文件的读写都是直接操作文件的。而对于第三个参数PAGE_WRITECOPY顾名思义就是写入时拷贝，任何向这段内存写入的操作(因为文件是映射到进程地址空间的，对这段空间的读写就相当于对文件进行的直接读写)都将被系统捕获，并重新在你的虚拟地址空间重新保留并分配一段内存，你所写入的一切东西都将在这里，而且你原先的指向映射文件的内存地址也会实际指向这段重新分配的内存，于是在进程结束后，映射文件内容并没有改变，只是在运行期间在那段私有拷贝的内存里面存在着你修改的内容。windows进程运行所需要映射的一些系统dll就是以这种方式映射的，比如常用的ntdll.dll,kernel32.dll,gdi32.dll.几乎所有的进程都会加载这三个动态库。如果你在一个进程里修改这个映射文件的内容，并不会影响到其他的进程使用他们。你所修改的只是在本进程的地址空间之内的。事实上原始文件并没有被改变。 <br />这样，在后面的修改系统api的时候，实际就是修改这些动态库地址内的内容。前面说到这不是修改全局api就是这个原因，因为他们都是以写入时拷贝的方式来映射的。不过这已经足够了，windows提供了2个强大的内存操作函数ReadProcessMemory和WriteProcessMemory.利用这两个函数我们就可以随便对任意进程的任意用户地址空间进行读写了。但是，现在有一个问题，我们该写什么，说了半天，怎么实现跳转呢？现在来看一个简单的例子： <br />MessageBox(NULL, "World", "Hello", 0); <br />我们在执行这条语句的时候，调用了系统api MessageBox，实际上在程序中我没有定义UNICODE宏，系统调用的是MessageBox的ANSI版本MessageBoxA,这个函数是由user32.dll导出的。下面是执行这条语句的汇编代码： <br />0040102A   push         0 <br />0040102C   push         offset string "Hello" (0041f024) <br />00401031   push         offset string "World" (0041f01c) <br />00401036   push         0 <br />00401038   call         dword ptr [__imp__MessageBoxA@16 (0042428c)] <br />前面四条指令分别为参数压栈，因为MessageBoxA是__stdcall调用约定，所以参数是从右往左压栈的。最后再CALL 0x0042428c <br /><br />看看0042428c这段内存的值： <br />0042428C   0B 05 D5 77 00 00 00 <br />可以看到这个值0x77d5050b,正是user32.dll导出函数MessageBoxA的入口地址。 <br /><br />这是0x77D5050B处的内容，   <br />77D5050B 8B FF                 mov         edi,edi <br />77D5050D 55                   push         ebp <br />77D5050E 8B EC                 mov         ebp,esp <br />理论上只要改变api入口和出口的任何机器码，都可以拦截该api。这里我选择最简单的修改方法，直接修改api入口的前十个字节来实现跳转。为什么是十字节呢？其实修改多少字节都没有关系，只要实现了函数的跳转之后，你能把他们恢复并让他继续运行才是最重要的。在CPU的指令里，有几条指令可以改变程序的流程：JMP，CALL，INT，RET，RETF，IRET等指令。这里我选择CALL指令，因为他是以函数调用的方式来实现跳转的，这样可以带一些你需要的参数。到这里，我该说说函数的堆栈了。 <br /><br />总结：windows进程所需要的动态库文件都是以写入时拷贝的方式映射到进程地址空间中的。这样，我们只能拦截指定的进程。修改目标进程地址空间中的指定api的入口和出口地址之间的任意数据，使之跳转到我们的拦截代码中去，然后再恢复这些字节，使之能顺利工作。 <br /><br /><br /><br /><br />二：函数堆栈的一些知识 <br /><br />  正如前面所看到MessageBoxA函数执行之前的汇编代码，首先将四个参数压栈，然后CALL MessageBoxA，这时候我们的线程堆栈看起来应该是这样的： <br /><br />|     |     &lt;---ESP <br />|返回地址| <br />|参数1| <br />|参数2| <br />|参数3| <br />|参数4| <br />|..   | <br /><br />我们再看MessageBoxA的汇编代码， <br />77D5050B 8B FF                 mov         edi,edi <br />77D5050D 55                   push         ebp <br />77D5050E 8B EC                 mov         ebp,esp <br />注意到堆栈的操作有PUSH ebp,这是保存当前的基址指针，以便一会儿恢复堆栈后返回调用线程时使用，然后再有mov ebp,esp就是把当前esp的值赋给ebp，这时候我们就可以使用 ebp+偏移 来表示堆栈中的数据，比如参数1就可以表示成[ebp+8]，返回地址就可以表示成[ebp+4]..如果我们在拦截的时候要对这些参数和返回地址做任何处理，就可以使用这种方法。如果这个时候函数有局部变量的话，就通过减小ESP的值的方式来为之分配空间。接下来就是保存一些寄存器：EDI,ESI,EBX.要注意的是，函数堆栈是反方向生长的。这时候堆栈的样子： <br />|....| <br />|EDI|   &lt;---ESP <br />|ESI| <br />|EBX| <br />|局部变量| <br />|EBP     |   <br />|返回地址| <br />|参数1| <br />|参数2| <br />|参数3| <br />|参数4| <br />|..   | <br /><br />在函数返回的时候，由函数自身来进行堆栈的清理，这时候清理的顺序和开始入栈的顺序恰恰相反，类似的汇编代码可能是这样的： <br /><br />pop edi <br />pop esi <br />pop ebx <br />add esp, 4 <br />pop ebp <br />ret 0010 <br />先恢复那些寄存器的值，然后通过增加ESP的值的方式来释放局部变量。这里可以用mov esp, ebp来实现清空所有局部变量和其他一些空闲分配空间。接着函数会恢复EBP的值，利用指令POP EBP来恢复该寄存器的值。接着函数运行ret 0010这个指令。该指令的意思是，函数把控制权交给当前栈顶的地址的指令，同时清理堆栈的16字节的参数。如果函数有返回值的话，那在EAX寄存器中保存着当前函数的返回值。如果是__cdecl调用方式，则执行ret指令，对于堆栈参数的处理交给调用线程去做。如wsprintf函数。 <br /><br />这个时候堆栈又恢复了原来的样子。线程得以继续往下执行... <br />在拦截api的过程之中一个重要的任务就是保证堆栈的正确性。你要理清每一步堆栈中发生了什么。 <br /><br /><br /><br />三：形成思路 <br />   <br />  呵呵，不知道你现在脑海是不是有什么想法。怎么去实现拦截一个api？ <br />  这里给出一个思路，事实上拦截的方法真的很多，理清了一个，其他的也就容易了。而且上面所说的2个关键知识，也可以以另外的形式来利用。 <br />  我以拦截CreateFile这个api为例子来简单说下这个思路吧： <br />   <br />  首先，既然我们要拦截这个api就应该知道这个函数在内存中的位置吧，至少需要知道从哪儿入口。CreateFile这个函数是由kernel32.dll这个动态库导出的。我们可以使用下面的方法来获取他映射到内存中的地址： <br />  HMODULE hkernel32 = LoadLibrary("Kernel32.dll"); <br />  PVOID dwCreateFile = GetProcAddress(hkernei32, "CreateFileA"); <br />这就可以得到createfile的地址了，注意这里是获取的createfile的ansic版本。对于UNICODE版本的则获取CreateFileW。这时dwCreateFile的值就是他的地址了。对于其他进程中的createfile函数也是这个地址，前面说过windows指定了他提供的所有的dll文件的加载地址。 <br />   <br />  接下来，我们该想办法实现跳转了。最简单的方法就是修改这个api入口处的代码了。但是我们该修改多少呢？修改的内容为什么呢？前面说过我们可以使用CALL的方式来实现跳转，这种方法的好处是可以为你的拦截函数提供一个或者多个参数。这里只要一个参数就足够了。带参数的函数调用的汇编代码是什么样子呢，前面也已经说了，类似与调用MessageBoxA时的代码： <br /><br />PUSH   参数地址 <br />CALL   函数入口地址(这里为一个偏移地址) <br /><br />执行这2条指令就能跳转到你要拦截的函数了，但是我们该修改成什么呢。首先，我们需要知道这2条指令的长度和具体的机器代码的值。其中PUSH对应0x68，而CALL指令对应的机器码为0xE8,而后面的则分别对应拦截函数的参数地址和函数的地址。注意第一个是一个直接的地址，而第二个则是一个相对地址。当然你也可以使用0xFF0x15这个CALL指令来进行直接地址的跳转。 <br />下面就是计算这2个地址的值了， <br />对于参数和函数体的地址，要分情况而定，对于对本进程中api的拦截，则直接取地址就可以了。对于参数，可以先定义一个参数变量，然后取变量地址就ok了。 <br />如果是想拦截其他进程中的api，则必须使用其他一些方法，最典型的方法是利用VirtualAllocEx函数来在其他进程中申请和提交内存空间。然后用WriteProcessMemory来分别把函数体和参数分别写入申请和分配的内存空间中去。然后再生成要修改的数据，最后用WriteProcessMemory来修改api入口，把入口的前10字节修改为刚刚生成的跳转数据。比如在远程进程中你写入的参数和函数体的内存地址分别为0x00010000和0x00011000,则生成的跳转数据为 68 00 00 01 00 E8 00 10 01 00(PUSH 00010000   CALL 00011000),这样程序运行createfile函数的时候将会先运行PUSH 00010000   CALL 00011000，这样就达到了跳转的目的。此刻我们应该时刻注意堆栈的状态，对于CreateFile有 <br />HANDLE CreateFile( <br />LPCTSTR lpFileName, <br />DWORD dwDesiredAccess, <br />DWORD dwShareMode, <br />LPSECURITY_ATTRIBUTES lpSecurityAttributes, <br />DWORD dwCreationDisposition, <br />DWORD dwFlagsAndAttributes, <br />HANDLE hTemplateFile <br />); <br />可以看到其有7个参数，于是在调用之前，堆栈应该已经被压入了这7个参数，堆栈的样子： <br />|....|     &lt;---ESP <br />|createfile执行后的下一条指令地址| <br />|参数1| <br />|参数2| <br />|参数3| <br />|参数4| <br />|参数5| <br />|参数6| <br />|参数7| <br />|..| <br /><br />这是执行到我们的跳转语句：PUSH 00010000,于是堆栈又变了： <br /><br />|....|     &lt;---ESP <br />|00010000| <br />|createfile执行后的下一条指令地址| <br />|参数1| <br />|参数2| <br />|参数3| <br />|参数4| <br />|参数5| <br />|参数6| <br />|参数7| <br />|..| <br /><br />接着执行CALL 00011000,堆栈变为： <br />|...|   &lt;---ESP <br />|api入口之后的第11个字节的指令的地址|     <br />|00010000| <br />|createfile执行后的下一条指令地址| <br />|参数1| <br />|参数2| <br />|参数3| <br />|参数4| <br />|参数5| <br />|参数6| <br />|参数7| <br />|..| <br /><br />接下来就到了我们的拦截函数中拉，当然，函数肯定也会做一些类似动作，把EBP压栈，为局部变量分配空间等。这时候堆栈的样子又变了： <br /><br />|EDI|   &lt;---ESP <br />|ESI| <br />|EBX| <br />|局部变量| <br />|EBP|     &lt;---EBP <br />|api入口之后的第11个字节的指令的地址|     <br />|00010000| <br />|createfile执行后的下一条指令地址| <br />|参数1| <br />|参数2| <br />|参数3| <br />|参数4| <br />|参数5| <br />|参数6| <br />|参数7| <br />|..| <br /><br />这时候，你想做什么就尽情地做吧，获取参数信息，延缓执行CreateFile函数等等。拿获取打开文件句柄的名字来说吧，文件名是第一个参数，前面说过我们可以用[EBP+8]来获取参数，但是对照上面的堆栈形状，中间又加了另外一些数据，所以我们用[EBP+16]来获取第一个参数的地址。比如： <br />char* PFileName = NULL; <br />__asm{ <br />MOV EAX,[EBP+16] <br />MOV [szFileName], EAX <br />} <br /><br />比如我们用一个messagebox来弹出一个信息，说明该程序即将打开一个某谋路径的文件句柄。但是有一个要注意的是，如果你想拦截远程进程的话，对于那个拦截函数中所使用到的任何函数或者以任何形式的相对地址的调用都要停止。因为每个进程中的地址分配都是独立的，比如上面的CALL MessageBoxA改成直接地址的调用。对于使用messagebox，我们应该定义一个函数指针，然后把这个指针的值赋值为user32.dll中导出该函数的直接地址。然后利用这个指针来进行函数调用。对于messagebox函数的调用可以这样，在源程序中定义一个参数结构体，参数中包含一个导出函数的地址,把这个地址设为MessageBoxA的直接地址，获取地址的方法就不说了。然后把这个参数传给拦截函数，就可以使用拉。这也是利用一个参数的原因。类似代码如下： <br /><br /><br />typedef struct _RemoteParam { <br />  DWORD dwMessageBox; <br />} RemoteParam, * PRemoteParam; <br /><br />typedef int (__stdcall * PFN_MESSAGEBOX)(HWND, LPCTSTR, LPCTSTR, DWORD);//定义一个函数指针 <br /><br />//拦截函数 <br />void HookCreateFile(LPVOID lParam) <br />{ <br />RemoteParam* pRP = (RemoteParam*)lParam;//获取参数地址 <br />char* PFileName = NULL;//定义一个指针 <br />__asm{ <br />MOV EAX,[EBP+16] <br />MOV [szFileName], EAX //把CreateFile第一个参数的值，文件的路径的地址传                       //给szFileName <br />} <br /><br />//定 义一个函数指针 <br />PFN_MESSAGEBOX pfnMessageBox = (PFN_MESSAGEBOX)pRP-&gt;dwMessageBox; <br /><br />pfnMessageBox(NULL, PFileName, PFileName, MB_ICONINformATION |MB_OK); <br />//输出要打开的文件的路径 <br />//..... <br />} <br /><br />对于你要使用的其他函数，都是使用同样的方式，利用这个参数来传递我们要传递的函数的绝对地址，然后定义这个函数指针，就可以使用了。 <br /><br /><br />好了，接下来我们该让被拦截的api正常工作了，这个不难，把他原来的数据恢复一下就可以了。那入口的10个字节。我们在改写他们的时候应该保存一下，然后也把他放在参数中传递给拦截函数，呵呵，参数的作用可多了。接着我们就可以用WriteProcessMemory函数来恢复这个api的入口了，代码如下： <br />PFN_GETCURRENTPROCESS pfnGetCurrentProcess = (PFN_GETCURRENTPROCESS)pRP-&gt;dwGetCurrentProcess; <br />PFN_WRITEPROCESSMEMORY pfnWriteProcessMemory = (PFN_WRITEPROCESSMEMORY)pRP-&gt;dwWriteProcessMemory; <br />   <br />if(!pfnWriteProcessMemory(pfnGetCurrentProcess(), <br />                                          (LPVOID)pfnConnect, <br />                                          (LPCVOID)pRP-&gt;szOldCode, <br />                                          10, <br />                                          NULL)) <br />pfnMessageBox(NULL, pRP-&gt;szModuleName1, pRP-&gt;szModuleName2,   MB_ICONINformATION | MB_OK); <br />其中这些函数指针的定义和上面的类似。 <br />而参数中的szoldcode则是在源程序中在修改api之前保存好，然后传给拦截函数，在源程序中是用ReadProcessMemory函数来获取他的前10个字节的： <br />ReadProcessMemory(GetCurrentProcess(), <br />                                (LPCVOID)RParam.dwCreateFile, <br />                                oldcode, <br />                                10, <br />                                &amp;dwPid) <br />strcat((char*)RParam.szOldCode, (char*)oldcode); <br /><br /><br />接下来如果你还继续保持对该api的拦截，则又该用WriteProcessMemory 来修改入口了，跟前面的恢复入口是一样的，只不过把szOldCode换成了szNewCode了而已。这样你又能对CreateFile继续拦截了。 <br /><br />好了，接下来该进行堆栈的清理了，也许你还要做点其他事情，尽管做去。但是清理堆栈是必须要做的，在函数结束的时候，因为在我们放任api恢复执行之后，他又return 到我们的函数中来了，这个时候的堆栈是什么样子呢？ <br />|EDI|   &lt;---ESP <br />|ESI| <br />|EBX| <br />|局部变量|   <br />|EBP|     &lt;---EBP <br />|api入口之后的第11个字节的指令的地址|     <br />|00010000| <br />|createfile执行后的下一条指令地址| <br />|参数1| <br />|参数2| <br />|参数3| <br />|参数4| <br />|参数5| <br />|参数6| <br />|参数7| <br />|..| <br /><br />我们的目标是把返回值记录下来放到EAX寄存器中去，把返回地址记录下来，同时把堆栈恢复成原来的样子。 <br />首先我们恢复那些寄存器的值，接着释放局部变量，可以用mov esp, ebp.因为我们不清楚具体的局部变量分配了多少空间。所以使用这个方法。 <br /><br /><br />__asm <br />{POP EDI <br />POP ESI <br />POP EBX   //恢复那些寄存器 <br />MOV EDX, [NextIpAddr]//把返回地址放到EDX中，因为待会儿                   //EBX被恢复后，线程中的所有局部变量就不能正常使用了。 <br />         <br />MOV EAX, [Retvalue]//返回值放到EAX中，当然也可以修改这个返回值 <br />MOV ESP, EBP//清理局部变量 <br />POP EBP//恢复EBP的值 <br />ADD ESP, 28H   //清理参数和返回地址，注意一共(7+1+1+1)*4 <br />PUSH EDX //把返回地址压栈，这样栈中就只有这一个返回地址了，返回之后栈           //就空了 <br />RET <br />} <br /><br />这样，一切就完成了，堆栈恢复了应该有的状态，而你想拦截的也拦截到了。 <br /><br /><br />四：后记 <br />  拦截的方式多种多样，不过大体的思路却都相同。要时刻注意你要拦截的函数的堆栈状态以及在拦截函数中的对数据的引用和函数的调用（地址问题）。 <img src ="http://www.cppblog.com/eday/aggbug/15377.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-18 16:51 <a href="http://www.cppblog.com/eday/archive/2006/11/18/15377.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>动态修改Windows内核</title><link>http://www.cppblog.com/eday/archive/2006/11/18/15373.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 18 Nov 2006 08:48:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/18/15373.html</guid><description><![CDATA[
		<div class="item-content">
				<p>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">如前所述，</span>
						<span>Windows95</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">不是一个“纯”</span>
						<span>32</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">位操作系统，其内核模块中的</span>
						<span>USER</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">和</span>
						<span>GDI</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">均是用</span>
						<span>16</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">位代码实现的。</span>
						<span>USER32.DLL</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">和</span>
						<span>GDI32.DLL</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">只是</span>
						<span>16</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">位的</span>
						<span>USER.EXE</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">和</span>
						<span>GDI.EXE</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">的</span>
						<span>32</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">位调用接口。因此，如果屏幕截获程序用</span>
						<span>32</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">位代码实现，则只能截获</span>
						<span>32</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">位应用程序对</span>
						<span>USER32.DLL</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">和</span>
						<span>GDI32.DLL</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">的调用，无法截获</span>
						<span>16</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">位应用程序对</span>
						<span>USER.EXE</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">和</span>
						<span>GDI.EXE</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">的调用，所以如果想截获所有应用程序（包括</span>
						<span>Windows95</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">的桌面程序</span>
						<span>Explorer</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">）中有关屏幕输出的系统调用，则应该用</span>
						<span>16</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">位代码实现屏幕截获功能。这就是</span>
						<span>LTW32</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">为什么不是“纯”</span>
						<span>32</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">位应用程序的原因。</span>
						<span>LTW32</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">主要截获两个系统调用</span>
						<span>TextOut()</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">和</span>
						<span>ExtTextOut()</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">，方法很简单，把这两个函数的头五个字节修改为一个</span>
						<span>JMP FAR </span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">指令，使得对这两个函数的调用均转向屏幕截获程序。这就涉及到一个关键问题：动态修改</span>
						<span>Windows</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">的代码。</span>
				</p>
				<p>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">在传统的</span>
						<span>DOS</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">程序中，动态修改程序代码无任何困难，但在</span>
						<span>Windows</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">中则不然，因为在</span>
						<span>Windows</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">中，代码可被同一程序的多个实例（进程）共享，所以系统不允许应用程序动态的修改代码。在</span>
						<span>16</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">位侧，内存的可读、写属性是与段选择符联系在一起的。段选择符基本上可分为两类：数据段选择符和代码段选择符。前者可读、可写、不可执行；后者可读、可执行、不可写。</span>
						<span>Windows</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">提供了这两类段选择符相转换的系统调用。未公开的</span>
						<span>16</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">位系统调用</span>
						<span>AllocCStoDSAlias()</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">为给定的代码段选择符分配一个具有相同线性基址和尺寸的数据段别名（</span>
						<span>DS Alias</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">）。通过</span>
						<span>DS</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">别名可以对给定的代码段进行修改。</span>
						<span>AllocCStoDSAlias()</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">的使用方法如下：</span>
				</p>
				<p>
						<span>WORD (FAR PASCAL *AllocCStoDSAlias)(WORD);</span>
				</p>
				<p>
						<span>AllocCStoDSAlias = GetProcAddress(</span>
				</p>
				<p>
						<span>GetModuleHandle(“KERNEL”), ”ALLOCCSTODSALIAS”);</span>
				</p>
				<p>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">调用参数为给定的代码选择符，调用成功时返回一个线性基址和尺寸均与原代码选择符相同的</span>
						<span>DS</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">别名。当不再使用此</span>
						<span>DS</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">别名时，要用系统调用</span>
						<span>FreeSelector()</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">把</span>
						<span>DS</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">别名释放掉。</span>
				</p>
				<p>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">使用上述技术，就可实现动态修改</span>
						<span>Windows</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">代码，从而改变</span>
						<span>GDI</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">的系统调用</span>
						<span>TextOut()</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">和</span>
						<span>ExtTextOut()</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">的执行动作，实时地截获屏幕输出，为实现鼠标随动翻译提供可能。</span>
				</p>
				<p>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">把上述的</span>
						<span>32</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">位到</span>
						<span>16</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">位的形式替换、</span>
						<span>32</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">位代码与</span>
						<span>16</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">位代码的数据交换、动态修改</span>
						<span>Windows</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: Arial">内核等技术综合应用在一起，配合单词查找算法和词组分析算法就可以实现鼠标随动翻译功能。</span>
				</p>
				<div class="clear">
				</div>
		</div>
<img src ="http://www.cppblog.com/eday/aggbug/15373.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-18 16:48 <a href="http://www.cppblog.com/eday/archive/2006/11/18/15373.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>在VC8.0中编译Release版本的程序在其他机器上无法正常运行的原因</title><link>http://www.cppblog.com/eday/archive/2006/11/09/14890.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Wed, 08 Nov 2006 16:37:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/09/14890.html</guid><description><![CDATA[在VC8.0中编译Release版本的程序在其他机器上无法正常运行的原因<br /><br />使用 C:\Program Files\Microsoft Visual Studio 8\Common7\Tools\Bin\Depends.Exe 查看一下你的程序用到了哪些DLL，如果缺少某个 DLL ，程序是无法正常运行的。　<br /><br />例如：Win32程序（使用Windows标准库），如果是 <font color="#ff0000">/MD</font> 编译的程序，那么使用Depends.exe会发现其中需要 USER32.DLL、KERNEL32.DLL、GDI32.DLL、MSVCR80.DLL 等文件支持。像前３个DLL文件是Windows系统自带的，我们不用去考虑它（除非你在程序中使用的API，windows版本不支持）。 MSVCR80.DLL 需要注意一下的，<br />  <br /><img src="http://www.cppblog.com/images/cppblog_com/eday/2991/r_cruntimelib.JPG" /><br /><br />我就一个程序文件，当然我是不想再放一个 MSVCR80.DLL 文件在我的安装包中，我用 <font color="#ff0000">/MT</font> 编译程序（静态连接），<br /><br /><img src="http://www.cppblog.com/images/cppblog_com/eday/2991/r_mt_bianyi.JPG" /><br /><br />这时就不需要这个DLL文件了．（如果有多个程序模块，还是把 MSVCR80.DLL 加上吧，这样也可减少各模块的体积。）<br /><br />如果程序中用到了 MFC 、ATL， 可以修改项目属性配置中的“MFC、ATL的使用方式”。<img src ="http://www.cppblog.com/eday/aggbug/14890.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-09 00:37 <a href="http://www.cppblog.com/eday/archive/2006/11/09/14890.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>VC8.0中使用GDI+遇到编译错误，一个解决办法</title><link>http://www.cppblog.com/eday/archive/2006/11/05/14706.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sun, 05 Nov 2006 13:50:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/05/14706.html</guid><description><![CDATA[今天在VC8.0中使用 GDI+,  但有很多编译错误，向 VC8.0中不支持返回默认int 这类错误在VC6.0中可以编译，起初被这类错误搞的直迷糊，后来将 stdafx.h 中的宏定义<br />//#define WIN32_LEAN_AND_MEAN <br />注释掉就可以正常编译了。<br /><br /><div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #008080">  1</span> <span style="COLOR: #008000">//</span><span style="COLOR: #008000"> win32app.cpp : 定义应用程序的入口点。<br /></span><span style="COLOR: #008080">  2</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//<br /></span><span style="COLOR: #008080">  3</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">  4</span> <span style="COLOR: #000000">#include </span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">stdafx.h</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">  5</span> <span style="COLOR: #000000">#include </span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">win32app.h</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">  6</span> <span style="COLOR: #000000">#include </span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">gdiplus.h</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">  7</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">  8</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">using</span><span style="COLOR: #000000"> </span><span style="COLOR: #0000ff">namespace</span><span style="COLOR: #000000"> Gdiplus;<br /></span><span style="COLOR: #008080">  9</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 10</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000"> MAX_LOADSTRING 100</span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 11</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 12</span> <span style="COLOR: #000000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"> 全局变量:</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 13</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">HINSTANCE hInst;                                </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"> 当前实例</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 14</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">TCHAR szTitle[MAX_LOADSTRING];                    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"> 标题栏文本</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 15</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">TCHAR szWindowClass[MAX_LOADSTRING];            </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"> 主窗口类名<br /></span><span style="COLOR: #008080"> 16</span> <span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 17</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"> 此代码模块中包含的函数的前向声明:</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 18</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">ATOM                MyRegisterClass(HINSTANCE hInstance);<br /></span><span style="COLOR: #008080"> 19</span> <span style="COLOR: #000000">BOOL                InitInstance(HINSTANCE, </span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">);<br /></span><span style="COLOR: #008080"> 20</span> <span style="COLOR: #000000">LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);<br /></span><span style="COLOR: #008080"> 21</span> <span style="COLOR: #000000">INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);<br /></span><span style="COLOR: #008080"> 22</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 23</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000"> APIENTRY _tWinMain(HINSTANCE hInstance,<br /></span><span style="COLOR: #008080"> 24</span> <span style="COLOR: #000000">                     HINSTANCE hPrevInstance,<br /></span><span style="COLOR: #008080"> 25</span> <span style="COLOR: #000000">                     LPTSTR    lpCmdLine,<br /></span><span style="COLOR: #008080"> 26</span> <span style="COLOR: #000000">                     </span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">       nCmdShow)<br /></span><span style="COLOR: #008080"> 27</span> <span style="COLOR: #000000">{<br /></span><span style="COLOR: #008080"> 28</span> <span style="COLOR: #000000">    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">UNREFERENCED_PARAMETER(hPrevInstance);<br /></span><span style="COLOR: #008080"> 29</span> <span style="COLOR: #008000">    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">UNREFERENCED_PARAMETER(lpCmdLine);<br /></span><span style="COLOR: #008080"> 30</span> <span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 31</span> <span style="COLOR: #008000">     </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"> TODO: 在此放置代码。</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 32</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">    GdiplusStartupInput gdiplusStartupInput;<br /></span><span style="COLOR: #008080"> 33</span> <span style="COLOR: #000000">    ULONG_PTR gdiplusToken;<br /></span><span style="COLOR: #008080"> 34</span> <span style="COLOR: #000000">    GdiplusStartup(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">gdiplusToken, </span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">gdiplusStartupInput, NULL);<br /></span><span style="COLOR: #008080"> 35</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 36</span> <span style="COLOR: #000000">    MSG msg;<br /></span><span style="COLOR: #008080"> 37</span> <span style="COLOR: #000000">    HACCEL hAccelTable;<br /></span><span style="COLOR: #008080"> 38</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 39</span> <span style="COLOR: #000000">    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"> 初始化全局字符串</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 40</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">    LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);<br /></span><span style="COLOR: #008080"> 41</span> <span style="COLOR: #000000">    LoadString(hInstance, IDC_WIN32APP, szWindowClass, MAX_LOADSTRING);<br /></span><span style="COLOR: #008080"> 42</span> <span style="COLOR: #000000">    MyRegisterClass(hInstance);<br /></span><span style="COLOR: #008080"> 43</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 44</span> <span style="COLOR: #000000">    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"> 执行应用程序初始化:</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 45</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000"> (</span><span style="COLOR: #000000">!</span><span style="COLOR: #000000">InitInstance (hInstance, nCmdShow))<br /></span><span style="COLOR: #008080"> 46</span> <span style="COLOR: #000000">    {<br /></span><span style="COLOR: #008080"> 47</span> <span style="COLOR: #000000">        </span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000"> FALSE;<br /></span><span style="COLOR: #008080"> 48</span> <span style="COLOR: #000000">    }<br /></span><span style="COLOR: #008080"> 49</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 50</span> <span style="COLOR: #000000">    hAccelTable </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WIN32APP));<br /></span><span style="COLOR: #008080"> 51</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 52</span> <span style="COLOR: #000000">    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"> 主消息循环:</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 53</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">while</span><span style="COLOR: #000000"> (GetMessage(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">msg, NULL, </span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">, </span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">))<br /></span><span style="COLOR: #008080"> 54</span> <span style="COLOR: #000000">    {<br /></span><span style="COLOR: #008080"> 55</span> <span style="COLOR: #000000">        </span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000"> (</span><span style="COLOR: #000000">!</span><span style="COLOR: #000000">TranslateAccelerator(msg.hwnd, hAccelTable, </span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">msg))<br /></span><span style="COLOR: #008080"> 56</span> <span style="COLOR: #000000">        {<br /></span><span style="COLOR: #008080"> 57</span> <span style="COLOR: #000000">            TranslateMessage(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">msg);<br /></span><span style="COLOR: #008080"> 58</span> <span style="COLOR: #000000">            DispatchMessage(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">msg);<br /></span><span style="COLOR: #008080"> 59</span> <span style="COLOR: #000000">        }<br /></span><span style="COLOR: #008080"> 60</span> <span style="COLOR: #000000">    }<br /></span><span style="COLOR: #008080"> 61</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 62</span> <span style="COLOR: #000000">    GdiplusShutdown(gdiplusToken);<br /></span><span style="COLOR: #008080"> 63</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 64</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000"> (</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">) msg.wParam;<br /></span><span style="COLOR: #008080"> 65</span> <span style="COLOR: #000000">}<br /></span><span style="COLOR: #008080"> 66</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 67</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 68</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 69</span> <span style="COLOR: #000000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 70</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">  函数: MyRegisterClass()<br /></span><span style="COLOR: #008080"> 71</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 72</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">  目的: 注册窗口类。<br /></span><span style="COLOR: #008080"> 73</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 74</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">  注释:<br /></span><span style="COLOR: #008080"> 75</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 76</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">    仅当希望<br /></span><span style="COLOR: #008080"> 77</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">    此代码与添加到 Windows 95 中的“RegisterClassEx”<br /></span><span style="COLOR: #008080"> 78</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">    函数之前的 Win32 系统兼容时，才需要此函数及其用法。调用此函数十分重要，<br /></span><span style="COLOR: #008080"> 79</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">    这样应用程序就可以获得关联的<br /></span><span style="COLOR: #008080"> 80</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">    “格式正确的”小图标。<br /></span><span style="COLOR: #008080"> 81</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//<br /></span><span style="COLOR: #008080"> 82</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">ATOM MyRegisterClass(HINSTANCE hInstance)<br /></span><span style="COLOR: #008080"> 83</span> <span style="COLOR: #000000">{<br /></span><span style="COLOR: #008080"> 84</span> <span style="COLOR: #000000">    WNDCLASSEX wcex;<br /></span><span style="COLOR: #008080"> 85</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 86</span> <span style="COLOR: #000000">    wcex.cbSize </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> </span><span style="COLOR: #0000ff">sizeof</span><span style="COLOR: #000000">(WNDCLASSEX);<br /></span><span style="COLOR: #008080"> 87</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 88</span> <span style="COLOR: #000000">    wcex.style            </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> CS_HREDRAW </span><span style="COLOR: #000000">|</span><span style="COLOR: #000000"> CS_VREDRAW;<br /></span><span style="COLOR: #008080"> 89</span> <span style="COLOR: #000000">    wcex.lpfnWndProc    </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> WndProc;<br /></span><span style="COLOR: #008080"> 90</span> <span style="COLOR: #000000">    wcex.cbClsExtra        </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;<br /></span><span style="COLOR: #008080"> 91</span> <span style="COLOR: #000000">    wcex.cbWndExtra        </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;<br /></span><span style="COLOR: #008080"> 92</span> <span style="COLOR: #000000">    wcex.hInstance        </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> hInstance;<br /></span><span style="COLOR: #008080"> 93</span> <span style="COLOR: #000000">    wcex.hIcon            </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WIN32APP));<br /></span><span style="COLOR: #008080"> 94</span> <span style="COLOR: #000000">    wcex.hCursor        </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> LoadCursor(NULL, IDC_ARROW);<br /></span><span style="COLOR: #008080"> 95</span> <span style="COLOR: #000000">    wcex.hbrBackground    </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> (HBRUSH)(COLOR_WINDOW</span><span style="COLOR: #000000">+</span><span style="COLOR: #000000">1</span><span style="COLOR: #000000">);<br /></span><span style="COLOR: #008080"> 96</span> <span style="COLOR: #000000">    wcex.lpszMenuName    </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> MAKEINTRESOURCE(IDC_WIN32APP);<br /></span><span style="COLOR: #008080"> 97</span> <span style="COLOR: #000000">    wcex.lpszClassName    </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> szWindowClass;<br /></span><span style="COLOR: #008080"> 98</span> <span style="COLOR: #000000">    wcex.hIconSm        </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));<br /></span><span style="COLOR: #008080"> 99</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">100</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000"> RegisterClassEx(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">wcex);<br /></span><span style="COLOR: #008080">101</span> <span style="COLOR: #000000">}<br /></span><span style="COLOR: #008080">102</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">103</span> <span style="COLOR: #000000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080">104</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">   函数: InitInstance(HINSTANCE, int)<br /></span><span style="COLOR: #008080">105</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080">106</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">   目的: 保存实例句柄并创建主窗口<br /></span><span style="COLOR: #008080">107</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080">108</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">   注释:<br /></span><span style="COLOR: #008080">109</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080">110</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">        在此函数中，我们在全局变量中保存实例句柄并<br /></span><span style="COLOR: #008080">111</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">        创建和显示主程序窗口。<br /></span><span style="COLOR: #008080">112</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//<br /></span><span style="COLOR: #008080">113</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">BOOL InitInstance(HINSTANCE hInstance, </span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000"> nCmdShow)<br /></span><span style="COLOR: #008080">114</span> <span style="COLOR: #000000">{<br /></span><span style="COLOR: #008080">115</span> <span style="COLOR: #000000">   HWND hWnd;<br /></span><span style="COLOR: #008080">116</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">117</span> <span style="COLOR: #000000">   hInst </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> hInstance; </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"> 将实例句柄存储在全局变量中</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080">118</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">119</span> <span style="COLOR: #000000">   hWnd </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,<br /></span><span style="COLOR: #008080">120</span> <span style="COLOR: #000000">      CW_USEDEFAULT, </span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">, CW_USEDEFAULT, </span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">, NULL, NULL, hInstance, NULL);<br /></span><span style="COLOR: #008080">121</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">122</span> <span style="COLOR: #000000">   </span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000"> (</span><span style="COLOR: #000000">!</span><span style="COLOR: #000000">hWnd)<br /></span><span style="COLOR: #008080">123</span> <span style="COLOR: #000000">   {<br /></span><span style="COLOR: #008080">124</span> <span style="COLOR: #000000">      </span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000"> FALSE;<br /></span><span style="COLOR: #008080">125</span> <span style="COLOR: #000000">   }<br /></span><span style="COLOR: #008080">126</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">127</span> <span style="COLOR: #000000">   ShowWindow(hWnd, nCmdShow);<br /></span><span style="COLOR: #008080">128</span> <span style="COLOR: #000000">   UpdateWindow(hWnd);<br /></span><span style="COLOR: #008080">129</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">130</span> <span style="COLOR: #000000">   </span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000"> TRUE;<br /></span><span style="COLOR: #008080">131</span> <span style="COLOR: #000000">}<br /></span><span style="COLOR: #008080">132</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">133</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000"> OnPaint(HDC hdc)<br /></span><span style="COLOR: #008080">134</span> <span style="COLOR: #000000">{<br /></span><span style="COLOR: #008080">135</span> <span style="COLOR: #000000">   </span><span style="COLOR: #008000">/*</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080">136</span> <span style="COLOR: #008000">   Graphics graphics(hdc);<br /></span><span style="COLOR: #008080">137</span> <span style="COLOR: #008000">   Pen      pen(Color(255, 0, 0, 255));<br /></span><span style="COLOR: #008080">138</span> <span style="COLOR: #008000">   graphics.DrawLine(&amp;pen, 0, 0, 200, 100);<br /></span><span style="COLOR: #008080">139</span> <span style="COLOR: #008000">   </span><span style="COLOR: #008000">*/</span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">140</span> <span style="COLOR: #000000">   Graphics    graphics(hdc);<br /></span><span style="COLOR: #008080">141</span> <span style="COLOR: #000000">   SolidBrush  brush(Color(</span><span style="COLOR: #000000">255</span><span style="COLOR: #000000">, </span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">, </span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">, </span><span style="COLOR: #000000">255</span><span style="COLOR: #000000">));<br /></span><span style="COLOR: #008080">142</span> <span style="COLOR: #000000">   FontFamily  fontFamily(L</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">Times New Roman</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">);<br /></span><span style="COLOR: #008080">143</span> <span style="COLOR: #000000">   Font        font(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">fontFamily, </span><span style="COLOR: #000000">24</span><span style="COLOR: #000000">, FontStyleRegular, UnitPixel);<br /></span><span style="COLOR: #008080">144</span> <span style="COLOR: #000000">   PointF      pointF(</span><span style="COLOR: #000000">10.0f</span><span style="COLOR: #000000">, </span><span style="COLOR: #000000">20.0f</span><span style="COLOR: #000000">);<br /></span><span style="COLOR: #008080">145</span> <span style="COLOR: #000000">   graphics.DrawString(L</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">Hello World!</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">, </span><span style="COLOR: #000000">-</span><span style="COLOR: #000000">1</span><span style="COLOR: #000000">, </span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">font, pointF, </span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">brush);<br /></span><span style="COLOR: #008080">146</span> <span style="COLOR: #000000">}<br /></span><span style="COLOR: #008080">147</span> <span style="COLOR: #000000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080">148</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">  函数: WndProc(HWND, UINT, WPARAM, LPARAM)<br /></span><span style="COLOR: #008080">149</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080">150</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">  目的: 处理主窗口的消息。<br /></span><span style="COLOR: #008080">151</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080">152</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">  WM_COMMAND    - 处理应用程序菜单<br /></span><span style="COLOR: #008080">153</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">  WM_PAINT    - 绘制主窗口<br /></span><span style="COLOR: #008080">154</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">  WM_DESTROY    - 发送退出消息并返回<br /></span><span style="COLOR: #008080">155</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080">156</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//<br /></span><span style="COLOR: #008080">157</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)<br /></span><span style="COLOR: #008080">158</span> <span style="COLOR: #000000">{<br /></span><span style="COLOR: #008080">159</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000"> wmId, wmEvent;<br /></span><span style="COLOR: #008080">160</span> <span style="COLOR: #000000">    PAINTSTRUCT ps;<br /></span><span style="COLOR: #008080">161</span> <span style="COLOR: #000000">    HDC hdc;<br /></span><span style="COLOR: #008080">162</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">163</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">switch</span><span style="COLOR: #000000"> (message)<br /></span><span style="COLOR: #008080">164</span> <span style="COLOR: #000000">    {<br /></span><span style="COLOR: #008080">165</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">case</span><span style="COLOR: #000000"> WM_COMMAND:<br /></span><span style="COLOR: #008080">166</span> <span style="COLOR: #000000">        wmId    </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> LOWORD(wParam);<br /></span><span style="COLOR: #008080">167</span> <span style="COLOR: #000000">        wmEvent </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> HIWORD(wParam);<br /></span><span style="COLOR: #008080">168</span> <span style="COLOR: #000000">        </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"> 分析菜单选择:</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080">169</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">        </span><span style="COLOR: #0000ff">switch</span><span style="COLOR: #000000"> (wmId)<br /></span><span style="COLOR: #008080">170</span> <span style="COLOR: #000000">        {<br /></span><span style="COLOR: #008080">171</span> <span style="COLOR: #000000">        </span><span style="COLOR: #0000ff">case</span><span style="COLOR: #000000"> IDM_ABOUT:<br /></span><span style="COLOR: #008080">172</span> <span style="COLOR: #000000">            DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);<br /></span><span style="COLOR: #008080">173</span> <span style="COLOR: #000000">            </span><span style="COLOR: #0000ff">break</span><span style="COLOR: #000000">;<br /></span><span style="COLOR: #008080">174</span> <span style="COLOR: #000000">        </span><span style="COLOR: #0000ff">case</span><span style="COLOR: #000000"> IDM_EXIT:<br /></span><span style="COLOR: #008080">175</span> <span style="COLOR: #000000">            DestroyWindow(hWnd);<br /></span><span style="COLOR: #008080">176</span> <span style="COLOR: #000000">            </span><span style="COLOR: #0000ff">break</span><span style="COLOR: #000000">;<br /></span><span style="COLOR: #008080">177</span> <span style="COLOR: #000000">        </span><span style="COLOR: #0000ff">default</span><span style="COLOR: #000000">:<br /></span><span style="COLOR: #008080">178</span> <span style="COLOR: #000000">            </span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000"> DefWindowProc(hWnd, message, wParam, lParam);<br /></span><span style="COLOR: #008080">179</span> <span style="COLOR: #000000">        }<br /></span><span style="COLOR: #008080">180</span> <span style="COLOR: #000000">        </span><span style="COLOR: #0000ff">break</span><span style="COLOR: #000000">;<br /></span><span style="COLOR: #008080">181</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">case</span><span style="COLOR: #000000"> WM_PAINT:<br /></span><span style="COLOR: #008080">182</span> <span style="COLOR: #000000">        hdc </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> BeginPaint(hWnd, </span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">ps);<br /></span><span style="COLOR: #008080">183</span> <span style="COLOR: #000000">        OnPaint(hdc);<br /></span><span style="COLOR: #008080">184</span> <span style="COLOR: #000000">        EndPaint(hWnd, </span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">ps);<br /></span><span style="COLOR: #008080">185</span> <span style="COLOR: #000000">        </span><span style="COLOR: #0000ff">break</span><span style="COLOR: #000000">;<br /></span><span style="COLOR: #008080">186</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">case</span><span style="COLOR: #000000"> WM_DESTROY:<br /></span><span style="COLOR: #008080">187</span> <span style="COLOR: #000000">        PostQuitMessage(</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">);<br /></span><span style="COLOR: #008080">188</span> <span style="COLOR: #000000">        </span><span style="COLOR: #0000ff">break</span><span style="COLOR: #000000">;<br /></span><span style="COLOR: #008080">189</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">default</span><span style="COLOR: #000000">:<br /></span><span style="COLOR: #008080">190</span> <span style="COLOR: #000000">        </span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000"> DefWindowProc(hWnd, message, wParam, lParam);<br /></span><span style="COLOR: #008080">191</span> <span style="COLOR: #000000">    }<br /></span><span style="COLOR: #008080">192</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;<br /></span><span style="COLOR: #008080">193</span> <span style="COLOR: #000000">}<br /></span><span style="COLOR: #008080">194</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">195</span> <span style="COLOR: #000000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"> “关于”框的消息处理程序。</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080">196</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)<br /></span><span style="COLOR: #008080">197</span> <span style="COLOR: #000000">{<br /></span><span style="COLOR: #008080">198</span> <span style="COLOR: #000000">    UNREFERENCED_PARAMETER(lParam);<br /></span><span style="COLOR: #008080">199</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">switch</span><span style="COLOR: #000000"> (message)<br /></span><span style="COLOR: #008080">200</span> <span style="COLOR: #000000">    {<br /></span><span style="COLOR: #008080">201</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">case</span><span style="COLOR: #000000"> WM_INITDIALOG:<br /></span><span style="COLOR: #008080">202</span> <span style="COLOR: #000000">        </span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000"> (INT_PTR)TRUE;<br /></span><span style="COLOR: #008080">203</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">204</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">case</span><span style="COLOR: #000000"> WM_COMMAND:<br /></span><span style="COLOR: #008080">205</span> <span style="COLOR: #000000">        </span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000"> (LOWORD(wParam) </span><span style="COLOR: #000000">==</span><span style="COLOR: #000000"> IDOK </span><span style="COLOR: #000000">||</span><span style="COLOR: #000000"> LOWORD(wParam) </span><span style="COLOR: #000000">==</span><span style="COLOR: #000000"> IDCANCEL)<br /></span><span style="COLOR: #008080">206</span> <span style="COLOR: #000000">        {<br /></span><span style="COLOR: #008080">207</span> <span style="COLOR: #000000">            EndDialog(hDlg, LOWORD(wParam));<br /></span><span style="COLOR: #008080">208</span> <span style="COLOR: #000000">            </span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000"> (INT_PTR)TRUE;<br /></span><span style="COLOR: #008080">209</span> <span style="COLOR: #000000">        }<br /></span><span style="COLOR: #008080">210</span> <span style="COLOR: #000000">        </span><span style="COLOR: #0000ff">break</span><span style="COLOR: #000000">;<br /></span><span style="COLOR: #008080">211</span> <span style="COLOR: #000000">    }<br /></span><span style="COLOR: #008080">212</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000"> (INT_PTR)FALSE;<br /></span><span style="COLOR: #008080">213</span> <span style="COLOR: #000000">}<br /></span><span style="COLOR: #008080">214</span> <span style="COLOR: #000000"></span></div><br /><img src ="http://www.cppblog.com/eday/aggbug/14706.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-05 21:50 <a href="http://www.cppblog.com/eday/archive/2006/11/05/14706.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>