BsidesSFCTF - FOR: dnscap


dnscap (500 pts)
CTF: BSidesSF 2017 CTF
URL: https://scoreboard.ctf.bsidessf.com/
CAT: forensics


Found this packet capture. Pretty sure there's a flag in here. Can you find it!?

dnscap.pcap


(1) ANALYSIS OF THE PCAP FILE USING Wireshark

On this challenge, a pcap file named 'dnscap.pcap' is presented for analysis. We open it with Wireshark and see that there are lots of DNS queries and responses of the following types: CNAME, TXT and MX. On each of them, the contents of both the queries and the responses contain large hex strings.

We can use the following Wireshark filters to isolate each query/response of interest:

TXT QUERIES:
(dns.qry.type == 16) and not (dns.txt)

TXT RESPONSES:
dns.txt

MX QUERIES:
(dns.qry.type == 15) and not (dns.mx.mail_exchange)

MX RESPONSES:
dns.mx.mail_exchange

CNAME QUERIES:
(dns.qry.type == 5) and not (dns.cname)

CNAME RESPONSES:
dns.cname

Using tshark, we can extract all those packets in separated files:

TXT QUERIES

# tshark -r dnscap.pcap -Y '(dns.qry.type == 16) and not (dns.txt)' -T fields -e frame.number -e dns.qry.name > queries_TXT.txt

TXT RESPONSES

# tshark -r dnscap.pcap -Y 'dns.txt' -T fields -e frame.number -e dns.txt > responses_TXT.txt

MX QUERIES

# tshark -r dnscap.pcap -Y '(dns.qry.type == 15) and not (dns.mx.mail_exchange)' -T fields -e frame.number -e dns.qry.name > queries_MX.txt

MX RESPONSES

# tshark -r dnscap.pcap -Y 'dns.mx.mail_exchange' -T fields -e frame.number -e dns.mx.mail_exchange > responses_MX.txt

CNAME QUERIES

# tshark -r dnscap.pcap -Y '(dns.qry.type == 5) and not (dns.cname)' -T fields -e frame.number -e dns.qry.name > queries_CNAME.txt

CNAME RESPONSES

# tshark -r dnscap.pcap -Y 'dns.cname' -T fields -e frame.number -e dns.cname > responses_CNAME.txt

Note that for each capture we include the frame number, just in case it is necessary for any purpose.

Then we merge all the files in a single one:

# cat queries_CNAME.txt queries_MX.txt queries_TXT.txt responses_CNAME.txt responses_MX.txt responses_TXT.txt > fusion.txt

And generate a new merged file with all the packets ordered by frame number (first column in the file):

# sort -k1n,3 fusion.txt > fusion_ordenado.txt

Here is how this new file looks like:

1       05e100a621c3620001636f6e736f6c65202873697276696d65732900.skullseclabs.org
2       958700a621c3620001636f6e736f6c65202873697276696d65732900.skullseclabs.org
3       634f00a621010a0000.skullseclabs.org
4       7cd501a621c362010a.skullseclabs.org
5       96b201a621010ac362
6       b11c01a621c362010a.skullseclabs.org
7       e14001a621010ac362
8       0ab801a621c362010a.skullseclabs.org
9       0e3d01a621010ac362.skullseclabs.org
10      772301a621c362010a.skullseclabs.org
11      d01b01a621010ac362.skullseclabs.org
12      b73f01a621c362010a57656c636f6d6520746f20646e7363617021205468.6520666c61672069732062656c6f772c20686176652066756e21210a.skullseclabs.org
13      aeb101a621010ac393.skullseclabs.org
/....../

(2) ANALYSIS OF THE CAPTURED DATA

In order to analyze the data we have just extracted, we use the following Perl script named 'bsidessf17_dnscap_script1.pl' (yeah, there is life beyond Python!):

#!/usr/bin/perl
#
# bsidessf17 - dnscap
#
# script de analisis inicial
#
# fusion_ordenado.txt tiene el formato: <paquete>\t<datos>
#
# Rev.20170212 by sn4fu

use strict;
use warnings;
use 5.016;

my $fichero = 'fusion_ordenado.txt';
open(my $fh,$fichero)
or die "No se ha encontrado el fichero '$fichero' $!";

