stabbish

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

commit 292edc9fe330e4c17298763c3efd8451c2adc486
parent d5ebfbdac2fd0471b76ce67e5651e998f53c4e15
Author: Szymon Mikulicz <szymon.mikulicz@posteo.net>
Date:   Sat,  4 Jul 2026 01:27:59 +0200

Code cleanup

Diffstat:
M.gitignore | 1-
MMakefile | 8++------
Aelf_payload.h | 42++++++++++++++++++++++++++++++++++++++++++
Amagic.h | 34++++++++++++++++++++++++++++++++++
Mmain.c | 128++++++++++++++++++++++++-------------------------------------------------------
Autils.h | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 199 insertions(+), 97 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -4,4 +4,3 @@ test/ *.inc.h *.inc.s stabbish -bbox/features.h diff --git a/Makefile b/Makefile @@ -19,14 +19,10 @@ stabbish: main.o bbox/busybox.inc.o | $(MUSL_CC) $(info $() LD $@) @$(CC) $(CFLAGS) $^ -o $@ -main.o: main.c bbox/busybox.inc.h bbox/features.h | $(MUSL_CC) +main.o: main.c bbox/busybox.inc.h magic.h elf_payload.h utils.h | $(MUSL_CC) $(info $() CC $@) @$(CC) $(CFLAGS) -c $< -o $@ -bbox/features.h: $(BBOX) - $(info $() GEN $@) - @$^ --list | sed 's/[-\[\.]/_/g' | awk '{printf "#define BB_HAVE_%s \n", $$1}' >$@ - %.inc.h: tmplt/inc.h $(info $() GEN $@) @sed 's@NAME@$(patsubst %.inc.h,%,$(@F))@g' $< > $@ @@ -85,7 +81,7 @@ test: test/stabbish_plain test/stabbish_bzip2 test/stabbish_gzip test/stabbish_x clean_bbox: $(info $() CLEAN bbox) - @rm -r bbox/*.inc.* bbox/features.h + @rm -r bbox/*.inc.* clean_test: $(info $() CLEAN test) diff --git a/elf_payload.h b/elf_payload.h @@ -0,0 +1,42 @@ +#pragma once +#include <elf.h> +#include <fcntl.h> +#include <sys/mman.h> +#include <sys/sendfile.h> +#include <unistd.h> + +#include "utils.h" + +#if defined(__LP64__) +#define ElfW(type) Elf64_##type +#else +#define ElfW(type) Elf32_##type +#endif + +#define ENOPAYLOAD -2 + +static fd elf_payload(const char *path) { + static fd try_return = -1; + + fd trys(in, open(path, O_RDONLY)); + + ElfW(Ehdr) header; + try(read(in, &header, sizeof(header))); + + size_t trys(size, lseek(in, 0, SEEK_END)); + off_t offset = header.e_shoff + (header.e_shentsize * header.e_shnum); + size_t payload_size = size - offset; + + if (payload_size == 0) { + close(in); + return ENOPAYLOAD; + } + + int trys(payload, memfd_create("payload", 0)); + try(sendfile(payload, in, &offset, payload_size)); + try(lseek(payload, SEEK_SET, 0)); + + close(in); + + return payload; +} diff --git a/magic.h b/magic.h @@ -0,0 +1,34 @@ +#pragma once +#include <stdlib.h> + +#include "utils.h" +#include <unistd.h> + +struct magic { + const char *tool; + size_t length; + unsigned char byte[16]; +}; + +const struct magic MAGICS[] = { + {"xzcat", 6, {0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00}}, + {"zcat", 2, {0x1F, 0x8B}}, + {"bzcat", 3, {0x42, 0x5A, 0x68}}, +}; + +static struct magic magic_detect(fd file) { + static struct magic try_return = {NULL, 0, {0x00}}; + + unsigned char header[10]; + + size_t trys(header_len, read(file, &header, 10)); + + for (int i = 0; i < tsizeof(MAGICS); i++) { + if (header_len > MAGICS[i].length && + memcmp(header, MAGICS[i].byte, MAGICS[i].length) == 0) { + return MAGICS[i]; + } + } + + return try_return; +} diff --git a/main.c b/main.c @@ -1,9 +1,8 @@ #define _GNU_SOURCE 1 #include <elf.h> -#include <errno.h> -#include <fcntl.h> #include <malloc.h> #include <sched.h> +#include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -11,107 +10,56 @@ #include <unistd.h> #include "bbox/busybox.inc.h" -#include "bbox/features.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[] = { -#ifdef BB_HAVE_xzcat - {.length = 6, - .byte = {0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00}, - .tool = "xzcat"}, -#endif -#ifdef BB_HAVE_zcat - {.length = 2, .byte = {0x1F, 0x8B}, .tool = "zcat"}, -#endif -#ifdef BB_HAVE_bzcat - {.length = 3, .byte = {0x42, 0x5A, 0x68}, .tool = "bzcat"}, -#endif -#ifdef BB_HAVE_zstdcat - {.length = 4, .byte = {0x28, 0xB5, 0x2F, 0xFD}, .tool = "zstdcat"}, -#endif -}; -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; \ - } +#include "elf_payload.h" +#include "magic.h" +#include "utils.h" int main(int argc, char **argv) { - int bbox, script, self, envfile; - size_t self_size; - unsigned char *self_map; - - checkerr(bbox = memfd_create("busybox", 0), != -1); - checkerr(envfile = memfd_create("envfile", 0), != -1); - dprintf(envfile, "export ENV; echo No payload present, starting shell."); + static int try_return = -1; - char *envstr; - checkerr(asprintf(&envstr, "/dev/fd/%i", envfile), != -1); - setenv("ENV", envstr, 1); + bool start_shell = argc > 1 && strcmp(argv[1], "--stabbish-shell") == 0; + if (start_shell) + shift(1); - checkerr(asprintf(&envstr, "/dev/fd/%i", bbox), != -1); - setenv("BUSYBOX", envstr, 1); + fd trys(busybox, memfd_create("busybox", 0)); + fd trys(rcfile, memfd_create("rcfile", 0)); + fd trys(payload, elf_payload("/proc/self/exe")); - checkerr(write(bbox, _inc_busybox, _inc_busybox_size), != -1); + try(write(busybox, _inc_busybox, _inc_busybox_size)); - checkerr(self = open("/proc/self/exe", O_RDONLY), != -1); - self_size = lseek(self, 0, SEEK_END); + setenv("ENV", pasprintf("/dev/fd/%i", rcfile), 1); + setenv("BUSYBOX", pasprintf("/dev/fd/%i", busybox), 1); - 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) { - argv[0] = "ash"; - return fexecve(bbox, argv, environ); + if (payload == ENOPAYLOAD) { + try(dprintf(rcfile, "echo No payload present, starting shell.")); + } else { + setenv("PAYLOAD", pasprintf("/dev/fd/%i", payload), 1); } - 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; - } + if (start_shell || payload == ENOPAYLOAD) { + argv[0] = "ash"; + return fexecve(busybox, argv, environ); } - checkerr(script = memfd_create("script", 0), != -1); - checkerr(write(script, &self_map[payload_i], payload_size), != -1); - munmap(self_map, self_size); + struct magic magic = magic_detect(payload); + if (magic.tool) { + fd trys(extracted, memfd_create("extracted", 0)); + char *tool = strdup(magic.tool); + struct stdstream std = {payload, extracted, -1}; - char *scriptarg; - if (tool) - asprintf(&scriptarg, ". <(%s /dev/fd/%i)", tool, script); - else - asprintf(&scriptarg, ". /dev/fd/%i", script); + lseek(payload, SEEK_SET, 0); + try(memexecv(busybox, (char *[]){tool, NULL}, NULL, std)); + dup2(extracted, payload); + free(tool); + } - 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]; + char **bb_argv = malloc(sizeof(char *) * (argc + 4)); + bb_argv[0] = "ash"; + bb_argv[1] = "-c"; + bb_argv[2] = pasprintf(". /dev/fd/%i", payload); + for (int i = 0; i <= argc; i++) { + bb_argv[i + 3] = argv[i]; } - newargv[argc + 3] = NULL; - return fexecve(bbox, newargv, environ); + return fexecve(busybox, bb_argv, environ); } diff --git a/utils.h b/utils.h @@ -0,0 +1,83 @@ +#pragma once + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/wait.h> +#include <unistd.h> + +typedef int fd; + +#define shift(n) \ + if (n < argc) { \ + argv[n] = argv[0]; \ + argv = &argv[n]; \ + argc -= n; \ + } + +#define tsizeof(table) sizeof(table) / sizeof(table[0]) + +#define trys2(nam, cmd, chk) \ + nam = cmd; \ + try2(nam, chk) + +#define trys(nam, cmd) trys2(nam, cmd, != -1) + +#define try2(cmd, chk) \ + if (!((cmd)chk)) { \ + fprintf(stderr, "%s:%i: %s: %s\n", __FILE__, __LINE__, #cmd, \ + strerror(errno)); \ + return try_return; \ + } + +#define try(cmd) try2(cmd, != -1) + +static char *pasprintf(const char *__restrict str, ...) { + va_list ap; + va_start(ap, str); + + char *out; + if (vasprintf(&out, str, ap) == -1) + out = NULL; + + va_end(ap); + return out; +} + +struct stdstream { + fd in; + fd out; + fd err; +}; + +static int memexecv(fd exe, char **argv, char **envp, struct stdstream std) { + int try_return = -1; + pid_t trys(pid, fork()); + + if (pid) { + int wstatus; + waitpid(pid, &wstatus, 0); + if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus)) { + fprintf(stderr, "Child process exited with code %i\n", + WEXITSTATUS(wstatus)); + return -1; + } + } else { + if (std.in >= 0) + dup2(std.in, 0); + if (std.out >= 0) + dup2(std.out, 1); + if (std.err >= 0) + dup2(std.err, 2); + + exit(fexecve(exe, argv, envp)); + } + + return 0; +}