/* 
 * scprint.c v0.2.1 - Print list of system call addresses using various means.
 * 
 * Based (very) loosely on http://www.securityfocus.com/infocus/1811
 * 
 * Creates 3 /proc entries that print to dmesg when read:
 *   user_syscall: Prints the syscall table specified by the table= param
 *   idt_syscall: Finds the syscall table by looking up int 0x80 in IDT
 *   sysenter_syscall: Finds the syscalll by looking up the sysenter MSR
 *
 * This module takes 3 optional parameters:
 *  table=0xaddr - manually specify syscall table for /proc/user_syscall
 *  system_call=0xaddr - manually specify the system_call symbol for idt_syscall
 *  se_entry=0xaddr - manually specify the sysenter_entry symbol
 *
 * Check /boot/System.map for symbol addresses if needed.
 * 
 * TODO:
 *   - Properly use /proc files instead of dmesg for output
 *   - Handle keyboard IRQ hook method in 
 *     http://www.phrack.org/phrack/59/p59-0x0e.txt
 *   - Provide ways to check for pagefault handler hooks
 *     to guard against shadow walker.
 */ 

#ifdef MODVERSIONS
#include <linux/modversions.h>
#endif

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <asm/unistd.h>
#include <linux/syscalls.h>
#include <linux/proc_fs.h>

#include <asm/msr.h>
#include <asm/uaccess.h>


static long table = 0;
static long se_entry = 0;
static long system_call = 0;
module_param(table, long, S_IRUGO);
module_param(se_entry, long, S_IRUGO);
module_param(system_call, long, S_IRUGO);


#define READ_ASM 128     /* How far to read into asm */

/* i386 sys_call_table for kernel 2.6.x
 */
