Reputation: 53
This is the source code.
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
void win()
{
printf("code flow successfully changed\n");
}
int main(int argc, char **argv)
{
char buffer[64];
gets(buffer);
}
Assembly code for main
0x0000000000400604 <+0>: stp x29, x30, [sp, #-96]!
0x0000000000400608 <+4>: mov x29, sp
0x000000000040060c <+8>: str w0, [sp, #28]
0x0000000000400610 <+12>: str x1, [sp, #16]
0x0000000000400614 <+16>: add x0, sp, #0x20
0x0000000000400618 <+20>: bl 0x4004d0 <gets@plt>
0x000000000040061c <+24>: mov w0, #0x0 // #0
0x0000000000400620 <+28>: ldp x29, x30, [sp], #96
0x0000000000400624 <+32>: ret
Assembly code for win
0x00000000004005e4 <+0>: stp x29, x30, [sp, #-16]!
0x00000000004005e8 <+4>: mov x29, sp
0x00000000004005ec <+8>: adrp x0, 0x400000
0x00000000004005f0 <+12>: add x0, x0, #0x6e0
0x00000000004005f4 <+16>: bl 0x4004c0 <puts@plt>
0x00000000004005f8 <+20>: nop
0x00000000004005fc <+24>: ldp x29, x30, [sp], #16
0x0000000000400600 <+28>: ret
The source code is from protostar-stack4. As far as I know, you have to overwrite $pc to run the win function. So I tried to overwrite $pc, but I can't. I put a breakpoint at main+32 and ran it with 100*a, but neither $pc nor $x30 has been overwritten. What should I do to overwrite $pc? Am I even on the right path? Please help.
$x0 : 0x0
$x1 : 0x0000fffff7fb1290 → 0x0000000000000000
$x2 : 0xfbad2288
$x3 : 0x0000fffff7fae8d0 → 0x00000000fbad2288
$x4 : 0x6161616161616161 ("aaaaaaaa"?)
$x5 : 0x0000fffffffff384 → 0x0000000000000000
$x6 : 0x6161616161616161 ("aaaaaaaa"?)
$x7 : 0x6161616161616161 ("aaaaaaaa"?)
$x8 : 0x6161616161616161 ("aaaaaaaa"?)
$x9 : 0x6161616161616161 ("aaaaaaaa"?)
$x10 : 0x6161616161616161 ("aaaaaaaa"?)
$x11 : 0x6161616161616161 ("aaaaaaaa"?)
$x12 : 0x6161616161616161 ("aaaaaaaa"?)
$x13 : 0x6161616161616161 ("aaaaaaaa"?)
$x14 : 0x6161616161616161 ("aaaaaaaa"?)
$x15 : 0x6161616161616161 ("aaaaaaaa"?)
$x16 : 0x1
$x17 : 0x6161616161616161 ("aaaaaaaa"?)
$x18 : 0x0
$x19 : 0x0000000000400630 → <__libc_csu_init+0> stp x29, x30, [sp, #-64]!
$x20 : 0x0
$x21 : 0x00000000004004e0 → <_start+0> mov x29, #0x0 // #0
$x22 : 0x0
$x23 : 0x0
$x24 : 0x0
$x25 : 0x0
$x26 : 0x0
$x27 : 0x0
$x28 : 0x0
$x29 : 0x0000fffffffff360 → "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
$x30 : 0x0000fffff7e62218 → <__libc_start_main+232> bl 0xfffff7e77d00 <__GI_exit>
$sp : 0x0000fffffffff360 → "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
$pc : 0x0000000000400624 → <main+32> ret
$cpsr: [negative ZERO CARRY overflow interrupt fast]
$fpsr: 0x0
$fpcr: 0x0
What should I do?
Upvotes: 5
Views: 990
Reputation: 21
Although it's impossible to overwrite the return address of the_function_called_by_main()
, actually we can overwrite the return address of main()
if you want to simulate buffer overflow attack.
Here is my BOF.c
code:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int bufferOverflow(const char * str)
{
char buffer[12];
/* This line has a buffer overflow vulnerability. */
strcpy(buffer, str);
return 1;
}
int main(int argc, char ** argv)
{
char aString[512];
FILE *badfile;
printf("Buffer overflow vulnerability starting up...\n");
badfile = fopen("badfile", "r");
fread(aString, sizeof(char), 512, badfile);
bufferOverflow(aString);
printf("bufferOverflow() function returned\n");
return 1;
}
The input badfile is created by the following lines:
for (int i = 0; i < 512; i++) {
buffer[i] = i+1;
}
// to locate the return address
buffer[24] = 0x01;
buffer[25] = 0x23;
buffer[26] = 0x45;
buffer[27] = 0x67;
Now we use gdb (here I use gef) to see what will happen if I run this program. I set break main
then run
and we can see that now $x30 contains the return address of main()
and it has been stored to $sp+8, which is 0x0000ffffffffe908
in my case. This store method will be performed every time another function is invoked and the return address will be loaded back to $x30 before returning back to the caller.
Stepping into bufferOverflow()
, we can see that the return address of this function has been stored into 0x0000ffffffffe8f8
and the buffer starts from 0x0000ffffffffe8f0
and will write towards higher address. As a result, the return address of bufferOverflow()
will not be overwritten, but the return address of main()
will be overwritten. We can see in the screenshot that our 0x0000ffffffffe908
has changed into 0x201f1e1d67452301
which is specified in the badfile.
Upvotes: 0
Reputation: 58673
As you can see from the code, the compiler has placed the return address below the buffer on the stack, so it is impossible for you to overwrite it, no matter how many bytes you write.
Specifically, stp x29, x30, [sp, #-96]!
is pre-decrement, so it stores x29
at the new address of [sp]
, and x30
, which contains the return address, at [sp, 8]
. The buffer, on the other hand, is at [sp, 32]
(note the add x0, sp, 0x20
). This is typical for ARM64; the pre-decrement addressing mode makes it convenient to store the return address at the bottom of the stack frame, below all the function's local variables.
What you may be able to overwrite instead is the return address saved by whatever function called main
(somewhere in the C startup code, e.g. glibc's __libc_start_main()
). That would only happen when that other function returns to its own caller, so you have stopped the program too soon.
Unfortunately for you, on many systems, that function does not return at all; it calls exit()
instead. That's what glibc's __libc_start_main
does.. As such, unless I am missing something, this kind of buffer overflow will not work on such a system when gets
is called from within main
. If you want to play with it, try writing a different program where it's called from some subroutine:
void other_func(void) {
char buf[64];
gets(buf);
}
int main(void) {
other_func();
return 0;
}
Now your overflow won't overwrite the return address stored by other_func()
(which is again below the buffer), but it can overwrite the return address stored by main()
(which is further up the stack). You'll get control, not when other_func()
returns, but when main()
returns. (Even so, that's after its ret
instruction executes; placing a breakpoint on the ret
instruction from main
is still too soon.)
It looks like this exercise was intended for x86, where the return address is normally at the top of the stack frame, since it's saved by the call
instruction which executes before the stack frame is set up. On an x86 system your overflow would indeed overwrite the return address saved by main
, and give you control over the program counter.
Upvotes: 5