Skip to content

Repository files navigation

uzpexec

(c) 2026, Roberto A. Foglietta roberto.foglietta@gmail.com, text published under CC BY-NC-ND 4.0

  •  Click on the button to know how to  Sponsor me  this project and get in touch with me.

A 512-byte polymorphic stub/payload (uzpexec) written in Assembler that converts ELF and scripts into executable gzip packages (UZP). It runs in RAM also when started by the STDIN pipe, ssh or wget, and the carryload is trivial to extract by dd skip=1.

  • Pre-compiled ELF32 (for all x86 arch) available in releases.
  • Development happens in devel branch, testing on devsrc tag.

Note

Only the stub, which executes the compressed binary or script, runs as ELF32 and it makes perfect sense since its role is to deal with few system calls and runs everywhere (x86 all arches, because the Assembler is a machine specific language). Obviously the ELF32 nature of the launcher doesn't affect in any manner what is executed which runs by its own kind (cfr. examples).

Warning

Since the release v0.93 packages contain uzpexec.arm source file for ARM64 versioned as v0.33. That source compiles but it is still experimental and reasonably affected by bugs because for a full validation it is a required a complete aarch64 system. However, testing with qemu-aarch64-static 10.2.3 in its original and customised form helped a lot to improve the x86 version.



INDEX


RELEASE

Current release is v0.98.1 on the master branch.

  • Since v0.96, it aims to keep the uzpexec uncustomised as much as possible and, instead, relies on system standards like binfmt_script, zutils zcat and busybox zcat with seamless decompression, and offering uzcat as a lightweight alternatives for system customisation.

  • This release v0.98, moves forward integrating the zstd support and being self-sufficient in executing from STDIN plain inputs, and it is freed from zcat -f potentially insecure option. In v0.98.1, the gzip / pigz format returns as default because it is universally available.

  • 35903e0 - 2026-07-29 - uzpack.sh: regression fix about re-converting from old versions


USAGE

The use of uzpexec extends every GitHub action from executing whatever is installable by their internal repository to whatever is available by an URL access. Despite this potential, running uzpexec directly isn't probably your goal as end-user, but uzpack to create compressed self-extracting in RAM executables:

Usage: uzpack [-h|--help] [-v|--version]
       uzpack origin [destination[.uzp]]
       uzpack [-x: debug | -1/-19: pigz]
 export UZCMD=[zstd | (any other ztool)]

This tool comes with its man page uzpack.1 which can be read by GitHub via uzpack.md. However, the help from the script is pretty clear, and its development is simplicity-oriented.

sh ./uzpack.sh uzpack.sh uzpack
./uzpack -v

Moreover, the shell script uzpack.sh is able to convert itself into a standalone executable converter by incorporating itself via base64 binary-to-text conversion. Via shebang and Linux kernel binfmt_script standard registration, script support is extended to any interpreter installed on the system, in particular python.

Requirements

  • /bin/sh, /bin/zcat (gunzip), /proc mounted, Linux kernel 3.19 or later.

  • /bin/zstd for creation, and /bin/zstdcat for 1GB/s in memory extraction.

  • /bin/base64 for stand-alone conversion and python in PATH for .py scripts.


LICENSE

In short: MIT the source and MIT the binary, because compiling the source we get the executable, and by disassembling the binary we get the source (var names apart) due its extreme nature and Assembler coding. Hence, the MIT terms apply to both.

; (C) 2026, Roberto A. Foglietta <roberto.foglietta@gmail.com>, MIT+1 license
;
;     MIT+1: due to the extreme nature of this software, an extra clause is
;            added to the standard MIT license, which forbids everyone to
;            remove or change the authorship string also from the binary.
;            The clause rationale is rooted in security fingerprinting and
;            due to the strong -- nearly 1:1 -- match between the Assembler
;            source code and the x86 32-bit executable code (human-machine).
;
;  The MIT+1 licensing terms apply to all the previous, current and future
;  versions, unless the author provides a public legal charter allowing a
;  designated entity to be exempted from this extra clause. Comply or delete.
;
;      Note: coded with the help of Kimi and Gemini for the size reduction,
;            this aspect deepens the link between the human and the machine.
;
;  In this specific case, MIT+1 is not a derivative of the MIT license but
;  due to the peculiar and extreme nature of this source code, the license
;  acts as a direct extension to the binary code generated by it (1sc:1bc).

