zoukankan      html  css  js  c++  java
  • kdress学习

    这两天看了一本书叫《linux二进制分析》,这里面提到的一个小工具kdress,这里分析一下

    源码在:https://github.com/elfmaster/kdress

    kdress介绍

    /boot目录下有一个vmlinux的文件,这是一个经过压缩的linux内核,不过缺少内核符号表,kdress就是用来从/proc/kallsyms或是System.map文件获取相关的符号信息,将获取到的符号信息重建到内核可执行文件中去。

    源码分析

    首先从一个python脚本启动,然后调用c语言的实现

    kunpress接受两个参数,第一个是输入的无符号表的vmlinux,第二个是输出文件,这个文件的作用就是解压vmlinux到指定输出

    build_ksyms是建立新ELF文件的核心实现,主要看这里的实现

    main函数中,显示保存了输出参数的几个文件位置

    meta.infile = strdup(argv[1]); // vmlinux,内核解压后的文件
    meta.outfile = strdup(argv[2]);// tmp目录下的临时文件
    meta.symfile = strdup(argv[3]);    //systemmap,符号文件

    然后是两个地址,表示text段的开始和结束,这标志了代码地址的有效范围

    low_limit = elf.seg_vaddr[TEXT];
    high_limit = elf.seg_vaddr[DATA1];
    calculate_symtab_size函数计算符号表大小,找到所有的位于text段的符号,计数,然后乘以符号表条目大小
    //计算符号表大小,找到所有的位于text段的符号,计数,然后乘以符号表条目大小
    static size_t calculate_symtab_size(struct metadata *meta)
    {
        FILE *fd;
        size_t c;
        char line[256], *s;
        loff_t foff;
        unsigned long vaddr;
        char ch;
        char name[128];
    
        if ((fd = fopen(meta->symfile, "r")) == NULL) {
            perror("fopen");
            exit(-1);
        }
        for (c = 0; fgets(line, sizeof(line), fd) != NULL; c++) {
                    //从systemmap中读取一行
                    sscanf(line, "%lx %c %s", &sysmap_entry.addr, &sysmap_entry.c, sysmap_entry.name);
            //判断符号是不是位于代码段
            if (!validate_va_range(sysmap_entry.addr)) {
                            c--;
                            continue;
                    }
                    //将这一行的数据读进kallsyms_entry中去
                    sscanf (line, "%lx %c %s", &kallsyms_entry[c].addr, &kallsyms_entry[c].c,
                            kallsyms_entry[c].name);
            switch(toupper(kallsyms_entry[c].c)) {
                case 'T': // text segment
                    kallsyms_entry[c].symtype = FUNC; //.text function
                    break;
                case 'R':
                    kallsyms_entry[c].symtype = OBJECT; //.rodata object
                    break;
                case 'D':
                    kallsyms_entry[c].symtype = OBJECT; //.data object
                    break;
            }
            //计算字符串表的大小,根据所有需要记录的符号的名字大小,还需要加上''这个字符
            strtab_size += strlen(kallsyms_entry[c].name) + 1;
            //此时偏移量已经指向了下一个符号,所以这里读出来的应该是下一个符号
            foff = ftell(fd);
            s = get_line_by_offset(meta->symfile, foff);
            sscanf(s, "%lx %c %s", &vaddr, &ch, name);
            //然后应下一个符号的地址减去这个符号的地址,最后算出这个符号所指向的代码大小
            kallsyms_entry[c].size = vaddr - sysmap_entry.addr;
        }
        
        meta->ksymcount = c;
            return c * sizeof(ElfW(Sym));
    }

    整个程序的任务是为vmlinux加入符号表,需要插入两个节,符号节和字符串节,其中字符串节用来存放符号的名字,当获得了符号的数量之后,就开始分配内存,并且按照符号表的格式和字符串表的格式填充这些数据

        //分配字符串表的空间
        if ((strtab = (char *)malloc(strtab_size)) == NULL) {
            perror("malloc");
            exit(-1);
        }
    
        /*
         * Create string table '.strtab' for symtab.
          */
        //将每个符号名称写道字符串表的空间去
        for (offset = 0, i = 0; i < meta.ksymcount; i++) {
            strcpy(&strtab[offset], kallsyms_entry[i].name);
            offset += strlen(kallsyms_entry[i].name) + 1;
        }
    
        /*
         * Add the .symtab section
         */
        //分配符号表的内存空间
        if ((symtab = (ElfW(Sym) *)malloc(sizeof(ElfW(Sym)) * meta.ksymcount)) == NULL) {
            perror("malloc");
            exit(-1);
        }
        //初始化符号表的各个字段
        for (st_offset = 0, i = 0; i < meta.ksymcount; i++) {
            symtype = kallsyms_entry[i].symtype == FUNC ? STT_FUNC : STT_OBJECT;
            symtab[i].st_info = (((STB_GLOBAL) << 4) + ((symtype) & 0x0f));
            symtab[i].st_value = kallsyms_entry[i].addr;
            symtab[i].st_other = 0;
            symtab[i].st_shndx = get_section_index_by_address(&elf, symtab[i].st_value);
            //字符串表的索引
            symtab[i].st_name = st_offset;
            //这段代码的大小
            symtab[i].st_size = kallsyms_entry[i].size;
            strcpy(&strtab[st_offset], kallsyms_entry[i].name);
            st_offset += strlen(kallsyms_entry[i].name) + 1;
        }
        //符号表的地址
        elf.new.symtab = symtab;
        //字符串表的地址
        elf.new.strtab = strtab;

    最后就是create_new_binary,这是生成最终可执行文件的步骤,思想就是先写入原文件,直到写到末尾的节区表之前,然后添加自定义的两个节,最后再将节区表写进去。当然各个字段是需要做修改的,具体代码如下了:

    int create_new_binary(elftype_t *elf, struct metadata *meta)
    {
        int fd;
        size_t b;
        ElfW(Shdr) shdr[2];
    
        if ((fd = open(meta->outfile, O_WRONLY|O_CREAT|O_TRUNC, S_IRWXU)) < 0) {
            perror("open");
            return -1;
        }
    
        /*
         * Write out first part of vmlinux (all of it actually, up until where shdrs start)
         */
    #if DEBUG
        printf("[DEBUG] writing first %u bytes of original vmlinux into new
    ", elf->shdr_offset);
    #endif
        int i;
        
        /*
         * Adjust new ELF file header, namely the e_shoff
         */
        //调整elf头,增加节头数量,因为节表在可执行文件的末尾,所以节表的大小需要相应的调整两个大小
        ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)elf->mem;
        ehdr->e_shoff += meta->symtab_size;
        ehdr->e_shoff += strtab_size;
        ehdr->e_shnum += 2;
    
        /*
         * Write out vmlinux up until where the shdr's originally started
         */
        //一直写到节区表的前面
        if ((b = write(fd, elf->mem, elf->shdr_offset)) < 0) {
            perror("write");
            return -1;
        }
        
        /*
         * write symtab  
         */
        ElfW(Off) new_e_shoff;
        //写入符号表
        if ((b = write(fd, elf->new.symtab, meta->symtab_size)) < 0) {
            perror("write");
            return -1;
        }
        
        /* write out strtab here
          */
        //写入字符串表
        loff_t soff = elf->shdr_offset + meta->symtab_size;
    
        if ((b = write(fd, elf->new.strtab, strtab_size)) < 0) {
            perror("write");
            return -1;
        }    
        /*
         * write section headers
         */
        //写入原节区表
        if ((b = write(fd, &elf->mem[elf->shdr_offset], elf->shdr_count * sizeof(ElfW(Shdr)))) < 0) {
            perror("write");
            return -1;
        }
        
        //写入新的两个节区的表
        /*
         * Add 2 new section headers '.symtab' and '.strtab'
         */
        shdr[0].sh_name = 0;
        shdr[0].sh_type = SHT_SYMTAB;
        shdr[0].sh_link = elf->shdr_count + 1;
        shdr[0].sh_addr = 0;
        shdr[0].sh_offset = elf->shdr_offset; 
        shdr[0].sh_size = meta->symtab_size;
        shdr[0].sh_entsize = sizeof(ElfW(Sym));
        shdr[0].sh_flags = 0;
        shdr[1].sh_name = 0;
        shdr[1].sh_type = SHT_STRTAB;
        shdr[1].sh_link = 0;
        shdr[1].sh_addr = 0;
        shdr[1].sh_offset = soff; //shdr_offset +  + sizeof(ElfW(Sym));
        shdr[1].sh_size = strtab_size;
        shdr[1].sh_entsize = 0;
        shdr[1].sh_flags = 0;
    
        loff_t offset = elf->shdr_offset + (elf->shdr_count * sizeof(ElfW(Shdr)));
        if ((b = write(fd, shdr, sizeof(ElfW(Shdr)) * 2)) < 0) {
            perror("write");
            return -1;
        }
        
        /* 
         * Write out shdrs
         */
        close(fd);
    }
  • 相关阅读:
    Shell 函数
    Shell 流程控制
    Shell test 命令
    Shell echo命令
    python 类、模块、包的区别
    postgresql vacuum table
    ssh连接断开后 shell进程退出
    ubuntu 搭建 svn服务器,使用http方式访问
    如何查看apache加载了哪些模块
    maven 的使用
  • 原文地址:https://www.cnblogs.com/likaiming/p/11083592.html
Copyright © 2011-2022 走看看