Binary Exploitation


CSE 365 - Fall 2024

Lectures and Reading


Challenges

Overflow a buffer on the stack to set the right conditions to obtain the flag!

This challenge is identical to its "easy" version from a security perspective, but has the following changes:

  1. Unlike the easy version, it does not give you helpful debug output. You will have to recover this information using a debugger.
  2. For all other "hard" versions, the source code will not be provided, and you will need to reverse-engineer the binary using your knowledge of the "easy" version as a reference. However, for this one challenge, to get you familiar with the differences between the easy and hard versions, we will provide the source code.
  3. Some randomization is different. Buffers might have different lengths, offsets might vary, etc. You will need to reverse engineer this information from the binary!

You found a buffer overflow! Now what? You can't just spew input data indiscriminately, because this can corrupt the program's control data into an unrecoverable state. We'll explore this concept in this challenge: you can overflow, but if you overflow too much, you won't get the flag.

This challenge is identical to its "easy" version from a security perspective, but has the following changes:

  1. Unlike the easy version, it does not give you helpful debug output. You will have to recover this information using a debugger.
  2. The source code is not provided. You will need to reverse-engineer the binary, but remember, the challenge is conceptually the same as the easy version, so it can be helpful to have the easy version's source code as a secondary reference during your reversing process!
  3. Some randomization is different. Buffers might have different lengths, offsets might vary, etc. You will need to reverse engineer this information from the binary!

So far, your buffer overflows have simply set variables to non-zero values. Of course, memory errors often enable significantly more advanced controls over a program's state. In this challenge, you must overflow the buffer precisely to set a win condition variable to a specific value. Some things to keep in mind:

  • You will need to write binary data. This can't be printed on the keyboard; we recommend that you use something like Python to produce these bytes.
  • Keep endianness in mind!
  • Depending on how you generate the input data, you might accidentally terminate it with a newline! For example, bash's echo will newline-terminate by default (this behavior can be disabled using the -n flag). These newlines can cause problems --- if you are relying on precise control of program variables (which you are, in this module), an errant newline can unexpectedly corrupt program state and break your exploit. If you have doubts about whether your input has an errant newline, save it to a file and look at it using a hex dumper such as hd.

This challenge is identical to its "easy" version from a security perspective, but has the following changes:

  1. Unlike the easy version, it does not give you helpful debug output. You will have to recover this information using a debugger.
  2. The source code is not provided. You will need to reverse-engineer the binary, but remember, the challenge is conceptually the same as the easy version, so it can be helpful to have the easy version's source code as a secondary reference during your reversing process!
  3. Some randomization is different. Buffers might have different lengths, offsets might vary, etc. You will need to reverse engineer this information from the binary!

Overflow a buffer and smash the stack to obtain the flag!

This challenge is identical to its "easy" version from a security perspective, but has the following changes:

  1. Unlike the easy version, it does not give you helpful debug output. You will have to recover this information using a debugger.
  2. The source code is not provided. You will need to reverse-engineer the binary, but remember, the challenge is conceptually the same as the easy version, so it can be helpful to have the easy version's source code as a secondary reference during your reversing process!
  3. Some randomization is different. Buffers might have different lengths, offsets might vary, etc. You will need to reverse engineer this information from the binary!

Overflow a buffer and smash the stack to obtain the flag, but this time bypass another check designed to prevent you from getting the flag!

This challenge is identical to its "easy" version from a security perspective, but has the following changes:

  1. Unlike the easy version, it does not give you helpful debug output. You will have to recover this information using a debugger.
  2. The source code is not provided. You will need to reverse-engineer the binary, but remember, the challenge is conceptually the same as the easy version, so it can be helpful to have the easy version's source code as a secondary reference during your reversing process!
  3. Some randomization is different. Buffers might have different lengths, offsets might vary, etc. You will need to reverse engineer this information from the 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 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).

This challenge is identical to its "easy" version from a security perspective, but has the following changes:

  1. Unlike the easy version, it does not give you helpful debug output. You will have to recover this information using a debugger.
  2. The source code is not provided. You will need to reverse-engineer the binary, but remember, the challenge is conceptually the same as the easy version, so it can be helpful to have the easy version's source code as a secondary reference during your reversing process!
  3. Some randomization is different. Buffers might have different lengths, offsets might vary, etc. You will need to reverse engineer this information from the binary!

Overflow a buffer and smash the stack to obtain the flag, but this time in a position independent (PIE) binary with an additional check on your input.

This challenge is identical to its "easy" version from a security perspective, but has the following changes:

  1. Unlike the easy version, it does not give you helpful debug output. You will have to recover this information using a debugger.
  2. The source code is not provided. You will need to reverse-engineer the binary, but remember, the challenge is conceptually the same as the easy version, so it can be helpful to have the easy version's source code as a secondary reference during your reversing process!
  3. Some randomization is different. Buffers might have different lengths, offsets might vary, etc. You will need to reverse engineer this information from the binary!

Write and execute shellcode to read the flag!

Write and execute shellcode to read the flag, but a portion of your input is randomly skipped.

Write and execute shellcode to read the flag, but your input space is restricted...

Put your skills together, hijacking control flow to a memory-mapped shellcode that you input separately!

This challenge is identical to its "easy" version from a security perspective, but has the following changes:

  1. Unlike the easy version, it does not give you helpful debug output. You will have to recover this information using a debugger.
  2. The source code is not provided. You will need to reverse-engineer the binary, but remember, the challenge is conceptually the same as the easy version, so it can be helpful to have the easy version's source code as a secondary reference during your reversing process!
  3. Some randomization is different. Buffers might have different lengths, offsets might vary, etc. You will need to reverse engineer this information from the binary!

Put your skills together, hijacking control flow to shellcode you inject as part of your attack!

This challenge is identical to its "easy" version from a security perspective, but has the following changes:

  1. Unlike the easy version, it does not give you helpful debug output. You will have to recover this information using a debugger.
  2. The source code is not provided. You will need to reverse-engineer the binary, but remember, the challenge is conceptually the same as the easy version, so it can be helpful to have the easy version's source code as a secondary reference during your reversing process!
  3. Some randomization is different. Buffers might have different lengths, offsets might vary, etc. You will need to reverse engineer this information from the binary!


30-Day Scoreboard:

This scoreboard reflects solves for challenges in this module after the module launched in this dojo.

Rank Hacker Badges Score