您的位置:首页 > 理论基础 > 计算机网络

C#对HTTP数据还原

2016-05-13 09:21 573 查看

使用C#对HTTP数据还原

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

NetAnalyzer下载地址

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

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

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

一,提取TCP会话数据

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

View Code

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

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


通过标志位挑选会话数据

View Code

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

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公众平台。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: