commit MAJOR elf crimes to invoke ld.so at runtime
This commit is contained in:
parent
fb05155915
commit
f6da2eb731
28
Makefile
28
Makefile
|
@ -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
|
||||
CACHE_DIR=.cache
|
||||
|
||||
PATCHELF=patchelf
|
||||
OBJCOPY=$(CROSS_COMPILE)objcopy
|
||||
OBJDUMP=$(CROSS_COMPILE)objdump
|
||||
|
||||
all: setup exec-in-container
|
||||
|
||||
# rerun inside container
|
||||
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)"
|
||||
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 *"
|
||||
|
||||
# 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)"' $^ /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
|
||||
# it's less portable but like, we're containerized already so it'll work
|
||||
|
@ -43,6 +64,7 @@ app.zo: $(RKT_NAME)
|
|||
|
||||
clean:
|
||||
$(RM) $(APP_NAME) *.zo *.o
|
||||
$(RM) scripts/crimes startup.bin startup.elf
|
||||
|
||||
mrproper: clean
|
||||
sudo $(RM) -rf $(CACHE_DIR)/alpine
|
||||
|
|
37
main_bc.c
37
main_bc.c
|
@ -3,26 +3,55 @@
|
|||
#include <racket/scheme.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// patchups for musl vs glibc stuff
|
||||
#include <sys/stat.h>
|
||||
int stat(const char *restrict path, struct stat *restrict buf) {
|
||||
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) {
|
||||
return __xstat(1, path, buf);
|
||||
}
|
||||
int fstat(int fildes, struct stat *buf) {
|
||||
int fstat(int fildes, struct stat* buf) {
|
||||
return __fxstat(1, fildes, buf);
|
||||
}
|
||||
int lstat(const char *restrict path, struct stat *restrict buf) {
|
||||
int lstat(const char* restrict path, struct stat* restrict buf) {
|
||||
return __lxstat(1, path, buf);
|
||||
}
|
||||
|
||||
extern void* __dso_handle;
|
||||
extern int __cxa_atexit(void (*)(), void*, void*);
|
||||
|
||||
int atexit(void (*function)(void)) {
|
||||
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_end;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -3,13 +3,15 @@
|
|||
set -e
|
||||
|
||||
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
|
||||
export CFLAGS="$CFLAGS -D_GNU_SOURCE"
|
||||
export LDFLAGS="$LDFLAGS -lucontext"
|
||||
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 install
|
||||
raco pkg install --deps search-auto cext-lib
|
||||
|
|
|
@ -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);
|
||||
}
|
Loading…
Reference in New Issue