taskverse学习
简介
taskverse是《linux二进制分析》一书作者编写的一个隐藏进程的检测工具,它使用/proc/kcore来访问内核内存,github的地址在这里:https://github.com/elfmaster/taskverse。
/proc/kcore
这个文件是内核提供的用来遍历内核内存的接口,使用elf文件格式,他的实现在内核文件\fs\proc\kcore.c中,文件的主要操作在proc_kcore_operations中,围绕一个链表kclist_head来组织elf中的各个段。而一个段就代表着一段内存,将这些内存映射成可执行文件的段。看看这些段的信息,在这之前有一个note段,这里面保存着一些其他信息,这里就不分析了,从内核源码里的get_kcore_size可以看出网note段里面放的是什么。
为了解释这个文件的用法,我写了一个简单的例子,代码在这里:https://github.com/smakk/kocre_sample
可以使用readelf -h的命令去查看,可以发现/proc/kcore这个文件是没有节区的,也就是没有办法访问到符号表,这里使用了/proc/kallsyms这个文件来访问符号地址,具体代码如下,根据名字,不断读取符号文件来找到符号地址
//由于/proc/kcore没有节头,找不到符号表,所以通过kallsym来找符号地址 unsigned long get_sym(char* name){ FILE *fd; char symbol_s[255]; int i; unsigned long address; char tmp[255], type; if ((fd = fopen("/proc/kallsyms", "r")) == NULL) { printf("fopen /proc/kallsym wrong\n"); exit(-1); } while(!feof(fd)) { if(fscanf(fd, "%lx %c %s", &address, &type, symbol_s)<=0){ printf("fscanf wrong\n"); exit(-1); } if (strcmp(symbol_s, name) == 0) { fclose(fd); return address; } if (!strcmp(symbol_s, "")) break; } fclose(fd); return 0; }
接着就是/proc/kcore的使用,这里使用一个全局的链表kcore,来表示/kcore的一个段信息中的虚拟地址,文件偏移量和大小,有这3个量就可以访问内存了
struct kcore_list{ struct kcore_list* list; unsigned long vaddr; unsigned long offset; size_t size; }; struct kcore_list kcore; /* 生成kcore链表,按照虚拟地址升序存放kcore提供的所有的地址空间,而且这些地址空间是不重叠的 */ void* init_kcore_list(){ int fd = open("/proc/kcore", O_RDONLY); if(fd < 0){ printf("open /proc/kcore wrong\n"); exit(-1); } Elf64_Ehdr head; read(fd,&head,sizeof(Elf64_Ehdr)); if(lseek(fd,head.e_phoff,SEEK_SET)<0){ printf("lseek /proc/kcore wrong\n"); exit(-1); } Elf64_Phdr phdr[head.e_phnum]; read(fd,&phdr,sizeof(Elf64_Phdr)*head.e_phnum); close(fd); int i; for(i=0;i<head.e_phnum;i++){ struct kcore_list* k_list = malloc(sizeof(struct kcore_list)); k_list->vaddr = phdr[i].p_vaddr; k_list->offset = head.e_phoff+sizeof(Elf64_Phdr)*i; k_list->size = phdr[i].p_memsz; struct kcore_list* tmplist = &kcore; while(tmplist->list != NULL && tmplist->list->vaddr<k_list->vaddr){ tmplist = tmplist->list; } k_list->list = tmplist->list; tmplist->list = k_list; //printf("%lx\n",k_list->vaddr); } return; }
现在已经存储完kcore的段信息,要想访问符号虚拟地址指向处的地址,就是要先找出虚拟地址位于哪个段中,然后转换成这个段在文件中的偏移,最终根据文件偏移去访问文件。代码如下:
/* 根据虚拟地址addr,从/proc/kcore这个位置读取size大小的内存 */ void* get_area(unsigned long addr, size_t size){ void* ret; struct kcore_list* k_list = kcore.list; while(k_list != NULL && addr>k_list->vaddr + k_list->size){ k_list = k_list->list; } if(addr>=k_list->vaddr && addr+size<k_list->vaddr + k_list->size){ int fd = open("/proc/kcore", O_RDONLY); if(fd < 0){ printf("open /proc/kcore wrong\n"); exit(-1); } if(lseek(fd,k_list->offset+(addr-k_list->vaddr),SEEK_SET)<0){ printf("lseek /proc/kcore wrong\n"); exit(-1); } ret = malloc(size); read(fd,ret,size); close(fd); } return ret; }
最后,使用这个借口,我做了一个简单的使用例子,找到init_task的进程描述符,在内核的进程描述符中,第一个字段表示的是进程状态,这里输出init_task的进程状态,打印这和结果。
int main(){ init_kcore_list(); printf("_text addrs is %lx\n",get_sym("_text")); void * code = get_area(get_sym("init_task"),100); unsigned long * statue = (unsigned long *)code; //0代表正在运行,大于0表示停止了 printf("init_task statue is %ld\n", *statue); return 0; }
taskverse分析
程序入口地址在主目录下的taskverse.c中的mian函数,在taskverse中,可以使用两种寻找符号的方式,一种是kallsyms,另一种是systemmap文件。
值得注意的是elf结构中的mem结构,这里的mem结构放了3个内容