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;
        }
        //計算字符串表的大小,根據所有需要記錄的符號的名字大小,還需要加上'\0'這個字符
        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\n", 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);
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章