Published on

Insomni'hack 2022 CTF write-up

14 min read
Insomni'hack 2022 CTF write-up Post Banner
Table of Contents

The Insomni'hack 2022 CTF is a CTF hosted during the Insomni'hack conference in Geneva, Switzerland. You had to register yourself so that you can attend to the on-site CTF. There was a total of 31 challenges. As a beginner in CTFs I decided to mostly take the easy challenges. The CTF was from March 25 (6pm UTC) to March 26 (4am UTC) 2022. As my first on-site CTF it was nice to see how many people were participating. It was also pretty amazing to see how some people were prepared; some people even decided to transport their own big monitor(s) 🤯 It was an overall amazing CTF, so many thanks to the organizers! All flags had the same format which was INS{...}. Anyways, enough talking, here are the write-ups:

GDBug - 120 points

We were given a binary file (Download the file) that we had to execute to get the right flag. Let's start by running it with basic ./gdbug: When executing the file, a serial number to check needs to be given as an argument. After providing one as argument it would check the serial and return if it was a valid one or not. So let's open up the binary in a disassembler and see how it works.

There also was another, invalid, flag in the strings
There also was another, invalid, flag in the strings

Looking at the top there was --debug, after running the binary with that serial it wasn't a success; INS{Th1$Fl4gSuck$}. There also was a call to ptrace(), typical anti-debugging trick. If the binary was to be run from any debugger it would give this flag INS{W0ULDNT-1T-B3-T00-34SY}. Simply NOPing the entire ptrace check was enough to bypass it, now the binary can be ran in a debugger if needed. So let's look at how the check for the serial works:

A loop that will iterate over the length of the given serial
A loop that will iterate over the length of the given serial

At first there is a variable x initialized with the value 0x539, then a new variable i initialized with the value 0x0. After that, a loop starts:

  • It loops forever until i has reached the length of the serial, so it goes over all characters in the serial
  • The hexadecimal value of the character is then added to the x value
  • i is incremented by one

gdb also confirmed that it was the hexadecimal value of each character that was added to x:

The binary was ran with 'AAAAAAA' as serial, 0x41 is the hexadecimal value of A
The binary was ran with 'AAAAAAA' as serial, 0x41 is the hexadecimal value of A

At the end of that loop there were more checks as you can see from this:

This is the last assembly picture for this challenge, no worries..
This is the last assembly picture for this challenge, no worries..

It will be checked if x is equals 0xb38, so the sum of all hexadecimal values of all characters in the serial must result to 0xb38. After this check, there are 5 more checks that are really easy to understand:

  • The length of the serial must be 0x18
  • At index 0x4, 0x9, 0xe and 0x13 of the serial, there should be a character with hexadecimal value 0x2d, which is the hexadecimal value of -.

A valid serial pattern would look like xxxx-xxxx-xxxx-xxxx-xxxx. Based on these restrictions, we could simply create a bruteforce tool in Python:

from string import ascii_uppercase
import itertools
 
def iter():
    for s in itertools.product(ascii_uppercase, repeat=24):
        s = list(s)
        s[4] = "-"
        s[9] = "-"
        s[14] = "-"
        s[19] = "-"
        yield "".join(s)
 
def solve():
    while True:
        for serial in iter():
            x = 0x539 # Setup the variables
            i = 0
            while (True):
                if (i >= len(serial)): # Go over the length of the string
                    break
                x += ord(serial[i]) # Append the hexadecimal value of the current character to 'x'
                i += 1
            print(f"Trying {serial}", end="\r")
            if (hex(0xb38)==hex(x)): # Check if the serial is a valid serial or not
                print(f"Found: INS{{{serial}}}")
                return # Remove this to see all possible flags
 
solve()

The output was quite interesting...

Found: INS{AAAA-AAAA-AAAA-AAAA-AFZZ}
Found: INS{AAAA-AAAA-AAAA-AAAA-AGYZ}
Found: INS{AAAA-AAAA-AAAA-AAAA-AGZY}
Found: INS{AAAA-AAAA-AAAA-AAAA-AHXZ}
Found: INS{AAAA-AAAA-AAAA-AAAA-AHYY}
Found: INS{AAAA-AAAA-AAAA-AAAA-AHZX}
...

The reason was quite simple, all of them were valid flags!

According to another player, the number is accurate
According to another player, the number is accurate

Bot Telegram - 75 points

This challenge was my personal favorite. We were given a Telegram bot to chat with, and we had as challenge to exploit it. So at first I tried to execute some of the commands it has. None of them were really surprising when being executed. Then I tried with some random arguments after the command, and boom! When executing the command /challs leet the bot returned

