Repository: MikhailProg/elf Branch: master Commit: e91be19dd585 Files: 22 Total size: 18.0 KB Directory structure: gitextract_t_ci1blm/ ├── LICENSE ├── README.md └── src/ ├── Makefile ├── aarch64/ │ ├── z_start.S │ ├── z_syscall.S │ └── z_trampo.S ├── amd64/ │ ├── z_start.S │ ├── z_syscall.S │ └── z_trampo.S ├── i386/ │ ├── z_start.S │ ├── z_syscall.S │ └── z_trampo.S ├── loader.c ├── test.sh ├── z_asm.h ├── z_elf.h ├── z_err.c ├── z_printf.c ├── z_syscalls.c ├── z_syscalls.h ├── z_utils.c └── z_utils.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2018 Mikhail Ilyin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # ELF loader A small elf loader. It can load static and dynamically linked ELF EXEC and DYN (pie) binaries. The loader is PIE program that doesn't depend on libc and calls kernel services directly (z_syscall.c). If the loader needs to load a dynamically linked ELF it places an interpreter (usually ld.so) and a requested binary into a memory and then calls the interpreter entry point. ## Build Default build is for amd64: ``` $ make ``` Build for i386: ``` $ make ARCH=i386 ``` Small build (exclude all messages and printf): ``` $ make SMALL=1 ``` ## Load binaries Run basic hello world test: ``` $ ./test.sh default : PASS static : PASS pie : PASS static pie : PASS ``` Run tests if the loader is built for i386: ``` $ M32= ./test.sh ... ``` Load ls: ``` $ ./loader /bin/ls ``` Load galculator: ``` $ ./loader /usr/bin/galculator ``` ================================================ FILE: src/Makefile ================================================ # make ARCH=i386 SMALL=1 DEBUG=1 ARCH ?= amd64 ARCHS32 := i386 ARCHS64 := amd64 aarch64 ARCHS := $(ARCHS32) $(ARCHS64) CFLAGS += -pipe -Wall -Wextra -fPIC -fno-ident -fno-stack-protector -U _FORTIFY_SOURCE LDFLAGS += -nostartfiles -nodefaultlibs -nostdlib LDFLAGS += -pie -e z_start -Wl,-Bsymbolic,--no-undefined,--build-id=none TARGET := loader ifeq "$(filter $(ARCH),$(ARCHS))" "" $(error ARCH='$(ARCH)' is not supported) endif ifeq "$(filter $(ARCH),$(ARCHS32))" "$(ARCH)" CFLAGS += -m32 -DELFCLASS=ELFCLASS32 ASFLAGS += -m32 LDFLAGS += -m32 else CFLAGS += -DELFCLASS=ELFCLASS64 endif ifdef DEBUG CFLAGS += -O0 -g ASFLAGS += -g else CFLAGS += -fvisibility=hidden # Disable unwind info to make prog smaller. CFLAGS += -Os -fno-asynchronous-unwind-tables -fno-unwind-tables LDFLAGS += -s endif OBJS := $(patsubst %.c,%.o, $(wildcard *.c)) OBJS += $(patsubst %.S,%.o, $(wildcard $(ARCH)/*.S)) ifdef SMALL OBJS := $(filter-out z_printf.%,$(OBJS)) OBJS := $(filter-out z_err.%,$(OBJS)) CFLAGS += -DZ_SMALL endif .PHONY: clean all all: $(TARGET) loader: $(OBJS) clean: rm -rf *.o $(TARGET) */*.o ================================================ FILE: src/aarch64/z_start.S ================================================ .text .align 4 .globl z_start .hidden z_start .type z_start,@function z_start: mov x29, #0 mov x30, #0 mov x1, x0 mov x0, sp bl z_entry wfi ================================================ FILE: src/aarch64/z_syscall.S ================================================ .text .align 4 .globl z_syscall .type z_syscall,@function z_syscall: uxtw x8, w0 mov x0, x1 mov x1, x2 mov x2, x3 mov x3, x4 mov x4, x5 mov x5, x6 mov x6, x7 svc 0x0 ret ================================================ FILE: src/aarch64/z_trampo.S ================================================ .text .align 4 .globl z_trampo .type z_trampo,@function z_trampo: mov x3, x0 mov sp, x1 mov x0, x2 br x3 /* Should not reach. */ wfi ================================================ FILE: src/amd64/z_start.S ================================================ .text .align 4 .globl z_start .hidden z_start .type z_start,@function z_start: mov %rsp, %rdi mov %rdx, %rsi call z_entry hlt ================================================ FILE: src/amd64/z_syscall.S ================================================ # User space ABI: rdi, rsi, rdx, rcx, r8, r9, the rest args on the stack # Kernel spaceABI: rax (nsys), rdi, rsi, rdx, r10, r8, r9 (stack pointer) .text .align 4 .globl z_syscall .type z_syscall,@function z_syscall: mov %rdi, %rax mov %rsi, %rdi mov %rdx, %rsi mov %rcx, %rdx mov %r8, %r10 mov %r9, %r8 mov 8(%rsp),%r9 syscall ret ================================================ FILE: src/amd64/z_trampo.S ================================================ .text .align 4 .globl z_trampo .type z_trampo,@function z_trampo: mov %rsi, %rsp jmp *%rdi /* Should not reach. */ hlt ================================================ FILE: src/i386/z_start.S ================================================ .text .globl z_start .hidden z_start .type z_start,@function z_start: mov %esp, %eax push %edx push %eax call z_entry hlt ================================================ FILE: src/i386/z_syscall.S ================================================ .text .globl z_syscall .type z_syscall,@function z_syscall: /* Preserve ABI required registers. */ push %ebp push %edi push %esi push %ebx /* Move arguments to registers. */ mov 44(%esp), %ebp mov 40(%esp), %edi mov 36(%esp), %esi mov 32(%esp), %edx mov 28(%esp), %ecx mov 24(%esp), %ebx mov 20(%esp), %eax /* syscall number */ /* Jump to kernel, return value comes to eax. */ int $0x80 /* Restore preserved registers. */ pop %ebx pop %esi pop %edi pop %ebp ret ================================================ FILE: src/i386/z_trampo.S ================================================ .text .globl z_trampo .type z_trampo,@function z_trampo: mov 4(%esp), %eax mov 8(%esp), %ecx mov 12(%esp), %edx mov %ecx, %esp jmp *%eax /* Should not reach. */ hlt ================================================ FILE: src/loader.c ================================================ #include "z_asm.h" #include "z_syscalls.h" #include "z_utils.h" #include "z_elf.h" #define PAGE_SIZE 4096 #define ALIGN (PAGE_SIZE - 1) #define ROUND_PG(x) (((x) + (ALIGN)) & ~(ALIGN)) #define TRUNC_PG(x) ((x) & ~(ALIGN)) #define PFLAGS(x) ((((x) & PF_R) ? PROT_READ : 0) | \ (((x) & PF_W) ? PROT_WRITE : 0) | \ (((x) & PF_X) ? PROT_EXEC : 0)) #define LOAD_ERR ((unsigned long)-1) static void z_fini(void) { z_printf("Fini at work\n"); } static int check_ehdr(Elf_Ehdr *ehdr) { unsigned char *e_ident = ehdr->e_ident; return (e_ident[EI_MAG0] != ELFMAG0 || e_ident[EI_MAG1] != ELFMAG1 || e_ident[EI_MAG2] != ELFMAG2 || e_ident[EI_MAG3] != ELFMAG3 || e_ident[EI_CLASS] != ELFCLASS || e_ident[EI_VERSION] != EV_CURRENT || (ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN)) ? 0 : 1; } static unsigned long loadelf_anon(int fd, Elf_Ehdr *ehdr, Elf_Phdr *phdr) { unsigned long minva, maxva; Elf_Phdr *iter; ssize_t sz; int flags, dyn = ehdr->e_type == ET_DYN; unsigned char *p, *base, *hint; minva = (unsigned long)-1; maxva = 0; for (iter = phdr; iter < &phdr[ehdr->e_phnum]; iter++) { if (iter->p_type != PT_LOAD) continue; if (iter->p_vaddr < minva) minva = iter->p_vaddr; if (iter->p_vaddr + iter->p_memsz > maxva) maxva = iter->p_vaddr + iter->p_memsz; } minva = TRUNC_PG(minva); maxva = ROUND_PG(maxva); /* For dynamic ELF let the kernel chose the address. */ hint = dyn ? NULL : (void *)minva; flags = dyn ? 0 : MAP_FIXED; flags |= (MAP_PRIVATE | MAP_ANONYMOUS); /* Check that we can hold the whole image. */ base = z_mmap(hint, maxva - minva, PROT_NONE, flags, -1, 0); if (base == (void *)-1) return -1; z_munmap(base, maxva - minva); flags = MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE; /* Now map each segment separately in precalculated address. */ for (iter = phdr; iter < &phdr[ehdr->e_phnum]; iter++) { unsigned long off, start; if (iter->p_type != PT_LOAD) continue; off = iter->p_vaddr & ALIGN; start = dyn ? (unsigned long)base : 0; start += TRUNC_PG(iter->p_vaddr); sz = ROUND_PG(iter->p_memsz + off); p = z_mmap((void *)start, sz, PROT_WRITE, flags, -1, 0); if (p == (void *)-1) goto err; if (z_lseek(fd, iter->p_offset, SEEK_SET) < 0) goto err; if (z_read(fd, p + off, iter->p_filesz) != (ssize_t)iter->p_filesz) goto err; z_mprotect(p, sz, PFLAGS(iter->p_flags)); } return (unsigned long)base; err: z_munmap(base, maxva - minva); return LOAD_ERR; } #define Z_PROG 0 #define Z_INTERP 1 void z_entry(unsigned long *sp, void (*fini)(void)) { Elf_Ehdr ehdrs[2], *ehdr = ehdrs; Elf_Phdr *phdr, *iter; Elf_auxv_t *av; char **argv, **env, **p, *elf_interp = NULL; unsigned long base[2], entry[2]; const char *file; ssize_t sz; int argc, fd, i; (void)fini; argc = (int)*(sp); argv = (char **)(sp + 1); env = p = (char **)&argv[argc + 1]; while (*p++ != NULL) ; av = (void *)p; (void)env; if (argc < 2) z_errx(1, "no input file"); file = argv[1]; for (i = 0;; i++, ehdr++) { /* Open file, read and than check ELF header.*/ if ((fd = z_open(file, O_RDONLY)) < 0) z_errx(1, "can't open %s", file); if (z_read(fd, ehdr, sizeof(*ehdr)) != sizeof(*ehdr)) z_errx(1, "can't read ELF header %s", file); if (!check_ehdr(ehdr)) z_errx(1, "bogus ELF header %s", file); /* Read the program header. */ sz = ehdr->e_phnum * sizeof(Elf_Phdr); phdr = z_alloca(sz); if (z_lseek(fd, ehdr->e_phoff, SEEK_SET) < 0) z_errx(1, "can't lseek to program header %s", file); if (z_read(fd, phdr, sz) != sz) z_errx(1, "can't read program header %s", file); /* Time to load ELF. */ if ((base[i] = loadelf_anon(fd, ehdr, phdr)) == LOAD_ERR) z_errx(1, "can't load ELF %s", file); /* Set the entry point, if the file is dynamic than add bias. */ entry[i] = ehdr->e_entry + (ehdr->e_type == ET_DYN ? base[i] : 0); /* The second round, we've loaded ELF interp. */ if (file == elf_interp) { z_close(fd); break; } for (iter = phdr; iter < &phdr[ehdr->e_phnum]; iter++) { if (iter->p_type != PT_INTERP) continue; elf_interp = z_alloca(iter->p_filesz); if (z_lseek(fd, iter->p_offset, SEEK_SET) < 0) z_errx(1, "can't lseek interp segment"); if (z_read(fd, elf_interp, iter->p_filesz) != (ssize_t)iter->p_filesz) z_errx(1, "can't read interp segment"); if (elf_interp[iter->p_filesz - 1] != '\0') z_errx(1, "bogus interp path"); file = elf_interp; } z_close(fd); /* Looks like the ELF is static -- leave the loop. */ if (elf_interp == NULL) break; } /* Reassign some vectors that are important for * the dynamic linker and for lib C. */ #define AVSET(t, v, expr) case (t): (v)->a_un.a_val = (expr); break while (av->a_type != AT_NULL) { switch (av->a_type) { AVSET(AT_PHDR, av, base[Z_PROG] + ehdrs[Z_PROG].e_phoff); AVSET(AT_PHNUM, av, ehdrs[Z_PROG].e_phnum); AVSET(AT_PHENT, av, ehdrs[Z_PROG].e_phentsize); AVSET(AT_ENTRY, av, entry[Z_PROG]); AVSET(AT_EXECFN, av, (unsigned long)argv[1]); AVSET(AT_BASE, av, elf_interp ? base[Z_INTERP] : av->a_un.a_val); } ++av; } #undef AVSET ++av; /* Shift argv, env and av. */ z_memcpy(&argv[0], &argv[1], (unsigned long)av - (unsigned long)&argv[1]); /* SP points to argc. */ (*sp)--; z_trampo((void (*)(void))(elf_interp ? entry[Z_INTERP] : entry[Z_PROG]), sp, z_fini); /* Should not reach. */ z_exit(0); } ================================================ FILE: src/test.sh ================================================ #!/bin/sh TEST_DIR=/tmp/loadertests TEST_SRC=${TEST_DIR}/test.c CFLAGS="-Wall -Wextra -s ${M32+-m32}" set -eu trap 'rm -rf $TEST_DIR' EXIT PATH=.:$PATH command -v loader > /dev/null || { echo >&2 "error: build the project"; exit 1; } mkdir -p $TEST_DIR cat > $TEST_SRC << EOF #include int main() { printf("Hello %s!\n", "world"); return 0; } EOF for opt in "default@" "static@ -static" "pie@ -fPIE" "static pie@ -static-pie -fPIE"; do label=${opt%@*} flags=${opt#*@} out=$TEST_DIR/$label printf "%-15s: " "$label" cc $CFLAGS $flags "$TEST_SRC" -o "$out" loader "$out" >/dev/null 2>&1 && echo PASS || echo FAIL done ================================================ FILE: src/z_asm.h ================================================ #ifndef Z_ASM_H #define Z_ASM_H #define PUBLIC __attribute__((visibility ("default"))) #define PRIVATE __attribute__((visibility ("hidden"))) PRIVATE void z_start(void); PRIVATE void z_trampo(void (*entry)(void), unsigned long *sp, void (*fini)(void)); PRIVATE long z_syscall(int n, ...); #endif /* Z_ASM_H */ ================================================ FILE: src/z_elf.h ================================================ #ifndef Z_ELF_H #define Z_ELF_H #include #if ELFCLASS == ELFCLASS64 # define Elf_Ehdr Elf64_Ehdr # define Elf_Phdr Elf64_Phdr # define Elf_auxv_t Elf64_auxv_t #elif ELFCLASS == ELFCLASS32 # define Elf_Ehdr Elf32_Ehdr # define Elf_Phdr Elf32_Phdr # define Elf_auxv_t Elf32_auxv_t #else # error "ELFCLASS is not defined" #endif #endif /* Z_ELF_H */ ================================================ FILE: src/z_err.c ================================================ #include "z_syscalls.h" #include "z_utils.h" void z_errx(int eval, const char *fmt, ...) { va_list ap; z_fdprintf(2, "error: "); va_start(ap, fmt); z_vfdprintf(2, fmt, ap); va_end(ap); z_fdprintf(2, "\n"); z_exit(eval); } ================================================ FILE: src/z_printf.c ================================================ #include #include #include "z_syscalls.h" static int lastfd = -1; #define OUTBUFSIZE 128 static char outbuf[OUTBUFSIZE]; static char *outptr; static void kprintn(int, unsigned long, int); static void kdoprnt(int, const char *, va_list); static void z_flushbuf(void); static void putcharfd(int, int ); static void putcharfd(int c, int fd) { char b = c; int len; if (fd != lastfd) { z_flushbuf(); lastfd = fd; } *outptr++ = b; len = outptr - outbuf; if ((len >= OUTBUFSIZE) || (b == '\n') || (b == '\r')) { z_flushbuf(); } } static void z_flushbuf() { int len = outptr - outbuf; if (len != 0) { if (lastfd != -1) z_write(lastfd, outbuf, len); outptr = outbuf; } } void z_printf(const char *fmt, ...) { va_list ap; va_start(ap, fmt); kdoprnt(2, fmt, ap); va_end(ap); } void z_vprintf(const char *fmt, va_list ap) { kdoprnt(2, fmt, ap); } void z_fdprintf(int fd, const char *fmt, ...) { va_list ap; va_start(ap, fmt); kdoprnt(fd, fmt, ap); va_end(ap); } void z_vfdprintf(int fd, const char *fmt, va_list ap) { kdoprnt(fd, fmt, ap); } static void kdoprnt(int fd, const char *fmt, va_list ap) { unsigned long ul; int lflag, ch; char *p; static int init; if (!init) { outptr = outbuf; init = 1; } for (;;) { while ((ch = *fmt++) != '%') { if (ch == '\0') return; putcharfd(ch, fd); } lflag = 0; reswitch: switch (ch = *fmt++) { case 'l': lflag = 1; goto reswitch; case 'c': ch = va_arg(ap, int); putcharfd(ch & 0x7f, fd); break; case 's': p = va_arg(ap, char *); while ((ch = *p++)) putcharfd(ch, fd); break; case 'd': ul = lflag ? va_arg(ap, long) : va_arg(ap, int); if ((long)ul < 0) { putcharfd('-', fd); ul = -(long)ul; } kprintn(fd, ul, 10); break; case 'o': ul = lflag ? va_arg(ap, unsigned long) : va_arg(ap, unsigned int); kprintn(fd, ul, 8); break; case 'u': ul = lflag ? va_arg(ap, unsigned long) : va_arg(ap, unsigned int); kprintn(fd, ul, 10); break; case 'p': putcharfd('0', fd); putcharfd('x', fd); lflag += sizeof(void *)==sizeof(unsigned long)? 1 : 0; /* FALLTHRU */ case 'x': ul = lflag ? va_arg(ap, unsigned long) : va_arg(ap, unsigned int); kprintn(fd, ul, 16); break; case 'X': { int l; ul = lflag ? va_arg(ap, unsigned long) : va_arg(ap, unsigned int); if (lflag) l = (sizeof(unsigned long) * 8) - 4; else l = (sizeof(unsigned int) * 8) - 4; while (l >= 0) { putcharfd("0123456789abcdef"[(ul >> l) & 0xf], fd); l -= 4; } break; } default: putcharfd('%', fd); if (lflag) putcharfd('l', fd); putcharfd(ch, fd); } } z_flushbuf(); } static void kprintn(int fd, unsigned long ul, int base) { /* hold a long in base 8 */ char *p, buf[(sizeof(long) * 8 / 3) + 1]; p = buf; do { *p++ = "0123456789abcdef"[ul % base]; } while (ul /= base); do { putcharfd(*--p, fd); } while (p > buf); } ================================================ FILE: src/z_syscalls.c ================================================ #include #include "z_asm.h" #include "z_syscalls.h" static int errno; int *z_perrno(void) { return &errno; } static long check_error(long rc) { if (rc < 0 && rc > -4096) { errno = -rc; rc = -1; } return rc; } #define SYSCALL(name, ...) check_error(z_syscall(SYS_##name, __VA_ARGS__)) #define DEF_SYSCALL1(ret, name, t1, a1) \ ret z_##name(t1 a1) \ { \ return (ret)SYSCALL(name, a1); \ } #define DEF_SYSCALL2(ret, name, t1, a1, t2, a2) \ ret z_##name(t1 a1, t2 a2) \ { \ return (ret)SYSCALL(name, a1, a2); \ } #define DEF_SYSCALL3(ret, name, t1, a1, t2, a2, t3, a3) \ ret z_##name(t1 a1, t2 a2, t3 a3) \ { \ return (ret)SYSCALL(name, a1, a2, a3); \ } DEF_SYSCALL3(int, openat, int, dirfd, const char *, filename, int, flags) DEF_SYSCALL3(ssize_t, read, int, fd, void *, buf, size_t, count) DEF_SYSCALL3(ssize_t, write, int, fd, const void *, buf, size_t, count) DEF_SYSCALL1(int, close, int, fd) DEF_SYSCALL3(int, lseek, int, fd, off_t, off, int, whence) DEF_SYSCALL1(int, exit, int, status) DEF_SYSCALL2(int, munmap, void *, addr, size_t, length) DEF_SYSCALL3(int, mprotect, void *, addr, size_t, length, int, prot) int z_open(const char * filename, int flags) { return z_openat(AT_FDCWD, filename, flags); } void * z_mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) { /* i386 has map (old_mmap) and mmap2, old_map is a legacy single arg * function, use mmap2 but it needs offset in page units. * In same time mmap2 does not exist on x86-64. */ #ifdef SYS_mmap2 return (void *)SYSCALL(mmap2, addr, length, prot, flags, fd, offset >> 12); #else return (void *)SYSCALL(mmap, addr, length, prot, flags, fd, offset); #endif } ================================================ FILE: src/z_syscalls.h ================================================ #ifndef Z_SYSCALLS_H #define Z_SYSCALLS_H #include #include #include #include #include #define z_errno (*z_perrno()) int z_exit(int status); int z_open(const char *pathname, int flags); int z_openat(int dirfd, const char *pathname, int flags); int z_close(int fd); int z_lseek(int fd, off_t offset, int whence); ssize_t z_read(int fd, void *buf, size_t count); ssize_t z_write(int fd, const void *buf, size_t count); void *z_mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); int z_munmap(void *addr, size_t length); int z_mprotect(void *addr, size_t length, int prot); int *z_perrno(void); #endif /* Z_SYSCALLS_H */ ================================================ FILE: src/z_utils.c ================================================ #include void *z_memset(void *s, int c, size_t n) { unsigned char *p = s, *e = p + n; while (p < e) *p++ = c; return s; } void *z_memcpy(void *dest, const void *src, size_t n) { unsigned char *d = dest; const unsigned char *p = src, *e = p + n; while (p < e) *d++ = *p++; return dest; } ================================================ FILE: src/z_utils.h ================================================ #ifndef Z_UTILS_H #define Z_UTILS_H #include #include #include #include #define z_alloca __builtin_alloca void *z_memset(void *s, int c, size_t n); void *z_memcpy(void *dest, const void *src, size_t n); void z_vprintf(const char *fmt, va_list ap); void z_vfdprintf(int fd, const char *fmt, va_list ap); void z_printf(const char *fmt, ...) __attribute__ ((format (printf, 1, 2))); void z_fdprintf(int fd, const char *fmt, ...) __attribute__ ((format (printf, 2, 3))); void z_errx(int eval, const char *fmt, ...) __attribute__ ((format (printf, 2, 3))); #ifdef Z_SMALL # define z_errx(eval, fmt, ...) z_exit(eval) # define z_printf(fmt, ...) do {} while(0) # define z_fdprintf(fd, fmt, ...) do {} while(0) #endif #endif /* Z_UTILS_H */