通过网卡驱动,实现高性能攻击器

 

  基于socket的SYN数据包攻击器早已不是什么新鲜事了.Windows Socket封装了复杂的底层网络,并且提供了简单实用的接口,所以各种网络应用里socket无不为首选.作为网络攻击里最经典的一种方式:SYN DOS,网络上绝大多数的实现均为socket.不过百行的代码即可发出每秒10万的包,不得不承认socket高端的效率及其成熟的应用.但今天讨论的是比socket更为高效的实现方法,因此我们必须绕过系统内部的验证检查,自动装包,路由计算...各种细节,直接把我们的数据包从内存写入网卡!所有需要一种更为底层的方法:NDIS网络驱动.此外,我们再设计一套新创的TCP/IP校验和算法,为攻击提供更强的动力.

  

  NDIS(Network Driver Interface Specification),即网络驱动接口规范,其主要目的就是为NIC(Netwok Interface Cards,网络接口卡)制定出标准的API接口...(具体看百度百科).知道这些也没用,关键要知道为什么要用驱动来实现,而不用普通应用程序来调用某某API.

   

  首先要明白NDIS是驱动级别的.Windows下的程序有两个级别:Ring3和Ring0.平时我们用的应用程序都是运行在Ring3模式下,这个模式下CPU对特权级别指令的作保护,所以即使应用程序发生了严重的错误,也只是弹出个对话框报告XXX不能为Read或Write之类的,不会影响到操作系统的崩溃.因此本着稳定压倒一切的原则,系统把应用程序都运行在这个模式下,这虽然需要一定的额外开销,但也造就了如今难得死机的好局面.但经常遇到蓝屏的朋友可能会反驳.不过别忘了还有个Ring0的模式:也就是驱动程序所在的环境.驱动要和那些硬件打交道,当然必须要有最高级别的特权,使用的内存也是最原始的不分页内存,并且有着最高的运行效率.当然,代价就是程序发生错误直接导致系统崩溃.所以蓝屏后仔细观察下,就会发现错误什么IRQL_XXX之类的,以及XXX.sys文件.这些都是驱动程序的信息,sys当然就是驱动程序的扩展名.到此大家应该对驱动的概念有个基本的了解吧.

   

  不过即使你有现成的驱动代码,光在vc下按个F5是不可能生成.sys的驱动程序的,更不用说调试了.所以要开发驱动程序可没那么简单,首先得安装DDK(驱动开发包),就像SDK一样,提供必要的头文件库文件及其杂七杂八的文件.本程序使用的版本是DDK 2003,可以制作vista系统之前的驱动程序.开发语言当然是纯C语言.之前说过了,驱动程序里发生一点点错乱,都会导致系统崩溃,所以在语言方面必须得过关.当然尽管你再熟悉,开发过程中频繁的重启还是不可避免的,所以推荐把调试过程放在虚拟机里进行,蓝屏了立即还原,可以事半功好几倍.

   

  安装完DDK,会发现src\network下有多个NDIS程序的模板,他们有哪些不同之处?这里还得简单的说下NDIS内部的结构关系.先要明确的是NDIS并不是一个驱动的名字,而是一个驱动系统,一个框架.Windows下NDIS共分3层:网卡驱动层,中间驱动层和协议驱动层.这样的设计保证了各种各样型号的网卡可以为系统提供网络,也为防火墙之类的网络过滤服务留下了空间,等等,总之就是为了灵活的可扩展性.我们这里用的是协议层驱动.DDK中有一个ndisprot的样例,正如其名.我们的驱动就从这个样例着手.不过不要被那上万行的代码吓倒,其中绝大部分都是实现的细节,我们大可不必理会,只需了解其中几个关键位置即可.

   

  正如每个动态链接库都是从DllMain开始运行,驱动程序的入口是DriverEntry(如果对驱动程序还不是很熟悉,可以去网上查找驱动版的Hello World.可以对驱动的格式,编译,运行及其调试有一定的了解).因为这是NDIS的驱动,所以在要先注册一张回调接口表(NdisRegisterProtocol).因为驱动程序是运行在系统内核中的,所以是根本不可能拥有界面的.因此需要有一个Win32应用程序处理用户的交互,以及发送参数和命令给驱动;而驱动则是通过注册IRP回调接口表与Win32通信.由于Ring3和Ring0不在一个模式下,他们之间的数据通信是比较消耗系统资源的,所以本程序中的数据包直接在驱动里构造,Win32只传入一些必要的参数.而ndisprot默认的功能是收发win32提供的数据,这样的好处是不用对驱动作修改任何,直接开发win32的程序就可以,但我们追求的不是容易,是效率.而且本程序只管发的快就可以,不需要任何接收的功能,所以要对驱动进行修改以达到我们的目的.(网络上有个别通过Winpcap库开发的syn攻击器,事实上效率不会比socket高,因为微软为socket做了大量的优化,收发数据不是以I/O模式传输的,可以从任务管理器的I/O读取写入字节可以观察到).

   

  关于数据包的发送流程细节,这里就不详细叙述.大致就是先调用NdisDprAllocatePacket()向网卡申请一个包资源,成功的话在其中附上数据内存的指针,及其一些发送的参数,然后调用NdisSend()即可发送,发送完成后在通过NdisDprFreePacket()释放包资源,其流程并不复杂.下面我们要详细讨论的是如何协调好这个流程,充分利用CPU和I/O的并行度,用最少的系统资源让网卡达到满负荷.

  

  先设计个最简单的模型,然后再逐步进行优化(数据包的内存事先已分配).

   while(!结束)
{
装填数据();
while(!申请包());
关联包();
发送包();
释放包();
}

  

  这种模型是最简单也是最快的,但CPU的使用率也是最高的.因为网卡吞吐量再快也不及CPU的速度.所以绝大多数的CPU都耗在申请包上面了.简单的改进方法就是当申请包不成功时,让线程做一个短暂的睡眠,从而交出CPU的占用权.驱动程序的Sleep()可不像Win32的那样,16ms内的有很大的误差,驱动的内核模式下理论上可以精确到百万分之一秒.所以在此做个微小的睡眠就可以解决CPU使用的问题,因为网卡只有在满负荷的情况下才申请不到包,让线程小睡一会后网卡又有较多的空闲资源.这里可不可以只申请一次包,不停的发送,结束了再释放呢?答案是不可以的,具体原因只有网卡驱动的开发人员才知道.我的猜测是发包函数调用过快导致缓冲溢出了.

  

  接着的优化的当然是装填数据这块.作为一个优秀的攻击器,应该保证syn的包可变的字段每次都随机,以达到目标网络的防火墙识别不出攻击的特征.关于算法的优化后面再叙述,这里讨论的是如何减少"装填数据()"这个过程的时间.不过就目前而言这步优化效果不明显,因为CPU的速度远大于网卡速度,但在以后千兆甚至更高的网络上有意义了.

  

  线程1:

