commit ab650de751e1020544ae51b0e941116a56d4d6dd
Author: Szymon Mikulicz <szymon.mikulicz@posteo.net>
Date: Thu, 2 Jul 2026 01:53:10 +0200
Initial commit
Diffstat:
| A | .gitignore | | | 8 | ++++++++ |
| A | Makefile | | | 102 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | main.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);
+}