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

rootkit for linux 18.tcp原理概述

2008-12-20 23:06 197 查看
前言

先说一个冷笑话缓解下气氛:

从前,有一个老师在发试卷,他说:“哪个学生叫林蛋大的?林蛋大上来拿试卷了!”

过了许久,一个学生站起来喊道:“老师,我叫楚中天!”

看懂了吗?

看不懂的话,把名字竖起来写一下就知道了。

当然今天我们不是要讨论谁的蛋大,我们来说一说tcp的状态机。

tcp状态机示意图

状态机呢,实际上是个很理论的东西,就是表示经过什么样的动作,它就到了什么样的状态。

跟人一样,人之初性本擅,你的猥琐习惯也不是一天两天养成的,是经过了一系列的事件,才到达了你今天这样一个状态。

先看看图:



看到这幅图,很多人又雷倒了。

当时我也被雷了一大下,但是我没有被雷倒。为什么呢?因为我被雷的太多了。

我们是服务器,只需要关注图的右边,你看看,少了一半了吧。

对于连接的关闭,我们只需要草草的处理,把重担全部留给客户端的os吧。你看看,又少了一半吧。

现在。我们需要关注的状态有三个 LISTEN,ESTABLISHED,SYN_SENT。

LISTEN:监听状态。我们是服务器,所以默认是处于监听状态的。现在顾客已经不是上帝了,服务器才是上帝。

SYN_SENT:三步握手的第一步。至于什么是三步握手,后面会说。

ESTABLISHED:连接建立,可以发送数据了。

这三个状态,是连接建立需要用到的,还是要认真处理的。

首先说说三步握手。什么是三步握手呢?

处理连接请求(三步握手)

我们是服务器,一开始处于LISTEN状态。一旦收到一个syn包(就是收到一个数据包,它的tcp头部的syn位置了1)。就给客户端发送一个syn,ack包,并且进入 SYN_SENT状态。这时候,如果收到一个 ack 包,就进入 ESTABLISHED 状态。

在ESTABLISHED状态,就是正常的数据传输状态了。

那幅图比较迷惑人。不用管它上面的那些连线上写了什么,自己抓几个包看看就清楚了。

我拿wireshark截获的包如下:



就是这样的。

我们代码要实现的,就是在收到第一个包后给客户端发送第二个包并从LISTEN状态进入SYN_SENT状态,在收到第三个包的时候从SYN_SENT进入ESTABLISHED状态。这还是比较简单的。

处理断开请求

除了连接请求之外,我们还要处理断开请求。

断开请求分两种,一种是异常断开,一种是正常断开。

正常断开的时候客户端会发送 FIN ACK包,从上面的图上也看得出来。

异常断开的时候客户端会发送 RST 包,上面的图就没写出来。但它的确很重要,RST是一定要处理的。所以现在的课本就是很混账,该写的你又不写。

我们看一下正常断开的情况:

客户端向服务端连续发送了4个FIN ACK包。在winsock的代码中,执行closesocket这个函数后,就是这个效果。

那我们的rootkit为什么不响应它呢?

我们收到FIN ACK包后,已经完成了状态切换,但就是不给客户端响应。因为我觉得没必要。客户端怎么处理这种断开到一半的socket是它自己的事,关我p事。

我们再看一下异常断开的情况:

我们的处理方法跟处理正常断开一样。代码也是比较简单的,就不说了。

ack与seq

tcp是一个可靠的传输协议。可靠之处就是它这样一种机制:

发送方发送了编号从13到20的包以后,不会马上把这些包所占的内存释放,也不会继续发后面的包,而是等待接收方的回应,如果接收方说“我收到编号为20包了”,那么发送方这个时候才把编号在20以前的包都释放,然后从编号为21的包开始继续发。如果等了很久都没有等到接收方的回应,那就把编号为13的包再发一遍。

这种机制课本里叫做“滑动窗口协议”。

其中编号13到20的包的总大小是接收方的“接收窗口”的大小。接收方告诉发送方“我收到了编号为xxx的包”是通过tcp数据报的ack字段。发送方告诉接收方“这个包的编号是xxx”是通过tcp数据报的seq字段。

注意,在我们的rootkit里,服务端可以作为接收方和发送方,是两面派。上面说的是我们作为发送方时候的情况,当我们作为接收方时,我们也要告诉客户端“我收到编号为xxx的包了”。

说到这里,又不得不提到tcp的“捎带确认”机制。

“捎带确认”机制是个啥呢。就是说,告诉客户端“我收到编号为xxx的包了”本来是要单独发一个包过去的。但是有了“捎带确认”机制以后,脚不疼了,腰不酸了,干活有劲了,以前要发一个包,现在不用发包了。为啥不用发包呢?当你往客户端发送数据包的时候,确认信息就顺带过去了,通过tcp数据报的ack字段。

所以每个tcp数据包都是带有确认信息的。也就是说,每次我们收到包的时候,客户端都在告诉我们“我收到编号为xxx的包了”。如果你没有发包给客户端,而客户端一直在发包给你,那每次的xxx都是一样的,客户端每次都会告诉你“我收到编号为xxx的包了”,“我收到编号为xxx的包了”,“我收到编号为xxx的包了”。。。

这东西只要用wireshark抓几个包看一下就行了。当时我看课本也看不懂,但是抓几个包看一下就马上懂了。

所以,seq字段和ack字段就是这么回事。

接收包

当我们收到数据报的时候,直接提交给上层(比如简单的网络文件议)处理,由上层负责分配内存空间,重组数据。我们并不需要接收队列。有的数据报的内容很少,完全没必要缓冲起来。就算要缓冲,由上层负责缓冲和重组也是合适的,因为只有上层才清楚当第一个数据包到的时候,要分配多少空间来缓冲后续的数据。

因此我们的接收包部分的代码还是很简单的。

发送包

当我们要发送数据报的时候。首先会对数据进行分段,每个段的大小为tcp的mss(最大分段长度)。然后把每个段放到发送队列中,那是一个循环队列。然后,我们会启动一个线程来发包,并且在发完包之后通过回调函数通知上层。实现了简单的异步发送。

为什么要这么麻烦呢?因为发送队列对于我们的rootkit是很有必要的。你放心,没有用的代码我不会费力气去写,你也应该清楚rootkit调试起来多麻烦。能省则省,我写代码的原则就是坚决不多写一行代码。

考虑一下前面所说的“滑动窗口协议”。

你想一下,如果没有发送队列,直接由上层协议控制超时重传,那基本没可能的嘛。

比如两个线程同时要发包,咋办?这种情况在我们的rookit里面会发生的,很可能几个线程一起发包。没有发送队列,你没法得知发包的顺序,重传的时候,你怎么知道传哪个包呢。

但是有了发送队列,一切就好办了。超时重传也比较容易实现,发的包都按顺序存放在队列里,反正一发包就有启动一个定时器,定时器过期的时候回调一个函数,那个函数检查是不是所有发出去的包客户端都响应了,如果没有的话,就重发。

总结

本篇文章,对rootkit的tcp部分实现是一个概述。记载下来主要是防止很久以后忘记了tcp是个咋回事。如果有讲的不对的地方,希望大家狂屌。

往后的几篇文章,将会分析代码。也相当与一份文档吧。因为我现在已经深刻地认识到,汇编写的东西。如果没有注释,没有文档,那过几天你再看自己的代码,真的想不起来是谁写的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: