commit MAJOR elf crimes to invoke ld.so at runtime

This commit is contained in:
xenia 2020-10-19 02:35:20 -04:00
parent fb05155915
commit f6da2eb731
6 changed files with 258 additions and 9 deletions

View File

@ -17,11 +17,17 @@ ALPINE_URL="http://dl-cdn.alpinelinux.org/alpine/v3.12/releases/x86_64/$(ALPINE_
# container is built and stored in here # container is built and stored in here
CACHE_DIR=.cache CACHE_DIR=.cache
PATCHELF=patchelf
OBJCOPY=$(CROSS_COMPILE)objcopy
OBJDUMP=$(CROSS_COMPILE)objdump
all: setup exec-in-container all: setup exec-in-container
# rerun inside container # rerun inside container
exec-in-container: setup exec-in-container: setup
rsync -av $(RKT_NAME) main_bc.c Makefile $(CACHE_DIR)/alpine/build/ rsync -avR $(RKT_NAME) main_bc.c startup.c Makefile scripts/crimes.c scripts/crimes.h \
$(CACHE_DIR)/alpine/build/
sudo chmod 777 $(CACHE_DIR)/alpine/build $(CACHE_DIR)/alpine/build/scripts
sudo systemd-nspawn -UD $(CACHE_DIR)/alpine -- sh -c "cd build; make $(APP_NAME)" sudo systemd-nspawn -UD $(CACHE_DIR)/alpine -- sh -c "cd build; make $(APP_NAME)"
cp $(CACHE_DIR)/alpine/build/$(APP_NAME) . cp $(CACHE_DIR)/alpine/build/$(APP_NAME) .
@ -29,9 +35,24 @@ clean-in-container:
sudo systemd-nspawn -UD $(CACHE_DIR)/alpine -- sh -c "cd build; rm -rf *" sudo systemd-nspawn -UD $(CACHE_DIR)/alpine -- sh -c "cd build; rm -rf *"
# main build stuff # main build stuff
$(APP_NAME): main_bc.c app.o DEBUG_CFLAGS=-g3 -no-pie -fno-pie
$(APP_NAME): main_bc.c app.o scripts/crimes startup.bin
# $(CC) -o $@ -pipe -O3 -DAPP_NAME='"$(APP_NAME)"' -static $^ -lracket3m -lrktio -lucontext -lffi # $(CC) -o $@ -pipe -O3 -DAPP_NAME='"$(APP_NAME)"' -static $^ -lracket3m -lrktio -lucontext -lffi
$(CC) -o $@ -pipe -O3 -DAPP_NAME='"$(APP_NAME)"' $^ /usr/lib/libracket3m.a /usr/lib/librktio.a /usr/lib/libffi.a /lib/libucontext.a $(CC) -o $@ -pipe -O3 $(DEBUG_CFLAGS) -DAPP_NAME='"$(APP_NAME)"' -Wl,-eelf_crimes \
main_bc.c app.o /usr/lib/libracket3m.a /usr/lib/librktio.a /usr/lib/libffi.a /lib/libucontext.a
$(PATCHELF) --remove-needed $$($(OBJDUMP) -x $@ | grep NEEDED | awk '{print $$2}') $@
$(OBJCOPY) --remove-section .interp -- $@
scripts/crimes $@
scripts/crimes: scripts/crimes.c scripts/crimes.h
$(CC) -o $@ -pipe -O3 -g0 $<
startup.elf: startup.c
$(CC) -flto -O3 -g3 -Wl,-z,norelro -ffunction-sections -fdata-sections -static -pipe \
-Wl,-ecrimes_startup_run -fno-stack-protector -nostartfiles -fpie -pie -o $@ $<
startup.bin: startup.elf
$(OBJCOPY) -O binary $< $@
# this is faster than --c-mods by a lot # this is faster than --c-mods by a lot
# it's less portable but like, we're containerized already so it'll work # it's less portable but like, we're containerized already so it'll work
@ -43,6 +64,7 @@ app.zo: $(RKT_NAME)
clean: clean:
$(RM) $(APP_NAME) *.zo *.o $(RM) $(APP_NAME) *.zo *.o
$(RM) scripts/crimes startup.bin startup.elf
mrproper: clean mrproper: clean
sudo $(RM) -rf $(CACHE_DIR)/alpine sudo $(RM) -rf $(CACHE_DIR)/alpine

View File

