13 June 2019

Net 2

Write-up for: https://exploit.education/phoenix/net-two/.

The challenge

This level is similar to the previous one, only a little more tricky. We can connect on port 64002 to examine the service manually:

user@phoenix-amd64:~$ nc 127.0.0.1 64002
Welcome to phoenix/net-two, brought to you by https://exploit.education
For this level, sizeof(long) == 8, keep that in mind :)
_>K70%!($v12345678
Whoops, better luck next time. Receieved 4050765991979987505, wanted 8877065176632751666

This time, the service requires us to send a fairly large number. Since this number is not immediately linked to the bytes we see, let’s look at the code.

unsigned long quad[sizeof(long)], result, wanted;

getrandom((void *)&quad, sizeof(quad), 0) != sizeof(quad)

result = 0;
for (i = 0; i < sizeof(long); i++) {
	result += quad[i];
	if (write(1, (void *)&quad[i], sizeof(long)) != sizeof(long)) {
	    errx(1, "Why have you foresaken me, write()");
	}
}

These are the key parts. The program generates 8 random long values and sums them up in result. But what is printed to the service is each individual value. Thus, our script must replicate this behavior: reading 8 long values from the service, summing them up and send them back in the right byte order. Here we go:

import struct
import socket

HOST = "127.0.0.1"
PORT = 64002

def read_line_from_sock(socket):
    buf = ""
    while True:
        b = s.recv(1)
        if b == b'\n':
            break
        buf += b.decode("ascii")
    return buf

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    print(read_line_from_sock(s))
    print(read_line_from_sock(s))

    query = s.recv(8*8)
    to_send = 0
    print("raw query: " + str(query))
 
	print("long values received")
    for i in range(0, len(query), 8):
        to_send += struct.unpack("Q", query[i:i+8])[0]
        print(struct.unpack("Q", query[i:i+8])[0])

    print("sum: " + str(to_send))
    to_send &= 0xffffffffffffffff

    print("without overflow: " + str(to_send))
    s.sendall(struct.pack('Q', to_send))

    print(read_line_from_sock(s))

Again, we start by reading the banner fully using the read_line_from_sock function. Then we read 64 (8 * 8) bytes, representing long values. In a loop, we unpack each value individually using the format specifier Q for unsigned long and sum them up in to_send. Last but not least, we need to account for a potential integer overflow of the sum within the service, we AND the sum with a 8-byte mask, removing any leading bytes. Then, we send it all back to the service.

Remember, integers can overflow when summed up:

#include <stdio.h>
#include <limits.h>

int main() {
    unsigned i = UINT_MAX / 2 + 100;
    unsigned j = i;

    unsigned x = i + j;
    printf("%d\n", x);
    return 0;
}
~ » gcc test.c
~ » ./a.out
198

Now, let’s run our script.

user@phoenix-amd64:~$ python3 net2.py

Welcome to phoenix/net-two, brought to you by https://exploit.education
For this level, sizeof(long) == 8, keep that in mind :)
raw query: b'\xf0\xe8Jp\x05.v\xec\x97LC\xb6\xfa\x90MI\x8e\x15K\xceR\x90\x01\x87\x01\x80\xfc\xc0F\xd0\xe4\xbc\x0e\x172\xf1\xe82D\xed5G\xac#\x11\xd2\rB\xb3Q?\xaa-7y\xb3\xb8\xa2k\x94\xa7\xd1\xc7\x86'
long values received
17038856841096521968
5282037344449547415
9728215355419727246
13611232976124542977
17096846061465638670
4759691352255252277
12932428474240422323
9711961639127589560
sum: 90161270044179242436
without overflow: 16374293749341035972
You have successfully passed this level, well done!

Done!