while (my $linea = <$fh>) {
 chomp $linea;                                          # quitar CR
 my ($paquete, $datos) = split /\t/, $linea;            # parsear usando TAB como separador
 print "$paquete\n";                                    # imprimir numero de paquete
 print "$datos\n";                                      # imprimir datos del paquete
 my @cadenas = split /\./, $datos;                      # parsear las partes de la cadena de datos separadas con '.'
 my $cadena_sobrante1 = 'org';                          # las cadenas 'org' no nos interesan
 @cadenas = grep {!/$cadena_sobrante1/} @cadenas;
 my $cadena_sobrante2 = 'skullseclabs';                 # las cadenas 'skullseclabs' no no sinteresan
 @cadenas = grep {!/$cadena_sobrante2/} @cadenas;
 foreach (@cadenas)
   {
      print "$_\n";                                     # imprimir la cadena HEX
      say (pack "H*",$_);                               # traducir la cadena a ASCII
   }
}

This script reads each line (packet) of the file, parses the frame number and the payload and parses the payload as well in order to extract the strings separated by '.'. Then it deletes all the strings 'org' and 'skullseclabs' (we are interested just in the hex strings) and converts the hex strings to ASCII.

We execute our script and get the following results:

Observing the packet #49, there is a reference to a file named '/tmp/dnscap.png'.

On the other hand, in the packet #50 we see the magic number of a PNG file (89 50 4E 47 0D 0A 1A 0A):

49
^A:^A<FD><F5>A}%2^@^@^@^T^@^A^@^C/tmp/dnscap.png^@
49
x^\^A<FD><F5>%2A}
50
<BB><CF>^A<FD><F5>%2A<95>^@^@,<ED><80>^A^@^C<89>PNG
^Z
^@^@^@^MI

Contents of packet 50:

50      bbcf01fdf52532419500002ced8001000389504e470d0a1a0a0000000d49.48445200000100000001000804000000f67b60ed0000000467414d410001.86a031e8965f00000002624b474400ff878fccbf00000009704859730000.0b1300000b1301009a9c1800000007.skullseclabs.org

We know that a PNG file is made of chunks and that in one image of this type we must find at least chunks of the following types: one IHDR (49 48 44 52), one or more IDAT (49 44 41 54) and one IEND (49 45 4E 44).

We look for the IEND chunk and find it on packet #339:

339     b8a101fdf551d24195315432313a30343a30302d30383a3030e382804f00.00002574455874646174653a6d6f6469667900323031372d30322d303154.32313a30343a30302d30383a303092df38f30000000049454e44ae426082.skullseclabs.org

So we can conclude that most probably there is a PNG image hidden between packets 50 and 339.


(3) REBUILDING A BINARY FILE

We will try to rebuild a binary file from the extrated hex strings from the payloads of the packets. We modify our script (now 'bsidessf17_dnscap_script2.pl') to dump the extrated hex strings to a file:

#!/usr/bin/perl
#
# bsidessf17 - dnscap
#
# script de volcado de datos de paquetes
#
# fusion_ordenado.txt tiene el formato: <paquete>\t<datos>
#
# Rev.20170212 by sn4fu

use strict;
use warnings;
use 5.016;

my $fichero = 'fusion_ordenado.txt';
open(my $fh,$fichero)
or die "No se ha encontrado el fichero '$fichero' $!";

while (my $linea = <$fh>) {
 chomp $linea;                                      # quitar CR
 my ($paquete, $datos) = split /\t/, $linea;        # parsear usando TAB como separador
 my @cadenas = split /\./, $datos;                  # parsear las partes de la cadena de datos separadas con '.'
 my $cadena_sobrante1 = 'org';                      # las cadenas 'org' no nos interesan
 @cadenas = grep {!/$cadena_sobrante1/} @cadenas;
 my $cadena_sobrante2 = 'skullseclabs';
 @cadenas = grep {!/$cadena_sobrante2/} @cadenas;   # las cadenas 'skullseclabs' no no sinteresan
 foreach (@cadenas)
   {
      print "$_\n";                                 # imprimir los contenidos HEX de interés
   }
}

We execute the script and dump the results to the 'hex.txt' file:

# ./script_dnscap2.pl > hex.txt

Due to the fact that this file contains a string on each line, we process the file to delete all carriage returns:

# tr -d '\r\n' < hex.txt > hex2.txt

And finally we use the 'xxd' utility to convert the hex file to binary:

# xxd -p -r hex2.txt > hex2.bin

(4) FILE CARVING IN THE REBUILT BINARY

We use 'binwalk' to try to extract the PNG file inside our rebuilt binary:

# binwalk -D 'png image:png' hex2.bin

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
664           0x298           PNG image, 256 x 256, 8-bit gray+alpha, non-interlaced

But unfortunately we are not able to display the file because we get some errors, so we decide to use the 'pngcheck' utility to get more information about the extracted PNG:

# pngcheck -v _hex2.bin.extracted/298.png 
File: _hex2.bin.extracted/298.png (19159 bytes)
  chunk IHDR at offset 0x0000c, length 13
    256 x 256 image, 16-bit grayscale+alpha, non-interlaced
  chunk gAMA at offset 0x00025, length 4: 1.0000
  chunk bKGD at offset 0x00035, length 2
    gray = 0x00ff
  chunk pHYs at offset 0x00043, length 9: 2835x2835 pixels/meter (72 dpi)
  invalid chunk name "???" (ffffff8c ffffff89 01 fffffffd)
ERRORS DETECTED in _hex2.bin.extracted/298.png

We see that there is a chunk in the PNG file with an invalid name (8C 89 01 in hex). Further investigation reveals that this string is at the beginning of packet #51 (the second packet containing data of the PNG file).

Then we use 'strings' on the binary and see some interesting data:

console (sirvimes)
Welcome to dnscap! The flag is below, have fun!!
!command (sirvimes)
/...../
/tmp/dnscap.png
IHDR
gAMA
bKGD
        pHYs
tIME
IDATx
HBBH
/...../
%tEXtdate:create
2017-02-0:
1T21:04:00-08:00
%tEXtdate:modify
2017-02-01T21:04:00-08:00
IEND
1T21:04:00-08:00
%tEXtdate:modify
2017-02-01T21:04:00-08:00
IEND
Session killed: The driver requested it be stopped!
console (sirvimes)
Good luck! That was dnscat2 traffic on a flaky connection with lots of re-transmits. Seriously, 
good luck. :)

It looks like someone tried to send a PNG fie using an application called 'dnscat2':

https://github.com/iagox86/dnscat2

Using this application, an attacker can establish a hidden tunnel inside normal DNS traffic. In a real environment, this tool can be used to establish stealth comms with a C&C server or for data exfiltration.

If this is the case, the data of the transmitted PNG file must be in one way only, more precisely within the DNS QUERIES. Considering this hypothesis, we will discard all the DNS RESPONSES.