After all, scripts are the sources and the "running code" at the same time.




PRESENTATION

The uzpexec is an utility for executing a compressed ELF binary or a script from STDIN or alternatively from a carryload appended to it, and the two are independent and complementary ways of working.

  • it extracts and executes
  • it adds a just 512-bytes stub
  • trivial to inflate by dd skip=1
  • it runs binary via ssh or wget
  • it runs in RAM only, no disk write
  • it converts ELF and shell scripts, both
  • it works with dash, bash, and busybox ash
  • it executes from STDIN by internal read/write
  • qemu-friendly execveat() bug fallback to execve()
  • reserved provider field for customisations

Using uzpexec to convert uzpack.sh generates a tool that can convert executables.

Soon after uzpack.sh incorporates uzpexec as payload, it can work in standalone mode.

In a standalone mode, it can convert itself in uzpack and it becomes self-hosted, also.

  • stub + script --> script w/ payload --> self-hosted ELF32 executable converter tool

It supports natively the gzip (legacy) and zstd (fastest) carryload formats extraction.

Features log

  • Support extentions: from dash-only to every shell in v0.92, python scripts since v0.95, zutils + binfmt_script since v0.97, zstdcat internal support since v0.98.

  • In v0.98 the sed customisation returns fully available for running a uzpexec self-extracting binary on a system / container with /usr/local from a make install installation.

  • RAM-only, without writing on the remote / local systems storage because memfd_create(). Obviously when RAM-only is a benefit otherwise gzcmd.sh writes on disk / tmpfs.

For providers

  • Customisations are allowed strictly within the licensing terms and the dedicated 8-char field for the provider identifier / nickname.

  • Providers who will disclose their changes with the author will (on their request) be listed here with their chosen identifier / nickname.

Trustability

  • defender notes: already in README.md
  • signed sha256sum releases: by GitHub
  • reproducible builds: by design / ASM
  • custom distribution: provider field

Notes for deploying

0. use the source

  • Unless a binary pre-compiled package is needed, download the source .zip archive from github master branch.

1. deploy responsibly

  • Those who are planning to deploy / distribute self-extracting binaries, may consider to recompile uzpexec as loader with make JE_STDIN=_NO_STDIN to avoid users extract it with dd count=1 for lazy / unsafe pipeline executions.

  • Instead, embedded system architects / engineers are going to appreciate this feature in uzpexec as command-line tool because they have a stricter control about users / accesses, thus over the pipeline executions.

2. awareness of power

  • Since the project didn't reach yet the v1.0, it is suggest to consult the documentation, the man page, the design choices in the Assembly source code and the comments, the coverage of tests.sh by make tests in Makefile.

  • Last but not least the licensing terms, which allows everyone to change the code (also at running time) but not to remove the authorship note, not even from the binary executable form. A powerful tool requires awareness about how to use it.

3. files easy to find

  • Suggested file extension: .uzp

How to compile

Compiling .asm files requires nasm, otherwise sudo apt install nasm if missing:

make tests

Test by yourself and then decide how to deploy.

  • { cat uzpexec; zstd -19c $elf; } > $elf.uzp
  • cp uzpexec $elf.uzp; gzip -c $elf >> $elf.uzp
  • wget $url/$elf[.gz] -O- | uzpexec [args]
  • uzpexec <&-||echo # for the version + github

It works as a single block 512-bytes self-inflating executable payload replacing also gzcmd.sh with the sole requirement of /bin/zcat available.


Python support

Using sed to change the interpreter from /bin/sh to every other available interpreter, the uzpexec can inflate and execute also non-shell scripts. Packaging a .pyz is a trivial procedure:

rm -f uzpexec; make uzpexec
export WORLD="Wonderful"; bin="hello.py"; cp -f uzpexec ${bin}z
pigz -11c $bin >> ${bin}z && chmod +x ${bin}z && ./${bin}z Nice

  Hello Nice World!
  lsfd: 0 1 2 3 9
  args: 'Nice'
  HOME: '/home/roberto'
  WORLD: 'Wonderful'

