linux,core从core映像文件中重新构造ELF可执行文件

本文涉及的产品
运维安全中心(堡垒机),免费版 6个月
运维安全中心(堡垒机),企业双擎版|50资产|一周时长
简介: 创建时间:2001-09-26文章属性:整理文章来源:http://e4gle.org文章提交:e4gle (e4gle_at_hackermail.
创建时间:2001-09-26
文章属性:整理
文章来源: http://e4gle.org
文章提交: e4gle (e4gle_at_hackermail.com)

从core映像文件中重新构造ELF可执行文件
------------------------------------------------

    - Silvio Cesare  <silvio@big.net.au>
    - December 1999
    - http://www.big.net.au/~silvio
    - http://virus.beergrave.net/
整理:e4gle<e4gle@21cn.com> from e4gle.org

目录
-----------------

2.0到2.2内核的改变
绪论
进程映像
core映像
重建可执行文件
重建失败的一些例子
实现


2.0到2.2内核的改变
------------------------------

本文主要是针对linux的2.0.x内核,但是这些代码应该也可以在2.2.x执行.2.0.x内核和2.2.x内
核的内存映像是有区别的,包括ELF的core dump的映像我想也有所改变.译者注:我尽力调试此文档
使它可以适合2.2.x内核.


绪论
------------

这篇文档实践并讲述了在给定一个core dump或者进程映像的快照文件下重新构造ELF可格式的二进制
可执行文件的技术.对于本文的读者,ELF格式的相关知识是必要的.


进程映像
-----------------

简单来说,一个core映像就是进程映像发生dump的那个时候的快照.进程映像包括了许多可加载的程序段
或虚拟内存区.这在一个ELF格式的二进制文件里涉及程序头,在linux内核里涉及到vm_area_struct
结构.一个core dump就是vm_area_struct的dump,而相应的可执行程序头和共享库用来创建进程
映像.在linux里,一组vm_area_struct为proc伪文件系统提供了内存映像.我们看一下下面这个例子,
这是一个拥了libc的典型的映像:

[e4gle@linux]# cat /proc/31189/maps
08048000-0804d000 r-xp 00000000 03:08 243714     /bin/login
0804d000-0804e000 rw-p 00004000 03:08 243714     /bin/login
0804e000-0805a000 rwxp 00000000 00:00 0
40000000-40013000 r-xp 00000000 03:08 304059     /lib/ld-2.1.3.so
40013000-40014000 rw-p 00012000 03:08 304059     /lib/ld-2.1.3.so
40014000-40016000 rw-p 00000000 00:00 0
40016000-40018000 r-xp 00000000 03:08 96347      /lib/security/pam_securetty.so
40018000-40019000 rw-p 00001000 03:08 96347      /lib/security/pam_securetty.so
40019000-4001a000 r-xp 00000000 03:08 96341      /lib/security/pam_nologin.so
4001a000-4001b000 rw-p 00000000 03:08 96341      /lib/security/pam_nologin.so
4001c000-40021000 r-xp 00000000 03:08 304068     /lib/libcrypt-2.1.3.so
40021000-40022000 rw-p 00004000 03:08 304068     /lib/libcrypt-2.1.3.so
40022000-40049000 rw-p 00000000 00:00 0
40049000-40050000 r-xp 00000000 03:08 304304     /lib/libpam.so.0.72
40050000-40051000 rw-p 00006000 03:08 304304     /lib/libpam.so.0.72
40051000-40053000 r-xp 00000000 03:08 304075     /lib/libdl-2.1.3.so
40053000-40055000 rw-p 00001000 03:08 304075     /lib/libdl-2.1.3.so
40055000-40057000 r-xp 00000000 03:08 304307     /lib/libpam_misc.so.0.72
40057000-40058000 rw-p 00001000 03:08 304307     /lib/libpam_misc.so.0.72
40058000-40059000 rw-p 00000000 00:00 0
40059000-40146000 r-xp 00000000 03:08 304066     /lib/libc-2.1.3.so
40146000-4014a000 rw-p 000ec000 03:08 304066     /lib/libc-2.1.3.so
bfff9000-c0000000 rwxp ffffa000 00:00 0


