M0leCon 2020 CTF

Write up Author: Jack “CylentKnight” Lambert

Challenge author: @mr96

Summary

Ok, let's start out with a full disclosure! This CTF kicked my butt! But like any good beat down you take in life, what really matters is what you take away from it. Although I didn’t have a collection of flags at the end I was inspired to write this up because it shows some real important python basics everyone should know as well as why python and wireshark go together REALLY WELL! So let’s get to it.

The Beer

So for this challenge, I braved the dangerous Covid-19 infested world to get myself a local brew from JAK’s Brewing company. The particular beer I chose was a Cranberry Saison and boy did it help fuel me through! Anybody out in the Colorado Springs area should certainly find time, put on a mask and try some JAK’s!

The Challenge

This was one of the warmup challenges and I’d be lying if I said I knocked it out fast! In fact, this took me a couple hours to do all because I thought I knew everything I needed, and didn’t follow my process…

So to start we aren’t given much just a simple blurb “Everyone says that you're a Number Theory Master, can you prove it?” and a netcat command nc challs.m0lecon.it 10000.

Well, I guess there’s not much to do but run the command and see what kind of numeric hell mr96 has in store for us.

Ok, so you can’t tell by the screenshot, but by the time I has halfway through reading the top line, the service rudely called me slow and closed the connection. It left us everything we need though to get started.

We know we need to write a script to solve this; even if you’re a math whiz 1 second isn’t enough time to solve the problem and enter the solution before the timer hits 0. To start we need to build a socket!

Now for those of you that don’t know, a socket, in simple terms, is just an active IP address and port connection. So when you connect to a service, say a website for example, you’re connecting to an IP address and a port number such as 1.2.3.4:80 which is a socket… If you still don’t understand, don’t worry about it. For now if I say socket you “something to connect to”.

There are 2 sides to each socket, a client side and a server side. The client is the IP and port used to initiate the connection where as the server side is the IP and port which is listening for the connection. Since we already know the server is listening (since we got a response to our netcat command) we need to build the client.

Time to memorize!

1    Import socket

2

3    host = ‘challs.m0lecon.it’

4    port = 10000

5

6    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

7    s.connect((host, port))

8    r = s.recv(1024).decode()

9    print(r)

10    s.close()

This is the simple Python tcp socket I use not only for CTF but also to make network capable tools at my job! Let’s break it down a little bit. For starters we need to import the socket module since Python doesn’t load it by default; this is what we see in line 1. The next two lines are the host and port variables. Like all variables, these can be named whatever you’d like, but be sure you use a string for the host and an integer for the port!

The next line is the most complicated and there was a time I knew what it all meant, but it’s really not super important to know the details. What is important on line 6 is that this line is specific to TCP sockets; UDP would be different. In a nutshell, this line prepares your computer to initiate a network connection and saves that socket object to our variable s.

Line 7 is pretty self explanatory in that it takes our host variable and port and attempts to connect to it. It’s important to mention that this is an acceptable way to write this for CTF, but for production code, you’d be better off putting this in inside a try catch. Once this line executes, the three way handshake completes and we are connected!

In this scenario, the server is configured to talk first. We know this because when we completed our nc command from earlier, the server immediately provided us with some content without needing to be prompted with some type of command. So once we connect to the server in line 7 the next thing we should do is get ready to catch some data! s.recv(<buffer size>) is the method used to give the data back to us, but this data is coming to us in a utf-8 format so I personally like to decode it immediately using the .decode() method. .decode() will convert the data into a more console friendly ascii format to include rendering newline characters.

Finally we’re going to print out our data to the screen, and close the connection just for good hygiene. Lets run it and see what happens!

As expected, we got some data! But the observant of you will notice a missing line.. The egregious insult we received earlier. This is missing for two reasons, first as soon as we collected and printed the initial data dump we closed the connection, this happens significantly faster than 1 second. The second reason is because we don’t have a line of code intended to catch another message from the server.