@ -3,10 +3,12 @@
#include <racket/scheme.h> #include <racket/scheme.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
// patchups for musl vs glibc stuff // patchups for musl vs glibc stuff
#include <sys/stat.h> #include <sys/stat.h>
extern int __xstat(int, const char* restrict, struct stat* restrict);
extern int __lxstat(int, const char* restrict, struct stat* restrict);
extern int __fxstat(int, int, struct stat*);
int stat(const char* restrict path, struct stat* restrict buf) { int stat(const char* restrict path, struct stat* restrict buf) {
return __xstat(1, path, buf); return __xstat(1, path, buf);
} }
@ -18,11 +20,38 @@ int lstat(const char *restrict path, struct stat *restrict buf) {
} }
extern void* __dso_handle; extern void* __dso_handle;
extern int __cxa_atexit(void (*)(), void*, void*);
int atexit(void (*function)(void)) { int atexit(void (*function)(void)) {
return __cxa_atexit(function, NULL, __dso_handle); return __cxa_atexit(function, NULL, __dso_handle);
} }
// custom entry wrapper
// the thing with this project is it's going to generate a binary that can run literally everwhere
// if you build it on exactly the right environment
// as opposed to the usual portable build, unportable output
// ok so this basically assumes startup.bin contains ONLY .text and .rodata and that it's PIE and
// that .rodata directly follows .text and that entry is at the top of .text, and like, this is all
// true but it's not _guaranteed_ so like, weh
void __attribute__((noinline)) crimes_startup_run(void* addr) {
// haha yes
asm volatile(".incbin \"startup.bin\"" :: "rm"(addr) : "memory");
}
#include <dlfcn.h>
extern void _start(unsigned long, unsigned long, unsigned long, unsigned long);
void elf_crimes(void) {
// re-exec if necessary
crimes_startup_run(__builtin_frame_address(0));
// this _should_ tail call and all should be well...hopefully...
return _start(0, 0, 0, 0);
}
// embedded zo (but fast, we avoid rendering and subsequently unrendering megabytes of C which is
// Mega Slow and simply ld -r -b the zo at the cost of build portability but idk i'm pretty sure
// bsd has -r -b too tbh)
extern const char _binary_app_zo_start; extern const char _binary_app_zo_start;
extern const char _binary_app_zo_end; extern const char _binary_app_zo_end;

53
scripts/crimes.c Normal file
View File

@ -0,0 +1,53 @@
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <elf.h>
#define CRIMES_ELF_BITS 32
#include "crimes.h"
#undef CRIMES_ELF_BITS
#define CRIMES_ELF_BITS 64
#include "crimes.h"
#undef CRIMES_ELF_BITS
int main(int argc, char* argv[]) {
if (argc < 2) {
printf("usage: %s <filename>\n", argv[0]);
return -1;
}
char* fname = argv[1];
int fd = open(fname, O_RDWR);
if (fd < 0) {
perror("open");
return -1;
}
ssize_t sz = lseek(fd, 0, SEEK_END);
if (sz < 0) {
perror("lseek");
return -1;
}
if (lseek(fd, 0, SEEK_SET) < 0) {
perror("lseek");
return -1;
}
void* elf = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (elf == MAP_FAILED) {
perror("mmap");
return -1;
}
close(fd);
// we're not going to emulate wacky technically nonstandard behavior here
if (((uint8_t*) elf)[EI_CLASS] < ELFCLASS64) {
crimes_32(elf);
} else {
crimes_64(elf);
}
munmap(elf, sz);
}

43
scripts/crimes.h Normal file
View File

@ -0,0 +1,43 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <elf.h>
#if CRIMES_ELF_BITS == 32
#define Elf_Ehdr Elf32_Ehdr
#define Elf_Phdr Elf32_Phdr
#define CRIMES_PATCH_FUNC crimes_32
#else
#define Elf_Ehdr Elf64_Ehdr
#define Elf_Phdr Elf64_Phdr
#define CRIMES_PATCH_FUNC crimes_64
#endif
#define crimes_str(x) #x
void CRIMES_PATCH_FUNC(Elf_Ehdr* elf) {
uint8_t* phdrs = ((uint8_t*) elf) + elf->e_phoff;
ssize_t src = -1;
ssize_t dst = -1;
for (size_t i = 0; i < elf->e_phnum; i++) {
Elf_Phdr* phdr = (Elf_Phdr*) (phdrs + elf->e_phentsize * i);
if (phdr->p_type == PT_INTERP) {
src = i + 1;
dst = i;
}
}
if (src < 0) {
printf("[%s] couldn't find PT_INTERP - skipping\n", crimes_str(CRIMES_PATCH_FUNC));
return;
}
memmove(phdrs + dst * elf->e_phentsize, phdrs + src * elf->e_phentsize,
(elf->e_phnum - dst) * elf->e_phentsize);
elf->e_phnum -= 1;
printf("[%s] patch complete\n", crimes_str(CRIMES_PATCH_FUNC));
}
#undef Elf_Ehdr
#undef Elf_Phdr
#undef CRIMES_PATCH_FUNC

