Flare-On 6 CTF WriteUp (Part 4)

Flare-On 6 CTF WriteUp (Part 4)

. 7 min read

This is the fourth part of the FlareOn 6 CTF WriteUp series.

4 - Dnschess

The challenge reads

Some suspicious network traffic led us to this unauthorized chess program running on an Ubuntu desktop. This appears to be the work of cyberspace computer hackers. You'll need to make the right moves to solve this one. Good luck!

We have three files - ChessUI, ChessAI.so and capture.pcap. The first two are ELF binaries compiled for Linux x64. Running ChessUI on an Ubuntu 18.04 system we are greeted with a Chess game.

Figure 1: A game of chess
Figure 1: A game of chess

Our opponent DeepFLARE is the black side and waits for our move. Let's make a move. We move the knight from B1 to A3.

Figure 2: Making our move
Figure 2: Making our move

DeepFLARE resigns immediately without making a move. Trying other first moves doesn't change the outcome. Let's have a look at the PCAP.

Figure 3: The PCAP
Figure 3: The PCAP

The PCAP in its entirety consists of DNS requests along with their responses. There are DNS A queries for domain names which have the form of <name>-<pos1>-<pos2>.game-of-thrones.flare-on.com where

  • name is the name of a chess piece
  • pos1 and pos2 are two positions on the chess board

Corresponding to these DNS queries, we have responses as well.

Figure 4: DNS response
Figure 4: DNS response

However, we get a NXDOMAIN response when we try to lookup the names on our system.

$ nslookup rook-c3-c6.game-of-thrones.flare-on.com 1.1.1.1
Server:		1.1.1.1
Address:	1.1.1.1#53

** server can't find rook-c3-c6.game-of-thrones.flare-on.com: NXDOMAIN

Analyzing ChessUI

ChessUI as it's name suggest must be responsible for the game GUI. Let's analyze the binary in Ghidra. Make sure that "Fixup Unresolved External Symbols" is unchecked in ELF loading options.

Figure 5: The main function in ChessUI
Figure 5: The main function in ChessUI

The presence of function names starting with gtk implies that the GUI was developed using the GIMP Toolkit framework. The third parameter to g_signal_connect_data takes the address of a callback handler function FUN_00103ab0.

Going through the decompiled code of FUN_00103ab0 we can notice that it loads ChessAI.so and obtains the addresses of symbols getAiName, getAiGreeting and getNextMove as shown in Figure 6.

Figure 6: ChessUI loads ChessAI
Figure 6: ChessUI loads ChessAI

Among the three functions, getNextMove looks interesting. Lets check out its code in ChessAI.so

Analyzing ChessAI.so

Figure 7: Decompiled code of getNextMove
Figure 7: Decompiled code of getNextMove

Near the beginning there is a call to gethostbyname. This function can be used to obtain the IPv4 address of a domain name. Calling this function will result in a DNS query as we saw in the PCAP. gethostbyname returns a hostent structure filled with the requested info.

struct hostent 
{
    char  *h_name;            /* official name of host */
    char **h_aliases;         /* alias list */
    int    h_addrtype;        /* host address type */
    int    h_length;          /* length of address */
    char **h_addr_list;       /* list of addresses */
};

For now, let's try to hook gethostbyname using the LD_PRELOAD technique. We define our own version of gethostbyname which will simply print its argument.

// Compile using
// gcc -Wall -fPIC -shared -o gethostbyname gethostbyname.c

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

struct hostent *gethostbyname(char *name)
{
	printf("[+] %s\n", name);
	return NULL;
}
gethostbyname.c

Let us inject the library using LD_PRELOAD and run ChessUI. When we make a move such as B1-A3 we get the following output on the terminal.

$ LD_PRELOAD=./gethostbyname ./ChessUI
[+] knight-b1-a3.game-of-thrones.flare-on.com

Essentially the application makes a DNS request for a name which contains the information about our move. The IP address returned as a response to this request must contain the move DeepFLARE should make.

The returned value from gethostbyname is a pointer to a hostent structure.

Figure 8: Checks on the returned hostent structure
Figure 8: Checks on the returned hostent structure

h_addr_list is a double pointer to the octets in the IP address. There are a few checks done on the octet. If the returned IP address is of the form W.X.Y.Z then W must be 127, the last bit of Z must be zero (even). uParm is a counter starting from 0 which increases by 1 for each turn. Y & 0xf i.e. the last 4 bits must equal to this counter value.

If all of the conditions satisfy, it xors some data from DAT_00102020 with Y - the second octet, with the result stored to DAT_00104060. The data at 102020 looks to be array of encrypted bytes.

Figure 9: An array of encrypted bytes
Figure 9: An array of encrypted bytes

The following is the list of IP addresses obtained from the DNS responses which have the first octet equal to 127 and the last octet even.

127.53.176.56
127.159.162.42
127.230.231.104
127.141.14.174
127.34.217.88
127.108.24.10
127.25.74.92
127.99.253.122
127.200.76.108
127.182.147.24
127.49.59.14
127.217.37.102
127.89.38.84
127.215.177.38
127.252.212.90

This can be further be sorted in ascending order according to the value of octet[2] & 0xf

>>> ip_list = ['127.53.176.56', '127.159.162.42', '127.230.231.104', '127.141.14.174', '127.34.217.88', '127.108.24.10', '127.25.74.92',  '127.99.253.122', '127.200.76.108', '127.182.147.24', '127.49.59.14', '127.217.37.102', '127.89.38.84', '127.215.177.38', '127.252.212.90']

