Sometimes you need to compile an exploit or application for a machine with a different architecture than your host machine. The target machine might have a compiler, but what would you do if it didn't have one?

One of the best options is to spin up a similar machine in a virtual box to compile the application, but spinning up a new virtual machine to compile one exploit is tedious and overkill. This is where cross-compiling takes the spotlight. It's a perfect solution for compiling the small application and exploits as per your need in your host machine without installing another OS.

In this article, I will be showing you how we can compile a C application for 32-bit Linux OS on your 64-bit Linux machine. For this article, I will be using an old exploit that you can find here for demo purposes.

Installing Necessary Packages

To start compiling, we need to install the necessary package i.e gcc and gcc-multilib.

sudo apt-get install gcc
sudo apt-get install gcc-multilib

Compiling the Binary

Let's try to compile the exploit normally.

~/test ❯ gcc exploit.c -o exploit     
exploit.c: Assembler messages:
exploit.c:48: Error: operand type mismatch for `push'
exploit.c:51: Error: invalid instruction suffix for `push'
exploit.c:54: Error: invalid instruction suffix for `pop'
Trying to Compile Normally

It will show you an error. Now, compile the exploit with -m32 option and check if it is 32-bit binary with file command. Also, if you are compiling a binary for a really old kernel, don't forget to add -Wl,--hash-style=both command-line arguments.

~/test ❯ gcc -m32 exploit.c -o exploit -Wl,--hash-style=both
~/test ❯ ls
exploit  exploit.c 
~/test ❯ file exploit 
exploit: ELF 32-bit LSB pie executable
Proper Compiling

Difference Between Static and Dynamic

This is not all. We also need to know the difference between a statically-linked (static) executable and a dynamically-linked (shared) executable.

A static executable includes all of the code needed for the executable to run (libraries that are chosen at a link-time). A shared executable will only contain the code you're actually compiling into the resulting file. Shared libraries end with .so are often found in /usr/lib32, /usr/lib64, /usr/lib however, you can install libraries in any location you want.

As you can probably guess from above, static executables are always larger than shared ones since they include all the necessary libraries. For example, below are the sizes of static and shared binaries compiled from the same exploit.

~/test ❯ ls -la
total 724
drwxr-xr-x 1 user user     74 Apr 20 09:59 .
drwxr-xr-x 1 user user    870 Apr 20 09:59 ..
-rw-r--r-- 1 user user   2536 Apr 20 09:03 exploit.c
-rwxr-xr-x 1 user user  19828 Apr 20 09:59 exploit-shared
-rwxr-xr-x 1 user user 706860 Apr 20 09:58 exploit-static
Shared and Static Sizes

You can use the ldd command to check the dependent libraries.

~/test ❯ file exploit-*
exploit-shared: ELF 32-bit LSB pie executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/, BuildID[sha1]=6f9dae988596c22bdcd6eccebd4f44e47bdb8ff6, for GNU/Linux 3.2.0, not stripped
exploit-static: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, BuildID[sha1]=4d4e20672b57a9b139a4d4d1cbe04c4a61a1db59, for GNU/Linux 3.2.0, not stripped

~/test ❯ ldd exploit-*
exploit-shared: (0xf7f3e000) => /lib32/ (0xf7d29000)
        /lib/ (0xf7f40000)
        not a dynamic executable
Dependent Libraries

Depending on the situation, you might need to compile a shared or static binary because one may work while the other may not (as shown in the picture below).

Use Cases
Thanks for reading! See you in the next one.