View File

@ -3,13 +3,15 @@
set -e set -e
apk upgrade --update-cache --available apk upgrade --update-cache --available
apk add alpine-sdk ca-certificates libcrypto1.1 libssl1.1 chrpath libffi-dev libucontext-dev apk add alpine-sdk ca-certificates libcrypto1.1 libssl1.1 chrpath libffi-dev libucontext-dev \
patchelf
cd /racket-*/src cd /racket-*/src
export CFLAGS="$CFLAGS -D_GNU_SOURCE" export CFLAGS="$CFLAGS -D_GNU_SOURCE"
export LDFLAGS="$LDFLAGS -lucontext" export LDFLAGS="$LDFLAGS -lucontext"
rm -Rf src/foreign/libffi rm -Rf src/foreign/libffi
./configure --prefix=/usr --sysconfdir=/etc --disable-docs --enable-strip --enable-libs --disable-gracket ./configure --prefix=/usr --sysconfdir=/etc --disable-docs --enable-strip --enable-libs \
--disable-gracket
make -j $(nproc) CPUS=$(nproc) make -j $(nproc) CPUS=$(nproc)
make install make install
raco pkg install --deps search-auto cext-lib raco pkg install --deps search-auto cext-lib

100
startup.c Normal file
View File

@ -0,0 +1,100 @@
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdbool.h>
void crimes_startup_run(uint8_t* frame) {
// we trash argv up to hack on ld.so arguments without needing malloc
// this is pad space to catch that and not explode uwu
volatile char _pad[(sizeof(char*) * 4)];
// XXX: assumes stack goes up
// big weh
size_t argc = *((size_t*) (frame + sizeof(size_t)));
char** argv = ((char**) (frame + sizeof(size_t) + sizeof(char*)));
char** envp = argv + argc + 1;
// fake usage of _pad
// also a barrier to ensure argc gets read
asm volatile("" :: "rm"(_pad) : "memory");
syscall(SYS_write, STDOUT_FILENO, "[crimes] detect maps\n", 21);
int fd = syscall(SYS_open, "/proc/self/maps", O_RDONLY);
if (fd < 0) {
syscall(SYS_write, STDOUT_FILENO, "[crimes] open failed\n", 21);
syscall(SYS_exit, -1);
}
char buf[32];
int state = 0;
while (true) {
ssize_t res = syscall(SYS_read, fd, buf, sizeof(buf));
if (res == 0) {
break;
} else if (res < 0) {
syscall(SYS_write, STDOUT_FILENO, "[crimes] read error\n", 20);
syscall(SYS_exit, -1);
}
for (ssize_t i = 0; i < res; i++) {
switch (state) {
case 0:
if (buf[i] == '/') { state++; }
else { state = 0; }
break;
case 1:
if (buf[i] == 'l') { state++; }
else { state = 0; }
break;
case 2:
if (buf[i] == 'd') { state++; }
else { state = 0; }
break;
case 3:
if (buf[i] == '-') { state++; }
else { state = 0; }
break;
default:
break;
}
if (state > 3) {
break;
}
}
if (state > 3) {
break;
}
}
if (state > 3) {
syscall(SYS_write, STDOUT_FILENO, "[crimes] detected libc. going back to startup\n", 46);
return;
}
syscall(SYS_write, STDOUT_FILENO, "[crimes] running ld.so\n", 23);
// XXX: currently we hardcode the glibc parameters
// detect glibc or musl at runtime and load the appropriate params
char* ld = "/usr/lib/ld-2.32.so";
char* pl = "--preload";
char* ldlp =
"/usr/lib/libm.so.6:/usr/lib/libpthread.so.0:/usr/lib/libdl.so.2:/usr/lib/libc.so.6";
uintptr_t args[4 + argc];
args[0] = (uintptr_t)ld;
args[1] = (uintptr_t)pl;
args[2] = (uintptr_t)ldlp;
for (size_t i = 0; i < argc; i++) {
args[3 + i] = (uintptr_t) argv[i];
}
args[3 + argc] = 0;
syscall(SYS_execve, ld, args, envp);
syscall(SYS_write, STDOUT_FILENO, "[crimes] exec failed!\n", 22);
syscall(SYS_exit, -1);
}