char * callz_2p6[] = {
	"sys_restart_syscall",	/* 0 - old "setup()" system call, used for restarting */
	"sys_exit",
	"sys_fork",
	"sys_read",
	"sys_write",
	"sys_open",		/* 5 */
	"sys_close",
	"sys_waitpid",
	"sys_creat",
	"sys_link",
	"sys_unlink",	/* 10 */
	"sys_execve",
	"sys_chdir",
	"sys_time",
	"sys_mknod",
	"sys_chmod",		/* 15 */
	"sys_lchown16",
	"sys_ni_syscall",	/* old break syscall holder */
	"sys_stat",
	"sys_lseek",
	"sys_getpid",	/* 20 */
	"sys_mount",
	"sys_oldumount",
	"sys_setuid16",
	"sys_getuid16",
	"sys_stime",		/* 25 */
	"sys_ptrace",
	"sys_alarm",
	"sys_fstat",
	"sys_pause",
	"sys_utime",		/* 30 */
	"sys_ni_syscall",	/* old stty syscall holder */
	"sys_ni_syscall",	/* old gtty syscall holder */
	"sys_access",
	"sys_nice",
	"sys_ni_syscall",	/* 35 - old ftime syscall holder */
	"sys_sync",
	"sys_kill",
	"sys_rename",
	"sys_mkdir",
	"sys_rmdir",		/* 40 */
	"sys_dup",
	"sys_pipe",
	"sys_times",
	"sys_ni_syscall",	/* old prof syscall holder */
	"sys_brk",		/* 45 */
	"sys_setgid16",
	"sys_getgid16",
	"sys_signal",
	"sys_geteuid16",
	"sys_getegid16",	/* 50 */
	"sys_acct",
	"sys_umount",	/* recycled never used phys() */
	"sys_ni_syscall",	/* old lock syscall holder */
	"sys_ioctl",
	"sys_fcntl",		/* 55 */
	"sys_ni_syscall",	/* old mpx syscall holder */
	"sys_setpgid",
	"sys_ni_syscall",	/* old ulimit syscall holder */
	"sys_olduname",
	"sys_umask",		/* 60 */
	"sys_chroot",
	"sys_ustat",
	"sys_dup2",
	"sys_getppid",
	"sys_getpgrp",	/* 65 */
	"sys_setsid",
	"sys_sigaction",
	"sys_sgetmask",
	"sys_ssetmask",
	"sys_setreuid16",	/* 70 */
	"sys_setregid16",
	"sys_sigsuspend",
	"sys_sigpending",
	"sys_sethostname",
	"sys_setrlimit",	/* 75 */
	"sys_old_getrlimit",
	"sys_getrusage",
	"sys_gettimeofday",
	"sys_settimeofday",
	"sys_getgroups16",	/* 80 */
	"sys_setgroups16",
	"old_select",
	"sys_symlink",
	"sys_lstat",
	"sys_readlink",	/* 85 */
	"sys_uselib",
	"sys_swapon",
	"sys_reboot",
	"old_readdir",
	"old_mmap",		/* 90 */
	"sys_munmap",
	"sys_truncate",
	"sys_ftruncate",
	"sys_fchmod",
	"sys_fchown16",	/* 95 */
	"sys_getpriority",
	"sys_setpriority",
	"sys_ni_syscall",	/* old profil syscall holder */
	"sys_statfs",
	"sys_fstatfs",	/* 100 */
	"sys_ioperm",
	"sys_socketcall",
	"sys_syslog",
	"sys_setitimer",
	"sys_getitimer",	/* 105 */
	"sys_newstat",
	"sys_newlstat",
	"sys_newfstat",
	"sys_uname",
	"sys_iopl",		/* 110 */
	"sys_vhangup",
	"sys_ni_syscall",	/* old "idle" system call */
	"sys_vm86old",
	"sys_wait4",
	"sys_swapoff",	/* 115 */
	"sys_sysinfo",
	"sys_ipc",
	"sys_fsync",
	"sys_sigreturn",
	"sys_clone",		/* 120 */
	"sys_setdomainname",
	"sys_newuname",
	"sys_modify_ldt",
	"sys_adjtimex",
	"sys_mprotect",	/* 125 */
	"sys_sigprocmask",
	"sys_ni_syscall",	/* old "create_module" */
	"sys_init_module",
	"sys_delete_module",
	"sys_ni_syscall",	/* 130:	old "get_kernel_syms" */
	"sys_quotactl",
	"sys_getpgid",
	"sys_fchdir",
	"sys_bdflush",
	"sys_sysfs",		/* 135 */
	"sys_personality",
	"sys_ni_syscall",	/* reserved for afs_syscall */
	"sys_setfsuid16",
	"sys_setfsgid16",
	"sys_llseek",	/* 140 */
	"sys_getdents",
	"sys_select",
	"sys_flock",
	"sys_msync",
	"sys_readv",		/* 145 */
	"sys_writev",
	"sys_getsid",
	"sys_fdatasync",
	"sys_sysctl",
	"sys_mlock",		/* 150 */
	"sys_munlock",
	"sys_mlockall",
	"sys_munlockall",
	"sys_sched_setparam",
	"sys_sched_getparam",   /* 155 */
	"sys_sched_setscheduler",
	"sys_sched_getscheduler",
	"sys_sched_yield",
	"sys_sched_get_priority_max",
	"sys_sched_get_priority_min",  /* 160 */
	"sys_sched_rr_get_interval",
	"sys_nanosleep",
	"sys_mremap",
	"sys_setresuid16",
	"sys_getresuid16",	/* 165 */
	"sys_vm86",
	"sys_ni_syscall",	/* Old sys_query_module */
	"sys_poll",
	"sys_nfsservctl",
	"sys_setresgid16",	/* 170 */
	"sys_getresgid16",
	"sys_prctl",
	"sys_rt_sigreturn",
	"sys_rt_sigaction",
	"sys_rt_sigprocmask",	/* 175 */
	"sys_rt_sigpending",
	"sys_rt_sigtimedwait",
	"sys_rt_sigqueueinfo",
	"sys_rt_sigsuspend",
	"sys_pread64",	/* 180 */
	"sys_pwrite64",
	"sys_chown16",
	"sys_getcwd",
	"sys_capget",
	"sys_capset",	/* 185 */
	"sys_sigaltstack",
	"sys_sendfile",
	"sys_ni_syscall",	/* reserved for streams1 */
	"sys_ni_syscall",	/* reserved for streams2 */
	"sys_vfork",		/* 190 */
	"sys_getrlimit",
	"sys_mmap2",
	"sys_truncate64",
	"sys_ftruncate64",
	"sys_stat64",	/* 195 */
	"sys_lstat64",
	"sys_fstat64",
	"sys_lchown",
	"sys_getuid",
	"sys_getgid",	/* 200 */
	"sys_geteuid",
	"sys_getegid",
	"sys_setreuid",
	"sys_setregid",
	"sys_getgroups",	/* 205 */
	"sys_setgroups",
	"sys_fchown",
	"sys_setresuid",
	"sys_getresuid",
	"sys_setresgid",	/* 210 */
	"sys_getresgid",
	"sys_chown",
	"sys_setuid",
	"sys_setgid",
	"sys_setfsuid",	/* 215 */
	"sys_setfsgid",
	"sys_pivot_root",
	"sys_mincore",
	"sys_madvise",
	"sys_getdents64",	/* 220 */
	"sys_fcntl64",
	"sys_ni_syscall",	/* reserved for TUX */
	"sys_ni_syscall",
	"sys_gettid",
	"sys_readahead",	/* 225 */
	"sys_setxattr",
	"sys_lsetxattr",
	"sys_fsetxattr",
	"sys_getxattr",
	"sys_lgetxattr",	/* 230 */
	"sys_fgetxattr",
	"sys_listxattr",
	"sys_llistxattr",
	"sys_flistxattr",
	"sys_removexattr",	/* 235 */
	"sys_lremovexattr",
	"sys_fremovexattr",
	"sys_tkill",
	"sys_sendfile64",
	"sys_futex",		/* 240 */
	"sys_sched_setaffinity",
	"sys_sched_getaffinity",
	"sys_set_thread_area",
	"sys_get_thread_area",
	"sys_io_setup",	/* 245 */
	"sys_io_destroy",
	"sys_io_getevents",
	"sys_io_submit",
	"sys_io_cancel",
	"sys_fadvise64",	/* 250 */
	"sys_ni_syscall",
	"sys_exit_group",
	"sys_lookup_dcookie",
	"sys_epoll_create",
	"sys_epoll_ctl",	/* 255 */
	"sys_epoll_wait",
 	"sys_remap_file_pages",
 	"sys_set_tid_address",
 	"sys_timer_create",
 	"sys_timer_settime",		/* 260 */
 	"sys_timer_gettime",
 	"sys_timer_getoverrun",
 	"sys_timer_delete",
 	"sys_clock_settime",
 	"sys_clock_gettime",		/* 265 */
 	"sys_clock_getres",
 	"sys_clock_nanosleep",
	"sys_statfs64",
	"sys_fstatfs64",
	"sys_tgkill",	/* 270 */
	"sys_utimes",
 	"sys_fadvise64_64",
	"sys_ni_syscall",	/* sys_vserver */
	"sys_mbind",
	"sys_get_mempolicy",
	"sys_set_mempolicy",
	"sys_mq_open",
	"sys_mq_unlink",
	"sys_mq_timedsend",
	"sys_mq_timedreceive",	/* 280 */
	"sys_mq_notify",
	"sys_mq_getsetattr",
	"sys_ni_syscall",		/* reserved for kexec */
	"sys_waitid",
	"sys_ni_syscall",		/* 285 */ /* available */
	"sys_add_key",
	"sys_request_key",
	"sys_keyctl",
    NULL
};


