事先声明,本人菜鸟一枚,文中如有不正确之处,敬请大侠指正.
(本文中举例均以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 "\" /proc/kallsyms
命令就可以把它的地址找到,不过最近内核里不知做了什么,看源码 init_task 明明被导出了,但我的 /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<