天天看點

linux,core從core映像檔案中重新構造ELF可執行檔案

建立時間:2001-09-26

文章屬性:整理

從core映像檔案中重新構造ELF可執行檔案

------------------------------------------------

    - Silvio Cesare  <[email protected]>

    - December 1999

整理:e4gle<[email protected]> from e4gle.org

目錄

-----------------

2.0到2.2核心的改變

緒論

程序映像

core映像

重建可執行檔案

重建失敗的一些例子

實作

------------------------------

本文主要是針對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

.

[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            <-測試我們提取出來的可執行檔案

以下是提取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;

            }

    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;

    data section

    tmpphdr = &core_phdr[core_prog[1]];

    shdr.sh_name = 115;

    shdr.sh_offset = tmpphdr->p_offset;

    shdr.sh_flags = SHF_ALLOC | SHF_WRITE;

    shdr.sh_addralign = 4;

    dynamic section

            tmpphdr = &core_phdr[i];

    shdr.sh_name = 140;

    shdr.sh_size = tmpphdr->p_memsz;

    shdr.sh_flags = SHF_ALLOC;

    shdr.sh_entsize = 8;

    bss section

    shdr.sh_name = 149;

    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_addralign = 1;

    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;

    return 0;

cleanup:

    unlink("a.out");

    die("Error writing file: %s", "a.out");

    return 1; /* not reached */

}  

繼續閱讀