kdress学习
阅读原文时间:2023年07月16日阅读:1

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

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

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

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

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

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

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

meta.infile = strdup(argv[]); // vmlinux,内核解压后的文件
meta.outfile = strdup(argv[]);// tmp目录下的临时文件
meta.symfile = strdup(argv[]); //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[], *s;
loff_t foff;
unsigned long vaddr;
char ch;
char name[];

if ((fd = fopen(meta->symfile, "r")) == NULL) {  
    perror("fopen");  
    exit(-);  
}  
for (c = ; 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) + ;  
    //此时偏移量已经指向了下一个符号,所以这里读出来的应该是下一个符号  
    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(-);  
}

/\*  
 \* Create string table '.strtab' for symtab.  
  \*/  
//将每个符号名称写道字符串表的空间去  
for (offset = , i = ; i < meta.ksymcount; i++) {  
    strcpy(&strtab\[offset\], kallsyms\_entry\[i\].name);  
    offset += strlen(kallsyms\_entry\[i\].name) + ;  
}

/\*  
 \* Add the .symtab section  
 \*/  
//分配符号表的内存空间  
if ((symtab = (ElfW(Sym) \*)malloc(sizeof(ElfW(Sym)) \* meta.ksymcount)) == NULL) {  
    perror("malloc");  
    exit(-);  
}  
//初始化符号表的各个字段  
for (st\_offset = , i = ; i < meta.ksymcount; i++) {  
    symtype = kallsyms\_entry\[i\].symtype == FUNC ? STT\_FUNC : STT\_OBJECT;  
    symtab\[i\].st\_info = (((STB\_GLOBAL) << ) + ((symtype) & 0x0f));  
    symtab\[i\].st\_value = kallsyms\_entry\[i\].addr;  
    symtab\[i\].st\_other = ;  
    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) + ;  
}  
//符号表的地址  
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[];

if ((fd = open(meta->outfile, O\_WRONLY|O\_CREAT|O\_TRUNC, S\_IRWXU)) < ) {  
    perror("open");  
    return -;  
}

/\*  
 \* 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 += ;

/\*  
 \* Write out vmlinux up until where the shdr's originally started  
 \*/  
//一直写到节区表的前面  
if ((b = write(fd, elf->mem, elf->shdr\_offset)) < ) {  
    perror("write");  
    return -;  
}

/\*  
 \* write symtab  
 \*/  
ElfW(Off) new\_e\_shoff;  
//写入符号表  
if ((b = write(fd, elf->new.symtab, meta->symtab\_size)) < ) {  
    perror("write");  
    return -;  
}

/\* write out strtab here  
  \*/  
//写入字符串表  
loff\_t soff = elf->shdr\_offset + meta->symtab\_size;

if ((b = write(fd, elf->new.strtab, strtab\_size)) < ) {  
    perror("write");  
    return -;  
}  
/\*  
 \* write section headers  
 \*/  
//写入原节区表  
if ((b = write(fd, &elf->mem\[elf->shdr\_offset\], elf->shdr\_count \* sizeof(ElfW(Shdr)))) < ) {  
    perror("write");  
    return -;  
}

//写入新的两个节区的表  
/\*  
 \* Add 2 new section headers '.symtab' and '.strtab'  
 \*/  
shdr\[\].sh\_name = ;  
shdr\[\].sh\_type = SHT\_SYMTAB;  
shdr\[\].sh\_link = elf->shdr\_count + ;  
shdr\[\].sh\_addr = ;  
shdr\[\].sh\_offset = elf->shdr\_offset;  
shdr\[\].sh\_size = meta->symtab\_size;  
shdr\[\].sh\_entsize = sizeof(ElfW(Sym));  
shdr\[\].sh\_flags = ;  
shdr\[\].sh\_name = ;  
shdr\[\].sh\_type = SHT\_STRTAB;  
shdr\[\].sh\_link = ;  
shdr\[\].sh\_addr = ;  
shdr\[\].sh\_offset = soff; //shdr\_offset +  + sizeof(ElfW(Sym));  
shdr\[\].sh\_size = strtab\_size;  
shdr\[\].sh\_entsize = ;  
shdr\[\].sh\_flags = ;

loff\_t offset = elf->shdr\_offset + (elf->shdr\_count \* sizeof(ElfW(Shdr)));  
if ((b = write(fd, shdr, sizeof(ElfW(Shdr)) \* )) < ) {  
    perror("write");  
    return -;  
}

/\*  
 \* Write out shdrs  
 \*/  
close(fd);  

}

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章