从上面可以看到,我举了一个login程序的例子,首先的两块内存区域用虚拟地址08048000-0804d000
和0804d000-0804e000分别对应了文本段和数据段.注意一下也是有权限设置的.同时内存区域仅仅
由页边界来决定.所有的core dump或映像内存区域都取决于页边界.意思是最小的内存区域就是一个
页的长度.需要注意的是由ELF格式的程序头表现的程序段是和页边界无关的,所以程序段不会在虚拟
内存区域产生映像.后面几个区域是动态链接相关的库的加载,最后一行是栈.


CORE映像
--------------

core映像就是进程dump出来的映像,具有一些额外寄存器的节和一些有用的信息.在一个ELF的core
映像里,进程映像的的内存区域相对应程序段,所以一个core文件拥有一个针对每个虚拟内存空间的
程序头列表.关于寄存器的信息存储在ELF二进制格式的notes节里.从一个core dump或者进程映像
里来重建可执行文件,我们可以忽略寄存器且把精力仅仅集中在内存区域上.


重建可执行文件
--------------------------

从一个core dump的文件里重建可执行文件我们必须从core映像中提取ELF可执行所需的文本段和
数据段对应的内存区域.当在加载代码段的时候,ELF头和程序头也同时加载进内存了(这样可以提高
效率)所以我们可以利用这些来创建可执行映像.可执行的ELF头包括一些象真实的代码段和数据段的
起始地址和大小这样的信息(记住,内存区域取决于页边界).

现在,假如我们只在我们重建的文件中用到代码段和数据段,导致的结果就使我们的可执行程序只可
以工作在被创建这个程序的系统上.因为PLT可能拥有一个共享库函数指向它的加载值.移动二进制
程序会使库函数不同的位置,或者使函数改变位置.所以,只能在重建的系统上运行,要使可以运行在
系统就必须使整个映像(栈除外)包括在重建的可执行程序里,这在下面的程序可以反应出来.


重建失败的一些例子
--------------------------

重建的一些问题,进程映像的快照是实时运行的,并不是起始时间,所以数据段的值可能会被改掉,数据
段是可写的.看看下面的代码

    static int i = 0;

    int main()
    {
        if (i++) exit(0);
        printf("Hi/n");
    }

在这个例子中,重建映像会导致程序立即退出,因为它依靠全局变量i的初始值来判定程序的流程.

实现
----------------------

其实重建可执行映像没用到很高深的理论,它只是把一个只有执行权限的可执行程序复制出来而已.
创建一个core dump不难,只需要给进程发送一个SIGSEGV信号,core映像就从进程映像中被拷贝
到了proc文件系统里了.

--

[e4gle@linux]$ cat test_harness.c
int main()
{
    for (;;) printf("Hi/n");
}
[e4gle@linux]$ gcc test_harness.c -o test_harness
[e4gle@linux]$ ./test_harness   <-验证该程序的输出(e4gle:好像杀不掉了,所以为了便于测试我采用后台运行它,再给它发送一个SIGSEGV信号)
Hi
Hi
Hi
.
.
.
[e4gle@linux]$ ./test_harness >/dev/null &
[1] 15254
[e4gle@linux]# ps -eaf|grep test_harness
root     15254 15229 99 17:16 pts/3    00:00:19 ./test_harness
root     15256 15229  0 17:17 pts/3    00:00:00 grep test_harness
[e4gle@linux]# kill -SIGSEGV 15254       <-使它core dump

[e4gle@linux]$ gcc -o core_reconstruct core_reconstruct.c
[e4gle@linux]$ ./core_reconstruct   <-我们写的提取例程来从core中提出可执行映像
[e4gle@linux]$ ./a.out            <-测试我们提取出来的可执行文件
Hi
Hi
Hi
.
.
.

以下是提取core文件到可执行程序的例程.(e4gle:这个程序还是很容易理解的:)
--------------------------------- CUT ---------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <elf.h>
#include <stdarg.h>
#include <string.h>

void die(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    va_end(ap);
    fputc('/n', stderr);
    exit(1);
}

#define PAGE_SIZE    4096

