Welcome back, aspiring security researchers! In the world of offensive security, understanding assembly language is important for exploit development, reverse engineering, and vulnerability research. Whether you’re analyzing malware, developing proof-of-concept, or conducting security assessments on ARM-based IoT devices, assembly knowledge gives you the skills to understand how vulnerabilities actually work at the processor level. As […]
The post ARM Assembly: Getting Started first appeared on Hackers Arise.
Welcome back, aspiring security researchers!
In the world of offensive security, understanding assembly language is important for exploit development, reverse engineering, and vulnerability research. Whether you’re analyzing malware, developing proof-of-concept, or conducting security assessments on ARM-based IoT devices, assembly knowledge gives you the skills to understand how vulnerabilities actually work at the processor level.
As you know, many security vulnerabilities exist at the boundary between high-level code and low-level system operations. Buffer overflows, return-oriented programming (ROP), and other exploitation techniques require deep understanding of how programs interact with memory and the processor. Assembly language is the key to understand these concepts.
Besides that, modern exploit development increasingly targets ARM processors due to their prevalence in mobile devices and IoT systems.
ARM Assembly knowledge enables several key security research capabilities:
- Binary Analysis: Understanding how compiled programs actually execute
- Exploit Development: Crafting precise memory corruption exploits
- Reverse Engineering: Analyzing malware and proprietary software
- IoT Security Research: Testing ARM-based embedded devices
- Mobile Security: Understanding Android and iOS internals
Core advantages of learning ARM Assembly:
- RISC Simplicity: Fewer instructions make reverse engineering more straightforward
- Predictable Behavior: ARM’s design makes exploit development more reliable
- Widespread Deployment: Skills apply across mobile, embedded, and cloud environments
- Research Platform: Raspberry Pi provides affordable hardware for security testing
Let’s set up a security research environment and explore ARM assembly!
Step #1: Hardware Setup
Generally you have a few options for creating an ARM environment:
1. Raspberry Pi (Recommended – I’ll use Pi 4)
- Real ARM hardware with excellent performance
- Perfect for IoT security research scenarios
- One-time cost (~$35-75)
- Direct hardware access for advanced experiments
2. Cloud Virtual Machines
- AWS, Google Cloud, Oracle instances
- Scalable but ongoing costs
- No hardware purchase required
3. Online CPU Emulators
- Zero setup, browser-based (CPUlator, VisUAL)
- Limited capabilities – only basic instruction execution
- Good for initial learning but not real exploit development
4. QEMU Emulators
- Full system emulation with debugging capabilities
- Requires technical knowledge and setup
- Limited performance compared to native hardware
My Recommendation: Start with Raspberry Pi for authentic ARM experience and real-world security research applicability.
Now let’s set up your chosen environment!
Step #2: Prepare Your Raspberry Pi
First of all, we need to install a GCC compiler:
raspberrypi> sudo apt install build-essential binutils

It should be installed before we can assemble the files we’re creating, as well as before creating new executable files.
Step #3: ARM Registers
Registers are small storage areas located close to the processor for quick access. They hold various temporary values. When writing an Assembly program, you’ll access them frequently since it’s faster than accessing disk memory or RAM.
If we run uname -a on our Raspberry Pi, we can see output similar to the following:

The output shows that the system is running aarch64, which means a 64-bit ARM architecture (ARMv8-A). The set of available registers depends on the ARM architecture version and execution state. On ARMv8-A in AArch64 state there are 31 general-purpose 64-bit registers, named x0–x30:

x0–x30
→ General-purpose registers: Store values while your program runs.x30
→ Link register (LR): By convention, stores the return address for function calls.x31
→ Alias register: Not a physical register; depending on context, it represents either:sp
(stack pointer): Points to the top of the stack (memory used for temporary storage, function calls, and local variables).zr
(zero register): Always reads as 0 and discards anything written to it.
pc
(program counter): Holds the address of the next instruction to execute. Usually not accessed directly.pstate
(processor state): Holds flags (zero, negative, carry, overflow) and controls the CPU’s current execution mode.
Although most registers can be used freely, software conventions (ABI – Application Binary Interface) assign specific roles, especially for Linux function calls:
x0–x7
→ Argument registers: Pass arguments to functions;x0
also holds the return value.x8
→ Indirect result register or syscall number.x9–x15
→ Temporary registers (caller-saved).x19–x28
→ Callee-saved registers: A function must restore them before returning.x29
→ Frame pointer (FP): Helps debuggers and stack traces.x30
→ Link register (LR): Stores return address after a function call.sp
→ Stack pointer: Points to the top of the stack.
Step #4: Getting Started with ARM Assembly
Let’s create a new file called hello.s
(the .s extension indicates it’s assembly source code).
We’ll start with .global _start. The .global directive makes the _start label visible outside the program for the linker. (A linker takes compiled code files, connects them by resolving function and variable references, adjusts memory addresses, and produces a single executable.)
The _start label marks a specific location in memory. If you have programming experience, you can think of it like a function name—it labels a memory location, and referencing it gives the value stored there.
Next, let’s add .section .text, which defines the section of the file that holds the program’s instructions.

Now that we’ve declared _start as global, we can define our code. We’ll begin with something very simple: just exiting the program and observing the exit code. Usually, when learning a new programming language, we print “Hello World!”, but in Assembly this is more complicated than beginners might expect. So we’ll start with something really basic.
In ARM64 Assembly, we use the special register x8 to pass a value to the kernel that tells it which system call to execute. To know which value to pass to x8
, we can refer to the Chromium Linux System Call Table.

Here we can look at the required table and find the exit
syscall.

We need to pass the syscall number to the x8 register to indicate that we want to invoke the exit syscall.
On the right-hand side, you’ll see int error_code
, which corresponds to x0, the first argument. This value will be the exit code returned by the program. We’ll print this code to the screen after we write the program.
Step #5: MOV Instructions
In ARM64 (AArch64) assembly, the mov
instruction is used to copy a value into a register.
The syntax is simple:
raspberrypi> mov
We’ll use the mov instruction to set x0—our exit code—to a value, for example, 7
. To indicate that it’s a constant, we prefix the number with a #
sign.

After that, we need to invoke the actual syscall by setting x8
.

As you can see, the value we assign to x8 is 93
, which is the system call number for exit
.

Next, we need to trigger a software interrupt to tell the kernel that we want to pass execution to handle a syscall. For this purpose, we use the svc instruction.

svc
requires a value from 0–255, which is passed to the exception handler. However, on Linux ARM64, this value is ignored — the kernel only uses x8
(the syscall number). So you can specify any number.
That’s all for the code. Now we can move on to compilation.
Step #6: Compilation
raspberrypi> gcc -nostdlib -o hello hello.s
nostdlib prevents linking with the standard C library and startup files.
To check the file format, you can use:
raspberrypi> file hello

It’s a Linux executable. Now we can run the program:
raspberrypi> ./hello

You might think that nothing happened, but let’s check the program’s exit code:
raspberrypi> echo $?

That’s the value 7
we passed to the x0 register.
Summary
In this tutorial, we looked at how to write a simple program in ARM Assembly. We set up a 64-bit Raspberry Pi for this task, learned about the mov instruction, and finally explored software interrupt instructions.
ARM Assembly can be a great first step in exploit development—a skill that makes you highly valuable to employers. If you want to dive deeper, consider becoming a Hackers-Arise Subscriber Pro to get access to our first edition of Exploit Development.
The post ARM Assembly: Getting Started first appeared on Hackers Arise.
Source: HackersArise
Source Link: https://hackers-arise.com/arm-assembly-getting-started/