In my previous blog post “Stack Overflows on Arm32” you learned how functions work on Arm32 and the way a stack-overflow vulnerability can give you control over the program flow. But what can you do after you took control over the Program Counter (PC)? This blog post will teach you how to exploit a stack-overflow without exploit mitigations and how the XN exploit mitigation changes the way these vulnerabilities are exploited. You can download the Lab VM with the challenges and setup mentioned in this blog post.
To exploit a stack-overflow, you need to redirect the program flow to an instruction sequence that achieves a certain goal. What goal might that be? Hackers could execute shellcode to install malware, attack other components on the system, or simply open a reverse shell to get remote command-line access to the target. If you want to learn how to write a reverse shell in Arm assembly, you can follow my tutorial TCP Reverse Shell in Arm Assembly.
The way you can exploit a stack-overflow to execute your shellcode is by putting it on the stack and directing the program flow to jump to your shellcode and execute its instructions. You can find a simple shellcode example in my blog post Writing Arm Shellcode where I also explain how you can convert your shellcode into a hex string. This hex string is what lands on the stack and is being executed if you make the program branch to it, assuming the stack is executable.
After reading the previous blog post you know that you can overwrite the return address on the stack with a value of your choice. This means that you need to construct your input string to contain a number of characters to fill the buffer bytes plus the instruction you want to be executed next. But where do you take this instruction from? Let’s take a step back and look at the memory layout of our example program (mentioned here).
If you would like to follow this process, copy the code from the previous blog post over to your Arm environment, compile it without an executable stack and run it in GDB/GEF.
user@azeria-labs-arm:~$ gcc program.c -o program -z execstack user@azeria-labs-arm:~$ gdb program gef➤ b main Breakpoint 1 at 0x560 gef➤ run
gef➤ vmmap Start End Offset Perm Path 0x00400000 0x00401000 0x00000000 r-x /home/user/program 0x00410000 0x00411000 0x00000000 r-x /home/user/program 0x00411000 0x00412000 0x00001000 rwx /home/user/program 0xb6edc000 0xb6fc0000 0x00000000 r-x /lib/arm-linux-gnueabihf/libc-2.28.so 0xb6fc0000 0xb6fd0000 0x000e4000 --- /lib/arm-linux-gnueabihf/libc-2.28.so 0xb6fd0000 0xb6fd2000 0x000e4000 r-x /lib/arm-linux-gnueabihf/libc-2.28.so 0xb6fd2000 0xb6fd3000 0x000e6000 rwx /lib/arm-linux-gnueabihf/libc-2.28.so 0xb6fd3000 0xb6fd6000 0x00000000 rwx 0xb6fd6000 0xb6fee000 0x00000000 r-x /lib/arm-linux-gnueabihf/ld-2.28.so 0xb6ff9000 0xb6ffb000 0x00000000 rwx 0xb6ffb000 0xb6ffc000 0x00000000 r-x [sigpage] 0xb6ffc000 0xb6ffd000 0x00000000 r-- [vvar] 0xb6ffd000 0xb6ffe000 0x00000000 r-x [vdso] 0xb6ffe000 0xb6fff000 0x00018000 r-x /lib/arm-linux-gnueabihf/ld-2.28.so 0xb6fff000 0xb7000000 0x00019000 rwx /lib/arm-linux-gnueabihf/ld-2.28.so 0xbefdf000 0xbf000000 0x00000000 rwx [stack] 0xffff0000 0xffff1000 0x00000000 r-x [vectors]
As you can see, the stack is read-write-execute. Let’s look at sections we can steal instructions from. The requirement for this is that this section contains executable parts, such as the libc library. You can make use of libraries loaded at predictable addresses and look for an instruction sequence that branches to your shellcode. You then overwrite the return address of your target with the address of that instruction sequence, also called “gadget”. If you went through my Arm Assembly tutorial, you have heard about branches (tutorial here). At the point when your gadget will be popped into PC, the Stack Pointer (SP) will point to the next 4 bytes on the stack. If you choose a gadget that branches to SP (like BX SP or BLX SP) the processor will place the value of SP into PC. Since the SP is pointing to your shellcode, the processor will begin to execute your shellcode instructions.
If Address Space Layout Randomization (ASLR) is enabled, libraries (and other parts of the process memory) will be loaded at different memory addresses each time you execute the program. This makes it hard to predict the exact address of your gadget. I won’t cover ASLR internals and bypasses in this tutorial. For this example you can turn ASLR off with by executing the following command inside your Arm environment (or executing the disable_aslr.sh script):
user@azeria-labs-arm:~$ sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space"
You can find gadgets with tools like Ropper. I suggest running Ropper on your host environment because running it from the Arm environment is slow. With vmmap you saw the file path of libc. Transfer this library over to your Ubuntu host:
user@Azeria-Lab-VM:~$ scp user@arm:/lib/arm-linux-gnueabihf/libc-2.28.so . libc-2.28.so 100% 930KB 4.8MB/s 00:00
Now you can look for gadgets with Ropper:
user@Azeria-Lab-VM:~$ ropper (ropper)> file libc-2.28.so [INFO] Load gadgets for section: LOAD [LOAD] loading... 100% [LOAD] removing double gadgets... 100% [INFO] File loaded. (libc-2.28.so/ELF/ARMTHUMB)> search /1/ b%x sp [INFO] Searching for gadgets: b%x sp [INFO] File: libc-2.28.so 0x0001488c (0x0001488d): blx sp; 0x0000a43c (0x0000a43d): bx sp;
You can use the % symbol to indicate that you want both, BX and BLX instructions. The /1/ tells Ropper to give you the smallest gadget sequence. Try it out and search for “mov r1” with and without the /1/. Messy, isn’t it?
You should always start your search with /1/ and only if you can’t find your “perfect gadget” you increase it to /2/, then /3/, and so on. You see two addresses for each instruction. This is because this library is mostly compiled with thumb instructions (intro to Arm and Thumb) and this is Ropper’s way of telling you that the address is off by 1 and you should take the green one (the one in brackets).
I won’t spoil the solution and let you have a little challenge. 🙂 You can download the Lab VM and try to write your own exploit. Hint: you can solve this in different ways. With a python script or by passing your payload through an environment variable, for example. The challenge for this part is “challenge1” inside the Arm environment in folder “challenges”.