本文为看雪论坛优秀文章
看雪论坛作者ID:abcz316
Linux它是一款开源的内核系统。本人也非常喜欢嵌入式Linux系统,特别是它的内核源码,书写的风格,都非常讨我心欢。这个驱动是之前业余的时候写的用于嵌入式开发版,点亮LED灯时候留下的,现在代码里已删除ioremap。不过对于新手来说,至少还是有学习价值的。
源码仅供交流学习之用,不得用于非法用途。
同时为了避免不法分子将此驱动用在非法的用途,我后面也将附上侦测建议。如果您是游戏厂商,那么可以尝试听取我的侦测建议。
下面我将带大家分析这个驱动,看看驱动是怎么写出来的。
首先是打开进程接口。
在Linux内核里,不区分进程与线程。统一按照线程来看待。那么每个线程都有一个对应的pid_t、pid、task_struct。他们之间的关系是这样的:pid_t <–> struct pid
nr为进程pid数值
#include <linux/pid.h>
pid_t pid_vnr(struct pid
*pid)
{
return
pid_nr_ns(pid, current->nsproxy->pid_ns);
}
EXPORT_SYMBOL_GPL(pid_vnr);
struct pid
*find_pid_ns(int
nr, struct pid_namespace
*ns);
EXPORT_SYMBOL_GPL(find_pid_ns);
struct pid
*find_vpid(int
nr)
{
return
find_pid_ns(nr, current->nsproxy->pid_ns);
}
EXPORT_SYMBOL_GPL(find_vpid);
struct pid
*find_get_pid(int
nr)
{
struct pid
*pid;
rcu_read_lock();
pid
=
get_pid(find_vpid(nr));
rcu_read_unlock();
return
pid;
}
EXPORT_SYMBOL_GPL(find_get_pid);
void put_pid(struct pid
*pid);
EXPORT_SYMBOL_GPL(put_pid);
struct pid
*
–> struct task_struct
*
#include <linux/pid.h>
struct pid
*get_task_pid(sturct task_struct
*task, enum pid_type);
EXPORT_SYMBOL_GPL(get_task_pid);
struct task_struct
*pid_task(struct pid
*pid, enum pid_type);
EXPORT_SYMBOL(pid_task);
struct task_struct
*get_pid_task(struct pid
*pid, enum pid_type)
{
struct task_struct
*result;
rcu_read_lock();
result
=
pid_task(pid,
type);
if
(result)
get_task_struct(result);
rcu_read_unlock();
return
result;
}
EXPORT_SYMBOL(get_pid_task);
#include <linux/sched.h>
#define get_task_struct(tsk) do { atomic_inc(&(tsk)->usage); } while (0)
static inline void put_task_struct(struct task_struct
*t)
{
if
(atomic_dec_and_test(&t->usage))
__put_task_struct(t);
}
void __put_task_struct(struct task_struct
*t);
EXPORT_SYMBOL_GPL(__put_task_struct);
看完以上的逻辑。大家是不是柳暗花明又一村,心里开朗了许多,他们之间是可以相互转换的。通过进程pid_t可以拿到pid,通过pid可以拿到task_struct。又可以反过来通过task_struct拿到进程pid。
接下来是关闭进程接口
驱动源码是使用put_pid将进程pid*的使用次数减去1
在Linux内核源码/kernel/pid.c下可以看到
void put_pid(struct pid
*pid)
{
struct pid_namespace
*ns;
if
(!pid)
return;
ns
=
pid->numbers[pid->level].ns;
if
((atomic_read(&pid->count)
==
1) ||
atomic_dec_and_test(&pid->count)) {
kmem_cache_free(ns->pid_cachep, pid);
put_pid_ns(ns);
}
}
EXPORT_SYMBOL_GPL(put_pid);
读、写进程内存接口
首先根据pid*用get_pid_task取出task_struct。再用get_task_mm取出mm_struct结构。因为这个结构包含了进程的内存信息。首先检查内存是否可读if (vma->vm_flags & VM_READ)
如果可读。那么开始计算物理内存地址位置。由于Linux内核默认开启MMU机制,所以只能以页为单位计算物理内存地址。计算物理内存地址的方法有很多。如
pagemap、pgd pud pmd pte、get_user_pages
驱动里演示pagemap。其他方法可自行参考
Linux内核源码/fs/proc/task_mmu.c
知道了物理内存地址后,读、写物理内存地址,Linux内核也有演示:drivers/char/mem.c。写的非常详细。最后还要注意MMU机制的离散内存,即buffer不连续问题
获取进程内存块列表
这个接口没什么技术含量,都是照抄Linux内核源码的代码,fsproctask_mmu.c。核心思想是通过task_struct取出mm_struct,接下来在mm_struct中遍历取出vma。
struct mm_struct {
struct vm_area_struct
*
mmap;
/*
list
of VMAs
*/
struct rb_root mm_rb;
struct vm_area_struct
*
mmap_cache;
/*
last find_vma result
*/
#ifdef CONFIG_MMU
unsigned
long
(*get_unmapped_area) (struct
file
*filp,
unsigned
long
addr, unsigned
long
len,
unsigned
long
pgoff, unsigned
long
flags);
void (*unmap_area) (struct mm_struct
*mm, unsigned
long
addr);
#endif
unsigned
long
mmap_base;
/*
base of mmap area
*/
unsigned
long
mmap_legacy_base;
/*
base of mmap area
in
bottom-up allocations
*/
unsigned
long
task_size;
/*
size of task vm space
*/
unsigned
long
cached_hole_size;
/*
if
non-zero, the largest hole below free_area_cache
*/
unsigned
long
free_area_cache;
/*
first hole of size cached_hole_size
or
larger
*/
unsigned
long
highest_vm_end;
/*
highest vma end address
*/
pgd_t
*
pgd;
atomic_t mm_users;
/*
How many users with user space?
*/
atomic_t mm_count;
/*
How many references to
"struct mm_struct"
(users count as
1)
*/
int
map_count;
/*
number of VMAs
*/
spinlock_t page_table_lock;
/*
Protects page tables
and
some counters
*/
struct rw_semaphore mmap_sem;
struct list_head mmlist;
/*
List
of maybe swapped mm's. These are globally strung
*
together off init_mm.mmlist,
and
are protected
*
by mmlist_lock
*/
unsigned
long
hiwater_rss;
/*
High-watermark of RSS usage
*/
unsigned
long
hiwater_vm;
/*
High-water virtual memory usage
*/
unsigned
long
total_vm;
/*
Total pages mapped
*/
unsigned
long
locked_vm;
/*
Pages that have PG_mlocked
set
*/
unsigned
long
pinned_vm;
/*
Refcount permanently increased
*/
unsigned
long
shared_vm;
/*
Shared pages (files)
*/
unsigned
long
exec_vm;
/*
VM_EXEC & ~VM_WRITE
*/
unsigned
long
stack_vm;
/*
VM_GROWSUP/DOWN
*/
unsigned
long
def_flags;
unsigned
long
nr_ptes;
/*
Page table pages
*/
unsigned
long
start_code, end_code, start_data, end_data;
unsigned
long
start_brk, brk, start_stack;
unsigned
long
arg_start, arg_end, env_start, env_end;
unsigned
long
saved_auxv[AT_VECTOR_SIZE];
/*
for
/proc/PID/auxv
*/
获取进程命令行
mm_struct结构体里面有个arg_start变量,储存的地址值即进程命令行
为了避免不法分子将此驱动用在非法的用途, 在此给出侦测建议:
- 检查是否有/dev/rwProcMem33此文件。
- 检查SELinux是否被关闭。从安卓5.0启用SELinux后,APP想要与驱动进行通讯,必须得关闭SELinux,如果发现SELinux是关闭状态,并且是高版本的安卓系统,那么此安卓使用者必有问题。检测SELinux的方法很多,如open打开某个文件、ioctl等等,如果SELinux是打开状态,那么这些都会直接返回EACCES (Permission denied),提示拒绝访问。另外,在高版本的安卓系统中,如安卓10,如果SELinux是打开的状态,那么lsmod查看驱动列表,也会直接返回Permission denied拒绝访问,如果lsmod能直接显示驱动列表,那么此使用者的SELinux也是有问题的,有可能被关闭了的。
最后开源地址(含demo)
Github跳转
总结:
首先,编译此源码需要一定的技巧,再者,手机在出厂时本身已设置多重障碍用来阻止第三方驱动的加载(即使你拥有root权限也无法加载),此源码仅供交流学习Linux系统使用。