Author Archives: nvisoblog

NVISO at Hack Belgium

Last week, a few of us attended the first edition of Hack Belgium. Describing what Hack Belgium is turns out to be a bit difficult: is it a conference? A hackaton? A hands-on workshop? A technology fair? A pitch? In my view, it’s all of those combined – and that made the event so interesting!

2017-05-04 16.55.56.jpg

9AM – Daan, Jeroen, Kris & Kurt looking sharp at Hack Belgium ( … and ready for some ☕️!).

Hack Belgium offers its attendees 14 crucial challenges around topics that keep our world busy: from healthcare to mobility and building intelligent cities. Over the course of 3 days, all attendees put their heads together to come up with a bright solution to these challenges. With the help of more than 300 experts, it’s certainly a great experience and exercise in idea generation! The objective is to pitch your idea on Saturday and to walk out of Hack Belgium with a blueprint to start working on your project.

2017-05-05 12.19.15.jpg

Our CEO Kurt ready to escape reality 🕶.

OK, I have to confess – we did not stay until Saturday for our pitch, however we did have a lot of fun and interesting moments on Thursday and Friday! We worked on a concept for the media to bring news in a more balanced format, we did a workshop on designing connected projects, we attended an expert session on the state of Virtual Reality (VR) and Augmented Reality (AR), and we had fun playing around with IBM Watson and other upcoming technologies.

IMG_1220.JPG

Kris showing off his drawing skills 🐁.

Pulling off this type of event at this scale takes a lot of preparation (and guts!) – we want to congratulate  the Hack Belgium staff and all the volunteers involved! You truly made this into an interesting & worthwhile event.

We hope to be present for Hack Belgium 2018 and beyond (… and we will stay for the pitch, promise 🐀!).

A few more impressions after the break!

IMG_1221.JPG

Proud to say we didn’t lose any fingers playing around with these beasts! 🖐

IMG_1223.JPG

Beep Boop Beep Boop

2017-05-05 15.43.58.jpg

Playing around with creative ways to design connected products during a workshop. My drawing skills are inferior to the one from Kris, so no close-up 😃!

CSCBE Challenge Write-up – Sufbo

The Sufbo challenge was tackled during the Cyber Security Challenge qualifiers and proved to be very difficult to solve. This write-up gives you a possible way of solving it!

Credits

All challenges of the Cyber Security Challenge are created by security professionals from many different organisations. The Sufbo challenge in particular was created by Adriaan Dens, one of our distinguished challenge contributors, from Proximus. Adriaan & Proximus have contributed multiple challenges over the years and they tend to be pretty hard to solve ;).

The challenge

And you thought Assembly was hard to read? Try this!

The solution

The challenge consists out of a heavily obfuscated piece of perl code. We can start by cleaning up the code which improves the readability by a small bit:

print"Flag: ";
chomp($_=<>);
$[=0;
die if y///c!=32;
chomp(@_=<DATA>);
$/=join'',map{chr(ord$_^$=)}split//,pack"H*",shift(@_).shift(@_);

while($,=substr$_,8*$-,8) {
    ($@,$*,$#,$x,$y,$z,$!,$.,$,) = (unpack("N*",$/.$,),0,2**31*(sqrt(5)-1),(1<<32)-1);
    map {
        $!+=$.;
        $!&=$,;
        $y+=((((($z<<4)&$,)+$@)&$,)^(($z+$!)&$,)^((($z>>5)+$*)&$,));
        $y&=$,;
        $z+=((((($y<<4)&$,)+$#)&$,)^(($y+$!)&$,)^((($y>>5)+$x)&$,));
        $z&=$,;
        $"=pack("N*",$y,$z);
        $/=$"x2
    }0..31;
    die if$"ne pack"H*",$_[$-];
    $-++;
}
print "OK\n"

__DATA__
6c594e50630d4f63
7d515d4655525b1d
7872575285c742da
15c670798094a00b
54f08c6b937ed1f2
6810afed7372cd76

This code might still not mean a lot to the average non-perl-speaking-person. Let’s take a look at the same code, but with some inline comments:

print"Flag: "; # Prints "Flag: " to STDIN
chomp($_=<>); # Reads in the input into the variable $_
$[=0; # Changes the starting index of an array to 0 (It's a useless command actually)
die if y///c!=32; # y///c is a Perl golfing idiom that is similar to length($_), so the length of your input has to be a string of length 32.
chomp(@_=<DATA>); # Store the data below (under __DATA__) in the array @_
$/=join'',map{chr(ord$_^$=)}split//,pack"H*",shift(@_).shift(@_); # Shift the first two elements of @_, "unhexify" the strings, split them per character, XOR with $= (default value is 60), and join the characters back in the variable $/.
 #
while($,=substr$_,8*$-,8) { # While there are 8 characters left in the input do:
($@,$*,$#,$x,$y,$z,$!,$.,$,) = (unpack("N*",$/.$,),0,2**31*(sqrt(5)-1),(1<<32)-1); # Convert the variable $/ (unknown) and $, (our input) to unsigned numbers, assign 0 to $!, assign 2**31*(sqrt(5)-1) to $/ and assign (1<<32)-1 to $,.
map { # Use map to loop 32 times (see below)
$!+=$.; # Add $. to $!
$!&=$,; # Bitwise AND $! with $,
$y+=((((($z<<4)&$,)+$@)&$,)^(($z+$!)&$,)^((($z>>5)+$*)&$,)); # Some bitwise operations added to $y
$y&=$,; # Bitwise AND $y with $,
$z+=((((($y<<4)&$,)+$#)&$,)^(($y+$!)&$,)^((($y>>5)+$x)&$,)); # Some bitwise operations added to $z
$z&=$,; # Bitwise AND $z with $,
$"=pack("N*",$y,$z); # Convert the unsigned numbers back to string representation
$/=$"x2 # Set $/ to two times $"
}0..31; # Use map to loop 32 times
die if$"ne pack"H*",$_[$-]; # Die if $" is not equal to the "unhexified" element ($- contains the index) in @_
$-++; # Increase the variable $-
} #
print "OK\n" # Printed if you have the key
 #
__DATA__ # Starting the DATA block (kinda like a here document)
6c594e50630d4f63 # This part was used for $/ in line 6.
7d515d4655525b1d # This part was used for $/ in line 6.
7872575285c742da # This part was used to compare with the input on line 20
15c670798094a00b # This part was used to compare with the input on line 20
54f08c6b937ed1f2 # This part was used to compare with the input on line 20
6810afed7372cd76 # This part was used to compare with the input on line 20

So now we more or less know what each line does but we still miss context on a higher level (what it is doing). As always in reverse engineering, you try to find some “known parts” which allow you to understand the code a lot faster. These parts are usually strings, metadata, fixed numbers or familiar code blocks.

In our case, we have 2 fixed numbers: 2**31*(sqrt(5)-1) and (1<<32)-1. In this representation they don’t mean much but if we convert them to hex numbers we get 0x9e3779b9 and 0xffffffff respectively.

Let’s see if our old friend Google knows more about this.

Screen Shot 2017-04-04 at 09.55.02

Hmm, interesting! Seems like we’ve got a Perl implementation of Tiny Encryption Algorithm (TEA) on our hands here!

More specifically, the while loop block in the code is the actual TEA implementation, which decrypts the second half of the __DATA__ section using the first half as the key.

Retrieving the key can be done using the following perl one-liner:

perl -E 'say join"",map{chr(ord$_^$=)}split//,pack"H*","6c594e50630d4f637d515d4655525b1d"

Which yields us “Perl_1s_Amazing!” as the key.

So now we have they key and the data to be decrypted. Let’s be lazy and copy the reference code listed on the wikipedia page we found earlier.

#include&lt;stdint.h&gt;
#include&lt;stdio.h&gt;

void decrypt (uint32_t* v, uint32_t* k) {
    uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i; /* set up */
    uint32_t delta=0x9e3779b9; /* a key schedule constant */
    uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
    for (i=0; i&lt;32; i++) { /* basic cycle start */
        v1 -= ((v0&lt;&lt;4) + k2) ^ (v0 + sum) ^ ((v0&gt;&gt;5) + k3);
        v0 -= ((v1&lt;&lt;4) + k0) ^ (v1 + sum) ^ ((v1&gt;&gt;5) + k1);
        sum -= delta;
    } /* end cycle */
    v[0]=v0; v[1]=v1;
    printf("%x%x", v0, v1);
}

void main() {
    /* Our cipher chunks, found in the __DATA__ block of the Perl code */
    uint32_t c0[] = { 0x78725752, 0x85c742da };
    uint32_t c1[] = { 0x15c67079, 0x8094a00b };
    uint32_t c2[] = { 0x54f08c6b, 0x937ed1f2 };
    uint32_t c3[] = { 0x6810afed, 0x7372cd76 };

    /* The used keys for encrypting */
    uint32_t k[] = { 0x5065726c, 0x5f31735f, 0x416d617a, 0x696e6721 }; /* Original key: Perl_1s_Amazing! */
    uint32_t k0[] = { 0x78725752, 0x85c742da, 0x78725752, 0x85c742da }; /* c0 . c0 */
    uint32_t k1[] = { 0x15c67079, 0x8094a00b, 0x15c67079, 0x8094a00b }; /* c1 . c1 */
    uint32_t k2[] = { 0x54f08c6b, 0x937ed1f2, 0x54f08c6b, 0x937ed1f2 }; /* c2 . c2 */

    /* Decrypting the chunks */
    decrypt(c0,k);
    decrypt(c1,k0);
    decrypt(c2,k1);
    decrypt(c3,k2);
}
$ gcc --std=c99 solution.c
$ ./a.out
43534342457b5065726c31735772317465306e6365526534646e33766552727d
$ ./a.out | perl -nle 'print pack("H*", $_)'
&gt;&gt;&gt; CSCBE{Perl1sWr1te0nceRe4dn3veRr}

There we go! CSCBE{Perl1sWr1te0nceRe4dn3veRr} was the flag.

Testimonial of Stefaan Truijen

Hi, I’m Stefaan Truijen and in 2014-2015 I did my master thesis at the department of computer science at KULeuven. I assessed the susceptibility of modern web browsers to RAM scrapers in collaboration with NVISO. Security had always been one of my passions, so I was excited to get started.

Writing a thesis is an intensive process. Happily, I was able to rely on both Arne (NVISO) and Raoul (KULeuven) throughout the entire year for advice/brainstorming.

First, I needed to get an overview of prior research on memory scraping. Arne supplied me with a couple of initial research documents and references, and I reviewed any new material I found with Arne and Raoul almost weekly.

After some preliminary tests, I had to determine how I would continue and I wanted to contribute at least a little bit to fighting memory scrapers. I was able to bounce a few ideas off Arne and Raoul. In the end we decided that, since I was unable to find any prior research that had already assessed the size of the problem – i.e. memory scraping web browsers – measuring the degree of susceptibility of each of the three most commonly used web browsers (Chrome, Firefox, IE) was the most interesting angle.

In order to get a sufficient amount of data to form a solid conclusion, I ran thousands of experiments. Of course, running thousands of experiments manually is not very efficient and it affects reproducibility of the results. Therefore I learned how to work with new tools. Most relevant were Selenium’s automated testing framework for web browsers and the Windows API. Whenever I had questions, Arne and Raoul gladly answered them.

Now that the dust has settled, I can say that I have acquired a deeper understanding of low level security, more specifically memory scraping, and the consequences of relatively relaxed memory and API access policies that I did not have before. I am very satisfied with the result of my thesis and NVISO played an important role in realizing it!

Testimonial of Nick Van Haver

Hi, I’m Nick Van Haver and I want to reflect briefly on my master thesis which I have worked out in cooperation with NVISO and the Ghent University. NVISO helped me in many ways while providing me with a lot of freedom to choose the course of my thesis. They showed me a lot of trust and respect, which I truly appreciate.

The topic of my thesis research was “The Detection of Client-side Vulnerabilities in Web Applications through the Browser”. This topic is deeply rooted in the field of web application security, and thus lead me far beyond its basics. At first I had quite some experience with the development of web applications, but far less with relation to their security aspects.

When looking into a new field or topic, it is hard to find the right sources and high quality references. The right resources can turn a week’s worth of work into a single day. NVISO provided me with these resources and handed me tools, enabling me to educate myself in the web application security field and to make the most out of my thesis. Thanks to NVISO, I had contact with some of the big names in the industry such as Google, Minded Security, Portswigger and many others. Furthermore they assisted me with their expertise in security during meetings.

In the end, my research resulted in a fairly high score of 16 out of 20. Because of these grades I graduated magna cum laude as a Civil Engineer in Computer Sciences. At the beginning of my thesis my knowledge on web application security was rather limited. Now I feel accomplished in this field of security and I now know where to find the most correct information when dealing with web application vulnerabilities. I now also feel more confident when contacting external parties.

I can highly recommend working with NVISO. Choosing to work together with them for your master thesis ensures you that the topic will be both challenging and interesting. You will receive the support and resources you need to achieve your goal. It really is a worthwhile experience! Once the results of my thesis are public, they will be shared with the community!

Cyber Security Challenge Belgium 2015 – Solving the NVISO Lottery challenge

This is the fourth and final blog post in the Cyber Security Challenge Belgium 2015 (CSCBE) solutions series. This time, we’re taking a look at one of the more programming oriented challenges: The NVISO Lottery.

The NVISO Lottery

The students were given the following info:

Come and throw away your money at the NViso Lottery!

They also received the IP address for the NVISO Lottery service.

Gathering information

Once again we take out our trusty pocket knife named netcat.
We have to guess the correct number from a set of 1000 possibilities. If we guess the right number, we get $75, but each guess costs us $10. If we want to win the prize, we have to earn $1337. This means we have to guess correctly at least 20 times without making too many mistakes. Let’s try!
We weren’t able to guess the correct number. We do get an ID, which we can use to get feedback from the NVISO casino. The ID looks completely random, but the last character (=) is a typical tell-tale for Base64 encoding. The equals sign is used as extra padding when the amount of bytes to encode is not dividable by 8. Decoding this using the Base64 algorithm gives the following:
The decoded string doesn’t give us the answer to the random number, but the content does appear to be structured and further decoding may be necessary.
As was explained at the beginning of this write up, this challenge is programming oriented. If you’ve worked with the Python language a lot, you may already recognize the decoded string as being a specific python file format.
In Python, you can use the Pickle module to serialize data objects. Serializing (or marshaling) objects is the processes of converting arbitrary data to a byte stream. This byte stream can then safely be transported over a network, or stored in a file.
Serializing is a reversible process. That means we can deserialize (or ‘unpickle’) the data we got from the Base64 decode:
 This is very promising. The unpickled value consists of a nested list with three random numbers.

Random number generators

Lets take a look at how random numbers are usually generated. Algorithms can not generate truly random numbers. An algorithm will always perform the exact same steps given the same input. Many software implementations therefore rely on Pseudo-Random Number Generators (PRNGs). These algorithms do not generate true random numbers, but they do share many properties with true random numbers. For example, a good PRNG will make it extremely difficult to determine the next random number based on the random number that was just received.
An example of a PRNG is a Linear Congruential Generator (LCG). The most simple LCG needs three numbers to calculate a random number sequence. These three numbers are called the seed of the LCG. Given these numbers (a, c, m), the LCG will calculate the sequence as follows:
The next number in the sequence is calculated by multiplying the current number by a, adding the result to c and taking the remainder of division by m.
From a programming point of view, PRNGs are very useful as they can be reverted to a certain state. If the application suddenly crashes based on a specific random input, it would be very hard to debug the application if the same random input can not be generated. For security critical implementations, of course, a PRNG should not be used.
Since we have to guess a random number, it may be a good guess to say that the decoded value is the seed for a PRNG.

Exploiting the vulnerability

Python allows the programmer to set the state of the random number generator. To confirm we’re on the right track, let’s print out the current state of the default random number generator:
Unfortunately, this seed appears to be a lot bigger than the seed we recovered from the lottery service. Python’s random module actually uses the Mersene Twister algorithm, which is not an LCG,
But there is good news, the output of the getstate() command is very similar to our decoded value. Python has a few other random libraries: random.SystemRandom() and random.WichmannHill(). According to the documentation, SystemRandom() doesn’t have a getstate() method. WichmannHill() does:
This is exactly what we were looking for. By using setstate() with our decoded lottery ID, we should be able to predict the number that will be generated:
Great! That was the solution we were looking for. Because we get the ticket ID before we have to enter our guess, we can predict the value that the server will expect and get our prize!
We could do this manually since there’s no timeout for our answer, but we can just as easily create a python script that does this for us:
We got the flag, which is “I’m_going_to_be_a_professional_gambler!

Statistics

We had many different connections to the server, so a lot of teams tried to solve the challenge. Most teams told us they managed to decipher the Base64 encoding, and some teams also found the Python pickle format. In the end, only four teams were able to completely solve this challenge: HacknamStyle Jr, ISW, Turla Tech Support and Vrije Universiteit Leuven. All of these teams made it to the finals.

Final thoughts

This challenge was partly aimed at testing the student’s programming skills. Although Python is a very popular programming language, some students may have never used it before, making this challenge a little bit harder. Even so, a security researcher will often encounter unknown file formats or protocols, and finding out what the data means or how to use it may be critical to a successful security audit or forensics investigation. Being able to automate custom tasks can often save lots of time or solve problems that would be impossible to do manually. Having some experience with any programming language is an invaluable tool in every security expert’s toolkit!

Cyber Security Challenge Belgium 2015 – Solving the One Way challenge

This is the third blog post in the Cyber Security Challenge Belgium 2015 (CSCBE) solutions series. This time, we’re taking on a very technical challenge: One Way.

Data Extraction

The challenge

The following challenge description was given to the students:

We want our employees to be able to send us confidential information which only we can decrypt. Since we don’t believe in PKI (we have our reasons!), we made our own crypto system (homemade is always better, right!). To prevent tampering, we took some precautions: A salt is added to each request and the IV is chosen at random for every connection. Take a look at the given clientFramework.py file for more info on how to use our crypto system.

The accompanying clientFramework.py file contains some helper methods so that the students could focus on the actually encryption logic instead of fighting with python to be able to correctly communicate with the server.

The details

The python file contains some information about the server, from which the following information can be deduced:

  • The Initiation Vector (IV) is chosen at random for every session
  • The IV is updated after every encryption request according to a known algorithm
  • The server encrypts the given plain text as follows: encryption = encrypt(plain text + FLAG, IV)
  • The encryption protocol is AES in CBC mode with blocks of 16 characters
  • The FLAG consists of 8 lowercase ASCII characters
  • The used IV is returned together with the encrypted string

The IV is randomly chosen at the start of the session, but the client can request multiple encryption operations during each session. After each encryption, the IV is updated according to a known function. That means that we can calculate the IV that will be used for the next iteration. This will prove to be very important in what follows.

Encryption 101

Let’s take a look at how the Cipher Block Chaining (CBC) algorithm works, which is what the challenge is using.
The following image shows the working of CBC:
Image taken from Wikipedia
The plaintext is split up into blocks of 16 bytes each and each block is encrypted separately. In order to counter certain attacks which are possible against the Electronic CodeBook algorithm (ECB), each plaintext is first XOR’ed with the ciphertext of the previous block. Because the first block doesn’t have a previous block which it can use to XOR with, an IV is used. The IV should always be random and unpredictable. 
After the plaintext has been encrypted, the IV has served its purpose and it no longer has to be secret. In this challenge, the IV is returned to the client together with the encrypted text.

Finding the flaw

You may have already noticed a small but very important mismatch between how CBC should be implemented, and how the challenge server implements CBC: the IV should always be random and unpredictable. The server’s IV is completely random and unpredictable, but only for the first encryption request. For every subsequent request, the IV can be calculated from the original IV, which creates a serious security flaw.
Take another look at the CBC diagram. By knowing which IV will be used to XOR with the plaintext, we can prevent the IV from having effect. If we XOR the plaintext with the predicted IV before sending it to the server, the server will apply the XOR again which undoes our original XOR:
plaintext \oplus IV \oplus IV = plaintext \oplus (IV \oplus IV) = plaintext \oplus 0 = plaintext.
The second flaw is that the flag is appended to the given plaintext. Since we have full control over the plaintext, we can decide at which position in the plaintext the flag will be, and hence we can control where it will end up in the encrypted string.

Exploiting the flaw

If we have complete control over which plaintext is entered into the first encrypted block, we can get the encrypted value of any given plaintext. This means we can create a rainbow table for every possible plaintext consisting of 16 bytes:
aaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaab
aaaaaaaaaaaaaaac
//…
zzzzzzzzzzzzzzzx
zzzzzzzzzzzzzzzy
zzzzzzzzzzzzzzzz
Remember that we have to XOR the plaintext string with the predicted IV before sending it to the server.
Before encrypting the plaintext, the server appends the flag to our input. If we only send 15 characters to the server, the server will encrypt aaaaaaaaaaaaaaaX where X is the first character of the flag. 
We can now look up the encrypted value of aaaaaaaaaaaaaaaX in our rainbow table. This will match to 
aaaaaaaaaaaaaaas and we now know that the first character of the flag is an ‘s’. 
To get the second character, we need to create a rainbow table based on the aaaaaaaaaaaaaas prefix (which has 14 a’s). When the table is complete, we can ask the server to encrypt “aaaaaaaaaaaaaas”. The encrypted string will contain the second character of the flag in the last position and we can look it up in our rainbow table. The encrypted string will match to aaaaaaaaaaaaaasa, so ‘a’ is the next character of the flag. We can keep doing this for every character:
aaaaaaaaaaaaaaas
aaaaaaaaaaaaaasa
aaaaaaaaaaaaasal
aaaaaaaaaaaasalt
aaaaaaaaaaasaltm
aaaaaaaaaasaltmi
aaaaaaaaasaltmin
aaaaaaaasaltmine
aaaaaaasaltmine0
aaaaaasaltmine00
aaaaasaltmine000
aaaasaltmine0000
aaasaltmine00000
aasaltmine000000
asaltmine0000000
saltmine00000000
After a few iterations, the padding zeros start showing up in the solution. These extra zeros after the flag are just padding that was added by the server in order to have a complete block to encrypt. When we’ve removed all the prefixed a’s, we end up with the flag, which is saltmine.

Padding attack

The attack we used above is a form of padding oracle attack. This attack is possible because of two distinct vulnerabilities in the server algorithm: We can predict the IV, and we can modify the padding in front of the flag. By combining these two flaws, we are able to get the flag, which would have been impossible without either of them.
In November 2014, the POODLE attack was discovered, which uses a padding oracle attack against SSL3.0.

Statistics

Nine of the participating teams were able to solve this challenge. Eight of these teams were able to secure a place in the CSCBE finals. There were a lot of random guesses for the solution of this challenge. Some even came close (“saltflag” or “salted00”) but luckily, only the teams who actually solved the challenge were able to get the points.

Final thoughts

A strong cryptographic algorithm is only effective when it is used correctly. The challenge demonstrated that small flaws can a have disastrous effects. Although cryptography can be very daunting at first, it certainly pays off to invest some time in to understanding how different algorithms work and how they should be used. Even if you don’t fully understand the internal workings of the AES encryption method, you may still be able to find flaws in the way it is used and thereby be able to break the encryption.

Cyber Security Challenge Belgium 2015 – Solving the Data Extraction challenge

This is the second blog post in the Cyber Security Challenge Belgium 2015 (CSCBE) solutions series. This time, we’re taking a look at the Data Extraction challenge.

Data Extraction

The challenge

The following challenge description was given to the students:

We messed up and contacted the wrong forensic department. They say they found data, but we can’t really make anything out of it. Can you?

The students were also given the following image:

The challenge was designed to test the students’ out of the box thinking capabilities, as well as their ability to research a certain subject.

Analyzing the image

Steganography is the art of hiding information inside another file. This can take on many different forms. Images are often used as a container for other kinds of information and the possibilities are endless. Steganography challenges are often part of a CTF and of course, we had to create one too. These challenges are usually one of the more feared challenges, as the number of possible approaches is literally endless.

There are two major categories for hiding information inside an image. Either you modify the internals of the image to add some extra data, or you visually encrypt your information and just add it to the image where everyone can see it. Because the image appears to contain a certain pattern, it is most likely that the second approach was taken.

Four different kind of shapes were used to create the pattern and every shape has its own color. If you’ve payed attention in your high school biology class, you may recognize the shapes. When you learned about the birds and the bees, you should have also learned about deoxyribonucleic acid, which is just a fancy way of saying DNA.

Research

DNA consists of two biopolymer strands coiled around each other, forming the well known double helix:

Image taken from classroom.synonym.com

Each strand consists of many different nucleotides which lock together in a certain way. There are two base pairs: Adenine (A) matches with Thymine (T) and Guanine (G) matches with Cytosine (C).

Image taken from thinglink.com

The colors of the different nucleotides may be different depending on which textbook you use, but the shapes are usually the same. That means we can convert our image into a string of letters consisting of A, T, G and C using a little bit of python:

This is already looking good, but we can’t really see anything that resembles a flag.

Digging a little deeper

 If we do some more research, we can find that different nucleotides are combined into amino acids when they are processed by your cells. Each triplet is combined into one of the amino acids according to the following diagram:

Image taken from commons.wikimedia.org
For example, AAA will be transformed into Lysine (K) and UCA will be transformed into Serine (S). 
We could write a python script that translates these nucleotides into amino acids, but it’s even easier to let the internet do it for us. The Bioinformatics Resource Portal hosts a tool that will do the hard work for us. If we enter our nucleotide string into the tool, we get the following:

This tool also automatically solves the last hurdle of the challenge: If you start combining from the first nucleotide, you only get a bunch of random letters. However, if you start combining from the second nucleotide, you can read “THE PASS IS METAPHYSIC LIGHTYEARS”, which is the solution to this challenge.

Statistics

At the end of the qualifiers, only five teams were able to solve this challenge. Apparently many teams recognized the image as being a DNA sequence and they successfully transformed and combined the nucleotides into amino acids. Only a handful of teams got past the last hurdle of starting with the correct nucleotide. 

Final thoughts

Not a lot of teams managed to solve this challenge. As steganography can be done in a million ways, it’s not always easy to see a path towards the solution. This can discourage teams and have them invest their time in more practical challenges that have a more well defined scope. Choosing which challenges you invest your time in is an important decision in any CTF and apparently most teams did not prefer to invest their time in this one.