Off-by-One Overflow Attack Analysis
Off-by-One Overflow Attack Analysis
Background
Last week, I attended a security course that included an example of an off-by-one overflow vulnerability. Here is the original code:
1 | /* Simple off-by-one overflow example */ |
The answer provided for exploiting this vulnerability is:
1 | perl -e 'system "./obo", "\x38\x84\x04\x08"x256' |
The result of running this command is that multiple lines of I've been hacked
are printed on the screen.
Analysis
When the program enters the foo
function, the memory layout looks like this (as observed using GDB):
From top to bottom, the layout contains the return address, the saved frame pointer, and the buffer (buf
).
When the line buf[sizeof(buf)] = '\0';
is executed, the least significant byte of the saved frame pointer (ebp
) is set to 0
. To ensure that ebp
still points within the buf
region after being partially overwritten, a buffer of at least 1024 bytes is required. Specifically, ebp
needs to be overwritten such that it remains within a reasonable range (— up to 0xff
), which is why the buffer is set to 0xff * 4
bytes.
Understanding Assembly Commands on foo
Return
When the foo
function returns, it typically executes the following key assembly instructions:
1. leave
Instruction
The leave
instruction is equivalent to:
1 | mov esp, ebp |
mov esp, ebp
: This sets the stack pointer (esp
) to the value of the frame pointer (ebp
), restoring the stack pointer to the top of the current stack frame and effectively releasing the space occupied by the current function.pop ebp
: This pops the value at the top of the stack and assigns it to the frame pointer (ebp
), thereby restoring the caller’s frame pointer. Essentially, it writes the return address intoebp
, meaning it assigns the stack value (usually the caller’s frame address) toebp
, restoring the caller’s stack frame.
The effect of leave
is to restore esp
to its state before the function was called and to pop the saved ebp
. If ebp
has been overwritten to point to a special address (such as an address within the buffer), it can result in an incorrect stack pointer location during function return.
2. ret
Instruction
The ret
instruction pops an address off the top of the stack and jumps to that address:
1 | pop eip |
If the return address has been overwritten with the address of the bar
function, the execution flow will jump to bar
, allowing an attacker to run arbitrary code. Essentially, ret
pops an address into the instruction pointer (eip
) and jumps to that address to continue execution.
Attack Steps
- When the command
perl -e 'system "./obo", "\x38\x84\x04\x08"x256'
is executed, the program takes these repeated bytes as the input to./obo
. - As the
foo
function returns, theleave
andret
instructions are executed, leading to the return address being overwritten. This causes the program to jump to thebar
function, printing the success message multiple times.
Further Analysis: Determining Effective Overwrite Locations
Stack Frame Layout Explanation
During the GDB debugging session, the memory layout for the stack frame of the foo
function looks like this:
1 | 0xbfffed10 return address |
Return address: Located at
0xbfffed10
, this is the address that the program will jump to after thefoo
function finishes executing. Overwriting this address can control the flow of the program and potentially redirect it to malicious code (e.g., thebar
function).Saved frame pointer (
ebp
): Stored at0xbfffed0c
, this value is used to restore the calling function’s stack frame afterfoo
finishes. In this example, we can see how the off-by-one overflow can overwrite the least significant byte ofebp
.Buffer (
buf
): The buffer starts at address0xbfffe90c
and extends to0xbfffed0b
, withbuf[0]
located at0xbfffe90c
andbuf[1023]
at0xbfffed0b
. The vulnerable line in the code,buf[sizeof(buf)] = '\0';
, writes a null terminator (\0
) just outside the bounds of this buffer, affecting the saved frame pointer.
In the off-by-one overflow scenario, the write operation overwrites the least significant byte of ebp
, which is stored at 0xbfffed0c
. By manipulating the value of ebp
, we can influence the stack behavior when the leave
and ret
instructions are executed, eventually allowing us to control the program flow and redirect execution to the bar
function.
To perform a successful attack, it’s crucial to determine precisely which bytes need to be overwritten in order to manipulate the control flow effectively. In this example, the overflow occurs when buf[sizeof(buf)] = '\0'
is executed, causing the least significant byte of the saved frame pointer (ebp
) to be set to 0
. Thus, the value of ebp
needs to be adjusted to ensure it points back into the buffer area, allowing the execution to proceed in the desired way and eventually jump to the bar
function.
Based on further analysis and testing, the following insights were obtained:
To accurately determine the overwrite location, the value of
ebp
is crucial. However, obtaining this value is challenging because:- GDB debugging affects address layout.
- The length of the input parameter affects the address layout.
Under GDB debugging, the layout within the
foo
function looks like this:After executing
buf[sizeof(buf)] = '\0';
,ebp
is modified such that the return address effectively takes the value atebp + 1
, which is the address0xbfffed00 + 1
, or0xbfffed04
.The corresponding offset is at position 255 in
buf
, meaning the attack can be constructed by filling in the return address only at that specific location. The following command was used for verification in GDB:1
r $(perl -e 'print "\x01\x01\x01\x01"x254 . "\x38\x84\x04\x08"x1 . "\x01\x01\x01\x01"x1')
This was verified to work under GDB debugging, with some details to note:
- The input parameter length must always be 256 bytes; otherwise, the value of
ebp
will change, as the input parameter occupies stack space, affecting the starting position of the frame and thereby affecting the value ofebp
. - Padding must use non-zero values such as
0x01
, becausestrncpy
will terminate early if it encounters a0
value.
- The input parameter length must always be 256 bytes; otherwise, the value of
When executing the program directly (i.e., without GDB), the memory layout differs, resulting in a different offset position. Through experimentation, it was found that the offset is at position 235. The corresponding attack command is:
1
./obo $(perl -e 'print "\x01\x01\x01\x01"x234 . "\x38\x84\x04\x08"x1 . "\x01\x01\x01\x01"x21')
This achieves the desired effect of accurately finding the overwrite location and successfully executing the attack.