struct {
    unsigned short limit;
    unsigned int base;
} __attribute__ ((packed)) idtr;

struct {
    unsigned short off1;
    unsigned short sel;
    unsigned char none,flags;
    unsigned short off2;
} __attribute__ ((packed)) idt;

// implementation of memmem since the kernel doesn't provide one for me
// used to find the call <something>(,eax,4) after the idt (something) is
// the location of the sys_call_table
void *memmem(const void* haystack, size_t hl, const void* needle, size_t nl)
{
    int i;
    if (nl>hl) return 0;
    for (i=hl-nl+1; i; --i) {
        if (!memcmp(haystack,needle,nl))
            return (char*)haystack;
        ++haystack;
    }
    return 0;
}

long find_system_call(void)
{
    long sys_call_off = 0;
    // ask processor for interrupt discriptor table
    asm ("sidt %0" : "=m" (idtr));

    /* read-in IDT for 0x80 vector (syscall) */
    memcpy(&idt,(void *)idtr.base+8*0x80,sizeof(idt));
    sys_call_off = (idt.off2 << 16) | idt.off1;

    return sys_call_off;
}

long find_sysenter_entry(void)
{
    long l = 0, h = 0;
    rdmsr(MSR_IA32_SYSENTER_EIP, (unsigned long) l, (unsigned long) h);

    if (h) {
        printk("Woopsies.. Perhaps we're misusing rdmsr. l: 0x%lx, h: 0x%lx\n", 
                l, h);
    }

    return l; /* I think this is correct.. I hope. */
}

