📌
Nightmare
  • Stack Buffer Overflow-01 (csaw18_boi)
  • Stack Buffer Overflow-02 (tamu19_pwn1)
Powered by GitBook
On this page
  • Decompiling the binary (Static Analysis)
  • Debugging with Radare2
  • Setting up Standard input and Standard output for debugging in Radare2
  • Analyzing the binary in Radare2
  • Radare2 Visual Mode
  • Debugging in Visual mode
  • Finding the Offset
  • The final Exploit
  • Final Thoughts

Was this helpful?

Stack Buffer Overflow-01 (csaw18_boi)

NextStack Buffer Overflow-02 (tamu19_pwn1)

Last updated 3 years ago

Was this helpful?

This is a challenge from big collection of Binary exploitation challenges. This one is from CSAW 2018 and its called boi about finding an offset to a target variable and overflowing it with a specific value. You can get the binary . I plan to solve all the Nightmare challenges using radare2, because I don't see many articles talking about the power of Radare2. Without further ado, let's get started!!

Decompiling the binary (Static Analysis)

We start with decompiling the binary using ghidra and looking at themain function. To see the decompiled code:

  • Open Ghidra and create a new project by going to File> New Project

  • Import the file in Ghidra by going File> Import File

  • Double click on the file imported (this is shown in the Active Project pane inside Ghidra)

  • Click ok a couple of times and your binary should be imported in the current project

  • Double Click the binary and Ghidra will ask if you want it to analyze. Click yes> Analyze

  • Give ghidra some time (depending on your machine) to analyze the binary

  • Now over on the left side of ghidra window, You can list all the functions under Symbol Tree> Functions

  • Under Functions click on main and you should be presented with the disassembly of the binary in the middle pane and on the right should be the decompiled version of the binary

undefined8 main(void)

