Tips for the challenges

ALIENQUIZ

Reading process output

When interacting with processes we need a way to read their output. Since read is blocking it is important to read a finite amount of bytes (usually 1), until a certain condition is fulfilled. The following function reads until a pattern appears in the string.

def read_until(stream, pattern):
   r = ''
   while pattern not in r: # checks if pattern is present in the string r using the 'in' operator
       b = stream.read(1).decode() # reads a byte and converts it from a bytestring to a regular string
       if b == '':
      		raise ValueError('program closed the output!')
       print(b, end='') # print it
       sys.stdout.flush() # flushes the stdout
       r += b # append the byte to the string r
   print('') # print a newline
   return r  # return the string

Solution to the exercise

We use read_until to solve the exercise proposed here. Since the program writes outputs of the form Write n: , where n is a value, we use read_until(p.stdout,': ') to capture each output. Then, the value n is extracted using a simple regular expression 'Write ([0-9]+): ' and sent back to the program (encoded as bytes).

import subprocess
import re
import sys

# regexp to extract the number in the challenge
write_re = re.compile('Write ([0-9]+): ') 

p = subprocess.Popen('./exercise',stdin=subprocess.PIPE,stdout=subprocess.PIPE)

while True:
  challenge = read_until(p.stdout,': ') # reads the challenge
  number = write_re.match(challenge).group(1) # extracts the number
  p.stdin.write((number + '\n').encode()) # writes it back
  p.stdin.flush() # flushes the write to the program

Lambdas

For this challenge you have to deal with functions, and lambdas might be of great help. Lambdas are one line, anonymous functions, that you might have seen in other languages, such as Javascript.

>>> lambda x,y: x+y
 at 0x7ff7cfe94ea0>

You can apply it right away or store it in a variable or data structure.

>>> (lambda x,y: x+y)(3,4)
7
>>> sum = lambda x,y: x+y
>>> sum(3,4)
7

You can use it, for example, to make a dictionary of functions that returns the correct operator indexed by its name