Just to add more reliability to our hypothesis, we repeat the previous process with the following scenarios, to no avail (we get new corrupted images:

  • CNAME queries only.
  • CNAME queries and responses.

(6) DETAILED ANALYSIS OF THE BINARY FILE

As we saw previously, the chunk with invalid name is in the first portion of packet #51. We look for information about valid chunk numbers and find the following:

http://purepng.readthedocs.io/en/latest/chunk.html

Now we hexedit our binary file to get further details about what's happening:

00000280   61 70 2E 70  6E 67 00 BB  CF 01 FD F5  25 32 41 95  00 00 2C ED  80 01 00 03  89 50 4E 47  0D 0A 1A 0A  00 00 00 0D  49 48 44 52  ap.png......%2A...,......PNG........IHDR
000002A8   00 00 01 00  00 00 01 00  08 04 00 00  00 F6 7B 60  ED 00 00 00  04 67 41 4D  41 00 01 86  A0 31 E8 96  5F 00 00 00  02 62 4B 47  ..............{`.....gAMA....1.._....bKG
000002D0   44 00 FF 87  8F CC BF 00  00 00 09 70  48 59 73 00  00 0B 13 00  00 0B 13 01  00 9A 9C 18  00 00 00 07  8C 89 01 FD  F5 41 95 25  D..........pHYs......................A.%
000002F8   92 CE 22 01  FD F5 25 92  41 95 74 49  4D 45 07 E1  02 02 05 0D  35 24 D3 81  E9 00 00 2C  08 49 44 41  54 78 DA ED  9D 77 9C 1B  .."...%.A.tIME......5$.....,.IDATx...w..

In the ASCII pane, after the valid chunk 'pHYs' we see another valid chunk named 'tIME', but the 'pngcheck' tool reports that between them there is what seems to be an invalid chunk '8C 89 01'. From the beginning of this invalid string to the beginning of the 'tIME' string, there are exactly 9 bytes of data. We see that those are the first 9 bytes of packet 51.

Our second hypothesis is that those first 9 bytes of the packet are garbage, dnscat2 overhead or other kind of data which may not be of interest. So we decide to delete the first 9 bytes of each packet.

On the other hand, if we delete those first 9 bytes on each packet, we rapidly see that there are duplicate packets, which seems consistent with the clue we found using 'strings':

'Good luck! That was dnscat2 traffic on a flaky connection with lots of re-transmits.'


(7) REBUILDING THE BINARY FILE TAKING INTO ACCOUNT THE EVIDENCES FOUND

We will rebuild again the binary file but this time taking into account our new hypothesis from the evidences we just found:

  • Consider DNS queries only.
  • Delete the first 9 bytes on each packet.
  • Delete the duplicated packets.

First, we merge all the DNS queries in a single file:

# cat queries_CNAME.txt queries_MX.txt queries_TXT.txt > fusion.txt

Second, we generate a new file with all the payloads ordered by frame number:

# sort -k1n,3 fusion.txt > fusion_ordenado.txt

We edit this file and delete all packets but [50..339], which are those we know that contain the PNG file.

Then we modify our script (now 'bsidessf17_dnscap_script3.pl') to delete the first 9 bytes on each packet:

#!/usr/bin/perl
#
# bsidessf17 - dnscap
#
# script de volcado de datos de paquetes
# elimina los primeros 9 bytes de cada paquete
#
# fusion_ordenado.txt tiene el formato: <paquete>\t<datos>
#
# Rev.20170212 by sn4fu

use strict;
use warnings;

my $fichero = 'fusion_ordenado.txt';
open(my $fh,$fichero)
or die "No se ha encontrado el fichero '$fichero' $!";

while (my $linea = <$fh>) {
 chomp $linea;                                      # quitar CR
 my ($paquete, $datos) = split /\t/, $linea;        # parsear usando TAB como separador
 my @cadenas = split /\./, $datos;                  # parsear las partes de la cadena de datos separadas con '.'
 my $cadena_sobrante1 = 'org';                      # las cadenas 'org' no nos interesan
 @cadenas = grep {!/$cadena_sobrante1/} @cadenas;
 my $cadena_sobrante2 = 'skullseclabs';
 @cadenas = grep {!/$cadena_sobrante2/} @cadenas;   # las cadenas 'skullseclabs' no no sinteresan
 $cadenas[0] = substr ($cadenas[0], 18);            # a la primera cadena de cada paquete le quitamos los 9 primeros bytes (18 simbolos HEX)
 foreach (@cadenas)
   {
      print "$_\n";                                 # imprimir los contenidos HEX de interés
   }
}

We execute the script and dump the results to the file 'hex.txt':

# ./script_dnscap3.pl > hex.txt

Now we delete the duplicate payloads using 'awk':

# awk '!x[$0]++' hex.txt > hex2.txt

Then, we delete the carriage returns at the end of each line:

# tr -d '\r\n' < hex2.txt > hex3.txt

We rebuild the binary using 'xxd':

# xxd -p -r hex3.txt > hex.bin

And finally we use 'binwalk' to carve the PNG file:

# binwalk -D 'png image:png' hex.bin

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
95            0x5F            PNG image, 256 x 256, 8-bit gray+alpha, non-interlaced
206           0xCE            Zlib compressed data, best compression

We check the resulting PNG with 'pngcheck' and it reports an error regarding some garbage after the IEND chunk (which may be deleted using hexedit):

# pngcheck 5F.png 
5F.png  additional data after IEND chunk
ERROR: 5F.png

However, this time we are able to display the PNG file and get the flag:

FLAG:b91011fc