static char shstr[] =
    "/0"
    ".symtab/0"
    ".strtab/0"
    ".shstrtab/0"
    ".interp/0"
    ".hash/0"
    ".dynsym/0"
    ".dynstr/0"
    ".rel.got/0"
    ".rel.bss/0"
    ".rel.plt/0"
    ".init/0"
    ".plt/0"
    ".text/0"
    ".fini/0"
    ".rodata/0"
    ".data/0"
    ".ctors/0"
    ".dtors/0"
    ".got/0"
    ".dynamic/0"
    ".bss/0"
    ".comment/0"
    ".note"
;

char *xget(int fd, int off, int sz)
{
    char *buf;

    if (lseek(fd, off, SEEK_SET) < 0) die("Seek error");
    buf = (char *)malloc(sz);
    if (buf == NULL) die("No memory");
    if (read(fd, buf, sz) != sz) die("Read error");
    return buf;
}


void do_elf_checks(Elf32_Ehdr *ehdr)
{
        if (strncmp(ehdr->e_ident, ELFMAG, SELFMAG)) die("File not ELF");
        if (ehdr->e_type != ET_CORE) die("ELF type not ET_CORE");
        if (ehdr->e_machine != EM_386 && ehdr->e_machine != EM_486)
                die("ELF machine type not EM_386 or EM_486");
        if (ehdr->e_version != EV_CURRENT) die("ELF version not current");
}

