[转]教大家如何打造使用Tcpview(tcp查看器
接关掉。后来VS 平台封掉了这个程序,只要一打开TcpView就会被 VS 关掉。于是我萌生了
自己做个 TcpView的想法。
Tcpview是Winternals公司 Sysinternals
系列工具之一,尽管大部分这些工具网上都
有源代码,唯独没有找到TcpView的源代码。能找到的其实只是个命令行下的一个很简易的
Tcpview。现在的目标就是尽量模拟一个界面版的Tcpview。
这个程序主要的功能就是显示系统中当前的TCP和UDP 连接信息,包括本地地址、端口,
远程地址、端口,连接状态,所属进程等,以及这些连接的上传、下载的数据量,并且可以
断开 TCP 连接。我还添加了一个功能,显示远程地址对应的物理地址。程序使用VC 6.0 MFC
的单文档开发,主界面是个ListView。
在该程序中获取 TCP 连接用的是 GetExtendedTcpTable 函数,获取 UDP 连接用的是
GetExtendedUdpTable函数,断开一个TCP 连接用的是 SetTcpEntry。其实这些信息通过调
试 Tcpview都能发现,不过关于如何获取上传下载流量,我真的没弄出来它到底是怎么实现
的(OD 水平太菜了)。但是经过测试,Tcpview 中包含的一个驱动并没有用到,因为任何工
具都没有检测到有驱动加载。希望有高手可以指点一下!
于是,我只好自己用原始套接字实现了,可惜唯一的缺陷是不能获取回环地址通信时的
流量。比如,我本地开了FTP 服务器,然后用127.0.0.1连接上去传输文件,此时是无法截
获到数据包的。关于这一点,我在网上查到的资料说是在Windows下原始套接字是无法办到
的,甚至 Winpcap包也不可以,要捕获的话可以用SPI或是API Hook。
具体实现
下面介绍实现的细节。由于GetExtendedUdpTable与GetExtendedTcpTable的用法非常
相似,故这里只介绍GetExtendedTcpTable的用法。
GetExtendedTcpTable函数在 SDK 中没有,所以要自己定义。
typedef DWORD (WINAPI *PFNGetExtendedTcpTable)(
__out
PVOID pTcpTable, //返回查询结构体指针
__in_out
PDWORD pdwSize, //第一次调用该参数会返回所需要的
缓冲区大小
__in
BOOL bOrder, //是否排序
__in
ULONG ulAf, //是 AF_INET还是AF_INET6
__in
TCP_TABLE_CLASS TableClass, // 表示结构体的种类,
此处设为 TCP_TABLE_OWNER_PID_ALL
__in
ULONG Reserved //保留不用,设为 0
);
pTcpTable 其实是一个指向 MIB_TCPTABLE_OWNER_PID
类型的指针。
MIB_TCPTABLE_OWNER_PID结构定义如下:
typedef struct _MIB_TCPTABLE_OWNER_PID
{
DWORD
dwNumEntries;
MIB_TCPROW_OWNER_PID table[ANY_SIZE];
} MIBTCPTABLEOWNERPID, *PMIBTCPTABLEOWNERPID;
dwNumEntries表示
MIB_TCPROW_OWNER_PID结构的数目,每个该结构指定一个TCP 连接
的信息。ANY_SIZE 的值被定义为1,可以理解为 table 是 MIB_TCPROW_OWNER_PID 结构体数
组的首地址,这样我们可以任意地访问每个数组的成员。这种定义方式就好比一列火车,告
诉你车厢数以及火车头的地址,我们就可以得到每节车厢的地址。再来看
MIB_TCPROW_OWNER_PID的定义:
介绍完相关的数据结构就可以来使用该函数了。这里是我程序
typedef struct _MIB_TCPROW_OWNER_PID
{
DWORD
dwState;//连接状态
DWORD
dwLocalAddr;//本地 IP地址
DWORD
dwLocalPort;//本地端口
DWORD
dwRemoteAddr;//远程 IP 地址
DWORD
dwRemotePort;//远程端口
DWORD
dwOwningPid;//关联的进程ID
} MIB_TCPROW_OWNER_PID, *PMIB_TCPROW_OWNER_PID;
int GetTcpConnect()
{
HMODULE hMod = LoadLibrary("Iphlpapi.dll");
if(!hMod)
{
AfxMessageBox("加载Iphlpapi.dll出错");
return 0;
}
PFNGetExtendedTcpTable pfnGetTcpTable =
(PFNGetExtendedTcpTable)::GetProcAddress(hMod,"GetExtendedTcpTable");//获取
函数地址
PMIB_TCPTABLE_OWNER_PID pTcpTable = new
MIB_TCPTABLE_OWNER_PID;
DWORD dwSize =
sizeof(MIB_TCPTABLE_OWNER_PID);
if (pfnGetTcpTable(pTcpTable,
&dwSize,
TRUE,AF_INET,TCP_TABLE_OWNER_PID_ALL,0) ==
ERROR_INSUFFICIENT_BUFFER)
{//第一次调用时不知道要传入的缓冲区大小,所以要试探一下,参数dwSize会返
回真正需要的大小
delete pTcpTable;
pTcpTable =
(MIB_TCPTABLE_OWNER_PID *)new char[dwSize];//重新分配缓
冲区
}
if(pfnGetTcpTable(pTcpTable,&dwSize,TRUE,AF_INET,TCP_TABLE_OWNER_PID_AL
L,0) != NO_ERROR)
{
AfxMessageBox("获取TCP连接出错");
delete pTcpTable;
return 0;
}
int nNum = (int)
pTcpTable->dwNumEntries; //TCP连接的数目
for(int i=0;i<nNum;i++)
{
printf(“本地地址:%s:%d
远程地址:%s:%d 状态:%d
进程ID:%d”,
inet_ntoa(*(in_addr*)&
pTcpTable->table[i].dwLocalAddr), //本地IP 地址
htons(pTcpTable->table[i].dwLocalPort), //本地端口
inet_ntoa(*(in_addr*)&
pTcpTable->table[i].dwRemoteAddr), //远程IP地址
htons(pTcpTable->table[i].dwRemotePort), //远程端口
pTcpTable->table[i].dwState, //状态
pTcpTable->table[i].dwOwningPid); //所属进程PID
}
delete pTcpTable;
}
获取进程 ID 之后就可以获取进程名和路径了。我使用的是
CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0)方法,在ROCESSENTRY32中没有进程的
路径信息,可以用 GetModuleFileNameEx 函数获得,或是通过
CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,nPId)
查找进程模块,出现的第一个模块
就是程序模块,路径存储在me32.szExePath中。这部分代码请参看我的源程序。
既然要模仿TcpView,当然要有进程图标了。一开始我选择去读取图标资源,但是这种
方法兼容性不好,比较麻烦,其实只要用ExtractIcon函数就好了。以下是提取代码:
遇到没有图标的程序可以使用系统默认的程序图标,只要调用
HICON GetExeIcon(CString strExe,int nIdx = 0)//获取第 nIdx+1
个图标,一般程序
将 nIdx 设为 0就可以了
{
if(strExe == "") return NULL;
HMODULE hExe = LoadLibrary(strExe);//把EXE
当二进制资源加载
if (hExe == NULL)
{
return NULL;
}
int nNum =
(int)::ExtractIcon(hExe,strExe,-1);//获取图标数
if(nNum == 0) return NULL;
HICON hIcon =
::ExtractIcon(hExe,strExe,nIdx);//提取图标
FreeLibrary(hExe);//释放资源
return hIcon;
}
遇到没有图标的程序可以使用系统默认的程序图标,只要调用
GetExeIcon("shell32.dll",2)就好了。要在每一项前面显示图标,需要在视图类的定义中
添加图像列表指针 CImageList *m_pImgList,并在构造函数中初始化 m_pImgList = new
CImageList; m_pImgList->Create(16, 16,
ILC_COLORDDB|ILC_COLOR32, 8, 8),然后在
List 添加列的同时将图像列表加入 List : m_list.SetImageList(m_pImgList,
LVSIL_SMALL)。获取图标句柄后调用m_pImgList->Add(hIcon),返回值为图标在
ImageList
中的索引,该索引即是InsertItem时设置的图标索引。
关闭 TCP 连接的方法是用 SetTcpEntry 函数设置连接的状态为:
MIB_TCP_STATE_DELETE_TCB,下面是具体代码:
MIB_TCPROW tcprow;
tcprow.dwLocalAddr = dwLocalIp;//本地 IP地址
tcprow.dwRemoteAddr = dwRemoteIp;//远程IP 地址
tcprow.dwLocalPort = ntohs(nLocalPort);//本地端口
tcprow.dwRemotePort = ntohs(nRemotePort);//远程端口
tcprow.dwState = MIB_TCP_STATE_DELETE_TCB;//删除连接
SetTcpEntry(&tcprow);
使用原始套接字可以监听到接收到的所有IP 数据包,只要分析TCP 包和 UDP 包的相关
信息就可以得到每个连接的流量信息。每个连接在内存中是以 UpDownInfo 结构体的形式存
储的。网上讲述原始套接字的文章有很多,黑防也有不少这种文章,这里只给出关键代码:
//由于代码很长,部分地方会省略冗长的代码,详细请见源程序
vector<DWORD> dwMyIp; //本机IP列表,存放所有IP
地址,以便嗅探所有数据包
char szHost[256];
// 取得本地主机名称
::gethostname(szHost, 256);
// 通过主机名得到地址信息
hostent *pHost = ::gethostbyname(szHost);
in_addr addr;
for(int i = 0; ; i++)
{
char *p =
pHost->h_addr_list[i];
if(p == NULL) break;
dwMyIp.push_back(inet_addr(inet_ntoa(*(in_addr
*)pHost->h_addr_list[i]))); //保存IP地址
}
nNetNum = i;
for(i = 0;i<nNetNum;i++)
{
::CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)WorkThread,(LPVOID)this,0,
0); //建立新线程,WorkThread是嗅探的工作线程
}
int WINAPI WorkThread(LPVOID Param)
{ // 创建原始套接字
SOCKET sock;
sock = socket(AF_INET, SOCK_RAW,
IPPROTO_IP);
// 设置 IP头操作选项,其中 flag 设置为ture,亲自对 IP头进行处理
BOOL flag=TRUE;
setsockopt(sock, IPPROTO_IP, IP_HDRINCL,
(char*)&flag, sizeof(flag));
SOCKADDR_IN addr_in;
addr_in.sin_addr.S_un.S_addr =
dwMyIp[nIndex++];//用于设置监听的IP
addr_in.sin_family = AF_INET;
addr_in.sin_port = htons(10013); //绑定任意端口
if(bind(sock,
(PSOCKADDR)&addr_in, sizeof(addr_in)) ==
SOCKET_ERROR)
{
AfxMessageBox("绑定地址失败");
return 1;
}
// dwValue为输入输出参数,为1 时执行,0时取消
DWORD dwValue = 1;
// 设置 SOCK_RAW
为SIO_RCVALL,以便接收所有的IP包。其中SIO_RCVALL
// 的定义为: #define SIO_RCVALL
_WSAIOW(IOC_VENDOR,1)
ioctlsocket(sock, SIO_RCVALL,
&dwValue); //将网卡设置为混合模式
char RecvBuf[BUFFER_SIZE];//接收数据的缓冲区
while(1) //死循环
{
int ret = recv(sock, RecvBuf, BUFFER_SIZE, 0);
if (ret >0)
{
IpHeader
*iphdr = (IpHeader *)RecvBuf; //此处略去IP头定义,下同,
详见代码
int nLen =
ntohs(iphdr->TotalLen) - sizeof(IpHeader);
//获取下层
数据包长度
int bUp =
IsMyIp(iphdr->SrcAddr,iphdr->DstAddr);
//判断是否是发
送数据,返回-1 表示不是本机数据包,因为原始套接字必须要设为混杂模式,否则无
法监听到数据
if(bUp == -1)
continue;//拒绝接收
DWORD dwLocalIp =
bUp?iphdr->SrcAddr:iphdr->DstAddr;
DWORD
dwRemoteIp =
bUp?iphdr->DstAddr:iphdr->SrcAddr;
int
hdrLen,i;
if(iphdr->Protocol == IPPROTO_TCP) //是TCP 包
{
TcpHeader *tcphdr = (TcpHeader
*)(RecvBuf + iphdr->HdrLen*4);
hdrLen
=iphdr->HdrLen*4+sizeof(TcpHeader);
USHORT
nLocalPort =
bUp?ntohs(tcphdr->SrcPort):ntohs(tcphdr->DstPort);
//获取本地端口
USHORT
nRemotePort =
bUp?ntohs(tcphdr->DstPort):ntohs(tcphdr->SrcPort);
//获取远程端口
for(int
i=0;i<pThis->m_connList.size();i++)
{//
m_connList为
vector<UpDownInfo>类型,UpDownInfo是保存连接相
关信息的结构体
if( 判断 m_connList中是否存在该连接
)
{
if(bUp)
{
m_connList[i].dwUpData += nLen - sizeof(TcpHeader);
m_connList[i].nUpPacket ++;
}else{
m_connList[i].dwDownData += nLen
-sizeof(TcpHeader);
m_connList[i].nDownPacket
++;
}
}
if(i>=m_connList.size())
{
UpDownInfo
info;
省略初始化结构体代码
m_connList.push_back(info);
}
}
if(iphdr->Protocol == IPPROTO_UDP) //是UDP 包
{
UdpHeader *udphdr = (UdpHeader *)(RecvBuf +
iphdr->HdrLen*4);
//省略
UDP部分的处理代码,与上类似
}
}else{
//出现错误
break;
}
}
return 0;
}
有一点要注意的是, UDP 协议与 TCP 不同,很多基于P2P 的程序只是绑定一个本地的 UDP
端口,然后监听,此时可能所有的数据包都是发送到该端口上来的。原版的Tcpview是不分
析 UDP 地址的,只看本地端口。我在此基础上做了点改进,会显示远程的 IP 地址,不过显
示的只是收到的最近包的地址。其实也是可以做成显示所有远程通信地址的,只不过编写上
会更麻烦一些(有兴趣读者可以自己完成)。
显示物理地址部分我用的是网上的代码,就是查找纯真 IP 数据库中的记录,得到物理
地址。如果当前程序目录下没有“qqwry.dat”文件,List中是不会添加“物理地址”这一
列的。程序启动后连接默认会根据进程名排序,新加入的连接也会自动进行插入排序。
总结
在程序制作过程中也花了不少精力,比如刚开始总是有严重的资源泄漏问题,后来用的
是 BoundsChecker不断调试解决的。现在我提供的这个版本还是有点简陋,有些细节也没有
处理好,欢迎大家批评指正。最后谈一谈开头的话题,就是 VS 中的踢人问题,原理很简单,作为主机(服务端)时
所有数据包都会发送到我电脑上来,只要我把要踢的人的TCP 连接关了,那他自然就掉线了,
但前提是你要清楚要踢的是谁。还有种踢人挂用的是API Hook的方式,主要是对 send函数
的挂钩,用SPI也是可以实现的。
[转]教大家如何打造使用Tcpview(tcp查看器的更多相关文章
- 打造自己的html5视频播放器
前段时间重新学习了一下html5的video部分,以前只是停留在标签的使用上,这一次决定深入了解相关的API,并运用这些API打造一个简单的视频播放器.所谓“打造自己的”,就是要自己重写video标签 ...
- H5打造属于自己的视频播放器(JS篇2)
回顾 算了不回顾了 直接搞起,打开JS1中写的bvd.js 播放视频 播放按钮隐藏 视频开始播放 当点击播放按钮的时候,播放按钮将会隐藏,播放视频,这个不难,在JS1中我们就已经实现.但我们改变一下思 ...
- [C#]手把手教你打造Socket的TCP通讯连接(一)
本文章将讲解基于TCP连接的Socket通讯,使用Socket异步功能,并且无粘包现象,通过事件驱动使用. 在编写Socket代码之前,我们得要定义一下Socket的基本功能. 作为一个TCP连接,不 ...
- iOS教你轻松打造瀑布流Layout
前言 : 在写这篇文章之前, 先祝贺自己, 属于我的GitHub终于来了. 这也是我的GitHub的第一份代码, 以下文章的代码均可以在Demo clone或下载. 欢迎大家给予意见. 觉得写得不错的 ...
- 手把手教你在netty中使用TCP协议请求DNS服务器
目录 简介 DNS传输协议简介 DNS的IP地址 Do53/TCP在netty中的使用 搭建DNS netty client 发送DNS查询消息 DNS查询的消息处理 总结 简介 DNS的全称doma ...
- 教你快速打造PHP MVC框架
简介 MVC框架在现在的开发中相当流行,不论你使用的是JAVA,C#,PHP或者IOS,你肯定都会选择一款框架.虽然不能保证100%的开发语言都会使用框架,但是在PHP社区当中拥有最多数量的MVC框架 ...
- NetAnalyzer笔记 之 六 用C#打造自己的网络连接进程查看器(为进程抓包做准备)
[创建时间:2016-04-13 22:37:00] NetAnalyzer下载地址 起因 最近因为NetAnalyzer2016的发布,好多人都提出是否可以在NetAnalyzer中加入一个基于进程 ...
- 通过TCPView工具查看foxmail用exchange方式连接exchange时用什么端口
TCPView下载地址 https://docs.microsoft.com/zh-cn/sysinternals/downloads/tcpview
- 手把手教你写Windows 64位平台调试器
本文网页排版有些差,已上传了doc,可以下载阅读.本文中的所有代码已打包,下载地址在此. ------------------------------------------------------- ...
随机推荐
- 面对一个“丢失了与用户“签订”的协议的修改”时进行的思考。
对于上图中的gauge,将value与label之间的比例值调整了,调整为1:1.2.这意味着,在新系统中打开老报表,老报表中的这个gauge的value可能会比以前大,二者可能是用户厌恶的效果. 严 ...
- 跟我学习dubbo-使用Maven构建Dubbo服务的可执行jar包(4)
Dubbo服务的运行方式: 1.使用Servlet容器运行(Tomcat.Jetty等)----不可取 缺点:增加复杂性(端口.管理) 浪费资源(内存) 官方:服务容器是一个standalone的启动 ...
- WEB系统架构
客户端方向:框架+控件+模板+元数据辅助:懒加载+合并请求+异步任务+推送+缓存技术:reactjs,requirejs,jquery,angularjs,bootstrap,ant.design,f ...
- c#抽象类相关
abstract class mylass { public int age{get;set} public abstract void SaiHi(); } 1,抽象类中可以有实例成员,也可以有抽象 ...
- apache Internal Server Error 的几个问题
Internal Server Error The server encountered an internal error or misconfiguration and was unable to ...
- Windows SDK 之 Hook的使用
在使用SetWindowsHookEx的过程中遇到的问题 函数原型 HHOOK WINAPI SetWindowsHookEx( _In_ int idHook, _In_ HOOKPROC lpfn ...
- MySQL写入插入数据优化配置
*innodb_buffer_pool_size 如果用Innodb,那么这是一个重要变量.相对于MyISAM来说,Innodb对于buffer size更敏感.MySIAM可能对于大数据量使用默认的 ...
- 读书笔记-常用设计模式之MVC
1.MVC(Model-View-Controller,模型-视图-控制器)模式是相当古老的设计模式之一,它最早出现在SmallTalk语言中.MVC模式是一种复合设计模式,由“观察者”(Observ ...
- 使用VS2015(c#)进行单元测试,显示测试结果与查看代码覆盖率
创建测试的过程可参考如下链接 http://www.cnblogs.com/libaoquan/p/5296384.html (一)如何使用VS2015查看测试结果 问题描述:使用VS2010执行单元 ...
- Access时间日期比较查询的方法总结
Access日期时间比较查询语句困扰过很多网友,种豆网整理了一下Access日期比较查询的几种方法,假定数据表明为TblName,日期/时间字段名为FDate(这里不能讲FDate设置为字符串,否则比 ...