The prerequisite for this part of the tutorial is a basic understanding of ARM assembly (covered in the first tutorial series “ARM Assembly Basics“). In this part, you will learn how to use your knowledge to create your first simple shellcode in ARM assembly. The examples used in this tutorial are compiled on an ARMv6 32-bit processor. If you don’t have access to an ARM device, you can create your own lab and emulate a Raspberry Pi distro in a VM by following this tutorial: Emulate Raspberry Pi with QEMU.
This tutorial is for people who think beyond running automated shellcode generators and want to learn how to write shellcode in ARM assembly themselves. After all, knowing how it works under the hood and having full control over the result is much more fun than simply running a tool, isn’t it? Writing your own shellcode in assembly is a skill that can turn out to be very useful in scenarios where you need to bypass shellcode-detection algorithms or other restrictions where automated tools could turn out to be insufficient. The good news is, it’s a skill that can be learned quite easily once you are familiar with the process.
For this tutorial we will use the following tools (most of them should be installed by default on your Linux distribution):
- GDB – our debugger of choice
- GEF – GDB Enhanced Features, highly recommended (created by @_hugsy_)
- GCC – Gnu Compiler Collection
- as – assembler
- ld – linker
- strace – utility to trace system calls
- objdump – to check for null-bytes in the disassembly
- objcopy – to extract raw shellcode from ELF binary
Make sure you compile and run all the examples in this tutorial in an ARM environment.
Before you start writing your shellcode, make sure you are aware of some basic principles, such as:
- You want your shellcode to be compact and free of null-bytes
- Reason: We are writing shellcode that we will use to exploit memory corruption vulnerabilities like buffer overflows. Some buffer overflows occur because of the use of the C function ‘strcpy’. Its job is to copy data until it receives a null-byte. We use the overflow to take control over the program flow and if strcpy hits a null-byte it will stop copying our shellcode and our exploit will not work.
- You also want to avoid library calls and absolute memory addresses
- Reason: To make our shellcode as universal as possible, we can’t rely on library calls that require specific dependencies and absolute memory addresses that depend on specific environments.
The Process of writing shellcode involves the following steps:
- Knowing what system calls you want to use
- Figuring out the syscall number and the parameters your chosen syscall function requires
- De-Nullifying your shellcode
- Converting your shellcode into a Hex string