pakkero

所属分类:加密解密
开发工具:GO
文件大小:883KB
下载次数:0
上传日期:2023-01-16 21:07:13
上 传 者sh-1993
说明:  Pakkero是一个用Go编写的二进制打包器,用于娱乐和教育目的。它的主要目标是接受输入一个项目...
(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.)

文件列表:
Dockerfile (342, 2023-01-17)
LICENSE (35149, 2023-01-17)
Makefile (1884, 2023-01-17)
data (0, 2023-01-17)
data\launcher.go (17943, 2023-01-17)
go.mod (100, 2023-01-17)
go.sum (173, 2023-01-17)
internal (0, 2023-01-17)
internal\pakkero (0, 2023-01-17)
internal\pakkero\encryption.go (1795, 2023-01-17)
internal\pakkero\obfuscation.go (10719, 2023-01-17)
internal\pakkero\pakkero.go (10016, 2023-01-17)
internal\pakkero\utilities.go (4615, 2023-01-17)
main.go (2766, 2023-01-17)
pics (0, 2023-01-17)
pics\bfd-std.png (45609, 2023-01-17)
pics\compressed-entropy.png (26279, 2023-01-17)
pics\decryption.png (24098, 2023-01-17)
pics\demo.png (50437, 2023-01-17)
pics\handle-stdout-2.png (84302, 2023-01-17)
pics\handle-stdout.png (90448, 2023-01-17)
pics\logo.jpg (111513, 2023-01-17)
pics\obfuscation.png (335709, 2023-01-17)
pics\original-entropy.png (48186, 2023-01-17)
pics\readelf.png (97950, 2023-01-17)
pics\uncompressed-entropy.png (28807, 2023-01-17)