>>> ip_list.sort(key=lambda x:int(x.split('.')[2]) & 0xf)

>>> ip_list
['127.53.176.56', '127.215.177.38', '127.159.162.42', '127.182.147.24', '127.252.212.90', '127.217.37.102', '127.89.38.84', '127.230.231.104', '127.108.24.10', '127.34.217.88', '127.25.74.92', '127.49.59.14', '127.200.76.108', '127.99.253.122', '127.141.14.174']

This is the order of DNS responses the game expects to receive. Note that the legality of our move is not checked client-side within the game. So its possible that we make any move as long as the DNS response is correct.

However, for completeness let's make our moves in proper order too. Corresponding to the sorted list of IP addresses we have this list of DNS requests which indicate the move we should make.

╔═════╦═════════════════╦═══════════════════════════════════════════╗
║ No. ║        IP       ║                Domain Name                ║
╠═════╬═════════════════╬═══════════════════════════════════════════╣
║  1  ║ 127.53.176.56   ║ pawn-d2-d4.game-of-thrones.flare-on.com   ║
╠═════╬═════════════════╬═══════════════════════════════════════════╣
║  2  ║ 127.215.177.38  ║ pawn-c2-c4.game-of-thrones.flare-on.com   ║
╠═════╬═════════════════╬═══════════════════════════════════════════╣
║  3  ║ 127.159.162.42  ║ knight-b1-c3.game-of-thrones.flare-on.com ║
╠═════╬═════════════════╬═══════════════════════════════════════════╣
║  4  ║ 127.182.147.24  ║ pawn-e2-e4.game-of-thrones.flare-on.com   ║
╠═════╬═════════════════╬═══════════════════════════════════════════╣
║  5  ║ 127.252.212.90  ║ knight-g1-f3.game-of-thrones.flare-on.com ║
╠═════╬═════════════════╬═══════════════════════════════════════════╣
║  6  ║ 127.217.37.102  ║ bishop-c1-f4.game-of-thrones.flare-on.com ║
╠═════╬═════════════════╬═══════════════════════════════════════════╣
║  7  ║ 127.89.38.84    ║ bishop-f1-e2.game-of-thrones.flare-on.com ║
╠═════╬═════════════════╬═══════════════════════════════════════════╣
║  8  ║ 127.230.231.104 ║ bishop-e2-f3.game-of-thrones.flare-on.com ║
╠═════╬═════════════════╬═══════════════════════════════════════════╣
║  9  ║ 127.108.24.10   ║ bishop-f4-g3.game-of-thrones.flare-on.com ║
╠═════╬═════════════════╬═══════════════════════════════════════════╣
║  10 ║ 127.34.217.88   ║ pawn-e4-e5.game-of-thrones.flare-on.com   ║
╠═════╬═════════════════╬═══════════════════════════════════════════╣
║  11 ║ 127.25.74.92    ║ bishop-f3-c6.game-of-thrones.flare-on.com ║
╠═════╬═════════════════╬═══════════════════════════════════════════╣
║  12 ║ 127.49.59.14    ║ bishop-c6-a8.game-of-thrones.flare-on.com ║
╠═════╬═════════════════╬═══════════════════════════════════════════╣
║  13 ║ 127.200.76.108  ║ pawn-e5-e6.game-of-thrones.flare-on.com   ║
╠═════╬═════════════════╬═══════════════════════════════════════════╣
║  14 ║ 127.99.253.122  ║ queen-d1-h5.game-of-thrones.flare-on.com  ║
╠═════╬═════════════════╬═══════════════════════════════════════════╣
║  15 ║ 127.141.14.174  ║ queen-h5-f7.game-of-thrones.flare-on.com  ║
╚═════╩═════════════════╩═══════════════════════════════════════════╝

For example, our first move will be to move the pawn from d2 to d4. Now all that is left is to modify gethostbyname.c  such that it also returns the response in the correct order.

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

unsigned char ip_list[16][4] = {
{127,53,176,56}, {127,215,177,38}, {127,159,162,42}, {127,182,147,24}, {127,252,212,90}, {127,217,37,102}, {127,89,38,84}, {127,230,231,104}, {127,108,24,10}, {127,34,217,88}, {127,25,74,92}, {127,49,59,14}, {127,200,76,108}, {127,99,253,122}, {127,141,14,174}};

struct hostent 
{
    char  *h_name;            /* official name of host */
    char **h_aliases;         /* alias list */
    int    h_addrtype;        /* host address type */
    int    h_length;          /* length of address */
    char **h_addr_list;       /* list of addresses */
}_hostent;

int idx = 0;

void* addr_list[] = {NULL, NULL};

struct hostent *gethostbyname(char *name)
{
	addr_list[0] = &ip_list[idx++];
	_hostent.h_addr_list = addr_list; 
	return &_hostent;
}

We can compile and LD_PRELOAD it the same way.

$ gcc -Wall -fPIC -shared -o gethostbyname gethostbyname.c
$ LD_PRELOAD=./gethostbyname ./ChessUI

Winning the game

Let's play the game executing the moves in the order specified in the table.

Figure 10: The Endgame
Figure 10: The Endgame

Playing out all 15 moves we win and reach the stage as shown in Figure 15. The flag is also printed.

FLAG: LooksLikeYouLockedUpTheLookupZ@flare-on.com