复制代码
   while(!结束)
{
while(!申请包())
Sleep(1);

关联包[n];
n++;
if(n==MAX)
{
f=有信号;
n = 0;
}
发送包();
释放包();
}


线程2:

while(!结束)
{
for(i=0;i<MAX;i++)
装填数据[i];

f = 无信号;
等待信号f;
}
复制代码

  

  利用多线程,我们可以让装包和发包在各自的线程里进行.事先也不再是申请一个包数据的内存,而是MAX个.当发完一轮(或快发完一轮)时,就可以通过信号量唤醒装包线程,装包线程一次性装填MAX个后进入睡眠等待下个信号.以此提高程序的并行度.

   

  本程序的驱动修改绝大多数放在send.c文件里,另外去掉了recv.c中的接收回调的处理.

  

  现在开始讨论数据装填上的优化.前面说了需要大量的随机数,不妨自己写个简单又实用的为随机公式:

ULONG _SEED_;

#define SRND(seed) (_SEED_ = seed)
#define RND() (_SEED_ *=10807L)

   

  该方法在网上较为流行,其产生的随机数有在分布上也有很好的特性.

  再比如使用htonl函数(或宏)转换ip的网络字节顺序,用常规的方法需要十余个指令,事实上intel处理器本身就有一条指令可以实现此功能:

  bswap reg 即可实现htonl(ip)的功能.

 

  当然最关键的优化要数Checksum校验和的计算了.传统的Checksum算法网上随便一搜一大堆,甚至不少攻击器也是用此算法.该算法并没有什么缺点,只是具有普遍性,可计算任意数据任意长度的Checksum. 对于syn包那样很短并且每次都是固定长度的包,这种算法显然有些累赘了.我们不妨仔细分析下Checksum算法的原理:

  