du -b hello.py*
  1370  hello.py
  1235  hello.pyz <-- the output is smaller than original !!!

In v0.95 or before, supporting phython scripts was possible only by properly customising uzpexec. After, by system changes like configuring the /bin/sh or Linux binfmt to properly routing shebang-ed scripts to their own interpreter. Which is the standard configuration in desktop and servers and some less-than-minimal embedded systems.


BusyBox support

Warning

Currently BusyBox uzcat alias zcat with seamless decompression fully supports only few of the available algorithms, and in particular it has not zstdcat.

Integrating #37ab6ac38 (0.1Kb) to busybox uchaosys edition, /bin/uzcat is created as an applet and its link signals that it can by magic-number auto-detection decompress any supported format by busybox. This allows uzpexec as stub to work "seamlessly" with any compression format (gz, bz2, xz, lzma) without further customisation, while the uncompressing algorithms are already included in BusyBox.

Moreover, by the integration of the /bin/uxsh applet (0.4Kb) in BusyBox, the uzpexec should no longer care about carrying the proper interpreter full path when it stubs as a launcher script. Calling directly /bin/uxsh the shebang line will be used to call the defined interpreter. This applet isn't strictly necessary because binfmt_script might be missing in some embedded or lightweight system (or secured systems that do not allow chmod +x on /proc/self/*).


Example #1

An example of use is related to this project about QEMU footprint reduction which uses uzpexec to deliver the executable binary in UZP format which can be downloaded from here

Note

The qemu-system-x86_64, provided in UZP self-inflate executable, appears to be an x86 ELF 32-bit LSB executable. That type file refers to the extractor. While qemu is expanded in RAM and execute in its original ELF 64-bit format.

Example #2

The shell script uzpack.sh converts a binary or a script into a self-extracting self-running in memory only. The most natural test is using the script to convert itself. Which is what happens during make and the result can be found as uzpack (2.7Kb circa).

Obviously, it is possible to convert an already converted binary. Which fails to run when it carries a shell script, but it is acceptable because it is totally useless to convert anything twice, especially in this case.

Note

Performance report: uzpexec in "fork loop" mode, is capable of sucking 2 core power from my i5-8365. Two! And this number can correctly taken as an index of its performance: no any lags but pure execution.


Quick customisation

Quick customisation by coreutil sed, or any other stings-based editor (or commands line combination like xxd or base64 with busybox sed or awk) which can deal with binary data containing '\0', is straightforward supported:

rm -f uzpexec hello; make distclean uzpexec hello; elf=hello
sed -e 's/zstdcat.\{7\}U238/xzcat\x00_2_4_6_8x238/' uzpexec > $elf.uxp
xz -9c $elf >>$elf.uxp && chmod +x $elf.uxp && strace -f ./$elf.uxp 2>&-

It is worth to note the relevant role of the U238 as string length sentinel:

  • U238 --> x238 to inform the running code about the customisation
  • between zcat_cat and eof_tests data size can change, U238 tags it

Alternatives to /bin/zstdcat are every equivalent executable for which its full pathname would fit into a 22 chars string plus the trailing \0, and it can contain /usr/local/bin/zstdcat, for example.

When compiled with _HAS_PROVIDER and _NO_INFOSIX boolean declarations, the full pathname length remains unchanged. Depending on the compilation, the length can vary between 16 and 28 chars plus the ending \0.

; ==============================================================================
; COMPACT DATA SECTION (appended to code)
; ==============================================================================
;                                                                  LN | XE
copy_vers:  db "(c) github/robang74/uzpexec v0.98"              ;  33 | 33
%ifdef  _HAS_PROVIDER
provider :  db  0x20, "12345678", 0x0a                          ;  10 |  -
%else
micro_ver:  db  0x20, 0x20, 0x20, 0x0a                          ;   - |  4
%endif
; following fields are conditionally overwritable, do unions
zcat_path:  db "/bin/z"
zcat_cmd :  db "std"
zcat_cat :  db "cat", 0                                         ;  13 | 23 (29)
%ifndef _HAS_PROVIDER
    times 6 db 0                                                ;   - |  -
