这两天看了一本书叫《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[]); // 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);
}

kdress学习的更多相关文章

  1. 从直播编程到直播教育:LiveEdu.tv开启多元化的在线学习直播时代

    2015年9月,一个叫Livecoding.tv的网站在互联网上引起了编程界的注意.缘于Pingwest品玩的一位编辑在上网时无意中发现了这个网站,并写了一篇文章<一个比直播睡觉更奇怪的网站:直 ...

  2. Angular2学习笔记(1)

    Angular2学习笔记(1) 1. 写在前面 之前基于Electron写过一个Markdown编辑器.就其功能而言,主要功能已经实现,一些小的不影响使用的功能由于时间关系还没有完成:但就代码而言,之 ...

  3. ABP入门系列(1)——学习Abp框架之实操演练

    作为.Net工地搬砖长工一名,一直致力于挖坑(Bug)填坑(Debug),但技术却不见长进.也曾热情于新技术的学习,憧憬过成为技术大拿.从前端到后端,从bootstrap到javascript,从py ...

  4. 消息队列——RabbitMQ学习笔记

    消息队列--RabbitMQ学习笔记 1. 写在前面 昨天简单学习了一个消息队列项目--RabbitMQ,今天趁热打铁,将学到的东西记录下来. 学习的资料主要是官网给出的6个基本的消息发送/接收模型, ...

  5. js学习笔记:webpack基础入门(一)

    之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...

  6. Unity3d学习 制作地形

    这周学习了如何在unity中制作地形,就是在一个Terrain的对象上盖几座小山,在山底种几棵树,那就讲一下如何完成上述内容. 1.在新键得项目的游戏的Hierarchy目录中新键一个Terrain对 ...

  7. 《Django By Example》第四章 中文 翻译 (个人学习,渣翻)

    书籍出处:https://www.packtpub.com/web-development/django-example 原作者:Antonio Melé (译者注:祝大家新年快乐,这次带来<D ...

  8. 菜鸟Python学习笔记第一天:关于一些函数库的使用

    2017年1月3日 星期二 大一学习一门新的计算机语言真的很难,有时候连函数拼写出错查错都能查半天,没办法,谁让我英语太渣. 关于计算机语言的学习我想还是从C语言学习开始为好,Python有很多语言的 ...

  9. 多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类)

    前言:刚学习了一段机器学习,最近需要重构一个java项目,又赶过来看java.大多是线程代码,没办法,那时候总觉得多线程是个很难的部分很少用到,所以一直没下决定去啃,那些年留下的坑,总是得自己跳进去填 ...

随机推荐

  1. C++cctype软件包函数摆脱,ASCII码!

    对于字符,你是否还在用ASCII码? 下面是C++的函数库,摆脱ASCI码! 1.isalnum(): 判断是否为数字和字母 2.isalpha(): 判断是否是字母 3.iscntrl(): 判断是 ...

  2. javascript数组的增删改和查询

    数组的增删改操作 对数组的增删改操作进行总结,下面(一,二,三)是对数组的增加,修改,删除操作都会改变原来的数组. (一)增加 向末尾增加 push() 返回新增后的数组长度 arr[arr.leng ...

  3. JavaWeb_(Mybatis框架)JDBC操作数据库和Mybatis框架操作数据库区别_一

    系列博文: JavaWeb_(Mybatis框架)JDBC操作数据库和Mybatis框架操作数据库区别_一 传送门 JavaWeb_(Mybatis框架)使用Mybatis对表进行增.删.改.查操作_ ...

  4. AGC032E modulo pairing

    题意 原题 给出\(2n\)个\(\leq m\)的数,求最优的两两配对方案 使\(n\)组\((x,y)\)的\((x+y)mod \space m\)最大值最小 \(n\leq 10^5,m \l ...

  5. 手把手教你在Linux系统下安装MySQL

    在CentOS中默认安装有MariaDB,这个是MySQL的分支,但为了需要,还是要在系统中安装MySQL,而且安装完成之后可以直接覆盖掉MariaDB. 1. 下载并安装MySQL官方的 Yum R ...

  6. 【软件工程】Alpha事后诸葛亮

    链接部分 队名:女生都队 组长博客: 博客链接 作业博客:博客链接 参考邹欣老师的问题模板进行总结思考 一.设想和目标 1.我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的 ...

  7. arcgis python获得字段唯一值

    arcgis python获得字段唯一值 # Import native arcgisscripting moduleimport arcgisscripting, sys# Create the g ...

  8. Flutter移动电商实战 --(8)dio基础_伪造请求头获取数据

    在很多时候,后端为了安全都会有一些请求头的限制,只有请求头对了,才能正确返回数据.这虽然限制了一些人恶意请求数据,但是对于我们聪明的程序员来说,就是形同虚设.这篇文章就以极客时间 为例,讲一下通过伪造 ...

  9. rocketmq备忘

    rocketmq unrecognized VM option 'MetaspaceSize=128m' => jdk1.8 JAVA_HOME https://blog.csdn.net/c3 ...

  10. leetcode1283 使结果不超过阈值的最小除数

    这道题第一思路是用二分查找 因为使用二分法:所以复杂度为O(n*logk), k介于 left=sum/threshold(向下取整) 和 right=num_max之间:而right<=10^ ...