corCTF 2022: babypwn

Just another one of those typical intro babypwn challs... wait, why is this in Rust?

TL;DR

Leak LIBC address via printf() and execute ROP chain by exploiting Stack Buffer Overflow to perform a ret2libc attack with ASLR enabled.

Basic File Checks

The binary first prompts the user for a name, followed by a greeting message that appends our name. Afterwards, it asks for our favorite 🐸 emote, and then prints some wonderful ASCII art.

┌──(kali💀JesusCries)-[~/Desktop]
└─$ ./babypwn    
Hello, world!
What is your name?
test
Hi, test
What's your favorite :msfrog: emote?
frog
          .......           ...----.
        .-+++++++&&&+++--.--++++&&&&&&++.
       +++++++++++++&&&&&&&&&&&&&&++-+++&+
      +---+&&&&&&&@&+&&&&&&&&&&&++-+&&&+&+-
     -+-+&&+-..--.-&++&&&&&&&&&++-+&&-. ....
    -+--+&+       .&&+&&&&&&&&&+--+&+... ..
   -++-.+&&&+----+&&-+&&&&&&&&&+--+&&&&&&+.
 .+++++---+&&&&&&&+-+&&&&&&&&&&&+---++++--
.++++++++---------+&&&&&&&&&&&&@&&++--+++&+
-+++&&&&&&&++++&&&&&&&&+++&&&+-+&&&&&&&&&&+-
.++&&++&&&&&&&&&&&&&&&&&++&&&&++&&&&&&&&+++-
 -++&+&+++++&&&&&&&&&&&&&&&&&&&&&&&&+++++&&
  -+&&&@&&&++++++++++&&&&&&&&&&&++++++&@@&
   -+&&&@@@@@&&&+++++++++++++++++&&&@@@@+
    .+&&&@@@@@@@@@@@&&&&&&&@@@@@@@@@@@&-
      .+&&@@@@@@@@@@@@@@@@@@@@@@@@@@@+
        .+&&&@@@@@@@@@@@@@@@@@@@@@&+.
          .-&&&&@@@@@@@@@@@@@@@&&-
             .-+&&&&&&&&&&&&&+-.
                 ..--++++--.                                  

In terms of binary protection, nothing seems out of the ordinary other than PIE being enabled, which randomizes the base address of the binary each time.

Static Code Analysis

It wouldn't be the Crusaders of Rust without Rust challenges! The source code is provided for this challenge. For context, Rust provides memory safety through its type system and compile time checks. However, the unsafe keyword is used here to tell the compiler to skip these safety checks for the entire chunk of code.

Even though the code is written in Rust, the unsafe block allows foreign LIBC functions to be loaded, essentially wrapping the entire Rust code in C++. These LIBC functions are usually the culprits for memory corruption vulnerabilities.

Fuzzing printf()

The first noticeable vulnerability is the improper usage of printf that will allow us to leak addresses from the stack. We can use this as a way to leak memory address in order to defeat ASLR (Address Space Layout Randomization) and perform a ret2libc exploit.

To quickly find address leak that might interest us, we can fuzz each offset individually using the format specifier %{}$p.

On each fuzzing attempt, notice how the address on certain offset always end with the same 3 bit. Based on previous experience, addresses with the prefix 0x55 are usually memory addresses of the binary; whereas addresses with the prefix 0x7f belongs to LIBC.

To understand what these leaked addresses are referencing exactly, we can use info proc mappings in GDB to show us where everything for the process is mapped in memory:

  • Offset 1 0x7ffcd3181550: [stack]

  • Offset 4 0x564619ddf51b: [heap]

  • Offset 6 0x564619ddf51b: [heap]

  • Offset 7 0x7fc28f597080: /usr/lib/x86_64-linux-gnu/libc.so.6

It is now clear that the 7th offset is the base address where LIBC is loaded at runtime.

Calculating Offset

Now that we know the 7th offset is particularly interested to us, we can automate the whole process using pwntools and provide GDB as our argument to attach our process to the debugger without doing it manually.

Note that vmmap also produces the same outcome as info proc mappings.

Knowing this information allows us to calculate the offset of address that we leaked in relative to the base address of LIBC. We can do this by performing the following calculation:

__libc_base - leaked_address = offset

It is important to note that this offset is a constant value. Each time we execute the binary, the address at the 7th offset that we leak will be different due to ASLR, along with the base address of LIBC, however, the leaked address will always be 0xf80 bytes away from the base of LIBC. With the knowledge of this offset value, we have just defeated ASLR!

Leaking LIBC Address

Thanks to the buffer overflow that occurs during the second user-input, we can now perform a ret2libc attack using the offset value calculated before this.

Reading the source code should become clear very quickly that the program is reading 128 bytes from the user and place them into the text buffer that is only 64 bytes long. Fuzzing the binary shows that the buffer overflow offset is 96.

Using the same formula from previous section, we can now calculate the LIBC base address:

__libc_base = leaked_address + offset

ret2libc

Flag: corctf{why_w4s_th4t_1n_rust???}

Last updated