stabbish

:)
git clone https://git.sr.ht/~ashymad/stabbish
Log | Files | Refs | README | LICENSE

commit ab650de751e1020544ae51b0e941116a56d4d6dd
Author: Szymon Mikulicz <szymon.mikulicz@posteo.net>
Date:   Thu,  2 Jul 2026 01:53:10 +0200

Initial commit

Diffstat:
A.gitignore | 8++++++++
AMakefile | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amain.c | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 212 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,8 @@ +busybox +*.o +.deps +test/ +*.inc.h +*.inc.s +applets.h +stabbish diff --git a/Makefile b/Makefile @@ -0,0 +1,102 @@ +.SUFFIXES: + +CFLAGS=-static +BB_VER=1.38.0 +CC=$(PWD)/.deps/x86_64-linux-musl-native/bin/gcc + +stabbish: main.o busybox.inc.o | $(CC) + $(info LD $@) + @$(CC) $(CFLAGS) $^ -o $@ + +main.o: main.c applets.h busybox.inc.h | $(CC) + $(info CC $@) + @$(CC) $(CFLAGS) -c $< -o $@ + +%.inc.h: %.inc.o + $(info GEN $@) + @echo "extern const char _inc_$(patsubst %.inc.o,%,$<)[];" >$@ + @echo "extern const int _inc_$(patsubst %.inc.o,%,$<)_size;" >>$@ + +%.inc.o: %.inc.s | $(CC) + $(info ASM $@) + @$(CC) $(CFLAGS) -c $< -o $@ + +%.inc.s: % + $(info GEN $@) + @echo ' .section ".rodata"' >$@ + @echo ' .globl _inc_$<' >>$@ + @echo ' .type _inc_$<, STT_OBJECT' >>$@ + @echo ' .globl _inc_$<_size' >>$@ + @echo ' .type _inc_$<_size, STT_OBJECT' >>$@ + @echo '_inc_$<:' >>$@ + @echo ' .incbin "$<"' >>$@ + @echo ' .byte 0' >>$@ + @echo ' .size _inc_$<, .-_inc_$<' >>$@ + @echo '_inc_$<_size:' >>$@ + @echo ' .int (. - _inc_$<)' >>$@ + +applets.h: busybox + $(info GEN $@) + @echo "static const char* busybox_applets[] = {" >$@ + @busybox --list | awk '{print " \""$$1"\","}' >>$@ + @echo " 0" >> $@ + @echo "};" >> $@ + +$(CC): + $(info DWNL $@) + @mkdir -p .deps + @cd .deps && curl -fSL 'https://musl.cc/x86_64-linux-musl-native.tgz' | tar xz + +.deps/busybox-$(BB_VER)/Config.in: + $(info DWNL $@) + @mkdir -p .deps + @cd .deps && curl -fSL https://busybox.net/downloads/busybox-$(BB_VER).tar.bz2 | tar xj + +.deps/busybox-$(BB_VER)/.config: .deps/busybox-$(BB_VER)/Config.in $(CC) + $(info MK $@) + @make -C .deps/busybox-$(BB_VER) defconfig CC=$(CC) HOSTCFLAGS+="-static" HOSTLDFLAGS+="-static" EXTRA_LDFLAGS="-static" + @sed -i "s/# CONFIG_STATIC is not set/CONFIG_STATIC=y/" $@ + @sed -i "s/# CONFIG_FEATURE_SH_STANDALONE is not set/CONFIG_FEATURE_SH_STANDALONE=y/" $@ + +.deps/busybox-$(BB_VER)/busybox: .deps/busybox-$(BB_VER)/.config $(CC) + $(info MK $@) + @make -C .deps/busybox-$(BB_VER) CC=$(CC) HOSTCFLAGS+="-static" HOSTLDFLAGS+="-static" EXTRA_LDFLAGS="-static" + +busybox: .deps/busybox-$(BB_VER)/busybox + $(info LN $@) + @ln -sf $< $@ + +test/test.sh: + $(info GEN $@) + @mkdir -p test + @echo "type ls" >$@ + +test/stabbish_plain: stabbish test/test.sh + $(info CAT $@) + @cat $^ > $@ + @chmod +x $@ + +test/stabbish_%: stabbish test/test.sh + $(info CAT $@) + @$(patsubst test/stabbish_%,%,$@) -c test/test.sh | cat $< - >$@ + @chmod +x $@ + +.PHONY: +test: test/stabbish_plain test/stabbish_bzip2 test/stabbish_gzip test/stabbish_xz test/stabbish_zstd + @for test in $^; do \ + printf "Test $$test..."; \ + if [ "$$(./$$test)" = "ls is ls" ]; then \ + echo " [OK]"; \ + else \ + echo " [ERR]"; \ + fi \ + done + +.PHONY: +clean: + rm -rf *.inc.* applets.h main.o stabbish test + +.PHONY: +cleanall: clean + make -C .deps/busybox-$(BB_VER) clean + rm .deps/busybox-$(BB_VER)/.config diff --git a/main.c b/main.c @@ -0,0 +1,102 @@ +#define _GNU_SOURCE 1 +#include <elf.h> +#include <errno.h> +#include <fcntl.h> +#include <malloc.h> +#include <sched.h> +#include <stdio.h> +#include <string.h> +#include <sys/mman.h> +#include <unistd.h> + +#include "applets.h" +#include "busybox.inc.h" + +#if defined(__LP64__) +#define ElfW(type) Elf64_##type +#else +#define ElfW(type) Elf32_##type +#endif + +struct magic { + size_t length; + unsigned char byte[16]; + const char *tool; +}; + +const struct magic MAGICS[] = { + {.length = 6, + .byte = {0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00}, + .tool = "xzcat"}, + {.length = 2, .byte = {0x1F, 0x8B}, .tool = "zcat"}, + {.length = 3, .byte = {0x42, 0x5A, 0x68}, .tool = "bzcat"}, + {.length = 4, .byte = {0x28, 0xB5, 0x2F, 0xFD}, .tool = "zstdcat"}, + +}; +const size_t MAGICS_size = sizeof(MAGICS) / sizeof(MAGICS[0]); + +#define checkerr(cmd, chk) \ + if (!((cmd)chk)) { \ + fprintf(stderr, "%s:%i: %s: %s\n", __FILE__, __LINE__, #cmd, \ + strerror(errno)); \ + return errno; \ + } + +int main(int argc, char **argv) { + int bbox, script, self; + size_t self_size; + unsigned char *self_map; + + checkerr(bbox = memfd_create("busybox", 0), != -1); + + checkerr(write(bbox, _inc_busybox, _inc_busybox_size), != -1); + + checkerr(self = open("/proc/self/exe", O_RDONLY), != -1); + self_size = lseek(self, 0, SEEK_END); + + checkerr(self_map = mmap(NULL, self_size, PROT_READ, MAP_PRIVATE, self, 0), + != (void *)-1); + close(self); + + const ElfW(Ehdr) *header = (const ElfW(Ehdr) *)self_map; + + int payload_i = header->e_shoff + (header->e_shentsize * header->e_shnum); + int payload_size = self_size - payload_i; + + if (payload_size <= 0) { + fprintf(stderr, "No payload found, starting shell.\n"); + argv[0] = "ash"; + + return fexecve(bbox, argv, environ); + } + + const char *tool = NULL; + for (int i = 0; i < MAGICS_size; i++) { + if (MAGICS[i].length < payload_size && + memcmp(MAGICS[i].byte, &self_map[payload_i], MAGICS[i].length) == 0) { + tool = MAGICS[i].tool; + break; + } + } + + checkerr(script = memfd_create("script", 0), != -1); + checkerr(write(script, &self_map[payload_i], payload_size), != -1); + munmap(self_map, self_size); + + char scriptarg[1024]; + if (tool) + snprintf(scriptarg, 1024, ". <(%s /dev/fd/%i)", tool, script); + else + snprintf(scriptarg, 1024, ". /dev/fd/%i", script); + + char **newargv = malloc(sizeof(char *) * (argc + 4)); + newargv[0] = "ash"; + newargv[1] = "-c"; + newargv[2] = scriptarg; + for (int i = 0; i < argc; i++) { + newargv[i + 3] = argv[i]; + } + newargv[argc + 3] = NULL; + + return fexecve(bbox, newargv, environ); +}