>>> operators = { '+': (lambda x,y : x+y), '*': (lambda x,y: x*y), '/': (lambda x,y: x//y)}
>>> operators['+'](3,4)
7

Python provides a useful module called operator that does a similar thing, but using lambdas is more insightful.

OVERSHADE

Here is a short video explaining why and how one-way hash functions are used to protect passwords.

Sending bytes in gdb

overshade program takes a password from the standard input. In order to send an input when debugging the program it is possible to use the following syntax:

(gdb) run < <(python -c "print('A'*50)")

What is inside <( ... ) is executed and the output is pipelined to the debugged program. In this particular example, we are sending 50 chars A, i.e., 0x41, to the program.

Observe the overflow

A good starting point for this challenge is to observe the effect of an overflow:

(gdb) run < <(python -c "print('A'*50)")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /opt/challenges/bof/overshade/overshade < <(python -c "print('A'*50)")

Breakpoint 1, 0x080488d8 in main ()
(gdb) x/32xb $ebp-0xcc
0xbffff2cc:     0x71    0xa9    0x86    0xaa    0xf6    0x26    0xbf    0xb8
0xbffff2d4:     0x05    0x90    0xb1    0xac    0x93    0x74    0x5d    0x25
0xbffff2dc:     0x79    0xba    0xf0    0xa7    0xaa    0x98    0xd1    0x66
0xbffff2e4:     0xc4    0x04    0xb8    0xf5    0x75    0x77    0xae    0x88
(gdb) nexti

Breakpoint 2, 0x080488dd in main ()
(gdb) x/32xb $ebp-0xcc
0xbffff2cc:     0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0xbffff2d4:     0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0xbffff2dc:     0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0xbffff2e4:     0x41    0x41    0x41    0x41    0x41    0x00    0xae    0x88

We can notice the A's overflowing 29 bytes of the correct hash, then we have the string terminator 0x00 put by scanf and the last two bytes are unchanged. So we can overwrite most but not all the hash. Think of how to exploit this!

This video illustrate more in detail how to use gdb to observe the overflow.

Recompiling the challenge

if you want to recompile the program (for example to make it simpler to exploit changing scanf("%49s", password) into scanf("%52s", password) so to make it possible to fully overflow the hash) you can just do it as follows gcc /opt/challenges/bof/overshade/overshade.c -o overshade -lcrypto. Of course the recompiled program won't be able to access the flag but it might be useful to solve the challenge step-by-step.

Sending bytes with python3

Be careful using print in python3 instead of sys.stdout.buffer.write because for bytes greater or equal 0x80 it sends a 0xc2 before (this is what UTF-8 states). For example:

$ python3 -c "print('\x80')"| hexdump -C
00000000  c2 80 0a                                          |...|
00000003
$ python3 -c "import sys;sys.stdout.buffer.write(b'\x80')"| hexdump -C
00000000  80                                                |.|
00000001

Notice that print also sends the newline which is `\x0a`, but this can be suppressed if needed.

Buffered output and Popen

If you are trying to interact with overshade from python with Popen you will fail because of buffering (and overshade is not flushing its standard output).

>>> p = subprocess.Popen('./overshade',stdin=subprocess.PIPE,stdout=subprocess.PIPE) 
>>> p.stdout.read(1)
(blocked, read never returns!)

In particular read(1) blocks because overshade output is in the buffer and won't reach the pipe. You can still send the payload on the stdin and try the attack:

>>> p = subprocess.Popen('./overshade',stdin=subprocess.PIPE,stdout=subprocess.PIPE) 
>>> p.stdin.write(b'test\n')
5
>>> p.stdin.flush()
>>> p.stdout.read(42)
b'Insert your password: test\nWRONG PASSWORD!'
>>>

However, if you want to fix this behaviour you can disable the output buffer through the command stdbuf (see the manpage). Option -o0 makes the job. So you can use Popen as follows

>>> p = subprocess.Popen(['stdbuf', '-o0', './overshade'],stdin=subprocess.PIPE,stdout=subprocess.PIPE) 
>>> p.stdout.read(1)
b'I'

OTPIZZA

Network layout

You see from the challenge description (and the network diagram) that the VPN gives you access to a network 10.0.0.0/16 in which you will have an IP address. The IP address of testbed in the 10.0.0.0/16 network is, for example, 10.0.0.2. In the network there is a firewall 10.0.241.57 that, having multiple network interfaces, is connected to the 172.17.0.0/16 network, in which there is a "hidden" server (172.17.0.2).

The /16 notation, as you probably already know, is the netmask: it refers to the number of bits that identify the network and gives you a way to know where the new IPs will be assigned. For example, in the two networks in the challenge, the /16 means that the first 16 bits are fixed, so every new IP will have the same 16 bit (10.0) and then a progressive number in the next two bytes.

You have two ways to work on this challenge: from testbed or connecting to the provided VPN. If you start the VPN client on your laptop with the provided vpn.conf, it will create a network interfaces called tap0 that behaves mostly like a normal network adapter. You can see that testbed is already connected using the ifconfig (interface config) program, that shows all possible interfaces. In the output, other than the eth0, the ethernet, and lo, the loopback interface, there is the tap0

:
tap0: flags=4163  mtu 1500
        inet 10.0.0.2  netmask 255.255.0.0  broadcast 10.0.255.255
        ether a6:fa:6f:9f:9e:93  txqueuelen 100  (Ethernet)
        RX packets 37297  bytes 3962150 (3.7 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 19710  bytes 1539152 (1.4 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

VPN clients

You can connect with the laptop using any openvpn:

netcat and ping

To practice with the netcat program, the firewall contains an *Echo Server* (on port 8080), a service that does absolutely nothing except sending out what it is given as input.
You can connect to it with the command

nc -v 10.0.241.57 8080

Also, to check if a server is online, you can use the ping program: it sends small icmp echo packets that almost every server replies to

ping 10.0.241.57