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
-
ABC Phonics (100)
-
Box (100)
-
Destination (100)
-
Shifty Business (100)
-
Ugomvi (100)
-
Tr3ppl3 Stuffs (300)
-
WarmUp (300)
-
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 shiftvby different bit lengths and then apply an XOR operation. **reduce(lambda v, s: (v << s) ^ s, n, v)**:- The
reducefunction applies a lambda function iteratively tovand each element inn. - The
lambdafunction takes two arguments:v(which is modified as it iterates) ands(each element inn). - Lambda Function:
**v << s**: This shiftsvto the left bysbits, which means multiplyingvby2^s.**(v << s) ^ s**: The result of the shift is then XORed withs, producing a transformed value.- Reduce Iteration:
- This operation repeats for each value in
n. Each time,vis updated with the result of(v << s) ^ s, and this updated value becomes the newvfor the next iteration. - Return Value: The final value of
vafter all transformations is returned as the result ofecv(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 (mandn) 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
nand reduced modulomto produce the encrypted value. - Main Execution:
- In the
__main__block, a message is defined, the knapsack is generated, and random values formandnare 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_messagefunction uses the modular inverse ofnto 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_messagefunction converts a list of bits back into a string, grouping them into bytes. - Brute Force Key:
- The
brute_force_keyfunction 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)

