From: Wang Jian [lark@marsec.net] Sent: Wednesday, April 17, 2002 12:51 AM To: bugtraq@securityfocus.com; forensics@securityfocus.com Subject: An alternative method to check LKM backdoor/rootkit Hello, I can't find information about the method I find. If I am wrong, I am sorry. PRINCIPLE LKM backdoor plays tricks to hide itself, including its running processes, loadable kernel module and arbitary files. It changes the kernel behavior, and hide things. Because it hides things, it creates a fake view hiding things the installer want to hide. Thus, differences between the real view and the fake view. The differences are some of running processes, files are hidden, or say, stealth. METHODS The discovery of LKM is important for the game. There are some ways to do so, such as using LKM against LKM. There are two styles in all ways: 1. Find the differences between the two views; 2. Find the LKM directly; LKM vs. LKM game involves the 2nd style. THE ALTERNATIVE METHOD Our alternative method uses the first style: to find the differences between the fake view and the real view. And we focus on filesystem view. A LKM backdoor is stealth, or it will be discovered by juse scan the filesystem. So we check if there are stealth files on filesystem. We read the raw disk and traverse the filesystem on disk, bypass the live filesystem, and create a real view of files on disk; then traverse the live filesystem to get the fake view. Compare the two view, we can find the differences. We will find the stealth files. The actual code can do comparision when traversing the filesystem, to save resouces. PROOF OF CONCEPT At the end, there is proof of concept code. The code is for linux and ext2/ext3 filesystem. It has been tested on Mandrake and RedHat. Beware, the code needs e2fsprogs 1.26 or above. A successful compilation on RedHat invovles upgrades e2fsprogs-devel; on Mandrake, you need libext2fs2-devel. The code is used for proof of concept. It is not perfect. For example, I don't add the check for files which points to INODE 0, I think leave it there can give you some fun to play with debug(e2)fs, and prove it works :-) Thanks go to Zhang JiaJun for help me to test this out, and so I can make it some smart when facing adore. Thanks go to Theodore Ts'o 's good libext2fs library (and bad docs), with which I can write simple code. We have developed proof of concept code for solaris/ufs and linux/reiserfs, but we still have some technical problems. We will release them when we resolv them. THE GAME WILL CONTINUE The method utilizes raw disk interface, such as /dev/rdsk/c0t0d0s2 or /dev/hda1. LKM backdoor CAN intecept the interface, of course it is rather complex and error prone. CODE START HERE /* stealth_file_checker.c * * Copyright (C) 2002 Wang Jian, Marsec System Inc. * * http://www.marsec.net/ * * Concept and Programming: * * Wang Jian * * Testing and Suggestion: * * Zhang JiaJun * * * No Warranty. This code is for educational use only, commercial use is * prohibited. * * This small program demonstrates how to detect stealth files/dirs * of lkm on linux ext2/ext3 filesystems. * * compile: gcc -o sfc main.c -lext2fs * * NOTE: You need e2fsprogs-1.26 or above to compile * * usage: run it without args to get hints * */ #include #include #include #include #include #include #include #include ext2_ino_t root, cwd; ext2_filsys fs; ext2_ino_t string_to_inode(ext2_filsys fs, char *str) { ext2_ino_t ino; int ret; ret = ext2fs_namei(fs, root, cwd, str, &ino); if (ret) { return 0; } return ino; } int list_dir_proc(ext2_ino_t dir, int entry, struct ext2_dir_entry *dirent, int offset, int blocksize, char *buf, void *private) { char name[EXT2_NAME_LEN]; char tmp[EXT2_NAME_LEN + 256]; char tmppath[EXT2_NAME_LEN + 256]; int len; char *path; struct stat stat_buf; int flag; char *prefix = (char *)private; DIR * dp; struct dirent *dirp; len = ((dirent->name_len & 0xFF) < EXT2_NAME_LEN) ? (dirent->name_len & 0xFF) : EXT2_NAME_LEN; strncpy(name, dirent->name, len); name[len] = '\0'; if(entry == DIRENT_DELETED_FILE) { return 0; } ext2fs_get_pathname(fs, dir, 0, &path); sprintf(tmp, "%s%s/%s", prefix, path, name); sprintf(tmppath, "%s%s", prefix, path); /* chdir() then readdir() is for adore LKM, anyway it works * for other LKM. */ chdir(tmppath); if( (dp = opendir(".")) == NULL ) { printf("open dir %s error\n", tmppath); exit(1); } flag=1; while ((dirp = readdir(dp)) != NULL) { if (strcmp(dirp->d_name, name) == 0) { flag=0; break; } } closedir(dp); if(flag) { printf("%s\n", tmp); } /* this will print files pointing to Inode 0 */ if (lstat(tmp, &stat_buf)) { printf("%s\n", tmp); } free(path); if (!ext2fs_check_directory(fs, dirent->inode) && strcmp(".", dirent->name) && strcmp("..", dirent->name)) { ext2fs_dir_iterate2(fs, dirent->inode, DIRENT_FLAG_INCLUDE_EMPTY, 0, list_dir_proc, prefix); } return 0; } int main(int argc, char **argv) { errcode_t ret; ext2_ino_t ino; if(argc != 3) { printf( "Usage: %s \n" " device on which filesystem resides\n" " the path that filesystem is mounted on\n", argv[0]); exit(0); } initialize_ext2_error_table(); ret = ext2fs_open(argv[1], 0, 0, 0, unix_io_manager, &fs); if (ret) { com_err(argv[1], ret, "while opening filesystem"); exit(1); } printf("The following files or directories are stealth\n"); root = cwd = EXT2_ROOT_INO; ino = string_to_inode(fs, "/"); ret = ext2fs_dir_iterate2(fs, ino, DIRENT_FLAG_INCLUDE_EMPTY, 0, list_dir_proc, (void *) argv[2]); ext2fs_free(fs); return 0; }