Oops an error occured : (1054, "Unknown column 'leet' in where clause")

The challenge is about an SQL injection. After trying to get the list of tables to perform an UNION attack the bot said that whitespaces were not allowed, only one argument was allowed. Fortunately there is an easy bypass, which is to replace all whitespaces with /**/ (comments), it will then be interpreted the same as a whitespace. The first discovery was that some fields are too long to be sent along in some fields, so using SUBSTR()did the trick. The bot ended up leaking theusers table.

I was unable to fit everything in one single picture...
I was unable to fit everything in one single picture...

Then we can leak the columns inside that table, there was the column username and password:

There were only 2 columns that could've been guessed
There were only 2 columns that could've been guessed

So now we can simply leak the credentials for the existing row(s). There was 3 users, one of them had the username admin and the password, the flag, INS{C0ngr@tz_YoU_d3$eRvE_T3iS_Fl@g}.

woohoo!
woohoo!

Also, dear staff members, sorry for bullying the bot :(

They sent these messages exactly when the challenge was solved
They sent these messages exactly when the challenge was solved

Wordle - 9 points

This challenge was based on the popular worlde game where you give a word of 5 characters and then you know if a character is in the word to guess, at the correct place or not in it at all. We were given a netcat server that was listening on port 1337 and we had to exploit the game. We also had a binary file that did exactly the same, so that we can debug it (Download the file). My first try was to actually spam the execution of the wordle binary and get the right word at some point since we were given an output that said what the correct word was. Even when getting the right word, it gave the error message. So I opened the file in a disassembler. The assembly code for the logic was very easy:

Some easy to understand Assembly code
Some easy to understand Assembly code

The program moves to the address 0xcb1 if the value in data_2020e0 is equals 0xdeadbeef. It will then call the system() function with a randomly taken string from the possible words. One of these words was /bin/sh, so it was clear there was shell access at the end.

So let's put deadbeef as user input? Well, the user input is stored in data_2020c0, so that won't work. However, the user input is parsed with the unsafe gets() method, which means it is possible to overflow that value. There is a difference of 32 bytes between both addresses. So a simple overflow of 32 bytes can let me put anything inside data_2020e0. The payload for that overflow would be the following:

(python2 -c 'print "A"*32 + "\xef\xbe\xad\xde"'; cat) | nc wordle.insomnihack.ch 1337

Since there was not only /bin/sh in the list of words, the program had to be executed multiple times. In the end the flag was INS{C0ngr@tulat1on5!_th3_word_was_d3adBeef!}.

Weak Rivest 4 - 7 points

Weak Rivest 4 reminded me of the RC4 cipher. The cipher is using a keystream and is, of course, considered as insecure. We were given 3 strings, 1 plaintext and 2 ciphertexts:

pt1 = I_do_not_like_ponies
ct1 = D058DECB037A10916E9D8B0E5345BB381AE8D8EE
ct2 = D049E9DF182420AB5EC6BD101229940517B59CE0

All the strings had the same length, we simply had to convert the pt1 to its hexadecimal representation 495F646F5F6E6F745F6C696B655F706F6E696573 to also get a length of 40. We had one plaintext, which was encrypted with a key and resulted to one of the cipher text. The flag was most likely the second plaintext (pt2), which is unknown.

To solve the challenge there is the reused key attack on stream ciphers, such as RC4. To explain is shortly, XORing both cipher texts will remove the key, which then results to the same when XORing the both plaintexts. So for the challenge:

ct1⊕ct2ct1 \oplus ct2 = pt1⊕pt2pt1 \oplus pt2

So to get pt2 we simply need to:

  • Take every time 2 characters from both ct1 and ct2
  • Convert them to a hexadecimal number
  • XOR them together to get a result
  • Get 2 characters from pt1
  • Find a hexadecimal number that, if XORed with the 2 characters taken from pt1, leads to the same result
  • Append the character corresponding to that value to a flag variable.

I ended up making the following Python script:

import time
 
pt1 = "495F646F5F6E6F745F6C696B655F706F6E696573"
ct1 = "D058DECB037A10916E9D8B0E5345BB381AE8D8EE"
ct2 = "D049E9DF182420AB5EC6BD101229940517B59CE0"
 
flag = ""
 
for i in range(0, len(ct1) - 1, 2):
    ct1_pair = int("0x" + ct1[i:i+2], 16) # Get two characters from ct1
    ct2_pair = int("0x" + ct2[i:i+2], 16) # Get two characters from ct2
    result = ct1_pair ^ ct2_pair
    for k in range (0x0, 0x7f): # Loop over all characters in the ASCII table
        if (int("0x" + pt1[i:i+2], 16) ^ k) == result: # Check if the XORed number is the same as the result
            flag += chr(k)
            print(flag, end="\r")
            time.sleep(0.5) # That is just to look cool 😎
            break

This resulted in the following flag: INS{D0_No7_u$3_Rc4!}

Magic Words - 7 points

For this challenge we were given a link to a website. The website asked for a sentence, so basically we had to get this sentence. When looking at the source code there was some checks, so the first thing to do was to create the same file on the local machine. Then simply take each check one by one and get the words in each paragraph. Then recreate the checks locally and execute the script.

// More code above...
 
// This is the needed code
p1 =
  "Later, on my walk, I wondered why I felt I had to be suspicious of ‘normality’. The striking thing about the normal is that there is nothing normal about it: normality is the gentrification of ordinary madness – ask an Surrealist. In analysis ‘the normal child’ is often synonymous with the obedient good child, the one who only wants to please parents and develops what Winnicott called ‘a false self’";
p2 =
  "In the hospital men’s room, as I’m washing my hands, I glance in the mirror. The man I see is not so much me as my father. When did he show up? There is no soap; I rub hand sanitizer into my face–it burns. I nearly drown myself in the sink trying to rinse it off.";
p3 =
  "Your only chance of survival, if you are sincerely smitten, lies in hiding this fact from the woman you love, of feigning a casual detachment under all circumstances. What sadness there is in this simple observation! What an accusation against man! However, it had never occurred to me to contest this law, nor to imagine disobeying it: love makes you weak, and the weaker of the two is oppressed, tortured and finally killed by the other, who in his or her turn oppresses, tortures and kills without having evil intentions, without even getting pleasure from it, with complete indifference; that’s what men, normally, call love.";
p4 =
  "Looking back on those incidents, he was always appalled by the memory of his passivity, hard though it was to see what else he could have done. He could have refused to pay for such gravy damage to his room, could have refused to change his shoes, could have refused to kneel to supplicate for his B.A.";
p5 =
  "The way he went after that plump sister in the lace tucker, was an outrage on the credulity of human nature. Knocking down the fire-irons, tumbling over the chairs, bumping against the piano, smothering himself among the curtains, wherever she went, there went he. He always knew where the plump sister was. He wouldn’t catch anybody else. If you had fallen up against him (as some of them did), on purpose, he would have made a feint of endeavouring to seize you, which would have been an affront to your understanding, and would instantly have sidled off in the direction of the plump sister. She often cried out that it wasn’t fair; and it really was not.";
 
var response = "";
 
var words = p1.split(" ");
for (var i = 0; i < words.length; i += 1) {
  if (words[i] == "please") response += words[i];
}
 
var words = p2.split(" ");
for (var i = 0; i < words.length; i += 1) {
  if (beautify(words[i]) == "siqz") response += " " + words[i];
}
 
var words = p3.split(" ");
for (var i = 0; i < words.length; i += 1) {
  if (uglify(words[i]) == "MDA=") response += " " + words[i];
}
 
var words = p4.split(" ");
for (var i = 0; i < words.length; i += 1) {
  if (digitalize(words[i]) == "746865") response += " " + words[i];
}
 
var words = p5.split(" ");
for (var i = 0; i < words.length; i += 1) {
  if (mystify(words[i]) == "c83b72dd001482ce10f0b106c7a0ed0e")
    response += " " + words[i];
}
 
console.log(response);

Then the result was please show of if in of is in an it to me to to of is by in or the way. There was just to find a good word that would fit in the sentence for pleas show [of if in of is in an it to me to to of is by in or] the way, the best is definitely please show me the way.

When submitting that, the flag (INS{jQu3ry_1$_Fr3@kiNg_C0oL}) was given as response.

Welcome challenge - 6 points

We get a link to a GitLab repository where there are instructions to follow to actually solve the challenge which included commiting changes. After folowing the steps the first time my shell looked kind of, weird:

You are infected 🧟 $

But I wasn't the only one who got this joke:

The staff was building a botnet, no worries
The staff was building a botnet, no worries

After checking where this came from, it was located in inso/.circleci/pre-commit, there also was a exec $SHELL command at the end of the file, to make sure you see that. By simply removing the lines that changed my PS1 variable and removing the exec $SHELL command, I was able to get the flag in return. The flag was given with a response to a failed commit like the following: remote: INS{S0_F4r_S0_g00d}.