建立時間: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 */
}