-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
25 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,25 @@ | ||
Overflow a buffer and smash the stack to obtain the flag, but this time in a position independent (PIE) binary! | ||
In prior levels, you knew the address of `win()` because the binary was always loaded into the memory at the same place. | ||
In this level, we'll explore challenges when the executable that you are overflowing is _Position Independent_! | ||
A Position Independent Executable is loaded into a random location in memory. | ||
Because of this, you cannot know exactly where the `win()` function is located. | ||
|
||
So how can you solve this? | ||
On x86 (and most other modern architectures), memory is mapped into a process' memory space _page by page_. | ||
A memory page is a contiguous block of 0x1000 (4096) bytes starting at a page address aligned to 0x1000 for performance and memory management reasons (more on this [much later](https://pwn.college/system-security/kernel-security) in the pwn.college curriculum!). | ||
For example, the following are all examples of potential page addresses: | ||
|
||
- `0x5f7be1ec2000` | ||
- `0x7ee1382c9000` | ||
- `0x6513a3b67000` | ||
|
||
Do you see how the last three digits (e.g., 12 bits, or 1.5 bytes, or affectionately known as 3 "nibbles") are all `0`? | ||
We can use this to _partially_ predict addresses in the binary. | ||
For an example, let's assume that our `win()` function is located `0x1337` bytes past the start of the binary (so, if the binary were not position independent, it would likely be located at `0x401337`). | ||
This means that, for example, if our PIE binary were loaded at page address `0x6513a3b67000`, it would have its `win` function at `0x6513a3b68337`. | ||
If it were loaded at `0x5f7be1ec2000`, its `win` function would be at `0x5f7be1ec3337`, and so on. | ||
|
||
So, realistically, know the last three nibbles of any address in the binary as these nibbles never change due to the page-alignment (to `0x1000` bytes). | ||
This gives us a workaround: we can overwrite the least significant byte of the saved return address, which we can know from debugging the binary, to retarget the return to main to any instruction that shares the other 7 bytes. | ||
Since that last byte will be constant between executions (due to page alignment), this will always work. | ||
If the address we want to redirect execution to is a bit farther away from the saved return address, and we need to write two bytes, then one of those nibbles (the fourth least-significant one) will be a guess, and it will be incorrect 15 of 16 times. | ||
This is okay: we can just run our exploit a few times until it works (statistically, ~50% chance after 11 times and ~90% chance after 36 times). |