SkillAgentSearch skills...

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/Pakkero
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Pakkero

<img src="pics/logo.jpg" data-canonical-src="pics/logo.jpg" width="250" height="250" />

Credit: alegrey91 for the logo! Thanks!

Go Report Card GPLv3 license FOSSA Status

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 pakkero without 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

demo

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;

  • make will compile

  • make test will 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.

payload

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:

obfuscation

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

View on GitHub
GitHub Stars272
CategoryEducation
Updated19d ago
Forks45

Languages

Go

Security Score

100/100

Audited on Mar 20, 2026

No findings