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
:
- Linux: Install openvpn then
sudo openvpn vpn.conf
- Mac: tunnelblick
- Windows: 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