ARM Ret2ZPhttps://www.lares.com/wp-content/uploads/2019/07/thomas-tastet-hSODeSbvzE0-unsplash.jpg60164016Thomas WhitmireThomas Whitmirehttps://secure.gravatar.com/avatar/cb45a36a12cd978326263cee28d6f487?s=96&d=mm&r=g
So straight off the bat, the first thing to understand when working with ARM when compared to x86 is the difference between RISC and CISC architecture designs. x86 using CISC, has the arguments to a function passed onto the stack, while in RISC, the arguments are passed to the register, so controlling the stack becomes a bit trickier. In order for this to work, we will need to find a function within the imported C libraries that will allow us to load our own registers and jump to the system() function.
Taking a look at the following code, we have a program that takes 1 argument and passes it into the function “Vulnerable”. When the function is called, it creates a buffer with the size of 10 bytes and proceeds to put our argument into the buffer with no bounds checking. When our argument is greater than the buffer size, it causes an overflow…
pi@raspberrypi:~/Desktop $ lscpu
Byte Order: Little Endian
On-line CPU(s) list: 0
Thread(s) per core: 1
Core(s) per socket: 1
Model name: ARMv6-compatible processor rev 7 (v6l)
Quick look at the disassembly of the main function, we want to find the return after the ‘vulnerable’ function.
Running through the program we hit a segfault with the payload ‘AAAABBBBCCCC’. With a quick look at the Stack Pointer register, we see our payload and eventually after 4 bytes, the return address we are looking for. So when contructing our exploit, we will need to fill this space up and then overflow the return address with the address of our LibC function.
Navigating through functions here, we are looking for a way to get the register ‘r0’ to call our system() function with ‘/bin/sh’ as the argument. In this example we will use seed48().
So with our ‘add’ instruction, we get r0 = r4+6. We then need to find the address of our shell command ‘/bin/sh’.
When running through the program, we can search memory for our loaded enviroment using ‘x\40s $r1’. This will print out the string representation of the next 40bytes from the r1 register to find the address (0xbefffe14) holding our ‘/bin/sh’ command.
Now in order to align the stack properly, we need to remember that the ‘add’ instruction from the seed48 function is adding 6 bytes. The address 0xbefffe14 is actually holding ‘SHELL=/bin/sh’ and we only want ‘/bin/sh’. Lucky for us, we wont actually need to do any calculations here because we want to move forward 6 bytes to move past the ‘SHELL=’ text to start at ‘/bin/sh’ and the ‘add’ instruction is going to do that for us.
Next we find the address for our system() function to be called later.
Alright, so we have everything we need to write out our payload to get a shell, time to break it up.
1.) “AAAABBBBCCCCDDDD” –> junk payload to get to our return address.
1.) “\xfc\x5d\xea\xb6” –> overflow the return with the address of the ‘pop’ instruction from the seed48() function.
1.) “\x14\xfe\xff\xbe” –> ‘pop’ will pass the address of our shell string to the register r4.
1.) “\xf8\x5d\xea\xb6” –> ‘add’ will add 6 bytes to our shell address and store it in r0 and continues down the stack a second time to the ‘pop’ instruction.
1.) “EEEE” –> junk payload, which will be popped into r4.
1.) “\xac\xdf\xea\xb6” –> system() function gets called with our shell address passed as an argument.
Let’s quickly run the payload r `python -c ‘print “AAAABBBBCCCCDDDD\xfc\x5d\xea\xb6\x14\xfe\xff\xbe\xf8\x5d\xea\xb6EEEE\xac\xdf\xea\xb6″‘` and debug it to make sure the stack is properly aligned.
We can see that the string ‘/bin/sh’ was aligned correctly as the ‘add’ instruction helped us move past the ‘SHELL=’ text. Towards the bottom, we can see that the system() function was properly called and our string to gain a shell was passed as an argument.
Lastly, we run the payload without breakpoints and successfully get our vulnerable program to execute a shell.