{
  long in_FS_OFFSET;
  undefined8 local_38;
  undefined8 local_30;
  undefined4 local_28;
  int iStack36;
  undefined4 local_20;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  local_38 = 0;
  local_30 = 0;
  local_20 = 0;
  local_28 = 0;
  iStack36 = -0x21524111;
  puts("Are you a big boiiiii??");
  read(0,&local_38,0x18);
  if (iStack36 == -0x350c4512) {
    run_cmd("/bin/bash");
  }
  else {
    run_cmd("/bin/date");
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}
  • We can see just after the puts instruction (line 18), there is a read() function (line 19) which is reading 0x18 bytes from stdin (0) storing the output in local_38

  • Then there's an if statement that checks the input to a constant value

Enough of static code analysis, lets debug the binary in radare2.

Debugging with Radare2

We open the binary with radare2 in debug mode with the following command

r2 -d ./boi

Before we start debugging in radare2 we need to do a quick setup.

Setting up Standard input and Standard output for debugging in Radare2

We will be using a file to provide the stdin to radare2 and we will be redirecting the stdout to a different terminal (different from the one where radare2 is running). This makes it very easy to change the stdin given to radare2 and it does not clobbers our radare2 terminal screen.

Before proceeding, it is recommended to use tmux or any terminal multiplexer

We will be creating a rarun2 profile with the following contents

#!/usr/bin/env rarun2
stdin=stdin.txt         # file name to read the stdin from
stdout=/dev/pts/5       # Redirect the stdout to tty with id 5

Line2: Enter the name of the file you want to use as input

Line3: Make a new pane (in tmux) and run tty comand to get the id of the terminal. This is where the output will be redirected when we run the binary in radare2

and save it as tty.rr2 (The name really doesn't matter) (the name really doesn't matters)

Now we can keep changing the contents of stdin.txt with the command (on another pane)

python -c "print('A'*100)" > stdin.txt

This is how my terminal looks like after making all the splits:

So we have 4 panes with the following purposes:

  • Pane 1:This is where we will work with radare2

  • Pane 2: We will be getting the stdout here

  • Pane3: We will use this to modify the contents of input file (stdin.txt)

  • Pane4: To look at the contents of stdin.txt all the time

Before proceeding, there's one more thing we need to take care of, when the std output comes on pane 2, zsh will try to execute them, so instead what we can do is, halt the execution of any commands on that terminal using sleep 999 . Enter this command in pane3 and everything should be setup for us now.

Analyzing the binary in Radare2

To import the rarun2 config file with radare2 run the following command:

r2 -e dbg.profile=tty.rr2 -d ./boi

Now we are presented with gdb commandline interface.

  • We start by analyzing the binary with aaa

  • And then we can list the available function from the symbol tree with afl

  • Let's disassemble the main function with pdf @ main or we can seek (move) to the main function with s main and then do a simple pdf which prints the disassembly of the function from the current position in the binary.

  • You can see your current position inside the memory at the start of the radare2 prompt (marked in red in the above image)

  • We see a read instruction at the address 0x004006a0 , this is where binary asks for an input.

  • Let's proceed by making a breakpoint at the read instruction0x004006a0

db 0x004006a0
  • Now we can execute the binary with dc (debugger continue) and we are presented with the output in r2 window saying hit breakpoint at: 0x4006a0

Note, sometimes binary might not execute with dc, just run dc command again

  • Now comes the time to show the true power of radare2, we will be going into "Visual Mode"

Radare2 Visual Mode

To enter into visual mode

vv

and you should be presented with the following screen

Get yourself acquainted with this window, we will be spending a lot of time here. The most important parts here are

  1. Disassembly View: This is the Disassembly view of the binary and you can see many comments starting with ; , you can even add in your own comments on the current instruction with ; and enter in your comment and press enter, this will add the comment on the first instruction that you see in the disasembly view.

  2. Stack View: This is the "live" view of the memory used by the binary, this starts at the top of the stack by default, but you can scroll with the navigation keys

  3. Register View: You can see the value of each register here, the blue highlight means the register was changed in the last instruction

  4. This is where current RIP points to

  5. These are all the local variables defined in the current function

A few tips for working in the visual mode more productive:

You can scroll up and down with arrow keys or use vim keys h j k and l

To cycle between panes, use the tab key and you should see the blue outline switching to different panes

The left column is the hex address in the virtual memory

The middle 16 columns are the hex values stored in the corresponding memory location

The last column is the ASCII representation of the hex data

You can cycle through the color scheme with shift + c , I prefer the third (press shift + c twice) which even shows you rsp and rbp in the stack view

Remeber you can edit the command being ran in any pane with e and then enter the command like drr , this gives you much more freedom over the visual mode

You can toggle into Window mode with w and then you can resize the panes with shift + arrow/vim keys

Debugging in Visual mode

Now, let's start the debugging process. At this point the program is halted because of our breakpoint. We need to step the binary to the next instruction with shift + s (this does not follows the function, s follows function calls). As soon as we hit shift + s program reads the input from stdin.txt and the input is placed onto the stack.

You can even see 0xdeadbeef stored onto the memory in little-endian 0xefbeadde

  • Let's keep stepping with shift + s until we hit the compare statement at 0x004006a8

  • Here you can see the register eax is being compared with the constant 0xcaf3baee , so lets see what's stored in the register rax/eax

As you can see rax holds 0xdeadbeef and since 0xdeadbeef is not equal to 0xcaf3baee This compares instruction will be false and the program ends.

At this point, it should be clear that we want to overwrite the 0xdeadbeef with 0xcaf3baee . We have at least two ways to go about it. First includes manually finding the difference between the start of our input and the start of the string 0xdeadbeef , but this is not at all elegant and will be very time consuming in upcoming challenges. So let's semi-automate this task.

Finding the Offset

We will be using Metasploit's pattern_create.rb and pattern_offset.rb script to generate a cyclic pattern and then find the offset.

  • using pattern_create.rb , create a cyclic pattern of length say 100:

pattern_create.rb -l 200 
  • Copy the output from the above command into stdin.txt. You can use echo command to do so

  • Now we need to rerun the binary inside radare2, we can do this first by going into command mode from visual mode by pressing : and then we can enter the following:

# start the binary again
ood
# continue the program to hit the breakpoint
dc

And we don't have to place the breakpoints again. After running these commands you can switch back to visual mode from command mode by simply pressing Enter key

  • Now let's step in again until we hit the compare instruction again

If you look at the rax register, this time it is filled with a different value. This is the input from our cyclic pattern.

  • Copy the value that rax holds, in this case 0x37614136 and run the following command

pattern_offset.rb -l 200 -q 0x37614136

We should get an exact match at an offset of 20. This means we need to have 20 characters in our input before we can start the overflow.

The final Exploit

After knowing the offset we can craft a simple exploit as follows

python -c "print('A' * offset + 'overflow value')" | ./boi

we already know the offset which is 20, and the overflow value is 0xcaf3baee , before we can exploit we need to do two things:

  1. We need to convert 0xcaf3baee into little-endian

  2. As soon as the python command finishes executing there is nothing in the stdin buffer, so we won't get a shell back and the program finishes executing without any error

After getting the above two tasks, this is how our final exploit should look like

(python2 -c "print('A'*20 + '\xee\xba\xf3\xca')"; cat) |./boi

We use cat command to get in our stdin and pass it out to shell

Final Thoughts

In my opinion, radare2 is very powerful and its ability to look at the memory and registers dynamically makes it so much powerful as a debugger. This was my first walkthrough on Nightmare's challenges, so any feedback and questions are welcome, You can find me on Twitter at @smash8tap .

PS: I would like to Thank Lorenzo_apd for proofreading the write-up and making it better.

Nightmares
here