int main(int argc, char *argv[])
{
    Elf32_Ehdr ehdr, *core_ehdr;
    Elf32_Phdr *phdr, *core_phdr, *tmpphdr;
    Elf32_Shdr shdr;
    char *core;
    char *data[2], *core_data[3];
    int prog[2], core_prog[3];
    int in, out;
    int i, p;
    int plen;

    if (argc > 2) die("usage: %s [core-file]");

    if (argc == 2) core = argv[1];
    else core = "core";
    in = open(core, O_RDONLY);
    if (in < 0) die("Coudln't open file: %s", core);

    if (read(in, &ehdr, sizeof(ehdr)) != sizeof(ehdr)) die("Read error");
    do_elf_checks(&ehdr);

    if (lseek(in, ehdr.e_phoff, SEEK_SET) < 0) die("Seek error");
    phdr = (Elf32_Phdr *)malloc(plen = sizeof(Elf32_Phdr)*ehdr.e_phnum);
    if (read(in, phdr, plen) != plen) die("Read error");

for (i = 0; i < ehdr.e_phnum; i++)
printf("0x%x - 0x%x (%i)/n",
phdr[i].p_vaddr, phdr[i].p_vaddr + phdr[i].p_memsz, phdr[i].p_memsz);

/*
    copy segments (in memory)

    prog/data[0] ... text
    prog/data[1] ... data
    prog/data[2] ... dynamic
*/
    for (i = 0, p = 0; i < ehdr.e_phnum; i++) {
        if (
            phdr[i].p_vaddr >= 0x8000000 &&
            phdr[i].p_type == PT_LOAD
        ) {
            prog[p] = i;
            if (p == 1) break;
            ++p;
        }
    }
    if (i == ehdr.e_phnum) die("Couldnt find TEXT/DATA");

    for (i = 0; i < 2; i++) data[i] = xget(
        in,
        phdr[prog[i]].p_offset,
        (phdr[prog[i]].p_memsz + 4095) & 4095
    );

    core_ehdr = (Elf32_Ehdr *)&data[0][0];
    core_phdr = (Elf32_Phdr *)&data[0][core_ehdr->e_phoff];

    for (i = 0, p = 0; i < core_ehdr->e_phnum; i++) {
        if (core_phdr[i].p_type == PT_LOAD) {
            core_prog[p] = i;
            if (p == 0) {
                core_data[0] = &data[0][0];
            } else {
                core_data[1] = &data[1][
                    (core_phdr[i].p_vaddr & 4095)
                ];
                break;
            }
            ++p;
        }
    }
    if (i == core_ehdr->e_phnum) die("No TEXT and DATA segment");
    for (i = 0; i < core_ehdr->e_phnum; i++) {
        if (core_phdr[i].p_type == PT_DYNAMIC) {
            core_prog[2] = i;
            core_data[2] = &data[1][64];
            break;
        }
    }
    if (i == core_ehdr->e_phnum) die("No DYNAMIC segment");

    out = open("a.out", O_WRONLY | O_CREAT | O_TRUNC);
    if (out < 0) die("Coudln't open file: %s", "a.out");

    core_ehdr->e_shoff =
        core_phdr[core_prog[2]].p_offset +
        core_phdr[core_prog[2]].p_filesz +
        sizeof(shstr);

/*
    text
    data
    bss
    dynamic
    shstrtab
*/
    core_ehdr->e_shnum = 6;
    core_ehdr->e_shstrndx = 5;

    for (i = 0; i < 2; i++) {
        Elf32_Phdr *p = &core_phdr[core_prog[i]];
        int sz = p->p_filesz;

        if (lseek(out, p->p_offset, SEEK_SET) < 0) goto cleanup;
        if (write(out, core_data[i], sz) != sz) goto cleanup;
    }

    if (write(out, shstr, sizeof(shstr)) != sizeof(shstr)) goto cleanup;

    memset(&shdr, 0, sizeof(shdr));
    if (write(out, &shdr, sizeof(shdr)) != sizeof(shdr)) goto cleanup;

/*
    text section
*/
    tmpphdr = &core_phdr[core_prog[0]];

    shdr.sh_name = 95;
    shdr.sh_type = SHT_PROGBITS;
    shdr.sh_addr = tmpphdr->p_vaddr;
    shdr.sh_offset = 0;
    shdr.sh_size = tmpphdr->p_filesz;
    shdr.sh_flags = SHF_ALLOC | SHF_EXECINSTR;
    shdr.sh_link = 0;
    shdr.sh_info = 0;
    shdr.sh_addralign = 16;
    shdr.sh_entsize = 0;

    if (write(out, &shdr, sizeof(shdr)) != sizeof(shdr)) goto cleanup;

/*
    data section
*/
    tmpphdr = &core_phdr[core_prog[1]];

    shdr.sh_name = 115;
    shdr.sh_type = SHT_PROGBITS;
    shdr.sh_addr = tmpphdr->p_vaddr;
    shdr.sh_offset = tmpphdr->p_offset;
    shdr.sh_size = tmpphdr->p_filesz;
    shdr.sh_flags = SHF_ALLOC | SHF_WRITE;
    shdr.sh_link = 0;
    shdr.sh_info = 0;
    shdr.sh_addralign = 4;
    shdr.sh_entsize = 0;

    if (write(out, &shdr, sizeof(shdr)) != sizeof(shdr)) goto cleanup;

/*
    dynamic section
*/
    for (i = 0; i < core_ehdr->e_phnum; i++) {
        if (core_phdr[i].p_type == PT_DYNAMIC) {
            tmpphdr = &core_phdr[i];
            break;
        }
    }

    shdr.sh_name = 140;
    shdr.sh_type = SHT_PROGBITS;
    shdr.sh_addr = tmpphdr->p_vaddr;
    shdr.sh_offset = tmpphdr->p_offset;
    shdr.sh_size = tmpphdr->p_memsz;
    shdr.sh_flags = SHF_ALLOC;
    shdr.sh_link = 0;
    shdr.sh_info = 0;
    shdr.sh_addralign = 4;
    shdr.sh_entsize = 8;

    if (write(out, &shdr, sizeof(shdr)) != sizeof(shdr)) goto cleanup;

/*
    bss section
*/
    shdr.sh_name = 149;
    shdr.sh_type = SHT_PROGBITS;
    shdr.sh_addr = tmpphdr->p_vaddr + tmpphdr->p_filesz;
    shdr.sh_offset = tmpphdr->p_offset + tmpphdr->p_filesz;
    shdr.sh_size = tmpphdr->p_memsz - tmpphdr->p_filesz;
    shdr.sh_flags = SHF_ALLOC;
    shdr.sh_link = 0;
    shdr.sh_info = 0;
    shdr.sh_addralign = 1;
    shdr.sh_entsize = 0;

    if (write(out, &shdr, sizeof(shdr)) != sizeof(shdr)) goto cleanup;

/*
    shstrtab
*/

    shdr.sh_name = 17;
    shdr.sh_type = SHT_STRTAB;
    shdr.sh_addr = 0;
    shdr.sh_offset = core_ehdr->e_shoff - sizeof(shstr);
    shdr.sh_size = sizeof(shstr);
    shdr.sh_flags = 0;
    shdr.sh_link = 0;
    shdr.sh_info = 0;
    shdr.sh_addralign = 1;
    shdr.sh_entsize = 0;

    if (write(out, &shdr, sizeof(shdr)) != sizeof(shdr)) goto cleanup;

    return 0;

cleanup:
    unlink("a.out");
    die("Error writing file: %s", "a.out");
    return 1; /* not reached */
}  
目录
相关文章
|
28天前
|
Linux 开发工具 Perl
在Linux中,有一个文件,如何删除包含“www“字样的字符?
在Linux中,如果你想删除一个文件中包含特定字样(如“www”)的所有字符或行,你可以使用多种文本处理工具来实现。以下是一些常见的方法:
41 5
|
28天前
|
安全 Linux 数据安全/隐私保护
在 Linux 系统中,查找文件所有者是系统管理和安全审计的重要技能。
在 Linux 系统中,查找文件所有者是系统管理和安全审计的重要技能。本文介绍了使用 `ls -l` 和 `stat` 命令查找文件所有者的基本方法,以及通过文件路径、通配符和结合其他命令的高级技巧。还提供了实际案例分析和注意事项,帮助读者更好地掌握这一操作。
45 6
|
28天前
|
Linux
在 Linux 系统中,`find` 命令是一个强大的文件查找工具
在 Linux 系统中,`find` 命令是一个强大的文件查找工具。本文详细介绍了 `find` 命令的基本语法、常用选项和具体应用示例,帮助用户快速掌握如何根据文件名、类型、大小、修改时间等条件查找文件,并展示了如何结合逻辑运算符、正则表达式和排除特定目录等高级用法。
67 6
|
29天前
|
监控 Linux Perl
Linux 命令小技巧:显示文件指定行的内容
在 Linux 系统中,处理文本文件是一项常见任务。本文介绍了如何使用 head、tail、sed 和 awk 等命令快速显示文件中的指定行内容,帮助你高效处理文本文件。通过实际应用场景和案例分析,展示了这些命令在代码审查、日志分析和文本处理中的具体用途。同时,还提供了注意事项和技巧,帮助你更好地掌握这些命令。
43 4
|
1月前
|
网络协议 Linux
linux系统重要文件目录
本文介绍了Linux系统中的重要目录及其历史背景,包括根目录、/usr、/etc、/var/log和/proc等目录的结构和功能。其中,/etc目录下包含了许多关键配置文件,如网卡配置、DNS解析、主机名设置等。文章还详细解释了各目录和文件的作用,帮助读者更好地理解和管理Linux系统。
55 2
|
1月前
|
缓存 监控 Linux
|
1月前
|
Linux Shell 数据库
文件查找是Linux用户日常工作的重要技能介绍了几种不常见的文件查找方法
文件查找是Linux用户日常工作的重要技能。本文介绍了几种不常见的文件查找方法,包括使用`find`和`column`组合、`locate`和`mlocate`快速查找、编写Shell脚本、使用现代工具`fd`、结合`grep`搜索文件内容,以及图形界面工具如`Gnome Search Tool`和`Albert`。这些方法能显著提升文件查找的效率和准确性。
48 2
|
1月前
|
Linux 数据库
linux 全局搜索文件
在 Linux 系统中,全局搜索文件常用 `find`、`locate` 和 `grep` 命令。`find` 根据文件名、类型、大小、时间戳等条件搜索;`locate` 通过预构建的数据库快速查找文件;`grep` 在文件中搜索特定文本,常与 `find` 结合使用。选择合适的命令取决于具体需求。
100 2
|
2月前
|
Linux 开发工具 Perl
Linux命令替换目录下所有文件里有"\n"的字符为""如何操作?
【10月更文挑战第20天】Linux命令替换目录下所有文件里有"\n"的字符为""如何操作?
44 4
|
2月前
|
运维 安全 Linux
Linux文件清空的五种方法总结分享
每种方法各有优势,选择最合适的一种或几种,可以极大提高您的工作效率。更多有关Linux系统管理的技巧与资源,欢迎访问,持续提升您的运维技能。
79 1