这两天看了一本书叫《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);
}
手机扫一扫
移动阅读更方便
你可能感兴趣的文章