Good Morning/Afternoon/Evening CTFers! I’m 5h1kh4r and I stood 1st in the UrchinSec Aware CTF 2024!!!

TOP 10 Scoreboard

These are the top 10, Cheers to them too! From this post onwards I will try to cover challenges of each category in each post. This post is about the Cryptography Category. These are the challenges in the Cryptography category:

crypto challs

  1. ABC Phonics (100)

  2. Box (100)

  3. Destination (100)

  4. Shifty Business (100)

  5. Ugomvi (100)

  6. Tr3ppl3 Stuffs (300)

  7. WarmUp (300)

  8. ABC Phonics:

Description:

Do you know your ABCs?

01183360092207851206169694241655940616

Flag Format : urchinsec{ANSWER}

author : @tahaafarooq

Solution:

This challenge is related to some cipher which we are supposed to find out. I put this text into cipher-identifier at dcode.fr website (I will be using this website alot in this whole writeup and maybe other ones also)

Decrypt a Message - Cipher Identifier - Online Code Recognizer *Tool to identify/recognize the type of encryption/encoding applied to a message (more 200 ciphers/codes are…*www.dcode.frhttps://www.dcode.fr/cipher-identifier")[](https://www.dcode.fr/cipher-identifier)

I see a long list of ciphers with which this text could be encoded, I try everyone and at last Homophonic Cipher works!

You put that answer into the flag format and that’s your flag!

urchinsec{COCOMELONTUTURUTUTU}

2. Box

Description:

I like thinking out of the box, it really gets my brain worked out! Can you say the same?

b__ooltxxio␣_k_␣ieb␣

Flag Format : urchinsec{your_answer_here}

author : @tahaafarooq

Solution:

For this challenge also I put the cipher text into dcode.fr’s cipher identifier and it gives the following results:

Hill Cipher doesn’t work so we go to Caesar Box Cipher (You should also use the title to your advantage as it states “Box”). Decoding with Caesar Box Cipher gives the following:

The Grid which used 6x4 seems to be readable, so just remove the extra underscores and the extra characters from it and you will get the flag!

urchinsec{box_i_like_to_box}

3. Destination

Description:

Take me to my destination 😫

118 115 100 105 106 111 116 102 100 124 66 84 68 74 74 96 117 115 53 111 116 103 49 115 110 96 50 99 105 57 102 54 126

author : @nicl4ssic

Solution:

The given encoded string seems to be ascii encoded, I can tell this by experience but even if you don’t know the cipher identifier always comes in handy. Decoding with ascii code gives the following output:

Now putting the newly found string in to the cipher identifier:

And we try the ascii shift cipher and there you have your flag

urchinsec{ASCII_tr4nsf0rm_1bh8e5}

4. Shifty Business

Description:

Shifting sands and stones is a funny business. Can you figure out what lies beneath the surface?

author: @nicl4ssic

Files: output.txt, script.py

Solution:

We open the output.txt file and get a list of numbers, which seem to encoded. I then open the script.py and see some code:

from functools import reduce

def ecv(v):

n = [16, 32, 64, 128]  
return reduce(lambda v, s: (v << s) ^ s, n, v)  

flag = “urchinsec{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}”
enc = [ecv(ord(char) ** 2) for char in flag]

print(enc)

for the function, the explanation is as follows:

  • List **n**: This list contains integers [16, 32, 64, 128]. These values are likely chosen to shift v by different bit lengths and then apply an XOR operation.
  • **reduce(lambda v, s: (v << s) ^ s, n, v)**:
  • The reduce function applies a lambda function iteratively to v and each element in n.
  • The lambda function takes two arguments: v (which is modified as it iterates) and s (each element in n).
  • Lambda Function:
  • **v << s**: This shifts v to the left by s bits, which means multiplying v by 2^s.
  • **(v << s) ^ s**: The result of the shift is then XORed with s, producing a transformed value.
  • Reduce Iteration:
  • This operation repeats for each value in n. Each time, v is updated with the result of (v << s) ^ s, and this updated value becomes the new v for the next iteration.
  • Return Value: The final value of v after all transformations is returned as the result of ecv(v)

So then we can make a decrypt script:

from functools import reduce
import math

def decrypt_ecv(encrypted_value):
# Reverse the reduce operation
n = [16, 32, 64, 128]
value = encrypted_value

# Undo the shifts and XORs in reverse order  
for s in reversed(n):  
    # To undo (v << s) ^ s  
    # First undo the XOR with s  
    value = value ^ s  
    # Then undo the left shift by right shifting  
    value = value >> s  

# Now we have the squared value, need to take square root  
# and find the character that when squared equals this value  
root = int(math.isqrt(value))  
return chr(root)  

The encrypted values from the output

encrypted = [24186369901110449962943251734715837419999850868110045655307176085634706047104, 22961944885219029622542026566700994888531404527575241717328902988138920542336, 17316868513252091689523391051827370230462593477057639146130371175009000358016, 19110218284002151784050438015081432524027489632386392388623801469321009430656, 19479489320540834108933347192736702493835433766833396750871217165391166963840, 21378849915177597263235392006035339898349978955974687130851943831780733223040, 23366552863053279634016601694371123228655898339959758458738750808234930471040, 18023607339163445421356710052124537636793587035807886729379013655526048268416, 17316868513252091689523391051827370230462593477057639146130371175009000358016, 26730629674391323397543200135785640082791427679610936955002289015496078524544, 4242200233892047650606989546329773213339212640178058856030485285443614015616, 23774694535017086414150343417042737405812047120138026438064841840733525639296, 2687374816887069440573687745676004919411026810927514172883471828306108612736, 23366552863053279634016601694371123228655898339959758458738750808234930471040, 15945795190984065449766752191250865462180465973082158834628004762805927411840, 4777554894519898102470728689054877523634940260931371400341331964435277807744, 16624264463858965032326738431536146172258219789482396514546701544102293405824, 20608504594934221695537074295711427425449195976936917265110923528017151000704, 20608504594934221695537074295711427425449195976936917265110923528017151000704, 15945795190984065449766752191250865462180465973082158834628004762805927411840, 16624264463858965032326738431536146172258219789482396514546701544102293405824, 16968799641490749976595481444181015282844579149373142211380414753354354262144, 4070816068608544370637409688757710117303946702181123817092689483918229897344, 24186369901110449962943251734715837419999850868110045655307176085634706047104, 3266900654134379500677009325919682192602441529102717191147358662330087899264, 15945795190984065449766752191250865462180465973082158834628004762805927411840, 18023607339163445421356710052124537636793587035807886729379013655526048268416, 4777554894519898102470728689054877523634940260931371400341331964435277807744, 4963073836321628457076974926632883967796826070103311390944100615571002884224, 25868408306779471844706550955423095847067615537935634903438945189265280073856, 15945795190984065449766752191250865462180465973082158834628004762805927411840, 4595569646847724516523649046478356916504709419553182647654806525702137970816, 21378849915177597263235392006035339898349978955974687130851943831780733223040, 17316868513252091689523391051827370230462593477057639146130371175009000358016, 11880280094929003107395584642041409957261425526371359611990188893631609307264, 25868408306779471844706550955423095847067615537935634903438945189265280073856, 11307821645940806584610596251800704358133320743783659069558788484412800499840, 12466873320435426704817239412288058904516150180134065106086562152460759072896, 4242200233892047650606989546329773213339212640178058856030485285443614015616, 4070816068608544370637409688757710117303946702181123817092689483918229897344, 10749497973470837136462274241565942107131835832370963478792360924804332650624, 15945795190984065449766752191250865462180465973082158834628004762805927411840, 18023607339163445421356710052124537636793587035807886729379013655526048268416, 4777554894519898102470728689054877523634940260931371400341331964435277807744, 17316868513252091689523391051827370230462593477057639146130371175009000358016, 5540832826504160132850713209375818322472413304381638790249865843393689550976, 16624264463858965032326738431536146172258219789482396514546701544102293405824, 4242200233892047650606989546329773213339212640178058856030485285443614015616, 27606985818521402025016515696154127666641859692461243958230605691337217933440]

Decrypt each value

decrypted = ‘’.join(decrypt_ecv(e) for e in encrypted)
print(decrypted)

run this code and you will get the flag!

urchinsec{1t’s_4all_ab0u+_e45y_3ncRyPT10N_e4c8a1}

5. Ugomvi

Description:

Encryption always win against encoding, are you sure?

author : @Alexius

Files: chall.txt

Solution:

For this challenge, we are given a chall.txt file and it looks something like this:

part 👍: 1i[“`2)-guAnZ3-

part 😁: NmN5NTBvNWd1Njc5NTE3NDhqNjczNWZia3og

part 😒: 4!1vj!+sEzv.+yRH64:ohTXG55FsANRuI+^[ZTdN.@mak;^D4c’3n36zFDCh

It’s alot of encoded texts. We solve each one by one, putting the first one in cipher identifier. We get the following results:

Ascii shift cipher doesn’t work so we try ascii85 encoding and we get something:

This seems to be another encoded text. We put it in the cipher identifier again

I tried some of the first ones but got no results so I moved on with the other parts and keep this ciphertext in the back of your mind it will come in handy later. So moving on to the next part, putting it in cipher identifier gives:

We try base64 encoding and we get the following:

Putting it in the cipher identifier again gives:

We try Twin Hex Cipher and get the following:

This should click in your mind that the first one was also Twin Hex encoded, putting it in the decoder gives:

Similarly for the third part, the encoding is base92 and putting it in the decoder for base92 gives another Twin Hex encoded string and then we decode that with Twin Hex and get the following:

So for now we have 3 parts of the flag:

urchinsec{Y3s_i7’s
tru3&_
I’H4v3_it_d0n3_w1th1n_4_s3cond}

so that makes the flag!

urchinsec{Y3s_i7’s_tru3_&_I’H4v3_it_d0n3_w1th1n_4_s3cond}

6. Tr3ppl3 Stuffs

Description:

I thought tripple staffs was cool, l3ts see if we can get this one done.

author: @AlienKeric

Files:

public-key.txt, script.py

Solution:

#!/usr/bin/env python3

from Crypto.Util.number import getPrime, bytes_to_long

with open(‘flag.txt’, ‘rb’) as f:
flag = f.read()

p = getPrime(1024)
q = getPrime(1024)
r = getPrime(1024)

n1 = p * q
n2 = p * r
n3 = q * r

moduli = [n1, n2, n3]

e = 65537
c = bytes_to_long(flag)

for n in moduli:
c = pow(c, e, n)

print(“Encrypted message:”, c)

with open(‘public-key.txt’, ‘w’) as f:
f.write(f’n1: {n1}\n’)
f.write(f’n2: {n2}\n’)
f.write(f’n3: {n3}\n’)
f.write(f’e: {e}\n’)
f.write(f’c: {c}\n’)

This is the content of the script.py file. It is RSA encryption with a twist, there are three primes used along with three moduli, and the ciphertext is encrypted 3 times, first with n1 and then the result is encrypted with n2 and then this result is encrypted with n3. So to decrypt, we will decrypt with n3 first and then the result is decrypted with n2 and then the result is decrypted with n1. But the question now arises, how do we get the private key for decryption? For that we can get the primes by performing GCD (Greatest common divisor) of the 3 moduli as follows:

GCD(n1,n2) = GCD(p*q,q*r) = q

GCD(n2,n3) = GCD(q*r, p*r) = r

GCD(n1,n3) = GCD(p*q,p*r) = p

Now we have obtained all the three primes which are used for encryption and we can calculate the respective values of the private and decrypt the flag. Below is the script I used:

from Crypto.Util.number import long_to_bytes, inverse, GCD

Values from public-key.txt

n1 = 13867714277838326859434141360807126266988181051937691542720478418900742622487476887000656853897982839471460140185684018914440756440556729087389072578013467286815328553713462055920345115855791765238366372179412680116075169975393328050865875339384668108111346738113960352417722977540340666758452044804022538440159211522070499214448440719690800137276711042521317382781533688637617761177287887907758146318457772914415682758679918032976640744321033974300959199696464501906455225891794560720844314968771280659472377943231331641875340677526653689250244217219907971921829288080314290063506963456144212483509162974366807921979 # replace with n1 value from the file
n2 = 17927735574177320194848318418235001068997271079087971789634703982572674848578451538875202615588580716164655574536384549093649682917017825684657701503860986896187290471258481237400622176271362761168908135788945003341162330346916547198330806215741808128176090032632472660890590964751813395114783904537959063721183330663548506084027893121866027955931624962212334321689943348627498071027965403990280434233875995691837345133010841620725043983468838002901540254921373846246649579342996291922616728645385101976043981088285904718975629169143452456299479123325380939400104364355380586108227679518185723459474280104976741611251 # replace with n2 value from the file
n3 = 22028871217815368590508935678901115805680414747406956084626690162228030261041925736297743271590455693409721333028841498368646841160349146338416150860715525872894476206762924468484783297112829812273714272505690386899539197391794335997773084843803810816300680240126661406634232555713608389689386797404392103317004971701232793270476882695037289851795372610834374445176629233245247506250070493438866110603354631599199020064180967657565037951617865082532325342883190214781057565516003407960037770643505735135704106444100281795297535233604930832284642745486553954322079734200972056930865484862720843375357617469824569196081 # replace with n3 value from the file
e = 65537
c = 17783899729431355396440516688228123843196492982874845631134443056870443793924213101254631163759585302807957991679330948485981267384854935616846216835608219346998904349470869412366074072676442422407130386296970678598266490745623163883256226384261430531037643193129944769773509312004412161545826448792023740299800765532517310026158452966810467878886013254430354183032777576962512688087738484840040014586983530698259387338744109645932344513992052338388317313476277541673858698882555238200780303155125203228876911676321595604240943295849386703221581029549888588427751600086169082403114472881082756229017002637464630300526 # replace with the encrypted message from the file

Step 1: Calculate the shared primes

p = GCD(n1, n2)
q = GCD(n1, n3)
r = GCD(n2, n3)
print(p, q, r)

Step 2: Calculate φ(n) for each modulus

phi_n1 = (p - 1) * (q - 1)
phi_n2 = (p - 1) * (r - 1)
phi_n3 = (q - 1) * (r - 1)

Step 3: Calculate the private exponents d1, d2, and d3

d1 = inverse(e, phi_n1)
d2 = inverse(e, phi_n2)
d3 = inverse(e, phi_n3)

m3 = pow(c, d3, n3)
m2 = pow(m3, d2, n2)
m1 = pow(m2, d1, n1)

print(long_to_bytes(m1))

And you get the flag!

URCHINSEC{Wh00ps_tr1ppl3_RS4_15_N0t_3v3n_h4rd_s0met1mes}

7. WarmUp

Description:

Is this a real warmup?

author : @Alexius

Files: enc.py, out.txt

Solution:

from sympy import mod_inverse

def generate_knapsack():
knapsack = [1, 2]
for i in range(6):
knapsack.append(sum(knapsack) + 1)
return knapsack

def convert_to_bits(message):
bits = []
for char in message:
char_bits = bin(ord(char))[2:].zfill(8)
bits.extend([int(b) for b in char_bits])
return bits

def encrypt_message(message, knapsack, m, n):
bits = convert_to_bits(message)
chunk_size = len(knapsack)
chunks = [bits[i:i + chunk_size] for i in range(0, len(bits), chunk_size)]
ciphertext = []
for chunk in chunks:
if len(chunk) < chunk_size:
chunk += [0] * (chunk_size - len(chunk))
c_value = sum(k * b for k, b in zip(knapsack, chunk))
encrypted_value = (c_value * n) % m
ciphertext.append(encrypted_value)
return ciphertext

if name == “main”:
message = “xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx”
knapsack = generate_knapsack()
m = 257
n = random.randint(-1000, 1000)
ciphertext = encrypt_message(message, knapsack, m, n)
print(“Ciphertext:”, ciphertext)

This is the content of the enc.py file and the out.txt is also given. Explanation of the code is as follows:

  • Generate Knapsack:
  • The generate_knapsack() function creates a superincreasing knapsack sequence. This sequence is generated by starting with the first two elements as 1 and 2, and each subsequent element is the sum of all previous elements plus one. The resulting knapsack is [1, 2, 4, 8, 16, 32, 64], etc.
  • Convert Message to Bits:
  • The convert_to_bits(message) function converts a given message (string) into its binary representation (bits). Each character is transformed into an 8-bit binary number, which is then flattened into a list of integers (0s and 1s).
  • Encrypt Message:
  • The encrypt_message(message, knapsack, m, n) function takes the message, the knapsack sequence, and two numbers (m and n) for the encryption process:
  • The message is split into chunks that match the size of the knapsack.
  • Each chunk is padded with zeros if it’s shorter than the knapsack length.
  • The function calculates a ciphertext value by summing the products of each knapsack element and the corresponding bit (0 or 1) from the chunk.
  • This sum is then multiplied by n and reduced modulo m to produce the encrypted value.
  • Main Execution:
  • In the __main__ block, a message is defined, the knapsack is generated, and random values for m and n are set. The message is then encrypted, and the ciphertext is printed.

The decrypt script for this is as follows:

import random
from sympy import mod_inverse

def generate_knapsack():
knapsack = [1, 2]
for i in range(6):
knapsack.append(sum(knapsack) + 1)
return knapsack

def decrypt_message(ciphertext, knapsack, m, n):
n_inv = mod_inverse(n, m) # Get the modular inverse of n
decrypted_values = [(c * n_inv) % m for c in ciphertext] # Decrypt values

# Use a binary knapsack solver to find the original bits  
bits = []  
for value in decrypted_values:  
    remaining_value = value  
    for k in reversed(knapsack):  
        if remaining_value >= k:  
            bits.append(1)  
            remaining_value -= k  
        else:  
            bits.append(0)  
  
return bits  

def bits_to_message(bits):
message = ‘’
for i in range(0, len(bits), 8):
byte = bits[i:i + 8]
if len(byte) < 8:
byte += [0] * (8 - len(byte)) # Pad with zeros if needed
char = chr(int(‘’.join(str(b) for b in byte), 2))
message += char
return message

def brute_force_key(ciphertext, m, n):
for i in range(1, 100): # Change range as needed
knapsack = generate_knapsack() # Generate the knapsack
try:
bits = decrypt_message(ciphertext, knapsack, m, n)
message = bits_to_message(bits)
print(f“Attempt with knapsack {knapsack}: Decrypted message: {message}”)
except Exception as e:
continue

if name == “main”:
ciphertext = # Replace with actual ciphertext
m = 257
n = random.randint(-1000, 1000)
brute_force_key(ciphertext, m, n)

  • Decrypt Message:
  • The decrypt_message function uses the modular inverse of n to get the original values from the ciphertext. It then attempts to determine the bits corresponding to each decrypted value by checking which knapsack values can sum up to it.
  • Convert Bits to Message:
  • The bits_to_message function converts a list of bits back into a string, grouping them into bytes.
  • Brute Force Key:
  • The brute_force_key function iterates over potential knapsack values and attempts to decrypt the ciphertext using each generated knapsack. It prints the decrypted message if successful.

and there you have your flag! Run the script for this, I will not be giving you the flag everytime -.-

Thank you for reading, and if you like my writeups, please clap for it. (the button I mean, press the button)