NetAnalyzer笔记 之 九 简单的HTTP数据还原

[创建时间:2016-05-12 00:19:00]

NetAnalyzer下载地址

在NetAnalyzer2016中加入了一个HTTP分析功能,很过用户对此都很感兴趣,那么今天写一下具体的实现方式,对我自己也算是一个总结吧,好了,废话就不多少了,直接开始吧。

本文是专注于HTTP数据的分析,所以前期数据包采集相关的内容并不会本文不会涉及,相关内容可以见 NetAnalyzer 笔记 四

在这边默认你已经获取到了http数据报文。

一,提取TCP会话数据

通过TCP/IP协议知道,我们可以通过网络层的IP地址可以唯一的确定一台主机,通过传输层的端口确定一个主机应用,而主机应用与另外一个主机应用的一次数据通信(报文交换)我们称之为一次会话,而建立的传输层协议为TCP上面的一次交互数据我们就称之为TCP会话数据。

一次常规的TCP会话

在这里我们可以看到本地主机应用标志 IP地址: 192.168.1.102 端口 55298 和远端主机应用标志 IP地址:124.238.254.191 端口 80 (80端口所对应的应用协议就是HTTP),通过这组标志,而下面每行记录都代表一TCP数据包,

每个TCP包包含了TCP状态标志,载荷数据(TCP数据包封装的数据)量,序列号等信息,大家可以留意一下每两条记录之间的序列号和载荷数据量之间的关系。

    我们这里要提取的数据正是这组TCP报文中的载荷数据

