问题
Thanks to the help in this question, the loader can now map a statically compiled hello world into memory and jump somewhere in that memory region. The problem I'm facing now is I seem not to jump to the right address or I'm calling the function in the wrong way (or wrong function?).
Below is the code to try; I can't find in glibc where the loader calls the entry point of the program to verify I'm doing the right thing. I tried:
- Calling _start(void)
- Calling _start(int, char**, char**)
- Calling __libc_start_main with the last parameters set to NULL (found here)
- Calling directly main
- Calling some addresses in (found with gdb in rdi: 400B4D, r8: 4018E0, rcx: 401840)
The entry point is 0x400a30, some instructions below __libc_start_main. With some of them it SIGSEGV, SIGABRT or prints:
hello, world! haswell xeon_phi ../csu/libc-start.c FATAL: kernel too old
__ehdr_start.e_phentsize == sizeof *GL(dl_phdr) unexpected reloc type in static binary FATAL: cannot determine kernel version
__libc_start_main /dev/full /dev/null cannot set %fs base address for thread-local storage : %s%s%s:%u: %s%sAssertion `%s' failed.
%n Unexpected error.
and some hundred of junk lines.
int main(int argc, char* argv[argc+1]) {
FILE *fp = fopen(argv[1], "r");
if (!fp) {
fprintf(stderr, "cannot open file %s", argv[1]);
return 1;
}
fseek(fp, 0L, SEEK_END);
size_t sz = ftell(fp) + 1;
rewind(fp);
char *region = mmap(NULL, sz, PROT_READ, MAP_PRIVATE, fileno(fp), 0);
if (region == MAP_FAILED) {
fprintf(stderr, "could not mmap");
return 1;
}
Elf64_Ehdr elf64Ehdr;
memmove(&elf64Ehdr, region, sizeof(elf64Ehdr));
size_t offset;
Elf64_Phdr elf64Phdr;
for (uint16_t i = 0; i != elf64Ehdr.e_phnum; ++i) {
offset = elf64Ehdr.e_phoff + i * elf64Ehdr.e_phentsize;
memmove(&elf64Phdr, region + offset, sizeof(elf64Phdr));
switch (elf64Phdr.p_type) {
case PT_NULL:
break;
case PT_LOAD:
if (load(&elf64Phdr, region + elf64Phdr.p_offset)) {
exit(EXIT_FAILURE);
}
break;
default:
break;
}
}
printf("jumping to: 0x%x\n", elf64Ehdr.e_entry);
char *argv1[] = {"", NULL};
int ret = ((int (*)(int, char **, char **)) elf64Ehdr.e_entry)(1, argv1, argv1);
return ret;
}
int load(const Elf64_Phdr *phdr, const void *elf_bytes_for_phdr) {
const size_t pagesize = getpagesize();
const size_t unaligned_bytes = phdr->p_vaddr % pagesize;
void *base_addr = phdr->p_vaddr - unaligned_bytes;
size_t total_bytes = phdr->p_memsz + unaligned_bytes;
void *region = mmap(base_addr, total_bytes,
phdr->p_flags | PROT_WRITE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
if (region != MAP_FAILED) {
memset(region, 0, unaligned_bytes);
return memcpy(region + unaligned_bytes, elf_bytes_for_phdr, phdr->p_filesz) != region + unaligned_bytes &&
! mprotect(region, total_bytes, phdr->p_flags);
}
return 1;
}
回答1:
The problem I'm facing now is I seem not to jump to the right address or I'm calling the function in the wrong way
Your problem is neither of the above (although "calling the wrong way" is not too far).
For a statically-linked executable, Elf64_Ehdr.e_entry
is the correct address to call (it points to _start
), and _start
takes no arguments.
The problem is that it is the job of _start
to
- initialize libc, and
- to find correct values of
argc
,argv
andenvp
, and finally - to call
main(argc, argv, envp)
.
The question then is: how can _start
accomplish step 2?
The answer: there is a protocol that Linux kernel implements and _start
uses to accomplish step 2.
In particular, the kernel copies the actual (string) values of argv[0]
, argv[1]
, ... envp[0]
, envp[1]
, etc. to the stack, then the pointers to these strings. There is also something called auxilliary vector.
The _start
expects to find all of this info on the stack, and will misbehave when it doesn't find it. I believe that is the root cause of your current problem.
Here is an article which explains expected setup with references to Linux kernel source code. Another article.
来源:https://stackoverflow.com/questions/55564620/jump-to-entry-point-of-elf-from-loader