# Pakkero Credit: [alegrey91](https://github.com/alegrey91) for the logo! Thanks! [![Go Report Card](https://goreportcard.com/badge/github.com/89luca89/pakkero)](https://goreportcard.com/report/github.com/89luca89/pakkero) [![GPLv3 license](https://img.shields.io/badge/License-GPLv3-blue.svg)](http://perso.crans.org/besson/LICENSE.html) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2F89luca89%2Fpakkero.svg?type=small)](https://app.fossa.com/projects/git%2Bgithub.com%2F89luca89%2Fpakkero?ref=badge_small) ## 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](https://golang.org/) environment ready to go, it's as easy as: ```bash go get github.com/89luca89/pakkero ``` Once you retrieved you are ready to build: ```bash cd $GOPATH/src/github.com/89luca89/pakkero; make ``` or to test ```bash 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: ```bash pakkero --file ./target-file -o ./output-file -register-dep dependency-file -c ``` ![demo](pics/demo.png) ### Usage Typing `pakker -h` the following output will be shown: ```bash Usage: pakkero -file /path/to/file -offset OFFSET (-o /path/to/output) (-c) (-register-dep /path/to/file) -file Target file to Pack -o place the output into (default is .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 **disabled by default, less secure** * **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: ```sh sudo docker build . -t pakkero ``` Run containerized Pakkero: ```sh sudo docker run -it -v :/ext pakkero --file /ext/ -o /ext/.packed ``` #### Building using Podman Build Pakkero image: ```sh sudo podman build . -t pakkero ``` Run containerized Pakkero: ```sh sudo podman run -it -v :/ext pakkero --file /ext/ -o /ext/.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](./pics/decryption.png) 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](./pics/obfuscation.png) 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: ```go mixedRunes := []rune("0OOOOOOOOOOΘΟ") ``` 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: ```go func OΘOOOOOOOOOOΘOΟ0OOOOOΟΘOOOOOOOO0OOΘOOO() string { OO0OOOOOOΘO0OOOOOOOOOOΘOOΟOOO00O0OO0OOOOΟOΟ := []string{"O0OOOOOΘOOOOOOΘOOOOOOOΘΘO", "OOOΟOOOOOOO", "OΘOOOΘO00ΘΘΟOOOΘOOOΘOΟOOOOΟOΘOO"} var ΟOOOOOΘOOOOOOOOΘ0OOΟOO0OOOOOOOOOO []byte for _, OΘOOOOΘOOOOOΟΘOΘOOOOΘOOOOOΟΘ := range OO0OOOOOOΘO0OOOOOOOOOOO00OOOOOOOOO0ΘOΘO0OOOOOO0O0OOOOOOOOOO0OOOOΟΟOOOOOOΟOO0OOOOΟOΟ { ΟOOOOOΘOOOOOOOOΘ0OOΟOO0OOOOOOOOOO = append(ΟOOOOOΘOOOOOOOOΘ0OOΟOO0OOOOOOOOOO, byte(len([]rune(OΘOOOOΘOOOOOΟΘOΘOOOOΘOOOOOΟΘ)))) } return string(ΟOOOOOΘOOOOOOOOΘ0OOΟOO0OOOOOOOOOO) } ``` 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: ```go 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: ```bash -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 simple things like `upx -d` will not work. Additionally a series of extra words are removed from the binary and replaced with random bytes, to make it harder to do static analysis: ``` .gopclntab .go.buildinfo .noptrdata .noptrbss .data .rodata .text .itablink .shstrtab .data .dynamic .dynstr .dynsym .gnu.version_r .gopclntab .got.plt .init_array .interp .itablink .rela.dyn .rela.plt .tbss .plt .init name runtime command cmd ptr process unicode main path get reflect context debug fmt sync sort size heap fatal call fixed slice bit file read write buffer encrypt decrypt hash state external internal float env trace pid ``` Output of readelf to see the effect: ![readelf](./pics/readelf.png) #### File Entropy Using binwalk to analyze the file entropy can give some hint on how the process works: This is the entropy of the binary we want to package (for this example /usr/bin/bash): ![original-entropy.png](./pics/original-entropy.png) This is the entropy of a packaged binary **without compression** ![uncompressed](./pics/uncompressed-entropy.png) This is the entropy of a packaged binary **with compression** ![compressed](./pics/compressed-entropy.png) In both cases (but mainly the first) it is possible to see when the launcher stops and the garbage before the payload starts. This is really not a problem, because the offset of garbage is both pre-poned **and** post-poned to the payload, and the "secret number" of when it starts is kept inside the launcher and computed at runtime. This is obviously vulnerable, reversing the binary will reveal the secret, all the launcher part is dedicated to the implementation of a series of measures to **block dynamic analysis** and try to force static analysis. ## Part 2: the launcher The launcher is the second part of the project, it allows to decompress, decrypt and launch the payload without touching storage, but using a file descriptor in RAM. This is a well known technique as it is possible to read: - [In-Memory-Only ELF Execution (Without tmpfs) Mrs Quis Will Think of a Better Title](https://magisterquis.github.io/2018/03/31/in-memory-only-elf-execution.html) - [ELF in-memory execution](https://blog.fbkcs.ru/en/elf-in-memory-execution/) and in many other places in C programming literature. ``` Put briefly, use syscall to create a memory file descriptor (syscall 319 for amd***), write the plaintext payload here, and execute. The fd will be automatically removed after the execution without leaving trace on the storage. ``` ### Possible weakpoints This approach is vulnerable to 1. "memory dump attack", for example pausing the VM during execution and manually search the ram for all file descriptors until you find the right one 2. "proc dump attack", in linux all the file descriptors are in `/proc` so dumping to another disk the complete folder will in a way or another dump the decrypted payload (if done before the execution finishes), **this is even more accentuated if stdout management is enabled** 3. dynamic analysis, during execution "pausing" the process and spot the right fd 4. reversing the binary to find the "secret" (which in our case is the offset) and from there, reverse the encryption process and reconstruct the plaintext For point 1, it is possible to insert hypervisor detection, sandbox detection etc... it is in my TODO list, but I would leave it optional, in case you genuinely want to run the binary in VMs or Dockers. Point 3 (and by consequence point 4) can be made harder by blocking dynamic analysis detecting debuggers, tracers and so on... Forcing static analysis of the decompiled code is already a big step forward in protecting the binary execution. ### Anti-debug Implemented here are a series of anti-debug techniques that are quite common in C/C++, from the **double-ptrace method** to the **ppid analysis** and breakpoints interception. First line of protection is breakpoints interception, on linux, a breakpoints is equivalent to signal *SIGILL* and *SIGTRAP* so: ```go /* Breakpoint on linux are 0xCC and will be interpreted as a SIGTRAP, we will intercept them. */ func obSigTrap(obInput chan obOS.Signal) { obMySignal := <-obInput switch obMySignal { case obSyscall.SIGILL: obExit() case obSyscall.SIGTRAP: obExit() default: return } } ``` This is pretty basic, so we go ahead and try to block **ptrace**: ```go // attach to PTRACE, register if successful // attach A G A I N , register if unsuccessful // this protects against custom ptrace (always returning 0) // against NOP attacks and LD_PRELOAD attacks func obPtraceDetect() { var obOffset = 0 obProc, _ := obOS.FindProcess(obOS.Getppid()) obErr := obSyscall.PtraceAttach(obProc.Pid) if obErr == nil { obOffset = 5 } obErr = obSyscall.PtraceAttach(obProc.Pid) if obErr != nil { obOffset *= 3 } if obOffset != (3 * 5) { obProc.Signal(obSyscall.SIGCONT) println(1) return } obErr = obSyscall.PtraceDetach(obProc.Pid) if obErr != nil { obProc.Signal(obSyscall.SIGCONT) println(0) return } obProc.Signal(obSyscall.SIGCONT) println(0) } ``` *Double ptraceme* ensures that tampering the ptrace loading with a fake ptrace lib that always returns 0, would result in a failure. CMD Line detection, would check for common processes for debugging, this is pretty naive check: ```go /* Check the process cmdline to spot if a debugger is inline */ func obParentCmdLineDetect() { obPidParent := obOS.Getppid() obNameFile := "/proc/" + obStrconv.FormatInt(int***(obPidParent), 10) + "/cmdline" obStatParent, _ := obUtilio.ReadFile(obNameFile) if obStrings.Contains(string(obStatParent), "gdb") || obStrings.Contains(string(obStatParent), "dlv") || obStrings.Contains(string(obStatParent), "edb") || obStrings.Contains(string(obStatParent), "frida") || obStrings.Contains(string(obStatParent), "ghidra") || obStrings.Contains(string(obStatParent), "godebug") || obStrings.Contains(string(obStatParent), "ida") || obStrings.Contains(string(obStatParent), "lldb") || obStrings.Contains(string(obStatParent), "ltrace") || obStrings.Contains(string(obStatParent), "strace") || obStrings.Contains(string(obStatParent), "valgrind") { obExit() } } ``` ```go /* Check the process cmdline to spot if a debugger is the PPID of our process */ func obParentDetect() { obPidParent := obOS.Getppid() obNameFile := "/proc/" + obStrconv.FormatInt(int***(obPidParent), 10) + "/stat" obStatParent, _ := obUtilio.ReadFile(obNameFile) if obStrings.Contains(string(obStatParent), "gdb") || obStrings.Contains(string(obStatParent), "dlv") || obStrings.Contains(string(obStatParent), "edb") || obStrings.Contains(string(obStatParent), "frida") || obStrings.Contains(string(obStatParent), "ghidra") || obStrings.Contains(string(obStatParent), "godebug") || obStrings.Contains(string(obStatParent), "ida") || obStrings.Contains(string(obStatParent), "lldb") || obStrings.Contains(string(obStatParent), "ltrace") || obStrings.Contains(string(obStatParent), "strace") || obStrings.Contains(string(obStatParent), "valgrind") { obExit() } } ``` this goes in conjunction with the TracePid check to see if a parent is tracing us: ```go /* Check the process status to spot if a debugger is active using the TracePid key */ func obParentTracerDetect() { obPidParent := obOS.Getppid() obNameFile := "/proc/" + obStrconv.FormatInt(int***(obPidParent), 10) + "/status" obStatParent, _ := obUtilio.ReadFile(obNameFile) obStatLines := obStrings.Split(string(obStatParent), "\n") for _, obValue := range obStatLines { if obStrings.Contains(obValue, "TracerPid") { obSplitArray := obStrings.Split(obValue, ":") obSplitValue := obStrings.Replace(obSplitArray[1], "\t", "", -1) if obSplitValue != "0" { obExit() } } } } ``` and verification that the process cmdline corresponds to "argv[0]" (for example, launching `strace mybin arg1` would result in a cmdline of `strace` and argv[0] of `mybin` ```go /* Check the process cmdline to spot if a debugger is launcher "_" and Args[0] should match otherwise */ func obEnvArgsDetect() { obLines, _ := obOS.LookupEnv("_") if obLines != obOS.Args[0] { obExit() } } ``` and check if there is none of the known debuggers inline with the command ```go /* Check the process cmdline to spot if a debugger is inline "_" should not contain the name of any debugger */ func obEnvParentDetect() { obLines, _ := obOS.LookupEnv("_") if obStrings.Contains(obLines, "gdb") || obStrings.Contains(obLines, "dlv") || obStrings.Contains(obLines, "edb") || obStrings.Contains(obLines, "frida") || obStrings.Contains(obLines, "ghidra") || obStrings.Contains(obLines, "godebug") || obStrings.Contains(obLines, "ida") || obStrings.Contains(obLines, "lldb") || obStrings.Contains(obLines, "ltrace") || obStri ... ...

近期下载者

相关文件


收藏者