制造恶作剧切断TCP连接和进程之间的关联
想不想再玩个恶作剧??
很多运维发现系统中有tcp连接异常的时候,会使用netstat/ss命令找出tcp连接对应的处理进程,然后去找研发debug这个进程。比如:
[root@localhost ~]# netstat -ntp
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local AddressForeign AddressStatePID/Program name
tcp00 192.168.56.110:22192.168.56.1:50069ESTABLISHED 1420/sshd: root@pts
tcp00 192.168.56.110:22192.168.56.1:50048ESTABLISHED 1357/sshd: root@pts
tcp00 192.168.56.110:22192.168.56.1:50060ESTABLISHED 1378/sshd: root@pts
tcp00 192.168.56.110:22192.168.56.1:50063ESTABLISHED 1399/sshd: root@pts
如果我把tcp连接和进程之间的关联拆除了会怎样?杂耍一个手艺,看看他们的表现?
或者,更甚,我将进程1和进程2处理的tcp连接交换,能不能达到嫁祸于人的目的呢?我让你查,我让你查个毛线球!
若完成此事,必然要先理解在Linux中,tcp连接和进程之间的关联是如何建立的。
OK,让我们开始。
在Linux系统中,一个tcp连接在底层用一个tcp_sock对象来表示。
在sock的OO设计中,存在下面的继承关系,我们从基类sock_common开始:【制造恶作剧切断TCP连接和进程之间的关联】然而…
sock_common <-- sock <-- inet_sock <-- inet_connection_sock <-- tcp_sock
然而进程和tcp_sock并非相互指向的,因为一个tcp连接并非唯一对应一个进程,我们知道,tcp_sock在进程上下文以文件描述符存在,多个进程可以操作同一个tcp连接。
所以,你可以从一个进程的文件描述符对应到一个tcp连接,但反过来却不行,你无法通过一个tcp_sock确认它所属的进程。
文章图片
基于tcp连接和进程并非一一对应的关系,在实现上,若要将一个tcp连接和一个进程建立关联,要分为两个步骤来处理:
- Linux将所有的tcp_sock导出在/proc/net/tcp中,其中的inode字段指向伯克利套接字层的socket_alloc结构体对象的inode地址的i_ino字段。
- Linux将所有进程导出在/proc/$pid目录,其中在/proc/$pid/fd子目录导出所有该进程打开的文件描述符。
- 将所有/proc/net/tcp中的tcp连接的inode号保存在一个链表。
- 遍历所有/proc下导出的进程的/proc/$pid/fd目录中的link,以此link inode号匹配tcp inode链表中的inode号。
- 将匹配成功的tcp inode号和/proc/$pid/fd/$fd->inode建立关联。
如果你用ss替代netstat,其过程依然如此,唯一的不同就是tcp inode链表不在再通过/proc/net/tcp获取,而是通过netlink来获取。这个过程和Linux内核驱动程序管理机制中在bus上driver/device相互probe的过程非常类似!
这一切你可以通过strace netstat/ss观察到,而无需去分析netstat/ss的源码。
为了完成这个恶作剧,常规的想法是把/proc/$pid/fd/x给hook住,让本来是一个tcp socket的link显示为指向其它诸如/dev/null。
但是有了对进程到tcp连接的关联过程的理解,拆除它们之间的关联就更容易了,根本不用去hook什么procfs的文件操作接口,如下图所示:
文章图片
这显然是一种比hook procfs文件操作接口更加接近本质的做法。我个人很不喜欢hook procfs,复杂,且不优雅,而且没有从根本上解决问题!我上述图示所描述的方案才是本质的方案。
完成这件事的代码如下:
#!/usr/bin/stap -gfunction relieve(fd:long)
%{
struct dentry *dentry = current->files->fdt->fd[STAP_ARG_fd]->f_path.dentry;
struct inode *ino = current->files->fdt->fd[0]->f_path.dentry->d_inode;
dentry->d_inode = ino;
%}probe kernel.function("__schedule").return
{
if (pid() == $1) {
relieve($2);
exit()
}
}function wakeup(pid:long)
%{
struct task_struct *tsk;
tsk = pid_task(find_vpid(STAP_ARG_pid), PIDTYPE_PID);
if (tsk)
wake_up_process(tsk);
%}probe timer.ms(500)
{
wakeup($1)
}
超级超级超级简单的代码。
解释一下为什么要hook __schedule.return,因为在这个位置可以确保被调度进程自身没有持有自旋锁(持有自旋锁是禁止schedule的),且同一个CPU上的其它进程没有持有自旋锁(否则它就不会被切换出去)来来来,看效果。拿本文开头的例子做实验,我的目的是消去netstat -ntp后面进程pid以及进程名的显示:
[root@localhost test]# for pid in $(netstat -ntp|egrep -o [0-9]+\/|egrep -o [0-9]+);
do ./relieve.stp $pid 3;
done
[root@localhost test]# netstat -ntp
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local AddressForeign AddressStatePID/Program name
tcp00 192.168.56.110:22192.168.56.1:50069ESTABLISHED -
tcp00 192.168.56.110:22192.168.56.1:50048ESTABLISHED -
tcp00 192.168.56.110:22192.168.56.1:50060ESTABLISHED -
tcp00 192.168.56.110:22192.168.56.1:50063ESTABLISHED -
[root@localhost test]#
[root@localhost test]# netstat -ntp|egrep -o [0-9]+\/|egrep -o [0-9]+
[root@localhost test]# echo $?
1
[root@localhost test]#
Oh,yes!
有朋友提示说直接搞没了没意思,最好是混淆进程和tcp连接的关系,更具有迷惑性,OK,这实在是太棒了。简单的代码如下所示:
struct dentry *den1 = tsk1->files->fdt->fd[STAP_ARG_fd1]->f_path.dentry;
struct dentry *den2 = tsk2->files->fdt->fd[STAP_ARG_fd2]->f_path.dentry;
struct inode *tmp;
tmp = den1->d_inode;
den1->d_inode = den2->d_inode;
den2->d_inode = tmp;
用上述替换代码,简单演示一个例子。
首先接入两个tcp连接,如下所示:
文章图片
[root@localhost ~]# netstat -ntp
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local AddressForeign AddressStatePID/Program name
tcp00 192.168.56.110:22192.168.56.1:52105ESTABLISHED 1046/sshd: root@pts
tcp00 192.168.56.110:22192.168.56.1:53346ESTABLISHED 1523/sshd: root@pts
然后用上述代码将二者的f_path.dentry->d_inode进行交换,就混淆了视听,如下所示:
文章图片
[root@localhost ~]# netstat -ntp
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local AddressForeign AddressStatePID/Program name
tcp00 192.168.56.110:22192.168.56.1:52105ESTABLISHED 1523/sshd: root@pts
tcp00 192.168.56.110:22192.168.56.1:53346ESTABLISHED 1046/sshd: root@pts
仔细看,进程和连接之间的关系交换了,如果这个时候运维发现了异常,是不是可以戏弄他们一把呢,至少可以让他们为此召开一个会议吧,哈哈!
什么?可能会宕机?玩这一行的,忍受宕机是必须的素养,要慢慢打磨,才能出精品,哈哈。
至于如何破解这类把戏,很简单,我们不要沿着procfs这条线索去关联,而是沿着socket/sock这条线索去关联即可:
- socket/sock这条线索是斩不断的!
文章图片
后面我会用crash插件来演示,至于今天的把戏,就先到此为止了。
浙江温州皮鞋湿,下雨进水不会胖。
推荐阅读
- 绝不要无端的给自己制造障碍,更不要放弃自己。
- 【春之声】世界经典童话寓言故事连载——聪明的提琴手、王子奇遇记、十二个猎人、恶作剧的精灵
- 如今东莞制造业工厂大量缺少工人,换套薪酬模式让员工拼命干
- Winner稳健医疗全棉水刺无纺布及其制品获制造业单项冠军
- 一只蚂蚱的恶作剧(第二十章)38
- 云制造(智能制造?新制造?)
- 如何引发客户的共鸣()
- 拓端tecdat|R语言时间序列和ARIMA模型预测拖拉机销售的制造案例研究
- 哲元科技×飞桨EasyDL|助力世界500强企业打造“灯塔工厂”,探索智能制造星辰大海
- 零基础,从制造业转互联网,薪资涨幅12k,真的有哪么难吗