这段代码用于定义并生成一个TCP标志

  1     /// <summary>
  2     /// 根据IP与端口构建ID
  3     /// </summary>
  4     public class ConnIDbyIPPort : IConnectionID
  5     {
  6         /// <summary>
  7         /// Client IP
  8         /// </summary>
  9         IPAddress _srcIPaddress;//Client IP
 10         /// <summary>
 11         /// Server IP
 12         /// </summary>
 13         IPAddress _dstIPaddress;//Server IP
 14         /// <summary>
 15         /// Client port
 16         /// </summary>
 17         int _srcPort;//Client port
 18         /// <summary>
 19         /// Server port
 20         /// </summary>
 21         int _dstPort;//Server port
 22         /// <summary>
 23         /// 源IP地址属性
 24         /// </summary>
 25         public IPAddress SrcIPAddress
 26         {
 27             get { return _srcIPaddress; }
 28         }
 29         /// <summary>
 30         /// 目标IP地址属性
 31         /// </summary>
 32         public IPAddress DstIPAddress
 33         {
 34             get { return _dstIPaddress; }
 35         }
 36         /// <summary>
 37         /// 源端口属性
 38         /// </summary>
 39         public int SrcPort
 40         {
 41             get { return _srcPort; }
 42         }
 43         /// <summary>
 44         /// 目的端口属性
 45         /// </summary>
 46         public int DstPort
 47         {
 48             get { return _dstPort; }
 49         }
 50
 51         /// <summary>
 52         /// 判断当前的数据包的方向
 53         /// 注意:
 54         ///      此处所说的方向具有相对性
 55         /// </summary>
 56         /// <param name="srcPort"></param>
 57         /// <returns></returns>
 58         public bool isSameDirection(int srcPort)
 59         {
 60             if (srcPort == _srcPort)
 61                 return true;
 62             else if (srcPort == _dstPort)
 63                 return false;
 64             else
 65                 throw new ArgumentException("无效的参数,不是该链接之中的数据!");
 66         }
 67         /// <summary>
 68         /// 构造方法
 69         /// </summary>
 70         /// <param name="ClientIPaddress">源IP地址</param>
 71         /// <param name="ClientPort">源端口</param>
 72         /// <param name="ServerIPaddress">目标IP地址</param>
 73         /// <param name="ServerPort">目标端口</param>
 74         public ConnIDbyIPPort(IPAddress ClientIPaddress, int ClientPort, IPAddress ServerIPaddress, int ServerPort)
 75         {
 76             _srcIPaddress = ClientIPaddress;
 77             _srcPort = ClientPort;
 78             _dstIPaddress = ServerIPaddress;
 79             _dstPort = ServerPort;
 80         }
 81         /// <summary>
 82         /// 构造函数
 83         /// </summary>
 84         /// <param name="rawpacket"></param>
 85         public ConnIDbyIPPort(RawCapture rawpacket)
 86         {
 87             SetInfo(rawpacket);
 88         }
 89         /// <summary>
 90         /// 创建ID
 91         /// </summary>
 92         /// <param name="rawpacket"></param>
 93         private void SetInfo(RawCapture rawpacket)
 94         {
 95             try
 96             {
 97                 Packet packet = Packet.ParsePacket(rawpacket.LinkLayerType, rawpacket.Data);
 98
 99                 TcpPacket tcp = TcpPacket.GetEncapsulated(packet);
100                 if (tcp != null)
101                 {
102                     IpPacket ip = (IpPacket)tcp.ParentPacket;
103                     _srcIPaddress = ip.SourceAddress;
104                     _srcPort = tcp.SourcePort;
105                     _dstIPaddress = ip.DestinationAddress;
106                     _dstPort = tcp.DestinationPort;
107                     return;
108                 }
109                 UdpPacket udp = UdpPacket.GetEncapsulated(packet);
110                 if (udp != null)
111                 {
112                     IpPacket ip = (IpPacket)udp.ParentPacket;
113                     _srcIPaddress = ip.SourceAddress;
114                     _srcPort = udp.SourcePort;
115                     _dstIPaddress = ip.DestinationAddress;
116                     _dstPort = udp.DestinationPort;
117                     return;
118                 }
119             }
120             catch (Exception ex)
121             {
122                 throw ex;
123             }
124         }
125         /// <summary>
126         /// 创建ConnectionID;
127         /// </summary>
128         /// <param name="rawpacket">原始数据报文</param>
129         /// <returns></returns>
130         public IConnectionID CreatID(RawCapture rawpacket)
131         {
132             try
133             {
134                 ConnIDbyIPPort id = new ConnIDbyIPPort(rawpacket);
135                 return id;
136
137             }
138             catch (Exception ex)
139             {
140                 return null;
141             }
142         }
143         /// <summary>
144         /// 重写Equals()方法
145         /// </summary>
146         /// <param name="obj">所要比较的ConnectionID</param>
147         /// <returns>比较结果</returns>
148         public override bool Equals(object obj)//重写的Equals,用于比较两个连接是否相同
149         {
150             if (obj == null)
151                 return false;
152             ConnIDbyIPPort tempID = (ConnIDbyIPPort)obj;
153             if (this._srcIPaddress.Equals(tempID._srcIPaddress) && this._srcPort == tempID._srcPort && this._dstIPaddress.Equals(tempID._dstIPaddress) && this._dstPort.Equals(tempID._dstPort))//正向比较
154             {
155                     return true;
156             }
157             else if (this._srcIPaddress.Equals(tempID._dstIPaddress) && this._srcPort == tempID._dstPort && this._dstIPaddress.Equals(tempID._srcIPaddress) &&this._dstPort==tempID._srcPort)//逆向比较
158             {
159                 return true;
160             }
161             else
162             {
163                 return false;
164             }
165         }
166         /// <summary>
167         /// 使标识用Equals进行比较
168         /// </summary>
169         /// <returns></returns>
170         public override int GetHashCode()//系统默认对象通过HashCode比较,故在此屏蔽此方法
171         {
172             return 1;
173         }
174         /// <summary>
175         /// 重写ToString()方法
176         /// </summary>
177         /// <returns>ConnectionID</returns>
178         public override string ToString()//获取ID字符串
179         {
180             return _srcIPaddress.ToString() + ":" + _srcPort.ToString() + "--" +
181                    _dstIPaddress.ToString() + ":" + _dstPort.ToString();
182         }
183         #region 操作符重载
184         //public static bool  operator==(ConnectionID id1,ConnectionID id2)
185         //{
186         //    return id1.Equals(id2);
187         //}
188         //public static bool operator !=(ConnectionID id1, ConnectionID id2)
189         //{
190         //    return !id1.Equals(id2);
191         //}
192         #endregion
193
194     }

对于这段代码的使用方法如下:

1 // 选择一个RawCapture
2 RawCapture rawPacket = PacketList[i];
3 // 通过RawCapture 获取到一个标志
4 var tmpId = new ConnIDbyIPPort(rawPacket);

通过标志位挑选会话数据

 1        /// <summary>
 2         /// 通过ID和连接类型获取原始数据列表
 3         /// </summary>
 4         /// <param name="id"></param>
 5         /// <param name="connType">之定义了三种,以后会扩展</param>
 6         /// <returns></returns>
 7         private RawCapture[] getConnectionList(IConnectionID id, ConnectionType connType)
 8         {
 9             if (id == null)
10                 return null;
11             try
12             {
13                 //var values = from v in PacketList
14                 //             where id.Equals(id.CreatID(v))
15                 //             select v;
16                 //if (values == null)
17                 //    return null;
18
19                 List<RawCapture> values = new List<RawCapture>();
20                 foreach (var i in PacketList)
21                 {
22                     if (id.Equals(id.CreatID(i)))
23                     {
24                         values.Add(i);
25                     }
26                 }
27
28                 return values.ToArray();
29             }
30             catch (Exception ex)
31             {
32                 MessageBox.Show("正在获取网络获取相关数据,请稍后再试!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
33                 return null;
34             

通过这段代码,我们最终获取到了这组TCP数据,接下来就是载荷数据的提取

二,TCP载荷数据的提取与组合

我们现在获取到的数据包都是TCP,接下来我们要面对两个重要的问题:

1、对于HTTP协议中传输的文本,图片,文件等数据有可能需要多个TCP数据包才能发送或接受完整;

2、存在一些载荷数据为0的TCP数据包,这些数据包对于TCP的会话特别重要(在TCP中完成确认或重传机制,保证数据可靠性),但是对于我们分析数据并没有太多的意义。

根据以上两点,我们需要对得到的这组会话数据进行重新的组合,这里的基本思路是,建立一个单向链表结构,以第一个载荷数据不为空的数据包作进行拆包提取特征量和载荷数据,然后移到下一个载荷数据不为空的数据包,提取数据包特征量,与当前节点特征量比较,如果方向一致(端口号相同)则将载荷数据缓存到当前节点,如果不一致(端口号不相同)则生成新的节点,生成特征,提取载荷数据缓存,并把上个节点指向新节点,

新结构示意图

对于每个节点的定义如下:

 1     /// <summary>
 2     /// TCP单向载荷数据节点
 3     /// </summary>
 4     class DataNode
 5     {
 6         /// <summary>
 7         /// 构造
 8         /// </summary>
 9         /// <param name="port"> 端口</param>
10         public DataNode(ushort port)
11         {
12             this.Port = port;
13             this.Buffer = new List<byte>();
14         }
15
16         public DataNode NextNode { get; set; }
17
18
19         public ushort Port { get; private set; }
20         public List<byte> Buffer { get; private set; }
21
22
23         public void InsertData(byte[] data)
24         {
25             this.Buffer.AddRange(data);
26         }
27     }

构建单向列表

        /// <summary>
        /// 头部节点
        /// </summary>
        private DataNode HeadNode;

        //当前比对的节点
        private DataNode flagNode;

        public void InsertData(byte[] data, ushort port)
        {
            // 载荷数据判空
            if (data == null || data.Length == 0)
                return;
            // 生成头部节点
            if (HeadNode == null)
            {
                flagNode = new DataNode(port);
                HeadNode = flagNode;
            }
            // 插入数据 和 生成后续节点
            if (port == flagNode.Port)
            {
                flagNode.InsertData(data);
            }
            else
            {
                var tmpNode = new DataNode(port);
                flagNode.NextNode = tmpNode;
                tmpNode.InsertData(data);
                flagNode = tmpNode;
            }
        }

TCP会话的转换

 1             hp = new HttpHelper.HttpHelper();
 2
 3             foreach (var rawPacket in PacketList)
 4             {
 5                 Packet packet = Packet.ParsePacket(rawPacket.LinkLayerType, rawPacket.Data);
 6                 TcpPacket tcp = TcpPacket.GetEncapsulated(packet);
 7                 if (tcp != null)//TCP
 8                 {
 9                     hp.InsertData(tcp.PayloadData, tcp.SourcePort);
10                 }
11             }

自此,我们已经获取到了,期望的数据结构模型,那么下一节就要着手开始提取HTTP信息了。

三,http特征量提取

我们知道常规的http协议包含消息头和数据两部分:消息头是由ASCII编码的多条命令行数据组成,每行之间使用CRLF(回车和换行)进行分割,消息头结束后,需要多增加一个CRLF,

一个典型的http请求与回复会话(根据上一节内容,这段数据,红色部分为头部节点,蓝色部分在第二个节点中)

有图可知,客户端发起一个 get 的请求(红色部分),该请求并没有携带数据;服务端回复  http/1.1 200 OK  表示已经接受请求,并返回数据(蓝色部分),服务请返回的数据除了http消息头 还有自己所带的数据。

我们可以很轻松的看到http协议具体的内容,但是对于数据部分是一对乱码,那么接下来任务就是要根据http消息头获取这些乱码的信息

因为本篇以实践为主,并不想讨论具体的http命令,所以此处只对部分命令做一些详细分析,其他命令,网上内容很多请自行搜索。

通过http协议对回复数据(第二个节点)查找到以下两个字段:

1. Content-Type: application/zip 通过查询MIME 我们知道这是一个*.zip文件,那也就是消息头后面的乱码事实上是一个zip压缩文件,

2. Content-Length: 7261 由此我们判断出这个zip文件的大小为 7261个字节

对于一些其他的命令对我们后续还原数据不大,直接跳过。

至此我们就找到了http协议特征量,但是因为http传输数据格式的多样性,传输过程的复杂性,有些服务器的http协议特征更为复杂

HTTP/1.1 200 OK
Date: Wed, 11 May 2016 14:47:47 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
Cache-Control: public, max-age=137
Expires: Wed, 11 May 2016 14:50:05 GMT
Last-Modified: Wed, 11 May 2016 14:45:05 GMT
Vary: *
X-UA-Compatible: IE=10
Content-Encoding: gzip

这段http回复中可以看到数据类型变为 Content-Type: text/html外,没有了content-Length 字段,但是增加几条新的字段,分别是:

1.  charset=utf-8 这部分对于数据类型为字符串的还原非常重要,尤其是对于非英语文本的还原,

2. Transfer-Encoding: chunked  这个字段,用于标出动态加载的数据 详细信息请参见:http://www.cnblogs.com/zhaozhan/archive/2010/08/24/1807639.html

3. Content-Encoding: gzip  为了缩小数据传输量,使用该字段进行数据压缩,所以我们在还原数据的时候遇到该字段就需要解压缩

好了,扯了一大堆,那我们开始准备提取这些特征字段吧

先定义一个http数据结构

 1    public class HttpEntity
 2     {
 3         /// <summary>
 4         /// 消息头
 5         /// </summary>
 6         public string HeadStr { get; set; }
 7         /// <summary>
 8         /// 数据 文本,图片或二进制数据
 9         /// </summary>
10         public object Data { get; set; }
11         /// <summary>
12         /// 数据类型
13         /// </summary>
14         public Type DataType { get; set; }
15         /// <summary>
16         /// 扩展名
17         /// </summary>
18         public string ExtName { get; set; }
19
20     }

接下来对上一节提到的数据链表以此进行消息头提取

 1         /// <summary>
 2         /// 获取http实体列表
 3         /// </summary>
 4         /// <param name="encode">预指定的文本编码方式</param>
 5         /// <returns></returns>
 6         public List<HttpEntity> GetHttpEntityList(Encoding encode)
 7         {
 8             List<HttpEntity> DataList = new List<HttpEntity>();
 9
10             for (DataNode tmp = HeadNode; tmp != null; tmp = tmp.NextNode)
11             {
12                 for (int i = 0; i <= tmp.Buffer.Count - 4; i++)
13                 {
14                     if (tmp.Buffer[i] == 0x0D && tmp.Buffer[i + 1] == 0x0A && tmp.Buffer[i + 2] == 0x0D && tmp.Buffer[i + 3] == 0x0A)
15                     {
16                         string headStr = Encoding.ASCII.GetString(tmp.Buffer.Take(i + 1).ToArray());
17                         DataList.Add(getInnerStr(tmp.Buffer.Skip(i + 4).ToArray(), headStr, encode));
18                         break;
19                     }
20                 }
21             }
22
23             return DataList;
24         }

这里使用for循环,从链表表头开始以此查找两组CRLF(0x0D 0x0A 0x0D 0x0A)直到直到之后,截取前面的数据并使用ASCII进行解码为headStr ,而后面的数据就可以根据前面获取到的消息头进行进一步还原了,在 getInnerStr() 方法中完成

接下来我们继续看getInnerStr()方法的前半部分

 1         private HttpEntity getInnerStr(byte[] data, string headFlag, Encoding defaultEncode)
 2         {
 3
 4             //最终数据呈现
 5             HttpEntity result = new HttpEntity();
 6             result.HeadStr = headFlag;
 7
 8
 9             if (data == null || data.Length == 0)
10                 return result;
11
12             StringBuilder sbr = new StringBuilder();
13
14             //是否使用chunked
15             bool isChunked = headFlag.ToLower().Contains("transfer-encoding: chunked");
16             bool isGzip = headFlag.ToLower().Contains("content-encoding: gzip");
17             string contentType = "";
18             string charSet = "";
19             int ContentLength = 0;
20             Encoding enCode = defaultEncode;
21             var mtype = Regex.Match(headFlag, @"Content-Type:\s*(\w+\/[\w\.\-\+]+)", RegexOptions.IgnoreCase);
22             if (mtype.Success && mtype.Groups.Count >= 2)
23             {
24                 contentType = mtype.Groups[1].Value.Trim().ToLower();
25             }
26             var Mchar = Regex.Match(headFlag, @"charset=\s*([-\w]+)", RegexOptions.IgnoreCase);
27             if (Mchar.Success && Mchar.Groups.Count >= 2)
28             {
29                 charSet = Mchar.Groups[1].Value.Trim();
30                 if (charSet.ToLower() == "utf8")
31                 {
32                     charSet = "utf-8";
33                 }
34                 try
35                 {
36                     enCode = Encoding.GetEncoding(charSet);
37                 }
38                 catch (Exception)
39                 {
40                     enCode = defaultEncode;
41                 }
42             }
43
44             var MLength = Regex.Match(headFlag, @"Content-Length:\s*(\d+)", RegexOptions.IgnoreCase);
45             if (MLength.Success && MLength.Groups.Count >= 2)
46             {
47                 ContentLength = int.Parse(MLength.Groups[1].Value.Trim());
48             }
49              …………

这里使用正则表达式和字符串查找功能,在消息头中分别查找和去取对应的字段(对于字符编码,有些服务器使用的utf8 在解码是需要改为utf-8 否则会引起异常)

代码相对比较简短,这里就不做过多介绍了。

四,http数据解析(包括chunked 和 gzip)

通过上一节,我们已经拿到了http消息头的特征信息和数据,这部分就开始还原数据,具体有三个步骤:

1. 数据提取,数据提取基本就是找开始位置和数据长度,根据页面加载方式的不同,数据头提取分为 通过Content-Length 提取和 通过 Chunked 提取两种方法,

2. 数据解压缩,有时候我们获得的数据可能经过gzip压缩的,所以这个时候要对数据进行解压缩处理。

3. 数据还原,对于不同的数据有着不同的还原方式,转为字符串、保存为图片,生成文件等等

那接下来就开始看代码吧,首先我们补上上一节方法的后半部分

 1            /// 数据整合
 2             byte[] rawData = null;
 3             if (isChunked)// 通过Chunked获取数据
 4             {
 5                 GetchunkedData(data);
 6                 rawData = ChunkBuffer.ToArray();
 7             }
 8             else if (ContentLength > 0 && ContentLength <= data.Length)//通过ContentLength 获取数
 9             {
10                 rawData = data.Take(ContentLength).ToArray();
11             }
12             else
13             {
14                 rawData = data;
15             }
16             if (rawData == null && rawData.Length == 0)
17                 return result;
18
19             /// 数据解压缩
20             if (isGzip)
21             {
22                 rawData = Tools.GzipDecompress(rawData);
23             }
24             if (rawData == null && rawData.Length == 0)
25                 return result;
26
27
28
29             //获取扩展名
30             string extName = "";
31             if (EimeData.EimeDic.Keys.Contains(contentType))
32             {
33                 extName = EimeData.EimeDic[contentType];
34             }
35             result.ExtName = extName;
36             //获取数据或类型
37             switch (Tools.GetMimeType(contentType))
38             {
39                 case MimeType.Text:
40                     result.Data = Tools.ShowPopFormStr(enCode.GetString(rawData));
41                     result.DataType = typeof(string);
42                     break;
43                 case MimeType.Image:
44                     result.Data = rawData;
45                     result.DataType = typeof(System.Drawing.Image);
46                     break;
47                 default:
48                     result.Data = rawData;
49                     result.DataType = typeof(byte[]);
50                     break;
51             }
52             return result;

首先是整合数据,我们通过chunked、content-length等特征字段对获取的数据进行重新整合,这里涉及到Chunked数据整合,那让我们一起来看看代码吧

(具体chunked数据形式自行搜索)

 1          /// <summary>
 2         /// Chunked数据缓存列表
 3         /// </summary>
 4         List<byte> ChunkBuffer = new List<byte>();
 5         private void GetchunkedData(byte[] data)
 6         {
 7             if (data.Length == 0)
 8                 return;
 9             for (int i = 0; i < data.Length; i++)
10             {
11                 //查找CRCL标志
12                 if (data[i] == 0x0D && data[i + 1] == 0x0A)
13                 {
14                     int count = data.Length - 2;
15                     try
16                     {
17                         //获取下一段数据的长度    bytes -> ASCII字符 ->加上0x前缀 (如:0x34) -> 转为数值(下一块的数量长度)
18                         count = Convert.ToInt32("0x" + Encoding.ASCII.GetString(data.Take(i).ToArray()), 16);
19                     }
20                     catch (Exception ex)
21                     {
22                         //产生异常的直接返回
23                         ChunkBuffer.AddRange(data.Skip(i - 2).ToArray());
24                         break;
25                     }
26                     //如果为0表示完成Chunked数据转化跳出循环返回
27                     if (count == 0)
28                         break;
29
30                     if (i + 2 + count <= data.Length)
31                     {
32                         //加入已经计算好的数据
33                         ChunkBuffer.AddRange(data.Skip(i + 2).Take(count));
34                         //递归 进行下一轮匹配
35                         GetchunkedData(data.Skip(i + 4 + count).ToArray());
36                     }
37                     else
38                     {
39                         //对于存在数据不完整的 直接返回
40                         ChunkBuffer.AddRange(data.Skip(i - 2).ToArray());
41                     }
42                     break;
43                 }
44             }
45         }

在这里预先定义一个缓存列表 ChunkBuffer, 然后通过GetchunkedData()方法递归调用最后获取到需要的数据。

此时我们已经获取到了数据,那么就可以考虑是否解压缩的问题了,解压缩比较简单,这里依然直接贴代码了

 1         /// <summary>
 2         /// 通过Gzip方式解压缩数据
 3         /// </summary>
 4         /// <param name="data">字节数据</param>
 5         /// <returns></returns>
 6         public static byte[] GzipDecompress(byte[] data)
 7         {
 8             //解压
 9             using (MemoryStream dms = new MemoryStream())
10             {
11                 using (MemoryStream cms = new MemoryStream(data))
12                 {
13                     using (System.IO.Compression.GZipStream gzip = new System.IO.Compression.GZipStream(cms, System.IO.Compression.CompressionMode.Decompress))
14                     {
15                         byte[] bytes = new byte[1024];
16                         int len = 0;
17                         int totalLen = 0;
18                         //读取压缩流,同时会被解压
19                         try
20                         {
21                             while ((len = gzip.Read(bytes, 0, bytes.Length)) > 0)
22                             {
23                                 dms.Write(bytes, 0, len);
24                                 totalLen += len;
25                             }
26                         }
27                         catch (InvalidDataException ex)
28                         {
29                             //dms.Write(data.Skip(totalLen).ToArray(),0,data.Length-totalLen);
30                         }
31                     }
32                 }
33                 return dms.ToArray();
34             }
35         }

这样我们就拿到了真正需要的数据了

最后我们就可以通过 Content-Type 来判断将数据还原为那种类型,就这样我们就获得了需要的数据

接下来我们看看解析完的数据

首先是zip的数据:

导出为文件后

最后是哪个带有Chunked 和gzip 标志的数据还原

五,更加可靠的数据还原

通过上面的数据还原方法,已经基本满足我们的数据还原需求,但是因为网络、计算机、以及我们程序本身的问题,我们拿到的数据包并不会严格的按照发包的先后顺序获取到数据包,而且因为TCP协议确认重传,有可能发生在某个数据包中,及发送端发送了一个1440的数据包,接收端有可能只取700个字节,剩余的需要重现传输,对于监控端,如果没有处理这种情形的机制,就会造成数据差生误差而不能进行正确还原,更别说还有丢包的情况。

而这种机制就是TCP的数据重组,通过建立合理的模型对tcp状态迁移、序列号、数量等一系列变量进行统一的规划提取还原,最终生成完成而且正确的数据。因为本篇只是简单讨论http数据的还原,所以此处不打算深入的展开, 如果有时间,再写一篇关于TCP重做的文章吧。

感谢你的阅读,欢迎使用NetAnalyzer,欢迎关注NetAnalyzer公众平台。

时间: 2024-10-12 08:33:26

NetAnalyzer笔记 之 九 简单的HTTP数据还原的相关文章

NetAnalyzer笔记 之 二. 简单的协议分析

[创建时间:2015-08-27 22:15:17] NetAnalyzer下载地址 上篇我们回顾完了NetAnalyzer一些可有可无的历史,在本篇,我决定先不对NetAnalyzer做介绍,而是先要了解一些关于构建NetAnalyzer的基础知识,如系统中可以分析的一些网络协议,了解它们的分布方式,字段含义等.在了解了协议的基础上,开始对Winpcap进行一些介绍,在这过程中,学习配置Winpcap的开发环境,做一些简单的数据采集程序.第三部分着重介绍过滤表达式的一些基本语法结构.写下来则要

NetAnalyzer笔记 目录

目录 NetAnalyzer笔记 之 一 开篇语 NetAnalyzer笔记 之 二 简单的协议分析 NetAnalyzer笔记 之 三 用C++做一个抓包程序 NetAnalyzer笔记 之 四 C#版的抓包软件 NetAnalyzer笔记 之 五 一些抓包技巧分享(不定期更新) NetAnalyzer笔记 之 六 用C#打造自己的网络连接进程查看器(为进程抓包做准备) NetAnalyzer笔记 之 七 NetAnalyzer2016使用方法(1) NetAnalyzer笔记 之 八 Net

简单的玩玩etimer &lt;contiki学习笔记之九&gt;

好吧,我承认etimer有点小复杂,主要是它似乎和contiki的process搅在一起,到处都在call_process.那就先搜搜contiki下的etimer的example看看,然后再试着写一个demo玩玩. 在写demo之前,先说说自己是怎么找到etimer 的example的文件的. 在core/sys/etimer.h 文件中,在描述etimer的数据结构的时候,作者显示的指出,如果要使用etimer,就必须先使用 etimer_set()这个函数进行一些工作,如图: 是的,数据结

简单的玩玩etimer &lt;contiki学习笔记之九 补充&gt;

这幅图片是对前面  <<contiki学习笔记之九>>  的一个补充说明. 简单的玩玩etimer <contiki学习笔记之九> 或许,自己正在掀开contiki process最后的一层面纱: 或许,还有一段路要走: 或许,已经掀开... --------------- 一切,都只是process:只有有了process,才会轮到etimer_process  发言,除非,抛却一切机制,裸机实现etimer... process,是什么? 一个链表,还是单向的,仅此

VSTO学习笔记(九)浅谈Excel内容比较

原文:VSTO学习笔记(九)浅谈Excel内容比较 说起文件内容比较,或许我们首先想到的是UltraCompare这类专业比较的软件,其功能非常强大,能够对基于文本的文件内容作出快速.准确的比较,有详细的差异报告,非常便于分析.其实,各种版本控制软件中也包含有或多或少的比较功能,如TFS.CVS.SVN等.但是如果待比较的文件不是基于文本类型的,那就无能为力了.今天我就来谈一谈Excel的比较方法及其特点,也和大家共同探讨一下,如果你有更好的方法,欢迎分享. 一.Excel的文件架构 Excel

GDI+学习笔记(九)带插件的排序算法演示器(MFC中的GDI+实例)

带插件的排序算法演示器 本节将通过一个实例来说明GDI+在MFC中的应用.这个算法演示器其实是本人算法系列的一个开端,由于csdn没有树状的目录结构,咱也只好使用链表了不是?好了,废话不多说,开始今天的文章. (一)功能说明 我们初步制定功能如下: (1). 能够通过柱状图,自动展示排序算法的交换比较过程 (2). 能够使用插件的形式进行开发.即,当新完成一个算法后,只需要完成一个插件文件(我们这里使用动态库dll),由主程序加载插件,即可进行执行,而不再需要重新编译主程序. (3). 保证主程

NetAnalyzer笔记 之 八 NetAnalyzer2016使用方法(2)

[创建时间:2016-05-06 22:07:00] NetAnalyzer下载地址 在写本篇的时候,NetAnalyzer 3.1版本已经发布,所以本篇就以最新版本的为例继续使用,并且顺带说明一下,新版本中一些功能. 那我们就开始吧 四.数据菜单 这部分主要是为开发和数据分析相关人员准备的一组功能,这部分主要针对的是对对数据窗口区域的操作 这部分需要借助软件中一个一个特殊的区域来完成,那就是转换窗口,点击 转换窗口 中的 显示 如下图所示: 就会打开一个转化窗口,本质就是一个可以输入输出的文本

MySQL学习笔记之九 字符串模式匹配

我们在使用查询的时候,经常会碰到模糊条件查询,而模糊查询就涉及到了字符串模式匹配. 在这里,主要讲两个:标准的SQL模式匹配.扩展正则表达式模式匹配.     一.标准的SQL模式匹配 SQL的模式匹配允许你使用"_"匹配任何单个字符,而"%"匹配任意数目字符(包括零个字符).在MySQL中,SQL的模式缺省是忽略大小写的.下面显示一些例子.注意在你 使用SQL模式时,你不能使用=或!=:而使用LIKE或NOT LIKE比较操作符. 为了找出包含正好5个字符的名字,

Wireshark学习笔记——如何快速抓取HTTP数据包

0.前言 在火狐浏览器和谷歌浏览器中可以非常方便的调试network(抓取HTTP数据包),但是在360系列浏览器(兼容模式或IE标准模式)中抓取HTTP数据包就不那么那么方便了.虽然也可使用HttpAnalyzer等工,但是毕竟都是收费软件.只需通过合适的过滤和操作,Wireshark也可抓取HTTP请求和响应.下面便说明具体操作. 假设在8080端口运行一个HTTP服务器,本例中使用Python Flask运行一个HTTP服务并侦听8080端口,实现一个简单的加法运算,网页中通过ajax提交