%endif
%ifdef  _NO_INFOSIX
    times 6 db 0                                                ;   - |  -
%endif
eof_tests:  db "U238"                            ; for tests    :   4 |  -
; This introduces the need of having the /proc mounted,granted after the /init
; The shorter alernative is /dev/fd/9, but it is NOT grated on embedded systems
commd_exe:  db "/proc/self/exe", 0,0                            ;  16 | 16
                                                                ; ----------
                                                                ;  76  tot.
; ==============================================================================
; PADDING: Aligned exactly to 512 bytes (dd skip=1)
; ==============================================================================
file_end:                       ; Physical end of the binary file!
times (512 - ($ - $$)) db 0     ; Padding to 512 bytes for skip=1

Since /path/name can point to a shell script, it can be changed to pair the input with the proper decompressing tool. While a tiny xcat binary in ASM would be much faster in properly pairing the matches.

Warning

The following script is provided untested AS-IS, just for the concept:

#!/bin/sh
HEADER=$(dd bs=1 count=4 2>/dev/null)
HEX=$(printf '%s' "$HEADER" | od -An -tx1 | tr -d ' \n')
case "$HEX" in
  1f8b*)     # GZIP
      (printf '%s' "$HEADER"; cat) | gzip  -d -c "$@"
      ;;
  fd377a58)  # XZ (\xfd7zX)
      (printf '%s' "$HEADER"; cat) | xz    -d -c "$@"
      ;;
  425a68*)   # BZIP2 (BZh)
      (printf '%s' "$HEADER"; cat) | bzip2 -d -c "$@"
      ;;
  28b52ffd)  # ZSTD
      (printf '%s' "$HEADER"; cat) | zstd  -d -c "$@"
      ;;
  *)         # to support -f / --force
      (printf '%s' "$HEADER"; cat)
      ;;
esac

From the desktop user perspective the GNU coreutils sed command, or a specific shell script, can properly deal with elf and different script interpreters like python. Installing the zutils the alternative zcat is able to autodetect the compression format and act accordingly.

From the system integration perspective, the approach explained in the busybox support section would deal with different decompressing formats and selecting the proper script interpreter on the fly, bypassing the binfmt_script settings and replacing zutils zcat with a tiny footprint.

Hence, the customisation can scale up to completely different usage and deploying paradigms while still relying on the same basic tools and uzpexec strings embedded in the .data section.

Quick deploy view

The execution by pipe allows a basic running system, then an app is piped into uzpexec and executed accordingly with its nature and compression format. And this is a great feature for a lightweight supervisor OS that can create separated virtual execution spaces for each app granting that there is no absolute way one can sniff or read data from the others (unless system vulnerabilities, obviously, but not for the design of the uzpexec).

As a standalone utility uzpexec doesn't need to subdue the strict dd skip=1 constraint, being just an utility on a system. And this explains why the full version for AMR64 is totally fine being 1Kb or whatever minimal size, while the stub (two different for elf and scripts) are designed for the same constraint of the x86 counterpart. Knowing that x86 is for data-centers and arm64 for mobile devices.

Quick ELF32 view

Just to have an idea of the compacting everything in a 1-dd-block challenge, this following schema shows the sections within the 512-byte binary file in which the effective code area is necessarily compressed between the headers and the static data stored in the bottom part: 320 bytes are two 160 chars GSM-era SMS with 8 bit/char text encoding, just to have an idea of the size.

 RAM ADDRESS      Stub/Payload 512 bytes, v0.95    DISK SIZE

 0x08048000 -- +---------------------------------+ -- 0x0000
               |  ELF Header          |   52 B   |
 0x08048034 -- +---------------------------------+ -- 0x0034
               |  Program Header      |   32 B   |
 0x08048054 -- +---------------------------------+ -- 0x0054
               |  Machine Code        |  320 B   |
               | (start -> exit)      |          |
 0x08048194 -- +---------------------------------+ -- 0x0194
               |  Embedded Data:      |  105 B   |
               |  version, provider,  |          |
               |  paths, args, etc.   |          |
 0x080481FD -- +---------------------------------+ -- 0x01FD
               |  Padding (zeros)     |    3 B   |
 0x08048200 -- +---------------------------------+ -- 0x0200
               |  .bss (RAM only)     |  not in  |
               |   buf[516]           |   file   |
               +---------------------------------+

Customising this little guy is easy because the data is stored in plain text which can easily be changed by a sed command line but altering the byte code to change its running behaviour or logic, is completely another story.

Quick ARM64 view

The uzarm64.arm for aarch64 is a 512-byte stub/payload function-tuned created by refactoring down from the full 1024-bytes uzpexec.arm version, introduces some limitations to cut down its size:

  • it uses argv[0] as filename
  • just gzipped elf by execve()
  • qemu-aarch64's bug friendly
  • still has the provider field

Potentially separating the ELF binary from script support, it is reasonable to have two stubs both dd single-block size that a script like zpack.sh can embed as payload and deploy each of them selectively.

$ make testb
$ export WORLD=Wonderful
$ { cat uzarm64; gzip -9c hello; } > hiwld && chmod +x hiwld
$ ~/bin/qemu-aarch64-static -d strace ./hiwld nice
    1147131 prctl(38,1,0,0,0,0) = 0
    1147131 prctl(4,0,0,0,0,0) = 0
    1147131 openat(AT_FDCWD,"/proc/self/exe",O_RDONLY|O_CLOEXEC) = 4
    1147131 memfd_create(124085102814410,3,524288,0,0,0) = 5
    1147131 read(4,0x400200,512) = 512
    1147131 clone(0x11,child_stack=0x0000000000000000,
            parent_tidptr=0x0000000000000000,tls=0x0000000000000000,
            child_tidptr=0x0000000000000000) = 1147134
    1147131 wait4(-1,0,0,0) = 0
    1147134 dup3(4,0,0) = 0
    1147134 dup3(5,1,0) = 1
    1147134 execve("/bin/zcat",{"/bin/zcat",NULL}) =
    1147131 fcntl(5,F_ADD_SEALS,0x000000000000000f) = 0
    1147131 execve("/proc/self/fd/5",{"./hiwld","nice",NULL})
    Hello nice World!
      ARGV0: './hiwld'
       ARGC: '2'
       HOME: '/home/roberto'
      WORLD: 'Wonderful'

The above reported console commands and output provide a reference about the running of a simple ARM64 compressed elf, the system call involved, the args/envp management and last but not the simplicity of assembling it.

Performance test

The use of uzpexec extends every GitHub action from executing whatever is installable by their internal repository to whatever is available by an URL access.

PRE v0.98 RELEASE QUICK TESTS

38.8MB w/ 1st degree dynamic libraries

time qemu-system-x86_64 -m4 >/dev/null 2>&1

    real 0m0.021s
    user 0m0.008s
    sys  0m0.013s

7.5MB musl-static, 2.5MB zstd compressed

time ./qemu-system-x86_64 -m4 >/dev/null 2>&1

    real 0m0.046s
    user 0m0.028s
    sys  0m0.022s

uzpexec executes a binary which has already uzpexec as loader

time cat ./qemu-system-x86_64 | ./uzpexec -m4 >/dev/null 2>&1

    real 0m0.073s
    user 0m0.029s
    sys  0m0.061s

uzpexec executes by STDIN pipe a binary which is zstd compressed

time dd skip=1 if=qemu-system-x86_64 2>&- | ./uzpexec -m4 >/dev/null 2>&1

    real 0m0.046s
    user 0m0.031s
    sys  0m0.029s

Quick tests aim to set a raw reference in differential launch latency

By a raw estimation a 1GBit/s network call is nearly equivalent to a local call, because the 1Gbit/s network transfer time (25 ms) is zeroed by decompression on stream.