// Function will set sct to the address of the syscall table
unsigned long **find_sys_call_table(long sc_asm)
{
    char *p;

    /* we have syscall routine address now, look for syscall table
       dispatch (indirect call) */
    p = (char*)memmem ((void *)sc_asm,READ_ASM,"\xff\x14\x85",3);

    if (p)
        return (unsigned long **)*(unsigned*)(p+3);
    else
        return NULL;
}

ssize_t
user_read(struct file *unused_file, char *buffer, size_t len, loff_t *off)
{
    unsigned long **sctable;
    int z;

    if (table == 0) {
        printk("User did not specify sys_call_table (use table=0xaddr from insmod)\n");
    } else {
        sctable = (unsigned long **)table;
        printk("\nThe user specified table is: %p\n",&sctable[0]);
        printk("---------------------------------\n");
        for(z=0;callz_2p6[z];z++) 
            printk("%p T %s\n", sctable[z], callz_2p6[z]);
    }
    return 0;
}

ssize_t
idt_read(struct file *unused_file, char *buffer, size_t len, loff_t *off)
{
    long found_system_call = find_system_call();
    unsigned long **sctable;
    int z;
    
    if (!system_call && found_system_call) {
        system_call = found_system_call;
    } else if(found_system_call != system_call) {
        printk("\nWARNING! The address you specified for system_call (%lx) is different from the one we detected (%lx)\n",
                system_call, found_system_call);
    }

    if (system_call) {
        sctable = find_sys_call_table(system_call);
        printk("\nThe IDT specified table is: %p\n", &sctable[0]);
        printk("-------------------------------------\n\n");
        for(z=0;callz_2p6[z];z++) 
            printk("%p T %s\n", sctable[z], callz_2p6[z]);
        printk("%p T %s\n", (void*)found_system_call, "system_call");
        //printk("%p T %s\n", (void*)find_kbd_irq(), "i8042_kbd_irq");
    } else {
        printk("Unable to read IDT. Try sysenter or specify the system_call symbol\n");
        printk("using system_call=0xaddr from insmod.\n");
    }

    return 0;
}

ssize_t
sysenter_read(struct file *unused_file, char *buffer, size_t len, loff_t *off)
{
    long found_se_entry = find_sysenter_entry();
    unsigned long **sctable;
    int z;

    if (!se_entry && found_se_entry) {
        se_entry = found_se_entry;
    } else if(found_se_entry != se_entry) {
        printk("\nWARNING! The address you specified for sysenter_entry (%lx) is different from the one we detected (%lx)\n",
                se_entry, found_se_entry);
    }

    if (se_entry) {
        sctable = find_sys_call_table(se_entry);
        printk("\nThe sysenter specified table is: %p\n",&sctable[0]);
        printk("-------------------------------------\n\n");
        for(z=0;callz_2p6[z];z++) 
            printk("%p T %s\n", sctable[z], callz_2p6[z]);
        printk("%p T %s\n", (void*)found_se_entry, "sysenter_entry");
        //printk("%p T %s\n", (void*)find_kbd_irq(), "i8042_kbd_irq");
    } else {
        printk("Unable to read sysenter MSR. Try IDT or specify the sysenter_entry symbol\n");
        printk("using se_entry=0xaddr from insmod.\n");
    }

    return 0;
}

int my_atoi(char *str)
{
    char *beg = str;
    int tot = 0;
    int pow = 1;

    while(*str) str++;
   
    if(str == beg) return -1;
    
    do {
        str--;
        if(*str < '0' || *str > '9') {
            return -1;
        }
        tot += (*str - '0')*pow;
        pow *= 10;
    } while(str != beg);

    return tot; 
}

