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公众平台。
相关文章推荐
- SpringMVC @ResponseBody 415错误处理及org.springframework.http.converter.json.MappingJacksonHttpMessageCon
- XDroidRequest - 一款基于Android 6.0 网络请求框架
- https原理:证书传递、验证和数据加密、解密过程解析
- NIO框架(2)---Channel
- 一个超级好用的加载网络图片的轮播图(3行代码就可搞定)
- android之HttpURLConnection
- HTTP Service服务去哪啦?
- 1.2.2 网络抓包工具之:Charles
- 安卓中进行基于Http协议的网络访问基础总结-1
- Android开发本地及网络Mp3音乐播放器(十七)已存在歌曲歌词下载
- Android开发本地及网络Mp3音乐播放器(十七)已存在歌曲歌词下载
- 在 .NET 中远程请求 https 内容时,发生错误:根据验证过程,远程证书无效。
- PHP问题 —— failed to open stream: HTTP request faile
- 因缺乏品牌意识 宁夏某知名企业网络域名遭抢注
- 安卓使用OkHttpClient进行网络请求
- 几种TCP连接中出现RST的情况
- Swift中的HTTP请求
- php http请求
- oracle12c不能进入到http://localhost:5500/em的解决办法
- NS3网络仿真(1)—— (14)