Trivial facts

  • Security is a matter of perception, mainly. Currently, more a bureaucratics market rather than a serious R&D field. Hence, it is destabilising seeing an independent developer combining and surfacing 10-20yo techniques that relates to: TeenyELF (2005, darkweb), memfd_create() and execveat() (2014 and 2015, Linux) in glibc (2018).

  • A 64 bit ELF would be much bigger, 2x potentially, and adding no value because this ELF32 doesn't process anything, not even the read() / write() hot loops (since the zero pipes implementation) but just a few system calls.

  • The uzpexec has been developed to compensate for the gzcmd.sh shortcomings and to add useful capability in dealing with STDIN pipe. So, the gzcmd.sh is this project's MVP to reach the production grade with uzpexec.

  • Every sane compressing algorithm is also self-validating in terms of output conformity with the original while executing from a url in pipe is popular but a dangerous action because man-in-the-middle attack.

  • Is uzpexec a stub or a payload or a pipexec? All of them, depending on the role it plays. When it gets embedded in uzpack.sh it is a payload, when uzpack.sh converts itself, it is a stub. When it is used vanilla is a pipexec.

  • Moreover, all these three roles are complementary and necessary. Without pipexec it would not be able to run compressed scripts, hence it would not be able to convert itself. But it does, hence it is also a stub and a payload.

  • This triade of roles, and related capabilities, underlines why uzpexec isn't limited by the unzip-and-run goal, which is the uzpack main pourpose. It is different by design, by audience, by roles.


WRX memory

Separating executable code (X) from writing memory (W) costs too many bytes of code in stubs when a proper design can prevent any practical exploitation of that memory by an unprivileged enough attacker (aka before privileges escalation happens, aka for uzpexec not being the primary vector because this W+X memory design).

  dd 7                        ; p_flags (R+W+X - Read, Write, and Execute)
  dd 0x1000                   ; p_align (Standard page alignment)

  ; Security-by-Design VS Security-by-Subtraction

  ; Because uzpexec contains no input parsing logic that could be corrupted and
  ; its fixed 512-byte read loop cannot be overflowed, the writeable-executable
  ; segment offers no exploitable attack vector.

  ; Since the loader immediately forfeits control through atomic fork() / exec()
  ; or execveat() transitions that never return, an attacker cannot redirect
  ; execution to modified code before the process image is replaced.

  ; An adversary who can already write to the process memory holds sufficient
  ; privileges to inject code via ptrace or mprotect on any standard binary,
  ; which means the R+W+X flag introduces zero additional risk.

  ; WRX is the least of your troubles, but uzpexec as obscenely-powerful tool.

Sealing in RO the anonymous file descriptor before execve() strongly supports security and integrity of the code/script set to run by uzpexec.

  ; ----------------------------------------------------------------------------
  ; HARDENING: F_ADD_SEALS TO MEMFD
  ;
  ; Apply F_ADD_SEALS (F_SEAL_WRITE, etc.) to the memfd exclusively
  ; within the ELF execution path before invoking execveat().
  ;
  ; - ELF hardening: prevents any runtime exploits from tampering with
  ;   or rewriting the binary payload resident in RAM via /proc/self/fd/.
  ;
  ; - Script compatibility: this security measure is omitted for the
  ;   shell interpreter branch because /bin/sh and its sub-utilities
  ;   often require standard read/write descriptors or create temporary
  ;   files, meaning write-restricted seals could break compatibility.
  ; ----------------------------------------------------------------------------

A couple more of constraints strengthen the overall security policy:

  ; ----------------------------------------------------------------------------
  ; HARDENING: NO_NEW_PRIVS & ANTI-CORE-DUMP
  ;
  ; Using umask() is omitted because memfd ignores filesystem permissions.
  ; Additional hardening:
  ; - prctl(NO_NEW_PRIVS) prevents SUID escalation,
  ; - prctl(PR_SET_DUMPABLE, 0) prevents core dumps,
  ; - F_SEAL_SEAL locks the memfd seals permanently,
  ; - MFD_CLOEXEC and F_SEAL_WRITE are already set.
  ; ----------------------------------------------------------------------------

Script to ELF32

  • every script is supported when provided by its own shebang
  • the related interpreter is registered with binfmt_scripts
#!/bin/sh
# ------------------------------------------------------------------------------

            # ############################################### #
            # ####### put your shell script code here ####### #
            # ############################################### #

