A makefile to build shellcodes

Spoiler: Instead of remembering (and typing) all the needed commands to build and inject our shellcodes, let’s delegate this pain to a makefile.

While writing our book about shellcodes, we had to write lots of them, with lots of variations to see which were more instructive and, of corse, we often had to test them all to check they worked as expected.

The book contains the commands to translate the shellcode source to their injectable form. They are quite simple but it’s always more convenient when routine tasks are automated.

And since the book used classic compilation tools, make was also available which bring us the ability to build files when we express the reciepes. The result was pretty usefull so we thought it was worth sharing with you.

Let’s start with a first rule to write in your makefile, for the target all, as follows:

all:

With that rule, you know that when you’ll run make without argument, that target will be build. It does nothing but if you want to build something, you can add any reciepe or dependency. Here or below in the file.

Shellcode Example

There are plenty ways to write shellcodes. Some authors giv them as C strings (where the characters are expressed as hex codes) and other give them in assembly (Intel or AT&T syntax).

In our book, we chose to write instructions in assembly (AT&T syntax) along with their translation in hexadecimal codes (in comments), face to face. That way, it’s easier so explain and understand the translation.

It won’t be the most impressive experience of your life, but here is how it looks with arthurdent.s, a shellcode that terminate with it specific error status 42 (cf. page 44).

ArthurDent:
    movabs $0x3c, %rax   # 48 B8 3C 00 00 00 00 00 00 00
    movabs $0x2a, %rdi   # 48 BF 2a 00 00 00 00 00 00 00
    syscall              # 0F 05

Build injectable files

Throught hexcodes

The advantage of putting the hexadecimal translation in comment, and using the number sign (#) to begin them, is that we can automatically extract the translation with one sed rule (cf. page 21) and put the rule in a reciepe as follows:

%.hex: %.s
    sed -n 's/.*#//p' $< > $@

This rule tell make that it can take any file ending with .s (our assembly files), one can produce a file with same name but extention .hex by using the bash command line on the second line. This rule uses two automatic variables:

Once the hexcodes extracted from the file, one can convert it in binary with xxd and once again, the reciepe is quite simple:

%.bin: %.hex
    xxd -r -p $< > $@

Thos two rules are sufficient to convert any of our shellcodes’ source code in a binary and injectable form. Here is an example of execution:

$ make arthurdent.bin
sed -n 's/.*#//p' arthurdent.s > arthurdent.hex
xxd -r -p arthurdent.hex > arthurdent.bin
rm arthurdent.hex

When we ask make to build the file, it managed itself to find and execute the needed reciepes (sed then xxd). And since those rules are generic, it deletes the temporary file.

Through assembly

The two previous reciepes need us to provide the translation in hexcodes. Since we do not always want to do this job, here are two other rules to produce the injectable shellcode through its assembly code (cf. page 24):

%.o: %.s
    as -o $@ $^
    
%.raw: %.o
    objdump -j .text -O binary $< $@

The first rule produce an object file containing the assembled code (by gas) and the second rule extract the instructions part to produce the injectable file.

Cleaning

To avoid polluting your directory with all those files produced by the reciepes, we add a cleaning rule (traditionnaly called clean) as follows:

clean:
    rm -f *.o *.raw *.hex *.bin
    
.PHONY: clean

The two first lines define the reciepe (delete the file corresponding to the four extensions). The las rule is a bit special, it tells make to consider the dependency (i.e. clean) as not being a file but a simple keyword (without this line, if you create a file named clean, newer than the shellcode, make will never execute the clean reciepe).

Variation on Windows

To keep it simple, the book propose to use WinLibs to compiles the loaders and the shellcodes, keeping the sames tools as GNU/Linux (gcc, gas, objcopy, …). The reciepes to produce shellcodes through assembly are the same, but those through hexcodes need to be replaced.

The command used to extract hexcode (cf. page 196) can not be written in the makefile because make dislike Powershell (and vice versa).

We’ve then written the command in a powershell script file grepsed.ps1. Changing it a little to allows the script to uses parameters from command line.

Select-String -Path $args[0] -Pattern "#"         `
| foreach-object {([string]$_).split("#")[1] }    `
> $args[1]

That way, the rule to produce hex file can be expressed in makefile: it tells make to launch the powershell script.

%.hex: %.s
    powershell grepsed.ps1 $< $@

The reciepe to produce the binary file from the hex is quite simple. We replace xxd with certutil.exe (and adapt some parameters).

%.bin: %.hex
    certutil.exe -decodehex $< $@

Automatic injection

The previous rules are sufficient to produces the injectable files from the book’s source codes. But we might go further and also automate the injection.

Building the loader

To automate the injectino, we need an operationnal loader. The rule are more classical:

loader: loader.c
    gcc -o loader loader.c
    
cleanall: clean
    rm -f loader
    
.PHONY: cleanall

Inject the shellcode

We only need to write rules to execute our shellcodes by injecting them in the loader. These reciepes are special because:

  1. They are not written to produce any file, we should put them in .PHONY,
  2. The rules in .PHONY can’t be generic… the classic way won’t work.

We’ll then use a trick to force make to execute those reciepes, whether a file already exists or not. This tric involve a target that have no dependency and no reciepe (hereafter called .FORCE) and put it as dependency of the reciepe we want to force.

.FORCE:

%-bin: %.bin loader .FORCE
    ./loader $<

%-raw: %.raw loader .FORCE
    ./loader $<

With those rules, we can easily run the shellcodes whenever we update it (or the loader) as follows:

$ make arthurdent-bin
gcc -o loader loader.c
sed -n 's/.*#//p' arthurdent.s > arthurdent.hex
xxd -r -p arthurdent.hex > arthurdent.bin
./loader arthurdent.bin
make: *** [makefile:33 : arthurdent] Error 42
rm arthurdent.bin arthurdent.hex

The error 42 in the last but one line is the expected behaviour. The loader has executed the shellcode which terminate with 42 code. This command have been launched by make, it’s thus make that catched the code. As it is expected to do, make interpret this non-zero code as an error and show this to us (and that’s we wanted to).

And after?

Nothing prevent you from adapt and extend this makefile. AFAIK, we added thos three reciepes:

%-gas: %.s .FORCE
    as $< -al -o /dev/null
    
%-objd: %.o .FORCE
    objdump -d $<
    
%-strace: % loader .FORCE
    strace ./loader $<

The first display the assembly produced by gas (cf. page 22), the second display the disassembly done by objdump (cf. page 23). The third is not in the book and uses strace to show (and follow) the list of syscalls made by the loader and the shellcode (usefull for debug).