Pakkero
Pakkero is a binary packer written in Go made for fun and educational purpose. Its main goal is to take in input a program file (elf binary, script, even appimage) and compress it, protect it from tampering and intrusion.
Install / Use
/learn @89luca89/PakkeroREADME
Pakkero
<img src="pics/logo.jpg" data-canonical-src="pics/logo.jpg" width="250" height="250" />Credit: alegrey91 for the logo! Thanks!
Introduction
Pakkero is a binary packer written in Go made for fun and educational purpose.
Its main goal is to take in input a program file (elf binary, script, even appimage) and compress it, protect it from tampering and intrusion.
It is not recommended for very small files as the launcher itself can vary from ~700kb to ~1.7mb depending on compression. On files above 2.6mb there is gain, else the resulting binary is larger than the original:
base-bin 1.2M -> 1.6M
smaller-bin 2.4M -> 2.3M
small-bin 3.7M -> 3.0M
medium-bin 25M -> 16M
big-bin 148M -> 88M
With compression disabled, all resulting file size are ~1mb higher, making it suitable for 5+mb files.
How compares to UPX?
Tested with a 24mb binary file (I didn't have a big project handy so I just concatenated a bunch of programs from /usr/bin/ to make one big elf) became:
- 12mb using
upx -9 - 13mb using
pakkero -c - 14mb using
pakkerowithout compression
Install
If you have a Go environment ready to go, it's as easy as:
go get github.com/89luca89/pakkero
Once you retrieved you are ready to build:
cd $GOPATH/src/github.com/89luca89/pakkero; make
or to test
cd $GOPATH/src/github.com/89luca89/pakkero; make test
The binary file will be in $GOPATH/src/github.com/89luca89/pakkero/dist
The following are hard dependencies:
- go -> to build the launcher
- ls
- sed
- strip -> to strip the launcher
The following are weak dependencies
- upx -> needed for launcher compression (optional)
GO 1.13+ needed
Dependencies are checked at runtime and an error message will specify what is missing
Disclaimer
This is a for-fun and educational project, complete protection for a binary is impossible, in a way or another there is always someone that will reverse it, even if only based on 0 an 1, so this is more about exploring some arguments that to create an anti-reverse launcher.
Pakkero is divided in two main pieces, the packer part (Pakkero itself) and the launcher part.
Part 1: the packer
Pakkero can be launched like:
pakkero --file ./target-file -o ./output-file -register-dep dependency-file -c

Usage
Typing pakker -h the following output will be shown:
Usage: pakkero -file /path/to/file -offset OFFSET (-o /path/to/output) (-c) (-register-dep /path/to/file)
-file <file> Target file to Pack
-o <file> place the output into <file> (default is <inputfile>.enc), optional
-c compress the output to occupy less space (uses UPX), optional
-offset Offset where to start the payload (Number of Bytes)
-enable-stdout Whether to wait and handle the process stdout/sterr or not (false by default, optional)
-register-dep /path/to/dependency to analyze and use as fingerprint (absolutea, optional)
-v Check pakkero version
Below there is a full explanation of provided arguments:
- file: The file we want to pack
- o: (optional) The file output that we will create
- c: (optional) If specified, UPX will be used to further compress the Launcher
- offset: (optional) The number of bytes from where to start the payload (increases if not using compression)
- enable-stdout (optional) whether to enable or not the handling of the stdout/err of the payload <u>disabled by default, less secure</u>
- regiser-dep (optional) Path to a file that can be used to register the fingerprint of a dependency to ensure that the Launcher runs only if a file with similar fingerprint is present
- v: Print version
Packaging
The main intent is to not alter the payload in any way, this can be very important for types of binary that rely on specific order of instructions or relatively fragile timings.
The target of pakkero is not to touch the "payload". Other packers like UPX works by compressing the sections stored within the Section Table of the executable file, relocating the sections and renaming them. It then alters the entry point where the binary will run. While this is really what defines storically a packer, this in some way or another "touches" the payload so can make it unusable (when it works on strict timing, precise elf sections tricks and so on)
Building
To build the project, you can simply use the Makefile;
-
makewill compile -
make testwill compile and run a run with a simple binary (echo)
Why not using simply go build?
Go build works fine, but will skip a fundamental step in the building process, the injection of the launcher stub inside Pakkero source
This way the Pakkero binary has inside the source of the Launcher to be used for each packaging.
Building using Docker
Build Pakkero image:
sudo docker build . -t pakkero
Run containerized Pakkero:
sudo docker run -it -v <target_dir>:/ext pakkero --file /ext/<target_elf> -o /ext/<target_elf>.packed
Building using Podman
Build Pakkero image:
sudo podman build . -t pakkero
Run containerized Pakkero:
sudo podman run -it -v <target_dir>:/ext pakkero --file /ext/<target_elf> -o /ext/<target_elf>.packed
Payload
For this purpose the payload is simply compressed using zlib then encrypted using AES256-GCM
During encryption, some basic operations are also performed on the payload:
- putting garbage random values before and after the payload to mask it
- reverse it and change each byte endianess
Encryption password is the hash SHA512 of the compiled launcher itself together with the garbage values added to fill the file till the offset, thus providing some integrity protection and anti-tampering.
Offset
The offset will decide where in the output file the payload starts.
Put simply, after the launcher is compiled (more on the launcher later), the payload is attached to it. The offset ensures that the payload can be put anywhere after it. All the space after the launcher until the payload is filled with random garbage.

Being part of the password itself, greater offset will make stronger the encryption, but enlarge the final output file.
Optimal value are at least 800000 when compression is enabled and 1900000 when disabled. *If not specified a random one will be chosen upon creation.
Obfuscation
The final thing the packer does is compiling the launcher. To protect some of the fundamental part of it (namely where the offset starts) the launcher is obfuscated and heavily stripped down.
The technique utilized for obfuscating the function and variables name is based on typo-squatting:

This is done in a pretty naive way, simply put, in the launcher each function/variable which name has to be obfuscated, needs to start with the suffix ob, it will be then put into a secret map, and each occurrence will be replaced in the file with a random string of length 128, composed only of runes that have similar shape, namely:
mixedRunes := []rune("0OÓÕÔÒÖŌŎŐƠΘΟ")
For pure strings in the launcher, they are detected using regular expressions, finding all the words that are comprised between the three type of ticks supported in go
`
'
"
All of the strings found this way, are then replaced with a function that performs a simple operation of reconstruction of the original string:
func ÓΘŌOÒŐÒŌÓÒOŎΘOΟ0ŐÒÖŎÕΟΘÕÓÕÓŎÓŌÕ0ŎŌΘÕŎÕ() string {
ŌÒ0ŎŎŐÓÖÖΘO0ŌŌŌÒŌŌÒƠÔÖΘŐÖΟŎƠƠ00Õ0ÖÕ0ÖŐŐÓΟŌΟ := []string{"Ò0ƠŐÖŐŎΘÖƠÔÖÕÓΘÕÕŌŎŐƠÔΘΘƠ", "ÔÒÕΟƠŐÒŌOƠÖ", "ƠΘŐÒƠΘŌ00ΘΘΟÔŎŎΘŐƠŐΘŎΟÕÖÕÖΟÖΘÒÖ"}
var ΟŐÔÒÖÔΘÕÔŎÒÓÖÖÒΘ0ÖÔΟÖŎ0ÔOÓÖƠŌÔÓŌŌ []byte
for _, ŌΘƠÔÕÔΘÔOÔOŐΟΘŌΘƠÔÕÔΘÔOÔOŐΟΘ := range ŌÒ0ŎŎŐÓÖÖΘO0ŌŌŌÒŌÕÒÔÕŌŎ00ÔÔÒOƠÓÕÔÒ0ΘƠΘŐ0OOŎÓÒŐ0Õ0ÓOÕÓŐƠŌŎÕÖ0ÕÖÔŌΟΟŌÒÖÒÖOΟOÒ0ÖŐŐÓΟŌΟ {
ΟŐÔÒÖÔΘÕÔŎÒÓÖÖÒΘ0ÖÔΟÖŎ0ÔOÓÖƠŌÔÓŌŌ = append(ΟŐÔÒÖÔΘÕÔŎÒÓÖÖÒΘ0ÖÔΟÖŎ0ÔOÓÖƠŌÔÓŌŌ, byte(len([]rune(ŌΘƠÔÕÔΘÔOÔOŐΟΘŌΘƠÔÕÔΘÔOÔOŐΟΘ))))
}
return string(ΟŐÔÒÖÔΘÕÔŎÒÓÖÖÒΘ0ÖÔΟÖŎ0ÔOÓÖƠŌÔÓŌŌ)
}
A slice of string is generated, with each element has a lenght derived from the byte value of the original char of the original string. This way each byte of the original string is computed and calculated as the lenght of the correspondent string, casted to rune slice.
The launcher is compiled then using:
flags = []string{"build", "-a",
"-trimpath",
"-gcflags",
"-N -l -nolocalimports",
"-ldflags",
"-s -w -extldflags -static",
}
exec.Command("go", flags...)
File is the stripped, using strip with the flags:
-sxX
--remove-section=.bss
--remove-section=.comment
--remove-section=.eh_frame
--remove-section=.eh_frame_hdr
--remove-section=.fini
--remove-section=.fini_array
--remove-section=.gnu.build.attributes
--remove-section=.gnu.hash
--remove-section=.gnu.version
--remove-section=.gosymtab
--remove-section=.got
--remove-section=.note.ABI-tag
--remove-section=.note.gnu.build-id
--remove-section=.note.go.buildid
--remove-section=.shstrtab
--remove-section=.typelink
Additionally, if using UPX, their headers are removed and replaced with randomness, to ensure si