ssize_t
common_write(struct file *file, const char __user *usr, 
            size_t len, loff_t *off, unsigned long **sctable)
{
    char *syscall = kmalloc(len+1, GFP_KERNEL);
    char *len_str;
    int call_len = 0;
    char *sc_asm = NULL;
    int i;
    
    if(copy_from_user(syscall, usr, len)) {
        printk("\nscprint: Error reading written data\n");
        len = -EFAULT;
        goto out;
    }
    if(syscall[len-1] == '\n') {
        syscall[len-1] = 0;
    }
    syscall[len] = 0;

    if(!(len_str = strchr(syscall, ' '))) {
        printk("\nscprint: Usage: <symbol> <len>\n");
        goto out; 
    }

    *len_str = 0;
    len_str++;
    
    if((call_len = my_atoi(len_str)) == -1) {
        printk("\nscprint: <len> must be base 10\n");
        goto out; 
    }

    for(i = 0; callz_2p6[i]; i++) {
        if(strcmp(syscall, callz_2p6[i]) == 0) {
            break;
        }
    }

    if(!callz_2p6[i]) {

        if(strcmp(syscall, "sysenter_entry") == 0) {
            sc_asm = (char*)se_entry;
        } else if(strcmp(syscall, "system_call") == 0) {
            sc_asm = (char*)system_call;
        } 
        if(!sc_asm) {
            printk("scprint: syscall %s not found\n", syscall);
            goto out;
        }
    } else {
        sc_asm = (char *)sctable[i];
    }

    printk("\nchar %s[] = \n\t\"", syscall);
    
    for(i=0; i < call_len; i++)
    {
        if(i%13 == 12)
            printk("\"\n\t\"");
        printk("\\x%x", sc_asm[i]&0xff);
    }
    printk("\";\n\n");

out: 
    kfree(syscall);
    return len; 
}

ssize_t
user_write(struct file *file, const char __user *usr, size_t len, loff_t *off)
{
    if (table == 0) {
        printk("User did not specify sys_call_table (use table=0xaddr from insmod)\n");
    } else {
        return common_write(file, usr, len, off, (unsigned long **)table);
    }
    return -EINVAL;
}

ssize_t
idt_write(struct file *file, const char __user *usr, size_t len, loff_t *off)
{
    if (!system_call) {
        system_call = find_system_call();
    }

    if (system_call) {
        return common_write(file, usr, len, off, find_sys_call_table(system_call));
    } else {
        printk("Unable to read IDT. Try sysenter or specify the system_call symbol\n");
        printk("using system_call=0xaddr from insmod.\n");
    }
    return -EINVAL;
}

ssize_t
sysenter_write(struct file *file, const char __user *usr, size_t len, loff_t *off)
{

    if (!se_entry) {
        se_entry = find_sysenter_entry();
    }

    if (se_entry) {
        return common_write(file, usr, len, off, find_sys_call_table(se_entry));
    } else {
        printk("Unable to read sysenter MSR. Try IDT or specify the sysenter_entry symbol\n");
        printk("using se_entry=0xaddr from insmod.\n");
    }
    return -EINVAL;
}


static struct file_operations user_ops = {
    read:    user_read,
    write:   user_write
};
static struct file_operations idt_ops = {
    read:    idt_read,
    write:   idt_write
};
static struct file_operations sysent_ops = {
    read:    sysenter_read,
    write:   sysenter_write
};

int init_module(void)
{
    struct proc_dir_entry *entry;

    printk("\nSyscall auditor v0.2\n");
    
    entry = create_proc_entry("user_syscall", S_IRUSR|S_IWUSR, &proc_root);
    entry->proc_fops = &user_ops;
    printk("Installed /proc/user_syscall\n");

    entry = create_proc_entry("idt_syscall", S_IRUSR|S_IWUSR, &proc_root);
    entry->proc_fops = &idt_ops;
    printk("Installed /proc/idt_syscall\n");

    entry = create_proc_entry("sysenter_syscall", S_IRUSR|S_IWUSR, &proc_root);
    entry->proc_fops = &sysent_ops;
    printk("Installed /proc/sysenter_syscall\n");

    return 0;
}

void cleanup_module(void)
{
    remove_proc_entry("sysenter_syscall", &proc_root);
    remove_proc_entry("idt_syscall", &proc_root);
    remove_proc_entry("user_syscall", &proc_root);
  	return;
}
MODULE_LICENSE("GPL");

