簡介
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個內容