Загрузка файла ELF в C в пространстве пользователя
Я пытаюсь загрузить файл ELF, скомпилированный с помощью "gcc -m32 test.c -o test.exe" в Linux, в 64-разрядной среде x86. Я пытаюсь загрузить этот 32-битный файл (test.exe) в загрузчик ELF пользовательского пространства, который имеет следующую базовую логику (32-битный ELF).
Проблема заключается в том, что обращение к возвращенному начальному адресу приводит к дампу ядра ошибки сегментации. Вот код:
void *image_load (char *elf_start, unsigned int size)
{
Elf32_Ehdr *hdr = NULL;
Elf32_Phdr *phdr = NULL;
unsigned char *start = NULL;
Elf32_Addr taddr = 0;
Elf32_Addr offset = 0;
int i = 0;
unsigned char *exec = NULL;
Elf32_Addr estart = 0;
hdr = (Elf32_Ehdr *) elf_start;
if(!is_image_valid(hdr)) {
printk("image_load:: invalid ELF image\n");
return 0;
}
exec = (unsigned char *)mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
if(!exec) {
printk("image_load:: error allocating memory\n");
return 0;
}
// Start with clean memory.
memset(exec,0x0,size);
phdr = (Elf32_Phdr *)(elf_start + hdr->e_phoff);
for(i=0; i < hdr->e_phnum; ++i) {
if(phdr[i].p_type != PT_LOAD) {
continue;
}
if(phdr[i].p_filesz > phdr[i].p_memsz) {
printk("image_load:: p_filesz > p_memsz\n");
munmap(exec, size);
return 0;
}
if(!phdr[i].p_filesz) {
continue;
}
// p_filesz can be smaller than p_memsz,
// the difference is zeroe'd out.
start = (unsigned char *) (elf_start + phdr[i].p_offset);
// taddr = phdr[i].p_vaddr + (Elf32_Addr)exec;
if(!estart) {
estart = phdr[i].p_paddr;
}
taddr = (Elf32_Addr)exec + offset + (phdr[i].p_paddr - estart);
memmove((unsigned char *)taddr,
(unsigned char *)start,phdr[i].p_filesz);
offset += (phdr[i].p_memsz + (phdr[i].p_paddr - estart));
if(!(phdr[i].p_flags & PF_W)) {
// Read-only.
mprotect((unsigned char *) taddr,
phdr[i].p_memsz,
PROT_READ);
}
if(phdr[i].p_flags & PF_X) {
// Executable.
mprotect((unsigned char *) taddr,
phdr[i].p_memsz,
PROT_EXEC);
}
}
return (void *)((hdr->e_entry - estart) + (Elf32_Addr)exec);
}/* image_load */
...
int (*main)(int, char **)=image_load(...);
main(argc,argv); // Crash...
...
3 ответа
Пожалуйста, предоставьте полный исполняемый код, включая ELF, который вы пытаетесь загрузить. Я потратил время, чтобы изменить ваш код как можно лучше, и, похоже, это сработает, по крайней мере, для этого простого кода.
Обратите внимание, что загрузчик также должен быть скомпилирован как 32-битный код, вы не можете загрузить 32-битный файл в 64-битный процесс. Кроме того, поскольку вы не загружаете код в исходное место, он должен быть перемещаемым. Наконец, это должен быть статический двоичный файл, потому что вы не загружаете никаких библиотек.
Обновление: ваш код ожидает, что точка входа загруженного кода будет соответствовать int (*main)(int, char **)
прототип, который не имеет место в целом (примечание стороны: main
на самом деле получает третий аргумент, среда тоже). Читайте о состоянии запуска ELF. Если вы вручную создадите макет стека, описанный там, вы должны перейти к точке входа, и она никогда не вернется. В случае программы на C вы можете найти адрес main
и это будет соответствовать прототипу. Однако вы затем пропускаете инициализацию библиотеки C (помните, ваш код не выполняет загрузку библиотеки, поэтому загруженная программа должна быть статически связана), и это может быть проблемой.
Я расширил код необходимыми битами для обработки простой программы на C, разрешив ссылки на libc и вызвав main
,
loader.c:
#include <stdio.h>
#include <string.h>
#include <libelf.h>
#include <sys/mman.h>
#include <dlfcn.h>
void printk(const char* msg)
{
fputs(msg, stderr);
}
int is_image_valid(Elf32_Ehdr *hdr)
{
return 1;
}
void *resolve(const char* sym)
{
static void *handle = NULL;
if (handle == NULL) {
handle = dlopen("libc.so", RTLD_NOW);
}
return dlsym(handle, sym);
}
void relocate(Elf32_Shdr* shdr, const Elf32_Sym* syms, const char* strings, const char* src, char* dst)
{
Elf32_Rel* rel = (Elf32_Rel*)(src + shdr->sh_offset);
int j;
for(j = 0; j < shdr->sh_size / sizeof(Elf32_Rel); j += 1) {
const char* sym = strings + syms[ELF32_R_SYM(rel[j].r_info)].st_name;
switch(ELF32_R_TYPE(rel[j].r_info)) {
case R_386_JMP_SLOT:
case R_386_GLOB_DAT:
*(Elf32_Word*)(dst + rel[j].r_offset) = (Elf32_Word)resolve(sym);
break;
}
}
}
void* find_sym(const char* name, Elf32_Shdr* shdr, const char* strings, const char* src, char* dst)
{
Elf32_Sym* syms = (Elf32_Sym*)(src + shdr->sh_offset);
int i;
for(i = 0; i < shdr->sh_size / sizeof(Elf32_Sym); i += 1) {
if (strcmp(name, strings + syms[i].st_name) == 0) {
return dst + syms[i].st_value;
}
}
return NULL;
}
void *image_load (char *elf_start, unsigned int size)
{
Elf32_Ehdr *hdr = NULL;
Elf32_Phdr *phdr = NULL;
Elf32_Shdr *shdr = NULL;
Elf32_Sym *syms = NULL;
char *strings = NULL;
char *start = NULL;
char *taddr = NULL;
void *entry = NULL;
int i = 0;
char *exec = NULL;
hdr = (Elf32_Ehdr *) elf_start;
if(!is_image_valid(hdr)) {
printk("image_load:: invalid ELF image\n");
return 0;
}
exec = mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
if(!exec) {
printk("image_load:: error allocating memory\n");
return 0;
}
// Start with clean memory.
memset(exec,0x0,size);
phdr = (Elf32_Phdr *)(elf_start + hdr->e_phoff);
for(i=0; i < hdr->e_phnum; ++i) {
if(phdr[i].p_type != PT_LOAD) {
continue;
}
if(phdr[i].p_filesz > phdr[i].p_memsz) {
printk("image_load:: p_filesz > p_memsz\n");
munmap(exec, size);
return 0;
}
if(!phdr[i].p_filesz) {
continue;
}
// p_filesz can be smaller than p_memsz,
// the difference is zeroe'd out.
start = elf_start + phdr[i].p_offset;
taddr = phdr[i].p_vaddr + exec;
memmove(taddr,start,phdr[i].p_filesz);
if(!(phdr[i].p_flags & PF_W)) {
// Read-only.
mprotect((unsigned char *) taddr,
phdr[i].p_memsz,
PROT_READ);
}
if(phdr[i].p_flags & PF_X) {
// Executable.
mprotect((unsigned char *) taddr,
phdr[i].p_memsz,
PROT_EXEC);
}
}
shdr = (Elf32_Shdr *)(elf_start + hdr->e_shoff);
for(i=0; i < hdr->e_shnum; ++i) {
if (shdr[i].sh_type == SHT_DYNSYM) {
syms = (Elf32_Sym*)(elf_start + shdr[i].sh_offset);
strings = elf_start + shdr[shdr[i].sh_link].sh_offset;
entry = find_sym("main", shdr + i, strings, elf_start, exec);
break;
}
}
for(i=0; i < hdr->e_shnum; ++i) {
if (shdr[i].sh_type == SHT_REL) {
relocate(shdr + i, syms, strings, elf_start, exec);
}
}
return entry;
}/* image_load */
int main(int argc, char** argv, char** envp)
{
int (*ptr)(int, char **, char**);
static char buf[1048576];
FILE* elf = fopen(argv[1], "rb");
fread(buf, sizeof buf, 1, elf);
ptr=image_load(buf, sizeof buf);
return ptr(argc,argv,envp);
}
elf.c:
#include <stdio.h>
int main()
{
fprintf(stdout, "Hello world! fprintf=%p, stdout=%p\n", fprintf, stdout);
return 0;
}
тестовый забег:
$ gcc -m32 -g -Wall -ldl -o loader loader.c
$ gcc -m32 -pie -fPIE -o elf elf.c
$ ./loader elf
Hello world! fprintf=0xf7612420, stdout=0xf770e4c0
exec = (без знака char *)mmap(NULL, размер,...
Это пытается загрузить исполняемый файл по произвольному адресу. Неисполняемый PIE исполняемый файл может быть загружен только по адресу, с которым он был связан (обычно 0x08048000
в Linux/ix86).
Проблема возникает, когда вы пытаетесь загрузить программу на C (даже простую, которая печатает hello world).
Если эта программа была динамически связана, это совсем не просто, и у вашего загрузчика есть еще много чего: загрузка и перемещение зависимых общих библиотек, исправление GOT
а также TLS
и т. д. и т. д.
Использование
exec = mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS, hdr, 0);
вместо
exec = mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);