taskverse学习

简介

taskverse是《linux二进制分析》一书作者编写的一个隐藏进程的检测工具,它使用/proc/kcore来访问内核内存,github的地址在这里:https://github.com/elfmaster/taskverse。

/proc/kcore

这个文件是内核提供的用来遍历内核内存的接口,使用elf文件格式,他的实现在内核文件fsprockcore.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
");
        exit(-1);
        }
    while(!feof(fd))
    {
        if(fscanf(fd, "%lx %c %s", &address, &type, symbol_s)<=0){
            printf("fscanf wrong
");
            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
");
        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
");
        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
",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
");
            exit(-1);
        }
        if(lseek(fd,k_list->offset+(addr-k_list->vaddr),SEEK_SET)<0){
            printf("lseek /proc/kcore wrong
");
            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
",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
", *statue);
    return 0;
}

taskverse分析

程序入口地址在主目录下的taskverse.c中的mian函数,在taskverse中,可以使用两种寻找符号的方式,一种是kallsyms,另一种是systemmap文件。

沿着主函数,load_live_kcore函数去分析kcore文件,然后取出该文件的一些部分,存放形成一个Elf_t,在遍历段的过程中,作者关注了3个段,一个是文本段,一个是vmalloc使用的段,一个是kmalloc使用的段。地址分别是0xffff880100000000和0xffff880000100000,text段的起始地址放在_text符号的位置。

值得注意的是elf结构中的mem结构,这里的mem结构放了3个内容

1、elf头
2、程序头表
3、text段内容
后面从mem中取值的时候要注意,这里的地址计算
然后就开始获取符号地址,这里获取到init_task的位置,然后kcore_translate_kernel将它翻译为文件地址,最后从文件中读取task_struct结构的内容,遍历tasklist来获取到进程列表,然后和proc目录下的进程进行对比,如果发现不一样则是发现了隐藏进程。
 
原文地址:https://www.cnblogs.com/likaiming/p/11114670.html