复制代码
USHORT ChechkSum(USHORT *buffer, int size)
{
DWORD chkSum = 0;

while(size > 1)
{
chkSum += *buffer++;
size -= sizeof(USHORT);
}

if(size)
chkSum += *(UCHAR*)buffer;

chkSum = (chkSum >> 16) + (chkSum & 0xffff);
chkSum += (chkSum >> 16);

return (USHORT)(~chkSum);
}
复制代码

   

  下面不考虑长度为奇数的情况.先看循环里面的代码,事实上只是把这些数据当作一个USHORT[],计算这个数组的和而已.即使数据的内容不一样,但只要这个累加的和不变,那后面不管对其做什么运算,函数结果肯定是一样的.简单的说:不同的数据可能有相同的和,必然也有相同的校验和.比如1+2+3=3+2+1=2+2+2,和都是6.当然他们也有相同的校验和.

  

  既然是这样的算法,我们就可以大作优化了.比如一个固定数组:USHORT A[10].他的和一共有65535*10+1种情况.这个应该很好理解:如果10个数全是0,那和也是0;反之全是最大数65535,那么和是655350;其余情况就在这之间波动.由此可见, N字长的数据校验和其实也只有65535*N+1种情况.即便是本程序中计算TCP校验和的数据有20字长(40字节),其结果也只有1,310,699种.如果你在思考的话一定也想到了经典的空间换时间算法.没错,正是如此!即使事先把这几个数的校验和全部计算好保存起来,也仅需2.5M的内存空间.不过问题是:以后如何去利用表里的这些缓存数据?

  

  因为这是一张”和”与”校验和”的映射表,所以当”和”确定后即可获得”校验和”.因此在预先填充数据包的时候,可变的字段一定要置0, 然后计算出这个半成品包的”和”当作”基值”.以后当可变部分填上数据时,比如IP头填上了id字段,那么这个IP的Checksum便是: 表[基值+id].当然,如果是4个字节的可变部分就不能这么简简单单的加上就可以了,还要对其做高位合并计算.比如IP头的SrcIP填上了数据,那么:

  表[基值+SUM(SrcIP)] 才是其正确的校验和.

  

  具体的SUM可以用个宏来定义:

    #define HI(VALUE)        (VALUE >> 16)
#define LOW(VALUE) (VALUE & 0xFFFF)
#define SUM(VALUE) (HI(VALUE) + LOW(VALUE))

  

  TCP/IP的装填部分就说到这里,但此程序不是socket,仅仅将IP包的数据发送出去就可以.而是需要构造一个完整的以太封包,作为最原始的链路数据写入网卡驱动中,应此在实现上,还必须通过ARP协议解析出网关的MAC地址,如果是LAN模式下的话直接解析目标主机的MAC;同时也可以设置以太层的源MAC,以达到一定程度上的自我隐藏.

  

  在封包的结构上还有个值得一提的小细节,对于以太头部:

typedef BYTE ADDR_MAC[6];
typedef struct
{
ADDR_MAC DstMAC;
ADDR_MAC SrcMAC;
USHORT Type;
} HEADER_Ethernet;

   

  单独定义它是毫无问题的.但将它嵌在一个总的包结构里就不一样了:

typedef struct
{
HEADER_Ethernet Ether;
HEADER_IP IP;
HEADER_TCP TCP;
HEADER_TCP_OPTS Opts;
} PACKET_SYN;

  

  咋一看好像完全没问题.但仔细观察就会发现,sizeof(HEADER_Ethernet)是6+6+2=14字节,不是4的倍数,所以在内存的对齐上就出问题了.通常的做法就是让编译器对这个结构体特殊对待,允许访问不对齐的字段.但这样会降低运行效率.事实上有更简单的解决方法:

typedef struct
{
USHORT Padding;
HEADER_Ethernet Ether;
HEADER_IP IP;
HEADER_TCP TCP;
HEADER_TCP_OPTS Opts;
} PACKET_SYN;

  在以太头之前放一个2字节的变量做填充,这样整个结构体里的所有字段都对齐了.

  

  驱动程序就讨论到此.当然,还必须要有一个完善的Win32应用程序来"驱动"驱动程序,并提供完整的命令操作及其交互,对于具体的实现过程可以参考源码.

   

  最后谈论的当然是缺点.此攻击器虽然能达到14.8万包/秒的极限速度,但CPU使用仍不理想,维持在20%-40%,尽管比socket效率大幅提高,但远没有达到当初设想的1%以下,虽然设法保持在6-7万包/秒的时候下确实不用1%的CPU,但超过该范围之后便陡升,研究多次后发现可能与NDIS内部一个链表有关系,但一直未能弄清此问题.

  

  另一个问题此程序是直接写包到网卡驱动的,所以发送的都是纯粹的IP包,对于ADSL或VPN拨号上网的用户来说这个攻击器不能攻击Internet上的任何一个主机(当然拨号的网络是禁止发送伪造源IP,所以syn攻击本身也是没有意义的).不过在服务器机房,局域网或者城域网内使用,可以产生极大的破坏力.对于2000/XP/2003的系统均可使用此程序,而不像socket版本的syn,只能在2003系统上使用.此外对于某些型号的无线网卡可能不兼容此程序.

posted @   EtherDream  阅读(2670)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示