There, now we’ve built our basic socket. It’s time give the server what it wants (this isn’t always the path to the flag in a ctf, but if you’re able to solve it start with that). So since this is just a math question we’re trying to solve, and explaining how I solved it will likely never help you in any way, I’m not going to go over the details but, I will put my script at the bottom for those who want to see it. More importantly lets get back to the client server communications and explain why wireshark solved a major problem for me.

The steps we need to find our answer is first to parse the data given to us by the server and save N to a variable. Next we need to iterate through a and b values until we find a pair that satisfies gcd(a,b) + lcm(a,b) = N. Once we have our a and b values it’s time to send that to the server.

In the next sample, you’ll see that I ran the result through two functions; getNum() and solve(). I’ve kept the functions out of this sample for brevity but assume the final returned value is a string “a b”.

1    Import socket

2

3    host = ‘challs.m0lecon.it’

4    port = 10000

5

6    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

7    s.connect((host, port))

8    r = s.recv(1024).decode()

9    n = getNum(r)

10    a = solve()

11    s.send(a.encode())

12    r = s.recv(1024).decode()

13    print(r)

14    s.close()

Feast your eyes on line 11! We have our solution stored in the variable ‘a’ now it’s time to send it to the server and get our prize! We send the string using s.send(a.encode()). First, the string gets encoded for it’s journey, then it is sent out our socket, s. Let’s run it and get our flag! Easy Money!

What?! WHAT DID YOU JUST SAY TO ME! The script certainly executed in less than a second! There must be something wrong with the math! This is where I found myself in a rabbit hole of chasing down a math ghost (that never existed). Fast forward a couple of frustrating hours when I decide to look at the conversation in wireshark.

There are 2 things to point out here. First on the follow stream window at the top, we can see the plain text conversation where our script sends the answer as expected… Next in the Wireshark window I set the time reference to packet 22 which is the packet where we receive N (and presumably when the 1 second timer begins). We can clearly see that our solution makes it out well before the timer expires… hmmm… let’s see what happens if I do it manually, I have to be quick and I won’t have the right answer but maybe it’ll shed some light on why the server doesn’t like our script.

Note: If you don’t know how to do these things with Wireshark, check out my other write up at www.hackersnhops.com!

In the manual execution using netcat, everything looks the same, except it accepted my answer. Something must be different. Lets look a little bit deeper. If we look at the same windows but with Hex Dump selected as our output…

The top was sent from the script, the bottom sent manually. Do you see it? Look very closely to the data we sent. Ignore the different numbers. If you look on the decoded side, it looks like a ‘.’ if you look at the hex side it looks like 0a.  Here’s a fun fact about Wireshark, when looking at the decoded text you’ll see a great deal of ‘.’s these are placeholder for Non-printable characters.. One such character is 0a, aka the newline character! It’s the flippin’ enter key!

Let’s tweak our script and try again! This time we’re going to add a newline character to our solution before we send it.

1    a = a + ‘\n’

2    s.send(a.encode())

SUCCESS!! After chasing down our what we thought was a math phantom turned out to be make or break all because of a simple ‘\n’... To finish off this challenge and get our reward we simply need to write a loop and catch our flag.

As promised, here is the full code:

import socket

import math

def lcm(a,b):

return abs(a * b) // math.gcd(a, b)

def getNum(r):

i = r.find('N = ')

return int(r[(i + 4):])

def solve(n):

a = n - 1

b = 1

r = ''

while a > b:

       g = math.gcd(a, b)

       l = lcm(a, b)

       if g + l == n:

           r = str(a) + ' ' + str(b)

           break

       a -= 1

       b += 1

return r

host = 'challs.m0lecon.it'

port = 10000

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.connect((host, port))

while True:

r = s.recv(2048).decode()

if r.find('ptm') != -1:

       break

n = getNum(r)

a = solve(n)

a = a + '\n'

s.sendall(a.encode())

print(r)

s.close

Finishing Move

Anyways, to those of you who read this, I hope it helps at least one of you. Feel free to let me know what you think of this write up. I try to not only give the technical details but also provide some of my thoughts during the process. Thanks for reading and as always… Cheers!