Cyberstakes CTF 2018: Protocol
Category: Reverse Engineering
Points: 150
Provided:
Hints
- The provided packet capture is of communication between the client and server.
- Analyze the server to determine how the communication happens and what it means!
- The server is runnable, you can try communicating with it locally.
Observations
We are provided with a server that implements a custom protocol and a packet capture of the protocol being used.
Opening the server in Binary Ninja, we are drawn to a function called RRQ_handler
. The rest of the main
function appears to be boilerplate socket programming.
A simple Google for RRQ
points us to the Wikipedia page for TFTP.
TFTP, as the name implies, is used for transferring files over a network and uses UDP as the transfer protocol.
A TFTP server will listen on one specific port for an initial connection.
Once a client has connected, the server chooses a random data transfer port to use for the remainder of the exchange.
Looking at the RRQ_handler
function, we can see a call to fork
and then subsequent calls to create a new socket within the new process.
This appears to be consistant with TFTP.
TFTP
- Client sends a RRQ (read request) to the server at port number 69, containing the filename, transfer mode, and any other options.
- Server responds with a DATA packet to the RRQ. Packet is sent from a randomly allocated ephemeral port, and all future packets to the server should be directed to this port.
- Server sends numbered DATA packets to the client.
- Client replies with numbered ACK packets for all DATA packets.
Packet Capture
Turning our attention to the pcap file, we can see a lot of extra junk such as SSH packets. We know that TFTP uses UDP so we can filter out non-UDP packets:
Solution
The common packet length (262) shows up in calls to malloc
inside the server program. This confirms that these are probably the right packets.
The client is using port 46722 and the server is initially using port 1234. The first message from the client is a 1 followed by the requested filename.
The server, upon reciept of the RRQ, forks off the child process that then opens a new socket and the rest of the transmission uses port 47639.
Each packet seems to start off with a sequence number that is used for ACKing data packets.
For each message from the server, we can see the last four bytes contain some kind of data. The server is sending us the contents of the requested flag file, but only four bytes at a time. These bytes appear to be encoded in some manner.
Looking back at the RRQ_handler
, we can see a call to rand
followed by the highlighted blocks:
This is a loop that will execute 4 times, xoring the current flag character with a random byte value before sending these encoded characters to the client.
If we look at the first block in the function, we can also see a call to srand
with a seed value of 1. Since calls to rand
will produce the same values given the same seed, we can now determine the exact sequence of random bytes and recover the contents of the flag.
The client.c file provides an implementation of a client that can communicate with the given server program.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define SERVER "127.0.0.1"
#define PORT 1234
#define BUFLEN 262
char buf[BUFLEN];
struct sockaddr_in addr;
int addrlen = sizeof(addr);
int seqnum = 0;
void hexdump(const void* data, size_t size) {
char ascii[17];
size_t i, j;
ascii[16] = '\0';
for (i = 0; i < size; ++i) {
printf("%02X ", ((unsigned char*)data)[i]);
if (((unsigned char*)data)[i] >= ' ' && ((unsigned char*)data)[i] <= '~') {
ascii[i % 16] = ((unsigned char*)data)[i];
} else {
ascii[i % 16] = '.';
}
if ((i+1) % 8 == 0 || i+1 == size) {
printf(" ");
if ((i+1) % 16 == 0) {
printf("| %s \n", ascii);
} else if (i+1 == size) {
ascii[(i+1) % 16] = '\0';
if ((i+1) % 16 <= 8) {
printf(" ");
}
for (j = (i+1) % 16; j < 16; ++j) {
printf(" ");
}
printf("| %s \n", ascii);
}
}
}
}
void process_packet(int sockfd) {
seqnum++;
memset(buf, 0, BUFLEN);
if (recvfrom(sockfd, buf, BUFLEN, 0, (struct sockaddr*) &addr, &addrlen) < 0) {
fprintf(stderr, "recvfrom() failed\n");
exit(1);
}
// Decrypt the message
uint8_t key = (uint8_t) rand();
printf("%x", key);
// for (int i = 0; i< 4; i++) {
// printf("%c", buf[258 + i] ^ key);
// }
memset(buf, 0, BUFLEN);
buf[1] = 4;
buf[3] = seqnum;
if (sendto(sockfd, buf, BUFLEN, 0, (struct sockaddr*) &addr, addrlen) < 0) {
fprintf(stderr, "sendto() failed\n");
exit(1);
}
}
int main(int argc, char* argv[]) {
memset(buf, 0, BUFLEN);
srand(1); // Call `srand()` with the same seed as the server
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockfd < 0) {
fprintf(stderr, "Failed to create socket\n");
return 1;
}
memset((char*) &addr, 0, addrlen);
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
if (inet_aton(SERVER, &addr.sin_addr) == 0) {
fprintf(stderr, "inet_aton() failed\n");
return 1;
}
printf("Sending initial Request to Read (RRQ)\n");
buf[1] = 1;
sprintf(&buf[2], "%s", "flag");
if (sendto(sockfd, buf, BUFLEN, 0, (struct sockaddr*) &addr, addrlen) < 0) {
fprintf(stderr, "sendto() failed\n");
exit(1);
}
if (recvfrom(sockfd, buf, BUFLEN, 0, (struct sockaddr*) &addr, &addrlen) < 0) {
fprintf(stderr, "recvfrom() failed\n");
exit(1);
}
while (seqnum < 9) {
process_packet(sockfd);
}
close(sockfd);
return 0;
}
By creating a fake flag file with known contents and then running the client program, we can recover the random byte values. The following Scapy script then uses these values to iterate through the UDP packets in the pcap and decode the flag.
from scapy.all import *
pkts = rdpcap("./challenge.pcap")
# The random bytes determined from running the client program
key = bytearray(b'\x67\xc6\x69\x73\x51\xff\x4a\xec\x29')
index = 0
flag = ''
for pkt in pkts:
# Only care about UDP packets
if (pkt.haslayer(UDP)):
chars = bytes(pkt[UDP].payload)[-4:]
# Check if this payload has part of the flag
if chars != '\x00\x00\x00\x00':
for c in chars:
flag += chr(ord(c) ^ key[index])
index = index + 1
print(flag)
Flag: ACI{1128d2e3744e402cd3cd594b30b}