ARMv64-Exploit Development [chapter 0x2] - No Boundary
Hello world!
This is going to be the part two of ARM exploit development series, last time we are talking about some fundamentals behind ARM architecture, how to debug it and also create a very simple ARM64 hello world program from assembly.
In this post, we are going to cover buffer overflow in ARM64 binary and the sample vulnerable code that we are going to use is from phoenix Exploit Education(this post will cover stack 0 and stack 1).
Disclaimer
I’m not an expert nor a tech savy, I’m just a simple guy who curious about everything. The real expert is the author who wrote this great books that I bought and reference it to write this series, please support them by buying the original copy of the book from official store:
-
Beginner Guide to Explotation on ARM volume I and II by Billy Ellis buy-book
-
Programming with 64-Bit ARM Assembly Language by Stephen Smith buy-book
-
Effective C: An Introduction to Professional C Programming by Robert C. Seacord buy-book
-
Hacking: The Art of Exploitation, 2nd Edition buy-book
Stack 0: Lets make the world most vulnerable code!
I use the code from stack 0 and make some adjustment based on my taste like this:
As you can see from the code above first few lines are used to create a struct which is “sequentially allocated memory object” - Robert C. Seacord, inside the struct we have two variables one is used to stored our input(char buffer) with 64 bytes of memory and the other one used to stored an integer that will be later assign 0x41(“A” in hex)
Afte the initialization of struct variable we continue our program by prompting user to insert a data into the program and finally it checks whether the variable changeme within the struct is changed or not.
you must be wondering, is it possible to change a hardcoded variable in a program.
well yes! and thats going to be our goal in this level
after writing the code, its time to compile the code into a binary. We are going to use GCC compiler with some extra parameters on it.
Just like the command above, we added three extra parameters:
-
-fno-stack-protector to disable stack canary
-
-z execstack to make the stack executable
-
-no-pie disable code randomization
these three features is basically whats so called defense mechanism to prevent exploit in C code base, but since we want to create the world most vulnerable code, we just going to disable three of them.
For starters, lets try to input a really long strings into the program and see what happen.
hhmmm wait! did we just change the “changeme” variable? from the result, it confirms that our input actually causing the program to change the content of the variable.
WHY?!
take a look at the second picture when we try to compile the program source code, the compiler stated that the “gets” function is dangerous and should not be used.
this is due to the fact that the function do not safely passed the input to the memory and “safe” in this term means it does not limiting the size of the input, thus, creating a phenomena called “buffer overflow” by putting a large value to the program it cause the program to overwrite other part of its memory causing it to behave unexpectely(in this case, it actually overwrite the “changeme” variable)
lets try to dissect the program in order to have a proper understanding of how buffer overflow occur
load the program to GDB that have GEF in it and input “checksec” inside the shell to check whether we have successfully turn off the defense mechanism in the binary. If the result is like the picture above that means, we did it.
Try to disassemble the function main by putting the following command line.
Now try to pay attention on instructions that have been cover with red box. The first redbox cover on how the program assign and saved the locals.changeme struct variable in stack:
mov w0, 0x41 => the program stored value “A” in register w0(this means the register only use 32 bit memory for efficiency)
str w0, [sp, #88] => register w0 that has “A” store its content to the stack pointer that’s located at(sp+88) so it can be referenced later.
add x0, sp,#0x18 => assign register X0 with the address of sp+0x18, this is used to prepare X0 to be used as the first parameter for gets() function for storing our value.
In conclusion our input is stored at sp+0x18 whereas the locals.changeme variable is stored at sp+#88. Move to the next box that contain instruction of checking whether the value is changed in locals.changeme variable.
But before the locals.changeme variable is compared, the program load the value that we stored earlier in sp+0x18 which is “A” to the register w0 again.
lets put a breakpoint at gets function call so we know where is the exact location of our input and locals.changeme variable. Run the program by entering “r” to the GEF console.
Upon entering the breakpoint, GEF will show you the address where our input will be stored in stack(0x7ffffff3a8)
lets try to move to the next instruction by input command “ni” into the GEF shell and after that you will be expected to input something in this case I will put “BBBB” so it easier to identify it in memory. Next, the GEF will show you the state of the register after inputing the value to the program and as you can see our value is stored excatly where we expect.
Now that we know the location of our input we can now try to guessing where is locals.changeme variable in stack, since struct is adjacent that means the location of it will not go away that far.
by putting the above command we actually dumping the next 20 bytes from the address of our input value and at the last row notice that there is value of 0x41 in 0x7ffffff3e8, this is where locals.changeme value is stored. Thus we can do a simple arithmetic calculation on how many string that is required to overflow and changed the variable.
The above command will do simple substraction between two address and the result is in decimal. it shows that We need 64 character to overflow the variable, try re-running the program and if you try to look at the stack again after inputting the program with the corresponding value, it overwrite the value within the variable.
Stack 1: Little Endian!
Now lets move to the next level, basically the concept of the program is still the same and only have a minor change in the source code.
This time the locals.changeme variable is assign with value 0 and to get the input from user the program used sys.argv function and to save the value it uses strcpy(). Using this function will ultimately lead to buffer overflow, because it doesn’t have a proper input limitation that make user can enter any data.
I compiled the program with the same parameters as the previous one. Lets take a loot at the main function and put a breakpoint when strcpy() function is called.
Run the program by entering “r AAAAAAAAAAAAAAAAAAAA” the second column is consider input by the program since it gets value from sys.argv[1] function.
Notice that in strcpy() function it requires two parameters that will act as source and destination thus the program prepare these two registers r0(1st parameter as destination) and r1(2nd parameter as source/input).
From the result we know that our input is stored at 0x7ffffff388, from this information we can tru to guess where is locals.changeme stored in the stack.
The concept is still the same with the previous challenge, we just need to substract these two address together which stores our input and locals.changeme value respectively. Now, all we need to do now is to figured out how to overwrite the locals.changeme with 0x496c5962, we can do that with the following command.
Using python we can generate 64 characters automatically and append the value 0x496c5962 but when we append it into our python script we need reverse it since arm64 is using little endian, although the fact it can use this two format(bi-endian) but most ARM CPU architecture used little endian format and dont forget to put “`” before and after the command so it will interpret as python script before passed to the program argument.
That’s all folks I hope you enjoy this post, see you in the next post.