# ------------------------------------------------------------------------------
exit $ret

Since /bin/dash is far faster than /bin/bash is usually the default shell on most Linux desktop installations and almost always available.

By contrast, gzcmd.sh is designed to create self-extracting executable scripts. Therefore, when the script is complex, implements bashisms, or performs peculiar activity with/by the console, or requires to be self-finding or self-editing as standard file by its executable path then gzcmd.sh is a solid way to go.





gzcmd.sh

A shell script that converts any ELF in a self-extracting executable for standard Unix/POSIX systems

The gzcmd.sh was the predecessor of uzpexec in terms of project planning and evolution. Once the gzcmd.sh shrunk in its payload version below 512 bytes (1 dd standard block), it sets the limit for developing a version in Assembler, the only other universal language that would have a chance to fit an ELF into 512 bytes.


Usage

Alternatively it can be used by activating the execution bit by chmod +x gzcmd.sh or calling it by the shell sh gzcmd.sh, the only parameter that matters is the ELF patch to convert and the converted file will be written in the current directory.

$ UZCMD=pigz ZCMPLVL=11  sh gzcmd.sh gzcmd.sh
FILE: 'gzcmd.gz.sh', HEAD: 506 (512), GZIP: 6635 (6 Kb, 37 %), GZSH: v0.3.3

Install

An ELF binary compressed with gzcmd.sh can be installed permanently providing a directory which resides on a writable and executable disk space:

TMPDIR=~/bin ./$filename.gz.sh    # to install in ~/bin

To use the converted file, launch it directly or by a shell because from the PoV of the Linux kernel is a shell script.


Payload

Previous versions than v0.1.8 had the payload size variable, which was an optimization in terms of overhead but confusing in doing an independent extraction.

Therefore the payload size had been set fixed to 1024 (two 512 blocks) because such a default value allows a trivial extraction of the gzipped appended file:

dd skip=1 if=${filename.gz.sh} | zcat - >${filename}

Starting from the v0.3.1 the payload size has reduced below the 512 (1 block) size. Hence skip=1 instead of skip=2. Moreover the SZE internal value, now it refers to the maximum memory pages (4kB) that the extracted ELF binary will take.

#!/bin/sh
# (C) 2026 robang74 l.MIT v0.3.1 git.new/ttRvFBu
BFN=hello:d713d0d05c;SZE=4
: ${PATH:=/bin:/usr/bin:/usr/local/bin}
exec 2>&-
T=".gzc-$BFN-${USER:-$(id -u)}"
for d in "${TMPDIR:-/tmp}" /run /dev/shm "${HOME:-.}/.cache"
do
F="$d/$T"
(umask 077;echo>"$F"&&chmod 700 "$F"&&"$F")&&break
rm -f "$F"
F=
done
dd if=$0 skip=1|$(command -v pigz gzip gunzip zcat|head -n1) -dc>"$F"&&{
grep -qe "tmpfs.*$d" /proc/mounts&&trap 'rm -f "$F"_' EXIT INT TERM
mv -f "$F" "$F"_&&(F=;exec "$d/$T"_ "$@")
}
exit
#1_345678

The padding line(s) provide(s) enough bytes to reach the fixed payload size, shortening more the payload, requires adding more padding lines, unless the size drops below 512 bytes and in that case bs skip=1 would be enough.

Minimum requirements

In order to run the paylod, a few minimum requirements are need:

  • a Unix/POSIX shell: even minimal, with basic file descriptor management.
  • shell internals: echo, exec, for, command, exit and possibly trap
  • utilities: dd, rm, mv, head, and possibly grep, id
  • alternatives: zcat or every equivalent, umask or chmod
  • environment: PATH, and optional HOME, USER, TMPDIR

All of the requirements are almost always granted in every Unix/POSIX.

About

A 512-byte polymorphic stub/payload (uzpexec) written in Assembler that converts ELF and scripts into executable gzip packages (UZP). It runs in RAM also when started by the STDIN pipe, ssh or wget, and the carryload is trivial to extract by 'dd skip=1'. Pre-compiled ELF32 (for all x86) available in releases. Development happens in 'devel' branch.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages